Files
fparkan/crates/fparkan-runtime/tests/handler19_anchor_native_oracle.cs
T

158 lines
9.6 KiB
C#
Raw Normal View History

using System;
using System.IO;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
internal static class Iron3dAnchorOracle {
private const uint LoadWithAlteredSearchPath = 0x00000008;
private const uint PageExecuteReadWrite = 0x40;
private const uint MemCommitReserve = 0x3000;
private const uint MemRelease = 0x8000;
private const int CallerRva = 0x392e0;
private const int GameGlobalRva = 0x10a5f8;
private const int CreateSuperAiIatRva = 0xe4010;
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr LoadLibraryEx(string path, IntPtr file, uint flags);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool FreeLibrary(IntPtr module);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr VirtualAlloc(IntPtr address, UIntPtr size, uint allocationType, uint protect);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool VirtualFree(IntPtr address, UIntPtr size, uint freeType);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool VirtualProtect(IntPtr address, UIntPtr size, uint protect, out uint oldProtect);
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate void CreateAiDelegate(IntPtr record, int clanIndex, uint networkPlayer, int networkFlag);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate IntPtr CreateSuperAiDelegate(uint p0, uint p1, uint p2, uint clanIndex, uint x, uint y, uint p6, uint p7, uint p8, uint callback);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate void FpuWordOperation(ref ushort value);
private static FpuWordOperation ReadControlWord;
private static uint CapturedX;
private static uint CapturedY;
private static ushort CapturedControlWord;
private static CreateSuperAiDelegate CaptureDelegate;
private static IntPtr CaptureCreateSuperAi(uint p0, uint p1, uint p2, uint clanIndex, uint x, uint y, uint p6, uint p7, uint p8, uint callback) {
CapturedX = x;
CapturedY = y;
ReadControlWord(ref CapturedControlWord);
return IntPtr.Zero;
}
private static void Require(bool value, string message) {
if (!value) throw new InvalidOperationException(message);
}
private static void Expect(CreateAiDelegate caller, IntPtr record, FpuWordOperation setControlWord, ushort mode, float x, float y, uint expectedX, uint expectedY, string label) {
ushort requested = mode;
setControlWord(ref requested);
Marshal.WriteInt32(record, 4, BitConverter.ToInt32(BitConverter.GetBytes(x), 0));
Marshal.WriteInt32(record, 8, BitConverter.ToInt32(BitConverter.GetBytes(y), 0));
CapturedX = 0xdeadbeef;
CapturedY = 0xdeadbeef;
CapturedControlWord = 0;
caller(record, 0, 0, 0);
string actual = String.Format(CultureInfo.InvariantCulture, "{0}: cw=0x{1:x4} rc={2}, ({3:R}, {4:R}) -> (0x{5:x8}, 0x{6:x8})", label, CapturedControlWord, (CapturedControlWord >> 10) & 3, x, y, CapturedX, CapturedY);
Console.WriteLine(actual);
Require(CapturedX == expectedX && CapturedY == expectedY, label + " expected (0x" + expectedX.ToString("x8") + ",0x" + expectedY.ToString("x8") + ")");
Require(((CapturedControlWord >> 10) & 3) == ((requested >> 10) & 3), label + " x87 RC changed before the native callback");
}
private static IntPtr BuildInstruction(byte[] bytes) {
IntPtr memory = VirtualAlloc(IntPtr.Zero, new UIntPtr(4096), MemCommitReserve, PageExecuteReadWrite);
if (memory == IntPtr.Zero) throw new InvalidOperationException("VirtualAlloc failed: " + Marshal.GetLastWin32Error());
Marshal.Copy(bytes, 0, memory, bytes.Length);
return memory;
}
private static void ProtectWrite(IntPtr address, int value, out uint oldProtect) {
if (!VirtualProtect(address, new UIntPtr(4), PageExecuteReadWrite, out oldProtect)) throw new InvalidOperationException("VirtualProtect failed: " + Marshal.GetLastWin32Error());
Marshal.WriteInt32(address, value);
}
private static void RestoreProtection(IntPtr address, uint oldProtect) {
uint ignored;
if (!VirtualProtect(address, new UIntPtr(4), oldProtect, out ignored)) throw new InvalidOperationException("VirtualProtect restore failed: " + Marshal.GetLastWin32Error());
}
private static void Main() {
Require(IntPtr.Size == 4, "This oracle must run as an x86 process");
string dll = Environment.GetEnvironmentVariable("FPARKAN_ORIGINAL_IRON3D_DLL");
Require(!String.IsNullOrEmpty(dll) && File.Exists(dll), "Set FPARKAN_ORIGINAL_IRON3D_DLL to the installed iron3d.dll");
string hash;
using (SHA256 sha = SHA256.Create()) using (FileStream stream = File.OpenRead(dll)) hash = BitConverter.ToString(sha.ComputeHash(stream)).Replace("-", "").ToLowerInvariant();
Require(hash == "d1dc4ea8535e2069b0a05c9e8be2724dd438ba44cca7a83007d8b2e50f9e35fa", "unexpected iron3d.dll hash: " + hash);
IntPtr module = LoadLibraryEx(dll, IntPtr.Zero, LoadWithAlteredSearchPath);
if (module == IntPtr.Zero) throw new InvalidOperationException("LoadLibraryEx failed: " + Marshal.GetLastWin32Error());
IntPtr readCode = IntPtr.Zero;
IntPtr writeCode = IntPtr.Zero;
IntPtr game = Marshal.AllocHGlobal(0x40);
IntPtr manager = Marshal.AllocHGlobal(0x40);
IntPtr record = Marshal.AllocHGlobal(0x68);
uint oldIatProtection = 0;
int oldGlobal = Marshal.ReadInt32(IntPtr.Add(module, GameGlobalRva));
int oldIat = Marshal.ReadInt32(IntPtr.Add(module, CreateSuperAiIatRva));
bool iatChanged = false;
bool controlWordCaptured = false;
ushort originalControlWord = 0;
FpuWordOperation setControlWord = null;
try {
byte[] readBytes = { 0x8b, 0x44, 0x24, 0x04, 0xd9, 0x38, 0xc2, 0x04, 0x00 }; // mov eax,[esp+4]; fnstcw [eax]; ret 4
byte[] writeBytes = { 0x8b, 0x44, 0x24, 0x04, 0xd9, 0x28, 0xc2, 0x04, 0x00 }; // mov eax,[esp+4]; fldcw [eax]; ret 4
readCode = BuildInstruction(readBytes);
writeCode = BuildInstruction(writeBytes);
ReadControlWord = (FpuWordOperation)Marshal.GetDelegateForFunctionPointer(readCode, typeof(FpuWordOperation));
setControlWord = (FpuWordOperation)Marshal.GetDelegateForFunctionPointer(writeCode, typeof(FpuWordOperation));
ReadControlWord(ref originalControlWord);
controlWordCaptured = true;
Console.WriteLine("iron3d.dll sha256=" + hash + " loadedBase=0x" + module.ToInt32().ToString("x8") + " defaultCW=0x" + originalControlWord.ToString("x4") + " defaultRC=" + ((originalControlWord >> 10) & 3));
for (int offset = 0; offset < 0x40; offset += 4) { Marshal.WriteInt32(game, offset, 0); Marshal.WriteInt32(manager, offset, 0); }
for (int offset = 0; offset < 0x68; offset += 4) Marshal.WriteInt32(record, offset, 0);
Marshal.WriteInt32(game, 0x1c, manager.ToInt32());
Marshal.WriteInt32(IntPtr.Add(module, GameGlobalRva), game.ToInt32());
CaptureDelegate = CaptureCreateSuperAi;
IntPtr callbackPointer = Marshal.GetFunctionPointerForDelegate(CaptureDelegate);
IntPtr iat = IntPtr.Add(module, CreateSuperAiIatRva);
ProtectWrite(iat, callbackPointer.ToInt32(), out oldIatProtection);
iatChanged = true;
CreateAiDelegate caller = (CreateAiDelegate)Marshal.GetDelegateForFunctionPointer(IntPtr.Add(module, CallerRva), typeof(CreateAiDelegate));
ushort nearest = (ushort)(originalControlWord & ~0x0c00);
ushort down = (ushort)((originalControlWord & ~0x0c00) | 0x0400);
ushort up = (ushort)((originalControlWord & ~0x0c00) | 0x0800);
ushort truncate = (ushort)((originalControlWord & ~0x0c00) | 0x0c00);
Expect(caller, record, setControlWord, nearest, 500.2857f, 751.8217f, 500, 752, "Autodemo anchors / nearest-even");
Expect(caller, record, setControlWord, nearest, 500.5f, 501.5f, 500, 502, "nearest tie pair");
Expect(caller, record, setControlWord, down, 500.5f, 501.5f, 500, 501, "down tie pair");
Expect(caller, record, setControlWord, up, 500.5f, 501.5f, 501, 502, "up tie pair");
Expect(caller, record, setControlWord, truncate, 500.5f, 501.5f, 500, 501, "truncate tie pair");
Expect(caller, record, setControlWord, nearest, -0.4f, 10000.4f, 0, 10000, "round-in lower/upper boundary");
Expect(caller, record, setControlWord, nearest, -0.6f, 10000.6f, 0xffffffff, 10001, "round-out lower/upper boundary");
Expect(caller, record, setControlWord, nearest, 4294967296.0f, 4294967808.0f, 0, 512, "low-DWORD wrap boundary");
}
finally {
if (iatChanged) {
IntPtr iat = IntPtr.Add(module, CreateSuperAiIatRva);
Marshal.WriteInt32(iat, oldIat);
RestoreProtection(iat, oldIatProtection);
}
Marshal.WriteInt32(IntPtr.Add(module, GameGlobalRva), oldGlobal);
if (controlWordCaptured) setControlWord(ref originalControlWord);
Marshal.FreeHGlobal(record);
Marshal.FreeHGlobal(manager);
Marshal.FreeHGlobal(game);
if (readCode != IntPtr.Zero) VirtualFree(readCode, UIntPtr.Zero, MemRelease);
if (writeCode != IntPtr.Zero) VirtualFree(writeCode, UIntPtr.Zero, MemRelease);
FreeLibrary(module);
}
}
}