Compare commits
8 Commits
6140db0d8f
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 745ee7cecb | |||
| 68737e12da | |||
| 85e8481cf4 | |||
| ec8bcdb5ed | |||
| 747702d256 | |||
| 717ef479fa | |||
| 5edae5d2d8 | |||
| d43ec53d8d |
@@ -2,5 +2,10 @@
|
|||||||
*.bin
|
*.bin
|
||||||
*.o
|
*.o
|
||||||
|
|
||||||
|
# Artifact output directory
|
||||||
|
build/
|
||||||
|
|
||||||
# Project management
|
# Project management
|
||||||
todo
|
todo
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,17 @@
|
|||||||
|
|
||||||
A CHIP-8 emulator / simulator written in [Odin](https://odin-lang.org/) using [Raylib](https://www.raylib.com/).
|
A CHIP-8 emulator / simulator written in [Odin](https://odin-lang.org/) using [Raylib](https://www.raylib.com/).
|
||||||
|
|
||||||

|
[](showcase.png)
|
||||||
|
|
||||||
|
**[Try it in your browser →](https://jasonhilder.dev/sim)**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
This was my refresher project to get back into lower-level programming — specifically the [Odin](https://odin-lang.org/) language and [Raylib](https://www.raylib.com/). Building a CHIP-8 emulator felt like the right scope: small enough to actually finish, but with enough surface area (opcode decoding, memory, timers, a display, input) to shake the rust off.
|
||||||
|
|
||||||
|
It's not perfect, but it's functioning and usable for the most part. Compiles to native and to WebAssembly, and the web build is playable directly at the link above.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -13,6 +23,17 @@ A CHIP-8 emulator / simulator written in [Odin](https://odin-lang.org/) using [R
|
|||||||
- Built-in collection of classic game ROMs (Pong, Tetris, Space Invaders, Brix, and more)
|
- Built-in collection of classic game ROMs (Pong, Tetris, Space Invaders, Brix, and more)
|
||||||
- Load your own ROMs via the file loader panel
|
- Load your own ROMs via the file loader panel
|
||||||
- Dev and release build modes via `make`
|
- Dev and release build modes via `make`
|
||||||
|
- Compiles to WebAssembly for running in-browser
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
Future work, mainly once I'm back in the headspace for it:
|
||||||
|
|
||||||
|
- General refactor/cleanup pass now that the core is working
|
||||||
|
- CHIP-8 quirks support (configurable behavior differences between interpreters)
|
||||||
|
- SUPER-CHIP instruction support
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Executable
+38
@@ -0,0 +1,38 @@
|
|||||||
|
#!/bin/bash -eu
|
||||||
|
|
||||||
|
# Point this to where you installed emscripten. Optional on systems that already
|
||||||
|
# have `emcc` in the path.
|
||||||
|
EMSCRIPTEN_SDK_DIR="$HOME/Probe/emsdk"
|
||||||
|
OUT_DIR="build/web"
|
||||||
|
|
||||||
|
mkdir -p $OUT_DIR
|
||||||
|
|
||||||
|
export EMSDK_QUIET=1
|
||||||
|
[[ -f "$EMSCRIPTEN_SDK_DIR/emsdk_env.sh" ]] && . "$EMSCRIPTEN_SDK_DIR/emsdk_env.sh"
|
||||||
|
|
||||||
|
# Note RAYLIB_WASM_LIB=env.o -- env.o is an internal WASM object file. You can
|
||||||
|
# see how RAYLIB_WASM_LIB is used inside <odin>/vendor/raylib/raylib.odin.
|
||||||
|
#
|
||||||
|
# The emcc call will be fed the actual raylib library file. That stuff will end
|
||||||
|
# up in env.o
|
||||||
|
#
|
||||||
|
# Note that there is a rayGUI equivalent: -define:RAYGUI_WASM_LIB=env.o
|
||||||
|
# odin build src/main_web -target:js_wasm32 -build-mode:obj -define:RAYLIB_WASM_LIB=env.o -define:RAYGUI_WASM_LIB=env.o -vet -strict-style -out:$OUT_DIR/game.wasm.o
|
||||||
|
odin build src/main_web -target:js_wasm32 -build-mode:obj -define:RAYLIB_WASM_LIB=env.o -define:RAYGUI_WASM_LIB=env.o -out:$OUT_DIR/game.wasm.o
|
||||||
|
|
||||||
|
ODIN_PATH=$(odin root)
|
||||||
|
|
||||||
|
cp $ODIN_PATH/core/sys/wasm/js/odin.js $OUT_DIR
|
||||||
|
|
||||||
|
files="$OUT_DIR/game.wasm.o ${ODIN_PATH}/vendor/raylib/wasm/libraylib.a ${ODIN_PATH}/vendor/raylib/wasm/libraygui.a"
|
||||||
|
|
||||||
|
# index_template.html contains the javascript code that calls the procedures in
|
||||||
|
# src/main_web/main_web.odin
|
||||||
|
flags="-sEXPORTED_RUNTIME_METHODS=['HEAPF32'] -sSTACK_SIZE=524288 -sUSE_GLFW=3 -sWASM_BIGINT -sWARN_ON_UNDEFINED_SYMBOLS=0 -sASSERTIONS --shell-file src/main_web/index_template.html --preload-file assets"
|
||||||
|
|
||||||
|
# For debugging: Add `-g` to `emcc` (gives better error callstack in chrome)
|
||||||
|
emcc -o $OUT_DIR/index.html $files $flags
|
||||||
|
|
||||||
|
rm $OUT_DIR/game.wasm.o
|
||||||
|
|
||||||
|
echo "Web build created in ${OUT_DIR}"
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#+build !js
|
||||||
package tinyfiledialogs
|
package tinyfiledialogs
|
||||||
|
|
||||||
import "base:builtin"
|
import "base:builtin"
|
||||||
|
|||||||
+12
-11
@@ -1,7 +1,5 @@
|
|||||||
package machine
|
package machine
|
||||||
|
|
||||||
import "core:log"
|
|
||||||
|
|
||||||
// System struct, init, constants, fontset
|
// System struct, init, constants, fontset
|
||||||
|
|
||||||
System :: struct {
|
System :: struct {
|
||||||
@@ -48,7 +46,7 @@ FONT_SET := [80]u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init :: proc() -> System {
|
init :: proc() -> System {
|
||||||
log.info("Booting chip 8 cpu")
|
// log.info("Booting chip 8 cpu")
|
||||||
|
|
||||||
// Structs are zero initialized so timers, sp etc are good.
|
// Structs are zero initialized so timers, sp etc are good.
|
||||||
s := System { pc = 0x200 }
|
s := System { pc = 0x200 }
|
||||||
@@ -69,17 +67,20 @@ run_machine :: proc(s: ^System, cycles: int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new_machine :: proc() -> System {
|
reset_machine :: proc(s: ^System) {
|
||||||
s: System
|
s.memory = {}
|
||||||
|
s.v = {}
|
||||||
|
s.stack = {}
|
||||||
|
s.sp = 0
|
||||||
|
s.i = 0
|
||||||
s.pc = 0x200
|
s.pc = 0x200
|
||||||
|
s.display = {}
|
||||||
|
s.keypad = {}
|
||||||
s.current_key = -1
|
s.current_key = -1
|
||||||
// load fonts into the memory
|
s.delay_timer = 0
|
||||||
|
s.sound_timer = 0
|
||||||
|
|
||||||
for v, i in FONT_SET {
|
for v, i in FONT_SET {
|
||||||
s.memory[i] = v
|
s.memory[i] = v
|
||||||
}
|
}
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
reset_machine :: proc(s: ^System) {
|
|
||||||
s^ = new_machine()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#+build !js
|
||||||
package machine
|
package machine
|
||||||
|
|
||||||
import "core:log"
|
import "core:log"
|
||||||
|
|||||||
@@ -34,10 +34,10 @@ main :: proc() {
|
|||||||
sim.init(&s)
|
sim.init(&s)
|
||||||
|
|
||||||
for sim.should_run() {
|
for sim.should_run() {
|
||||||
sim.update(&s)
|
sim.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
sim.shutdown(&s)
|
sim.shutdown()
|
||||||
|
|
||||||
when DEV {
|
when DEV {
|
||||||
if len(track.allocation_map) > 0 {
|
if len(track.allocation_map) > 0 {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
background-color: black;
|
background-color: black;
|
||||||
}
|
}
|
||||||
canvas.game_canvas {
|
canvas.game_canvas {
|
||||||
|
max-width: 1550px;
|
||||||
border: 0px none;
|
border: 0px none;
|
||||||
background-color: black;
|
background-color: black;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ main_start :: proc "c" () {
|
|||||||
// emscripten. There is some kind of conflict with how the manage memory.
|
// emscripten. There is some kind of conflict with how the manage memory.
|
||||||
// So this sets up an allocator that uses emscripten's malloc.
|
// So this sets up an allocator that uses emscripten's malloc.
|
||||||
context.allocator = emscripten_allocator()
|
context.allocator = emscripten_allocator()
|
||||||
runtime.init_global_temporary_allocator(1*mem.Megabyte)
|
runtime.init_global_temporary_allocator(4*mem.Megabyte)
|
||||||
|
|
||||||
// Since we now use js_wasm32 we should be able to remove this and use
|
// Since we now use js_wasm32 we should be able to remove this and use
|
||||||
// context.logger = log.create_console_logger(). However, that one produces
|
// context.logger = log.create_console_logger(). However, that one produces
|
||||||
@@ -30,25 +30,25 @@ main_start :: proc "c" () {
|
|||||||
|
|
||||||
web_context = context
|
web_context = context
|
||||||
|
|
||||||
// Init the emu 8 "cpu"
|
system := new(emu.System)
|
||||||
system := emu.init()
|
system^ = emu.init()
|
||||||
s := sim.Simulator {
|
|
||||||
machine = &system,
|
s := new(sim.Simulator)
|
||||||
|
s^ = sim.Simulator {
|
||||||
|
machine = system,
|
||||||
rom_loaded = false,
|
rom_loaded = false,
|
||||||
paused = true,
|
paused = true,
|
||||||
step = false,
|
step = false,
|
||||||
cpu_hz = 700,
|
cpu_hz = 700,
|
||||||
disasm_follow = true,
|
disasm_follow = true,
|
||||||
}
|
}
|
||||||
|
sim.init(s)
|
||||||
sim.init(&s)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@export
|
@export
|
||||||
main_update :: proc "c" () -> bool {
|
main_update :: proc "c" () -> bool {
|
||||||
context = web_context
|
context = web_context
|
||||||
|
|
||||||
// TODO
|
|
||||||
sim.update()
|
sim.update()
|
||||||
return sim.should_run()
|
return sim.should_run()
|
||||||
}
|
}
|
||||||
@@ -56,13 +56,10 @@ main_update :: proc "c" () -> bool {
|
|||||||
@export
|
@export
|
||||||
main_end :: proc "c" () {
|
main_end :: proc "c" () {
|
||||||
context = web_context
|
context = web_context
|
||||||
// TODO
|
|
||||||
sim.shutdown()
|
sim.shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
@export
|
@export
|
||||||
web_window_size_changed :: proc "c" (w: c.int, h: c.int) {
|
web_window_size_changed :: proc "c" (w: c.int, h: c.int) {
|
||||||
context = web_context
|
context = web_context
|
||||||
// TODO
|
|
||||||
game.parent_window_size_changed(int(w), int(h))
|
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-15
@@ -3,8 +3,7 @@ package simulator
|
|||||||
import emu "../machine"
|
import emu "../machine"
|
||||||
import rl "vendor:raylib"
|
import rl "vendor:raylib"
|
||||||
|
|
||||||
// Globals
|
sim_run: bool
|
||||||
run: bool
|
|
||||||
|
|
||||||
// Window
|
// Window
|
||||||
WINDOW_WIDTH :: 1920
|
WINDOW_WIDTH :: 1920
|
||||||
@@ -42,21 +41,31 @@ Layout :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init :: proc(sim: ^Simulator) {
|
init :: proc(sim: ^Simulator) {
|
||||||
run = true
|
_sim = sim
|
||||||
|
sim_run = true
|
||||||
|
|
||||||
|
// desktop
|
||||||
|
when ODIN_OS != .JS {
|
||||||
rl.SetConfigFlags({.WINDOW_RESIZABLE})
|
rl.SetConfigFlags({.WINDOW_RESIZABLE})
|
||||||
|
rl.SetTargetFPS(60)
|
||||||
|
}
|
||||||
|
|
||||||
|
// web
|
||||||
|
when ODIN_OS == .JS {
|
||||||
|
// rl.SetConfigFlags({.VSYNC_HINT})
|
||||||
|
}
|
||||||
|
|
||||||
rl.InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Octal Cookie - Chip 8 Simulator")
|
rl.InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Octal Cookie - Chip 8 Simulator")
|
||||||
rl.InitAudioDevice()
|
rl.InitAudioDevice()
|
||||||
rl.SetTargetFPS(60)
|
|
||||||
|
|
||||||
// Load sound
|
// Load sound
|
||||||
beep := rl.LoadSound("./assets/sounds/beep.wav")
|
beep := rl.LoadSound("./assets/sounds/beep.wav")
|
||||||
sim.sound = beep
|
_sim.sound = beep
|
||||||
|
|
||||||
// Load fonts
|
// Load fonts
|
||||||
font := rl.LoadFontEx("./assets/fonts/Inter_18pt-Regular.ttf", 18, nil, 0)
|
font := rl.LoadFontEx("./assets/fonts/Inter_18pt-Regular.ttf", 18, nil, 0)
|
||||||
rl.SetTextureFilter(font.texture, .BILINEAR)
|
rl.SetTextureFilter(font.texture, .BILINEAR)
|
||||||
sim.font = font
|
_sim.font = font
|
||||||
|
|
||||||
rl.GuiLoadStyleDefault()
|
rl.GuiLoadStyleDefault()
|
||||||
rl.GuiLoadStyle("./assets/raygui_styles/genesis.rgs")
|
rl.GuiLoadStyle("./assets/raygui_styles/genesis.rgs")
|
||||||
@@ -65,7 +74,9 @@ init :: proc(sim: ^Simulator) {
|
|||||||
rl.GuiSetStyle(.DEFAULT, i32(rl.GuiDefaultProperty.TEXT_SIZE), 18)
|
rl.GuiSetStyle(.DEFAULT, i32(rl.GuiDefaultProperty.TEXT_SIZE), 18)
|
||||||
}
|
}
|
||||||
|
|
||||||
update :: proc(sim: ^Simulator) {
|
update :: proc() {
|
||||||
|
sim := _sim
|
||||||
|
|
||||||
// Recalculate layout each frame based on current window size
|
// Recalculate layout each frame based on current window size
|
||||||
// Pass these down to gui functions so they can setup their sizes?
|
// Pass these down to gui functions so they can setup their sizes?
|
||||||
screen_width := f32(rl.GetScreenWidth())
|
screen_width := f32(rl.GetScreenWidth())
|
||||||
@@ -78,8 +89,8 @@ update :: proc(sim: ^Simulator) {
|
|||||||
rl.BeginDrawing()
|
rl.BeginDrawing()
|
||||||
rl.ClearBackground(rl.Color{0x18, 0x18, 0x18, 0xFF})
|
rl.ClearBackground(rl.Color{0x18, 0x18, 0x18, 0xFF})
|
||||||
|
|
||||||
cycles := int(sim.cpu_hz / SIM_FPS)
|
|
||||||
if (!sim.paused) {
|
if (!sim.paused) {
|
||||||
|
cycles := int(sim.cpu_hz / SIM_FPS)
|
||||||
// Cycle the machine to update memory etc
|
// Cycle the machine to update memory etc
|
||||||
emu.run_machine(sim.machine, cycles)
|
emu.run_machine(sim.machine, cycles)
|
||||||
tick_timers(sim)
|
tick_timers(sim)
|
||||||
@@ -119,9 +130,11 @@ update :: proc(sim: ^Simulator) {
|
|||||||
gui_info_box(layout.info_box, sim, screen_width, screen_height)
|
gui_info_box(layout.info_box, sim, screen_width, screen_height)
|
||||||
|
|
||||||
rl.EndDrawing()
|
rl.EndDrawing()
|
||||||
|
free_all(context.temp_allocator)
|
||||||
}
|
}
|
||||||
|
|
||||||
shutdown :: proc(sim: ^Simulator) {
|
shutdown :: proc() {
|
||||||
|
sim := _sim
|
||||||
rl.UnloadFont(sim.font)
|
rl.UnloadFont(sim.font)
|
||||||
rl.UnloadSound(sim.sound)
|
rl.UnloadSound(sim.sound)
|
||||||
rl.CloseAudioDevice()
|
rl.CloseAudioDevice()
|
||||||
@@ -130,12 +143,10 @@ shutdown :: proc(sim: ^Simulator) {
|
|||||||
|
|
||||||
should_run :: proc() -> bool {
|
should_run :: proc() -> bool {
|
||||||
when ODIN_OS != .JS {
|
when ODIN_OS != .JS {
|
||||||
if rl.WindowShouldClose() {
|
return !rl.WindowShouldClose()
|
||||||
run = false
|
|
||||||
}
|
}
|
||||||
}
|
// web loop is controlled by JS requestAnimationFrame
|
||||||
|
return true
|
||||||
return run
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tick_timers :: proc(sim: ^Simulator) {
|
tick_timers :: proc(sim: ^Simulator) {
|
||||||
@@ -146,7 +157,8 @@ tick_timers :: proc(sim: ^Simulator) {
|
|||||||
sim.machine.sound_timer -= 1
|
sim.machine.sound_timer -= 1
|
||||||
if !rl.IsSoundPlaying(beep) do rl.PlaySound(beep)
|
if !rl.IsSoundPlaying(beep) do rl.PlaySound(beep)
|
||||||
} else {
|
} else {
|
||||||
rl.StopSound(beep)
|
// only stop if actually playing
|
||||||
|
if rl.IsSoundPlaying(beep) do rl.StopSound(beep)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package simulator
|
package simulator
|
||||||
|
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:log"
|
|
||||||
|
|
||||||
import emu "../machine"
|
|
||||||
import rl "vendor:raylib"
|
import rl "vendor:raylib"
|
||||||
|
|
||||||
gui_control_bar :: proc(rect: rl.Rectangle, sim: ^Simulator) {
|
gui_control_bar :: proc(rect: rl.Rectangle, sim: ^Simulator) {
|
||||||
@@ -19,7 +17,7 @@ gui_control_bar :: proc(rect: rl.Rectangle, sim: ^Simulator) {
|
|||||||
if sim.rom_loaded {
|
if sim.rom_loaded {
|
||||||
sim.paused = false
|
sim.paused = false
|
||||||
} else {
|
} else {
|
||||||
log.info("no rom selected, can't run")
|
// log.info("no rom selected, can't run")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,9 +30,7 @@ gui_control_bar :: proc(rect: rl.Rectangle, sim: ^Simulator) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if btn(&cursor, rect, BUTTON_HEIGHT, BUTTON_WIDTH, PADDING_X, "RESET") {
|
if btn(&cursor, rect, BUTTON_HEIGHT, BUTTON_WIDTH, PADDING_X, "RESET") {
|
||||||
sim.paused = true
|
reset_sim(sim)
|
||||||
sim.disasm_count = 0
|
|
||||||
emu.reset_machine(sim.machine)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
slider_rect := rl.Rectangle{
|
slider_rect := rl.Rectangle{
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
#+build !js
|
||||||
package simulator
|
package simulator
|
||||||
|
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:log"
|
|
||||||
import rl "vendor:raylib"
|
import rl "vendor:raylib"
|
||||||
|
|
||||||
import emu "../machine"
|
import emu "../machine"
|
||||||
@@ -59,10 +59,10 @@ gui_file_loader :: proc(rect: rl.Rectangle, sim: ^Simulator) {
|
|||||||
mouse := rl.GetMousePosition()
|
mouse := rl.GetMousePosition()
|
||||||
|
|
||||||
if rl.CheckCollisionPointRec(mouse, drop_zone) {
|
if rl.CheckCollisionPointRec(mouse, drop_zone) {
|
||||||
path_str := string(dropped_file.paths[0])
|
// path_str := string(dropped_file.paths[0])
|
||||||
log.info("file dropped: ", path_str)
|
// log.info("file dropped: ", path_str)
|
||||||
} else {
|
} else {
|
||||||
log.info("File dropped outside drop zone, ignoring")
|
// log.info("File dropped outside drop zone, ignoring")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rl.UnloadDroppedFiles(dropped_file)
|
rl.UnloadDroppedFiles(dropped_file)
|
||||||
@@ -135,22 +135,22 @@ gui_file_loader :: proc(rect: rl.Rectangle, sim: ^Simulator) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
load_selected_rom :: proc(sim: ^Simulator, rom_path: string) {
|
load_selected_rom :: proc(sim: ^Simulator, rom_path: string) {
|
||||||
emu.reset_machine(sim.machine)
|
reset_sim(sim)
|
||||||
rom_size, err := emu.load_rom(sim.machine, rom_path)
|
rom_size, err := emu.load_rom(sim.machine, rom_path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// @TODO: update status bar here
|
// @TODO: update status bar here
|
||||||
panic("failed to load rom!")
|
panic("failed to load rom!")
|
||||||
}
|
}
|
||||||
|
|
||||||
sim.rom_loaded = true
|
sim.rom_loaded = true
|
||||||
sim.paused = true
|
|
||||||
sim.disasm_count = disassemble_rom_n(sim.machine.memory[:], 0x200, 0x200 + u16(rom_size), sim.disasm[:])
|
sim.disasm_count = disassemble_rom_n(sim.machine.memory[:], 0x200, 0x200 + u16(rom_size), sim.disasm[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
load_embedded_rom :: proc(sim: ^Simulator, path: string, data: []byte) {
|
load_embedded_rom :: proc(sim: ^Simulator, path: string, data: []byte) {
|
||||||
emu.reset_machine(sim.machine)
|
reset_sim(sim)
|
||||||
|
|
||||||
copy(sim.machine.memory[0x200:], data)
|
copy(sim.machine.memory[0x200:], data)
|
||||||
sim.rom_loaded = true
|
sim.rom_loaded = true
|
||||||
sim.paused = true
|
|
||||||
sim.disasm_count = disassemble_rom_n(sim.machine.memory[:], 0x200, 0x200 + u16(len(data)), sim.disasm[:])
|
sim.disasm_count = disassemble_rom_n(sim.machine.memory[:], 0x200, 0x200 + u16(len(data)), sim.disasm[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
#+build js
|
||||||
|
package simulator
|
||||||
|
|
||||||
|
import "core:strings"
|
||||||
|
import "core:fmt"
|
||||||
|
import rl "vendor:raylib"
|
||||||
|
|
||||||
|
import emu "../machine"
|
||||||
|
|
||||||
|
gui_file_loader :: proc(rect: rl.Rectangle, sim: ^Simulator) {
|
||||||
|
rl.DrawRectangleLinesEx(rect, 1, rl.GRAY)
|
||||||
|
|
||||||
|
// Embedded Rom Section:
|
||||||
|
// ---------------------------------------------
|
||||||
|
bottom_bounds := rl.Rectangle {
|
||||||
|
x = rect.x,
|
||||||
|
y = rect.y,
|
||||||
|
width = rect.width - (PADDING_X * 2),
|
||||||
|
height = rect.height - (PADDING_X * 2)
|
||||||
|
}
|
||||||
|
rl.GuiPanel(bottom_bounds, "ROM List")
|
||||||
|
|
||||||
|
panel_rect := rl.Rectangle {
|
||||||
|
x = bottom_bounds.x,
|
||||||
|
y = bottom_bounds.y + PANEL_HEADER,
|
||||||
|
width = rect.width - (PADDING_X * 2),
|
||||||
|
height = rect.height - (PADDING_X * 2) - PANEL_HEADER
|
||||||
|
}
|
||||||
|
|
||||||
|
content_rect := rl.Rectangle {
|
||||||
|
x = panel_rect.x,
|
||||||
|
y = panel_rect.y - PANEL_HEADER,
|
||||||
|
width = panel_rect.width - 15,
|
||||||
|
height = f32(len(roms)) * LINE_HEIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
view: rl.Rectangle
|
||||||
|
rl.GuiScrollPanel(panel_rect, nil, content_rect, &sim.rompick_scroll, &view)
|
||||||
|
|
||||||
|
rl.BeginScissorMode(i32(view.x), i32(view.y), i32(view.width), i32(view.height))
|
||||||
|
defer rl.EndScissorMode()
|
||||||
|
|
||||||
|
for path, index in roms {
|
||||||
|
y_pos := panel_rect.y + f32(index * LINE_HEIGHT) + sim.rompick_scroll.y
|
||||||
|
|
||||||
|
row_rect := rl.Rectangle {
|
||||||
|
x = view.x,
|
||||||
|
y = y_pos,
|
||||||
|
width = view.width,
|
||||||
|
height = LINE_HEIGHT,
|
||||||
|
}
|
||||||
|
|
||||||
|
// make selected visible
|
||||||
|
if path.name == sim.selected_rom {
|
||||||
|
rl.DrawRectangleRec(row_rect, rl.ColorAlpha(rl.SKYBLUE, 0.25))
|
||||||
|
}
|
||||||
|
|
||||||
|
// hover rows
|
||||||
|
mouse := rl.GetMousePosition()
|
||||||
|
if rl.CheckCollisionPointRec(mouse, row_rect) {
|
||||||
|
rl.DrawRectangleRec(row_rect, rl.ColorAlpha(rl.WHITE, 0.08))
|
||||||
|
if rl.IsMouseButtonPressed(.LEFT) {
|
||||||
|
sim.selected_rom = path.name
|
||||||
|
load_embedded_rom(sim, path.name, roms[index].data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
txt := fmt.tprintf("%v: %v", (index + 1), path.name)
|
||||||
|
rl.DrawTextEx(
|
||||||
|
sim.font,
|
||||||
|
strings.clone_to_cstring(txt, context.temp_allocator),
|
||||||
|
{panel_rect.x + PADDING_X + sim.rompick_scroll.x, y_pos + (LINE_HEIGHT - 18) * 0.5},
|
||||||
|
18, 1,
|
||||||
|
rl.WHITE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
load_embedded_rom :: proc(sim: ^Simulator, path: string, data: []byte) {
|
||||||
|
reset_sim(sim)
|
||||||
|
copy(sim.machine.memory[0x200:], data)
|
||||||
|
sim.rom_loaded = true
|
||||||
|
sim.disasm_count = disassemble_rom_n(sim.machine.memory[:], 0x200, 0x200 + u16(len(data)), sim.disasm[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
disassemble_rom_n :: proc(memory: []u8, start_addr: u16, end_addr: u16, out: []Instruction) -> int {
|
||||||
|
count := 0
|
||||||
|
i := start_addr
|
||||||
|
|
||||||
|
for i < end_addr {
|
||||||
|
if int(i) + 1 >= len(memory) do break
|
||||||
|
if count >= len(out) do break
|
||||||
|
|
||||||
|
addr := i
|
||||||
|
high_byte := memory[i]
|
||||||
|
low_byte := memory[i + 1]
|
||||||
|
i += 2
|
||||||
|
|
||||||
|
// Get full opcode from the 2 bytes
|
||||||
|
opcode := (u16(high_byte) << 8) | u16(low_byte)
|
||||||
|
|
||||||
|
// Pre-decode common components of opcodes
|
||||||
|
first_nibble := (opcode & 0xF000) >> 12
|
||||||
|
vx := (opcode & 0x0F00) >> 8 // A 4-bit value, the lower 4 bits of the high byte of the instruction
|
||||||
|
vy := (opcode & 0x00F0) >> 4 // A 4-bit value, the upper 4 bits of the low byte of the instruction
|
||||||
|
last_nibble := (opcode & 0x000F) // A 4-bit value, the lowest 4 bits of the instruction
|
||||||
|
kk := u8(opcode & 0x00FF) // An 8-bit value, the lowest 8 bits of the instruction
|
||||||
|
nnn := (opcode & 0x0FFF) // A 12-bit value, the lowest 12 bits of the instruction
|
||||||
|
|
||||||
|
|
||||||
|
instruction := &out[count]
|
||||||
|
instruction.address = addr
|
||||||
|
instruction.raw = opcode
|
||||||
|
|
||||||
|
switch first_nibble {
|
||||||
|
case 0x0:
|
||||||
|
switch opcode {
|
||||||
|
//case 0x00E0: str = "CLS"
|
||||||
|
case 0x00E0: fmt.bprintf(instruction.str[:], "CLS")
|
||||||
|
case 0x00EE: fmt.bprintf(instruction.str[:], "RET")
|
||||||
|
case: fmt.bprintf(instruction.str[:], "DATA 0x%04X", opcode)
|
||||||
|
}
|
||||||
|
case 0x1: fmt.bprintf(instruction.str[:], "JP 0x%03X", nnn)
|
||||||
|
case 0x2: fmt.bprintf(instruction.str[:], "CALL 0x%03X", nnn)
|
||||||
|
case 0x3: fmt.bprintf(instruction.str[:], "SE V%X, 0x%02X", vx, kk)
|
||||||
|
case 0x4: fmt.bprintf(instruction.str[:], "SNE V%X, 0x%02X", vx, kk)
|
||||||
|
case 0x5: fmt.bprintf(instruction.str[:], "SE V%X, V%X", vx, vy)
|
||||||
|
case 0x6: fmt.bprintf(instruction.str[:], "LD V%X, 0x%02X", vx, kk)
|
||||||
|
case 0x7: fmt.bprintf(instruction.str[:], "ADD V%X, 0x%02X", vx, kk)
|
||||||
|
case 0x8:
|
||||||
|
switch last_nibble {
|
||||||
|
case 0x0: fmt.bprintf(instruction.str[:], "LD V%X, V%X", vx, vy)
|
||||||
|
case 0x1: fmt.bprintf(instruction.str[:], "OR V%X, V%X", vx, vy)
|
||||||
|
case 0x2: fmt.bprintf(instruction.str[:], "AND V%X, V%X", vx, vy)
|
||||||
|
case 0x3: fmt.bprintf(instruction.str[:], "XOR V%X, V%X", vx, vy)
|
||||||
|
case 0x4: fmt.bprintf(instruction.str[:], "ADD V%X, V%X", vx, vy)
|
||||||
|
case 0x5: fmt.bprintf(instruction.str[:], "SUB V%X, V%X", vx, vy)
|
||||||
|
case 0x6: fmt.bprintf(instruction.str[:], "SHR V%X", vx)
|
||||||
|
case 0x7: fmt.bprintf(instruction.str[:], "SUBN V%X, V%X", vx, vy)
|
||||||
|
case 0xE: fmt.bprintf(instruction.str[:], "SHL V%X", vx)
|
||||||
|
case: fmt.bprintf(instruction.str[:], "DATA 0x%04X", opcode)
|
||||||
|
}
|
||||||
|
case 0x9: fmt.bprintf(instruction.str[:], "SNE V%X, V%X", vx, vy)
|
||||||
|
case 0xA: fmt.bprintf(instruction.str[:], "LD I, 0x%03X", nnn)
|
||||||
|
case 0xB: fmt.bprintf(instruction.str[:], "JP V0, 0x%03X", nnn)
|
||||||
|
case 0xC: fmt.bprintf(instruction.str[:], "RND V%X, 0x%02X", vx, kk)
|
||||||
|
case 0xD: fmt.bprintf(instruction.str[:], "DRW V%X, V%X, %X", vx, vy, last_nibble)
|
||||||
|
case 0xE:
|
||||||
|
switch kk {
|
||||||
|
case 0x9E: fmt.bprintf(instruction.str[:], "SKP V%X", vx)
|
||||||
|
case 0xA1: fmt.bprintf(instruction.str[:], "SKNP V%X", vx)
|
||||||
|
case: fmt.bprintf(instruction.str[:], "DATA 0x%04X", opcode)
|
||||||
|
}
|
||||||
|
case 0xF:
|
||||||
|
switch kk {
|
||||||
|
case 0x07: fmt.bprintf(instruction.str[:], "LD V%X, DT", vx)
|
||||||
|
case 0x0A: fmt.bprintf(instruction.str[:], "LD V%X, K", vx)
|
||||||
|
case 0x15: fmt.bprintf(instruction.str[:], "LD DT, V%X", vx)
|
||||||
|
case 0x18: fmt.bprintf(instruction.str[:], "LD ST, V%X", vx)
|
||||||
|
case 0x1E: fmt.bprintf(instruction.str[:], "ADD I, V%X", vx)
|
||||||
|
case 0x29: fmt.bprintf(instruction.str[:], "LD F, V%X", vx)
|
||||||
|
case 0x33: fmt.bprintf(instruction.str[:], "LD B, V%X", vx)
|
||||||
|
case 0x55: fmt.bprintf(instruction.str[:], "LD [I], V%X", vx)
|
||||||
|
case 0x65: fmt.bprintf(instruction.str[:], "LD V%X, [I]", vx)
|
||||||
|
case: fmt.bprintf(instruction.str[:], "DATA 0x%04X", opcode)
|
||||||
|
}
|
||||||
|
case: fmt.bprintf(instruction.str[:], "DATA 0x%04X", opcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return count
|
||||||
|
}
|
||||||
@@ -3,6 +3,9 @@ package simulator
|
|||||||
import emu "../machine"
|
import emu "../machine"
|
||||||
import rl "vendor:raylib"
|
import rl "vendor:raylib"
|
||||||
|
|
||||||
|
@(private="package")
|
||||||
|
_sim: ^Simulator
|
||||||
|
|
||||||
// Embed roms
|
// Embed roms
|
||||||
roms := #load_directory("../roms")
|
roms := #load_directory("../roms")
|
||||||
|
|
||||||
@@ -31,7 +34,6 @@ Simulator :: struct {
|
|||||||
// GUI
|
// GUI
|
||||||
sound: rl.Sound,
|
sound: rl.Sound,
|
||||||
font: rl.Font,
|
font: rl.Font,
|
||||||
active_tab: i32,
|
|
||||||
mem_scroll: rl.Vector2,
|
mem_scroll: rl.Vector2,
|
||||||
rompick_scroll: rl.Vector2,
|
rompick_scroll: rl.Vector2,
|
||||||
selected_rom: string,
|
selected_rom: string,
|
||||||
@@ -44,18 +46,12 @@ Simulator :: struct {
|
|||||||
disasm_scroll: rl.Vector2,
|
disasm_scroll: rl.Vector2,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Requires an initilized emulatore System Struct
|
reset_sim :: proc(sim: ^Simulator) {
|
||||||
/*
|
sim.paused = true
|
||||||
run_simulator :: proc(s: ^emu.System) {
|
sim.disasm_count = 0
|
||||||
sim := Simulator {
|
sim.rom_loaded = false
|
||||||
machine = s,
|
sim.selected_rom = ""
|
||||||
rom_loaded = false,
|
|
||||||
paused = true,
|
emu.reset_machine(sim.machine)
|
||||||
step = false,
|
|
||||||
cpu_hz = 700,
|
|
||||||
disasm_follow = true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
run_gui(&sim)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|||||||
Reference in New Issue
Block a user