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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
|
#include "pch.h"
#include "convar.h"
#include "hoststate.h"
#include "r2engine.h"
#include "NSMem.h"
#include <fstream>
#include <filesystem>
AUTOHOOK_INIT()
namespace fs = std::filesystem;
const int AINET_VERSION_NUMBER = 57;
const int AINET_SCRIPT_VERSION_NUMBER = 21;
const int PLACEHOLDER_CRC = 0;
const int MAX_HULLS = 5;
#pragma pack(push, 1)
struct CAI_NodeLink
{
short srcId;
short destId;
bool hulls[MAX_HULLS];
char unk0;
char unk1; // maps => unk0 on disk
char unk2[5];
int64_t flags;
};
#pragma pack(pop)
#pragma pack(push, 1)
struct CAI_NodeLinkDisk
{
short srcId;
short destId;
char unk0;
bool hulls[MAX_HULLS];
};
#pragma pack(pop)
#pragma pack(push, 1)
struct CAI_Node
{
int index; // not present on disk
float x;
float y;
float z;
float hulls[MAX_HULLS];
float yaw;
int unk0; // always 2 in buildainfile, maps directly to unk0 in disk struct
int unk1; // maps directly to unk1 in disk struct
int unk2[MAX_HULLS]; // maps directly to unk2 in disk struct, despite being ints rather than shorts
// view server.dll+393672 for context and death wish
char unk3[MAX_HULLS]; // hell on earth, should map to unk3 on disk
char pad[3]; // aligns next bytes
float unk4[MAX_HULLS]; // i have no fucking clue, calculated using some kind of demon hell function float magic
CAI_NodeLink** links;
char unk5[16];
int linkcount;
int unk11; // bad name lmao
short unk6; // should match up to unk4 on disk
char unk7[16]; // padding until next bit
short unk8; // should match up to unk5 on disk
char unk9[8]; // padding until next bit
char unk10[8]; // should match up to unk6 on disk
};
#pragma pack(pop)
// the way CAI_Nodes are represented in on-disk ain files
#pragma pack(push, 1)
struct CAI_NodeDisk
{
float x;
float y;
float z;
float yaw;
float hulls[MAX_HULLS];
char unk0;
int unk1;
short unk2[MAX_HULLS];
char unk3[MAX_HULLS];
short unk4;
short unk5;
char unk6[8];
}; // total size of 68 bytes
#pragma pack(pop)
#pragma pack(push, 1)
struct UnkNodeStruct0
{
int index;
char unk0;
char unk1; // maps to unk1 on disk
char pad0[2]; // padding to +8
float x;
float y;
float z;
char pad5[4];
int* unk2; // maps to unk5 on disk;
char pad1[16]; // pad to +48
int unkcount0; // maps to unkcount0 on disk
char pad2[4]; // pad to +56
int* unk3;
char pad3[16]; // pad to +80
int unkcount1;
char pad4[132];
char unk5;
};
#pragma pack(pop)
int* pUnkStruct0Count;
UnkNodeStruct0*** pppUnkNodeStruct0s;
#pragma pack(push, 1)
struct UnkLinkStruct1
{
short unk0;
short unk1;
int unk2;
char unk3;
char unk4;
char unk5;
};
#pragma pack(pop)
int* pUnkLinkStruct1Count;
UnkLinkStruct1*** pppUnkStruct1s;
#pragma pack(push, 1)
struct CAI_ScriptNode
{
float x;
float y;
float z;
uint64_t scriptdata;
};
#pragma pack(pop)
#pragma pack(push, 1)
struct CAI_Network
{
// +0
char unk0[8];
// +8
int linkcount; // this is uninitialised and never set on ain build, fun!
// +12
char unk1[124];
// +136
int zonecount;
// +140
char unk2[16];
// +156
int unk5; // unk8 on disk
// +160
char unk6[4];
// +164
int hintcount;
// +168
short hints[2000]; // these probably aren't actually hints, but there's 1 of them per hint so idk
// +4168
int scriptnodecount;
// +4172
CAI_ScriptNode scriptnodes[4000];
// +84172
int nodecount;
// +84176
CAI_Node** nodes;
};
#pragma pack(pop)
char** pUnkServerMapversionGlobal;
ConVar* Cvar_ns_ai_dumpAINfileFromLoad;
void DumpAINInfo(CAI_Network* aiNetwork)
{
fs::path writePath(fmt::format("{}/maps/graphs", "r2"));
writePath /= R2::g_pHostState->m_levelName;
writePath += ".ain";
// dump from memory
spdlog::info("writing ain file {}", writePath.string());
spdlog::info("");
spdlog::info("");
spdlog::info("");
spdlog::info("");
spdlog::info("");
std::ofstream writeStream(writePath, std::ofstream::binary);
spdlog::info("writing ainet version: {}", AINET_VERSION_NUMBER);
writeStream.write((char*)&AINET_VERSION_NUMBER, sizeof(int));
// could probably be cleaner but whatever
int mapVersion = *(int*)(*pUnkServerMapversionGlobal + 104);
spdlog::info("writing map version: {}", mapVersion); // temp
writeStream.write((char*)&mapVersion, sizeof(int));
spdlog::info("writing placeholder crc: {}", PLACEHOLDER_CRC);
writeStream.write((char*)&PLACEHOLDER_CRC, sizeof(int));
int calculatedLinkcount = 0;
// path nodes
spdlog::info("writing nodecount: {}", aiNetwork->nodecount);
writeStream.write((char*)&aiNetwork->nodecount, sizeof(int));
for (int i = 0; i < aiNetwork->nodecount; i++)
{
// construct on-disk node struct
CAI_NodeDisk diskNode;
diskNode.x = aiNetwork->nodes[i]->x;
diskNode.y = aiNetwork->nodes[i]->y;
diskNode.z = aiNetwork->nodes[i]->z;
diskNode.yaw = aiNetwork->nodes[i]->yaw;
memcpy(diskNode.hulls, aiNetwork->nodes[i]->hulls, sizeof(diskNode.hulls));
diskNode.unk0 = (char)aiNetwork->nodes[i]->unk0;
diskNode.unk1 = aiNetwork->nodes[i]->unk1;
for (int j = 0; j < MAX_HULLS; j++)
{
diskNode.unk2[j] = (short)aiNetwork->nodes[i]->unk2[j];
spdlog::info((short)aiNetwork->nodes[i]->unk2[j]);
}
memcpy(diskNode.unk3, aiNetwork->nodes[i]->unk3, sizeof(diskNode.unk3));
diskNode.unk4 = aiNetwork->nodes[i]->unk6;
diskNode.unk5 =
-1; // aiNetwork->nodes[i]->unk8; // this field is wrong, however, it's always -1 in vanilla navmeshes anyway, so no biggie
memcpy(diskNode.unk6, aiNetwork->nodes[i]->unk10, sizeof(diskNode.unk6));
spdlog::info("writing node {} from {} to {:x}", aiNetwork->nodes[i]->index, (void*)aiNetwork->nodes[i], writeStream.tellp());
writeStream.write((char*)&diskNode, sizeof(CAI_NodeDisk));
calculatedLinkcount += aiNetwork->nodes[i]->linkcount;
}
// links
spdlog::info("linkcount: {}", aiNetwork->linkcount);
spdlog::info("calculated total linkcount: {}", calculatedLinkcount);
calculatedLinkcount /= 2;
if (Cvar_ns_ai_dumpAINfileFromLoad->GetBool())
{
if (aiNetwork->linkcount == calculatedLinkcount)
spdlog::info("caculated linkcount is normal!");
else
spdlog::warn("calculated linkcount has weird value! this is expected on build!");
}
spdlog::info("writing linkcount: {}", calculatedLinkcount);
writeStream.write((char*)&calculatedLinkcount, sizeof(int));
for (int i = 0; i < aiNetwork->nodecount; i++)
{
for (int j = 0; j < aiNetwork->nodes[i]->linkcount; j++)
{
// skip links that don't originate from current node
if (aiNetwork->nodes[i]->links[j]->srcId != aiNetwork->nodes[i]->index)
continue;
CAI_NodeLinkDisk diskLink;
diskLink.srcId = aiNetwork->nodes[i]->links[j]->srcId;
diskLink.destId = aiNetwork->nodes[i]->links[j]->destId;
diskLink.unk0 = aiNetwork->nodes[i]->links[j]->unk1;
memcpy(diskLink.hulls, aiNetwork->nodes[i]->links[j]->hulls, sizeof(diskLink.hulls));
spdlog::info("writing link {} => {} to {:x}", diskLink.srcId, diskLink.destId, writeStream.tellp());
writeStream.write((char*)&diskLink, sizeof(CAI_NodeLinkDisk));
}
}
// don't know what this is, it's likely a block from tf1 that got deprecated? should just be 1 int per node
spdlog::info("writing {:x} bytes for unknown block at {:x}", aiNetwork->nodecount * sizeof(uint32_t), writeStream.tellp());
uint32_t* unkNodeBlock = new uint32_t[aiNetwork->nodecount];
memset(unkNodeBlock, 0, aiNetwork->nodecount * sizeof(uint32_t));
writeStream.write((char*)unkNodeBlock, aiNetwork->nodecount * sizeof(uint32_t));
delete[] unkNodeBlock;
// TODO: this is traverse nodes i think? these aren't used in tf2 ains so we can get away with just writing count=0 and skipping
// but ideally should actually dump these
spdlog::info("writing {} traversal nodes at {:x}...", 0, writeStream.tellp());
short traverseNodeCount = 0;
writeStream.write((char*)&traverseNodeCount, sizeof(short));
// only write count since count=0 means we don't have to actually do anything here
// TODO: ideally these should be actually dumped, but they're always 0 in tf2 from what i can tell
spdlog::info("writing {} bytes for unknown hull block at {:x}", MAX_HULLS * 8, writeStream.tellp());
char* unkHullBlock = new char[MAX_HULLS * 8];
memset(unkHullBlock, 0, MAX_HULLS * 8);
writeStream.write(unkHullBlock, MAX_HULLS * 8);
delete[] unkHullBlock;
// unknown struct that's seemingly node-related
spdlog::info("writing {} unknown node structs at {:x}", *pUnkStruct0Count, writeStream.tellp());
writeStream.write((char*)pUnkStruct0Count, sizeof(*pUnkStruct0Count));
for (int i = 0; i < *pUnkStruct0Count; i++)
{
spdlog::info("writing unknown node struct {} at {:x}", i, writeStream.tellp());
UnkNodeStruct0* nodeStruct = (*pppUnkNodeStruct0s)[i];
writeStream.write((char*)&nodeStruct->index, sizeof(nodeStruct->index));
writeStream.write((char*)&nodeStruct->unk1, sizeof(nodeStruct->unk1));
writeStream.write((char*)&nodeStruct->x, sizeof(nodeStruct->x));
writeStream.write((char*)&nodeStruct->y, sizeof(nodeStruct->y));
writeStream.write((char*)&nodeStruct->z, sizeof(nodeStruct->z));
writeStream.write((char*)&nodeStruct->unkcount0, sizeof(nodeStruct->unkcount0));
for (int j = 0; j < nodeStruct->unkcount0; j++)
{
short unk2Short = (short)nodeStruct->unk2[j];
writeStream.write((char*)&unk2Short, sizeof(unk2Short));
}
writeStream.write((char*)&nodeStruct->unkcount1, sizeof(nodeStruct->unkcount1));
for (int j = 0; j < nodeStruct->unkcount1; j++)
{
short unk3Short = (short)nodeStruct->unk3[j];
writeStream.write((char*)&unk3Short, sizeof(unk3Short));
}
writeStream.write((char*)&nodeStruct->unk5, sizeof(nodeStruct->unk5));
}
// unknown struct that's seemingly link-related
spdlog::info("writing {} unknown link structs at {:x}", *pUnkLinkStruct1Count, writeStream.tellp());
writeStream.write((char*)pUnkLinkStruct1Count, sizeof(*pUnkLinkStruct1Count));
for (int i = 0; i < *pUnkLinkStruct1Count; i++)
{
// disk and memory structs are literally identical here so just directly write
spdlog::info("writing unknown link struct {} at {:x}", i, writeStream.tellp());
writeStream.write((char*)(*pppUnkStruct1s)[i], sizeof(*(*pppUnkStruct1s)[i]));
}
// some weird int idk what this is used for
writeStream.write((char*)&aiNetwork->unk5, sizeof(aiNetwork->unk5));
// tf2-exclusive stuff past this point, i.e. ain v57 only
spdlog::info("writing {} script nodes at {:x}", aiNetwork->scriptnodecount, writeStream.tellp());
writeStream.write((char*)&aiNetwork->scriptnodecount, sizeof(aiNetwork->scriptnodecount));
for (int i = 0; i < aiNetwork->scriptnodecount; i++)
{
// disk and memory structs are literally identical here so just directly write
spdlog::info("writing script node {} at {:x}", i, writeStream.tellp());
writeStream.write((char*)&aiNetwork->scriptnodes[i], sizeof(aiNetwork->scriptnodes[i]));
}
spdlog::info("writing {} hints at {:x}", aiNetwork->hintcount, writeStream.tellp());
writeStream.write((char*)&aiNetwork->hintcount, sizeof(aiNetwork->hintcount));
for (int i = 0; i < aiNetwork->hintcount; i++)
{
spdlog::info("writing hint data {} at {:x}", i, writeStream.tellp());
writeStream.write((char*)&aiNetwork->hints[i], sizeof(aiNetwork->hints[i]));
}
writeStream.close();
}
AUTOHOOK(CAI_NetworkBuilder__Build, server.dll + 0x385E20,
void,, (void* builder, CAI_Network* aiNetwork, void* unknown))
{
CAI_NetworkBuilder__Build(builder, aiNetwork, unknown);
DumpAINInfo(aiNetwork);
}
AUTOHOOK(LoadAINFile, server.dll + 0x3933A0,
void,, (void* aimanager, void* buf, const char* filename))
{
LoadAINFile(aimanager, buf, filename);
if (Cvar_ns_ai_dumpAINfileFromLoad->GetBool())
{
spdlog::info("running DumpAINInfo for loaded file {}", filename);
DumpAINInfo(*(CAI_Network**)((char*)aimanager + 2536));
}
}
ON_DLL_LOAD("server.dll", BuildAINFile, (HMODULE baseAddress))
{
AUTOHOOK_DISPATCH()
Cvar_ns_ai_dumpAINfileFromLoad = new ConVar(
"ns_ai_dumpAINfileFromLoad", "0", FCVAR_NONE, "For debugging: whether we should dump ain data for ains loaded from disk");
pUnkStruct0Count = (int*)((char*)baseAddress + 0x1063BF8);
pppUnkNodeStruct0s = (UnkNodeStruct0***)((char*)baseAddress + 0x1063BE0);
pUnkLinkStruct1Count = (int*)((char*)baseAddress + 0x1063AA8);
pppUnkStruct1s = (UnkLinkStruct1***)((char*)baseAddress + 0x1063A90);
pUnkServerMapversionGlobal = (char**)((char*)baseAddress + 0xBFBE08);
}
|