1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
|
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);
}
}
}
|