/**
 * Is a disc image supported by this class?
 * @param pHeader Disc image header.
 * @param szHeader Size of header.
 * @return Class-specific disc format ID (>= 0) if supported; -1 if not.
 */
int DiscReader::isDiscSupported_static(const uint8_t *pHeader, size_t szHeader)
{
	// DiscReader supports everything.
	RP_UNUSED(pHeader);
	RP_UNUSED(szHeader);
	return 0;
}
/**
 * Write data to the file.
 * (NOTE: Not valid for RpMemFile; this will always return 0.)
 * @param ptr Input data buffer.
 * @param size Amount of data to read, in bytes.
 * @return Number of bytes written.
 */
size_t RpMemFile::write(const void *ptr, size_t size)
{
	// Not a valid operation for RpMemFile.
	RP_UNUSED(ptr);
	RP_UNUSED(size);
	m_lastError = EBADF;
	return 0;
}
/**
 * Truncate the file.
 * @param size New size. (default is 0)
 * @return 0 on success; -1 on error.
 */
int RpMemFile::truncate(int64_t size)
{
	// Not supported.
	// TODO: Writable RpMemFile?
	RP_UNUSED(size);
	m_lastError = ENOTSUP;
	return -1;
}
int CALLBACK WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow)
{
	HANDLE hSingleInstanceMutex;

	// Filename and path buffers.
	TCHAR *exe_path = NULL;	// MAX_PATH
	#define EXE_PATH_LEN (MAX_PATH)
	TCHAR *dll_filename = NULL;	// MAX_PATH+32
	#define DLL_FILENAME_LEN (MAX_PATH+32)

	TCHAR *last_backslash;
	HKEY hkeyCLSID = NULL;	// HKEY_CLASSES_ROOT\\CLSID
	DWORD exe_path_len;
	LONG lResult;
	unsigned int i;

	static const TCHAR prg_title[] = _T("ROM Properties Page Configuration");

	RP_UNUSED(hPrevInstance);

	// Set Win32 security options.
	secoptions_init();

	// Check if another instance of rp-config is already running.
	// References:
	// - https://stackoverflow.com/questions/4191465/how-to-run-only-one-instance-of-application
	// - https://stackoverflow.com/a/33531179
	// TODO: Localized window title?
	hSingleInstanceMutex = CreateMutex(nullptr, TRUE, _T("Local\\com.gerbilsoft.rom-properties.rp-config"));
	if (!hSingleInstanceMutex || GetLastError() == ERROR_ALREADY_EXISTS) {
		// Mutex already exists.
		// Set focus to the existing instance.
		// TODO: Localized window titles?
		HWND hWnd = FindWindow(_T("#32770"), prg_title);
		if (hWnd) {
			SetForegroundWindow(hWnd);
		}
		return EXIT_SUCCESS;
	}

	// Set the C locale.
	// TODO: C++ locale?
	setlocale(LC_ALL, "");

#define FAIL_MESSAGE(msg) do { \
		MessageBox(NULL, (msg), prg_title, MB_ICONSTOP); \
		goto fail; \
	} while (0)

	// Get the executable path.
	// TODO: Support longer than MAX_PATH?
	exe_path = malloc(MAX_PATH*sizeof(TCHAR));
	if (!exe_path)
		FAIL_MESSAGE(_T("Failed to allocate memory for the EXE path."));
	exe_path_len = GetModuleFileName(hInstance, exe_path, EXE_PATH_LEN);
	if (exe_path_len == 0 || exe_path_len >= EXE_PATH_LEN)
		FAIL_MESSAGE(_T("Failed to get the EXE path."));

	// Find the last backslash.
	last_backslash = _tcsrchr(exe_path, L'\\');
	if (last_backslash) {
		// NULL out everything after the backslash.
		last_backslash[1] = 0;
		exe_path_len = (DWORD)(last_backslash - exe_path + 1);
	} else {
		// Invalid path...
		// FIXME: Handle this.
		FAIL_MESSAGE(_T("EXE path is invalid."));
	}

	// Initialize dll_filename with exe_path.
	dll_filename = malloc(DLL_FILENAME_LEN*sizeof(TCHAR));
	if (!dll_filename)
		FAIL_MESSAGE(_T("Failed to allocate memory for the DLL filename."));
	memcpy(dll_filename, exe_path, exe_path_len*sizeof(TCHAR));

	// First, check for rom-properties.dll in rp-config.exe's directory.
	_tcscpy(&dll_filename[exe_path_len], _T("rom-properties.dll"));
	TRY_LOAD_DLL(dll_filename);

	// Check the architecture-specific subdirectory.
	_tcscpy(&dll_filename[exe_path_len], rp_subdir);
	// NOTE: -1 because _countof() includes the NULL terminator.
	_tcscpy(&dll_filename[exe_path_len + _countof(rp_subdir) - 1], _T("rom-properties.dll"));
	TRY_LOAD_DLL(dll_filename);

	// Check the CLSIDs.
	lResult = RegOpenKeyEx(HKEY_CLASSES_ROOT, _T("CLSID"), 0, KEY_ENUMERATE_SUB_KEYS, &hkeyCLSID);
	if (lResult != ERROR_SUCCESS)
		FAIL_MESSAGE(_T("Failed to open HKEY_CLASSES_ROOT\\CLSID."));

	// Need to open "HKCR\\CLSID\\{CLSID}\\InprocServer32".
	for (i = 0; i < _countof(CLSIDs); i++) {
		HKEY hkeyClass, hkeyInprocServer32;
		DWORD cbData, dwType;

		lResult = RegOpenKeyEx(hkeyCLSID, CLSIDs[i], 0, KEY_ENUMERATE_SUB_KEYS, &hkeyClass);
		if (lResult != ERROR_SUCCESS)
			continue;

		lResult = RegOpenKeyEx(hkeyClass, _T("InprocServer32"), 0, KEY_READ, &hkeyInprocServer32);
		if (lResult != ERROR_SUCCESS) {
			RegCloseKey(hkeyClass);
			continue;
		}

		// Read the default value to get the DLL filename.
		cbData = DLL_FILENAME_LEN*sizeof(TCHAR);
		lResult = RegQueryValueEx(
			hkeyInprocServer32,	// hKey
			NULL,			// lpValueName
			NULL,			// lpReserved
			&dwType,		// lpType
			(LPBYTE)dll_filename,	// lpData
			&cbData);		// lpcbData
		RegCloseKey(hkeyInprocServer32);
		RegCloseKey(hkeyClass);

		if (lResult != ERROR_SUCCESS || (dwType != REG_SZ && dwType != REG_EXPAND_SZ))
			continue;

		// Verify the NULL terminator.
		if ((cbData % sizeof(TCHAR) != 0) || dll_filename[(cbData/sizeof(TCHAR))-1] != 0) {
			// Either this isn't a multiple of 2 bytes,
			// or there's no NULL terminator.
			continue;
		}

		if (dll_filename[0] != 0 && dwType == REG_EXPAND_SZ) {
			// Expand the string.
			// cchExpand includes the NULL terminator.
			TCHAR *wbuf;
			DWORD cchExpand = ExpandEnvironmentStrings(dll_filename, nullptr, 0);
			if (cchExpand == 0) {
				// Error expanding the string.
				continue;
			}
			wbuf = malloc(cchExpand*sizeof(TCHAR));
			cchExpand = ExpandEnvironmentStrings(dll_filename, wbuf, cchExpand);
			if (cchExpand == 0) {
				// Error expanding the string.
				free(wbuf);
			} else {
				// String has been expanded.
				free(dll_filename);
				dll_filename = wbuf;
			}
		}

		// Attempt to load this DLL.
		TRY_LOAD_DLL(dll_filename);
	}

	// All options have failed...
	MessageBox(NULL, _T("Could not find rom-properties.dll.\n\n")
		_T("Please ensure the DLL is present in the same\ndirectory as rp-config.exe."),
		prg_title, MB_ICONWARNING);

fail:
	free(exe_path);
	free(dll_filename);
	if (hkeyCLSID) {
		RegCloseKey(hkeyCLSID);
	}
	return EXIT_FAILURE;
}