#define WIN32_LEAN_AND_MEAN
#define WIN32_EXTRA_LEAN
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <malloc.h>
#include <assert.h>

#define HP38G	0
#define HP48S	1
#define HP48G	2

LPBYTE pRom;
WORD   wCRC;
WORD   wType;

static WORD crc_table[16] =
{
	0x0000, 0x1081, 0x2102, 0x3183, 0x4204, 0x5285, 0x6306, 0x7387,
	0x8408, 0x9489, 0xA50A, 0xB58B, 0xC60C, 0xD68D, 0xE70E, 0xF78F
};
static __inline VOID CRC(BYTE nib)
{
	wCRC = (WORD)((wCRC>>4)^crc_table[(wCRC^nib)&0xf]);
}

BOOL CheckCRC()
{
	DWORD dwD0, dwD1;
	WORD  wRomCRC;
	UINT  i;
	DWORD dwBase = 0x00000;
	UINT  nPass = 0;
	UINT  nPasses = (wType != HP48S)?2:1;

again:

	wRomCRC = pRom[dwBase+0x7FFFC]
			|(pRom[dwBase+0x7FFFD]<<4)
			|(pRom[dwBase+0x7FFFE]<<8)
			|(pRom[dwBase+0x7FFFF]<<12);

	wCRC = 0x0000;
	dwD0 = dwBase + 0x00000;
	dwD1 = dwBase + 0x40000;
	do
	{
		for (i=0; i<16; i++) CRC(pRom[dwD0+i]);
		for (i=0; i<16; i++) CRC(pRom[dwD1+i]);
		dwD0 += 16;
		dwD1 += 16;
	} while (dwD0&0x3FFFF);

	if (wCRC==0xFFFF)
	{
		printf("CRC%i: %04X Ok\n", nPass, wRomCRC);
	}
	else
	{
		printf("CRC%i: %04X Failed (%04X)\n", nPass, wRomCRC, wCRC);
		return FALSE;
	}

	if (++nPass == nPasses) return TRUE;

	dwBase += 0x80000;
	goto again;

}

static BYTE Asc2Nib(char 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;
}

static DWORD Asc2Nib5(LPSTR lpBuf)
{
	return (
		 ((DWORD)Asc2Nib(lpBuf[0])<<16)
		|((DWORD)Asc2Nib(lpBuf[1])<<12)
		|((DWORD)Asc2Nib(lpBuf[2])<<8)
		|((DWORD)Asc2Nib(lpBuf[3])<<4)
		|((DWORD)Asc2Nib(lpBuf[4])));
}

static BOOL IsHP(DWORD dwAddress)
{
	char cH = (pRom[dwAddress + 1] << 4) | pRom[dwAddress];
	char cP = (pRom[dwAddress + 3] << 4) | pRom[dwAddress + 2];
	return cH == 'H' && cP == 'P';
}

UINT main(int argc, char *argv[])
{
	HANDLE hFile;
	HANDLE hMap;
	HANDLE hOut;
	LPBYTE pIn;
	DWORD  dwSizeLo;
	BYTE   szVersion[16];
	DWORD  dwWritten;
	UINT  i,uLen;
	DWORD dwAddress;

	DWORD dwAddrOffset = 0x00000;

	BOOL   bFormatDetected = FALSE;

	BOOL   bUnpack = FALSE;
	BOOL   bSwap = FALSE;
	BOOL   bText = FALSE;
	BOOL   bDA19 = FALSE;

	if ((argc!=2)&&(argc!=3))
	{
		printf("Usage:\n\t%s <old-rom-dump> [<new-rom-dump>]\n", argv[0]);
		return 1;
	}
	pRom = LocalAlloc(LMEM_FIXED,512*1024*2);
	if (pRom == NULL)
	{
		printf("Memory Allocation Failed !");
		return 1;
	}

	hFile = CreateFile(argv[1],GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
	if (hFile == INVALID_HANDLE_VALUE)
	{
		LocalFree(pRom);
		printf("Cannot open file %s.\n", argv[1]);
		return 1;
	}
	dwSizeLo = GetFileSize(hFile, NULL);
	hMap  = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
	if (hMap == NULL)
	{
		LocalFree(pRom);
		CloseHandle(hFile);
		puts("CreateFileMapping failed.");
		return 1;
	}
	pIn   = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
	if (pIn == NULL)
	{
		LocalFree(pRom);
		CloseHandle(hMap);
		CloseHandle(hFile);
		puts("MapViewOfFile failed.\n");
		return 1;
	}

	for (i = 0; i < 2 && !bFormatDetected; ++i)
	{
		switch (pIn[0+dwAddrOffset])
		{
		case '0':
			if (pIn[1+dwAddrOffset]!='0') break;
			if (pIn[2+dwAddrOffset]!='0') break;
			if (pIn[3+dwAddrOffset]!='0') break;
			if (pIn[4+dwAddrOffset]!='0') break;
			if (pIn[5+dwAddrOffset]!=':') break;
			bText = TRUE;
			bFormatDetected = TRUE;
			break;
		case 0x23:
			bUnpack = TRUE;
			bSwap   = TRUE;
			bFormatDetected = TRUE;
			break;
		case 0x32:
			bUnpack = TRUE;
			bFormatDetected = TRUE;
			break;
		case 0x03:
			bSwap = TRUE;
		case 0x02:
			if (pIn[1+dwAddrOffset] == (bSwap ? 0x02 : 0x03))
			{
				bFormatDetected = TRUE;
				break;
			}
			bSwap = FALSE;
		default:
			dwAddrOffset = dwSizeLo / 2;
			bDA19 = TRUE;
			break;
		}
	}

	if (!bFormatDetected)
	{
		LocalFree(pRom);
		UnmapViewOfFile(pIn);
		CloseHandle(hMap);
		CloseHandle(hFile);
		printf("Stopped, unknown format.\n");
		return 1;
	}

	if (bUnpack) printf("Unpacking nibbles.\n");
	if (bSwap)   printf("Swapping nibbles.\n");
	if (bText)   printf("Reading text file.\n");
	if (bDA19)   printf("Swapping banks.\n");

	if (bText)
	{
		DWORD i = 0;
		while (i<dwSizeLo)
		{
			DWORD d = Asc2Nib5(pIn+i);
			i+=6;
			pRom[d+0x0] = Asc2Nib(pIn[i+0x0]);
			pRom[d+0x1] = Asc2Nib(pIn[i+0x1]);
			pRom[d+0x2] = Asc2Nib(pIn[i+0x2]);
			pRom[d+0x3] = Asc2Nib(pIn[i+0x3]);
			pRom[d+0x4] = Asc2Nib(pIn[i+0x4]);
			pRom[d+0x5] = Asc2Nib(pIn[i+0x5]);
			pRom[d+0x6] = Asc2Nib(pIn[i+0x6]);
			pRom[d+0x7] = Asc2Nib(pIn[i+0x7]);
			pRom[d+0x8] = Asc2Nib(pIn[i+0x8]);
			pRom[d+0x9] = Asc2Nib(pIn[i+0x9]);
			pRom[d+0xA] = Asc2Nib(pIn[i+0xA]);
			pRom[d+0xB] = Asc2Nib(pIn[i+0xB]);
			pRom[d+0xC] = Asc2Nib(pIn[i+0xC]);
			pRom[d+0xD] = Asc2Nib(pIn[i+0xD]);
			pRom[d+0xE] = Asc2Nib(pIn[i+0xE]);
			pRom[d+0xF] = Asc2Nib(pIn[i+0xF]);
			i+=16;
			while ((i<dwSizeLo)&&((pIn[i]==0x0D)||(pIn[i]==0x0A))) i++;
		}
	}
	else
	{
		if (bUnpack)
		{
			if (bSwap)
			{
				DWORD i;
				for (i=0; i<dwSizeLo; i++)
				{
					BYTE byC = pIn[(i+dwAddrOffset)&(dwSizeLo-1)];
					pRom[(i<<1)  ] = byC>>4;
					pRom[(i<<1)+1] = byC&0xF;
				}
			}
			else
			{
				DWORD i;
				for (i=0; i<dwSizeLo; i++)
				{
					BYTE byC = pIn[(i+dwAddrOffset)&(dwSizeLo-1)];
					pRom[(i<<1)  ] = byC&0xF;
					pRom[(i<<1)+1] = byC>>4;
				}
			}
		}
		else
		{
			if (bSwap)
			{
				DWORD i;
				for (i=0; i<dwSizeLo; i+=2)
				{
					BYTE a, b;
					a = pIn[(i+dwAddrOffset)&(dwSizeLo-1)];
					b = pIn[(i+1+dwAddrOffset)&(dwSizeLo-1)];
					pRom[i] = b;
					pRom[i+1] = a;
				}
			}
			else
			{
				if(bDA19)
				{
					assert(dwAddrOffset == dwSizeLo/2);
					CopyMemory(&pRom[0]           , &pIn[dwAddrOffset], dwAddrOffset);
					CopyMemory(&pRom[dwAddrOffset], &pIn[0]           , dwAddrOffset);
				}
				else
				{
					CopyMemory(pRom, pIn, dwSizeLo);
				}
			}
		}
	}

	UnmapViewOfFile(pIn);
	CloseHandle(hMap);
	CloseHandle(hFile);

	if (bText||bUnpack||bSwap||bDA19)
	{
		printf("File converted.\n\n");
	}

	do
	{
		// HP38G
		wType = HP38G;
		dwAddress = 0x7FFAF;
		uLen = 10;
		if (IsHP(dwAddress)) break;

		// HP48SX
		wType = HP48S;
		dwAddress = 0x7FFF0;
		uLen = 6;
		if (IsHP(dwAddress)) break;

		// HP48GX
		wType = HP48G;
		dwAddress = 0x7FFBF;
		uLen = 6;
		if (IsHP(dwAddress)) break;

		// unknown
		uLen = 0;
	}
	while (0);

	printf("ROM Model Detected : HP%c8%c\n",(wType == HP38G)?'3':'4',(wType != HP48S)?'G':'S');

	for (i=0; i<uLen; ++i)
	{
		szVersion[i] = pRom[dwAddress + (i<<1) + 1] << 4;
		szVersion[i] |= pRom[dwAddress + (i<<1)];
	}
	szVersion[i] = 0;

	printf("ROM Version : %s\n", szVersion);

	ZeroMemory(pRom+0x100, 0x40);			// clear IO register area

	if (CheckCRC())
	{
		printf("ROM CRC Test Passed.\n");
	}
	else
	{
		printf("ROM CRC Test FAILED !\n");
	}

	if (argc == 3)
	{
		hOut = CreateFile(argv[2],GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
		if (hFile == INVALID_HANDLE_VALUE)
		{
			LocalFree(pRom);
			printf("Cannot open file %s.\n", argv[2]);
			return 1;
		}
		WriteFile(hOut,pRom,(wType != HP48S)?(512*1024*2):(256*1024*2),&dwWritten,NULL);
		CloseHandle(hOut);
	}

	LocalFree(pRom);

	return 0;
}