diff --git a/Cargo.lock b/Cargo.lock index 7ff2913..da34195 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -80,6 +80,9 @@ name = "arbitrary" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] [[package]] name = "arg_enum_proc_macro" @@ -285,6 +288,28 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "either" version = "1.13.0" @@ -559,6 +584,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + [[package]] name = "log" version = "0.4.22" @@ -629,6 +660,7 @@ dependencies = [ "wasm-bindgen", "web-sys", "yare", + "zip", ] [[package]] @@ -1328,6 +1360,37 @@ dependencies = [ "syn", ] +[[package]] +name = "zip" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "flate2", + "indexmap", + "memchr", + "thiserror", + "zopfli", +] + +[[package]] +name = "zopfli" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log", + "once_cell", + "simd-adler32", +] + [[package]] name = "zune-core" version = "0.4.12" diff --git a/Cargo.toml b/Cargo.toml index e461a5f..8240798 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ crate-type = ["cdylib", "rlib"] [features] default = ["sdl", "web"] sdl = ["dep:sdl2", "dep:env_logger"] -web = ["dep:wasm-bindgen", "dep:web-sys", "dep:console_log"] +web = ["dep:wasm-bindgen", "dep:web-sys", "dep:zip", "dep:console_log"] [dependencies] log = "0.4.22" @@ -41,6 +41,9 @@ web-sys = { version = "0.3.70", optional = true, features = [ 'DataTransferItemList', 'File', ] } +zip = { version = "2.2.0", optional = true, default-features = false, features = [ + "deflate", +] } console_log = { version = "1.0.0", optional = true } [dev-dependencies] diff --git a/src/runtime/web.rs b/src/runtime/web.rs index 4060cb2..4340a7d 100644 --- a/src/runtime/web.rs +++ b/src/runtime/web.rs @@ -1,14 +1,19 @@ #![allow(dead_code)] // Might be disabled by features use crate::{runtime::Runtime, BufferDisplay, Buttons, INes, HEIGHT, NES, WIDTH}; use anyhow::{anyhow, Context}; -use std::{cell::RefCell, error::Error, rc::Rc}; +use std::{ + cell::RefCell, + error::Error, + io::{Cursor, Read}, + rc::Rc, +}; use wasm_bindgen::{convert::FromWasmAbi, prelude::*, Clamped}; use web_sys::{ js_sys::{ArrayBuffer, Uint8Array}, CanvasRenderingContext2d, DragEvent, HtmlCanvasElement, ImageData, KeyboardEvent, Window, }; +use zip::ZipArchive; -const ROM: &[u8] = include_bytes!("../../roms/AlwasAwakening_demo.nes"); const MS_PER_FRAME: f64 = 1000.0 / 60.0; pub struct Web; @@ -18,26 +23,33 @@ impl Runtime for Web { console_log::init_with_level(log::Level::Debug) .map_err(|_| anyhow!("Failed to initialize logger"))?; - let ines = INes::read(ROM)?; - let cartridge = ines.into_cartridge(); - let display = BufferDisplay::default(); - let nes = Rc::new(RefCell::new(NES::new(cartridge, display))); + let base_nes = Rc::new(RefCell::new(Option::>::None)); - let nesdown = nes.clone(); + let nes = base_nes.clone(); add_event_listener("keydown", move |event: KeyboardEvent| { + let mut nes = nes.borrow_mut(); + let nes = match &mut *nes { + Some(nes) => nes, + None => return Ok(()), + }; let button = keycode_binding(&event.code()); - nesdown.borrow_mut().controller().press(button); + nes.controller().press(button); Ok(()) })?; - let nesup = nes.clone(); + let nes = base_nes.clone(); add_event_listener("keyup", move |event: KeyboardEvent| { + let mut nes = nes.borrow_mut(); + let nes = match &mut *nes { + Some(nes) => nes, + None => return Ok(()), + }; let button = keycode_binding(&event.code()); - nesup.borrow_mut().controller().release(button); + nes.controller().release(button); Ok(()) })?; - let nesdrop = nes.clone(); + let nes = base_nes.clone(); add_event_listener("drop", move |event: DragEvent| { event.prevent_default(); let items = event.data_transfer().context("No data transfered")?.items(); @@ -48,7 +60,8 @@ impl Runtime for Web { .get_as_file() .map_err(|_| anyhow!("Failed to get file"))? { - let nesread = nesdrop.clone(); + let filename = file.name(); + let nes = nes.clone(); let success = closure(move |array_buffer: JsValue| { let array_buffer = array_buffer @@ -56,15 +69,35 @@ impl Runtime for Web { .map_err(|_| anyhow!("Failed to convert to ArrayBuffer"))?; let array = Uint8Array::new(&array_buffer); - let mut rom = vec![0; array.length() as usize]; - array.copy_to(&mut rom); + let mut data = vec![0; array.length() as usize]; + array.copy_to(&mut data); + + let mut rom: Option> = None; + + if filename.ends_with(".zip") { + let mut zip = ZipArchive::new(Cursor::new(data))?; + + for index in 0..zip.len() { + let mut file = zip.by_index(index)?; + if file.name().ends_with(".nes") { + let mut rom_data = vec![0; file.size() as usize]; + file.read_exact(&mut rom_data)?; + rom = Some(rom_data); + break; + } + } + } else { + rom = Some(data); + } + + let rom = rom.ok_or_else(|| anyhow!("No .nes file found"))?; let ines = INes::read(&mut rom.as_slice())?; let cartridge = ines.into_cartridge(); let display = BufferDisplay::default(); let nes_new = NES::new(cartridge, display); - nesread.replace(nes_new); + nes.replace(Some(nes_new)); Ok(()) }); let failure = closure(move |_| { @@ -93,17 +126,22 @@ impl Runtime for Web { let mut timestamp_last_frame_ms = 0.0; - *g.borrow_mut() = Some(Closure::new(move |timestamp_ms: f64| { - request_animation_frame(f.borrow().as_ref().unwrap()) - .expect("failed to request animation frame"); + let nes = base_nes.clone(); + *g.borrow_mut() = Some(closure(move |timestamp_ms: f64| { + request_animation_frame(f.borrow().as_ref().unwrap())?; if timestamp_ms - timestamp_last_frame_ms < MS_PER_FRAME { - return; + return Ok(()); } timestamp_last_frame_ms = timestamp_ms; - // Run NES until frame starts let mut nes = nes.borrow_mut(); + let nes = match &mut *nes { + Some(nes) => nes, + None => return Ok(()), + }; + + // Run NES until frame starts while nes.display().vblank() { nes.tick(); } @@ -117,10 +155,11 @@ impl Runtime for Web { WIDTH as u32, HEIGHT as u32, ) - .expect("failed to create image data"); + .map_err(|_| anyhow!("Failed to create image data"))?; context .put_image_data(&image_data, 0.0, 0.0) - .expect("failed to put image data"); + .map_err(|_| anyhow!("Failed to put image data"))?; + Ok(()) })); request_animation_frame(g.borrow().as_ref().unwrap())?;