Eyepiece/src/ips.rs
2024-07-07 03:04:41 -05:00

128 lines
3.4 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_utils::*, Apply};
use std::io::{self, Read, Seek, SeekFrom, Write};
pub struct IPS {
pub magic: [u8; 5],
pub patches: Vec<Patch>,
}
// A single IPS patch
pub struct Patch {
pub offset: u32,
pub data: Data,
}
/// The 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 {
/// The expected magic number
pub const MAGIC: &[u8] = b"PATCH";
/// 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)?;
if Self::MAGIC != magic {
return Ok(None);
}
let mut patches = vec![];
while let Some(patch) = Patch::parse(reader)? {
patches.push(patch)
}
Ok(Some(Self { magic, patches }))
}
}
impl Patch {
pub fn parse(reader: &mut impl Read) -> io::Result<Option<Self>> {
match offset(reader)? {
None => Ok(None),
Some(offset) => {
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))
}
}
}
}