Created
March 27, 2020 15:08
-
-
Save tjanas/db70782a04ee4627eaec7cd4bf2351cf to your computer and use it in GitHub Desktop.
IPS Decoder: prints instructions from IPS patches
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <string.h> | |
// IPS Decoder. Created by tjanas. | |
// Prints instructions from IPS patches. | |
// Usage: <ips_decode> <patch_file.ips> | |
// Public domain | |
// | |
// IPS file format (https://zerosoft.zophar.net/ips.php): | |
// _______________________________________________________________________ | |
// | Section | Size (Bytes) | Description | | |
// |---------|-------------------------------------------------------------| | |
// | Header | 5 | 'P' 'A' 'T' 'C' 'H' | | |
// |---------|-------------------------------------------------------------| | |
// | Record | 3+2+Variable | It's the record of a single patch, see below | | |
// |---------|--------------|----------------------------------------------| | |
// | ... | | The numbers of record may vary | | |
// |---------|--------------|----------------------------------------------| | |
// | Trailer | 3 | 'E' 'O' 'F' | | |
// \=======================================================================/ | |
// | |
// IPS Record: | |
// _______________________________________________________________________ | |
// | Section | Size (Bytes) | Description | | |
// |---------|-------------------------------------------------------------| | |
// | Offset | 3 | Write offset in file being patched | | |
// |-----------------------------------------------------------------------| | |
// | Size | 2 | If 0, this is an IPS RLE Record, see below | | |
// | | | Else, the size of data to be copied | | |
// |-----------------------------------------------------------------------| | |
// | Data | Size | Data to be copied to file being patched | | |
// \=======================================================================/ | |
// | |
// IPS RLE Record: | |
// ________________________________________________________________________ | |
// | Section | Size (Bytes) | Description | | |
// |----------|-------------------------------------------------------------| | |
// | Offset | 3 | Write offset in file being patched | | |
// |------------------------------------------------------------------------| | |
// | Size | 2 | 0 | | |
// |------------------------------------------------------------------------| | |
// | RLE_Size | 2 | Write <Size> copies of RLE_Val byte at Offet | | |
// |------------------------------------------------------------------------| | |
// | RLE_Val | 1 | Byte to write starting at Offset | | |
// \=======================================================================/ | |
// | |
// NOTE: Sizes are in big endian. | |
#define BYTE3_TO_UINT(bp) \ | |
(((unsigned int)(bp)[0] << 16) & 0x00FF0000) | \ | |
(((unsigned int)(bp)[1] << 8) & 0x0000FF00) | \ | |
((unsigned int)(bp)[2] & 0x000000FF) | |
#define BYTE2_TO_UINT(bp) \ | |
(((unsigned int)(bp)[0] << 8) & 0xFF00) | \ | |
((unsigned int)(bp)[1] & 0x00FF) | |
bool continue_reading(FILE* fp, long int fileSize) | |
{ | |
long int pos = ftell(fp); | |
return (pos < (fileSize - 3)) && (pos != -1); | |
} | |
int main(int argc, char *argv[]) | |
{ | |
// Crude endian/architecture check | |
unsigned int test = 0x01; | |
if ( (sizeof(test) < 4) || (((char*)&test)[0] != 1) ) | |
{ | |
fprintf(stderr, "architecture not supported\n"); | |
return -1; | |
} | |
if (argc < 2) | |
{ | |
fprintf(stderr, "usage: %s <patch_file.ips>\n", argv[0]); | |
return 0; | |
} | |
FILE* ipsFile = fopen(argv[1], "rb"); | |
if (ipsFile == NULL) | |
{ | |
fprintf(stderr, "could not open patch file\n"); | |
return 1; | |
} | |
// Get IPS file size | |
if (fseek(ipsFile,0,SEEK_END)) | |
{ | |
fprintf(stderr, "fseek error\n"); | |
return 2; | |
} | |
const long int ipsSize = ftell(ipsFile); | |
if (ipsSize == -1) | |
{ | |
fprintf(stderr, "ftell error\n"); | |
return 3; | |
} | |
if (fseek(ipsFile,0,SEEK_SET)) | |
{ | |
fprintf(stderr, "fseek error\n"); | |
return 2; | |
} | |
// 5 byte header | |
// minimum 3 bytes for source, target, metadata sizes | |
// 3 bytes for End Of File marker | |
if (ipsSize < 8) | |
{ | |
fprintf(stderr, "IPS file size should be at least 8 bytes\n"); | |
return 5; | |
} | |
char fileFormatMarker[5] = " "; | |
fread(fileFormatMarker, 1, 5, ipsFile); | |
if (memcmp(fileFormatMarker, "PATCH", 5) != 0) | |
{ | |
fprintf(stderr, "invalid IPS header\n"); | |
return 6; | |
} | |
while ( continue_reading(ipsFile, ipsSize) ) | |
{ | |
char buf_offset[3]; | |
char buf_size[2]; | |
if ( (fread(buf_offset, 1, 3, ipsFile) != 3) || (fread(buf_size, 1, 2, ipsFile) != 2)) | |
{ | |
fprintf(stderr, "fread error\n"); | |
return 4; | |
} | |
unsigned int record_offset = BYTE3_TO_UINT(buf_offset); | |
unsigned int record_size = BYTE2_TO_UINT(buf_size); | |
if (record_size != 0) | |
{ | |
long int ipsOffset = ftell(ipsFile); | |
printf("write %u bytes from IPS patch @ 0x%lX to file @ 0x%X\n", record_size, ipsOffset, record_offset); | |
if (fseek(ipsFile, record_size, SEEK_CUR)) | |
{ | |
fprintf(stderr, "fseek error\n"); | |
return 2; | |
} | |
} | |
else // RLE | |
{ | |
if (fread(buf_size, 1, 2, ipsFile) != 2) | |
{ | |
fprintf(stderr, "fread error\n"); | |
return 4; | |
} | |
unsigned int rle_size = BYTE2_TO_UINT(buf_size); | |
char rle_value; | |
if (fread(&rle_value, 1, 1, ipsFile) != 1) | |
{ | |
fprintf(stderr, "fread error\n"); | |
return 4; | |
} | |
printf("write %u bytes of 0x%hhX to file @ 0x%X\n", rle_size, rle_value, record_offset); | |
} | |
} | |
char trailer[3] = " "; | |
fread(trailer, 1, 3, ipsFile); | |
if (memcmp(trailer, "EOF", 3) != 0) | |
{ | |
fprintf(stderr, "invalid IPS trailer\n"); | |
return 7; | |
} | |
else | |
{ | |
puts("DONE."); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment