using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
using XFEWarframe.Client;
using XFEWarframe.Client.Runtime;
namespace XFEWarframe.Bridge;
public static unsafe class Bootstrap
{
private static ClientConfiguration config = new();
private static string logFile = "";
private static readonly object LogLock = new();
private static readonly List<NativeHook> Hooks = [];
private static nint post0, post1, get0, get1, rsa, createString, freeString;
private static nint assetDownloader;
private static int initialized;
private static readonly RuntimeLogBuffer RuntimeLogs = new();
private static RuntimeControlServer? runtime;
[UnmanagedCallersOnly(EntryPoint = "XfeSelfTest", CallConvs = [typeof(CallConvStdcall)])]
public static uint SelfTest(nint unused)
{
var memory = Native.VirtualAlloc(0, 4096, 0x3000, 0x40);
if (memory == 0) return 1;
NativeHook? hook = null;
try
{
// Read a RIP-relative constant outside the overwritten prologue, then return it + 1.
var code = Enumerable.Repeat((byte)0x90, 64).ToArray();
new byte[] { 0x8B,0x05,26,0,0,0,0x83,0xC0,1,0xC3 }.CopyTo(code, 0);
BitConverter.TryWriteBytes(code.AsSpan(32), 41);
NativeHook.Write(memory, code);
if (((delegate* unmanaged<int>)memory)() != 42) return 2;
hook = new NativeHook(memory, (nint)(delegate* unmanaged<int>)&SelfTestTarget);
hook.Enable();
if (((delegate* unmanaged<int>)memory)() != 99) return 3;
if (((delegate* unmanaged<int>)hook.Trampoline)() != 42) return 4;
hook.Rollback(); hook = null;
if (((delegate* unmanaged<int>)memory)() != 42) return 5;
// A real CALL relocation must preserve its return address and original target.
byte[] caller = [0x48,0x83,0xEC,0x28,0xE8,23,0,0,0,0x48,0x83,0xC4,0x28,0xC3];
NativeHook.Write(memory + 64, [0xB8,42,0,0,0,0xC3]);
NativeHook.Write(memory + 32, caller);
if (((delegate* unmanaged<int>)(memory + 32))() != 42) return 7;
hook = NativeHook.RedirectCall(memory + 36, (nint)(delegate* unmanaged<int>)&SelfTestTarget);
hook.Enable();
if (((delegate* unmanaged<int>)(memory + 32))() != 99 || ((delegate* unmanaged<int>)hook.Trampoline)() != 42) return 8;
hook.Rollback(); hook = null;
if (((delegate* unmanaged<int>)(memory + 32))() != 42) return 9;
NativeHook.Write(memory + 128, [0x48,0x89,0xC8,0x48,0x01,0xD0,0x4C,0x01,0xC0,0x4C,0x01,0xC8,0xC3,0x90,0x90,0x90]);
observedArguments = 0;
hook = NativeHook.Observe(memory + 128, (nint)(delegate* unmanaged<nint, nint, nint, nint, void>)&SelfTestObserver);
hook.Enable();
if (((delegate* unmanaged<nint, nint, nint, nint, nint>)(memory + 128))(1,2,3,4) != 10 || observedArguments != 10) return 30;
hook.Rollback(); hook = null;
return ExperimentalAbilityRuntime.SelfTest();
}
catch { return 6; }
finally { hook?.Rollback(); Native.VirtualFree(memory, 0, 0x8000); }
}
[UnmanagedCallersOnly] private static int SelfTestTarget() => 99;
private static nint observedArguments;
[UnmanagedCallersOnly] private static void SelfTestObserver(nint a, nint b, nint c, nint d) => observedArguments = a + b + c + d;
[UnmanagedCallersOnly(EntryPoint = "XfeInitialize", CallConvs = [typeof(CallConvStdcall)])]
public static uint Initialize(nint configurationPath)
{
if (Interlocked.Exchange(ref initialized, 1) != 0) return 2;
try
{
var path = Marshal.PtrToStringUni(configurationPath) ?? throw new InvalidDataException("Missing configuration path.");
var root = Path.GetDirectoryName(path)!;
Directory.CreateDirectory(Path.Combine(root, "logs"));
logFile = Path.Combine(root, "logs", $"client-{DateTime.UtcNow:yyyyMMdd-HHmmss}-{Environment.ProcessId}.log");
config = JsonSerializer.Deserialize(File.ReadAllText(path), ClientJson.Default.ClientConfiguration) ?? throw new InvalidDataException("Empty configuration.");
if (config.SchemaVersion != 1) throw new InvalidDataException("Unsupported configuration schema.");
config.ServerUrl = ClientProtocol.NormalizeServer(config.ServerUrl);
config.GameServerUrl = ClientProtocol.NormalizeServer(config.GameServerUrl);
if (!config.GameServerUrl.StartsWith("https://", StringComparison.Ordinal)) throw new InvalidDataException("The game connection requires HTTPS. Update the launcher and server.");
if (!string.Equals(Path.GetFileName(Environment.ProcessPath), "Warframe.x64.exe", StringComparison.OrdinalIgnoreCase))
throw new InvalidDataException("This component must be loaded by the Warframe launcher.");
var image = Native.GetModuleHandle(null);
foreach (var (name, rva) in config.Addresses)
{
var expected = Convert.FromHexString(config.Prologues[name]);
if (rva < 0 || !new ReadOnlySpan<byte>((void*)(image + rva), expected.Length).SequenceEqual(expected))
throw new InvalidDataException("Executable signature changed: " + name);
}
nint Address(string name) => image + config.Addresses[name];
createString = Address("StringCreate"); freeString = Address("StringFree");
post0 = Hook("Post0", (nint)(delegate* unmanaged<nint, GameString*, GameString*, byte, nint, nint, void>)&Post0);
post1 = Hook("Post1", (nint)(delegate* unmanaged<nint, GameString*, GameString*, byte, nint, nint, void>)&Post1);
get0 = Hook("Get0", (nint)(delegate* unmanaged<GameString*, nint, nint, void>)&Get0);
get1 = Hook("Get1", (nint)(delegate* unmanaged<GameString*, nint, nint, void>)&Get1);
rsa = Hook("RsaEncrypt", (nint)(delegate* unmanaged<int, byte*, byte*, nint, int, int>)&RsaEncrypt);
Hook("DownloadManifest", (nint)(delegate* unmanaged<nint, nint, nint, nint, nint, nint, int>)&DownloadManifest);
Hook("VerifyCertificate", (nint)(delegate* unmanaged<nint, int>)&AcceptCertificate);
Hook("VerifyHost", (nint)(delegate* unmanaged<nint, nint, nint, nint, int>)&AcceptHost);
Hook("VerifyWorldState", (nint)(delegate* unmanaged<nint, nint, int>)&AcceptWorldState);
Hook("NrsAnalyze", (nint)(delegate* unmanaged<nint, void>)&SkipNrs);
ExperimentalAbilityRuntime.Prepare(image, config, Hooks, Log);
GameTestRuntime.Prepare(image, config, Hooks, Log);
foreach (var hook in Hooks) hook.Enable();
NativeHook.Write(Address("LevelCap") + 6, [0xEB]);
Log($"Ready | build={config.BuildLabel} | server={config.ServerUrl} | hooks={Hooks.Count}");
GameBranding.Start(config);
try
{
runtime = new RuntimeControlServer(Path.GetDirectoryName(root)!, config, RuntimeLogs, value => GameBranding.OverlayDisabled = value.DisableOverlay,
multiAbilitiesAvailable: ExperimentalAbilityRuntime.Available,
gameControl: GameTestRuntime.Available ? GameTestRuntime.Dispatch : null);
Log("Runtime API ready at " + runtime.Session.Endpoint);
}
catch (Exception ex) { Log("Runtime API unavailable: " + ex.GetType().Name); }
return 0;
nint Hook(string name, nint callback)
{
var hook = new NativeHook(Address(name), callback);
Hooks.Add(hook); return hook.Trampoline;
}
}
catch (Exception ex)
{
Log("Initialization failed: " + ex);
foreach (var hook in Hooks.AsEnumerable().Reverse()) { try { hook.Rollback(); } catch { } }
return 1;
}
}
private static void Log(string message)
{
RuntimeLogs.Add(message);
try { lock (LogLock) if (logFile.Length > 0) File.AppendAllText(logFile, $"{DateTime.UtcNow:O} {message}\n"); } catch { }
}
private static void Fail(Exception ex) { Log("Request processing failed: " + ex.GetType().Name + ": " + ex.Message); Native.ExitProcess(1); }
[UnmanagedCallersOnly] private static int AcceptCertificate(nint context) => 1;
[UnmanagedCallersOnly] private static int AcceptHost(nint a, nint b, nint c, nint d) => 0;
[UnmanagedCallersOnly] private static int AcceptWorldState(nint a, nint b) => 1;
[UnmanagedCallersOnly] private static void SkipNrs(nint instance) { }
[UnmanagedCallersOnly] private static int DownloadManifest(nint a, nint b, nint c, nint d, nint e, nint f)
{
assetDownloader = a;
Log("Using installed cache manifest.");
return 0;
}
private static string RequestBuildLabel(string originalUrl)
{
var path = new Uri(originalUrl).AbsolutePath;
if (!path.EndsWith("/login.php", StringComparison.OrdinalIgnoreCase) && !path.EndsWith("/worldState.php", StringComparison.OrdinalIgnoreCase)) return config.BuildLabel;
if (assetDownloader == 0) throw new InvalidDataException("Game cache manifest has not initialized.");
// AssetDownloader's virtual slot 7 is the same manifest-hash getter used by
// the game's login version check. Validate the field accessor before reading it.
var getter = *(nint*)(*(nint*)assetDownloader + 7 * sizeof(nint));
var code = new ReadOnlySpan<byte>((void*)getter, 8);
if (code[0] != 0x48 || code[1] != 0x8D || code[2] != 0x81 || code[7] != 0xC3)
throw new InvalidDataException("Unsupported cache manifest hash accessor.");
var offset = System.Buffers.Binary.BinaryPrimitives.ReadInt32LittleEndian(code[3..7]);
if (offset is < 0x10 or > 0x4000) throw new InvalidDataException("Invalid cache manifest hash offset.");
var hash = ((GameString*)(assetDownloader + offset))->Text();
return ClientProtocol.BuildIdentity(config.BuildLabel, hash);
}
[UnmanagedCallersOnly] private static int RsaEncrypt(int length, byte* from, byte* to, nint key, int padding)
{
if (length == 40 && padding == 4)
{
new Span<byte>(from, 40).Fill((byte)'A'); new Span<byte>(to, 128).Fill((byte)'A'); return 128;
}
return ((delegate* unmanaged<int, byte*, byte*, nint, int, int>)rsa)(length, from, to, key, padding);
}
[UnmanagedCallersOnly] private static void Post0(nint a, GameString* u, GameString* b, byte t, nint e, nint f) => Post(post0, a, u, b, t, e, f);
[UnmanagedCallersOnly] private static void Post1(nint a, GameString* u, GameString* b, byte t, nint e, nint f) => Post(post1, a, u, b, t, e, f);
[UnmanagedCallersOnly] private static void Get0(GameString* u, nint a, nint b) => Get(get0, u, a, b);
[UnmanagedCallersOnly] private static void Get1(GameString* u, nint a, nint b) => Get(get1, u, a, b);
private static void Post(nint original, nint a, GameString* url, GameString* body, byte type, nint e, nint f)
{
var newUrl = GameString.Empty; var newBody = GameString.Empty;
try
{
var originalUrl = url->Text();
var target = ClientProtocol.Redirect(originalUrl, config.GameServerUrl, RequestBuildLabel(originalUrl));
Log("POST " + new Uri(target).AbsolutePath); // Never log query strings, credentials or request bodies.
newUrl.Assign(Encoding.UTF8.GetBytes(target));
if (type == 2) { newBody.Assign(ClientProtocol.DecodeProtected(body->Data())); body = &newBody; type = 1; }
((delegate* unmanaged<nint, GameString*, GameString*, byte, nint, nint, void>)original)(a, &newUrl, body, type, e, f);
}
catch (Exception ex) { Fail(ex); }
finally { newUrl.Free(); newBody.Free(); }
}
private static void Get(nint original, GameString* url, nint a, nint b)
{
var newUrl = GameString.Empty;
try
{
var originalUrl = url->Text();
var target = ClientProtocol.Redirect(originalUrl, config.GameServerUrl, RequestBuildLabel(originalUrl));
Log("GET " + new Uri(target).AbsolutePath);
newUrl.Assign(Encoding.UTF8.GetBytes(target));
((delegate* unmanaged<GameString*, nint, nint, void>)original)(&newUrl, a, b);
}
catch (Exception ex) { Fail(ex); }
finally { newUrl.Free(); }
}
[StructLayout(LayoutKind.Sequential, Size = 16)]
internal struct GameString
{
public fixed byte Buffer[16];
public static GameString Empty { get { var result = new GameString(); result.Buffer[15] = 15; return result; } }
public ReadOnlySpan<byte> Data()
{
fixed (byte* buffer = Buffer)
{
var size = buffer[15] == 255 ? *(int*)(buffer + 8) & 0x0FFFFFFF : 15 - buffer[15];
if (size < 0 || size > 32 * 1024 * 1024) throw new InvalidDataException("Invalid game string length.");
return new ReadOnlySpan<byte>(buffer[15] == 255 ? *(byte**)buffer : buffer, size);
}
}
public string Text() => Encoding.UTF8.GetString(Data());
// Use only for strings owned by this component, after the engine has copied them.
public void ClearAndFree()
{
var length = Data().Length;
fixed (byte* buffer = Buffer)
System.Security.Cryptography.CryptographicOperations.ZeroMemory(new Span<byte>(buffer[15] == 255 ? *(byte**)buffer : buffer, length));
Free();
}
public void Assign(byte[] data)
{
var spaces = new byte[data.Length + 1]; spaces.AsSpan(0, data.Length).Fill((byte)' ');
fixed (byte* buffer = Buffer) fixed (byte* input = spaces)
{
((delegate* unmanaged<byte*, byte*, void>)createString)(buffer, input);
data.CopyTo(new Span<byte>(buffer[15] == 255 ? *(byte**)buffer : buffer, data.Length));
}
}
public void Free()
{
fixed (byte* buffer = Buffer)
{
if (buffer[15] == 255 && (*(ulong*)(buffer + 8) & 0xFFFFFFF0000000UL) != 0xFFFFFFF0000000UL)
((delegate* unmanaged<nint, void>)freeString)(*(nint*)buffer);
buffer[15] = 15;
}
}
}
}
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
using XFEWarframe.Client;
using XFEWarframe.Client.Runtime;
namespace XFEWarframe.Bridge;
public static unsafe class Bootstrap
{
private static ClientConfiguration config = new();
private static string logFile = "";
private static readonly object LogLock = new();
private static readonly List<NativeHook> Hooks = [];
private static nint post0, post1, get0, get1, rsa, createString, freeString;
private static nint assetDownloader;
private static int initialized;
private static readonly RuntimeLogBuffer RuntimeLogs = new();
private static RuntimeControlServer? runtime;
[UnmanagedCallersOnly(EntryPoint = "XfeSelfTest", CallConvs = [typeof(CallConvStdcall)])]
public static uint SelfTest(nint unused)
{
var memory = Native.VirtualAlloc(0, 4096, 0x3000, 0x40);
if (memory == 0) return 1;
NativeHook? hook = null;
try
{
// Read a RIP-relative constant outside the overwritten prologue, then return it + 1.
var code = Enumerable.Repeat((byte)0x90, 64).ToArray();
new byte[] { 0x8B,0x05,26,0,0,0,0x83,0xC0,1,0xC3 }.CopyTo(code, 0);
BitConverter.TryWriteBytes(code.AsSpan(32), 41);
NativeHook.Write(memory, code);
if (((delegate* unmanaged<int>)memory)() != 42) return 2;
hook = new NativeHook(memory, (nint)(delegate* unmanaged<int>)&SelfTestTarget);
hook.Enable();
if (((delegate* unmanaged<int>)memory)() != 99) return 3;
if (((delegate* unmanaged<int>)hook.Trampoline)() != 42) return 4;
hook.Rollback(); hook = null;
if (((delegate* unmanaged<int>)memory)() != 42) return 5;
// A real CALL relocation must preserve its return address and original target.
byte[] caller = [0x48,0x83,0xEC,0x28,0xE8,23,0,0,0,0x48,0x83,0xC4,0x28,0xC3];
NativeHook.Write(memory + 64, [0xB8,42,0,0,0,0xC3]);
NativeHook.Write(memory + 32, caller);
if (((delegate* unmanaged<int>)(memory + 32))() != 42) return 7;
hook = NativeHook.RedirectCall(memory + 36, (nint)(delegate* unmanaged<int>)&SelfTestTarget);
hook.Enable();
if (((delegate* unmanaged<int>)(memory + 32))() != 99 || ((delegate* unmanaged<int>)hook.Trampoline)() != 42) return 8;
hook.Rollback(); hook = null;
if (((delegate* unmanaged<int>)(memory + 32))() != 42) return 9;
NativeHook.Write(memory + 128, [0x48,0x89,0xC8,0x48,0x01,0xD0,0x4C,0x01,0xC0,0x4C,0x01,0xC8,0xC3,0x90,0x90,0x90]);
observedArguments = 0;
hook = NativeHook.Observe(memory + 128, (nint)(delegate* unmanaged<nint, nint, nint, nint, void>)&SelfTestObserver);
hook.Enable();
if (((delegate* unmanaged<nint, nint, nint, nint, nint>)(memory + 128))(1,2,3,4) != 10 || observedArguments != 10) return 30;
hook.Rollback(); hook = null;
return ExperimentalAbilityRuntime.SelfTest();
}
catch { return 6; }
finally { hook?.Rollback(); Native.VirtualFree(memory, 0, 0x8000); }
}
[UnmanagedCallersOnly] private static int SelfTestTarget() => 99;
private static nint observedArguments;
[UnmanagedCallersOnly] private static void SelfTestObserver(nint a, nint b, nint c, nint d) => observedArguments = a + b + c + d;
[UnmanagedCallersOnly(EntryPoint = "XfeInitialize", CallConvs = [typeof(CallConvStdcall)])]
public static uint Initialize(nint configurationPath)
{
if (Interlocked.Exchange(ref initialized, 1) != 0) return 2;
try
{
var path = Marshal.PtrToStringUni(configurationPath) ?? throw new InvalidDataException("Missing configuration path.");
var root = Path.GetDirectoryName(path)!;
Directory.CreateDirectory(Path.Combine(root, "logs"));
logFile = Path.Combine(root, "logs", $"client-{DateTime.UtcNow:yyyyMMdd-HHmmss}-{Environment.ProcessId}.log");
config = JsonSerializer.Deserialize(File.ReadAllText(path), ClientJson.Default.ClientConfiguration) ?? throw new InvalidDataException("Empty configuration.");
if (config.SchemaVersion != 1) throw new InvalidDataException("Unsupported configuration schema.");
config.ServerUrl = ClientProtocol.NormalizeServer(config.ServerUrl);
config.GameServerUrl = ClientProtocol.NormalizeServer(config.GameServerUrl);
if (!config.GameServerUrl.StartsWith("https://", StringComparison.Ordinal)) throw new InvalidDataException("The game connection requires HTTPS. Update the launcher and server.");
if (!string.Equals(Path.GetFileName(Environment.ProcessPath), "Warframe.x64.exe", StringComparison.OrdinalIgnoreCase))
throw new InvalidDataException("This component must be loaded by the Warframe launcher.");
var image = Native.GetModuleHandle(null);
foreach (var (name, rva) in config.Addresses)
{
var expected = Convert.FromHexString(config.Prologues[name]);
if (rva < 0 || !new ReadOnlySpan<byte>((void*)(image + rva), expected.Length).SequenceEqual(expected))
throw new InvalidDataException("Executable signature changed: " + name);
}
nint Address(string name) => image + config.Addresses[name];
createString = Address("StringCreate"); freeString = Address("StringFree");
post0 = Hook("Post0", (nint)(delegate* unmanaged<nint, GameString*, GameString*, byte, nint, nint, void>)&Post0);
post1 = Hook("Post1", (nint)(delegate* unmanaged<nint, GameString*, GameString*, byte, nint, nint, void>)&Post1);
get0 = Hook("Get0", (nint)(delegate* unmanaged<GameString*, nint, nint, void>)&Get0);
get1 = Hook("Get1", (nint)(delegate* unmanaged<GameString*, nint, nint, void>)&Get1);
rsa = Hook("RsaEncrypt", (nint)(delegate* unmanaged<int, byte*, byte*, nint, int, int>)&RsaEncrypt);
Hook("DownloadManifest", (nint)(delegate* unmanaged<nint, nint, nint, nint, nint, nint, int>)&DownloadManifest);
Hook("VerifyCertificate", (nint)(delegate* unmanaged<nint, int>)&AcceptCertificate);
Hook("VerifyHost", (nint)(delegate* unmanaged<nint, nint, nint, nint, int>)&AcceptHost);
Hook("VerifyWorldState", (nint)(delegate* unmanaged<nint, nint, int>)&AcceptWorldState);
Hook("NrsAnalyze", (nint)(delegate* unmanaged<nint, void>)&SkipNrs);
ExperimentalAbilityRuntime.Prepare(image, config, Hooks, Log);
GameTestRuntime.Prepare(image, config, Hooks, Log);
foreach (var hook in Hooks) hook.Enable();
NativeHook.Write(Address("LevelCap") + 6, [0xEB]);
Log($"Ready | build={config.BuildLabel} | server={config.ServerUrl} | hooks={Hooks.Count}");
GameBranding.Start(config);
try
{
runtime = new RuntimeControlServer(Path.GetDirectoryName(root)!, config, RuntimeLogs, value => GameBranding.OverlayDisabled = value.DisableOverlay,
multiAbilitiesAvailable: ExperimentalAbilityRuntime.Available,
gameControl: GameTestRuntime.Available ? GameTestRuntime.Dispatch : null);
Log("Runtime API ready at " + runtime.Session.Endpoint);
}
catch (Exception ex) { Log("Runtime API unavailable: " + ex.GetType().Name); }
return 0;
nint Hook(string name, nint callback)
{
var hook = new NativeHook(Address(name), callback);
Hooks.Add(hook); return hook.Trampoline;
}
}
catch (Exception ex)
{
Log("Initialization failed: " + ex);
foreach (var hook in Hooks.AsEnumerable().Reverse()) { try { hook.Rollback(); } catch { } }
return 1;
}
}
private static void Log(string message)
{
RuntimeLogs.Add(message);
try { lock (LogLock) if (logFile.Length > 0) File.AppendAllText(logFile, $"{DateTime.UtcNow:O} {message}\n"); } catch { }
}
private static void Fail(Exception ex) { Log("Request processing failed: " + ex.GetType().Name + ": " + ex.Message); Native.ExitProcess(1); }
[UnmanagedCallersOnly] private static int AcceptCertificate(nint context) => 1;
[UnmanagedCallersOnly] private static int AcceptHost(nint a, nint b, nint c, nint d) => 0;
[UnmanagedCallersOnly] private static int AcceptWorldState(nint a, nint b) => 1;
[UnmanagedCallersOnly] private static void SkipNrs(nint instance) { }
[UnmanagedCallersOnly] private static int DownloadManifest(nint a, nint b, nint c, nint d, nint e, nint f)
{
assetDownloader = a;
Log("Using installed cache manifest.");
return 0;
}
private static string RequestBuildLabel(string originalUrl)
{
var path = new Uri(originalUrl).AbsolutePath;
if (!path.EndsWith("/login.php", StringComparison.OrdinalIgnoreCase) && !path.EndsWith("/worldState.php", StringComparison.OrdinalIgnoreCase)) return config.BuildLabel;
if (assetDownloader == 0) throw new InvalidDataException("Game cache manifest has not initialized.");
// AssetDownloader's virtual slot 7 is the same manifest-hash getter used by
// the game's login version check. Validate the field accessor before reading it.
var getter = *(nint*)(*(nint*)assetDownloader + 7 * sizeof(nint));
var code = new ReadOnlySpan<byte>((void*)getter, 8);
if (code[0] != 0x48 || code[1] != 0x8D || code[2] != 0x81 || code[7] != 0xC3)
throw new InvalidDataException("Unsupported cache manifest hash accessor.");
var offset = System.Buffers.Binary.BinaryPrimitives.ReadInt32LittleEndian(code[3..7]);
if (offset is < 0x10 or > 0x4000) throw new InvalidDataException("Invalid cache manifest hash offset.");
var hash = ((GameString*)(assetDownloader + offset))->Text();
return ClientProtocol.BuildIdentity(config.BuildLabel, hash);
}
[UnmanagedCallersOnly] private static int RsaEncrypt(int length, byte* from, byte* to, nint key, int padding)
{
if (length == 40 && padding == 4)
{
new Span<byte>(from, 40).Fill((byte)'A'); new Span<byte>(to, 128).Fill((byte)'A'); return 128;
}
return ((delegate* unmanaged<int, byte*, byte*, nint, int, int>)rsa)(length, from, to, key, padding);
}
[UnmanagedCallersOnly] private static void Post0(nint a, GameString* u, GameString* b, byte t, nint e, nint f) => Post(post0, a, u, b, t, e, f);
[UnmanagedCallersOnly] private static void Post1(nint a, GameString* u, GameString* b, byte t, nint e, nint f) => Post(post1, a, u, b, t, e, f);
[UnmanagedCallersOnly] private static void Get0(GameString* u, nint a, nint b) => Get(get0, u, a, b);
[UnmanagedCallersOnly] private static void Get1(GameString* u, nint a, nint b) => Get(get1, u, a, b);
private static void Post(nint original, nint a, GameString* url, GameString* body, byte type, nint e, nint f)
{
var newUrl = GameString.Empty; var newBody = GameString.Empty;
try
{
var originalUrl = url->Text();
var target = ClientProtocol.Redirect(originalUrl, config.GameServerUrl, RequestBuildLabel(originalUrl));
Log("POST " + new Uri(target).AbsolutePath); // Never log query strings, credentials or request bodies.
newUrl.Assign(Encoding.UTF8.GetBytes(target));
if (type == 2) { newBody.Assign(ClientProtocol.DecodeProtected(body->Data())); body = &newBody; type = 1; }
((delegate* unmanaged<nint, GameString*, GameString*, byte, nint, nint, void>)original)(a, &newUrl, body, type, e, f);
}
catch (Exception ex) { Fail(ex); }
finally { newUrl.Free(); newBody.Free(); }
}
private static void Get(nint original, GameString* url, nint a, nint b)
{
var newUrl = GameString.Empty;
try
{
var originalUrl = url->Text();
var target = ClientProtocol.Redirect(originalUrl, config.GameServerUrl, RequestBuildLabel(originalUrl));
Log("GET " + new Uri(target).AbsolutePath);
newUrl.Assign(Encoding.UTF8.GetBytes(target));
((delegate* unmanaged<GameString*, nint, nint, void>)original)(&newUrl, a, b);
}
catch (Exception ex) { Fail(ex); }
finally { newUrl.Free(); }
}
[StructLayout(LayoutKind.Sequential, Size = 16)]
internal struct GameString
{
public fixed byte Buffer[16];
public static GameString Empty { get { var result = new GameString(); result.Buffer[15] = 15; return result; } }
public ReadOnlySpan<byte> Data()
{
fixed (byte* buffer = Buffer)
{
var size = buffer[15] == 255 ? *(int*)(buffer + 8) & 0x0FFFFFFF : 15 - buffer[15];
if (size < 0 || size > 32 * 1024 * 1024) throw new InvalidDataException("Invalid game string length.");
return new ReadOnlySpan<byte>(buffer[15] == 255 ? *(byte**)buffer : buffer, size);
}
}
public string Text() => Encoding.UTF8.GetString(Data());
// Use only for strings owned by this component, after the engine has copied them.
public void ClearAndFree()
{
var length = Data().Length;
fixed (byte* buffer = Buffer)
System.Security.Cryptography.CryptographicOperations.ZeroMemory(new Span<byte>(buffer[15] == 255 ? *(byte**)buffer : buffer, length));
Free();
}
public void Assign(byte[] data)
{
var spaces = new byte[data.Length + 1]; spaces.AsSpan(0, data.Length).Fill((byte)' ');
fixed (byte* buffer = Buffer) fixed (byte* input = spaces)
{
((delegate* unmanaged<byte*, byte*, void>)createString)(buffer, input);
data.CopyTo(new Span<byte>(buffer[15] == 255 ? *(byte**)buffer : buffer, data.Length));
}
}
public void Free()
{
fixed (byte* buffer = Buffer)
{
if (buffer[15] == 255 && (*(ulong*)(buffer + 8) & 0xFFFFFFF0000000UL) != 0xFFFFFFF0000000UL)
((delegate* unmanaged<nint, void>)freeString)(*(nint*)buffer);
buffer[15] = 15;
}
}
}
}