From 77c7b0164a47d9c41635cac9da1befddaceaed32 Mon Sep 17 00:00:00 2001 From: HuLiNap Date: Thu, 6 Nov 2025 00:36:13 +0700 Subject: [PATCH] Supports CNBETA 3.7.51 --- .envrc | 1 + .gitignore | 3 + README.md | 2 - assets/sdk_public_key.xml | 1 + build.zig | 35 +++++ build.zig.zon | 13 ++ flake.lock | 270 ++++++++++++++++++++++++++++++++++++++ flake.nix | 43 ++++++ injector.zig | 127 ++++++++++++++++++ src/root.zig | 189 ++++++++++++++++++++++++++ src/util.zig | 19 +++ 11 files changed, 701 insertions(+), 2 deletions(-) create mode 100644 .envrc create mode 100644 .gitignore delete mode 100644 README.md create mode 100644 assets/sdk_public_key.xml create mode 100644 build.zig create mode 100644 build.zig.zon create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 injector.zig create mode 100644 src/root.zig create mode 100644 src/util.zig diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d657e23 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.direnv/ +.zig-cache/ +zig-out/ diff --git a/README.md b/README.md deleted file mode 100644 index 0deacda..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# sr-launcher - diff --git a/assets/sdk_public_key.xml b/assets/sdk_public_key.xml new file mode 100644 index 0000000..49b7b3d --- /dev/null +++ b/assets/sdk_public_key.xml @@ -0,0 +1 @@ +AQABhEegnKISgDas5VTuRBUlixB+bvmPvXKa3kVO22UEZjPGMUFLmIl3DhH+dsZo7qJn/GfJCUkP1FA0MJ5Bj8PX8IatLJKIJ9dMCNdnAlkXTlMg86QQAhHZN83vP4swj5ILcrGNKl3YAZ49fvzo7nheuTt0/40f0HkHdNa1dUHECBs= diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..db30f30 --- /dev/null +++ b/build.zig @@ -0,0 +1,35 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const optimize = b.standardOptimizeOption(.{}); + const target = b.standardTargetOptions(.{ .default_target = .{ .os_tag = .windows } }); + + const launcher = b.addExecutable(.{ + .name = "cyrene", + .root_module = b.createModule(.{ + .root_source_file = b.path("injector.zig"), + .target = target, + .optimize = optimize, + }), + }); + + const dll = b.addLibrary(.{ + .name = "cyrene", + .linkage = .dynamic, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + .imports = &.{.{ .name = "zigzag", .module = b.dependency("zigzag", .{}).module("zigzag") }}, + }), + }); + + const assets = &.{"sdk_public_key.xml"}; + + inline for (assets) |asset| { + dll.root_module.addAnonymousImport(asset, .{ .root_source_file = b.path("assets/" ++ asset) }); + } + + b.installArtifact(launcher); + b.installArtifact(dll); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..d190de9 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,13 @@ +.{ + .name = .cyrene_patch, + .version = "0.0.1", + .dependencies = .{ + .zigzag = .{ + .url = "git+https://github.com/uniboi/zigzag#5d90b669bd64cbbb445f5555de3b4c28cf508b80", + .hash = "zigzag-0.0.1-wqJDFn2LAAArjRA9fApCcj5xG3JSZcDexs5ZRM9GWUzM", + }, + }, + .minimum_zig_version = "0.15.1", + .paths = .{""}, + .fingerprint = 0x80008f6dd30735e9, +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..765b41a --- /dev/null +++ b/flake.lock @@ -0,0 +1,270 @@ +{ + "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-compat_2": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1705309234, + "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_3": { + "inputs": { + "systems": "systems_3" + }, + "locked": { + "lastModified": 1705309234, + "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "zls", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1755704039, + "narHash": "sha256-gKlP0LbyJ3qX0KObfIWcp5nbuHSb5EHwIvU6UcNBg2A=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "9cb344e96d5b6918e94e1bca2d9f3ea1e9615545", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "zig-overlay": "zig-overlay", + "zig2nix": "zig2nix", + "zls": "zls" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_3": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "zig-overlay": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1755864794, + "narHash": "sha256-hgnov6RLA+DD4Uocs/vCbiH3/3sKvqiJOKHpdhGyVAI=", + "owner": "mitchellh", + "repo": "zig-overlay", + "rev": "5cd601f8760d2383210b7b8c8a45fc79388f3ddf", + "type": "github" + }, + "original": { + "owner": "mitchellh", + "repo": "zig-overlay", + "type": "github" + } + }, + "zig-overlay_2": { + "inputs": { + "flake-compat": "flake-compat_2", + "flake-utils": "flake-utils_3", + "nixpkgs": [ + "zls", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1755864794, + "narHash": "sha256-hgnov6RLA+DD4Uocs/vCbiH3/3sKvqiJOKHpdhGyVAI=", + "owner": "mitchellh", + "repo": "zig-overlay", + "rev": "5cd601f8760d2383210b7b8c8a45fc79388f3ddf", + "type": "github" + }, + "original": { + "owner": "mitchellh", + "repo": "zig-overlay", + "type": "github" + } + }, + "zig2nix": { + "inputs": { + "flake-utils": "flake-utils_2", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1755679254, + "narHash": "sha256-PXiDdFjJAbFYIk9hfRdgbrsSRob0/UcEfLeihRiTD0Y=", + "owner": "Cloudef", + "repo": "zig2nix", + "rev": "cf297c89f32c94d42023d3ab5a496b16d0d3daf2", + "type": "github" + }, + "original": { + "owner": "Cloudef", + "repo": "zig2nix", + "type": "github" + } + }, + "zls": { + "inputs": { + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ], + "zig-overlay": "zig-overlay_2" + }, + "locked": { + "lastModified": 1755880254, + "narHash": "sha256-B1Ta6oShfEUSeABk8ahVR0GVzXuuMIf+zDCbtrFmbX4=", + "owner": "zigtools", + "repo": "zls", + "rev": "da5c31e8e380bcf397276702cfa44571d96043af", + "type": "github" + }, + "original": { + "owner": "zigtools", + "repo": "zls", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..b1edd5b --- /dev/null +++ b/flake.nix @@ -0,0 +1,43 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05"; + zig-overlay = { + url = "github:mitchellh/zig-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + zig2nix = { + url = "github:Cloudef/zig2nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + zls = { + url = "github:zigtools/zls"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = + { + self, + nixpkgs, + zig-overlay, + zig2nix, + zls, + }: + let + system = "x86_64-linux"; + pkgs = nixpkgs.legacyPackages.${system}; + zig-version = "0.15.1"; + zig = zig-overlay.packages.${system}.${zig-version}; + in + { + devShells.${system}.default = pkgs.callPackage ( + { mkShell }: + mkShell { + nativeBuildInputs = [ + zig + zls.packages.${system}.zls + ]; + } + ) { }; + }; +} diff --git a/injector.zig b/injector.zig new file mode 100644 index 0000000..4a8ef69 --- /dev/null +++ b/injector.zig @@ -0,0 +1,127 @@ +const std = @import("std"); +const unicode = std.unicode; +const windows = std.os.windows; + +const game_executables = &.{"StarRail.exe"}; + +const dll_path = "cyrene.dll" ++ .{0}; +const kernel32_name = unicode.utf8ToUtf16LeStringLiteral("kernel32.dll"); + +pub extern "kernel32" fn ResumeThread(*anyopaque) callconv(.winapi) void; + +extern "kernel32" fn VirtualAllocEx( + windows.HANDLE, + ?*anyopaque, + windows.SIZE_T, + windows.DWORD, + windows.DWORD, +) callconv(.winapi) windows.LPVOID; + +extern "kernel32" fn VirtualFreeEx( + windows.HANDLE, + windows.LPVOID, + windows.SIZE_T, + windows.DWORD, +) callconv(.winapi) windows.BOOL; + +extern "kernel32" fn CreateRemoteThread( + windows.HANDLE, + ?*anyopaque, + windows.SIZE_T, + windows.LPTHREAD_START_ROUTINE, + windows.LPVOID, + windows.DWORD, + *windows.DWORD, +) callconv(.winapi) windows.HANDLE; + +pub fn main() !void { + const game_executable = whichExecutable() orelse { + try std.fs.File.stdout().writeAll("Game executable doesn't exist. Press any key to exit...\n"); + + var buf: [1]u8 = undefined; + _ = std.fs.File.stdin().read(&buf) catch {}; + return; + }; + + var proc_info: windows.PROCESS_INFORMATION = undefined; + var startup_info: windows.STARTUPINFOW = .{ + .cb = 0, + .lpReserved = null, + .lpDesktop = null, + .lpTitle = null, + .dwX = 0, + .dwY = 0, + .dwXSize = 0, + .dwYSize = 0, + .dwXCountChars = 0, + .dwYCountChars = 0, + .dwFillAttribute = 0, + .dwFlags = 0, + .wShowWindow = 0, + .cbReserved2 = 0, + .lpReserved2 = null, + .hStdInput = null, + .hStdOutput = null, + .hStdError = null, + }; + + try windows.CreateProcessW( + game_executable, + null, + null, + null, + 0, + .{ .create_suspended = true }, + null, + null, + &startup_info, + &proc_info, + ); + + const load_library = windows.kernel32.GetProcAddress( + windows.kernel32.GetModuleHandleW(kernel32_name).?, + "LoadLibraryA", + ).?; + + const dll_path_addr = VirtualAllocEx( + proc_info.hProcess, + null, + dll_path.len, + windows.MEM_COMMIT | windows.MEM_RESERVE, + windows.PAGE_READWRITE, + ); + _ = try windows.WriteProcessMemory(proc_info.hProcess, dll_path_addr, dll_path); + + // call LoadLibraryA in the remote process, this will also call DllMain so we should wait for it and then resume the target. + var thread_id: windows.DWORD = 0; + const loader_thread = CreateRemoteThread( + proc_info.hProcess, + null, + 0, + @ptrCast(load_library), + dll_path_addr, + 0, + &thread_id, + ); + + try windows.WaitForSingleObject(loader_thread, 0xFFFFFFFF); + + // cleanup + _ = VirtualFreeEx(proc_info.hProcess, dll_path_addr, 0, windows.MEM_RELEASE); + windows.CloseHandle(loader_thread); + ResumeThread(proc_info.hThread); + windows.CloseHandle(proc_info.hThread); + windows.CloseHandle(proc_info.hProcess); +} + +fn whichExecutable() ?[:0]const u16 { + inline for (game_executables) |exe_name| { + if (fileExists(exe_name)) return unicode.utf8ToUtf16LeStringLiteral(exe_name); + } + + return null; +} + +fn fileExists(name: []const u8) bool { + return if (windows.GetFileAttributes(name)) |_| true else |_| false; +} diff --git a/src/root.zig b/src/root.zig new file mode 100644 index 0000000..0b64f56 --- /dev/null +++ b/src/root.zig @@ -0,0 +1,189 @@ +const std = @import("std"); +const zz = @import("zigzag"); +const util = @import("util.zig"); + +const windows = std.os.windows; +const unicode = std.unicode; + +const DLL_PROCESS_ATTACH = 1; + +extern "kernel32" fn AllocConsole() callconv(.winapi) void; +extern "kernel32" fn FreeConsole() callconv(.winapi) void; + +const ntdll_name = unicode.utf8ToUtf16LeStringLiteral("ntdll.dll"); +const wintrust_dll_name = std.unicode.utf8ToUtf16LeStringLiteral("wintrust.dll"); +const game_assembly_name = unicode.utf8ToUtf16LeStringLiteral("GameAssembly.dll"); + +const wintrust_funcs: []const [:0]const u8 = &.{ + "CryptCATAdminEnumCatalogFromHash", + "CryptCATCatalogInfoFromContext", + "CryptCATAdminReleaseCatalogContext", +}; + +const wintrust_stub: []const u8 = &.{ 0xB8, 0x01, 0x00, 0x00, 0x00, 0xC3 }; + +pub var base: usize = undefined; + +fn onAttach() void { + FreeConsole(); + AllocConsole(); + + std.fs.File.stdout().writeAll( + \\ .@@::: # + \\ @::@@-%:@ @@@@@@@@ % @ + \\ .-= #--@::* @#:::::::::@@@@@ @ + \\ @=. =:-:@@@:::::::-#----=@@ .= + \\ @:#.-:-@:%.-@@::::---@:----@-@ .:@ @ + \\ . -*@@::@---------:---@:--@ @::::-@ @# + \\ @-@:++=+*@#--:+@------@----@:-. ::@:- @@@ + \\ @@:+@@=@@-@--------------@----@:-@. :::@-@%@@ @ + \\ @:::-*:@--@-----:-::---::------@::::----@-+-@ + \\ @::-----:@-----::::::-:-::-:-:---@:::-::::@----@% + \\ @::----::@-------:::-:---:--:-:---@:::---::#------@ + \\ @:------::*----@---------------@=--@:%:-----:@------ + \\ @-:-----::@----@-::--------------@--@:-#-------@%----@ + \\ @----+:::@----+@:::---------------@-@:-@:-------::%@@@ + \\ @----@:-::@--@@@#:::--------------#..@::@::---------@-@ + \\ @--@@:--::@--@@@=@@::---------@---@..@-@--:---@@+*@@--# + \\ @-@@:---::@@@@ ###@@-------@@@@@@@@@@-@#:------@%..@% + \\ *@ @:---@@:::++++==##@@.@@@@@=.@@@@@@#.@*:::-----@..-@@ + \\ #@@::-@::::::+=======@.........@ ###@.@:::------@@--@ + \\ @ @::%::::::::@...=+@..........+++=+###@@:::----@-----@ + \\ :::@::::::::::...............+=======@::------@----@@ + \\ @:::-@:::::::::....@..@........=...==@:::------@---@=* + \\ *::--@:::::::.....-@@.@@.....:::@@::@::-------@--@====@ + \\ @:::---@::::.................::::::@::-----@--@@====++++@ + \\ @-@-=:-----@@@................:::::@:--@----@--+===+++**@ + \\ @====@::--@@--@ @@@@@@+.....:#@@%---@@----@---@@ @@@@ + \\ @@@@@+@:---@ *---@+@@%++@ + \\ @=++@@@@@@ @---@ @@+++@@ + \\ @@-@@@ + \\ + ) catch {}; + + std.log.debug("Successfully injected. Waiting for the game startup.", .{}); + + if (isWine()) { + const wintrust = windows.kernel32.LoadLibraryW(wintrust_dll_name).?; + inline for (wintrust_funcs) |func_name| { + const func: [*]u8 = @ptrCast(windows.kernel32.GetProcAddress(wintrust, func_name).?); + var prot: windows.DWORD = windows.PAGE_EXECUTE_READWRITE; + windows.VirtualProtect(@ptrCast(func), 6, prot, &prot) catch unreachable; + @memcpy(func, wintrust_stub); + windows.VirtualProtect(@ptrCast(func), 6, prot, &prot) catch unreachable; + } + } + + base = while (true) { + if (windows.kernel32.GetModuleHandleW(game_assembly_name)) |addr| break @intFromPtr(addr); + std.Thread.sleep(std.time.ns_per_ms * 100); + }; + + std.log.debug("GameAssembly is located at: 0x{X}", .{base}); + std.Thread.sleep(std.time.ns_per_s * 2); + + disableMemoryProtection() catch |err| { + std.log.err("Failed to disable memory protection: {}", .{err}); + return; + }; + + var pca = zz.PageChunkAllocator.init() catch unreachable; + const allocator = pca.allocator(); + + _ = intercept(allocator, base + 0x15E60900, MakeInitialUrlHook); + + const dither_func: usize = 0x75F4C90; + + var prot: windows.DWORD = windows.PAGE_EXECUTE_READWRITE; + windows.VirtualProtect(@ptrFromInt(base + dither_func), 1, prot, &prot) catch unreachable; + @as(*u8, @ptrFromInt(base + dither_func)).* = 0xC3; + windows.VirtualProtect(@ptrFromInt(base + dither_func), 1, prot, &prot) catch unreachable; + + prot = windows.PAGE_EXECUTE_READWRITE; + + std.log.debug("Successfully initialized", .{}); +} + +const MakeInitialUrlHook = struct { + const global_dispatch_prefix = unicode.utf8ToUtf16LeStringLiteral("https://globaldp-beta-cn01.bhsr.com"); + const cn_sdk_domain = unicode.utf8ToUtf16LeStringLiteral("mihoyo.com"); + const global_sdk_domain = unicode.utf8ToUtf16LeStringLiteral("hoyoverse.com"); + + const custom_dispatch_prefix = unicode.utf8ToUtf16LeStringLiteral("http://127.0.0.1:21000"); + const custom_sdk_prefix = unicode.utf8ToUtf16LeStringLiteral("http://127.0.0.1:21000"); + + pub var originalFn: *const fn (usize, usize) callconv(.c) usize = undefined; + + pub fn callback(a1: usize, a2: usize) callconv(.c) usize { + var buf: [4096]u8 = undefined; + + const str = util.readCSharpString(a1); + const len = std.unicode.utf16LeToUtf8(&buf, str) catch unreachable; + std.log.debug("{s}", .{buf[0..len]}); + + if (std.mem.startsWith(u16, str, global_dispatch_prefix)) { + std.log.debug("dispatch request detected.", .{}); + util.csharpStringReplace(a1, global_dispatch_prefix, custom_dispatch_prefix); + } else if (std.mem.indexOf(u16, str, cn_sdk_domain)) |index| { + std.log.debug("CN SDK request detected.", .{}); + util.csharpStringReplace(a1, str[0 .. index + cn_sdk_domain.len], custom_sdk_prefix); + } else if (std.mem.indexOf(u16, str, global_sdk_domain)) |index| { + std.log.debug("GLOBAL SDK request detected.", .{}); + util.csharpStringReplace(a1, str[0 .. index + global_sdk_domain.len], custom_sdk_prefix); + } + + return @This().originalFn(a1, a2); + } +}; + +pub fn intercept(ca: zz.ChunkAllocator, address: usize, hook_struct: anytype) zz.Hook(@TypeOf(hook_struct.callback)) { + const hook = zz.Hook(@TypeOf(hook_struct.callback)).init(ca, @ptrFromInt(address), hook_struct.callback) catch |err| { + std.log.err("failed to intercept function at 0x{X}: {}", .{ address - base, err }); + @panic("intercept failed"); + }; + + hook_struct.originalFn = hook.delegate; + return hook; +} + +pub export fn DllMain(_: windows.HINSTANCE, reason: windows.DWORD, _: windows.LPVOID) callconv(.winapi) windows.BOOL { + if (reason == DLL_PROCESS_ATTACH) { + const thread = std.Thread.spawn(.{}, onAttach, .{}) catch unreachable; + thread.detach(); + } + + return 1; +} + +fn disableMemoryProtection() !void { + const ntdll = windows.kernel32.GetModuleHandleW(ntdll_name).?; + const proc_addr = windows.kernel32.GetProcAddress(ntdll, "NtProtectVirtualMemory").?; + + const nt_func = nt_func: { + if (isWine()) { + break :nt_func windows.kernel32.GetProcAddress(ntdll, "NtPulseEvent").?; + } else { + break :nt_func windows.kernel32.GetProcAddress(ntdll, "NtQuerySection").?; + } + }; + + var protection: windows.DWORD = windows.PAGE_EXECUTE_READWRITE; + try windows.VirtualProtect(proc_addr, 1, protection, &protection); + + const routine: *u32 = @ptrCast(@alignCast(nt_func)); + const routine_val = @as(*usize, @ptrCast(@alignCast(routine))).*; + const lower_bits_mask = ~(@as(u64, 0xFF) << 32); + const lower_bits = routine_val & @as(usize, @intCast(lower_bits_mask)); + + const offset_val = @as(*const u32, @ptrFromInt(@as(usize, @intFromPtr(routine)) + 4)).*; + const upper_bits = @as(usize, @intCast(@subWithOverflow(offset_val, 1).@"0")) << 32; + const result = lower_bits | upper_bits; + @as(*usize, @ptrCast(@alignCast(proc_addr))).* = result; + + try windows.VirtualProtect(proc_addr, 1, protection, &protection); +} + +fn isWine() bool { + const ntdll = windows.kernel32.GetModuleHandleW(ntdll_name).?; + return windows.kernel32.GetProcAddress(ntdll, "wine_get_version") != null; +} diff --git a/src/util.zig b/src/util.zig new file mode 100644 index 0000000..c405c94 --- /dev/null +++ b/src/util.zig @@ -0,0 +1,19 @@ +const root = @import("root"); + +pub fn readCSharpString(data: usize) []u16 { + const len = @as(*const u32, @ptrFromInt(data + 16)).*; + const ptr = @as([*]u16, @ptrFromInt(data + 20)); + return ptr[0..len]; +} + +pub fn csharpStringReplace(object: usize, pattern: []const u16, replacement: []const u16) void { + const str = readCSharpString(object); + + @memcpy(str[0..replacement.len], replacement); + @memmove(str[replacement.len .. str.len - (pattern.len - replacement.len)], str[pattern.len..str.len]); + @as(*u32, @ptrFromInt(object + 16)).* = @intCast(str.len - (pattern.len - replacement.len)); +} + +pub fn il2cppStringNew(str: []const u8) usize { + return @as(*const fn ([*]const u8) callconv(.c) usize, @ptrFromInt(root.base + 0x1690F70))(str.ptr); +}