102 Patches: Detours to the Rescue
C reference for DttR maintainers and modders.
Loading...
Searching...
No Matches
hook_crt_open_file.c
Go to the documentation of this file.
1#include "hooks_private.h"
2#include "sidecar_private.h"
3#include <dttr_config.h>
4#include <dttr_log.h>
5#include <dttr_path.h>
6#include <dttr_pcdogs.h>
7#include <dttr_sdl.h>
8
9#include <SDL3/SDL.h>
10#include <sds.h>
11#include <sys/stat.h>
12#include <windows.h>
13
14#define IS_READ_ONLY_MODE(m) ((m) && (m)[0] == 'r' && !strchr((m), '+'))
15
16// Calls the game's CRT wrapper with the sharing flag expected by original file access.
18 const char *path,
19 const char *mode,
20 uint8_t sharing_flag
21) {
25 path,
26 mode,
27 sharing_flag,
28 &handle
29 );
30 if (!DTTR_ResultOK(result)) {
32 "File_OpenWithMode failed for \"%s\" (mode \"%s\"): %s",
33 path,
34 mode,
36 );
37 return NULL;
38 }
39
40 return handle;
41}
42
43// Accepts only non-empty relative paths for save redirection and game-data lookup.
44static bool is_relative_path(const char *path) {
45 return DTTR_Path_IsSafeRelative(path);
46}
47
48// Detects write modes so permission repair prompts for the correct file bits.
49static bool mode_wants_write(const char *mode) {
50 return mode && (strchr(mode, 'w') || strchr(mode, 'a') || strchr(mode, '+'));
51}
52
53static bool redirect_saves_initialized = false;
54
55// Creates save directories only after save redirection resolves a path.
56static void create_dir_if_set(const char *path) {
57 if (path && path[0]) {
58 CreateDirectoryA(path, NULL);
59 }
60}
61
62// Resolves the root save directory used to move game writes out of the install tree.
63static void build_saves_dir(char *buf, size_t buf_size) {
64 sds dir = NULL;
65 if (is_relative_path(dttr_config.saves_path)) {
66 dir = sdsnew(dttr_loader_dir);
67 if (!dir || !DTTR_Path_AppendSegment(&dir, dttr_config.saves_path, '\\')) {
68 sdsfree(dir);
69 buf[0] = '\0';
70 return;
71 }
72 } else {
73 dir = sdsnew(dttr_config.saves_path);
74 }
75
76 if (!DTTR_Path_CopySds(buf, buf_size, dir)) {
77 buf[0] = '\0';
78 }
79
80 sdsfree(dir);
81}
82
83// Maps slot-specific saves into the redirected save root.
84static void build_save_slot_dir(char *buf, size_t buf_size) {
85 build_saves_dir(buf, buf_size);
86
87 sds dir = sdsnew(buf);
88 if (!dir || !DTTR_Path_AppendSegment(&dir, dttr_exe_hash, '\\')
89 || !DTTR_Path_CopySds(buf, buf_size, dir)) {
90 buf[0] = '\0';
91 }
92
93 sdsfree(dir);
94}
95
96// Creates redirected save folders before the CRT hook returns a writable path.
97static void ensure_save_dir() {
99 return;
100 }
101
103
104 char dir[MAX_PATH];
105 build_saves_dir(dir, sizeof(dir));
107
108 build_save_slot_dir(dir, sizeof(dir));
110}
111
112// Redirects relative save writes into the configured per-executable save directory.
113static const char *redirect_path(
114 const char *path,
115 char *buf,
116 size_t buf_size,
117 const char *mode
118) {
119 if (!dttr_config.saves_path[0]) {
120 return path;
121 }
122
123 if (!is_relative_path(path)) {
124 return path;
125 }
126
128
129 build_save_slot_dir(buf, buf_size);
130 sds redirected = sdsnew(buf);
131 if (!redirected || !DTTR_Path_AppendSegment(&redirected, path, '\\')
132 || !DTTR_Path_CopySds(buf, buf_size, redirected)) {
133 sdsfree(redirected);
134 return path;
135 }
136
137 sdsfree(redirected);
138
139 if (IS_READ_ONLY_MODE(mode) && !DTTR_Path_ExactExists(buf)) {
140 return path;
141 }
142
143 DTTR_LOG_DEBUG("Redirecting \"%s\" -> \"%s\"", path, buf);
144 return buf;
145}
146
147// Reports a failed write/open path without pretending the CRT call succeeded.
149 const char *path,
150 const char *mode
151) {
152 sds msg = sdscatprintf(
153 sdsempty(),
154 "Failed to open \"%s\" (mode \"%s\"). This file will not be written.\n\n%s",
155 path,
156 mode,
157 strerror(errno)
158 );
159 DTTR_SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "DttR: File Error", msg, NULL);
160 sdsfree(msg);
161
162 return NULL;
163}
164
165// Offers to repair file permissions when Wine or the host blocks a requested write.
166static DTTR_PCDOGS_T_File_Handle *try_fix_permissions(const char *path, const char *mode) {
167 const bool wants_write = mode_wants_write(mode);
168 const int perms = ((mode && strchr(mode, 'r')) ? _S_IREAD : 0)
169 | (wants_write ? _S_IWRITE : 0);
170
172 "Permission error opening \"%s\" (mode \"%s\"): %s",
173 path,
174 mode,
175 strerror(errno)
176 );
177
178 sds prompt = sdscatprintf(
179 sdsempty(),
180 "Failed to open file \"%s\" (mode \"%s\"): %s\n\n"
181 "This is typically the result of a permissions issue, especially if you're "
182 "using "
183 "Wine.\n\n"
184 "Try granting permissions 0o%03o?",
185 path,
186 mode,
187 strerror(errno),
188 perms
189 );
190
191 const SDL_MessageBoxButtonData buttons[] = {
192 {SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 0, "No"},
193 {SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 1, "Yes"},
194 };
195
196 const SDL_MessageBoxData msgbox = {
197 .flags = SDL_MESSAGEBOX_WARNING,
198 .window = NULL,
199 .title = "DttR: File Permission Error",
200 .message = prompt,
201 .numbuttons = 2,
202 .buttons = buttons,
203 };
204
205 int button_id = 0;
206 DTTR_SDL_ShowMessageBox(&msgbox, &button_id);
207 sdsfree(prompt);
208
209 if (button_id != 1) {
210 return NULL;
211 }
212
213 DTTR_LOG_DEBUG("chmod \"%s\" 0o%03o", path, perms);
214 chmod(path, perms);
215
216 DTTR_PCDOGS_T_File_Handle *result = file_open_with_mode(path, mode, 0x40);
217 if (result) {
218 return result;
219 }
220
222 "chmod didn't resolve permission error for \"%s\": %s",
223 path,
224 strerror(errno)
225 );
226 return NULL;
227}
228
229// Resolves case-insensitive and ISO-backed read paths before the game sees a miss.
230static DTTR_PCDOGS_T_File_Handle *try_open_read_path(const char *path, const char *mode) {
231 char resolved[MAX_PATH];
232
233 if (dttr_game_data_resolve_existing_read_path(path, resolved, sizeof(resolved))) {
234 DTTR_LOG_DEBUG("Resolved case-insensitive read \"%s\" -> \"%s\"", path, resolved);
235 return file_open_with_mode(resolved, mode, 0x40);
236 }
237
238 char cached[MAX_PATH];
239 if (!dttr_game_data_resolve_read_path(path, cached, sizeof(cached))) {
240 return NULL;
241 }
242
243 DTTR_LOG_DEBUG("Resolved ISO-backed read \"%s\" -> \"%s\"", path, cached);
244 return file_open_with_mode(cached, mode, 0x40);
245}
246
247// Replaces the game file-open callback with save redirection plus data-file fallback.
249 const char *path,
250 const char *mode
251) {
252 char redirected[MAX_PATH];
253 path = redirect_path(path, redirected, sizeof(redirected), mode);
254
255 DTTR_PCDOGS_T_File_Handle *result = file_open_with_mode(path, mode, 0x40);
256 if (result) {
257 return result;
258 }
259
260 if (IS_READ_ONLY_MODE(mode) || errno == 0 || errno == ENOENT) {
261 if (IS_READ_ONLY_MODE(mode)) {
262 result = try_open_read_path(path, mode);
263 if (result) {
264 return result;
265 }
266 }
267
268 DTTR_LOG_ERROR("File \"%s\" does not exist; passing to game.", path);
269 return result;
270 }
271
272 const bool wants_write = mode_wants_write(mode);
273 const bool is_perm_error
274 = (errno == EACCES || errno == EPERM || (errno == EBADF && wants_write));
275
276 if (is_perm_error) {
277 result = try_fix_permissions(path, mode);
278 if (result) {
279 return result;
280 }
281 } else {
283 "Failed to open \"%s\" (mode \"%s\"): %s",
284 path,
285 mode,
286 strerror(errno)
287 );
288 }
289
290 return report_file_open_failure(path, mode);
291}
DTTR_Graphics_COM_DirectDrawSurface7 DWORD flags void NULL
DTTR_Config dttr_config
Definition defaults.c:53
#define DTTR_LOG_DEBUG(...)
Definition dttr_log.h:28
#define DTTR_LOG_ERROR(...)
Definition dttr_log.h:31
bool DTTR_Path_CopySds(char *out, size_t out_size, sds value)
Definition path.c:72
bool DTTR_Path_ExactExists(const char *path)
Definition path.c:187
bool DTTR_Path_AppendSegment(sds *path, const char *segment, char separator)
Definition path.c:276
bool DTTR_Path_IsSafeRelative(const char *path)
Definition path.c:107
DTTR_PCDOGS_API const struct dttr_pcdogs_function_accessor_File_OpenWithMode *const DTTR_PCDOGS_F_File_OpenWithMode
Accessor object for File_OpenWithMode.
bool DTTR_ResultOK(DTTR_Result result)
Definition core.c:78
bool DTTR_SDL_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
Definition sdl.c:135
bool DTTR_SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags flags, const char *title, const char *message, SDL_Window *window)
Definition sdl.c:121
const DTTR_Core_Context * dttr_sidecar_runtime_context()
Definition entrypoint.c:156
char dttr_exe_hash[DTTR_EXE_HASH_LENGTH+1]
Definition entrypoint.c:37
char dttr_loader_dir[MAX_PATH]
Definition entrypoint.c:36
bool dttr_game_data_resolve_read_path(const char *path, char *out_path, size_t out_path_size)
Definition game_data.c:184
bool dttr_game_data_resolve_existing_read_path(const char *path, char *out_path, size_t out_path_size)
Definition game_data.c:94
#define IS_READ_ONLY_MODE(m)
static void ensure_save_dir()
static DTTR_PCDOGS_T_File_Handle * try_open_read_path(const char *path, const char *mode)
static DTTR_PCDOGS_T_File_Handle * file_open_with_mode(const char *path, const char *mode, uint8_t sharing_flag)
static DTTR_PCDOGS_T_File_Handle * report_file_open_failure(const char *path, const char *mode)
static const char * redirect_path(const char *path, char *buf, size_t buf_size, const char *mode)
static bool redirect_saves_initialized
DTTR_PCDOGS_T_File_Handle * dttr_crt_hook_open_file_callback(const char *path, const char *mode)
static bool mode_wants_write(const char *mode)
static bool is_relative_path(const char *path)
static void build_saves_dir(char *buf, size_t buf_size)
static DTTR_PCDOGS_T_File_Handle * try_fix_permissions(const char *path, const char *mode)
static void create_dir_if_set(const char *path)
static void build_save_slot_dir(char *buf, size_t buf_size)
static const char * dttr_sidecar_result_detail(DTTR_Result result)
CRT-compatible file handle layout, used by package and asset loading streams.