/*
 * This code is released under the Microsoft Public License (MS-PL). See License.txt, below.
 */
#include "stdafx.h"
#include "struct.h"
#include "compare.h"
#include "create.h"

BYTE getbyte(
	LPBYTE ptr
	)
{
	return *ptr;
}

WORD getword(
	LPBYTE ptr
	)
{
	return (WORD)((getbyte(ptr + 1) <<  8) + getbyte(ptr));
}

DWORD getlong(
	LPBYTE ptr
	)
{
	return (DWORD)((getword(ptr + 2) << 16) + getword(ptr));
}

_int64 getlonglong(
	LPBYTE ptr
	)
{
	return ((_int64)getlong(ptr + 4) << 32) + getlong(ptr);
}

DWORD getFilesize(
	FILE *fp
	)
{
	DWORD filesize = 0;
	if (fp != NULL) {
		fseek(fp, 0, SEEK_END);
		filesize = (DWORD)ftell(fp);
		rewind(fp);
	}

	return filesize;
}

DWORD getBitCount(
	DWORD n
	)
{
	n = ((n & 0xaaaaaaaa) >> 1) + (n & 0x55555555); 
	n = ((n & 0xcccccccc) >> 2) + (n & 0x33333333); 
	n = ((n & 0xf0f0f0f0) >> 4) + (n & 0x0f0f0f0f); 
	n = ((n & 0xff00ff00) >> 8) + (n & 0x00ff00ff); 
	n = ((n & 0xffff0000) >> 16) + (n & 0x0000ffff); 
    return n;
}

void GetType(
	LPBYTE buf,
	DWORD size,
	DWORD i,
	PINTERNAL_MUSIC im, 
	PEXEC_TYPE pExecType
	)
{
	// "pQES" (for General)
	if (buf[i] == 0x70 && buf[i + 1] == 0x51 &&
		buf[i + 2] == 0x45 && buf[i + 3] == 0x53) {
		if (buf[i + 7] == 0x01) {
			_tcscpy(im->pszSeqExt, _T(".seq"));
		}
		else if (buf[i + 7] == 0) {
			_tcscpy(im->pszSeqExt, _T(".sep"));
		}
		if (*pExecType == dec) {
			// Subete ga F ni Naru
			if (buf[i + 4] == 0xEB && buf[i + 5] == 0xF0 &&
				buf[i + 6] == 0x01 && buf[i + 7] == 0xFF) {
				_tcscpy(im->pszSeqExt, _T(".seq"));
			}
			// Leading Jockey Highbred
			else if (buf[i + 4] == 0x0a && buf[i + 5] == 0x01 &&
				buf[i + 6] == 0x00 && buf[i + 7] == 0x30) {
				_tcscpy(im->pszSeqExt, _T(".seq"));
			}
		}
	}
	// "SSsq" (for Sony)
	else if ((buf[i] == 0x53 && buf[i + 1] == 0x53 &&
		buf[i + 2] == 0x73 && buf[i + 3] == 0x71)) {
		_tcscpy(im->pszSeqExt, _T(".sssq"));
	}
	// "SS sq (for WildARMs2)
	else if (*pExecType == dec) {
		if (buf[i] == 0x53 && buf[i + 1] == 0x53 &&
			buf[i + 2] == 0x7F && buf[i + 3] == 0x73 &&
			buf[i + 4] == 0x71 && buf[i + 5] == 0x00 &&
			buf[i + 6] == 0x00) {
			_tcscpy(im->pszSeqExt, _T(".sssq"));
		}
	}
	// "KCET" (for Konami)
	else if (buf[i] == 0x4B && buf[i + 1] == 0x43 &&
		buf[i + 2] == 0x45 && buf[i + 3] == 0x54) {
		if (i < size - 0x24) {
			//"TSDB" && "KDT2" && "KDT1"
			if (buf[i + 0x08] == 0x54 && buf[i + 0x08 + 1] == 0x53 &&
				buf[i + 0x08 + 2] == 0x44 && buf[i + 0x08 + 3] == 0x42 &&
				buf[i + 0x14] == 0x4B && buf[i + 0x14 + 1] == 0x44 &&
				buf[i + 0x14 + 2] == 0x54 && buf[i + 0x14 + 3] == 0x32 &&
				buf[i + 0x24] == 0x4B && buf[i + 0x24 + 1] == 0x44 &&
				buf[i + 0x24 + 2] == 0x54 && buf[i + 0x24 + 3] == 0x31) {
				_tcscpy(im->pszSeqExt, _T(".kcet"));
			}
		}
	}
	// "SNTp" (for Final Fantasy 6, Chrono Trigger)
	else if (buf[i] == 0x53 && buf[i + 1] == 0x4E &&
		buf[i + 2] == 0x54 && buf[i + 3] == 0x70) {
		if (i > 11 && buf[i - 12] == 0x02 && buf[i - 11] == 0x00 &&
			buf[i - 10] == 0x00 && buf[i - 9] == 0x00) {
			_tcscpy(im->pszSeqExt, _T(".sntp"));
		}
	}
	// "SEQG" (for Gran Turismo 2)
	else if (buf[i] == 0x53 && buf[i + 1] == 0x45 &&
		buf[i + 2] == 0x51 && buf[i + 3] == 0x47 &&
		buf[i + 4] == 0x00 && buf[i + 5] == 0x00 &&
		buf[i + 6] == 0x00 && buf[i + 7] == 0x00) {
		_tcscpy(im->pszSeqExt, _T(".seqg"));
	}
	// "qQES" (for Dragon Quest 4, 7)
	else if (buf[i] == 0x71 && buf[i + 1] == 0x51 &&
		buf[i + 2] == 0x45 && buf[i + 3] == 0x53) {
		_tcscpy(im->pszSeqExt, _T(".seqq"));
	}
	// ".sqv" (for Brave Fencer Musashiden)
	else if (buf[i] == 0x2E && buf[i + 1] == 0x73 &&
		buf[i + 2] == 0x71 && buf[i + 3] == 0x76) {
		_tcscpy(im->pszSeqExt, _T(".sqv"));
	}
	// "SSEQ" (for Bastard!! - Utsuronaru Kamigami no Utsuwa)
	else if (buf[i] == 0x53 && buf[i + 1] == 0x53 &&
		buf[i + 2] == 0x45 && buf[i + 3] == 0x51 &&
		buf[i + 4] == 0x28 && buf[i + 5] == 0x00 &&
		buf[i + 6] == 0x00 && buf[i + 7] == 0x00) {
		_tcscpy(im->pszSeqExt, _T(".sseq"));
	}
	// "pBAV" (for General)
	else if (buf[i] == 0x70 && buf[i + 1] == 0x42 &&
		buf[i + 2] == 0x41 && buf[i + 3] == 0x56) {
		if (buf[i + 40] == 0xFF && buf[i + 41] == 0xFF) {
			_tcscpy(im->pszVhExt, _T(".vh"));
		}
		// "pBAV" (for Bastard!! - Utsuronaru Kamigami no Utsuwa)
		else if (buf[i + 34] == 0x00 && buf[i + 35] == 0x00) {
			_tcscpy(im->pszVhExt, _T(".vh2"));
		}
	}
	// "SShd" (for Sony)
	else if (buf[i] == 0x53 && buf[i + 1] == 0x53 &&
		buf[i + 2] == 0x68 && buf[i + 3] == 0x64) {
		if (buf[i - 4] == 0 && buf[i - 3] == 0 &&
			buf[i - 2] == 0 && buf[i - 1] == 0) {
			_tcscpy(im->pszVhExt, _T(".sshd"));
		}
	}
	// "INST" (for Gran Turismo 2)
	else if (buf[i] == 0x49 && buf[i + 1] == 0x4E &&
		buf[i + 2] == 0x53 && buf[i + 3] == 0x54 &&
		buf[i + 4] == 0 && buf[i + 5] == 0 &&
		buf[i + 6] == 0 && buf[i + 7] == 0) {
		_tcscpy(im->pszVhExt, _T(".inst"));
	}
}

BOOL GetDataOfs(
	LPBYTE buf,
	DWORD size,
	DWORD startIdx,
	DWORD maxSearchFileNum,
	BOOL bSkip,
	PINTERNAL_MUSIC im, 
	PEXEC_TYPE pExecType
	)
{
	DWORD allocSize = maxSearchFileNum * sizeof(DWORD);
	LPDWORD ofs = (LPDWORD)malloc(allocSize);
	if (!ofs) {
		OutputLastErrorNumAndString(_T(__FUNCTION__), __LINE__);
		return FALSE;
	}
	memset(ofs, 0x7F, allocSize);

	DWORD idx = 0;
	_TCHAR seqType[6] = { 0 };
	_TCHAR vhType[6] = { 0 };

	for (DWORD i = startIdx; i < size; i++) {
		if (idx >= maxSearchFileNum) {
			OutputErrorLog(
				_T("Reached to max. Seq and|or Vh exists more over\n"));
			break;
		}
		GetType(buf, size, i, im, pExecType);
		if (!_tcscmp(im->pszSeqExt, _T(".seq")) || !_tcscmp(im->pszSeqExt, _T(".sep")) ||
			!_tcscmp(im->pszSeqExt, _T(".sssq")) || !_tcscmp(im->pszSeqExt, _T(".kcet")) ||
			!_tcscmp(im->pszSeqExt, _T(".sntp")) ||	!_tcscmp(im->pszSeqExt, _T(".seqg")) ||
			!_tcscmp(im->pszSeqExt, _T(".seqq")) ||	!_tcscmp(im->pszSeqExt, _T(".sqv")) ||
			!_tcscmp(im->pszSeqExt, _T(".sseq"))) {
			BOOL bInvalid = FALSE;
			if (!_tcscmp(im->pszSeqExt, _T(".seq")) && bSkip) {
				// check that seq is valid
				DWORD maxSeqBodySize = MAX_SEQ_SIZE - sizeof(SEQ_HDR);
				for (DWORD seqbody = sizeof(SEQ_HDR); seqbody <= maxSeqBodySize; seqbody++) {
					if (size <= i + seqbody) {
						OutputErrorLog(_T("Invalid seq. (EOF doesn't exist) ofs: %u, %#x\n"), i, i);
						bInvalid = TRUE;
						break;
					}
					DWORD longByte = getlong(buf + i + seqbody);
					// search SEQp (for Houshin Engi)
					if ((longByte & 0xFFFFFFFF) == 0x53455170) {
						OutputErrorLog(_T("Invalid seq. (EOF doesn't exist) ofs: %u, %#x\n"), i, i);
						bInvalid = TRUE;
						i += seqbody - 1;
						break;
					}
					else if ((longByte & 0xFFFFFFFF) == 0x002FFF00) {
						break;
					}
				}
				if (bInvalid) {
					memset(im->pszSeqExt, 0, 5);
					continue;
				}
			}
			ofs[idx++] = i;
			if (!_tcscmp(im->pszSeqExt, _T(".seq")) && bSkip) {
				i += sizeof(SEQ_HDR) + 4 - 1;
			}
			else if (!_tcscmp(im->pszSeqExt, _T(".sep")) && bSkip) {
				i += sizeof(SEQ_HDR) + 8 - 1;
			}
			else if (!_tcscmp(im->pszSeqExt, _T(".sssq")) && bSkip) {
				i += 260;
			}
			if (seqType[0] == 0) {
				_tcscpy(seqType, im->pszSeqExt);
			}
			im->nSeqIdx++;
			memset(im->pszSeqExt, 0, 5);
			OutputString(_T("\rSearchFile %10u/%10u"), i + 1, size);
		}
		else if (!_tcscmp(im->pszVhExt, _T(".vh")) || !_tcscmp(im->pszVhExt, _T(".sshd")) ||
			!_tcscmp(im->pszVhExt, _T(".inst")) || !_tcscmp(im->pszVhExt, _T(".vh2"))) {
			DWORD vhSizeExceptVag = 0;
			if (!_tcscmp(im->pszVhExt, _T(".vh")) && bSkip) {
				// check vag size data (256 word data)
				WORD tmpTotalNumOfPrograms = getword(buf + i + 18);
				DWORD maxToneAttrSize = 
					(DWORD)(MIN_TONE_ATTR_SIZE * tmpTotalNumOfPrograms);
				vhSizeExceptVag = sizeof(VH) +
					MAX_PROG_ATTR_SIZE + maxToneAttrSize;
				DWORD vagPtrIdx = i + vhSizeExceptVag;
				if (size >= vagPtrIdx + VAG_SIZE_TBL_SIZE) {
					WORD vagSizeTbl[VAG_SIZE_TBL_NUM] = { 0 };
					memcpy(vagSizeTbl, buf + vagPtrIdx, VAG_SIZE_TBL_SIZE);
					// 1st word data is alway zero
					if (vagSizeTbl[0] != 0) {
						OutputErrorLog(
							_T("Invalid vh. ofs: %u, %#x, VagSizeTbl[0]: %04x\n"),
							i, i, vagSizeTbl[0]);
						memset(im->pszVhExt, 0, 5);
						continue;
					}
					WORD tmpTotalNumOfVag = getword(buf + i + 22);
					// word data over TotalNumOfVags is alway zero
					INT start = tmpTotalNumOfVag + 1;
					// Vag size table is 254 word data
					// 255th word data is almost zero, but DAT000.BIN of "Nemuru Mayu" isn't zero.
					INT end = VAG_SIZE_TBL_NUM - 1;
					BOOL bInvalid = FALSE;
					for (INT w = start; w < end; w++) {
						if (vagSizeTbl[w] != 0) {
							OutputErrorLog(
								_T("Invalid vh. ofs: %u, %#x, VagSizeTbl[%d]: %04x\n"),
								i, i, w, vagSizeTbl[w]);
							bInvalid = TRUE;
							break;
						}
					}
					if (bInvalid) {
						memset(im->pszVhExt, 0, 5);
						continue;
					}
				}
				else {
					OutputErrorLog(
						_T("Invalid vh. ofs: %u, %#x, File size is over\n"), i, i);
					memset(im->pszVhExt, 0, 5);
					continue;
				}
			}
			ofs[idx++] = i;
			if (!_tcscmp(im->pszVhExt, _T(".vh")) && bSkip) {
				i += vhSizeExceptVag + VAG_SIZE_TBL_SIZE - 1;
			}
			else if (!_tcscmp(im->pszVhExt, _T(".sshd")) && bSkip) {
				i += getlong(buf + i - 12) - 12;
			}
			if (vhType[0] == 0) {
				_tcscpy(vhType, im->pszVhExt);
			}
			im->nVhIdx++;
			memset(im->pszVhExt, 0, 5);
			OutputString(_T("\rSearchFile %10u/%10u"), i + 1, size);
		}
	}
	OutputString(_T("\n"));

	if (im->nSeqIdx > 0) {
		im->nSeqOfs = (LPDWORD)calloc(im->nSeqIdx, sizeof(DWORD));
		if (!im->nSeqOfs) {
			OutputLastErrorNumAndString(_T(__FUNCTION__), __LINE__);
			return FALSE;
		}
		im->nSeqSize = (LPDWORD)calloc(im->nSeqIdx, sizeof(DWORD));
		if (!im->nSeqSize) {
			OutputLastErrorNumAndString(_T(__FUNCTION__), __LINE__);
			return FALSE;
		}
		im->isSeq = (LPDWORD)calloc(im->nSeqIdx, sizeof(DWORD));
		if (!im->isSeq) {
			OutputLastErrorNumAndString(_T(__FUNCTION__), __LINE__);
			return FALSE;
		}
	}
	if (im->nVhIdx > 0) {
		im->nVhOfs = (LPDWORD)calloc(im->nVhIdx, sizeof(DWORD));
		if (!im->nVhOfs) {
			OutputLastErrorNumAndString(_T(__FUNCTION__), __LINE__);
			return FALSE;
		}
		im->nVhSize = (LPDWORD)calloc(im->nVhIdx, sizeof(DWORD));
		if (!im->nVhSize) {
			OutputLastErrorNumAndString(_T(__FUNCTION__), __LINE__);
			return FALSE;
		}
		im->isVh = (LPDWORD)calloc(im->nVhIdx, sizeof(DWORD));
		if (!im->isVh) {
			OutputLastErrorNumAndString(_T(__FUNCTION__), __LINE__);
			return FALSE;
		}
		im->isVb = (LPDWORD)calloc(im->nVhIdx, sizeof(DWORD));
		if (!im->isVb) {
			OutputLastErrorNumAndString(_T(__FUNCTION__), __LINE__);
			return FALSE;
		}
	}

	DWORD idxSeq = 0;
	DWORD idxVh = 0;
	idx = 0;
	qsort(ofs, maxSearchFileNum, sizeof(DWORD), CompareUnsignedLong);

	for (DWORD i = startIdx; i < size; i++, idx++) {
		if (idx < maxSearchFileNum && ofs[idx] != 0x7F7F7F7F) {
			i = ofs[idx];
		}
		else {
			break;
		}
		GetType(buf, size, i, im, pExecType);
		if (!_tcscmp(im->pszSeqExt, _T(".seq")) || !_tcscmp(im->pszSeqExt, _T(".sep")) ||
			!_tcscmp(im->pszSeqExt, _T(".sssq")) || !_tcscmp(im->pszSeqExt, _T(".kcet")) ||
			!_tcscmp(im->pszSeqExt, _T(".sntp")) ||	!_tcscmp(im->pszSeqExt, _T(".seqg")) ||
			!_tcscmp(im->pszSeqExt, _T(".seqq")) ||	!_tcscmp(im->pszSeqExt, _T(".sqv")) ||
			!_tcscmp(im->pszSeqExt, _T(".sseq"))) {
			im->nSeqOfs[idxSeq++] = i;
			memset(im->pszSeqExt, 0, 5);
		}
		else if (!_tcscmp(im->pszVhExt, _T(".vh")) || !_tcscmp(im->pszVhExt, _T(".sshd")) ||
			!_tcscmp(im->pszVhExt, _T(".inst")) || !_tcscmp(im->pszVhExt, _T(".vh2"))) {
			im->nVhOfs[idxVh++] = i;
			memset(im->pszVhExt, 0, 5);
		}
	}
	_tcscpy(im->pszSeqExt, seqType);
	_tcscpy(im->pszVhExt, vhType);
	FreeAndNull(ofs);
	return TRUE;
}
