Compare commits

...

8 Commits

Author SHA1 Message Date
jasonhilder 745ee7cecb Readme update for v1. 2026-07-03 11:26:21 +02:00
jasonhilder 68737e12da test remote. 2026-07-02 10:26:01 +02:00
jasonhilder 85e8481cf4 Testing new remote. 2026-07-02 10:24:22 +02:00
jasonhilder ec8bcdb5ed Fighting the wasm build... 2026-07-02 10:20:35 +02:00
jasonhilder 747702d256 Updated gitignore to exclude build output. 2026-06-28 07:01:00 +02:00
jasonhilder 717ef479fa Added globals and refactor for wasm. 2026-06-28 06:59:46 +02:00
jasonhilder 5edae5d2d8 Web build script and updates to web source.
Added Karl Zylinski web build script.
Updated template for better width.
Removed resizing function.
2026-06-28 06:57:48 +02:00
jasonhilder d43ec53d8d Added build tag for non web only. 2026-06-28 06:55:13 +02:00
14 changed files with 322 additions and 79 deletions
+5
View File
@@ -2,5 +2,10 @@
*.bin *.bin
*.o *.o
# Artifact output directory
build/
# Project management # Project management
todo todo
+22 -1
View File
@@ -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](showcase.png) [![showcase](showcase.png)](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
View File
@@ -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
View File
@@ -1,3 +1,4 @@
#+build !js
package tinyfiledialogs package tinyfiledialogs
import "base:builtin" import "base:builtin"
+12 -11
View File
@@ -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
View File
@@ -1,3 +1,4 @@
#+build !js
package machine package machine
import "core:log" import "core:log"
+2 -2
View File
@@ -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 {
+1
View File
@@ -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;
+8 -11
View File
@@ -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
View File
@@ -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)
} }
} }
+2 -6
View File
@@ -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{
+8 -8
View File
@@ -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[:])
} }
+174
View File
@@ -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
}
+10 -14
View File
@@ -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,
step = false,
cpu_hz = 700,
disasm_follow = true,
}
run_gui(&sim) emu.reset_machine(sim.machine)
} }
*/