Just thought I would release this program I thought up. It's a simple IPS patcher. Yeah, dozens of them already exist, but I decided to make one for fun.
For those of you who don't know, an IPS patch is typically used in emulation, for patching a rom. For example, a translation patch will translate a game from a language into another. IPS files are used for this most commonly.
This program does have it's limitations. First of all, it doesn't support RLE compression (although barely any ips patches I find use RLE compression, so that doesn't really matter) and it doesn't support files that are padded with 0s, which means it won't work with the RPGe translation for Final Fantasy V.
So far I confirmed it works with multiple translation patches for Final Fantasy III, demiforce's patch for Final Fantasy II, and Legend of Zelda Polish Translation patch
#include "fileIO.h"
/* Usage goes as follows
Argument 1 is the source file, this is the patch file
Argument 2 is the destination file, this is the file to be patched
IPS File format: First 5 bytes =
"PATCH" in ascii
Multiple patch records
3 bytes - Big endian integer (other than 0x454F46) to give offset to patch in dest
2 byte - Big endian integer (non zero) to give the size of the data to patch
(size) bytes - the data to copy
Last 3 bytes = "EOF" in ascii
*/
int main(int argc, char* argv[])
{
string clarg;
switch(argc)
{
case 1: //no command
printf("Parameters missing. For usage help use -help\n");
return 0;
case 2:
clarg = argv[1];
if(clarg == "-help")
{
printf("\nUsage:\nFirst Parameter - source patch file\n"
"Second Parameter - destination file to be patched\n"
"Example: superips rompatch.ips destrom.smc\n");
return 0;
}
else
{
printf("\nInvalid Command. For usage see -help\n");
return 0;
}
case 3:
break; //leave the switch statement to allow normal control to continue outside of the switch
default:
printf("\nParameter error: see -help for usage instructions\n");
return 0;
}
fstream fSource, fDest; //open source and dest files
fSource.open(argv[1], ios::in | ios::out | ios::binary);
fDest.open(argv[2], ios::in | ios::out | ios::binary);
printf("\nUsing file %s as source and %s as destination.\nProceed? (y/n):",argv[1],argv[2]);
char choice;
scanf("%c",&choice);
if(choice != 'y')
{
printf("\n**--__--EXITING--__--**\n");
fSource.close();
fDest.close();
return 0;
}
//===========================================================================================
// Test if the file is a valid IPS file
string sigCode; //if this is a valid IPS file, it should store "PATCHEOF"
for(int i = 0x0; i < 0x05; i++)
sigCode += readData(fSource,i);
fSource.seekg(0, fSource.end); //get position of last byte in file
int _pos_t = fSource.tellg(); //the last byte in the file
int _pos_t2 = _pos_t - 0x03; //temp to store offset of 3rd to last byte
//had to do these gimmicks because I kept getting overload ambiguity compiler errors
for(int i = _pos_t2; i < _pos_t; i++)
sigCode += readData(fSource,i);
if(sigCode != "PATCHEOF")
{
printf("Error: Source file is not a valid IPS file - Verify that you are using a valid file!\n");
return 0;
}
printf("\nVerified that the source file is a valid IPS file\n");
//===========================================================================================
//Assemble records
int offsetCounter = 0x05; //initially start reading from right after the header
int iRecords = 0; //tally of amount of records
record records[1000];
while(readData24(fSource,offsetCounter) != _32to24(swapEndian32(0x454F46))) //while not EOF
{
records[iRecords].offset = _32to24(swapEndian32(readData24(fSource,offsetCounter))); //Gets the offset where the data will be written
offsetCounter+=0x03; //increment to length section of the record
records[iRecords].length = swapEndian16(readData16(fSource,offsetCounter)); //get the length
offsetCounter+=0x02; //increment to the data field
records[iRecords].data = (byte*)malloc((sizeof(byte))*(records[iRecords].length));
for(int i = 0; i < records[iRecords].length; i++)
records[iRecords].data[i] = readData(fSource,offsetCounter+i);
offsetCounter+=(records[iRecords].length); //increment to the start of the next record
iRecords++;
}
for(int i = 0; i < iRecords; i++)
printf("Record [%d]\tOffset: 0x%X\t\tLength (bytes): %d\n",i,records[i].offset,records[i].length);
//========================================================================
//write to dest file
for(int i = 0; i < iRecords; i++)
{
for(int j = 0; j < records[i].length; j++)
{
writeData(fDest,records[i].offset+j,records[i].data[j]);
}
printf("\nWrote %d bytes in dest file at: 0x%X",records[i].length,records[i].offset);
}
printf("\n\nReached EOF at location 0x%X in source file %s\nPatch Complete!\n",offsetCounter,argv[1]);
for(int i = 0; i < iRecords; i++) //some final cleanup
free(records[i].data);
fSource.close();
fDest.close();
return 0;
}
fileIO.h
Code:
#ifndef FILEIO_H
#define FILEIO_H
#include <stdio.h>
#include <fstream>
#include <string>
using namespace std;
//Word of caution: this was made on a 32-bit system.
typedef unsigned char byte;
typedef unsigned short word;
typedef unsigned int dword;
void writeData(fstream&, int, byte);
byte readData(fstream&, int);
word readData16(fstream&, int);
dword readData24(fstream&, int);
word swapEndian16(word x);
dword swapEndian32(dword x);
dword _32to24(dword x);
struct record
{
dword offset; //where to write data in dest file
word length; //how long the stream of data to write is
byte* data; //the data to write
};
#endif
fileIO.cpp
Code:
#include "fileIO.h"
void writeData(fstream& File, int location, byte data)
{
File.seekp(location); //point to the location to write
File.write((char*)&data,sizeof(byte)); //writes data, specifies 1 byte to be written
}
byte readData(fstream& File, int location)
{
byte value;
File.seekp(location); //point to location to read from
File.read((char*)&value,sizeof(byte));
return value;
}
word readData16(fstream& File, int location)
{
word value = 0;
File.seekp(location); //point to location to read from
File.read((char*)&value,sizeof(word));
return value;
}
dword readData24(fstream& File, int location)
{
dword value = 0;
File.seekp(location); //point to location to read from
File.read((char*)&value,3); //read 3 bytes (24 bit)
return value;
}
word swapEndian16(word x)
{
return (x >> 8) |
(x << 8);
}
dword swapEndian32(dword x)
{
return (x >> 24) |
((x << 8) & 0x00FF0000) |
((x >> 8) & 0x0000FF00) |
(x << 24);
}
dword _32to24(dword x)
{
return ((x >> 8) & 0x00FFFFFF);
}