From 4e77d2c9883d1fae66d61d2ddd93ec8531e93696 Mon Sep 17 00:00:00 2001 From: "Joseph C. Osborn" Date: Fri, 31 Oct 2025 13:23:51 -0700 Subject: [PATCH] v0--v2 replay upgrade, make decoder own file --- Cargo.lock | 361 +++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + src/bin/dump.rs | 4 +- src/bin/reencode.rs | 10 +- src/bin/upgrade0.rs | 73 +++++++++ src/rply.rs | 146 +++++++++++------- 6 files changed, 534 insertions(+), 61 deletions(-) create mode 100644 src/bin/upgrade0.rs diff --git a/Cargo.lock b/Cargo.lock index 8a926b7..139d02c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,12 +8,55 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "bindgen" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36d860121800b2a9a94f9b5604b332d5cffb234ce17609ea479d723dbc9d3885" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 1.0.109", + "which", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + [[package]] name = "bytemuck" version = "1.24.0" @@ -38,12 +81,32 @@ dependencies = [ "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -53,6 +116,22 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "find-msvc-tools" version = "0.1.4" @@ -82,6 +161,21 @@ dependencies = [ "wasip2", ] +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "jobserver" version = "0.1.34" @@ -92,12 +186,34 @@ dependencies = [ "libc", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + [[package]] name = "libz-rs-sys" version = "0.5.2" @@ -107,6 +223,30 @@ dependencies = [ "zlib-rs", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -123,6 +263,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -132,12 +282,24 @@ dependencies = [ "autocfg", ] +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "pkg-config" version = "0.3.32" @@ -168,6 +330,46 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "retro-rs" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "656d99fcb289714d4b7e294187c09fa8d0563414b8b3f1dca2f5d258bd221441" +dependencies = [ + "libc", + "libloading", + "rust-libretro-sys", +] + [[package]] name = "rmp" version = "0.8.14" @@ -187,6 +389,7 @@ dependencies = [ "byteorder", "flate2", "nohash-hasher", + "retro-rs", "rmp", "smallvec", "thiserror", @@ -194,6 +397,47 @@ dependencies = [ "zstd", ] +[[package]] +name = "rust-libretro-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cbdb106ca97d195be38097412623e1e89926fa3de9f3af995145f6fa0c958c6" +dependencies = [ + "bindgen", + "libc", + "rust-libretro-sys-proc", +] + +[[package]] +name = "rust-libretro-sys-proc" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad70ae46523827ed74c292727aa40021db22397e570a31dd86240c87fc2d929" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + [[package]] name = "shlex" version = "1.3.0" @@ -212,6 +456,17 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.108" @@ -240,14 +495,14 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.108", ] [[package]] name = "unicode-ident" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "wasip2" @@ -258,6 +513,106 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "wit-bindgen" version = "0.46.0" diff --git a/Cargo.toml b/Cargo.toml index b599b36..7360245 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,4 @@ smallvec = "1.15.1" thiserror = "2.0.17" xxhash-rust = { version = "0.8.15", features = ["xxh3"] } zstd = "0.13.3" +retro-rs = { version = "0.5.2", default-features=false } diff --git a/src/bin/dump.rs b/src/bin/dump.rs index 5f04bd7..161713c 100644 --- a/src/bin/dump.rs +++ b/src/bin/dump.rs @@ -4,8 +4,8 @@ fn main() { let args: Vec<_> = std::env::args().collect(); let file = std::fs::File::open(args.get(1).unwrap_or(&"examples/bobl.replay".to_string())).unwrap(); - let mut file = std::io::BufReader::new(file); - let mut rply = decode(&mut file).unwrap(); + let file = std::io::BufReader::new(file); + let mut rply = decode(file).unwrap(); let header = &rply.header; println!("{header:?}"); let mut frame = Frame::default(); diff --git a/src/bin/reencode.rs b/src/bin/reencode.rs index e02165f..ec22a3d 100644 --- a/src/bin/reencode.rs +++ b/src/bin/reencode.rs @@ -9,13 +9,13 @@ fn main() { .unwrap_or(&"examples/bobl_smallblocks.replay".to_string()), ) .unwrap(); - let mut file = std::io::BufReader::new(file); + let file = std::io::BufReader::new(file); let mut outfile = std::io::BufWriter::new(outfile); - let mut rply = decode(&mut file).unwrap(); + let mut rply = decode(file).unwrap(); let header = &rply.header; println!("{header:?}"); if header.version() == 0 { - println!("Can't upgrade v0 replays without a core"); + println!("Can't upgrade v0 replays with reencode, upgrade to v1 first using upgrade0"); std::process::exit(-1); } let mut header_out = header.clone(); @@ -38,6 +38,10 @@ fn main() { rply.frame_number, frame.inputs(), ); + + // TODO run libretro core here, maybe serialize into frame.checkpoint_bytes + // TODO maybe get screenshot or add to video + //frame.drop_checkpoint(); out.write_frame(&frame).unwrap(); if Some(rply.frame_number) == rply.header.frame_count() { diff --git a/src/bin/upgrade0.rs b/src/bin/upgrade0.rs new file mode 100644 index 0000000..ed90a5f --- /dev/null +++ b/src/bin/upgrade0.rs @@ -0,0 +1,73 @@ +use retro_rs::Emulator; +use rply_codec::{Frame, InputData, ReplayError, decode, encode}; +use std::cell::RefCell; +use std::path::Path; +use std::rc::Rc; + +fn main() { + let args: Vec<_> = std::env::args().collect(); + let file = + std::fs::File::open(args.get(1).unwrap_or(&"examples/v0.replay".to_string())).unwrap(); + let outfile = + std::fs::File::create(args.get(2).unwrap_or(&"examples/v2.replay".to_string())).unwrap(); + let corefile = args + .get(3) + .unwrap_or(&"cores/fceumm_libretro".to_string()) + .clone(); + let romfile = args.get(4).unwrap_or(&"roms/demo.nes".to_string()).clone(); + let mut emu = Emulator::create(Path::new(&corefile), Path::new(&romfile)); + let file = std::io::BufReader::new(file); + let mut outfile = std::io::BufWriter::new(outfile); + let mut rply = decode(file).unwrap(); + let header = &rply.header; + println!("Header in: {header:?}"); + if header.version() != 0 { + println!("Only use this program for v0 replays!"); + std::process::exit(-1); + } + assert!(emu.load(&rply.initial_state)); + let mut header_out = header.clone(); + header_out.upgrade(); + let mut encoder = encode(header_out, &rply.initial_state, &mut outfile).unwrap(); + let mut frame = Frame::default(); + rply.read_key_events(&mut frame).unwrap(); + rply.read_end_of_frame(&mut frame).unwrap(); + let frame = Rc::new(RefCell::new(frame)); + let rply = Rc::new(RefCell::new(rply)); + let cb = { + let frame = Rc::clone(&frame); + let rply = Rc::clone(&rply); + Box::new(move |port, device, idx, id| { + let val = rply.borrow_mut().read_v0_button().unwrap(); + //println!("{port}-{device}-{idx}-{id}: 0x{val:x}"); + frame.borrow_mut().input_events.push(InputData { + port: u8::try_from(port).unwrap(), + device: u8::try_from(device).unwrap(), + idx: u8::try_from(idx).unwrap(), + id: u16::try_from(id).unwrap(), + val, + }); + val + }) + }; + loop { + frame.borrow_mut().clear(); + emu.run_with_button_callback(cb.clone()); + match rply.borrow_mut().read_key_events(&mut frame.borrow_mut()) { + Ok(()) => {} + Err(ReplayError::IO(e)) => { + if e.kind() == std::io::ErrorKind::UnexpectedEof { + break; + } + panic!("{e}"); + } + Err(e) => panic!("{e}"), + } + rply.borrow_mut() + .read_end_of_frame(&mut frame.borrow_mut()) + .unwrap(); + encoder.write_frame(&frame.borrow()).unwrap(); + } + encoder.finish().unwrap(); + println!("Header out: {:?}", encoder.header); +} diff --git a/src/rply.rs b/src/rply.rs index 8da6b6a..5420116 100644 --- a/src/rply.rs +++ b/src/rply.rs @@ -176,15 +176,15 @@ pub enum ReplayError { type Result = std::result::Result; -pub struct ReplayDecoder<'a, R: std::io::BufRead> { - rply: &'a mut R, +pub struct ReplayDecoder { + rply: R, pub header: Header, pub initial_state: Vec, pub frame_number: u64, ss_state: statestream::Ctx, } -impl ReplayDecoder<'_, R> { +impl ReplayDecoder { /// Creates a [`ReplayDecoder`] for the given buffered readable stream. /// /// # Errors @@ -192,7 +192,7 @@ impl ReplayDecoder<'_, R> { /// [`ReplayError::Magic`]: Invalid magic number at beginning of file /// [`ReplayError::Version`]: Version identifier not recognized by parser /// [`ReplayError::Compression`]: Unsupported compression scheme for checkpoints - pub fn new(rply: &mut R) -> Result> { + pub fn new(mut rply: R) -> Result> { use byteorder::{LittleEndian, ReadBytesExt}; let magic = rply.read_u32::()?; if magic != MAGIC { @@ -245,35 +245,20 @@ impl ReplayDecoder<'_, R> { frame_number: 0, ss_state: statestream::Ctx::new(block_size, superblock_size), }; - if replay.header.version() == 1 { - replay.rply.read_exact(&mut replay.initial_state)?; - } else { - replay.decode_initial_checkpoint()?; - } + replay.decode_initial_checkpoint()?; Ok(replay) } - /// Reads a single frame at the current decoder position. + pub fn inner(&mut self) -> &mut R { + &mut self.rply + } + + /// Reads keyboard event records at the current input position. Only really appropriate to explicitly call for v0 replays. /// # Errors /// [`ReplayError::IO`]: Unexpected end of stream or other I/O error - /// [`ReplayError::Compression`]: Unsupported compression scheme - /// [`ReplayError::Encoding`]: Unsupported encoding scheme - /// [`ReplayError::BadFrameToken`]: Frame token not recognized or misaligned - /// [`ReplayError::NoCoreRead`]: Tried to read a frame on a version 0 replay without a loaded core - /// [`ReplayError::CheckpointTooBig`]: Tried to read a checkpoint bigger than the address space - #[allow(clippy::too_many_lines)] - pub fn read_frame(&mut self, frame: &mut Frame) -> Result<()> { + pub fn read_key_events(&mut self, frame: &mut Frame) -> Result<()> { use byteorder::{LittleEndian, ReadBytesExt}; - let stopwatch = clock::time(Timer::DecodeFrame); - let vsn = self.header.version(); - let rply = &mut *self.rply; - if vsn == 0 { - return Err(ReplayError::NoCoreRead()); - } - if vsn > 1 { - /* skip over the backref */ - let _ = rply.read_u32::()?; - } + let rply = &mut self.rply; let key_count = rply.read_u8()? as usize; frame.key_events.resize_with(key_count, Default::default); for ki in 0..key_count { @@ -294,6 +279,74 @@ impl ReplayDecoder<'_, R> { }; frame.key_events[ki] = key_data; } + Ok(()) + } + + /// Reads an end of frame marker at the current input position. Only really appropriate to explicitly call for v0 replays. + /// # Errors + /// [`ReplayError::IO`]: Unexpected end of stream or other I/O error + /// [`ReplayError::Compression`]: Unsupported compression scheme + /// [`ReplayError::Encoding`]: Unsupported encoding scheme + /// [`ReplayError::BadFrameToken`]: Frame token not recognized or misaligned + /// [`ReplayError::CheckpointTooBig`]: Tried to read a checkpoint bigger than the address space + pub fn read_end_of_frame(&mut self, frame: &mut Frame) -> Result<()> { + use byteorder::{LittleEndian, ReadBytesExt}; + let rply = &mut self.rply; + let tok = rply.read_u8()?; + match FrameToken::from(tok) { + FrameToken::Regular => { + frame.checkpoint_compression = Compression::None; + frame.checkpoint_encoding = Encoding::Raw; + frame.checkpoint_bytes.clear(); + } + FrameToken::Checkpoint => { + frame.checkpoint_compression = Compression::None; + frame.checkpoint_encoding = Encoding::Raw; + let cp_size = usize::try_from(rply.read_u64::()?) + .map_err(ReplayError::CheckpointTooBig)?; + frame.checkpoint_bytes.resize(cp_size, 0); + rply.read_exact(frame.checkpoint_bytes.as_mut_slice())?; + } + FrameToken::Checkpoint2 => { + self.decode_checkpoint(&mut frame.checkpoint_bytes)?; + } + _ => return Err(ReplayError::BadFrameToken(tok)), + } + Ok(()) + } + + /// Reads a single button value at the current input position. Only appropriate for v0 replays and only if you are implementing an input callback for a core. + /// # Errors + /// [`ReplayError::IO`]: Unexpected end of stream or other I/O error + pub fn read_v0_button(&mut self) -> Result { + use byteorder::{LittleEndian, ReadBytesExt}; + self.rply + .read_i16::() + .map_err(ReplayError::IO) + } + + /// Reads a single frame at the current decoder position. + /// # Errors + /// [`ReplayError::IO`]: Unexpected end of stream or other I/O error + /// [`ReplayError::Compression`]: Unsupported compression scheme + /// [`ReplayError::Encoding`]: Unsupported encoding scheme + /// [`ReplayError::BadFrameToken`]: Frame token not recognized or misaligned + /// [`ReplayError::NoCoreRead`]: Tried to read a frame on a version 0 replay without a loaded core + /// [`ReplayError::CheckpointTooBig`]: Tried to read a checkpoint bigger than the address space + #[allow(clippy::too_many_lines)] + pub fn read_frame(&mut self, frame: &mut Frame) -> Result<()> { + use byteorder::{LittleEndian, ReadBytesExt}; + let stopwatch = clock::time(Timer::DecodeFrame); + let vsn = self.header.version(); + if vsn == 0 { + return Err(ReplayError::NoCoreRead()); + } + if vsn > 1 { + /* skip over the backref */ + let _ = self.rply.read_u32::()?; + } + self.read_key_events(frame)?; + let rply = &mut self.rply; let input_count = rply.read_u16::()? as usize; frame .input_events @@ -315,26 +368,7 @@ impl ReplayDecoder<'_, R> { }; frame.input_events[ii] = inp_data; } - let tok = rply.read_u8()?; - match FrameToken::from(tok) { - FrameToken::Invalid => return Err(ReplayError::BadFrameToken(tok)), - FrameToken::Regular => { - frame.checkpoint_compression = Compression::None; - frame.checkpoint_encoding = Encoding::Raw; - frame.checkpoint_bytes.clear(); - } - FrameToken::Checkpoint => { - frame.checkpoint_compression = Compression::None; - frame.checkpoint_encoding = Encoding::Raw; - let cp_size = usize::try_from(rply.read_u64::()?) - .map_err(ReplayError::CheckpointTooBig)?; - frame.checkpoint_bytes.resize(cp_size, 0); - rply.read_exact(frame.checkpoint_bytes.as_mut_slice())?; - } - FrameToken::Checkpoint2 => { - self.decode_checkpoint(&mut frame.checkpoint_bytes)?; - } - } + self.read_end_of_frame(frame)?; self.frame_number += 1; drop(stopwatch); Ok(()) @@ -350,7 +384,7 @@ impl ReplayDecoder<'_, R> { fn decode_checkpoint(&mut self, checkpoint_bytes: &mut Vec) -> Result<()> { use byteorder::{LittleEndian, ReadBytesExt}; let stopwatch = clock::time(Timer::DecodeCheckpoint); - let rply = &mut *self.rply; + let rply = &mut self.rply; // read a 1 byte compression code let compression = Compression::try_from(rply.read_u8()?).map_err(ReplayError::Compression)?; @@ -424,7 +458,7 @@ impl ReplayDecoder<'_, R> { /// /// # Errors /// See [`ReplayDecoder::new`]. -pub fn decode(rply: &mut R) -> Result> { +pub fn decode(rply: R) -> Result> { ReplayDecoder::new(rply) } @@ -723,14 +757,15 @@ impl Header { *self = Header::V2(HeaderV2 { base: base.clone(), frame_count: 0, - block_size: 0, - superblock_size: 0, - checkpoint_commit_interval: 0, - checkpoint_commit_threshold: 0, + block_size: 256, + superblock_size: 256, + checkpoint_commit_interval: 8, + checkpoint_commit_threshold: 4, checkpoint_compression: Compression::None, }); } let Header::V2(v2) = self else { unreachable!() }; + v2.base.version = 2; v2 } #[must_use] @@ -830,6 +865,11 @@ impl Frame { self.checkpoint_compression = Compression::None; self.checkpoint_encoding = Encoding::Raw; } + pub fn clear(&mut self) { + self.key_events.clear(); + self.input_events.clear(); + self.drop_checkpoint(); + } } impl Default for Frame {