mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
635 lines
18 KiB
635 lines
18 KiB
/*==========================================================================
|
|
*
|
|
* Copyright (C) 1999-2000 Microsoft Corporation. All Rights Reserved.
|
|
*
|
|
* File: volume.cpp
|
|
* Content: Implementation of the CVolume and CDriverVolumne classes
|
|
*
|
|
*
|
|
***************************************************************************/
|
|
|
|
#include "ddrawpr.h"
|
|
#include "volume.hpp"
|
|
|
|
// IUnknown methods
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CVolume::QueryInterface"
|
|
|
|
STDMETHODIMP CVolume::QueryInterface (REFIID riid,
|
|
VOID **ppvObj)
|
|
{
|
|
API_ENTER(Device());
|
|
|
|
if (!VALID_PTR_PTR(ppvObj))
|
|
{
|
|
DPF_ERR("Invalid ppvObj parameter to QueryInterface for a level of a VolumeTexture");
|
|
return D3DERR_INVALIDCALL;
|
|
}
|
|
|
|
if (!VALID_PTR(&riid, sizeof(GUID)))
|
|
{
|
|
DPF_ERR("Invalid guid memory address to QueryInterface for a level of a VolumeTexture");
|
|
return D3DERR_INVALIDCALL;
|
|
}
|
|
|
|
|
|
if (riid == IID_IDirect3DVolume8 ||
|
|
riid == IID_IUnknown)
|
|
{
|
|
*ppvObj = static_cast<void*>(static_cast<IDirect3DVolume8 *>(this));
|
|
AddRef();
|
|
return S_OK;
|
|
}
|
|
|
|
DPF_ERR("Unsupported Interface identifier passed to QueryInterface for a level of a VolumeTexture");
|
|
|
|
// Null out param
|
|
*ppvObj = NULL;
|
|
return E_NOINTERFACE;
|
|
} // QueryInterface
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CVolume::AddRef"
|
|
|
|
STDMETHODIMP_(ULONG) CVolume::AddRef()
|
|
{
|
|
API_ENTER_NO_LOCK(Device());
|
|
|
|
#ifdef DEBUG
|
|
m_cRefDebug++;
|
|
#endif // DEBUG
|
|
return m_pParent->AddRefImpl();
|
|
} // AddRef
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CVolume::Release"
|
|
|
|
STDMETHODIMP_(ULONG) CVolume::Release()
|
|
{
|
|
API_ENTER_SUBOBJECT_RELEASE(Device());
|
|
|
|
#ifdef DEBUG
|
|
m_cRefDebug--;
|
|
if (m_cRefDebug & 0x80000000)
|
|
{
|
|
DPF_ERR("A level of a mip-volume has been released more often than it has been add-ref'ed! Danger!!");
|
|
}
|
|
#endif // DEBUG
|
|
return m_pParent->ReleaseImpl();
|
|
} // Release
|
|
|
|
// IBuffer methods
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CVolume::SetPrivateData"
|
|
|
|
STDMETHODIMP CVolume::SetPrivateData(REFGUID riid,
|
|
CONST VOID *pvData,
|
|
DWORD cbData,
|
|
DWORD dwFlags)
|
|
{
|
|
API_ENTER(Device());
|
|
|
|
return m_pParent->SetPrivateDataImpl(riid,
|
|
pvData,
|
|
cbData,
|
|
dwFlags,
|
|
m_iLevel);
|
|
} // SetPrivateData
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CVolume::GetPrivateData"
|
|
|
|
STDMETHODIMP CVolume::GetPrivateData(REFGUID riid,
|
|
VOID *pvData,
|
|
DWORD *pcbData)
|
|
{
|
|
API_ENTER(Device());
|
|
|
|
return m_pParent->GetPrivateDataImpl(riid,
|
|
pvData,
|
|
pcbData,
|
|
m_iLevel);
|
|
|
|
} // GetPrivateData
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CVolume::FreePrivateData"
|
|
|
|
STDMETHODIMP CVolume::FreePrivateData(REFGUID riid)
|
|
{
|
|
API_ENTER(Device());
|
|
|
|
return m_pParent->FreePrivateDataImpl(riid,
|
|
m_iLevel);
|
|
} // FreePrivateData
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CVolume::GetContainer"
|
|
|
|
STDMETHODIMP CVolume::GetContainer(REFIID riid,
|
|
void **ppContainer)
|
|
{
|
|
API_ENTER(Device());
|
|
|
|
return m_pParent->QueryInterface(riid, ppContainer);
|
|
} // OpenContainer
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CVolume::GetDevice"
|
|
|
|
STDMETHODIMP CVolume::GetDevice(IDirect3DDevice8 **ppDevice)
|
|
{
|
|
API_ENTER(Device());
|
|
|
|
return m_pParent->GetDevice(ppDevice);
|
|
} // OpenDevice
|
|
|
|
// IDirect3DVolume8 methods
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CVolume::GetDesc"
|
|
|
|
STDMETHODIMP CVolume::GetDesc(D3DVOLUME_DESC *pDesc)
|
|
{
|
|
API_ENTER(Device());
|
|
|
|
// If parameters are bad, then we should fail some stuff
|
|
if (!VALID_WRITEPTR(pDesc, sizeof(D3DVOLUME_DESC)))
|
|
{
|
|
DPF_ERR("bad pointer for pDesc passed to GetDesc for a level of a VolumeTexture");
|
|
return D3DERR_INVALIDCALL;
|
|
}
|
|
|
|
// We basically get our volume desc from our parent
|
|
// and then modify the width, height, and depth fields.
|
|
*pDesc = *m_pParent->Desc();
|
|
|
|
pDesc->Width >>= m_iLevel;
|
|
pDesc->Height >>= m_iLevel;
|
|
pDesc->Depth >>= m_iLevel;
|
|
|
|
if (pDesc->Width == 0)
|
|
{
|
|
pDesc->Width = 1;
|
|
}
|
|
if (pDesc->Height == 0)
|
|
{
|
|
pDesc->Height = 1;
|
|
}
|
|
if (pDesc->Depth == 0)
|
|
{
|
|
pDesc->Depth = 1;
|
|
}
|
|
|
|
// Also need to modify the type field
|
|
pDesc->Type = D3DRTYPE_VOLUME;
|
|
|
|
// Also modify the size field
|
|
pDesc->Size = CPixel::ComputeVolumeSize(pDesc->Width,
|
|
pDesc->Height,
|
|
pDesc->Depth,
|
|
pDesc->Format);
|
|
|
|
// We also need to modify the pool and format
|
|
// to reflect the data the user passed to us
|
|
pDesc->Pool = m_pParent->GetUserPool();
|
|
pDesc->Format = m_pParent->GetUserFormat();
|
|
pDesc->Usage &= D3DUSAGE_EXTERNAL;
|
|
|
|
// We're done
|
|
return S_OK;
|
|
} // GetDesc
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CVolume::LockBox"
|
|
|
|
STDMETHODIMP CVolume::LockBox(D3DLOCKED_BOX *pLockedBoxData,
|
|
CONST D3DBOX *pBox,
|
|
DWORD dwFlags)
|
|
{
|
|
API_ENTER(Device());
|
|
|
|
// If parameters are bad, then we should fail some stuff
|
|
if (!VALID_WRITEPTR(pLockedBoxData, sizeof(D3DLOCKED_BOX)))
|
|
{
|
|
DPF_ERR("bad pointer for pLockedBoxData passed to LockBox for a level of a VolumeTexture");
|
|
return D3DERR_INVALIDCALL;
|
|
}
|
|
|
|
// Zero out returned data
|
|
ZeroMemory(pLockedBoxData, sizeof(D3DLOCKED_BOX));
|
|
|
|
// Validate Box
|
|
if (pBox != NULL)
|
|
{
|
|
DWORD Width = m_pParent->Desc()->Width >> m_iLevel;
|
|
DWORD Height = m_pParent->Desc()->Height >> m_iLevel;
|
|
DWORD Depth = m_pParent->Desc()->Depth >> m_iLevel;
|
|
|
|
if (!CPixel::IsValidBox(m_pParent->Desc()->Format,
|
|
Width,
|
|
Height,
|
|
Depth,
|
|
pBox))
|
|
{
|
|
DPF_ERR("LockBox for a Volume fails");
|
|
return D3DERR_INVALIDCALL;
|
|
}
|
|
}
|
|
|
|
if (dwFlags & ~D3DLOCK_VOL_VALID)
|
|
{
|
|
if (dwFlags & D3DLOCK_DISCARD)
|
|
{
|
|
if (dwFlags & D3DLOCK_READONLY)
|
|
{
|
|
DPF_ERR("D3DLOCK_READONLY is not allowed with D3DLOCK_DISCARD");
|
|
return D3DERR_INVALIDCALL;
|
|
}
|
|
if (!(m_pParent->Desc()->Usage & D3DUSAGE_DYNAMIC))
|
|
{
|
|
DPF_ERR("D3DLOCK_DISCARD is allowed only with dynamic textures");
|
|
return D3DERR_INVALIDCALL;
|
|
}
|
|
if (m_iLevel > 0)
|
|
{
|
|
DPF_ERR("D3DLOCK_DISCARD is allowed only on level 0"
|
|
" (the top mip level). DISCARD in this case will discard"
|
|
" the entire volume.");
|
|
return D3DERR_INVALIDCALL;
|
|
}
|
|
if (pBox != NULL)
|
|
{
|
|
DPF_ERR("Subboxes not allowed with D3DLOCK_DISCARD");
|
|
return D3DERR_INVALIDCALL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DPF_ERR("Invalid dwFlags parameter passed to LockBox for a level of a VolumeTexture");
|
|
DPF_EXPLAIN_BAD_LOCK_FLAGS(0, dwFlags & ~D3DLOCK_VOL_VALID);
|
|
return D3DERR_INVALIDCALL;
|
|
}
|
|
}
|
|
|
|
if (!m_isLockable)
|
|
{
|
|
m_pParent->ReportWhyLockFailed();
|
|
return D3DERR_INVALIDCALL;
|
|
}
|
|
|
|
return InternalLockBox(pLockedBoxData, pBox, dwFlags);
|
|
} // LockBox
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CVolume::InternalLockBox"
|
|
|
|
HRESULT CVolume::InternalLockBox(D3DLOCKED_BOX *pLockedBoxData,
|
|
CONST D3DBOX *pBox,
|
|
DWORD dwFlags)
|
|
{
|
|
// Only one lock outstanding at a time is supported
|
|
if (IsLocked())
|
|
{
|
|
DPF_ERR("LockBox failed on a mip level; volume was already locked.");
|
|
return D3DERR_INVALIDCALL;
|
|
}
|
|
|
|
// Notify the parent/device if we are about to be modified
|
|
if ( (m_pParent->GetUserPool() != D3DPOOL_SCRATCH) && (!(dwFlags & D3DLOCK_READONLY)) )
|
|
{
|
|
m_pParent->OnVolumeLock(m_iLevel, pBox, dwFlags);
|
|
}
|
|
|
|
// Figure out our stride/pointer to bits
|
|
m_pParent->ComputeMipVolumeOffset(m_iLevel,
|
|
pBox,
|
|
pLockedBoxData);
|
|
|
|
// Mark ourselves as locked
|
|
m_isLocked = TRUE;
|
|
|
|
// Done
|
|
return S_OK;
|
|
} // InternalLockBox
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CVolume::UnlockBox"
|
|
|
|
STDMETHODIMP CVolume::UnlockBox()
|
|
{
|
|
API_ENTER(Device());
|
|
|
|
// If we aren't locked; then something is wrong
|
|
if (!IsLocked())
|
|
{
|
|
DPF_ERR("UnlockBox failed on a volume level; volume wasn't locked.");
|
|
return D3DERR_INVALIDCALL;
|
|
}
|
|
DXGASSERT(m_isLockable);
|
|
return InternalUnlockBox();
|
|
} // UnlockBox
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CVolume::InternalUnlockBox"
|
|
|
|
HRESULT CVolume::InternalUnlockBox()
|
|
{
|
|
// Clear our locked state
|
|
m_isLocked = FALSE;
|
|
|
|
// If we are lock-once; then we mark ourselves as not lockable
|
|
if (m_pParent->Desc()->Usage & D3DUSAGE_LOADONCE)
|
|
{
|
|
m_isLockable = FALSE;
|
|
}
|
|
|
|
// Done
|
|
return S_OK;
|
|
} // InternalUnlockBox
|
|
|
|
//
|
|
// CDriverVolume class modifies the implementation
|
|
// of the LockBox and UnlockBox methods of the CVolume class
|
|
//
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CDriverVolume::LockBox"
|
|
|
|
STDMETHODIMP CDriverVolume::LockBox(D3DLOCKED_BOX *pLockedBoxData,
|
|
CONST D3DBOX *pBox,
|
|
DWORD dwFlags)
|
|
{
|
|
API_ENTER(Device());
|
|
|
|
// If parameters are bad, then we should fail some stuff
|
|
if (!VALID_WRITEPTR(pLockedBoxData, sizeof(D3DLOCKED_BOX)))
|
|
{
|
|
DPF_ERR("bad pointer for pLockedBoxData passed to LockBox for a level of a VolumeTexture");
|
|
return D3DERR_INVALIDCALL;
|
|
}
|
|
|
|
// Zero out returned data
|
|
ZeroMemory(pLockedBoxData, sizeof(D3DLOCKED_BOX));
|
|
|
|
// Validate Box
|
|
if (pBox != NULL)
|
|
{
|
|
DWORD Width = m_pParent->Desc()->Width >> m_iLevel;
|
|
DWORD Height = m_pParent->Desc()->Height >> m_iLevel;
|
|
DWORD Depth = m_pParent->Desc()->Depth >> m_iLevel;
|
|
|
|
if (!CPixel::IsValidBox(m_pParent->Desc()->Format,
|
|
Width,
|
|
Height,
|
|
Depth,
|
|
pBox))
|
|
{
|
|
DPF_ERR("LockBox for a Volume fails");
|
|
return D3DERR_INVALIDCALL;
|
|
}
|
|
}
|
|
|
|
if (dwFlags & ~D3DLOCK_VOL_VALID)
|
|
{
|
|
if (dwFlags & D3DLOCK_DISCARD)
|
|
{
|
|
if (dwFlags & D3DLOCK_READONLY)
|
|
{
|
|
DPF_ERR("D3DLOCK_READONLY is not allowed with D3DLOCK_DISCARD");
|
|
return D3DERR_INVALIDCALL;
|
|
}
|
|
if (!(m_pParent->Desc()->Usage & D3DUSAGE_DYNAMIC))
|
|
{
|
|
DPF_ERR("D3DLOCK_DISCARD is allowed only with dynamic textures");
|
|
return D3DERR_INVALIDCALL;
|
|
}
|
|
if (m_iLevel > 0)
|
|
{
|
|
DPF_ERR("D3DLOCK_DISCARD is allowed only on level 0"
|
|
" (the top mip level). DISCARD in this case will discard"
|
|
" the entire volume.");
|
|
return D3DERR_INVALIDCALL;
|
|
}
|
|
if (pBox != NULL)
|
|
{
|
|
DPF_ERR("Subboxes not allowed with D3DLOCK_DISCARD");
|
|
return D3DERR_INVALIDCALL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DPF_ERR("Invalid dwFlags parameter passed to LockBox for a level of a VolumeTexture");
|
|
DPF_EXPLAIN_BAD_LOCK_FLAGS(0, dwFlags & ~D3DLOCK_VOL_VALID);
|
|
return D3DERR_INVALIDCALL;
|
|
}
|
|
}
|
|
|
|
if (!m_isLockable)
|
|
{
|
|
m_pParent->ReportWhyLockFailed();
|
|
return D3DERR_INVALIDCALL;
|
|
}
|
|
return InternalLockBox(pLockedBoxData, pBox, dwFlags);
|
|
} // CDriverVolume::LockBox
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CDriverVolume::InternalLockBox"
|
|
|
|
HRESULT CDriverVolume::InternalLockBox(D3DLOCKED_BOX *pLockedBoxData,
|
|
CONST D3DBOX *pBox,
|
|
DWORD dwFlags)
|
|
{
|
|
// Only one lock outstanding at a time is supported
|
|
if (IsLocked())
|
|
{
|
|
DPF_ERR("LockBox failed on a volume level; volume was already locked.");
|
|
return D3DERR_INVALIDCALL;
|
|
}
|
|
|
|
// Notify the parent/device if we are about to be accessed.
|
|
// Driver volume textures may be written to by HW through
|
|
// UpdateTexture. So we may need to sync with the current
|
|
// command batch.
|
|
m_pParent->OnVolumeLock(m_iLevel, pBox, dwFlags);
|
|
|
|
// Prepare a LockData structure for the HAL call
|
|
D3D8_LOCKDATA lockData;
|
|
ZeroMemory(&lockData, sizeof lockData);
|
|
|
|
lockData.hDD = m_pParent->Device()->GetHandle();
|
|
lockData.hSurface = m_hKernelHandle;
|
|
lockData.dwFlags = dwFlags;
|
|
if (pBox != NULL)
|
|
{
|
|
lockData.bHasBox = TRUE;
|
|
lockData.box = *pBox;
|
|
}
|
|
|
|
HRESULT hr = m_pParent->Device()->GetHalCallbacks()->Lock(&lockData);
|
|
if (FAILED(hr))
|
|
{
|
|
DPF_ERR("Failed to lock level of a driver volume");
|
|
return hr;
|
|
}
|
|
|
|
// Fill in the Locked_Box fields
|
|
D3DFORMAT Format = m_pParent->Desc()->Format;
|
|
|
|
if (CPixel::IsDXT(Format))
|
|
{
|
|
// Start with our current width/height
|
|
DWORD Width = m_pParent->Desc()->Width >> m_iLevel;
|
|
DWORD Height = m_pParent->Desc()->Height >> m_iLevel;
|
|
|
|
// Convert to blocks
|
|
Width = Width / 4;
|
|
Height = Height / 4;
|
|
|
|
// At least one block
|
|
if (Width == 0)
|
|
Width = 1;
|
|
if (Height == 0)
|
|
Height = 1;
|
|
|
|
switch (Format)
|
|
{
|
|
// For linear formats,
|
|
// Row Pitch is a row of blocks; and SlicePitch is for
|
|
// a plane of blocks.
|
|
|
|
case D3DFMT_DXT1:
|
|
// DXT1 is 8 bytes per block
|
|
pLockedBoxData->RowPitch = Width * 8;
|
|
pLockedBoxData->SlicePitch = Height * pLockedBoxData->RowPitch;
|
|
break;
|
|
|
|
case D3DFMT_DXT2:
|
|
case D3DFMT_DXT3:
|
|
case D3DFMT_DXT4:
|
|
case D3DFMT_DXT5:
|
|
// DXT2-5 are 16 bytes per block
|
|
pLockedBoxData->RowPitch = Width * 16;
|
|
pLockedBoxData->SlicePitch = Height * pLockedBoxData->RowPitch;
|
|
break;
|
|
|
|
#ifdef VOLUME_DXT
|
|
case D3DFMT_DXV1:
|
|
// DXV1 is 32-bytes per block
|
|
pLockedBoxData->RowPitch = Width * 32;
|
|
pLockedBoxData->SlicePitch = Height * pLockedBoxData->RowPitch;
|
|
break;
|
|
|
|
case D3DFMT_DXV2:
|
|
case D3DFMT_DXV3:
|
|
case D3DFMT_DXV4:
|
|
case D3DFMT_DXV5:
|
|
// DXV2-5 are 64-bytes per block
|
|
pLockedBoxData->RowPitch = Width * 64;
|
|
pLockedBoxData->SlicePitch = Height * pLockedBoxData->RowPitch;
|
|
break;
|
|
#endif //VOLUME_DXT
|
|
|
|
default:
|
|
DPF_ERR("Unknown DXT format?");
|
|
DXGASSERT(FALSE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// For all other formats, just return what
|
|
// the driver gave us
|
|
pLockedBoxData->RowPitch = lockData.lPitch;
|
|
pLockedBoxData->SlicePitch = lockData.lSlicePitch;
|
|
}
|
|
|
|
|
|
pLockedBoxData->pBits = lockData.lpSurfData;
|
|
|
|
#ifdef DEBUG
|
|
if ((dwFlags & D3DLOCK_DISCARD))
|
|
{
|
|
DXGASSERT(m_iLevel == 0);
|
|
if (!CPixel::IsFourCC(Format) &&
|
|
!CPixel::IsIHVFormat(Format))
|
|
{
|
|
DXGASSERT(pBox == NULL);
|
|
memset(pLockedBoxData->pBits, 0xDD, pLockedBoxData->SlicePitch * m_pParent->Desc()->Depth);
|
|
for (UINT i = 1; i < m_pParent->GetLevelCount(); ++i)
|
|
{
|
|
D3DLOCKED_BOX Box;
|
|
HRESULT hr = m_pParent->LockBox(i, &Box, NULL, 0);
|
|
if (FAILED(hr))
|
|
{
|
|
DPF(1, "Lock to mipsublevel failed. Not good.");
|
|
break;
|
|
}
|
|
D3DVOLUME_DESC LevelDesc;
|
|
m_pParent->GetLevelDesc(i, &LevelDesc);
|
|
memset(Box.pBits, 0xDD, Box.SlicePitch * LevelDesc.Depth);
|
|
m_pParent->UnlockBox(i);
|
|
}
|
|
}
|
|
}
|
|
#endif // DEBUG
|
|
|
|
// Mark ourselves as locked
|
|
m_isLocked = TRUE;
|
|
|
|
// Done
|
|
return S_OK;
|
|
} // CDriverVolume::InternalLockBox
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CDriverVolume::UnlockBox"
|
|
|
|
STDMETHODIMP CDriverVolume::UnlockBox()
|
|
{
|
|
API_ENTER(Device());
|
|
|
|
// If we aren't locked; then something is wrong
|
|
if (!IsLocked())
|
|
{
|
|
DPF_ERR("UnlockBox failed on a mip level; volume wasn't locked.");
|
|
return D3DERR_INVALIDCALL;
|
|
}
|
|
|
|
DXGASSERT(m_isLockable);
|
|
return InternalUnlockBox();
|
|
} // CDriverVolume::UnlockBox
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CDriverVolume::InternalUnlockBox"
|
|
|
|
HRESULT CDriverVolume::InternalUnlockBox()
|
|
{
|
|
// Call the driver to perform the unlock
|
|
D3D8_UNLOCKDATA unlockData = {
|
|
m_pParent->Device()->GetHandle(),
|
|
m_hKernelHandle
|
|
};
|
|
|
|
HRESULT hr = m_pParent->Device()->GetHalCallbacks()->Unlock(&unlockData);
|
|
if (FAILED(hr))
|
|
{
|
|
DPF_ERR("Driver volume failed to unlock");
|
|
return hr;
|
|
}
|
|
|
|
// Clear our locked state
|
|
m_isLocked = FALSE;
|
|
|
|
// If we are lock-once; then we mark ourselves as not lockable
|
|
if (m_pParent->Desc()->Usage & D3DUSAGE_LOADONCE)
|
|
{
|
|
m_isLockable = FALSE;
|
|
}
|
|
|
|
// Done
|
|
return S_OK;
|
|
} // CDriverVolume::InternalUnlockBox
|
|
|
|
|
|
// End of file : volume.cpp
|