Skip to content

Instantly share code, notes, and snippets.

@tjanas
Created March 27, 2020 15:08
Show Gist options
  • Save tjanas/db70782a04ee4627eaec7cd4bf2351cf to your computer and use it in GitHub Desktop.
Save tjanas/db70782a04ee4627eaec7cd4bf2351cf to your computer and use it in GitHub Desktop.
IPS Decoder: prints instructions from IPS patches
#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