11a3dc67ce
Signed-off-by: Gwenhael Le Moine <gwenhael.le.moine@gmail.com>
2829 lines
73 KiB
C
2829 lines
73 KiB
C
/*
|
|
* 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
|
|
}
|