From 27d639b2b17d911c6907bd319709b2d9d6b0de94 Mon Sep 17 00:00:00 2001 From: Ilari Liusvaara Date: Mon, 12 Dec 2011 15:27:44 +0200 Subject: [PATCH] Use bsnes core internal IPS and BPS patchers Now .bps patches are also supported --- include/core/patchrom.hpp | 130 +++------------- src/core/patchrom.cpp | 370 +++++++++++++++++++++++----------------------- 2 files changed, 209 insertions(+), 291 deletions(-) rewrite include/core/patchrom.hpp (80%) rewrite src/core/patchrom.cpp (83%) diff --git a/include/core/patchrom.hpp b/include/core/patchrom.hpp dissimilarity index 80% index 23b2bd6b..fddfc972 100644 --- a/include/core/patchrom.hpp +++ b/include/core/patchrom.hpp @@ -1,110 +1,20 @@ -#ifndef _patchrom__hpp__included__ -#define _patchrom__hpp__included__ - -#include -#include - -/** - * ROM patcher. - */ -class rom_patcher -{ -public: -/** - * Construct new rom patcher. - * - * Parameter original: The original. - * Parameter size: Estimate for target size. May be 0. - * - * Throws std::bad_alloc: Not enough memory. - */ - rom_patcher(const std::vector& original, size_t size = 0) throw(std::bad_alloc); -/** - * Destructor. - */ - ~rom_patcher() throw(); -/** - * Literial insert. - * - * If insertion offset is beyond the end of target, target is extended with zeroes. - * - * Parameter pos: Position to insert the data to. - * Parameter buf: The buffer of data to insert. - * Parameter bufsize: Size of the buffer. - * Parameter time: Number of times to insert. - * - * Throws std::bad_alloc: Not enough memory. - */ - void literial_insert(size_t pos, const char* buf, size_t bufsize, size_t times = 1) throw(std::bad_alloc); -/** - * Copy data from source. - * - * Data outside source is read as zeroes. - * - * Parameter srcpos: Position of data in source. - * Parameter dstpos: Position of data in destination - * Parameter size: Size to copy. - * - * Throws std::bad_alloc: Not enough memory. - */ - void copy_source(size_t srcpos, size_t dstpos, size_t size) throw(std::bad_alloc); -/** - * Copy data from destination. - * - * Reads the data written by call (like in LZ77 decompression). Data outside target reads as zeroes. - * - * Parameter srcpos: Position of data to copy from. - * Parameter dstpos: Position of data to copy to. - * Parameter size: Size to copy. - * - * Throws std::bad_alloc: Not enough memory. - */ - void copy_destination(size_t srcpos, size_t dstpos, size_t size) throw(std::bad_alloc); -/** - * Change the size. - * - * Parameter size: New size for target. - * - * Throws std::bad_alloc: Not enough memory. - */ - void change_size(size_t size) throw(std::bad_alloc); -/** - * Set apply offset. - * - * All passed offsets are adjusted by this amount. Writes to negative offsets are ignored. - * - * Parameter _offset: The new offset. Can be negative. - */ - void set_offset(int32_t _offset) throw(); -/** - * Get the patched output. - * - * Returns the output. - */ - std::vector get_output() throw(std::bad_alloc); -private: - void do_oob_read_warning() throw(); - void do_oob_write_warning() throw(); - void resize_request(size_t dstpos, size_t reqsize); - const std::vector& original; - std::vector target; - int32_t offset; - bool oob_write_warning; - bool oob_read_warning; -}; - -/** - * Patch a ROM. Autodetects type of patch. - * - * Parameter original: The orignal file to patch. - * Parameter patch: The patch to apply. - * Parameter offset: Offset to apply. - * Returns The patched file. - * - * Throws std::bad_alloc: Not enough memory. - * Throws std::runtime_error: Invalid patch file. - */ -std::vector do_patch_file(const std::vector& original, const std::vector& patch, - int32_t offset) throw(std::bad_alloc, std::runtime_error); - -#endif +#ifndef _patchrom__hpp__included__ +#define _patchrom__hpp__included__ + +#include +#include + +/** + * Patch a ROM. Autodetects type of patch. + * + * Parameter original: The orignal file to patch. + * Parameter patch: The patch to apply. + * Parameter offset: Offset to apply. + * Returns The patched file. + * Throws std::bad_alloc: Not enough memory. + * Throws std::runtime_error: Invalid patch file. + */ +std::vector do_patch_file(const std::vector& original, const std::vector& patch, + int32_t offset) throw(std::bad_alloc, std::runtime_error); + +#endif diff --git a/src/core/patchrom.cpp b/src/core/patchrom.cpp dissimilarity index 83% index d78c843a..68b5c8ff 100644 --- a/src/core/patchrom.cpp +++ b/src/core/patchrom.cpp @@ -1,181 +1,189 @@ -#include "core/patchrom.hpp" -#include "core/misc.hpp" - -#include - -rom_patcher::rom_patcher(const std::vector& _original, size_t size) throw(std::bad_alloc) - : original(_original) -{ - target.resize(size); - offset = 0; -} - -rom_patcher::~rom_patcher() throw() -{ -} - -void rom_patcher::resize_request(size_t dstpos, size_t reqsize) -{ - int64_t _dstpos = dstpos; - if(_dstpos + reqsize + offset < 0) - return; - size_t rsize = _dstpos + reqsize + offset; - size_t csize = target.size(); - if(rsize > csize) { - target.resize(rsize); - for(size_t i = csize; i < rsize; i++) - target[i] = 0; - } -} - -void rom_patcher::literial_insert(size_t pos, const char* buf, size_t bufsize, size_t times) throw(std::bad_alloc) -{ - int64_t _pos = pos; - const char* obuf = buf; - size_t obufsize = bufsize; - resize_request(pos, bufsize); - while(bufsize > 0) { - if(_pos + offset > 0) - target[_pos + offset] = *buf; - else - do_oob_write_warning(); - _pos++; - buf++; - bufsize--; - if(bufsize == 0 && times > 0) { - buf = obuf; - bufsize = obufsize; - } - } -} - -void rom_patcher::change_size(size_t size) throw(std::bad_alloc) -{ - target.resize(size); -} - -void rom_patcher::set_offset(int32_t _offset) throw() -{ - offset = _offset; -} - -std::vector rom_patcher::get_output() throw(std::bad_alloc) -{ - return target; -} - -void rom_patcher::copy_source(size_t srcpos, size_t dstpos, size_t size) throw(std::bad_alloc) -{ - resize_request(dstpos, size); - int64_t _srcpos = srcpos; - int64_t _dstpos = dstpos; - while(size > 0) { - if(_dstpos + offset) { - char byte = 0; - if(_srcpos + offset >= 0 && _srcpos + offset < original.size()) - byte = original[_srcpos + offset]; - else - do_oob_read_warning(); - target[_dstpos + offset] = byte; - } - _srcpos++; - _dstpos++; - size--; - } -} - -void rom_patcher::copy_destination(size_t srcpos, size_t dstpos, size_t size) throw(std::bad_alloc) -{ - int64_t _srcpos = srcpos; - int64_t _dstpos = dstpos; - resize_request(dstpos, size); - while(size > 0) { - if(_dstpos + offset) { - char byte = 0; - if(_srcpos + offset >= 0 && _srcpos + offset < target.size()) - byte = target[_srcpos + offset]; - else - do_oob_read_warning(); - target[_dstpos + offset] = byte; - } - _srcpos++; - _dstpos++; - size--; - } -} - -void rom_patcher::do_oob_read_warning() throw() -{ - if(oob_read_warning) - return; - messages << "WARNING: Patch copy read out of bounds (this is likely not going to work)" << std::endl; - oob_read_warning = true; -} - -void rom_patcher::do_oob_write_warning() throw() -{ - if(oob_write_warning) - return; - messages << "WARNING: Patch write out of bounds (this is likely not going to work)" << std::endl; - oob_write_warning = true; -} - - -namespace -{ - size_t handle_ips_record(rom_patcher& r, const std::vector& patch, size_t roffset) - { - if(patch.size() < roffset + 3) - throw std::runtime_error("Unexpected end file in middle of IPS record"); - uint32_t a = static_cast(patch[roffset + 0]); - uint32_t b = static_cast(patch[roffset + 1]); - uint32_t c = static_cast(patch[roffset + 2]); - uint32_t offset = a * 65536 + b * 256 + c; - if(offset == 0x454F46) - return 0; - if(patch.size() < roffset + 5) - throw std::runtime_error("Unexpected end file in middle of IPS record"); - a = static_cast(patch[roffset + 3]); - b = static_cast(patch[roffset + 4]); - uint32_t size = a * 256 + b; - if(size == 0) { - //RLE. - if(patch.size() < roffset + 8) - throw std::runtime_error("Unexpected end file in middle of IPS record"); - a = static_cast(patch[roffset + 5]); - b = static_cast(patch[roffset + 6]); - size = a * 256 + b; - r.literial_insert(offset, &patch[roffset + 7], 1, size); - return roffset + 8; - } else { - //Literial. - if(patch.size() < roffset + 5 + size) - throw std::runtime_error("Unexpected end file in middle of IPS record"); - r.literial_insert(offset, &patch[roffset + 5], size); - return roffset + 5 + size; - } - } - - std::vector do_patch_ips(const std::vector& original, const std::vector& patch, - int32_t offset) throw(std::bad_alloc, std::runtime_error) - { - rom_patcher r(original); - //IPS implicitly starts from original. - r.copy_source(0, 0, original.size()); - r.set_offset(offset); - size_t roffset = 5; - while((roffset = handle_ips_record(r, patch, roffset))); - return r.get_output(); - } -} - -std::vector do_patch_file(const std::vector& original, const std::vector& patch, - int32_t offset) throw(std::bad_alloc, std::runtime_error) -{ - if(patch.size() > 5 && patch[0] == 'P' && patch[1] == 'A' && patch[2] == 'T' && patch[3] == 'C' && - patch[4] == 'H') { - return do_patch_ips(original, patch, offset); - } else { - throw std::runtime_error("Unknown patch file format"); - } -} +#include "core/patchrom.hpp" +#include "core/misc.hpp" + +#include +#include +#include + +namespace +{ + void throw_bps_error(nall::bpspatch::result r) + { + switch(r) + { + case nall::bpspatch::unknown: + throw std::runtime_error("Unknown error status"); + case nall::bpspatch::success: + break; + case nall::bpspatch::patch_too_small: + throw std::runtime_error("Patch too small to be valid"); + case nall::bpspatch::patch_invalid_header: + throw std::runtime_error("Patch has invalid header"); + case nall::bpspatch::source_too_small: + throw std::runtime_error("Source file is too small"); + case nall::bpspatch::target_too_small: + throw std::runtime_error("INTERNAL ERROR: Target file is too small"); + case nall::bpspatch::source_checksum_invalid: + throw std::runtime_error("Source file fails CRC check"); + case nall::bpspatch::target_checksum_invalid: + throw std::runtime_error("Result fails CRC check"); + case nall::bpspatch::patch_checksum_invalid: + throw std::runtime_error("Corrupt patch file"); + default: + throw std::runtime_error("Unknown error applying patch"); + }; + } + + std::vector do_patch_bps(const std::vector& original, const std::vector& patch, + int32_t offset) throw(std::bad_alloc, std::runtime_error) + { + if(offset) + throw std::runtime_error("Offsets are not supported for .bps patches"); + std::vector _original = original; + std::vector _patch = patch; + nall::bpspatch p; + + p.source(reinterpret_cast(&_original[0]), _original.size()); + p.modify(reinterpret_cast(&_patch[0]), _patch.size()); + + //Do trial apply to get the size. + uint8_t tmp; + p.target(&tmp, 1); + nall::bpspatch::result r = p.apply(); + if(r == nall::bpspatch::success) { + //Fun, the output is 0 or 1 bytes. + std::vector ret; + ret.resize(p.size()); + memcpy(&ret[0], &tmp, p.size()); + return ret; + } else if(r != nall::bpspatch::target_too_small) { + //This is actual error in patch. + throw_bps_error(r); + } + size_t tsize = p.size(); + + //Okay, do it for real. + std::vector ret; + ret.resize(tsize); + p.source(reinterpret_cast(&_original[0]), _original.size()); + p.modify(reinterpret_cast(&_patch[0]), _patch.size()); + p.target(reinterpret_cast(&ret[0]), tsize); + r = p.apply(); + if(r != nall::bpspatch::success) + throw_bps_error(r); + return ret; + } + + std::pair rewrite_ips_record(std::vector& _patch, size_t woffset, + const std::vector& patch, size_t roffset, int32_t offset) + { + if(patch.size() < roffset + 3) + throw std::runtime_error("Patch incomplete"); + uint32_t a, b, c; + a = patch[roffset + 0]; + b = patch[roffset + 1]; + c = patch[roffset + 2]; + uint32_t rec_off = a * 65536 + b * 256 + c; + if(rec_off == 0x454F46) { + //EOF. + memcpy(&_patch[woffset], "EOF", 3); + return std::make_pair(3, 3); + } + if(patch.size() < roffset + 5) + throw std::runtime_error("Patch incomplete"); + a = patch[roffset + 3]; + b = patch[roffset + 4]; + uint32_t rec_size = a * 256 + b; + uint32_t rec_rlesize = 0; + uint32_t rec_rawsize = 0; + if(!rec_size) { + if(patch.size() < roffset + 8) + throw std::runtime_error("Patch incomplete"); + a = patch[roffset + 5]; + b = patch[roffset + 6]; + rec_rlesize = a * 256 + b; + rec_rawsize = 8; + } else + rec_rawsize = 5 + rec_size; + int32_t rec_noff = rec_off + offset; + if(rec_noff > 0xFFFFFF) + throw std::runtime_error("Offset exceeds IPS 16MiB limit"); + if(rec_noff < 0) { + //This operation needs to clip the start as it is out of bounds. + while(rec_size > 0 && rec_noff + rec_size <= 0) { + rec_noff++; + rec_size--; + } + while(rec_rlesize > 0 && rec_noff + rec_rlesize <= 0) { + rec_noff++; + rec_rlesize--; + } + if(rec_noff + rec_size + rec_rlesize <= 0) + return std::make_pair(0, rec_rawsize); //Completely out of bounds. + } + //Write the modified record. + _patch[woffset + 0] = (rec_noff >> 16); + _patch[woffset + 1] = (rec_noff >> 8); + _patch[woffset + 2] = rec_noff; + _patch[woffset + 3] = (rec_size >> 8); + _patch[woffset + 4] = rec_size; + if(rec_size == 0) { + //RLE. + _patch[woffset + 5] = (rec_rlesize >> 8); + _patch[woffset + 6] = rec_rlesize; + _patch[woffset + 7] = patch[roffset + 7]; + return std::make_pair(8, 8); + } else + memcpy(&_patch[woffset + 5], &patch[roffset + rec_rawsize - rec_size], rec_size); + return std::make_pair(5 + rec_size, rec_rawsize); + } + + std::vector rewrite_ips_offset(const std::vector& patch, int32_t offset) + { + size_t wsize = 5; + size_t roffset = 5; + if(patch.size() < 5) + throw std::runtime_error("IPS file doesn't even have magic"); + std::vector _patch; + //The result is at most the size of the original. + _patch.resize(patch.size()); + memcpy(&_patch[0], "PATCH", 5); + while(true) { + std::pair r = rewrite_ips_record(_patch, wsize, patch, roffset, offset); + wsize += r.first; + roffset += r.second; + if(r.first == 3) + break; //EOF. + } + _patch.resize(wsize); + return _patch; + } + + std::vector do_patch_ips(const std::vector& original, const std::vector& patch, + int32_t offset) throw(std::bad_alloc, std::runtime_error) + { + std::vector _original = original; + std::vector _patch = rewrite_ips_offset(patch, offset); + nall::ips p; + p.source(reinterpret_cast(&_original[0]), _original.size()); + p.modify(reinterpret_cast(&_patch[0]), _patch.size()); + if(!p.apply()) + throw std::runtime_error("Error applying IPS patch"); + std::vector ret; + ret.resize(p.size); + memcpy(&ret[0], p.data, p.size); + return ret; + } +} + +std::vector do_patch_file(const std::vector& original, const std::vector& patch, + int32_t offset) throw(std::bad_alloc, std::runtime_error) +{ + if(patch.size() > 5 && patch[0] == 'P' && patch[1] == 'A' && patch[2] == 'T' && patch[3] == 'C' && + patch[4] == 'H') + return do_patch_ips(original, patch, offset); + else if(patch.size() > 4 && patch[0] == 'B' && patch[1] == 'P' && patch[2] == 'S' && patch[3] == '1') + return do_patch_bps(original, patch, offset); + else + throw std::runtime_error("Unknown patch file format"); +} -- 2.11.4.GIT