Compare commits
	
		
			No commits in common. "main" and "profiling" have entirely different histories.
		
	
	
		| 
						 | 
				
			
			@ -2165,7 +2165,7 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
 | 
			
		|||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "lemur"
 | 
			
		||||
version = "0.9.2"
 | 
			
		||||
version = "0.7.3"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "atoi",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@ description = "An emulator for the Virtual Boy."
 | 
			
		|||
repository = "https://git.virtual-boy.com/PVB/lemur"
 | 
			
		||||
publish = false
 | 
			
		||||
license = "MIT"
 | 
			
		||||
version = "0.9.2"
 | 
			
		||||
version = "0.7.3"
 | 
			
		||||
edition = "2024"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -41,7 +41,6 @@ ENV PATH="/osxcross/bin:$PATH" \
 | 
			
		|||
    SHROOMS_CFLAGS_x86_64-unknown-linux-gnu="-flto" \
 | 
			
		||||
    CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-Clinker-plugin-lto -Clinker=clang -Clink-arg=-fuse-ld=lld" \
 | 
			
		||||
    SHROOMS_CFLAGS_x86_64-pc-windows-msvc="-flto" \
 | 
			
		||||
    CFLAGS_x86_64-pc-windows-msvc="-I/xwin/crt/include -I/xwin/sdk/include/ucrt -I/xwin/sdk/include/um -I/xwin/sdk/include/shared" \
 | 
			
		||||
    CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS="-Lnative=/xwin/crt/lib/x86_64 -Lnative=/xwin/sdk/lib/um/x86_64 -Lnative=/xwin/sdk/lib/ucrt/x86_64 -Clinker-plugin-lto -Clink-arg=-fuse-ld=lld" \
 | 
			
		||||
    CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER="lld-link-20" \
 | 
			
		||||
    CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER="o64-clang" \
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										4
									
								
								build.rs
								
								
								
								
							
							
						
						
									
										4
									
								
								build.rs
								
								
								
								
							| 
						 | 
				
			
			@ -18,7 +18,6 @@ fn main() -> Result<(), Box<dyn Error>> {
 | 
			
		|||
    };
 | 
			
		||||
    builder
 | 
			
		||||
        .include(Path::new("shrooms-vb-core/core"))
 | 
			
		||||
        .include(Path::new("shrooms-vb-core/util"))
 | 
			
		||||
        .opt_level(opt_level)
 | 
			
		||||
        .flag_if_supported("-fno-strict-aliasing")
 | 
			
		||||
        .define("VB_LITTLE_ENDIAN", None)
 | 
			
		||||
| 
						 | 
				
			
			@ -30,10 +29,7 @@ fn main() -> Result<(), Box<dyn Error>> {
 | 
			
		|||
        .define("VB_DIRECT_FRAME", "on_frame")
 | 
			
		||||
        .define("VB_DIRECT_READ", "on_read")
 | 
			
		||||
        .define("VB_DIRECT_WRITE", "on_write")
 | 
			
		||||
        .define("VBU_REALLOC", "vbu_realloc_shim")
 | 
			
		||||
        .file(Path::new("shrooms-vb-core/core/vb.c"))
 | 
			
		||||
        .file(Path::new("shrooms-vb-core/util/isx.c"))
 | 
			
		||||
        .file(Path::new("shrooms-vb-core/util/vbu.c"))
 | 
			
		||||
        .compile("vb");
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1 +1 @@
 | 
			
		|||
Subproject commit 4ed3b7299507b8ea0079a0965f33b0c8a6886572
 | 
			
		||||
Subproject commit 8598eab087cced12b92a411f6c7f2eb9de51310f
 | 
			
		||||
							
								
								
									
										12
									
								
								src/app.rs
								
								
								
								
							
							
						
						
									
										12
									
								
								src/app.rs
								
								
								
								
							| 
						 | 
				
			
			@ -24,8 +24,8 @@ use crate::{
 | 
			
		|||
    persistence::Persistence,
 | 
			
		||||
    window::{
 | 
			
		||||
        AboutWindow, AppWindow, BgMapWindow, CharacterDataWindow, FrameBufferWindow, GameWindow,
 | 
			
		||||
        GdbServerWindow, HotkeysWindow, InitArgs, InputWindow, ObjectWindow, ProfileWindow,
 | 
			
		||||
        RegisterWindow, TerminalWindow, WorldWindow,
 | 
			
		||||
        GdbServerWindow, HotkeysWindow, InputWindow, ObjectWindow, ProfileWindow, RegisterWindow,
 | 
			
		||||
        TerminalWindow, WorldWindow,
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -423,13 +423,7 @@ impl Viewport {
 | 
			
		|||
        let (window, state) = create_window_and_state(&ctx, event_loop, &builder, &mut painter);
 | 
			
		||||
        egui_winit::update_viewport_info(&mut info, &ctx, &window, true);
 | 
			
		||||
 | 
			
		||||
        let render_state = painter.render_state();
 | 
			
		||||
        let args = InitArgs {
 | 
			
		||||
            ctx: &ctx,
 | 
			
		||||
            window: &window,
 | 
			
		||||
            render_state: render_state.as_ref().unwrap(),
 | 
			
		||||
        };
 | 
			
		||||
        app.on_init(args);
 | 
			
		||||
        app.on_init(&ctx, painter.render_state().as_ref().unwrap());
 | 
			
		||||
        Self {
 | 
			
		||||
            painter,
 | 
			
		||||
            ctx,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,7 +33,6 @@ mod cart;
 | 
			
		|||
mod game_info;
 | 
			
		||||
mod inline_stack_map;
 | 
			
		||||
mod shrooms_vb_core;
 | 
			
		||||
mod shrooms_vb_util;
 | 
			
		||||
 | 
			
		||||
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
 | 
			
		||||
pub enum SimId {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,7 @@ use std::{
 | 
			
		|||
    sync::Arc,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::emulator::{SimId, game_info::GameInfo, shrooms_vb_util::rom_from_isx};
 | 
			
		||||
use crate::emulator::{SimId, game_info::GameInfo};
 | 
			
		||||
 | 
			
		||||
pub struct Cart {
 | 
			
		||||
    pub file_path: PathBuf,
 | 
			
		||||
| 
						 | 
				
			
			@ -20,9 +20,8 @@ pub struct Cart {
 | 
			
		|||
impl Cart {
 | 
			
		||||
    pub fn load(file_path: &Path, sim_id: SimId) -> Result<Self> {
 | 
			
		||||
        let rom = fs::read(file_path)?;
 | 
			
		||||
        let (rom, info) = try_parse_isx(file_path, &rom)
 | 
			
		||||
            .or_else(|| try_parse_elf(file_path, &rom))
 | 
			
		||||
            .unwrap_or_else(|| (rom, GameInfo::empty(file_path)));
 | 
			
		||||
        let (rom, info) =
 | 
			
		||||
            try_parse_elf(file_path, &rom).unwrap_or_else(|| (rom, GameInfo::empty(file_path)));
 | 
			
		||||
 | 
			
		||||
        let mut sram_file = File::options()
 | 
			
		||||
            .read(true)
 | 
			
		||||
| 
						 | 
				
			
			@ -61,12 +60,6 @@ impl Cart {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn try_parse_isx(file_path: &Path, data: &[u8]) -> Option<(Vec<u8>, GameInfo)> {
 | 
			
		||||
    let rom = rom_from_isx(data)?;
 | 
			
		||||
    let info = GameInfo::from_isx(file_path, data);
 | 
			
		||||
    Some((rom, info))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn try_parse_elf(file_path: &Path, data: &[u8]) -> Option<(Vec<u8>, GameInfo)> {
 | 
			
		||||
    use object::read::elf::FileHeader;
 | 
			
		||||
    let program = match object::FileKind::parse(data).ok()? {
 | 
			
		||||
| 
						 | 
				
			
			@ -80,7 +73,7 @@ fn try_parse_elf(file_path: &Path, data: &[u8]) -> Option<(Vec<u8>, GameInfo)> {
 | 
			
		|||
        }
 | 
			
		||||
        _ => return None,
 | 
			
		||||
    };
 | 
			
		||||
    let info = GameInfo::from_elf(file_path, data).unwrap_or_else(|_| GameInfo::empty(file_path));
 | 
			
		||||
    let info = GameInfo::new(file_path, data).unwrap_or_else(|_| GameInfo::empty(file_path));
 | 
			
		||||
    Some((program, info))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -93,13 +86,12 @@ fn parse_elf_program<Elf: object::read::elf::FileHeader<Endian = object::Endiann
 | 
			
		|||
    let mut bytes = vec![];
 | 
			
		||||
    let mut pstart = None;
 | 
			
		||||
    for phdr in header.program_headers(endian, data).ok()? {
 | 
			
		||||
        let pma = phdr.p_paddr(endian).into();
 | 
			
		||||
        if pma < 0x07000000 || phdr.p_filesz(endian).into() == 0 {
 | 
			
		||||
        if phdr.p_filesz(endian).into() == 0 {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        let start = pstart.unwrap_or(pma);
 | 
			
		||||
        let start = pstart.unwrap_or(phdr.p_paddr(endian).into());
 | 
			
		||||
        pstart = Some(start);
 | 
			
		||||
        bytes.resize((pma - start) as usize, 0);
 | 
			
		||||
        bytes.resize((phdr.p_paddr(endian).into() - start) as usize, 0);
 | 
			
		||||
        let data = phdr.data(endian, data).ok()?;
 | 
			
		||||
        bytes.extend_from_slice(data);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,7 @@ pub struct GameInfo {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
impl GameInfo {
 | 
			
		||||
    pub fn from_elf(file_path: &Path, input: &[u8]) -> Result<Self> {
 | 
			
		||||
    pub fn new(file_path: &Path, input: &[u8]) -> Result<Self> {
 | 
			
		||||
        let file = object::File::parse(input)?;
 | 
			
		||||
 | 
			
		||||
        let (name, path) = name_and_path(file_path);
 | 
			
		||||
| 
						 | 
				
			
			@ -52,26 +52,6 @@ impl GameInfo {
 | 
			
		|||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn from_isx(file_path: &Path, input: &[u8]) -> Self {
 | 
			
		||||
        let (name, path) = name_and_path(file_path);
 | 
			
		||||
        let symbols = extract_isx_symbols(input);
 | 
			
		||||
        let library_info = LibraryInfo {
 | 
			
		||||
            name: name.clone(),
 | 
			
		||||
            debug_name: name,
 | 
			
		||||
            path: path.clone(),
 | 
			
		||||
            debug_path: path,
 | 
			
		||||
            debug_id: DebugId::default(),
 | 
			
		||||
            code_id: None,
 | 
			
		||||
            arch: None,
 | 
			
		||||
            symbol_table: symbols.map(|syms| Arc::new(SymbolTable::new(syms))),
 | 
			
		||||
        };
 | 
			
		||||
        let inline_stack_map = InlineStackMap::empty();
 | 
			
		||||
        Self {
 | 
			
		||||
            library_info,
 | 
			
		||||
            inline_stack_map,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn empty(file_path: &Path) -> Self {
 | 
			
		||||
        let (name, path) = name_and_path(file_path);
 | 
			
		||||
        let library_info = LibraryInfo {
 | 
			
		||||
| 
						 | 
				
			
			@ -135,66 +115,6 @@ fn build_inline_stack_map(file: object::File) -> Result<InlineStackMap> {
 | 
			
		|||
    Ok(frames.build())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn extract_isx_symbols(input: &[u8]) -> Option<Vec<Symbol>> {
 | 
			
		||||
    let mut syms = vec![];
 | 
			
		||||
    let (_, mut buf) = input.split_at_checked(32)?;
 | 
			
		||||
    while !buf.is_empty() {
 | 
			
		||||
        let typ;
 | 
			
		||||
        (typ, buf) = buf.split_first()?;
 | 
			
		||||
        match typ {
 | 
			
		||||
            0x11 => {
 | 
			
		||||
                // Code (Virtual Boy)
 | 
			
		||||
                (_, buf) = buf.split_at_checked(4)?;
 | 
			
		||||
                let len_bytes;
 | 
			
		||||
                (len_bytes, buf) = buf.split_first_chunk()?;
 | 
			
		||||
                let len = u32::from_le_bytes(*len_bytes);
 | 
			
		||||
                (_, buf) = buf.split_at_checked(len as usize)?;
 | 
			
		||||
            }
 | 
			
		||||
            0x13 => {
 | 
			
		||||
                // Range (Virtual Boy)
 | 
			
		||||
                let count_bytes;
 | 
			
		||||
                (count_bytes, buf) = buf.split_first_chunk()?;
 | 
			
		||||
                let count = u16::from_le_bytes(*count_bytes);
 | 
			
		||||
                (_, buf) = buf.split_at_checked(count as usize * 9)?;
 | 
			
		||||
            }
 | 
			
		||||
            0x14 => {
 | 
			
		||||
                // Symbol (Virtual Boy)
 | 
			
		||||
                let count_bytes;
 | 
			
		||||
                (count_bytes, buf) = buf.split_first_chunk()?;
 | 
			
		||||
                let count = u16::from_le_bytes(*count_bytes);
 | 
			
		||||
                for _ in 0..count {
 | 
			
		||||
                    let name_len;
 | 
			
		||||
                    (name_len, buf) = buf.split_first()?;
 | 
			
		||||
                    let name_bytes;
 | 
			
		||||
                    (name_bytes, buf) = buf.split_at_checked(*name_len as usize)?;
 | 
			
		||||
                    (_, buf) = buf.split_at_checked(2)?;
 | 
			
		||||
                    let address_bytes;
 | 
			
		||||
                    (address_bytes, buf) = buf.split_first_chunk()?;
 | 
			
		||||
 | 
			
		||||
                    let name_str = String::from_utf8_lossy(name_bytes);
 | 
			
		||||
                    let address = u32::from_le_bytes(*address_bytes);
 | 
			
		||||
                    syms.push(Symbol {
 | 
			
		||||
                        address: address & 0x07ffffff,
 | 
			
		||||
                        size: Some(4),
 | 
			
		||||
                        name: demangle_any(&name_str),
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            0x20..=0x22 => {
 | 
			
		||||
                // System (undocumented)
 | 
			
		||||
                let length_bytes;
 | 
			
		||||
                (length_bytes, buf) = buf.split_first_chunk()?;
 | 
			
		||||
                let length = u32::from_le_bytes(*length_bytes);
 | 
			
		||||
                (_, buf) = buf.split_at_checked(length as usize)?;
 | 
			
		||||
            }
 | 
			
		||||
            _ => {
 | 
			
		||||
                return None;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    Some(syms)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Reader<'a> = gimli::EndianSlice<'a, gimli::RunTimeEndian>;
 | 
			
		||||
 | 
			
		||||
struct ParseContext<'a> {
 | 
			
		||||
| 
						 | 
				
			
			@ -253,8 +173,8 @@ fn parse_inline(ctx: &mut ParseContext, node: gimli::EntriesTreeNode<Reader>) ->
 | 
			
		|||
        let name = Arc::new(name);
 | 
			
		||||
        let mut ranges = ctx.dorf.die_ranges(ctx.unit, node.entry())?;
 | 
			
		||||
        while let Some(range) = ranges.next()? {
 | 
			
		||||
            let start = range.begin as u32 & 0x07ffffff;
 | 
			
		||||
            let end = range.end as u32 & 0x07ffffff;
 | 
			
		||||
            let start = range.begin as u32;
 | 
			
		||||
            let end = range.end as u32;
 | 
			
		||||
            ctx.frames.add(start, end, name.clone());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -261,7 +261,7 @@ extern "C" fn on_exception(sim: *mut VB, cause: *mut u16) -> c_int {
 | 
			
		|||
    };
 | 
			
		||||
    data.monitor.event = data.monitor.queued_event.take();
 | 
			
		||||
    data.monitor.new_inline_stack = data.monitor.detect_new_inline_stack(pc);
 | 
			
		||||
    data.monitor.queued_event = Some(SimEvent::Interrupt(cause, pc & 0x07ffffff));
 | 
			
		||||
    data.monitor.queued_event = Some(SimEvent::Interrupt(cause, pc));
 | 
			
		||||
    unsafe { vb_set_exception_callback(sim, None) };
 | 
			
		||||
    if data.monitor.event.is_some() || data.monitor.new_inline_stack.is_some() {
 | 
			
		||||
        1
 | 
			
		||||
| 
						 | 
				
			
			@ -439,9 +439,7 @@ impl EventMonitor {
 | 
			
		|||
                // JAL .+4 is how programs get r31 to a known value for indirect calls
 | 
			
		||||
                // (which we detect later.)
 | 
			
		||||
                // Any other JAL is a function call.
 | 
			
		||||
                return Some(SimEvent::Call(
 | 
			
		||||
                    address.wrapping_add_signed(disp) & 0x07ffffff,
 | 
			
		||||
                ));
 | 
			
		||||
                return Some(SimEvent::Call(address.wrapping_add_signed(disp)));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -455,7 +453,7 @@ impl EventMonitor {
 | 
			
		|||
            if r31 as u32 == address.wrapping_add(2) {
 | 
			
		||||
                // JMP anywhere else, if r31 points to after the JMP, is an indirect call
 | 
			
		||||
                let target = unsafe { vb_get_program_register(sim, jmp_reg as u32) };
 | 
			
		||||
                return Some(SimEvent::Call(target as u32 & 0x07ffffff));
 | 
			
		||||
                return Some(SimEvent::Call(target as u32));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,31 +0,0 @@
 | 
			
		|||
use std::ffi::c_void;
 | 
			
		||||
 | 
			
		||||
#[unsafe(no_mangle)]
 | 
			
		||||
unsafe fn vbu_realloc_shim(ptr: *mut c_void, new_size: usize) -> *mut c_void {
 | 
			
		||||
    if !ptr.is_null() {
 | 
			
		||||
        // not supporting proper realloc because it needs bookkeeping and it's unnecessary
 | 
			
		||||
        return std::ptr::null_mut();
 | 
			
		||||
    }
 | 
			
		||||
    let allocation = vec![0u8; new_size].into_boxed_slice();
 | 
			
		||||
    Box::into_raw(allocation).cast()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[link(name = "vb")]
 | 
			
		||||
unsafe extern "C" {
 | 
			
		||||
    #[link_name = "vbuFromISX"]
 | 
			
		||||
    fn vbu_from_isx(bytes: *const c_void, length: usize, rom_length: *mut usize) -> *mut c_void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn rom_from_isx(bytes: &[u8]) -> Option<Vec<u8>> {
 | 
			
		||||
    if !bytes.starts_with(b"ISX") {
 | 
			
		||||
        return None;
 | 
			
		||||
    }
 | 
			
		||||
    let mut rom_length = 0;
 | 
			
		||||
    let raw_rom = unsafe { vbu_from_isx(bytes.as_ptr().cast(), bytes.len(), &mut rom_length) };
 | 
			
		||||
    if raw_rom.is_null() {
 | 
			
		||||
        return None;
 | 
			
		||||
    }
 | 
			
		||||
    // SAFETY: the rom was allocated by vbu_realloc_shim, which created it from a Vec<u8>.
 | 
			
		||||
    let rom = unsafe { Vec::from_raw_parts(raw_rom.cast(), rom_length, rom_length) };
 | 
			
		||||
    Some(rom)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,3 @@
 | 
			
		|||
use std::sync::Arc;
 | 
			
		||||
 | 
			
		||||
pub use about::AboutWindow;
 | 
			
		||||
use egui::{Context, ViewportBuilder, ViewportId};
 | 
			
		||||
pub use game::GameWindow;
 | 
			
		||||
| 
						 | 
				
			
			@ -11,7 +9,7 @@ pub use terminal::TerminalWindow;
 | 
			
		|||
pub use vip::{
 | 
			
		||||
    BgMapWindow, CharacterDataWindow, FrameBufferWindow, ObjectWindow, RegisterWindow, WorldWindow,
 | 
			
		||||
};
 | 
			
		||||
use winit::{event::KeyEvent, window::Window};
 | 
			
		||||
use winit::event::KeyEvent;
 | 
			
		||||
 | 
			
		||||
use crate::emulator::SimId;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -33,8 +31,9 @@ pub trait AppWindow {
 | 
			
		|||
    }
 | 
			
		||||
    fn initial_viewport(&self) -> ViewportBuilder;
 | 
			
		||||
    fn show(&mut self, ctx: &Context);
 | 
			
		||||
    fn on_init(&mut self, args: InitArgs) {
 | 
			
		||||
        let _ = args;
 | 
			
		||||
    fn on_init(&mut self, ctx: &Context, render_state: &egui_wgpu::RenderState) {
 | 
			
		||||
        let _ = ctx;
 | 
			
		||||
        let _ = render_state;
 | 
			
		||||
    }
 | 
			
		||||
    fn on_destroy(&mut self) {}
 | 
			
		||||
    fn handle_key_event(&mut self, event: &KeyEvent) -> bool {
 | 
			
		||||
| 
						 | 
				
			
			@ -46,9 +45,3 @@ pub trait AppWindow {
 | 
			
		|||
        false
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct InitArgs<'a> {
 | 
			
		||||
    pub ctx: &'a Context,
 | 
			
		||||
    pub window: &'a Arc<Window>,
 | 
			
		||||
    pub render_state: &'a egui_wgpu::RenderState,
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,14 +1,10 @@
 | 
			
		|||
use std::{
 | 
			
		||||
    sync::{Arc, mpsc},
 | 
			
		||||
    time::Duration,
 | 
			
		||||
};
 | 
			
		||||
use std::{sync::mpsc, time::Duration};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    app::UserEvent,
 | 
			
		||||
    emulator::{EmulatorClient, EmulatorCommand, EmulatorState, SimId, SimState},
 | 
			
		||||
    input::{Command, ShortcutProvider},
 | 
			
		||||
    persistence::Persistence,
 | 
			
		||||
    window::InitArgs,
 | 
			
		||||
};
 | 
			
		||||
use anyhow::Context as _;
 | 
			
		||||
use egui::{
 | 
			
		||||
| 
						 | 
				
			
			@ -51,7 +47,6 @@ pub struct GameWindow {
 | 
			
		|||
    screen: Option<GameScreen>,
 | 
			
		||||
    messages: Option<mpsc::Receiver<Toast>>,
 | 
			
		||||
    color_picker: Option<ColorPickerState>,
 | 
			
		||||
    window: Option<Arc<winit::window::Window>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl GameWindow {
 | 
			
		||||
| 
						 | 
				
			
			@ -78,7 +73,6 @@ impl GameWindow {
 | 
			
		|||
            screen: None,
 | 
			
		||||
            messages: None,
 | 
			
		||||
            color_picker: None,
 | 
			
		||||
            window: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -93,7 +87,7 @@ impl GameWindow {
 | 
			
		|||
            match command {
 | 
			
		||||
                Command::OpenRom => {
 | 
			
		||||
                    let rom = rfd::FileDialog::new()
 | 
			
		||||
                        .add_filter("Virtual Boy ROMs", &["vb", "vbrom", "elf", "isx"])
 | 
			
		||||
                        .add_filter("Virtual Boy ROMs", &["vb", "vbrom"])
 | 
			
		||||
                        .pick_file();
 | 
			
		||||
                    if let Some(path) = rom {
 | 
			
		||||
                        self.client
 | 
			
		||||
| 
						 | 
				
			
			@ -145,7 +139,7 @@ impl GameWindow {
 | 
			
		|||
                .clicked()
 | 
			
		||||
            {
 | 
			
		||||
                let rom = rfd::FileDialog::new()
 | 
			
		||||
                    .add_filter("Virtual Boy ROMs", &["vb", "vbrom", "elf", "isx"])
 | 
			
		||||
                    .add_filter("Virtual Boy ROMs", &["vb", "vbrom"])
 | 
			
		||||
                    .pick_file();
 | 
			
		||||
                if let Some(path) = rom {
 | 
			
		||||
                    self.client
 | 
			
		||||
| 
						 | 
				
			
			@ -304,13 +298,10 @@ impl GameWindow {
 | 
			
		|||
        self.client
 | 
			
		||||
            .send_command(EmulatorCommand::Screenshot(self.sim_id, tx));
 | 
			
		||||
        let bytes = rx.await.context("Could not take screenshot")?;
 | 
			
		||||
        let mut file_dialog = rfd::FileDialog::new()
 | 
			
		||||
        let file = rfd::FileDialog::new()
 | 
			
		||||
            .add_filter("PNG images", &["png"])
 | 
			
		||||
            .set_file_name("screenshot.png");
 | 
			
		||||
        if let Some(window) = self.window.as_ref() {
 | 
			
		||||
            file_dialog = file_dialog.set_parent(window);
 | 
			
		||||
        }
 | 
			
		||||
        let file = file_dialog.save_file();
 | 
			
		||||
            .set_file_name("screenshot.png")
 | 
			
		||||
            .save_file();
 | 
			
		||||
        let Some(path) = file else {
 | 
			
		||||
            return Ok(None);
 | 
			
		||||
        };
 | 
			
		||||
| 
						 | 
				
			
			@ -554,8 +545,8 @@ impl AppWindow for GameWindow {
 | 
			
		|||
        self.toasts.show(ctx);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn on_init(&mut self, args: InitArgs) {
 | 
			
		||||
        let (screen, sink) = GameScreen::init(args.render_state);
 | 
			
		||||
    fn on_init(&mut self, _ctx: &Context, render_state: &egui_wgpu::RenderState) {
 | 
			
		||||
        let (screen, sink) = GameScreen::init(render_state);
 | 
			
		||||
        let (message_sink, message_source) = mpsc::channel();
 | 
			
		||||
        self.client.send_command(EmulatorCommand::ConnectToSim(
 | 
			
		||||
            self.sim_id,
 | 
			
		||||
| 
						 | 
				
			
			@ -564,7 +555,6 @@ impl AppWindow for GameWindow {
 | 
			
		|||
        ));
 | 
			
		||||
        self.screen = Some(screen);
 | 
			
		||||
        self.messages = Some(message_source);
 | 
			
		||||
        self.window = Some(args.window.clone());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn on_destroy(&mut self) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,14 +1,13 @@
 | 
			
		|||
use std::{fs, sync::Arc, time::Duration};
 | 
			
		||||
use std::{fs, time::Duration};
 | 
			
		||||
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use egui::{Button, CentralPanel, Checkbox, Label, ViewportBuilder, ViewportId};
 | 
			
		||||
use egui_notify::{Anchor, Toast, Toasts};
 | 
			
		||||
use winit::window::Window;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    emulator::{EmulatorClient, EmulatorCommand, EmulatorState, SimId},
 | 
			
		||||
    profiler::{Profiler, ProfilerStatus},
 | 
			
		||||
    window::{AppWindow, InitArgs},
 | 
			
		||||
    window::AppWindow,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub struct ProfileWindow {
 | 
			
		||||
| 
						 | 
				
			
			@ -16,7 +15,6 @@ pub struct ProfileWindow {
 | 
			
		|||
    client: EmulatorClient,
 | 
			
		||||
    profiler: Profiler,
 | 
			
		||||
    toasts: Toasts,
 | 
			
		||||
    window: Option<Arc<Window>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ProfileWindow {
 | 
			
		||||
| 
						 | 
				
			
			@ -29,7 +27,6 @@ impl ProfileWindow {
 | 
			
		|||
                .with_anchor(Anchor::BottomLeft)
 | 
			
		||||
                .with_margin((10.0, 10.0).into())
 | 
			
		||||
                .reverse(true),
 | 
			
		||||
            window: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -62,16 +59,12 @@ impl ProfileWindow {
 | 
			
		|||
 | 
			
		||||
    fn try_finish_recording(&mut self) -> Result<Option<String>> {
 | 
			
		||||
        let bytes_receiver = self.profiler.finish_recording();
 | 
			
		||||
        let mut file_dialog = rfd::FileDialog::new()
 | 
			
		||||
        let file = rfd::FileDialog::new()
 | 
			
		||||
            .add_filter("Profiler files", &["json"])
 | 
			
		||||
            .set_file_name("profile.json");
 | 
			
		||||
        if let Some(window) = self.window.as_ref() {
 | 
			
		||||
            file_dialog = file_dialog.set_parent(window);
 | 
			
		||||
        }
 | 
			
		||||
        let file = file_dialog.save_file();
 | 
			
		||||
            .set_file_name("profile.json")
 | 
			
		||||
            .save_file();
 | 
			
		||||
        if let Some(path) = file {
 | 
			
		||||
            let bytes = pollster::block_on(bytes_receiver)?;
 | 
			
		||||
            let _ = fs::remove_file(&path);
 | 
			
		||||
            fs::write(&path, bytes)?;
 | 
			
		||||
            Ok(Some(path.display().to_string()))
 | 
			
		||||
        } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -166,8 +159,4 @@ impl AppWindow for ProfileWindow {
 | 
			
		|||
        });
 | 
			
		||||
        self.toasts.show(ctx);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn on_init(&mut self, args: InitArgs) {
 | 
			
		||||
        self.window = Some(args.window.clone());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,7 +11,7 @@ use crate::{
 | 
			
		|||
    images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader},
 | 
			
		||||
    memory::{MemoryClient, MemoryView},
 | 
			
		||||
    window::{
 | 
			
		||||
        AppWindow, InitArgs,
 | 
			
		||||
        AppWindow,
 | 
			
		||||
        utils::{NumberEdit, UiExt},
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -187,8 +187,8 @@ impl AppWindow for BgMapWindow {
 | 
			
		|||
            .with_inner_size((640.0, 480.0))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn on_init(&mut self, args: InitArgs) {
 | 
			
		||||
        args.ctx.add_texture_loader(self.loader.clone());
 | 
			
		||||
    fn on_init(&mut self, ctx: &Context, _render_state: &egui_wgpu::RenderState) {
 | 
			
		||||
        ctx.add_texture_loader(self.loader.clone());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn show(&mut self, ctx: &Context) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,7 @@ use crate::{
 | 
			
		|||
    images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader},
 | 
			
		||||
    memory::{MemoryClient, MemoryView},
 | 
			
		||||
    window::{
 | 
			
		||||
        AppWindow, InitArgs,
 | 
			
		||||
        AppWindow,
 | 
			
		||||
        utils::{NumberEdit, UiExt as _},
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -251,8 +251,8 @@ impl AppWindow for CharacterDataWindow {
 | 
			
		|||
            .with_inner_size((640.0, 480.0))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn on_init(&mut self, args: InitArgs) {
 | 
			
		||||
        args.ctx.add_texture_loader(self.loader.clone());
 | 
			
		||||
    fn on_init(&mut self, ctx: &Context, _render_state: &egui_wgpu::RenderState) {
 | 
			
		||||
        ctx.add_texture_loader(self.loader.clone());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn show(&mut self, ctx: &Context) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,7 +11,7 @@ use crate::{
 | 
			
		|||
    images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader},
 | 
			
		||||
    memory::{MemoryClient, MemoryView},
 | 
			
		||||
    window::{
 | 
			
		||||
        AppWindow, InitArgs,
 | 
			
		||||
        AppWindow,
 | 
			
		||||
        utils::{NumberEdit, UiExt as _},
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -153,8 +153,8 @@ impl AppWindow for FrameBufferWindow {
 | 
			
		|||
            .with_inner_size((640.0, 480.0))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn on_init(&mut self, args: InitArgs) {
 | 
			
		||||
        args.ctx.add_texture_loader(self.loader.clone());
 | 
			
		||||
    fn on_init(&mut self, ctx: &Context, _render_state: &egui_wgpu::RenderState) {
 | 
			
		||||
        ctx.add_texture_loader(self.loader.clone());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn show(&mut self, ctx: &Context) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,7 +11,7 @@ use crate::{
 | 
			
		|||
    images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader},
 | 
			
		||||
    memory::{MemoryClient, MemoryView},
 | 
			
		||||
    window::{
 | 
			
		||||
        AppWindow, InitArgs,
 | 
			
		||||
        AppWindow,
 | 
			
		||||
        utils::{NumberEdit, UiExt as _},
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -208,8 +208,8 @@ impl AppWindow for ObjectWindow {
 | 
			
		|||
            .with_inner_size((640.0, 500.0))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn on_init(&mut self, args: InitArgs) {
 | 
			
		||||
        args.ctx.add_texture_loader(self.loader.clone());
 | 
			
		||||
    fn on_init(&mut self, ctx: &Context, _render_state: &egui_wgpu::RenderState) {
 | 
			
		||||
        ctx.add_texture_loader(self.loader.clone());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn show(&mut self, ctx: &Context) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,7 +17,7 @@ use crate::{
 | 
			
		|||
    images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader},
 | 
			
		||||
    memory::{MemoryClient, MemoryRef, MemoryView},
 | 
			
		||||
    window::{
 | 
			
		||||
        AppWindow, InitArgs,
 | 
			
		||||
        AppWindow,
 | 
			
		||||
        utils::{NumberEdit, UiExt as _},
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -523,8 +523,8 @@ impl AppWindow for WorldWindow {
 | 
			
		|||
            .with_inner_size((640.0, 520.0))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn on_init(&mut self, args: InitArgs) {
 | 
			
		||||
        args.ctx.add_texture_loader(self.loader.clone());
 | 
			
		||||
    fn on_init(&mut self, ctx: &Context, _render_state: &egui_wgpu::RenderState) {
 | 
			
		||||
        ctx.add_texture_loader(self.loader.clone());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn show(&mut self, ctx: &Context) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue