micetools/src/micetools/launcher/main.c

454 lines
17 KiB
C

#include <Windows.h>
#include <stdio.h>
#include "../lib/mice/ipc.h"
#include "../lib/mice/mice.h"
#include "../lib/util/pid.h"
#include "locate.h"
#include "spawn.h"
bool debug_wait = false;
int boot_delay = 0;
char exe_name[MAX_PATH + 1] = "";
BYTE _MiceLauncherNext = 2;
HANDLE _MiceLauncherNextEvent = INVALID_HANDLE_VALUE;
size_t nCommandLine = 0;
char* commandLine = NULL;
static void print_help(char* exe) {
log_info(plfBoot, "Usage: %s [-h] [-t] [-b executable.exe] [-d]", exe);
log_info(plfBoot, " -h: Print this help message and exit");
log_info(plfBoot, " -b: Specify the game binary to use");
log_info(plfBoot, " --mice-d: Wait for a debugger to attach when starting");
log_info(plfBoot, " --mice.<config>=<setting>: Set launcher options");
exit(0);
}
static bool parse_cmdline(int argc, char* argv[]) {
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--mice-h") == 0) {
print_help(argv[0]);
} else if (strcmp(argv[i], "-b") == 0) {
if (i + 1 == argc) print_help(argv[0]);
char* val = argv[++i];
memcpy(exe_name, val, strlen(val) + 1);
} else if (strcmp(argv[i], "--mice-d") == 0) {
debug_wait = true;
} else if (strncmp(argv[i], "--mice.", strlen("--mice.")) == 0) {
char key[256];
char value[256];
key[0] = value[0] = '\0';
char* arg = argv[i] + strlen("--mice.");
int j = 0;
for (; j < _countof(key) - 2; j++) {
if (arg[j] == '\0') break;
if (arg[j] == '=') {
strcpy_s(value, _countof(value), arg + j + 1);
break;
}
key[j] = arg[j];
}
key[j] = '\0';
bool handled = false;
for (j = 0; j < _countof(mxspawns); j++) {
if (strcmp(key, mxspawns[j].name) != 0) continue;
handled = true;
if (strcmp(value, "no") == 0)
mxspawns[j].mode = SPAWN_NONE;
else if (strcmp(value, "mx") == 0)
mxspawns[j].mode |= SPAWN_REAL;
else if (strcmp(value, "md") == 0)
mxspawns[j].mode |= SPAWN_DUMMY;
else {
log_error(plfBoot, "Unknown spawn mode: %s=%s", key, value);
return false;
}
}
if (!handled) {
log_error(plfBoot, "Unknown launcher config: %s=%s", key, value);
return false;
}
} else {
nCommandLine += 1 + strlen(argv[i]) + 1;
if (commandLine == NULL) {
commandLine = malloc(nCommandLine);
commandLine[0] = '\0';
} else {
commandLine = realloc(commandLine, nCommandLine);
}
strcat_s(commandLine, nCommandLine, " ");
strcat_s(commandLine, nCommandLine, argv[i]);
commandLine[nCommandLine - 1] = '\0';
}
}
return true;
}
static DWORD WINAPI MiceIpcMessageWatcher(PVOID _) {
// Enable colour support in conhost
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD dwMode = 0;
if (GetConsoleMode(hConsole, &dwMode))
SetConsoleMode(hConsole, dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
HANDLE hLogFile = INVALID_HANDLE_VALUE;
if (MiceConfig.mice.log_to_file) {
hLogFile = CreateFileA(MiceConfig.mice.log_file, GENERIC_WRITE, FILE_SHARE_WRITE, NULL,
CREATE_ALWAYS, 0, NULL);
if (hLogFile == INVALID_HANDLE_VALUE) {
fprintf(stderr, "Failed to initialise logging: open %s failed: %d",
MiceConfig.mice.log_file, GetLastError());
ExitProcess(2);
}
}
while (1) {
MICE_IPC_MESSAGE message;
MiceIpcPopMessage(&message, TRUE);
switch (message.m_Type) {
case MICE_IPC_TYPE_LOG:
// printf("%s", message.m_Message);
DWORD nWrote;
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), message.m_Message, message.m_Size,
&nWrote, NULL);
if (hLogFile != INVALID_HANDLE_VALUE) {
// The only VT100 sequences being used are colours, so this lazy approach works
BOOL inVt100 = FALSE;
for (unsigned int i = 0; i < message.m_Size; i++) {
if (message.m_Message[i] == '\033') {
inVt100 = TRUE;
continue;
}
if (inVt100 && message.m_Message[i] == 'm') {
inVt100 = FALSE;
continue;
}
if (!inVt100)
WriteFile(hLogFile, &(message.m_Message[i]), 1, &nWrote, NULL);
}
// FlushFileBuffers(hLogFile);
}
// FlushFileBuffers(GetStdHandle(STD_OUTPUT_HANDLE));
break;
}
}
}
/**
* Because logging is via IPC, we need to give it a chance to flush messages before
* terminating. While we could engineer something overly fancy, the only situation
* this ever occurs is in the main() function just below.
*
* I'll let the code speak for itself.
*/
static int terminate(int err, char* szMessage) {
printf("Mice termination occuring... %d\n", err);
log_info(plfBoot, "Terminating");
Sleep(1000);
extern unsigned char g_szFault[255 + 1];
extern BYTE g_nFault;
if (g_nFault) {
MessageBeep(MB_ICONERROR);
MessageBoxA(NULL, (LPCSTR)g_szFault, "System Fault",
MB_OK | MB_SETFOREGROUND | MB_SYSTEMMODAL);
} else if (szMessage) {
MessageBeep(MB_ICONERROR);
MessageBoxA(NULL, szMessage, "Boot Failed", MB_OK | MB_SETFOREGROUND | MB_SYSTEMMODAL);
}
return err;
}
HANDLE hJob = INVALID_HANDLE_VALUE;
BOOL WINAPI MiceHandlerRoutine(DWORD CtrlType) {
if (hJob != INVALID_HANDLE_VALUE) CloseHandle(hJob);
ExitProcess(terminate(0, NULL));
}
static BOOL StartGame(LPPROCESS_INFORMATION lpPi, LPCSTR args) {
// Clear out the next flag. If the game closes without setting this, we don't want to restart
// the game.
_MiceLauncherNext = 0;
hJob = CreateJobObject(NULL, NULL);
JOBOBJECT_EXTENDED_LIMIT_INFORMATION info;
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
SetInformationJobObject(hJob, JobObjectExtendedLimitInformation, &info, sizeof info);
size_t nFullCommandLine = strlen(exe_name) + 1;
if (args != NULL && strlen(args) > 0) nFullCommandLine += 1 + strlen(args);
if (nCommandLine > 0) nFullCommandLine += 1 + nCommandLine;
char* fullCommandLine = malloc(nFullCommandLine);
if (args != NULL && nCommandLine > 0) {
sprintf_s(fullCommandLine, nFullCommandLine, "%s %s %s", exe_name, args, commandLine);
} else if (args == NULL && nCommandLine > 0) {
sprintf_s(fullCommandLine, nFullCommandLine, "%s %s", exe_name, commandLine);
} else if (args != NULL) {
sprintf_s(fullCommandLine, nFullCommandLine, "%s %s", exe_name, args);
} else {
sprintf_s(fullCommandLine, nFullCommandLine, "%s", exe_name);
}
char* extra_injections = MiceConfig.launcher.inject;
if (!start_and_inject(hJob, exe_name, fullCommandLine, _miceIpcData->m_MiceDll, debug_wait,
boot_delay, extra_injections, 0, lpPi)) {
free(fullCommandLine);
return FALSE;
}
free(fullCommandLine);
return TRUE;
}
static BOOL _GameIdSearcher(char* lpBasePath, char* lpGameId) {
WIN32_FIND_DATAA findFileData;
CHAR szSearchPath[MAX_PATH + 1];
sprintf_s(szSearchPath, _countof(szSearchPath), "%s\\*", lpBasePath);
// Search for a table in the CWD
HANDLE hFind = FindFirstFileA(szSearchPath, &findFileData);
do {
if (strcmp(findFileData.cFileName, ".") == 0 || strcmp(findFileData.cFileName, "..") == 0)
continue;
if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
sprintf_s(szSearchPath, _countof(szSearchPath), "%s\\%s", lpBasePath,
findFileData.cFileName);
if (_GameIdSearcher(szSearchPath, lpGameId)) {
FindClose(hFind);
return TRUE;
}
} else {
if (strlen(findFileData.cFileName) == strlen("SAAA_Table.dat") &&
PathMatchSpecA(findFileData.cFileName, "S*_Table.dat")) {
// maimai games have a copy of the R&D table; ignore it.
if (strncmp(findFileData.cFileName, "SBRZ", 4) == 0) continue;
lpGameId[0] = findFileData.cFileName[0];
lpGameId[1] = findFileData.cFileName[1];
lpGameId[2] = findFileData.cFileName[2];
lpGameId[3] = findFileData.cFileName[3];
FindClose(hFind);
return TRUE;
}
if (strlen(findFileData.cFileName) == strlen("SAAA_banner.bmp") &&
PathMatchSpecA(findFileData.cFileName, "S*_banner.bmp")) {
lpGameId[0] = findFileData.cFileName[0];
lpGameId[1] = findFileData.cFileName[1];
lpGameId[2] = findFileData.cFileName[2];
lpGameId[3] = findFileData.cFileName[3];
FindClose(hFind);
return TRUE;
}
if (strlen(findFileData.cFileName) == strlen("SAAA_main.wmv") &&
PathMatchSpecA(findFileData.cFileName, "S*_main.wmv")) {
lpGameId[0] = findFileData.cFileName[0];
lpGameId[1] = findFileData.cFileName[1];
lpGameId[2] = findFileData.cFileName[2];
lpGameId[3] = findFileData.cFileName[3];
FindClose(hFind);
return TRUE;
}
}
} while (FindNextFile(hFind, &findFileData));
FindClose(hFind);
return FALSE;
}
BOOL MiceGuessGameId(char* lpGameId) {
if (_GameIdSearcher(".", lpGameId)) return TRUE;
// TODO: Some more lookups. Exe name is probably not an awful fallback
return FALSE;
}
BOOL g_bIsInDllMain = FALSE;
int main(int argc, char* argv[]) {
#if 0
// char buffer[] = "41A800003F000000000000006801";
char* buffer = argv[1];
char local_10[4];
sscanf_s(buffer, "%08x", local_10, sizeof local_10);
float measure_whole = *(float*)(void*)&local_10;
printf("measure_whole: %f\n", measure_whole);
sscanf_s(buffer + 8, "%08x", local_10, sizeof local_10);
float measure_fraction = *(float*)(void*)&local_10;
printf("measure_fraction: %f\n", measure_fraction);
sscanf_s(buffer + 16, "%08x", local_10, sizeof local_10);
float duration = *(float*)(void*)&local_10;
printf("duration: %f\n", duration);
sscanf_s(buffer + 24, "%01x", local_10, sizeof local_10);
unsigned char location = local_10[0];
printf("location: %d\n", location);
sscanf_s(buffer + 25, "%01x", local_10, sizeof local_10);
unsigned char type = local_10[0];
printf("Type: %d\n", local_10[0]);
if (local_10[0] < '\0') {
type = type ^ 0x80;
puts("<0");
// field_0x42 = 1;
}
if ((type & 0x40) != 0) {
puts("&0x40");
type = type ^ 0x40;
// field15_0x18 = 0x40000000;
}
if ((type & 0x20) != 0) {
puts("&0x20");
type = type ^ 0x20;
// field15_0x18 = 0x40800000;
}
printf("TypeO: %01x\n", type);
return 0;
#endif
load_mice_config();
if (!MiceIpcSetup(TRUE)) return 1;
_miceIpcData->m_LauncherIsReady = TRUE;
char gameId[4];
if (MiceGuessGameId(gameId)) memcpy(_miceIpcData->m_GameId, gameId, 4);
CreateThread(NULL, 0, MiceIpcMessageWatcher, NULL, 0, NULL);
setup_logging();
log_info(plfBoot, "Micetools version: %s", MICE_VERSION);
if (_miceIpcData->m_GameId[0] == '\0') {
if (MiceConfig.devices.do_auto) {
log_error(
plfBoot,
"Failed to identify game. Please edit [devices].do_auto to false and restart.");
log_error(plfBoot, "Please also report this.");
return terminate(-1, "Failed to identify game.");
}
log_warning(plfBoot, "Failed to identify game, but proceeding regardless.");
} else {
log_info(plfBoot, "Booting game: %.*s", 4, _miceIpcData->m_GameId);
}
CHAR szPath[MAX_PATH + 1];
GetCurrentDirectory(MAX_PATH, szPath);
szPath[sizeof szPath - 1] = '\0';
log_info(plfBoot, "Current directory: %s", szPath);
if (!parse_cmdline(argc, argv)) {
puts("parse_cmdline failed");
return terminate(-2, "parse_cmdline failed");
}
if (exe_name[0] == '\0' && MiceConfig.launcher.game_binary[0] != '\0') {
snprintf(exe_name, sizeof exe_name, "%s", MiceConfig.launcher.game_binary);
}
if (MiceConfig.launcher.wait_for_debugger) debug_wait = true;
boot_delay = MiceConfig.launcher.startup_delay;
if (exe_name[0] == '\0') {
if (!locate_game(exe_name, MAX_PATH + 1)) {
log_error(plfBoot, "Fatal: Failed to locate a game");
return terminate(-3, "Failed to locate a game");
}
} else {
DWORD dwAttrib = GetFileAttributes(exe_name);
if (dwAttrib == INVALID_FILE_ATTRIBUTES || dwAttrib & FILE_ATTRIBUTE_DIRECTORY) {
log_error(plfBoot, "Fatal: %s: no such file found", exe_name);
return terminate(-4, "Specified executable not locatable");
}
}
int i;
// Build IPC.m_PathPrefix
{
_miceIpcData->m_PathPrefix[0] = '\0';
for (i = 0; i < sizeof _miceIpcData->m_PathPrefix; i++) {
_miceIpcData->m_PathPrefix[i] = exe_name[i];
if (exe_name[i] == '\0') break;
}
// Find the final slash in the path
for (; i > 0; i--) {
if (_miceIpcData->m_PathPrefix[i] == '/' || _miceIpcData->m_PathPrefix[i] == '\\')
break;
}
_miceIpcData->m_PathPrefix[i] = '\0';
}
strcpy_s(_miceIpcData->m_MiceBase, sizeof _miceIpcData->m_MiceBase, szPath);
if (!SearchPathA(NULL, MiceConfig.launcher.mice_dll, NULL, sizeof _miceIpcData->m_MiceDll,
_miceIpcData->m_MiceDll, NULL)) {
log_error(plfBoot, "Unable to locate micelib. Check your mice_dll setting!");
return terminate(-5, "Unable to locate micelib");
}
_miceIpcData->m_MiceDll[sizeof _miceIpcData->m_MiceDll - 1] = '\0';
_MiceLauncherNextEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
SetConsoleCtrlHandler(MiceHandlerRoutine, TRUE);
spawn_pcp_processes();
log_info(plfBoot, "exec: %s %s", exe_name, commandLine);
while (1) {
if (hJob != INVALID_HANDLE_VALUE) {
CloseHandle(hJob);
hJob = INVALID_HANDLE_VALUE;
}
PROCESS_INFORMATION pi;
if (_MiceLauncherNext == 2) {
if (!StartGame(&pi, NULL))
return terminate(-6, "Start failed. Check console for errors.");
} else if (_MiceLauncherNext == 3 || _MiceLauncherNext == 4) {
// TODO: Boot mode 3 is actually system test. We could do something here?
if (!StartGame(&pi, "gametest"))
return terminate(-7, "Gametest failed. Check console for errors.");
} else {
log_info(plfBoot, "Boot mode %d not supported. Exiting.", _MiceLauncherNext);
return terminate(-8, NULL);
}
HANDLE objects[2];
objects[0] = pi.hProcess;
objects[1] = _MiceLauncherNextEvent;
DWORD waited = WaitForMultipleObjects(_countof(objects), objects, FALSE, INFINITE);
if (FAILED(waited)) {
log_error(plfBoot, "Fatal: WaitForSingleObject failed: %03x", GetLastError());
return terminate(-9, "WFSO failed");
}
if (waited == WAIT_OBJECT_0) {
DWORD exitCode;
if (GetExitCodeProcess(pi.hProcess, &exitCode))
log_info(plfBoot, "Shutting down (ret:%d)", exitCode);
else
log_info(plfBoot, "Shutting down (failed to get exit code)");
CloseHandle(pi.hProcess);
continue;
// return terminate(-10);
} else if (waited == WAIT_OBJECT_0 + 1) {
continue;
} else {
log_error(plfBoot, "Unknown wait object retured: %d", waited);
return terminate(-11, "Unknown wait object!");
}
}
}