All 5 phases of the open-source Decal rebuild: Phase 1: 14 decompiled .NET projects (Interop.*, Adapter, FileService, DecalUtil) Phase 2: 10 native DLLs rewritten as C# COM servers with matching GUIDs - DecalDat, DHS, SpellFilter, DecalInput, DecalNet, DecalFilters - Decal.Core, DecalControls, DecalRender, D3DService Phase 3: C++ shims for Inject.DLL (D3D9 hooking) and LauncherHook.DLL Phase 4: DenAgent WinForms tray application Phase 5: WiX installer and build script 25 C# projects building with 0 errors. Native C++ projects require VS 2022 + Windows SDK (x86). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
344 lines
No EOL
8.9 KiB
C++
344 lines
No EOL
8.9 KiB
C++
// Image.cpp : Implementation of cImage
|
|
#include "stdafx.h"
|
|
#include "Inject.h"
|
|
#include "Image.h"
|
|
|
|
#include "Manager.h"
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// cImage
|
|
|
|
cImage::~cImage()
|
|
{
|
|
cManager::_p->removeImage( this );
|
|
}
|
|
|
|
void cImage::loadFile()
|
|
{
|
|
// ::MessageBox( NULL, _T( "cImage::loadFile" ), _T( "Inject.dll" ), MB_OK );
|
|
|
|
HANDLE hBitmap = ::CreateFile( m_szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL );
|
|
if( hBitmap == INVALID_HANDLE_VALUE )
|
|
{
|
|
int nError = ::GetLastError();
|
|
return;
|
|
}
|
|
|
|
BITMAPFILEHEADER bfh;
|
|
DWORD dwRead;
|
|
::ReadFile( hBitmap, &bfh, sizeof( BITMAPFILEHEADER ), &dwRead, NULL );
|
|
|
|
BITMAPINFOHEADER bih;
|
|
::ReadFile( hBitmap, &bih, sizeof( BITMAPINFOHEADER ), &dwRead, NULL );
|
|
|
|
RGBQUAD pal[ 256 ];
|
|
::memset( pal, 0, sizeof( RGBQUAD[ 256 ] ) );
|
|
// Figure out the number of colors used
|
|
int nEntries = 1 << ( bih.biBitCount );
|
|
|
|
if( bih.biClrUsed != 0 )
|
|
nEntries = bih.biClrUsed;
|
|
|
|
::ReadFile( hBitmap, pal, sizeof( RGBQUAD ) * nEntries, &dwRead, NULL );
|
|
|
|
SIZE szImage = { bih.biWidth, bih.biHeight };
|
|
cManager::_p->CreateCanvas( &szImage, &m_pImage );
|
|
|
|
CComPtr< IDirectDrawSurface4 > pSurface;
|
|
m_pImage->GetSurface( IID_IDirectDrawSurface4, reinterpret_cast< void ** >( &pSurface ) );
|
|
|
|
DDSURFACEDESC2 desc;
|
|
desc.dwSize = sizeof( DDSURFACEDESC2 );
|
|
|
|
pSurface->Lock( NULL, &desc, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR | DDLOCK_WRITEONLY, NULL );
|
|
|
|
WORD wDownMixed[ 256 ];
|
|
RGBQUAD *pSrc = pal;
|
|
for( WORD *pDest = wDownMixed; pDest != wDownMixed + ( nEntries - 1 ); ++ pDest, ++ pSrc ) {
|
|
m_pImage->DownMixRGB(pSrc->rgbRed, pSrc->rgbGreen, pSrc->rgbBlue, pDest);
|
|
}
|
|
|
|
*pDest = 0x07FF;
|
|
|
|
// Mekle: Added the (bih.biWidth % 4) sections below, as BMP rows MUST end on a 32bit boundry
|
|
BYTE *pbSurf = reinterpret_cast< BYTE * >( desc.lpSurface ) + ( desc.dwHeight - 1 ) * desc.lPitch;
|
|
BYTE *pbRow = new BYTE[ bih.biWidth + (bih.biWidth % 4) ];
|
|
|
|
for( int nRow = 0; nRow < bih.biHeight; ++ nRow )
|
|
{
|
|
::ReadFile( hBitmap, pbRow, bih.biWidth + (bih.biWidth % 4), &dwRead, NULL );
|
|
|
|
WORD *pwDest = reinterpret_cast< WORD * >( pbSurf );
|
|
for( BYTE *pbSrc = pbRow; pbSrc != pbRow + bih.biWidth; ++ pbSrc, ++ pwDest )
|
|
// Lookup the color in the downmixed palette
|
|
*pwDest = wDownMixed[ *pbSrc ];
|
|
|
|
pbSurf -= desc.lPitch;
|
|
}
|
|
|
|
delete[] pbRow;
|
|
|
|
pSurface->Unlock( NULL );
|
|
::CloseHandle( hBitmap );
|
|
}
|
|
|
|
void cImage::loadResource()
|
|
{
|
|
}
|
|
|
|
void cImage::loadPortal()
|
|
{
|
|
// ::MessageBox( NULL, _T( "cImage::loadPortal" ), _T( "Inject.dll" ), MB_OK );
|
|
|
|
struct cColorImage
|
|
{
|
|
DWORD m_dwID,
|
|
m_dwX,
|
|
m_dwY;
|
|
};
|
|
|
|
try
|
|
{
|
|
cDatFile::cFile icon = cManager::_p->m_portal.getFile( m_dwID );
|
|
cColorImage header;
|
|
|
|
icon.read( reinterpret_cast< BYTE * >( &header ), sizeof( cColorImage ) );
|
|
|
|
SIZE szImage = { header.m_dwX, header.m_dwY };
|
|
|
|
cManager::_p->CreateCanvas( &szImage, &m_pImage );
|
|
|
|
CComPtr< IDirectDrawSurface4 > pSurface;
|
|
m_pImage->GetSurface( IID_IDirectDrawSurface4, reinterpret_cast< void ** >( &pSurface ) );
|
|
|
|
DDSURFACEDESC2 desc;
|
|
desc.dwSize = sizeof( DDSURFACEDESC2 );
|
|
|
|
pSurface->Lock( NULL, &desc, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR | DDLOCK_WRITEONLY, NULL );
|
|
|
|
BYTE *pSrcLine = new BYTE[ 3 * header.m_dwX ],
|
|
*pEndLine = pSrcLine + 3 * header.m_dwX;
|
|
|
|
for( int i = 0; i < header.m_dwY; ++ i )
|
|
{
|
|
// Read in a line
|
|
icon.read( pSrcLine, 3 * header.m_dwX );
|
|
WORD *pwDest = reinterpret_cast< WORD * >( reinterpret_cast< BYTE * >( desc.lpSurface ) + desc.lPitch * i );
|
|
|
|
for( BYTE *pbSrc = pSrcLine; pbSrc != pEndLine; pbSrc += 3, ++ pwDest )
|
|
{
|
|
// Check for black (transparent)
|
|
if( pbSrc[ 0 ] == 0 && pbSrc[ 1 ] == 0 && pbSrc[ 2 ] == 0 )
|
|
*pwDest = 0x07FF;
|
|
else
|
|
{
|
|
// Downmix the pixel value
|
|
m_pImage->DownMixRGB(pbSrc[0], pbSrc[1], pbSrc[2], pwDest);
|
|
}
|
|
}
|
|
}
|
|
|
|
delete[] pSrcLine;
|
|
|
|
pSurface->Unlock( NULL );
|
|
}
|
|
catch( ... )
|
|
{
|
|
// Failure, file not found
|
|
}
|
|
}
|
|
|
|
bool cImage::testImage()
|
|
{
|
|
bool bReload = false;
|
|
|
|
if( m_pImage.p == NULL )
|
|
bReload = true;
|
|
else
|
|
{
|
|
VARIANT_BOOL bLost;
|
|
m_pImage->get_WasLost( &bLost );
|
|
|
|
if( bLost )
|
|
{
|
|
m_pImage.Release();
|
|
bReload = true;
|
|
}
|
|
}
|
|
|
|
if( bReload )
|
|
{
|
|
// Reload the image
|
|
switch( m_source )
|
|
{
|
|
case eFile:
|
|
loadFile();
|
|
break;
|
|
|
|
case eResource:
|
|
loadResource();
|
|
break;
|
|
|
|
case ePortal:
|
|
loadPortal();
|
|
break;
|
|
}
|
|
}
|
|
return ( m_pImage.p != NULL );
|
|
}
|
|
|
|
STDMETHODIMP cImage::PatBlt(ICanvas *pDest, LPRECT prcDest, LPPOINT ptOrigin)
|
|
{
|
|
// ::MessageBox( NULL, _T( "cImage::PatBlt" ), _T( "Inject.dll" ), MB_OK );
|
|
|
|
if( !testImage() )
|
|
return E_FAIL;
|
|
|
|
SIZE sz;
|
|
m_pImage->get_Size( &sz );
|
|
|
|
POINT ptAdjOrigin = { ptOrigin->x % sz.cx, ptOrigin->y % sz.cy };
|
|
|
|
if( ptAdjOrigin.x > 0 )
|
|
ptAdjOrigin.x -= sz.cx;
|
|
if( ptAdjOrigin.y > 0 )
|
|
ptAdjOrigin.y -= sz.cy;
|
|
|
|
RECT rcPlacement;
|
|
RECT rcSrc;
|
|
POINT ptSrcOrigin;
|
|
|
|
// Scan down
|
|
for( int y = prcDest->top + ptAdjOrigin.y; y < prcDest->bottom; y += sz.cy )
|
|
{
|
|
rcPlacement.top = y;
|
|
rcPlacement.bottom = y + sz.cy;
|
|
|
|
if( rcPlacement.top < prcDest->top )
|
|
rcPlacement.top = prcDest->top;
|
|
|
|
if( rcPlacement.bottom > prcDest->bottom )
|
|
rcPlacement.bottom = prcDest->bottom;
|
|
|
|
ptSrcOrigin.y = ( rcPlacement.top - prcDest->top - ptAdjOrigin.y ) % sz.cy;
|
|
|
|
rcSrc.top = ptSrcOrigin.y;
|
|
rcSrc.bottom = rcSrc.top + ( rcPlacement.bottom - rcPlacement.top );
|
|
|
|
// Scan across
|
|
for( int x = prcDest->left + ptAdjOrigin.x; x < prcDest->right; x += sz.cx )
|
|
{
|
|
rcPlacement.left = x;
|
|
rcPlacement.right = x + sz.cx;
|
|
|
|
if( rcPlacement.left < prcDest->left )
|
|
rcPlacement.left = prcDest->left;
|
|
if( rcPlacement.right > prcDest->right )
|
|
rcPlacement.right = prcDest->right;
|
|
|
|
ptSrcOrigin.x = ( rcPlacement.left - prcDest->left - ptAdjOrigin.x ) % sz.cx;
|
|
|
|
rcSrc.left = ptSrcOrigin.x;
|
|
rcSrc.right = rcSrc.left + ( rcPlacement.right - rcPlacement.left );
|
|
|
|
POINT pt = { rcPlacement.left, rcPlacement.top };
|
|
pDest->Blt( &rcSrc, m_pImage, &pt );
|
|
}
|
|
}
|
|
|
|
_ASSERTMEM( _CrtCheckMemory( ) );
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP cImage::StretchBlt(ICanvas *pDest, LPPOINT pptDest, long nWidth, long nStartStretch, long nEndStretch)
|
|
{
|
|
if( !testImage() )
|
|
return E_FAIL;
|
|
|
|
// Ok, the image is getting placed in three sections, fixed beginning and end
|
|
// and variable length middle sections (delimited by nStartStretch and nEndStretch)
|
|
|
|
// First we place the fixed beginning and end lengths
|
|
SIZE sz;
|
|
m_pImage->get_Size( &sz );
|
|
|
|
RECT rcSrcHead = { 0, 0, nStartStretch, sz.cy },
|
|
rcSrcTail = { nEndStretch, 0, sz.cx, sz.cy },
|
|
rcSrcMiddle = { nStartStretch, 0, nEndStretch, sz.cy };
|
|
|
|
int nTailPlacement = pptDest->x + nWidth - ( sz.cx - nEndStretch );
|
|
|
|
POINT ptHead = { pptDest->x, pptDest->y },
|
|
ptTail = { nTailPlacement, pptDest->y },
|
|
ptStretch = { 0, pptDest->y };
|
|
|
|
// Do the first Blts
|
|
pDest->Blt( &rcSrcHead, m_pImage, &ptHead );
|
|
pDest->Blt( &rcSrcTail, m_pImage, &ptTail );
|
|
|
|
// Now do the middle sections
|
|
for( ptStretch.x = pptDest->x + nStartStretch; ptStretch.x < nTailPlacement; ptStretch.x += ( nEndStretch - nStartStretch ) )
|
|
{
|
|
if( ( nTailPlacement - ptStretch.x ) < ( rcSrcMiddle.right - rcSrcMiddle.left ) )
|
|
rcSrcMiddle.right = rcSrcMiddle.left + ( nTailPlacement - ptStretch.x );
|
|
|
|
pDest->Blt( &rcSrcMiddle, m_pImage, &ptStretch );
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP cImage::Blt(LPRECT rcSrc, ICanvas *pDest, LPPOINT pptDest)
|
|
{
|
|
if( !testImage() )
|
|
return E_FAIL;
|
|
|
|
SIZE sz;
|
|
m_pImage->get_Size( &sz );
|
|
RECT rcImage = { 0, 0, sz.cx, sz.cy };
|
|
|
|
pDest->Blt( &rcImage, m_pImage, pptDest );
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP cImage::get_Size(SIZE *pVal)
|
|
{
|
|
if( !testImage() )
|
|
return E_FAIL;
|
|
|
|
m_pImage->get_Size( pVal );
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP cImage::StretchBltArea(LPRECT prcSrc, ICanvas *pDest, LPRECT prcDest)
|
|
{
|
|
if( !testImage() )
|
|
return E_FAIL;
|
|
|
|
SIZE sizeImage;
|
|
get_Size( &sizeImage );
|
|
|
|
if( prcSrc->right > sizeImage.cx )
|
|
prcSrc->right = sizeImage.cx ;
|
|
|
|
if( prcSrc->bottom > sizeImage.cy )
|
|
prcSrc->bottom = sizeImage.cy;
|
|
|
|
if( prcSrc->left < 0 )
|
|
prcSrc->left = 0;
|
|
|
|
if( prcSrc->top < 0 )
|
|
prcSrc->top = 0;
|
|
|
|
if( prcSrc->left >= prcSrc->right )
|
|
prcSrc->left = prcSrc->right - 1;
|
|
|
|
if( prcSrc->top >= prcSrc->bottom )
|
|
prcSrc->top = prcSrc->bottom - 1;
|
|
|
|
pDest->StretchBlt( prcSrc, m_pImage, prcDest );
|
|
return S_OK;
|
|
} |