102 Patches: Detours to the Rescue
C reference for DttR maintainers and modders.
Loading...
Searching...
No Matches
mods.c
Go to the documentation of this file.
2#include "mods_private.h"
3#include "sidecar_private.h"
4#include <dttr_runtime.h>
5
6#include <dttr_config.h>
7#include <dttr_errors.h>
8#include <dttr_log.h>
9#include <dttr_path.h>
10
11#include <kvec.h>
12#include <sds.h>
13
14#include <stdlib.h>
15#include <string.h>
16
17#define MOD_MAX_SHADOW_ATTEMPTS 32
18#define MOD_RELOAD_STABLE_MS 500u
19#define MOD_RELOAD_POLL_MS 1000u
20
21typedef kvec_t(loaded_mod) mod_vec;
22
23static mod_vec loaded_mods;
24static char mods_dir[MAX_PATH];
25static DWORD last_reload_scan_ms = 0;
26static unsigned long shadow_counter = 0;
27static uintptr_t hook_owner_counter = 0;
28
29static DTTR_Mods_Context mod_context(const DTTR_Mods_Context *base_ctx) {
30 return (DTTR_Mods_Context){
31 .abi_version = base_ctx->abi_version,
32 .runtime = base_ctx->runtime,
33 .sidecar_module = base_ctx->sidecar_module,
34 .window = dttr_graphics_get_window(),
35 .loader_dir = dttr_loader_dir,
36 .exe_hash = dttr_exe_hash,
37 .config = base_ctx->config,
38 .api = base_ctx->api,
40 };
41}
42
43static bool file_id_equal(const mod_file_id *lhs, const mod_file_id *rhs) {
44 return CompareFileTime(&lhs->write_time, &rhs->write_time) == 0
45 && lhs->size_high == rhs->size_high && lhs->size_low == rhs->size_low;
46}
47
48static mod_file_id make_mod_file_id(const WIN32_FIND_DATAA *find_data) {
49 return (mod_file_id){
50 .write_time = find_data->ftLastWriteTime,
51 .size_high = find_data->nFileSizeHigh,
52 .size_low = find_data->nFileSizeLow,
53 };
54}
55
56static bool make_mod_path(sds *out, const char *filename) {
57 *out = sdsnew(mods_dir);
58 return *out && DTTR_Path_AppendSegment(out, filename, '\\');
59}
60
61static void log_mod_deleted(const loaded_mod *mod) {
62 DTTR_LOG_INFO("Mod deleted, unloading: %s", mod->filename);
63}
64
65// Recognizes temporary shadow-copy DLLs so hot reload never treats them as source mods.
66static bool is_shadow_mod(const char *filename) {
67 return strncmp(filename, DTTR_MODS_SHADOW_PREFIX, strlen(DTTR_MODS_SHADOW_PREFIX))
68 == 0;
69}
70
72 return info_fn ? info_fn() : NULL;
73}
74
75static void set_mod_display_name(loaded_mod *mod, const DTTR_Mods_Info *info) {
76 const char *name = info && info->name && info->name[0] ? info->name : mod->filename;
77 DTTR_Path_CopyString(mod->display_name, sizeof(mod->display_name), name);
78}
79
80static void log_mod_info(const char *filename, const DTTR_Mods_Info *info) {
81 if (!info) {
82 return;
83 }
84
86 "Mod: %s v%s by %s (%s)",
87 info->name ? info->name : "unknown",
88 info->version ? info->version : "?",
89 info->author ? info->author : "unknown",
90 filename
91 );
92}
93
95 free(mod->context);
96 mod->context = NULL;
97}
98
99// Builds the stable context pointer exposed from DTTR_Mod_Init through cleanup.
100static bool refresh_mod_context(loaded_mod *mod, const DTTR_Mods_Context *base_ctx) {
101 if (!mod->context) {
102 mod->context = (DTTR_Mods_Context *)calloc(1, sizeof(*mod->context));
103 if (!mod->context) {
104 DTTR_LOG_WARN("Failed to allocate mod context for %s", mod->filename);
105 return false;
106 }
107 }
108
109 *mod->context = mod_context(base_ctx);
110 return true;
111}
112
113#define MOD_WITH_OWNER(mod, call) \
114 do { \
115 void *previous_owner = DTTR_Core_HookSetOwner((mod)->hook_owner); \
116 call; \
117 DTTR_Core_HookSetOwner(previous_owner); \
118 } while (0)
119
120#define MOD_DISPATCH(field, ...) \
121 do { \
122 for (size_t i = 0; i < kv_size(loaded_mods); i++) { \
123 loaded_mod *mod = &kv_A(loaded_mods, i); \
124 if (mod->field) { \
125 MOD_WITH_OWNER(mod, mod->field(__VA_ARGS__)); \
126 } \
127 } \
128 } while (0)
129
130#define MOD_OPTIONAL_EXPORTS(X) \
131 X(tick, DTTR_Mods_TickFn, "DTTR_Mod_Tick") \
132 X(event, DTTR_Mods_EventFn, "DTTR_Mod_Event") \
133 X(info, DTTR_Mods_InfoFn, "DTTR_Mod_Info") \
134 X(late_init, DTTR_Mods_LateInitFn, "DTTR_Mod_LateInit") \
135 X(before_unload, DTTR_Mods_BeforeUnloadFn, "DTTR_Mod_BeforeUnload") \
136 X(frame_begin, DTTR_Mods_FrameBeginFn, "DTTR_Mod_FrameBegin") \
137 X(before_game_frame, DTTR_Mods_BeforeGameFrameFn, "DTTR_Mod_BeforeGameFrame") \
138 X(after_game_frame, DTTR_Mods_AfterGameFrameFn, "DTTR_Mod_AfterGameFrame") \
139 X(before_present, DTTR_Mods_BeforePresentFn, "DTTR_Mod_BeforePresent") \
140 X(after_present, DTTR_Mods_AfterPresentFn, "DTTR_Mod_AfterPresent") \
141 X(frame_end, DTTR_Mods_FrameEndFn, "DTTR_Mod_FrameEnd") \
142 X(imgui_begin, DTTR_Mods_ImGuiBeginFn, "DTTR_Mod_ImGuiBegin") \
143 X(imgui_end, DTTR_Mods_ImGuiEndFn, "DTTR_Mod_ImGuiEnd") \
144 X(overlay_visible_changed, \
145 DTTR_Mods_OverlayVisibleChangedFn, \
146 "DTTR_Mod_OverlayVisibleChanged") \
147 X(window_created, DTTR_Mods_WindowCreatedFn, "DTTR_Mod_WindowCreated") \
148 X(window_resized, DTTR_Mods_WindowResizedFn, "DTTR_Mod_WindowResized") \
149 X(window_destroying, DTTR_Mods_WindowDestroyingFn, "DTTR_Mod_WindowDestroying") \
150 X(graphics_device_created, \
151 DTTR_Mods_GraphicsDeviceCreatedFn, \
152 "DTTR_Mod_GraphicsDeviceCreated") \
153 X(graphics_device_lost, \
154 DTTR_Mods_GraphicsDeviceLostFn, \
155 "DTTR_Mod_GraphicsDeviceLost") \
156 X(graphics_device_restored, \
157 DTTR_Mods_GraphicsDeviceRestoredFn, \
158 "DTTR_Mod_GraphicsDeviceRestored") \
159 X(graphics_device_destroying, \
160 DTTR_Mods_GraphicsDeviceDestroyingFn, \
161 "DTTR_Mod_GraphicsDeviceDestroying") \
162 X(before_event, DTTR_Mods_BeforeEventFn, "DTTR_Mod_BeforeEvent") \
163 X(after_event, DTTR_Mods_AfterEventFn, "DTTR_Mod_AfterEvent") \
164 X(input_mode_changed, DTTR_Mods_InputModeChangedFn, "DTTR_Mod_InputModeChanged") \
165 X(render_game, DTTR_Mods_RenderGameFn, "DTTR_Mod_RenderGame") \
166 X(render, DTTR_Mods_RenderFn, "DTTR_Mod_Render") \
167 X(should_advance_game_frame, \
168 DTTR_Mods_ShouldAdvanceGameFrameFn, \
169 "DTTR_Mod_ShouldAdvanceGameFrame") \
170 X(game_frame_advanced, DTTR_Mods_GameFrameAdvancedFn, "DTTR_Mod_GameFrameAdvanced") \
171 X(game_frame_blocked, DTTR_Mods_GameFrameBlockedFn, "DTTR_Mod_GameFrameBlocked")
172
174 if (!mod->shadow_path[0]) {
175 return;
176 }
177
178 DeleteFileA(mod->shadow_path);
179 mod->shadow_path[0] = '\0';
180}
181
182static int find_mod(const char *filename) {
183 for (size_t i = 0; i < kv_size(loaded_mods); i++) {
184 if (strcmp(kv_A(loaded_mods, i).filename, filename) == 0) {
185 return (int)i;
186 }
187 }
188
189 return -1;
190}
191
192// Runs mod unload callbacks and removes hook ownership before freeing the DLL.
193static void unload_mod(loaded_mod *mod) {
194 if (!mod->handle) {
196 return;
197 }
198
199 if (mod->initialized) {
200 DTTR_LOG_INFO("Cleaning up mod: %s", mod->filename);
201 if (mod->before_unload) {
202 MOD_WITH_OWNER(mod, mod->before_unload());
203 }
204
205 if (mod->cleanup) {
206 MOD_WITH_OWNER(mod, mod->cleanup());
207 }
208
209 mod->initialized = false;
210 }
211
214 "Refusing to unload mod %s because one or more hooks could not be restored",
215 mod->filename
216 );
217 }
218
219 FreeLibrary(mod->handle);
220 mod->handle = NULL;
223}
224
225static void remove_mod_at(int index) {
226 if (index < 0 || (size_t)index >= kv_size(loaded_mods)) {
227 return;
228 }
229
230 unload_mod(&kv_A(loaded_mods, index));
231 const size_t last = kv_size(loaded_mods) - 1;
232 if ((size_t)index < last) {
233 memmove(
234 &kv_A(loaded_mods, index),
235 &kv_A(loaded_mods, index + 1),
236 (last - (size_t)index) * sizeof(kv_A(loaded_mods, 0))
237 );
238 }
239
240 loaded_mods.n--;
241}
242
243// Builds a unique shadow-copy path so Windows can reload a changed mod DLL.
244static bool make_shadow_path(loaded_mod *mod) {
245 const DWORD process_id = GetCurrentProcessId();
246
247 for (int attempt = 0; attempt < MOD_MAX_SHADOW_ATTEMPTS; attempt++) {
248 sds shadow_name = sdscatprintf(
249 sdsempty(),
250 DTTR_MODS_SHADOW_PREFIX "%lu_%lu_%s",
251 (unsigned long)process_id,
252 ++shadow_counter,
253 mod->filename
254 );
255 if (!shadow_name) {
256 return false;
257 }
258
259 sds shadow_path = NULL;
260 const bool made_shadow_path = make_mod_path(&shadow_path, shadow_name);
261 sdsfree(shadow_name);
262 if (!made_shadow_path) {
263 sdsfree(shadow_path);
264 return false;
265 }
266
267 const bool copied_shadow_path = DTTR_Path_CopySds(
268 mod->shadow_path,
269 sizeof(mod->shadow_path),
270 shadow_path
271 );
272 sdsfree(shadow_path);
273 if (!copied_shadow_path) {
274 return false;
275 }
276
277 if (CopyFileA(mod->source_path, mod->shadow_path, TRUE)) {
278 return true;
279 }
280
281 if (GetLastError() != ERROR_FILE_EXISTS) {
282 return false;
283 }
284 }
285
286 return false;
287}
288
290#define LOAD_OPTIONAL_EXPORT(field, fn_type, symbol) \
291 mod->field = (fn_type)GetProcAddress(mod->handle, symbol);
292
294
295#undef LOAD_OPTIONAL_EXPORT
296}
297
298static bool prepare_mod(
299 const char *filename,
300 const char *source_path,
301 const mod_file_id *source_file,
302 loaded_mod *out
303) {
304 memset(out, 0, sizeof(*out));
305 out->hook_owner = (void *)(++hook_owner_counter);
306 DTTR_Path_CopyString(out->filename, sizeof(out->filename), filename);
307 DTTR_Path_CopyString(out->source_path, sizeof(out->source_path), source_path);
308 out->source_file = *source_file;
309
310 const char *load_path = out->source_path;
311 if (dttr_config.hot_reload) {
312 if (!make_shadow_path(out)) {
314 "Failed to copy mod DLL for hot reload: %s (error %lu)",
315 filename,
316 GetLastError()
317 );
318 return false;
319 }
320
321 load_path = out->shadow_path;
322 }
323
324 out->handle = LoadLibraryA(load_path);
325 if (!out->handle) {
326 DTTR_LOG_WARN("Failed to load mod DLL: %s (error %lu)", filename, GetLastError());
328 return false;
329 }
330
331 out->init = (DTTR_Mods_InitFn)GetProcAddress(out->handle, "DTTR_Mod_Init");
332 out->cleanup = (DTTR_Mods_CleanupFn)GetProcAddress(out->handle, "DTTR_Mod_Cleanup");
333
334 if (!out->init || !out->cleanup) {
336 "Mod %s missing required exports "
337 "(DTTR_Mod_Init/DTTR_Mod_Cleanup) - skipping",
338 filename
339 );
340 unload_mod(out);
341 return false;
342 }
343
345 return true;
346}
347
348// Calls mod initialization and records ownership for hooks installed by that DLL.
349static bool init_mod(loaded_mod *mod) {
350 const DTTR_Mods_Info *info = get_mod_info(mod->info);
351 set_mod_display_name(mod, info);
352 log_mod_info(mod->filename, info);
353
354 const DTTR_Mods_Context *base_ctx = dttr_sidecar_context();
355 if (!refresh_mod_context(mod, base_ctx)) {
356 unload_mod(mod);
357 return false;
358 }
359
360 void *previous_owner = DTTR_Core_HookSetOwner(mod->hook_owner);
361 const bool initialized = mod->init(mod->context);
362 DTTR_Core_HookSetOwner(previous_owner);
363
364 if (!initialized) {
365 DTTR_LOG_WARN("Mod %s init failed - skipping", mod->filename);
366 unload_mod(mod);
367 return false;
368 }
369
370 mod->initialized = true;
371 mod->reload_pending = false;
372 mod->loaded_at_ms = GetTickCount();
373 return true;
374}
375
377 loaded_mod *mod,
378 const mod_file_id *source_file,
379 DWORD now_ms
380) {
381 if (file_id_equal(&mod->source_file, source_file)) {
382 mod->reload_pending = false;
383 return false;
384 }
385
386 if (!mod->reload_pending || !file_id_equal(&mod->pending_file, source_file)) {
387 mod->pending_file = *source_file;
388 mod->pending_since_ms = now_ms;
389 mod->reload_pending = true;
390 return false;
391 }
392
393 return now_ms - mod->pending_since_ms >= MOD_RELOAD_STABLE_MS;
394}
395
396static void load_mod(
397 const char *filename,
398 const char *source_path,
399 const mod_file_id *source_file
400) {
401 if (kv_size(loaded_mods) >= MODS_MAX) {
402 DTTR_LOG_WARN("Maximum mod count (%d) reached - skipping %s", MODS_MAX, filename);
403 return;
404 }
405
406 loaded_mod mod;
407 if (!prepare_mod(filename, source_path, source_file, &mod)) {
408 return;
409 }
410
411 if (!init_mod(&mod)) {
412 return;
413 }
414
415 kv_push(loaded_mod, loaded_mods, mod);
416 DTTR_LOG_INFO("Loaded mod: %s", filename);
417}
418
419static bool reload_mod(int index, const char *source_path, const mod_file_id *source_file) {
420 loaded_mod *old_mod = &kv_A(loaded_mods, index);
421
422 loaded_mod new_mod;
423 if (!prepare_mod(old_mod->filename, source_path, source_file, &new_mod)) {
424 return false;
425 }
426
427 DTTR_LOG_INFO("Reloading mod: %s", old_mod->filename);
428 unload_mod(old_mod);
429
430 if (!init_mod(&new_mod)) {
431 remove_mod_at(index);
432 return true;
433 }
434
435 kv_A(loaded_mods, index) = new_mod;
436 return false;
437}
438
439static void remove_missing_mods(const bool *seen, bool initial_scan) {
440 if (initial_scan) {
441 return;
442 }
443
444 for (int i = (int)kv_size(loaded_mods) - 1; i >= 0; i--) {
445 if (seen[i]) {
446 continue;
447 }
448
449 log_mod_deleted(&kv_A(loaded_mods, i));
450 remove_mod_at(i);
451 }
452}
453
454static void remove_all_mods(bool log_deleted) {
455 while (kv_size(loaded_mods) > 0) {
456 const int i = (int)kv_size(loaded_mods) - 1;
457 if (log_deleted) {
458 log_mod_deleted(&kv_A(loaded_mods, i));
459 }
460
461 remove_mod_at(i);
462 }
463}
464
465static bool scan_mod_file(
466 const WIN32_FIND_DATAA *find_data,
467 DWORD now_ms,
468 bool initial_scan,
469 bool *seen
470) {
471 if (find_data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
472 return false;
473 }
474
475 if (is_shadow_mod(find_data->cFileName)) {
476 return false;
477 }
478
479 if (DTTR_Config_IsModDisabled(&dttr_config, find_data->cFileName)) {
480 DTTR_LOG_INFO("Mod disabled, skipping: %s", find_data->cFileName);
481 return false;
482 }
483
484 sds source_path = NULL;
485 if (!make_mod_path(&source_path, find_data->cFileName)) {
486 sdsfree(source_path);
487 return false;
488 }
489
490 const mod_file_id source_file = make_mod_file_id(find_data);
491 const int index = find_mod(find_data->cFileName);
492 if (index < 0) {
493 load_mod(find_data->cFileName, source_path, &source_file);
494 const int new_index = find_mod(find_data->cFileName);
495 if (new_index >= 0) {
496 seen[new_index] = true;
497 }
498
499 sdsfree(source_path);
500 return false;
501 }
502
503 seen[index] = true;
504 bool restart_scan = false;
505 if (!initial_scan
506 && should_reload_now(&kv_A(loaded_mods, index), &source_file, now_ms)) {
507 restart_scan = reload_mod(index, source_path, &source_file);
508 }
509
510 sdsfree(source_path);
511 return restart_scan;
512}
513
514static void scan_mods(bool initial_scan) {
515 for (;;) {
516 bool seen[MODS_MAX] = {0};
517
518 sds search_pattern = NULL;
519 if (!make_mod_path(&search_pattern, "*.dll")) {
520 sdsfree(search_pattern);
521 return;
522 }
523
524 WIN32_FIND_DATAA find_data;
525 HANDLE find_handle = FindFirstFileA(search_pattern, &find_data);
526 sdsfree(search_pattern);
527
528 if (find_handle == INVALID_HANDLE_VALUE) {
529 if (initial_scan) {
530 DTTR_LOG_INFO("Loaded 0 mod(s)");
531 }
532
533 remove_all_mods(!initial_scan);
534 return;
535 }
536
537 const DWORD now_ms = GetTickCount();
538 bool restart_scan = false;
539
540 do {
541 if (scan_mod_file(&find_data, now_ms, initial_scan, seen)) {
542 restart_scan = true;
543 break;
544 }
545 } while (FindNextFileA(find_handle, &find_data));
546
547 FindClose(find_handle);
548 if (restart_scan) {
549 continue;
550 }
551
552 remove_missing_mods(seen, initial_scan);
553 return;
554 }
555}
556
557// Resolves the mod directory relative to the loader so mods live beside the game.
558static bool resolve_mods_dir() {
559 sds resolved_mods_dir = sdsnew(dttr_loader_dir);
560 if (!resolved_mods_dir
561 || !DTTR_Path_AppendSegment(&resolved_mods_dir, "mods", '\\')) {
562 sdsfree(resolved_mods_dir);
563 return false;
564 }
565
566 const DWORD attrs = GetFileAttributesA(resolved_mods_dir);
567 if (attrs == INVALID_FILE_ATTRIBUTES || !(attrs & FILE_ATTRIBUTE_DIRECTORY)) {
568 DTTR_LOG_INFO("No mods directory found at %s - skipping", resolved_mods_dir);
569 sdsfree(resolved_mods_dir);
570 return false;
571 }
572
573 const bool copied = DTTR_Path_CopySds(mods_dir, sizeof(mods_dir), resolved_mods_dir);
574 sdsfree(resolved_mods_dir);
575 return copied;
576}
577
579 if (!dttr_config.hot_reload) {
580 return;
581 }
582
583 const DWORD now_ms = GetTickCount();
584 if (now_ms - last_reload_scan_ms < MOD_RELOAD_POLL_MS) {
585 return;
586 }
587
588 last_reload_scan_ms = now_ms;
589 scan_mods(false);
590}
591
593 DTTR_LOG_INFO("Loading mods...");
594
595 if (!resolve_mods_dir()) {
596 return;
597 }
598
599 scan_mods(true);
600 last_reload_scan_ms = GetTickCount();
601
602 DTTR_LOG_INFO("Loaded %d mod(s)", (int)kv_size(loaded_mods));
603}
604
607
608 for (size_t i = 0; i < kv_size(loaded_mods); i++) {
609 loaded_mod *mod = &kv_A(loaded_mods, i);
610 if (!mod->tick) {
611 continue;
612 }
613
614 MOD_WITH_OWNER(mod, mod->tick());
615 }
616}
617
619 MOD_DISPATCH(late_init);
620}
621
623 MOD_DISPATCH(frame_begin, ctx);
624}
625
627 MOD_DISPATCH(before_game_frame, ctx);
628}
629
631 MOD_DISPATCH(after_game_frame, ctx);
632}
633
635 MOD_DISPATCH(before_present, ctx);
636}
637
639 MOD_DISPATCH(after_present, ctx);
640}
641
643 MOD_DISPATCH(frame_end, ctx);
644}
645
647 MOD_DISPATCH(imgui_begin, ctx);
648}
649
653
655 MOD_DISPATCH(overlay_visible_changed, visible);
656}
657
659 MOD_DISPATCH(window_created, ctx);
660}
661
663 MOD_DISPATCH(window_resized, ctx);
664}
665
667 MOD_DISPATCH(window_destroying, ctx);
668}
669
671 MOD_DISPATCH(graphics_device_created, ctx);
672}
673
675 MOD_DISPATCH(graphics_device_lost, ctx);
676}
677
679 MOD_DISPATCH(graphics_device_restored, ctx);
680}
681
683 MOD_DISPATCH(graphics_device_destroying, ctx);
684}
685
686static bool dispatch_event_until_consumed(const SDL_Event *event, bool before_event) {
687 for (size_t i = 0; i < kv_size(loaded_mods); i++) {
688 loaded_mod *mod = &kv_A(loaded_mods, i);
689 DTTR_Mods_EventFn event_fn = before_event ? mod->before_event : mod->event;
690 if (!event_fn) {
691 continue;
692 }
693
694 bool consumed = false;
695 MOD_WITH_OWNER(mod, consumed = event_fn(event));
696 if (consumed) {
697 return true;
698 }
699 }
700
701 return false;
702}
703
707
708void dttr_mods_after_event(const SDL_Event *event, bool consumed) {
709 MOD_DISPATCH(after_event, event, consumed);
710}
711
713 MOD_DISPATCH(input_mode_changed, ctx);
714}
715
717 for (size_t i = 0; i < kv_size(loaded_mods); i++) {
718 loaded_mod *mod = &kv_A(loaded_mods, i);
719 if (!mod->should_advance_game_frame) {
720 continue;
721 }
722
723 bool should_advance = true;
724 MOD_WITH_OWNER(mod, should_advance = mod->should_advance_game_frame());
725 if (!should_advance) {
726 return false;
727 }
728 }
729
730 return true;
731}
732
734 MOD_DISPATCH(game_frame_advanced);
735}
736
738 MOD_DISPATCH(game_frame_blocked);
739}
740
742 for (size_t i = 0; i < kv_size(loaded_mods); i++) {
743 if (kv_A(loaded_mods, i).render_game) {
744 return true;
745 }
746 }
747
748 return false;
749}
750
754
758
762
764 return kv_size(loaded_mods);
765}
766
767const char *dttr_mods_loaded_name(size_t index) {
768 if (index >= kv_size(loaded_mods)) {
769 return NULL;
770 }
771
772 loaded_mod *mod = &kv_A(loaded_mods, index);
773 return mod->display_name[0] ? mod->display_name : mod->filename;
774}
775
777 if (index >= kv_size(loaded_mods)) {
778 return 0;
779 }
780
781 return GetTickCount() - kv_A(loaded_mods, index).loaded_at_ms;
782}
783
785 return dttr_config.hot_reload;
786}
787
789 remove_all_mods(false);
790 kv_destroy(loaded_mods);
791 kv_init(loaded_mods);
792 mods_dir[0] = '\0';
793 last_reload_scan_ms = 0;
794}
void void * ctx
void DWORD DWORD * free
void void DWORD HANDLE event
DTTR_Graphics_COM_DirectDrawSurface7 DWORD flags void NULL
#define DTTR_MODS_SHADOW_PREFIX
Definition dttr_config.h:57
DTTR_Config dttr_config
Definition defaults.c:53
bool DTTR_Config_IsModDisabled(const DTTR_Config *config, const char *mod_filename)
Definition defaults.c:98
#define DTTR_FATAL(error_message,...)
Definition dttr_errors.h:30
#define DTTR_LOG_WARN(...)
Definition dttr_log.h:30
#define DTTR_LOG_INFO(...)
Definition dttr_log.h:29
void(* DTTR_Mods_CleanupFn)()
Definition dttr_mods.h:102
union SDL_Event SDL_Event
Definition dttr_mods.h:20
bool(* DTTR_Mods_EventFn)(const SDL_Event *event)
Definition dttr_mods.h:104
bool(* DTTR_Mods_InitFn)(const DTTR_Mods_Context *ctx)
Definition dttr_mods.h:101
const DTTR_Mods_Info *(* DTTR_Mods_InfoFn)()
Definition dttr_mods.h:105
bool DTTR_Path_CopyString(char *out, size_t out_size, const char *value)
Definition path.c:68
bool DTTR_Path_CopySds(char *out, size_t out_size, sds value)
Definition path.c:72
bool DTTR_Path_AppendSegment(sds *path, const char *segment, char separator)
Definition path.c:276
bool DTTR_Core_HookDetachOwnerChecked(void *owner)
void * DTTR_Core_HookSetOwner(void *owner)
const DTTR_Mods_Context * dttr_sidecar_context()
Definition entrypoint.c:152
char dttr_exe_hash[DTTR_EXE_HASH_LENGTH+1]
Definition entrypoint.c:37
char dttr_loader_dir[MAX_PATH]
Definition entrypoint.c:36
SDL_Window * dttr_graphics_get_window()
Definition graphics.c:578
void dttr_mods_render_game(const DTTR_Mods_RenderGameContext *ctx)
Definition mods.c:751
void dttr_mods_input_mode_changed(const DTTR_Mods_InputContext *ctx)
Definition mods.c:712
static void log_mod_info(const char *filename, const DTTR_Mods_Info *info)
Definition mods.c:80
static void attempt_hot_reload_mods()
Definition mods.c:578
#define MOD_RELOAD_POLL_MS
Definition mods.c:19
void dttr_mods_tick()
Definition mods.c:605
static void set_mod_display_name(loaded_mod *mod, const DTTR_Mods_Info *info)
Definition mods.c:75
void dttr_mods_overlay_visible_changed(bool visible)
Definition mods.c:654
static void destroy_mod_context(loaded_mod *mod)
Definition mods.c:94
void dttr_mods_render(const DTTR_Mods_RenderContext *ctx)
Definition mods.c:755
static void load_mod(const char *filename, const char *source_path, const mod_file_id *source_file)
Definition mods.c:396
void dttr_mods_window_destroying(const DTTR_Mods_WindowContext *ctx)
Definition mods.c:666
static bool resolve_mods_dir()
Definition mods.c:558
void dttr_mods_after_game_frame(const DTTR_Mods_FrameContext *ctx)
Definition mods.c:630
static void remove_all_mods(bool log_deleted)
Definition mods.c:454
void dttr_mods_imgui_end(const DTTR_Mods_RenderContext *ctx)
Definition mods.c:650
static void delete_shadow_copy(loaded_mod *mod)
Definition mods.c:173
static void scan_mods(bool initial_scan)
Definition mods.c:514
void dttr_mods_frame_begin(const DTTR_Mods_FrameContext *ctx)
Definition mods.c:622
static bool refresh_mod_context(loaded_mod *mod, const DTTR_Mods_Context *base_ctx)
Definition mods.c:100
static bool make_mod_path(sds *out, const char *filename)
Definition mods.c:56
#define MOD_MAX_SHADOW_ATTEMPTS
Definition mods.c:17
void dttr_mods_before_present(const DTTR_Mods_PresentContext *ctx)
Definition mods.c:634
size_t dttr_mods_loaded_count()
Definition mods.c:763
static const DTTR_Mods_Info * get_mod_info(DTTR_Mods_InfoFn info_fn)
Definition mods.c:71
void dttr_mods_late_init()
Definition mods.c:618
bool dttr_mods_handle_event(const SDL_Event *event)
Definition mods.c:759
void dttr_mods_graphics_device_destroying(const DTTR_Mods_GraphicsContext *ctx)
Definition mods.c:682
void dttr_mods_after_present(const DTTR_Mods_PresentContext *ctx)
Definition mods.c:638
void dttr_mods_window_resized(const DTTR_Mods_WindowContext *ctx)
Definition mods.c:662
void dttr_mods_imgui_begin(const DTTR_Mods_RenderContext *ctx)
Definition mods.c:646
static bool dispatch_event_until_consumed(const SDL_Event *event, bool before_event)
Definition mods.c:686
bool dttr_mods_has_render_game()
Definition mods.c:741
static bool prepare_mod(const char *filename, const char *source_path, const mod_file_id *source_file, loaded_mod *out)
Definition mods.c:298
void dttr_mods_cleanup()
Definition mods.c:788
#define MOD_DISPATCH(field,...)
Definition mods.c:120
static bool file_id_equal(const mod_file_id *lhs, const mod_file_id *rhs)
Definition mods.c:43
DWORD dttr_mods_loaded_elapsed_ms(size_t index)
Definition mods.c:776
static bool make_shadow_path(loaded_mod *mod)
Definition mods.c:244
static mod_file_id make_mod_file_id(const WIN32_FIND_DATAA *find_data)
Definition mods.c:48
static void remove_missing_mods(const bool *seen, bool initial_scan)
Definition mods.c:439
static bool is_shadow_mod(const char *filename)
Definition mods.c:66
static void remove_mod_at(int index)
Definition mods.c:225
void dttr_mods_init()
Definition mods.c:592
#define MOD_WITH_OWNER(mod, call)
Definition mods.c:113
static int find_mod(const char *filename)
Definition mods.c:182
void dttr_mods_frame_end(const DTTR_Mods_FrameContext *ctx)
Definition mods.c:642
static bool should_reload_now(loaded_mod *mod, const mod_file_id *source_file, DWORD now_ms)
Definition mods.c:376
void dttr_mods_game_frame_blocked()
Definition mods.c:737
typedef kvec_t(loaded_mod)
Definition mods.c:21
bool dttr_mods_should_advance_game_frame()
Definition mods.c:716
static void unload_mod(loaded_mod *mod)
Definition mods.c:193
void dttr_mods_graphics_device_created(const DTTR_Mods_GraphicsContext *ctx)
Definition mods.c:670
void dttr_mods_window_created(const DTTR_Mods_WindowContext *ctx)
Definition mods.c:658
static bool reload_mod(int index, const char *source_path, const mod_file_id *source_file)
Definition mods.c:419
#define MOD_OPTIONAL_EXPORTS(X)
Definition mods.c:130
static bool scan_mod_file(const WIN32_FIND_DATAA *find_data, DWORD now_ms, bool initial_scan, bool *seen)
Definition mods.c:465
const char * dttr_mods_loaded_name(size_t index)
Definition mods.c:767
#define MOD_RELOAD_STABLE_MS
Definition mods.c:18
bool dttr_mods_before_event(const SDL_Event *event)
Definition mods.c:704
static void log_mod_deleted(const loaded_mod *mod)
Definition mods.c:61
void dttr_mods_before_game_frame(const DTTR_Mods_FrameContext *ctx)
Definition mods.c:626
void dttr_mods_game_frame_advanced()
Definition mods.c:733
static void load_optional_exports(loaded_mod *mod)
Definition mods.c:289
static bool init_mod(loaded_mod *mod)
Definition mods.c:349
void dttr_mods_graphics_device_restored(const DTTR_Mods_GraphicsContext *ctx)
Definition mods.c:678
void dttr_mods_graphics_device_lost(const DTTR_Mods_GraphicsContext *ctx)
Definition mods.c:674
void dttr_mods_after_event(const SDL_Event *event, bool consumed)
Definition mods.c:708
#define LOAD_OPTIONAL_EXPORT(field, fn_type, symbol)
bool dttr_mods_hot_reload_enabled()
Definition mods.c:784
#define MODS_MAX
uint32_t struct_size
Definition dttr_mods.h:58
HMODULE sidecar_module
Definition dttr_mods.h:84
const void * config
Definition dttr_mods.h:88
uint32_t abi_version
Definition dttr_mods.h:82
DTTR_Core_Context runtime
Definition dttr_mods.h:83
const DTTR_Mods_API * api
Definition dttr_mods.h:89
const char * author
Definition dttr_mods.h:98
const char * version
Definition dttr_mods.h:97
const char * name
Definition dttr_mods.h:96
void * hook_owner
DTTR_Mods_Context * context
bool reload_pending
HMODULE handle
bool initialized
DTTR_Mods_CleanupFn cleanup
char filename[MAX_PATH]
mod_file_id pending_file
DWORD pending_since_ms
DTTR_Mods_ShouldAdvanceGameFrameFn should_advance_game_frame
DTTR_Mods_InitFn init
DTTR_Mods_EventFn event
char display_name[MAX_PATH]
DTTR_Mods_TickFn tick
DTTR_Mods_BeforeEventFn before_event
mod_file_id source_file
char shadow_path[MAX_PATH]
DWORD loaded_at_ms
char source_path[MAX_PATH]
DTTR_Mods_BeforeUnloadFn before_unload
DTTR_Mods_InfoFn info
DWORD size_high
FILETIME write_time