diff --git a/crates/console/prometeu-drivers/src/asset.rs b/crates/console/prometeu-drivers/src/asset.rs index 9521d95e..d374e1fb 100644 --- a/crates/console/prometeu-drivers/src/asset.rs +++ b/crates/console/prometeu-drivers/src/asset.rs @@ -11,6 +11,7 @@ use prometeu_hal::sample::Sample; use prometeu_hal::sound_bank::SoundBank; use prometeu_hal::tile_bank::{TileBank, TileSize}; use std::collections::HashMap; +use std::io::Read; use std::sync::{Arc, Mutex, RwLock}; use std::thread; use std::time::Instant; @@ -132,6 +133,12 @@ struct LoadHandleInfo { status: LoadStatus, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum AssetOpMode { + DirectFromSlice, + StageInMemory, +} + impl AssetBridge for AssetManager { fn initialize_for_cartridge( &self, @@ -171,6 +178,14 @@ impl AssetBridge for AssetManager { } impl AssetManager { + fn op_mode_for(entry: &AssetEntry) -> Result { + match (entry.bank_type, entry.codec.as_str()) { + (BankType::TILES, "RAW") => Ok(AssetOpMode::StageInMemory), + (BankType::SOUNDS, "RAW") => Ok(AssetOpMode::DirectFromSlice), + _ => Err(format!("Unsupported codec: {}", entry.codec)), + } + } + pub fn new( assets: Vec, assets_data: AssetsPayloadSource, @@ -453,19 +468,27 @@ impl AssetManager { entry: &AssetEntry, assets_data: Arc>, ) -> Result { - if entry.codec != "RAW" { - return Err(format!("Unsupported codec: {}", entry.codec)); - } - - let buffer = { + let op_mode = Self::op_mode_for(entry)?; + let slice = { let assets_data = assets_data.read().unwrap(); assets_data .open_slice(entry.offset, entry.size) .map_err(|_| "Asset offset/size out of bounds".to_string())? - .read_all() - .map_err(|_| "Asset payload read failed".to_string())? }; + match op_mode { + AssetOpMode::StageInMemory => { + let buffer = slice.read_all().map_err(|_| "Asset payload read failed".to_string())?; + Self::decode_tile_bank_from_buffer(entry, &buffer) + } + AssetOpMode::DirectFromSlice => { + let mut reader = slice.open_reader().map_err(|_| "Asset payload read failed".to_string())?; + Self::decode_tile_bank_from_reader(entry, &mut reader) + } + } + } + + fn decode_tile_bank_from_buffer(entry: &AssetEntry, buffer: &[u8]) -> Result { // Decode TILEBANK metadata let tile_size_val = entry.metadata.get("tile_size").and_then(|v| v.as_u64()).ok_or("Missing tile_size")?; @@ -502,23 +525,69 @@ impl AssetManager { Ok(TileBank { tile_size, width, height, pixel_indices, palettes }) } + fn decode_tile_bank_from_reader( + entry: &AssetEntry, + reader: &mut impl Read, + ) -> Result { + let tile_size_val = + entry.metadata.get("tile_size").and_then(|v| v.as_u64()).ok_or("Missing tile_size")?; + let width = + entry.metadata.get("width").and_then(|v| v.as_u64()).ok_or("Missing width")? as usize; + let height = + entry.metadata.get("height").and_then(|v| v.as_u64()).ok_or("Missing height")? as usize; + + let tile_size = match tile_size_val { + 8 => TileSize::Size8, + 16 => TileSize::Size16, + 32 => TileSize::Size32, + _ => return Err(format!("Invalid tile_size: {}", tile_size_val)), + }; + + let pixel_data_size = width * height; + let mut pixel_indices = vec![0_u8; pixel_data_size]; + reader.read_exact(&mut pixel_indices).map_err(|_| "Buffer too small for TILEBANK".to_string())?; + + let mut palette_data = [0_u8; 2048]; + reader.read_exact(&mut palette_data).map_err(|_| "Buffer too small for TILEBANK".to_string())?; + + let mut palettes = [[Color::BLACK; 16]; 64]; + for (p, pal) in palettes.iter_mut().enumerate() { + for (c, slot) in pal.iter_mut().enumerate() { + let offset = (p * 16 + c) * 2; + let color_raw = + u16::from_le_bytes([palette_data[offset], palette_data[offset + 1]]); + *slot = Color(color_raw); + } + } + + Ok(TileBank { tile_size, width, height, pixel_indices, palettes }) + } + fn perform_load_sound_bank( entry: &AssetEntry, assets_data: Arc>, ) -> Result { - if entry.codec != "RAW" { - return Err(format!("Unsupported codec: {}", entry.codec)); - } - - let buffer = { + let op_mode = Self::op_mode_for(entry)?; + let slice = { let assets_data = assets_data.read().unwrap(); assets_data .open_slice(entry.offset, entry.size) .map_err(|_| "Asset offset/size out of bounds".to_string())? - .read_all() - .map_err(|_| "Asset payload read failed".to_string())? }; + match op_mode { + AssetOpMode::DirectFromSlice => { + let mut reader = slice.open_reader().map_err(|_| "Asset payload read failed".to_string())?; + Self::decode_sound_bank_from_reader(entry, &mut reader) + } + AssetOpMode::StageInMemory => { + let buffer = slice.read_all().map_err(|_| "Asset payload read failed".to_string())?; + Self::decode_sound_bank_from_buffer(entry, &buffer) + } + } + } + + fn decode_sound_bank_from_buffer(entry: &AssetEntry, buffer: &[u8]) -> Result { let sample_rate = entry.metadata.get("sample_rate").and_then(|v| v.as_u64()).unwrap_or(44100) as u32; @@ -533,6 +602,15 @@ impl AssetManager { Ok(SoundBank::new(vec![sample])) } + fn decode_sound_bank_from_reader( + entry: &AssetEntry, + reader: &mut impl Read, + ) -> Result { + let mut raw = Vec::new(); + reader.read_to_end(&mut raw).map_err(|_| "Asset payload read failed".to_string())?; + Self::decode_sound_bank_from_buffer(entry, &raw) + } + pub fn status(&self, handle: HandleId) -> LoadStatus { self.handles .read() @@ -806,6 +884,31 @@ mod tests { } } + #[test] + fn test_op_mode_for_tiles_raw_stages_in_memory() { + let entry = test_tile_asset_entry("tiles", test_tile_asset_data().len()); + + assert_eq!(AssetManager::op_mode_for(&entry), Ok(AssetOpMode::StageInMemory)); + } + + #[test] + fn test_op_mode_for_sounds_raw_reads_direct_from_slice() { + let entry = AssetEntry { + asset_id: 1, + asset_name: "sound".to_string(), + bank_type: BankType::SOUNDS, + offset: 0, + size: 8, + decoded_size: 8, + codec: "RAW".to_string(), + metadata: serde_json::json!({ + "sample_rate": 44100 + }), + }; + + assert_eq!(AssetManager::op_mode_for(&entry), Ok(AssetOpMode::DirectFromSlice)); + } + #[test] fn test_asset_loading_flow() { let banks = Arc::new(MemoryBanks::new()); diff --git a/crates/console/prometeu-hal/src/cartridge.rs b/crates/console/prometeu-hal/src/cartridge.rs index 19a5a247..5a808c88 100644 --- a/crates/console/prometeu-hal/src/cartridge.rs +++ b/crates/console/prometeu-hal/src/cartridge.rs @@ -2,7 +2,7 @@ use crate::asset::{AssetEntry, PreloadEntry}; use crate::syscalls::CapFlags; use serde::{Deserialize, Serialize}; use std::fs::File; -use std::io::{self, Read, Seek, SeekFrom}; +use std::io::{self, Cursor, Read, Seek, SeekFrom}; use std::path::PathBuf; use std::sync::Arc; @@ -125,18 +125,95 @@ pub enum AssetsPayloadSlice { } impl AssetsPayloadSlice { - pub fn read_all(&self) -> io::Result> { + pub fn open_reader(&self) -> io::Result { match self { - Self::Memory { bytes, start, len } => Ok(bytes[*start..*start + *len].to_vec()), + Self::Memory { bytes, start, len } => { + let data = Arc::<[u8]>::from(bytes[*start..*start + *len].to_vec()); + Ok(AssetsPayloadReader::Memory(Cursor::new(data))) + } Self::File { source, offset, size } => { let mut file = File::open(&source.path)?; - file.seek(SeekFrom::Start(source.payload_offset + offset))?; - let mut buffer = vec![0_u8; usize::try_from(*size).map_err(|_| invalid_input("asset size overflow"))?]; - file.read_exact(&mut buffer)?; - Ok(buffer) + let absolute_start = source.payload_offset + offset; + file.seek(SeekFrom::Start(absolute_start))?; + Ok(AssetsPayloadReader::File(FileSliceReader { + file, + start: absolute_start, + len: *size, + position: 0, + })) } } } + + pub fn read_all(&self) -> io::Result> { + let mut reader = self.open_reader()?; + let mut buffer = Vec::new(); + reader.read_to_end(&mut buffer)?; + Ok(buffer) + } +} + +pub enum AssetsPayloadReader { + Memory(Cursor>), + File(FileSliceReader), +} + +impl Read for AssetsPayloadReader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match self { + Self::Memory(reader) => reader.read(buf), + Self::File(reader) => reader.read(buf), + } + } +} + +impl Seek for AssetsPayloadReader { + fn seek(&mut self, pos: SeekFrom) -> io::Result { + match self { + Self::Memory(reader) => reader.seek(pos), + Self::File(reader) => reader.seek(pos), + } + } +} + +pub struct FileSliceReader { + file: File, + start: u64, + len: u64, + position: u64, +} + +impl Read for FileSliceReader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + if self.position >= self.len { + return Ok(0); + } + + let remaining = (self.len - self.position) as usize; + let to_read = remaining.min(buf.len()); + let read = self.file.read(&mut buf[..to_read])?; + self.position += read as u64; + Ok(read) + } +} + +impl Seek for FileSliceReader { + fn seek(&mut self, pos: SeekFrom) -> io::Result { + let next = match pos { + SeekFrom::Start(offset) => offset as i128, + SeekFrom::Current(delta) => self.position as i128 + delta as i128, + SeekFrom::End(delta) => self.len as i128 + delta as i128, + }; + + if next < 0 || next as u64 > self.len { + return Err(invalid_input("slice seek out of bounds")); + } + + let next = next as u64; + self.file.seek(SeekFrom::Start(self.start + next))?; + self.position = next; + Ok(self.position) + } } fn invalid_input(message: &'static str) -> io::Error {