/* * loadxan.c * by Mike Melanson (mike at multimedia.cx) * * This program loads xanlib.dll into memory and uses its functions to * decode video data from wc4trailer.avi. Both files are available from * http://www.mplayerhq.hu/MPlayer/samples/game-formats/wc4-xan/ * This program is specifically written to decode wc4trailer.avi since * I didn't want to write a general-purpose AVI parser for this experiment. * * Compile with * gcc -Wall loadxan.c -o loadxan * Running the program logs every address executed in the xanlib DLL. * * Thanks to Mario Brito for figuring out how to use xanlib.dll: * http://www.wcrevival.de/hcl/ */ #include #include #include #include #include #include #include #include //#include "xandata.h" #define XANLIB_DLL "xanlib.dll" #define AVIFILE "wc4trailer.avi" #define ADDRESS_FILE "xanlib-addresses.txt" /* these are in the global scope so that they don't mess up the stack when * going into the trap handler */ static unsigned int reg_esp = 0; static int i; static FILE *addressfile; void TrapHandler(int parm) { asm("movl %%esp, %0" : "=r"(reg_esp)); /* dig the EIP out of the stack; this should be at index 19 (quadwords) */ //for (i = 18; i < 24; i++) fprintf (addressfile, ":%08X\n", *(unsigned int *)(reg_esp + 19 * 4)); } /* this macro starts execution logging */ #define START_TRAP() \ asm("push %eax"); \ asm("pushf"); \ asm("pop %eax"); \ asm("or $0x100, %eax"); \ asm("push %eax"); \ asm("popf"); \ asm("pop %eax"); /* this macro shuts off execution logging */ #define STOP_TRAP() \ asm("push %eax"); \ asm("pushf"); \ asm("pop %eax"); \ asm("and $0xFFFFFEFF, %eax"); \ asm("push %eax"); \ asm("popf"); \ asm("pop %eax"); /* this is a structure needed to process data through the Xan functions */ typedef struct __attribute__((__packed__)) { /* 0x00 */ unsigned int struct_size_info; /* 0x04 */ unsigned int struct_size_total; /* 0x08 */ unsigned int unknown_0x08; /* 0x0C */ unsigned int width; /* 0x10 */ unsigned int height; /* 0x14 */ unsigned int pixel_count; /* 0x18 */ unsigned int unknown_0x18; /* 0x1C */ unsigned int unknown_0x1C; /* 0x20 */ unsigned int unknown_0x20; /* 0x24 */ unsigned int unknown_0x24; /* 0x28 */ unsigned int unknown_0x28; /* 0x2C */ unsigned char *buffer1; /* 0x30 */ unsigned char *buffer2; /* 0x34 */ unsigned char *buffer3; /* 0x38 */ unsigned char *buffer4; } xan_struct; /* anonymous function definitions */ typedef int(*func1parm)(unsigned int); typedef int(*func2parm)(unsigned int, unsigned int); typedef int(*func3parm)(unsigned int, unsigned int, unsigned int); typedef int(*func4parm)(unsigned int, unsigned int, unsigned int, unsigned int); int main() { FILE *lib; void *xan_code = 0; void *xan_data = 0; xan_struct xan; func1parm func1; func3parm func3; func4parm func4; unsigned char buffer1[0xCE40]; unsigned char buffer2[0x67C0]; unsigned char buffer3[0xCE40]; unsigned char buffer4[0x20000]; /* this is supposed to be a lookup table */ unsigned char output_buffer[320 * 165 * 4]; int j; unsigned short pixel; FILE *avifile; FILE *ppmfile; char ppm_filename[20]; int frame_counter = 0; unsigned char preamble[8]; unsigned int chunk_length; unsigned char chunk_payload[0x10000]; /* table1 comes from xandata.h if available; it should eventually be * generated */ // memcpy(buffer4, table1, 0x20000); memset(output_buffer, 0x00, 320 * 165 * 4); /* this is the area where the DLL will be loaded into */ xan_code = mmap( (void *)0x90001000, 40 * 1024, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FIXED | MAP_SHARED | MAP_ANONYMOUS, 0, 0); if ((unsigned int)xan_code == 0xFFFFFFFF) { perror("mmap"); return 1; } /* sometimes, the Xan DLL wants to write into this area */ xan_data = mmap( (void *)0x90009000, 0x1000, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED | MAP_ANONYMOUS, 0, 0); if ((unsigned int)xan_data == 0xFFFFFFFF) { perror("mmap"); return 1; } /* load the xanlib.dll file */ lib = fopen(XANLIB_DLL, "rb"); if (!lib) { perror(XANLIB_DLL); return 1; } /* go straight to the code @ offset 0x400 */ fseek(lib, 0x400, SEEK_SET); if (fread(xan_code, 35840 - 0x400, 1, lib) != 1) { perror(XANLIB_DLL); fclose(lib); return 1; } fclose(lib); /* load up the Xan struct with the known values */ xan.struct_size_info = 0x3C; xan.struct_size_total = 0x40480; xan.unknown_0x08 = 3; xan.width = 320; xan.height = 165; xan.pixel_count = 320 * 165; xan.unknown_0x18 = 2; xan.unknown_0x1C = 0x7C; xan.unknown_0x20 = 0x3E0; xan.unknown_0x24 = 0x1F; xan.unknown_0x28 = 0; xan.buffer1 = buffer1; xan.buffer2 = buffer2; xan.buffer3 = buffer3; xan.buffer4 = buffer4; func1 = 0x90001000; func3 = 0x90002DD0; func4 = 0x90002E60; /* set up the address logging file */ addressfile = fopen(ADDRESS_FILE, "w"); if (!addressfile) { perror(ADDRESS_FILE); return 1; } /* reroute the breakpoint interrupt */ signal(SIGTRAP, TrapHandler); /* open the wc4trailer.avi file */ avifile = fopen(AVIFILE, "rb"); if (!avifile) { perror(AVIFILE); return 1; } /* seek straight to the first chunk */ fseek(avifile, 0xBE800, SEEK_SET); /* read the first 2 frames; this should be a keyframe and an interframe */ while (frame_counter < 2) { if (fread(preamble, 8, 1, avifile) != 1) { perror("fread"); fclose(avifile); return 1; } chunk_length = (preamble[4] << 0) | (preamble[5] << 8) | (preamble[6] << 16) | (preamble[7] << 24); chunk_length = (chunk_length + 1) & 0xFFFFFFFE; if ((preamble[0] != '0') || (preamble[1] != '0') || (preamble[2] != 'd') || (preamble[3] != 'b')) { fseek(avifile, chunk_length, SEEK_CUR); continue; } if (fread(chunk_payload, chunk_length, 1, avifile) != 1) { perror("fread"); fclose(avifile); return 1; } fprintf(addressfile, "START PROFILE: xanlib.dll:DecompressXD(), frame #%d\n", frame_counter); START_TRAP(); /* decode the data */ func3((unsigned int)&xan, (unsigned int)chunk_payload, 0); STOP_TRAP(); fprintf(addressfile, "STOP PROFILE\n\n"); fprintf(addressfile, "START PROFILE: xanlib.dll:DrawFrameXD(), frame #%d\n", frame_counter); START_TRAP(); /* this xanlib.dll function can render the data onto the output frame * using a variety of quality settings; 6 is reported to be the best */ func4((unsigned int)&xan, (unsigned int)output_buffer, 320 * 2, 6); STOP_TRAP(); fprintf(addressfile, "STOP PROFILE\n\n"); #if 0 /* write out the decoded frame; no reason to do this, though, since it * requires another lookup table that is not included with this program */ sprintf(ppm_filename, "output%03d.ppm", frame_counter); ppmfile = fopen(ppm_filename, "w"); if (!ppmfile) { perror(ppm_filename); return 1; } fprintf(ppmfile, "P3\n640 330\n255\n"); for (j = 0; j < 320 * 165 * 4 * 2; j += 2) { pixel = output_buffer[j] | (output_buffer[j + 1] << 8); fprintf (ppmfile, "%3d %3d %3d\n", (((pixel >> 10) & 0x1F) << 3), (((pixel >> 5) & 0x1F) << 3), (((pixel >> 0) & 0x1F) << 3)); } fclose(ppmfile); #endif frame_counter++; } fclose(avifile); fclose(addressfile); return 0; }