/* * files.c * * This file is part of Emu48 * * Copyright (C) 1995 Sebastien Carlier * */ #include "pch.h" #include "emu48.h" #include "ops.h" #include "io.h" // I/O register definitions #include "kml.h" #include "i28f160.h" // flash support #include "debugger.h" #include "lodepng.h" #pragma intrinsic(abs,labs) TCHAR szEmuDirectory[MAX_PATH]; TCHAR szRomDirectory[MAX_PATH]; TCHAR szCurrentDirectory[MAX_PATH]; TCHAR szCurrentKml[MAX_PATH]; TCHAR szBackupKml[MAX_PATH]; TCHAR szCurrentFilename[MAX_PATH]; TCHAR szBackupFilename[MAX_PATH]; TCHAR szBufferFilename[MAX_PATH]; TCHAR szPort2Filename[MAX_PATH]; BOOL bDocumentAvail = FALSE; // document not available BYTE cCurrentRomType = 0; // Model -> hardware UINT nCurrentClass = 0; // Class -> derivate LPBYTE Port0 = NULL; LPBYTE Port1 = NULL; LPBYTE Port2 = NULL; LPBYTE pbyRom = NULL; BOOL bRomWriteable = TRUE; // flag if ROM writeable DWORD dwRomSize = 0; LPBYTE pbyRomDirtyPage = NULL; DWORD dwRomDirtyPageSize = 0; WORD wRomCrc = 0; // fingerprint of patched ROM BOOL bRomCrcCorrection = FALSE; // ROM CRC correction disabled LPBYTE pbyPort2 = NULL; BOOL bPort2Writeable = FALSE; BOOL bPort2IsShared = FALSE; DWORD dwPort2Size = 0; // size of mapped port2 DWORD dwPort2Mask = 0; WORD wPort2Crc = 0; // fingerprint of port2 BOOL bBackup = FALSE; static HANDLE hRomFile = NULL; static HANDLE hPort2File = NULL; static HANDLE hPort2Map = NULL; static HANDLE hCurrentFile = NULL; // valid document signatures static CONST LPBYTE bySignature[] = { (CONST LPBYTE) "Emu48 Document\xFE", (CONST LPBYTE) "Emu38 Document\xFE", (CONST LPBYTE) "Emu39 Document\xFE", (CONST LPBYTE) "Emu49 Document\xFE", (CONST LPBYTE) "Win48 Document\xFE", (CONST LPBYTE) "Win48 Document\xFF" }; static CHIPSET BackupChipset; static LPBYTE BackupPort0; static LPBYTE BackupPort1; static LPBYTE BackupPort2; static BOOL bRomPacked; //################ //# //# Window Position Tools //# //################ VOID SetWindowLocation(HWND hWnd,INT nPosX,INT nPosY) { WINDOWPLACEMENT wndpl; RECT *pRc = &wndpl.rcNormalPosition; wndpl.length = sizeof(wndpl); GetWindowPlacement(hWnd,&wndpl); pRc->right = pRc->right - pRc->left + nPosX; pRc->bottom = pRc->bottom - pRc->top + nPosY; pRc->left = nPosX; pRc->top = nPosY; SetWindowPlacement(hWnd,&wndpl); return; } //################ //# //# Filename Title Helper Tool //# //################ DWORD GetCutPathName(LPCTSTR szFileName, LPTSTR szBuffer, DWORD dwBufferLength, INT nCutLength) { TCHAR cPath[_MAX_PATH]; // full filename TCHAR cDrive[_MAX_DRIVE]; TCHAR cDir[_MAX_DIR]; TCHAR cFname[_MAX_FNAME]; TCHAR cExt[_MAX_EXT]; _ASSERT(nCutLength >= 0); // 0 = only drive and name // split original filename into parts _tsplitpath(szFileName,cDrive,cDir,cFname,cExt); if (*cDir != 0) // contain directory part { LPTSTR lpFilePart; // address of file name in path INT nNameLen,nPathLen,nMaxPathLen; GetFullPathName(szFileName,ARRAYSIZEOF(cPath),cPath,&lpFilePart); _tsplitpath(cPath,cDrive,cDir,cFname,cExt); // calculate size of drive/name and path nNameLen = lstrlen(cDrive) + lstrlen(cFname) + lstrlen(cExt); nPathLen = lstrlen(cDir); // maximum length for path nMaxPathLen = nCutLength - nNameLen; if (nPathLen > nMaxPathLen) // have to cut path { TCHAR cDirTemp[_MAX_DIR] = _T(""); LPTSTR szPtr; // UNC name if (cDir[0] == _T('\\') && cDir[1] == _T('\\')) { // skip server if ((szPtr = _tcschr(cDir + 2,_T('\\'))) != NULL) { // skip share if ((szPtr = _tcschr(szPtr + 1,_T('\\'))) != NULL) { INT nLength = (INT) (szPtr - cDir); *szPtr = 0; // set EOS behind share // enough room for \\server\\share and "\...\" if (nLength + 5 <= nMaxPathLen) { lstrcpyn(cDirTemp,cDir,ARRAYSIZEOF(cDirTemp)); nMaxPathLen -= nLength; } } } } lstrcat(cDirTemp,_T("\\...")); nMaxPathLen -= 5; // need 6 chars for additional "\..." + "\" if (nMaxPathLen < 0) nMaxPathLen = 0; // get earliest possible '\' character szPtr = &cDir[nPathLen - nMaxPathLen]; szPtr = _tcschr(szPtr,_T('\\')); // not found if (szPtr == NULL) szPtr = _T(""); lstrcat(cDirTemp,szPtr); // copy path with preample to dir buffer lstrcpyn(cDir,cDirTemp,ARRAYSIZEOF(cDir)); } } _tmakepath(cPath,cDrive,cDir,cFname,cExt); lstrcpyn(szBuffer,cPath,dwBufferLength); return lstrlen(szBuffer); } VOID SetWindowPathTitle(LPCTSTR szFileName) { TCHAR cPath[MAX_PATH]; RECT rectClient; if (*szFileName != 0) // set new title { _ASSERT(hWnd != NULL); VERIFY(GetClientRect(hWnd,&rectClient)); GetCutPathName(szFileName,cPath,ARRAYSIZEOF(cPath),rectClient.right/11); SetWindowTitle(cPath); } return; } //################ //# //# BEEP Patch check //# //################ BOOL CheckForBeepPatch(VOID) { typedef struct beeppatch { const DWORD dwAddress; // patch address const BYTE byPattern[4]; // patch pattern } BEEPPATCH, *PBEEPPATCH; // known beep patches const BEEPPATCH s38[] = { { 0x017D0, { 0x8, 0x1, 0xB, 0x1 } } }; const BEEPPATCH s39[] = { { 0x017BC, { 0x8, 0x1, 0xB, 0x1 } } }; const BEEPPATCH s48[] = { { 0x017A6, { 0x8, 0x1, 0xB, 0x1 } } }; const BEEPPATCH s49[] = { { 0x4157A, { 0x8, 0x1, 0xB, 0x1 } }, // 1.18/1.19-5/1.19-6 { 0x41609, { 0x8, 0x1, 0xB, 0x1 } } }; // 1.24/2.01/2.09 const BEEPPATCH *psData; UINT nDataItems; BOOL bMatch; switch (cCurrentRomType) { case '6': case 'A': // HP38G psData = s38; nDataItems = ARRAYSIZEOF(s38); break; case 'E': // HP39/40G psData = s39; nDataItems = ARRAYSIZEOF(s39); break; case 'S': // HP48SX case 'G': // HP48GX psData = s48; nDataItems = ARRAYSIZEOF(s48); break; case 'X': // HP49G psData = s49; nDataItems = ARRAYSIZEOF(s49); break; default: psData = NULL; nDataItems = 0; } // check if one data set match for (bMatch = FALSE; !bMatch && nDataItems > 0; --nDataItems) { _ASSERT(pbyRom != NULL && psData != NULL); // pattern matching? bMatch = (psData->dwAddress + ARRAYSIZEOF(psData->byPattern) < dwRomSize) && (memcmp(&pbyRom[psData->dwAddress],psData->byPattern,ARRAYSIZEOF(psData->byPattern))) == 0; ++psData; // next data set } return bMatch; } //################ //# //# Patch //# //################ static __inline BYTE Asc2Nib(BYTE c) { if (c<'0') return 0; if (c<='9') return c-'0'; if (c<'A') return 0; if (c<='F') return c-'A'+10; if (c<'a') return 0; if (c<='f') return c-'a'+10; return 0; } // functions to restore ROM patches typedef struct tnode { BOOL bPatch; // TRUE = ROM address patched DWORD dwAddress; // patch address BYTE byROM; // original ROM value BYTE byPatch; // patched ROM value struct tnode *prev; // previous node struct tnode *next; // next node } TREENODE, *PTREENODE; static TREENODE *nodePatch = NULL; BOOL PatchNibble(DWORD dwAddress, BYTE byPatch) { PTREENODE p; _ASSERT(pbyRom); // ROM defined if ((p = (PTREENODE) malloc(sizeof(TREENODE))) == NULL) return TRUE; p->bPatch = TRUE; // address patched p->dwAddress = dwAddress; // save current values p->byROM = pbyRom[dwAddress]; p->byPatch = byPatch; p->prev = NULL; p->next = nodePatch; // save node if (nodePatch) nodePatch->prev = p; // add as previous element nodePatch = p; pbyRom[dwAddress] = byPatch; // patch ROM return FALSE; } static VOID RestorePatches(VOID) { TREENODE *p; _ASSERT(pbyRom); // ROM defined while (nodePatch != NULL) { // restore original data pbyRom[nodePatch->dwAddress] = nodePatch->byROM; p = nodePatch->next; // save pointer to next node free(nodePatch); // free node nodePatch = p; // new node } return; } VOID UpdatePatches(BOOL bPatch) { TREENODE *p = nodePatch; _ASSERT(pbyRom); // ROM defined if (bPatch) // patch ROM { if (p) // something in patch list { // goto last element in list for (; p->next != NULL; p = p->next) {} do { if (!p->bPatch) // patch only if not patched { // use original data for patch restore p->byROM = pbyRom[p->dwAddress]; // restore patch data pbyRom[p->dwAddress] = p->byPatch; p->bPatch = TRUE; // address patched } else { _ASSERT(FALSE); // call ROM patch on a patched ROM } p = p->prev; } while (p != NULL); } } else // restore ROM { for (; p != NULL; p = p->next) { // restore original data pbyRom[p->dwAddress] = p->byROM; p->bPatch = FALSE; // address not patched } } return; } BOOL PatchRom(LPCTSTR szFilename) { HANDLE hFile = NULL; DWORD dwFileSizeLow = 0; DWORD dwFileSizeHigh = 0; DWORD lBytesRead = 0; PSZ lpStop,lpBuf = NULL; DWORD dwAddress = 0; UINT nPos = 0; BOOL bSucc = TRUE; if (pbyRom == NULL) return FALSE; SetCurrentDirectory((*szRomDirectory == 0) ? szEmuDirectory : szRomDirectory); hFile = CreateFile(szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); SetCurrentDirectory(szCurrentDirectory); if (hFile == INVALID_HANDLE_VALUE) return FALSE; dwFileSizeLow = GetFileSize(hFile, &dwFileSizeHigh); if (dwFileSizeHigh != 0 || dwFileSizeLow == 0) { // file is too large or empty CloseHandle(hFile); return FALSE; } lpBuf = (PSZ) malloc(dwFileSizeLow+1); if (lpBuf == NULL) { CloseHandle(hFile); return FALSE; } ReadFile(hFile, lpBuf, dwFileSizeLow, &lBytesRead, NULL); CloseHandle(hFile); lpBuf[dwFileSizeLow] = 0; nPos = 0; while (lpBuf[nPos]) { // skip whitespace characters nPos += (UINT) strspn(&lpBuf[nPos]," \t\n\r"); if (lpBuf[nPos] == ';') // comment? { do { nPos++; if (lpBuf[nPos] == '\n') { nPos++; break; } } while (lpBuf[nPos]); continue; } dwAddress = strtoul(&lpBuf[nPos], &lpStop, 16); nPos = (UINT) (lpStop - lpBuf); // position of lpStop if (*lpStop != 0) // data behind address { if (*lpStop != ':') // invalid syntax { // skip to end of line while (lpBuf[nPos] != '\n' && lpBuf[nPos] != 0) { ++nPos; } bSucc = FALSE; continue; } while (lpBuf[++nPos]) { if (isxdigit(lpBuf[nPos]) == FALSE) break; if (dwAddress < dwRomSize) // patch ROM { // patch ROM and save original nibble PatchNibble(dwAddress, Asc2Nib(lpBuf[nPos])); bRomCrcCorrection = TRUE; } ++dwAddress; } } } _ASSERT(nPos <= dwFileSizeLow); // buffer overflow? free(lpBuf); return bSucc; } //################ //# //# ROM //# //################ BOOL CrcRom(WORD *pwChk) // calculate fingerprint of ROM { DWORD *pdwData,dwSize; DWORD dwChk = 0; if (pbyRom == NULL) return TRUE; // ROM CRC isn't available _ASSERT(pbyRom); // view on ROM pdwData = (DWORD *) pbyRom; _ASSERT((dwRomSize % sizeof(*pdwData)) == 0); dwSize = dwRomSize / sizeof(*pdwData); // file size in DWORD's // use checksum, because it's faster while (dwSize-- > 0) { CONST DWORD dwData = *pdwData++; if ((dwData & 0xF0F0F0F0) != 0) // data packed? return FALSE; dwChk += dwData; } *pwChk = (WORD) ((dwChk >> 16) + (dwChk & 0xFFFF)); return TRUE; } BOOL MapRom(LPCTSTR szFilename) { DWORD dwSize,dwFileSize,dwRead; // open ROM for writing BOOL bRomRW = (cCurrentRomType == 'X' || cCurrentRomType == 'Q') ? bRomWriteable : FALSE; // CdB for HP: add apples if (pbyRom != NULL) { return FALSE; } bRomCrcCorrection = FALSE; // ROM CRC correction disabled SetCurrentDirectory((*szRomDirectory == 0) ? szEmuDirectory : szRomDirectory); if (bRomRW) // ROM writeable { hRomFile = CreateFile(szFilename, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); if (hRomFile == INVALID_HANDLE_VALUE) { bRomRW = FALSE; // ROM not writeable hRomFile = CreateFile(szFilename, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); } } else // writing ROM disabled { hRomFile = CreateFile(szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); } SetCurrentDirectory(szCurrentDirectory); if (hRomFile == INVALID_HANDLE_VALUE) { hRomFile = NULL; return FALSE; } dwRomSize = GetFileSize(hRomFile, NULL); // read the first 4 bytes ReadFile(hRomFile,&dwSize,sizeof(dwSize),&dwRead,NULL); if (dwRead < sizeof(dwSize)) { // file is too small. CloseHandle(hRomFile); hRomFile = NULL; dwRomSize = 0; return FALSE; } dwFileSize = dwRomSize; // calculate ROM image buffer size bRomPacked = (dwSize & 0xF0F0F0F0) != 0; // ROM image packed if (bRomPacked) dwRomSize *= 2; // unpacked ROM image has double size pbyRom = (LPBYTE) malloc(dwRomSize); if (pbyRom == NULL) { CloseHandle(hRomFile); hRomFile = NULL; dwRomSize = 0; return FALSE; } *(DWORD *) pbyRom = dwSize; // save first 4 bytes // load rest of file content ReadFile(hRomFile,&pbyRom[sizeof(dwSize)],dwFileSize - sizeof(dwSize),&dwRead,NULL); _ASSERT(dwFileSize - sizeof(dwSize) == dwRead); if (bRomRW) // ROM is writeable { // no. of dirty pages dwRomDirtyPageSize = dwRomSize / ROMPAGESIZE; // alloc dirty page table pbyRomDirtyPage = (LPBYTE) calloc(dwRomDirtyPageSize,sizeof(*pbyRomDirtyPage)); if (pbyRomDirtyPage == NULL) { free(pbyRom); // free ROM image CloseHandle(hRomFile); dwRomDirtyPageSize = 0; pbyRom = NULL; hRomFile = NULL; dwRomSize = 0; return FALSE; } } else { dwRomDirtyPageSize = 0; CloseHandle(hRomFile); hRomFile = NULL; } if (bRomPacked) // packed ROM image { LPBYTE pbySrc = pbyRom+dwFileSize; // source start address LPBYTE pbyDest = pbyRom+dwRomSize; // destination start address while (pbySrc != pbyDest) // unpack source { CONST BYTE byValue = *(--pbySrc); *(--pbyDest) = byValue >> 4; *(--pbyDest) = byValue & 0xF; } } return TRUE; } VOID UnmapRom(VOID) { if (pbyRom == NULL) return; // ROM not mapped RestorePatches(); // restore ROM patches if (hRomFile) // ROM file still open (only in R/W case) { DWORD i; _ASSERT(pbyRomDirtyPage != NULL); // scan for every dirty page for (i = 0; i < dwRomDirtyPageSize; ++i) { if (pbyRomDirtyPage[i]) // page dirty { DWORD dwSize,dwLinPos,dwFilePos,dwWritten; dwLinPos = i * ROMPAGESIZE; // position inside emulator memory dwSize = ROMPAGESIZE; // bytes to write while (i+1 < dwRomDirtyPageSize && pbyRomDirtyPage[i+1]) { dwSize += ROMPAGESIZE; // next page is also dirty ++i; // skip next page in outer loop } dwFilePos = dwLinPos; // ROM file position if (bRomPacked) // repack data { LPBYTE pbySrc,pbyDest,pbyEnd; dwSize /= 2; // adjust no. of bytes to write dwFilePos /= 2; // linear pos in packed file // pack data in page pbySrc = pbyDest = &pbyRom[dwLinPos]; pbyEnd = pbyDest + dwSize; while (pbyDest < pbyEnd) { *pbyDest = *pbySrc++; *pbyDest |= *pbySrc++ << 4; ++pbyDest; } } SetFilePointer(hRomFile,dwFilePos,NULL,FILE_BEGIN); WriteFile(hRomFile,&pbyRom[dwLinPos],dwSize,&dwWritten,NULL); } } free(pbyRomDirtyPage); CloseHandle(hRomFile); pbyRomDirtyPage = NULL; dwRomDirtyPageSize = 0; hRomFile = NULL; } free(pbyRom); // free ROM image pbyRom = NULL; dwRomSize = 0; wRomCrc = 0; bRomCrcCorrection = FALSE; // ROM CRC correction disabled return; } //################ //# //# Port2 //# //################ static BOOL CrcPort2(WORD *pwCrc) // calculate fingerprint of port2 { *pwCrc = 0; if (pbyPort2 != NULL) // port2 CRC available { LPBYTE pbyMem; // get real filesize DWORD dwFileSize = GetFileSize(hPort2File, NULL); for (pbyMem = pbyPort2; dwFileSize > 0; --dwFileSize) { if ((*pbyMem & 0xF0) != 0) // data packed? return FALSE; *pwCrc = UpCRC(*pwCrc,*pbyMem++); } } return TRUE; } BOOL MapPort2(LPCTSTR szFilename) { DWORD dwFileSizeLo,dwFileSizeHi; if (pbyPort2 != NULL) return FALSE; bPort2Writeable = TRUE; dwPort2Size = 0; // reset size of port2 SetCurrentDirectory(szEmuDirectory); hPort2File = CreateFile(szFilename, GENERIC_READ|GENERIC_WRITE, bPort2IsShared ? FILE_SHARE_READ : 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hPort2File == INVALID_HANDLE_VALUE) { bPort2Writeable = FALSE; hPort2File = CreateFile(szFilename, GENERIC_READ, bPort2IsShared ? (FILE_SHARE_READ|FILE_SHARE_WRITE) : 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hPort2File == INVALID_HANDLE_VALUE) { SetCurrentDirectory(szCurrentDirectory); hPort2File = NULL; return FALSE; } } SetCurrentDirectory(szCurrentDirectory); dwFileSizeLo = GetFileSize(hPort2File, &dwFileSizeHi); // size not 32, 128, 256, 512, 1024, 2048 or 4096 KB if ( dwFileSizeHi != 0 || dwFileSizeLo == 0 || (dwFileSizeLo & (dwFileSizeLo - 1)) != 0 || (dwFileSizeLo & 0xFF02FFFF) != 0) { UnmapPort2(); return FALSE; } hPort2Map = CreateFileMapping(hPort2File, NULL, bPort2Writeable ? PAGE_READWRITE : PAGE_READONLY, 0, dwFileSizeLo, NULL); if (hPort2Map == NULL) { UnmapPort2(); return FALSE; } pbyPort2 = (LPBYTE) MapViewOfFile(hPort2Map, bPort2Writeable ? FILE_MAP_WRITE : FILE_MAP_READ, 0, 0, dwFileSizeLo); if (pbyPort2 == NULL) { UnmapPort2(); return FALSE; } dwPort2Mask = (dwFileSizeLo - 1) >> 18; // mask for valid address lines of the BS-FF dwPort2Size = dwFileSizeLo / 2048; // mapping size of port2 if (CrcPort2(&wPort2Crc) == FALSE) // calculate fingerprint of port2 { UnmapPort2(); // free memory AbortMessage(_T("Packed Port 2 image detected!")); return FALSE; } return TRUE; } VOID UnmapPort2(VOID) { if (pbyPort2 != NULL) { UnmapViewOfFile(pbyPort2); pbyPort2 = NULL; } if (hPort2Map != NULL) { CloseHandle(hPort2Map); hPort2Map = NULL; } if (hPort2File != NULL) { CloseHandle(hPort2File); hPort2File = NULL; } dwPort2Size = 0; // reset size of port2 dwPort2Mask = 0; bPort2Writeable = FALSE; wPort2Crc = 0; return; } //################ //# //# Documents //# //################ static BOOL IsDataPacked(VOID *pMem, DWORD dwSize) { DWORD *pdwMem = (DWORD *) pMem; _ASSERT((dwSize % sizeof(DWORD)) == 0); if ((dwSize % sizeof(DWORD)) != 0) return TRUE; for (dwSize /= sizeof(DWORD); dwSize-- > 0;) { if ((*pdwMem++ & 0xF0F0F0F0) != 0) return TRUE; } return FALSE; } VOID ResetDocument(VOID) { DisableDebugger(); if (szCurrentKml[0]) { KillKML(); } if (hCurrentFile) { CloseHandle(hCurrentFile); hCurrentFile = NULL; } szCurrentKml[0] = 0; szCurrentFilename[0] = 0; if (Port0) { free(Port0); Port0 = NULL; } if (Port1) { free(Port1); Port1 = NULL; } if (Port2) { free(Port2); Port2 = NULL; } else UnmapPort2(); ZeroMemory(&Chipset,sizeof(Chipset)); ZeroMemory(&RMap,sizeof(RMap)); // delete MMU mappings ZeroMemory(&WMap,sizeof(WMap)); bDocumentAvail = FALSE; // document not available return; } BOOL NewDocument(VOID) { SaveBackup(); ResetDocument(); if (!DisplayChooseKml(0)) goto restore; if (!InitKML(szCurrentKml,FALSE)) goto restore; Chipset.type = cCurrentRomType; CrcRom(&Chipset.wRomCrc); // save fingerprint of loaded ROM if (Chipset.type == '6' || Chipset.type == 'A') // HP38G { Chipset.Port0Size = (Chipset.type == 'A') ? 32 : 64; Chipset.Port1Size = 0; Chipset.Port2Size = 0; Chipset.cards_status = 0x0; } if (Chipset.type == 'E' || Chipset.type == 'P') // HP39/40G/HP39G+ // CdB for HP: add apples { Chipset.Port0Size = 128; Chipset.Port1Size = 0; Chipset.Port2Size = 128; Chipset.cards_status = 0xF; bPort2Writeable = TRUE; // port2 is writeable } if (Chipset.type == 'S') // HP48SX { Chipset.Port0Size = 32; Chipset.Port1Size = 128; Chipset.Port2Size = 0; Chipset.cards_status = 0x5; // use 2nd command line argument if defined MapPort2((nArgc < 3) ? szPort2Filename : ppArgv[2]); } if (Chipset.type == 'G') // HP48GX { Chipset.Port0Size = 128; Chipset.Port1Size = 128; Chipset.Port2Size = 0; Chipset.cards_status = 0xA; // use 2nd command line argument if defined MapPort2((nArgc < 3) ? szPort2Filename : ppArgv[2]); } if (Chipset.type == 'X' || Chipset.type == '2' || Chipset.type == 'Q') // HP49G/HP48gII/HP49g+/50g // CdB for HP: add apples { Chipset.Port0Size = 256; Chipset.Port1Size = 128; Chipset.Port2Size = 128; Chipset.cards_status = 0xF; bPort2Writeable = TRUE; // port2 is writeable FlashInit(); // init flash structure } if (Chipset.type == 'Q') // HP49g+/50g // CdB for HP: add apples { Chipset.d0size = 16; } Chipset.IORam[LPE] = RST; // set ReSeT bit at power on reset // allocate port memory if (Chipset.Port0Size) { Port0 = (LPBYTE) calloc(Chipset.Port0Size*2048,sizeof(*Port0)); if (Port0 == NULL) goto restore; } if (Chipset.Port1Size) { Port1 = (LPBYTE) calloc(Chipset.Port1Size*2048,sizeof(*Port1)); if (Port1 == NULL) goto restore; } if (Chipset.Port2Size) { Port2 = (LPBYTE) calloc(Chipset.Port2Size*2048,sizeof(*Port2)); if (Port2 == NULL) goto restore; } LoadBreakpointList(NULL); // clear debugger breakpoint list RomSwitch(0); // boot ROM view of HP49G and map memory bDocumentAvail = TRUE; // document available return TRUE; restore: RestoreBackup(); ResetBackup(); // HP48SX/GX if (Chipset.type == 'S' || Chipset.type == 'G') { // use 2nd command line argument if defined MapPort2((nArgc < 3) ? szPort2Filename : ppArgv[2]); } if (pbyRom) { Map(0x00,0xFF); } return FALSE; } BOOL OpenDocument(LPCTSTR szFilename) { #define CHECKAREA(s,e) (offsetof(CHIPSET,e)-offsetof(CHIPSET,s)+sizeof(((CHIPSET *)NULL)->e)) HANDLE hFile = INVALID_HANDLE_VALUE; DWORD lBytesRead,lSizeofChipset; BYTE byFileSignature[16]; BOOL bMatch; UINT i,nLength; // Open file if (lstrcmpi(szCurrentFilename,szFilename) == 0) { if (YesNoMessage(_T("Do you want to reload this document?")) == IDNO) return TRUE; } SaveBackup(); ResetDocument(); hFile = CreateFile(szFilename, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (hFile == INVALID_HANDLE_VALUE) { AbortMessage(_T("This file is missing or already loaded in another instance of Emu48.")); goto restore; } // Read and Compare signature ReadFile(hFile, byFileSignature, sizeof(byFileSignature), &lBytesRead, NULL); // go through all valid document signatures for (bMatch = FALSE, i = 0; !bMatch && i < ARRAYSIZEOF(bySignature); ++i) { bMatch = (memcmp(byFileSignature, bySignature[i], sizeof(byFileSignature)) == 0); } if (!bMatch) // no valid document signature found { AbortMessage(_T("This file is not a valid Emu48 document.")); goto restore; } switch (byFileSignature[14]) { case 0xFE: // Win48 2.1 / Emu4x 0.99.x format // read length of KML script name ReadFile(hFile,&nLength,sizeof(nLength),&lBytesRead,NULL); // KML script name too long for file buffer if (nLength >= ARRAYSIZEOF(szCurrentKml)) { // skip heading KML script name characters until remainder fits into file buffer UINT nSkip = nLength - (ARRAYSIZEOF(szCurrentKml) - 1); SetFilePointer(hFile, nSkip, NULL, FILE_CURRENT); nLength = ARRAYSIZEOF(szCurrentKml) - 1; } #if defined _UNICODE { LPSTR szTmp = (LPSTR) malloc(nLength); if (szTmp == NULL) { AbortMessage(_T("Memory Allocation Failure.")); goto restore; } ReadFile(hFile, szTmp, nLength, &lBytesRead, NULL); MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szTmp, lBytesRead, szCurrentKml, ARRAYSIZEOF(szCurrentKml)); free(szTmp); } #else { ReadFile(hFile, szCurrentKml, nLength, &lBytesRead, NULL); } #endif if (nLength != lBytesRead) goto read_err; szCurrentKml[nLength] = 0; break; case 0xFF: // Win48 2.05 format break; default: _ASSERT(FALSE); } // read chipset size inside file ReadFile(hFile, &lSizeofChipset, sizeof(lSizeofChipset), &lBytesRead, NULL); if (lBytesRead != sizeof(lSizeofChipset)) goto read_err; if (lSizeofChipset <= sizeof(Chipset)) // actual or older chipset version { // read chipset content ZeroMemory(&Chipset,sizeof(Chipset)); // init chipset ReadFile(hFile, &Chipset, lSizeofChipset, &lBytesRead, NULL); } else // newer chipset version { // read my used chipset content ReadFile(hFile, &Chipset, sizeof(Chipset), &lBytesRead, NULL); // skip rest of chipset SetFilePointer(hFile, lSizeofChipset-sizeof(Chipset), NULL, FILE_CURRENT); lSizeofChipset = sizeof(Chipset); } if (lBytesRead != lSizeofChipset) goto read_err; if (!isModelValid(Chipset.type)) // check for valid model in emulator state file { AbortMessage(_T("Emulator state file with invalid calculator model.")); goto restore; } SetWindowLocation(hWnd,Chipset.nPosX,Chipset.nPosY); while (TRUE) { if (szCurrentKml[0]) // KML file name { BOOL bOK = InitKML(szCurrentKml,FALSE); bOK = bOK && (cCurrentRomType == Chipset.type); if (bOK) break; KillKML(); } if (!DisplayChooseKml(Chipset.type)) goto restore; } // reload old button state ReloadButtons(Chipset.Keyboard_Row,ARRAYSIZEOF(Chipset.Keyboard_Row)); FlashInit(); // init flash structure if (Chipset.Port0Size) { Port0 = (LPBYTE) malloc(Chipset.Port0Size*2048); if (Port0 == NULL) { AbortMessage(_T("Memory Allocation Failure.")); goto restore; } ReadFile(hFile, Port0, Chipset.Port0Size*2048, &lBytesRead, NULL); if (lBytesRead != Chipset.Port0Size*2048) goto read_err; if (IsDataPacked(Port0,Chipset.Port0Size*2048)) goto read_err; } if (Chipset.Port1Size) { Port1 = (LPBYTE) malloc(Chipset.Port1Size*2048); if (Port1 == NULL) { AbortMessage(_T("Memory Allocation Failure.")); goto restore; } ReadFile(hFile, Port1, Chipset.Port1Size*2048, &lBytesRead, NULL); if (lBytesRead != Chipset.Port1Size*2048) goto read_err; if (IsDataPacked(Port1,Chipset.Port1Size*2048)) goto read_err; } // HP48SX/GX if (cCurrentRomType=='S' || cCurrentRomType=='G') { MapPort2((nArgc < 3) ? szPort2Filename : ppArgv[2]); // port2 changed and card detection enabled if ( Chipset.wPort2Crc != wPort2Crc && (Chipset.IORam[CARDCTL] & ECDT) != 0 && (Chipset.IORam[TIMER2_CTRL] & RUN) != 0 ) { Chipset.HST |= MP; // set Module Pulled IOBit(SRQ2,NINT,FALSE); // set NINT to low Chipset.SoftInt = TRUE; // set interrupt bInterrupt = TRUE; } } else // HP38G, HP39/40G, HP49G { if (Chipset.Port2Size) { Port2 = (LPBYTE) malloc(Chipset.Port2Size*2048); if (Port2 == NULL) { AbortMessage(_T("Memory Allocation Failure.")); goto restore; } ReadFile(hFile, Port2, Chipset.Port2Size*2048, &lBytesRead, NULL); if (lBytesRead != Chipset.Port2Size*2048) goto read_err; if (IsDataPacked(Port2,Chipset.Port2Size*2048)) goto read_err; bPort2Writeable = TRUE; Chipset.cards_status = 0xF; } } RomSwitch(Chipset.Bank_FF); // reload ROM view of HP49G and map memory if (Chipset.wRomCrc != wRomCrc) // ROM changed { CpuReset(); Chipset.Shutdn = FALSE; // automatic restart } // check CPU main registers if (IsDataPacked(Chipset.A,CHECKAREA(A,R4))) goto read_err; LoadBreakpointList(hFile); // load debugger breakpoint list lstrcpy(szCurrentFilename, szFilename); _ASSERT(hCurrentFile == NULL); hCurrentFile = hFile; #if defined _USRDLL // DLL version // notify main proc about current document file if (pEmuDocumentNotify) pEmuDocumentNotify(szCurrentFilename); #endif SetWindowPathTitle(szCurrentFilename); // update window title line bDocumentAvail = TRUE; // document available return TRUE; read_err: AbortMessage(_T("This file must be truncated, and cannot be loaded.")); restore: if (INVALID_HANDLE_VALUE != hFile) // close if valid handle CloseHandle(hFile); RestoreBackup(); ResetBackup(); // HP48SX/GX if (cCurrentRomType=='S' || cCurrentRomType=='G') { // use 2nd command line argument if defined MapPort2((nArgc < 3) ? szPort2Filename : ppArgv[2]); } return FALSE; #undef CHECKAREA } BOOL SaveDocument(VOID) { DWORD lBytesWritten; DWORD lSizeofChipset; UINT nLength; WINDOWPLACEMENT wndpl; if (hCurrentFile == NULL) return FALSE; _ASSERT(hWnd); // window open wndpl.length = sizeof(wndpl); // update saved window position GetWindowPlacement(hWnd, &wndpl); Chipset.nPosX = (SWORD) wndpl.rcNormalPosition.left; Chipset.nPosY = (SWORD) wndpl.rcNormalPosition.top; SetFilePointer(hCurrentFile,0,NULL,FILE_BEGIN); // write default "Emu48 Document\xFE" signature if (!WriteFile(hCurrentFile, bySignature[0], 16, &lBytesWritten, NULL)) { AbortMessage(_T("Could not write into document file!")); return FALSE; } CrcRom(&Chipset.wRomCrc); // save fingerprint of ROM CrcPort2(&Chipset.wPort2Crc); // save fingerprint of port2 nLength = lstrlen(szCurrentKml); WriteFile(hCurrentFile, &nLength, sizeof(nLength), &lBytesWritten, NULL); #if defined _UNICODE { LPSTR szTmp = (LPSTR) malloc(nLength); if (szTmp != NULL) { WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, szCurrentKml, nLength, szTmp, nLength, NULL, NULL); WriteFile(hCurrentFile, szTmp, nLength, &lBytesWritten, NULL); free(szTmp); } } #else { WriteFile(hCurrentFile, szCurrentKml, nLength, &lBytesWritten, NULL); } #endif lSizeofChipset = sizeof(CHIPSET); WriteFile(hCurrentFile, &lSizeofChipset, sizeof(lSizeofChipset), &lBytesWritten, NULL); WriteFile(hCurrentFile, &Chipset, lSizeofChipset, &lBytesWritten, NULL); if (Chipset.Port0Size) WriteFile(hCurrentFile, Port0, Chipset.Port0Size*2048, &lBytesWritten, NULL); if (Chipset.Port1Size) WriteFile(hCurrentFile, Port1, Chipset.Port1Size*2048, &lBytesWritten, NULL); if (Chipset.Port2Size) WriteFile(hCurrentFile, Port2, Chipset.Port2Size*2048, &lBytesWritten, NULL); SaveBreakpointList(hCurrentFile); // save debugger breakpoint list SetEndOfFile(hCurrentFile); // cut the rest return TRUE; } BOOL SaveDocumentAs(LPCTSTR szFilename) { HANDLE hFile; if (hCurrentFile) // already file in use { CloseHandle(hCurrentFile); // close it, even it's same, so data always will be saved hCurrentFile = NULL; } hFile = CreateFile(szFilename, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL); if (hFile == INVALID_HANDLE_VALUE) // error, couldn't create a new file { AbortMessage(_T("This file must be currently used by another instance of Emu48.")); return FALSE; } lstrcpy(szCurrentFilename, szFilename); // save new file name hCurrentFile = hFile; // and the corresponding handle #if defined _USRDLL // DLL version // notify main proc about current document file if (pEmuDocumentNotify) pEmuDocumentNotify(szCurrentFilename); #endif SetWindowPathTitle(szCurrentFilename); // update window title line return SaveDocument(); // save current content } //################ //# //# Backup //# //################ BOOL SaveBackup(VOID) { WINDOWPLACEMENT wndpl; BOOL bSucc = TRUE; if (!bDocumentAvail) return FALSE; _ASSERT(nState != SM_RUN); // emulation engine is running // save window position _ASSERT(hWnd); // window open wndpl.length = sizeof(wndpl); // update saved window position GetWindowPlacement(hWnd, &wndpl); Chipset.nPosX = (SWORD) wndpl.rcNormalPosition.left; Chipset.nPosY = (SWORD) wndpl.rcNormalPosition.top; lstrcpy(szBackupFilename, szCurrentFilename); lstrcpy(szBackupKml, szCurrentKml); if (BackupPort0) { free(BackupPort0); BackupPort0 = NULL; } if (BackupPort1) { free(BackupPort1); BackupPort1 = NULL; } if (BackupPort2) { free(BackupPort2); BackupPort2 = NULL; } CopyMemory(&BackupChipset, &Chipset, sizeof(Chipset)); if (Port0 && Chipset.Port0Size) { BackupPort0 = (LPBYTE) malloc(Chipset.Port0Size*2048); if (BackupPort0) { CopyMemory(BackupPort0,Port0,Chipset.Port0Size*2048); } bSucc = bSucc && (BackupPort0 != NULL); } if (Port1 && Chipset.Port1Size) { BackupPort1 = (LPBYTE) malloc(Chipset.Port1Size*2048); if (BackupPort1) { CopyMemory(BackupPort1,Port1,Chipset.Port1Size*2048); } bSucc = bSucc && (BackupPort1 != NULL); } if (Port2 && Chipset.Port2Size) // internal port2 { BackupPort2 = (LPBYTE) malloc(Chipset.Port2Size*2048); if (BackupPort2) { CopyMemory(BackupPort2,Port2,Chipset.Port2Size*2048); } bSucc = bSucc && (BackupPort2 != NULL); } CreateBackupBreakpointList(); bBackup = bSucc; return bSucc; } BOOL RestoreBackup(VOID) { BOOL bDbgOpen; BOOL bSucc = TRUE; if (!bBackup) return FALSE; bDbgOpen = (nDbgState != DBG_OFF); // debugger window open? ResetDocument(); // need chipset for contrast setting in InitKML() Chipset.contrast = BackupChipset.contrast; if (!InitKML(szBackupKml,TRUE)) { InitKML(szCurrentKml,TRUE); return FALSE; } lstrcpy(szCurrentKml, szBackupKml); lstrcpy(szCurrentFilename, szBackupFilename); if (szCurrentFilename[0]) { hCurrentFile = CreateFile(szCurrentFilename, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (hCurrentFile == INVALID_HANDLE_VALUE) { hCurrentFile = NULL; szCurrentFilename[0] = 0; } } CopyMemory(&Chipset, &BackupChipset, sizeof(Chipset)); if (BackupPort0 && Chipset.Port0Size) { Port0 = (LPBYTE) malloc(Chipset.Port0Size*2048); if (Port0) { CopyMemory(Port0,BackupPort0,Chipset.Port0Size*2048); } bSucc = bSucc && (Port0 != NULL); } if (BackupPort1 && Chipset.Port1Size) { Port1 = (LPBYTE) malloc(Chipset.Port1Size*2048); if (Port1) { CopyMemory(Port1,BackupPort1,Chipset.Port1Size*2048); } bSucc = bSucc && (Port1 != NULL); } if (BackupPort2 && Chipset.Port2Size) // internal port2 { Port2 = (LPBYTE) malloc(Chipset.Port2Size*2048); if (Port2) { CopyMemory(Port2,BackupPort2,Chipset.Port2Size*2048); } bSucc = bSucc && (Port2 != NULL); } // map port2 else { if (cCurrentRomType=='S' || cCurrentRomType=='G') // HP48SX/GX { // use 2nd command line argument if defined MapPort2((nArgc < 3) ? szPort2Filename : ppArgv[2]); } } Map(0x00,0xFF); // update memory mapping SetWindowPathTitle(szCurrentFilename); // update window title line SetWindowLocation(hWnd,Chipset.nPosX,Chipset.nPosY); RestoreBackupBreakpointList(); // restore the debugger breakpoint list if (bDbgOpen) OnToolDebug(); // reopen the debugger if (!bSucc) // restore not successful (memory allocation errors) { ResetDocument(); // cleanup remainders } bDocumentAvail = bSucc; // document available return bSucc; } BOOL ResetBackup(VOID) { if (!bBackup) return FALSE; szBackupFilename[0] = 0; szBackupKml[0] = 0; if (BackupPort0) { free(BackupPort0); BackupPort0 = NULL; } if (BackupPort1) { free(BackupPort1); BackupPort1 = NULL; } if (BackupPort2) { free(BackupPort2); BackupPort2 = NULL; } ZeroMemory(&BackupChipset,sizeof(BackupChipset)); bBackup = FALSE; return TRUE; } //################ //# //# Open File Common Dialog Boxes //# //################ static VOID InitializeOFN(LPOPENFILENAME ofn) { ZeroMemory((LPVOID)ofn, sizeof(OPENFILENAME)); ofn->lStructSize = sizeof(OPENFILENAME); ofn->hwndOwner = hWnd; ofn->Flags = OFN_EXPLORER|OFN_HIDEREADONLY; return; } BOOL GetOpenFilename(VOID) { TCHAR szBuffer[ARRAYSIZEOF(szBufferFilename)]; OPENFILENAME ofn; InitializeOFN(&ofn); ofn.lpstrFilter = _T("Emu48 Files (*.e38;*.e39;*.e48;*.e49)\0") _T("*.e38;*.e39;*.e48;*.e49\0") _T("HP-38 Files (*.e38)\0*.e38\0") _T("HP-39 Files (*.e39)\0*.e39\0") _T("HP-48 Files (*.e48)\0*.e48\0") _T("HP-49 Files (*.e49)\0*.e49\0") _T("Win48 Files (*.w48)\0*.w48\0"); ofn.nFilterIndex = 1; ofn.lpstrFile = szBuffer; ofn.lpstrFile[0] = 0; ofn.nMaxFile = ARRAYSIZEOF(szBuffer); ofn.Flags |= OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST; if (GetOpenFileName(&ofn) == FALSE) return FALSE; _ASSERT(ARRAYSIZEOF(szBufferFilename) == ofn.nMaxFile); lstrcpy(szBufferFilename, ofn.lpstrFile); return TRUE; } BOOL GetSaveAsFilename(VOID) { TCHAR szBuffer[ARRAYSIZEOF(szBufferFilename)]; OPENFILENAME ofn; InitializeOFN(&ofn); ofn.lpstrFilter = _T("HP-38 Files (*.e38)\0*.e38\0") _T("HP-39 Files (*.e39)\0*.e39\0") _T("HP-48 Files (*.e48)\0*.e48\0") _T("HP-49 Files (*.e49)\0*.e49\0"); ofn.lpstrDefExt = _T("e48"); // HP48SX/GX ofn.nFilterIndex = 3; if (cCurrentRomType=='6' || cCurrentRomType=='A') // HP38G { ofn.lpstrDefExt = _T("e38"); ofn.nFilterIndex = 1; } if (cCurrentRomType=='E' || cCurrentRomType=='P') // HP39/40 // CdB for HP: add apples { ofn.lpstrDefExt = _T("e39"); ofn.nFilterIndex = 2; } if (cCurrentRomType=='X' || cCurrentRomType=='2' || cCurrentRomType=='Q') // HP49G/HP48gII/HP49g+/HP50g // CdB for HP: add apples { ofn.lpstrDefExt = _T("e49"); ofn.nFilterIndex = 4; } ofn.lpstrFile = szBuffer; ofn.lpstrFile[0] = 0; ofn.nMaxFile = ARRAYSIZEOF(szBuffer); ofn.Flags |= OFN_CREATEPROMPT|OFN_OVERWRITEPROMPT; if (GetSaveFileName(&ofn) == FALSE) return FALSE; _ASSERT(ARRAYSIZEOF(szBufferFilename) == ofn.nMaxFile); lstrcpy(szBufferFilename, ofn.lpstrFile); return TRUE; } BOOL GetLoadObjectFilename(LPCTSTR lpstrFilter,LPCTSTR lpstrDefExt) { TCHAR szBuffer[ARRAYSIZEOF(szBufferFilename)]; OPENFILENAME ofn; InitializeOFN(&ofn); ofn.lpstrFilter = lpstrFilter; ofn.lpstrDefExt = lpstrDefExt; ofn.nFilterIndex = 1; ofn.lpstrFile = szBuffer; ofn.lpstrFile[0] = 0; ofn.nMaxFile = ARRAYSIZEOF(szBuffer); ofn.Flags |= OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST; if (GetOpenFileName(&ofn) == FALSE) return FALSE; _ASSERT(ARRAYSIZEOF(szBufferFilename) == ofn.nMaxFile); lstrcpy(szBufferFilename, ofn.lpstrFile); return TRUE; } BOOL GetSaveObjectFilename(LPCTSTR lpstrFilter,LPCTSTR lpstrDefExt) { TCHAR szBuffer[ARRAYSIZEOF(szBufferFilename)]; OPENFILENAME ofn; InitializeOFN(&ofn); ofn.lpstrFilter = lpstrFilter; ofn.lpstrDefExt = NULL; ofn.nFilterIndex = 1; ofn.lpstrFile = szBuffer; ofn.lpstrFile[0] = 0; ofn.nMaxFile = ARRAYSIZEOF(szBuffer); ofn.Flags |= OFN_CREATEPROMPT|OFN_OVERWRITEPROMPT; if (GetSaveFileName(&ofn) == FALSE) return FALSE; _ASSERT(ARRAYSIZEOF(szBufferFilename) == ofn.nMaxFile); lstrcpy(szBufferFilename, ofn.lpstrFile); if (ofn.nFileExtension == 0) // given filename has no extension { // actual name length UINT nLength = lstrlen(szBufferFilename); // destination buffer has room for the default extension if (nLength + 1 + lstrlen(lpstrDefExt) < ARRAYSIZEOF(szBufferFilename)) { // add default extension szBufferFilename[nLength++] = _T('.'); lstrcpy(&szBufferFilename[nLength], lpstrDefExt); } } return TRUE; } //################ //# //# Load and Save HP48 Objects //# //################ WORD WriteStack(UINT nStkLevel,LPBYTE lpBuf,DWORD dwSize) // separated from LoadObject() { BOOL bBinary; DWORD dwAddress, i; bBinary = ((lpBuf[dwSize+0]=='H') && (lpBuf[dwSize+1]=='P') && (lpBuf[dwSize+2]=='H') && (lpBuf[dwSize+3]=='P') && (lpBuf[dwSize+4]=='4') && (lpBuf[dwSize+5]==((cCurrentRomType=='X' || cCurrentRomType=='2' || cCurrentRomType=='Q') ? '9' : '8')) // CdB for HP: add apples && (lpBuf[dwSize+6]=='-')); for (dwAddress = 0, i = 0; i < dwSize; i++) { BYTE byTwoNibs = lpBuf[i+dwSize]; lpBuf[dwAddress++] = (BYTE)(byTwoNibs&0xF); lpBuf[dwAddress++] = (BYTE)(byTwoNibs>>4); } dwSize = dwAddress; // unpacked buffer size if (bBinary == TRUE) { // load as binary dwSize = RPL_ObjectSize(lpBuf+16,dwSize-16); if (dwSize == BAD_OB) return S_ERR_OBJECT; dwAddress = RPL_CreateTemp(dwSize,TRUE); if (dwAddress == 0) return S_ERR_BINARY; Nwrite(lpBuf+16,dwAddress,dwSize); } else { // load as string dwAddress = RPL_CreateTemp(dwSize+10,TRUE); if (dwAddress == 0) return S_ERR_ASCII; Write5(dwAddress,0x02A2C); // String Write5(dwAddress+5,dwSize+5); // length of String Nwrite(lpBuf,dwAddress+10,dwSize); // data } RPL_Push(nStkLevel,dwAddress); return S_ERR_NO; } BOOL LoadObject(LPCTSTR szFilename) // separated stack writing part { HANDLE hFile; DWORD dwFileSizeLow; DWORD dwFileSizeHigh; LPBYTE lpBuf; WORD wError; hFile = CreateFile(szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); if (hFile == INVALID_HANDLE_VALUE) return FALSE; dwFileSizeLow = GetFileSize(hFile, &dwFileSizeHigh); if (dwFileSizeHigh != 0) { // file is too large. CloseHandle(hFile); return FALSE; } lpBuf = (LPBYTE) malloc(dwFileSizeLow*2); if (lpBuf == NULL) { CloseHandle(hFile); return FALSE; } ReadFile(hFile, lpBuf+dwFileSizeLow, dwFileSizeLow, &dwFileSizeHigh, NULL); CloseHandle(hFile); wError = WriteStack(1,lpBuf,dwFileSizeLow); if (wError == S_ERR_OBJECT) AbortMessage(_T("This isn't a valid binary file.")); if (wError == S_ERR_BINARY) AbortMessage(_T("The calculator does not have enough\nfree memory to load this binary file.")); if (wError == S_ERR_ASCII) AbortMessage(_T("The calculator does not have enough\nfree memory to load this text file.")); free(lpBuf); return (wError == S_ERR_NO); } BOOL SaveObject(LPCTSTR szFilename) // separated stack reading part { HANDLE hFile; LPBYTE pbyHeader; DWORD lBytesWritten; DWORD dwAddress; DWORD dwLength; dwAddress = RPL_Pick(1); if (dwAddress == 0) { AbortMessage(_T("Too Few Arguments.")); return FALSE; } dwLength = (RPL_SkipOb(dwAddress) - dwAddress + 1) / 2; hFile = CreateFile(szFilename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN, NULL); if (hFile == INVALID_HANDLE_VALUE) { AbortMessage(_T("Cannot open file.")); return FALSE; } pbyHeader = ((cCurrentRomType=='X' || cCurrentRomType=='2' || cCurrentRomType=='Q')) ? (LPBYTE) BINARYHEADER49 : (LPBYTE) BINARYHEADER48; WriteFile(hFile, pbyHeader, 8, &lBytesWritten, NULL); while (dwLength--) { BYTE byByte = Read2(dwAddress); WriteFile(hFile, &byByte, 1, &lBytesWritten, NULL); dwAddress += 2; } CloseHandle(hFile); return TRUE; } //################ //# //# Load Icon //# //################ BOOL LoadIconFromFile(LPCTSTR szFilename) { HANDLE hIcon; SetCurrentDirectory(szEmuDirectory); // not necessary to destroy because icon is shared hIcon = LoadImage(NULL, szFilename, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE|LR_LOADFROMFILE|LR_SHARED); SetCurrentDirectory(szCurrentDirectory); if (hIcon) { SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM) hIcon); SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM) hIcon); } return hIcon != NULL; } VOID LoadIconDefault(VOID) { // use window class icon SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM) NULL); SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM) NULL); return; } //################ //# //# Load Bitmap //# //################ #define WIDTHBYTES(bits) ((((bits) + 31) / 32) * 4) typedef struct _BmpFile { DWORD dwPos; // actual reading pos DWORD dwFileSize; // file size LPBYTE pbyFile; // buffer } BMPFILE, FAR *LPBMPFILE, *PBMPFILE; static __inline WORD DibNumColors(__unaligned BITMAPINFOHEADER CONST *lpbi) { if (lpbi->biClrUsed != 0) return (WORD) lpbi->biClrUsed; /* a 24 bitcount DIB has no color table */ return (lpbi->biBitCount <= 8) ? (1 << lpbi->biBitCount) : 0; } static HPALETTE CreateBIPalette(__unaligned BITMAPINFOHEADER CONST *lpbi) { LOGPALETTE* pPal; HPALETTE hpal = NULL; WORD wNumColors; BYTE red; BYTE green; BYTE blue; UINT i; __unaligned RGBQUAD* pRgb; if (!lpbi || lpbi->biSize != sizeof(BITMAPINFOHEADER)) return NULL; // Get a pointer to the color table and the number of colors in it pRgb = (RGBQUAD FAR *)((LPBYTE)lpbi + (WORD)lpbi->biSize); wNumColors = DibNumColors(lpbi); if (wNumColors) { // Allocate for the logical palette structure pPal = (PLOGPALETTE) malloc(sizeof(LOGPALETTE) + wNumColors * sizeof(PALETTEENTRY)); if (!pPal) return NULL; pPal->palVersion = 0x300; pPal->palNumEntries = wNumColors; // Fill in the palette entries from the DIB color table and // create a logical color palette. for (i = 0; i < pPal->palNumEntries; i++) { pPal->palPalEntry[i].peRed = pRgb[i].rgbRed; pPal->palPalEntry[i].peGreen = pRgb[i].rgbGreen; pPal->palPalEntry[i].peBlue = pRgb[i].rgbBlue; pPal->palPalEntry[i].peFlags = 0; } hpal = CreatePalette(pPal); free(pPal); } else { // create halftone palette for 16, 24 and 32 bitcount bitmaps // 16, 24 and 32 bitcount DIB's have no color table entries so, set the // number of to the maximum value (256). wNumColors = 256; pPal = (PLOGPALETTE) malloc(sizeof(LOGPALETTE) + wNumColors * sizeof(PALETTEENTRY)); if (!pPal) return NULL; pPal->palVersion = 0x300; pPal->palNumEntries = wNumColors; red = green = blue = 0; // Generate 256 (= 8*8*4) RGB combinations to fill the palette // entries. for (i = 0; i < pPal->palNumEntries; i++) { pPal->palPalEntry[i].peRed = red; pPal->palPalEntry[i].peGreen = green; pPal->palPalEntry[i].peBlue = blue; pPal->palPalEntry[i].peFlags = 0; if (!(red += 32)) if (!(green += 32)) blue += 64; } hpal = CreatePalette(pPal); free(pPal); } return hpal; } static HBITMAP DecodeBmp(LPBMPFILE pBmp,BOOL bPalette) { DWORD dwFileSize; HBITMAP hBitmap = NULL; // map memory to BITMAPFILEHEADER and BITMAPINFO const LPBITMAPFILEHEADER pBmfh = (LPBITMAPFILEHEADER) pBmp->pbyFile; const __unaligned LPBITMAPINFO pBmi = (__unaligned LPBITMAPINFO) & pBmfh[1]; // size of bitmap header information & check for bitmap dwFileSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); if ( pBmp->dwFileSize < dwFileSize // minimum size to read data from BITMAPFILEHEADER + BITMAPINFOHEADER || pBmfh->bfType != 0x4D42) // "BM" return NULL; // size with color table if (pBmi->bmiHeader.biCompression == BI_BITFIELDS) { dwFileSize += 3 * sizeof(DWORD); } else { dwFileSize += DibNumColors(&pBmi->bmiHeader) * sizeof(RGBQUAD); } if (dwFileSize != pBmfh->bfOffBits) return NULL; // size with bitmap data if (pBmi->bmiHeader.biCompression != BI_RGB) { dwFileSize += pBmi->bmiHeader.biSizeImage; } else { dwFileSize += WIDTHBYTES(pBmi->bmiHeader.biWidth * pBmi->bmiHeader.biBitCount) * labs(pBmi->bmiHeader.biHeight); } if (pBmp->dwFileSize < dwFileSize) return NULL; VERIFY(hBitmap = CreateDIBitmap( hWindowDC, &pBmi->bmiHeader, CBM_INIT, pBmp->pbyFile + pBmfh->bfOffBits, pBmi, DIB_RGB_COLORS)); if (hBitmap == NULL) return NULL; if (bPalette && hPalette == NULL) { hPalette = CreateBIPalette(&pBmi->bmiHeader); // save old palette hOldPalette = SelectPalette(hWindowDC, hPalette, FALSE); RealizePalette(hWindowDC); } return hBitmap; } static BOOL ReadGifByte(LPBMPFILE pGif, INT *n) { // outside GIF file if (pGif->dwPos >= pGif->dwFileSize) return TRUE; *n = pGif->pbyFile[pGif->dwPos++]; return FALSE; } static BOOL ReadGifWord(LPBMPFILE pGif, INT *n) { // outside GIF file if (pGif->dwPos + 1 >= pGif->dwFileSize) return TRUE; *n = pGif->pbyFile[pGif->dwPos++]; *n |= (pGif->pbyFile[pGif->dwPos++] << 8); return FALSE; } static HBITMAP DecodeGif(LPBMPFILE pBmp,DWORD *pdwTransparentColor,BOOL bPalette) { // this implementation base on the GIF image file // decoder engine of Free42 (c) by Thomas Okken HBITMAP hBitmap; typedef struct cmap { WORD biBitCount; // bits used in color map DWORD biClrUsed; // no of colors in color map RGBQUAD bmiColors[256]; // color map } CMAP; BOOL bHasGlobalCmap; CMAP sGlb; // data of global color map INT nWidth,nHeight,nInfo,nBackground,nZero; LONG lBytesPerLine; LPBYTE pbyPixels; BITMAPINFO bmi; // global bitmap info BOOL bDecoding = TRUE; hBitmap = NULL; pBmp->dwPos = 6; // position behind GIF header /* Bits 6..4 of info contain one less than the "color resolution", * defined as the number of significant bits per RGB component in * the source image's color palette. If the source image (from * which the GIF was generated) was 24-bit true color, the color * resolution is 8, so the value in bits 6..4 is 7. If the source * image had an EGA color cube (2x2x2), the color resolution would * be 2, etc. * Bit 3 of info must be zero in GIF87a; in GIF89a, if it is set, * it indicates that the global colormap is sorted, the most * important entries being first. In PseudoColor environments this * can be used to make sure to get the most important colors from * the X server first, to optimize the image's appearance in the * event that not all the colors from the colormap can actually be * obtained at the same time. * The 'zero' field is always 0 in GIF87a; in GIF89a, it indicates * the pixel aspect ratio, as (PAR + 15) : 64. If PAR is zero, * this means no aspect ratio information is given, PAR = 1 means * 1:4 (narrow), PAR = 49 means 1:1 (square), PAR = 255 means * slightly over 4:1 (wide), etc. */ if ( ReadGifWord(pBmp,&nWidth) || ReadGifWord(pBmp,&nHeight) || ReadGifByte(pBmp,&nInfo) || ReadGifByte(pBmp,&nBackground) || ReadGifByte(pBmp,&nZero) || nZero != 0) goto quit; ZeroMemory(&bmi,sizeof(bmi)); // init bitmap info bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = nWidth; bmi.bmiHeader.biHeight = nHeight; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 24; // create a true color DIB bmi.bmiHeader.biCompression = BI_RGB; ZeroMemory(&sGlb,sizeof(sGlb)); // init global color map bHasGlobalCmap = (nInfo & 0x80) != 0; sGlb.biBitCount = (nInfo & 7) + 1; // bits used in global color map sGlb.biClrUsed = (1 << sGlb.biBitCount); // no of colors in global color map // color table should not exceed 256 colors _ASSERT(sGlb.biClrUsed <= ARRAYSIZEOF(sGlb.bmiColors)); if (bHasGlobalCmap) // global color map { DWORD i; for (i = 0; i < sGlb.biClrUsed; ++i) { int r, g, b; if (ReadGifByte(pBmp,&r) || ReadGifByte(pBmp,&g) || ReadGifByte(pBmp,&b)) goto quit; sGlb.bmiColors[i].rgbRed = r; sGlb.bmiColors[i].rgbGreen = g; sGlb.bmiColors[i].rgbBlue = b; } } else // no color map { DWORD i; for (i = 0; i < sGlb.biClrUsed; ++i) { BYTE k = (BYTE) ((i * sGlb.biClrUsed) / (sGlb.biClrUsed - 1)); sGlb.bmiColors[i].rgbRed = k; sGlb.bmiColors[i].rgbGreen = k; sGlb.bmiColors[i].rgbBlue = k; } } // bitmap dimensions lBytesPerLine = WIDTHBYTES(bmi.bmiHeader.biWidth * bmi.bmiHeader.biBitCount); bmi.bmiHeader.biSizeImage = lBytesPerLine * bmi.bmiHeader.biHeight; // create top-down DIB bmi.bmiHeader.biHeight = -bmi.bmiHeader.biHeight; // allocate buffer for pixels VERIFY(hBitmap = CreateDIBSection(hWindowDC, &bmi, DIB_RGB_COLORS, (VOID **)&pbyPixels, NULL, 0)); if (hBitmap == NULL) goto quit; // fill pixel buffer with background color for (nHeight = 0; nHeight < labs(bmi.bmiHeader.biHeight); ++nHeight) { LPBYTE pbyLine = pbyPixels + nHeight * lBytesPerLine; for (nWidth = 0; nWidth < bmi.bmiHeader.biWidth; ++nWidth) { *pbyLine++ = sGlb.bmiColors[nBackground].rgbBlue; *pbyLine++ = sGlb.bmiColors[nBackground].rgbGreen; *pbyLine++ = sGlb.bmiColors[nBackground].rgbRed; } _ASSERT((DWORD) (pbyLine - pbyPixels) <= bmi.bmiHeader.biSizeImage); } while (bDecoding) { INT nBlockType; if (ReadGifByte(pBmp,&nBlockType)) goto quit; switch (nBlockType) { case ',': // image { CMAP sAct; // data of actual color map INT nLeft,nTop,nWidth,nHeight; INT i,nInfo; BOOL bInterlaced; INT h,v; INT nCodesize; // LZW codesize in bits INT nBytecount; SHORT prefix_table[4096]; SHORT code_table[4096]; INT nMaxcode; INT nClearCode; INT nEndCode; INT nCurrCodesize; INT nCurrCode; INT nOldCode; INT nBitsNeeded; BOOL bEndCodeSeen; // read image dimensions if ( ReadGifWord(pBmp,&nLeft) || ReadGifWord(pBmp,&nTop) || ReadGifWord(pBmp,&nWidth) || ReadGifWord(pBmp,&nHeight) || ReadGifByte(pBmp,&nInfo)) goto quit; if ( nTop + nHeight > labs(bmi.bmiHeader.biHeight) || nLeft + nWidth > bmi.bmiHeader.biWidth) goto quit; /* Bit 3 of info must be zero in GIF87a; in GIF89a, if it * is set, it indicates that the local colormap is sorted, * the most important entries being first. In PseudoColor * environments this can be used to make sure to get the * most important colors from the X server first, to * optimize the image's appearance in the event that not * all the colors from the colormap can actually be * obtained at the same time. */ if ((nInfo & 0x80) == 0) // using global color map { sAct = sGlb; } else // using local color map { DWORD i; sAct.biBitCount = (nInfo & 7) + 1; // bits used in color map sAct.biClrUsed = (1 << sAct.biBitCount); // no of colors in color map for (i = 0; i < sAct.biClrUsed; ++i) { int r, g, b; if (ReadGifByte(pBmp,&r) || ReadGifByte(pBmp,&g) || ReadGifByte(pBmp,&b)) goto quit; sAct.bmiColors[i].rgbRed = r; sAct.bmiColors[i].rgbGreen = g; sAct.bmiColors[i].rgbBlue = b; } } // interlaced image bInterlaced = (nInfo & 0x40) != 0; h = 0; v = 0; if ( ReadGifByte(pBmp,&nCodesize) || ReadGifByte(pBmp,&nBytecount)) goto quit; nMaxcode = (1 << nCodesize); // preset LZW table for (i = 0; i < nMaxcode + 2; ++i) { prefix_table[i] = -1; code_table[i] = i; } nClearCode = nMaxcode++; nEndCode = nMaxcode++; nCurrCodesize = nCodesize + 1; nCurrCode = 0; nOldCode = -1; nBitsNeeded = nCurrCodesize; bEndCodeSeen = FALSE; while (nBytecount != 0) { for (i = 0; i < nBytecount; ++i) { INT nCurrByte; INT nBitsAvailable; if (ReadGifByte(pBmp,&nCurrByte)) goto quit; if (bEndCodeSeen) continue; nBitsAvailable = 8; while (nBitsAvailable != 0) { INT nBitsCopied = (nBitsNeeded < nBitsAvailable) ? nBitsNeeded : nBitsAvailable; INT nBits = nCurrByte >> (8 - nBitsAvailable); nBits &= 0xFF >> (8 - nBitsCopied); nCurrCode |= nBits << (nCurrCodesize - nBitsNeeded); nBitsAvailable -= nBitsCopied; nBitsNeeded -= nBitsCopied; if (nBitsNeeded == 0) { BYTE byExpanded[4096]; INT nExplen; do { if (nCurrCode == nEndCode) { bEndCodeSeen = TRUE; break; } if (nCurrCode == nClearCode) { nMaxcode = (1 << nCodesize) + 2; nCurrCodesize = nCodesize + 1; nOldCode = -1; break; } if (nCurrCode < nMaxcode) { if (nMaxcode < 4096 && nOldCode != -1) { INT c = nCurrCode; while (prefix_table[c] != -1) c = prefix_table[c]; c = code_table[c]; prefix_table[nMaxcode] = nOldCode; code_table[nMaxcode] = c; nMaxcode++; if (nMaxcode == (1 << nCurrCodesize) && nCurrCodesize < 12) nCurrCodesize++; } } else { INT c; if (nCurrCode > nMaxcode || nOldCode == -1) goto quit; _ASSERT(nCurrCode == nMaxcode); /* Once maxcode == 4096, we can't get here * any more, because we refuse to raise * nCurrCodeSize above 12 -- so we can * never read a bigger code than 4095. */ c = nOldCode; while (prefix_table[c] != -1) c = prefix_table[c]; c = code_table[c]; prefix_table[nMaxcode] = nOldCode; code_table[nMaxcode] = c; nMaxcode++; if (nMaxcode == (1 << nCurrCodesize) && nCurrCodesize < 12) nCurrCodesize++; } nOldCode = nCurrCode; // output nCurrCode! nExplen = 0; while (nCurrCode != -1) { _ASSERT(nExplen < ARRAYSIZEOF(byExpanded)); byExpanded[nExplen++] = (BYTE) code_table[nCurrCode]; nCurrCode = prefix_table[nCurrCode]; } _ASSERT(nExplen > 0); while (--nExplen >= 0) { // get color map index BYTE byColIndex = byExpanded[nExplen]; LPBYTE pbyRgbr = pbyPixels + (lBytesPerLine * (nTop + v) + 3 * (nLeft + h)); _ASSERT(pbyRgbr + 2 < pbyPixels + bmi.bmiHeader.biSizeImage); _ASSERT(byColIndex < sAct.biClrUsed); *pbyRgbr++ = sAct.bmiColors[byColIndex].rgbBlue; *pbyRgbr++ = sAct.bmiColors[byColIndex].rgbGreen; *pbyRgbr = sAct.bmiColors[byColIndex].rgbRed; if (++h == nWidth) { h = 0; if (bInterlaced) { switch (v & 7) { case 0: v += 8; if (v < nHeight) break; /* Some GIF en/decoders go * straight from the '0' * pass to the '4' pass * without checking the * height, and blow up on * 2/3/4 pixel high * interlaced images. */ if (nHeight > 4) v = 4; else if (nHeight > 2) v = 2; else if (nHeight > 1) v = 1; else bEndCodeSeen = TRUE; break; case 4: v += 8; if (v >= nHeight) v = 2; break; case 2: case 6: v += 4; if (v >= nHeight) v = 1; break; case 1: case 3: case 5: case 7: v += 2; if (v >= nHeight) bEndCodeSeen = TRUE; break; } if (bEndCodeSeen) break; // while (--nExplen >= 0) } else // non interlaced { if (++v == nHeight) { bEndCodeSeen = TRUE; break; // while (--nExplen >= 0) } } } } } while (FALSE); nCurrCode = 0; nBitsNeeded = nCurrCodesize; } } } if (ReadGifByte(pBmp,&nBytecount)) goto quit; } } break; case '!': // extension block { INT i,nFunctionCode,nByteCount,nDummy; if (ReadGifByte(pBmp,&nFunctionCode)) goto quit; if (ReadGifByte(pBmp,&nByteCount)) goto quit; // Graphic Control Label & correct Block Size if (nFunctionCode == 0xF9 && nByteCount == 0x04) { INT nPackedFields,nColorIndex; // packed fields if (ReadGifByte(pBmp,&nPackedFields)) goto quit; // delay time if (ReadGifWord(pBmp,&nDummy)) goto quit; // transparent color index if (ReadGifByte(pBmp,&nColorIndex)) goto quit; // transparent color flag set if ((nPackedFields & 0x1) != 0) { if (pdwTransparentColor != NULL) { *pdwTransparentColor = RGB(sGlb.bmiColors[nColorIndex].rgbRed, sGlb.bmiColors[nColorIndex].rgbGreen, sGlb.bmiColors[nColorIndex].rgbBlue); } } // block terminator (0 byte) if (!(!ReadGifByte(pBmp,&nDummy) && nDummy == 0)) goto quit; } else // other function { while (nByteCount != 0) { for (i = 0; i < nByteCount; ++i) { if (ReadGifByte(pBmp,&nDummy)) goto quit; } if (ReadGifByte(pBmp,&nByteCount)) goto quit; } } } break; case ';': // terminator bDecoding = FALSE; break; default: goto quit; } } _ASSERT(bDecoding == FALSE); // decoding successful // normal decoding exit if (bPalette && hPalette == NULL) { hPalette = CreateBIPalette((PBITMAPINFOHEADER) &bmi); // save old palette hOldPalette = SelectPalette(hWindowDC, hPalette, FALSE); RealizePalette(hWindowDC); } quit: if (hBitmap != NULL && bDecoding) // creation failed { DeleteObject(hBitmap); // delete bitmap hBitmap = NULL; } return hBitmap; } static HBITMAP DecodePng(LPBMPFILE pBmp,BOOL bPalette) { // this implementation use the PNG image file decoder // engine of Copyright (c) 2005-2018 Lode Vandevenne HBITMAP hBitmap; UINT uError,uWidth,uHeight; INT nWidth,nHeight; LONG lBytesPerLine; LPBYTE pbyImage; // PNG RGB image data LPBYTE pbySrc; // source buffer pointer LPBYTE pbyPixels; // BMP buffer BITMAPINFO bmi; hBitmap = NULL; pbyImage = NULL; // decode PNG image uError = lodepng_decode_memory(&pbyImage,&uWidth,&uHeight,pBmp->pbyFile,pBmp->dwFileSize,LCT_RGB,8); if (uError) goto quit; ZeroMemory(&bmi,sizeof(bmi)); // init bitmap info bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = (LONG) uWidth; bmi.bmiHeader.biHeight = (LONG) uHeight; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 24; // create a true color DIB bmi.bmiHeader.biCompression = BI_RGB; // bitmap dimensions lBytesPerLine = WIDTHBYTES(bmi.bmiHeader.biWidth * bmi.bmiHeader.biBitCount); bmi.bmiHeader.biSizeImage = lBytesPerLine * bmi.bmiHeader.biHeight; // allocate buffer for pixels VERIFY(hBitmap = CreateDIBSection(hWindowDC, &bmi, DIB_RGB_COLORS, (VOID **)&pbyPixels, NULL, 0)); if (hBitmap == NULL) goto quit; pbySrc = pbyImage; // init source loop pointer pbyPixels += bmi.bmiHeader.biSizeImage; // end of destination bitmap // fill bottom up DIB pixel buffer with color information for (nHeight = 0; nHeight < bmi.bmiHeader.biHeight; ++nHeight) { LPBYTE pbyLine; pbyPixels -= lBytesPerLine; // begin of previous row pbyLine = pbyPixels; // row working copy for (nWidth = 0; nWidth < bmi.bmiHeader.biWidth; ++nWidth) { *pbyLine++ = pbySrc[2]; // blue *pbyLine++ = pbySrc[1]; // green *pbyLine++ = pbySrc[0]; // red pbySrc += 3; } } if (bPalette && hPalette == NULL) { hPalette = CreateBIPalette((PBITMAPINFOHEADER) &bmi); // save old palette hOldPalette = SelectPalette(hWindowDC, hPalette, FALSE); RealizePalette(hWindowDC); } quit: if (pbyImage != NULL) // buffer for PNG image allocated { free(pbyImage); // free PNG image data } return hBitmap; } HBITMAP LoadBitmapFile(LPCTSTR szFilename,BOOL bPalette) { HANDLE hFile; HANDLE hMap; BMPFILE Bmp; HBITMAP hBitmap; SetCurrentDirectory(szEmuDirectory); hFile = CreateFile(szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); SetCurrentDirectory(szCurrentDirectory); if (hFile == INVALID_HANDLE_VALUE) return NULL; Bmp.dwFileSize = GetFileSize(hFile, NULL); hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); if (hMap == NULL) { CloseHandle(hFile); return NULL; } Bmp.pbyFile = (LPBYTE) MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); if (Bmp.pbyFile == NULL) { CloseHandle(hMap); CloseHandle(hFile); return NULL; } do { // check for bitmap file header "BM" if (Bmp.dwFileSize >= 2 && *(WORD *) Bmp.pbyFile == 0x4D42) { hBitmap = DecodeBmp(&Bmp,bPalette); break; } // check for GIF file header if ( Bmp.dwFileSize >= 6 && (memcmp(Bmp.pbyFile,"GIF87a",6) == 0 || memcmp(Bmp.pbyFile,"GIF89a",6) == 0)) { hBitmap = DecodeGif(&Bmp,&dwTColor,bPalette); break; } // check for PNG file header if (Bmp.dwFileSize >= 8 && memcmp(Bmp.pbyFile,"\x89PNG\r\n\x1a\n",8) == 0) { hBitmap = DecodePng(&Bmp,bPalette); break; } // unknown file type hBitmap = NULL; } while (FALSE); UnmapViewOfFile(Bmp.pbyFile); CloseHandle(hMap); CloseHandle(hFile); return hBitmap; } static BOOL AbsColorCmp(DWORD dwColor1,DWORD dwColor2,DWORD dwTol) { DWORD dwDiff; dwDiff = (DWORD) abs((INT) (dwColor1 & 0xFF) - (INT) (dwColor2 & 0xFF)); dwColor1 >>= 8; dwColor2 >>= 8; dwDiff += (DWORD) abs((INT) (dwColor1 & 0xFF) - (INT) (dwColor2 & 0xFF)); dwColor1 >>= 8; dwColor2 >>= 8; dwDiff += (DWORD) abs((INT) (dwColor1 & 0xFF) - (INT) (dwColor2 & 0xFF)); return dwDiff > dwTol; // FALSE = colors match } static BOOL LabColorCmp(DWORD dwColor1,DWORD dwColor2,DWORD dwTol) { DWORD dwDiff; INT nDiffCol; nDiffCol = (INT) (dwColor1 & 0xFF) - (INT) (dwColor2 & 0xFF); dwDiff = (DWORD) (nDiffCol * nDiffCol); dwColor1 >>= 8; dwColor2 >>= 8; nDiffCol = (INT) (dwColor1 & 0xFF) - (INT) (dwColor2 & 0xFF); dwDiff += (DWORD) (nDiffCol * nDiffCol); dwColor1 >>= 8; dwColor2 >>= 8; nDiffCol = (INT) (dwColor1 & 0xFF) - (INT) (dwColor2 & 0xFF); dwDiff += (DWORD) (nDiffCol * nDiffCol); dwTol *= dwTol; return dwDiff > dwTol; // FALSE = colors match } static DWORD EncodeColorBits(DWORD dwColorVal,DWORD dwMask) { #define MAXBIT 32 UINT uLshift = MAXBIT; UINT uRshift = 8; DWORD dwBitMask = dwMask; dwColorVal &= 0xFF; // the color component using the lowest 8 bit // position of highest bit while ((dwBitMask & (1<<(MAXBIT-1))) == 0 && uLshift > 0) { dwBitMask <<= 1; // next bit --uLshift; // next position } if (uLshift > 24) // avoid overflow on 32bit mask { uLshift -= uRshift; // normalize left shift uRshift = 0; } return ((dwColorVal << uLshift) >> uRshift) & dwMask; #undef MAXBIT } HRGN CreateRgnFromBitmap(HBITMAP hBmp,COLORREF color,DWORD dwTol) { #define ADD_RECTS_COUNT 256 BOOL (*fnColorCmp)(DWORD dwColor1,DWORD dwColor2,DWORD dwTol); DWORD dwRed,dwGreen,dwBlue; LPRGNDATA pRgnData; LPBITMAPINFO bi; LPBYTE pbyBits; LPBYTE pbyColor; DWORD dwAlignedWidthBytes; DWORD dwBpp; DWORD dwRectsCount; LONG x,y,xleft; BOOL bFoundLeft; BOOL bIsMask; HRGN hRgn = NULL; // no region defined if (dwTol >= 1000) // use CIE L*a*b compare { fnColorCmp = LabColorCmp; dwTol -= 1000; // remove L*a*b compare selector } else // use Abs summation compare { fnColorCmp = AbsColorCmp; } // allocate memory for extended image information incl. RGBQUAD color table if ((bi = (LPBITMAPINFO) calloc(1,sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD))) == NULL) { return hRgn; // no region } bi->bmiHeader.biSize = sizeof(bi->bmiHeader); _ASSERT(bi->bmiHeader.biBitCount == 0); // for query without color table // get information about image GetDIBits(hWindowDC,hBmp,0,0,NULL,bi,DIB_RGB_COLORS); // DWORD aligned bitmap width in BYTES dwAlignedWidthBytes = WIDTHBYTES( bi->bmiHeader.biWidth * bi->bmiHeader.biPlanes * bi->bmiHeader.biBitCount); // biSizeImage is empty if (bi->bmiHeader.biSizeImage == 0 && bi->bmiHeader.biCompression == BI_RGB) { bi->bmiHeader.biSizeImage = dwAlignedWidthBytes * bi->bmiHeader.biHeight; } // allocate memory for image data (colors) if ((pbyBits = (LPBYTE) malloc(bi->bmiHeader.biSizeImage)) == NULL) { free(bi); // free bitmap info return hRgn; // no region } // fill bits buffer GetDIBits(hWindowDC,hBmp,0,bi->bmiHeader.biHeight,pbyBits,bi,DIB_RGB_COLORS); // convert color if current DC is 16-bit/32-bit bitfield coded if (bi->bmiHeader.biCompression == BI_BITFIELDS) { dwRed = *(LPDWORD) &bi->bmiColors[0]; dwGreen = *(LPDWORD) &bi->bmiColors[1]; dwBlue = *(LPDWORD) &bi->bmiColors[2]; } else // RGB coded { // convert color if current DC is 16-bit RGB coded if (bi->bmiHeader.biBitCount == 16) { // for 15 bit (5:5:5) dwRed = 0x00007C00; dwGreen = 0x000003E0; dwBlue = 0x0000001F; } else { // convert COLORREF to RGBQUAD color dwRed = 0x00FF0000; dwGreen = 0x0000FF00; dwBlue = 0x000000FF; } } color = EncodeColorBits((color >> 16), dwBlue) | EncodeColorBits((color >> 8), dwGreen) | EncodeColorBits((color >> 0), dwRed); dwBpp = bi->bmiHeader.biBitCount >> 3; // bytes per pixel // DIB is bottom up image so we begin with the last scanline pbyColor = pbyBits + (bi->bmiHeader.biHeight - 1) * dwAlignedWidthBytes; dwRectsCount = bi->bmiHeader.biHeight; // number of rects in allocated buffer bFoundLeft = FALSE; // set when mask has been found in current scan line // allocate memory for region data pRgnData = (PRGNDATA) malloc(sizeof(RGNDATAHEADER) + dwRectsCount * sizeof(RECT)); if (pRgnData) { // fill it by default ZeroMemory(&pRgnData->rdh,sizeof(pRgnData->rdh)); pRgnData->rdh.dwSize = sizeof(pRgnData->rdh); pRgnData->rdh.iType = RDH_RECTANGLES; SetRect(&pRgnData->rdh.rcBound,MAXLONG,MAXLONG,0,0); } for (y = 0; pRgnData && y < bi->bmiHeader.biHeight; ++y) { LPBYTE pbyLineStart = pbyColor; for (x = 0; pRgnData && x < bi->bmiHeader.biWidth; ++x) { // get color switch (bi->bmiHeader.biBitCount) { case 8: bIsMask = fnColorCmp(*(LPDWORD)(&bi->bmiColors)[*pbyColor],color,dwTol); break; case 16: // it makes no sense to allow a tolerance here bIsMask = (*(LPWORD)pbyColor != (WORD) color); break; case 24: bIsMask = fnColorCmp((*(LPDWORD)pbyColor & 0x00ffffff),color,dwTol); break; case 32: bIsMask = fnColorCmp(*(LPDWORD)pbyColor,color,dwTol); } pbyColor += dwBpp; // shift pointer to next color if (!bFoundLeft && bIsMask) // non transparent color found { xleft = x; bFoundLeft = TRUE; } if (bFoundLeft) // found non transparent color in scanline { // transparent color or last column if (!bIsMask || x + 1 == bi->bmiHeader.biWidth) { // non transparent color and last column if (bIsMask && x + 1 == bi->bmiHeader.biWidth) ++x; // save current RECT ((LPRECT) pRgnData->Buffer)[pRgnData->rdh.nCount].left = xleft; ((LPRECT) pRgnData->Buffer)[pRgnData->rdh.nCount].top = y; ((LPRECT) pRgnData->Buffer)[pRgnData->rdh.nCount].right = x; ((LPRECT) pRgnData->Buffer)[pRgnData->rdh.nCount].bottom = y + 1; pRgnData->rdh.nCount++; if (xleft < pRgnData->rdh.rcBound.left) pRgnData->rdh.rcBound.left = xleft; if (y < pRgnData->rdh.rcBound.top) pRgnData->rdh.rcBound.top = y; if (x > pRgnData->rdh.rcBound.right) pRgnData->rdh.rcBound.right = x; if (y + 1 > pRgnData->rdh.rcBound.bottom) pRgnData->rdh.rcBound.bottom = y + 1; // if buffer full reallocate it with more room if (pRgnData->rdh.nCount >= dwRectsCount) { LPRGNDATA pNewRgnData; dwRectsCount += ADD_RECTS_COUNT; pNewRgnData = (LPRGNDATA) realloc(pRgnData,sizeof(RGNDATAHEADER) + dwRectsCount * sizeof(RECT)); if (pNewRgnData) { pRgnData = pNewRgnData; } else { free(pRgnData); pRgnData = NULL; } } bFoundLeft = FALSE; } } } // previous scanline pbyColor = pbyLineStart - dwAlignedWidthBytes; } // release image data free(pbyBits); free(bi); if (pRgnData) // has region data, create region { hRgn = ExtCreateRegion(NULL,sizeof(RGNDATAHEADER) + pRgnData->rdh.nCount * sizeof(RECT),pRgnData); free(pRgnData); } return hRgn; #undef ADD_RECTS_COUNT }