#include #include #include #define EXE_SIZE 2142208 #define MIN_WIDTH 1024 #define MAX_WIDTH 7680 #define MIN_HEIGHT 720 #define MAX_HEIGHT 4320 unsigned long int widthOffsets[] = { 0xcfa0a, 0xcfda3 }; unsigned long int heightOffsets[] = { 0xcfa0f, 0xcfdad }; #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) unsigned char exeFile[EXE_SIZE]; /* Patch a single byte. */ void PatchByte(unsigned long int offset, unsigned char value) { exeFile[offset] = value; } /* Patch an array of bytes in */ void PatchBytes(unsigned long int offset, int count, unsigned char *bytes) { int i; for (i = 0; i < count; ++i) { exeFile[offset+i] = bytes[i]; } } /* Patch a little-endian 32bit value in. */ void PatchU32(unsigned long int offset, unsigned long int value) { exeFile[offset] = value & 0xFF; exeFile[offset+1] = (value >> 8) & 0xFF; exeFile[offset+2] = (value >> 16) & 0xFF; exeFile[offset+3] = (value >> 24) & 0xFF; } /* Helper function for Nopping out a region of code. */ void NopBytes(unsigned long int offset, int count) { int i; for (i = 0; i < count; ++i) { exeFile[offset+i] = 0x90; } } /* Sometimes we pad regions with non-nop bytes. int 3 (0xCC) is popular. */ void PadBytes(unsigned long int offset, int count, unsigned char byte) { int i; for (i = 0; i < count; ++i) { exeFile[offset+i] = byte; } } /* Apply all of the patches from the PharaohResizer * version to a raw Cleopatra exe (assuming DRM, etc, * skipped). */ void DoCrudeliosPatches() { unsigned char jmp_loc_56E331[] = {0xE9, 0x0B, 0xBB, 0x12, 0x00}; unsigned char jmp_loc_56E37B[] = {0xE9, 0x84, 0x04, 0x09, 0x00, 0x90}; unsigned char jmp_loc_56E35E[] = {0xE9, 0x11, 0x04, 0x09, 0x00, 0x90}; unsigned char jmp_loc_56E491[] = {0xE9, 0xEE, 0x5B, 0x07, 0x00}; unsigned char new_loc_51E96D[] = { 0x7C, 0x1B, /* jl short locret_51E98F */ 0x52, 0x50, /* push edx | push eax */ 0xE8, 0x21, 0xFA, 0x04, 0x00, /* call sub_56E39C */ 0x58, 0x29, 0xD0, /* pop eax | sub eax, edx */ 0x83, 0xE8, 0x2A, /* sub eax, 42 */ 0xA3, 0x60, 0x3F, 0xF5, 0x00, /* mov dword_F53F60, eax */ 0x83, 0xE8, 0x78, /* sub eax, 120 */ 0xA3, 0x64, 0x3F, 0xF5, 0x00, /* mov dword_F53F64, eax */ 0x5A, 0xC3 /* pop edx | ret */ }; unsigned char sub_51E990_patch[] = { 0x8B, 0x0D, 0xAC, 0x8D, 0xE3, 0x00, /* mov ecx, dword_E38DAC */ 0x6A, 0x09, /* push 9 */ 0x50, 0x51 /* push eax | push ecx */ }; unsigned char sub_51E990_patch2[] = { 0xE8, 0xA6, 0xFA, 0x04, 0x00, 0xC3 }; unsigned char sub_51E990_patch3[] = { 0xE8, 0x6B, 0xFA, 0x04, 0x00, 0x0C3 }; unsigned char jmp_loc_56E3C7[] = {0xE9, 0xAB, 0xE5, 0x04, 0x00}; unsigned char jmp_loc_56E3DF[] = {0xE9, 0xF2, 0xEA, 0x03, 0x00, 0x90}; unsigned char sub_56E24F[] = { 0x53, 0x56, 0xE8, 0x46, 0x01, 0x00, 0x00, 0x85, 0xD2, 0x74, 0x74, 0x52, 0x51, 0x31, 0xD2, 0xA1, 0xAC, 0x8D, 0xE3, 0x00, 0xBB, 0xA0, 0x02, 0x00, 0x00, 0xF7, 0xFB, 0xF7, 0xEB, 0x05, 0x50, 0x01, 0x00, 0x00, 0x8B, 0x1D, 0xE4, 0x67, 0xF5, 0x00, 0x59, 0x01, 0xCB, 0x5A, 0x31, 0xF6, 0x8B, 0x0D, 0xB0, 0x8D, 0xE3, 0x00, 0x83, 0xFA, 0x28, 0x7E, 0x11, 0x83, 0xFA, 0x32, 0x7E, 0x07, 0xBE, 0x14, 0x00, 0x00, 0x00, 0xEB, 0x05, 0xBE, 0x0A, 0x00, 0x00, 0x00, 0x29, 0xD1, 0x01, 0xF1, 0x2D, 0x50, 0x01, 0x00, 0x00, 0x3D, 0x50, 0x01, 0x00, 0x00, 0x7D, 0x02, 0x31, 0xC0, 0x50, 0x51, 0x52, 0x50, 0x51, 0x53, 0xB9, 0xB0, 0x5B, 0xF5, 0x00, 0xE8, 0xD5, 0x02, 0xFD, 0xFF, 0x5A, 0x59, 0x58, 0x85, 0xF6, 0x74, 0x08, 0x83, 0xEE, 0x0A, 0x83, 0xE9, 0x0A, 0xEB, 0xE1, 0x85, 0xC0, 0x75, 0xAD, 0x5E, 0x5B, 0xC3 }; unsigned char loc_56E2D3[] = { 0xE8, 0xB8, 0x02, 0xFD, 0xFF, 0x53, 0xA1, 0xB0, 0x8D, 0xE3, 0x00, 0xBB, 0x48, 0x03, 0x00, 0x00, 0x29, 0xD8, 0x8B, 0x0D, 0xE4, 0x67, 0xF5, 0x00, 0x83, 0xC1, 0x08, 0x50, 0x51, 0x6A, 0x00, 0x53, 0x51, 0xB9, 0xB0, 0x5B, 0xF5, 0x00, 0xE8, 0x92, 0x02, 0xFD, 0xFF, 0x59, 0x58, 0x81, 0xC3, 0x48, 0x03, 0x00, 0x00, 0x39, 0xC3, 0x7C, 0xE4, 0x51, 0xE8, 0x8C, 0x00, 0x00, 0x00, 0x59, 0x8B, 0x1D, 0xB0, 0x8D, 0xE3, 0x00, 0x81, 0xEB, 0xE8, 0x03, 0x00, 0x00, 0x29, 0xD3, 0x6A, 0x00, 0x53, 0x51, 0xB9, 0xB0, 0x5B, 0xF5, 0x00, 0xE8, 0x63, 0x02, 0xFD, 0xFF, 0x5B, 0xC3 }; unsigned char loc_56E331[] = { 0x52, 0xA1, 0xB0, 0x8D, 0xE3, 0x00, 0x8B, 0x15, 0x10, 0x2A, 0x5D, 0x00, 0x83, 0xE8, 0x20, 0x39, 0xC2, 0x7E, 0x02, 0x89, 0xD0, 0x5A, 0xE9, 0xDA, 0x44, 0xED, 0xFF }; unsigned char loc_56E35E[] = { 0x31, 0xD2, 0xA1, 0xAC, 0x8D, 0xE3, 0x00, 0xB9, 0x0F, 0x00, 0x00, 0x00, 0x29, 0xC8, 0xF7, 0xF1, 0x50, 0xE8, 0x28, 0x00, 0x00, 0x00, 0x50, 0xE9, 0xDC, 0xFB, 0xF6, 0xFF }; unsigned char loc_56E37B[] = { 0x31, 0xD2, 0xA1, 0xAC, 0x8D, 0xE3, 0x00, 0xB9, 0x0F, 0x00, 0x00, 0x00, 0x29, 0xC8, 0xF7, 0xF1, 0x50, 0xE8, 0x0B, 0x00, 0x00, 0x00, 0x83, 0xC0, 0x02, 0x50, 0xE9, 0x62, 0xFB, 0xF6, 0xFF, }; unsigned char sub_56E39C[] = { 0x31, 0xD2, 0xA1, 0xB0, 0x8D, 0xE3, 0x00, 0x2D, 0xA0, 0x00, 0x00, 0x00, 0xB9, 0x3C, 0x00, 0x00, 0x00, 0xF7, 0xF1, 0x83, 0xFA, 0x18, 0x7F, 0x06, 0xB9, 0x02, 0x00, 0x00, 0x00, 0xC3, 0xB9, 0x03, 0x00, 0x00, 0x00, 0xC3 }; unsigned char loc_56E3C7[] = { 0x50, 0xE8, 0xCF, 0xFF, 0xFF, 0xFF, 0x58, 0x89, 0xC1, 0x29, 0xD1, 0x89, 0x0D, 0x58, 0xB5, 0xD3, 0x00, 0xE9, 0x57, 0x1A, 0xFB, 0xFF }; unsigned char loc_56E3DF[] = { 0xA1, 0xB0, 0x8D, 0xE3, 0x00, 0x8B, 0x15, 0x10, 0x2A, 0x5D, 0x00, 0x83, 0xE8, 0x20, 0x39, 0xC2, 0x7F, 0x02, 0x89, 0xC2, 0xE9, 0xF6, 0x14, 0xFC, 0xFF }; unsigned char loc_56E491[] = { 0x8B, 0x15, 0x30, 0x8E, 0xE3, 0x00, 0x83, 0xFA, 0x26, 0x7D, 0x05, 0xBA, 0x26, 0x00, 0x00, 0x00, 0xC1, 0xE0, 0x04, 0x52, 0x83, 0xC2, 0x08, 0x01, 0xC2, 0x3B, 0x15, 0xAC, 0x8D, 0xE3, 0x00, 0x5A, 0x7F, 0x05, 0x83, 0xC2, 0x00, 0xEB, 0x05, 0x83, 0xEA, 0x00, 0x29, 0xC2, 0x89, 0x15, 0x8C, 0xDD, 0xEA, 0x00, 0xE8, 0xD4, 0xFE, 0xFF, 0xFF, 0x8B, 0x0D, 0xB0, 0x8D, 0xE3, 0x00, 0x29, 0xD1, 0x31, 0xD2, 0x3B, 0x15, 0x78, 0x3F, 0xF5, 0x00, 0x75, 0x08, 0x81, 0xE9, 0xA2, 0x00, 0x00, 0x00, 0xEB, 0x03, 0x83, 0xE9, 0x2A, 0x8B, 0x15, 0x34, 0x8E, 0xE3, 0x00, 0x52, 0x83, 0xC2, 0x08, 0xA1, 0x88, 0xDD, 0xEA, 0x00, 0xC1, 0xE0, 0x04, 0x01, 0xC2, 0x39, 0xCA, 0x5A, 0x7F, 0x05, 0x83, 0xC2, 0x00, 0xEB, 0x07, 0x89, 0xCA, 0x83, 0xEA, 0x08, 0x29, 0xC2, 0x89, 0x15, 0x90, 0xDD, 0xEA, 0x00, 0xE9, 0xC7, 0xA3, 0xF8, 0xFF }; unsigned char sub_56E532[] = { 0x53, 0x8B, 0x5C, 0x24, 0x10, 0x53, 0x53, 0xFF, 0x74, 0x24, 0x14, 0xFF, 0x74, 0x24, 0x14, 0xB9, 0xB0, 0x5B, 0xF5, 0x00, 0xE8, 0x45, 0x00, 0xFD, 0xFF, 0x6A, 0x01, 0xE8, 0x4E, 0x0B, 0xFB, 0xFF, 0x6A, 0x01, 0xE8, 0x27, 0x11, 0xFB, 0xFF, 0x83, 0xC4, 0x08, 0x5B, 0x81, 0xC3, 0x1D, 0x01, 0x00, 0x00, 0x3B, 0x1D, 0xAC, 0x8D, 0xE3, 0x00, 0x7C, 0xCC, 0x5B, 0xC2, 0x0C, 0x00 }; unsigned char sub_56E5A6[] = { 0xA1, 0xAC, 0x8D, 0xE3, 0x00, 0x2D, 0x00, 0x03, 0x00, 0x00, 0xD1, 0xF8, 0x50, 0xA1, 0xB0, 0x8D, 0xE3, 0x00, 0x2D, 0x00, 0x04, 0x00, 0x00, 0xD1, 0xF8, 0x50, 0xFF, 0x74, 0x24, 0x0C, 0xE8, 0xC7, 0xFF, 0xFC, 0xFF, 0xC2, 0x0C, 0x00 }; /* Patch a function call? */ PatchU32(0x1A236, 0x15436C); PatchU32(0x1A8C7, 0x153CDB); PatchU32(0x1A8F3, 0x153CAF); PatchU32(0x1A964, 0x153C3E); PatchU32(0x1A9AA, 0x153BF8); PatchU32(0x1A9D1, 0x153BD1); PatchU32(0x1AB2F, 0x153A73); /* Patch a comparison out */ PatchByte(0x1E657, 0xEB); /*jmp*/ /* Patch a massive jump? */ PatchBytes(0x42821, 5, jmp_loc_56E331); /* Patch a jnz -> jl */ PatchByte(0xDDEF0, 0x7C); /* ...and a jump in */ PatchBytes(0xDDEF2, 6, jmp_loc_56E37B); /* Patch a jnz -> jl */ PatchByte(0xDDF42, 0x7C); /* ...and a jump in */ PatchBytes(0xDDF48, 6, jmp_loc_56E35E); /* Patch a jnz -> jl */ PatchByte(0xF889C, 0x7C); /* ...and a jump in */ PatchBytes(0xF889E, 5, jmp_loc_56E491); /* ...and nop out a bunch. */ NopBytes(0xF88A3, 57); /* Rewrite another functon. */ PatchBytes(0x11E972, ARRAY_SIZE(new_loc_51E96D), new_loc_51E96D); /* sub_51E990 changes: */ /* jnz -> jl */ PatchByte(0x11E99A, 0x7C); /* call target -> sub_56E24F */ PatchU32(0x11E99D, 0x4F8AE); /* jnz -> jl */ PatchByte(0x11E9BB, 0x7C); /* Modify setup for call to sub_4CD9D0 */ PatchBytes(0x11E9BD, ARRAY_SIZE(sub_51E990_patch), sub_51E990_patch); NopBytes(0x11E9C7, 6); /* add eax, _0_ instead of 250 */ PatchByte(0x11E9D9, 0); /* jnz -> jl */ PatchByte(0x11EA67, 0x7C); /* call sub_56E532 and return */ PatchBytes(0x11EA87, ARRAY_SIZE(sub_51E990_patch2), sub_51E990_patch2); NopBytes(0x11EA8D, 22); /* and again, call sub_56E532 and return */ PatchBytes(0x11EAC2, ARRAY_SIZE(sub_51E990_patch3), sub_51E990_patch3); NopBytes(0x11EAC8, 22); /* Another jnz -> jl */ PatchByte(0x11EBB0, 0x7C); /* Another one, but this time a longer encoding */ PatchByte(0x11F4AD, 0x8C); /* Another jnz -> jl */ PatchByte(0x11FE15, 0x7C); /* Then a jmp loc_56E3C7 */ PatchBytes(0x11FE17, 5, jmp_loc_56E3C7); NopBytes(0x11FE1C, 7); /* sub_51FD60 patches: */ /* A jnz -> jl */ PatchByte(0x11FF3E, 0x7C); /* Patch call target -> sub_56E532 */ PatchU32(0x11FF5C, 0x4E5D2); /* Another call target -> sub_56E532 */ PatchU32(0x11FFBD, 0x4E571); /* Another call target to sub_56E5A6 */ PatchU32(0x120F95, 0x4D60D); /* A massive jump to loc_56E3DF */ PatchBytes(0x12F8E8, 6, jmp_loc_56E3DF); /* jnz -> jl */ PatchByte(0x136689, 0x8C); /* A big jump to loc_56E2D3 */ PatchByte(0x1366F5, 0xE9); PatchU32(0x1366F6, 0x37BD9); /* Patch 1003 -> 1900 in a push statement */ PatchU32(0x13712B, 1900); /* Now patch in a bunch of custom functions and/or function fragments in the free space at the end of the .exe */ PadBytes(0x16E24D, 2, 0xCC); PatchBytes(0x16E24F, ARRAY_SIZE(sub_56E24F), sub_56E24F); PadBytes(0x16E2D1, 2, 0xCC); /* FUNCTION CHUNK FOR sub_536670 */ PatchBytes(0x16E2D3, ARRAY_SIZE(loc_56E2D3), loc_56E2D3); PadBytes(0x16E32F, 2, 0xCC); /* FUNCTION CHUNK FOR sub_442800 */ PatchBytes(0x16E331, ARRAY_SIZE(loc_56E331), loc_56E331); /* FUNCTION CHUNK FOR sub_4DDF20 */ PatchBytes(0x16E35E, ARRAY_SIZE(loc_56E35E), loc_56E35E); PadBytes(0x16E37A, 1, 0xCC); /* FUNCTION CHUNK FOR sub_4DDEC0 */ PatchBytes(0x16E37B, ARRAY_SIZE(loc_56E37B), loc_56E37B); PadBytes(0x16E39A, 2, 0xCC); /* sub_56E39C */ PatchBytes(0x16E39C, ARRAY_SIZE(sub_56E39C), sub_56E39C); PadBytes(0x16E3C0, 7, 0xCC); /* FUNCTION CHUNK FOR sub_51FD60 */ PatchBytes(0x16E3C7, ARRAY_SIZE(loc_56E3C7), loc_56E3C7); PadBytes(0x16E3DD, 2, 0xCC); /* FUNCTION CHUNK FOR sub_52F8B0 */ PatchBytes(0x16E3DF, ARRAY_SIZE(loc_56E3DF), loc_56E3DF); /* FUNCTION CHUNK FOR sub_4F7E50 */ PatchBytes(0x16E491, ARRAY_SIZE(loc_56E491), loc_56E491); /* sub_56E532 */ PatchBytes(0x16E532, ARRAY_SIZE(sub_56E532), sub_56E532); /* sub_56E5A6 */ PatchBytes(0x16E5A6, ARRAY_SIZE(sub_56E5A6), sub_56E5A6); } void PatchResolution(int width, int height) { int i; for (i = 0; i < ARRAY_SIZE(widthOffsets); ++i) { PatchU32(widthOffsets[i], width); } for (i = 0; i < ARRAY_SIZE(heightOffsets); ++i) { PatchU32(heightOffsets[i], height); } } /* Patch in some code to auto-detect the resolution */ void PatchAutoResolution() { unsigned char call_SetScreenSize[] = { 0xE8, 0x57, 0x03, 0x00, 0x00, /* call SetScreenSize */ 0xEB, 0x20 /* jmp loc_4CFA2B */ }; unsigned char new_SetScreenSize[] = { 0x8B, 0x2D, 0xA0, 0xF1, 0x56, 0x00, /* mov ebp, ds:GetSystemMetrics */ 0x6A, 0x00, /* push SM_CXSCREEN (0) */ 0xFF, 0xD5, /* call ebp (GetSystemMetrics) */ 0xA3, 0xB0, 0x8D, 0xE3, 0x00, /* mov ScreenWidth, eax */ 0x6A, 0x01, /* push SM_CYSCREEN (1) */ 0xFF, 0xD5, /* call ebp (GetSystemMetrics) */ 0xA3, 0xAC, 0x8D, 0xE3, 0x00, /* mov ScreenHeight, eax */ 0xC3 /* ret */ }; /* Don't default to 800x600: jnz -> jmp */ PatchByte(0xCF9F0, 0xEB); PatchBytes(0xCFA04, ARRAY_SIZE(call_SetScreenSize), call_SetScreenSize); NopBytes(0xCFA0B, 32); PatchBytes(0xCFD60, ARRAY_SIZE(new_SetScreenSize), new_SetScreenSize); } int ValidateResolution(int width, int height) { int valid = 1; if (width & 3) { fprintf(stderr, "The width %d is not a multiple of 4.\n", width); fprintf(stderr, "Pharaoh requires the resolution's width to be divisible by 4.\n"); valid = 0; } if (width < MIN_WIDTH) { fprintf(stderr, "The width %d is less than %d.\n", width, MIN_WIDTH); fprintf(stderr, "The selected width must be at least %d pixels.\n", MIN_WIDTH); valid = 0; } if (width > MAX_WIDTH) { fprintf(stderr, "The width %d is greater than %d.\n", width, MAX_WIDTH); fprintf(stderr, "The selected width must be no greater than %d pixels.\n", MAX_WIDTH); valid = 0; } if (height & 3) { fprintf(stderr, "The height %d is not a multiple of 4.\n", height); fprintf(stderr, "Pharaoh requres the resolution's height to be divisible by 4.\n"); valid = 0; } if (height < MIN_HEIGHT) { fprintf(stderr, "The height %d is less than %d.\n", height, MIN_HEIGHT); fprintf(stderr, "The selected height must be at least %d pixels.\n", MIN_HEIGHT); valid = 0; } if (height > MAX_HEIGHT) { fprintf(stderr, "The height %d is greater than %d.\n", height, MAX_HEIGHT); fprintf(stderr, "The selected height must be no greater than %d pixels.\n", MAX_HEIGHT); valid = 0; } return valid; } int main(int argc, char **argv) { const char *inName; const char *outName; int width; int height; FILE *inFile; FILE *outFile; size_t compSize; int retVal; if (argc < 2) { printf("%s: Run Pharaoh+Cleopatra at different resolutions\n", argv[0]); printf("\n"); printf("Usage:\n"); printf("\t%s input [output] [width] [height]\n", argv[0]); printf("\tinput: the Cleopatra 2.1 .exe file, or extracted EXE_RESOURCE from PharaohResizer\n"); printf("\t[output]: The .exe file to write, defaults to PharaohNew.exe\n"); printf("\t[width]: The width of the resolution to use, otherwise autodetected\n"); printf("\t[height]: The height of the resolution to use, otherwise autodetected\n"); return 1; } inName = argv[1]; outName = (argc >= 3)?argv[2]:"PharaohNew.exe"; width = (argc >= 4)?atoi(argv[3]):0; height = (argc >= 5)?atoi(argv[4]):0; /* Validate the resolution before we do anything further. */ if ((width || height) && !ValidateResolution(width, height)) { /* ValidateResolution prints its own errors. */ return 7; } inFile = fopen(inName, "rb"); if (!inFile) { fprintf(stderr, "Couldn't open input file \"%s\"\n", inName); return 2; } outFile = fopen(outName, "wb"); if (!outFile) { fprintf(stderr, "Couldn't open output file \"%s\"\n", outName); fprintf(stderr, "Check that the path is correct and is writable.\n"); fclose(inFile); return 3; } fseek(inFile, 0, SEEK_END); compSize = ftell(inFile); fseek(inFile, 0, SEEK_SET); if (compSize == EXE_SIZE) { /* We are patching an original Cleopatra 2.1 .exe */ if (fread(exeFile, EXE_SIZE, 1, inFile) != 1) { fprintf(stderr, "Couldn't read input file \"%s\"\n", inName); fclose(inFile); fclose(outFile); return 5; } fclose(inFile); /* As we're using an original .exe, we need to patch it. */ DoCrudeliosPatches(); } else { unsigned char *compData = malloc(compSize); if (!compData) { fprintf(stderr, "Not enough memory to load compressed file \"%s\"\n", inName); fprintf(stderr, "Is it mysteriously enormous? It appears to be %lu MiB.\n", compSize / (1024 * 1024)); fclose(inFile); fclose(outFile); return 4; } if (fread(compData, compSize, 1, inFile) != 1) { fprintf(stderr, "Couldn't read input file \"%s\"\n", inName); fclose(inFile); fclose(outFile); return 5; } fclose(inFile); retVal = LZ4_decompress_safe((char *) compData, (char *) exeFile, compSize, EXE_SIZE); if (retVal < 1) { fprintf(stderr, "Couldn't decompress input file \"%s\"\n", inName); fprintf(stderr, "Check that it's the correct resource extracted from PharaohResizer.exe\n"); fprintf(stderr, "You can extract it from PharaohResizer.exe with:\n"); fprintf(stderr, "\twrestool PharaohResizer.exe -x --type='EXECUTABLE' --name=101\n"); fprintf(stderr, "Alternatively, make sure you're providing a Cleopatra 2.1 English .exe file\n"); return 6; } } if (width && height) { /* Patch the user's width and height in. */ PatchResolution(width, height); } else { /* Write some code to autodetect the resolution. */ PatchAutoResolution(); } if (fwrite(exeFile, EXE_SIZE, 1, outFile) != 1) { fprintf(stderr, "Couldn't write output executable \"%s\"\n", outName); fprintf(stderr, "Have you run out of disk space or something?\n"); fclose(outFile); return 8; } fclose(outFile); return 0; }