102 Patches: Detours to the Rescue
C reference for DttR maintainers and modders.
Loading...
Searching...
No Matches
launcher.c
Go to the documentation of this file.
1#include <dttr_config.h>
2#include <dttr_crashdump.h>
3#include <dttr_errors.h>
4#include <dttr_imgui.h>
5#include <dttr_loader.h>
6#include <dttr_loader_ui.h>
7#include <dttr_log.h>
8#include <dttr_path.h>
9#include <sds.h>
10#include <stdio.h>
11#include <string.h>
12#include <windows.h>
13
14#include <gen/packed_sdb.h>
15
16static const char *const MODULES_DIR_NAME = "modules";
17
18enum { PATH_ENV_BUFFER_SIZE = 1u << 15 };
19
20static char config_path_buf[MAX_PATH];
22
23static bool set_env(const char *name, const char *value) {
24 if (SetEnvironmentVariableA(name, value)) {
25 return true;
26 }
27
28 DTTR_LOG_ERROR("Could not set %s", name);
29 return false;
30}
31
32static bool resolve_modules_dir(char *out, size_t out_size) {
34 const bool copied = DTTR_Path_CopySds(out, out_size, modules_dir);
35 sdsfree(modules_dir);
36 return copied;
37}
38
39static bool resolve_loader_dir(char *out, size_t out_size) {
40 sds loader_dir = DTTR_Path_ModuleDir(NULL);
41 const bool copied = DTTR_Path_CopySds(out, out_size, loader_dir);
42 sdsfree(loader_dir);
43 return copied;
44}
45
46static void resolve_config_path(int argc, char *argv[]) {
47 sds default_config_path = NULL;
48 const char *config_path = argc > 1 ? argv[1] : NULL;
49 if (!config_path || !config_path[0]) {
51 config_path = default_config_path;
52 }
53
54 if (!config_path || !config_path[0]) {
55 sdsfree(default_config_path);
56 DTTR_FATAL("Could not resolve default loader config path");
57 }
58
59 const DWORD len = GetFullPathNameA(config_path, MAX_PATH, config_path_buf, NULL);
60 sdsfree(default_config_path);
61 if (len == 0 || len >= MAX_PATH) {
62 DTTR_FATAL("Could not resolve loader config path");
63 }
64
66}
67
68static FILE *open_log_file(int log_level) {
69 char loader_dir[MAX_PATH];
70 const char *base_dir = NULL;
71 if (resolve_loader_dir(loader_dir, sizeof(loader_dir))) {
72 base_dir = loader_dir;
73 }
74
75 sds log_path = DTTR_Path_ResolveRelativeTo(base_dir, dttr_config.log_file_path);
76 if (!log_path) {
77 return NULL;
78 }
79
80 FILE *log_file = fopen(log_path, "a+");
81 if (log_file) {
82 DTTR_Log_AddFP(log_file, log_level);
83 }
84
85 sdsfree(log_path);
86 return log_file;
87}
88
89static bool terminate_child(PROCESS_INFORMATION *child_info, DWORD exit_code) {
90 if (!child_info->hProcess) {
91 return true;
92 }
93
94 if (!TerminateProcess(child_info->hProcess, exit_code)) {
95 DTTR_LOG_ERROR("Could not terminate game process after launch failure");
96 return false;
97 }
98
99 const DWORD wait_result = WaitForSingleObject(child_info->hProcess, 10000);
100 if (wait_result != WAIT_OBJECT_0) {
102 "Timed out waiting for game process to terminate after launch failure"
103 );
104 return false;
105 }
106
107 return true;
108}
109
110static void close_child_handles(PROCESS_INFORMATION *child_info) {
111 if (child_info->hThread) {
112 CloseHandle(child_info->hThread);
113 }
114
115 if (child_info->hProcess) {
116 CloseHandle(child_info->hProcess);
117 }
118}
119
121 char old_path[PATH_ENV_BUFFER_SIZE] = "";
122
123 const DWORD old_len = GetEnvironmentVariableA("PATH", old_path, sizeof(old_path));
124 if (old_len >= sizeof(old_path)) {
125 DTTR_LOG_ERROR("PATH is too long to prepend DttR modules directory");
126 return;
127 }
128
129 char modules_dir[MAX_PATH];
130 if (!resolve_modules_dir(modules_dir, sizeof(modules_dir))) {
131 DTTR_LOG_ERROR("Could not resolve DttR modules directory for PATH");
132 return;
133 }
134
135 char new_path[PATH_ENV_BUFFER_SIZE];
136 const int written = old_len > 0
137 ? snprintf(
138 new_path,
139 sizeof(new_path),
140 "%s;%s",
141 modules_dir,
142 old_path
143 )
144 : snprintf(new_path, sizeof(new_path), "%s", modules_dir);
145
146 if (written <= 0 || (size_t)written >= sizeof(new_path)) {
147 DTTR_LOG_ERROR("PATH is too long after prepending DttR modules directory");
148 return;
149 }
150
151 if (!set_env("PATH", new_path)) {
152 return;
153 }
154
155 DTTR_LOG_DEBUG("Prepended DttR modules directory to PATH: %s", modules_dir);
156}
157
158__declspec(dllexport) int dttr_launcher_main(int argc, char *argv[]) {
159 char exe_dir[MAX_PATH];
160 PROCESS_INFORMATION child_info = {0};
161 DWORD child_exit_code = 1;
162
163 if (!resolve_loader_dir(exe_dir, sizeof(exe_dir))) {
164 DTTR_FATAL("Could not resolve loader directory");
165 }
166
167 DTTR_CrashDump_Init(exe_dir);
169
170 resolve_config_path(argc, argv);
172 const char *details = DTTR_Config_LastError();
174 "Could not load configuration file %s%s%s",
176 details ? ":\n" : "",
177 details ? details : ""
178 );
179 }
180
181 const int log_level = dttr_config.log_level;
182 DTTR_Log_SetLevel(log_level);
183
184 FILE *log_file = open_log_file(log_level);
185
186 DTTR_LOG_INFO("Starting DttR loader (log level: %s)", log_level_string(log_level));
187
188 DTTR_LoaderIsoContext iso_context = {0};
189
190 WCHAR exe_path[MAX_PATH];
191 if (!DTTR_Loader_ResolveEXEPath(exe_path, dttr_config.pcdogs_path, &iso_context)) {
192 DTTR_LOG_INFO("User exited without selecting a game path");
193 goto cleanup;
194 }
195
196 if (!set_env("DTTR_CONFIG_PATH", dttr_config_path)) {
197 DTTR_FATAL("Could not pass configuration path to game process");
198 }
199
201
202 if (iso_context.is_iso) {
203 if (!set_env("DTTR_ISO_CACHE_ROOT", iso_context.cache_root)
204 || !set_env("DTTR_ISO_GAME_ROOT", iso_context.game_root)) {
205 DTTR_FATAL("Could not pass ISO extraction paths to game process");
206 }
207 }
208
209 // Override compatibility shims before the sidecar starts.
211 exe_path,
212 (const char *)packed_sdb,
213 packed_sdb_len,
214 &child_info
215 );
216
217 DTTR_Loader_WatchdogAttach(&child_info);
218 if (!DTTR_Loader_InjectSidecar(&child_info)) {
219 DTTR_ERROR("Could not inject sidecar into the game process." DTTR_REPORT_SUFFIX);
220 DTTR_Loader_WatchdogDetach(&child_info);
221 terminate_child(&child_info, 1);
222 } else if (!DTTR_Loader_WatchdogWait(&child_info)) {
223 terminate_child(&child_info, 1);
224 }
225
226 WaitForSingleObject(child_info.hProcess, INFINITE);
227 if (!GetExitCodeProcess(child_info.hProcess, &child_exit_code)) {
228 DTTR_LOG_ERROR("Could not read game process exit code");
229 child_exit_code = 1;
230 }
231
232 DTTR_LOG_INFO("Exiting loader with child exit code %lu", child_exit_code);
233
234cleanup:
235 close_child_handles(&child_info);
237
238 if (log_file) {
239 fclose(log_file);
240 }
241
242 return child_info.hProcess ? (int)child_exit_code : 0;
243}
static void cleanup(DTTR_BackendState *state)
DTTR_Graphics_COM_DirectDrawSurface7 DWORD flags void NULL
const char * DTTR_Config_LastError()
Returns details from the most recent config load failure, or NULL when none exist.
Definition io.c:46
#define DTTR_CONFIG_FILENAME
DTTR_Config dttr_config
Definition defaults.c:53
bool DTTR_Config_Load(const char *filename)
Loads config values from a strict JSON file into the global config object.
Definition io.c:259
void DTTR_CrashDump_Init(const char *dump_dir)
Definition crashdump.c:636
#define DTTR_REPORT_SUFFIX
Definition dttr_errors.h:26
void DTTR_Errors_SetMessageHandler(DTTR_ErrorMessageHandler handler)
Definition errors.c:6
#define DTTR_FATAL(error_message,...)
Definition dttr_errors.h:30
#define DTTR_ERROR(error_message,...)
Definition dttr_errors.h:18
void DTTR_ImGuiDialog_Shutdown()
void DTTR_Loader_WatchdogDetach(const PROCESS_INFORMATION *child_info)
Definition watchdog.c:137
bool DTTR_Loader_InjectSidecar(const PROCESS_INFORMATION *child_info)
Definition load.c:291
bool DTTR_Loader_WatchdogWait(const PROCESS_INFORMATION *child_info)
Definition watchdog.c:163
const char * dttr_config_path
Definition launcher.c:21
void DTTR_Loader_WatchdogAttach(const PROCESS_INFORMATION *child_info)
Definition watchdog.c:115
bool DTTR_Loader_ResolveEXEPath(WCHAR *out, const char *configured_path, DTTR_LoaderIsoContext *iso_context)
Definition browse.c:459
void DTTR_Compat_CreateProcess(const WCHAR *image_name, const char *shim_data, size_t shim_data_len, PROCESS_INFORMATION *child_info)
Definition nt_process.c:163
void DTTR_LoaderUI_ShowError(const char *title, const char *message)
Definition ui.c:236
#define DTTR_LOG_DEBUG(...)
Definition dttr_log.h:28
void DTTR_Log_SetLevel(int level)
Definition log.c:143
#define DTTR_LOG_INFO(...)
Definition dttr_log.h:29
int DTTR_Log_AddFP(FILE *fp, int level)
Definition log.c:161
#define DTTR_LOG_ERROR(...)
Definition dttr_log.h:31
sds DTTR_Path_ModuleSibling(void *module, const char *relative_path)
Definition path.c:217
bool DTTR_Path_CopySds(char *out, size_t out_size, sds value)
Definition path.c:72
sds DTTR_Path_ResolveRelativeTo(const char *base_dir, const char *path)
Definition path.c:228
sds DTTR_Path_ModuleDir(void *module)
Definition path.c:201
@ PATH_ENV_BUFFER_SIZE
Definition launcher.c:18
static const char *const MODULES_DIR_NAME
Definition launcher.c:16
int dttr_launcher_main(int argc, char *argv[])
Definition launcher.c:158
static bool terminate_child(PROCESS_INFORMATION *child_info, DWORD exit_code)
Definition launcher.c:89
static bool set_env(const char *name, const char *value)
Definition launcher.c:23
static void resolve_config_path(int argc, char *argv[])
Definition launcher.c:46
static bool resolve_modules_dir(char *out, size_t out_size)
Definition launcher.c:32
static void prepend_modules_to_path()
Definition launcher.c:120
static FILE * open_log_file(int log_level)
Definition launcher.c:68
static char config_path_buf[MAX_PATH]
Definition launcher.c:20
static bool resolve_loader_dir(char *out, size_t out_size)
Definition launcher.c:39
static void close_child_handles(PROCESS_INFORMATION *child_info)
Definition launcher.c:110
char cache_root[MAX_PATH]
Definition dttr_loader.h:9
char game_root[MAX_PATH]
Definition dttr_loader.h:10