diff --git a/LEGO1/mxbitmap.cpp b/LEGO1/mxbitmap.cpp index 6ad26f94..5987f31f 100644 --- a/LEGO1/mxbitmap.cpp +++ b/LEGO1/mxbitmap.cpp @@ -1,4 +1,12 @@ #include "mxbitmap.h" +#include "decomp.h" + +DECOMP_SIZE_ASSERT(MxBITMAPINFO, 1064); + +// The way that the BITMAPFILEHEADER structure ensures the file type is by ensuring it is "BM", which is literally just 0x424d. +// Sources: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapfileheader, DirectX Complete (1998) +// GLOBAL: LEGO1 0x10102184 +undefined2 g_bitmapSignature = 0x424d; // OFFSET: LEGO1 0x100bc980 MxBitmap::MxBitmap() @@ -7,43 +15,214 @@ MxBitmap::MxBitmap() this->m_bmiHeader = NULL; this->m_paletteData = NULL; this->m_data = NULL; - this->m_unk18 = FALSE; + this->m_bitDepth = LOWCOLOR; this->m_palette = NULL; } // OFFSET: LEGO1 0x100bca10 MxBitmap::~MxBitmap() { - if (this->m_info != NULL) + if (this->m_info) delete m_info; - if (this->m_data != NULL) + if (this->m_data) delete m_data; - if (this->m_palette != NULL) + if (this->m_palette) delete m_palette; } -// OFFSET: LEGO1 0x100bcc40 STUB -int MxBitmap::vtable14(int) +// OFFSET: LEGO1 0x100bcc40 +MxResult MxBitmap::ImportBitmap(MxBitmap *p_bitmap) { - return 0; + MxLong height; + MxResult result = FAILURE; + + this->m_info = new MxBITMAPINFO; + if(this->m_info) { + height = p_bitmap->m_bmiHeader->biHeight; + if (height <= 0L) { + height = -height; + } + this->m_data = (LPVOID*) new MxU8[(p_bitmap->m_bmiHeader->biWidth + 3U & -4) * height]; + if(this->m_data) { + memcpy(this->m_info, p_bitmap->m_info, sizeof(MxBITMAPINFO)); + + height = p_bitmap->m_bmiHeader->biHeight; + if (height <= 0L) { + height = -height; + } + memcpy(this->m_data, p_bitmap->m_data, (p_bitmap->m_bmiHeader->biWidth + 3U & -4) * height); + + result = SUCCESS; + this->m_bmiHeader = &this->m_info->bmiHeader; + this->m_paletteData = this->m_info->bmiColors; + } + } + if (result != SUCCESS) { + if (this->m_info) { + delete this->m_info; + this->m_info = NULL; + } + if (this->m_data) { + delete this->m_data; + this->m_data = NULL; + } + } + return result; } -// OFFSET: LEGO1 0x100bcba0 STUB -int MxBitmap::vtable18(BITMAPINFOHEADER *p_bmiHeader) +// OFFSET: LEGO1 0x100bcba0 +MxResult MxBitmap::ImportBitmapInfo(MxBITMAPINFO *p_info) { - return 0; + MxResult result = FAILURE; + MxLong width = p_info->bmiHeader.biWidth; + MxLong height = p_info->bmiHeader.biHeight; + // ((width + 3) & -4) clamps width to nearest 4-byte boundary + MxLong size = ((width + 3) & -4) * height; + + this->m_info = new MxBITMAPINFO; + if (this->m_info) { + this->m_data = (LPVOID*) new MxU8[size]; + if(this->m_data) { + memcpy(this->m_info, p_info, sizeof(MxBITMAPINFO)); + this->m_bmiHeader = &this->m_info->bmiHeader; + this->m_paletteData = this->m_info->bmiColors; + result = SUCCESS; + } + } + if (result != SUCCESS) { + if (this->m_info) { + delete this->m_info; + this->m_info = NULL; + } + if (this->m_data) { + delete this->m_data; + this->m_data = NULL; + } + } + return result; } -// OFFSET: LEGO1 0x100bcaa0 STUB -int MxBitmap::vtable1c(int p_width, int p_height, MxPalette *p_palette, int) + +// OFFSET: LEGO1 0x100bd450 +MxResult MxBitmap::ImportColorsToPalette(RGBQUAD* p_rgbquad, MxPalette* p_palette) { - return 0; + MxResult ret = FAILURE; + PALETTEENTRY entries[256]; + + if (p_palette) { + if (p_palette->GetEntries(entries)) + return ret; + } else { + MxPalette local_pal; + if (local_pal.GetEntries(entries)) + return ret; + } + + for (int i = 0; i < 256; i++) { + p_rgbquad[i].rgbRed = entries[i].peRed; + p_rgbquad[i].rgbGreen = entries[i].peGreen; + p_rgbquad[i].rgbBlue = entries[i].peBlue; + p_rgbquad[i].rgbReserved = 0; + } + + ret = SUCCESS; + return ret; } -// OFFSET: LEGO1 0x100bcd60 STUB +// OFFSET: LEGO1 0x100bcaa0 +MxResult MxBitmap::SetSize(int p_width, int p_height, MxPalette *p_palette, int p_bitDepth) +{ + MxResult ret = FAILURE; + MxLong size = ((p_width + 3) & -4) * p_height; + + m_info = new MxBITMAPINFO; + if (m_info) { + m_data = (LPVOID*) new MxU8[size]; + if (m_data) { + m_bmiHeader = &m_info->bmiHeader; + m_paletteData = m_info->bmiColors; + memset(&m_info->bmiHeader, 0, sizeof(m_info->bmiHeader)); + + m_bmiHeader->biSize = sizeof(*m_bmiHeader); // should be 40 bytes + m_bmiHeader->biWidth = p_width; + m_bmiHeader->biHeight = p_height; + m_bmiHeader->biPlanes = 1; + m_bmiHeader->biBitCount = 8; + m_bmiHeader->biCompression = 0; + m_bmiHeader->biSizeImage = size; + + if (!ImportColorsToPalette(m_paletteData, p_palette)) { + if (!SetBitDepth(p_bitDepth)) { + ret = SUCCESS; + } + } + } + } + + if (ret) { + if (m_info) { + delete m_info; + m_info = NULL; + } + + if (m_data) { + delete[] m_data; + m_data = NULL; + } + } + + return ret; +} + +// OFFSET: LEGO1 0x100bcd60 MxResult MxBitmap::LoadFile(HANDLE p_handle) { - return SUCCESS; + BOOL ret; + LPVOID* lpBuffer; + MxLong height; + MxResult result = FAILURE; + DWORD bytesRead; + BITMAPFILEHEADER hdr; + MxLong size; + + ret = ReadFile(p_handle, &hdr, sizeof(hdr), &bytesRead, NULL); + if (ret && (hdr.bfType == g_bitmapSignature)) { + this->m_info = new MxBITMAPINFO; + if(this->m_info) { + ret = ReadFile(p_handle, this->m_info, sizeof(MxBITMAPINFO), &bytesRead, NULL); + if (ret && ((this->m_info->bmiHeader).biBitCount == 8)) { + size = hdr.bfSize - (sizeof(MxBITMAPINFO) + sizeof(BITMAPFILEHEADER)); + lpBuffer = (LPVOID*) new MxU8[size]; + this->m_data = lpBuffer; + if (lpBuffer) { + ret = ReadFile(p_handle, lpBuffer, size, &bytesRead, NULL); + if(ret) { + this->m_bmiHeader = &this->m_info->bmiHeader; + this->m_paletteData = this->m_info->bmiColors; + if((this->m_info->bmiHeader).biSizeImage == 0) { + height = (this->m_info->bmiHeader).biHeight; + if (height <= 0L) { + height = -height; + } + (this->m_info->bmiHeader).biSizeImage = ((this->m_info->bmiHeader).biWidth + 3U & -4) * height; + } + result = SUCCESS; + } + } + } + } + } + if (result != SUCCESS) { + if (this->m_info) { + delete this->m_info; + this->m_info = NULL; + } + if (this->m_data) { + delete this->m_data; + this->m_data = NULL; + } + } + return result; } // OFFSET: LEGO1 0x100bcd10 @@ -75,12 +254,12 @@ int MxBitmap::vtable28(int) return -1; } -// OFFSET: LEGO1 0x100ce70 STUB +// OFFSET: LEGO1 0x100bce70 STUB void MxBitmap::vtable2c(int, int, int, int, int, int, int) { } -// OFFSET: LEGO1 0x100d020 STUB +// OFFSET: LEGO1 0x100bd020 STUB void MxBitmap::vtable30(int, int, int, int, int, int, int) { } @@ -88,42 +267,104 @@ void MxBitmap::vtable30(int, int, int, int, int, int, int) // OFFSET: LEGO1 0x100bd1c0 MxPalette *MxBitmap::CreatePalette() { - // FIXME: This function needs MxPalette to be completed. Also INFERRING usage of MxBool - MxPalette *pal = NULL; - MxPalette *ppal; MxBool success = FALSE; + MxPalette *palette = NULL; - if(this->m_unk18 == FALSE) { - // ppal = MxPalette::FromBitmapPalette(this->m_paletteData); - } else { - if(this->m_unk18 != TRUE) { - if(!success && pal != NULL) { - delete pal; - pal = NULL; + switch (this->m_bitDepth) { + case LOWCOLOR: + palette = new MxPalette(this->m_paletteData); + if (palette) + success = TRUE; + break; + + case HIGHCOLOR: + palette = this->m_palette->Clone(); + if (palette) + success = TRUE; + break; + } + + if (!success && palette) { + delete palette; + palette = NULL; + } + + return palette; +} + +// OFFSET: LEGO1 0x100bd280 +void MxBitmap::ImportPalette(MxPalette* p_palette) +{ + // This is weird but it matches. Maybe m_bmiColorsProvided had more + // potential values than just true/false at some point? + switch (this->m_bitDepth) { + case LOWCOLOR: + ImportColorsToPalette(this->m_paletteData, p_palette); + break; + + case HIGHCOLOR: + if (this->m_palette) { + delete this->m_palette; } + this->m_palette = p_palette->Clone(); + break; + } +} + +// OFFSET: LEGO1 0x100bd2d0 +MxResult MxBitmap::SetBitDepth(MxBool p_bitDepth) +{ + MxResult ret = FAILURE; + MxPalette *pal = NULL; + + if (m_bitDepth == p_bitDepth) { + // no change: do nothing. + ret = SUCCESS; + } else { + // TODO: Another switch used for this boolean value? Is it not a bool? + switch (p_bitDepth) { + case 0: + ImportColorsToPalette(m_paletteData, m_palette); + if (m_palette) + delete m_palette; + + m_palette = NULL; + break; + + case 1: + pal = NULL; + pal = new MxPalette(m_paletteData); + if (pal) { + m_palette = pal; + + // TODO: what is this? zeroing out top half of palette? + MxU16 *buf = (MxU16*)m_paletteData; + for (MxU16 i = 0; i < 256; i++) { + buf[i] = i; + } + + m_bitDepth = p_bitDepth; + ret = SUCCESS; + } + break; } - //.pal = MxPalette::Clone(this->m_palette); - } - if(pal != NULL) { - success = TRUE; } - return pal; + // If we were unsuccessful overall but did manage to alloc + // the MxPalette, free it. + if (ret && pal) + delete pal; + + return ret; } -// OFFSET: LEGO1 0x100bd280 STUB -void MxBitmap::vtable38(void*) +// OFFSET: LEGO1 0x100bd3e0 +MxResult MxBitmap::StretchBits(HDC p_hdc, int p_xSrc, int p_ySrc, int p_xDest, int p_yDest, int p_destWidth, int p_destHeight) { -} + // Compression fix? + if ((this->m_bmiHeader->biCompression != 16) && (0 < this->m_bmiHeader->biHeight)) { + p_ySrc = (this->m_bmiHeader->biHeight - p_destHeight) - p_ySrc; + } -// OFFSET: LEGO1 0x100bd2d0 STUB -int MxBitmap::vtable3c(MxBool) -{ - return 0; -} - -// OFFSET: LEGO1 0x100bd3e0 STUB -int MxBitmap::vtable40(HDC p_hdc, int p_xSrc, int p_ySrc, int p_xDest, int p_yDest, int p_destWidth, int p_destHeight) -{ - return 0; + return StretchDIBits(p_hdc, p_xDest, p_yDest, p_destWidth, p_destHeight, p_xSrc, p_ySrc, p_destWidth, p_destHeight, this->m_data, (BITMAPINFO*)this->m_info, this->m_bitDepth, SRCCOPY); } \ No newline at end of file diff --git a/LEGO1/mxbitmap.h b/LEGO1/mxbitmap.h index 66550f7e..ba90b7da 100644 --- a/LEGO1/mxbitmap.h +++ b/LEGO1/mxbitmap.h @@ -7,32 +7,52 @@ #include "mxpalette.h" #include "mxtypes.h" +// The stock BITMAPINFO struct from wingdi.h only makes room for one color +// in the palette. It seems like the expectation (if you use the struct) +// is to malloc as much as you actually need, and then index into the array +// anyway even though its stated size is [1]. +// https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfo +// In our case, the size 0x428 is used frequently, which matches +// a 40-byte header plus 256 colors, so just use that as our template. + +// SIZE 0x428 +struct MxBITMAPINFO { + BITMAPINFOHEADER bmiHeader; + RGBQUAD bmiColors[256]; +}; + +// These values are the bit depth, set in the registry +#define LOWCOLOR 0 // 256 color +#define HIGHCOLOR 1 // High Color (16-bit) + class MxBitmap : public MxCore { public: __declspec(dllexport) MxBitmap(); __declspec(dllexport) virtual ~MxBitmap(); // vtable+00 - virtual int vtable14(int); - virtual int vtable18(BITMAPINFOHEADER *p_bmiHeader); - virtual int vtable1c(int p_width, int p_height, MxPalette *p_palette, int); - virtual MxResult LoadFile(HANDLE p_handle); + virtual MxResult ImportBitmap(MxBitmap *p_bitmap); // vtable+14 + virtual MxResult ImportBitmapInfo(MxBITMAPINFO *p_info); // vtable+18 + virtual MxResult SetSize(int p_width, int p_height, MxPalette *p_palette, int); // vtable+1c + virtual MxResult LoadFile(HANDLE p_handle); // vtable+20 __declspec(dllexport) virtual MxLong Read(const char *p_filename); // vtable+24 virtual int vtable28(int); virtual void vtable2c(int, int, int, int, int, int, int); virtual void vtable30(int, int, int, int, int, int, int); __declspec(dllexport) virtual MxPalette *CreatePalette(); // vtable+34 - virtual void vtable38(void*); - virtual int vtable3c(MxBool); - virtual int vtable40(HDC p_hdc, int p_xSrc, int p_ySrc, int p_xDest, int p_yDest, int p_destWidth, int p_destHeight); + virtual void ImportPalette(MxPalette* p_palette); // vtable+38 + virtual MxResult SetBitDepth(MxBool); // vtable+3c + virtual MxResult StretchBits(HDC p_hdc, int p_xSrc, int p_ySrc, int p_xDest, int p_yDest, int p_destWidth, int p_destHeight); // vtable+40 private: - BITMAPINFO *m_info; - BITMAPINFOHEADER *m_bmiHeader; - RGBQUAD *m_paletteData; - LPVOID *m_data; - MxBool m_unk18; - MxPalette *m_palette; + MxResult ImportColorsToPalette(RGBQUAD*, MxPalette*); + + MxBITMAPINFO *m_info; // 0x8 + BITMAPINFOHEADER *m_bmiHeader; // 0xc + RGBQUAD *m_paletteData; // 0x10 + LPVOID *m_data; // 0x14 + MxBool m_bitDepth; // 0x18 + MxPalette *m_palette; // 0x1c }; #endif // MXBITMAP_H