├── .gitignore ├── .gitattributes ├── favicon.ico ├── img ├── croom.png ├── pacman.png ├── mega_man.png ├── donkey_kong.png ├── flappy_bird.png └── super_mario.png ├── roms ├── croom.nes ├── bootee.nes ├── jetpaco.nes ├── nestest.nes ├── nomolos.nes ├── snake2.nes ├── flappybird.nes └── tests │ ├── 13-rts.nes │ ├── 14-rti.nes │ ├── 15-brk.nes │ ├── 01-basics.nes │ ├── 05-zp_xy.nes │ ├── 07-abs_xy.nes │ ├── 08-ind_x.nes │ ├── 09-ind_y.nes │ ├── 11-stack.nes │ ├── litewall2.nes │ ├── 02-implied.nes │ ├── 06-absolute.nes │ ├── 10-branches.nes │ ├── 12-jmp_jsr.nes │ ├── 16-special.nes │ ├── all_instrs.nes │ ├── 03-immediate.nes │ ├── 04-zero_page.nes │ ├── official_only.nes │ ├── cpu_dummy_reads.nes │ ├── cpu_dummy_writes_oam.nes │ └── cpu_dummy_writes_ppumem.nes ├── wasi.mg ├── LICENSE ├── wasm.mg ├── joypad.mg ├── README.md ├── fmt.mg ├── bus.mg ├── nes.mg ├── rom.mg ├── platform └── web │ ├── index.html │ └── main.js ├── mem.mg ├── ppu.mg └── cpu.mg /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.mg linguist-language=WebAssembly 2 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/favicon.ico -------------------------------------------------------------------------------- /img/croom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/img/croom.png -------------------------------------------------------------------------------- /img/pacman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/img/pacman.png -------------------------------------------------------------------------------- /roms/croom.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/croom.nes -------------------------------------------------------------------------------- /img/mega_man.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/img/mega_man.png -------------------------------------------------------------------------------- /roms/bootee.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/bootee.nes -------------------------------------------------------------------------------- /roms/jetpaco.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/jetpaco.nes -------------------------------------------------------------------------------- /roms/nestest.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/nestest.nes -------------------------------------------------------------------------------- /roms/nomolos.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/nomolos.nes -------------------------------------------------------------------------------- /roms/snake2.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/snake2.nes -------------------------------------------------------------------------------- /img/donkey_kong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/img/donkey_kong.png -------------------------------------------------------------------------------- /img/flappy_bird.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/img/flappy_bird.png -------------------------------------------------------------------------------- /img/super_mario.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/img/super_mario.png -------------------------------------------------------------------------------- /roms/flappybird.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/flappybird.nes -------------------------------------------------------------------------------- /roms/tests/13-rts.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/tests/13-rts.nes -------------------------------------------------------------------------------- /roms/tests/14-rti.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/tests/14-rti.nes -------------------------------------------------------------------------------- /roms/tests/15-brk.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/tests/15-brk.nes -------------------------------------------------------------------------------- /roms/tests/01-basics.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/tests/01-basics.nes -------------------------------------------------------------------------------- /roms/tests/05-zp_xy.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/tests/05-zp_xy.nes -------------------------------------------------------------------------------- /roms/tests/07-abs_xy.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/tests/07-abs_xy.nes -------------------------------------------------------------------------------- /roms/tests/08-ind_x.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/tests/08-ind_x.nes -------------------------------------------------------------------------------- /roms/tests/09-ind_y.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/tests/09-ind_y.nes -------------------------------------------------------------------------------- /roms/tests/11-stack.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/tests/11-stack.nes -------------------------------------------------------------------------------- /roms/tests/litewall2.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/tests/litewall2.nes -------------------------------------------------------------------------------- /roms/tests/02-implied.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/tests/02-implied.nes -------------------------------------------------------------------------------- /roms/tests/06-absolute.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/tests/06-absolute.nes -------------------------------------------------------------------------------- /roms/tests/10-branches.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/tests/10-branches.nes -------------------------------------------------------------------------------- /roms/tests/12-jmp_jsr.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/tests/12-jmp_jsr.nes -------------------------------------------------------------------------------- /roms/tests/16-special.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/tests/16-special.nes -------------------------------------------------------------------------------- /roms/tests/all_instrs.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/tests/all_instrs.nes -------------------------------------------------------------------------------- /roms/tests/03-immediate.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/tests/03-immediate.nes -------------------------------------------------------------------------------- /roms/tests/04-zero_page.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/tests/04-zero_page.nes -------------------------------------------------------------------------------- /roms/tests/official_only.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/tests/official_only.nes -------------------------------------------------------------------------------- /roms/tests/cpu_dummy_reads.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/tests/cpu_dummy_reads.nes -------------------------------------------------------------------------------- /roms/tests/cpu_dummy_writes_oam.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/tests/cpu_dummy_writes_oam.nes -------------------------------------------------------------------------------- /roms/tests/cpu_dummy_writes_ppumem.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jauhararifin/magnes/HEAD/roms/tests/cpu_dummy_writes_ppumem.nes -------------------------------------------------------------------------------- /wasi.mg: -------------------------------------------------------------------------------- 1 | struct IoVec { 2 | p: [*]u8, 3 | len: i32, 4 | } 5 | 6 | @wasm_import("wasi_snapshot_preview1", "fd_write") 7 | fn fd_write(fd: i32, iovec_addr: *IoVec, count: i32, n_written_ptr: *i32): i32; 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /wasm.mg: -------------------------------------------------------------------------------- 1 | @intrinsic("data.end") 2 | fn data_end(): usize; 3 | 4 | @intrinsic("size_of") 5 | fn size_of(): usize; 6 | 7 | @intrinsic("align_of") 8 | fn align_of(): usize; 9 | 10 | @intrinsic("memory.size") 11 | fn memory_size(): usize; 12 | 13 | @intrinsic("memory.grow") 14 | fn memory_grow(sz: usize): usize; 15 | 16 | @intrinsic("unreachable") 17 | fn trap(); 18 | 19 | @intrinsic("f32.floor") 20 | fn floor_f32(v: f32): f32; 21 | 22 | @intrinsic("f32.ceil") 23 | fn ceil_f32(v: f32): f32; 24 | 25 | @intrinsic("f64.floor") 26 | fn floor_f64(v: f64): f64; 27 | 28 | @intrinsic("f64.ceil") 29 | fn ceil_f64(v: f64): f64; 30 | 31 | @intrinsic("table.get") 32 | fn table_get(id: usize): opaque; 33 | 34 | @intrinsic("table.set") 35 | fn table_set(id: usize, val: opaque); 36 | 37 | 38 | -------------------------------------------------------------------------------- /joypad.mg: -------------------------------------------------------------------------------- 1 | import mem "mem"; 2 | import fmt "fmt"; 3 | 4 | let JOYPAD_BUTTON_A: u8 = 1; 5 | let JOYPAD_BUTTON_B: u8 = 1<<1; 6 | let JOYPAD_BUTTON_SELECT: u8 = 1<<2; 7 | let JOYPAD_BUTTON_START: u8 = 1<<3; 8 | let JOYPAD_BUTTON_UP: u8 = 1<<4; 9 | let JOYPAD_BUTTON_DOWN: u8 = 1<<5; 10 | let JOYPAD_BUTTON_LEFT: u8 = 1<<6; 11 | let JOYPAD_BUTTON_RIGHT: u8 = 1<<7; 12 | 13 | struct Joypad { 14 | strobe: bool, 15 | button_i: u8, 16 | status: u8, 17 | } 18 | 19 | fn new(): *Joypad { 20 | let p = mem::alloc::(); 21 | p.* = Joypad { 22 | strobe: false, 23 | button_i: 0, 24 | status: 0, 25 | } 26 | return p; 27 | } 28 | 29 | fn reset(joypad: *Joypad) { 30 | joypad.* = Joypad { 31 | strobe: false, 32 | button_i: 0, 33 | status: 0, 34 | } 35 | } 36 | 37 | fn write(joypad: *Joypad, data: u8) { 38 | joypad.strobe.* = (data & 1) != 0; 39 | if joypad.strobe.* { 40 | joypad.button_i.* = 0; 41 | } 42 | } 43 | 44 | fn read(joypad: *Joypad): u8 { 45 | if joypad.button_i.* > 7 { 46 | return 0x4; // I have no idea why returning 4 fix hanging menu on SMB. 47 | } 48 | 49 | let result = (joypad.status.* >> joypad.button_i.*) & 1; 50 | if !joypad.strobe.* && joypad.button_i.* <= 7 { 51 | joypad.button_i.* = joypad.button_i.* + 1; 52 | } 53 | if joypad.strobe.* { 54 | joypad.button_i.* = 0; 55 | } 56 | 57 | // fmt::print_str("joypad read, result="); 58 | // fmt::print_u8(result); 59 | // fmt::print_str("\n"); 60 | return result; 61 | } 62 | 63 | fn press(joypad: *Joypad, mask: u8) { 64 | // fmt::print_str("joypad pressed, staus="); fmt::print_u8(joypad.status.*); fmt::print_str("\n"); 65 | joypad.status.* = joypad.status.* | mask; 66 | } 67 | 68 | fn unpress(joypad: *Joypad, mask: u8) { 69 | // fmt::print_str("joypad unpressed, staus="); fmt::print_u8(joypad.status.*); fmt::print_str("\n"); 70 | joypad.status.* = joypad.status.* & ~mask; 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Magnes 2 | 3 | [Magnes](https://magnes.jauhar.dev) is NES (Nintendo Entertaiment System) emulator written in [Magelang](https://github.com/jauhararifin/magelang). 4 | 5 | This project is in WIP state. 6 | 7 | ## Screeshots 8 | 9 | ![Super Mario](img/super_mario.png) 10 | ![Donkey Kong](img/donkey_kong.png) 11 | ![Pacman](img/pacman.png) 12 | ![Mega-Man](img/mega_man.png) 13 | ![Concentration Room](img/croom.png) 14 | ![Flappy Bird](img/flappy_bird.png) 15 | 16 | ## Compiling 17 | 18 | To compile this game, you must first install the Magelang compiler. 19 | 20 | 1. Before installing Magelang, make sure you have the following tools installed: 21 | - Git 22 | - [Rust](https://www.rust-lang.org/tools/install) and [Cargo](https://github.com/rust-lang/cargo) 23 | - [NPM](https://www.npmjs.com/) (Optional). 24 | 25 | 2. Installing magelang compiler 26 | 27 | ```bash 28 | # At the time of writing this project, Magelang has not been officially released. 29 | # The game was compiled using Magelang with this commit ID: bf61fb3cdda14f762614f3017553d3c584fac9f3. 30 | # Use this commit ID to install Magelang to ensure version compatibility. 31 | cargo install \ 32 | --git https://github.com/jauhararifin/magelang.git \ 33 | --rev bf61fb3cdda14f762614f3017553d3c584fac9f3 \ 34 | magelang 35 | 36 | # Verify the installation 37 | magelang --version 38 | ``` 39 | 40 | 3. Run the build script 41 | 42 | ```bash 43 | # Clone the flappybird repository 44 | git clone https://github.com/jauhararifin/magnes.git 45 | cd magnes 46 | 47 | # Execute the build script: 48 | bash ./build.sh 49 | ``` 50 | 51 | After running this script, you should see a `./build` directory in your current working directory, containing all the files required to run the game. 52 | Checkout `./build/platform/web/` directory to run it on the web. 53 | 54 | ## Running 55 | 56 | 1. Serve the build directory using an HTTP server. You can use the `http-server` package from NPM for this purpose. 57 | 58 | ```bash 59 | npx http-server ./build/platform/web/ -p 8080 60 | ``` 61 | 62 | 2. Open your browser and navigate to http://localhost:8080. 63 | 64 | ## Demo 65 | 66 | Checkout the demo [here](https://magnes.jauhar.dev). 67 | 68 | ## Reference 69 | 70 | - [6502 CPU Reference](https://www.masswerk.at/6502/6502_instruction_set.html#opcodes-footnote2) 71 | - [Nesdev Wiki](https://www.nesdev.org/wiki) 72 | -------------------------------------------------------------------------------- /fmt.mg: -------------------------------------------------------------------------------- 1 | import wasi "wasi"; 2 | import mem "mem"; 3 | 4 | fn print_str(p: [*]u8) { 5 | let len = strlen(p); 6 | let iovec = mem::alloc::(); 7 | iovec.* = wasi::IoVec{ 8 | len: len, 9 | p: p, 10 | }; 11 | 12 | wasi::fd_write(1, iovec, 1, 0 as *i32); 13 | mem::dealloc::(iovec); 14 | } 15 | 16 | fn strlen(p: [*]u8): i32 { 17 | let i: i32 = 0; 18 | while p[i].* != 0 { 19 | i = i + 1; 20 | } 21 | return i; 22 | } 23 | 24 | fn print_i8(val: i8) { print_i64(val as i64); } 25 | fn print_i16(val: i16) { print_i64(val as i64); } 26 | fn print_i32(val: i32) { print_i64(val as i64); } 27 | fn print_isize(val: isize) { print_i64(val as i64); } 28 | 29 | fn print_u8(val: u8) { print_u64(val as u64); } 30 | fn print_u16(val: u16) { print_u64(val as u64); } 31 | fn print_u32(val: u32) { print_u64(val as u64); } 32 | fn print_usize(val: usize) { print_u64(val as u64); } 33 | 34 | fn print_i64(val: i64) { 35 | if val == 0 { 36 | print_str("0"); 37 | return; 38 | } 39 | 40 | let str = mem::alloc_array::(10); 41 | let str_n: usize = 0; 42 | 43 | let start: usize = 0; 44 | if val < 0 { 45 | str[str_n].* = 45; // ascii for '-' 46 | str_n = str_n + 1; 47 | start = 1; 48 | } 49 | 50 | while val != 0 { 51 | let d = val % 10; 52 | if d < 0 { 53 | d = -d; 54 | } 55 | str[str_n].* = 48 + d as u8; // 48 is ascii for '0' 56 | str_n = str_n + 1; 57 | val = val / 10; 58 | } 59 | 60 | let i: usize = start; 61 | let j = str_n - 1; 62 | while i < j { 63 | let tmp = str[i].*; 64 | str[i].* = str[j].*; 65 | str[j].* = tmp; 66 | i = i + 1; 67 | j = j - 1; 68 | } 69 | 70 | str[str_n].* = 0; 71 | str_n = str_n + 1; 72 | print_str(str); 73 | mem::dealloc_array::(str); 74 | } 75 | 76 | fn print_u64(val: u64) { 77 | if val == 0 { 78 | print_str("0"); 79 | return; 80 | } 81 | 82 | let str = mem::alloc_array::(10); 83 | let str_n: usize = 0; 84 | 85 | while val != 0 { 86 | let d = val % 16; 87 | if d > 9 { 88 | str[str_n].* = 97 + (d as u8) - 10; // ascii for 'a' 89 | } else { 90 | str[str_n].* = 48 + (d as u8); // ascii for '0' 91 | } 92 | str_n = str_n + 1; 93 | val = val / 16; 94 | } 95 | 96 | let i: usize = 0; 97 | let j = str_n - 1; 98 | while i < j { 99 | let tmp = str[i].*; 100 | str[i].* = str[j].*; 101 | str[j].* = tmp; 102 | i = i + 1; 103 | j = j - 1; 104 | } 105 | 106 | str[str_n].* = 0; 107 | str_n = str_n + 1; 108 | print_str("0x"); 109 | print_str(str); 110 | mem::dealloc_array::(str); 111 | } 112 | 113 | 114 | -------------------------------------------------------------------------------- /bus.mg: -------------------------------------------------------------------------------- 1 | import mem "mem"; 2 | import fmt "fmt"; 3 | import wasm "wasm"; 4 | import cpu "cpu"; 5 | import rom "rom"; 6 | import ppu "ppu"; 7 | import joypad "joypad"; 8 | 9 | let the_cpu: *cpu::CPU = cpu::new(); 10 | let the_ppu: *ppu::PPU = ppu::new(); 11 | let the_rom: *rom::ROM = mem::alloc::(); 12 | let ram: [*]u8 = mem::alloc_array::(0x2000); 13 | let debug: bool = false; 14 | let joypad_1: *joypad::Joypad = joypad::new(); 15 | let joypad_2: *joypad::Joypad = joypad::new(); 16 | 17 | fn init() { 18 | cpu::wire(the_cpu, read, write); 19 | ppu::wire( 20 | the_ppu, 21 | send_non_maskable_interrupt, 22 | read_chr_rom, 23 | write_chr_rom, 24 | ) 25 | } 26 | 27 | fn send_non_maskable_interrupt() { 28 | cpu::trigger_non_maskable_interrupt(the_cpu); 29 | } 30 | 31 | fn read_chr_rom(addr: u16): u8 { 32 | return rom::read_chr(the_rom, addr); 33 | } 34 | 35 | fn write_chr_rom(addr: u16, data: u8) { 36 | return rom::write_chr(the_rom, addr, data); 37 | } 38 | 39 | fn reset() { 40 | let i = 0; 41 | while i < 0x2000 { 42 | ram[i].* = 0; 43 | i = i + 1; 44 | } 45 | } 46 | 47 | fn read(addr: u16): u8 { 48 | if debug { 49 | fmt::print_str("read "); 50 | fmt::print_u16(addr); 51 | fmt::print_str("\n"); 52 | } 53 | 54 | if (addr >= 0) && (addr < 0x2000) { 55 | if debug { 56 | fmt::print_u8(ram[addr & 0x07ff].*); 57 | fmt::print_str("\n"); 58 | } 59 | return ram[addr & 0x07ff].*; 60 | } else if (addr >= 0x2000) && (addr < 0x4000) { 61 | let data = ppu::get_register(the_ppu, (addr & 0x07) as u8); 62 | return data; 63 | } else if addr == 0x4016 { 64 | return joypad::read(joypad_1); 65 | } else if addr == 0x4017 { 66 | return joypad::read(joypad_2); 67 | } else if addr >= 0x8000 { 68 | let addr = addr - 0x8000; 69 | return rom::read_program(the_rom, addr); 70 | } 71 | 72 | if debug { 73 | fmt::print_str("invalid mem read at "); 74 | fmt::print_u16(addr); 75 | fmt::print_str("\n"); 76 | } 77 | return 0; 78 | } 79 | 80 | fn write(addr: u16, data: u8) { 81 | if debug { 82 | fmt::print_str("write addr="); 83 | fmt::print_u16(addr); 84 | fmt::print_str(",data="); 85 | fmt::print_u8(data); 86 | fmt::print_str("\n"); 87 | } 88 | 89 | if (addr >= 0) && (addr < 0x2000) { 90 | ram[addr & 0x07ff].* = data; 91 | } else if (addr >= 0x2000) && (addr < 0x4000) { 92 | if debug { 93 | fmt::print_str("set ppu register i="); 94 | fmt::print_u16(addr); 95 | fmt::print_str(",data="); 96 | fmt::print_u8(data); 97 | fmt::print_str("\n"); 98 | } 99 | 100 | ppu::set_register(the_ppu, (addr as u8) & 0x07, data); 101 | } else if addr == 0x4014 { 102 | let addr = (data as u16) << 8; 103 | let i: u16 = 0; 104 | while i < 256 { 105 | let b = read(addr + i); 106 | ppu::write_oam(the_ppu, b); 107 | i = i + 1; 108 | } 109 | } else if addr == 0x4016 { 110 | joypad::write(joypad_1, data); 111 | } else if addr == 0x4017 { 112 | joypad::write(joypad_2, data); 113 | } else if addr >= 0x8000 { 114 | let addr = addr - 0x8000; 115 | return rom::write_program(the_rom, addr, data); 116 | } else { 117 | if debug { 118 | fmt::print_str("invalid write "); 119 | fmt::print_u8(data); 120 | fmt::print_str(" at "); 121 | fmt::print_u16(addr); 122 | fmt::print_str("\n"); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /nes.mg: -------------------------------------------------------------------------------- 1 | import mem "mem"; 2 | import rom "rom"; 3 | import bus "bus"; 4 | import cpu "cpu"; 5 | import ppu "ppu"; 6 | import fmt "fmt"; 7 | import joypad "joypad"; 8 | 9 | @main() 10 | fn main() { 11 | bus::init(); 12 | } 13 | 14 | let remaining_elapsed_nanosecond: i64 = 0; 15 | let cycle_rate: i64 = 1_789_773; // cycles per second 16 | let cycle_period: i64 = 1_000_000_000 / cycle_rate; 17 | @wasm_export("tick") 18 | fn tick(elapsed_ns: i64) { 19 | remaining_elapsed_nanosecond = remaining_elapsed_nanosecond + elapsed_ns; 20 | if remaining_elapsed_nanosecond > 16_000_000 { 21 | remaining_elapsed_nanosecond = 16_000_000; 22 | } 23 | 24 | let cpu_cycle = remaining_elapsed_nanosecond / cycle_period; 25 | 26 | while cpu_cycle > 0 { 27 | let cycle = cpu::tick(bus::the_cpu) as i64; 28 | ppu::tick(bus::the_ppu, cycle*3); 29 | cpu_cycle = cpu_cycle - cycle; 30 | } 31 | 32 | remaining_elapsed_nanosecond = cpu_cycle; 33 | 34 | ppu::render(bus::the_ppu); 35 | } 36 | 37 | let rom_buffer: [*]u8 = mem::alloc_array::(0x100000); 38 | @wasm_export("getRom") 39 | fn get_rom(): [*]u8 { 40 | return rom_buffer; 41 | } 42 | 43 | struct LoadRomResult { 44 | valid: bool, 45 | error: [*]u8, 46 | } 47 | 48 | @wasm_export("loadRom") 49 | fn load_rom(): LoadRomResult { 50 | let result = rom::load(rom_buffer); 51 | if !result.valid { 52 | return LoadRomResult{valid: false, error: result.error}; 53 | } 54 | 55 | bus::the_rom.* = result; 56 | 57 | ppu::load_rom(bus::the_ppu, bus::the_rom); 58 | 59 | return LoadRomResult{valid: true, error: 0 as [*]u8}; 60 | } 61 | 62 | @wasm_export("getRam") 63 | fn get_ram(): [*]u8 { 64 | return bus::ram as [*]u8; 65 | } 66 | 67 | @wasm_export("reset") 68 | fn reset() { 69 | bus::reset(); 70 | rom::reset(bus::the_rom); 71 | cpu::reset(bus::the_cpu); 72 | ppu::reset(bus::the_ppu); 73 | joypad::reset(bus::joypad_1); 74 | joypad::reset(bus::joypad_2); 75 | } 76 | 77 | @wasm_export("debugCPU") 78 | fn debug_cpu(): cpu::CPU { 79 | return bus::the_cpu.*; 80 | } 81 | 82 | @wasm_export("getDebugTileFramebufer") 83 | fn get_debug_tile_framebuffer(): ppu::Image { 84 | return ppu::get_debug_tile_framebuffer(bus::the_ppu); 85 | } 86 | 87 | @wasm_export("getDebugPaletteImage") 88 | fn get_debug_palette_framebuffer(): ppu::DebugPalette { 89 | return ppu::get_debug_palette_framebuffer(bus::the_ppu); 90 | } 91 | 92 | @wasm_export("setDebugPaletteId") 93 | fn set_debug_palette_id(id: u8) { 94 | bus::the_ppu.debug.debug_palette_id.* = id; 95 | } 96 | 97 | @wasm_export("getScreenFramebuffer") 98 | fn get_screen_framebuffer(): ppu::Image { 99 | return ppu::get_screen_framebuffer(bus::the_ppu); 100 | } 101 | 102 | @wasm_export("getNametable1Framebuffer") 103 | fn get_nametable_1_framebuffer(): ppu::Image { 104 | return ppu::get_nametable_1_framebuffer(bus::the_ppu); 105 | } 106 | 107 | @wasm_export("getNametable2Framebuffer") 108 | fn get_nametable_2_framebuffer(): ppu::Image { 109 | return ppu::get_nametable_2_framebuffer(bus::the_ppu); 110 | } 111 | 112 | @wasm_export("getNametable3Framebuffer") 113 | fn get_nametable_3_framebuffer(): ppu::Image { 114 | return ppu::get_nametable_3_framebuffer(bus::the_ppu); 115 | } 116 | 117 | @wasm_export("getNametable4Framebuffer") 118 | fn get_nametable_4_framebuffer(): ppu::Image { 119 | return ppu::get_nametable_4_framebuffer(bus::the_ppu); 120 | } 121 | 122 | @wasm_export("keydownJoypad1A") 123 | fn keydown_joypad1_a() { joypad::press(bus::joypad_1, joypad::JOYPAD_BUTTON_A); } 124 | 125 | @wasm_export("keydownJoypad1B") 126 | fn keydown_joypad1_b() { joypad::press(bus::joypad_1, joypad::JOYPAD_BUTTON_B);} 127 | 128 | @wasm_export("keydownJoypad1Select") 129 | fn keydown_joypad1_select() { joypad::press(bus::joypad_1, joypad::JOYPAD_BUTTON_SELECT);} 130 | 131 | @wasm_export("keydownJoypad1Start") 132 | fn keydown_joypad1_start() { joypad::press(bus::joypad_1, joypad::JOYPAD_BUTTON_START);} 133 | 134 | @wasm_export("keydownJoypad1Up") 135 | fn keydown_joypad1_up() { joypad::press(bus::joypad_1, joypad::JOYPAD_BUTTON_UP);} 136 | 137 | @wasm_export("keydownJoypad1Down") 138 | fn keydown_joypad1_down() { joypad::press(bus::joypad_1, joypad::JOYPAD_BUTTON_DOWN); } 139 | 140 | @wasm_export("keydownJoypad1Left") 141 | fn keydown_joypad1_left() { joypad::press(bus::joypad_1, joypad::JOYPAD_BUTTON_LEFT);} 142 | 143 | @wasm_export("keydownJoypad1Right") 144 | fn keydown_joypad1_right() { joypad::press(bus::joypad_1, joypad::JOYPAD_BUTTON_RIGHT);} 145 | 146 | @wasm_export("keyupJoypad1A") 147 | fn keyup_joypad1_a() { joypad::unpress(bus::joypad_1, joypad::JOYPAD_BUTTON_A); } 148 | 149 | @wasm_export("keyupJoypad1B") 150 | fn keyup_joypad1_b() { joypad::unpress(bus::joypad_1, joypad::JOYPAD_BUTTON_B);} 151 | 152 | @wasm_export("keyupJoypad1Select") 153 | fn keyup_joypad1_select() { joypad::unpress(bus::joypad_1, joypad::JOYPAD_BUTTON_SELECT);} 154 | 155 | @wasm_export("keyupJoypad1Start") 156 | fn keyup_joypad1_start() { joypad::unpress(bus::joypad_1, joypad::JOYPAD_BUTTON_START);} 157 | 158 | @wasm_export("keyupJoypad1Up") 159 | fn keyup_joypad1_up() { joypad::unpress(bus::joypad_1, joypad::JOYPAD_BUTTON_UP);} 160 | 161 | @wasm_export("keyupJoypad1Down") 162 | fn keyup_joypad1_down() { joypad::unpress(bus::joypad_1, joypad::JOYPAD_BUTTON_DOWN);} 163 | 164 | @wasm_export("keyupJoypad1Left") 165 | fn keyup_joypad1_left() { joypad::unpress(bus::joypad_1, joypad::JOYPAD_BUTTON_LEFT);} 166 | 167 | @wasm_export("keyupJoypad1Right") 168 | fn keyup_joypad1_right() { joypad::unpress(bus::joypad_1, joypad::JOYPAD_BUTTON_RIGHT);} 169 | -------------------------------------------------------------------------------- /rom.mg: -------------------------------------------------------------------------------- 1 | import fmt "fmt"; 2 | import wasm "wasm"; 3 | import mem "mem"; 4 | 5 | let MIRRORING_VERTICAL: u8 = 0; 6 | let MIRRORING_HORIZONTAL: u8 = 1; 7 | let MIRRORING_FOUR_SCREEN: u8 = 2; 8 | 9 | let PRG_ROM_PAGE_SIZE: usize = 0x4000; 10 | let CHR_ROM_PAGE_SIZE: usize = 0x2000; 11 | 12 | let debug: bool = true; 13 | 14 | struct ROM { 15 | valid: bool, 16 | error: [*]u8, 17 | program: [*]u8, 18 | program_size: usize, 19 | characters: [*]u8, 20 | characters_size: usize, 21 | mirroring: u8, 22 | mapper: Mapper, 23 | } 24 | 25 | fn load(raw_bytes: [*]u8): ROM { 26 | if raw_bytes[0].* != 0x4e { 27 | return ROM{valid: false, error: "File is not a valid iNES file format"}; 28 | } 29 | if raw_bytes[1].* != 0x45 { 30 | return ROM{valid: false, error: "File is not a valid iNES file format"}; 31 | } 32 | if raw_bytes[2].* != 0x53 { 33 | return ROM{valid: false, error: "File is not a valid iNES file format"}; 34 | } 35 | if raw_bytes[3].* != 0x1a { 36 | return ROM{valid: false, error: "File is not a valid iNES file format"}; 37 | } 38 | 39 | let mapper_id = (raw_bytes[7].* & 0xf0) | (raw_bytes[6].* >> 4); 40 | if debug { 41 | fmt::print_str("program mapper = "); 42 | fmt::print_u8(mapper_id); 43 | fmt::print_str("\n"); 44 | } 45 | 46 | let mapper: Mapper; 47 | if mapper_id == 0 { 48 | mapper = mapper0; 49 | } else if mapper_id == 2 { 50 | mapper = mapper2; 51 | } else { 52 | return ROM{valid: false, error: "The rom uses mapper type that is not supported"}; 53 | } 54 | 55 | let ines_ver = (raw_bytes[7].* >> 2) & 0b11; 56 | if ines_ver != 0 { 57 | return ROM{valid: false, error: "NES2.0 format is not supported"}; 58 | } 59 | 60 | let four_screen = (raw_bytes[6].* & 0b1000) != 0; 61 | let vertical_mirroring = (raw_bytes[6].* & 0b1) != 0; 62 | let mirroring: u8; 63 | if four_screen { 64 | mirroring = MIRRORING_FOUR_SCREEN; 65 | if debug { 66 | fmt::print_str("mirroring=four screen\n"); 67 | } 68 | } else if vertical_mirroring { 69 | mirroring = MIRRORING_VERTICAL; 70 | if debug { 71 | fmt::print_str("mirroring=vertical\n"); 72 | } 73 | } else { 74 | mirroring = MIRRORING_HORIZONTAL; 75 | if debug { 76 | fmt::print_str("mirroring=horizontal\n"); 77 | } 78 | } 79 | 80 | let prg_rom_size = (raw_bytes[4].* as usize) * PRG_ROM_PAGE_SIZE; 81 | let chr_rom_size = (raw_bytes[5].* as usize) * CHR_ROM_PAGE_SIZE; 82 | 83 | if debug { 84 | fmt::print_str("program rom size = "); 85 | fmt::print_usize(prg_rom_size); 86 | fmt::print_str(", character rom size = "); 87 | fmt::print_usize(chr_rom_size); 88 | fmt::print_str("\n"); 89 | } 90 | 91 | let skip_trainer = (raw_bytes[6].* & 0b100) != 0; 92 | 93 | if debug { 94 | if skip_trainer { 95 | fmt::print_str("skip_trainer = true\n"); 96 | } else { 97 | fmt::print_str("skip_trainer = false\n"); 98 | } 99 | } 100 | 101 | let prg_start: usize = 16; 102 | if skip_trainer { 103 | prg_start = prg_start + 512; 104 | } 105 | let chr_start: usize = prg_start + prg_rom_size; 106 | 107 | return ROM{ 108 | valid: true, 109 | error: 0 as [*]u8, 110 | program: raw_bytes[prg_start] as [*]u8, 111 | program_size: prg_rom_size, 112 | characters: raw_bytes[chr_start] as [*]u8, 113 | characters_size: chr_rom_size, 114 | mirroring: mirroring, 115 | mapper: mapper, 116 | }; 117 | } 118 | 119 | fn reset(rom: *ROM) { 120 | rom.mapper.reset.*(rom); 121 | } 122 | 123 | fn read_program(rom: *ROM, addr: u16): u8 { 124 | return rom.mapper.read_prg.*(rom, addr); 125 | } 126 | 127 | fn write_program(rom: *ROM, addr: u16, data: u8) { 128 | rom.mapper.write_prg.*(rom, addr, data); 129 | } 130 | 131 | fn read_chr(rom: *ROM, addr: u16): u8 { 132 | return rom.mapper.read_chr.*(rom, addr); 133 | } 134 | 135 | fn write_chr(rom: *ROM, addr: u16, data: u8) { 136 | rom.mapper.write_chr.*(rom, addr, data); 137 | } 138 | 139 | let mapper0: Mapper = Mapper { 140 | id: 0, 141 | reset: mapper_0_reset, 142 | read_prg: mapper_0_read_prg, 143 | write_prg: mapper_0_write_prg, 144 | read_chr: mapper_0_read_chr, 145 | write_chr: mapper_0_write_chr, 146 | }; 147 | 148 | let mapper2: Mapper = Mapper { 149 | id: 2, 150 | reset: mapper_2_reset, 151 | read_prg: mapper_2_read_prg, 152 | write_prg: mapper_2_write_prg, 153 | read_chr: mapper_2_read_chr, 154 | write_chr: mapper_2_write_chr, 155 | }; 156 | 157 | struct Mapper { 158 | id: u8, 159 | reset: fn(rom: *ROM), 160 | read_prg: fn(rom: *ROM, addr: u16): u8, 161 | write_prg: fn(rom: *ROM, addr: u16, data: u8), 162 | read_chr: fn(rom: *ROM, addr: u16): u8, 163 | write_chr: fn(rom: *ROM, addr: u16, data: u8), 164 | } 165 | 166 | fn mapper_0_reset(rom: *ROM) { 167 | } 168 | 169 | fn mapper_0_read_prg(rom: *ROM, addr: u16): u8 { 170 | if (rom.program_size.* == 0x4000) && (addr >= 0x4000) { 171 | addr = addr & 0x3fff; 172 | } 173 | return rom.program.*[addr].*; 174 | } 175 | 176 | fn mapper_0_write_prg(rom: *ROM, addr: u16, data: u8) { 177 | fmt::print_str("invalid write to ROM program at "); 178 | fmt::print_u16(addr); 179 | fmt::print_str("\n"); 180 | } 181 | 182 | fn mapper_0_read_chr(rom: *ROM, addr: u16): u8 { 183 | return rom.characters.*[addr].*; 184 | } 185 | 186 | fn mapper_0_write_chr(rom: *ROM, addr: u16, data: u8) { 187 | rom.characters.*[addr].* = data; 188 | } 189 | 190 | // TODO: move this into local variable 191 | let mapper_2_selected_bank: u8 = 0; 192 | let fallback_chr_rom: [*]u8 = mem::alloc_array::(0x2000); 193 | 194 | fn mapper_2_reset(rom: *ROM) { 195 | mapper_2_selected_bank = 0; 196 | let i = 0; 197 | while i < 0x2000 { 198 | fallback_chr_rom[i].* = 0; 199 | i = i + 1; 200 | } 201 | } 202 | 203 | fn mapper_2_read_prg(rom: *ROM, addr: u16): u8 { 204 | if addr >= 0x4000 { 205 | return rom.program.*[rom.program_size.* - PRG_ROM_PAGE_SIZE + addr as usize - 0x4000].*; 206 | } 207 | 208 | return rom.program.*[mapper_2_selected_bank as usize * 0x4000 + addr as usize].*; 209 | } 210 | 211 | fn mapper_2_write_prg(rom: *ROM, addr: u16, data: u8) { 212 | mapper_2_selected_bank = data & 0x0f; 213 | } 214 | 215 | fn mapper_2_read_chr(rom: *ROM, addr: u16): u8 { 216 | if rom.characters_size.* < addr as usize { 217 | return fallback_chr_rom[addr].*; 218 | } 219 | return rom.characters.*[addr].*; 220 | } 221 | 222 | fn mapper_2_write_chr(rom: *ROM, addr: u16, data: u8) { 223 | if rom.characters_size.* < addr as usize { 224 | fallback_chr_rom[addr].* = data; 225 | return; 226 | } 227 | rom.characters.*[addr].* = data; 228 | } 229 | -------------------------------------------------------------------------------- /platform/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Magnes - Magelang NES Emulator 5 | 6 | 7 | 8 | 9 | 10 | 11 | 36 | 37 | 38 |
39 |
40 |

Magnes

41 |
 
42 |

NES (Nintendo Entertaiment System) Emulator written In Magelang

43 |
44 |
 
45 |
46 |
47 |
48 | 58 | 59 | 60 |
61 | 62 |
63 |

Controls

64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 |
KeyJoypad 1Joypad 2
LeftLeftNot Supported Yet
RightRightNot Supported Yet
UpUpNot Supported Yet
DownDownNot Supported Yet
StartEnterNot Supported Yet
SelectCtrlNot Supported Yet
AXNot Supported Yet
BZNot Supported Yet
115 |
116 |
117 |
118 |
119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 |
128 | 129 | 130 |
131 |
132 | 133 | 134 |
135 |
136 | 137 | 138 |
139 |
140 |
141 |
142 |
 
143 |
144 | Magnes, NES Emulator written by Jauhar Arifin. Source code available on Github. 145 |
146 |
147 | 148 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /platform/web/main.js: -------------------------------------------------------------------------------- 1 | window.onload = async function() { 2 | const ctx = canvas.getContext('2d') 3 | ctx.webkitImageSmoothingEnabled = false; 4 | ctx.mozImageSmoothingEnabled = false; 5 | ctx.imageSmoothingEnabled = false; 6 | 7 | const charDebugCtx = charTileCanvas.getContext('2d') 8 | charDebugCtx.webkitImageSmoothingEnabled = false; 9 | charDebugCtx.mozImageSmoothingEnabled = false; 10 | charDebugCtx.imageSmoothingEnabled = false; 11 | 12 | paletteCanvasCtx = paletteCanvas.getContext('2d') 13 | paletteCanvasCtx.webkitImageSmoothingEnabled = false 14 | paletteCanvasCtx.mozImageSmoothingEnabled = false 15 | paletteCanvasCtx.imageSmoothingEnabled = false 16 | 17 | nametable1Ctx = nametable1.getContext('2d') 18 | nametable1Ctx.webkitImageSmoothingEnabled = false 19 | nametable1Ctx.mozImageSmoothingEnabled = false 20 | nametable1Ctx.imageSmoothingEnabled = false 21 | 22 | nametable2Ctx = nametable2.getContext('2d') 23 | nametable2Ctx.webkitImageSmoothingEnabled = false 24 | nametable2Ctx.mozImageSmoothingEnabled = false 25 | nametable2Ctx.imageSmoothingEnabled = false 26 | 27 | nametable3Ctx = nametable3.getContext('2d') 28 | nametable3Ctx.webkitImageSmoothingEnabled = false 29 | nametable3Ctx.mozImageSmoothingEnabled = false 30 | nametable3Ctx.imageSmoothingEnabled = false 31 | 32 | nametable4Ctx = nametable4.getContext('2d') 33 | nametable4Ctx.webkitImageSmoothingEnabled = false 34 | nametable4Ctx.mozImageSmoothingEnabled = false 35 | nametable4Ctx.imageSmoothingEnabled = false 36 | 37 | preselectedRom.addEventListener('change', function(e) { 38 | romInput.disabled = e.target.value !== 'select-file' 39 | }) 40 | 41 | const resp = await fetch('/nes.wasm') 42 | const bytes = await resp.arrayBuffer() 43 | let memoryBuffer; 44 | let debuggingBuffer = ""; 45 | const module = await WebAssembly.instantiate(bytes, { 46 | wasi_snapshot_preview1: { 47 | fd_write: (fd, iovec, count, result) => { 48 | for (let i = 0; i < count; i++) { 49 | const buff = new Uint32Array(memoryBuffer, iovec + 2*i, 2); 50 | const p = buff[0] 51 | const len = buff[1] 52 | 53 | const stringBytes = new Uint8Array(memoryBuffer, p, len); 54 | const text = new TextDecoder().decode(stringBytes); 55 | debuggingBuffer += text 56 | const x = debuggingBuffer.indexOf("\n"); 57 | if (x > -1) { 58 | console.log(debuggingBuffer.substr(0, x)) 59 | debuggingBuffer = debuggingBuffer.substr(x + 1) 60 | } 61 | } 62 | } 63 | }, 64 | }) 65 | const { 66 | tick, 67 | memory, 68 | getRom, loadRom, 69 | getRam, 70 | reset, 71 | getFrameBuffer, 72 | debugCPU, 73 | getDebugTileFramebufer, 74 | getDebugPaletteImage, 75 | setDebugPaletteId, 76 | getScreenFramebuffer, 77 | getNametable1Framebuffer, getNametable2Framebuffer, getNametable3Framebuffer, getNametable4Framebuffer, 78 | keydownJoypad1A, keydownJoypad1B, keydownJoypad1Select, keydownJoypad1Start, keydownJoypad1Up, keydownJoypad1Down, keydownJoypad1Left, keydownJoypad1Right, 79 | keyupJoypad1A, keyupJoypad1B, keyupJoypad1Select, keyupJoypad1Start, keyupJoypad1Up, keyupJoypad1Down, keyupJoypad1Left, keyupJoypad1Right, 80 | } = module.instance.exports; 81 | memoryBuffer = memory.buffer 82 | 83 | window.setPalette = function(id) { 84 | setDebugPaletteId(id) 85 | } 86 | 87 | document.addEventListener('keyup', (event) => { 88 | const key = event.key.toLowerCase(); 89 | if (key === 'arrowup') 90 | keyupJoypad1Up(); 91 | else if (key === 'arrowright') 92 | keyupJoypad1Right(); 93 | else if (key === 'arrowleft') 94 | keyupJoypad1Left(); 95 | else if (key === 'arrowdown') 96 | keyupJoypad1Down(); 97 | else if (key === 'x') 98 | keyupJoypad1A(); 99 | else if (key === 'z') 100 | keyupJoypad1B(); 101 | else if (key === 'enter') 102 | keyupJoypad1Start(); 103 | else if (key === 'control') 104 | keyupJoypad1Select(); 105 | else 106 | return 107 | 108 | event.preventDefault() 109 | }); 110 | 111 | document.addEventListener('keydown', (event) => { 112 | const key = event.key.toLowerCase(); 113 | if (key === 'arrowup') 114 | keydownJoypad1Up(); 115 | else if (key === 'arrowright') 116 | keydownJoypad1Right(); 117 | else if (key === 'arrowleft') 118 | keydownJoypad1Left(); 119 | else if (key === 'arrowdown') 120 | keydownJoypad1Down(); 121 | else if (key === 'x') 122 | keydownJoypad1A(); 123 | else if (key === 'z') 124 | keydownJoypad1B(); 125 | else if (key === 'enter') 126 | keydownJoypad1Start(); 127 | else if (key === 'control') 128 | keydownJoypad1Select(); 129 | else 130 | return 131 | 132 | event.preventDefault() 133 | }); 134 | 135 | function getString(offset) { 136 | const buff = new Uint8Array(memoryBuffer); 137 | let length = 0; 138 | while (buff[offset + length] !== 0) { 139 | length++; 140 | } 141 | const stringBytes = new Uint8Array(memoryBuffer, offset, length); 142 | const text = new TextDecoder().decode(stringBytes); 143 | return text 144 | } 145 | 146 | function renderToCanvas(theCanvas, ctx, image) { 147 | const [framebuffer, width, height] = image; 148 | const pixels = new Uint8ClampedArray(memoryBuffer, framebuffer, width*height*4); 149 | const imageData = new ImageData(pixels, width, height); 150 | 151 | theCanvas.width = width; 152 | theCanvas.height = height; 153 | ctx.putImageData(imageData, 0, 0) 154 | } 155 | 156 | let playing = false 157 | let crashed = false 158 | let lastExecuted = Math.round(performance.now() * 1_000_000) 159 | function frame() { 160 | if (playing && !crashed) { 161 | const currentTime = Math.round(performance.now() * 1_000_000) 162 | const elapsed = currentTime - lastExecuted 163 | try { 164 | tick(BigInt(Math.round(elapsed))) 165 | } catch (e) { 166 | crashed = true; 167 | alert("The emulator is crashing. The browser will be reloaded") 168 | location.reload(true) 169 | } 170 | lastExecuted = currentTime 171 | 172 | const debugTile = getDebugTileFramebufer() 173 | renderToCanvas(charTileCanvas, charDebugCtx, debugTile) 174 | 175 | const palettes = getDebugPaletteImage() 176 | renderToCanvas(paletteCanvas, paletteCanvasCtx, [palettes[0], 33, 1]) 177 | 178 | const screen = getScreenFramebuffer(); 179 | renderToCanvas(canvas, ctx, screen) 180 | 181 | const nametable1Image = getNametable1Framebuffer() 182 | renderToCanvas(nametable1, nametable1Ctx, nametable1Image) 183 | 184 | const nametable2Image = getNametable2Framebuffer() 185 | renderToCanvas(nametable2, nametable2Ctx, nametable2Image) 186 | 187 | const nametable3Image = getNametable3Framebuffer() 188 | renderToCanvas(nametable3, nametable3Ctx, nametable3Image) 189 | 190 | const nametable4Image = getNametable4Framebuffer() 191 | renderToCanvas(nametable4, nametable4Ctx, nametable4Image) 192 | } 193 | 194 | requestAnimationFrame(frame) 195 | } 196 | requestAnimationFrame(frame) 197 | 198 | playButton.onclick = function() { 199 | document.activeElement.blur() 200 | 201 | const reader = new FileReader(); 202 | reader.onload = function(e) { 203 | const arrayBuffer = e.target.result; 204 | const byteArray = new Uint8Array(arrayBuffer); 205 | if (byteArray.length > 1073741824) { 206 | playing = false 207 | alert("ROM is too big") 208 | return 209 | } 210 | 211 | const offset = getRom() 212 | const target = new Uint8Array(memoryBuffer) 213 | target.set(byteArray, offset) 214 | 215 | const [resultIsValid, resultError] = loadRom(); 216 | if (!resultIsValid) { 217 | playing = false 218 | const message = getString(resultError); 219 | alert(message); 220 | } else { 221 | reset(); 222 | lastExecuted = performance.now() * 1_000_000; 223 | playing = true; 224 | } 225 | } 226 | 227 | if (preselectedRom.value === 'select-file') { 228 | if (romInput.files.length === 0) { 229 | alert("Missing ROM file"); 230 | return 231 | } 232 | const file = romInput.files[0]; 233 | reader.readAsArrayBuffer(file); 234 | } else { 235 | fetch(preselectedRom.value).then(res => res.blob()).then(blob => { 236 | reader.readAsArrayBuffer(blob); 237 | }) 238 | } 239 | } 240 | 241 | }; 242 | -------------------------------------------------------------------------------- /mem.mg: -------------------------------------------------------------------------------- 1 | import wasm "wasm"; 2 | 3 | let lower_bound: usize = 0; 4 | let upper_bound: usize = 0; 5 | let page_size: usize = 65536; 6 | let freelist_head: *Header = 0 as *Header; 7 | 8 | fn alloc(): *T { 9 | return alloc_size(wasm::size_of::()) as *T; 10 | } 11 | 12 | fn dealloc(p: *T) { 13 | dealloc_from_ptr(p as usize); 14 | } 15 | 16 | fn alloc_array(len: usize): [*]T { 17 | return alloc_size(wasm::size_of::() * len) as [*]T; 18 | } 19 | 20 | fn dealloc_array(p: [*]T) { 21 | dealloc_from_ptr(p as usize); 22 | } 23 | 24 | struct Header{ 25 | size: usize, 26 | prev: usize, 27 | next: usize, 28 | is_used: bool, 29 | } 30 | 31 | let header_size: usize = wasm::size_of::
(); 32 | 33 | // memory layout: 34 | // - Header: (aligned to 8 byte) 35 | // - size 36 | // - next 37 | // - is_used 38 | // - padding 39 | // - content (aligned to 8 byte) 40 | // - padding 41 | // - Footer (aligned to 8 byte) 42 | // - size 43 | // - is_used 44 | // - padding 45 | 46 | struct Footer{ 47 | size: usize, 48 | is_used: bool, 49 | } 50 | 51 | let footer_size: usize = wasm::size_of::