136 lines
3.6 KiB
Rust
136 lines
3.6 KiB
Rust
//! The IPS(24/16) patchfile format is one of the simplest possible patchfile formats.
|
|
//! It contains no way to identify the correct target file, and cannot insert or remove bytes.
|
|
//!
|
|
//! An IPS file starts with the magic number "PATCH"
|
|
//! ```console
|
|
//! 0000: 50 41 54 43 48 |PATCH|
|
|
//! ```
|
|
//!
|
|
//! Patches are encoded linearly with no padding or alignment, and
|
|
//! all numeric values are encoded big-endian. Patches cannot have
|
|
//! a length of b"EOF", as "EOF" marks the end of the patchfile.
|
|
//! ```console
|
|
//! xxxx: 45 4f 46 |EOF|
|
|
//! ```
|
|
//!
|
|
//! The patchfile matches the following pseudo-grammar
|
|
//! ```console
|
|
//! IPS = "PATCH" Patch* "EOF"
|
|
//! Patch = { offset: u24 != "EOF", kind: PatchKind }
|
|
//! PatchData = { len: u16 != 0, data: [u8; len] } // Plain ol' data
|
|
//! | { _: u16 == 0, len: u16, byte: u8 } // Run-length-encoded data
|
|
//! ```
|
|
|
|
use crate::{parse_helpers::*, Apply};
|
|
use std::io::{self, Read, Seek, SeekFrom, Write};
|
|
|
|
pub struct IPS {
|
|
pub magic: [u8; 5],
|
|
pub patches: Vec<Patch>,
|
|
}
|
|
|
|
pub struct Patch {
|
|
pub offset: u32,
|
|
pub data: Data,
|
|
}
|
|
|
|
pub enum Data {
|
|
/// Run-length-encoded (repeated) data
|
|
RLEnc(u16, u8),
|
|
|
|
/// Verbatim data
|
|
Plain(Vec<u8>),
|
|
}
|
|
|
|
impl Apply for IPS {
|
|
fn apply<W: Write + Seek>(&self, writer: &mut W) -> io::Result<()> {
|
|
for patch in &self.patches {
|
|
patch.apply(writer)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Apply for Patch {
|
|
fn apply<W: Write + Seek>(&self, writer: &mut W) -> io::Result<()> {
|
|
let Self { offset, data } = self;
|
|
writer.seek(SeekFrom::Start(*offset as _))?;
|
|
data.apply(writer)
|
|
}
|
|
}
|
|
|
|
impl Apply for Data {
|
|
fn apply<W: Write + Seek>(&self, writer: &mut W) -> io::Result<()> {
|
|
match self {
|
|
&Data::RLEnc(len, value) => {
|
|
io::copy(&mut io::repeat(value).take(len as _), writer)?;
|
|
Ok(())
|
|
}
|
|
Data::Plain(buf) => writer.write_all(buf),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl IPS {
|
|
const MAGIC24: &[u8] = b"PATCH";
|
|
const MAGIC32: &[u8] = b"IPS32";
|
|
|
|
/// Reads an IPS file out of the provided reader.
|
|
///
|
|
/// Consumes 5 bytes and returns Ok(None) if the reader doesn't yield an IPS file.
|
|
pub fn parse(reader: &mut impl Read) -> io::Result<Option<Self>> {
|
|
let magic = read_bytes(reader)?;
|
|
|
|
let parse = match magic.as_slice() {
|
|
Self::MAGIC24 => Patch::parse::<3>,
|
|
Self::MAGIC32 => Patch::parse::<4>,
|
|
_ => return Ok(None),
|
|
};
|
|
|
|
let mut patches = vec![];
|
|
while let Some(patch) = parse(reader)? {
|
|
patches.push(patch)
|
|
}
|
|
|
|
Ok(Some(Self { magic, patches }))
|
|
}
|
|
}
|
|
|
|
impl Patch {
|
|
pub fn parse<const WIDTH: usize>(reader: &mut impl Read) -> io::Result<Option<Self>> {
|
|
const EOF: [&[u8]; 2] = [b"EOF", b"EEOF"];
|
|
|
|
let buf: [u8; WIDTH] = read_bytes(reader)?;
|
|
if const {EOF[WIDTH - 3]} == buf {
|
|
return Ok(None);
|
|
}
|
|
|
|
let mut offset = 0;
|
|
for byte in buf {
|
|
offset = offset << 8 | byte as u32;
|
|
}
|
|
|
|
let data = Data::parse(reader)?;
|
|
Ok(Some(Self { offset, data }))
|
|
}
|
|
}
|
|
|
|
impl Data {
|
|
pub fn parse(reader: &mut impl Read) -> io::Result<Self> {
|
|
match read16(reader)? {
|
|
0 => {
|
|
let len = read16(reader)?;
|
|
let value = read_bytes::<1>(reader)?[0];
|
|
|
|
Ok(Data::RLEnc(len, value))
|
|
}
|
|
len => {
|
|
let mut data = vec![0u8; len as _];
|
|
reader.read_exact(&mut data)?;
|
|
|
|
Ok(Data::Plain(data))
|
|
}
|
|
}
|
|
}
|
|
}
|