Skip to content

Commit f3ac93b

Browse files
committed
make patch not use source file header, add tests to confirm patches work
1 parent f487037 commit f3ac93b

File tree

7 files changed

+210
-13
lines changed

7 files changed

+210
-13
lines changed

tests/Cargo.lock

Lines changed: 107 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,8 @@ edition = "2021"
77
gumdrop = "0.8.1"
88
rusticnes-core = { git = "https://github.com/zeta0134/rusticnes-core.git", version = "0.2.0" }
99
minifb = "0.25"
10+
11+
# for patch testing
12+
flips = "0.2.1"
13+
flips-sys = "0.2.1"
14+
md5 = "0.7.0"

tests/src/main.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ mod tspins;
2020
mod hz_display;
2121
mod nmi;
2222
mod constants;
23+
mod patch;
2324

2425
use gumdrop::Options;
2526

@@ -50,7 +51,7 @@ struct TestOptions {
5051
fn main() {
5152
let options = TestOptions::parse_args_default_or_exit();
5253

53-
let tests: [(&str, fn()); 14] = [
54+
let tests: [(&str, fn()); 15] = [
5455
("garbage4", garbage::test_garbage4_crash),
5556
("floor", floor::test),
5657
("tspins", tspins::test),
@@ -65,6 +66,7 @@ fn main() {
6566
("hz_display", hz_display::test),
6667
("nmi", nmi::test),
6768
("constants", constants::test),
69+
("patch", patch::test),
6870
];
6971

7072
// run tests

tests/src/patch.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
pub fn test() {
2+
let clean = include_bytes!("../../clean.nes");
3+
let patch = include_bytes!("../../tetris.bps");
4+
5+
// check original file hasn't changed
6+
assert_eq!("ec58574d96bee8c8927884ae6e7a2508", format!("{:x}", md5::compute(clean)));
7+
8+
// check patch produces the final ROM
9+
let output = flips::BpsPatch::new(patch).apply(clean)
10+
.expect("could not apply patch");
11+
12+
assert_eq!(output.as_bytes(), crate::util::ROM);
13+
14+
// check NES2.0 files work
15+
let mut clean2 = Vec::new();
16+
clean2.extend([0x4e,0x45,0x53,0x1a,0x02,0x02,0x10,0x08,0x50,0x00,0x00,0x07,0x00,0x00,0x00,0x00]);
17+
clean2.extend(&clean[0x10..]);
18+
19+
assert_eq!("204c0f64d291737e23c0345b59cf1c05", format!("{:x}", md5::compute(clean2.clone())));
20+
21+
let result = {
22+
let slice_p = patch.as_ref();
23+
let slice_s: &[u8] = clean2.as_ref();
24+
let mut mem_m = flips_sys::mem::default();
25+
let mut mem_o = flips_sys::mem::default();
26+
27+
let _result = unsafe {
28+
let mem_i = flips_sys::mem::new(slice_s.as_ptr() as *mut _, slice_s.len());
29+
let mem_p = flips_sys::mem::new(slice_p.as_ptr() as *mut _, slice_p.len());
30+
flips_sys::bps::bps_apply(mem_p, mem_i, &mut mem_o as *mut _, &mut mem_m as *mut _, true)
31+
};
32+
33+
mem_o.to_owned()
34+
};
35+
36+
let mut invalid_indices = Vec::new();
37+
38+
for (i, v) in result.as_ref().iter().enumerate() {
39+
if v != &crate::util::ROM[i] {
40+
invalid_indices.push(i);
41+
}
42+
}
43+
44+
assert_eq!(invalid_indices, Vec::new());
45+
}

tests/src/util.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use rusticnes_core::nes::NesState;
22
use rusticnes_core::{ cartridge, opcodes, opcode_info };
33
use crate::{input, labels};
44

5-
static ROM: &'static [u8] = include_bytes!("../../tetris.nes");
5+
pub static ROM: &'static [u8] = include_bytes!("../../tetris.nes");
66

77
pub fn rom_data() -> &'static [u8] {
88
&ROM[0x10..]

tools/patch/bps.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/patch/create.js

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,64 @@
1-
const { MarcFile, createBPSFromFiles, parseBPSFile } = require('./bps.js');
1+
const {
2+
MarcFile,
3+
createBPSFromFiles,
4+
parseBPSFile,
5+
crc32,
6+
} = require('./bps.js');
27
const fs = require('fs');
38

49
// https://github.com/blakesmith/rombp/blob/master/docs/bps_spec.md
510

611
module.exports = function patch(original, modified, destination) {
7-
const patch = createBPSFromFiles(
8-
new MarcFile(original),
9-
new MarcFile(modified),
10-
true,
11-
).export('');
12+
const originalFile = new MarcFile(original);
13+
const modifiedFile = new MarcFile(modified);
14+
const patch = createBPSFromFiles(originalFile, modifiedFile, true);
1215

13-
fs.writeFileSync(destination, patch._u8array);
16+
// post-process BPS so that header bytes are ignored
17+
18+
let outputOffset = 0;
19+
20+
patch.actions.forEach((action) => {
21+
if (action.type === 0) {
22+
let { length } = action;
23+
24+
let usesHeader = false;
25+
for (let i = 0; length--; i++) {
26+
if (outputOffset + i < 0x10) {
27+
usesHeader = true;
28+
break;
29+
}
30+
}
31+
32+
if (usesHeader) {
33+
action.type = 1;
34+
action.bytes = Array.from(
35+
modifiedFile._u8array.slice(
36+
outputOffset,
37+
outputOffset + action.length,
38+
),
39+
);
40+
}
41+
42+
outputOffset += action.length;
43+
} else {
44+
outputOffset += action.length;
45+
}
46+
});
47+
48+
// reapply the checksum
49+
patch.patchChecksum = crc32(patch.export(), 0, true);
50+
51+
fs.writeFileSync(destination, patch.export()._u8array);
1452

1553
const bps = parseBPSFile(new MarcFile('tetris.bps'));
1654

1755
// count patch sizes
1856
let source = 0;
19-
bps.actions.forEach(action => {
57+
bps.actions.forEach((action) => {
2058
if ([0, 2].includes(action.type)) {
2159
source += action.length;
2260
}
2361
});
2462

25-
return (source / bps.sourceSize) * 100 | 0;
26-
}
63+
return ((source / bps.sourceSize) * 100) | 0;
64+
};

0 commit comments

Comments
 (0)