// bin2seq.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//
/*
 * This code is released under the Microsoft Public License (MS-PL). See License.txt, below.
 */
#include "stdafx.h"
#include "struct.h"
#include "enum.h"
#include "compare.h"
#include "create.h"
#include "get.h"
#include "uncompress.h"

#ifdef _DEBUG
_TCHAR logBuffer[2048];
#else
// These global variable is set at _tmain().
FILE* g_ErrorLog = NULL;
#endif

// These static variable is set at checkArg().
static DWORD s_dwOfs = 0;
static DWORD s_dwFileNum = 0;
static BOOL s_bCompare = FALSE;
static BOOL s_bNo16 = TRUE;
static SEARCH_TYPE s_searchType = vab;

int execDecode(_TCHAR* argv[], LPBYTE data, DWORD dataSize, PINTERNAL_MUSIC im, PEXEC_TYPE pExecType)
{
	BOOL ret = GetDataOfs(data, dataSize, 0, 2048, FALSE, im, pExecType);
	if (!ret) {
		return FALSE;
	}
	else if (im->nSeqIdx == 0 && im->nVhIdx == 0) {
		OutputErrorString(_T("Not found seq and|or vh: %s\n"), argv[2]);
		return FALSE;
	}
	if (
		!_tcscmp(argv[3], _T("00348")) // Leading Jockey Hybrid
		) {
		ret = uncompressLzss(data, argv[2], 5, 1, Zero, 0x0f,
			Uncompress, LZSS_TYPE1, _T(".seq"), 1, im);
	}
	else if (
		!_tcscmp(argv[3], _T("10028")) || // WILD ARMS
		!_tcscmp(argv[3], _T("10037")) // Crime Crackers 2
		) {
		ret = uncompressLzss(data, argv[2], 5, 1, Zero, 0x0f,
			Uncompress, LZSS_TYPE1, _T(".seq"), 0, im);
	}
#if 0
	else if (
		!_tcscmp(argv[3], _T("10124")) // LOVE & DESTROY
		) {
		ret = uncompressLzss(data, argv[2], 6, 1, Zero, 0x0f,
			Uncompress, LZSS_TYPE1, _T(".seq"), 0, im);
	}
	else if (
		!_tcscmp(argv[3], _T("10053")) // Yarudora Series Vol.1 - Double Cast
		) {
		ret = uncompressLzss(data, argv[2], 5, 1, One, 0x0f,
			Uncompress, LZSS_TYPE3, _T(".seq"), 0, im);
	}
#endif
	else if (
		!_tcscmp(argv[3], _T("00844")) // Eberouge
		) {
		ret = uncompressLzss(data, argv[2], 9, 1, One, 0x0f,
			Uncompress, LZSS_TYPE1, _T(".seq"), 0, im);
	}
	else if (
		!_tcscmp(argv[3], _T("10089")) // WILD ARMS 2nd IGNITION
		) {
		ret = uncompressLzss(data, argv[2], 12, 8, Zero, 0x0f,
			Uncompress, LZSS_TYPE1, _T(".sssq"), 0, im);
	}
	else if (
		!_tcscmp(argv[3], _T("02804")) || // Meitantei Conan - 3nin no Meisuiri
		!_tcscmp(argv[3], _T("03425")) // Meitantei Conan - Saikou no Partner
		) {
		ret = uncompressLzss(data, argv[2], 5, 1, Zero, 0x0f,
			Compress, LZSS_TYPE1, _T(".seq"), 0, im);
	}
#if 0
	else if (
		!_tcscmp(argv[3], _T("03413")) // Subete ga F ni Naru
		) {
		ret = uncompressLzss(data, argv[2], 17, 3, Zero, 0x0f,
			Compress, LZSS_TYPE1, _T(".seq"), 0, im);
	}
	else if (
		!_tcscmp(argv[3], _T("10074")) // Ore no Shikabane wo Koete Yuke
		) {
		ret = uncompressLzss(data, argv[2], 14, 10, Zero, 0xc0,
			Uncompress, LZSS_TYPE2, _T(".sssq"), 0, im);
	}
#endif
	else if (
		!_tcscmp(argv[3], _T("10105")) // Brightis
		) {
		ret = uncompressBrightis(data, argv[2], im);
	}
	else if (
		!_tcscmp(argv[3], _T("00856")) || // Marie no Atelier
		!_tcscmp(argv[3], _T("01418")) || // Marie no Atelier Plus
		!_tcscmp(argv[3], _T("01751")) || // Elie no Atelier
		!_tcscmp(argv[3], _T("91180")) // Elie no Atelier (Best)
		) {
		ret = uncompressGust(data, dataSize, argv[2], im);
	}
	else {
		OutputErrorString(_T("Unsupported number: %s\n"), argv[3]);
		ret = FALSE;
	}
	return ret;
}

int execRip(_TCHAR* argv[], LPBYTE data, DWORD dataSize, PINTERNAL_MUSIC im, PEXEC_TYPE pExecType)
{
	BOOL ret = GetDataOfs(data, dataSize, s_dwOfs, s_dwFileNum, TRUE, im, pExecType);
	if (!ret) {
		return FALSE;
	}
	else if (im->nSeqIdx == 0 && im->nVhIdx == 0) {
		OutputErrorString(_T("Not found seq and|or vh: %s\n"), argv[2]);
		return FALSE;
	}
	FILE* csvFp = NULL;
	try {
		csvFp = CreateOrOpenFile(argv[2],
			NULL, NULL, NULL, _T(".csv"), _T(WFLAG), 0, 0);
		if (!csvFp) {
			OutputLastErrorNumAndString(_T(__FUNCTION__), __LINE__);
			throw FALSE;
		}
		_ftprintf(csvFp,
			_T("Ext,No,Ofs(dec),Ofs(hex),Size(dec),Size(hex),SeqNumInSep\n"));

		if (im->nSeqIdx > 0 &&
			(!_tcscmp(im->pszSeqExt, _T(".seq")) || !_tcscmp(im->pszSeqExt, _T(".sep")))) {
			ret = CreateSeq(argv[2], data, dataSize, csvFp, im);
		}
		else if (im->nSeqIdx > 0 && !_tcscmp(im->pszSeqExt, _T(".kcet"))) {
			ret = CreateKcet(argv[2], data, dataSize, csvFp, im);
		}
		else if (im->nSeqIdx > 0 && !_tcscmp(im->pszSeqExt, _T(".sssq"))) {
			ret = CreateSSsq(argv[2], data, dataSize, csvFp, im);
		}
		else if (im->nSeqIdx > 0 && !_tcscmp(im->pszSeqExt, _T(".sntp"))) {
			ret = CreateSntp(argv[2], data, dataSize, csvFp, im);
		}
		else if (im->nSeqIdx > 0 && !_tcscmp(im->pszSeqExt, _T(".seqg"))) {
			ret = CreateSeqg(argv[2], data, csvFp, im);
		}
		else if (im->nSeqIdx > 0 && !_tcscmp(im->pszSeqExt, _T(".seqq"))) {
			ret = CreateSeqq(argv[2], data, csvFp, im);
		}
		else if (im->nSeqIdx > 0 && !_tcscmp(im->pszSeqExt, _T(".sqv"))) {
			ret = CreateSqv(argv[2], data, csvFp, im);
		}
		else if (im->nSeqIdx > 0 && !_tcscmp(im->pszSeqExt, _T(".sseq"))) {
			ret = CreateSseq(argv[2], data, csvFp, im);
		}

		if (im->nVhIdx > 0 &&
			(!_tcscmp(im->pszVhExt, _T(".vh")) || !_tcscmp(im->pszVhExt, _T(".vh2")))) {
			PVH vh = (PVH)calloc(im->nVhIdx, sizeof(VH));
			if (!vh) {
				OutputLastErrorNumAndString(_T(__FUNCTION__), __LINE__);
				throw FALSE;
			}
			LPWORD vagSizeTbl = (LPWORD)calloc(im->nVhIdx * VAG_SIZE_TBL_NUM, sizeof(LPWORD));
			if (!vagSizeTbl) {
				OutputLastErrorNumAndString(_T(__FUNCTION__), __LINE__);
				throw FALSE;
			}

			if (!_tcscmp(im->pszVhExt, _T(".vh"))) {
				ret = CreateVh(argv[2], data, dataSize, csvFp, im, vh, vagSizeTbl);
			}
			else if (!_tcscmp(im->pszVhExt, _T(".vh2"))) {
				ret = CreateVhForBastard(argv[2], data, csvFp, im, vh, vagSizeTbl);
			}
			if (ret) {
				ret = CreateVbForVh(argv[2], data, dataSize,
					s_bNo16, s_searchType, csvFp, im, vh, vagSizeTbl);
			}
			FreeAndNull(vh);
			FreeAndNull(vagSizeTbl);
		}
		else if (im->nVhIdx > 0 && !_tcscmp(im->pszVhExt, _T(".sshd"))) {
			LPDWORD vbSize = (LPDWORD)calloc(im->nVhIdx, sizeof(DWORD));
			if (!vbSize) {
				OutputLastErrorNumAndString(_T(__FUNCTION__), __LINE__);
				throw FALSE;
			}

			ret = CreateSShd(argv[2], data, dataSize, vbSize, csvFp, im);
			if (ret) {
				ret = CreateVbForSShd(argv[2], data, dataSize,
					vbSize, s_bNo16, csvFp, im);
			}
			FreeAndNull(vbSize);
		}
		else if (im->nVhIdx > 0 && !_tcscmp(im->pszVhExt, _T(".inst"))) {
			ret = CreateInst(argv[2], data, csvFp, im);
			if (ret) {
				ret = CreateVbForInst(argv[2], data, csvFp, im);
			}
		}
	}
	catch (BOOL bErr) {
		ret = bErr;
	}
	FcloseAndNull(csvFp);
	return TRUE;
}

int exec(_TCHAR* argv[], PEXEC_TYPE pExecType)
{
	FILE* fp = _tfopen(argv[2], _T("rb"));
	if (!fp) {
		OutputLastErrorNumAndString(_T(__FUNCTION__), __LINE__);
		return FALSE;
	}
	DWORD dataSize = getFilesize(fp);
	LPBYTE data = (LPBYTE)calloc(dataSize, sizeof(BYTE));
	if (!data) {
		OutputLastErrorNumAndString(_T(__FUNCTION__), __LINE__);
		FcloseAndNull(fp);
		return FALSE;
	}
	fread(data, sizeof(CHAR), dataSize, fp);
	FcloseAndNull(fp);

	int ret = TRUE;
	INTERNAL_MUSIC im = { 0 };
	if (*pExecType == rip) {
		ret = execRip(argv, data, dataSize, &im, pExecType);
	}
	else if (*pExecType == dec) {
		ret = execDecode(argv, data, dataSize, &im, pExecType);
	}

	if (ret && s_bCompare) {
		_TCHAR pszSrcPathWithoutFileName[_MAX_PATH] = { 0 };
		_TCHAR pszDupsFilePath[_MAX_PATH] = { 0 };
		FILE* logFp = NULL;
		try {
			logFp = CreateOrOpenFile(argv[2], NULL,
				pszDupsFilePath, NULL, _T("_DupFiles.txt"), _T(WFLAG), 0, 0);
			if (!logFp) {
				OutputLastErrorNumAndString(_T(__FUNCTION__), __LINE__);
				throw FALSE;
			}
			_tcsncpy(pszSrcPathWithoutFileName, argv[2], _tcslen(argv[2]));
			_TCHAR newDir[] = {
				_T("_DupFiles")
			};
			if (_tcslen(argv[2]) + _tcslen(newDir) >= _MAX_PATH) {
				OutputErrorString(_T("Path too long\n"));
				throw FALSE;
			}
			_tcsncat(pszSrcPathWithoutFileName, newDir, _tcslen(newDir));
			if (!PathIsDirectory(pszSrcPathWithoutFileName)) {
				if (!CreateDirectory(pszSrcPathWithoutFileName, NULL)) {
					OutputLastErrorNumAndString(_T(__FUNCTION__), __LINE__);
					throw FALSE;
				}
			}
			CompareFile(argv[2],
				pszSrcPathWithoutFileName, im.nSeqIdx, im.pszSeqExt, im.isSeq, logFp);
			CompareFile(argv[2],
				pszSrcPathWithoutFileName, im.nVhIdx, im.pszVhExt, im.isVh, logFp);

			DWORD filesize = getFilesize(logFp);
			FcloseAndNull(logFp);
			if (filesize == 0) {
				if (_tremove(pszDupsFilePath) != 0) {
					OutputLastErrorNumAndString(_T(__FUNCTION__), __LINE__);
				}
				if (_trmdir(pszSrcPathWithoutFileName) != 0) {
					OutputLastErrorNumAndString(_T(__FUNCTION__), __LINE__);
				}
			}
		}
		catch (BOOL bErr) {
			ret = bErr;
		}
		FcloseAndNull(logFp);
	}

	FreeAndNull(data);
	FreeAndNull(im.isSeq);
	FreeAndNull(im.isVb);
	FreeAndNull(im.isVh);
	FreeAndNull(im.nSeqOfs);
	FreeAndNull(im.nSeqSize);
	FreeAndNull(im.nVhOfs);
	FreeAndNull(im.nVhSize);
	return ret;
}

void printUsage(void)
{
	OutputString(
		_T("Usage\n")
		_T("\trip <file> <startOfs(hex)> <maxSearchFileNum(dec)> [/c] [/p1|/p2] [/n]\n")
		_T("\t\tRip sequence, rhythm data automatically\n")
		_T("\t\tstartOfs: search with ofs of file\n")
		_T("\t\tmaxSearchFileNum: sum of seq&vh or sssq&sshd\n")
		_T("\t\t/c\tif you compare ripped files, use this\n")
		_T("\t\t/p1\tvb searching pattern 1. (vh, vb, vh, vb...)\n")
		_T("\t\t/p2\tvb searching pattern 2. (vb, vb, vb, vb...)\n")
		_T("\t\t/n\tif first 16 byte of vb isn't zero, use this\n")
		_T("\tdec <file> <serial number(5 digit)> [/c]\n")
		_T("\t\tUncompress seq for lzss\n")
		_T("\t\t\tCrime Crackers 2 (SCPS-10037)\n")
		_T("\t\t\tLeading Jockey Hybrid (SLPS-00348)\n")
		_T("\t\t\tWILD ARMS (SCPS-10028)\n")
		_T("\t\t\tEberouge (SLPS-00844)\n")
		_T("\t\t\tMeitantei Conan - 3nin no Meisuiri (SLPS-02804)\n")
		_T("\t\t\tMeitantei Conan - Saikou no Partner (SLPS-03425)\n")
//		_T("\t\t\tSubete ga F ni Naru (SLPS-03413)\n")
//		_T("\t\t\tLOVE & DESTROY (SCPS-10124)\n")
//		_T("\t\t\tYarudora Series Vol.1 - Double Cast (SCPS-10053)\n")
		_T("\t\tUncompress sssq for lzss\n")
		_T("\t\t\tWILD ARMS 2nd IGNITION (SCPS-10089)\n")
//		_T("\t\t\tOre no Shikabane wo Koete Yuke (SLPS-10074)\n")
		_T("\t\tUncompress seq for other\n")
		_T("\t\t\tBrightis (SCPS-10105)\n")
		_T("\t\t\tMarie no Atelier (SLPS-00856, SLPS-01418)\n")
		_T("\t\t\tElie no Atelier (SLPS-01751, SLPS-91180)\n"));
}

int checkArg(int argc, _TCHAR* argv[], PEXEC_TYPE pExecType)
{
	if (argc >= 5 && !_tcscmp(argv[1], _T("rip"))) {
		_TCHAR* endptr = NULL;
		s_dwOfs = _tcstoul(argv[3], &endptr, 16);
		if (*endptr) {
			OutputErrorString(_T("Invalid arg: %s\n"), endptr);
			return FALSE;
		}
		s_dwFileNum = _tcstoul(argv[4], &endptr, 10);
		if (*endptr) {
			OutputErrorString(_T("Invalid arg: %s\n"), endptr);
			return FALSE;
		}
		for (INT i = 6; i <= argc; i++) {
			if (!_tcscmp(argv[i - 1], _T("/c"))) {
				s_bCompare = TRUE;
			}
			else if (!_tcscmp(argv[i - 1], _T("/p1"))) {
				s_searchType = vab;
			}
			else if (!_tcscmp(argv[i - 1], _T("/p2"))) {
				s_searchType = vb;
			}
			else if (!_tcscmp(argv[i - 1], _T("/n"))) {
				s_bNo16 = FALSE;
			}
		}
		*pExecType = rip;
	}
	else if (argc >= 4 && !_tcscmp(argv[1], _T("dec"))) {
		for (INT i = 5; i <= argc; i++) {
			if (!_tcscmp(argv[i - 1], _T("/c"))) {
				s_bCompare = TRUE;
			}
		}
		*pExecType = dec;
	}
	else {
		return FALSE;
	}
	return TRUE;
}

int printSeveralInfo()
{
#if 0
	OSVERSIONINFO OSver;
	OSver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	if (!GetVersionEx(&OSver)) {
		OutputLastErrorNumAndString(_T(__FUNCTION__), __LINE__);
		return FALSE;
	}
	OutputString(
		_T("OS\n")
		_T("\tMajorVersion: %u, MinorVersion: %u, BuildNumber: %u\n")
		_T("\tPlatformId: %u, CSDVersion: %s\n"),
		OSver.dwMajorVersion, OSver.dwMinorVersion, OSver.dwBuildNumber,
		OSver.dwPlatformId, OSver.szCSDVersion);
#endif
	OutputString(_T("AppVersion\n"));
#ifdef _WIN64
	OutputString(_T("\tx64, "));
#else
	OutputString(_T("\tx86, "));
#endif
#ifdef UNICODE
	OutputString(_T("UnicodeBuild, "));
#else
	OutputString(_T("AnsiBuild, "));
#endif
	OutputString(
		_T("%s %s\n"), _T(__DATE__), _T(__TIME__));
	return TRUE;
}

int _tmain(int argc, _TCHAR* argv[])
{
#ifdef _DEBUG
	_CrtSetDbgFlag(_CRTDBG_CHECK_ALWAYS_DF | _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
	printSeveralInfo();

	EXEC_TYPE execType;
	if (!checkArg(argc, argv, &execType)) {
		printUsage();
		return EXIT_FAILURE;
	}
	time_t now;
	struct tm  *ts;
	_TCHAR szBuf[128] = { 0 };

	now = time(NULL);
	ts = localtime(&now);
	_tcsftime(szBuf, sizeof(szBuf) / sizeof(szBuf[0]), _T("%a %Y-%m-%d %H:%M:%S"), ts);
	OutputString(_T("Start: %s\n"), szBuf);

#ifndef _DEBUG
	_TCHAR pszOutFilePath[_MAX_PATH] = { 0 };
	g_ErrorLog = CreateOrOpenFile(argv[2], NULL,
		pszOutFilePath, NULL, _T("_Error.txt"), _T(WFLAG), 0, 0);
	if (!g_ErrorLog) {
		OutputLastErrorNumAndString(_T(__FUNCTION__), __LINE__);
		return EXIT_FAILURE;
	}
#endif
	int nRet = exec(argv, &execType);
#ifndef _DEBUG
	DWORD filesize = getFilesize(g_ErrorLog);
	FcloseAndNull(g_ErrorLog);
	if (filesize == 0) {
		if (_tremove(pszOutFilePath) != 0) {
			OutputLastErrorNumAndString(_T(__FUNCTION__), __LINE__);
		}
	}
#endif
	now = time(NULL);
	ts = localtime(&now);
	_tcsftime(szBuf, sizeof(szBuf) / sizeof(szBuf[0]), _T("%a %Y-%m-%d %H:%M:%S"), ts);
	OutputString(_T("End: %s\n"), szBuf);

	nRet = nRet == TRUE ? EXIT_SUCCESS : EXIT_FAILURE;
	return nRet;
}
