Skip to content

Commit ba14975

Browse files
committed
Less gross way to override boot.config
1 parent b5cea01 commit ba14975

5 files changed

Lines changed: 225 additions & 49 deletions

File tree

src/hooks.zig

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,23 @@ comptime {
1616
};
1717
}
1818

19-
pub var defaultBootConfigPath: [:0]os_char = undefined;
19+
pub var defaultBootConfig: util.file_identity.FileIdentity = undefined;
2020

2121
/// The returned buffer is **not** allocated using `util.alloc`.
2222
export fn initDefaultBootConfigPath() void {
23-
switch (builtin.os.tag) {
24-
.macos => {
23+
const path = switch (builtin.os.tag) {
24+
.macos => blk: {
2525
const program_path = util.paths.programPath();
2626
defer util.alloc.free(program_path);
2727
const app_folder = util.paths.getFolderNameRef(util.paths.getFolderNameRef(program_path));
2828

29-
defaultBootConfigPath = std.fmt.allocPrintZ(
29+
break :blk std.fmt.allocPrintZ(
3030
alloc,
3131
"{s}/Resources/Data/boot.config",
3232
.{app_folder},
3333
) catch @panic("Out of memory");
3434
},
35-
// This code is equivalent to the `else` case, and they could be collapsed into
36-
// just this one. However, I'm leaving the `else` case as it is more readable
37-
// than this one
38-
.windows => {
35+
.windows => blk: {
3936
const working_dir = util.paths.getWorkingDir();
4037
defer util.alloc.free(working_dir);
4138
const program_path = util.paths.programPath();
@@ -49,33 +46,44 @@ export fn initDefaultBootConfigPath() void {
4946
};
5047

5148
var buf = std.ArrayListUnmanaged(os_char){};
49+
5250
buf.ensureTotalCapacityPrecise(
5351
alloc,
5452
working_dir.len + 1 + file_name.len + suffix.len + 1,
5553
) catch @panic("Out of memory");
54+
errdefer buf.deinit(alloc);
5655

5756
buf.appendSliceAssumeCapacity(working_dir);
5857
buf.appendAssumeCapacity(std.fs.path.sep);
5958
buf.appendSliceAssumeCapacity(file_name);
6059
buf.appendSliceAssumeCapacity(suffix);
6160
buf.appendAssumeCapacity(0);
6261

63-
defaultBootConfigPath = buf.items[0 .. buf.items.len - 1 :0];
62+
break :blk buf.items[0 .. buf.items.len - 1 :0];
6463
},
65-
else => {
64+
else => blk: {
6665
const working_dir = util.paths.getWorkingDir();
6766
defer util.alloc.free(working_dir);
6867
const program_path = util.paths.programPath();
6968
defer util.alloc.free(program_path);
7069
const file_name = util.paths.getFileNameRef(program_path, false);
7170

72-
defaultBootConfigPath = std.fmt.allocPrintZ(
71+
break :blk std.fmt.allocPrintZ(
7372
alloc,
7473
"{s}" ++ std.fs.path.sep_str ++ "{s}_Data" ++ std.fs.path.sep_str ++ "boot.config",
7574
.{ working_dir, file_name },
7675
) catch @panic("Out of memory");
7776
},
78-
}
77+
};
78+
79+
defer alloc.free(path);
80+
81+
defaultBootConfig = util.file_identity.getFileIdentity(null, path) catch |e| {
82+
std.debug.panic("Failed to get identity of default boot.config file at \"{s}\": {}", .{
83+
if (builtin.os.tag == .windows) std.unicode.fmtUtf16Le(path) else path,
84+
e,
85+
});
86+
};
7987
}
8088

8189
fn capture_mono_path(handle: ?*anyopaque) void {
@@ -145,7 +153,9 @@ const bootstrap = @cImport(@cInclude("bootstrap.h"));
145153
export fn dlsym_hook(handle: Module, name_ptr: [*:0]const u8) ?*anyopaque {
146154
const name = std.mem.span(name_ptr);
147155

148-
root.logger.debug("dlsym({*}, \"{s}\")", .{ handle, name });
156+
if (builtin.mode == .Debug) {
157+
root.logger.debug(std.fmt.comptimePrint("dlsym(0x{{?x:0>{}}}, \"{{s}}\")", .{@sizeOf(*anyopaque) * 2}), .{ handle, name });
158+
}
149159

150160
inline for (.{
151161
.{ "il2cpp_init", bootstrap.load_il2cpp_funcs, &bootstrap.init_il2cpp, false },

src/nix/hooks.zig

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,28 @@ fn export_fopen_hook(comptime real_fn: @TypeOf(std.c.fopen), comptime name: []co
1818
comptime {
1919
const f = struct {
2020
fn fopen_hook(noalias filename: [*:0]const u8, noalias mode: [*:0]const u8) callconv(.c) ?*std.c.FILE {
21-
var open_filename = filename;
22-
23-
if (std.mem.eql(u8, std.mem.span(filename), root.hooks.defaultBootConfigPath)) {
24-
open_filename = root.config.boot_config_override;
25-
root.logger.debug("Overriding boot.config to {s}", .{open_filename});
21+
const stream = real_fn(filename, mode) orelse return null;
22+
23+
const fd = fileno(stream);
24+
25+
const id = root.util.file_identity.getFileIdentity(fd, "") catch |e| {
26+
root.logger.err("Failed to get identity of file \"{s}\": {}", .{ filename, e });
27+
return stream;
28+
};
29+
30+
if (root.util.file_identity.are_same(id, root.hooks.defaultBootConfig)) {
31+
const rc = std.posix.system.fclose(stream);
32+
if (rc != 0) {
33+
switch (std.posix.errno(rc)) {
34+
.BADF => unreachable,
35+
else => |err| std.posix.unexpectedErrno(err) catch {},
36+
}
37+
}
38+
root.logger.debug("Overriding boot.config to \"{s}\"", .{root.config.boot_config_override});
39+
return real_fn(root.config.boot_config_override, mode);
2640
}
2741

28-
return real_fn(open_filename, mode);
42+
return stream;
2943
}
3044
}.fopen_hook;
3145
@export(&f, .{ .name = name });

src/util.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const builtin = @import("builtin");
22
const std = @import("std");
33

4+
pub const file_identity = @import("util/file_identity.zig");
45
pub const paths = @import("util/paths.zig");
56

67
/// The allocator used by any C-export APIs, and any APIs marked as such.

src/util/file_identity.zig

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
const builtin = @import("builtin");
2+
const std = @import("std");
3+
4+
const root = @import("../root.zig");
5+
6+
const os_char = root.util.os_char;
7+
8+
pub const FileIdentity = switch (builtin.os.tag) {
9+
.linux => struct {
10+
dev_major: u32,
11+
dev_minor: u32,
12+
ino: u64,
13+
},
14+
.macos => struct {
15+
dev: i32,
16+
ino: u64,
17+
},
18+
.windows => FILE_ID_INFO,
19+
else => @compileError("Unsupported OS"),
20+
};
21+
22+
pub fn are_same(a: FileIdentity, b: FileIdentity) bool {
23+
return switch (builtin.os.tag) {
24+
.linux => a.dev_major == b.dev_major and a.dev_minor == b.dev_minor and a.ino == b.ino,
25+
.macos => a.dev == b.dev and a.ino == b.ino,
26+
.windows => a.volume_serial_number == b.volume_serial_number and std.mem.eql(u8, &a.file_id.identifier, &b.file_id.identifier),
27+
else => @compileError("Unsupported OS"),
28+
};
29+
}
30+
31+
const Handle = if (builtin.os.tag == .windows) std.os.windows.HANDLE else i32;
32+
33+
/// If path is empty, `dir` itself will be inspected and can be any kind of file handle.
34+
pub fn getFileIdentity(dir: ?Handle, path: [:0]const os_char) !FileIdentity {
35+
switch (builtin.os.tag) {
36+
.linux => {
37+
var buf: std.os.linux.Statx = undefined;
38+
if (std.os.linux.statx(
39+
dir orelse std.os.linux.AT.FDCWD,
40+
path,
41+
if (path.len == 0) std.os.linux.AT.EMPTY_PATH else 0,
42+
std.os.linux.STATX_INO,
43+
&buf,
44+
) != 0) {
45+
return switch (std.posix.errno(std.c._errno().*)) {
46+
.ACCES => error.AccessDenied,
47+
.BADF => unreachable,
48+
.FAULT => unreachable,
49+
.INVAL => unreachable,
50+
.LOOP => error.SymLinkLoop,
51+
.NAMETOOLONG => error.NameTooLong,
52+
.NOENT => error.FileNotFound,
53+
.NOMEM => error.SystemResources,
54+
.NOTDIR => error.NotDir,
55+
else => |err| std.posix.unexpectedErrno(err),
56+
};
57+
}
58+
return .{ .dev_major = buf.dev_major, .dev_minor = buf.dev_minor, .ino = buf.ino };
59+
},
60+
.macos => {
61+
if (dir != null and path.len != 0) {
62+
return error.Unsupported;
63+
}
64+
var buf: std.c.Stat = undefined;
65+
if ((if (dir) |fd| std.c.fstat(fd, &buf) else std.c.stat(path, &buf)) != 0) {
66+
return switch (std.posix.errno(std.c._errno().*)) {
67+
.ACCES => error.AccessDenied,
68+
.IO => error.FileSystem,
69+
.BADF => unreachable,
70+
.FAULT => unreachable,
71+
.LOOP => error.SymLinkLoop,
72+
.NAMETOOLONG => error.NameTooLong,
73+
.NOENT => error.FileNotFound,
74+
.NOTDIR => error.NotDir,
75+
.OVERFLOW => error.FileTooBig,
76+
else => |err| std.posix.unexpectedErrno(err),
77+
};
78+
}
79+
return .{ .dev = buf.dev, .ino = buf.ino };
80+
},
81+
.windows => {
82+
if (dir != null and path.len != 0) {
83+
return error.Unsupported;
84+
}
85+
86+
const handle = try std.os.windows.OpenFile(path, .{
87+
// Not sure if this is sufficient. Might need GENERIC_READ.
88+
.access_mask = 0,
89+
.creation = std.os.windows.OPEN_EXISTING,
90+
});
91+
defer std.os.windows.CloseHandle(handle);
92+
93+
return getFileInfo(handle, .FileIdInfo);
94+
},
95+
else => @compileError("Unsupported OS"),
96+
}
97+
}
98+
99+
extern "kernel32" fn GetFileInformationByHandleEx(
100+
in_hFile: std.os.windows.HANDLE,
101+
in_FileInformationClass: std.os.windows.FILE_INFO_BY_HANDLE_CLASS,
102+
out_lpFileInformation: *anyopaque,
103+
in_dwBufferSize: std.os.windows.DWORD,
104+
) callconv(.winapi) std.os.windows.BOOL;
105+
106+
const FILE_ID_INFO = extern struct {
107+
volume_serial_number: u64,
108+
file_id: extern struct {
109+
identifier: [16]u8,
110+
},
111+
};
112+
113+
fn GetFileInfo(comptime class: std.os.windows.FILE_INFO_BY_HANDLE_CLASS) type {
114+
return switch (class) {
115+
.FileBasicInfo => std.os.windows.FILE_BASIC_INFORMATION,
116+
.FileStandardInfo => std.os.windows.FILE_STANDARD_INFORMATION,
117+
.FileNameInfo => std.os.windows.FILE_NAME_INFO,
118+
.FileRenameInfo => std.os.windows.FILE_RENAME_INFORMATION_EX,
119+
.FileDispositionInfo => std.os.windows.FILE_DISPOSITION_INFORMATION_EX,
120+
.FileEndOfFileInfo => std.os.windows.FILE_END_OF_FILE_INFORMATION,
121+
.FileAttributeTagInfo => std.os.windows.FILE_ATTRIBUTE_TAG_INFO,
122+
.FileAlignmentInfo => std.os.windows.FILE_ALIGNMENT_INFORMATION,
123+
.FileIdInfo => FILE_ID_INFO,
124+
else => @compileError("Type of class " ++ @tagName(class) ++ " is unknown"),
125+
};
126+
}
127+
128+
fn getFileInfo(file: std.os.windows.HANDLE, comptime class: std.os.windows.FILE_INFO_BY_HANDLE_CLASS) error{Unexpected}!GetFileInfo(class) {
129+
return switch (class) {
130+
inline else => |c| {
131+
var buf: GetFileInfo(c) = undefined;
132+
if (GetFileInformationByHandleEx(file, class, &buf, @sizeOf(@TypeOf(buf))) == 0) {
133+
return std.os.windows.unexpectedError(std.os.windows.GetLastError());
134+
}
135+
return buf;
136+
},
137+
};
138+
}

src/windows/hooks.zig

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ const root = @import("../root.zig");
55
const alloc = root.alloc;
66
const os_char = root.util.os_char;
77

8-
// TODO: use GetFileInformationByHandleEx to compare the files with absolute certainty instead comparing paths.
9-
108
extern var stdout_handle: ?std.os.windows.HANDLE;
119
extern var stderr_handle: ?std.os.windows.HANDLE;
1210
export fn close_handle_hook(handle: std.os.windows.HANDLE) callconv(.winapi) root.util.c_bool {
@@ -25,7 +23,15 @@ extern "kernel32" fn CreateFileA(
2523
hTemplateFile: ?std.os.windows.HANDLE,
2624
) callconv(.winapi) std.os.windows.HANDLE;
2725

28-
fn create_file_hook(comptime char: type) *const anyopaque {
26+
fn create_file_hook(comptime char: type, comptime real_fn: fn (
27+
lpFileName: [*:0]const char,
28+
dwDesiredAccess: std.os.windows.DWORD,
29+
dwShareMode: std.os.windows.DWORD,
30+
lpSecurityAttributes: ?*std.os.windows.SECURITY_ATTRIBUTES,
31+
dwCreationDisposition: std.os.windows.DWORD,
32+
dwFlagsAndAttributes: std.os.windows.DWORD,
33+
hTemplateFile: ?std.os.windows.HANDLE,
34+
) callconv(.winapi) std.os.windows.HANDLE) *const anyopaque {
2935
return struct {
3036
fn create_file_hook(
3137
lpFileName: [*:0]const char,
@@ -36,43 +42,50 @@ fn create_file_hook(comptime char: type) *const anyopaque {
3642
dwFlagsAndAttributes: std.os.windows.DWORD,
3743
hTemplateFile: ?std.os.windows.HANDLE,
3844
) callconv(.winapi) std.os.windows.HANDLE {
39-
const normalized_path = switch (char) {
40-
os_char => alloc.dupeZ(os_char, std.mem.span(lpFileName)) catch @panic("Out of memory"),
41-
u8 => std.unicode.utf8ToUtf16LeAllocZ(alloc, std.mem.span(lpFileName)) catch |e| switch (e) {
42-
error.OutOfMemory => @panic("Out of memory"),
43-
error.InvalidUtf8 => @panic("Invalid ASCII provided to CreateFileA"),
44-
},
45-
else => @compileError("Unexpected char type: " ++ @typeName(char)),
46-
};
47-
48-
defer alloc.free(normalized_path);
49-
for (normalized_path) |*c| {
50-
if (c.* == '/') {
51-
c.* = '\\';
52-
}
53-
}
54-
55-
var open_path: [*:0]const u16 = normalized_path.ptr;
56-
57-
if (std.mem.eql(os_char, normalized_path, root.hooks.defaultBootConfigPath)) {
58-
open_path = root.config.boot_config_override;
59-
root.logger.debug("Overriding boot.config to {s}", .{std.unicode.fmtUtf16Le(std.mem.span(open_path))});
60-
}
61-
62-
return std.os.windows.kernel32.CreateFileW(
63-
open_path,
45+
const handle = real_fn(
46+
lpFileName,
6447
dwDesiredAccess,
6548
dwShareMode,
6649
lpSecurityAttributes,
6750
dwCreationDisposition,
6851
dwFlagsAndAttributes,
6952
hTemplateFile,
7053
);
54+
if (handle == std.os.windows.INVALID_HANDLE_VALUE) {
55+
// caller can handle the error
56+
return handle;
57+
}
58+
59+
const id = root.util.file_identity.getFileIdentity(handle, &.{}) catch |e| {
60+
root.logger.err("Failed to get identity of file \"{s}\": {}", .{ switch (char) {
61+
u8 => lpFileName,
62+
u16 => std.unicode.fmtUtf16Le(std.mem.span(lpFileName)),
63+
else => comptime unreachable,
64+
}, e });
65+
return handle;
66+
};
67+
68+
if (root.util.file_identity.are_same(id, root.hooks.defaultBootConfig)) {
69+
std.os.windows.CloseHandle(handle);
70+
root.logger.debug("Overriding boot.config to \"{s}\"", .{std.unicode.fmtUtf16Le(std.mem.span(root.config.boot_config_override))});
71+
// caller can handle the error
72+
return std.os.windows.kernel32.CreateFileW(
73+
root.config.boot_config_override,
74+
dwDesiredAccess,
75+
dwShareMode,
76+
lpSecurityAttributes,
77+
dwCreationDisposition,
78+
dwFlagsAndAttributes,
79+
hTemplateFile,
80+
);
81+
}
82+
83+
return handle;
7184
}
7285
}.create_file_hook;
7386
}
7487

7588
comptime {
76-
@export(&create_file_hook(std.os.windows.WCHAR), .{ .name = "create_file_hook" });
77-
@export(&create_file_hook(std.os.windows.CHAR), .{ .name = "create_file_hook_narrow" });
89+
@export(&create_file_hook(std.os.windows.WCHAR, std.os.windows.kernel32.CreateFileW), .{ .name = "create_file_hook" });
90+
@export(&create_file_hook(std.os.windows.CHAR, CreateFileA), .{ .name = "create_file_hook_narrow" });
7891
}

0 commit comments

Comments
 (0)