diff --git a/include/core/patchrom.hpp b/include/core/patchrom.hpp deleted file mode 100644 index fddfc972..00000000 --- a/include/core/patchrom.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#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/include/library/patchrom.hpp b/include/library/patchrom.hpp new file mode 100644 index 00000000..59110fdc --- /dev/null +++ b/include/library/patchrom.hpp @@ -0,0 +1,47 @@ +#ifndef _library_patchrom__hpp__included__ +#define _library_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); + +/** + * ROM patcher. + */ +struct rom_patcher +{ +/** + * Constructor. + */ + rom_patcher() throw(std::bad_alloc); +/** + * Destructor. + */ + virtual ~rom_patcher() throw(); +/** + * Identify patch. + * + * Parameter patch: The patch. + * Returns: True if my format, false if not. + */ + virtual bool identify(const std::vector& patch) throw() = 0; +/** + * Do the patch. + */ + virtual void dopatch(std::vector& out, const std::vector& original, + const std::vector& patch, int32_t offset) throw(std::bad_alloc, std::runtime_error) = 0; +}; + +#endif diff --git a/include/library/serialization.hpp b/include/library/serialization.hpp index 4385b7e0..81cbd240 100644 --- a/include/library/serialization.hpp +++ b/include/library/serialization.hpp @@ -12,7 +12,7 @@ void _write_common(unsigned char* target, T1 value) } template -T1 _read_common(unsigned char* source) +T1 _read_common(const unsigned char* source) { T2 value = 0; for(size_t i = 0; i < ssize; i++) @@ -39,22 +39,22 @@ T1 _read_common(unsigned char* source) #define write64sle(t, v) _write_common< int64_t, uint64_t, 8, false>(reinterpret_cast(t), (v)); #define write64ube(t, v) _write_common(reinterpret_cast(t), (v)); #define write64ule(t, v) _write_common(reinterpret_cast(t), (v)); -#define read8sbe(t) _read_common< int8_t, uint8_t, 1, true>(reinterpret_cast(t)); -#define read8sle(t) _read_common< int8_t, uint8_t, 1, false>(reinterpret_cast(t)); -#define read8ube(t) _read_common< uint8_t, uint8_t, 1, true>(reinterpret_cast(t)); -#define read8ule(t) _read_common< uint8_t, uint8_t, 1, false>(reinterpret_cast(t)); -#define read16sbe(t) _read_common< int16_t, uint16_t, 2, true>(reinterpret_cast(t)); -#define read16sle(t) _read_common< int16_t, uint16_t, 2, false>(reinterpret_cast(t)); -#define read16ube(t) _read_common(reinterpret_cast(t)); -#define read16ule(t) _read_common(reinterpret_cast(t)); -#define read32sbe(t) _read_common< int32_t, uint32_t, 4, true>(reinterpret_cast(t)); -#define read32sle(t) _read_common< int32_t, uint32_t, 4, false>(reinterpret_cast(t)); -#define read32ube(t) _read_common(reinterpret_cast(t)); -#define read32ule(t) _read_common(reinterpret_cast(t)); -#define read64sbe(t) _read_common< int64_t, uint64_t, 8, true>(reinterpret_cast(t)); -#define read64sle(t) _read_common< int64_t, uint64_t, 8, false>(reinterpret_cast(t)); -#define read64ube(t) _read_common(reinterpret_cast(t)); -#define read64ule(t) _read_common(reinterpret_cast(t)); +#define read8sbe(t) _read_common< int8_t, uint8_t, 1, true>(reinterpret_cast(t)); +#define read8sle(t) _read_common< int8_t, uint8_t, 1, false>(reinterpret_cast(t)); +#define read8ube(t) _read_common< uint8_t, uint8_t, 1, true>(reinterpret_cast(t)); +#define read8ule(t) _read_common< uint8_t, uint8_t, 1, false>(reinterpret_cast(t)); +#define read16sbe(t) _read_common< int16_t, uint16_t, 2, true>(reinterpret_cast(t)); +#define read16sle(t) _read_common< int16_t, uint16_t, 2, false>(reinterpret_cast(t)); +#define read16ube(t) _read_common(reinterpret_cast(t)); +#define read16ule(t) _read_common(reinterpret_cast(t)); +#define read32sbe(t) _read_common< int32_t, uint32_t, 4, true>(reinterpret_cast(t)); +#define read32sle(t) _read_common< int32_t, uint32_t, 4, false>(reinterpret_cast(t)); +#define read32ube(t) _read_common(reinterpret_cast(t)); +#define read32ule(t) _read_common(reinterpret_cast(t)); +#define read64sbe(t) _read_common< int64_t, uint64_t, 8, true>(reinterpret_cast(t)); +#define read64sle(t) _read_common< int64_t, uint64_t, 8, false>(reinterpret_cast(t)); +#define read64ube(t) _read_common(reinterpret_cast(t)); +#define read64ule(t) _read_common(reinterpret_cast(t)); diff --git a/src/core/patchrom.cpp b/src/core/patchrom.cpp deleted file mode 100644 index f5a3e3e9..00000000 --- a/src/core/patchrom.cpp +++ /dev/null @@ -1,198 +0,0 @@ -#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 = static_cast(patch[roffset + 0]); - b = static_cast(patch[roffset + 1]); - c = static_cast(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 = static_cast(patch[roffset + 3]); - b = static_cast(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 = static_cast(patch[roffset + 5]); - b = static_cast(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. - if(rec_size) { - if(rec_size > -rec_noff) { - rec_noff = 0; - rec_size -= -rec_noff; - } else - rec_size = 0; - } - if(rec_rlesize) { - if(rec_rlesize > -rec_noff) { - rec_noff = 0; - rec_rlesize -= -rec_noff; - } else - rec_rlesize = 0; - } - if(!rec_size && !rec_rlesize) - 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); - //No, these can't be freed. - p.source(NULL, 0); - p.modify(NULL, 0); - 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"); -} diff --git a/src/core/rom.cpp b/src/core/rom.cpp index d18d1d2e..f59afcd5 100644 --- a/src/core/rom.cpp +++ b/src/core/rom.cpp @@ -5,10 +5,10 @@ #include "core/framerate.hpp" #include "core/memorymanip.hpp" #include "core/misc.hpp" -#include "core/patchrom.hpp" #include "core/rom.hpp" #include "core/window.hpp" #include "interface/core.hpp" +#include "library/patchrom.hpp" #include "library/sha256.hpp" #include "library/string.hpp" #include "library/zip.hpp" diff --git a/src/library/patchrom-bps.cpp b/src/library/patchrom-bps.cpp new file mode 100644 index 00000000..caa9fe98 --- /dev/null +++ b/src/library/patchrom-bps.cpp @@ -0,0 +1,173 @@ +#include "library/minmax.hpp" +#include "library/patchrom.hpp" +#include "library/serialization.hpp" +#include "library/string.hpp" +#include +#include +#include +#include +#include + +namespace +{ + uint8_t readbyte(const char* buf, uint64_t& pos, uint64_t size) + { + if(pos >= size) + (stringfmt() << "Attempted to read byte past the end of patch (" << pos << " >= " + << size << ").").throwex(); + return static_cast(buf[pos++]); + } + + uint64_t safe_add(uint64_t a, uint64_t b) + { + if(a + b < a) + (stringfmt() << "Integer overflow (" << a << " + " << b << ") processing patch.").throwex(); + return a + b; + } + + uint64_t safe_sub(uint64_t a, uint64_t b) + { + if(a < b) + (stringfmt() << "Integer underflow (" << a << " - " << b << ") processing patch.").throwex(); + return a - b; + } + + uint64_t decode_varint(const char* buf, uint64_t& pos, uint64_t size) + { + uint64_t v = 0; + size_t i; + uint64_t y; + for(i = 0; i < 10; i++) { + y = readbyte(buf, pos, size) ^ 0x80; + v += (y << (7 * i)); + if(i == 8 && (y | ((v >> 63) ^ 1)) == 255) + (stringfmt() << "Varint decoding overlows: v=" << v << " y=" << y << ".").throwex(); + if(i == 9 && y > 0) + (stringfmt() << "Varint decoding overlows: v=" << v << " y=" << y << ".").throwex(); + if(y < 128) + return v; + } + } + + struct bps_patcher : public rom_patcher + { + ~bps_patcher() throw(); + bool identify(const std::vector& patch) throw(); + void dopatch(std::vector& out, const std::vector& original, + const std::vector& patch, int32_t offset) throw(std::bad_alloc, std::runtime_error); + } bpspatch; + + bps_patcher::~bps_patcher() throw() + { + } + + bool bps_patcher::identify(const std::vector& patch) throw() + { + return (patch.size() > 4 && patch[0] == 'B' && patch[1] == 'P' && patch[2] == 'S' && patch[3] == '1'); + } + + void bps_patcher::dopatch(std::vector& out, const std::vector& original, + const std::vector& patch, int32_t offset) throw(std::bad_alloc, std::runtime_error) + { + if(offset) + (stringfmt() << "Nonzero offsets (" << offset << ") not allowed in BPS mode.").throwex(); + if(patch.size() < 19) + (stringfmt() << "Patch is too masll to be valid BPS patch (" << patch.size() + << " < 19).").throwex(); + size_t ioffset = 4; + const char* _patch = &patch[0]; + size_t psize = patch.size() - 12; + uint32_t crc_init = crc32(0, NULL, 0); + uint32_t pchcrc_c = crc32(crc_init, reinterpret_cast(&patch[0]), patch.size() - 4); + uint32_t pchcrc = read32ule(_patch + psize + 8); + if(pchcrc_c != pchcrc) + (stringfmt() << "CRC mismatch on patch: Claimed: " << pchcrc << " Actual: " << pchcrc_c + << ".").throwex(); + uint32_t srccrc = read32ule(_patch + psize + 0); + uint32_t dstcrc = read32ule(_patch + psize + 4); + uint64_t srcsize = decode_varint(_patch, ioffset, psize); + uint64_t dstsize = decode_varint(_patch, ioffset, psize); + uint64_t mdtsize = decode_varint(_patch, ioffset, psize); + ioffset += mdtsize; + if(ioffset < mdtsize || ioffset > psize) + (stringfmt() << "Metadata size invalid: " << mdtsize << "@" << ioffset << ", plimit=" + << patch.size() << ".").throwex(); + + if(srcsize != original.size()) + (stringfmt() << "Size mismatch on original: Claimed: " << srcsize << " Actual: " + << original.size() << ".").throwex(); + uint32_t srccrc_c = crc32(crc_init, reinterpret_cast(&original[0]), original.size()); + if(srccrc_c != srccrc) + (stringfmt() << "CRC mismatch on original: Claimed: " << srccrc << " Actual: " << srccrc_c + << ".").throwex(); + + out.resize(dstsize); + uint64_t target_ptr = 0; + uint64_t source_rptr = 0; + uint64_t target_rptr = 0; + while(ioffset < psize) { + uint64_t opc = decode_varint(_patch, ioffset, psize); + uint64_t len = (opc >> 2) + 1; + uint64_t off = (opc & 2) ? decode_varint(_patch, ioffset, psize) : 0; + bool negative = ((off & 1) != 0); + off >>= 1; + if(safe_add(target_ptr, len) > dstsize) + (stringfmt() << "Illegal write: " << len << "@" << target_ptr << ", wlimit=" + << dstsize << ".").throwex(); + const char* src; + size_t srcoffset; + size_t srclimit; + const char* msg; + switch(opc & 3) { + case 0: + src = &original[0]; + srcoffset = target_ptr; + srclimit = srcsize; + msg = "source"; + break; + case 1: + src = &patch[0]; + srcoffset = ioffset; + srclimit = psize - 12; + ioffset += len; + msg = "patch"; + break; + case 2: + if(negative) + source_rptr = safe_sub(source_rptr, off); + else + source_rptr = safe_add(source_rptr, off); + src = &original[0]; + srcoffset = source_rptr; + srclimit = srcsize; + source_rptr += len; + msg = "source"; + break; + case 3: + if(negative) + target_rptr = safe_sub(target_rptr, off); + else + target_rptr = safe_add(target_rptr, off); + src = &out[0]; + srcoffset = target_rptr; + srclimit = min(dstsize, target_rptr + len); + target_rptr += len; + msg = "target"; + break; + }; + if(safe_add(srcoffset, len) > srclimit) + (stringfmt() << "Illegal read: " << len << "@" << srcoffset << " from " << msg + << ", limit=" << srclimit << ".").throwex(); + for(uint64_t i = 0; i < len; i++) + out[target_ptr + i] = src[srcoffset + i]; + target_ptr += len; + } + if(target_ptr != out.size()) + (stringfmt() << "Size mismatch on result: Claimed: " << out.size() << " Actual: " + << target_ptr << ".").throwex(); + uint32_t dstcrc_c = crc32(crc_init, reinterpret_cast(&out[0]), out.size()); + if(dstcrc_c != dstcrc) + (stringfmt() << "CRC mismatch on result: Claimed: " << dstcrc << " Actual: " << dstcrc_c + << ".").throwex(); + } +} diff --git a/src/library/patchrom-ips.cpp b/src/library/patchrom-ips.cpp new file mode 100644 index 00000000..368fbc66 --- /dev/null +++ b/src/library/patchrom-ips.cpp @@ -0,0 +1,87 @@ +#include "library/minmax.hpp" +#include "library/patchrom.hpp" +#include "library/serialization.hpp" +#include "library/string.hpp" +#include +#include +#include +#include + +namespace +{ + uint8_t readbyte(const char* buf, uint64_t& pos, uint64_t size) + { + if(pos >= size) + (stringfmt() << "Attempted to read byte past the end of patch (" << pos << " >= " + << size << ").").throwex(); + return static_cast(buf[pos++]); + } + + struct ips_patcher : public rom_patcher + { + ~ips_patcher() throw(); + bool identify(const std::vector& patch) throw(); + void dopatch(std::vector& out, const std::vector& original, + const std::vector& patch, int32_t offset) throw(std::bad_alloc, std::runtime_error); + } ipspatch; + + ips_patcher::~ips_patcher() throw() + { + } + + bool ips_patcher::identify(const std::vector& patch) throw() + { + return (patch.size() > 5 && patch[0] == 'P' && patch[1] == 'A' && patch[2] == 'T' && patch[3] == 'C' && + patch[4] == 'H'); + } + + void ips_patcher::dopatch(std::vector& out, const std::vector& original, + const std::vector& patch, int32_t offset) throw(std::bad_alloc, std::runtime_error) + { + //Initial guess. + out = original; + const char* _patch = &patch[0]; + size_t psize = patch.size(); + + uint64_t ioffset = 5; + while(true) { + bool rle = false; + size_t left = patch.size() - ioffset; + uint8_t b; + uint32_t off = 0, l = 0; + off |= static_cast(readbyte(_patch, ioffset, psize)) << 16; + off |= static_cast(readbyte(_patch, ioffset, psize)) << 8; + off |= static_cast(readbyte(_patch, ioffset, psize)); + if(off == 0x454F46) + break; //EOF code. + l |= static_cast(readbyte(_patch, ioffset, psize)) << 8; + l |= static_cast(readbyte(_patch, ioffset, psize)); + if(l == 0) { + //RLE. + l |= static_cast(readbyte(_patch, ioffset, psize)) << 8; + l |= static_cast(readbyte(_patch, ioffset, psize)); + b = readbyte(_patch, ioffset, psize); + rle = true; + } + uint64_t extra = 0; + if(offset >= 0) + off += offset; + else { + uint32_t noffset = static_cast(-offset); + uint32_t fromoff = min(noffset, off); + off -= fromoff; + extra = min(noffset - fromoff, l); + l -= extra; + } + if(off + l >= out.size()) + out.resize(off + l); + if(!rle) { + ioffset += extra; + for(uint64_t i = 0; i < l; i++) + out[off + i] = readbyte(_patch, ioffset, psize); + } else + for(uint64_t i = 0; i < l; i++) + out[off + i] = b; + } + } +} \ No newline at end of file diff --git a/src/library/patchrom.cpp b/src/library/patchrom.cpp new file mode 100644 index 00000000..18d341fa --- /dev/null +++ b/src/library/patchrom.cpp @@ -0,0 +1,37 @@ +#include "library/patchrom.hpp" +#include "library/string.hpp" +#include +#include +#include +#include + +namespace +{ + std::set& patchers() + { + static std::set t; + return t; + } +} + +std::vector do_patch_file(const std::vector& original, const std::vector& patch, + int32_t offset) throw(std::bad_alloc, std::runtime_error) +{ + std::vector out; + for(auto i : patchers()) + if(i->identify(patch)) { + i->dopatch(out, original, patch, offset); + return out; + } + throw std::runtime_error("Unknown patch file format"); +} + +rom_patcher::rom_patcher() throw(std::bad_alloc) +{ + patchers().insert(this); +} + +rom_patcher::~rom_patcher() throw() +{ + patchers().erase(this); +}