From abd3e563a8dc5c15dd4f4a718f2c72e427598a09 Mon Sep 17 00:00:00 2001 From: claude Date: Tue, 22 Sep 2026 04:15:29 +0000 Subject: [PATCH] gb: refuse a map header that does not fit wOverworldMap The buffer is ds 1300 and every real map plus its three-block border fits in it. A header that says otherwise is one read mid-load, and decoding it would read past the buffer into somebody else s WRAM, so it is a refusal rather than a clamp. --- .../flysim/crates/flybrain-gb/src/pokemon_red/mapgrid.rs | 7 +++++++ .../flysim/crates/flybrain-gb/src/pokemon_red/state.rs | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/services/flysim/crates/flybrain-gb/src/pokemon_red/mapgrid.rs b/services/flysim/crates/flybrain-gb/src/pokemon_red/mapgrid.rs index 75db101..a9184ea 100644 --- a/services/flysim/crates/flybrain-gb/src/pokemon_red/mapgrid.rs +++ b/services/flysim/crates/flybrain-gb/src/pokemon_red/mapgrid.rs @@ -49,6 +49,13 @@ pub const MAP_BORDER: usize = 3; /// which is why `wCurMapWidth` in blocks is `map_size().width` in tiles divided by this. pub const TILES_PER_BLOCK: u8 = 2; +/// Bytes `wOverworldMap` has for the loaded map: `ds 1300` (`ram/wram.asm`). +/// +/// A map plus its border of [`MAP_BORDER`] blocks has to fit in this, and every real map does. A +/// header that says otherwise is a header read mid-load, which is why the bound is a refusal +/// rather than a clamp. +pub const OVERWORLD_MAP_BYTES: usize = 1300; + /// Which of a quadrant's two rows the collision read takes its tile id from: the lower one. /// /// A map tile is a 2x2 patch of screen tiles and only one of the four is ever asked about, because diff --git a/services/flysim/crates/flybrain-gb/src/pokemon_red/state.rs b/services/flysim/crates/flybrain-gb/src/pokemon_red/state.rs index 74e4858..fb56825 100644 --- a/services/flysim/crates/flybrain-gb/src/pokemon_red/state.rs +++ b/services/flysim/crates/flybrain-gb/src/pokemon_red/state.rs @@ -895,6 +895,14 @@ pub fn map_grid(memory: &mut dyn MemoryReader) -> Result { let height_blocks = read(memory, ram::wCurMapHeight); let stride = u16::from(width_blocks) + (mapgrid::MAP_BORDER as u16) * 2; let border = mapgrid::MAP_BORDER as u16; + // The map plus its border has to fit in `wOverworldMap`, which every real map does. One that + // does not is a header caught mid-load, and reading past the buffer would be reading somebody + // else's WRAM. + if usize::from(stride) * (usize::from(height_blocks) + mapgrid::MAP_BORDER * 2) + > mapgrid::OVERWORLD_MAP_BYTES + { + return Err(GridRefusal::NoHeader); + } let mut blocks = Vec::with_capacity(usize::from(width_blocks) * usize::from(height_blocks)); for row in 0..u16::from(height_blocks) { for column in 0..u16::from(width_blocks) {