├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── emu ├── .gitignore ├── Cargo.toml └── src │ ├── debug.rs │ ├── frame_timer.rs │ ├── gfx.rs │ ├── io.rs │ ├── lib.rs │ ├── mem.rs │ ├── region.rs │ ├── rom.rs │ ├── vdp.rs │ └── z80.rs ├── frontend-minimal ├── Cargo.toml ├── build.sh ├── src │ └── lib.rs └── static │ ├── app.js │ ├── frontend_minimal.js │ ├── frontend_minimal_bg.wasm │ ├── gamepad.js │ └── index.html └── frontend ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── build.sh ├── icon.png ├── publish.sh ├── setup_web.sh ├── src ├── app.rs ├── debug │ ├── cpu.rs │ ├── memory.rs │ ├── mod.rs │ ├── palette.rs │ └── vram.rs ├── input │ └── mod.rs ├── lib.rs ├── main.rs ├── s1proto.bin └── widgets │ ├── file.rs │ ├── menu.rs │ ├── mod.rs │ └── viewport.rs └── static └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | **/*.rs.bk 3 | notes 4 | 5 | node_modules 6 | npm-debug.log 7 | package-lock.json 8 | yarn-error.log 9 | 10 | static/* 11 | !static/index.html 12 | perf* 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "emu", 5 | "frontend", 6 | "frontend-minimal", 7 | ] 8 | 9 | [profile.release] 10 | opt-level = 2 11 | # debug = true 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Kirjava 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [initial demo](https://kirjava.xyz/sonic2) 2 | 3 | trueLMAO is an in-progress Sega Megadrive (Genesis) emulator for web & native 4 | 5 | ### crates 6 | 7 | * emu - portable emulator library 8 | * frontend - UI app for web & native 9 | * frontend-minimal - simple webapp embedding example 10 | 11 | ### attribution 12 | 13 | This project uses the 68k CPU from [marhel/r68k](https://github.com/marhel/r68k) 14 | 15 | The Z80 CPU comes from [carmiker/jgz80](https://github.com/carmiker/jgz80) which was ported to Rust at [kirjavascript/z80](https://github.com/kirjavascript/z80) for this project 16 | -------------------------------------------------------------------------------- /emu/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk -------------------------------------------------------------------------------- /emu/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "emu" 3 | version = "0.0.1" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | r68k-emu = { git = "https://github.com/marhel/r68k.git" } 8 | r68k-tools = { git = "https://github.com/marhel/r68k.git" } 9 | z80 = "1.0.2" 10 | instant = { version = "0.1.12", features = [ "wasm-bindgen" ] } 11 | -------------------------------------------------------------------------------- /emu/src/debug.rs: -------------------------------------------------------------------------------- 1 | use crate::Megadrive; 2 | 3 | pub fn disasm_demo(emu: &Megadrive) -> Vec<(u32, String)> { 4 | use r68k_tools::PC; 5 | let mut buffer = Vec::new(); 6 | let mut opcodes = Vec::new(); 7 | // longest opcode is 16 bytes 8 | for i in 0..(16 * 10) { 9 | buffer.push(emu.core.mem.rom.read_byte(emu.core.pc + i)); 10 | } 11 | let mvec = r68k_tools::memory::MemoryVec::new8(PC(0), buffer); 12 | let mut cursor = PC(0); 13 | for _ in 0..10 { 14 | let disasm = r68k_tools::disassembler::disassemble(cursor, &mvec); 15 | if let Ok((pc, opcode)) = disasm { 16 | opcodes.push((cursor.0 + emu.core.pc, opcode.to_string().to_lowercase())); 17 | cursor = pc; 18 | } 19 | } 20 | 21 | opcodes 22 | 23 | } 24 | -------------------------------------------------------------------------------- /emu/src/frame_timer.rs: -------------------------------------------------------------------------------- 1 | use instant::Instant; 2 | 3 | pub struct FrameTimer { 4 | pub frame_count: u64, 5 | pub frames: u64, 6 | pub epoch: Instant, 7 | } 8 | 9 | impl Default for FrameTimer { 10 | fn default() -> Self { 11 | Self { 12 | frames: 0, 13 | frame_count: 0, 14 | epoch: Instant::now(), 15 | } 16 | } 17 | } 18 | 19 | impl FrameTimer { 20 | pub fn frame_count(&mut self, region: &crate::region::Region) -> u64 { 21 | let diff = Instant::now().duration_since(self.epoch); 22 | let rate = if region.is_pal() { 23 | 0.049701459 24 | } else { 25 | 0.05992274 26 | }; 27 | let frames = (diff.as_millis() as f64 * rate) as u64; 28 | self.frame_count = frames - self.frames; 29 | self.frames = frames; 30 | self.frame_count 31 | } 32 | /// use when unpausing 33 | pub fn reset_epoch(&mut self) { 34 | self.epoch = Instant::now(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /emu/src/gfx.rs: -------------------------------------------------------------------------------- 1 | use super::Megadrive; 2 | 3 | pub type Screen = [u8; 320 * 240 * 3]; 4 | 5 | pub struct Gfx { 6 | pub screen: Screen, 7 | } 8 | 9 | impl Gfx { 10 | pub fn new() -> Self { 11 | Gfx { screen: [0; 320 * 240 * 3], } 12 | } 13 | 14 | pub fn clear_screen(emu: &mut Megadrive) { 15 | let bg_color = emu.core.mem.vdp.bg_color(); 16 | for pixel in emu.gfx.screen.chunks_mut(3) { 17 | pixel[0] = bg_color.0; 18 | pixel[1] = bg_color.1; 19 | pixel[2] = bg_color.2; 20 | }; 21 | } 22 | 23 | pub fn line_slice(&mut self, screen_y: usize) -> &mut [u8] { 24 | // TODO: screen_width 25 | let offset = screen_y * 320 * 3; 26 | &mut self.screen[offset..offset + (320 * 3)] 27 | } 28 | 29 | pub fn draw_plane_line<'a>( 30 | emu: &'a mut Megadrive, 31 | mut line_high: &'a mut [u8], 32 | cell_w: usize, 33 | cell_h: usize, 34 | screen_y: usize, 35 | screen_width: usize, 36 | nametable: usize, 37 | hscroll: usize, 38 | vscroll_offset: usize, // 0 is A, 1 is B 39 | ) { 40 | let mut line_low = emu.gfx.line_slice(screen_y); 41 | 42 | let columns_mode = emu.core.mem.vdp.vcolumns_mode(); 43 | let vscroll_base = emu.core.mem.vdp.VSRAM[vscroll_offset] as usize; 44 | 45 | let plane_width = cell_w * 8; 46 | let plane_height = cell_h * 8; 47 | 48 | let mut screen_x = 0; 49 | let mut target = 0; 50 | let inc = if columns_mode { 16 } else { screen_width }; 51 | 52 | while target < screen_width { 53 | target += inc; 54 | 55 | let vscroll = if columns_mode { 56 | let column_offset = (screen_x / 16) * 2; 57 | emu.core.mem.vdp.VSRAM[vscroll_offset + column_offset] as usize 58 | } else { 59 | vscroll_base 60 | }; 61 | 62 | let mut first_item = true; 63 | 64 | while screen_x < target { 65 | let mut start = 0; 66 | let end = 8.min(target - screen_x); 67 | let mut width = end; 68 | 69 | if first_item { 70 | let hoff = hscroll % 8; 71 | if hoff > 0 { 72 | start = 8 - hoff; 73 | width = hoff; 74 | } 75 | first_item = false; 76 | } 77 | 78 | let start = start; 79 | let width = width; 80 | 81 | let hscroll_rem = hscroll % plane_width; 82 | let x_offset = (screen_x + plane_width - hscroll_rem) % plane_width; 83 | let y_offset = (screen_y + vscroll) % plane_height; 84 | 85 | let tile_index = ((x_offset / 8) + (y_offset / 8 * cell_w)) * 2; 86 | 87 | let byte = emu.core.mem.vdp.VRAM[nametable + tile_index] as usize; 88 | let next = emu.core.mem.vdp.VRAM[nametable + tile_index + 1] as usize; 89 | let word = byte << 8 | next; 90 | 91 | let tile = word & 0x7FF; 92 | let vflip = (byte & 0x10) != 0; 93 | let hflip = (byte & 0x8) != 0; 94 | let palette = (byte & 0x60) >> 5; 95 | let priority = (byte >> 7) & 1; 96 | 97 | let y = y_offset & 7; 98 | let y = if vflip { y ^ 7 } else { y }; 99 | let index = (tile * 32) + (y * 4); 100 | 101 | let mut pixels = [0; 8]; 102 | let mut pos = 0; 103 | for duxel in &emu.core.mem.vdp.VRAM[index..index+4] { 104 | let abs_pos = if hflip { 7 - pos } else { pos }; 105 | pixels[abs_pos] = duxel >> 4; 106 | let abs_pos = if hflip { 7 - (pos + 1) } else { pos + 1 }; 107 | pixels[abs_pos] = duxel & 0xF; 108 | pos += 2; 109 | } 110 | 111 | let target = if priority == 0 { &mut line_low } else { &mut line_high }; 112 | 113 | for (x, px) in (&pixels[start..end]).iter().enumerate() { 114 | if *px != 0 { 115 | let (r, g, b) = emu.core.mem.vdp.cram_rgb[*px as usize + (palette * 0x10)]; 116 | let offset = (screen_x + x) * 3; 117 | (*target)[offset] = r; 118 | (*target)[offset + 1] = g; 119 | (*target)[offset + 2] = b; 120 | } 121 | } 122 | 123 | screen_x += width; 124 | }; 125 | } 126 | } 127 | 128 | pub fn draw_sprite_line<'a>( 129 | emu: &'a mut Megadrive, 130 | mut line_high: &'a mut [u8], 131 | screen_y: usize, 132 | screen_width: usize, 133 | ) { 134 | let mut line_low = emu.gfx.line_slice(screen_y); 135 | 136 | let sprites = emu.core.mem.vdp.sprites(screen_y); 137 | 138 | for sprite in sprites.iter().rev() { 139 | let target = if sprite.priority == 0 { &mut line_low } else { &mut line_high }; 140 | let sprite_y = screen_y as isize - sprite.y_coord(); 141 | let tiles = &emu.core.mem.vdp.VRAM[sprite.tile..]; 142 | for sprite_x in 0..sprite.width * 8 { 143 | let x_offset = sprite.x_coord() + sprite_x as isize; 144 | 145 | if x_offset >= 0 && x_offset < screen_width as isize { 146 | 147 | let sprite_base_x = if sprite.h_flip { sprite_x ^ 7 } else { sprite_x }; 148 | let x_base = (sprite_base_x & 6) >> 1; 149 | let y_base = sprite_y & 7; 150 | let y_base = if sprite.v_flip { y_base ^ 7 } else { y_base } * 4; 151 | 152 | let tile_offset = (x_base as usize) + y_base as usize; 153 | 154 | let x_tile = sprite_x as usize / 8; 155 | let x_tile = if sprite.h_flip { sprite.width - x_tile - 1} else { x_tile }; 156 | let y_tile = sprite_y as usize / 8; 157 | let y_tile = if sprite.v_flip { sprite.height - y_tile - 1} else { y_tile }; 158 | let extra = (y_tile * 32) + (x_tile * 32 * sprite.height); 159 | 160 | let px = tiles[tile_offset + extra]; 161 | let px = if sprite_base_x & 1 == 0 { px >> 4 } else { px & 0xF }; 162 | 163 | if px != 0 { 164 | let (r, g, b) = emu.core.mem.vdp.cram_rgb[px as usize + (sprite.palette * 0x10)]; 165 | let offset = x_offset as usize * 3; 166 | 167 | if offset + 2 <= target.len() { 168 | (*target)[offset] = r; 169 | (*target)[offset + 1] = g; 170 | (*target)[offset + 2] = b; 171 | } 172 | } 173 | } 174 | } 175 | } 176 | } 177 | 178 | 179 | pub fn draw_window_line<'a>( 180 | emu: &'a mut Megadrive, 181 | mut line_high: &'a mut [u8], 182 | screen_y: usize, 183 | ) { 184 | let mut line_low = emu.gfx.line_slice(screen_y); 185 | // TODO: support non-320 size nametable 186 | // TODO: plane A / window exclusivity (perf) 187 | let nametable = (emu.core.mem.vdp.registers[3] as usize >> 1) * 0x800; 188 | let window_x = emu.core.mem.vdp.registers[0x11]; 189 | let window_y = emu.core.mem.vdp.registers[0x12]; 190 | let window_left = window_x >> 7 == 0; 191 | let window_top = window_y >> 7 == 0; 192 | let window_x = window_x as usize & 0x1F; 193 | let window_y = window_y as usize & 0x1F; 194 | let cell_w = 64; // TODO 195 | let _cell_h = 30; // TODO: PAL / screen size 196 | 197 | if window_left && window_top && window_x == 0 && window_y == 0 { 198 | return; // TODO: not exhausative, will catch most cases 199 | } 200 | 201 | // draw TO left, TO top 202 | 203 | if window_left && window_top && screen_y < window_y * 8 { 204 | 205 | let row = screen_y / 8; 206 | 207 | for n in 0..cell_w - window_x { 208 | let tile_offset = (n + (row * cell_w)) * 2; 209 | let tile_slice = &emu.core.mem.vdp.VRAM[nametable + tile_offset..]; 210 | 211 | let word = (tile_slice[0] as usize) << 8 | tile_slice[1] as usize; 212 | let byte = word >> 8; 213 | 214 | let priority = (byte >> 7) & 1; 215 | 216 | let tile = word & 0x7FF; 217 | let vflip = (byte & 0x10) != 0; 218 | let hflip = (byte & 0x8) != 0; 219 | let palette = (byte & 0x60) >> 5; 220 | 221 | let x = n * 8; 222 | let y = screen_y & 7; 223 | let y = if vflip { y ^ 7 } else { y }; 224 | let index = (tile * 32) + (y * 4); 225 | 226 | let target = if priority == 0 { &mut line_low } else { &mut line_high }; 227 | 228 | for cursor in 0..8 { 229 | let duxel = emu.core.mem.vdp.VRAM[index + (cursor / 2)]; 230 | let px = if cursor & 1 != 0 { duxel & 0xF } else { duxel >> 4 }; 231 | 232 | if px != 0 { 233 | let screen_x = if hflip { cursor ^ 7 } else { cursor } + x; 234 | let (r, g, b) = emu.core.mem.vdp.cram_rgb[px as usize + (palette * 0x10)]; 235 | let offset = screen_x * 3; 236 | (*target)[offset] = r; 237 | (*target)[offset + 1] = g; 238 | (*target)[offset + 2] = b; 239 | } 240 | } 241 | } 242 | 243 | } 244 | 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /emu/src/io.rs: -------------------------------------------------------------------------------- 1 | pub struct IO { 2 | pub registers: [u8; 0x10], 3 | pub gamepad: [Gamepad; 2], 4 | } 5 | 6 | pub struct Gamepad(usize); 7 | 8 | impl From for Gamepad { 9 | fn from(state: usize) -> Self { 10 | Gamepad(state) 11 | } 12 | } 13 | 14 | impl Gamepad { 15 | pub const START: usize = 1 << 7; 16 | pub const A: usize = 1 << 6; 17 | pub const C: usize = 1 << 5; 18 | pub const B: usize = 1 << 4; 19 | pub const R: usize = 1 << 3; 20 | pub const L: usize = 1 << 2; 21 | pub const D: usize = 1 << 1; 22 | pub const U: usize = 1; 23 | 24 | pub fn read(&self, register: u8, _control: u8) -> u8 { 25 | let select = register & 0x40; 26 | let latch = register & 0x80; 27 | 28 | // TODO: add control 29 | 30 | let value = if select != 0 { 31 | self.0 & 0x3F // CBRLDU 32 | } else { 33 | (self.0 >> 2) & 0x30 // SA 34 | | self.0 & 3 // DU 35 | }; 36 | 37 | latch | select | !value as u8 38 | } 39 | 40 | pub fn set(&mut self, value: usize) { 41 | self.0 = value; 42 | } 43 | } 44 | 45 | 46 | impl IO { 47 | pub fn new() -> Self { 48 | Self { 49 | registers: [ 50 | 0xA0, // version 51 | 0x7F, // ctrl 1 data 52 | 0x7F, // ctrl 2 data 53 | 0x7F, // exp data 54 | 0, // ctrl 1 ctrl 55 | 0, // ctrl 2 ctrl 56 | 0, // exp control 57 | 0xFF, 58 | 0, 59 | 0, 60 | 0xFF, 61 | 0, 62 | 0, 63 | 0xFF, 64 | 0, 65 | 0, 66 | ], 67 | gamepad: [ 68 | 0.into(), 69 | 0.into(), 70 | ], 71 | } 72 | } 73 | 74 | pub fn read_byte(&self, address: u32) -> u8 { 75 | let index = (address as usize >> 1) & 0x1f; 76 | 77 | if (1..=2).contains(&index) { 78 | let reg = self.registers[index]; 79 | let ctrl = self.registers[index + 3]; 80 | self.gamepad[index - 1].read(reg, ctrl) 81 | } else { 82 | self.registers[index & 0xF] 83 | } 84 | } 85 | 86 | pub fn write_byte(&mut self, address: u32, value: u32) { 87 | let index = (address as usize >> 1) & 0x1F; 88 | 89 | if (1..=6).contains(&index) { 90 | self.registers[index] = value as _; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /emu/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod debug; 2 | pub mod frame_timer; 3 | pub mod gfx; 4 | pub mod io; 5 | pub mod mem; 6 | pub mod region; 7 | pub mod rom; 8 | pub mod vdp; 9 | pub mod z80; 10 | 11 | use r68k_emu::cpu::{STACK_POINTER_REG, ConfiguredCore}; 12 | use r68k_emu::interrupts::AutoInterruptController; 13 | use gfx::Gfx; 14 | 15 | // TODO: composit layers in gfx istead of multiple buffers 16 | // just draw directly to layers 17 | // just have two layers 18 | 19 | // TODO: dump resolution and support it 20 | 21 | pub struct Megadrive { 22 | pub core: ConfiguredCore, 23 | pub gfx: Gfx, 24 | pub frame_timer: frame_timer::FrameTimer, 25 | pub region: region::Region, 26 | } 27 | 28 | impl Megadrive { 29 | pub fn new(buf: Vec) -> Self { 30 | let mem = mem::Mem::new(buf.into()); 31 | 32 | let int_ctrl = AutoInterruptController::new(); 33 | let mut core = ConfiguredCore::new_with(mem.rom.entry_point(), int_ctrl, mem); 34 | 35 | core.dar[STACK_POINTER_REG] = core.mem.rom.stack_pointer(); 36 | 37 | let region = region::Region::detect(&core.mem.rom); 38 | 39 | let mut emu = Megadrive { 40 | core, 41 | gfx: Gfx::new(), 42 | frame_timer: Default::default(), 43 | region, 44 | }; 45 | 46 | region::Region::set_io_region(&emu.region, &mut emu.core.mem.io); 47 | 48 | emu 49 | } 50 | 51 | /// render frame(s) at current instant 52 | /// returns number of rendered frames 53 | pub fn render(&mut self) -> u64 { 54 | let frame_count = self.frame_timer.frame_count(&self.region); 55 | if frame_count > 3 { // magic number 56 | self.frame(true); 57 | } else if frame_count > 0 { 58 | for _ in 0..frame_count - 1 { 59 | self.frame(false); 60 | } 61 | self.frame(true); 62 | } 63 | frame_count 64 | } 65 | 66 | /// renders a single frame 67 | pub fn frame(&mut self, draw: bool) { 68 | /* cycle counts initially taken from drx/kiwi */ 69 | // TODO: use a counter instead 70 | // TODO: patch gfx.screen_width here for gfx.draw() 71 | 72 | if draw { 73 | Gfx::clear_screen(self); 74 | } 75 | 76 | self.core.mem.vdp.unset_status(vdp::VBLANK_MASK); 77 | self.core.mem.vdp.unset_status(vdp::VINT_MASK); 78 | 79 | let screen_height = self.core.mem.vdp.screen_height(); 80 | let mut hint_counter = self.core.mem.vdp.hint_counter(); 81 | for screen_y in 0..screen_height { 82 | self.core.execute(2680); 83 | 84 | hint_counter -= 1; 85 | if hint_counter < 0 { 86 | hint_counter = self.core.mem.vdp.hint_counter(); 87 | 88 | if self.core.mem.vdp.hint_enabled() { 89 | self.core.int_ctrl.request_interrupt(4); 90 | } 91 | 92 | } 93 | 94 | self.core.mem.vdp.set_status(vdp::HBLANK_MASK); 95 | self.core.execute(636); 96 | self.core.mem.vdp.unset_status(vdp::HBLANK_MASK); 97 | 98 | self.core.execute(104); 99 | 100 | if draw { 101 | self.fire_beam(screen_y); 102 | } 103 | } 104 | 105 | self.core.mem.vdp.set_status(vdp::VBLANK_MASK); 106 | 107 | self.core.execute(588); 108 | 109 | if self.core.mem.vdp.vint_enabled() { 110 | self.core.int_ctrl.request_interrupt(6); 111 | self.core.mem.vdp.set_status(vdp::VINT_MASK); 112 | } 113 | 114 | self.core.execute(3420-588); 115 | 116 | let scanlines = if self.region.is_pal() { 313 } else { 262 }; // TODO: PAL scanlines 117 | for _ in screen_height..scanlines { 118 | self.core.execute(3420); 119 | } 120 | } 121 | 122 | fn fire_beam(&mut self, screen_y: usize) { 123 | let (cell_w, cell_h) = self.core.mem.vdp.scroll_size(); 124 | let (plane_a, plane_b) = self.core.mem.vdp.nametables(); 125 | let (hscroll_a, hscroll_b) = self.core.mem.vdp.hscroll(screen_y); 126 | let screen_width = self.core.mem.vdp.screen_width(); 127 | 128 | // TODO: use slices for RGB copy 129 | // TODO: move clear_screen here 130 | 131 | // 0xFE is an invalid MD colour we use as a marker 132 | const MARKER: u8 = 0xFE; 133 | 134 | // have a dummy line we write high priority stuff to and copy after 135 | let mut line_high: [u8; 320 * 3] = [MARKER; 320 * 3]; // TODO: PAL 136 | 137 | // plane B 138 | Gfx::draw_plane_line( 139 | self, 140 | &mut line_high, 141 | cell_w, 142 | cell_h, 143 | screen_y, 144 | screen_width, 145 | plane_b, 146 | hscroll_b, 147 | 1, // vscroll_offset 148 | ); 149 | 150 | // plane A 151 | Gfx::draw_plane_line( 152 | self, 153 | &mut line_high, 154 | cell_w, 155 | cell_h, 156 | screen_y, 157 | screen_width, 158 | plane_a, 159 | hscroll_a, 160 | 0, 161 | ); 162 | 163 | // sprites 164 | Gfx::draw_sprite_line( 165 | self, 166 | &mut line_high, 167 | screen_y, 168 | screen_width, 169 | ); 170 | 171 | // window 172 | Gfx::draw_window_line( 173 | self, 174 | &mut line_high, 175 | screen_y, 176 | ); 177 | 178 | // copy high priority back to screen 179 | 180 | let line = self.gfx.line_slice(screen_y); 181 | for (i, rgb) in line_high.chunks(3).enumerate() { 182 | if rgb[0] != MARKER { 183 | let offset = i * 3; 184 | line[offset..offset+3].copy_from_slice(&rgb[0..3]); 185 | } 186 | } 187 | } 188 | 189 | } 190 | -------------------------------------------------------------------------------- /emu/src/mem.rs: -------------------------------------------------------------------------------- 1 | use r68k_emu::ram::AddressBus; 2 | use r68k_emu::ram::AddressSpace; 3 | use crate::rom::ROM; 4 | use crate::io::IO; 5 | use crate::vdp::VDP; 6 | use crate::z80::Z80; 7 | 8 | pub struct Mem { 9 | pub rom: ROM, 10 | pub io: IO, 11 | pub vdp: VDP, 12 | pub ram: [u8; 0x10000], 13 | pub z80: Z80, 14 | } 15 | 16 | impl Mem { 17 | pub fn new(rom: ROM) -> Self { 18 | Mem { 19 | rom, 20 | io: IO::new(), 21 | vdp: VDP::new(), 22 | ram: [0; 0x10000], 23 | z80: Z80::new(), 24 | } 25 | } 26 | } 27 | 28 | impl Mem { 29 | pub fn read_u8(&self, address: u32) -> u32 { 30 | match address & 0xFFFFFF { 31 | 0..=0x7FFFFF => self.rom.read_byte(address & 0x3FFFFF) as _, 32 | 0x800000..=0x9FFFFF => /* reserved */ 0, 33 | 0xA00000..=0xA03FFF => self.z80.read_byte(address as u16) as _, 34 | 0xA04000..=0xA0FFFF => /* Z80 */ 0, 35 | 0xA10000..=0xA1001F => self.io.read_byte(address) as _, 36 | 0xA10020..=0xA10FFF => /* reserved */ 0, 37 | 0xA11100..=0xA112FF => self.z80.ctrl_read(address) as _, 38 | 0xC00000..=0xDFFFFF => self.vdp.read(address), 39 | 0xFF0000..=0xFFFFFF => self.ram[address as usize & 0xFFFF] as _, 40 | _ => { 41 | println!("todo: read byte {:X}", address); 42 | 0 43 | }, 44 | } 45 | } 46 | pub fn read_u16(&self, address: u32) -> u32 { 47 | if (0xC00000..=0xDFFFFF).contains(&(address & 0xFFFFFF)) { 48 | return self.vdp.read(address); 49 | } 50 | self.read_u8(address) << 8 51 | | self.read_u8(address + 1) 52 | } 53 | pub fn read_u32(&self, address: u32) -> u32 { 54 | self.read_u16(address) << 16 55 | | self.read_u16(address + 2) 56 | } 57 | pub fn write_u8(&mut self, address: u32, value: u32) { 58 | match address & 0xFFFFFF { 59 | 0..=0x3FFFFF => {/* ROM */}, 60 | 0x400000..=0x9FFFFF => {/* reserved */}, 61 | 0xA00000..=0xA03FFF => self.z80.write_byte(address as u16, value as u8), 62 | 0xA04000..=0xA0FFFF => {/* Z80 */}, 63 | 0xA10000..=0xA1001F => self.io.write_byte(address, value), 64 | 0xA10020..=0xA10FFF => {/* reserved */}, 65 | 0xA11000..=0xA11001 => {/* memory mode register (no-op?) */}, 66 | 0xA11002..=0xA110FF => {/* reserved */}, 67 | 0xA11100..=0xA112FF => self.z80.ctrl_write(address, value), 68 | 0xA14101..=0xBFFFFF => {/* reserved */}, 69 | 0xC00000..=0xDFFFFF => {/* VDP / PSG? */}, 70 | 0xFF0000..=0xFFFFFF => { 71 | self.ram[address as usize & 0xFFFF] = value as u8; 72 | }, 73 | _ => println!("todo: write byte {:X} {:X}", address, value), 74 | } 75 | } 76 | pub fn write_u16(&mut self, address: u32, value: u32) { 77 | if (0xC00000..=0xDFFFFF).contains(&(address & 0xFFFFFF)) { 78 | return VDP::write(self, address, value); 79 | } 80 | self.write_u8(address, value >> 8); 81 | self.write_u8(address + 1, value & 0xFF); 82 | } 83 | pub fn write_u32(&mut self, address: u32, value: u32) { 84 | self.write_u16(address, value >> 16); 85 | self.write_u16(address + 2, value & 0xFFFF); 86 | } 87 | } 88 | 89 | impl AddressBus for Mem { 90 | fn copy_from(&mut self, _other: &Self) { 91 | todo!("copy from"); 92 | } 93 | #[inline] 94 | fn read_byte(&self, _address_space: AddressSpace, address: u32) -> u32 { 95 | self.read_u8(address) 96 | } 97 | #[inline] 98 | fn read_word(&self, _address_space: AddressSpace, address: u32) -> u32 { 99 | self.read_u16(address) 100 | } 101 | #[inline] 102 | fn read_long(&self, _address_space: AddressSpace, address: u32) -> u32 { 103 | self.read_u32(address) 104 | } 105 | #[inline] 106 | fn write_byte(&mut self, _address_space: AddressSpace, address: u32, value: u32) { 107 | self.write_u8(address, value) 108 | } 109 | #[inline] 110 | fn write_word(&mut self, _address_space: AddressSpace, address: u32, value: u32) { 111 | self.write_u16(address, value) 112 | } 113 | #[inline] 114 | fn write_long(&mut self, _address_space: AddressSpace, address: u32, value: u32) { 115 | self.write_u32(address, value) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /emu/src/region.rs: -------------------------------------------------------------------------------- 1 | use crate::rom::ROM; 2 | use crate::io::IO; 3 | 4 | #[derive(PartialEq)] 5 | pub enum Region { 6 | US, 7 | EU, 8 | JP, 9 | } 10 | 11 | impl Region { 12 | pub fn is_pal(&self) -> bool { 13 | self == &Region::EU 14 | } 15 | 16 | pub fn detect(rom: &ROM) -> Self { 17 | let region_str = rom.region(); 18 | if region_str.contains('U') { 19 | return Region::US 20 | } else if region_str.contains('J') { 21 | return Region::JP 22 | } else if region_str.contains('E') { 23 | return Region::EU 24 | } 25 | Region::US 26 | } 27 | 28 | pub fn set_io_region(region: &Region, io: &mut IO) { 29 | io.registers[0] = match *region { 30 | Region::US => 0xA1, 31 | Region::EU => 0xE1, 32 | Region::JP => 0x21, 33 | }; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /emu/src/rom.rs: -------------------------------------------------------------------------------- 1 | pub struct ROM { 2 | bytes: Vec, 3 | } 4 | 5 | impl From> for ROM { 6 | fn from(bytes: Vec) -> Self { 7 | ROM { bytes } 8 | } 9 | } 10 | 11 | impl ROM { 12 | pub fn read_byte(&self, addr: u32) -> u8 { 13 | // https://godbolt.org/z/decb18Yb5 14 | self.bytes.get(addr as usize).copied().unwrap_or(0) 15 | } 16 | 17 | pub fn read_word(&self, addr: u32) -> u16 { 18 | ((self.read_byte(addr) as u16) << 8) | self.read_byte(addr + 1) as u16 19 | } 20 | 21 | pub fn read_long(&self, addr: u32) -> u32 { 22 | ((self.read_word(addr) as u32) << 16) | self.read_word(addr + 2) as u32 23 | } 24 | 25 | pub fn size(&self) -> usize { 26 | self.bytes.len() 27 | } 28 | 29 | pub fn read_string(&self, range: std::ops::Range) -> String { 30 | if range.end >= self.bytes.len() { 31 | return format!("end {} >= length {}", range.end, self.bytes.len()) 32 | } 33 | String::from_utf8_lossy(&self.bytes[range]).to_string() 34 | } 35 | 36 | pub fn stack_pointer(&self) -> u32 { self.read_long(0x0) } 37 | pub fn entry_point(&self) -> u32 { self.read_long(0x4) } 38 | 39 | pub fn system_type(&self) -> String { self.read_string(0x100..0x110) } 40 | pub fn copyright(&self) -> String { self.read_string(0x110..0x120) } 41 | pub fn domestic_name(&self) -> String { self.read_string(0x120..0x150) } 42 | pub fn overseas_name(&self) -> String { self.read_string(0x150..0x180) } 43 | pub fn serial_number(&self) -> String { self.read_string(0x180..0x18E) } 44 | pub fn checksum(&self) -> u16 { self.read_word(0x18E) } 45 | pub fn device_support(&self) -> String { self.read_string(0x190..0x1A0) } 46 | pub fn region(&self) -> String { self.read_string(0x1F0..0x1F3) } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /emu/src/vdp.rs: -------------------------------------------------------------------------------- 1 | use crate::mem::Mem; 2 | 3 | #[allow(non_snake_case)] 4 | pub struct VDP { 5 | pub VRAM: [u8; 0x10000], 6 | pub CRAM: [u16; 0x40], 7 | pub VSRAM: [u16; 0x40], 8 | pub registers: [u8; 0x20], 9 | pub status: u32, 10 | pub control_code: u32, 11 | pub control_address: u32, 12 | pub control_pending: bool, 13 | pub dma_pending: bool, 14 | pub cram_rgb: [(u8, u8, u8); 0x40], 15 | } 16 | 17 | pub const VBLANK_MASK: u32 = 8; 18 | pub const HBLANK_MASK: u32 = 4; 19 | pub const VINT_MASK: u32 = 0x80; 20 | 21 | pub enum VDPType { 22 | VRAM, CRAM, VSRAM, 23 | } 24 | 25 | impl From for VDPType { 26 | fn from(value: u32) -> Self { 27 | match value { 28 | 0 | 1 => VDPType::VRAM, 29 | 2 | 3 => VDPType::CRAM, 30 | 4 | 5 => VDPType::VSRAM, 31 | _ => unreachable!("VDPType {:X}", value), 32 | } 33 | } 34 | } 35 | 36 | pub struct Sprite { 37 | pub tile: usize, 38 | pub x_pos: usize, 39 | pub y_pos: usize, 40 | pub width: usize, 41 | pub height: usize, 42 | pub palette: usize, 43 | pub priority: usize, 44 | pub v_flip: bool, 45 | pub h_flip: bool, 46 | } 47 | 48 | impl Sprite { 49 | pub fn x_coord(&self) -> isize { (self.x_pos as isize)-128 } 50 | pub fn y_coord(&self) -> isize { (self.y_pos as isize)-128 } 51 | } 52 | 53 | fn cram_to_rgb(color: u16) -> (u8, u8, u8) { 54 | let red = color & 0xf; 55 | let green = (color & 0xf0) >> 4; 56 | let blue = (color & 0xf00) >> 8; 57 | let dupe = |x| (x << 4) | x; 58 | (dupe(red as u8), dupe(green as u8), dupe(blue as u8)) 59 | } 60 | 61 | impl VDP { 62 | pub fn new() -> Self { 63 | Self { 64 | VRAM: [0; 0x10000], 65 | CRAM: [0; 0x40], 66 | VSRAM: [0; 0x40], 67 | registers: [0; 0x20], 68 | status: 0x3400, 69 | control_code: 0, 70 | control_address: 0, 71 | control_pending: false, 72 | dma_pending: false, 73 | cram_rgb: [(0, 0, 0); 0x40], 74 | } 75 | } 76 | 77 | pub fn bg_color(&self) -> (u8, u8, u8) { 78 | let vdp_bg = self.registers[7]; 79 | let index = vdp_bg & 0xF; 80 | let line = (vdp_bg >> 4) & 3; 81 | cram_to_rgb(self.CRAM[index as usize + (line as usize * 0x10)]) 82 | } 83 | 84 | pub fn screen_width(&self) -> usize { 85 | if self.registers[0xC] & 1 > 0 { 320 } else { 256 } 86 | } 87 | 88 | pub fn screen_height(&self) -> usize { 89 | if self.registers[1] & 0x08 > 0 { 240 } else { 224 } 90 | } 91 | 92 | pub fn hint_counter(&self) -> isize { 93 | self.registers[0xA] as isize 94 | } 95 | 96 | pub fn hint_enabled(&self) -> bool { 97 | self.registers[0] & 0x10 != 0 98 | } 99 | 100 | pub fn vint_enabled(&self) -> bool { 101 | self.registers[1] & 0x20 != 0 102 | } 103 | 104 | pub fn dma_length(&self) -> u32 { 105 | self.registers[0x13] as u32 | ((self.registers[0x14] as u32) << 8) 106 | } 107 | 108 | pub fn set_status(&mut self, mask: u32) { 109 | self.status |= mask; 110 | } 111 | 112 | pub fn unset_status(&mut self, mask: u32) { 113 | self.status &= !mask; 114 | } 115 | 116 | pub fn scroll_size(&self) -> (usize, usize) { 117 | let to_cells = |size| (size as usize + 1) * 32; 118 | // TODO: 96 is invalid 119 | ( 120 | to_cells(self.registers[0x10] & 3), 121 | to_cells((self.registers[0x10] >> 4) & 3), 122 | ) 123 | } 124 | 125 | pub fn nametables(&self) -> (usize, usize) { 126 | let plane_a = ((self.registers[2] >> 3) as usize) * 0x2000; 127 | let plane_b = (self.registers[4] as usize) * 0x2000; 128 | (plane_a, plane_b) 129 | } 130 | 131 | pub fn sprites(&self, screen_y: usize) -> Vec { 132 | let cell40 = self.screen_width() == 320; 133 | let mask = if cell40 { 0x7F } else { 0x7E }; 134 | // max_sprites for screen: if cell40 { 80 } else { 64 } 135 | let max_sprites = if cell40 { 20 } else { 16 }; 136 | let mut pixel_quota = if cell40 { 320 } else { 256 }; 137 | 138 | let addr = ((self.registers[5] as usize) & mask) << 9; 139 | 140 | 141 | let mut index = 0usize; 142 | let mut sprites = vec![]; 143 | loop { 144 | let sprite_screen_y = screen_y + 128; 145 | let offset = addr + (index * 8); 146 | let sprite = &self.VRAM[offset..]; 147 | let next = sprite[3].into(); 148 | let y_pos = ((sprite[0] as usize) << 8) | sprite[1] as usize; 149 | let height = (sprite[2] as usize & 3) + 1; 150 | if sprite_screen_y >= y_pos && sprite_screen_y < y_pos + (height * 8) { 151 | let mut width = (sprite[2] as usize >> 2) + 1; 152 | let priority = sprite[4] as usize >> 7; 153 | let palette = sprite[4] as usize >> 5 & 3; 154 | let v_flip = sprite[4] as usize >> 4 & 1 == 1; 155 | let h_flip = sprite[4] as usize >> 3 & 1 == 1; 156 | let tile = (((sprite[4] as usize & 7) << 8) | sprite[5] as usize) * 0x20; 157 | let x_pos = ((sprite[6] as usize) << 8) | sprite[7] as usize; 158 | 159 | let quota = width * 8; 160 | if quota > pixel_quota { 161 | width = pixel_quota / 8; 162 | } 163 | sprites.push(Sprite { 164 | y_pos, 165 | width, 166 | height, 167 | priority, 168 | palette, 169 | v_flip, 170 | h_flip, 171 | tile, 172 | x_pos, 173 | }); 174 | pixel_quota -= width * 8; 175 | } 176 | 177 | 178 | index = next; 179 | 180 | if pixel_quota == 0 || index == 0 || sprites.len() == max_sprites { 181 | break; 182 | } 183 | } 184 | 185 | sprites 186 | } 187 | 188 | pub fn hscroll(&self, screen_y: usize) -> (usize, usize) { 189 | let addr = (self.registers[0xD] as usize & 0x3F) << 10; 190 | let mode = self.registers[0xB] & 3; 191 | 192 | let index = match mode { 193 | 0 => 0, 194 | 2 => screen_y & 0xFFF8, 195 | 3 => screen_y, 196 | _ => unreachable!("invalid hscroll"), 197 | }; 198 | 199 | let hscroll = &self.VRAM[addr + (index * 4)..]; 200 | 201 | let hscroll_a = ((hscroll[0] as usize) << 8) + hscroll[1] as usize; 202 | let hscroll_b = ((hscroll[2] as usize) << 8) + hscroll[3] as usize; 203 | (hscroll_a, hscroll_b) 204 | } 205 | 206 | pub fn vcolumns_mode(&self) -> bool { 207 | self.registers[0xB] & 4 != 0 208 | } 209 | 210 | pub fn autoinc(&self) -> u32 { 211 | self.registers[15] as _ 212 | } 213 | 214 | pub fn read(&self, mut address: u32) -> u32 { 215 | address &= 0x1F; 216 | 217 | if (0x4..=0x7).contains(&address) { 218 | return self.status; 219 | } 220 | 221 | println!("TODO: vdp read {:X}", address); 222 | 0 223 | } 224 | 225 | pub fn write(mem: &mut Mem, mut address: u32, value: u32) { 226 | address &= 0x1F; 227 | if address < 0x4 { 228 | mem.vdp.write_data_port(value); 229 | } else if address < 0x8 { 230 | VDP::write_control_port(mem, value); 231 | } else { 232 | todo!("vdp write {:X} {:X}", address, value); 233 | } 234 | } 235 | 236 | fn write_data(&mut self, target: VDPType, value: u32) { 237 | match target { 238 | VDPType::VRAM => { 239 | let addr = self.control_address as usize; 240 | self.VRAM[addr] = ((value >> 8) & 0xff) as _; 241 | self.VRAM[addr + 1] = (value & 0xff) as _; 242 | }, 243 | VDPType::CRAM => { 244 | let address = ((self.control_address & 0x7f) >> 1) as usize; 245 | self.CRAM[address] = value as _; 246 | self.cram_rgb[address] = cram_to_rgb(value as _); 247 | }, 248 | VDPType::VSRAM => { 249 | self.VSRAM[((self.control_address & 0x7f) >> 1) as usize] = value as _; 250 | }, 251 | } 252 | } 253 | 254 | fn write_data_port(&mut self, value: u32) { 255 | if self.control_code & 1 == 1 { 256 | self.write_data(VDPType::from(self.control_code & 0xE), value); 257 | } 258 | self.control_address = (self.control_address + self.autoinc()) & 0xFFFF; 259 | self.control_pending = false; 260 | 261 | if self.dma_pending { 262 | self.dma_pending = false; 263 | for _ in 0..self.dma_length() { 264 | self.VRAM[self.control_address as usize] = (value >> 8) as _; 265 | self.control_address += self.autoinc(); 266 | self.control_address &= 0xFFFF; 267 | } 268 | } 269 | } 270 | 271 | fn write_control_port(mem: &mut Mem, value: u32) { 272 | if mem.vdp.control_pending { 273 | mem.vdp.control_code = (mem.vdp.control_code & 3) | ((value >> 2) & 0x3c); 274 | mem.vdp.control_address = (mem.vdp.control_address & 0x3fff) | ((value & 3) << 14); 275 | mem.vdp.control_pending = false; 276 | 277 | if mem.vdp.control_code & 0x20 > 0 && mem.vdp.registers[1] & 0x10 > 0 { 278 | if (mem.vdp.registers[23] >> 6) == 2 && (mem.vdp.control_code & 7) == 1 { 279 | mem.vdp.dma_pending = true; 280 | } else if (mem.vdp.registers[23] as u32 >> 6) == 3 { 281 | todo!("DMA copy"); 282 | } else { 283 | let mut source = 284 | ((mem.vdp.registers[21] as u32) << 1) 285 | | ((mem.vdp.registers[22] as u32) << 9) 286 | | ((mem.vdp.registers[23] as u32) << 17); 287 | 288 | for _ in 0..mem.vdp.dma_length() { 289 | let word = mem.read_u16(source); 290 | source += 2; 291 | mem.vdp.write_data(VDPType::from(mem.vdp.control_code & 0x7), word); 292 | mem.vdp.control_address += mem.vdp.autoinc(); 293 | mem.vdp.control_address &= 0xFFFF; 294 | } 295 | 296 | } 297 | } 298 | } else { 299 | if value & 0xc000 == 0x8000 { 300 | let (register, value) = ((value >> 8) & 0x1F, value & 0xFF); 301 | if mem.vdp.registers[1] & 4 > 0 || register <= 10 { 302 | mem.vdp.registers[register as usize] = value as u8; 303 | } 304 | mem.vdp.control_code = 0; 305 | } else { 306 | mem.vdp.control_code = (mem.vdp.control_code & 0x3c) | ((value >> 14) & 3); 307 | mem.vdp.control_address = (mem.vdp.control_address & 0xc000) | (value & 0x3fff); 308 | mem.vdp.control_pending = true; 309 | } 310 | } 311 | } 312 | 313 | } 314 | -------------------------------------------------------------------------------- /emu/src/z80.rs: -------------------------------------------------------------------------------- 1 | use z80::{Z80 as Zilog80, Z80_io}; 2 | 3 | pub struct Z80 { 4 | cpu: Zilog80, 5 | bus_ack: bool, 6 | reset: bool, 7 | } 8 | 9 | struct Z80Mem { 10 | ram: [u8; 0x2000], 11 | } 12 | 13 | impl Z80_io for Z80Mem { 14 | fn read_byte(&self, address: u16) -> u8 { 15 | self.ram[address as usize & 0x1FFF] 16 | } 17 | 18 | fn write_byte(&mut self, address: u16, value: u8) { 19 | self.ram[address as usize & 0x1FFF] = value as u8; 20 | } 21 | } 22 | 23 | impl Z80 { 24 | pub fn new() -> Self { 25 | Self { 26 | cpu: Zilog80::new(Z80Mem { 27 | ram: [0; 0x2000], 28 | }), 29 | bus_ack: false, 30 | reset: false, 31 | } 32 | } 33 | 34 | pub fn read_byte(&self, address: u16) -> u8 { 35 | self.cpu.io.read_byte(address) 36 | } 37 | 38 | pub fn write_byte(&mut self, address: u16, value: u8) { 39 | self.cpu.io.write_byte(address, value); 40 | } 41 | 42 | 43 | pub fn ctrl_read(&self, address: u32) -> u8 { 44 | if address & 0xFFFF == 0x1100 { 45 | !(self.reset && self.bus_ack) as u8 46 | } else { 47 | 0 48 | } 49 | } 50 | 51 | pub fn ctrl_write(&mut self, mut address: u32, value: u32) { 52 | address &= 0xFFFF; 53 | self.cpu.io.ram[address as usize & 0x1FFF] = value as u8; 54 | if address == 0x1100 { 55 | self.bus_ack = value == 1; 56 | } else if address == 0x1200 { 57 | self.reset = value == 1; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /frontend-minimal/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "frontend-minimal" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | 9 | [dependencies] 10 | emu = { path = "../emu" } 11 | wasm-bindgen = "*" 12 | js-sys="*" 13 | -------------------------------------------------------------------------------- /frontend-minimal/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eu 3 | script_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) 4 | cd "$script_path" 5 | 6 | FAST=false 7 | 8 | while test $# -gt 0; do 9 | case "$1" in 10 | -h|--help) 11 | echo "build_web.sh [--fast]" 12 | echo " --fast: skip optimization step" 13 | exit 0 14 | ;; 15 | --fast) 16 | shift 17 | FAST=true 18 | ;; 19 | *) 20 | break 21 | ;; 22 | esac 23 | done 24 | 25 | FOLDER_NAME=${PWD##*/} 26 | CRATE_NAME=$FOLDER_NAME # assume crate name is the same as the folder name 27 | CRATE_NAME_SNAKE_CASE="${CRATE_NAME//-/_}" # for those who name crates with-kebab-case 28 | WASM_BIN="static/${CRATE_NAME_SNAKE_CASE}_bg.wasm" 29 | 30 | rm -f "${WASM_BIN}" 31 | 32 | echo "Building rust…" 33 | BUILD=release 34 | cargo build -p "${CRATE_NAME}" --release --lib --target wasm32-unknown-unknown 35 | 36 | # Get the output directory (in the workspace it is in another location) 37 | TARGET=$(cargo metadata --format-version=1 | jq --raw-output .target_directory) 38 | 39 | echo "Generating JS bindings for wasm…" 40 | TARGET_NAME="${CRATE_NAME_SNAKE_CASE}.wasm" 41 | WASM_PATH="${TARGET}/wasm32-unknown-unknown/${BUILD}/${TARGET_NAME}" 42 | wasm-bindgen "${WASM_PATH}" --out-dir static --target web --no-typescript 43 | 44 | if [[ "${FAST}" == false ]]; then 45 | echo "Optimizing wasm…" 46 | # to get wasm-opt: apt/brew/dnf install binaryen 47 | # https://github.com/WebAssembly/binaryen/releases 48 | wasm-opt "${WASM_BIN}" -O2 --fast-math -o "${WASM_BIN}" # add -g to get debug symbols 49 | fi 50 | -------------------------------------------------------------------------------- /frontend-minimal/src/lib.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::prelude::*; 2 | use js_sys::Uint8Array; 3 | 4 | use emu::Megadrive; 5 | 6 | #[wasm_bindgen] 7 | pub struct MDEmu(Megadrive); 8 | 9 | #[wasm_bindgen] 10 | impl MDEmu { 11 | #[wasm_bindgen(constructor)] 12 | pub fn new(rom: Uint8Array) -> MDEmu { 13 | MDEmu(Megadrive::new(rom.to_vec())) 14 | } 15 | 16 | pub fn render(&mut self) -> u64 { 17 | self.0.render() 18 | } 19 | 20 | pub fn screen(&self) -> *const u8 { 21 | self.0.gfx.screen.as_ptr() 22 | } 23 | 24 | pub fn gamepad_p1(&mut self, value: usize) { 25 | self.0.core.mem.io.gamepad[0].set(value) 26 | } 27 | 28 | pub fn change_rom(&mut self, rom: Uint8Array) { 29 | self.0 = Megadrive::new(rom.to_vec()) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /frontend-minimal/static/app.js: -------------------------------------------------------------------------------- 1 | import init, { MDEmu as Megadrive } from './frontend_minimal.js'; 2 | import gamepad from './gamepad.js'; 3 | 4 | const canvas = document.querySelector('canvas'); 5 | const ctx = canvas.getContext('2d', { alpha: false }); 6 | const img = ctx.createImageData(320, 240); 7 | 8 | (async () => { 9 | const instance = await init(); 10 | 11 | const savedRom = window.localStorage.getItem('ROM'); 12 | const rom = savedRom?.length ? Uint8Array.from(JSON.parse(savedRom)) : new Uint8Array(0); 13 | 14 | const emu = new Megadrive(rom); 15 | 16 | function draw() { 17 | const buffer = new Uint8ClampedArray( 18 | instance.memory.buffer, emu.screen(), 320 * 240 * 3 19 | ); 20 | for (let i = 0; i < 320*240; i++) { 21 | const bufferIndex = i * 3; 22 | const imgIndex = i * 4; 23 | img.data[imgIndex+0] = buffer[bufferIndex+0]; 24 | img.data[imgIndex+1] = buffer[bufferIndex+1]; 25 | img.data[imgIndex+2] = buffer[bufferIndex+2]; 26 | img.data[imgIndex+3] = 255; 27 | } 28 | 29 | ctx.putImageData(img, 0, 0); 30 | 31 | // ctx.putImageData(new ImageData(buffer, 320), 0, 0); 32 | } 33 | 34 | const frameCountEl = document.querySelector('.frameCount'); 35 | const frames = []; 36 | (function loop() { 37 | requestAnimationFrame(loop); 38 | if (document.visibilityState !== 'hidden') { 39 | emu.gamepad_p1(gamepad()); 40 | 41 | const frameCount = emu.render(); 42 | if (frameCount > 0) draw(); 43 | 44 | 45 | frames.push(frameCount); 46 | if (frames.length > 60) frames.shift(); 47 | frameCountEl.textContent = `frames this frame: ${frameCount}\navg frames: ${(frames.reduce((a, b) => a + Number(b), 0) / frames.length).toFixed(2)}`; 48 | } 49 | })(); 50 | 51 | // change ROM 52 | (document.querySelector('#file')).addEventListener( 53 | 'change', 54 | (e) => { 55 | const reader = new FileReader(); 56 | reader.readAsArrayBuffer(e.target.files[0]); 57 | reader.onloadend = () => { 58 | const rom = new Uint8Array(reader.result); 59 | emu.change_rom(rom); 60 | window.localStorage.setItem('ROM', JSON.stringify([...rom])); 61 | }; 62 | e.preventDefault(); 63 | }, 64 | ); 65 | })(); 66 | -------------------------------------------------------------------------------- /frontend-minimal/static/frontend_minimal.js: -------------------------------------------------------------------------------- 1 | let wasm; 2 | 3 | const heap = new Array(128).fill(undefined); 4 | 5 | heap.push(undefined, null, true, false); 6 | 7 | function getObject(idx) { return heap[idx]; } 8 | 9 | let heap_next = heap.length; 10 | 11 | function dropObject(idx) { 12 | if (idx < 132) return; 13 | heap[idx] = heap_next; 14 | heap_next = idx; 15 | } 16 | 17 | function takeObject(idx) { 18 | const ret = getObject(idx); 19 | dropObject(idx); 20 | return ret; 21 | } 22 | 23 | const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); 24 | 25 | cachedTextDecoder.decode(); 26 | 27 | let cachedUint8Memory0 = null; 28 | 29 | function getUint8Memory0() { 30 | if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { 31 | cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); 32 | } 33 | return cachedUint8Memory0; 34 | } 35 | 36 | function getStringFromWasm0(ptr, len) { 37 | return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); 38 | } 39 | 40 | function addHeapObject(obj) { 41 | if (heap_next === heap.length) heap.push(heap.length + 1); 42 | const idx = heap_next; 43 | heap_next = heap[idx]; 44 | 45 | heap[idx] = obj; 46 | return idx; 47 | } 48 | 49 | function debugString(val) { 50 | // primitive types 51 | const type = typeof val; 52 | if (type == 'number' || type == 'boolean' || val == null) { 53 | return `${val}`; 54 | } 55 | if (type == 'string') { 56 | return `"${val}"`; 57 | } 58 | if (type == 'symbol') { 59 | const description = val.description; 60 | if (description == null) { 61 | return 'Symbol'; 62 | } else { 63 | return `Symbol(${description})`; 64 | } 65 | } 66 | if (type == 'function') { 67 | const name = val.name; 68 | if (typeof name == 'string' && name.length > 0) { 69 | return `Function(${name})`; 70 | } else { 71 | return 'Function'; 72 | } 73 | } 74 | // objects 75 | if (Array.isArray(val)) { 76 | const length = val.length; 77 | let debug = '['; 78 | if (length > 0) { 79 | debug += debugString(val[0]); 80 | } 81 | for(let i = 1; i < length; i++) { 82 | debug += ', ' + debugString(val[i]); 83 | } 84 | debug += ']'; 85 | return debug; 86 | } 87 | // Test for built-in 88 | const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); 89 | let className; 90 | if (builtInMatches.length > 1) { 91 | className = builtInMatches[1]; 92 | } else { 93 | // Failed to match the standard '[object ClassName]' 94 | return toString.call(val); 95 | } 96 | if (className == 'Object') { 97 | // we're a user defined class or Object 98 | // JSON.stringify avoids problems with cycles, and is generally much 99 | // easier than looping through ownProperties of `val`. 100 | try { 101 | return 'Object(' + JSON.stringify(val) + ')'; 102 | } catch (_) { 103 | return 'Object'; 104 | } 105 | } 106 | // errors 107 | if (val instanceof Error) { 108 | return `${val.name}: ${val.message}\n${val.stack}`; 109 | } 110 | // TODO we could test for more things here, like `Set`s and `Map`s. 111 | return className; 112 | } 113 | 114 | let WASM_VECTOR_LEN = 0; 115 | 116 | const cachedTextEncoder = new TextEncoder('utf-8'); 117 | 118 | const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' 119 | ? function (arg, view) { 120 | return cachedTextEncoder.encodeInto(arg, view); 121 | } 122 | : function (arg, view) { 123 | const buf = cachedTextEncoder.encode(arg); 124 | view.set(buf); 125 | return { 126 | read: arg.length, 127 | written: buf.length 128 | }; 129 | }); 130 | 131 | function passStringToWasm0(arg, malloc, realloc) { 132 | 133 | if (realloc === undefined) { 134 | const buf = cachedTextEncoder.encode(arg); 135 | const ptr = malloc(buf.length); 136 | getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf); 137 | WASM_VECTOR_LEN = buf.length; 138 | return ptr; 139 | } 140 | 141 | let len = arg.length; 142 | let ptr = malloc(len); 143 | 144 | const mem = getUint8Memory0(); 145 | 146 | let offset = 0; 147 | 148 | for (; offset < len; offset++) { 149 | const code = arg.charCodeAt(offset); 150 | if (code > 0x7F) break; 151 | mem[ptr + offset] = code; 152 | } 153 | 154 | if (offset !== len) { 155 | if (offset !== 0) { 156 | arg = arg.slice(offset); 157 | } 158 | ptr = realloc(ptr, len, len = offset + arg.length * 3); 159 | const view = getUint8Memory0().subarray(ptr + offset, ptr + len); 160 | const ret = encodeString(arg, view); 161 | 162 | offset += ret.written; 163 | } 164 | 165 | WASM_VECTOR_LEN = offset; 166 | return ptr; 167 | } 168 | 169 | let cachedInt32Memory0 = null; 170 | 171 | function getInt32Memory0() { 172 | if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) { 173 | cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); 174 | } 175 | return cachedInt32Memory0; 176 | } 177 | 178 | function handleError(f, args) { 179 | try { 180 | return f.apply(this, args); 181 | } catch (e) { 182 | wasm.__wbindgen_exn_store(addHeapObject(e)); 183 | } 184 | } 185 | /** 186 | */ 187 | export class MDEmu { 188 | 189 | static __wrap(ptr) { 190 | const obj = Object.create(MDEmu.prototype); 191 | obj.ptr = ptr; 192 | 193 | return obj; 194 | } 195 | 196 | __destroy_into_raw() { 197 | const ptr = this.ptr; 198 | this.ptr = 0; 199 | 200 | return ptr; 201 | } 202 | 203 | free() { 204 | const ptr = this.__destroy_into_raw(); 205 | wasm.__wbg_mdemu_free(ptr); 206 | } 207 | /** 208 | * @param {Uint8Array} rom 209 | */ 210 | constructor(rom) { 211 | const ret = wasm.mdemu_new(addHeapObject(rom)); 212 | return MDEmu.__wrap(ret); 213 | } 214 | /** 215 | * @returns {bigint} 216 | */ 217 | render() { 218 | const ret = wasm.mdemu_render(this.ptr); 219 | return BigInt.asUintN(64, ret); 220 | } 221 | /** 222 | * @returns {number} 223 | */ 224 | screen() { 225 | const ret = wasm.mdemu_screen(this.ptr); 226 | return ret; 227 | } 228 | /** 229 | * @param {number} value 230 | */ 231 | gamepad_p1(value) { 232 | wasm.mdemu_gamepad_p1(this.ptr, value); 233 | } 234 | /** 235 | * @param {Uint8Array} rom 236 | */ 237 | change_rom(rom) { 238 | wasm.mdemu_change_rom(this.ptr, addHeapObject(rom)); 239 | } 240 | } 241 | 242 | async function load(module, imports) { 243 | if (typeof Response === 'function' && module instanceof Response) { 244 | if (typeof WebAssembly.instantiateStreaming === 'function') { 245 | try { 246 | return await WebAssembly.instantiateStreaming(module, imports); 247 | 248 | } catch (e) { 249 | if (module.headers.get('Content-Type') != 'application/wasm') { 250 | console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); 251 | 252 | } else { 253 | throw e; 254 | } 255 | } 256 | } 257 | 258 | const bytes = await module.arrayBuffer(); 259 | return await WebAssembly.instantiate(bytes, imports); 260 | 261 | } else { 262 | const instance = await WebAssembly.instantiate(module, imports); 263 | 264 | if (instance instanceof WebAssembly.Instance) { 265 | return { instance, module }; 266 | 267 | } else { 268 | return instance; 269 | } 270 | } 271 | } 272 | 273 | function getImports() { 274 | const imports = {}; 275 | imports.wbg = {}; 276 | imports.wbg.__wbindgen_object_drop_ref = function(arg0) { 277 | takeObject(arg0); 278 | }; 279 | imports.wbg.__wbindgen_string_new = function(arg0, arg1) { 280 | const ret = getStringFromWasm0(arg0, arg1); 281 | return addHeapObject(ret); 282 | }; 283 | imports.wbg.__wbg_now_8172cd917e5eda6b = function(arg0) { 284 | const ret = getObject(arg0).now(); 285 | return ret; 286 | }; 287 | imports.wbg.__wbg_newnoargs_b5b063fc6c2f0376 = function(arg0, arg1) { 288 | const ret = new Function(getStringFromWasm0(arg0, arg1)); 289 | return addHeapObject(ret); 290 | }; 291 | imports.wbg.__wbg_get_765201544a2b6869 = function() { return handleError(function (arg0, arg1) { 292 | const ret = Reflect.get(getObject(arg0), getObject(arg1)); 293 | return addHeapObject(ret); 294 | }, arguments) }; 295 | imports.wbg.__wbg_call_97ae9d8645dc388b = function() { return handleError(function (arg0, arg1) { 296 | const ret = getObject(arg0).call(getObject(arg1)); 297 | return addHeapObject(ret); 298 | }, arguments) }; 299 | imports.wbg.__wbindgen_object_clone_ref = function(arg0) { 300 | const ret = getObject(arg0); 301 | return addHeapObject(ret); 302 | }; 303 | imports.wbg.__wbg_self_6d479506f72c6a71 = function() { return handleError(function () { 304 | const ret = self.self; 305 | return addHeapObject(ret); 306 | }, arguments) }; 307 | imports.wbg.__wbg_window_f2557cc78490aceb = function() { return handleError(function () { 308 | const ret = window.window; 309 | return addHeapObject(ret); 310 | }, arguments) }; 311 | imports.wbg.__wbg_globalThis_7f206bda628d5286 = function() { return handleError(function () { 312 | const ret = globalThis.globalThis; 313 | return addHeapObject(ret); 314 | }, arguments) }; 315 | imports.wbg.__wbg_global_ba75c50d1cf384f4 = function() { return handleError(function () { 316 | const ret = global.global; 317 | return addHeapObject(ret); 318 | }, arguments) }; 319 | imports.wbg.__wbindgen_is_undefined = function(arg0) { 320 | const ret = getObject(arg0) === undefined; 321 | return ret; 322 | }; 323 | imports.wbg.__wbg_buffer_3f3d764d4747d564 = function(arg0) { 324 | const ret = getObject(arg0).buffer; 325 | return addHeapObject(ret); 326 | }; 327 | imports.wbg.__wbg_new_8c3f0052272a457a = function(arg0) { 328 | const ret = new Uint8Array(getObject(arg0)); 329 | return addHeapObject(ret); 330 | }; 331 | imports.wbg.__wbg_set_83db9690f9353e79 = function(arg0, arg1, arg2) { 332 | getObject(arg0).set(getObject(arg1), arg2 >>> 0); 333 | }; 334 | imports.wbg.__wbg_length_9e1ae1900cb0fbd5 = function(arg0) { 335 | const ret = getObject(arg0).length; 336 | return ret; 337 | }; 338 | imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { 339 | const ret = debugString(getObject(arg1)); 340 | const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); 341 | const len0 = WASM_VECTOR_LEN; 342 | getInt32Memory0()[arg0 / 4 + 1] = len0; 343 | getInt32Memory0()[arg0 / 4 + 0] = ptr0; 344 | }; 345 | imports.wbg.__wbindgen_throw = function(arg0, arg1) { 346 | throw new Error(getStringFromWasm0(arg0, arg1)); 347 | }; 348 | imports.wbg.__wbindgen_memory = function() { 349 | const ret = wasm.memory; 350 | return addHeapObject(ret); 351 | }; 352 | 353 | return imports; 354 | } 355 | 356 | function initMemory(imports, maybe_memory) { 357 | 358 | } 359 | 360 | function finalizeInit(instance, module) { 361 | wasm = instance.exports; 362 | init.__wbindgen_wasm_module = module; 363 | cachedInt32Memory0 = null; 364 | cachedUint8Memory0 = null; 365 | 366 | 367 | return wasm; 368 | } 369 | 370 | function initSync(module) { 371 | const imports = getImports(); 372 | 373 | initMemory(imports); 374 | 375 | if (!(module instanceof WebAssembly.Module)) { 376 | module = new WebAssembly.Module(module); 377 | } 378 | 379 | const instance = new WebAssembly.Instance(module, imports); 380 | 381 | return finalizeInit(instance, module); 382 | } 383 | 384 | async function init(input) { 385 | if (typeof input === 'undefined') { 386 | input = new URL('frontend_minimal_bg.wasm', import.meta.url); 387 | } 388 | const imports = getImports(); 389 | 390 | if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) { 391 | input = fetch(input); 392 | } 393 | 394 | initMemory(imports); 395 | 396 | const { instance, module } = await load(await input, imports); 397 | 398 | return finalizeInit(instance, module); 399 | } 400 | 401 | export { initSync } 402 | export default init; 403 | -------------------------------------------------------------------------------- /frontend-minimal/static/frontend_minimal_bg.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirjavascript/trueLMAO/3bab516e577359cb8374a381dd803a651632fcad/frontend-minimal/static/frontend_minimal_bg.wasm -------------------------------------------------------------------------------- /frontend-minimal/static/gamepad.js: -------------------------------------------------------------------------------- 1 | const controls = new Set([]); 2 | 3 | window.addEventListener('blur', () => { controls.clear(); }); 4 | window.addEventListener('focus', () => { controls.clear(); }); 5 | 6 | const keymap = {}; 7 | 8 | [ 9 | [1 << 7, ['Enter', 'Shift']], // start 10 | [1 << 6, ['z', 'Z', ',', '<']], // A 11 | [1 << 5, ['c', 'C', '/', '?']], // C 12 | [1 << 4, ['x', 'X', '.', '>', ' ']], // B 13 | [1 << 3, ['ArrowRight', 'd', 'D']], // R 14 | [1 << 2, ['ArrowLeft', 'a', 'A']], // L 15 | [1 << 1, ['ArrowDown', 's', 'S']], // D 16 | [1, ['ArrowUp', 'w', 'W']], // U 17 | ].forEach(([value, keys]) => { 18 | keys.forEach(key => { keymap[key] = value; }); 19 | }); 20 | 21 | const html = document.documentElement; 22 | 23 | html.addEventListener('keydown', e => { 24 | if (e.key in keymap) { 25 | controls.add(e.key); 26 | e.preventDefault(); 27 | } 28 | }); 29 | html.addEventListener('keyup', e => { 30 | if (e.key in keymap) { 31 | controls.delete(e.key); 32 | e.preventDefault(); 33 | } 34 | }); 35 | 36 | export default function() { 37 | return [...controls].reduce((a, c) => a | keymap[c], 0); 38 | } 39 | -------------------------------------------------------------------------------- /frontend-minimal/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 14 | 58 | trueLMAO 59 | 60 | 61 | 62 |
63 | 64 | 67 |
68 | 69 |

70 |             example rom
71 |         
72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | static/* 3 | !static/index.html 4 | -------------------------------------------------------------------------------- /frontend/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ab_glyph" 7 | version = "0.2.15" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "24606928a235e73cdef55a0c909719cadd72fce573e5713d58cb2952d8f5794c" 10 | dependencies = [ 11 | "ab_glyph_rasterizer", 12 | "owned_ttf_parser", 13 | ] 14 | 15 | [[package]] 16 | name = "ab_glyph_rasterizer" 17 | version = "0.1.5" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "a13739d7177fbd22bb0ed28badfff9f372f8bef46c863db4e1c6248f6b223b6e" 20 | 21 | [[package]] 22 | name = "ahash" 23 | version = "0.7.6" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" 26 | dependencies = [ 27 | "getrandom", 28 | "once_cell", 29 | "serde", 30 | "version_check", 31 | ] 32 | 33 | [[package]] 34 | name = "android_glue" 35 | version = "0.2.3" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407" 38 | 39 | [[package]] 40 | name = "ansi_term" 41 | version = "0.12.1" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 44 | dependencies = [ 45 | "winapi", 46 | ] 47 | 48 | [[package]] 49 | name = "arboard" 50 | version = "2.1.0" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "6045ca509e4abacde2b884ac4618a51d0c017b5d85a3ee84a7226eb33b3154a9" 53 | dependencies = [ 54 | "clipboard-win", 55 | "log", 56 | "objc", 57 | "objc-foundation", 58 | "objc_id", 59 | "once_cell", 60 | "parking_lot 0.12.0", 61 | "thiserror", 62 | "winapi", 63 | "x11rb", 64 | ] 65 | 66 | [[package]] 67 | name = "atomic_refcell" 68 | version = "0.1.8" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "73b5e5f48b927f04e952dedc932f31995a65a0bf65ec971c74436e51bf6e970d" 71 | 72 | [[package]] 73 | name = "autocfg" 74 | version = "1.1.0" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 77 | 78 | [[package]] 79 | name = "base64" 80 | version = "0.13.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 83 | 84 | [[package]] 85 | name = "bitflags" 86 | version = "1.3.2" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 89 | 90 | [[package]] 91 | name = "block" 92 | version = "0.1.6" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" 95 | 96 | [[package]] 97 | name = "bumpalo" 98 | version = "3.9.1" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" 101 | 102 | [[package]] 103 | name = "bytemuck" 104 | version = "1.9.1" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "cdead85bdec19c194affaeeb670c0e41fe23de31459efd1c174d049269cf02cc" 107 | dependencies = [ 108 | "bytemuck_derive", 109 | ] 110 | 111 | [[package]] 112 | name = "bytemuck_derive" 113 | version = "1.1.0" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "562e382481975bc61d11275ac5e62a19abd00b0547d99516a415336f183dcd0e" 116 | dependencies = [ 117 | "proc-macro2", 118 | "quote", 119 | "syn", 120 | ] 121 | 122 | [[package]] 123 | name = "bytes" 124 | version = "1.1.0" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 127 | 128 | [[package]] 129 | name = "calloop" 130 | version = "0.9.3" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "bf2eec61efe56aa1e813f5126959296933cf0700030e4314786c48779a66ab82" 133 | dependencies = [ 134 | "log", 135 | "nix", 136 | ] 137 | 138 | [[package]] 139 | name = "cc" 140 | version = "1.0.73" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 143 | 144 | [[package]] 145 | name = "cesu8" 146 | version = "1.1.0" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" 149 | 150 | [[package]] 151 | name = "cfg-if" 152 | version = "0.1.10" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 155 | 156 | [[package]] 157 | name = "cfg-if" 158 | version = "1.0.0" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 161 | 162 | [[package]] 163 | name = "cgl" 164 | version = "0.3.2" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" 167 | dependencies = [ 168 | "libc", 169 | ] 170 | 171 | [[package]] 172 | name = "clipboard-win" 173 | version = "4.4.1" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "2f3e1238132dc01f081e1cbb9dace14e5ef4c3a51ee244bd982275fb514605db" 176 | dependencies = [ 177 | "error-code", 178 | "str-buf", 179 | "winapi", 180 | ] 181 | 182 | [[package]] 183 | name = "cocoa" 184 | version = "0.24.0" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" 187 | dependencies = [ 188 | "bitflags", 189 | "block", 190 | "cocoa-foundation", 191 | "core-foundation 0.9.3", 192 | "core-graphics 0.22.3", 193 | "foreign-types", 194 | "libc", 195 | "objc", 196 | ] 197 | 198 | [[package]] 199 | name = "cocoa-foundation" 200 | version = "0.1.0" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" 203 | dependencies = [ 204 | "bitflags", 205 | "block", 206 | "core-foundation 0.9.3", 207 | "core-graphics-types", 208 | "foreign-types", 209 | "libc", 210 | "objc", 211 | ] 212 | 213 | [[package]] 214 | name = "combine" 215 | version = "4.6.4" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "2a604e93b79d1808327a6fca85a6f2d69de66461e7620f5a4cbf5fb4d1d7c948" 218 | dependencies = [ 219 | "bytes", 220 | "memchr", 221 | ] 222 | 223 | [[package]] 224 | name = "console_error_panic_hook" 225 | version = "0.1.7" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" 228 | dependencies = [ 229 | "cfg-if 1.0.0", 230 | "wasm-bindgen", 231 | ] 232 | 233 | [[package]] 234 | name = "core-foundation" 235 | version = "0.7.0" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" 238 | dependencies = [ 239 | "core-foundation-sys 0.7.0", 240 | "libc", 241 | ] 242 | 243 | [[package]] 244 | name = "core-foundation" 245 | version = "0.9.3" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 248 | dependencies = [ 249 | "core-foundation-sys 0.8.3", 250 | "libc", 251 | ] 252 | 253 | [[package]] 254 | name = "core-foundation-sys" 255 | version = "0.7.0" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" 258 | 259 | [[package]] 260 | name = "core-foundation-sys" 261 | version = "0.8.3" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 264 | 265 | [[package]] 266 | name = "core-graphics" 267 | version = "0.19.2" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" 270 | dependencies = [ 271 | "bitflags", 272 | "core-foundation 0.7.0", 273 | "foreign-types", 274 | "libc", 275 | ] 276 | 277 | [[package]] 278 | name = "core-graphics" 279 | version = "0.22.3" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" 282 | dependencies = [ 283 | "bitflags", 284 | "core-foundation 0.9.3", 285 | "core-graphics-types", 286 | "foreign-types", 287 | "libc", 288 | ] 289 | 290 | [[package]] 291 | name = "core-graphics-types" 292 | version = "0.1.1" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" 295 | dependencies = [ 296 | "bitflags", 297 | "core-foundation 0.9.3", 298 | "foreign-types", 299 | "libc", 300 | ] 301 | 302 | [[package]] 303 | name = "core-video-sys" 304 | version = "0.1.4" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" 307 | dependencies = [ 308 | "cfg-if 0.1.10", 309 | "core-foundation-sys 0.7.0", 310 | "core-graphics 0.19.2", 311 | "libc", 312 | "objc", 313 | ] 314 | 315 | [[package]] 316 | name = "cty" 317 | version = "0.2.2" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" 320 | 321 | [[package]] 322 | name = "darling" 323 | version = "0.13.4" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" 326 | dependencies = [ 327 | "darling_core", 328 | "darling_macro", 329 | ] 330 | 331 | [[package]] 332 | name = "darling_core" 333 | version = "0.13.4" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" 336 | dependencies = [ 337 | "fnv", 338 | "ident_case", 339 | "proc-macro2", 340 | "quote", 341 | "strsim", 342 | "syn", 343 | ] 344 | 345 | [[package]] 346 | name = "darling_macro" 347 | version = "0.13.4" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" 350 | dependencies = [ 351 | "darling_core", 352 | "quote", 353 | "syn", 354 | ] 355 | 356 | [[package]] 357 | name = "directories-next" 358 | version = "2.0.0" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" 361 | dependencies = [ 362 | "cfg-if 1.0.0", 363 | "dirs-sys-next", 364 | ] 365 | 366 | [[package]] 367 | name = "dirs-sys-next" 368 | version = "0.1.2" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 371 | dependencies = [ 372 | "libc", 373 | "redox_users", 374 | "winapi", 375 | ] 376 | 377 | [[package]] 378 | name = "dispatch" 379 | version = "0.2.0" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" 382 | 383 | [[package]] 384 | name = "dlib" 385 | version = "0.5.0" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" 388 | dependencies = [ 389 | "libloading", 390 | ] 391 | 392 | [[package]] 393 | name = "downcast-rs" 394 | version = "1.2.0" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" 397 | 398 | [[package]] 399 | name = "eframe" 400 | version = "0.18.0" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "53fa97a8188c36261ea162e625dbb23599f67b60777b462b834fe38161b81dce" 403 | dependencies = [ 404 | "bytemuck", 405 | "directories-next", 406 | "egui", 407 | "egui-winit", 408 | "egui_glow", 409 | "glow", 410 | "glutin", 411 | "js-sys", 412 | "percent-encoding", 413 | "ron", 414 | "serde", 415 | "tracing", 416 | "wasm-bindgen", 417 | "wasm-bindgen-futures", 418 | "web-sys", 419 | "winit", 420 | ] 421 | 422 | [[package]] 423 | name = "frontend" 424 | version = "0.1.0" 425 | dependencies = [ 426 | "console_error_panic_hook", 427 | "eframe", 428 | "egui", 429 | "serde", 430 | "tracing-subscriber", 431 | "tracing-wasm", 432 | ] 433 | 434 | [[package]] 435 | name = "egui" 436 | version = "0.18.0" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "6c4be5abc5b868ade7db0caf8e96f2c3ddccb243f7e93d382cf5d0e43f85ae86" 439 | dependencies = [ 440 | "ahash", 441 | "epaint", 442 | "nohash-hasher", 443 | "ron", 444 | "serde", 445 | "tracing", 446 | ] 447 | 448 | [[package]] 449 | name = "egui-winit" 450 | version = "0.18.0" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "b040afd583fd95a9b9578d4399214a13d948ed26bc0ff7cc0104502501f34e68" 453 | dependencies = [ 454 | "arboard", 455 | "egui", 456 | "instant", 457 | "serde", 458 | "tracing", 459 | "webbrowser", 460 | "winit", 461 | ] 462 | 463 | [[package]] 464 | name = "egui_glow" 465 | version = "0.18.0" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "54cbe18dacf32b29a015d1d2eaa0ee803f1a5468def004f956864a4b89e7d1e5" 468 | dependencies = [ 469 | "bytemuck", 470 | "egui", 471 | "glow", 472 | "memoffset", 473 | "tracing", 474 | "wasm-bindgen", 475 | "web-sys", 476 | ] 477 | 478 | [[package]] 479 | name = "emath" 480 | version = "0.18.0" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "c223f58c7e38abe1770f367b969f1b3fbd4704b67666bcb65dbb1adb0980ba72" 483 | dependencies = [ 484 | "bytemuck", 485 | "serde", 486 | ] 487 | 488 | [[package]] 489 | name = "epaint" 490 | version = "0.18.0" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "4dfd8ef097bd349762fcf1c7a5ef2f0e686f9919253b7a298827d6ff9d401dee" 493 | dependencies = [ 494 | "ab_glyph", 495 | "ahash", 496 | "atomic_refcell", 497 | "bytemuck", 498 | "emath", 499 | "nohash-hasher", 500 | "parking_lot 0.12.0", 501 | "serde", 502 | ] 503 | 504 | [[package]] 505 | name = "error-code" 506 | version = "2.3.1" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" 509 | dependencies = [ 510 | "libc", 511 | "str-buf", 512 | ] 513 | 514 | [[package]] 515 | name = "fnv" 516 | version = "1.0.7" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 519 | 520 | [[package]] 521 | name = "foreign-types" 522 | version = "0.3.2" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 525 | dependencies = [ 526 | "foreign-types-shared", 527 | ] 528 | 529 | [[package]] 530 | name = "foreign-types-shared" 531 | version = "0.1.1" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 534 | 535 | [[package]] 536 | name = "form_urlencoded" 537 | version = "1.0.1" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 540 | dependencies = [ 541 | "matches", 542 | "percent-encoding", 543 | ] 544 | 545 | [[package]] 546 | name = "gethostname" 547 | version = "0.2.3" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" 550 | dependencies = [ 551 | "libc", 552 | "winapi", 553 | ] 554 | 555 | [[package]] 556 | name = "getrandom" 557 | version = "0.2.6" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" 560 | dependencies = [ 561 | "cfg-if 1.0.0", 562 | "libc", 563 | "wasi 0.10.2+wasi-snapshot-preview1", 564 | ] 565 | 566 | [[package]] 567 | name = "gl_generator" 568 | version = "0.14.0" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" 571 | dependencies = [ 572 | "khronos_api", 573 | "log", 574 | "xml-rs", 575 | ] 576 | 577 | [[package]] 578 | name = "glow" 579 | version = "0.11.2" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "d8bd5877156a19b8ac83a29b2306fe20537429d318f3ff0a1a2119f8d9c61919" 582 | dependencies = [ 583 | "js-sys", 584 | "slotmap", 585 | "wasm-bindgen", 586 | "web-sys", 587 | ] 588 | 589 | [[package]] 590 | name = "glutin" 591 | version = "0.28.0" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "00ea9dbe544bc8a657c4c4a798c2d16cd01b549820e47657297549d28371f6d2" 594 | dependencies = [ 595 | "android_glue", 596 | "cgl", 597 | "cocoa", 598 | "core-foundation 0.9.3", 599 | "glutin_egl_sys", 600 | "glutin_emscripten_sys", 601 | "glutin_gles2_sys", 602 | "glutin_glx_sys", 603 | "glutin_wgl_sys", 604 | "lazy_static", 605 | "libloading", 606 | "log", 607 | "objc", 608 | "osmesa-sys", 609 | "parking_lot 0.11.2", 610 | "wayland-client", 611 | "wayland-egl", 612 | "winapi", 613 | "winit", 614 | ] 615 | 616 | [[package]] 617 | name = "glutin_egl_sys" 618 | version = "0.1.5" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "2abb6aa55523480c4adc5a56bbaa249992e2dddb2fc63dc96e04a3355364c211" 621 | dependencies = [ 622 | "gl_generator", 623 | "winapi", 624 | ] 625 | 626 | [[package]] 627 | name = "glutin_emscripten_sys" 628 | version = "0.1.1" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "80de4146df76e8a6c32b03007bc764ff3249dcaeb4f675d68a06caf1bac363f1" 631 | 632 | [[package]] 633 | name = "glutin_gles2_sys" 634 | version = "0.1.5" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "e8094e708b730a7c8a1954f4f8a31880af00eb8a1c5b5bf85d28a0a3c6d69103" 637 | dependencies = [ 638 | "gl_generator", 639 | "objc", 640 | ] 641 | 642 | [[package]] 643 | name = "glutin_glx_sys" 644 | version = "0.1.7" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "7e393c8fc02b807459410429150e9c4faffdb312d59b8c038566173c81991351" 647 | dependencies = [ 648 | "gl_generator", 649 | "x11-dl", 650 | ] 651 | 652 | [[package]] 653 | name = "glutin_wgl_sys" 654 | version = "0.1.5" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "3da5951a1569dbab865c6f2a863efafff193a93caf05538d193e9e3816d21696" 657 | dependencies = [ 658 | "gl_generator", 659 | ] 660 | 661 | [[package]] 662 | name = "ident_case" 663 | version = "1.0.1" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 666 | 667 | [[package]] 668 | name = "idna" 669 | version = "0.2.3" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 672 | dependencies = [ 673 | "matches", 674 | "unicode-bidi", 675 | "unicode-normalization", 676 | ] 677 | 678 | [[package]] 679 | name = "instant" 680 | version = "0.1.12" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 683 | dependencies = [ 684 | "cfg-if 1.0.0", 685 | "js-sys", 686 | "wasm-bindgen", 687 | "web-sys", 688 | ] 689 | 690 | [[package]] 691 | name = "jni" 692 | version = "0.19.0" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" 695 | dependencies = [ 696 | "cesu8", 697 | "combine", 698 | "jni-sys", 699 | "log", 700 | "thiserror", 701 | "walkdir", 702 | ] 703 | 704 | [[package]] 705 | name = "jni-sys" 706 | version = "0.3.0" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" 709 | 710 | [[package]] 711 | name = "js-sys" 712 | version = "0.3.57" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" 715 | dependencies = [ 716 | "wasm-bindgen", 717 | ] 718 | 719 | [[package]] 720 | name = "khronos_api" 721 | version = "3.1.0" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" 724 | 725 | [[package]] 726 | name = "lazy_static" 727 | version = "1.4.0" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 730 | 731 | [[package]] 732 | name = "libc" 733 | version = "0.2.125" 734 | source = "registry+https://github.com/rust-lang/crates.io-index" 735 | checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" 736 | 737 | [[package]] 738 | name = "libloading" 739 | version = "0.7.3" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" 742 | dependencies = [ 743 | "cfg-if 1.0.0", 744 | "winapi", 745 | ] 746 | 747 | [[package]] 748 | name = "lock_api" 749 | version = "0.4.7" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" 752 | dependencies = [ 753 | "autocfg", 754 | "scopeguard", 755 | ] 756 | 757 | [[package]] 758 | name = "log" 759 | version = "0.4.16" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" 762 | dependencies = [ 763 | "cfg-if 1.0.0", 764 | ] 765 | 766 | [[package]] 767 | name = "malloc_buf" 768 | version = "0.0.6" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" 771 | dependencies = [ 772 | "libc", 773 | ] 774 | 775 | [[package]] 776 | name = "matches" 777 | version = "0.1.9" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 780 | 781 | [[package]] 782 | name = "memchr" 783 | version = "2.5.0" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 786 | 787 | [[package]] 788 | name = "memmap2" 789 | version = "0.3.1" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "00b6c2ebff6180198788f5db08d7ce3bc1d0b617176678831a7510825973e357" 792 | dependencies = [ 793 | "libc", 794 | ] 795 | 796 | [[package]] 797 | name = "memoffset" 798 | version = "0.6.5" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 801 | dependencies = [ 802 | "autocfg", 803 | ] 804 | 805 | [[package]] 806 | name = "minimal-lexical" 807 | version = "0.2.1" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 810 | 811 | [[package]] 812 | name = "mio" 813 | version = "0.8.2" 814 | source = "registry+https://github.com/rust-lang/crates.io-index" 815 | checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" 816 | dependencies = [ 817 | "libc", 818 | "log", 819 | "miow", 820 | "ntapi", 821 | "wasi 0.11.0+wasi-snapshot-preview1", 822 | "winapi", 823 | ] 824 | 825 | [[package]] 826 | name = "miow" 827 | version = "0.3.7" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 830 | dependencies = [ 831 | "winapi", 832 | ] 833 | 834 | [[package]] 835 | name = "ndk" 836 | version = "0.5.0" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "96d868f654c72e75f8687572699cdabe755f03effbb62542768e995d5b8d699d" 839 | dependencies = [ 840 | "bitflags", 841 | "jni-sys", 842 | "ndk-sys 0.2.2", 843 | "num_enum", 844 | "thiserror", 845 | ] 846 | 847 | [[package]] 848 | name = "ndk" 849 | version = "0.6.0" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" 852 | dependencies = [ 853 | "bitflags", 854 | "jni-sys", 855 | "ndk-sys 0.3.0", 856 | "num_enum", 857 | "thiserror", 858 | ] 859 | 860 | [[package]] 861 | name = "ndk-context" 862 | version = "0.1.1" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" 865 | 866 | [[package]] 867 | name = "ndk-glue" 868 | version = "0.5.2" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "c71bee8ea72d685477e28bd004cfe1bf99c754d688cd78cad139eae4089484d4" 871 | dependencies = [ 872 | "lazy_static", 873 | "libc", 874 | "log", 875 | "ndk 0.5.0", 876 | "ndk-context", 877 | "ndk-macro", 878 | "ndk-sys 0.2.2", 879 | ] 880 | 881 | [[package]] 882 | name = "ndk-glue" 883 | version = "0.6.2" 884 | source = "registry+https://github.com/rust-lang/crates.io-index" 885 | checksum = "0d0c4a7b83860226e6b4183edac21851f05d5a51756e97a1144b7f5a6b63e65f" 886 | dependencies = [ 887 | "lazy_static", 888 | "libc", 889 | "log", 890 | "ndk 0.6.0", 891 | "ndk-context", 892 | "ndk-macro", 893 | "ndk-sys 0.3.0", 894 | ] 895 | 896 | [[package]] 897 | name = "ndk-macro" 898 | version = "0.3.0" 899 | source = "registry+https://github.com/rust-lang/crates.io-index" 900 | checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" 901 | dependencies = [ 902 | "darling", 903 | "proc-macro-crate", 904 | "proc-macro2", 905 | "quote", 906 | "syn", 907 | ] 908 | 909 | [[package]] 910 | name = "ndk-sys" 911 | version = "0.2.2" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" 914 | 915 | [[package]] 916 | name = "ndk-sys" 917 | version = "0.3.0" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" 920 | dependencies = [ 921 | "jni-sys", 922 | ] 923 | 924 | [[package]] 925 | name = "nix" 926 | version = "0.22.3" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" 929 | dependencies = [ 930 | "bitflags", 931 | "cc", 932 | "cfg-if 1.0.0", 933 | "libc", 934 | "memoffset", 935 | ] 936 | 937 | [[package]] 938 | name = "nohash-hasher" 939 | version = "0.2.0" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" 942 | 943 | [[package]] 944 | name = "nom" 945 | version = "7.1.1" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" 948 | dependencies = [ 949 | "memchr", 950 | "minimal-lexical", 951 | ] 952 | 953 | [[package]] 954 | name = "ntapi" 955 | version = "0.3.7" 956 | source = "registry+https://github.com/rust-lang/crates.io-index" 957 | checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" 958 | dependencies = [ 959 | "winapi", 960 | ] 961 | 962 | [[package]] 963 | name = "num_enum" 964 | version = "0.5.7" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" 967 | dependencies = [ 968 | "num_enum_derive", 969 | ] 970 | 971 | [[package]] 972 | name = "num_enum_derive" 973 | version = "0.5.7" 974 | source = "registry+https://github.com/rust-lang/crates.io-index" 975 | checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" 976 | dependencies = [ 977 | "proc-macro-crate", 978 | "proc-macro2", 979 | "quote", 980 | "syn", 981 | ] 982 | 983 | [[package]] 984 | name = "objc" 985 | version = "0.2.7" 986 | source = "registry+https://github.com/rust-lang/crates.io-index" 987 | checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" 988 | dependencies = [ 989 | "malloc_buf", 990 | ] 991 | 992 | [[package]] 993 | name = "objc-foundation" 994 | version = "0.1.1" 995 | source = "registry+https://github.com/rust-lang/crates.io-index" 996 | checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" 997 | dependencies = [ 998 | "block", 999 | "objc", 1000 | "objc_id", 1001 | ] 1002 | 1003 | [[package]] 1004 | name = "objc_id" 1005 | version = "0.1.1" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" 1008 | dependencies = [ 1009 | "objc", 1010 | ] 1011 | 1012 | [[package]] 1013 | name = "once_cell" 1014 | version = "1.10.0" 1015 | source = "registry+https://github.com/rust-lang/crates.io-index" 1016 | checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" 1017 | 1018 | [[package]] 1019 | name = "osmesa-sys" 1020 | version = "0.1.2" 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" 1022 | checksum = "88cfece6e95d2e717e0872a7f53a8684712ad13822a7979bc760b9c77ec0013b" 1023 | dependencies = [ 1024 | "shared_library", 1025 | ] 1026 | 1027 | [[package]] 1028 | name = "owned_ttf_parser" 1029 | version = "0.15.0" 1030 | source = "registry+https://github.com/rust-lang/crates.io-index" 1031 | checksum = "4fb1e509cfe7a12db2a90bfa057dfcdbc55a347f5da677c506b53dd099cfec9d" 1032 | dependencies = [ 1033 | "ttf-parser", 1034 | ] 1035 | 1036 | [[package]] 1037 | name = "parking_lot" 1038 | version = "0.11.2" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" 1041 | dependencies = [ 1042 | "instant", 1043 | "lock_api", 1044 | "parking_lot_core 0.8.5", 1045 | ] 1046 | 1047 | [[package]] 1048 | name = "parking_lot" 1049 | version = "0.12.0" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" 1052 | dependencies = [ 1053 | "lock_api", 1054 | "parking_lot_core 0.9.3", 1055 | ] 1056 | 1057 | [[package]] 1058 | name = "parking_lot_core" 1059 | version = "0.8.5" 1060 | source = "registry+https://github.com/rust-lang/crates.io-index" 1061 | checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" 1062 | dependencies = [ 1063 | "cfg-if 1.0.0", 1064 | "instant", 1065 | "libc", 1066 | "redox_syscall", 1067 | "smallvec", 1068 | "winapi", 1069 | ] 1070 | 1071 | [[package]] 1072 | name = "parking_lot_core" 1073 | version = "0.9.3" 1074 | source = "registry+https://github.com/rust-lang/crates.io-index" 1075 | checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" 1076 | dependencies = [ 1077 | "cfg-if 1.0.0", 1078 | "libc", 1079 | "redox_syscall", 1080 | "smallvec", 1081 | "windows-sys", 1082 | ] 1083 | 1084 | [[package]] 1085 | name = "percent-encoding" 1086 | version = "2.1.0" 1087 | source = "registry+https://github.com/rust-lang/crates.io-index" 1088 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 1089 | 1090 | [[package]] 1091 | name = "pin-project-lite" 1092 | version = "0.2.9" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 1095 | 1096 | [[package]] 1097 | name = "pkg-config" 1098 | version = "0.3.25" 1099 | source = "registry+https://github.com/rust-lang/crates.io-index" 1100 | checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" 1101 | 1102 | [[package]] 1103 | name = "proc-macro-crate" 1104 | version = "1.1.3" 1105 | source = "registry+https://github.com/rust-lang/crates.io-index" 1106 | checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" 1107 | dependencies = [ 1108 | "thiserror", 1109 | "toml", 1110 | ] 1111 | 1112 | [[package]] 1113 | name = "proc-macro2" 1114 | version = "1.0.37" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" 1117 | dependencies = [ 1118 | "unicode-xid", 1119 | ] 1120 | 1121 | [[package]] 1122 | name = "quote" 1123 | version = "1.0.18" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" 1126 | dependencies = [ 1127 | "proc-macro2", 1128 | ] 1129 | 1130 | [[package]] 1131 | name = "raw-window-handle" 1132 | version = "0.4.3" 1133 | source = "registry+https://github.com/rust-lang/crates.io-index" 1134 | checksum = "b800beb9b6e7d2df1fe337c9e3d04e3af22a124460fb4c30fcc22c9117cefb41" 1135 | dependencies = [ 1136 | "cty", 1137 | ] 1138 | 1139 | [[package]] 1140 | name = "redox_syscall" 1141 | version = "0.2.13" 1142 | source = "registry+https://github.com/rust-lang/crates.io-index" 1143 | checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" 1144 | dependencies = [ 1145 | "bitflags", 1146 | ] 1147 | 1148 | [[package]] 1149 | name = "redox_users" 1150 | version = "0.4.3" 1151 | source = "registry+https://github.com/rust-lang/crates.io-index" 1152 | checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" 1153 | dependencies = [ 1154 | "getrandom", 1155 | "redox_syscall", 1156 | "thiserror", 1157 | ] 1158 | 1159 | [[package]] 1160 | name = "ron" 1161 | version = "0.7.0" 1162 | source = "registry+https://github.com/rust-lang/crates.io-index" 1163 | checksum = "1b861ecaade43ac97886a512b360d01d66be9f41f3c61088b42cedf92e03d678" 1164 | dependencies = [ 1165 | "base64", 1166 | "bitflags", 1167 | "serde", 1168 | ] 1169 | 1170 | [[package]] 1171 | name = "same-file" 1172 | version = "1.0.6" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1175 | dependencies = [ 1176 | "winapi-util", 1177 | ] 1178 | 1179 | [[package]] 1180 | name = "scoped-tls" 1181 | version = "1.0.0" 1182 | source = "registry+https://github.com/rust-lang/crates.io-index" 1183 | checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" 1184 | 1185 | [[package]] 1186 | name = "scopeguard" 1187 | version = "1.1.0" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1190 | 1191 | [[package]] 1192 | name = "serde" 1193 | version = "1.0.136" 1194 | source = "registry+https://github.com/rust-lang/crates.io-index" 1195 | checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" 1196 | dependencies = [ 1197 | "serde_derive", 1198 | ] 1199 | 1200 | [[package]] 1201 | name = "serde_derive" 1202 | version = "1.0.136" 1203 | source = "registry+https://github.com/rust-lang/crates.io-index" 1204 | checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" 1205 | dependencies = [ 1206 | "proc-macro2", 1207 | "quote", 1208 | "syn", 1209 | ] 1210 | 1211 | [[package]] 1212 | name = "sharded-slab" 1213 | version = "0.1.4" 1214 | source = "registry+https://github.com/rust-lang/crates.io-index" 1215 | checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" 1216 | dependencies = [ 1217 | "lazy_static", 1218 | ] 1219 | 1220 | [[package]] 1221 | name = "shared_library" 1222 | version = "0.1.9" 1223 | source = "registry+https://github.com/rust-lang/crates.io-index" 1224 | checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" 1225 | dependencies = [ 1226 | "lazy_static", 1227 | "libc", 1228 | ] 1229 | 1230 | [[package]] 1231 | name = "slotmap" 1232 | version = "1.0.6" 1233 | source = "registry+https://github.com/rust-lang/crates.io-index" 1234 | checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" 1235 | dependencies = [ 1236 | "version_check", 1237 | ] 1238 | 1239 | [[package]] 1240 | name = "smallvec" 1241 | version = "1.8.0" 1242 | source = "registry+https://github.com/rust-lang/crates.io-index" 1243 | checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" 1244 | 1245 | [[package]] 1246 | name = "smithay-client-toolkit" 1247 | version = "0.15.4" 1248 | source = "registry+https://github.com/rust-lang/crates.io-index" 1249 | checksum = "8a28f16a97fa0e8ce563b2774d1e732dd5d4025d2772c5dba0a41a0f90a29da3" 1250 | dependencies = [ 1251 | "bitflags", 1252 | "calloop", 1253 | "dlib", 1254 | "lazy_static", 1255 | "log", 1256 | "memmap2", 1257 | "nix", 1258 | "pkg-config", 1259 | "wayland-client", 1260 | "wayland-cursor", 1261 | "wayland-protocols", 1262 | ] 1263 | 1264 | [[package]] 1265 | name = "str-buf" 1266 | version = "1.0.5" 1267 | source = "registry+https://github.com/rust-lang/crates.io-index" 1268 | checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a" 1269 | 1270 | [[package]] 1271 | name = "strsim" 1272 | version = "0.10.0" 1273 | source = "registry+https://github.com/rust-lang/crates.io-index" 1274 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1275 | 1276 | [[package]] 1277 | name = "syn" 1278 | version = "1.0.92" 1279 | source = "registry+https://github.com/rust-lang/crates.io-index" 1280 | checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" 1281 | dependencies = [ 1282 | "proc-macro2", 1283 | "quote", 1284 | "unicode-xid", 1285 | ] 1286 | 1287 | [[package]] 1288 | name = "thiserror" 1289 | version = "1.0.30" 1290 | source = "registry+https://github.com/rust-lang/crates.io-index" 1291 | checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" 1292 | dependencies = [ 1293 | "thiserror-impl", 1294 | ] 1295 | 1296 | [[package]] 1297 | name = "thiserror-impl" 1298 | version = "1.0.30" 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" 1300 | checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" 1301 | dependencies = [ 1302 | "proc-macro2", 1303 | "quote", 1304 | "syn", 1305 | ] 1306 | 1307 | [[package]] 1308 | name = "thread_local" 1309 | version = "1.1.4" 1310 | source = "registry+https://github.com/rust-lang/crates.io-index" 1311 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 1312 | dependencies = [ 1313 | "once_cell", 1314 | ] 1315 | 1316 | [[package]] 1317 | name = "tinyvec" 1318 | version = "1.6.0" 1319 | source = "registry+https://github.com/rust-lang/crates.io-index" 1320 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1321 | dependencies = [ 1322 | "tinyvec_macros", 1323 | ] 1324 | 1325 | [[package]] 1326 | name = "tinyvec_macros" 1327 | version = "0.1.0" 1328 | source = "registry+https://github.com/rust-lang/crates.io-index" 1329 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1330 | 1331 | [[package]] 1332 | name = "toml" 1333 | version = "0.5.9" 1334 | source = "registry+https://github.com/rust-lang/crates.io-index" 1335 | checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" 1336 | dependencies = [ 1337 | "serde", 1338 | ] 1339 | 1340 | [[package]] 1341 | name = "tracing" 1342 | version = "0.1.34" 1343 | source = "registry+https://github.com/rust-lang/crates.io-index" 1344 | checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" 1345 | dependencies = [ 1346 | "cfg-if 1.0.0", 1347 | "pin-project-lite", 1348 | "tracing-attributes", 1349 | "tracing-core", 1350 | ] 1351 | 1352 | [[package]] 1353 | name = "tracing-attributes" 1354 | version = "0.1.21" 1355 | source = "registry+https://github.com/rust-lang/crates.io-index" 1356 | checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" 1357 | dependencies = [ 1358 | "proc-macro2", 1359 | "quote", 1360 | "syn", 1361 | ] 1362 | 1363 | [[package]] 1364 | name = "tracing-core" 1365 | version = "0.1.26" 1366 | source = "registry+https://github.com/rust-lang/crates.io-index" 1367 | checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" 1368 | dependencies = [ 1369 | "lazy_static", 1370 | "valuable", 1371 | ] 1372 | 1373 | [[package]] 1374 | name = "tracing-log" 1375 | version = "0.1.3" 1376 | source = "registry+https://github.com/rust-lang/crates.io-index" 1377 | checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" 1378 | dependencies = [ 1379 | "lazy_static", 1380 | "log", 1381 | "tracing-core", 1382 | ] 1383 | 1384 | [[package]] 1385 | name = "tracing-subscriber" 1386 | version = "0.3.11" 1387 | source = "registry+https://github.com/rust-lang/crates.io-index" 1388 | checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596" 1389 | dependencies = [ 1390 | "ansi_term", 1391 | "sharded-slab", 1392 | "smallvec", 1393 | "thread_local", 1394 | "tracing-core", 1395 | "tracing-log", 1396 | ] 1397 | 1398 | [[package]] 1399 | name = "tracing-wasm" 1400 | version = "0.2.1" 1401 | source = "registry+https://github.com/rust-lang/crates.io-index" 1402 | checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07" 1403 | dependencies = [ 1404 | "tracing", 1405 | "tracing-subscriber", 1406 | "wasm-bindgen", 1407 | ] 1408 | 1409 | [[package]] 1410 | name = "ttf-parser" 1411 | version = "0.15.0" 1412 | source = "registry+https://github.com/rust-lang/crates.io-index" 1413 | checksum = "c74c96594835e10fa545e2a51e8709f30b173a092bfd6036ef2cec53376244f3" 1414 | 1415 | [[package]] 1416 | name = "unicode-bidi" 1417 | version = "0.3.8" 1418 | source = "registry+https://github.com/rust-lang/crates.io-index" 1419 | checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" 1420 | 1421 | [[package]] 1422 | name = "unicode-normalization" 1423 | version = "0.1.19" 1424 | source = "registry+https://github.com/rust-lang/crates.io-index" 1425 | checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" 1426 | dependencies = [ 1427 | "tinyvec", 1428 | ] 1429 | 1430 | [[package]] 1431 | name = "unicode-xid" 1432 | version = "0.2.2" 1433 | source = "registry+https://github.com/rust-lang/crates.io-index" 1434 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 1435 | 1436 | [[package]] 1437 | name = "url" 1438 | version = "2.2.2" 1439 | source = "registry+https://github.com/rust-lang/crates.io-index" 1440 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 1441 | dependencies = [ 1442 | "form_urlencoded", 1443 | "idna", 1444 | "matches", 1445 | "percent-encoding", 1446 | ] 1447 | 1448 | [[package]] 1449 | name = "valuable" 1450 | version = "0.1.0" 1451 | source = "registry+https://github.com/rust-lang/crates.io-index" 1452 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1453 | 1454 | [[package]] 1455 | name = "version_check" 1456 | version = "0.9.4" 1457 | source = "registry+https://github.com/rust-lang/crates.io-index" 1458 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1459 | 1460 | [[package]] 1461 | name = "walkdir" 1462 | version = "2.3.2" 1463 | source = "registry+https://github.com/rust-lang/crates.io-index" 1464 | checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" 1465 | dependencies = [ 1466 | "same-file", 1467 | "winapi", 1468 | "winapi-util", 1469 | ] 1470 | 1471 | [[package]] 1472 | name = "wasi" 1473 | version = "0.10.2+wasi-snapshot-preview1" 1474 | source = "registry+https://github.com/rust-lang/crates.io-index" 1475 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 1476 | 1477 | [[package]] 1478 | name = "wasi" 1479 | version = "0.11.0+wasi-snapshot-preview1" 1480 | source = "registry+https://github.com/rust-lang/crates.io-index" 1481 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1482 | 1483 | [[package]] 1484 | name = "wasm-bindgen" 1485 | version = "0.2.80" 1486 | source = "registry+https://github.com/rust-lang/crates.io-index" 1487 | checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" 1488 | dependencies = [ 1489 | "cfg-if 1.0.0", 1490 | "wasm-bindgen-macro", 1491 | ] 1492 | 1493 | [[package]] 1494 | name = "wasm-bindgen-backend" 1495 | version = "0.2.80" 1496 | source = "registry+https://github.com/rust-lang/crates.io-index" 1497 | checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" 1498 | dependencies = [ 1499 | "bumpalo", 1500 | "lazy_static", 1501 | "log", 1502 | "proc-macro2", 1503 | "quote", 1504 | "syn", 1505 | "wasm-bindgen-shared", 1506 | ] 1507 | 1508 | [[package]] 1509 | name = "wasm-bindgen-futures" 1510 | version = "0.4.30" 1511 | source = "registry+https://github.com/rust-lang/crates.io-index" 1512 | checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" 1513 | dependencies = [ 1514 | "cfg-if 1.0.0", 1515 | "js-sys", 1516 | "wasm-bindgen", 1517 | "web-sys", 1518 | ] 1519 | 1520 | [[package]] 1521 | name = "wasm-bindgen-macro" 1522 | version = "0.2.80" 1523 | source = "registry+https://github.com/rust-lang/crates.io-index" 1524 | checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" 1525 | dependencies = [ 1526 | "quote", 1527 | "wasm-bindgen-macro-support", 1528 | ] 1529 | 1530 | [[package]] 1531 | name = "wasm-bindgen-macro-support" 1532 | version = "0.2.80" 1533 | source = "registry+https://github.com/rust-lang/crates.io-index" 1534 | checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" 1535 | dependencies = [ 1536 | "proc-macro2", 1537 | "quote", 1538 | "syn", 1539 | "wasm-bindgen-backend", 1540 | "wasm-bindgen-shared", 1541 | ] 1542 | 1543 | [[package]] 1544 | name = "wasm-bindgen-shared" 1545 | version = "0.2.80" 1546 | source = "registry+https://github.com/rust-lang/crates.io-index" 1547 | checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" 1548 | 1549 | [[package]] 1550 | name = "wayland-client" 1551 | version = "0.29.4" 1552 | source = "registry+https://github.com/rust-lang/crates.io-index" 1553 | checksum = "91223460e73257f697d9e23d401279123d36039a3f7a449e983f123292d4458f" 1554 | dependencies = [ 1555 | "bitflags", 1556 | "downcast-rs", 1557 | "libc", 1558 | "nix", 1559 | "scoped-tls", 1560 | "wayland-commons", 1561 | "wayland-scanner", 1562 | "wayland-sys", 1563 | ] 1564 | 1565 | [[package]] 1566 | name = "wayland-commons" 1567 | version = "0.29.4" 1568 | source = "registry+https://github.com/rust-lang/crates.io-index" 1569 | checksum = "94f6e5e340d7c13490eca867898c4cec5af56c27a5ffe5c80c6fc4708e22d33e" 1570 | dependencies = [ 1571 | "nix", 1572 | "once_cell", 1573 | "smallvec", 1574 | "wayland-sys", 1575 | ] 1576 | 1577 | [[package]] 1578 | name = "wayland-cursor" 1579 | version = "0.29.4" 1580 | source = "registry+https://github.com/rust-lang/crates.io-index" 1581 | checksum = "c52758f13d5e7861fc83d942d3d99bf270c83269575e52ac29e5b73cb956a6bd" 1582 | dependencies = [ 1583 | "nix", 1584 | "wayland-client", 1585 | "xcursor", 1586 | ] 1587 | 1588 | [[package]] 1589 | name = "wayland-egl" 1590 | version = "0.29.4" 1591 | source = "registry+https://github.com/rust-lang/crates.io-index" 1592 | checksum = "83281d69ee162b59031c666385e93bde4039ec553b90c4191cdb128ceea29a3a" 1593 | dependencies = [ 1594 | "wayland-client", 1595 | "wayland-sys", 1596 | ] 1597 | 1598 | [[package]] 1599 | name = "wayland-protocols" 1600 | version = "0.29.4" 1601 | source = "registry+https://github.com/rust-lang/crates.io-index" 1602 | checksum = "60147ae23303402e41fe034f74fb2c35ad0780ee88a1c40ac09a3be1e7465741" 1603 | dependencies = [ 1604 | "bitflags", 1605 | "wayland-client", 1606 | "wayland-commons", 1607 | "wayland-scanner", 1608 | ] 1609 | 1610 | [[package]] 1611 | name = "wayland-scanner" 1612 | version = "0.29.4" 1613 | source = "registry+https://github.com/rust-lang/crates.io-index" 1614 | checksum = "39a1ed3143f7a143187156a2ab52742e89dac33245ba505c17224df48939f9e0" 1615 | dependencies = [ 1616 | "proc-macro2", 1617 | "quote", 1618 | "xml-rs", 1619 | ] 1620 | 1621 | [[package]] 1622 | name = "wayland-sys" 1623 | version = "0.29.4" 1624 | source = "registry+https://github.com/rust-lang/crates.io-index" 1625 | checksum = "d9341df79a8975679188e37dab3889bfa57c44ac2cb6da166f519a81cbe452d4" 1626 | dependencies = [ 1627 | "dlib", 1628 | "lazy_static", 1629 | "pkg-config", 1630 | ] 1631 | 1632 | [[package]] 1633 | name = "web-sys" 1634 | version = "0.3.57" 1635 | source = "registry+https://github.com/rust-lang/crates.io-index" 1636 | checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" 1637 | dependencies = [ 1638 | "js-sys", 1639 | "wasm-bindgen", 1640 | ] 1641 | 1642 | [[package]] 1643 | name = "webbrowser" 1644 | version = "0.7.1" 1645 | source = "registry+https://github.com/rust-lang/crates.io-index" 1646 | checksum = "fc6a3cffdb686fbb24d9fb8f03a213803277ed2300f11026a3afe1f108dc021b" 1647 | dependencies = [ 1648 | "jni", 1649 | "ndk-glue 0.6.2", 1650 | "url", 1651 | "web-sys", 1652 | "widestring", 1653 | "winapi", 1654 | ] 1655 | 1656 | [[package]] 1657 | name = "widestring" 1658 | version = "0.5.1" 1659 | source = "registry+https://github.com/rust-lang/crates.io-index" 1660 | checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983" 1661 | 1662 | [[package]] 1663 | name = "winapi" 1664 | version = "0.3.9" 1665 | source = "registry+https://github.com/rust-lang/crates.io-index" 1666 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1667 | dependencies = [ 1668 | "winapi-i686-pc-windows-gnu", 1669 | "winapi-x86_64-pc-windows-gnu", 1670 | ] 1671 | 1672 | [[package]] 1673 | name = "winapi-i686-pc-windows-gnu" 1674 | version = "0.4.0" 1675 | source = "registry+https://github.com/rust-lang/crates.io-index" 1676 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1677 | 1678 | [[package]] 1679 | name = "winapi-util" 1680 | version = "0.1.5" 1681 | source = "registry+https://github.com/rust-lang/crates.io-index" 1682 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1683 | dependencies = [ 1684 | "winapi", 1685 | ] 1686 | 1687 | [[package]] 1688 | name = "winapi-wsapoll" 1689 | version = "0.1.1" 1690 | source = "registry+https://github.com/rust-lang/crates.io-index" 1691 | checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e" 1692 | dependencies = [ 1693 | "winapi", 1694 | ] 1695 | 1696 | [[package]] 1697 | name = "winapi-x86_64-pc-windows-gnu" 1698 | version = "0.4.0" 1699 | source = "registry+https://github.com/rust-lang/crates.io-index" 1700 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1701 | 1702 | [[package]] 1703 | name = "windows-sys" 1704 | version = "0.36.1" 1705 | source = "registry+https://github.com/rust-lang/crates.io-index" 1706 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 1707 | dependencies = [ 1708 | "windows_aarch64_msvc", 1709 | "windows_i686_gnu", 1710 | "windows_i686_msvc", 1711 | "windows_x86_64_gnu", 1712 | "windows_x86_64_msvc", 1713 | ] 1714 | 1715 | [[package]] 1716 | name = "windows_aarch64_msvc" 1717 | version = "0.36.1" 1718 | source = "registry+https://github.com/rust-lang/crates.io-index" 1719 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 1720 | 1721 | [[package]] 1722 | name = "windows_i686_gnu" 1723 | version = "0.36.1" 1724 | source = "registry+https://github.com/rust-lang/crates.io-index" 1725 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 1726 | 1727 | [[package]] 1728 | name = "windows_i686_msvc" 1729 | version = "0.36.1" 1730 | source = "registry+https://github.com/rust-lang/crates.io-index" 1731 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 1732 | 1733 | [[package]] 1734 | name = "windows_x86_64_gnu" 1735 | version = "0.36.1" 1736 | source = "registry+https://github.com/rust-lang/crates.io-index" 1737 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 1738 | 1739 | [[package]] 1740 | name = "windows_x86_64_msvc" 1741 | version = "0.36.1" 1742 | source = "registry+https://github.com/rust-lang/crates.io-index" 1743 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 1744 | 1745 | [[package]] 1746 | name = "winit" 1747 | version = "0.26.1" 1748 | source = "registry+https://github.com/rust-lang/crates.io-index" 1749 | checksum = "9b43cc931d58b99461188607efd7acb2a093e65fc621f54cad78517a6063e73a" 1750 | dependencies = [ 1751 | "bitflags", 1752 | "cocoa", 1753 | "core-foundation 0.9.3", 1754 | "core-graphics 0.22.3", 1755 | "core-video-sys", 1756 | "dispatch", 1757 | "instant", 1758 | "lazy_static", 1759 | "libc", 1760 | "log", 1761 | "mio", 1762 | "ndk 0.5.0", 1763 | "ndk-glue 0.5.2", 1764 | "ndk-sys 0.2.2", 1765 | "objc", 1766 | "parking_lot 0.11.2", 1767 | "percent-encoding", 1768 | "raw-window-handle", 1769 | "smithay-client-toolkit", 1770 | "wasm-bindgen", 1771 | "wayland-client", 1772 | "wayland-protocols", 1773 | "web-sys", 1774 | "winapi", 1775 | "x11-dl", 1776 | ] 1777 | 1778 | [[package]] 1779 | name = "x11-dl" 1780 | version = "2.19.1" 1781 | source = "registry+https://github.com/rust-lang/crates.io-index" 1782 | checksum = "ea26926b4ce81a6f5d9d0f3a0bc401e5a37c6ae14a1bfaa8ff6099ca80038c59" 1783 | dependencies = [ 1784 | "lazy_static", 1785 | "libc", 1786 | "pkg-config", 1787 | ] 1788 | 1789 | [[package]] 1790 | name = "x11rb" 1791 | version = "0.9.0" 1792 | source = "registry+https://github.com/rust-lang/crates.io-index" 1793 | checksum = "6e99be55648b3ae2a52342f9a870c0e138709a3493261ce9b469afe6e4df6d8a" 1794 | dependencies = [ 1795 | "gethostname", 1796 | "nix", 1797 | "winapi", 1798 | "winapi-wsapoll", 1799 | ] 1800 | 1801 | [[package]] 1802 | name = "xcursor" 1803 | version = "0.3.4" 1804 | source = "registry+https://github.com/rust-lang/crates.io-index" 1805 | checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" 1806 | dependencies = [ 1807 | "nom", 1808 | ] 1809 | 1810 | [[package]] 1811 | name = "xml-rs" 1812 | version = "0.8.4" 1813 | source = "registry+https://github.com/rust-lang/crates.io-index" 1814 | checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" 1815 | -------------------------------------------------------------------------------- /frontend/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "frontend" 3 | default-run = "truelmao" 4 | version = "0.1.0" 5 | edition = "2021" 6 | 7 | [[bin]] 8 | name = "truelmao" 9 | path = "src/main.rs" 10 | 11 | [lib] 12 | crate-type = ["cdylib", "rlib"] 13 | 14 | 15 | [dependencies] 16 | emu = { path = "../emu" } 17 | 18 | # hqx = { git = "https://github.com/CryZe/wasmboy-rs", branch = "master" } 19 | 20 | egui = "0.21.0" 21 | eframe = { version = "0.21.0", features = ["persistence"] } 22 | # serde = { version = "1", features = ["derive"] } # You only need this if you want app persistence 23 | 24 | # native: 25 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 26 | tracing-subscriber = "0.3" 27 | rfd = "0.11" 28 | 29 | # web: 30 | [target.'cfg(target_arch = "wasm32")'.dependencies] 31 | console_error_panic_hook = "0.1.6" 32 | tracing-wasm = "0.2" 33 | wasm-bindgen-futures = "0.4" 34 | web-sys = { version = "0.3.61", features = ["Window", "Url", "File", "Blob", "HtmlAnchorElement", "BlobPropertyBag", "FilePropertyBag"] } 35 | wasm-bindgen = "=0.2.84" 36 | js-sys = "0.3" 37 | -------------------------------------------------------------------------------- /frontend/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eu 3 | script_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) 4 | cd "$script_path" 5 | 6 | FAST=false 7 | 8 | while test $# -gt 0; do 9 | case "$1" in 10 | -h|--help) 11 | echo "build_web.sh [--fast]" 12 | echo " --fast: skip optimization step" 13 | exit 0 14 | ;; 15 | --fast) 16 | shift 17 | FAST=true 18 | ;; 19 | *) 20 | break 21 | ;; 22 | esac 23 | done 24 | 25 | FOLDER_NAME=${PWD##*/} 26 | CRATE_NAME=$FOLDER_NAME # assume crate name is the same as the folder name 27 | CRATE_NAME_SNAKE_CASE="${CRATE_NAME//-/_}" # for those who name crates with-kebab-case 28 | 29 | # This is required to enable the web_sys clipboard API which egui_web uses 30 | # https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Clipboard.html 31 | # https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html 32 | export RUSTFLAGS=--cfg=web_sys_unstable_apis 33 | WASM_BIN="static/${CRATE_NAME_SNAKE_CASE}_bg.wasm" 34 | 35 | rm -f "${WASM_BIN}" 36 | 37 | echo "Building rust…" 38 | BUILD=release 39 | cargo build -p "${CRATE_NAME}" --release --lib --target wasm32-unknown-unknown 40 | 41 | # Get the output directory (in the workspace it is in another location) 42 | TARGET=$(cargo metadata --format-version=1 | jq --raw-output .target_directory) 43 | 44 | echo "Generating JS bindings for wasm…" 45 | TARGET_NAME="${CRATE_NAME_SNAKE_CASE}.wasm" 46 | WASM_PATH="${TARGET}/wasm32-unknown-unknown/${BUILD}/${TARGET_NAME}" 47 | wasm-bindgen "${WASM_PATH}" --out-dir static --no-modules --no-typescript 48 | 49 | if [[ "${FAST}" == false ]]; then 50 | echo "Optimizing wasm…" 51 | # to get wasm-opt: apt/brew/dnf install binaryen 52 | # https://github.com/WebAssembly/binaryen/releases 53 | wasm-opt "${WASM_BIN}" -O2 --fast-math -o "${WASM_BIN}" # add -g to get debug symbols 54 | fi 55 | -------------------------------------------------------------------------------- /frontend/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirjavascript/trueLMAO/3bab516e577359cb8374a381dd803a651632fcad/frontend/icon.png -------------------------------------------------------------------------------- /frontend/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # cargo install cross 3 | # cargo install cargo-appimage 4 | 5 | # windows 6 | CROSS_CONTAINER_ENGINE=podman 7 | cross build --release --target x86_64-pc-windows-gnu 8 | cross build --release --target i686-pc-windows-gnu 9 | mv ../target/x86_64-pc-windows-gnu/release/frontend_bin.exe 10 | cross clean 11 | 12 | # appimage 13 | cargo appimage 14 | 15 | # web 16 | sh build.sh 17 | -------------------------------------------------------------------------------- /frontend/setup_web.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eu 3 | 4 | # Pre-requisites: 5 | rustup target add wasm32-unknown-unknown 6 | cargo install wasm-bindgen-cli 7 | cargo update -p wasm-bindgen 8 | -------------------------------------------------------------------------------- /frontend/src/app.rs: -------------------------------------------------------------------------------- 1 | use emu::Megadrive; 2 | use crate::widgets; 3 | use widgets::file::FileDialog; 4 | use std::collections::VecDeque; 5 | 6 | pub struct App { 7 | emu: Megadrive, 8 | debug: crate::debug::Debug, 9 | pub fullscreen: bool, 10 | pub vsync: bool, 11 | pub running: bool, 12 | test_vec: VecDeque, 13 | file: FileDialog, 14 | } 15 | 16 | impl Default for App { 17 | fn default() -> Self { 18 | let buf: Vec = include_bytes!("./s1proto.bin").to_vec(); 19 | Self { 20 | emu: Megadrive::new(buf), 21 | debug: Default::default(), 22 | fullscreen: false, 23 | vsync: false, 24 | running: true, 25 | test_vec: VecDeque::with_capacity(60), 26 | file: Default::default(), 27 | } 28 | } 29 | } 30 | 31 | impl App { 32 | pub fn new(cc: &eframe::CreationContext<'_>) -> Self { 33 | cc.egui_ctx.set_visuals(egui::Visuals { 34 | dark_mode: true, 35 | ..egui::Visuals::default() 36 | }); 37 | 38 | // Load previous app state (if any). 39 | // Note that you must enable the `persistence` feature for this to work. 40 | // if let Some(storage) = cc.storage { 41 | // return eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default(); 42 | // } 43 | 44 | 45 | Default::default() 46 | } 47 | } 48 | 49 | impl eframe::App for App { 50 | fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { 51 | ctx.request_repaint(); 52 | 53 | // game logic 54 | 55 | if self.running { 56 | 57 | crate::input::dummy_input(ctx, &mut self.emu); 58 | 59 | self.emu.render(); 60 | } 61 | 62 | // layout starts with fullscreen 63 | 64 | if self.fullscreen { 65 | egui::CentralPanel::default() 66 | .frame(egui::containers::Frame::none()) 67 | .show(ctx, |ui| { 68 | let response = ui.add(widgets::viewport_centred(&self.emu)); 69 | if response.double_clicked() { 70 | self.fullscreen = false; 71 | } 72 | }); 73 | return 74 | } 75 | 76 | egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { 77 | ui.add(crate::widgets::menu(&mut self.fullscreen, frame)); 78 | }); 79 | 80 | // game window 81 | 82 | egui::Window::new("screen") 83 | .min_height(100.) 84 | .show(ctx, |ui| { 85 | let response = ui.add(widgets::viewport(&self.emu)); 86 | if response.double_clicked() { 87 | self.fullscreen = true; 88 | } 89 | }); 90 | 91 | // debug stuff 92 | 93 | self.debug.render(&ctx, &mut self.emu); 94 | 95 | egui::CentralPanel::default().show(ctx, |ui| { 96 | egui::warn_if_debug_build(ui); 97 | // ctx.inspection_ui(ui); 98 | 99 | ui.label(&format!("MD frames this frame: {}", self.emu.frame_timer.frame_count)); 100 | ui.label(&format!("avg frames {:.2}", self.test_vec.iter().sum::() as f32 / self.test_vec.len() as f32)); 101 | 102 | if ui.button(if self.running { "pause" } else { "play" }).clicked() { 103 | self.running = !self.running; 104 | } 105 | ui.radio_value(&mut self.vsync, true, "vsync"); 106 | ui.radio_value(&mut self.vsync, false, "not vsync"); 107 | 108 | self.test_vec.push_back(self.emu.frame_timer.frame_count.min(4)); 109 | 110 | if self.test_vec.len() > 60 { 111 | self.test_vec.pop_front(); 112 | } 113 | 114 | if ui.button("Open file").clicked() { 115 | self.file.open(); 116 | } 117 | 118 | if let Some(file) = self.file.get() { 119 | let _ = std::mem::replace(&mut self.emu, Megadrive::new(file)); 120 | } 121 | }); 122 | 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /frontend/src/debug/cpu.rs: -------------------------------------------------------------------------------- 1 | pub fn cpu_window(ctx: &egui::Context, emu: &emu::Megadrive) { 2 | egui::Window::new("cpu") 3 | .min_width(800.) 4 | .show(ctx, |ui| { 5 | let mut debug = String::new(); 6 | debug.push_str(&format!("PC: {:X}\n\n", emu.core.pc)); 7 | 8 | 9 | // let v = emu.core.mem.vdp.VSRAM.iter().map(|x|format!("{:X}", x)).collect::>().join(" "); 10 | // debug.push_str(&format!("VSRAM: {}\n\n", v)); 11 | 12 | debug.push_str(&format!("D ")); 13 | for i in 0..=7 { 14 | debug.push_str(&format!("{:X} ", emu.core.dar[i])); 15 | } 16 | debug.push_str(&format!("\n")); 17 | 18 | debug.push_str(&format!("A ")); 19 | for i in 0..=7 { 20 | debug.push_str(&format!("{:X} ", emu.core.dar[i + 8])); 21 | } 22 | debug.push_str(&format!("\n")); 23 | debug.push_str(&format!("\n")); 24 | 25 | for (pc, opcode) in emu::debug::disasm_demo(&emu) { 26 | debug.push_str(&format!("0x{:X}\t{}\n", pc, opcode)); 27 | } 28 | ui.label(&debug); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /frontend/src/debug/memory.rs: -------------------------------------------------------------------------------- 1 | pub struct Memory { 2 | tab_index: usize, 3 | } 4 | 5 | impl Default for Memory { 6 | fn default() -> Self { 7 | Self { 8 | tab_index: 0, 9 | } 10 | } 11 | } 12 | 13 | const ASCII: &str = r##"................................ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~................................. ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"##; 14 | 15 | impl Memory { 16 | pub fn render(&mut self, ctx: &egui::Context, emu: &emu::Megadrive) { 17 | egui::Window::new("memory") 18 | .min_width(600.) 19 | .show(ctx, |ui| { 20 | 21 | let tabs: [(&str, usize, Box u8>); 4] = [ 22 | ("68K RAM", 0x10000, 23 | Box::new(|offset: usize| emu.core.mem.ram[offset])), 24 | ("Z80 RAM", 0x2000, 25 | Box::new(|offset: usize| emu.core.mem.z80.read_byte(offset as _))), 26 | ("ROM", emu.core.mem.rom.size(), 27 | Box::new(|offset: usize| emu.core.mem.rom.read_byte(offset as _))), 28 | ("IO", 0x20, 29 | Box::new(|offset: usize| emu.core.mem.io.read_byte(offset as _))), 30 | ]; 31 | 32 | let (selected_name, total_bytes, accessor) = &tabs[self.tab_index]; 33 | 34 | ui.horizontal(|ui| { 35 | for (i, (name, _, _)) in tabs.iter().enumerate() { 36 | if ui 37 | .selectable_label(selected_name == name, *name) 38 | .clicked() 39 | { 40 | self.tab_index = i; 41 | } 42 | } 43 | }); 44 | 45 | let bytes_row = 16; 46 | let rows = total_bytes / bytes_row; 47 | 48 | egui::ScrollArea::vertical() 49 | .auto_shrink([true, false]) 50 | .always_show_scroll(true) 51 | .show_rows(ui, 8., rows, |ui, row_range| { 52 | for i in row_range { 53 | let offset = i * bytes_row; 54 | let bytes = (offset..offset+bytes_row) 55 | .map(|offset| { 56 | format!(" {:02X}", accessor(offset)) 57 | }).collect::(); 58 | 59 | let ascii = (offset..offset+bytes_row) 60 | .map(|offset| { 61 | format!("{}", ASCII.chars().nth(accessor(offset) as _).unwrap_or('.')) 62 | }).collect::(); 63 | ui.monospace(format!("{:06X} {} {}", i * 16, bytes, ascii)); 64 | } 65 | }); 66 | 67 | }); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /frontend/src/debug/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod palette; 2 | pub mod vram; 3 | pub mod cpu; 4 | pub mod memory; 5 | 6 | 7 | pub struct Debug { 8 | pub vram: vram::VRAM, 9 | pub memory: memory::Memory, 10 | } 11 | 12 | impl Default for Debug { 13 | fn default() -> Self { 14 | Self { 15 | vram: Default::default(), 16 | memory: Default::default(), 17 | } 18 | } 19 | } 20 | 21 | impl Debug { 22 | pub fn render(&mut self, ctx: &egui::Context, emu: &mut emu::Megadrive) { 23 | cpu::cpu_window(&ctx, &emu); 24 | palette::palette_window(&ctx, emu); 25 | self.vram.render(&ctx, &emu); 26 | self.memory.render(&ctx, &emu); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /frontend/src/debug/palette.rs: -------------------------------------------------------------------------------- 1 | pub fn palette_window(ctx: &egui::Context, emu: &mut emu::Megadrive) { 2 | egui::Window::new("palette") 3 | .show(ctx, |ui| { 4 | let pixels = emu.core.mem.vdp.cram_rgb.iter() 5 | .map(|&(r, g, b)| egui::Color32::from_rgb(r, g, b)) 6 | .collect(); 7 | let texture: &egui::TextureHandle = &ui.ctx().load_texture( 8 | "palette", 9 | egui::ColorImage { 10 | size: [16, 4], 11 | pixels, 12 | }, 13 | egui::TextureOptions::NEAREST 14 | ); 15 | let img = egui::Image::new(texture, texture.size_vec2() * 20.); 16 | 17 | ui.add(img); 18 | 19 | // let rgb = &emu.core.mem.vdp.cram_rgb; 20 | 21 | // for i in 0..rgb.len() { 22 | 23 | // let (r, g, b) = emu.core.mem.vdp.cram_rgb[i]; 24 | // let mut color = [r, g, b]; 25 | // ui.color_edit_button_srgb(&mut color); 26 | // emu.core.mem.vdp.cram_rgb[i] = (color[0], color[1], color[2]); 27 | // } 28 | }); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /frontend/src/debug/vram.rs: -------------------------------------------------------------------------------- 1 | pub struct VRAM { 2 | palette_line: usize, 3 | } 4 | 5 | impl Default for VRAM { 6 | fn default() -> Self { 7 | Self { 8 | palette_line: 0, 9 | } 10 | } 11 | } 12 | 13 | impl VRAM { 14 | pub fn render(&mut self, ctx: &egui::Context, emu: &emu::Megadrive) { 15 | egui::Window::new("vram") 16 | .show(ctx, |ui| { 17 | ui.horizontal(|ui| { 18 | ui.radio_value(&mut self.palette_line, 0, "0"); 19 | ui.radio_value(&mut self.palette_line, 1, "1"); 20 | ui.radio_value(&mut self.palette_line, 2, "2"); 21 | ui.radio_value(&mut self.palette_line, 3, "3"); 22 | }); 23 | 24 | const WIDTH: usize = 16; // tiles 25 | const HEIGHT: usize = 128; // tiles 26 | 27 | egui::ScrollArea::vertical() 28 | .max_height(512.) 29 | .show_rows(ui, 8., HEIGHT, |ui, row_range| { 30 | let std::ops::Range { start, end } = row_range; 31 | let pixel_qty = (WIDTH*8)*((end - start)*8); 32 | let mut pixels = vec![egui::Color32::from_rgb(0, 0, 0); pixel_qty]; 33 | 34 | let palette_offset = self.palette_line * 0x10; 35 | for x_tile in 0..WIDTH { 36 | for y_tile in start..end { 37 | let offset = x_tile + (y_tile * WIDTH); 38 | let vram_offset = offset * 32; 39 | let mut view_offset = (x_tile * 8) + ((y_tile-start) * 8 * (WIDTH * 8)); 40 | 41 | for duxel in &emu.core.mem.vdp.VRAM[vram_offset..vram_offset+32] { 42 | let pixel = (*duxel & 0xF0) >> 4; 43 | 44 | let (r, g, b) = emu.core.mem.vdp.cram_rgb[palette_offset + pixel as usize]; 45 | pixels[view_offset] = egui::Color32::from_rgb(r, g, b); 46 | view_offset += 1; 47 | let pixel = *duxel & 0xF; 48 | let (r, g, b) = emu.core.mem.vdp.cram_rgb[palette_offset + pixel as usize]; 49 | pixels[view_offset] = egui::Color32::from_rgb(r, g, b); 50 | view_offset += 1; 51 | if view_offset % 8 == 0 { 52 | view_offset += (WIDTH-1) * 8; 53 | } 54 | } 55 | 56 | } 57 | } 58 | 59 | let texture: &egui::TextureHandle = &ui.ctx().load_texture( 60 | "vram", 61 | egui::ColorImage { 62 | size: [WIDTH*8, (end - start) * 8], 63 | pixels, 64 | }, 65 | egui::TextureOptions::NEAREST 66 | ); 67 | let img = egui::Image::new(texture, texture.size_vec2() * 2.); 68 | 69 | ui.add(img); 70 | }); 71 | 72 | }); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /frontend/src/input/mod.rs: -------------------------------------------------------------------------------- 1 | pub fn dummy_input(ctx: &egui::Context, emu: &mut emu::Megadrive) { 2 | let mut value = 0; 3 | if ctx.input(|i| i.key_down(egui::Key::Enter)) { 4 | value += emu::io::Gamepad::START; 5 | } 6 | if ctx.input(|i| i.key_down(egui::Key::W)) { 7 | value += emu::io::Gamepad::U; 8 | } 9 | if ctx.input(|i| i.key_down(egui::Key::S)) { 10 | value += emu::io::Gamepad::D; 11 | } 12 | if ctx.input(|i| i.key_down(egui::Key::A)) { 13 | value += emu::io::Gamepad::L; 14 | } 15 | if ctx.input(|i| i.key_down(egui::Key::D)) { 16 | value += emu::io::Gamepad::R; 17 | } 18 | if ctx.input(|i| i.key_down(egui::Key::J)) { 19 | value += emu::io::Gamepad::A; 20 | } 21 | if ctx.input(|i| i.key_down(egui::Key::K)) { 22 | value += emu::io::Gamepad::B; 23 | } 24 | if ctx.input(|i| i.key_down(egui::Key::L)) { 25 | value += emu::io::Gamepad::C; 26 | } 27 | 28 | emu.core.mem.io.gamepad[0].set(value); 29 | } 30 | -------------------------------------------------------------------------------- /frontend/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod app; 2 | mod debug; 3 | mod input; 4 | mod widgets; 5 | pub use app::App; 6 | 7 | #[cfg(target_arch = "wasm32")] 8 | use eframe::wasm_bindgen::{self, prelude::*}; 9 | 10 | #[cfg(target_arch = "wasm32")] 11 | #[wasm_bindgen] 12 | pub fn start() { 13 | // make sure panics are logged using `console.error` 14 | console_error_panic_hook::set_once(); 15 | 16 | // redirect tracing to console.log and friends 17 | tracing_wasm::set_as_global_default(); 18 | 19 | wasm_bindgen_futures::spawn_local(async { 20 | eframe::start_web( 21 | "emu", 22 | eframe::WebOptions { 23 | follow_system_theme: false, 24 | default_theme: eframe::Theme::Dark, 25 | ..Default::default() 26 | }, 27 | Box::new(|cc| Box::new(App::new(cc))), 28 | ) 29 | .await 30 | .expect("failed to start eframe"); 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::all, rust_2018_idioms)] 2 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release 3 | 4 | // When compiling natively: 5 | #[cfg(not(target_arch = "wasm32"))] 6 | fn main() -> eframe::Result<()> { 7 | // Log to stdout (if you run with `RUST_LOG=debug`). 8 | tracing_subscriber::fmt::init(); 9 | 10 | let native_options = eframe::NativeOptions::default(); 11 | eframe::run_native( 12 | "trueLMAO", 13 | native_options, 14 | Box::new(|cc| Box::new(frontend::App::new(cc))), 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/s1proto.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kirjavascript/trueLMAO/3bab516e577359cb8374a381dd803a651632fcad/frontend/src/s1proto.bin -------------------------------------------------------------------------------- /frontend/src/widgets/file.rs: -------------------------------------------------------------------------------- 1 | type FileData = Vec; 2 | 3 | // wasm 4 | 5 | #[cfg(target_arch = "wasm32")] 6 | use wasm_bindgen::prelude::*; 7 | #[cfg(target_arch = "wasm32")] 8 | use wasm_bindgen::JsCast; 9 | #[cfg(target_arch = "wasm32")] 10 | use web_sys::{window, Url, File, HtmlInputElement, FileReader}; 11 | #[cfg(target_arch = "wasm32")] 12 | use js_sys::{Uint8Array, Array, ArrayBuffer}; 13 | 14 | 15 | #[cfg(target_arch = "wasm32")] 16 | pub struct FileDialog { 17 | tx: std::sync::mpsc::Sender, 18 | rx: std::sync::mpsc::Receiver, 19 | input: HtmlInputElement, 20 | closure: Option>, 21 | } 22 | 23 | #[cfg(target_arch = "wasm32")] 24 | impl Default for FileDialog { 25 | fn default() -> Self { 26 | let (tx, rx) = std::sync::mpsc::channel(); 27 | 28 | let document = window().unwrap().document().unwrap(); 29 | let body = document.body().unwrap(); 30 | let input = document.create_element("input").unwrap().dyn_into::().unwrap(); 31 | input.set_attribute("type", "file").unwrap(); 32 | input.style().set_property("display", "none").unwrap(); 33 | body.append_child(&input).unwrap(); 34 | 35 | Self { 36 | rx, 37 | tx, 38 | input, 39 | closure: None, 40 | } 41 | } 42 | } 43 | 44 | #[cfg(target_arch = "wasm32")] 45 | impl Drop for FileDialog { 46 | fn drop(&mut self) { 47 | self.input.remove(); 48 | if self.closure.is_some() { 49 | std::mem::replace(&mut self.closure, None).unwrap().forget(); 50 | } 51 | } 52 | } 53 | 54 | #[cfg(target_arch = "wasm32")] 55 | impl FileDialog { 56 | pub fn open(&mut self) { 57 | if let Some(closure) = &self.closure { 58 | self.input.remove_event_listener_with_callback("change", closure.as_ref().unchecked_ref()).unwrap(); 59 | std::mem::replace(&mut self.closure, None).unwrap().forget(); 60 | } 61 | 62 | let tx = self.tx.clone(); 63 | let input_clone = self.input.clone(); 64 | 65 | let closure = Closure::once(move || { 66 | if let Some(file) = input_clone.files().and_then(|files| files.get(0)) { 67 | let reader = FileReader::new().unwrap(); 68 | let reader_clone = reader.clone(); 69 | let onload_closure = Closure::once(Box::new(move || { 70 | let array_buffer = reader_clone.result().unwrap().dyn_into::().unwrap(); 71 | let buffer = Uint8Array::new(&array_buffer).to_vec(); 72 | tx.send(buffer).ok(); 73 | })); 74 | 75 | reader.set_onload(Some(onload_closure.as_ref().unchecked_ref())); 76 | reader.read_as_array_buffer(&file).unwrap(); 77 | onload_closure.forget(); 78 | } 79 | }); 80 | 81 | self.input.add_event_listener_with_callback("change", closure.as_ref().unchecked_ref()).unwrap(); 82 | self.closure = Some(closure); 83 | self.input.click(); 84 | } 85 | 86 | pub fn get(&self) -> Option> { 87 | if let Ok(file) = self.rx.try_recv() { 88 | Some(file) 89 | } else { 90 | None 91 | } 92 | } 93 | 94 | pub fn save(&self, filename: &str, filedata: FileData) { 95 | let array = Uint8Array::from(filedata.as_slice()); 96 | let blob_parts = Array::new(); 97 | blob_parts.push(&array.buffer()); 98 | 99 | let file = File::new_with_blob_sequence_and_options( 100 | &blob_parts.into(), 101 | filename, 102 | web_sys::FilePropertyBag::new().type_("application/octet-stream") 103 | ).unwrap(); 104 | let url = Url::create_object_url_with_blob(&file); 105 | if let Some(window) = web_sys::window() { 106 | window.location().set_href(&url.unwrap()).ok(); 107 | } 108 | } 109 | } 110 | 111 | // native 112 | 113 | #[cfg(not(target_arch = "wasm32"))] 114 | use rfd; 115 | 116 | #[cfg(not(target_arch = "wasm32"))] 117 | pub struct FileDialog { 118 | file: Option, 119 | } 120 | 121 | #[cfg(not(target_arch = "wasm32"))] 122 | impl Default for FileDialog { 123 | fn default() -> Self { 124 | Self { 125 | file: None, 126 | } 127 | } 128 | } 129 | 130 | #[cfg(not(target_arch = "wasm32"))] 131 | impl FileDialog { 132 | pub fn open(&mut self) { 133 | let path = rfd::FileDialog::new().pick_file(); 134 | if let Some(path) = path { 135 | self.file = std::fs::read(path).ok(); 136 | } 137 | } 138 | 139 | pub fn get(&mut self) -> Option { 140 | std::mem::replace(&mut self.file, None) 141 | } 142 | 143 | pub fn save(&self, filename: &str, file: FileData) { 144 | let path = rfd::FileDialog::new() 145 | .set_file_name(filename) 146 | .save_file(); 147 | 148 | if let Some(path) = path { 149 | std::fs::write(path, file).ok(); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /frontend/src/widgets/menu.rs: -------------------------------------------------------------------------------- 1 | pub fn menu<'a>(fullscreen: &'a mut bool, _frame: &'a mut eframe::Frame) -> 2 | impl egui::Widget + 'a 3 | { 4 | move |ui: &mut egui::Ui| { 5 | egui::menu::bar(ui, |ui| { 6 | ui.menu_button("File", |_ui| { 7 | #[cfg(not(target_arch = "wasm32"))] 8 | if _ui.button("Quit").clicked() { 9 | _frame.close(); 10 | } 11 | }); 12 | 13 | ui.menu_button("Window", |ui| { 14 | if ui.button("Auto-arrange").clicked() { 15 | ui.ctx().memory_mut(|mem| mem.reset_areas()); 16 | ui.close_menu(); 17 | } 18 | if ui.button("Fullscreen").clicked() { 19 | *fullscreen = true; 20 | ui.close_menu(); 21 | } 22 | }); 23 | }).response 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /frontend/src/widgets/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod viewport; 2 | pub use viewport::*; 3 | pub mod menu; 4 | pub use menu::*; 5 | pub mod file; 6 | pub use file::*; 7 | -------------------------------------------------------------------------------- /frontend/src/widgets/viewport.rs: -------------------------------------------------------------------------------- 1 | use emu::Megadrive; 2 | 3 | fn viewport_ui(ui: &mut egui::Ui, emu: &Megadrive, centered: bool) -> egui::Response { 4 | let pixels = emu.gfx.screen.chunks_exact(3) 5 | .map(|p| egui::Color32::from_rgb(p[0], p[1], p[2])) 6 | .collect(); 7 | let texture: &egui::TextureHandle = &ui.ctx().load_texture( 8 | "viewport", 9 | egui::ColorImage { 10 | size: [320, 240], 11 | pixels, 12 | }, 13 | egui::TextureOptions::NEAREST 14 | ); 15 | 16 | let (width, height) = (ui.available_width(), ui.available_height()); 17 | 18 | let size = texture.size_vec2(); 19 | 20 | let size_w = size * (width / size.x); 21 | 22 | let size = if size_w.y > height { 23 | size * (height / size.y) 24 | } else { 25 | size_w 26 | }; 27 | 28 | let img = egui::Image::new(texture, size) 29 | .sense(egui::Sense::click()); 30 | 31 | if centered { 32 | let y_margin = (height - size.y) / 2.; 33 | let x_margin = (width - size.x) / 2.; 34 | 35 | let (r, g, b) = emu.core.mem.vdp.bg_color(); 36 | 37 | egui::Frame::none() 38 | .fill(egui::Color32::from_rgb(r, g, b)) 39 | // .sense(egui::Sense::click()) 40 | .inner_margin(egui::style::Margin::symmetric(x_margin, y_margin)) 41 | .show(ui, |ui| { 42 | ui.add(img) 43 | 44 | 45 | }) 46 | .inner 47 | .union(ui.interact( 48 | egui::Rect::EVERYTHING, 49 | ui.id(), 50 | egui::Sense::click() 51 | )) 52 | } else { 53 | ui.add(img) 54 | } 55 | } 56 | 57 | pub fn viewport(emu: &Megadrive) -> impl egui::Widget + '_ { 58 | move |ui: &mut egui::Ui| viewport_ui(ui, emu, false) 59 | } 60 | 61 | pub fn viewport_centred(emu: &Megadrive) -> impl egui::Widget + '_ { 62 | move |ui: &mut egui::Ui| viewport_ui(ui, emu, true) 63 | } 64 | -------------------------------------------------------------------------------- /frontend/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 97 | trueLMAO 98 | 99 | 100 | 101 | 102 | 103 |
104 |

105 | Loading… 106 |

107 |
108 |
109 | 110 | 122 | 123 | 124 | 125 | 126 | 160 | 161 | 162 | 163 | --------------------------------------------------------------------------------