-
Notifications
You must be signed in to change notification settings - Fork 0
/
residmap.cpp
431 lines (373 loc) · 11.3 KB
/
residmap.cpp
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
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
#include "pakDataTypes.h"
#include "residmap.h"
#include <sstream>
//#define RESIDMAP_FILENAME "vdata/residmap.dat.xml"
map<wstring, u32> g_repakMappings;
map<u32, wstring> g_pakMappings;
//Get a resource ID from the lookup table
u32 getKnownResID(wstring sName)
{
if(!g_repakMappings.count(sName))
{
//TODO: Hash filenames yadda yadda yadda
//cout << "ERROR: Invalid filename for recompression: " << ws2s(sName) << ". Only files in the original residmap.dat can be compressed." << endl;
return 0;
}
return g_repakMappings[sName];
}
//Get a resource ID from the lookup table, or by hashing if unknown
u32 getResID(wstring sName)
{
if(!g_repakMappings.count(sName))
return hash(sName);
return g_repakMappings[sName];
}
//Get a filename from a resource ID
const wchar_t* getName(u32 resId)
{
if(!g_pakMappings.count(resId))
{
cout << "No residmap entry for id " << resId << endl;
ostringstream oss;
oss << "output/" << resId;
return s2ws(oss.str()).c_str();
}
return g_pakMappings[resId].c_str();
}
//Parse our array of values to get the mappings
void initResMap()
{
for(u32 i = 0; i < NUM_MAPPINGS; i++)
{
g_repakMappings[s2ws(g_residMap[i].name)] = g_residMap[i].id;
g_pakMappings[g_residMap[i].id] = s2ws(g_residMap[i].name);
}
}
//Functions from Stack Overflow peoples
wstring s2ws(const string& s)
{
int len;
int slength = (int)s.length();
len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0);
wstring r(len, L'\0');
MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, &r[0], len);
return r;
}
string ws2s(const wstring& s)
{
int len;
int slength = (int)s.length();
len = WideCharToMultiByte(CP_ACP, 0, s.c_str(), slength, 0, 0, 0, 0);
string r(len, '\0');
WideCharToMultiByte(CP_ACP, 0, s.c_str(), slength, &r[0], len, 0, 0);
return r;
}
//convert a string to lowercase. Also change forward slashes back to backslashes.
wstring stolower( const wstring s )
{
wstring result = s;
for(unsigned int i = 0; i < s.size(); i++)
{
wchar_t c = s[i];
if( (c >= L'A') && (c <= 'Z') )
{
c += L'a' - L'A';
result[i] = c;
}
if(c == L'/')
result[i] = L'\\';
}
return result;
}
//Converts forward slashes to backslashes
wstring toBackslashes(const wstring s)
{
wstring result = s;
for(unsigned int i = 0; i < s.size(); i++)
{
wchar_t c = s[i];
if(c == L'/')
result[i] = L'\\';
}
return result;
}
//Function from Allan for getting a hash from a filename NOTE: Don't use directly! Use hash() instead
u32 LIHash( const wchar_t *pCaseInsensitiveStr )
{
u32 hash = 0xABABABAB;
while( *pCaseInsensitiveStr )
{
hash ^= *pCaseInsensitiveStr;
hash = (hash << 7) | (hash >> (32-7));
++pCaseInsensitiveStr;
}
return hash;
}
//Have to do some converting to get from std::wstring to wchar_t*
u32 hash(wstring sFilename)
{
//Convert to lowercase first
return LIHash(stolower(sFilename).c_str());
}
//Read in mappings from a residmap.dat file
bool parseResidMap(const wchar_t* cFilename)
{
//Read in the mappings directly from residmap.dat
FILE* fp = _wfopen(cFilename, TEXT("rb"));
if(fp == NULL)
{
cout << "Error: Unable to open file " << ws2s(cFilename) << endl;
return false;
}
//Read in the headers
ResidMapHeader rmh;
if(fread((void*)&(rmh), 1, sizeof(ResidMapHeader), fp) != sizeof(ResidMapHeader))
{
cout << "ResidMapHeader malformed" << endl;
fclose(fp);
return false;
}
//Read in the mappings
map<u32,i32> mIDMappings;
fseek(fp, rmh.maps.offset, SEEK_SET);
for(int i = 0; i < rmh.maps.count; i++)
{
MappingHeader mh;
if(fread((void*)&mh, 1, sizeof(MappingHeader), fp) != sizeof(MappingHeader))
{
cout << "MappingHeader malformed" << endl;
fclose(fp);
return false;
}
//Store
mIDMappings[mh.resId] = mh.strId;
}
//Now for string table header
StringTableHeader sth;
fseek(fp, rmh.stringTableBytes.offset, SEEK_SET);
if(fread((void*)&sth, 1, sizeof(StringTableHeader), fp) != sizeof(StringTableHeader))
{
cout << "StringTableHeader malformed" << endl;
fclose(fp);
return false;
}
//Allocate memory for this many string table & pointer entries
vector<StringTableEntry> vStringTableList;
vector<StringPointerEntry> vStringPointerList;
vector<wchar_t> vStringList;
vStringTableList.reserve(sth.numStrings);
vStringPointerList.reserve(sth.numPointers);
vStringList.reserve((sizeof(wchar_t) * sth.numStrings)*256);
//Read in string table entries
for(int i = 0; i < sth.numStrings; i++)
{
StringTableEntry ste;
if(fread((void*)&ste, 1, sizeof(StringTableEntry), fp) != sizeof(StringTableEntry))
{
cout << "StringTableEntry " << i << " malformed out of " << sth.numStrings << endl;
fclose(fp);
return false;
}
//Store
vStringTableList[i] = ste;
}
//and string table pointers
for(int i = 0; i < sth.numPointers; i++)
{
StringPointerEntry spe;
if(fread((void*)&spe, 1, sizeof(StringPointerEntry), fp) != sizeof(StringPointerEntry))
{
cout << "StringPointerEntry " << i << " malformed out of " << sth.numPointers << endl;
fclose(fp);
return false;
}
//Store
vStringPointerList[i] = spe;
}
//Now read in the strings until we hit the end of the file
int c;
while((c = fgetc(fp)) != EOF)
{
if(c == '\\') //Change all backslashes to forward slashes. Tsk, tsk, Allan.
c = '/';
vStringList.push_back(c);
}
fclose(fp);
//Add these mappings to our mapping list
for(map<u32, i32>::iterator i = mIDMappings.begin(); i != mIDMappings.end(); i++)
{
i32 strId = i->second;
u32 finalNum = i->first;
wchar_t* cData = vStringList.data();
wstring s = &cData[vStringPointerList[vStringTableList[strId].pointerIndex].offset];
//Store forward and reverse mappings for this file
g_repakMappings[s] = finalNum;
g_pakMappings[finalNum] = s;
}
return true;
}
//TODO: Severe problem if unknown ID and this isn't read first. Write small residmap.dat files to beginnings of pakfiles
bool residMapToXML(const wchar_t* cFilename)
{
//DEBUG: Write it all out to a text file as well
//ofstream ofile("residmapout.txt");
//int iTotal = 0;
//Pull our mappings out of this file
if(!parseResidMap(cFilename))
return false;
//Now save this out to XML
wstring sFilename = cFilename;
sFilename += TEXT(".xml");
XMLDocument* doc = new XMLDocument;
int iErr = doc->LoadFile(ws2s(sFilename).c_str());
XMLElement* root;
map<u32,wstring> mCurXMLMappings;
if(iErr != XML_NO_ERROR)
{
// residmap.xml isn't here or is malformed; overwrite
delete doc;
doc = new XMLDocument;
root = doc->NewElement("mappings"); //Create the root element
doc->InsertFirstChild(root);
}
else
{
root = doc->RootElement(); //Get the root element
//Parse all elements in the XML and populate our map
for(XMLElement* elem = root->FirstChildElement(); elem != NULL; elem = elem->NextSiblingElement())
{
u32 id = 0;
elem->QueryUnsignedAttribute("id", &id);
if(id != 0)
mCurXMLMappings[id] = s2ws(elem->Attribute("filename"));
}
}
//Merge with preexisting XML
for(map<u32, wstring>::iterator i = g_pakMappings.begin(); i != g_pakMappings.end(); i++)
{
//ofile << "{" << i->first << "u,\"" << ws2s(i->second).c_str() << "\"}," << endl;
//iTotal++;
if(mCurXMLMappings.count(i->first))
{
if(mCurXMLMappings[i->first] != i->second)
{
cout << "Error: Conflict in IDs in residMapToXML(). Abort." << endl;
delete doc;
return false;
}
continue;
}
XMLElement* elem = doc->NewElement("mapping");
elem->SetAttribute("id", i->first);
elem->SetAttribute("filename", ws2s(i->second).c_str());
root->InsertEndChild(elem);
}
//Done
doc->SaveFile(ws2s(sFilename).c_str());
delete doc;
//ofile << endl << iTotal << endl;
//ofile.close();
return true;
}
//Save residmap.dat.xml back out to residmap.dat
bool XMLToResidMap(const wchar_t* cFilename)
{
//Open file
wstring sXMLFile = cFilename;
sXMLFile += TEXT(".xml");
XMLDocument* doc = new XMLDocument;
int iErr = doc->LoadFile(ws2s(sXMLFile).c_str());
if(iErr != XML_NO_ERROR)
{
cout << "Error parsing XML file " << ws2s(sXMLFile) << ": Error " << iErr << endl;
delete doc;
return false;
}
//Grab root element
XMLElement* root = doc->RootElement();
if(root == NULL)
{
cout << "Error: Root element NULL in XML file " << ws2s(sXMLFile) << endl;
delete doc;
return false;
}
//Read in XML data
list<char> lUTFData;
list<MappingHeader> lMappings;
list<StringTableEntry> lStringTable;
list<StringPointerEntry> lStringPointers;
for(XMLElement* elem = root->FirstChildElement("mapping"); elem != NULL; elem = elem->NextSiblingElement("mapping"))
{
int id;
if(elem->QueryIntAttribute("id", &id) != XML_NO_ERROR)
{
cout << "Unable to get mapping ID from XML file " << ws2s(sXMLFile) << endl;
delete doc;
return false;
}
const char* cName = elem->Attribute("filename");
if(cName == NULL)
{
cout << "Unable to get mapping filename from XML file " << ws2s(sXMLFile) << endl;
delete doc;
return false;
}
//Make mapping header that maps this resource ID to the wstring ID
MappingHeader mh;
mh.resId = id;
mh.strId = lStringTable.size();
lMappings.push_back(mh);
//Make StringTableEntry that maps this wstring ID to a wstring data pointer
StringTableEntry ste;
ste.pointerIndex = lStringPointers.size();
ste.pointerCount = 1;
lStringTable.push_back(ste);
//Make the StringPointerEntry that maps this pointer to a location in the wstring data
StringPointerEntry spe;
spe.languageId = LANGID_ENGLISH;
spe.offset = lUTFData.size();
lStringPointers.push_back(spe);
//Add this string to our string list
const char* cFile = ws2s(toBackslashes(s2ws(cName))).c_str();
unsigned int iStrLen = strlen(cFile)+1; //+1 so we can keep the terminating \0 character
for(unsigned int i = 0; i < iStrLen; i++)
lUTFData.push_back(cFile[i]); //Copy data over
}
delete doc; //Done reading XML
//Open our output file
FILE* f = _wfopen(cFilename, TEXT("wb"));
if(f == NULL)
{
cout << "Error: Unable to open output file " << ws2s(cFilename) << endl;
return false;
}
//Write out our ResidMapHeader
ResidMapHeader rmh;
size_t curOffset = sizeof(ResidMapHeader);
rmh.maps.count = lMappings.size();
rmh.maps.offset = curOffset;
curOffset += sizeof(MappingHeader) * lMappings.size();
//The count for this is the number of bytes for all of it
rmh.stringTableBytes.count = sizeof(StringTableHeader) + sizeof(StringTableEntry) * lStringTable.size() + lUTFData.size();
rmh.stringTableBytes.offset = curOffset;
fwrite(&rmh, 1, sizeof(ResidMapHeader), f);
//Write out our MappingHeaders
for(list<MappingHeader>::iterator i = lMappings.begin(); i != lMappings.end(); i++)
fwrite(&(*i), 1, sizeof(MappingHeader), f);
//Write out our StringTableHeader
StringTableHeader sth;
sth.numStrings = lStringTable.size();
sth.numPointers = lStringPointers.size();
fwrite(&sth, 1, sizeof(StringTableHeader), f);
//Write out our StringTableEntries
for(list<StringTableEntry>::iterator i = lStringTable.begin(); i != lStringTable.end(); i++)
fwrite(&(*i), 1, sizeof(StringTableEntry), f);
//Write out our StringPointerEntries
for(list<StringPointerEntry>::iterator i = lStringPointers.begin(); i != lStringPointers.end(); i++)
fwrite(&(*i), 1, sizeof(StringPointerEntry), f);
//Write out our wstring data
for(list<char>::iterator i = lUTFData.begin(); i != lUTFData.end(); i++)
fwrite(&(*i), 1, 1, f);
fclose(f); //Done
return true;
}