Supports CNBETA 3.7.51

This commit is contained in:
HuLiNap
2025-11-06 00:36:13 +07:00
parent 3844f7b4cf
commit 77c7b0164a
11 changed files with 701 additions and 2 deletions

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.direnv/
.zig-cache/
zig-out/

View File

@@ -1,2 +0,0 @@
# sr-launcher

View File

@@ -0,0 +1 @@
<RSAKeyValue><Exponent>AQAB</Exponent><Modulus>hEegnKISgDas5VTuRBUlixB+bvmPvXKa3kVO22UEZjPGMUFLmIl3DhH+dsZo7qJn/GfJCUkP1FA0MJ5Bj8PX8IatLJKIJ9dMCNdnAlkXTlMg86QQAhHZN83vP4swj5ILcrGNKl3YAZ49fvzo7nheuTt0/40f0HkHdNa1dUHECBs=</Modulus></RSAKeyValue>

35
build.zig Normal file
View File

@@ -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);
}

13
build.zig.zon Normal file
View File

@@ -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,
}

270
flake.lock generated Normal file
View File

@@ -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
}

43
flake.nix Normal file
View File

@@ -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
];
}
) { };
};
}

127
injector.zig Normal file
View File

@@ -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;
}

189
src/root.zig Normal file
View File

@@ -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;
}

19
src/util.zig Normal file
View File

@@ -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);
}