Waternoose, the XBOX 360 emulator
A very young XBOX 360 emulator that aims to boot games some day
Loading...
Searching...
No Matches
xex.cpp
1#include "xex.h"
2#include <cstring>
3#include <cstdio>
4#include <cstdlib>
5#include <vector>
6#include <cassert>
7#include <fstream>
8#include <crypto/rijndael-alg-fst.h>
9#include <memory/memory.h>
10#include <util.h>
11#include <loader/lzx.h>
12#include <kernel/kernel.h>
13#include <kernel/modules/xboxkrnl.h>
14
15static uint32_t handle = 0x10000001;
16
17XexLoader* xam;
18
19uint32_t mainXexBase, mainXexSize;
20
21// Am I allowed to have this here?
22// Xenia gets away with it, I'm sure it's fine
23static const uint8_t xe_xex2_retail_key[16] = {
24 0x20, 0xB1, 0x85, 0xA5, 0x9D, 0x28, 0xFD, 0xC3,
25 0x40, 0x58, 0x3F, 0xBB, 0x08, 0x96, 0xBF, 0x91};
26
27void aes_decrypt_buffer(const uint8_t* session_key, const uint8_t* input_buffer,
28 const size_t input_size, uint8_t* output_buffer,
29 const size_t output_size)
30{
31 uint32_t rk[4 * (MAXNR + 1)];
32 uint8_t ivec[16] = {0};
33 int32_t Nr = rijndaelKeySetupDec(rk, session_key, 128);
34 const uint8_t* ct = input_buffer;
35 uint8_t* pt = output_buffer;
36 for (size_t n = 0; n < input_size; n += 16, ct += 16, pt += 16)
37 {
38 rijndaelDecrypt(rk, Nr, ct, pt);
39 for (size_t i = 0; i < 16; i++)
40 {
41 pt[i] ^= ivec[i];
42 ivec[i] = ct[i];
43 }
44 }
45}
46
47XexLoader::XexLoader(uint8_t *buffer, size_t len, std::string path)
48: IModule(path.substr(path.find_last_of('/')+1).c_str())
49{
50 this->buffer = buffer;
51 this->path = path.substr(0, path.find_last_of('/'));
52 this->xexHandle = handle++;
53
54 header = *(xexHeader_t*)buffer;
55 header.module_flags = bswap32(header.module_flags);
56 header.header_size = bswap32(header.header_size);
57 header.sec_info_offset = bswap32(header.sec_info_offset);
58 header.optional_header_count = bswap32(header.optional_header_count);
59
60 if (memcmp(header.magic, "XEX2", 4) != 0)
61 {
62 char magic[5];
63 strncpy(magic, header.magic, 4);
64 magic[4] = 0;
65 printf("ERROR: Invalid magic: Expected \"XEX2\", got \"%s\"\n", magic);
66 exit(1);
67 }
68
69 printf("%d optional headers found\n", header.optional_header_count);
70 printf("PE data is at offset 0x%08x\n", header.header_size);
71
72 std::vector<optionalHeader_t> optHeaders;
73 size_t optHeaderSize = header.optional_header_count*sizeof(optionalHeader_t);
74 for (size_t i = 0; i < optHeaderSize; i += sizeof(optionalHeader_t))
75 {
76 size_t offs = i + sizeof(xexHeader_t);
77 optionalHeader_t opt = *(optionalHeader_t*)&buffer[offs];
78 opt.id = bswap32(opt.id);
79 opt.offset = bswap32(opt.offset);
80 optHeaders.push_back(opt);
81 }
82
83 if (!mainXexSize)
84 mainXexSize = image_size();
85
86 // Parse security info, including AES key decryption
87 uint8_t* aes_key = (buffer+header.sec_info_offset+336);
88 aes_decrypt_buffer(xe_xex2_retail_key, aes_key, 16, session_key, 16);
89
90 printf("AES key is 0x%02x", session_key[0]);
91 for (int i = 0; i < 15; i++)
92 {
93 printf("%02x", session_key[i+1]);
94 }
95 printf("\n");
96
97 for (auto& hdr : optHeaders)
98 {
99 switch (hdr.id)
100 {
101 case 0x2ff:
102 break;
103 case 0x3ff:
104 {
105 fileInfoOffset = hdr.offset;
106 ParseFileInfo(hdr.offset);
107 break;
108 }
109 case 0x10100:
110 entryPoint = hdr.value;
111 printf("Image entry point is 0x%08x\n", entryPoint);
112 break;
113 case 0x10201:
114 baseAddress = hdr.value;
115 if (!mainXexBase)
116 mainXexBase = baseAddress;
117 printf("Image base is 0x%08x\n", baseAddress);
118 break;
119 case 0x103FF:
120 importBaseAddr = hdr.offset;
121 break;
122 case 0x20200:
123 stackSize = hdr.value;
124 printf("Stack size is 0x%08x\n", hdr.value);
125 break;
126 default:
127 printf("Unknown optional header ID: 0x%08x\n", hdr.id);
128 }
129 }
130
131 // Decrypt/decompress the file
132 printf("%d, %d\n", encryptionFormat, compressionFormat);
133 char* outBuffer;
134 uint32_t uncompressedSize;
135 switch (compressionFormat)
136 {
137 case 1:
138 uncompressedSize = ReadImageBasicCompressed(buffer, len, &outBuffer);
139 break;
140 case 2:
141 uncompressedSize = ReadImageCompressed(buffer, len, &outBuffer);
142 break;
143 default:
144 printf("Unknown compression format %d\n", compressionFormat);
145 exit(1);
146 }
147
148 std::ofstream out("out.pe");
149 out.write(outBuffer, uncompressedSize);
150 out.close();
151
152 // We've got the PE header inside outBuffer now
153 if (*(uint32_t*)outBuffer != 0x00905a4d /*"PE" followed by 0x9000*/)
154 {
155 printf("Invalid PE magic\n");
156 }
157 else
158 printf("Found valid PE header file\n");
159
160 void* base = Memory::AllocMemory(baseAddress, uncompressedSize);
161 memcpy(base, outBuffer, uncompressedSize);
162
163 // Load exports
164 exportBaseAddr = bswap32(*(uint32_t*)&buffer[header.sec_info_offset+0x160]);
165 if (exportBaseAddr)
166 {
167 exportTable.magic[0] = Memory::Read32(exportBaseAddr+0x00);
168 exportTable.magic[1] = Memory::Read32(exportBaseAddr+0x04);
169 exportTable.magic[2] = Memory::Read32(exportBaseAddr+0x08);
170 exportTable.modulenumber[0] = Memory::Read32(exportBaseAddr+0x0C);
171 exportTable.modulenumber[1] = Memory::Read32(exportBaseAddr+0x10);
172 exportTable.version[0] = Memory::Read32(exportBaseAddr+0x14);
173 exportTable.version[1] = Memory::Read32(exportBaseAddr+0x18);
174 exportTable.version[2] = Memory::Read32(exportBaseAddr+0x1C);
175 exportTable.imagebaseaddr = Memory::Read32(exportBaseAddr+0x20);
176 exportTable.count = Memory::Read32(exportBaseAddr+0x24);
177 exportTable.base = Memory::Read32(exportBaseAddr+0x28);
178 }
179
180 // Now we can patch module calls
181 // xam.xex and xboxkrnl.exe are the two most common imports afaict
182 importHeader_t importHdr = *(importHeader_t*)&buffer[importBaseAddr];
183 importHdr.size = bswap32(importHdr.size);
184 importHdr.stringTable.count = bswap32(importHdr.stringTable.count);
185 importHdr.stringTable.size = bswap32(importHdr.stringTable.size);
186
187 std::vector<std::string> importNames;
188 for (size_t i = 0, j = 0; i < importHdr.stringTable.size && j < importHdr.stringTable.count; j++)
189 {
190 const char* string = (const char*)&buffer[importBaseAddr+sizeof(importHeader_t)+i];
191 importNames.push_back(std::string(string));
192
193 i += strlen(string) + 1;
194 if ((i % 4) != 0)
195 i += 4 - (i % 4);
196
197 printf("Found import \"%s\"\n", string);
198 }
199
200 uint32_t libraryoffs = importHdr.stringTable.size + 12;
201 while (libraryoffs < importHdr.size)
202 {
203 libraryHeader_t libHdr = *(libraryHeader_t*)&buffer[importBaseAddr + libraryoffs];
204
205 libHdr.count = bswap16(libHdr.count);
206 libHdr.id = bswap32(libHdr.id);
207 libHdr.name_index = bswap16(libHdr.name_index);
208 libHdr.size = bswap32(libHdr.size);
209 libHdr.version_min_value = bswap32(libHdr.version_min_value);
210 libHdr.version_value = bswap32(libHdr.version_value);
211
212 xexLibrary_t lib;
213 lib.header = libHdr;
214 lib.name = importNames[libHdr.name_index];
215
216 printf("Parsing imports for \"%s\"\n", lib.name.c_str());
217
218 ParseLibraryInfo(importBaseAddr+libraryoffs+sizeof(libraryHeader_t), lib, libraries.size(), lib.name);
219
220 libraries.push_back(lib);
221
222 libraryoffs += libHdr.size;
223 }
224
225 Kernel::RegisterModuleForName(GetName().c_str(), this);
226}
227
228uint32_t XexLoader::GetEntryPoint() const
229{
230 return entryPoint;
231}
232
233uint32_t XexLoader::GetStackSize() const
234{
235 return stackSize;
236}
237
238size_t XexLoader::GetLibraryIndexByName(const char *name) const
239{
240 for (int i = 0; i < libraries.size(); i++)
241 {
242 auto& lib = libraries[i];
243 if (lib.name == name)
244 return i;
245 }
246 return SIZE_MAX;
247}
248
249uint32_t XexLoader::LookupOrdinal(uint32_t ordinal)
250{
251 ordinal -= exportTable.base;
252 if (ordinal >= exportTable.count)
253 {
254 printf("ERROR: Imported unknown function 0x%08x\n", ordinal);
255 exit(1);
256 }
257
258 uint32_t num = ordinal;
259 uint32_t ordinal_offset = Memory::Read32(exportBaseAddr+sizeof(xexExport_t)+(num*4));
260 ordinal_offset += exportTable.imagebaseaddr << 16;
261 return ordinal_offset;
262}
263
264void XexLoader::ParseFileInfo(uint32_t offset)
265{
266 fileFormatInfo_t fileInfo = *(fileFormatInfo_t*)&buffer[offset];
267 fileInfo.info_size = bswap32(fileInfo.info_size);
268 fileInfo.compression_type = bswap16(fileInfo.compression_type);
269 fileInfo.encryption_type = bswap16(fileInfo.encryption_type);
270
271 printf("Found file info optional header: %d bytes, compression of type %d, encryption of type %d\n", fileInfo.info_size, fileInfo.compression_type, fileInfo.encryption_type);
272
273 compressionFormat = fileInfo.compression_type;
274 encryptionFormat = fileInfo.encryption_type;
275 info = fileInfo;
276}
277
278void XexLoader::ParseLibraryInfo(uint32_t offset, xexLibrary_t &lib, int index, std::string& name)
279{
280 for (uint32_t i = 0; i < lib.header.count; i++)
281 {
282 uint32_t recordAddr = bswap32(*(uint32_t*)&buffer[offset]);
283 offset += 4;
284
285 uint32_t record = Memory::Read32(recordAddr);
286
287 // Write the following routine to RAM:
288 // li r11, mod_func_id
289 // sc 2
290 // blr
291 // nop
292 if ((record >> 24) == 1 && name != "xam.xex")
293 {
294 Memory::Write32(recordAddr+0x00, 0x39600000 | (index << 12) | (record & 0xFFFF));
295 Memory::Write32(recordAddr+0x04, 0x44000042);
296 Memory::Write32(recordAddr+0x08, 0x4e800020);
297 Memory::Write32(recordAddr+0x0C, 0x60000000);
298 }
299 else if ((record >> 24) == 1 && name == "xam.xex")
300 {
301 // We instead write a stub to call xam.xex functions since we LLE it
302 assert(this != xam); // Should never happen, but just in case
303
304 // Get exports from xam.xex
305 uint32_t addr = xam->LookupOrdinal(record & 0xFFFF);
306 Memory::Write32(recordAddr+0x00, 0x3D600000 | addr >> 16);
307 Memory::Write32(recordAddr+0x04, 0x616B0000 | (addr & 0xFFFF));
308 Memory::Write32(recordAddr+0x08, 0x7D6903A6);
309 Memory::Write32(recordAddr+0x0C, 0x4E800420);
310 }
311 else
312 {
313 if (name == "xboxkrnl.exe" && krnlModule.IsExportVariable(record & 0xFFFF))
314 {
315 printf("TODO: Variable import 0x%08x\n", record);
316 }
317 }
318 }
319}
320
321int XexLoader::ReadImageBasicCompressed(uint8_t *buffer, size_t xex_len, char** outBuffer)
322{
323 const uint8_t* p = buffer+header.header_size;
324 std::vector<basicCompression_t> blocks((info.info_size - 8) / 8);
325 uint32_t uncompressedSize = 0;
326 for (size_t i = 0; i < (info.info_size - 8) / 8; i++)
327 {
328 uint32_t offset = fileInfoOffset + 8 + (i * 8);
329 basicCompression_t comp = *(basicCompression_t*)&buffer[offset];
330 comp.data_size = bswap32(comp.data_size);
331 comp.zero_size = bswap32(comp.zero_size);
332 blocks.push_back(comp);
333 uncompressedSize += comp.data_size + comp.zero_size;
334 }
335
336 printf("Image is %d bytes uncompressed\n", uncompressedSize);
337
338 char* out = new char[uncompressedSize];
339 *outBuffer = out;
340 uint8_t* d = (uint8_t*)out;
341
342 uint32_t rk[4 * (MAXNR + 1)];
343 uint8_t ivec[16] = {0};
344 int32_t Nr = rijndaelKeySetupDec(rk, session_key, 128);
345
346 for (size_t n = 0; n < blocks.size(); n++)
347 {
348 const uint32_t dataSize = blocks[n].data_size;
349 const uint32_t zeroSize = blocks[n].zero_size;
350
351 const uint8_t* ct = p;
352 uint8_t* pt = d;
353 for (size_t m = 0; m < dataSize; m += 16, ct += 16, pt += 16)
354 {
355 rijndaelDecrypt(rk, Nr, ct, pt);
356 for (size_t i = 0; i < 16; i++)
357 {
358 pt[i] ^= ivec[i];
359 ivec[i] = ct[i];
360 }
361 }
362
363 p += dataSize;
364 d += dataSize + zeroSize;
365 }
366
367 return uncompressedSize;
368}
369
370int XexLoader::ReadImageCompressed(uint8_t *buffer, size_t xex_len, char **outBuffer)
371{
372 const uint32_t exe_length = (uint32_t)(xex_len - header.header_size);
373 const uint8_t* exe_buffer = (const uint8_t*)(buffer + header.header_size);
374
375 uint8_t* compress_buffer = NULL;
376 const uint8_t* p = NULL;
377 uint8_t* d = NULL;
378
379 bool free_input = false;
380 const uint8_t* input_buffer = exe_buffer;
381 size_t input_size = exe_length;
382
383 switch (encryptionFormat)
384 {
385 case 0:
386 break;
387 case 1:
388 free_input = true;
389 input_buffer = (const uint8_t*)calloc(1, exe_length);
390 aes_decrypt_buffer(session_key, exe_buffer, exe_length, (uint8_t*)input_buffer, exe_length);
391 break;
392 }
393
394 normalCompressionHeader_t hdr = *(normalCompressionHeader_t*)(buffer + fileInfoOffset + sizeof(fileFormatInfo_t));
395 hdr.windowSize = bswap32(hdr.windowSize);
396 hdr.firstBlock.blockSize = bswap32(hdr.firstBlock.blockSize);
397
398 normalCompressionBlock_t curBlock = hdr.firstBlock;
399
400 compress_buffer = (uint8_t*)calloc(1, exe_length);
401
402 p = input_buffer;
403 d = compress_buffer;
404
405 int result_code = 0;
406
407 uint32_t blockOffs = sizeof(normalCompressionBlock_t);
408 while (curBlock.blockSize)
409 {
410 const uint8_t* pnext = p + curBlock.blockSize;
412 blockOffs += sizeof(normalCompressionBlock_t);
413
414 next_block.blockSize = bswap32(next_block.blockSize);
415
416 p += 4;
417 p += 20;
418
419 while (true)
420 {
421 const size_t chunk_size = (p[0] << 8) | p[1];
422 p += 2;
423 if (!chunk_size)
424 break;
425
426 memcpy(d, p, chunk_size);
427 p += chunk_size;
428 d += chunk_size;
429 }
430
431 p = pnext;
432 curBlock = next_block;
433 }
434
435 uint32_t uncompressed_size = image_size();
436 char* out = new char[uncompressed_size];
437
438 (*outBuffer) = out;
439
440 if (!result_code)
441 {
442 std::memset(out, 0, uncompressed_size);
443
444 result_code = lzx_decompress(compress_buffer, d - compress_buffer, out, uncompressed_size,
445 hdr.windowSize, nullptr, 0);
446 }
447
448 if (compress_buffer)
449 free((void*)compress_buffer);
450 if (free_input)
451 free((void*)input_buffer);
452 return uncompressed_size;
453}
454
455uint32_t XexLoader::image_size()
456{
457 uint32_t pageDescriptorCount = bswap32(*(uint32_t*)&buffer[header.sec_info_offset + 0x180]);
458
459 uint32_t totalSize = 0;
460
461 for (int i = 0; i < pageDescriptorCount; i++)
462 {
463 uint32_t offs = header.sec_info_offset + 0x184 + (i * 0x18);
464 pageDescriptor_t page = *(pageDescriptor_t*)&buffer[offs];
465 page.value = bswap32(page.value);
466
467 totalSize += page.value * 4096;
468 }
469
470 return totalSize;
471}
XexLoader(uint8_t *buffer, size_t len, std::string path)
Loads a .xex file from a buffer into memory.
Definition xex.cpp:47
Following the XEX header are xexHeader_t::optional_header_count optional headers, which either contai...
Definition xex.h:22
The header of a .xex file.
Definition xex.h:11