Eyepiece/src/ips.rs

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))
}
}
}
}