Mesen-SX/Utilities/IpsPatcher.cpp
2020-12-19 23:32:47 +03:00

230 lines
5.1 KiB
C++

#include "stdafx.h"
#include <assert.h>
#include <cstring>
#include <sstream>
#include "IpsPatcher.h"
class IpsRecord
{
public:
uint32_t Address = 0;
uint16_t Length = 0;
vector<uint8_t> Replacement;
//For RLE records (when length == 0)
uint16_t RepeatCount = 0;
uint8_t Value = 0;
bool ReadRecord(std::istream& ipsFile)
{
uint8_t buffer[3];
ipsFile.read((char*)buffer, 3);
if (memcmp(buffer, "EOF", 3) == 0)
{
//EOF reached
return false;
}
else
{
Address = buffer[2] | (buffer[1] << 8) | (buffer[0] << 16);
ipsFile.read((char*)buffer, 2);
Length = buffer[1] | (buffer[0] << 8);
if (Length == 0)
{
//RLE record
ipsFile.read((char*)buffer, 3);
RepeatCount = buffer[1] | (buffer[0] << 8);
Value = buffer[2];
}
else
{
Replacement.resize(Length);
ipsFile.read((char*)Replacement.data(), Length);
}
return true;
}
}
void WriteRecord(vector<uint8_t>& output)
{
output.push_back((Address >> 16) & 0xFF);
output.push_back((Address >> 8) & 0xFF);
output.push_back(Address & 0xFF);
output.push_back((Length >> 8) & 0xFF);
output.push_back(Length & 0xFF);
if (Length == 0)
{
output.push_back((RepeatCount >> 8) & 0xFF);
output.push_back(RepeatCount & 0xFF);
output.push_back(Value);
}
else
{
output.insert(output.end(), Replacement.data(), Replacement.data() + Replacement.size());
}
}
};
bool IpsPatcher::PatchBuffer(string ipsFilepath, vector<uint8_t>& input, vector<uint8_t>& output)
{
ifstream ipsFile(ipsFilepath, std::ios::in | std::ios::binary);
if (ipsFile)
{
return PatchBuffer(ipsFile, input, output);
}
return false;
}
bool IpsPatcher::PatchBuffer(vector<uint8_t>& ipsData, vector<uint8_t>& input, vector<uint8_t>& output)
{
std::stringstream ss;
ss.write((char*)ipsData.data(), ipsData.size());
return PatchBuffer(ss, input, output);
}
bool IpsPatcher::PatchBuffer(std::istream& ipsFile, vector<uint8_t>& input, vector<uint8_t>& output)
{
char header[5];
ipsFile.read((char*)&header, 5);
if (memcmp((char*)&header, "PATCH", 5) != 0)
{
//Invalid ips file
return false;
}
vector<IpsRecord> records;
int32_t truncateOffset = -1;
size_t maxOutputSize = input.size();
while (!ipsFile.eof())
{
IpsRecord record;
if (record.ReadRecord(ipsFile))
{
if (record.Address + record.Length + record.RepeatCount > maxOutputSize)
{
maxOutputSize = record.Address + record.Length + record.RepeatCount;
}
records.push_back(record);
}
else
{
//EOF, try to read truncate offset record if it exists
uint8_t buffer[3];
ipsFile.read((char*)buffer, 3);
if (!ipsFile.eof())
{
truncateOffset = buffer[2] | (buffer[1] << 8) | (buffer[0] << 16);
}
break;
}
}
output.resize(maxOutputSize);
std::copy(input.begin(), input.end(), output.begin());
for (IpsRecord record : records)
{
if (record.Length == 0)
{
std::fill(&output[record.Address], &output[record.Address] + record.RepeatCount, record.Value);
}
else
{
std::copy(record.Replacement.begin(), record.Replacement.end(), output.begin() + record.Address);
}
}
if (truncateOffset != -1 && (int32_t)output.size() > truncateOffset)
{
output.resize(truncateOffset);
}
return true;
}
vector<uint8_t> IpsPatcher::CreatePatch(vector<uint8_t> originalData, vector<uint8_t> newData)
{
vector<uint8_t> patchFile;
if (originalData.size() != newData.size())
{
return patchFile;
}
uint8_t header[5] = {'P', 'A', 'T', 'C', 'H'};
patchFile.insert(patchFile.end(), header, header + sizeof(header));
size_t i = 0, len = originalData.size();
while (i < len)
{
while (i < len && originalData[i] == newData[i])
{
i++;
}
if (i < len)
{
IpsRecord patchRecord;
uint8_t rleByte = newData[i];
uint8_t rleCount = 0;
bool createRleRecord = false;
patchRecord.Address = (uint32_t)i;
patchRecord.Length = 0;
while (i < len && patchRecord.Length < 65535 && originalData[i] != newData[i])
{
if (newData[i] == rleByte)
{
rleCount++;
}
else if (createRleRecord)
{
break;
}
else
{
rleByte = newData[i];
rleCount = 1;
}
patchRecord.Length++;
i++;
if ((patchRecord.Length == rleCount && rleCount > 3) || rleCount > 13)
{
//Making a RLE entry would probably save space, so write the current entry and create a RLE entry after it
if (patchRecord.Length == rleCount)
{
//Same character since the start of this entry, make the RLE entry now
createRleRecord = true;
}
else
{
patchRecord.Length -= rleCount;
i -= rleCount;
break;
}
}
}
if (createRleRecord)
{
patchRecord.Length = 0;
patchRecord.RepeatCount = rleCount;
patchRecord.Value = rleByte;
}
else
{
patchRecord.Replacement = vector<uint8_t>(&newData[patchRecord.Address],
&newData[patchRecord.Address + patchRecord.Length]);
}
patchRecord.WriteRecord(patchFile);
}
}
uint8_t endOfFile[3] = {'E', 'O', 'F'};
patchFile.insert(patchFile.end(), endOfFile, endOfFile + sizeof(endOfFile));
return patchFile;
}