/*
 *   symbfile.c
 *
 *   This file is part of Emu48
 *
 *   Copyright (C) 2008 Christoph Gießelink
 *
 */
#include "pch.h"
#include "Emu48.h"

//################
//#
//#    Saturn Object File Reading
//#
//################

#define RECORD_BLOCK	256					// block size
#define OS_RESOLVED		0x8000				// resolved symbol
#define OS_RELOCATABLE	0x4000				// relocatable symbol

#define SAT_ID			"Saturn3"			// saturn block header
#define SYMB_ID			"Symb"				// symbol block header

#define HASHENTRIES		199					// size of hash table

typedef struct _REFDATA
{
	LPTSTR lpszName;						// symbol name
	DWORD  dwAddr;							// resolved address
	struct _REFDATA* pNext;
} REFDATA, *PREFDATA;

static PREFDATA ppsBase[HASHENTRIES];		// base of symbol references (initialized with NULL)

static __inline DWORD GetHash(DWORD dwVal)
{
	return dwVal % HASHENTRIES;				// hash function
}

static DWORD GetBigEndian(LPBYTE pbyData, INT nSize)
{
	DWORD dwVal = 0;

	while (nSize-- > 0)
	{
		dwVal <<= 8;
		dwVal += *pbyData++;
	}
	return dwVal;
}

//
// check if entry table is empty
//
BOOL RplTableEmpty(VOID)
{
	DWORD i;

	BOOL bEmpty = TRUE;

	// check if hash table is empty
	for (i = 0; bEmpty && i < ARRAYSIZEOF(ppsBase); ++i)
	{
		bEmpty = (ppsBase[i] == NULL);		// check if empty
	}
	return bEmpty;
}

//
// load entry table
//
BOOL RplLoadTable(LPCTSTR lpszFilename)
{
	BYTE   byPage[RECORD_BLOCK];			// record page size
	HANDLE hFile;
	DWORD  dwFileLength,dwCodeLength,dwNoSymbols,dwNoReferences;
	DWORD  dwFilePos,dwBytesRead,dwSymb,dwPageIndex,dwResolvedSymb;
	BOOL   bSymbol,bSucc;

	bSucc = FALSE;

	hFile = CreateFile(lpszFilename,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,NULL);
	if (hFile != INVALID_HANDLE_VALUE)
	{
		dwResolvedSymb = 0;					// no resolved symbols added
		bSymbol = TRUE;						// next set is a symbol

		// read first page
		ReadFile(hFile,byPage,sizeof(byPage),&dwBytesRead,NULL);
		if (dwBytesRead == sizeof(byPage) && memcmp(byPage,SAT_ID,7) == 0)
		{
			// file length in bytes
			dwFileLength = GetBigEndian(byPage+7,sizeof(WORD)) * sizeof(byPage);

			// code area in nibbles
			dwCodeLength = GetBigEndian(byPage+9,sizeof(DWORD));

			// no. of symbols & references
			dwNoSymbols = GetBigEndian(byPage+13,sizeof(WORD));

			// no. of references
			dwNoReferences = GetBigEndian(byPage+15,sizeof(WORD));

			// convert code area length into no. of pages
			dwPageIndex = (dwCodeLength + (2 * sizeof(byPage) - 1)) / (2 * sizeof(byPage));

			// calculate no. of code pages
			dwFilePos = dwPageIndex * sizeof(byPage);

			// jump to begin of symbols by skipping no. of code pages
			bSucc = SetFilePointer(hFile,dwFilePos,NULL,FILE_CURRENT) != INVALID_SET_FILE_POINTER;

			dwFilePos += sizeof(byPage);	// actual file position
		}

		// read all symbol pages
		for (dwPageIndex = 256, dwSymb = 0; bSucc && dwSymb < dwNoSymbols; dwPageIndex += 42)
		{
			if (dwPageIndex >= 256)			// read complete page
			{
				// read new symbol page
				ReadFile(hFile,byPage,sizeof(byPage),&dwBytesRead,NULL);
				dwFilePos += dwBytesRead;	// update file position
				if (   dwFilePos > dwFileLength
					|| dwBytesRead != sizeof(byPage)
					|| memcmp(byPage,SYMB_ID,4) != 0)
				{
					bSucc = FALSE;
					break;
				}

				dwPageIndex = 4;			// begin of new symbol
			}

			if (bSymbol)					// this is the 42 byte symbol set
			{
				WORD wSymbolType = (WORD) GetBigEndian(byPage+dwPageIndex+36,sizeof(WORD));

				// check if it's a resolved or relocatable symbol
				bSymbol = (wSymbolType & OS_RESOLVED) != 0;

				if (bSymbol) ++dwResolvedSymb; // added resolved symbol

				if (wSymbolType == OS_RESOLVED) // resolved symbol type
				{
					TCHAR szSymbolName[36+1],*pcPtr;
					PREFDATA pData;
					DWORD dwHash;

					#if defined _UNICODE
					{
						MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED,(LPCSTR)byPage+dwPageIndex,36,
											szSymbolName,ARRAYSIZEOF(szSymbolName));
						szSymbolName[36] = 0; // set EOS
					}
					#else
					{
						lstrcpyn(szSymbolName,(LPCSTR)byPage+dwPageIndex,ARRAYSIZEOF(szSymbolName));
					}
					#endif

					// cut symbol name at first space character
					if ((pcPtr = _tcschr(szSymbolName,_T(' '))) != NULL)
						*pcPtr = 0;			// set EOS

					// allocate symbol memory
					VERIFY(pData = (PREFDATA) malloc(sizeof(*pData)));
					pData->lpszName = DuplicateString(szSymbolName);
					pData->dwAddr = GetBigEndian(byPage+dwPageIndex+38,sizeof(DWORD));

					// add to hash table
					dwHash = GetHash(pData->dwAddr);
					pData->pNext = ppsBase[dwHash];
					ppsBase[dwHash] = pData;
				}

				++dwSymb;					// got symbol
			}
			else							// 42 byte fill reference
			{
				bSymbol = TRUE;				// nothing to do, next is a symbol set
			}
		}

		bSucc = bSucc && (dwFilePos <= dwFileLength)
					  && (dwNoSymbols == (dwResolvedSymb + dwNoReferences));

		CloseHandle(hFile);
	}

	if (!bSucc) RplDeleteTable();			// delete current table
	return bSucc;
}

//
// delete entry table
//
VOID RplDeleteTable(VOID)
{
	PREFDATA pData;
	DWORD    i;

	// clear hash entries
	for (i = 0; i < ARRAYSIZEOF(ppsBase); ++i)
	{
		while (ppsBase[i] != NULL)			// walk through all datasets
		{
			pData = ppsBase[i]->pNext;
			free(ppsBase[i]->lpszName);
			free(ppsBase[i]);
			ppsBase[i] = pData;
		}
	}
	return;
}

//
// return name for given entry address
//
LPCTSTR RplGetName(DWORD dwAddr)
{
	PREFDATA pData = ppsBase[GetHash(dwAddr)];

	// walk through all datasets of hash entry
	for (; pData != NULL; pData = pData->pNext)
	{
		if (pData->dwAddr == dwAddr)		// found address
			return pData->lpszName;			// return symbol name
	}
	return NULL;
}

//
// return entry address for given name
//
BOOL RplGetAddr(LPCTSTR lpszName, DWORD *pdwAddr)
{
	PREFDATA pData;
	DWORD    i;

	// check for every dataset in hash table
	for (i = 0; i < ARRAYSIZEOF(ppsBase); ++i)
	{
		// walk through all datasets of hash entry
		for (pData = ppsBase[i]; pData != NULL; pData = pData->pNext)
		{
			// found symbol name
			if (lstrcmp(lpszName,pData->lpszName) == 0)
			{
				*pdwAddr = pData->dwAddr;	// return address
				return FALSE;				// found
			}
		}
	}
	return TRUE;							// not found
}
