128 lines
3.4 KiB
Rust
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))
|
|
}
|
|
}
|
|
}
|
|
}
|