-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathOpcodeExtractorVTable.cs
More file actions
145 lines (123 loc) · 5.39 KB
/
OpcodeExtractorVTable.cs
File metadata and controls
145 lines (123 loc) · 5.39 KB
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
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
namespace OpcodeExtractor;
public static class OpcodeExtractorVTable
{
private static Regex WhitespaceRegex = new Regex("\\s");
internal unsafe static Dictionary<int, string> Extract(JsonNode opcodeMapData, byte[] gameData, bool dumpAllOpcodes, string inputMapKey)
{
var signatureData = opcodeMapData["signature"]!;
string signature = "";
if (signatureData.GetValueKind() == System.Text.Json.JsonValueKind.Array)
{
foreach (var sigLine in signatureData.AsArray())
{
signature += WhitespaceRegex.Replace(sigLine!.ToString(), "");
}
}
else
{
signature = signatureData.ToString();
}
var matches = Common.SigScan(gameData, signature);
if (matches.Count != 1)
{
Console.Error.WriteLine($"Invalid matches count {matches.Count} from SigScan");
return [];
}
Dictionary<int, string> indexMap = [];
var mapData = opcodeMapData[inputMapKey]!;
if (mapData == null || mapData.GetValueKind() != System.Text.Json.JsonValueKind.Object)
{
Console.Error.WriteLine("Invalid data type for \"map\" in opcodes file");
return [];
}
foreach (var entry in mapData.AsObject())
{
var entryIndex = (int)entry.Value!;
indexMap[entryIndex] = entry.Key;
}
if (!ReadJSONInt(opcodeMapData, "switchTableOffset_offset", out var switchTableOffset_offset)) return [];
if (!ReadJSONInt(opcodeMapData, "switchTableCount_offset", out var switchTableCount_offset)) return [];
if (!ReadJSONInt(opcodeMapData, "expectedSwitchTableCount", out var expectedSwitchTableCount)) return [];
if (!ReadJSONInt(opcodeMapData, "defaultCaseAddr_offset", out var defaultCaseAddr_offset)) return [];
if (!ReadJSONInt(opcodeMapData, "imageBaseOffset_offset", out var imageBaseOffset_offset)) return [];
if (!ReadJSONInt(opcodeMapData, "switchTableDataOffset_offset", out var switchTableDataOffset_offset)) return [];
Console.WriteLine($"Scanning for opcode maps for {indexMap.Count} opcodes, dumping all: {dumpAllOpcodes}");
var offset = matches[0];
var opcodeMap = new Dictionary<int, string>();
fixed (byte* ptr = gameData)
{
byte* funcPtr = ptr + offset;
var switchTableOffset = *(sbyte*)(funcPtr + switchTableOffset_offset);
var switchTableCount = *(int*)(funcPtr + switchTableCount_offset);
Console.WriteLine($"Expected {expectedSwitchTableCount} entries, found {switchTableCount}");
if (expectedSwitchTableCount != switchTableCount)
{
Console.WriteLine("Switch table count mismatch, press any key to continue anyways");
Console.ReadKey(true);
}
var defaultCaseAddr = offset + defaultCaseAddr_offset + Common.ExtractRIPOffsetFromPtr(funcPtr + defaultCaseAddr_offset);
var imageBaseOffset = offset + imageBaseOffset_offset + Common.ExtractRIPOffsetFromPtr(funcPtr + imageBaseOffset_offset);
var switchTableDataOffset = *(int*)(funcPtr + switchTableDataOffset_offset);
var switchTableDataPtr = (int*)(ptr + imageBaseOffset + switchTableDataOffset);
for (int i = 0; i <= switchTableCount; ++i)
{
var switchTableDataPtrValue = switchTableDataPtr[i];
if (switchTableDataPtrValue + imageBaseOffset == defaultCaseAddr)
continue;
var caseBodyPtr = ptr + imageBaseOffset + switchTableDataPtrValue;
var opcode = i - switchTableOffset;
var vfTableIndex = GetVFTableIndex(caseBodyPtr);
if (indexMap.TryGetValue(vfTableIndex, out var name))
{
opcodeMap[opcode] = name;
if (dumpAllOpcodes)
{
opcodeMap[opcode + 0x10000] = $"Index_{vfTableIndex}";
}
}
else if (dumpAllOpcodes)
{
opcodeMap[opcode] = $"Index_{vfTableIndex}";
}
}
}
return opcodeMap;
}
private static bool ReadJSONInt(JsonNode opcodeMapData, string key, out int value)
{
var mapData = opcodeMapData[key]!;
if (mapData.GetValueKind() != System.Text.Json.JsonValueKind.Number)
{
Console.Error.WriteLine($"Invalid data type for \"${key}\" in opcodes file");
value = 0;
return false;
}
value = mapData.GetValue<int>();
return true;
}
private static unsafe int GetVFTableIndex(byte* caseBodyPtr)
{
int index;
switch (caseBodyPtr[9])
{
// One-byte value
case 0x60:
index = *(caseBodyPtr + 10);
break;
// Four-byte value
case 0xA0:
index = *(int*)(caseBodyPtr + 10);
break;
default:
return -1;
}
// Make sure we're divisible by 8
if (index % 8 != 0)
return -1;
// First two VF table entries are constructor and destructor
return (index / 8) - 2;
}
}