102 Patches: Detours to the Rescue
C reference for DttR maintainers and modders.
Loading...
Searching...
No Matches
browse.c
Go to the documentation of this file.
1#include <dttr_config.h>
2#include <dttr_iso.h>
3#include <dttr_loader.h>
4#include <dttr_loader_paths.h>
5#include <dttr_loader_ui.h>
6#include <dttr_log.h>
7#include <dttr_path.h>
8#include <dttr_sdl.h>
9
10#include <SDL3/SDL.h>
11#include <string.h>
12#include <windows.h>
13
14static bool utf8_to_wide_path(WCHAR *out, const char *path) {
15 if (MultiByteToWideChar(CP_UTF8, 0, path, -1, out, MAX_PATH) == 0) {
16 out[0] = L'\0';
17 return false;
18 }
19
20 out[MAX_PATH - 1] = L'\0';
21 return true;
22}
23
24// Resolves user paths before deriving ISO cache keys.
25static bool get_full_path(char *out, size_t out_size, const char *path) {
26 const DWORD len = GetFullPathNameA(path, (DWORD)out_size, out, NULL);
27 return len > 0 && len < out_size;
28}
29
30// Uses LOCALAPPDATA, then temp, as the ISO cache base.
31static bool get_os_cache_base_dir(char *buf, size_t buf_size) {
32 const DWORD len = GetEnvironmentVariableA("LOCALAPPDATA", buf, (DWORD)buf_size);
33
34 if (len > 0 && len < buf_size) {
35 return true;
36 }
37
38 const DWORD temp_len = GetTempPathA((DWORD)buf_size, buf);
39 return temp_len > 0 && temp_len < buf_size;
40}
41
42// Tests one known PCDOGS executable subpath.
43static bool try_path(WCHAR *out, const WCHAR *dir, const WCHAR *subpath) {
44 WCHAR candidate[MAX_PATH];
45 _snwprintf(candidate, MAX_PATH, L"%s\\%s", dir, subpath);
46 candidate[MAX_PATH - 1] = L'\0';
47
48 if (GetFileAttributesW(candidate) == INVALID_FILE_ATTRIBUTES) {
49 return false;
50 }
51
52 wcscpy(out, candidate);
53 return true;
54}
55
56// Finds a supported PCDOGS executable layout.
57static bool try_dir(WCHAR *out, const WCHAR *dir) {
58 const size_t subpath_count = DTTR_Loader_GameSubpathCount();
59
60 for (size_t i = 0; i < subpath_count; i++) {
61 if (try_path(out, dir, DTTR_Loader_GameSubpathAt(i))) {
62 return true;
63 }
64 }
65
66 return false;
67}
68
69// Copies optional paths and keeps cancelled selections empty.
70static void copy_path(char *out, size_t out_size, const char *path) {
71 if (out_size == 0) {
72 return;
73 }
74
75 if (!path || !DTTR_Path_CopyString(out, out_size, path)) {
76 out[0] = '\0';
77 }
78}
79
80// Extracts one required ISO member into cache.
81static bool extract_iso_file(
82 DTTR_IsoImage *iso,
83 const char *cache_root,
84 const char *iso_path,
85 char *out_path,
86 size_t out_path_size
87) {
88 if (DTTR_ISO_ExtractFile(iso, iso_path, cache_root, out_path, out_path_size)) {
89 return true;
90 }
91
92 DTTR_LOG_ERROR("Could not extract %s (%s)", iso_path, DTTR_ISO_LastError());
93 return false;
94}
95
96// Extracts the ISO files needed for launch.
98 DTTR_IsoImage *iso,
99 const char *cache_root,
100 char *exe_path,
101 size_t exe_path_size
102) {
103 if (!extract_iso_file(
104 iso,
105 cache_root,
107 exe_path,
108 exe_path_size
109 )) {
110 return false;
111 }
112
113 char pkg_path[MAX_PATH];
114
115 if (!extract_iso_file(
116 iso,
117 cache_root,
119 pkg_path,
120 sizeof(pkg_path)
121 )) {
122 return false;
123 }
124
125 const char *data_path = DTTR_LoaderISO_GameDataPath();
126
127 if (DTTR_ISO_ExtractTree(iso, data_path, cache_root)) {
128 return true;
129 }
130
131 DTTR_LOG_ERROR("Could not extract %s (%s)", data_path, DTTR_ISO_LastError());
132 return false;
133}
134
135// Opens an ISO and returns the cached executable path.
137 WCHAR *out,
138 const char *iso_path,
139 DTTR_LoaderIsoContext *iso_context
140) {
141 char full_iso_path[MAX_PATH];
142
143 if (!get_full_path(full_iso_path, sizeof(full_iso_path), iso_path)) {
144 DTTR_LOG_ERROR("ISO path is too long: %s", iso_path);
145 return false;
146 }
147
148 char cache_base_dir[MAX_PATH];
149
150 if (!get_os_cache_base_dir(cache_base_dir, sizeof(cache_base_dir))
152 cache_base_dir,
153 full_iso_path,
154 iso_context->cache_root,
155 sizeof(iso_context->cache_root)
156 )) {
157 DTTR_LOG_ERROR("Could not build ISO cache path for %s", full_iso_path);
158 return false;
159 }
160
161 DTTR_IsoImage iso = {0};
162 bool ok = false;
163
164 if (!DTTR_ISO_Open(&iso, full_iso_path)) {
166 "Could not open ISO directly: %s (%s)",
167 full_iso_path,
169 );
170 return false;
171 }
172
173 char exe_path[MAX_PATH];
174
176 &iso,
177 iso_context->cache_root,
178 exe_path,
179 sizeof(exe_path)
180 )) {
181 DTTR_LOG_ERROR("ISO source: %s", full_iso_path);
182 goto done;
183 }
184
185 if (!utf8_to_wide_path(out, exe_path)) {
186 DTTR_LOG_ERROR("Could not convert cached ISO executable path: %s", exe_path);
187 goto done;
188 }
189
190 iso_context->is_iso = true;
191 copy_path(
192 iso_context->game_root,
193 sizeof(iso_context->game_root),
195 );
196 DTTR_LOG_INFO("Cached ISO game files under %s", iso_context->cache_root);
197 ok = true;
198
199done:
200 DTTR_ISO_Close(&iso);
201 return ok;
202}
203
204// Clears ISO context before direct ISO resolution.
205static bool resolve_iso(
206 WCHAR *out,
207 const char *iso_path,
208 DTTR_LoaderIsoContext *iso_context
209) {
210 if (!iso_context) {
211 DTTR_LOG_ERROR("ISO path provided without context");
212 return false;
213 }
214
215 memset(iso_context, 0, sizeof(*iso_context));
216
217 if (resolve_iso_direct(out, iso_path, iso_context)) {
218 return true;
219 }
220
222 "DttR: ISO Load Failed",
223 "DttR could not read the selected ISO. Consider using the extracted game files "
224 "instead."
225 );
226 return false;
227}
228
229// Tries the saved ISO or game folder before prompting.
231 WCHAR *out,
232 const char *configured_path,
233 DTTR_LoaderIsoContext *iso_context
234) {
235 WCHAR wide_path[MAX_PATH];
236 if (!utf8_to_wide_path(wide_path, configured_path)) {
237 DTTR_LOG_ERROR("Could not convert configured path: %s", configured_path);
238 return false;
239 }
240
241 if (DTTR_LoaderPath_IsISOW(wide_path)) {
242 DTTR_LOG_INFO("Using configured ISO path: %s", configured_path);
243 return resolve_iso(out, configured_path, iso_context);
244 }
245
246 if (try_dir(out, wide_path)) {
247 DTTR_LOG_INFO("Using configured PCDOGS path: %s", configured_path);
248 return true;
249 }
250
251 return false;
252}
253
254static char browse_result[MAX_PATH];
255static HANDLE browse_event;
256
257// Builds the chooser label and root path for a disc.
258static void fill_disc_candidate(DTTR_LoaderUIDiscCandidate *candidate, char drive) {
259 snprintf(candidate->label, sizeof(candidate->label), "Open Disc %c:", drive);
260 snprintf(candidate->path, sizeof(candidate->path), "%c:\\", drive);
261}
262
263// Stores the SDL dialog result and wakes the browse loop.
264static void SDLCALL browse_callback(void *, const char *const *filelist, int) {
265 copy_path(
267 sizeof(browse_result),
268 (filelist && filelist[0]) ? filelist[0] : NULL
269 );
270
271 if (browse_event) {
272 SetEvent(browse_event);
273 }
274}
275
276// Pumps SDL while the native browse dialog is open.
278 while (WaitForSingleObject(browse_event, 0) == WAIT_TIMEOUT) {
280 DTTR_SDL_Delay(10);
281 }
282
283 return browse_result[0] != '\0';
284}
285
286// Saves the chosen source for the next launch without blocking this launch.
287static void save_selected_path(const char *path) {
288 copy_path(dttr_config.pcdogs_path, sizeof(dttr_config.pcdogs_path), path);
291 "Could not save selected game path to %s; continuing for this launch",
293 );
294 }
295}
296
297// Finds mounted discs that contain a known game layout.
299 DTTR_LoaderUIDiscCandidate *candidates,
300 size_t *candidate_count
301) {
302 *candidate_count = 0;
303 const DWORD drives = GetLogicalDrives();
304
305 for (char drive = 'A'; drive <= 'Z'; drive++) {
306 const DWORD bit = 1u << (drive - 'A');
307
308 if ((drives & bit) == 0) {
309 continue;
310 }
311
312 WCHAR root_w[] = {drive, L':', L'\\', L'\0'};
313 const UINT drive_type = GetDriveTypeW(root_w);
314
315 if (drive_type == DRIVE_UNKNOWN || drive_type == DRIVE_NO_ROOT_DIR) {
316 continue;
317 }
318
319 WCHAR game_path[MAX_PATH];
320
321 if (!try_dir(game_path, root_w)) {
322 continue;
323 }
324
325 DTTR_LoaderUIDiscCandidate *candidate = &candidates[*candidate_count];
326 fill_disc_candidate(candidate, drive);
327 (*candidate_count)++;
328
329 if (*candidate_count >= DTTR_LOADER_UI_MAX_DISC_CANDIDATES) {
330 return;
331 }
332 }
333}
334
335// Revalidates a disc before saving it.
336static bool try_disc_candidate(WCHAR *out, const DTTR_LoaderUIDiscCandidate *candidate) {
337 WCHAR wide_path[MAX_PATH];
338 if (!utf8_to_wide_path(wide_path, candidate->path) || !try_dir(out, wide_path)) {
340 "DttR: Disc Not Found",
341 "The selected disc no longer contains pcdogs.exe."
342 );
343 return false;
344 }
345
346 DTTR_LOG_INFO("Selected game disc: %s", candidate->path);
347 save_selected_path(candidate->path);
348 return true;
349}
350
351// Opens the requested native picker.
353 if (!browse_event) {
354 browse_event = CreateEventW(NULL, TRUE, FALSE, NULL);
355
356 if (!browse_event) {
357 DTTR_LOG_ERROR("Could not create browse completion event");
358 return false;
359 }
360 }
361
362 browse_result[0] = '\0';
363 ResetEvent(browse_event);
364
367 return wait_for_browse_result();
368 }
369
370 const SDL_DialogFileFilter filters[] = {{"ISO images", "iso"}};
372 return wait_for_browse_result();
373}
374
375static bool try_browsed_path(
376 WCHAR *out,
377 DTTR_LoaderUIChoice choice,
378 DTTR_LoaderIsoContext *iso_context
379);
380
381// Resolves the path returned by the requested picker.
383 WCHAR *out,
384 DTTR_LoaderUIChoice choice,
385 DTTR_LoaderIsoContext *iso_context
386) {
387 return run_browse_dialog(choice) && try_browsed_path(out, choice, iso_context);
388}
389
390// Validates the latest browse result before saving it.
392 WCHAR *out,
393 DTTR_LoaderUIChoice choice,
394 DTTR_LoaderIsoContext *iso_context
395) {
396 WCHAR wide_path[MAX_PATH];
397 if (!utf8_to_wide_path(wide_path, browse_result)) {
399 "DttR: Game Not Found",
400 "The selected folder does not contain pcdogs.exe."
401 );
402 return false;
403 }
404
405 if (choice == DTTR_LOADER_UI_CHOICE_BROWSE_ISO || DTTR_LoaderPath_IsISOW(wide_path)) {
406 return resolve_iso(out, browse_result, iso_context);
407 }
408
409 if (try_dir(out, wide_path)) {
410 return true;
411 }
412
414 "DttR: Game Not Found",
415 "The selected folder does not contain pcdogs.exe."
416 );
417 return false;
418}
419
420// Prompts until a source resolves to an executable.
421static bool prompt_browse_for_path(WCHAR *out, DTTR_LoaderIsoContext *iso_context) {
423 size_t disc_candidate_count = 0;
424 scan_disc_candidates(disc_candidates, &disc_candidate_count);
425
426 for (;;) {
428 disc_candidates,
429 disc_candidate_count
430 );
431
432 size_t disc_index = 0;
433
434 if (DTTR_LoaderUI_ChoiceIsDisc(choice, &disc_index)) {
435 if (disc_index < disc_candidate_count
436 && try_disc_candidate(out, &disc_candidates[disc_index])) {
437 return true;
438 }
439
440 scan_disc_candidates(disc_candidates, &disc_candidate_count);
441 continue;
442 }
443
444 if (!DTTR_LoaderUI_ChoiceIsBrowse(choice)) {
445 return false;
446 }
447
448 if (!try_browse_choice(out, choice, iso_context)) {
449 continue;
450 }
451
452 DTTR_LOG_INFO("Selected game path: %s", browse_result);
454 return true;
455 }
456}
457
458// Uses the saved source when valid, otherwise prompts.
460 WCHAR *out,
461 const char *configured_path,
462 DTTR_LoaderIsoContext *iso_context
463) {
464 if (configured_path && configured_path[0]
465 && try_configured_path(out, configured_path, iso_context)) {
466 return true;
467 }
468
469 return prompt_browse_for_path(out, iso_context);
470}
static bool try_path(WCHAR *out, const WCHAR *dir, const WCHAR *subpath)
Definition browse.c:43
static char browse_result[MAX_PATH]
Definition browse.c:254
static bool resolve_iso_direct(WCHAR *out, const char *iso_path, DTTR_LoaderIsoContext *iso_context)
Definition browse.c:136
static bool get_os_cache_base_dir(char *buf, size_t buf_size)
Definition browse.c:31
static void fill_disc_candidate(DTTR_LoaderUIDiscCandidate *candidate, char drive)
Definition browse.c:258
static bool prompt_browse_for_path(WCHAR *out, DTTR_LoaderIsoContext *iso_context)
Definition browse.c:421
static bool extract_iso_file(DTTR_IsoImage *iso, const char *cache_root, const char *iso_path, char *out_path, size_t out_path_size)
Definition browse.c:81
static bool wait_for_browse_result()
Definition browse.c:277
static bool try_browse_choice(WCHAR *out, DTTR_LoaderUIChoice choice, DTTR_LoaderIsoContext *iso_context)
Definition browse.c:382
static bool run_browse_dialog(DTTR_LoaderUIChoice choice)
Definition browse.c:352
static void scan_disc_candidates(DTTR_LoaderUIDiscCandidate *candidates, size_t *candidate_count)
Definition browse.c:298
static bool try_browsed_path(WCHAR *out, DTTR_LoaderUIChoice choice, DTTR_LoaderIsoContext *iso_context)
Definition browse.c:391
static void copy_path(char *out, size_t out_size, const char *path)
Definition browse.c:70
static bool extract_iso_game_cache(DTTR_IsoImage *iso, const char *cache_root, char *exe_path, size_t exe_path_size)
Definition browse.c:97
static bool try_dir(WCHAR *out, const WCHAR *dir)
Definition browse.c:57
static bool resolve_iso(WCHAR *out, const char *iso_path, DTTR_LoaderIsoContext *iso_context)
Definition browse.c:205
static HANDLE browse_event
Definition browse.c:255
bool DTTR_Loader_ResolveEXEPath(WCHAR *out, const char *configured_path, DTTR_LoaderIsoContext *iso_context)
Definition browse.c:459
static bool get_full_path(char *out, size_t out_size, const char *path)
Definition browse.c:25
static bool try_disc_candidate(WCHAR *out, const DTTR_LoaderUIDiscCandidate *candidate)
Definition browse.c:336
static bool try_configured_path(WCHAR *out, const char *configured_path, DTTR_LoaderIsoContext *iso_context)
Definition browse.c:230
static void SDLCALL browse_callback(void *, const char *const *filelist, int)
Definition browse.c:264
static bool utf8_to_wide_path(WCHAR *out, const char *path)
Definition browse.c:14
static void save_selected_path(const char *path)
Definition browse.c:287
DTTR_Graphics_COM_Direct3DDevice7 DWORD block DTTR_Graphics_COM_Direct3DDevice7 DWORD block DTTR_Graphics_COM_Direct3DDevice7 void void void void DWORD f FALSE
DTTR_Graphics_COM_DirectDrawSurface7 DWORD flags void NULL
DTTR_Config dttr_config
Definition defaults.c:53
bool DTTR_Config_Save(const char *filename, const DTTR_Config *config)
Saves config values back to a strict JSON file.
Definition io.c:483
bool DTTR_ISO_ExtractFile(DTTR_IsoImage *iso, const char *iso_relative_path, const char *cache_root, char *out_path, size_t out_path_size)
Definition iso.c:411
void DTTR_ISO_Close(DTTR_IsoImage *iso)
Definition iso.c:593
bool DTTR_ISO_Open(DTTR_IsoImage *iso, const char *iso_path)
Definition iso.c:185
bool DTTR_ISO_ExtractTree(DTTR_IsoImage *iso, const char *iso_relative_path, const char *cache_root)
Definition iso.c:560
const char * DTTR_ISO_LastError()
Definition iso.c:44
const char * dttr_config_path
Definition launcher.c:21
const char * DTTR_LoaderISO_GameEXEPath()
Definition paths.c:63
size_t DTTR_Loader_GameSubpathCount()
Definition paths.c:51
const char * DTTR_LoaderISO_GameDataPath()
Definition paths.c:71
bool DTTR_LoaderISO_CacheRootForPath(const char *cache_base_dir, const char *iso_path, char *out_path, size_t out_path_size)
Definition paths.c:94
const wchar_t * DTTR_Loader_GameSubpathAt(size_t index)
Definition paths.c:55
const char * DTTR_LoaderISO_GameRoot()
Definition paths.c:59
bool DTTR_LoaderPath_IsISOW(const wchar_t *path)
Definition paths.c:47
const char * DTTR_LoaderISO_GamePkgPath()
Definition paths.c:67
@ DTTR_LOADER_UI_MAX_DISC_CANDIDATES
DTTR_LoaderUIChoice DTTR_LoaderUI_ChooseGameSource(const DTTR_LoaderUIDiscCandidate *disc_candidates, size_t disc_candidate_count)
Definition ui.c:182
bool DTTR_LoaderUI_ChoiceIsBrowse(DTTR_LoaderUIChoice choice)
Definition ui_logic.c:3
DTTR_LoaderUIChoice
@ DTTR_LOADER_UI_CHOICE_BROWSE_ISO
@ DTTR_LOADER_UI_CHOICE_BROWSE_FOLDER
bool DTTR_LoaderUI_ChoiceIsDisc(DTTR_LoaderUIChoice choice, size_t *out_index)
Definition ui_logic.c:8
void DTTR_LoaderUI_ShowError(const char *title, const char *message)
Definition ui.c:236
#define DTTR_LOG_INFO(...)
Definition dttr_log.h:29
#define DTTR_LOG_ERROR(...)
Definition dttr_log.h:31
bool DTTR_Path_CopyString(char *out, size_t out_size, const char *value)
Definition path.c:68
void DTTR_SDL_Delay(Uint32 ms)
Definition sdl.c:191
void DTTR_SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, int nfilters, const char *default_location, bool allow_many)
Definition sdl.c:158
void DTTR_SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const char *default_location, bool allow_many)
Definition sdl.c:143
void DTTR_SDL_PumpEvents()
Definition sdl.c:183
char cache_root[MAX_PATH]
Definition dttr_loader.h:9
char game_root[MAX_PATH]
Definition dttr_loader.h:10