├── server
├── mute.sh
├── quit.sh
├── pause.sh
├── reset.sh
├── continue.sh
├── disasm.sh
├── list.sh
├── cartridge.sh
├── history.sh
├── register.sh
├── read1.sh
├── read2.sh
├── trace.sh
├── delete.sh
├── break.sh
├── historyw.sh
├── registerw.sh
├── sprview.html
├── io.html
├── tileview.html
└── README.md
├── logo.png
├── .gitattributes
├── test
├── mooneye-gb
│ ├── div_timing
│ │ ├── rom.gb
│ │ └── expected.jpg
│ ├── ei_timing
│ │ ├── rom.gb
│ │ └── expected.jpg
│ ├── instr
│ │ └── daa
│ │ │ ├── rom.gb
│ │ │ └── expected.jpg
│ ├── jp_timing
│ │ ├── rom.gb
│ │ └── expected.jpg
│ ├── pop_timing
│ │ ├── rom.gb
│ │ └── expected.jpg
│ ├── call_timing2
│ │ ├── rom.gb
│ │ └── expected.jpg
│ ├── ei_sequence
│ │ ├── rom.gb
│ │ └── expected.jpg
│ ├── halt_ime0_ei
│ │ ├── rom.gb
│ │ └── expected.jpg
│ ├── intr_timing
│ │ ├── rom.gb
│ │ └── expected.jpg
│ ├── push_timing
│ │ └── rom.gb
│ ├── rapid_di_ei
│ │ ├── rom.gb
│ │ └── expected.jpg
│ ├── timer
│ │ ├── tim00
│ │ │ ├── rom.gb
│ │ │ └── expected.jpg
│ │ ├── tim01
│ │ │ ├── rom.gb
│ │ │ └── expected.jpg
│ │ ├── tim10
│ │ │ ├── rom.gb
│ │ │ └── expected.jpg
│ │ ├── tim11
│ │ │ ├── rom.gb
│ │ │ └── expected.jpg
│ │ ├── div_write
│ │ │ ├── rom.gb
│ │ │ └── expected.jpg
│ │ ├── tima_reload
│ │ │ ├── rom.gb
│ │ │ └── expected.jpg
│ │ ├── rapid_toggle
│ │ │ ├── rom.gb
│ │ │ └── expected.jpg
│ │ ├── tim00_div_trigger
│ │ │ ├── rom.gb
│ │ │ └── expected.jpg
│ │ ├── tim01_div_trigger
│ │ │ ├── rom.gb
│ │ │ └── expected.jpg
│ │ ├── tim10_div_trigger
│ │ │ ├── rom.gb
│ │ │ └── expected.jpg
│ │ ├── tim11_div_trigger
│ │ │ ├── rom.gb
│ │ │ └── expected.jpg
│ │ ├── tma_write_reloading
│ │ │ ├── rom.gb
│ │ │ └── expected.jpg
│ │ └── tima_write_reloading
│ │ │ ├── rom.gb
│ │ │ └── expected.jpg
│ ├── add_sp_e_timing
│ │ ├── rom.gb
│ │ └── expected.jpg
│ ├── call_cc_timing
│ │ ├── rom.gb
│ │ └── expected.jpg
│ ├── call_cc_timing2
│ │ ├── rom.gb
│ │ └── expected.jpg
│ ├── if_ie_registers
│ │ ├── rom.gb
│ │ └── expected.jpg
│ ├── oam_dma
│ │ ├── basic
│ │ │ ├── rom.gb
│ │ │ ├── expected.jpg
│ │ │ └── rom.sym
│ │ ├── reg_read
│ │ │ ├── rom.gb
│ │ │ ├── expected.jpg
│ │ │ └── reg_read.sym
│ │ └── sources-GS
│ │ │ ├── rom.gb
│ │ │ └── sources-GS.sym
│ ├── oam_dma_restart
│ │ ├── rom.gb
│ │ └── expected.jpg
│ ├── halt_ime1_timing
│ │ ├── rom.gb
│ │ └── expected.jpg
│ ├── ld_hl_sp_e_timing
│ │ ├── rom.gb
│ │ └── expected.jpg
│ ├── interrupts
│ │ └── ie_push
│ │ │ └── rom.gb
│ └── halt_ime0_nointr_timing
│ │ ├── rom.gb
│ │ └── expected.jpg
└── gb-test-roms
│ ├── cpu_instrs
│ ├── rom.gb
│ └── expected.jpg
│ └── instr_timing
│ ├── rom.gb
│ └── expected.jpg
├── .gitignore
├── go.mod
├── pkg
├── util
│ ├── model.go
│ ├── callback.go
│ └── util.go
├── emulator
│ ├── debug
│ │ ├── io.go
│ │ ├── read.go
│ │ ├── trace.go
│ │ ├── cart.go
│ │ ├── history.go
│ │ ├── debug.go
│ │ ├── break.go
│ │ ├── sprview.go
│ │ ├── tileview.go
│ │ ├── register.go
│ │ └── disasm.go
│ ├── audio
│ │ └── audio.go
│ ├── joypad
│ │ └── joypad.go
│ ├── server.go
│ ├── save.go
│ └── emulator.go
└── gbc
│ ├── scheduler
│ ├── event.go
│ └── scheduler.go
│ ├── stack.go
│ ├── cart
│ └── cart.go
│ ├── video
│ ├── oam.go
│ ├── constants.go
│ ├── video.go
│ └── renderer.go
│ ├── apu
│ ├── waves.go
│ ├── channel.go
│ └── apu.go
│ ├── joypad
│ └── joypad.go
│ ├── register.go
│ ├── rtc
│ └── rtc.go
│ ├── timer.go
│ ├── ram.go
│ ├── io.go
│ ├── gbc.go
│ └── optable.go
├── .vscode
└── settings.json
├── .github
└── workflows
│ └── go.yml
├── LICENSE
├── cmd
└── main.go
├── README.ja.md
├── README.md
├── makefile
└── go.sum
/server/mute.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | curl localhost:8888/mute
--------------------------------------------------------------------------------
/server/quit.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | curl localhost:8888/quit
--------------------------------------------------------------------------------
/server/pause.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | curl localhost:8888/pause
--------------------------------------------------------------------------------
/server/reset.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | curl localhost:8888/reset
--------------------------------------------------------------------------------
/server/continue.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | curl localhost:8888/continue
--------------------------------------------------------------------------------
/server/disasm.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | curl localhost:8888/debug/disasm
--------------------------------------------------------------------------------
/server/list.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | curl localhost:8888/debug/break
--------------------------------------------------------------------------------
/server/cartridge.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | curl localhost:8888/debug/cartridge
--------------------------------------------------------------------------------
/server/history.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | curl localhost:8888/debug/history
--------------------------------------------------------------------------------
/server/register.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | curl localhost:8888/debug/register
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/logo.png
--------------------------------------------------------------------------------
/server/read1.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | curl "localhost:8888/debug/read1?addr="$1
--------------------------------------------------------------------------------
/server/read2.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | curl "localhost:8888/debug/read2?addr="$1
--------------------------------------------------------------------------------
/server/trace.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | curl "localhost:8888/debug/trace?step="$1
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | test/* linguist-vendored
2 | server/* linguist-vendored
3 |
--------------------------------------------------------------------------------
/test/mooneye-gb/div_timing/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/div_timing/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/ei_timing/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/ei_timing/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/instr/daa/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/instr/daa/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/jp_timing/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/jp_timing/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/pop_timing/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/pop_timing/rom.gb
--------------------------------------------------------------------------------
/test/gb-test-roms/cpu_instrs/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/gb-test-roms/cpu_instrs/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/call_timing2/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/call_timing2/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/ei_sequence/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/ei_sequence/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/halt_ime0_ei/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/halt_ime0_ei/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/intr_timing/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/intr_timing/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/push_timing/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/push_timing/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/rapid_di_ei/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/rapid_di_ei/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/tim00/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/tim00/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/tim01/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/tim01/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/tim10/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/tim10/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/tim11/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/tim11/rom.gb
--------------------------------------------------------------------------------
/test/gb-test-roms/instr_timing/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/gb-test-roms/instr_timing/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/add_sp_e_timing/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/add_sp_e_timing/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/call_cc_timing/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/call_cc_timing/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/call_cc_timing2/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/call_cc_timing2/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/ei_timing/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/ei_timing/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/if_ie_registers/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/if_ie_registers/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/instr/daa/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/instr/daa/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/jp_timing/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/jp_timing/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/oam_dma/basic/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/oam_dma/basic/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/oam_dma_restart/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/oam_dma_restart/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/div_write/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/div_write/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/div_timing/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/div_timing/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/ei_sequence/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/ei_sequence/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/halt_ime1_timing/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/halt_ime1_timing/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/intr_timing/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/intr_timing/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/ld_hl_sp_e_timing/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/ld_hl_sp_e_timing/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/oam_dma/reg_read/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/oam_dma/reg_read/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/pop_timing/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/pop_timing/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/rapid_di_ei/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/rapid_di_ei/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/tim00/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/tim00/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/tim01/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/tim01/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/tim10/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/tim10/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/tim11/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/tim11/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/tima_reload/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/tima_reload/rom.gb
--------------------------------------------------------------------------------
/test/gb-test-roms/cpu_instrs/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/gb-test-roms/cpu_instrs/expected.jpg
--------------------------------------------------------------------------------
/test/gb-test-roms/instr_timing/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/gb-test-roms/instr_timing/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/call_cc_timing/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/call_cc_timing/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/call_timing2/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/call_timing2/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/halt_ime0_ei/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/halt_ime0_ei/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/interrupts/ie_push/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/interrupts/ie_push/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/oam_dma/basic/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/oam_dma/basic/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/oam_dma/sources-GS/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/oam_dma/sources-GS/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/rapid_toggle/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/rapid_toggle/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/add_sp_e_timing/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/add_sp_e_timing/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/call_cc_timing2/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/call_cc_timing2/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/halt_ime1_timing/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/halt_ime1_timing/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/if_ie_registers/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/if_ie_registers/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/oam_dma/reg_read/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/oam_dma/reg_read/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/oam_dma_restart/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/oam_dma_restart/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/div_write/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/div_write/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/halt_ime0_nointr_timing/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/halt_ime0_nointr_timing/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/ld_hl_sp_e_timing/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/ld_hl_sp_e_timing/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/rapid_toggle/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/rapid_toggle/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/tim00_div_trigger/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/tim00_div_trigger/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/tim01_div_trigger/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/tim01_div_trigger/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/tim10_div_trigger/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/tim10_div_trigger/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/tim11_div_trigger/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/tim11_div_trigger/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/tima_reload/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/tima_reload/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/tma_write_reloading/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/tma_write_reloading/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/tima_write_reloading/rom.gb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/tima_write_reloading/rom.gb
--------------------------------------------------------------------------------
/test/mooneye-gb/halt_ime0_nointr_timing/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/halt_ime0_nointr_timing/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/tim00_div_trigger/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/tim00_div_trigger/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/tim01_div_trigger/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/tim01_div_trigger/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/tim10_div_trigger/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/tim10_div_trigger/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/tim11_div_trigger/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/tim11_div_trigger/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/tima_write_reloading/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/tima_write_reloading/expected.jpg
--------------------------------------------------------------------------------
/test/mooneye-gb/timer/tma_write_reloading/expected.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akatsuki105/worldwide/HEAD/test/mooneye-gb/timer/tma_write_reloading/expected.jpg
--------------------------------------------------------------------------------
/server/delete.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | if [ $# != 1 ]; then
3 | echo "please input addr(e.g. 0x486)"
4 | exit 1
5 | else
6 | curl -X DELETE "localhost:8888/debug/break?addr="$1
7 | fi
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # bin
2 | *.exe
3 | *.app
4 | worldwide
5 | worldwide.wasm
6 |
7 | # config
8 | *.sav
9 | *.ini
10 | *.toml
11 |
12 | public/
13 | build/
14 | game/
15 | dump/
16 | sav/
17 | .DS_store
18 | cpu.pprof
19 |
--------------------------------------------------------------------------------
/server/break.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | if [ $# != 1 ]; then
3 | echo "please input addr(e.g. 0x486)"
4 | exit 1
5 | else
6 | curl -X POST -d '{"addr":"'$1'"}' -H "Content-Type: application/json" localhost:8888/debug/break
7 | fi
8 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/pokemium/worldwide
2 |
3 | go 1.16
4 |
5 | require (
6 | github.com/hajimehoshi/ebiten/v2 v2.0.6
7 | github.com/hajimehoshi/oto v0.7.1
8 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e
9 | )
10 |
--------------------------------------------------------------------------------
/server/historyw.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | if [ $# != 1 ]; then
3 | echo "please input history count(e.g. 0x20)"
4 | exit 1
5 | else
6 | curl -X POST -d '{"history":"'$1'"}' -H "Content-Type: application/json" localhost:8888/debug/history
7 | fi
8 |
--------------------------------------------------------------------------------
/server/registerw.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | if [ $# != 2 ]; then
3 | echo "please input target and value(e.g. ime, 0x1)"
4 | exit 1
5 | else
6 | curl -X POST -d '{"target":"'$1'", "value":"'$2'"}' -H "Content-Type: application/json" localhost:8888/debug/register
7 | fi
8 |
--------------------------------------------------------------------------------
/pkg/util/model.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | type GBModel byte
4 |
5 | const (
6 | GB_MODEL_AUTODETECT GBModel = 0xFF
7 | GB_MODEL_DMG GBModel = 0x00
8 | GB_MODEL_SGB GBModel = 0x20
9 | GB_MODEL_MGB GBModel = 0x40
10 | GB_MODEL_SGB2 GBModel = 0x60
11 | GB_MODEL_CGB GBModel = 0x80
12 | GB_MODEL_AGB GBModel = 0xC0
13 | )
14 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "go.useLanguageServer": true,
3 | "[go]": {
4 | "editor.formatOnSave": true,
5 | "editor.codeActionsOnSave": {
6 | "source.organizeImports": true
7 | }
8 | },
9 | "[toml]": {
10 | "editor.formatOnSave": true,
11 | },
12 | "go.autocompleteUnimportedPackages": true,
13 | "gopls": {
14 | "usePlaceholders": true
15 | }
16 | }
--------------------------------------------------------------------------------
/pkg/emulator/debug/io.go:
--------------------------------------------------------------------------------
1 | package debug
2 |
3 | import (
4 | "log"
5 | "time"
6 |
7 | "golang.org/x/net/websocket"
8 | )
9 |
10 | func (d *Debugger) IO(ws *websocket.Conn) {
11 | err := websocket.Message.Send(ws, d.g.IO[:])
12 | if err != nil {
13 | log.Printf("error sending data: %v\n", err)
14 | return
15 | }
16 |
17 | for range time.NewTicker(time.Millisecond * 100).C {
18 | err := websocket.Message.Send(ws, d.g.IO[:])
19 | if err != nil {
20 | log.Printf("error sending data: %v\n", err)
21 | return
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/pkg/gbc/scheduler/event.go:
--------------------------------------------------------------------------------
1 | package scheduler
2 |
3 | type EventName string
4 |
5 | const (
6 | TimerUpdate EventName = "TimerUpdate"
7 | TimerIRQ EventName = "TimerIRQ"
8 | OAMDMA EventName = "Oamdma"
9 | HDMA EventName = "Hdma"
10 | EndMode0 EventName = "EndMode0"
11 | EndMode1 EventName = "EndMode1"
12 | EndMode2 EventName = "EndMode2"
13 | EndMode3 EventName = "EndMode3"
14 | UpdateFrame EventName = "UpdateFrame"
15 | EiPending EventName = "EiPending"
16 | )
17 |
18 | type Event struct {
19 | name EventName
20 | callback func(cyclesLate uint64)
21 | when uint64
22 | next *Event
23 | }
24 |
--------------------------------------------------------------------------------
/pkg/emulator/debug/read.go:
--------------------------------------------------------------------------------
1 | package debug
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | )
7 |
8 | func (d *Debugger) Read1(w http.ResponseWriter, req *http.Request) {
9 | val := d.g.Load8(getAddr(w, req))
10 |
11 | w.Header().Set("Content-Type", "text/plain")
12 | w.Write([]byte(fmt.Sprintf("0x%02x", val)))
13 | }
14 |
15 | func (d *Debugger) Read2(w http.ResponseWriter, req *http.Request) {
16 | addr := getAddr(w, req)
17 | lower := uint16(d.g.Load8(addr))
18 | upper := uint16(d.g.Load8(addr + 1))
19 | val := upper<<8 | lower
20 |
21 | w.Header().Set("Content-Type", "text/plain")
22 | w.Write([]byte(fmt.Sprintf("0x%04x", val)))
23 | }
24 |
--------------------------------------------------------------------------------
/pkg/emulator/audio/audio.go:
--------------------------------------------------------------------------------
1 | package audio
2 |
3 | import (
4 | "github.com/hajimehoshi/oto"
5 | "github.com/pokemium/worldwide/pkg/gbc/apu"
6 | )
7 |
8 | var context *oto.Context
9 | var player *oto.Player
10 | var Stream []byte
11 | var enable *bool
12 |
13 | func Reset(enablePtr *bool) {
14 | enable = enablePtr
15 |
16 | var err error
17 | context, err = oto.NewContext(apu.SAMPLE_RATE, 2, 1, apu.SAMPLE_RATE/apu.BUF_SEC)
18 | if err != nil {
19 | panic(err)
20 | }
21 |
22 | player = context.NewPlayer()
23 | }
24 |
25 | func Play() {
26 | if player == nil || !*enable {
27 | return
28 | }
29 | player.Write(Stream)
30 | }
31 |
32 | func SetStream(b []byte) { Stream = b }
33 |
--------------------------------------------------------------------------------
/.github/workflows/go.yml:
--------------------------------------------------------------------------------
1 | name: Go
2 | on:
3 | push:
4 | branches:
5 | - master
6 | paths-ignore:
7 | - "**.md"
8 | - "logo.png"
9 | - ".vscode/**"
10 | - "server/**"
11 | - "go.mod"
12 | - "go.sum"
13 | pull_request:
14 | branches:
15 | - master
16 | paths-ignore:
17 | - "**.md"
18 | - "logo.png"
19 | - ".vscode/**"
20 | - "server/**"
21 | - "go.mod"
22 | - "go.sum"
23 |
24 | jobs:
25 | build:
26 | name: Build
27 | runs-on: windows-latest
28 | steps:
29 | - uses: actions/checkout@main
30 | - uses: actions/setup-go@v2
31 | with:
32 | go-version: 1.16
33 | - run: go build -o gbc ./cmd/
--------------------------------------------------------------------------------
/server/sprview.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SprView
6 |
7 |
8 |
9 |
10 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/pkg/gbc/stack.go:
--------------------------------------------------------------------------------
1 | package gbc
2 |
3 | import "github.com/pokemium/worldwide/pkg/util"
4 |
5 | func (g *GBC) push(b byte) {
6 | g.Store8(g.Reg.SP-1, b)
7 | g.Reg.SP--
8 | }
9 |
10 | func (g *GBC) pop() byte {
11 | value := g.Load8(g.Reg.SP)
12 | g.Reg.SP++
13 | return value
14 | }
15 |
16 | func (g *GBC) pushPC() {
17 | upper, lower := byte(g.Reg.PC>>8), byte(g.Reg.PC)
18 | g.push(upper)
19 | g.push(lower)
20 | }
21 |
22 | func (g *GBC) pushPCCALL() {
23 | upper, lower := byte(g.Reg.PC>>8), byte(g.Reg.PC&0x00ff)
24 | g.push(upper)
25 | g.timer.tick(1 * 4 >> util.Bool2U32(g.DoubleSpeed)) // M = 4: PC push: memory access for high byte
26 | g.push(lower)
27 | g.timer.tick(1 * 4 >> util.Bool2U32(g.DoubleSpeed)) // M = 5: PC push: memory access for low byte
28 | }
29 |
30 | func (g *GBC) popPC() {
31 | lower := uint16(g.pop())
32 | upper := uint16(g.pop())
33 | g.Reg.PC = (upper << 8) | lower
34 | }
35 |
--------------------------------------------------------------------------------
/pkg/emulator/joypad/joypad.go:
--------------------------------------------------------------------------------
1 | package joypad
2 |
3 | import (
4 | "github.com/hajimehoshi/ebiten/v2"
5 | )
6 |
7 | var Handler = [8](func() bool){
8 | btnA, btnB, btnSelect, btnStart, keyRight, keyLeft, keyUp, keyDown,
9 | }
10 |
11 | func btnA() bool {
12 | return ebiten.IsKeyPressed(ebiten.KeyX)
13 | }
14 |
15 | func btnB() bool {
16 | return ebiten.IsKeyPressed(ebiten.KeyZ)
17 | }
18 |
19 | func btnStart() bool {
20 | return ebiten.IsKeyPressed(ebiten.KeyEnter)
21 | }
22 |
23 | func btnSelect() bool {
24 | return ebiten.IsKeyPressed(ebiten.KeyBackspace)
25 | }
26 |
27 | func keyUp() bool {
28 | return ebiten.IsKeyPressed(ebiten.KeyUp)
29 | }
30 |
31 | func keyDown() bool {
32 | return ebiten.IsKeyPressed(ebiten.KeyDown)
33 | }
34 |
35 | func keyRight() bool {
36 | return ebiten.IsKeyPressed(ebiten.KeyRight)
37 | }
38 |
39 | func keyLeft() bool {
40 | return ebiten.IsKeyPressed(ebiten.KeyLeft)
41 | }
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2020 Akihiro Otomo
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/server/io.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IO
6 |
7 |
8 |
9 |
10 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/pkg/gbc/cart/cart.go:
--------------------------------------------------------------------------------
1 | package cart
2 |
3 | const (
4 | ROM = iota
5 | MBC1
6 | MBC2
7 | MBC3
8 | MBC5
9 | )
10 |
11 | // ram size
12 | const (
13 | NO_RAM = iota
14 | RAM_UNUSED
15 | RAM_8KB
16 | RAM_32KB
17 | RAM_128KB
18 | RAM_64KB
19 | )
20 |
21 | // Cartridge - Cartridge info from ROM Header
22 | type Cartridge struct {
23 | Title string
24 | IsCGB bool // gameboy color ROM is true
25 | Type, ROMSize, RAMSize byte
26 | MBC int
27 | }
28 |
29 | // Parse - load cartridge info from byte slice
30 | func New(rom []byte) *Cartridge {
31 | var buf []byte
32 | for i := 0x0134; i < 0x0143; i++ {
33 | if rom[i] == 0 {
34 | break
35 | }
36 | buf = append(buf, rom[i])
37 | }
38 |
39 | return &Cartridge{
40 | Title: string(buf),
41 | IsCGB: rom[0x0143] == 0x80 || rom[0x0143] == 0xc0,
42 | Type: rom[0x0147],
43 | ROMSize: rom[0x0148],
44 | RAMSize: rom[0x0149],
45 | }
46 | }
47 |
48 | func (c *Cartridge) HasRTC() bool {
49 | return c.Type == 0x0f || c.Type == 0x10
50 | }
51 |
--------------------------------------------------------------------------------
/pkg/gbc/video/oam.go:
--------------------------------------------------------------------------------
1 | package video
2 |
3 | type OAM struct {
4 | Objs [40]*Obj
5 | Buffer [0xa0]byte
6 | }
7 |
8 | func NewOAM() *OAM {
9 | o := &OAM{}
10 | for i := 0; i < 40; i++ {
11 | o.Objs[i] = &Obj{}
12 | }
13 | return o
14 | }
15 |
16 | func (o *OAM) Get(offset uint16) byte {
17 | obj := offset >> 2
18 | idx := offset & 3
19 | return o.Objs[obj].get(idx)
20 | }
21 |
22 | func (o *OAM) Set(offset uint16, value byte) {
23 | obj := offset >> 2
24 | idx := offset & 3
25 | o.Objs[obj].set(idx, value)
26 | }
27 |
28 | // GBObj
29 | type Obj struct {
30 | y, x, tile, attr byte
31 | }
32 |
33 | func (o *Obj) get(idx uint16) byte {
34 | switch idx {
35 | case 0:
36 | return o.y
37 | case 1:
38 | return o.x
39 | case 2:
40 | return o.tile
41 | case 3:
42 | return o.attr
43 | }
44 | return 0xff
45 | }
46 |
47 | func (o *Obj) set(idx uint16, value byte) {
48 | switch idx {
49 | case 0:
50 | o.y = value
51 | case 1:
52 | o.x = value
53 | case 2:
54 | o.tile = value
55 | case 3:
56 | o.attr = value
57 | }
58 | }
59 |
60 | type Sprite struct {
61 | obj Obj
62 | index int8
63 | }
64 |
--------------------------------------------------------------------------------
/pkg/gbc/apu/waves.go:
--------------------------------------------------------------------------------
1 | package apu
2 |
3 | import (
4 | "math"
5 | "math/rand"
6 | )
7 |
8 | // WaveGenerator is a function which can be used for generating waveform
9 | // samples for different channels.
10 | type WaveGenerator func(t float64) byte
11 |
12 | // Square returns a square wave generator with a given mod. This is used
13 | // for channels 1 and 2.
14 | func Square(mod float64) WaveGenerator {
15 | return func(t float64) byte {
16 | if math.Sin(t) <= mod {
17 | return 0xFF
18 | }
19 | return 0
20 | }
21 | }
22 |
23 | // Waveform returns a wave generator for some waveform ram. This is used
24 | // by channel 3.
25 | func Waveform(ram func(i int) byte) WaveGenerator {
26 | return func(t float64) byte {
27 | idx := int(math.Floor(t/twoPi*32)) % 0x20
28 | return ram(idx)
29 | }
30 | }
31 |
32 | // Noise returns a wave generator for a noise channel. This is used by
33 | // channel 4.
34 | func Noise() WaveGenerator {
35 | var last float64
36 | var val byte
37 | return func(t float64) byte {
38 | if t-last > twoPi {
39 | last = t
40 | val = byte(rand.Intn(2)) * 0xFF
41 | }
42 | return val
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/pkg/emulator/debug/trace.go:
--------------------------------------------------------------------------------
1 | package debug
2 |
3 | import (
4 | "net/http"
5 | "strconv"
6 |
7 | "github.com/pokemium/worldwide/pkg/util"
8 | )
9 |
10 | func (d *Debugger) Trace(w http.ResponseWriter, req *http.Request) {
11 | switch req.Method {
12 | case "GET":
13 | if !*d.pause {
14 | http.Error(w, "trace API is available on pause state", http.StatusBadRequest)
15 | return
16 | }
17 |
18 | q := req.URL.Query()
19 | steps := uint16(0)
20 | for key, val := range q {
21 | if key == "step" {
22 | s, _ := strconv.ParseUint(val[0], 10, 16)
23 | steps = uint16(s)
24 | }
25 | }
26 | if steps == 0 {
27 | http.Error(w, "`step` is needed on query parameter(e.g. ?step=20)", http.StatusBadRequest)
28 | return
29 | }
30 |
31 | result := ""
32 | for s := uint16(0); s < steps; s++ {
33 | d.g.Step()
34 | for _, callback := range d.g.Callbacks {
35 | if callback.Priority == util.PRIO_BREAKPOINT {
36 | continue
37 | }
38 | if callback.Func() {
39 | break
40 | }
41 | }
42 | result += stringfyCurInst(d.g.Inst) + "\n"
43 | }
44 | w.Header().Set("Content-Type", "text/plain")
45 | w.Write([]byte(result))
46 | default:
47 | http.NotFound(w, req)
48 | return
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/pkg/util/callback.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "errors"
5 | "sort"
6 | )
7 |
8 | type Priority uint
9 |
10 | const (
11 | PRIO_HISTORY = 0
12 | PRIO_BREAKPOINT = 1
13 | )
14 |
15 | type Callback struct {
16 | Name string
17 | Priority Priority
18 | Func func() bool
19 | }
20 |
21 | func SetCallback(callbacks []*Callback, name string, priority Priority, callback func() bool) ([]*Callback, error) {
22 | for _, c := range callbacks {
23 | if name == c.Name {
24 | return callbacks, errors.New("callback name is already used")
25 | }
26 | if priority == c.Priority {
27 | return callbacks, errors.New("callback priority is already used")
28 | }
29 | }
30 | callbacks = append(callbacks, &Callback{name, priority, callback})
31 | sortCallbacks(callbacks)
32 | return callbacks, nil
33 | }
34 |
35 | func RemoveCallback(callbacks []*Callback, name string) []*Callback {
36 | for i, c := range callbacks {
37 | if name == c.Name {
38 | sortCallbacks(callbacks)
39 | callbacks = append(callbacks[:i], callbacks[i+1:]...)
40 | return callbacks
41 | }
42 | }
43 | return callbacks
44 | }
45 |
46 | func sortCallbacks(callbacks []*Callback) {
47 | sort.Slice(callbacks, func(i, j int) bool {
48 | return callbacks[i].Priority < callbacks[j].Priority
49 | })
50 | }
51 |
--------------------------------------------------------------------------------
/server/tileview.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | TileView
6 |
7 |
8 |
9 |
10 |
11 |
31 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/pkg/gbc/joypad/joypad.go:
--------------------------------------------------------------------------------
1 | package joypad
2 |
3 | import "github.com/pokemium/worldwide/pkg/util"
4 |
5 | const (
6 | A = 0
7 | B = 1
8 | Select = 2
9 | Start = 3
10 | Right = 4
11 | Left = 5
12 | Up = 6
13 | Down = 7
14 | )
15 |
16 | // Joypad state
17 | type Joypad struct {
18 | P1 byte
19 | button, direction [4]bool // start, select, b, a, down, up, left, right
20 | handler [8](func() bool)
21 | }
22 |
23 | func New(h [8](func() bool)) *Joypad {
24 | return &Joypad{
25 | handler: h,
26 | }
27 | }
28 |
29 | // Output returns joypad state in bitfield format
30 | func (pad *Joypad) Output() byte {
31 | joypad := byte(0x00)
32 | if p15 := !util.Bit(pad.P1, 5); p15 {
33 | for i := 0; i < 4; i++ {
34 | joypad = util.SetBit8(joypad, i, pad.button[i])
35 | }
36 | }
37 | if p14 := !util.Bit(pad.P1, 4); p14 {
38 | for i := 0; i < 4; i++ {
39 | joypad = util.SetBit8(joypad, i, pad.direction[i])
40 | }
41 | }
42 | return ^joypad
43 | }
44 |
45 | // Input joypad
46 | func (j *Joypad) Input() bool {
47 | pressed := false
48 |
49 | // A,B,Start,Select
50 | for i := 0; i < 4; i++ {
51 | if j.handler[i]() {
52 | old := j.button[i]
53 | j.button[i] = true
54 | if !old && j.button[i] {
55 | pressed = true
56 | }
57 | } else {
58 | j.button[i] = false
59 | }
60 | }
61 |
62 | // Right, Left, Up, Down
63 | for i := 0; i < 4; i++ {
64 | if j.handler[i+4]() {
65 | old := j.direction[i]
66 | j.direction[i] = true
67 | if !old && j.direction[i] {
68 | pressed = true
69 | }
70 | } else {
71 | j.direction[i] = false
72 | }
73 | }
74 |
75 | return pressed
76 | }
77 |
--------------------------------------------------------------------------------
/pkg/emulator/debug/cart.go:
--------------------------------------------------------------------------------
1 | package debug
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 | )
7 |
8 | var rom = map[byte]string{
9 | 0x00: "32KB",
10 | 0x01: "64KB",
11 | 0x02: "128KB",
12 | 0x03: "256KB",
13 | 0x04: "512KB",
14 | 0x05: "1MB",
15 | 0x06: "2MB",
16 | 0x07: "4MB",
17 | 0x08: "8MB",
18 | 0x52: "1.1MB",
19 | 0x53: "1.2MB",
20 | 0x54: "1.5MB",
21 | }
22 |
23 | var ram = [6]string{
24 | "None",
25 | "2KB",
26 | "8KB",
27 | "32KB",
28 | "128KB",
29 | "64KB",
30 | }
31 |
32 | var cartType = map[byte]string{0x00: "ROM ONLY", 0x01: "MBC1", 0x02: "MBC1+RAM", 0x03: "MBC1+RAM+BATTERY", 0x05: "MBC2", 0x06: "MBC2+BATTERY", 0x08: "ROM+RAM", 0x09: "ROM+RAM+BATTERY", 0x0b: "MBC1", 0x0c: "MBC1+RAM", 0x0d: "MBC1+RAM+BATTERY", 0x0f: "MBC3+TIMER+BATTERY", 0x10: "MBC3+TIMER+RAM+BATTERY", 0x11: "MBC3", 0x12: "MBC3+RAM", 0x13: "MBC3+RAM+BATTERY", 0x19: "MBC5", 0x1a: "MBC5+RAM", 0x1b: "MBC5+RAM+BATTERY", 0x1c: "MBC5+RUMBLE", 0x1d: "MBC5+RUMBLE+RAM", 0x1e: "MBC5+RUMBLE+RAM+BATTERY"}
33 |
34 | type Cartridge struct {
35 | Title string `json:"title"`
36 | CartridgeType string `json:"cartridge_type"`
37 | RomSize string `json:"rom_size"`
38 | RamSize string `json:"ram_size"`
39 | }
40 |
41 | func (d *Debugger) Cartridge(w http.ResponseWriter, req *http.Request) {
42 | c := Cartridge{
43 | Title: d.g.Cartridge.Title,
44 | CartridgeType: cartType[d.g.Cartridge.Type],
45 | RomSize: rom[d.g.Cartridge.ROMSize],
46 | RamSize: ram[d.g.Cartridge.RAMSize],
47 | }
48 |
49 | res, err := json.Marshal(c)
50 |
51 | if err != nil {
52 | http.Error(w, err.Error(), http.StatusInternalServerError)
53 | return
54 | }
55 |
56 | w.Header().Set("Content-Type", "application/json")
57 | w.Write(res)
58 | }
59 |
--------------------------------------------------------------------------------
/pkg/emulator/server.go:
--------------------------------------------------------------------------------
1 | package emulator
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "golang.org/x/net/websocket"
8 | )
9 |
10 | func (e *Emulator) RunServer(port int) {
11 | http.HandleFunc("/pause", e.Pause)
12 | http.HandleFunc("/continue", e.Continue)
13 | http.HandleFunc("/reset", e.Reset)
14 | http.HandleFunc("/quit", e.Quit)
15 | http.HandleFunc("/mute", e.toggleSound)
16 | http.HandleFunc("/debug/register", e.debugger.Register)
17 | http.HandleFunc("/debug/break", e.debugger.Break)
18 | http.HandleFunc("/debug/cartridge", e.debugger.Cartridge)
19 | http.HandleFunc("/debug/read1", e.debugger.Read1)
20 | http.HandleFunc("/debug/read2", e.debugger.Read2)
21 | http.HandleFunc("/debug/disasm", e.debugger.Disasm)
22 | http.HandleFunc("/debug/trace", e.debugger.Trace)
23 | http.HandleFunc("/debug/history", e.debugger.Hisotry)
24 | http.Handle("/debug/tileview/bank0", websocket.Handler(e.debugger.TileView0))
25 | http.Handle("/debug/tileview/bank1", websocket.Handler(e.debugger.TileView1))
26 | http.Handle("/debug/sprview", websocket.Handler(e.debugger.SprView))
27 | http.Handle("/debug/io", websocket.Handler(e.debugger.IO))
28 | http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
29 | }
30 |
31 | // these requests accept get method, but have side effect ummmm....
32 | func (e *Emulator) Pause(w http.ResponseWriter, req *http.Request) { e.pause = true }
33 | func (e *Emulator) Continue(w http.ResponseWriter, req *http.Request) { e.pause = false }
34 | func (e *Emulator) Reset(w http.ResponseWriter, req *http.Request) { e.reset = true }
35 | func (e *Emulator) Quit(w http.ResponseWriter, req *http.Request) { e.quit = true }
36 | func (e *Emulator) toggleSound(w http.ResponseWriter, req *http.Request) {
37 | e.GBC.Sound.Enable = !e.GBC.Sound.Enable
38 | }
39 |
--------------------------------------------------------------------------------
/test/mooneye-gb/oam_dma/basic/rom.sym:
--------------------------------------------------------------------------------
1 | ; this file was created with wlalink by ville helin .
2 | ; wla symbolic information for "acceptance/oam_dma/basic.gb".
3 |
4 | [labels]
5 | 01:48d3 clear_oam
6 | 01:48dd clear_vram
7 | 01:4884 disable_lcd_safe
8 | 01:488a disable_lcd_safe@wait_ly_0
9 | 01:48a2 memcmp
10 | 01:48f1 memcpy
11 | 01:48fa memset
12 | 01:48b0 print_hex4
13 | 01:48e7 print_hex8
14 | 01:490a print_inline_string
15 | 01:48bc print_load_font
16 | 01:48c8 print_newline
17 | 01:4903 print_string
18 | 01:47f0 quit
19 | 01:4805 quit@cb_return
20 | 01:480a quit@wait_ly_1
21 | 01:4810 quit@wait_ly_2
22 | 01:4816 quit@wait_ly_3
23 | 01:481c quit@wait_ly_4
24 | 01:4826 quit@success
25 | 01:484d quit@failure
26 | 01:486c quit@halt
27 | 01:486d quit@halt_execution_0
28 | 01:4870 reset_screen
29 | 01:4893 serial_send_byte
30 | 01:4000 font
31 | 00:0150 main
32 | 00:0179 fail
33 | 00:0183 fail@quit_inline_1
34 | 00:0198 finish
35 | 00:019f finish@quit_inline_2
36 | 00:01b0 dma_proc
37 | 00:01b8 _end_dma_proc
38 | 00:1200 random_data
39 | 00:ff80 hram.dma_proc
40 | 00:ff90 fail_offset
41 |
42 | [definitions]
43 | 0000000a _sizeof_clear_oam
44 | 0000000a _sizeof_clear_vram
45 | 0000000f _sizeof_disable_lcd_safe
46 | 0000000e _sizeof_memcmp
47 | 00000009 _sizeof_memcpy
48 | 00000009 _sizeof_memset
49 | 0000000c _sizeof_print_hex4
50 | 0000000a _sizeof_print_hex8
51 | 00000006 _sizeof_print_inline_string
52 | 0000000c _sizeof_print_load_font
53 | 0000000b _sizeof_print_newline
54 | 00000007 _sizeof_print_string
55 | 00000080 _sizeof_quit
56 | 00000014 _sizeof_reset_screen
57 | 0000000f _sizeof_serial_send_byte
58 | 000007f0 _sizeof_font
59 | 00000010 _sizeof_hram.dma_proc
60 | 00000001 _sizeof_fail_offset
61 | 00000029 _sizeof_main
62 | 0000001f _sizeof_fail
63 | 00000018 _sizeof_finish
64 | 00000008 _sizeof_dma_proc
65 | 00001048 _sizeof__end_dma_proc
66 |
--------------------------------------------------------------------------------
/pkg/emulator/debug/history.go:
--------------------------------------------------------------------------------
1 | package debug
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/pokemium/worldwide/pkg/gbc"
7 | "github.com/pokemium/worldwide/pkg/util"
8 | )
9 |
10 | func (d *Debugger) Hisotry(w http.ResponseWriter, req *http.Request) {
11 | switch req.Method {
12 | case "GET":
13 | d.getHistory(w, req)
14 | case "POST":
15 | d.postHistory(w, req)
16 | default:
17 | http.NotFound(w, req)
18 | }
19 | }
20 |
21 | func getHistory(w http.ResponseWriter, req *http.Request) uint16 {
22 | return getU16FromQuery(w, req, "history")
23 | }
24 |
25 | func (d *Debugger) getHistory(w http.ResponseWriter, req *http.Request) {
26 | result := ""
27 | for _, inst := range d.history {
28 | result += stringfyCurInst(inst) + "\n"
29 | }
30 | w.Header().Set("Content-Type", "text/plain")
31 | w.Write([]byte(result))
32 | }
33 |
34 | func (d *Debugger) postHistory(w http.ResponseWriter, req *http.Request) {
35 | length := getHistory(w, req)
36 | if length == 0 {
37 | d.history = []gbc.CurInst{}
38 | d.g.Callbacks = util.RemoveCallback(d.g.Callbacks, "history")
39 | return
40 | }
41 | if length > 100 {
42 | http.Error(w, "history's count cannot be greater than 100", http.StatusBadRequest)
43 | return
44 | }
45 |
46 | d.history = make([]gbc.CurInst, length)
47 | d.g.Callbacks, _ = util.SetCallback(d.g.Callbacks, "history", util.PRIO_HISTORY, d.putHistory)
48 | }
49 |
50 | func (d *Debugger) putHistory() bool {
51 | curInst := d.g.Inst
52 | if curInst.PC == d.history[len(d.history)-1].PC && curInst.Opcode == d.history[len(d.history)-1].Opcode {
53 | return false
54 | }
55 |
56 | for i, h := range d.history {
57 | if h.Opcode == 0 && h.PC == 0 {
58 | d.history[i] = curInst
59 | return false
60 | }
61 |
62 | if i == len(d.history)-1 {
63 | d.history[i] = curInst
64 | } else {
65 | d.history[i] = d.history[i+1]
66 | }
67 | }
68 | return false
69 | }
70 |
--------------------------------------------------------------------------------
/pkg/emulator/debug/debug.go:
--------------------------------------------------------------------------------
1 | package debug
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io"
7 | "net/http"
8 | "strconv"
9 | "strings"
10 |
11 | "github.com/pokemium/worldwide/pkg/gbc"
12 | )
13 |
14 | const (
15 | TILE_PER_ROW = 16
16 | )
17 |
18 | type Debugger struct {
19 | g *gbc.GBC
20 | pause *bool
21 | Breakpoints []uint16
22 | history []gbc.CurInst
23 | }
24 |
25 | func New(g *gbc.GBC, pause *bool) *Debugger {
26 | return &Debugger{
27 | g: g,
28 | pause: pause,
29 | }
30 | }
31 |
32 | func (d *Debugger) Reset(g *gbc.GBC) {
33 | d.g = g
34 | d.history = make([]gbc.CurInst, len(d.history))
35 | }
36 |
37 | func getAddr(w http.ResponseWriter, req *http.Request) uint16 {
38 | return getU16FromQuery(w, req, "addr")
39 | }
40 |
41 | func getU16FromQuery(w http.ResponseWriter, req *http.Request, queryKey string) uint16 {
42 | switch req.Method {
43 | case "GET", "DELETE":
44 | q := req.URL.Query()
45 | addr := uint16(0)
46 | for key, val := range q {
47 | if key == queryKey {
48 | hexString := val[0]
49 | if !strings.HasPrefix(hexString, "0x") {
50 | http.Error(w, fmt.Sprintf("query parameter('%s') must be hex value(e.g. 0x486)", queryKey), http.StatusBadRequest)
51 | return 0
52 | }
53 | a, _ := strconv.ParseUint(hexString[2:], 16, 16)
54 | addr = uint16(a)
55 | }
56 | }
57 | return addr
58 | case "POST":
59 | body, _ := io.ReadAll(req.Body)
60 | keyVal := make(map[string]string)
61 | json.Unmarshal(body, &keyVal)
62 | hexString := keyVal[queryKey]
63 | if !strings.HasPrefix(hexString, "0x") {
64 | http.Error(w, fmt.Sprintf("request parameter('%s') must be hex value(e.g. 0x486)", queryKey), http.StatusBadRequest)
65 | return 0
66 | }
67 |
68 | addr := uint16(0)
69 | a, _ := strconv.ParseUint(hexString[2:], 16, 16)
70 | addr = uint16(a)
71 | return addr
72 | }
73 | return 0
74 | }
75 |
76 | func stringfyCurInst(i gbc.CurInst) string {
77 | return fmt.Sprintf("0x%04x: %s", i.PC, disasm(i.Opcode))
78 | }
79 |
--------------------------------------------------------------------------------
/pkg/emulator/debug/break.go:
--------------------------------------------------------------------------------
1 | package debug
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "strings"
7 |
8 | "github.com/pokemium/worldwide/pkg/util"
9 | )
10 |
11 | func (d *Debugger) Break(w http.ResponseWriter, req *http.Request) {
12 | switch req.Method {
13 | case "GET":
14 | d.getBreakpoints(w, req)
15 | case "POST":
16 | d.postBreakpoint(w, req)
17 | case "DELETE":
18 | d.deleteBreakpoint(w, req)
19 | }
20 | }
21 |
22 | func (d *Debugger) getBreakpoints(w http.ResponseWriter, req *http.Request) {
23 | result := "["
24 | for _, bk := range d.Breakpoints {
25 | result += fmt.Sprintf("0x%04x, ", bk)
26 | }
27 | result = strings.TrimRight(result, ", ")
28 | result += "]"
29 |
30 | w.Header().Set("Content-Type", "text/plain")
31 | w.Write([]byte(result))
32 | }
33 |
34 | func (d *Debugger) postBreakpoint(w http.ResponseWriter, req *http.Request) {
35 | addr := getAddr(w, req)
36 |
37 | alreadyExists := false
38 | for _, bk := range d.Breakpoints {
39 | if addr == bk {
40 | alreadyExists = true
41 | break
42 | }
43 | }
44 |
45 | if !alreadyExists {
46 | d.Breakpoints = append(d.Breakpoints, addr)
47 | d.g.Callbacks = util.RemoveCallback(d.g.Callbacks, "break")
48 | d.g.Callbacks, _ = util.SetCallback(d.g.Callbacks, "break", util.PRIO_BREAKPOINT, d.checkBreakpoint)
49 | }
50 | }
51 |
52 | func (d *Debugger) deleteBreakpoint(w http.ResponseWriter, req *http.Request) {
53 | addr := getAddr(w, req)
54 |
55 | newBreakpoints := make([]uint16, 0)
56 | for _, bk := range d.Breakpoints {
57 | if addr != bk {
58 | newBreakpoints = append(newBreakpoints, bk)
59 | }
60 | }
61 |
62 | d.Breakpoints = newBreakpoints
63 |
64 | d.g.Callbacks = util.RemoveCallback(d.g.Callbacks, "break")
65 | d.g.Callbacks, _ = util.SetCallback(d.g.Callbacks, "break", util.PRIO_BREAKPOINT, d.checkBreakpoint)
66 | }
67 |
68 | func (d *Debugger) checkBreakpoint() bool {
69 | for _, bk := range d.Breakpoints {
70 | if d.g.Reg.PC == bk {
71 | *d.pause = true
72 | return true
73 | }
74 | }
75 | return false
76 | }
77 |
--------------------------------------------------------------------------------
/pkg/gbc/video/constants.go:
--------------------------------------------------------------------------------
1 | package video
2 |
3 | const (
4 | HORIZONTAL_PIXELS = 160
5 | VERTICAL_PIXELS = 144
6 | MODE_2_LENGTH = 80
7 | MODE_3_LENGTH = 172
8 | MODE_0_LENGTH = 204
9 | HORIZONTAL_LENGTH = 456
10 | TOTAL_LENGTH = 70224
11 | MAX_OBJ = 40
12 | MAX_LINE_OBJ = 10
13 | VERTICAL_TOTAL_PIXELS = 154
14 | )
15 |
16 | const (
17 | GB_BASE_MAP = 0x1800
18 | GB_SIZE_MAP = 0x400
19 | GB_SIZE_VRAM_BANK0 = 0x2000
20 | )
21 |
22 | const (
23 | OBJ_PRIORITY = 0x100
24 | OBJ_PRIO_MASK = 0xff
25 | )
26 |
27 | const (
28 | PAL_BG = 0x0
29 | PAL_OBJ = 0x20
30 | PAL_HIGHLIGHT = 0x80
31 | PAL_HIGHLIGHT_BG = PAL_HIGHLIGHT | PAL_BG
32 | PAL_HIGHLIGHT_OBJ = PAL_HIGHLIGHT | PAL_OBJ
33 | PAL_SGB_BORDER = 0x40
34 | )
35 |
36 | const (
37 | BgEnable = iota
38 | ObjEnable
39 | ObjSize
40 | TileMap
41 | TileData
42 | Window
43 | WindowTileMap
44 | Enable
45 | )
46 |
47 | const (
48 | ObjAttrBank = iota + 3
49 | ObjAttrPalette
50 | ObjAttrXFlip
51 | ObjAttrYFlip
52 | ObjAttrPriority
53 | )
54 |
55 | const (
56 | // Interrupts
57 | GB_REG_IF = 0x0F
58 | GB_REG_IE = 0xFF
59 |
60 | GB_REG_LCDC = 0x40
61 | GB_REG_STAT = 0x41
62 | GB_REG_SCY = 0x42
63 | GB_REG_SCX = 0x43
64 | GB_REG_LY = 0x44
65 | GB_REG_LYC = 0x45
66 | GB_REG_DMA = 0x46
67 | GB_REG_BGP = 0x47
68 | GB_REG_OBP0 = 0x48
69 | GB_REG_OBP1 = 0x49
70 | GB_REG_WY = 0x4A
71 | GB_REG_WX = 0x4B
72 |
73 | GB_REG_KEY0 = 0x4C
74 | GB_REG_KEY1 = 0x4D
75 | GB_REG_VBK = 0x4F
76 | GB_REG_BANK = 0x50
77 | GB_REG_HDMA1 = 0x51
78 | GB_REG_HDMA2 = 0x52
79 | GB_REG_HDMA3 = 0x53
80 | GB_REG_HDMA4 = 0x54
81 | GB_REG_HDMA5 = 0x55
82 | GB_REG_RP = 0x56
83 | GB_REG_BCPS = 0x68
84 | GB_REG_BCPD = 0x69
85 | GB_REG_OCPS = 0x6A
86 | GB_REG_OCPD = 0x6B
87 | GB_REG_OPRI = 0x6C
88 | GB_REG_SVBK = 0x70
89 | GB_REG_UNK72 = 0x72
90 | GB_REG_UNK73 = 0x73
91 | GB_REG_UNK74 = 0x74
92 | GB_REG_UNK75 = 0x75
93 | GB_REG_PCM12 = 0x76
94 | GB_REG_PCM34 = 0x77
95 | GB_REG_MAX = 0x100
96 | )
97 |
--------------------------------------------------------------------------------
/pkg/util/util.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | // Bit check val's idx bit
4 | func Bit(val interface{}, idx int) bool {
5 | switch val := val.(type) {
6 | case uint64:
7 | if idx < 0 || idx > 63 {
8 | return false
9 | }
10 | return (val & (1 << idx)) != 0
11 |
12 | case uint32:
13 | if idx < 0 || idx > 31 {
14 | return false
15 | }
16 | return (val & (1 << idx)) != 0
17 |
18 | case uint:
19 | if idx < 0 || idx > 31 {
20 | return false
21 | }
22 | return (val & (1 << idx)) != 0
23 |
24 | case int:
25 | if idx < 0 || idx > 31 {
26 | return false
27 | }
28 | return (val & (1 << idx)) != 0
29 |
30 | case uint16:
31 | if idx < 0 || idx > 15 {
32 | return false
33 | }
34 | return (val & (1 << idx)) != 0
35 |
36 | case byte:
37 | if idx < 0 || idx > 7 {
38 | return false
39 | }
40 | return (val & (1 << idx)) != 0
41 | }
42 | return false
43 | }
44 |
45 | func SetBit16(val uint16, idx int, b bool) uint16 {
46 | if b {
47 | val = val | (1 << idx)
48 | } else {
49 | val = val & ^(1 << idx)
50 | }
51 | return val
52 | }
53 |
54 | func SetBit8(val byte, idx int, b bool) byte {
55 | if b {
56 | val = val | (1 << idx)
57 | } else {
58 | val = val & ^(1 << idx)
59 | }
60 | return val
61 | }
62 |
63 | func Bool2Int(b bool) int {
64 | if b {
65 | return 1
66 | }
67 | return 0
68 | }
69 | func Bool2U8(b bool) byte {
70 | if b {
71 | return 1
72 | }
73 | return 0
74 | }
75 | func Bool2U16(b bool) uint16 {
76 | if b {
77 | return 1
78 | }
79 | return 0
80 | }
81 | func Bool2U32(b bool) uint32 {
82 | if b {
83 | return 1
84 | }
85 | return 0
86 | }
87 | func Bool2U64(b bool) uint64 {
88 | if b {
89 | return 1
90 | }
91 | return 0
92 | }
93 |
94 | func SetMSB(val byte, b bool) byte {
95 | if b {
96 | val |= 0x80
97 | } else {
98 | val &= 0x7f
99 | }
100 | return val
101 | }
102 |
103 | func SetLSB(val byte, b bool) byte {
104 | if b {
105 | val |= 1
106 | } else {
107 | val &= 0xfe
108 | }
109 | return val
110 | }
111 |
--------------------------------------------------------------------------------
/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "flag"
6 | "fmt"
7 | "io/ioutil"
8 | "os"
9 | "path/filepath"
10 |
11 | "github.com/hajimehoshi/ebiten/v2"
12 | "github.com/pokemium/worldwide/pkg/emulator"
13 | )
14 |
15 | var version string
16 |
17 | const (
18 | title = "worldwide"
19 | )
20 |
21 | const (
22 | ExitCodeOK int = iota
23 | ExitCodeError
24 | )
25 |
26 | func init() {
27 | if version == "" {
28 | version = "Develop"
29 | }
30 |
31 | flag.Usage = func() {
32 | usage := fmt.Sprintf(`Usage:
33 | %s [arg] [input]
34 | e.g. %s -p 8888 ./PM_PRISM.gbc
35 | Input: ROM filepath, ***.gb or ***.gbc
36 | Arguments:
37 | `, title, title)
38 | fmt.Println(Version())
39 | fmt.Fprint(os.Stderr, usage)
40 | flag.PrintDefaults()
41 | }
42 | }
43 |
44 | func main() {
45 | os.Exit(Run())
46 | }
47 |
48 | // Run program
49 | func Run() int {
50 | var (
51 | showVersion = flag.Bool("v", false, "show version")
52 | port = flag.Int("p", 0, "HTTP server port (>1023)")
53 | )
54 |
55 | flag.Parse()
56 |
57 | if *showVersion {
58 | fmt.Println(Version())
59 | return ExitCodeOK
60 | }
61 |
62 | romPath := flag.Arg(0)
63 | cur, _ := os.Getwd()
64 |
65 | romDir := filepath.Dir(romPath)
66 | romData, err := readROM(romPath)
67 | if err != nil {
68 | fmt.Fprintf(os.Stderr, "ROM Error: %s\n", err)
69 | return ExitCodeError
70 | }
71 |
72 | emu := emulator.New(romData, romDir)
73 | if *port > 0 {
74 | if *port < 1024 {
75 | fmt.Fprintf(os.Stderr, "Server Error: cannot use well-known port for server")
76 | } else {
77 | go emu.RunServer(*port)
78 | }
79 | }
80 |
81 | os.Chdir(cur)
82 | defer func() {
83 | os.Chdir(cur)
84 | }()
85 |
86 | if err := ebiten.RunGame(emu); err != nil {
87 | if err.Error() == "quit" {
88 | emu.Exit()
89 | return ExitCodeOK
90 | }
91 | return ExitCodeError
92 | }
93 | emu.Exit()
94 | return ExitCodeOK
95 | }
96 |
97 | func Version() string {
98 | return fmt.Sprintf("%s: %s", title, version)
99 | }
100 |
101 | func readROM(path string) ([]byte, error) {
102 | if path == "" {
103 | return []byte{}, errors.New("please type .gb or .gbc file path")
104 | }
105 | if filepath.Ext(path) != ".gb" && filepath.Ext(path) != ".gbc" {
106 | return []byte{}, errors.New("please type .gb or .gbc file")
107 | }
108 |
109 | bytes, err := ioutil.ReadFile(path)
110 | if err != nil {
111 | return []byte{}, errors.New("fail to read file")
112 | }
113 | return bytes, nil
114 | }
115 |
--------------------------------------------------------------------------------
/test/mooneye-gb/oam_dma/reg_read/reg_read.sym:
--------------------------------------------------------------------------------
1 | ; this file was created with wlalink by ville helin .
2 | ; wla symbolic information for "acceptance/oam_dma/reg_read.gb".
3 |
4 | [labels]
5 | 01:48b9 clear_vram
6 | 01:4884 disable_lcd_safe
7 | 01:488a disable_lcd_safe@wait_ly_0
8 | 01:48c3 memcpy
9 | 01:48cc memset
10 | 01:48dc print_inline_string
11 | 01:48a2 print_load_font
12 | 01:48ae print_newline
13 | 01:48d5 print_string
14 | 01:47f0 quit
15 | 01:4805 quit@cb_return
16 | 01:480a quit@wait_ly_1
17 | 01:4810 quit@wait_ly_2
18 | 01:4816 quit@wait_ly_3
19 | 01:481c quit@wait_ly_4
20 | 01:4826 quit@success
21 | 01:484d quit@failure
22 | 01:486c quit@halt
23 | 01:486d quit@halt_execution_0
24 | 01:4870 reset_screen
25 | 01:4893 serial_send_byte
26 | 01:4000 font
27 | 00:0150 main
28 | 00:0156 prepare_part1
29 | 00:0162 round1
30 | 00:016e round2
31 | 00:017a prepare_part2
32 | 00:0186 round3
33 | 00:0190 round4
34 | 00:019a prepare_part3
35 | 00:01a6 round5
36 | 00:01b2 round6
37 | 00:01be finish
38 | 00:01c5 finish@quit_inline_1
39 | 00:01d6 fail_round1
40 | 00:01dd fail_round1@quit_inline_2
41 | 00:01ef fail_round2
42 | 00:01f6 fail_round2@quit_inline_3
43 | 00:0208 fail_round3
44 | 00:020f fail_round3@quit_inline_4
45 | 00:0221 fail_round4
46 | 00:0228 fail_round4@quit_inline_5
47 | 00:023a fail_round5
48 | 00:0241 fail_round5@quit_inline_6
49 | 00:0253 fail_round6
50 | 00:025a fail_round6@quit_inline_7
51 | 00:026c dma_proc1
52 | 00:0274 dma_proc2
53 | 00:027e dma_proc3
54 | 00:0289 _end_dma_procs
55 | 00:ff80 hram.dma_proc
56 |
57 | [definitions]
58 | 0000000a _sizeof_clear_vram
59 | 0000000f _sizeof_disable_lcd_safe
60 | 00000009 _sizeof_memcpy
61 | 00000009 _sizeof_memset
62 | 00000006 _sizeof_print_inline_string
63 | 0000000c _sizeof_print_load_font
64 | 0000000b _sizeof_print_newline
65 | 00000007 _sizeof_print_string
66 | 00000080 _sizeof_quit
67 | 00000014 _sizeof_reset_screen
68 | 0000000f _sizeof_serial_send_byte
69 | 000007f0 _sizeof_font
70 | 00000010 _sizeof_hram.dma_proc
71 | 00000006 _sizeof_main
72 | 0000000c _sizeof_prepare_part1
73 | 0000000c _sizeof_round1
74 | 0000000c _sizeof_round2
75 | 0000000c _sizeof_prepare_part2
76 | 0000000a _sizeof_round3
77 | 0000000a _sizeof_round4
78 | 0000000c _sizeof_prepare_part3
79 | 0000000c _sizeof_round5
80 | 0000000c _sizeof_round6
81 | 00000018 _sizeof_finish
82 | 00000019 _sizeof_fail_round1
83 | 00000019 _sizeof_fail_round2
84 | 00000019 _sizeof_fail_round3
85 | 00000019 _sizeof_fail_round4
86 | 00000019 _sizeof_fail_round5
87 | 00000019 _sizeof_fail_round6
88 | 00000008 _sizeof_dma_proc1
89 | 0000000a _sizeof_dma_proc2
90 | 0000000b _sizeof_dma_proc3
91 |
--------------------------------------------------------------------------------
/README.ja.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # 🌏 worldwide
4 | 
5 | [](https://goreportcard.com/report/github.com/pokemium/worldwide)
6 | [](https://github.com/pokemium/worldwide/stargazers)
7 | [](https://github.com/pokemium/worldwide/blob/master/LICENSE)
8 |
9 | Go言語で書かれたゲームボーイカラーエミュレータです。
10 |
11 | 多くのROMが問題なく動作し、サウンド機能やセーブ機能など幅広い機能を備えたエミュレータです。
12 |
13 |

14 |
15 |
16 | ## 🚩 このエミュレータの特徴 & 今後実装予定の機能
17 | - [x] 60fpsで動作
18 | - [x] [cpu_instrs](https://github.com/retrio/gb-test-roms/tree/master/cpu_instrs) と [instr_timing](https://github.com/retrio/gb-test-roms/tree/master/instr_timing)というテストROMをクリアしています
19 | - [x] 少ないCPU使用率
20 | - [x] サウンドの実装
21 | - [x] ゲームボーイカラーのソフトに対応
22 | - [x] WindowsやLinuxなど様々なプラットフォームに対応
23 | - [x] MBC1, MBC2, MBC3, MBC5に対応
24 | - [x] RTCの実装
25 | - [x] セーブ機能をサポート(得られたsavファイルは実機やBGBなどの一般的なエミュレータで利用できます)
26 | - [x] ウィンドウの縮小拡大が可能
27 | - [x] HTTPサーバーAPI
28 | - [ ] プラグイン機能
29 | - [ ] [Libretro](https://docs.libretro.com/) APIのサポート
30 | - [ ] ローカルネットワーク内での通信プレイ
31 | - [ ] グローバルネットワーク内での通信プレイ
32 | - [ ] SGBのサポート
33 | - [ ] シェーダのサポート
34 |
35 | ## 🎮 使い方
36 |
37 | [ここ](https://github.com/pokemium/worldwide/releases)から実行ファイルをダウンロードした後、次のように起動します。
38 |
39 | ```sh
40 | ./worldwide "***.gb" # もしくは `***.gbc`
41 | ```
42 |
43 | ## 🐛 HTTPサーバー
44 |
45 | `worldwide`はHTTPサーバーを内包しており、ユーザーはHTTPリクエストを通じて `worldwide`にさまざまな指示を出すことが可能です。
46 |
47 | [サーバードキュメント](./server/README.md)を参照してください。
48 |
49 | ## 🔨 ビルド
50 |
51 | ソースコードからビルドしたい方向けです。
52 |
53 | requirements
54 | - Go 1.16
55 | - make
56 |
57 | ```sh
58 | make build # Windowsなら `make build-windows`
59 | ./build/darwin-amd64/worldwide "***.gb" # Windowsなら `./build/windows-amd64/worldwide.exe "***.gb"`
60 | ```
61 |
62 | ## 📄 コマンド
63 |
64 | | キー入力 | コマンド |
65 | | -------------------- | ------------- |
66 | | ← | ← ボタン |
67 | | ↑ | ↑ ボタン |
68 | | ↓ | ↓ ボタン |
69 | | → | → ボタン |
70 | | X | A ボタン |
71 | | Z | B ボタン |
72 | | Enter | Start ボタン |
73 | | Backspace | Select ボタン |
74 |
--------------------------------------------------------------------------------
/pkg/emulator/save.go:
--------------------------------------------------------------------------------
1 | package emulator
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 |
7 | "github.com/pokemium/worldwide/pkg/gbc/cart"
8 | )
9 |
10 | // GameBoy save data is SRAM core dump
11 | func (e *Emulator) writeSav() {
12 | savname := filepath.Join(e.RomDir, e.GBC.Cartridge.Title+".sav")
13 |
14 | savfile, err := os.Create(savname)
15 | if err != nil {
16 | return
17 | }
18 | defer savfile.Close()
19 |
20 | var buffer []byte
21 | switch e.GBC.Cartridge.RAMSize {
22 | case cart.RAM_UNUSED:
23 | buffer = make([]byte, 0x800)
24 | for index := 0; index < 0x800; index++ {
25 | buffer[index] = e.GBC.RAM.Buffer[0][index]
26 | }
27 | case cart.RAM_8KB:
28 | buffer = make([]byte, 0x2000*1)
29 | for index := 0; index < 0x2000; index++ {
30 | buffer[index] = e.GBC.RAM.Buffer[0][index]
31 | }
32 | case cart.RAM_32KB:
33 | buffer = make([]byte, 0x2000*4)
34 | for i := 0; i < 4; i++ {
35 | for j := 0; j < 0x2000; j++ {
36 | index := i*0x2000 + j
37 | buffer[index] = e.GBC.RAM.Buffer[i][j]
38 | }
39 | }
40 | case cart.RAM_64KB:
41 | buffer = make([]byte, 0x2000*8)
42 | for i := 0; i < 8; i++ {
43 | for j := 0; j < 0x2000; j++ {
44 | index := i*0x2000 + j
45 | buffer[index] = e.GBC.RAM.Buffer[i][j]
46 | }
47 | }
48 | }
49 |
50 | if e.GBC.Cartridge.HasRTC() {
51 | rtcData := e.GBC.RTC.Dump()
52 | for i := 0; i < 48; i++ {
53 | buffer = append(buffer, rtcData[i])
54 | }
55 | }
56 |
57 | _, err = savfile.Write(buffer)
58 | if err != nil {
59 | return
60 | }
61 | }
62 |
63 | func (e *Emulator) loadSav() {
64 | savname := filepath.Join(e.RomDir, e.GBC.Cartridge.Title+".sav")
65 |
66 | savdata, err := os.ReadFile(savname)
67 | if err != nil {
68 | return
69 | }
70 |
71 | switch e.GBC.Cartridge.RAMSize {
72 | case cart.RAM_UNUSED:
73 | for index := 0; index < 0x800; index++ {
74 | e.GBC.RAM.Buffer[0][index] = savdata[index]
75 | }
76 | case cart.RAM_8KB:
77 | for index := 0; index < 0x2000; index++ {
78 | e.GBC.RAM.Buffer[0][index] = savdata[index]
79 | }
80 | case cart.RAM_32KB:
81 | for i := 0; i < 4; i++ {
82 | for j := 0; j < 0x2000; j++ {
83 | index := i*0x2000 + j
84 | e.GBC.RAM.Buffer[i][j] = savdata[index]
85 | }
86 | }
87 | case cart.RAM_64KB:
88 | for i := 0; i < 8; i++ {
89 | for j := 0; j < 0x2000; j++ {
90 | index := i*0x2000 + j
91 | e.GBC.RAM.Buffer[i][j] = savdata[index]
92 | }
93 | }
94 | }
95 |
96 | if e.GBC.Cartridge.HasRTC() {
97 | start := (len(savdata) / 0x1000) * 0x1000
98 | rtcData := savdata[start : start+48]
99 | e.GBC.RTC.Sync(rtcData)
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/pkg/emulator/debug/sprview.go:
--------------------------------------------------------------------------------
1 | package debug
2 |
3 | import (
4 | "bytes"
5 | "image"
6 | "image/color"
7 | "image/jpeg"
8 | "log"
9 | "sync"
10 | "time"
11 |
12 | "github.com/pokemium/worldwide/pkg/gbc/video"
13 | "github.com/pokemium/worldwide/pkg/util"
14 | "golang.org/x/net/websocket"
15 | )
16 |
17 | func (d *Debugger) getRawSprView() [40][64 * 4]byte {
18 | buffer := [40][64 * 4]byte{}
19 |
20 | for i := 0; i < 40; i++ {
21 | for j := 0; j < 64; j++ {
22 | buffer[i][j*4] = 0xff
23 | buffer[i][j*4+1] = 0xff
24 | buffer[i][j*4+2] = 0xff
25 | buffer[i][j*4+3] = 0xff
26 | }
27 |
28 | y := int(d.g.Video.Oam.Get(4 * uint16(i)))
29 | if y <= 0 || y >= 160 {
30 | continue
31 | }
32 |
33 | objTile := int(d.g.Video.Oam.Get(4*uint16(i) + 2))
34 | attr := d.g.Video.Oam.Get(4*uint16(i) + 3)
35 |
36 | for y := 0; y < 8; y++ {
37 | tileDataLower := d.g.Video.VRAM.Buffer[(objTile*8+y)*2]
38 | tileDataUpper := d.g.Video.VRAM.Buffer[(objTile*8+y)*2+1]
39 |
40 | for x := 0; x < 8; x++ {
41 | b := 7 - x
42 | palIdx := uint16(((tileDataUpper>>b)&0b1)<<1) | uint16((tileDataLower>>b)&1) // 0 or 1 or 2 or 3
43 | base := video.PAL_OBJ + 4*util.Bool2U16(util.Bit(attr, 4)) // 8*4 or 9*4
44 | p := d.g.Video.Renderer.Palette[d.g.Video.Renderer.Lookup[base+palIdx]]
45 | buffer[i][(y*8+x)*4], buffer[i][(y*8+x)*4+1], buffer[i][(y*8+x)*4+2] = byte((p&0b11111)*8), byte(((p>>5)&0b11111)*8), byte(((p>>10)&0b11111)*8)
46 | }
47 | }
48 | }
49 |
50 | return buffer
51 | }
52 |
53 | func (d *Debugger) getSprView() []byte {
54 | rawBuffer := d.getRawSprView()
55 | m := image.NewRGBA(image.Rect(0, 0, 8*8, 8*5))
56 |
57 | var wg sync.WaitGroup
58 | wg.Add(40)
59 | for i := 0; i < 40; i++ {
60 | go func(i int) {
61 | col, row := i&0x7, i/8
62 | for y := 0; y < 8; y++ {
63 | for x := 0; x < 8; x++ {
64 | idx := (y*8 + x) * 4
65 | m.Set(col*8+x, row*8+y, color.RGBA{rawBuffer[i][idx], rawBuffer[i][idx+1], rawBuffer[i][idx+2], rawBuffer[i][idx+3]})
66 | }
67 | }
68 | wg.Done()
69 | }(i)
70 | }
71 | wg.Wait()
72 |
73 | buffer := new(bytes.Buffer)
74 | if err := jpeg.Encode(buffer, m, nil); err != nil {
75 | log.Println("unable to encode image.")
76 | }
77 |
78 | return buffer.Bytes()
79 | }
80 |
81 | func (d *Debugger) SprView(ws *websocket.Conn) {
82 | err := websocket.Message.Send(ws, d.getSprView())
83 | if err != nil {
84 | log.Printf("error sending data: %v\n", err)
85 | return
86 | }
87 |
88 | for range time.NewTicker(time.Millisecond * 100).C {
89 | err := websocket.Message.Send(ws, d.getSprView())
90 | if err != nil {
91 | log.Printf("error sending data: %v\n", err)
92 | return
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/pkg/emulator/emulator.go:
--------------------------------------------------------------------------------
1 | package emulator
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "os"
7 | "os/signal"
8 | "syscall"
9 | "time"
10 |
11 | "github.com/hajimehoshi/ebiten/v2"
12 | "github.com/pokemium/worldwide/pkg/emulator/audio"
13 | "github.com/pokemium/worldwide/pkg/emulator/debug"
14 | "github.com/pokemium/worldwide/pkg/emulator/joypad"
15 | "github.com/pokemium/worldwide/pkg/gbc"
16 | )
17 |
18 | var (
19 | second = time.NewTicker(time.Second)
20 | cache []byte
21 | )
22 |
23 | type Emulator struct {
24 | GBC *gbc.GBC
25 | Rom []byte
26 | RomDir string
27 | debugger *debug.Debugger
28 | pause bool
29 | reset bool
30 | quit bool
31 | }
32 |
33 | func New(romData []byte, romDir string) *Emulator {
34 | g := gbc.New(romData, joypad.Handler, audio.SetStream)
35 | audio.Reset(&g.Sound.Enable)
36 |
37 | ebiten.SetWindowResizable(true)
38 | ebiten.SetWindowTitle("60fps")
39 | ebiten.SetWindowSize(160*2, 144*2)
40 |
41 | e := &Emulator{
42 | GBC: g,
43 | Rom: romData,
44 | RomDir: romDir,
45 | }
46 | e.debugger = debug.New(g, &e.pause)
47 | e.setupCloseHandler()
48 |
49 | e.loadSav()
50 | return e
51 | }
52 |
53 | func (e *Emulator) ResetGBC() {
54 | e.writeSav()
55 |
56 | oldCallbacks := e.GBC.Callbacks
57 | e.GBC = gbc.New(e.Rom, joypad.Handler, audio.SetStream)
58 | e.GBC.Callbacks = oldCallbacks
59 |
60 | e.debugger.Reset(e.GBC)
61 | e.loadSav()
62 |
63 | e.reset = false
64 | }
65 |
66 | func (e *Emulator) Update() error {
67 | if e.quit {
68 | return errors.New("quit")
69 | }
70 | if e.reset {
71 | e.ResetGBC()
72 | return nil
73 | }
74 | if e.pause {
75 | return nil
76 | }
77 |
78 | defer e.GBC.PanicHandler("update", true)
79 | e.GBC.Update()
80 | if e.pause {
81 | return nil
82 | }
83 |
84 | audio.Play()
85 |
86 | select {
87 | case <-second.C:
88 | e.GBC.RTC.IncrementSecond()
89 | ebiten.SetWindowTitle(fmt.Sprintf("%dfps", int(ebiten.CurrentTPS())))
90 | default:
91 | }
92 |
93 | return nil
94 | }
95 |
96 | func (e *Emulator) Draw(screen *ebiten.Image) {
97 | if e.pause {
98 | screen.ReplacePixels(cache)
99 | return
100 | }
101 |
102 | defer e.GBC.PanicHandler("draw", true)
103 | cache = e.GBC.Draw()
104 | screen.ReplacePixels(cache)
105 | }
106 |
107 | func (e *Emulator) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
108 | return 160, 144
109 | }
110 |
111 | func (e *Emulator) Exit() {
112 | e.writeSav()
113 | }
114 |
115 | func (e *Emulator) setupCloseHandler() {
116 | c := make(chan os.Signal)
117 | signal.Notify(c, os.Interrupt, syscall.SIGTERM)
118 | go func() {
119 | <-c
120 | e.Exit()
121 | os.Exit(0)
122 | }()
123 | }
124 |
--------------------------------------------------------------------------------
/pkg/gbc/register.go:
--------------------------------------------------------------------------------
1 | package gbc
2 |
3 | const (
4 | A = iota
5 | B
6 | C
7 | D
8 | E
9 | H
10 | L
11 | F
12 | )
13 |
14 | const (
15 | AF = iota
16 | BC
17 | DE
18 | HL
19 | HLI
20 | HLD
21 | SP
22 | PC
23 | )
24 | const (
25 | flagZ, flagN, flagH, flagC = 7, 6, 5, 4
26 | )
27 |
28 | // Register Z80
29 | type Register struct {
30 | R [8]byte
31 | SP uint16
32 | PC uint16
33 | IME bool
34 | }
35 |
36 | func (r *Register) R16(i int) uint16 {
37 | switch i {
38 | case AF:
39 | return r.AF()
40 | case BC:
41 | return r.BC()
42 | case DE:
43 | return r.DE()
44 | case HL:
45 | return r.HL()
46 | case HLD:
47 | hl := r.HL()
48 | r.setHL(hl - 1)
49 | return hl
50 | case HLI:
51 | hl := r.HL()
52 | r.setHL(hl + 1)
53 | return hl
54 | case SP:
55 | return r.SP
56 | case PC:
57 | return r.PC
58 | }
59 | panic("invalid register16")
60 | }
61 |
62 | func (r *Register) setR16(i int, val uint16) {
63 | switch i {
64 | case AF:
65 | r.setAF(val)
66 | case BC:
67 | r.setBC(val)
68 | case DE:
69 | r.setDE(val)
70 | case HL:
71 | r.setHL(val)
72 | case SP:
73 | r.SP = val
74 | case PC:
75 | r.PC = val
76 | }
77 | }
78 |
79 | func (r *Register) AF() uint16 {
80 | return (uint16(r.R[A]) << 8) | uint16(r.R[F])
81 | }
82 | func (r *Register) setAF(value uint16) {
83 | r.R[A], r.R[F] = byte(value>>8), byte(value)
84 | }
85 |
86 | func (r *Register) BC() uint16 {
87 | return (uint16(r.R[B]) << 8) | uint16(r.R[C])
88 | }
89 | func (r *Register) setBC(value uint16) {
90 | r.R[B], r.R[C] = byte(value>>8), byte(value)
91 | }
92 |
93 | func (r *Register) DE() uint16 {
94 | return (uint16(r.R[D]) << 8) | uint16(r.R[E])
95 | }
96 | func (r *Register) setDE(value uint16) {
97 | r.R[D], r.R[E] = byte(value>>8), byte(value)
98 | }
99 |
100 | func (r *Register) HL() uint16 {
101 | return (uint16(r.R[H]) << 8) | uint16(r.R[L])
102 | }
103 | func (r *Register) setHL(value uint16) {
104 | r.R[H], r.R[L] = byte(value>>8), byte(value)
105 | }
106 |
107 | // flag
108 |
109 | func subC(dst, src byte) bool { return dst < uint8(dst-src) }
110 |
111 | func (g *GBC) f(idx int) bool {
112 | return g.Reg.R[F]&(1<", event.name, event.when)
129 | event = event.next
130 | }
131 | return result
132 | }
133 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > [!CAUTION]
2 | > This GameBoy emulator project is old and NOT MAINTAINED, please see [DawnGB](https://github.com/akatsuki105/dawngb), my new GameBoy emulator project!
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | 
11 |
12 | # 🌏 worldwide
13 | 
14 | [](https://goreportcard.com/report/github.com/pokemium/worldwide)
15 | [](https://github.com/pokemium/worldwide/stargazers)
16 | [](https://github.com/pokemium/worldwide/blob/master/LICENSE)
17 |
18 | 日本語のドキュメントは[こちら](./README.ja.md)
19 |
20 | GameBoyColor emulator written in golang.
21 |
22 | This emulator can play a lot of ROMs work without problems and has many features.
23 |
24 |

25 |
26 |
27 | ## 🚩 Features & TODO list
28 | - [x] 60fps
29 | - [x] Pass [cpu_instrs](https://github.com/retrio/gb-test-roms/tree/master/cpu_instrs) and [instr_timing](https://github.com/retrio/gb-test-roms/tree/master/instr_timing)
30 | - [x] Low CPU consumption
31 | - [x] Sound(ported from goboy)
32 | - [x] GameBoy Color ROM support
33 | - [x] Multi-platform support
34 | - [x] MBC1, MBC2, MBC3, MBC5 support
35 | - [x] RTC
36 | - [x] SRAM save
37 | - [x] Resizable window
38 | - [x] HTTP server API
39 | - [ ] Plugins support
40 | - [ ] [Libretro](https://docs.libretro.com/) support
41 | - [ ] Netplay in local network
42 | - [ ] Netplay in global network
43 | - [ ] SGB support
44 | - [ ] Shader support
45 |
46 | ## 🎮 Usage
47 |
48 | Download binary from [here](https://github.com/pokemium/worldwide/releases).
49 |
50 | ```sh
51 | ./worldwide "***.gb" # or ***.gbc
52 | ```
53 |
54 | ## 🐛 HTTP Server
55 |
56 | `worldwide` contains an HTTP server, and the user can give various instructions to it through HTTP requests.
57 |
58 | Please read [Server Document](./server/README.md).
59 |
60 | ## 🔨 Build
61 |
62 | For those who want to build from source code.
63 |
64 | Requirements
65 | - Go 1.16
66 | - make
67 |
68 | ```sh
69 | make build # If you use Windows, `make build-windows`
70 | ./build/darwin-amd64/worldwide "***.gb" # If you use Windows, `./build/windows-amd64/worldwide.exe "***.gb"`
71 | ```
72 |
73 | ## 📄 Command
74 |
75 | | keyboard | game pad |
76 | | -------------------- | ------------- |
77 | | ← | ← button |
78 | | ↑ | ↑ button |
79 | | ↓ | ↓ button |
80 | | → | → button |
81 | | X | A button |
82 | | Z | B button |
83 | | Enter | Start button |
84 | | Backspace | Select button |
85 |
--------------------------------------------------------------------------------
/pkg/emulator/debug/tileview.go:
--------------------------------------------------------------------------------
1 | package debug
2 |
3 | import (
4 | "bytes"
5 | "image"
6 | "image/color"
7 | "image/jpeg"
8 | "log"
9 | "sync"
10 | "time"
11 |
12 | "golang.org/x/net/websocket"
13 | )
14 |
15 | const TILENUM = 384 // cgb -> 384*2 (bank1)
16 |
17 | func (d *Debugger) getRawTileView(bank int) []byte {
18 | buffer := make([]byte, TILENUM*64*4)
19 |
20 | for i := 0; i < TILENUM; i++ {
21 | addr := 16 * i
22 |
23 | for y := 0; y < 8; y++ {
24 | tileAddr := addr + 2*y + 0x2000*bank
25 | tileDataLower, tileDataUpper := d.g.Video.VRAM.Buffer[tileAddr], d.g.Video.VRAM.Buffer[tileAddr+1]
26 |
27 | for x := 0; x < 8; x++ {
28 | b := (7 - uint(x))
29 | upperColor := (tileDataUpper >> b) & 0x01
30 | lowerColor := (tileDataLower >> b) & 0x01
31 | palIdx := (upperColor << 1) | lowerColor // 0 or 1 or 2 or 3
32 | p := d.g.Video.Palette[d.g.Video.Renderer.Lookup[palIdx]]
33 | red, green, blue := byte((p&0b11111)*8), byte(((p>>5)&0b11111)*8), byte(((p>>10)&0b11111)*8)
34 | bufferIdx := i*64*4 + y*8*4 + x*4
35 | buffer[bufferIdx] = red
36 | buffer[bufferIdx+1] = green
37 | buffer[bufferIdx+2] = blue
38 | buffer[bufferIdx+3] = 0xff
39 | }
40 | }
41 | }
42 |
43 | return buffer
44 | }
45 |
46 | func (d *Debugger) getTileView(bank int) []byte {
47 | rawBuffer := d.getRawTileView(bank)
48 | m := image.NewRGBA(image.Rect(0, 0, 8*16, 8*384/TILE_PER_ROW))
49 | var wg sync.WaitGroup
50 | wg.Add(384 / TILE_PER_ROW)
51 | for row := 0; row < 384/TILE_PER_ROW; row++ {
52 | // 0..63, 0..63, 0..63, .. -> 0..7, 0..7, ... 8..15, 8..15,
53 | go func(row int) {
54 | rowStart, rowEnd := row*TILE_PER_ROW, (row+1)*TILE_PER_ROW
55 | rowBuffer := rawBuffer[rowStart*64*4 : rowEnd*64*4]
56 |
57 | for t := 0; t < TILE_PER_ROW; t++ {
58 | rowBufferBase := t * 64 * 4
59 | for y := 0; y < 8; y++ {
60 | tileRowBuffer := rowBuffer[rowBufferBase+y*8*4 : rowBufferBase+(y+1)*8*4] // (y*8)..((y*8)+7)
61 | for x := 0; x < 8; x++ {
62 | m.SetRGBA(t*8+x, row*8+y, color.RGBA{tileRowBuffer[x*4], tileRowBuffer[x*4+1], tileRowBuffer[x*4+2], tileRowBuffer[x*4+3]})
63 | }
64 | }
65 | }
66 | wg.Done()
67 | }(row)
68 | }
69 | wg.Wait()
70 |
71 | buffer := new(bytes.Buffer)
72 | if err := jpeg.Encode(buffer, m, nil); err != nil {
73 | log.Println("unable to encode image.")
74 | }
75 |
76 | return buffer.Bytes()
77 | }
78 |
79 | func (d *Debugger) tileView(ws *websocket.Conn, bank int) {
80 | err := websocket.Message.Send(ws, d.getTileView(bank))
81 | if err != nil {
82 | log.Printf("error sending data: %v\n", err)
83 | return
84 | }
85 |
86 | for range time.NewTicker(time.Millisecond * 100).C {
87 | err := websocket.Message.Send(ws, d.getTileView(bank))
88 | if err != nil {
89 | log.Printf("error sending data: %v\n", err)
90 | return
91 | }
92 | }
93 | }
94 |
95 | func (d *Debugger) TileView0(ws *websocket.Conn) {
96 | d.tileView(ws, 0)
97 | }
98 |
99 | func (d *Debugger) TileView1(ws *websocket.Conn) {
100 | d.tileView(ws, 1)
101 | }
102 |
--------------------------------------------------------------------------------
/pkg/emulator/debug/register.go:
--------------------------------------------------------------------------------
1 | package debug
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io"
7 | "net/http"
8 | "strconv"
9 | "strings"
10 |
11 | "github.com/pokemium/worldwide/pkg/gbc"
12 | "github.com/pokemium/worldwide/pkg/util"
13 | )
14 |
15 | type Register struct {
16 | A string
17 | F string
18 | B string
19 | C string
20 | D string
21 | E string
22 | H string
23 | L string
24 | PC string
25 | SP string
26 | IE, IF string
27 | IME string
28 | Halt string
29 | DoubleSpeed string
30 | }
31 |
32 | var regs = map[string]int{
33 | "a": 0,
34 | "b": 1,
35 | "c": 2,
36 | "d": 3,
37 | "e": 4,
38 | "h": 5,
39 | "l": 6,
40 | "f": 7,
41 | }
42 |
43 | func (d *Debugger) Register(w http.ResponseWriter, req *http.Request) {
44 | switch req.Method {
45 | case "GET":
46 | r := Register{
47 | A: fmt.Sprintf("0x%02x", d.g.Reg.R[gbc.A]),
48 | F: fmt.Sprintf("0x%02x", d.g.Reg.R[gbc.F]),
49 | B: fmt.Sprintf("0x%02x", d.g.Reg.R[gbc.B]),
50 | C: fmt.Sprintf("0x%02x", d.g.Reg.R[gbc.C]),
51 | D: fmt.Sprintf("0x%02x", d.g.Reg.R[gbc.D]),
52 | E: fmt.Sprintf("0x%02x", d.g.Reg.R[gbc.E]),
53 | H: fmt.Sprintf("0x%02x", d.g.Reg.R[gbc.H]),
54 | L: fmt.Sprintf("0x%02x", d.g.Reg.R[gbc.L]),
55 | PC: fmt.Sprintf("0x%04x", d.g.Reg.PC),
56 | SP: fmt.Sprintf("0x%04x", d.g.Reg.SP),
57 | IE: fmt.Sprintf("0x%02x", d.g.IO[gbc.IEIO]),
58 | IF: fmt.Sprintf("0x%02x", d.g.IO[gbc.IFIO]),
59 | IME: fmt.Sprintf("0x%02x", util.Bool2U8(d.g.Reg.IME)),
60 | Halt: fmt.Sprintf("%+v", d.g.Halt),
61 | DoubleSpeed: fmt.Sprintf("%+v", d.g.DoubleSpeed),
62 | }
63 |
64 | res, err := json.Marshal(r)
65 | if err != nil {
66 | http.Error(w, err.Error(), http.StatusInternalServerError)
67 | return
68 | }
69 |
70 | w.Header().Set("Content-Type", "application/json")
71 | w.Write(res)
72 | case "POST":
73 | body, _ := io.ReadAll(req.Body)
74 | keyVal := make(map[string]string)
75 | json.Unmarshal(body, &keyVal)
76 |
77 | target := strings.ToLower(keyVal["target"])
78 | val := strings.ToLower(keyVal["value"])
79 | if !strings.HasPrefix(val, "0x") {
80 | http.Error(w, "value must be hexadecimal (e.g. 0x04)", http.StatusBadRequest)
81 | return
82 | }
83 |
84 | switch target {
85 | case "a", "b", "c", "d", "e", "h", "l", "f":
86 | a, _ := strconv.ParseUint(val[2:], 16, 8)
87 | val := byte(a)
88 | d.g.Reg.R[regs[target]] = val
89 | case "pc":
90 | a, _ := strconv.ParseUint(val[2:], 16, 16)
91 | val := uint16(a)
92 | d.g.Reg.PC = val
93 | case "sp":
94 | a, _ := strconv.ParseUint(val[2:], 16, 16)
95 | val := uint16(a)
96 | d.g.Reg.SP = val
97 | case "ime":
98 | val, _ := strconv.ParseUint(val[2:], 16, 1) // 0x0 or 0x1
99 | d.g.Reg.IME = val > 0
100 | default:
101 | http.Error(w, "invalid target", http.StatusBadRequest)
102 | }
103 | default:
104 | http.NotFound(w, req)
105 | return
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/test/mooneye-gb/oam_dma/sources-GS/sources-GS.sym:
--------------------------------------------------------------------------------
1 | ; this file was created with wlalink by ville helin .
2 | ; wla symbolic information for "acceptance/oam_dma/sources-GS.gb".
3 |
4 | [labels]
5 | 01:4967 clear_oam
6 | 01:4971 clear_vram
7 | 01:497b clear_wram
8 | 01:4924 disable_lcd_safe
9 | 01:492a disable_lcd_safe@wait_ly_0
10 | 01:4942 memcmp
11 | 01:4985 memcpy
12 | 01:498e memset
13 | 01:499e print_inline_string
14 | 01:4950 print_load_font
15 | 01:495c print_newline
16 | 01:4997 print_string
17 | 01:4890 quit
18 | 01:48a5 quit@cb_return
19 | 01:48aa quit@wait_ly_1
20 | 01:48b0 quit@wait_ly_2
21 | 01:48b6 quit@wait_ly_3
22 | 01:48bc quit@wait_ly_4
23 | 01:48c6 quit@success
24 | 01:48ed quit@failure
25 | 01:490c quit@halt
26 | 01:490d quit@halt_execution_0
27 | 01:4910 reset_screen
28 | 01:4933 serial_send_byte
29 | 01:40a0 font
30 | 00:0150 main
31 | 00:015f prepare_part1
32 | 00:016b test_0000
33 | 00:0180 test_0000@quit_inline_1
34 | 00:0195 test_3f00
35 | 00:01aa test_3f00@quit_inline_2
36 | 00:01bf test_4000
37 | 00:01d4 test_4000@quit_inline_3
38 | 00:01e9 test_7f00
39 | 00:01fe test_7f00@quit_inline_4
40 | 00:0213 prepare_part2
41 | 00:021f test_8000
42 | 00:0234 test_8000@quit_inline_5
43 | 00:0249 test_9f00
44 | 00:025e test_9f00@quit_inline_6
45 | 00:0273 prepare_part3
46 | 00:0291 test_a000
47 | 00:02a6 test_a000@quit_inline_7
48 | 00:02bb test_bf00
49 | 00:02d0 test_bf00@quit_inline_8
50 | 00:02e5 prepare_part4
51 | 00:02fb test_c000
52 | 00:0310 test_c000@quit_inline_9
53 | 00:0325 test_df00
54 | 00:033a test_df00@quit_inline_10
55 | 00:034f test_e000
56 | 00:0364 test_e000@quit_inline_11
57 | 00:0379 test_fe00
58 | 00:0391 test_fe00@quit_inline_12
59 | 00:03a6 test_ff00
60 | 00:03bb test_ff00@quit_inline_13
61 | 00:03d0 test_finish
62 | 00:03d7 test_finish@quit_inline_14
63 | 00:03e8 check_oam
64 | 00:03f1 dma_proc
65 | 00:03f9 copy_ram_pattern_1
66 | 00:0402 ram_pattern_1
67 | 00:04a2 copy_ram_pattern_2
68 | 00:04ab ram_pattern_2
69 | 00:ff80 hram.dma_proc
70 |
71 | [definitions]
72 | 0000000a _sizeof_clear_oam
73 | 0000000a _sizeof_clear_vram
74 | 0000000a _sizeof_clear_wram
75 | 0000000f _sizeof_disable_lcd_safe
76 | 0000000e _sizeof_memcmp
77 | 00000009 _sizeof_memcpy
78 | 00000009 _sizeof_memset
79 | 00000006 _sizeof_print_inline_string
80 | 0000000c _sizeof_print_load_font
81 | 0000000b _sizeof_print_newline
82 | 00000007 _sizeof_print_string
83 | 00000080 _sizeof_quit
84 | 00000014 _sizeof_reset_screen
85 | 0000000f _sizeof_serial_send_byte
86 | 000007f0 _sizeof_font
87 | 00000010 _sizeof_hram.dma_proc
88 | 0000000f _sizeof_main
89 | 0000000c _sizeof_prepare_part1
90 | 0000002a _sizeof_test_0000
91 | 0000002a _sizeof_test_3f00
92 | 0000002a _sizeof_test_4000
93 | 0000002a _sizeof_test_7f00
94 | 0000000c _sizeof_prepare_part2
95 | 0000002a _sizeof_test_8000
96 | 0000002a _sizeof_test_9f00
97 | 0000001e _sizeof_prepare_part3
98 | 0000002a _sizeof_test_a000
99 | 0000002a _sizeof_test_bf00
100 | 00000016 _sizeof_prepare_part4
101 | 0000002a _sizeof_test_c000
102 | 0000002a _sizeof_test_df00
103 | 0000002a _sizeof_test_e000
104 | 0000002d _sizeof_test_fe00
105 | 0000002a _sizeof_test_ff00
106 | 00000018 _sizeof_test_finish
107 | 00000009 _sizeof_check_oam
108 | 00000008 _sizeof_dma_proc
109 | 00000009 _sizeof_copy_ram_pattern_1
110 | 000000a0 _sizeof_ram_pattern_1
111 | 00000009 _sizeof_copy_ram_pattern_2
112 |
--------------------------------------------------------------------------------
/pkg/gbc/apu/channel.go:
--------------------------------------------------------------------------------
1 | package apu
2 |
3 | import "math"
4 |
5 | // NewChannel returns a new sound channel using a sampling function.
6 | func NewChannel() *Channel {
7 | return &Channel{}
8 | }
9 |
10 | // Channel represents one of four Gameboy sound channels.
11 | type Channel struct {
12 | frequency float64
13 | generator WaveGenerator
14 | time float64
15 | amplitude float64
16 |
17 | // Duration in samples
18 | duration int
19 | length int
20 |
21 | envelopeVolume int
22 | envelopeTime int
23 | envelopeSteps int
24 | envelopeStepsInit int
25 | envelopeSamples int
26 | envelopeIncreasing bool
27 |
28 | sweepTime float64
29 | sweepStepLen byte
30 | sweepSteps byte
31 | sweepStep byte
32 | sweepIncrease bool
33 |
34 | onL bool
35 | onR bool
36 | // Debug flag to turn off sound output
37 | debugOff bool
38 | }
39 |
40 | // Sample returns a single sample for streaming the sound output. Each sample
41 | // will increase the internal timer based on the global sample rate.
42 | func (chn *Channel) Sample() (outputL, outputR float64) {
43 | var output float64
44 | step := chn.frequency * twoPi / float64(SAMPLE_RATE)
45 | chn.time += step
46 | if chn.shouldPlay() {
47 | // Take the sample value from the generator
48 | if !chn.debugOff {
49 | output = float64(chn.generator(chn.time)) * chn.amplitude
50 | }
51 | if chn.duration > 0 {
52 | chn.duration--
53 | }
54 | }
55 | chn.updateEnvelope()
56 | chn.updateSweep()
57 | if chn.onL {
58 | outputL = output
59 | }
60 | if chn.onR {
61 | outputR = output
62 | }
63 | return
64 | }
65 |
66 | // Reset the channel to some default variables for the sweep, amplitude,
67 | // envelope and duration.
68 | func (chn *Channel) Reset(duration int) {
69 | chn.amplitude = 1
70 | chn.envelopeTime = 0
71 | chn.sweepTime = 0
72 | chn.sweepStep = 0
73 | chn.duration = duration
74 | }
75 |
76 | // Returns if the channel should be playing or not.
77 | func (chn *Channel) shouldPlay() bool {
78 | return (chn.duration == -1 || chn.duration > 0) &&
79 | chn.generator != nil && chn.envelopeStepsInit > 0
80 | }
81 |
82 | // Update the state of the channels envelope.
83 | func (chn *Channel) updateEnvelope() {
84 | if chn.envelopeSamples > 0 {
85 | chn.envelopeTime += 1
86 | if chn.envelopeSteps > 0 && chn.envelopeTime >= chn.envelopeSamples {
87 | chn.envelopeTime -= chn.envelopeSamples
88 | chn.envelopeSteps--
89 | if chn.envelopeSteps == 0 {
90 | chn.amplitude = 0
91 | } else if chn.envelopeIncreasing {
92 | chn.amplitude = 1 - float64(chn.envelopeSteps)/float64(chn.envelopeStepsInit)
93 | } else {
94 | chn.amplitude = float64(chn.envelopeSteps) / float64(chn.envelopeStepsInit)
95 | }
96 | }
97 | }
98 | }
99 |
100 | var sweepTimes = map[byte]float64{
101 | 1: 7.8 / 1000,
102 | 2: 15.6 / 1000,
103 | 3: 23.4 / 1000,
104 | 4: 31.3 / 1000,
105 | 5: 39.1 / 1000,
106 | 6: 46.9 / 1000,
107 | 7: 54.7 / 1000,
108 | }
109 |
110 | // Update the state of the channels sweep.
111 | func (chn *Channel) updateSweep() {
112 | if chn.sweepStep < chn.sweepSteps {
113 | t := sweepTimes[chn.sweepStepLen]
114 | chn.sweepTime += perSample
115 | if chn.sweepTime > t {
116 | chn.sweepTime -= t
117 | chn.sweepStep += 1
118 |
119 | if chn.sweepIncrease {
120 | chn.frequency += chn.frequency / math.Pow(2, float64(chn.sweepStep))
121 | } else {
122 | chn.frequency -= chn.frequency / math.Pow(2, float64(chn.sweepStep))
123 | }
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/pkg/gbc/rtc/rtc.go:
--------------------------------------------------------------------------------
1 | package rtc
2 |
3 | import (
4 | "fmt"
5 | "time"
6 |
7 | "github.com/pokemium/worldwide/pkg/util"
8 | )
9 |
10 | const (
11 | S = iota
12 | M
13 | H
14 | DL
15 | DH
16 | )
17 |
18 | // RTC Real Time Clock
19 | type RTC struct {
20 | Enable bool
21 | Mapped uint
22 | Ctr [5]byte
23 | Latched bool
24 | LatchedRTC LatchedRTC
25 | }
26 |
27 | // LatchedRTC Latched RTC
28 | type LatchedRTC struct{ Ctr [5]byte }
29 |
30 | func New(enable bool) *RTC { return &RTC{Enable: enable} }
31 |
32 | func (rtc *RTC) IncrementSecond() {
33 | if rtc.Enable && rtc.isActive() {
34 | rtc.incrementSecond()
35 | }
36 | }
37 |
38 | // Read fetch clock register
39 | func (rtc *RTC) Read(target byte) byte {
40 | if rtc.Latched {
41 | return rtc.LatchedRTC.Ctr[target-0x08]
42 | }
43 | return rtc.Ctr[target-0x08]
44 | }
45 |
46 | // Latch rtc
47 | func (rtc *RTC) Latch() {
48 | for i := 0; i < 5; i++ {
49 | rtc.LatchedRTC.Ctr[i] = rtc.Ctr[i]
50 | }
51 | }
52 |
53 | // Write set clock register
54 | func (rtc *RTC) Write(target, value byte) {
55 | rtc.Ctr[target-0x08] = value
56 | }
57 |
58 | func (rtc *RTC) incrementSecond() {
59 | rtc.Ctr[S]++
60 | if rtc.Ctr[S] == 60 {
61 | rtc.incrementMinute()
62 | }
63 | }
64 |
65 | func (rtc *RTC) incrementMinute() {
66 | rtc.Ctr[M]++
67 | rtc.Ctr[S] = 0
68 | if rtc.Ctr[M] == 60 {
69 | rtc.incrementHour()
70 | }
71 | }
72 |
73 | func (rtc *RTC) incrementHour() {
74 | rtc.Ctr[H]++
75 | rtc.Ctr[M] = 0
76 | if rtc.Ctr[H] == 24 {
77 | rtc.incrementDay()
78 | }
79 | }
80 |
81 | func (rtc *RTC) incrementDay() {
82 | old := rtc.Ctr[DL]
83 | rtc.Ctr[DL]++
84 | rtc.Ctr[H] = 0
85 | // pass 256 days
86 | if rtc.Ctr[DL] < old {
87 | rtc.Ctr[DL] = 0
88 | if rtc.Ctr[DH]&0x01 == 1 {
89 | // msb on day is set
90 | rtc.Ctr[DH] |= 0x80
91 | rtc.Ctr[DH] &= 0x7f
92 | } else {
93 | // msb on day is clear
94 | rtc.Ctr[DH] |= 0x01
95 | }
96 | }
97 | }
98 |
99 | func (rtc *RTC) isActive() bool { return !util.Bit(rtc.Ctr[DH], 6) }
100 |
101 | // Dump RTC on .sav format
102 | //
103 | // offset size desc
104 | // 0 4 time seconds
105 | // 4 4 time minutes
106 | // 8 4 time hours
107 | // 12 4 time days
108 | // 16 4 time days high
109 | // 20 4 latched time seconds
110 | // 24 4 latched time minutes
111 | // 28 4 latched time hours
112 | // 32 4 latched time days
113 | // 36 4 latched time days high
114 | // 40 4 unix timestamp when saving
115 | // 44 4 0 (probably the high dword of 64 bits time), absent in the 44 bytes version
116 | func (rtc *RTC) Dump() []byte {
117 | result := make([]byte, 48)
118 |
119 | result[0], result[4], result[8], result[12], result[16] = rtc.Ctr[S], rtc.Ctr[M], rtc.Ctr[H], rtc.Ctr[DL], rtc.Ctr[DH]
120 |
121 | latch := rtc.LatchedRTC
122 | result[20], result[24], result[28], result[32], result[36] = latch.Ctr[S], latch.Ctr[M], latch.Ctr[H], latch.Ctr[DL], latch.Ctr[DH]
123 |
124 | now := time.Now().Unix()
125 | result[40], result[41], result[42], result[43] = byte(now), byte(now>>8), byte(now>>16), byte(now>>24)
126 | return result
127 | }
128 |
129 | // Sync RTC data
130 | func (rtc *RTC) Sync(value []byte) {
131 | if len(value) != 44 && len(value) != 48 {
132 | fmt.Println("invalid RTC format")
133 | return
134 | }
135 | rtc.Ctr = [5]byte{value[0], value[4], value[8], value[12], value[16]}
136 | rtc.LatchedRTC.Ctr = [5]byte{value[20], value[24], value[28], value[32], value[36]}
137 |
138 | savTime := (uint32(value[43]) << 24) | (uint32(value[42]) << 16) | (uint32(value[41]) << 8) | uint32(value[40])
139 | delta := uint32(time.Now().Unix()) - savTime
140 | for i := uint32(0); i < delta; i++ {
141 | rtc.incrementSecond()
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/pkg/gbc/timer.go:
--------------------------------------------------------------------------------
1 | package gbc
2 |
3 | import (
4 | "math"
5 |
6 | "github.com/pokemium/worldwide/pkg/gbc/scheduler"
7 | "github.com/pokemium/worldwide/pkg/util"
8 | )
9 |
10 | const (
11 | GB_DMG_DIV_PERIOD = 16 // this is INTERNAL div interval, 16cycle or 8cycles
12 | )
13 |
14 | type Timer struct {
15 | p *GBC
16 | internalDiv uint32 // INTERNAL div counter, real div is `internalDiv >> 4`
17 | nextDiv uint32 // next INTERNAL div
18 | timaPeriod uint32
19 | }
20 |
21 | func NewTimer(p *GBC) *Timer {
22 | t := &Timer{
23 | p: p,
24 | }
25 | t.reset()
26 | return t
27 | }
28 |
29 | // GBTimerReset
30 | func (t *Timer) reset() {
31 | t.nextDiv = GB_DMG_DIV_PERIOD
32 | t.timaPeriod = 1024 >> 4
33 | }
34 |
35 | // mTimingTick
36 | func (t *Timer) tick(cycles uint32) {
37 | t.p.Sound.Buffer(int(cycles))
38 | t.p.scheduler.Add(uint64(cycles))
39 | for {
40 | if t.p.scheduler.Next() > t.p.scheduler.Cycle() {
41 | break
42 | }
43 | t.p.scheduler.DoEvent()
44 | }
45 | }
46 |
47 | // _GBTimerIRQ
48 | func (t *Timer) irq(cyclesLate uint64) {
49 | t.p.IO[TIMAIO] = t.p.IO[TMAIO]
50 | t.p.IO[IFIO] = util.SetBit8(t.p.IO[IFIO], 2, true)
51 | t.p.updateIRQs()
52 | }
53 |
54 | // _GBTimerDivIncrement
55 | // oneloop equals to every n cycles (n=16cycles or 8cycles, p.s. 16384Hz=256cycles or 128cycles)
56 | func (t *Timer) internalDivIncrement() {
57 | tMultiplier := util.Bool2U32(t.p.DoubleSpeed)
58 | interval := uint32(GB_DMG_DIV_PERIOD >> tMultiplier) // 16 or 8
59 |
60 | // normally, t.nextDiv is greater than 256 or 128, so real div increment should occur.
61 | for t.nextDiv >= interval {
62 | t.nextDiv -= interval
63 |
64 | if t.timaPeriod > 0 && (t.internalDiv&(t.timaPeriod-1)) == (t.timaPeriod-1) {
65 | t.p.IO[TIMAIO]++
66 | if t.p.IO[TIMAIO] == 0 {
67 | // overflow(4 cycles delay https://github.com/Gekkio/mooneye-gb/blob/master/tests/acceptance/timer/tima_reload.s)
68 | t.p.scheduler.ScheduleEvent(scheduler.TimerIRQ, t.irq, 4<> 4)
74 | }
75 | }
76 |
77 | // _GBTimerUpdate (system count)
78 | // 1/16384sec(256cycles) or 1/32768sec(128cycles)
79 | func (t *Timer) update(cyclesLate uint64) {
80 | t.nextDiv += uint32(cyclesLate)
81 | t.internalDivIncrement()
82 |
83 | // Batch div increments into real div increment
84 | divsToGo := 16 - (t.internalDiv & 15)
85 | timaToGo := uint32(math.MaxUint32)
86 | if t.timaPeriod > 0 {
87 | timaToGo = t.timaPeriod - (t.internalDiv & (t.timaPeriod - 1))
88 | }
89 | if timaToGo < divsToGo {
90 | divsToGo = timaToGo
91 | }
92 | t.nextDiv = (GB_DMG_DIV_PERIOD * divsToGo) >> util.Bool2U32(t.p.DoubleSpeed) // 256 or 128
93 |
94 | t.p.scheduler.ScheduleEvent(scheduler.TimerUpdate, t.update, uint64(t.nextDiv)-cyclesLate)
95 | }
96 |
97 | // GBTimerDivReset
98 | // triggered on writing DIV
99 | func (t *Timer) divReset() {
100 | t.nextDiv -= uint32(t.p.scheduler.Until(scheduler.TimerUpdate))
101 | t.p.scheduler.DescheduleEvent(scheduler.TimerUpdate)
102 | t.internalDivIncrement()
103 |
104 | t.p.IO[DIVIO] = 0
105 | t.internalDiv = 0
106 | t.nextDiv = GB_DMG_DIV_PERIOD >> util.Bool2U32(t.p.DoubleSpeed) // 16 or 8 -> 1/16384 sec or 1/32768 sec
107 | t.p.scheduler.ScheduleEvent(scheduler.TimerUpdate, t.update, uint64(t.nextDiv))
108 | }
109 |
110 | // triggerd on writing TAC
111 | func (t *Timer) updateTAC(tac byte) byte {
112 | if util.Bit(tac, 2) {
113 | t.nextDiv -= uint32(t.p.scheduler.Until(scheduler.TimerUpdate))
114 | t.p.scheduler.DescheduleEvent(scheduler.TimerUpdate)
115 | t.internalDivIncrement()
116 |
117 | timaLt := [4]uint32{1024 >> 4, 16 >> 4, 64 >> 4, 256 >> 4}
118 | t.timaPeriod = timaLt[tac&0x3]
119 |
120 | t.nextDiv += GB_DMG_DIV_PERIOD >> util.Bool2U32(t.p.DoubleSpeed)
121 | t.p.scheduler.ScheduleEvent(scheduler.TimerUpdate, t.update, uint64(t.nextDiv))
122 | } else {
123 | t.timaPeriod = 0
124 | }
125 | return tac
126 | }
127 |
--------------------------------------------------------------------------------
/pkg/gbc/ram.go:
--------------------------------------------------------------------------------
1 | package gbc
2 |
3 | import (
4 | "github.com/pokemium/worldwide/pkg/gbc/cart"
5 | )
6 |
7 | // Load8 fetch value from ram
8 | func (g *GBC) Load8(addr uint16) (value byte) {
9 | switch {
10 |
11 | case addr < 0x4000:
12 | // ROM bank0
13 | value = g.ROM.buffer[0][addr]
14 | case addr >= 0x4000 && addr < 0x8000:
15 | // ROM bank1..256
16 | value = g.ROM.buffer[g.ROM.bank][addr-0x4000]
17 |
18 | case addr >= 0x8000 && addr < 0xa000:
19 | // VRAM
20 | value = g.Video.VRAM.Buffer[addr-0x8000+(0x2000*g.Video.VRAM.Bank)]
21 |
22 | case addr >= 0xa000 && addr < 0xc000:
23 | // RTC or RAM
24 | if g.RTC.Mapped != 0 {
25 | value = g.RTC.Read(byte(g.RTC.Mapped))
26 | } else {
27 | value = g.RAM.Buffer[g.RAM.bank][addr-0xa000]
28 | }
29 |
30 | case addr >= 0xc000 && addr < 0xd000:
31 | // WRAM bank0
32 | value = g.WRAM.buffer[0][addr-0xc000]
33 | case addr >= 0xd000 && addr < 0xe000:
34 | // WRAM bank1..7
35 | value = g.WRAM.buffer[g.WRAM.bank][addr-0xd000]
36 |
37 | case addr >= 0xfe00 && addr < 0xfea0:
38 | // OAM
39 | value = g.Video.Oam.Get(addr - 0xfe00)
40 |
41 | case addr >= 0xff00:
42 | // IO, HRAM, IE
43 | value = g.loadIO(byte(addr))
44 | }
45 | return value
46 | }
47 |
48 | // Store8 set value into RAM
49 | func (g *GBC) Store8(addr uint16, value byte) {
50 | if addr <= 0x7fff {
51 | g.mbcWrite(addr, value)
52 | return
53 | }
54 |
55 | switch {
56 | case addr >= 0x8000 && addr < 0xa000: // vram
57 | g.Video.VRAM.Buffer[addr-0x8000+(0x2000*g.Video.VRAM.Bank)] = value
58 |
59 | case addr >= 0xa000 && addr < 0xc000:
60 | // RTC or RAM
61 | if g.RTC.Mapped == 0 {
62 | g.RAM.Buffer[g.RAM.bank][addr-0xa000] = value
63 | return
64 | }
65 | g.RTC.Write(byte(g.RTC.Mapped), value)
66 |
67 | case addr >= 0xc000 && addr < 0xd000:
68 | // WRAM bank0
69 | g.WRAM.buffer[0][addr-0xc000] = value
70 | case addr >= 0xd000 && addr < 0xe000:
71 | // WRAM bank1 or 7
72 | g.WRAM.buffer[g.WRAM.bank][addr-0xd000] = value
73 |
74 | case addr >= 0xfe00 && addr <= 0xfe9f:
75 | // OAM
76 | g.Video.Oam.Set(addr-0xfe00, value)
77 |
78 | case addr >= 0xff00:
79 | // IO, HRAM, IE
80 | g.storeIO(byte(addr), value)
81 | }
82 | }
83 |
84 | func (g *GBC) mbcWrite(addr uint16, value byte) {
85 | if (addr >= 0x2000) && (addr <= 0x3fff) {
86 | switch g.Cartridge.MBC {
87 | case cart.MBC1: // lower 5bit in romptr
88 | if value == 0 {
89 | value++
90 | }
91 | upper2 := g.ROM.bank >> 5
92 | lower5 := value
93 | bank := (upper2 << 5) | lower5
94 | g.switchROMBank(bank)
95 | case cart.MBC3:
96 | newROMBankPtr := value & 0x7f
97 | if newROMBankPtr == 0 {
98 | newROMBankPtr++
99 | }
100 | g.switchROMBank(newROMBankPtr)
101 | case cart.MBC5:
102 | if addr < 0x3000 { // lower 8bit
103 | g.switchROMBank(value)
104 | }
105 | }
106 | } else if (addr >= 0x4000) && (addr <= 0x5fff) {
107 | switch g.Cartridge.MBC {
108 | case cart.MBC1:
109 | if g.bankMode == 0 { // switch upper 2bit in romptr
110 | upper2 := value
111 | lower5 := g.ROM.bank & 0x1f
112 | bank := (upper2 << 5) | lower5
113 | g.switchROMBank(bank)
114 | } else if g.bankMode == 1 { // switch RAMptr
115 | g.RAM.bank = value
116 | }
117 | case cart.MBC3:
118 | switch {
119 | case value <= 0x07:
120 | g.RTC.Mapped = 0
121 | g.RAM.bank = value
122 | case value >= 0x08 && value <= 0x0c:
123 | g.RTC.Mapped = uint(value)
124 | }
125 | case cart.MBC5:
126 | g.RAM.bank = value & 0x0f
127 | }
128 | } else if (addr >= 0x6000) && (addr <= 0x7fff) {
129 | switch g.Cartridge.MBC {
130 | case cart.MBC1:
131 | // ROM/RAM mode selection
132 | if value == 1 || value == 0 {
133 | g.bankMode = uint(value)
134 | }
135 | case cart.MBC3:
136 | if value == 1 {
137 | g.RTC.Latched = false
138 | } else if value == 0 {
139 | g.RTC.Latched = true
140 | g.RTC.Latch()
141 | }
142 | }
143 | }
144 | }
145 |
146 | func (g *GBC) switchROMBank(bank uint8) {
147 | switchFlag := (bank < (2 << g.Cartridge.ROMSize))
148 | if switchFlag {
149 | g.ROM.bank = bank
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/makefile:
--------------------------------------------------------------------------------
1 | NAME := worldwide
2 | BINDIR := ./build
3 | VERSION := $(shell git describe --tags 2>/dev/null)
4 | LDFLAGS := -X 'main.version=$(VERSION)'
5 |
6 | .PHONY: build
7 | build:
8 | @go build -tags macos -o $(BINDIR)/darwin-amd64/$(NAME) -ldflags "$(LDFLAGS)" ./cmd/
9 |
10 | .PHONY: ci
11 | ci:
12 | go build -o gbc ./cmd/ && echo "OK" && rm -rf gbc
13 |
14 | .PHONY: build-linux
15 | build-linux:
16 | @GOOS=linux GOARCH=amd64 go build -tags windows -o $(BINDIR)/linux-amd64/$(NAME) -ldflags "$(LDFLAGS)" ./cmd/
17 |
18 | .PHONY: build-windows
19 | build-windows:
20 | @GOOS=windows GOARCH=amd64 go build -tags windows -o $(BINDIR)/windows-amd64/$(NAME).exe -ldflags "$(LDFLAGS)" ./cmd/
21 |
22 | .PHONY: clean
23 | clean:
24 | @-rm -rf $(BINDIR)
25 |
26 | .PHONY: test
27 |
28 | TEST0=gb-test-roms/cpu_instrs/
29 | TEST1=gb-test-roms/instr_timing/
30 | TEST2=mooneye-gb/add_sp_e_timing/
31 | TEST3=mooneye-gb/call_cc_timing/
32 | TEST4=mooneye-gb/div_timing/
33 | TEST5=mooneye-gb/instr/daa/
34 | TEST6=mooneye-gb/intr_timing/
35 | TEST7=mooneye-gb/jp_timing/
36 | TEST8=mooneye-gb/ld_hl_sp_e_timing/
37 | TEST9=mooneye-gb/oam_dma/basic/
38 | TEST10=mooneye-gb/oam_dma/reg_read/
39 | TEST11=mooneye-gb/instr/daa/
40 | TEST12=mooneye-gb/ld_hl_sp_e_timing/
41 | TEST13=mooneye-gb/intr_timing/
42 | TEST14=mooneye-gb/oam_dma_restart/
43 | TEST15=mooneye-gb/call_cc_timing2/
44 | TEST16=mooneye-gb/call_timing2/
45 | TEST17=mooneye-gb/ei_sequence/
46 | TEST18=mooneye-gb/ei_timing/
47 | TEST19=mooneye-gb/if_ie_registers/
48 | TEST20=mooneye-gb/pop_timing/
49 | TEST21=mooneye-gb/rapid_di_ei/
50 | TEST22=mooneye-gb/halt_ime0_ei/
51 | TEST23=mooneye-gb/halt_ime1_timing/
52 | TEST24=mooneye-gb/halt_ime0_nointr_timing/
53 |
54 | TIM_TEST0=mooneye-gb/timer/div_write/
55 | TIM_TEST1=mooneye-gb/timer/rapid_toggle/
56 | TIM_TEST2=mooneye-gb/timer/tim00/
57 | TIM_TEST3=mooneye-gb/timer/tim00_div_trigger/
58 | TIM_TEST4=mooneye-gb/timer/tim01/
59 | TIM_TEST5=mooneye-gb/timer/tim01_div_trigger/
60 | TIM_TEST6=mooneye-gb/timer/tim10/
61 | TIM_TEST7=mooneye-gb/timer/tim10_div_trigger/
62 | TIM_TEST8=mooneye-gb/timer/tim11/
63 | TIM_TEST9=mooneye-gb/timer/tim11_div_trigger/
64 | TIM_TEST10=mooneye-gb/timer/tima_reload/
65 | TIM_TEST11=mooneye-gb/timer/tima_write_reloading/
66 | TIM_TEST12=mooneye-gb/timer/tma_write_reloading/
67 |
68 | define compare
69 | ./$(BINDIR)/darwin-amd64/$(NAME) --test="./test/$1actual.jpg" ./test/$1rom.gb
70 | -diff "./test/$1actual.jpg" "./test/$1expected.jpg" && echo "$1 OK"
71 | endef
72 |
73 | .SILENT:
74 | test:
75 | make build
76 | -$(call compare,$(TEST0))
77 | -$(call compare,$(TEST1))
78 | -$(call compare,$(TEST2))
79 | -$(call compare,$(TEST3))
80 | -$(call compare,$(TEST4))
81 | -$(call compare,$(TEST5))
82 | -$(call compare,$(TEST6))
83 | -$(call compare,$(TEST7))
84 | -$(call compare,$(TEST8))
85 | -$(call compare,$(TEST9))
86 | -$(call compare,$(TEST10))
87 | -$(call compare,$(TEST11))
88 | -$(call compare,$(TEST12))
89 | -$(call compare,$(TEST13))
90 | -$(call compare,$(TEST14))
91 | -$(call compare,$(TEST15))
92 | -$(call compare,$(TEST16))
93 | -$(call compare,$(TEST17))
94 | -$(call compare,$(TEST18))
95 | -$(call compare,$(TEST19))
96 | -$(call compare,$(TEST20))
97 | -$(call compare,$(TEST21))
98 | -$(call compare,$(TEST22))
99 | -$(call compare,$(TEST23))
100 | -$(call compare,$(TEST24))
101 |
102 | -rm -f ./test/$(TEST0)actual.jpg \
103 | ./test/$(TEST1)actual.jpg \
104 | ./test/$(TEST2)actual.jpg \
105 | ./test/$(TEST3)actual.jpg \
106 | ./test/$(TEST4)actual.jpg \
107 | ./test/$(TEST5)actual.jpg \
108 | ./test/$(TEST6)actual.jpg \
109 | ./test/$(TEST7)actual.jpg \
110 | ./test/$(TEST8)actual.jpg \
111 | ./test/$(TEST9)actual.jpg \
112 | ./test/$(TEST10)actual.jpg \
113 | ./test/$(TEST11)actual.jpg \
114 | ./test/$(TEST12)actual.jpg \
115 | ./test/$(TEST13)actual.jpg \
116 | ./test/$(TEST14)actual.jpg \
117 | ./test/$(TEST15)actual.jpg \
118 | ./test/$(TEST16)actual.jpg \
119 | ./test/$(TEST17)actual.jpg \
120 | ./test/$(TEST18)actual.jpg \
121 | ./test/$(TEST19)actual.jpg \
122 | ./test/$(TEST20)actual.jpg \
123 | ./test/$(TEST21)actual.jpg \
124 | ./test/$(TEST22)actual.jpg \
125 | ./test/$(TEST23)actual.jpg \
126 | ./test/$(TEST24)actual.jpg \
127 |
128 | .SILENT:
129 | timer-test:
130 | make build
131 | -$(call compare,$(TIM_TEST0))
132 | -$(call compare,$(TIM_TEST1))
133 | -$(call compare,$(TIM_TEST2))
134 | -$(call compare,$(TIM_TEST3))
135 | -$(call compare,$(TIM_TEST4))
136 | -$(call compare,$(TIM_TEST5))
137 | -$(call compare,$(TIM_TEST6))
138 | -$(call compare,$(TIM_TEST7))
139 | -$(call compare,$(TIM_TEST8))
140 | -$(call compare,$(TIM_TEST9))
141 | -$(call compare,$(TIM_TEST10))
142 | -$(call compare,$(TIM_TEST11))
143 | -$(call compare,$(TIM_TEST12))
144 |
145 | -rm -f ./test/$(TIM_TEST0)actual.jpg \
146 | ./test/$(TIM_TEST1)actual.jpg \
147 | ./test/$(TIM_TEST2)actual.jpg \
148 | ./test/$(TIM_TEST3)actual.jpg \
149 | ./test/$(TIM_TEST4)actual.jpg \
150 | ./test/$(TIM_TEST5)actual.jpg \
151 | ./test/$(TIM_TEST6)actual.jpg \
152 | ./test/$(TIM_TEST7)actual.jpg \
153 | ./test/$(TIM_TEST8)actual.jpg \
154 | ./test/$(TIM_TEST9)actual.jpg \
155 | ./test/$(TIM_TEST10)actual.jpg \
156 | ./test/$(TIM_TEST11)actual.jpg \
157 | ./test/$(TIM_TEST12)actual.jpg \
158 |
--------------------------------------------------------------------------------
/server/README.md:
--------------------------------------------------------------------------------
1 | # Server
2 |
3 | `worldwide` contains an HTTP server, and the user can give various instructions to it through HTTP requests.
4 |
5 | ## Start
6 |
7 | Normal execution will not start the HTTP server. You can start the HTTP server by running it with the port option specified.
8 |
9 | ```sh
10 | go run ./cmd -p 8888 ./PM_PRISM.gbc # start HTTP server on localhost:8888
11 | ```
12 |
13 | Note: On this API server, GET method changes emulator's state!
14 |
15 | ## Commands
16 |
17 | **pause**
18 |
19 | Pause emulator
20 |
21 | ```sh
22 | curl localhost:8888/pause
23 | ```
24 |
25 | **continue**
26 |
27 | Continue emulator from pause state
28 |
29 | ```sh
30 | curl localhost:8888/continue
31 | ```
32 |
33 | **reset**
34 |
35 | Reset emulator. Debug information(e.g. breakpoints, history-mode) is not reset.
36 |
37 | ```sh
38 | curl localhost:8888/reset
39 | ```
40 |
41 | **quit**
42 |
43 | Quit emulator. Exitcode is 0 and savefile is written.
44 |
45 | ```sh
46 | curl localhost:8888/quit
47 | ```
48 |
49 | **mute**
50 |
51 | Mute emulator(toggle)
52 |
53 | ```sh
54 | curl localhost:8888/mute
55 | ```
56 |
57 | ## Debug commands
58 |
59 | **debug/register(GET)**
60 |
61 | Get value from CPU-registers and other important registers
62 |
63 | ```sh
64 | curl localhost:8888/debug/register
65 | ```
66 |
67 | ```jsonc
68 | // application/json
69 | {
70 | "A":"0x00", "F":"0xa0",
71 | "B":"0x00", "C":"0x00",
72 | "D":"0x00", "E":"0x04",
73 | "H":"0xff", "L":"0xfe",
74 | "PC":"0x4a10","SP":"0xc0f8",
75 | "IE":"0x0f", "IF":"0xe1", "IME":"0x01",
76 | "Halt":"true", "DoubleSpeed":"true"
77 | }
78 | ```
79 |
80 | **debug/register(POST)**
81 |
82 | Set a value into register
83 |
84 | ```sh
85 | # target: register's name (a, b, c, d, e, h, l, f, sp, pc, ime)
86 | # value: hex value (e.g. 0x0486)
87 | curl -X POST -d '{"target":"ime", "value":"0x1"}' -H "Content-Type: application/json" localhost:8888/debug/register
88 | ```
89 |
90 | **debug/break(POST)**
91 |
92 | Set a breakpoint
93 |
94 | ```sh
95 | curl -X POST -d '{"addr":"0x0486"}' -H "Content-Type: application/json" localhost:8888/debug/break
96 | ```
97 |
98 | **debug/break(GET)**
99 |
100 | List breakpoints
101 |
102 | ```sh
103 | curl localhost:8888/debug/break
104 | ```
105 |
106 | ```sh
107 | [0x0486, 0x0490] # text/plain
108 | ```
109 |
110 | **debug/break(DELETE)**
111 |
112 | Delete a breakpoint
113 |
114 | ```sh
115 | curl -X DELETE "localhost:8888/debug/break?addr=0x0486"
116 | ```
117 |
118 | **debug/read1**
119 |
120 | Read a byte from memory
121 |
122 | ```sh
123 | curl "localhost:8888/debug/read1?addr=0x0150"
124 | ```
125 |
126 | ```sh
127 | 0x12 # text/plain
128 | ```
129 | **debug/read2**
130 |
131 | Read two bytes from memory
132 |
133 | ```sh
134 | curl "localhost:8888/debug/read2?addr=0x0150"
135 | ```
136 |
137 | ```sh
138 | 0x1411 # text/plain
139 | ```
140 |
141 | **debug/history(POST)**
142 |
143 | Start recording the history of the executed instructions.
144 |
145 | Specify how many past histories to record in the `history` parameter.(Max 100)
146 |
147 | The larger the number, the greater the load on the emulator CPU.
148 |
149 | ```sh
150 | curl -X POST -d '{"history":"0x20"}' -H "Content-Type: application/json" localhost:8888/debug/history
151 | ```
152 |
153 | **debug/history(GET)**
154 |
155 | Displays the history of records started by POST requests.
156 |
157 | ```sh
158 | curl localhost:8888/debug/history
159 | ```
160 |
161 | ```sh
162 | # text/plain
163 | 0x0048: JP a16
164 | 0xcda1: PUSH AF
165 | 0xcda2: PUSH HL
166 | ...
167 | 0x048a: JR NZ r8
168 | 0x0485: HALT
169 | 0x0486: LD A (a16)
170 | ```
171 |
172 | **debug/cartridge**
173 |
174 | Get cartridge info
175 |
176 | ```sh
177 | curl localhost:8888/debug/cartridge
178 | ```
179 |
180 | ```jsonc
181 | // application/json
182 | {
183 | "title":"PM_PRISM",
184 | "cartridge_type":"MBC3+TIMER+RAM+BATTERY",
185 | "rom_size":"2MB",
186 | "ram_size":"32KB"
187 | }
188 | ```
189 |
190 | **debug/disasm**
191 |
192 | Disassemble instructions
193 |
194 | ```sh
195 | curl localhost:8888/debug/disasm
196 | ```
197 |
198 | ```jsonc
199 | // application/json
200 | {
201 | "pc":"0x0486",
202 | "mnemonic":"LD A (a16)"
203 | }
204 | ```
205 |
206 | **debug/trace**
207 |
208 | Trace a number of instructions
209 |
210 | ```sh
211 | curl "localhost:8888/debug/trace?step=20" # trace 20 instructions
212 | ```
213 |
214 | ```sh
215 | # text/plain
216 | 0x16cf: INC L
217 | 0x16d0: LD (HL) D
218 | 0x16d1: INC L
219 | 0x16d2: POP DE
220 | 0x16d3: LD (HL) E
221 | ...
222 | 0x16dd: LD (HL) E
223 | 0x16de: INC L
224 | 0x16df: LD (HL) D
225 | 0x16e0: INC L
226 | 0x16e1: POP DE
227 | 0x16e2: LD (HL) E
228 | ```
229 |
230 | **debug/io(Websocket)**
231 |
232 | Get IO registers(`0xff00-0xffff`) at 100-milisecond intervals using Websocket.
233 |
234 | IO registers is sent in arraybuffer. Please refer to [io.html](./io.html) for how to display it.
235 |
236 | ```sh
237 | wscat -c ws://localhost:8888/debug/io
238 | ```
239 |
240 | **debug/tileview/bank0(Websocket)**
241 |
242 | Get tile data at 100-milisecond intervals using Websocket.
243 |
244 | Tile data is sent in binary format. Please refer to [tileview.html](./tileview.html) for how to display it.
245 |
246 | ```sh
247 | wscat -c ws://localhost:8888/debug/tileview/bank0 # or ws://localhost:8888/debug/tileview/bank1
248 | ```
249 |
250 | **debug/sprview(Websocket)**
251 |
252 | Get sprite data at 100-milisecond intervals using Websocket.
253 |
254 | Sprite data is sent in binary format. Please refer to [sprview.html](./sprview.html) for how to display it.
255 |
256 | ```sh
257 | wscat -c ws://localhost:8888/debug/sprview
258 | ```
259 |
--------------------------------------------------------------------------------
/pkg/gbc/io.go:
--------------------------------------------------------------------------------
1 | package gbc
2 |
3 | import (
4 | "github.com/pokemium/worldwide/pkg/gbc/scheduler"
5 | "github.com/pokemium/worldwide/pkg/gbc/video"
6 | "github.com/pokemium/worldwide/pkg/util"
7 | )
8 |
9 | const (
10 | JOYPIO byte = 0x00
11 | SBIO byte = 0x01
12 | SCIO byte = 0x02
13 | DIVIO byte = 0x04
14 | TIMAIO byte = 0x05
15 | TMAIO byte = 0x06
16 | TACIO byte = 0x07
17 | IFIO byte = 0x0f
18 | LCDCIO byte = 0x40
19 | LCDSTATIO byte = 0x41
20 | SCYIO byte = 0x42
21 | SCXIO byte = 0x43
22 | LYIO byte = 0x44
23 | LYCIO byte = 0x45
24 | DMAIO byte = 0x46
25 | BGPIO byte = 0x47
26 | OBP0IO byte = 0x48
27 | OBP1IO byte = 0x49
28 | WYIO byte = 0x4a
29 | WXIO byte = 0x4b
30 | KEY0IO byte = 0x4c
31 | KEY1IO byte = 0x4d
32 | VBKIO byte = 0x4f
33 | BANKIO byte = 0x50
34 | HDMA1IO byte = 0x51
35 | HDMA2IO byte = 0x52
36 | HDMA3IO byte = 0x53
37 | HDMA4IO byte = 0x54
38 | HDMA5IO byte = 0x55
39 | BCPSIO byte = 0x68
40 | BCPDIO byte = 0x69
41 | OCPSIO byte = 0x6a
42 | OCPDIO byte = 0x6b
43 | SVBKIO byte = 0x70
44 | IEIO byte = 0xff
45 | )
46 |
47 | // GBIOReset
48 | func (g *GBC) resetIO() {
49 | model := g.Video.Renderer.Model
50 |
51 | g.storeIO(TIMAIO, 0x00)
52 | g.storeIO(TMAIO, 0x00)
53 | g.storeIO(TACIO, 0x00)
54 | g.storeIO(IFIO, 0x01)
55 |
56 | // sound
57 | g.IO[0x10], g.IO[0x11], g.IO[0x12], g.IO[0x14] = 0x80, 0xbf, 0xf3, 0xbf // sound1
58 | g.IO[0x16], g.IO[0x19] = 0x3f, 0xbf // sound2
59 | g.IO[0x1a], g.IO[0x1b], g.IO[0x1c], g.IO[0x1e] = 0x7f, 0xff, 0x9f, 0xbf // sound3
60 | g.IO[0x20], g.IO[0x23] = 0xff, 0xbf // sound4
61 | g.IO[0x24], g.IO[0x25], g.IO[0x26] = 0x77, 0xf3, 0xf1 // sound control
62 |
63 | g.storeIO(LCDCIO, 0x91)
64 | g.IO[BANKIO] = 0x01
65 |
66 | g.storeIO(SCYIO, 0x00)
67 | g.storeIO(SCXIO, 0x00)
68 | g.storeIO(LYCIO, 0x00)
69 |
70 | g.IO[DMAIO] = 0xff
71 |
72 | g.storeIO(BGPIO, 0xfc)
73 | if model < util.GB_MODEL_CGB {
74 | g.storeIO(OBP0IO, 0xff)
75 | g.storeIO(OBP1IO, 0xff)
76 | }
77 |
78 | g.storeIO(WYIO, 0x00)
79 | g.storeIO(WXIO, 0x00)
80 |
81 | if model&util.GB_MODEL_CGB != 0 {
82 | g.storeIO(KEY0IO, 0x00)
83 | g.storeIO(JOYPIO, 0xff)
84 | g.storeIO(VBKIO, 0x00)
85 | g.storeIO(BCPSIO, 0x80)
86 | g.storeIO(OCPSIO, 0x00)
87 | g.storeIO(SVBKIO, 0x01)
88 | g.storeIO(HDMA1IO, 0xff)
89 | g.storeIO(HDMA2IO, 0xff)
90 | g.storeIO(HDMA3IO, 0xff)
91 | g.storeIO(HDMA4IO, 0xff)
92 | g.IO[HDMA5IO] = 0xff
93 | }
94 |
95 | g.storeIO(IEIO, 0x00)
96 | }
97 |
98 | func (g *GBC) loadIO(offset byte) (value byte) {
99 | switch offset {
100 | case JOYPIO:
101 | value = g.joypad.Output()
102 | case LCDCIO:
103 | value = g.Video.LCDC
104 | case LCDSTATIO:
105 | value = g.Video.Stat
106 | default:
107 | if (offset >= 0x10 && offset <= 0x26) || (offset >= 0x30 && offset <= 0x3f) {
108 | value = g.Sound.Read(offset)
109 | } else {
110 | value = g.IO[offset]
111 | }
112 | }
113 | return value
114 | }
115 |
116 | func (g *GBC) storeIO(offset byte, value byte) {
117 | switch offset {
118 | case JOYPIO:
119 | g.IO[JOYPIO] = value | 0x0f
120 | g.joypad.P1 = g.IO[JOYPIO]
121 | return
122 |
123 | case DIVIO:
124 | g.timer.divReset()
125 | return
126 |
127 | case TIMAIO:
128 | if value > 0 && g.scheduler.Until(scheduler.TimerIRQ) > (2-util.Bool2U64(g.DoubleSpeed)) {
129 | g.scheduler.DescheduleEvent(scheduler.TimerIRQ)
130 | }
131 | if g.scheduler.Until(scheduler.TimerIRQ) == util.Bool2U64(g.DoubleSpeed)-2 {
132 | return
133 | }
134 |
135 | case TACIO:
136 | value = g.timer.updateTAC(value)
137 |
138 | case IFIO:
139 | g.IO[IFIO] = value | 0xe0 // IF[4-7] always set
140 | g.updateIRQs()
141 | return
142 |
143 | case DMAIO: // dma transfer
144 | base := uint16(value) << 8
145 | if base >= 0xe000 {
146 | base &= 0xdfff
147 | }
148 | g.scheduler.DescheduleEvent(scheduler.OAMDMA)
149 | g.scheduler.ScheduleEvent(scheduler.OAMDMA, g.dmaService, 4>>util.Bool2Int(g.DoubleSpeed)) // 4 * 40 = 160cycle
150 | g.dma.src = base
151 | g.dma.dest = 0xFE00
152 | g.dma.remaining = 0xa0
153 |
154 | case LCDCIO:
155 | g.Video.ProcessDots(0)
156 | old := g.Video.LCDC
157 | g.Video.Renderer.WriteVideoRegister(offset, value)
158 | g.Video.WriteLCDC(old, value)
159 |
160 | case LCDSTATIO:
161 | g.Video.WriteSTAT(value)
162 |
163 | case LYCIO:
164 | g.Video.WriteLYC(value)
165 |
166 | case SCYIO, SCXIO, WYIO, WXIO:
167 | g.Video.ProcessDots(0)
168 | value = g.Video.Renderer.WriteVideoRegister(offset, value)
169 |
170 | case BGPIO, OBP0IO, OBP1IO:
171 | g.Video.ProcessDots(0)
172 | g.Video.WritePalette(offset, value)
173 |
174 | // below case statements, gbc only
175 | case KEY1IO:
176 | value &= 0x1
177 | value |= g.IO[KEY1IO] & 0x80
178 |
179 | case VBKIO: // switch vram bank
180 | g.Video.SwitchBank(value)
181 |
182 | case HDMA5IO:
183 | value = g.writeHDMA5(value)
184 |
185 | case BCPSIO:
186 | g.Video.BcpIndex = int(value & 0x3f)
187 | g.Video.BcpIncrement = int(value & 0x80)
188 | g.IO[BCPDIO] = byte(g.Video.Palette[g.Video.BcpIndex>>1] >> (8 * (g.Video.BcpIndex & 1)))
189 |
190 | case OCPSIO:
191 | g.Video.OcpIndex = int(value & 0x3f)
192 | g.Video.OcpIncrement = int(value & 0x80)
193 | g.IO[OCPDIO] = byte(g.Video.Palette[8*4+(g.Video.OcpIndex>>1)] >> (8 * (g.Video.OcpIndex & 1)))
194 |
195 | case BCPDIO, OCPDIO:
196 | g.Video.ProcessDots(0)
197 | g.Video.WritePalette(offset, value)
198 |
199 | case SVBKIO: // switch wram bank
200 | bank := value & 0x07
201 | if bank == 0 {
202 | bank = 1
203 | }
204 | g.WRAM.bank = bank
205 |
206 | case IEIO:
207 | g.IO[IEIO] = value
208 | g.updateIRQs()
209 | return
210 |
211 | default:
212 | if offset >= 0x10 && offset <= 0x26 {
213 | g.Sound.Write(offset, value)
214 | }
215 | if offset >= 0x30 && offset <= 0x3f {
216 | g.Sound.WriteWaveform(offset, value)
217 | }
218 | }
219 |
220 | g.IO[offset] = value
221 | }
222 |
223 | // GBMemoryWriteHDMA5
224 | func (g *GBC) writeHDMA5(value byte) byte {
225 | g.hdma.src = (uint16(g.IO[HDMA1IO]) << 8) | uint16(g.IO[HDMA2IO])
226 | g.hdma.dest = (uint16(g.IO[HDMA3IO]) << 8) | uint16(g.IO[HDMA4IO])
227 | g.hdma.src &= 0xfff0
228 |
229 | g.hdma.dest &= 0x1ff0
230 | g.hdma.dest |= 0x8000
231 | wasHdma := g.hdma.enable
232 | g.hdma.enable = util.Bit(value, 7)
233 |
234 | if (!wasHdma && !g.hdma.enable) || (util.Bit(g.Video.LCDC, video.Enable) && g.Video.Mode() == 0) {
235 | if g.hdma.enable {
236 | g.hdma.remaining = 0x10
237 | } else {
238 | g.hdma.remaining = ((int(value) & 0x7F) + 1) * 0x10
239 | }
240 | g.cpuBlocked = true
241 | g.scheduler.ScheduleEvent(scheduler.HDMA, g.hdmaService, 0)
242 | } else if g.hdma.enable && !util.Bit(g.Video.LCDC, video.Enable) {
243 | return 0x80 | byte((value+1)&0x7f)
244 | }
245 |
246 | return value & 0x7f
247 | }
248 |
--------------------------------------------------------------------------------
/pkg/emulator/debug/disasm.go:
--------------------------------------------------------------------------------
1 | package debug
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "net/http"
7 | "strings"
8 | )
9 |
10 | type Inst struct {
11 | PC string `json:"pc"`
12 | Mnemonic string `json:"mnemonic"`
13 | }
14 |
15 | var mnemonics [256][4]string = [256][4]string{
16 | /* 0x0x */ {"NOP", "*", "*", "1"}, {"LD", "BC", "d16", "3"}, {"LD", "(BC)", "A", "2"}, {"INC", "BC", "*", "2"}, {"INC", "B", "*", "1"}, {"DEC", "B", "*", "1"}, {"LD", "B", "d8", "2"}, {"RLCA", "*", "*", "1"}, {"LD", "(a16)", "SP", "5"}, {"ADD", "HL", "BC", "2"}, {"LD", "A", "(BC)", "2"}, {"DEC", "BC", "*", "2"}, {"INC", "C", "*", "1"}, {"DEC", "C", "*", "1"}, {"LD", "C", "d8", "2"}, {"RRCA", "*", "*", "1"},
17 | /* 0x1x */ {"STOP", "0", "*", "1"}, {"LD", "DE", "d16", "3"}, {"LD", "(DE)", "A", "2"}, {"INC", "DE", "*", "2"}, {"INC", "D", "*", "1"}, {"DEC", "D", "*", "1"}, {"LD", "D", "d8", "2"}, {"RLA", "*", "*", "1"}, {"JR", "r8", "*", "3"}, {"ADD", "HL", "DE", "2"}, {"LD", "A", "(DE)", "2"}, {"DEC", "DE", "*", "2"}, {"INC", "E", "*", "1"}, {"DEC", "E", "*", "1"}, {"LD", "E", "d8", "2"}, {"RRA", "*", "*", "1"},
18 | /* 0x2x */ {"JR", "NZ", "r8", "1.5"}, {"LD", "HL", "d16", "3"}, {"LD", "(HL+)", "A", "2"}, {"INC", "HL", "*", "2"}, {"INC", "H", "*", "1"}, {"DEC", "H", "*", "1"}, {"LD", "H", "d8", "2"}, {"DAA", "*", "*", "1"}, {"JR", "Z", "r8", "1.5"}, {"ADD", "HL", "HL", "2"}, {"LD", "A", "(HL+)", "2"}, {"DEC", "HL", "*", "2"}, {"INC", "L", "*", "1"}, {"DEC", "L", "*", "1"}, {"LD", "L", "d8", "2"}, {"CPL", "*", "*", "1"},
19 | /* 0x3x */ {"JR", "NC", "r8", "1.5"}, {"LD", "SP", "d16", "3"}, {"LD", "(HL-)", "A", "2"}, {"INC", "SP", "*", "2"}, {"INC", "(HL)", "*", "3"}, {"DEC", "(HL)", "*", "3"}, {"LD", "(HL)", "d8", "3"}, {"SCF", "*", "*", "1"}, {"JR", "C", "r8", "1.5"}, {"ADD", "HL", "SP", "2"}, {"LD", "A", "(HL-)", "2"}, {"DEC", "SP", "*", "2"}, {"INC", "A", "*", "1"}, {"DEC", "A", "*", "1"}, {"LD", "A", "d8", "2"}, {"CCF", "*", "*", "1"},
20 | /* 0x4x */ {"LD", "B", "B", "1"}, {"LD", "B", "C", "1"}, {"LD", "B", "D", "1"}, {"LD", "B", "E", "1"}, {"LD", "B", "H", "1"}, {"LD", "B", "L", "1"}, {"LD", "B", "(HL)", "2"}, {"LD", "B", "A", "1"}, {"LD", "C", "B", "1"}, {"LD", "C", "C", "1"}, {"LD", "C", "D", "1"}, {"LD", "C", "E", "1"}, {"LD", "C", "H", "1"}, {"LD", "C", "L", "1"}, {"LD", "C", "(HL)", "2"}, {"LD", "C", "A", "1"},
21 | /* 0x5x */ {"LD", "D", "B", "1"}, {"LD", "D", "C", "1"}, {"LD", "D", "D", "1"}, {"LD", "D", "E", "1"}, {"LD", "D", "H", "1"}, {"LD", "D", "L", "1"}, {"LD", "D", "(HL)", "2"}, {"LD", "D", "A", "1"}, {"LD", "E", "B", "1"}, {"LD", "E", "C", "1"}, {"LD", "E", "D", "1"}, {"LD", "E", "E", "1"}, {"LD", "E", "H", "1"}, {"LD", "E", "L", "1"}, {"LD", "E", "(HL)", "2"}, {"LD", "E", "A", "1"},
22 | /* 0x6x */ {"LD", "H", "B", "1"}, {"LD", "H", "C", "1"}, {"LD", "H", "D", "1"}, {"LD", "H", "E", "1"}, {"LD", "H", "H", "1"}, {"LD", "H", "L", "1"}, {"LD", "H", "(HL)", "2"}, {"LD", "H", "A", "1"}, {"LD", "L", "B", "1"}, {"LD", "L", "C", "1"}, {"LD", "L", "D", "1"}, {"LD", "L", "E", "1"}, {"LD", "L", "H", "1"}, {"LD", "L", "L", "1"}, {"LD", "L", "(HL)", "2"}, {"LD", "L", "A", "1"},
23 | /* 0x7x */ {"LD", "(HL)", "B", "2"}, {"LD", "(HL)", "C", "2"}, {"LD", "(HL)", "D", "2"}, {"LD", "(HL)", "E", "2"}, {"LD", "(HL)", "H", "2"}, {"LD", "(HL)", "L", "2"}, {"HALT", "*", "*", "1"}, {"LD", "(HL)", "A", "2"}, {"LD", "A", "B", "1"}, {"LD", "A", "C", "1"}, {"LD", "A", "D", "1"}, {"LD", "A", "E", "1"}, {"LD", "A", "H", "1"}, {"LD", "A", "L", "1"}, {"LD", "A", "(HL)", "2"}, {"LD", "A", "A", "1"},
24 | /* 0x8x */ {"ADD", "A", "B", "1"}, {"ADD", "A", "C", "1"}, {"ADD", "A", "D", "1"}, {"ADD", "A", "E", "1"}, {"ADD", "A", "H", "1"}, {"ADD", "A", "L", "1"}, {"ADD", "A", "(HL)", "2"}, {"ADD", "A", "A", "1"}, {"ADC", "A", "B", "1"}, {"ADC", "A", "C", "1"}, {"ADC", "A", "D", "1"}, {"ADC", "A", "E", "1"}, {"ADC", "A", "H", "1"}, {"ADC", "A", "L", "1"}, {"ADC", "A", "(HL)", "2"}, {"ADC", "A", "A", "1"},
25 | /* 0x9x */ {"SUB", "B", "*", "1"}, {"SUB", "C", "*", "1"}, {"SUB", "D", "*", "1"}, {"SUB", "E", "*", "1"}, {"SUB", "H", "*", "1"}, {"SUB", "L", "*", "1"}, {"SUB", "(HL)", "*", "2"}, {"SUB", "A", "*", "1"}, {"SBC", "A", "B", "1"}, {"SBC", "A", "C", "1"}, {"SBC", "A", "D", "1"}, {"SBC", "A", "E", "1"}, {"SBC", "A", "H", "1"}, {"SBC", "A", "L", "1"}, {"SBC", "A", "(HL)", "2"}, {"SBC", "A", "A", "1"},
26 | /* 0xax */ {"AND", "B", "*", "1"}, {"AND", "C", "*", "1"}, {"AND", "D", "*", "1"}, {"AND", "E", "*", "1"}, {"AND", "H", "*", "1"}, {"AND", "L", "*", "1"}, {"AND", "(HL)", "*", "2"}, {"AND", "A", "*", "1"}, {"XOR", "B", "*", "1"}, {"XOR", "C", "*", "1"}, {"XOR", "D", "*", "1"}, {"XOR", "E", "*", "1"}, {"XOR", "H", "*", "1"}, {"XOR", "L", "*", "1"}, {"XOR", "(HL)", "*", "2"}, {"XOR", "A", "*", "1"},
27 | /* 0xbx */ {"OR", "B", "*", "1"}, {"OR", "C", "*", "1"}, {"OR", "D", "*", "1"}, {"OR", "E", "*", "1"}, {"OR", "H", "*", "1"}, {"OR", "L", "*", "1"}, {"OR", "(HL)", "*", "2"}, {"OR", "A", "*", "1"}, {"CP", "B", "*", "1"}, {"CP", "C", "*", "1"}, {"CP", "D", "*", "1"}, {"CP", "E", "*", "1"}, {"CP", "H", "*", "1"}, {"CP", "L", "*", "1"}, {"CP", "(HL)", "*", "2"}, {"CP", "A", "*", "1"},
28 | /* 0xcx */ {"RET", "NZ", "*", "2.5"}, {"POP", "BC", "*", "3"}, {"JP", "NZ", "a16", "1.333"}, {"JP", "a16", "*", "4"}, {"CALL", "NZ", "a16", "2"}, {"PUSH", "BC", "*", "4"}, {"ADD", "A", "d8", "2"}, {"RST", "00", "*", "4"}, {"RET", "Z", "*", "2.5"}, {"RET", "*", "*", "4"}, {"JP", "Z", "a16", "1.333"}, {"PREFIX CB", "*", "*", "1"}, {"CALL", "Z", "a16", "2"}, {"CALL", "a16", "*", "6"}, {"ADC", "A", "d8", "2"}, {"RST", "08", "*", "4"},
29 | /* 0xdx */ {"RET", "NC", "*", "2.5"}, {"POP", "DE", "*", "3"}, {"JP", "NC", "a16", "1.333"}, {"*", "*", "*", "*"}, {"CALL", "NC", "a16", "2"}, {"PUSH", "DE", "*", "4"}, {"SUB", "d8", "*", "2"}, {"RST", "16", "*", "4"}, {"RET", "C", "*", "2.5"}, {"RETI", "*", "*", "4"}, {"JP", "C", "a16", "1.333"}, {"*", "*", "*", "*"}, {"CALL", "C", "a16", "2"}, {"*", "*", "*", "*"}, {"SBC", "A", "d8", "2"}, {"RST", "24", "*", "4"},
30 | /* 0xex */ {"LDH", "(a8)", "A", "3"}, {"POP", "HL", "*", "3"}, {"LD", "(C)", "A", "2"}, {"*", "*", "*", "*"}, {"*", "*", "*", "*"}, {"PUSH", "HL", "*", "4"}, {"AND", "d8", "*", "2"}, {"RST", "32", "*", "4"}, {"ADD", "SP", "r8", "4"}, {"JP", "(HL)", "*", "1"}, {"LD", "(a16)", "A", "4"}, {"*", "*", "*", "*"}, {"*", "*", "*", "*"}, {"*", "*", "*", "*"}, {"XOR", "d8", "*", "2"}, {"RST", "40", "*", "4"},
31 | /* 0xfx */ {"LDH", "A", "(a8)", "3"}, {"POP", "AF", "*", "3"}, {"LD", "A", "(C)", "2"}, {"DI", "*", "*", "1"}, {"*", "*", "*", "*"}, {"PUSH", "AF", "*", "4"}, {"OR", "d8", "*", "2"}, {"RST", "48", "*", "4"}, {"LD", "HL", "SP+r8", "3"}, {"LD", "SP", "HL", "2"}, {"LD", "A", "(a16)", "4"}, {"EI", "*", "*", "1"}, {"*", "*", "*", "*"}, {"*", "*", "*", "*"}, {"CP", "d8", "*", "2"}, {"RST", "56", "*", "4"},
32 | }
33 |
34 | func disasm(opcode byte) string {
35 | result := ""
36 |
37 | mnemonic := mnemonics[opcode]
38 | for i := 0; i < 3; i++ {
39 | if mnemonic[i] == "*" {
40 | break
41 | }
42 |
43 | result += mnemonic[i] + " "
44 | }
45 |
46 | result = strings.TrimRight(result, " ")
47 | return result
48 | }
49 |
50 | func (d *Debugger) Disasm(w http.ResponseWriter, req *http.Request) {
51 | res, err := json.Marshal(Inst{
52 | PC: fmt.Sprintf("0x%04x", d.g.Reg.PC),
53 | Mnemonic: disasm(d.g.Load8(d.g.Reg.PC)),
54 | })
55 | if err != nil {
56 | http.Error(w, err.Error(), http.StatusInternalServerError)
57 | return
58 | }
59 |
60 | w.Header().Set("Content-Type", "application/json")
61 | w.Write(res)
62 | }
63 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
2 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200707082815-5321531c36a2 h1:Ac1OEHHkbAZ6EUnJahF0GKcU0FjPc/V8F1DvjhKngFE=
3 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200707082815-5321531c36a2/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
4 | github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY=
5 | github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
6 | github.com/hajimehoshi/bitmapfont/v2 v2.1.0/go.mod h1:2BnYrkTQGThpr/CY6LorYtt/zEPNzvE/ND69CRTaHMs=
7 | github.com/hajimehoshi/ebiten/v2 v2.0.6 h1:sHNymgI+q80xasP69oFyrpup6r2qCNsKxqwsGEh6PWE=
8 | github.com/hajimehoshi/ebiten/v2 v2.0.6/go.mod h1:uS3OjMW3f2DRDMtWoIF7yMMmrMkv+fZ6pXcwR1pfA0Y=
9 | github.com/hajimehoshi/file2byteslice v0.0.0-20200812174855-0e5e8a80490e/go.mod h1:CqqAHp7Dk/AqQiwuhV1yT2334qbA/tFWQW0MD2dGqUE=
10 | github.com/hajimehoshi/go-mp3 v0.3.1/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
11 | github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
12 | github.com/hajimehoshi/oto v0.6.8/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
13 | github.com/hajimehoshi/oto v0.7.1 h1:I7maFPz5MBCwiutOrz++DLdbr4rTzBsbBuV2VpgU9kk=
14 | github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos=
15 | github.com/jakecoffman/cp v1.0.0/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg=
16 | github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk=
17 | github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
18 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
19 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
20 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
21 | github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
22 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
23 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
24 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
25 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
26 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
27 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
28 | golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU=
29 | golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
30 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
31 | golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
32 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
33 | golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM=
34 | golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
35 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
36 | golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
37 | golang.org/x/mobile v0.0.0-20210208171126-f462b3930c8f h1:aEcjdTsycgPqO/caTgnxfR9xwWOltP/21vtJyFztEy0=
38 | golang.org/x/mobile v0.0.0-20210208171126-f462b3930c8f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
39 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
40 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
41 | golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
42 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
43 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
44 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
45 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
46 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
47 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
48 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
49 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
50 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
51 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
52 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
53 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
54 | golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
55 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
56 | golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
57 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
58 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
59 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
60 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
61 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
62 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
63 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
64 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
65 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
66 | golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
67 | golang.org/x/tools v0.0.0-20201009162240-fcf82128ed91/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
68 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
69 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
70 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
71 | gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
72 |
--------------------------------------------------------------------------------
/pkg/gbc/apu/apu.go:
--------------------------------------------------------------------------------
1 | package apu
2 |
3 | // copy and hack source code from goboy
4 |
5 | import (
6 | "fmt"
7 | "log"
8 | "math"
9 |
10 | "github.com/pokemium/worldwide/pkg/util"
11 | )
12 |
13 | const (
14 | SAMPLE_RATE = 44100
15 | twoPi = 2 * math.Pi
16 | perSample = 1 / float64(SAMPLE_RATE)
17 |
18 | cpuTicksPerSample = float64(4194304) / SAMPLE_RATE
19 | STREAM_LEN = 2940 // 2 * 2 * SAMPLE_RATE * (1/60)
20 | VOLUME = 0.07
21 | BUF_SEC = 60
22 | )
23 |
24 | // APU is the GameBoy's audio processing unit. Audio comprises four
25 | // channels, each one controlled by a set of registers.
26 | //
27 | // Channels 1 and 2 are both Square channels, channel 3 is a arbitrary
28 | // waveform channel which can be set in RAM, and channel 4 outputs noise.
29 | type APU struct {
30 | Enable bool
31 |
32 | memory [52]byte
33 | waveformRAM []byte
34 |
35 | chn1, chn2, chn3, chn4 *Channel
36 | tickCounter float64
37 | lVol, rVol float64
38 |
39 | audioBuffer chan [2]byte
40 | setAudioStream func([]byte)
41 | }
42 |
43 | // Init the sound emulation for a Gameboy.
44 | func New(enable bool, setAudioStream func([]byte)) *APU {
45 | a := &APU{
46 | Enable: enable,
47 | setAudioStream: setAudioStream,
48 | }
49 | a.waveformRAM = make([]byte, 0x20)
50 | a.audioBuffer = make(chan [2]byte, STREAM_LEN)
51 |
52 | // Sets waveform ram to:
53 | // 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF
54 | for x := 0x0; x < 0x20; x++ {
55 | if x&2 == 0 {
56 | a.waveformRAM[x] = 0x00
57 | } else {
58 | a.waveformRAM[x] = 0xFF
59 | }
60 | }
61 |
62 | // Create the channels with their sounds
63 | a.chn1 = NewChannel()
64 | a.chn2 = NewChannel()
65 | a.chn3 = NewChannel()
66 | a.chn4 = NewChannel()
67 |
68 | return a
69 | }
70 |
71 | // Plays the sound
72 | //
73 | // This function is called 60 times per second.
74 | func (a *APU) Update() {
75 | if !a.Enable {
76 | return
77 | }
78 |
79 | targetSamples := SAMPLE_RATE / BUF_SEC
80 | var reading [2]byte
81 | var buffer []byte
82 | fbLen := len(a.audioBuffer)
83 | if fbLen >= targetSamples/2 {
84 | newBuffer := make([]byte, fbLen*2)
85 | for i := 0; i < fbLen*2; i += 2 {
86 | reading = <-a.audioBuffer
87 | newBuffer[i], newBuffer[i+1] = reading[0], reading[1]
88 | }
89 | buffer = newBuffer
90 | }
91 | a.setAudioStream(buffer)
92 | }
93 |
94 | func (a *APU) Buffer(cpuTicks int) {
95 | if !a.Enable {
96 | return
97 | }
98 | a.tickCounter += float64(cpuTicks)
99 | if a.tickCounter < cpuTicksPerSample {
100 | return
101 | }
102 | a.tickCounter -= cpuTicksPerSample
103 |
104 | chn1l, chn1r := a.chn1.Sample()
105 | chn2l, chn2r := a.chn2.Sample()
106 | chn3l, chn3r := a.chn3.Sample()
107 | chn4l, chn4r := a.chn4.Sample()
108 |
109 | valL := (chn1l + chn2l + chn3l + chn4l) / 4
110 | valR := (chn1r + chn2r + chn3r + chn4r) / 4
111 |
112 | lVol, rVol := valL*a.lVol*VOLUME, valR*a.rVol*VOLUME
113 | a.audioBuffer <- [2]byte{byte(lVol), byte(rVol)}
114 | }
115 |
116 | var soundMask = []byte{
117 | /* 0xFF10 */ 0xFF, 0xC0, 0xFF, 0x00, 0x40,
118 | /* 0xFF15 */ 0x00, 0xC0, 0xFF, 0x00, 0x40,
119 | /* 0xFF1A */ 0x80, 0x00, 0x60, 0x00, 0x40,
120 | /* 0xFF20 */ 0x00, 0x3F, 0xFF, 0xFF, 0x40,
121 | /* 0xFF24 */ 0xFF, 0xFF, 0x80,
122 | }
123 |
124 | var channel3Volume = map[byte]float64{0: 0, 1: 1, 2: 0.5, 3: 0.25}
125 |
126 | var squareLimits = map[byte]float64{
127 | 0: -0.25, // 12.5% ( _-------_-------_------- )
128 | 1: -0.5, // 25% ( __------__------__------ )
129 | 2: 0, // 50% ( ____----____----____---- ) (normal)
130 | 3: 0.5, // 75% ( ______--______--______-- )
131 | }
132 |
133 | // Read returns a value from the APU.
134 | func (a *APU) Read(offset byte) byte {
135 | if offset >= 0x30 {
136 | return a.waveformRAM[offset-0x30]
137 | }
138 | // TODO: we should modify the sound memory as we're sampling
139 | return a.memory[offset-0x00] & soundMask[offset-0x10]
140 | }
141 |
142 | // Write a value to the APU registers.
143 | func (a *APU) Write(offset byte, value byte) {
144 | a.memory[offset] = value
145 |
146 | switch uint16(offset) + 0xff00 {
147 | // Channel 1
148 | case 0xFF10:
149 | // -PPP NSSS Sweep period, negate, shift
150 | a.chn1.sweepStepLen = (a.memory[0x10] & 0b111_0000) >> 4
151 | a.chn1.sweepSteps = a.memory[0x10] & 0b111
152 | a.chn1.sweepIncrease = !util.Bit(a.memory[0x10], 3) // 1 = decrease
153 | case 0xFF11:
154 | // DDLL LLLL Duty, Length load
155 | duty := (value & 0b1100_0000) >> 6
156 | a.chn1.generator = Square(squareLimits[duty])
157 | a.chn1.length = int(value & 0b0011_1111)
158 | case 0xFF12:
159 | // VVVV APPP - Starting volume, Envelop add mode, period
160 | envVolume, envDirection, envSweep := a.extractEnvelope(value)
161 | a.chn1.envelopeVolume = int(envVolume)
162 | a.chn1.envelopeSamples = int(envSweep) * SAMPLE_RATE / 64
163 | a.chn1.envelopeIncreasing = envDirection == 1
164 | case 0xFF13:
165 | // FFFF FFFF Frequency LSB
166 | frequencyValue := uint16(a.memory[0x14]&0b111)<<8 | uint16(value)
167 | a.chn1.frequency = 131072 / (2048 - float64(frequencyValue))
168 | case 0xFF14:
169 | // TL-- -FFF Trigger, Length Enable, Frequencu MSB
170 | frequencyValue := uint16(value&0b111)<<8 | uint16(a.memory[0x13])
171 | a.chn1.frequency = 131072 / (2048 - float64(frequencyValue))
172 | if util.Bit(value, 7) {
173 | if a.chn1.length == 0 {
174 | a.chn1.length = 64
175 | }
176 | duration := -1
177 | if util.Bit(value, 6) { // 1 = use length
178 | duration = int(float64(a.chn1.length)*(1/64)) * SAMPLE_RATE
179 | }
180 | a.chn1.Reset(duration)
181 | a.chn1.envelopeSteps = a.chn1.envelopeVolume
182 | a.chn1.envelopeStepsInit = a.chn1.envelopeVolume
183 | // TODO: Square 1's sweep does several things (see frequency sweep).
184 | }
185 |
186 | // Channel 2
187 | case 0xFF15:
188 | // ---- ---- Not used
189 | case 0xFF16:
190 | // DDLL LLLL Duty, Length load (64-L)
191 | pattern := (value & 0b1100_0000) >> 6
192 | a.chn2.generator = Square(squareLimits[pattern])
193 | a.chn2.length = int(value & 0b11_1111)
194 | case 0xFF17:
195 | // VVVV APPP Starting volume, Envelope add mode, period
196 | envVolume, envDirection, envSweep := a.extractEnvelope(value)
197 | a.chn2.envelopeVolume = int(envVolume)
198 | a.chn2.envelopeSamples = int(envSweep) * SAMPLE_RATE / 64
199 | a.chn2.envelopeIncreasing = envDirection == 1
200 | case 0xFF18:
201 | // FFFF FFFF Frequency LSB
202 | frequencyValue := uint16(a.memory[0x19]&0b111)<<8 | uint16(value)
203 | a.chn2.frequency = 131072 / (2048 - float64(frequencyValue))
204 | case 0xFF19:
205 | // TL-- -FFF Trigger, Length enable, Frequency MSB
206 | if util.Bit(value, 7) {
207 | if a.chn2.length == 0 {
208 | a.chn2.length = 64
209 | }
210 | duration := -1
211 | if util.Bit(value, 6) {
212 | duration = int(float64(a.chn2.length)*(1/64)) * SAMPLE_RATE
213 | }
214 | a.chn2.Reset(duration)
215 | a.chn2.envelopeSteps = a.chn2.envelopeVolume
216 | a.chn2.envelopeStepsInit = a.chn2.envelopeVolume
217 | }
218 | frequencyValue := uint16(value&0b111)<<8 | uint16(a.memory[0x18])
219 | a.chn2.frequency = 131072 / (2048 - float64(frequencyValue))
220 |
221 | // Channel 3
222 | case 0xFF1A:
223 | // E--- ---- DAC power
224 | a.chn3.envelopeStepsInit = int((value & 0b1000_0000) >> 7)
225 | case 0xFF1B:
226 | // LLLL LLLL Length load
227 | a.chn3.length = int(value)
228 | case 0xFF1C:
229 | // -VV- ---- Volume code
230 | selection := (value & 0b110_0000) >> 5
231 | a.chn3.amplitude = channel3Volume[selection]
232 | case 0xFF1D:
233 | // FFFF FFFF Frequency LSB
234 | frequencyValue := uint16(a.memory[0x1E]&0b111)<<8 | uint16(value)
235 | a.chn3.frequency = 65536 / (2048 - float64(frequencyValue))
236 | case 0xFF1E:
237 | // TL-- -FFF Trigger, Length enable, Frequency MSB
238 | if util.Bit(value, 7) {
239 | if a.chn3.length == 0 {
240 | a.chn3.length = 256
241 | }
242 | duration := -1
243 | if value&0b100_0000 != 0 { // 1 = use length
244 | duration = int((256-float64(a.chn3.length))*(1/256)) * SAMPLE_RATE
245 | }
246 | a.chn3.generator = Waveform(func(i int) byte { return a.waveformRAM[i] })
247 | a.chn3.duration = duration
248 | }
249 | frequencyValue := uint16(value&0b111)<<8 | uint16(a.memory[0x1D])
250 | a.chn3.frequency = 65536 / (2048 - float64(frequencyValue))
251 |
252 | // Channel 4
253 | case 0xFF1F:
254 | // ---- ---- Not used
255 | case 0xFF20:
256 | // --LL LLLL Length load
257 | a.chn4.length = int(value & 0b11_1111)
258 | case 0xFF21:
259 | // VVVV APPP Starting volume, Envelope add mode, period
260 | envVolume, envDirection, envSweep := a.extractEnvelope(value)
261 | a.chn4.envelopeVolume = int(envVolume)
262 | a.chn4.envelopeSamples = int(envSweep) * SAMPLE_RATE / 64
263 | a.chn4.envelopeIncreasing = envDirection == 1
264 | case 0xFF22:
265 | // SSSS WDDD Clock shift, Width mode of LFSR, Divisor code
266 | shiftClock := float64((value & 0b1111_0000) >> 4)
267 | // TODO: counter step width
268 | divRatio := float64(value & 0b111)
269 | if divRatio == 0 {
270 | divRatio = 0.5
271 | }
272 | a.chn4.frequency = 524288 / divRatio / math.Pow(2, shiftClock+1)
273 | case 0xFF23:
274 | // TL-- ---- Trigger, Length enable
275 | if util.Bit(value, 7) {
276 | duration := -1
277 | if util.Bit(value, 6) { // 1 = use length
278 | duration = int(float64(61-a.chn4.length)*(1/256)) * SAMPLE_RATE
279 | }
280 | a.chn4.generator = Noise()
281 | a.chn4.Reset(duration)
282 | a.chn4.envelopeSteps = a.chn4.envelopeVolume
283 | a.chn4.envelopeStepsInit = a.chn4.envelopeVolume
284 | }
285 |
286 | case 0xFF24:
287 | // Volume control
288 | a.lVol = float64((a.memory[0x24]&0x70)>>4) / 7
289 | a.rVol = float64(a.memory[0x24]&0x7) / 7
290 |
291 | case 0xFF25:
292 | // Channel control
293 | a.chn1.onR = value&0x1 != 0
294 | a.chn2.onR = value&0x2 != 0
295 | a.chn3.onR = value&0x4 != 0
296 | a.chn4.onR = value&0x8 != 0
297 | a.chn1.onL = value&0x10 != 0
298 | a.chn2.onL = value&0x20 != 0
299 | a.chn3.onL = value&0x40 != 0
300 | a.chn4.onL = value&0x80 != 0
301 | }
302 | // TODO: if writing to FF26 bit 7 destroy all contents (also cannot access)
303 | }
304 |
305 | // WriteWaveform writes a value to the waveform ram.
306 | func (a *APU) WriteWaveform(offset byte, value byte) {
307 | soundIndex := (offset - 0x30) * 2
308 | a.waveformRAM[soundIndex] = (value >> 4) & 0xF * 0x11
309 | a.waveformRAM[soundIndex+1] = value & 0xF * 0x11
310 | }
311 |
312 | // ToggleSoundChannel toggles a sound channel for debugging.
313 | func (a *APU) ToggleSoundChannel(channel int) {
314 | switch channel {
315 | case 1:
316 | a.chn1.debugOff = !a.chn1.debugOff
317 | case 2:
318 | a.chn2.debugOff = !a.chn2.debugOff
319 | case 3:
320 | a.chn3.debugOff = !a.chn3.debugOff
321 | case 4:
322 | a.chn4.debugOff = !a.chn4.debugOff
323 | }
324 | log.Printf("Toggle Channel %v mute", channel)
325 | }
326 |
327 | func (a *APU) LogSoundState() {
328 | fmt.Println("Channel 3")
329 | fmt.Printf(" 0xFF1A E--- ---- = %08b\n", a.memory[0x1A])
330 | fmt.Printf(" 0xFF1B LLLL LLLL = %08b\n", a.memory[0x1B])
331 | fmt.Printf(" 0xFF1C -VV- ---- = %08b\n", a.memory[0x1C])
332 | fmt.Printf(" 0xFF1D FFFF FFFF = %08b\n", a.memory[0x1D])
333 | fmt.Printf(" 0xFF1E TL-- -FFF = %08b\n", a.memory[0x1E])
334 | }
335 |
336 | // Extract some envelope variables from a byte.
337 | func (a *APU) extractEnvelope(val byte) (volume, direction, sweep byte) {
338 | volume = (val & 0xF0) >> 4
339 | direction = (val & 0x8) >> 3 // 1 or 0
340 | sweep = val & 0x7
341 | return
342 | }
343 |
--------------------------------------------------------------------------------
/pkg/gbc/gbc.go:
--------------------------------------------------------------------------------
1 | package gbc
2 |
3 | import (
4 | "fmt"
5 | "math"
6 | "os"
7 | "runtime"
8 |
9 | "github.com/pokemium/worldwide/pkg/gbc/apu"
10 | "github.com/pokemium/worldwide/pkg/gbc/cart"
11 | "github.com/pokemium/worldwide/pkg/gbc/joypad"
12 | "github.com/pokemium/worldwide/pkg/gbc/rtc"
13 | "github.com/pokemium/worldwide/pkg/gbc/scheduler"
14 | "github.com/pokemium/worldwide/pkg/gbc/video"
15 | "github.com/pokemium/worldwide/pkg/util"
16 | )
17 |
18 | var irqVec = [5]uint16{0x0040, 0x0048, 0x0050, 0x0058, 0x0060}
19 |
20 | // ROM
21 | //
22 | // 0x0000-0x3fff: bank0
23 | //
24 | // 0x4000-0x7fff: bank1-256
25 | type ROM struct {
26 | bank byte
27 | buffer [256][0x4000]byte
28 | }
29 |
30 | // RAM - 0xa000-0xbfff
31 | type RAM struct {
32 | bank byte
33 | Buffer [16][0x2000]byte // num of banks changes depending on ROM
34 | }
35 |
36 | // WRAM
37 | //
38 | // 0xc000-0xcfff: bank0
39 | //
40 | // 0xd000-0xdfff: bank1-7
41 | type WRAM struct {
42 | // fixed at 1 on DMG, changes from 1 to 7 on CGB
43 | bank byte
44 | buffer [8][0x1000]byte
45 | }
46 |
47 | type Dma struct {
48 | src, dest uint16
49 | remaining int
50 | }
51 |
52 | type Hdma struct {
53 | enable bool
54 | src, dest uint16
55 | remaining int
56 | }
57 |
58 | const (
59 | NoIRQ = iota
60 | VBlankIRQ
61 | LCDCIRQ
62 | TimerIRQ
63 | SerialIRQ
64 | JoypadIRQ
65 | )
66 |
67 | type CurInst struct {
68 | Opcode byte
69 | PC uint16
70 | }
71 |
72 | // GBC core structure
73 | type GBC struct {
74 | Reg Register
75 | Inst CurInst
76 |
77 | // memory
78 | ROM ROM
79 | RAM RAM
80 | WRAM WRAM
81 | IO [0x100]byte // 0xff00-0xffff
82 |
83 | Cartridge *cart.Cartridge
84 | joypad *joypad.Joypad
85 | Halt bool
86 | timer *Timer
87 | bankMode uint
88 | Sound *apu.APU
89 | Video *video.Video
90 | RTC *rtc.RTC
91 | DoubleSpeed bool
92 | model util.GBModel
93 | irqPending int
94 | scheduler *scheduler.Scheduler
95 | dma Dma
96 | hdma Hdma
97 | cpuBlocked bool
98 |
99 | // plugins
100 | Callbacks []*util.Callback
101 | }
102 |
103 | // TransferROM Transfer ROM from cartridge to Memory
104 | func (g *GBC) TransferROM(rom []byte) {
105 | switch g.Cartridge.Type {
106 | case 0x00:
107 | g.Cartridge.MBC = cart.ROM
108 | g.transferROM(2, rom)
109 | case 0x01: // Type : 1 => MBC1
110 | g.Cartridge.MBC = cart.MBC1
111 | switch r := int(g.Cartridge.ROMSize); r {
112 | case 0, 1, 2, 3, 4, 5, 6:
113 | g.transferROM(int(math.Pow(2, float64(r+1))), rom)
114 | default:
115 | errorMsg := fmt.Sprintf("ROMSize is invalid => type:%x rom:%x ram:%x\n", g.Cartridge.Type, g.Cartridge.ROMSize, g.Cartridge.RAMSize)
116 | panic(errorMsg)
117 | }
118 | case 0x02, 0x03: // Type : 2, 3 => MBC1+RAM
119 | g.Cartridge.MBC = cart.MBC1
120 | switch g.Cartridge.RAMSize {
121 | case 0, 1, 2:
122 | switch r := int(g.Cartridge.ROMSize); r {
123 | case 0, 1, 2, 3, 4, 5, 6:
124 | g.transferROM(int(math.Pow(2, float64(r+1))), rom)
125 | default:
126 | errorMsg := fmt.Sprintf("ROMSize is invalid => type:%x rom:%x ram:%x\n", g.Cartridge.Type, g.Cartridge.ROMSize, g.Cartridge.RAMSize)
127 | panic(errorMsg)
128 | }
129 | case 3:
130 | g.bankMode = 1
131 | switch r := int(g.Cartridge.ROMSize); r {
132 | case 0:
133 | case 1, 2, 3, 4:
134 | g.transferROM(int(math.Pow(2, float64(r+1))), rom)
135 | default:
136 | errorMsg := fmt.Sprintf("ROMSize is invalid => type:%x rom:%x ram:%x\n", g.Cartridge.Type, g.Cartridge.ROMSize, g.Cartridge.RAMSize)
137 | panic(errorMsg)
138 | }
139 | default:
140 | errorMsg := fmt.Sprintf("RAMSize is invalid => type:%x rom:%x ram:%x\n", g.Cartridge.Type, g.Cartridge.ROMSize, g.Cartridge.RAMSize)
141 | panic(errorMsg)
142 | }
143 | case 0x05, 0x06: // Type : 5, 6 => MBC2
144 | g.Cartridge.MBC = cart.MBC2
145 | switch g.Cartridge.RAMSize {
146 | case 0, 1, 2:
147 | switch r := int(g.Cartridge.ROMSize); r {
148 | case 0, 1, 2, 3:
149 | g.transferROM(int(math.Pow(2, float64(r+1))), rom)
150 | default:
151 | errorMsg := fmt.Sprintf("ROMSize is invalid => type:%x rom:%x ram:%x\n", g.Cartridge.Type, g.Cartridge.ROMSize, g.Cartridge.RAMSize)
152 | panic(errorMsg)
153 | }
154 | case 3:
155 | g.bankMode = 1
156 | switch r := int(g.Cartridge.ROMSize); r {
157 | case 0:
158 | case 1, 2, 3:
159 | g.transferROM(int(math.Pow(2, float64(r+1))), rom)
160 | default:
161 | errorMsg := fmt.Sprintf("ROMSize is invalid => type:%x rom:%x ram:%x\n", g.Cartridge.Type, g.Cartridge.ROMSize, g.Cartridge.RAMSize)
162 | panic(errorMsg)
163 | }
164 | default:
165 | errorMsg := fmt.Sprintf("RAMSize is invalid => type:%x rom:%x ram:%x\n", g.Cartridge.Type, g.Cartridge.ROMSize, g.Cartridge.RAMSize)
166 | panic(errorMsg)
167 | }
168 | case 0x0f, 0x10, 0x11, 0x12, 0x13: // Type : 0x0f, 0x10, 0x11, 0x12, 0x13 => MBC3
169 | g.Cartridge.MBC, g.RTC.Enable = cart.MBC3, true
170 | switch r := int(g.Cartridge.ROMSize); r {
171 | case 0, 1, 2, 3, 4, 5, 6:
172 | g.transferROM(int(math.Pow(2, float64(r+1))), rom)
173 | default:
174 | errorMsg := fmt.Sprintf("ROMSize is invalid => type:%x rom:%x ram:%x\n", g.Cartridge.Type, g.Cartridge.ROMSize, g.Cartridge.RAMSize)
175 | panic(errorMsg)
176 | }
177 | case 0x19, 0x1a, 0x1b: // Type : 0x19, 0x1a, 0x1b => MBC5
178 | g.Cartridge.MBC = cart.MBC5
179 | switch r := int(g.Cartridge.ROMSize); r {
180 | case 0, 1, 2, 3, 4, 5, 6, 7:
181 | g.transferROM(int(math.Pow(2, float64(r+1))), rom)
182 | default:
183 | errorMsg := fmt.Sprintf("ROMSize is invalid => type:%x rom:%x ram:%x\n", g.Cartridge.Type, g.Cartridge.ROMSize, g.Cartridge.RAMSize)
184 | panic(errorMsg)
185 | }
186 | default:
187 | errorMsg := fmt.Sprintf("Type is invalid => type:%x rom:%x ram:%x\n", g.Cartridge.Type, g.Cartridge.ROMSize, g.Cartridge.RAMSize)
188 | panic(errorMsg)
189 | }
190 | }
191 |
192 | func (g *GBC) transferROM(bankNum int, rom []byte) {
193 | for bank := 0; bank < bankNum; bank++ {
194 | for i := 0x0000; i <= 0x3fff; i++ {
195 | g.ROM.buffer[bank][i] = rom[bank*0x4000+i]
196 | }
197 | }
198 | }
199 |
200 | func (g *GBC) resetRegister() {
201 | g.Reg.setAF(0x11b0) // A=01 => GB, A=11 => CGB
202 | g.Reg.setBC(0x0013)
203 | g.Reg.setDE(0x00d8)
204 | g.Reg.setHL(0x014d)
205 | g.Reg.PC, g.Reg.SP = 0x0100, 0xfffe
206 | }
207 |
208 | func New(romData []byte, j [8](func() bool), setAudioStream func([]byte)) *GBC {
209 | c := cart.New(romData)
210 | g := &GBC{
211 | Cartridge: c,
212 | scheduler: scheduler.New(),
213 | joypad: joypad.New(j),
214 | RTC: rtc.New(c.HasRTC()),
215 | Sound: apu.New(true, setAudioStream),
216 | }
217 |
218 | // init graphics
219 | g.Video = video.New(&g.IO, g.updateIRQs, g.hdmaMode3, g.scheduler)
220 | if g.Cartridge.IsCGB {
221 | g.setModel(util.GB_MODEL_CGB)
222 | }
223 |
224 | // init timer
225 | g.timer = NewTimer(g)
226 | g.skipBIOS()
227 | g.TransferROM(romData)
228 | return g
229 | }
230 |
231 | // Exec 1cycle
232 | func (g *GBC) Step() {
233 | pc := g.Reg.PC
234 | opcode := g.Load8(pc)
235 | g.Inst.Opcode, g.Inst.PC = opcode, pc
236 |
237 | inst := gbz80insts[opcode]
238 | operand1, operand2, cycle, handler := inst.Operand1, inst.Operand2, inst.Cycle1, inst.Handler
239 |
240 | if g.Halt || g.cpuBlocked {
241 | cycle = int(g.scheduler.Next() - g.scheduler.Cycle())
242 | } else {
243 | if g.irqPending > 0 {
244 | oldIrqPending := g.irqPending
245 | g.irqPending = 0
246 | g.setInterrupts(false)
247 | g.triggerIRQ(int(oldIrqPending - 1))
248 | return
249 | }
250 |
251 | g.Reg.PC++
252 | handler(g, operand1, operand2)
253 | cycle *= (4 >> uint32(util.Bool2U64(g.DoubleSpeed)))
254 | }
255 |
256 | g.timer.tick(uint32(cycle))
257 | }
258 |
259 | // 1 frame
260 | func (g *GBC) Update() {
261 | frame := g.Frame()
262 | if frame%3 == 0 {
263 | g.handleJoypad()
264 | }
265 |
266 | for frame == g.Video.FrameCounter {
267 | g.Step()
268 |
269 | // trigger callbacks
270 | for _, callback := range g.Callbacks {
271 | if callback.Func() {
272 | break
273 | }
274 | }
275 | }
276 |
277 | g.Sound.Update()
278 | }
279 |
280 | func (g *GBC) PanicHandler(place string, stack bool) {
281 | if err := recover(); err != nil {
282 | fmt.Fprintf(os.Stderr, "%s emulation error: %s in 0x%04x\n", place, err, g.Reg.PC)
283 | for depth := 0; ; depth++ {
284 | _, file, line, ok := runtime.Caller(depth)
285 | if !ok {
286 | break
287 | }
288 | fmt.Fprintf(os.Stderr, "======> %d: %v:%d\n", depth, file, line)
289 | }
290 | os.Exit(1)
291 | }
292 | }
293 |
294 | func (g *GBC) setModel(m util.GBModel) {
295 | g.model = m
296 | g.Video.Renderer.Model = m
297 | }
298 |
299 | // GBUpdateIRQs
300 | func (g *GBC) updateIRQs() {
301 | irqs := g.IO[IEIO] & g.IO[IFIO] & 0x1f
302 | if irqs == 0 {
303 | g.irqPending = 0
304 | return
305 | }
306 |
307 | g.Halt = false
308 | if !g.Reg.IME {
309 | g.irqPending = 0
310 | return
311 | }
312 | if g.irqPending > 0 {
313 | return
314 | }
315 |
316 | for i := 0; i < 4; i++ {
317 | if util.Bit(irqs, i) {
318 | g.irqPending = i + 1
319 | return
320 | }
321 | }
322 | }
323 |
324 | func (g *GBC) Draw() []byte { return g.Video.Display().Pix }
325 |
326 | func (g *GBC) handleJoypad() {
327 | pressed := g.joypad.Input()
328 | if pressed {
329 | g.IO[IFIO] = util.SetBit8(g.IO[IFIO], 4, true)
330 | g.updateIRQs()
331 | }
332 | }
333 |
334 | func (g *GBC) Frame() int { return g.Video.FrameCounter }
335 |
336 | // _GBMemoryDMAService
337 | func (g *GBC) dmaService(cyclesLate uint64) {
338 | remaining := g.dma.remaining
339 | g.dma.remaining = 0
340 |
341 | b := g.Load8(g.dma.src)
342 | g.Store8(g.dma.dest, b)
343 |
344 | g.dma.src++
345 | g.dma.dest++
346 | g.dma.remaining = remaining - 1
347 | if g.dma.remaining > 0 {
348 | g.scheduler.ScheduleEvent(scheduler.OAMDMA, g.dmaService, (4>>util.Bool2U64(g.DoubleSpeed))-cyclesLate) // 4 * 40 = 160cycle
349 | }
350 | }
351 |
352 | // _GBMemoryHDMAService
353 | func (g *GBC) hdmaService(cyclesLate uint64) {
354 | g.cpuBlocked = true
355 |
356 | b := g.Load8(g.hdma.src)
357 | g.Store8(g.hdma.dest, b)
358 |
359 | g.hdma.src++
360 | g.hdma.dest++
361 | g.hdma.remaining--
362 |
363 | if g.hdma.remaining > 0 {
364 | g.scheduler.DescheduleEvent(scheduler.HDMA)
365 | g.scheduler.ScheduleEvent(scheduler.HDMA, g.hdmaService, 4-cyclesLate)
366 | return
367 | }
368 |
369 | g.cpuBlocked = false
370 | g.IO[HDMA1IO] = byte(g.hdma.src >> 8)
371 | g.IO[HDMA2IO] = byte(g.hdma.src)
372 | g.IO[HDMA3IO] = byte(g.hdma.dest >> 8)
373 | g.IO[HDMA4IO] = byte(g.hdma.dest)
374 | if g.hdma.enable {
375 | g.IO[HDMA5IO]--
376 | if g.IO[HDMA5IO] == 0xff {
377 | g.hdma.enable = false
378 | }
379 | } else {
380 | g.IO[HDMA5IO] = 0xff
381 | }
382 | }
383 |
384 | func (g *GBC) triggerIRQ(idx int) {
385 | g.IO[IFIO] = util.SetBit8(g.IO[IFIO], idx, false)
386 | g.timer.tick(20)
387 | g.pushPC()
388 | g.Reg.PC = irqVec[idx]
389 | }
390 |
391 | func (g *GBC) hdmaMode3() {
392 | if g.Video.Ly < video.VERTICAL_PIXELS && g.hdma.enable && g.IO[HDMA5IO] != 0xff {
393 | g.hdma.remaining = 0x10
394 | g.cpuBlocked = true
395 | g.scheduler.DescheduleEvent(scheduler.HDMA)
396 | g.scheduler.ScheduleEvent(scheduler.HDMA, g.hdmaService, 0)
397 | }
398 | }
399 |
400 | func (g *GBC) skipBIOS() {
401 | g.resetRegister()
402 | g.resetIO()
403 | g.ROM.bank, g.WRAM.bank = 1, 1
404 |
405 | g.storeIO(LCDCIO, 0x91)
406 | g.Video.SkipBIOS()
407 | }
408 |
409 | // GBSetInterrupts
410 | func (g *GBC) setInterrupts(enable bool) {
411 | g.scheduler.DescheduleEvent(scheduler.EiPending)
412 | if enable {
413 | g.scheduler.ScheduleEvent(scheduler.EiPending, func(_ uint64) {
414 | g.Reg.IME = true
415 | g.updateIRQs()
416 | }, 8>>util.Bool2Int(g.DoubleSpeed))
417 | return
418 | }
419 |
420 | g.Reg.IME = false
421 | g.updateIRQs()
422 | }
423 |
--------------------------------------------------------------------------------
/pkg/gbc/video/video.go:
--------------------------------------------------------------------------------
1 | package video
2 |
3 | import (
4 | "image"
5 | "image/color"
6 |
7 | "github.com/pokemium/worldwide/pkg/gbc/scheduler"
8 | "github.com/pokemium/worldwide/pkg/util"
9 | )
10 |
11 | // uint16 = Bit0-4(R) | Bit5-9(G) | Bit10-14(B)
12 | type Color uint16
13 |
14 | var defaultDmgPalette = [12]Color{
15 | // BGP
16 | 0x7fff, // -> 0b11111, 0b11111, 0b11111 (white)
17 | 0x56b5, // -> 0b10101, 0b10101, 0b10101 (light gray)
18 | 0x294a, // -> 0b01010, 0b01010, 0b01010 (dark gray)
19 | 0x0000, // -> 0b00000, 0b00000, 0b00000 (black)
20 |
21 | // OBP0
22 | 0x7fff,
23 | 0x56b5,
24 | 0x294a,
25 | 0x0000,
26 |
27 | // OBP1
28 | 0x7fff,
29 | 0x56b5,
30 | 0x294a,
31 | 0x0000,
32 | }
33 |
34 | type VRAM struct {
35 | Bank uint16 // 0 or 1
36 | Buffer [0x4000]byte // 0x4000 = (0x8000..0x9fff)x2 (using bank on CGB)
37 | }
38 |
39 | // Video processes graphics
40 | type Video struct {
41 | LCDC byte // LCD Control
42 | VRAM
43 | io *[0x100]byte
44 |
45 | X, Ly int
46 | Stat byte // LCD Status
47 |
48 | Renderer *Renderer
49 | Oam *OAM
50 |
51 | // 0xff68
52 | BcpIndex, BcpIncrement int
53 |
54 | // 0xff6a
55 | OcpIndex, OcpIncrement int
56 |
57 | dmgPalette [12]Color
58 | Palette [64]Color
59 |
60 | FrameCounter, frameskip, frameskipCounter int
61 | updateIRQs func()
62 |
63 | scheduler *scheduler.Scheduler
64 | hdma func()
65 | }
66 |
67 | var (
68 | // colors {R, G, B}
69 | DmgColor [4][3]uint8 = [4][3]uint8{
70 | {175, 197, 160}, {93, 147, 66}, {22, 63, 48}, {0, 40, 0},
71 | }
72 | )
73 |
74 | const (
75 | BGP = iota
76 | OBP0
77 | OBP1
78 | )
79 |
80 | func New(io *[0x100]byte, updateIRQs, hdma func(), scheduler *scheduler.Scheduler) *Video {
81 | g := &Video{
82 | io: io,
83 | Oam: NewOAM(),
84 | dmgPalette: defaultDmgPalette,
85 | updateIRQs: updateIRQs,
86 | scheduler: scheduler,
87 | hdma: hdma,
88 | }
89 |
90 | g.Renderer = NewRenderer(g)
91 | g.Reset()
92 | return g
93 | }
94 |
95 | func (g *Video) Reset() {
96 | g.Ly, g.X = 0, 0
97 | g.Stat = 1
98 | g.FrameCounter, g.frameskipCounter = 0, 0
99 |
100 | g.SwitchBank(0)
101 | for i := 0; i < len(g.VRAM.Buffer); i++ {
102 | g.VRAM.Buffer[i] = 0
103 | }
104 |
105 | g.Palette[0] = Color(g.dmgPalette[0])
106 | g.Palette[1] = Color(g.dmgPalette[1])
107 | g.Palette[2] = Color(g.dmgPalette[2])
108 | g.Palette[3] = Color(g.dmgPalette[3])
109 | g.Palette[8*4+0] = Color(g.dmgPalette[4])
110 | g.Palette[8*4+1] = Color(g.dmgPalette[5])
111 | g.Palette[8*4+2] = Color(g.dmgPalette[6])
112 | g.Palette[8*4+3] = Color(g.dmgPalette[7])
113 | g.Palette[9*4+0] = Color(g.dmgPalette[8])
114 | g.Palette[9*4+1] = Color(g.dmgPalette[9])
115 | g.Palette[9*4+2] = Color(g.dmgPalette[10])
116 | g.Palette[9*4+3] = Color(g.dmgPalette[11])
117 |
118 | g.Renderer.writePalette(0, g.Palette[0])
119 | g.Renderer.writePalette(1, g.Palette[1])
120 | g.Renderer.writePalette(2, g.Palette[2])
121 | g.Renderer.writePalette(3, g.Palette[3])
122 | g.Renderer.writePalette(8*4+0, g.Palette[8*4+0])
123 | g.Renderer.writePalette(8*4+1, g.Palette[8*4+1])
124 | g.Renderer.writePalette(8*4+2, g.Palette[8*4+2])
125 | g.Renderer.writePalette(8*4+3, g.Palette[8*4+3])
126 | g.Renderer.writePalette(9*4+0, g.Palette[9*4+0])
127 | g.Renderer.writePalette(9*4+1, g.Palette[9*4+1])
128 | g.Renderer.writePalette(9*4+2, g.Palette[9*4+2])
129 | g.Renderer.writePalette(9*4+3, g.Palette[9*4+3])
130 | }
131 |
132 | // GBVideoSkipBIOS
133 | func (g *Video) SkipBIOS() {
134 | next := uint64(56)
135 | if g.Renderer.Model == util.GB_MODEL_CGB {
136 | next = 20
137 | }
138 | g.Ly, g.io[GB_REG_LY] = VERTICAL_PIXELS, VERTICAL_PIXELS
139 | g.setMode(1)
140 |
141 | g.io[GB_REG_IF] = util.SetBit8(g.io[GB_REG_IF], 0, true)
142 | g.updateIRQs()
143 | g.scheduler.DescheduleEvent(scheduler.EndMode1)
144 | g.scheduler.ScheduleEvent(scheduler.EndMode1, g.EndMode1, next)
145 | }
146 |
147 | // Display returns gameboy display data
148 | func (g *Video) Display() *image.RGBA {
149 | i := image.NewRGBA(image.Rect(0, 0, HORIZONTAL_PIXELS, VERTICAL_PIXELS))
150 | for y := 0; y < VERTICAL_PIXELS; y++ {
151 | for x := 0; x < HORIZONTAL_PIXELS; x++ {
152 | p := g.Renderer.outputBuffer[y*HORIZONTAL_PIXELS+x]
153 | red, green, blue := byte((p&0b11111)*8), byte(((p>>5)&0b11111)*8), byte(((p>>10)&0b11111)*8)
154 |
155 | i.SetRGBA(x, y, color.RGBA{red, green, blue, 0xff})
156 | }
157 | }
158 | return i
159 | }
160 |
161 | // GBVideoWritePalette
162 | // 0xff47, 0xff48, 0xff49, 0xff69, 0xff6b
163 | func (g *Video) WritePalette(offset byte, value byte) {
164 | if g.Renderer.Model < util.GB_MODEL_SGB {
165 | switch offset {
166 | case GB_REG_BGP:
167 | // Palette = 0(white) or 1(light gray) or 2(dark gray) or 3(black)
168 | g.Palette[0] = Color(g.dmgPalette[value&3])
169 | g.Palette[1] = Color(g.dmgPalette[(value>>2)&3])
170 | g.Palette[2] = Color(g.dmgPalette[(value>>4)&3])
171 | g.Palette[3] = Color(g.dmgPalette[(value>>6)&3])
172 | g.Renderer.writePalette(0, g.Palette[0])
173 | g.Renderer.writePalette(1, g.Palette[1])
174 | g.Renderer.writePalette(2, g.Palette[2])
175 | g.Renderer.writePalette(3, g.Palette[3])
176 | case GB_REG_OBP0:
177 | g.Palette[8*4+0] = Color(g.dmgPalette[(value&3)+4])
178 | g.Palette[8*4+1] = Color(g.dmgPalette[((value>>2)&3)+4])
179 | g.Palette[8*4+2] = Color(g.dmgPalette[((value>>4)&3)+4])
180 | g.Palette[8*4+3] = Color(g.dmgPalette[((value>>6)&3)+4])
181 | g.Renderer.writePalette(8*4+0, g.Palette[8*4+0])
182 | g.Renderer.writePalette(8*4+1, g.Palette[8*4+1])
183 | g.Renderer.writePalette(8*4+2, g.Palette[8*4+2])
184 | g.Renderer.writePalette(8*4+3, g.Palette[8*4+3])
185 | case GB_REG_OBP1:
186 | g.Palette[9*4+0] = Color(g.dmgPalette[(value&3)+8])
187 | g.Palette[9*4+1] = Color(g.dmgPalette[((value>>2)&3)+8])
188 | g.Palette[9*4+2] = Color(g.dmgPalette[((value>>4)&3)+8])
189 | g.Palette[9*4+3] = Color(g.dmgPalette[((value>>6)&3)+8])
190 | g.Renderer.writePalette(9*4+0, g.Palette[9*4+0])
191 | g.Renderer.writePalette(9*4+1, g.Palette[9*4+1])
192 | g.Renderer.writePalette(9*4+2, g.Palette[9*4+2])
193 | g.Renderer.writePalette(9*4+3, g.Palette[9*4+3])
194 | }
195 | } else if g.Renderer.Model&util.GB_MODEL_SGB != 0 {
196 | g.Renderer.WriteVideoRegister(offset&0xff, value)
197 | } else {
198 | switch offset {
199 | // gameboy color
200 | case GB_REG_BCPD:
201 | if g.Mode() != 3 {
202 | if g.BcpIndex&1 == 1 {
203 | // update upper
204 | g.Palette[g.BcpIndex>>1] &= 0x00FF
205 | g.Palette[g.BcpIndex>>1] |= Color(uint16(value) << 8)
206 | } else {
207 | // update lower
208 | g.Palette[g.BcpIndex>>1] &= 0xFF00
209 | g.Palette[g.BcpIndex>>1] |= Color(value)
210 | }
211 | g.Renderer.writePalette(g.BcpIndex>>1, g.Palette[g.BcpIndex>>1])
212 | }
213 | if g.BcpIncrement != 0 {
214 | g.BcpIndex++
215 | g.BcpIndex &= 0x3F
216 | g.io[GB_REG_BCPS] &= 0x80
217 | g.io[GB_REG_BCPS] |= byte(g.BcpIndex)
218 | }
219 | g.io[GB_REG_BCPD] = byte(g.Palette[g.BcpIndex>>1] >> (8 * (g.BcpIndex & 1)))
220 | case GB_REG_OCPD:
221 | if g.Mode() != 3 {
222 | if g.OcpIndex&1 == 1 {
223 | g.Palette[8*4+(g.OcpIndex>>1)] &= 0x00FF
224 | g.Palette[8*4+(g.OcpIndex>>1)] |= Color(uint16(value) << 8)
225 | } else {
226 | g.Palette[8*4+(g.OcpIndex>>1)] &= 0xFF00
227 | g.Palette[8*4+(g.OcpIndex>>1)] |= Color(value)
228 | }
229 | g.Renderer.writePalette(8*4+(g.OcpIndex>>1), g.Palette[8*4+(g.OcpIndex>>1)])
230 | }
231 | if g.OcpIncrement != 0 {
232 | g.OcpIndex++
233 | g.OcpIndex &= 0x3F
234 | g.io[GB_REG_OCPS] &= 0x80
235 | g.io[GB_REG_OCPS] |= byte(g.OcpIndex)
236 | }
237 | g.io[GB_REG_OCPD] = byte(g.Palette[8*4+(g.OcpIndex>>1)] >> (8 * (g.OcpIndex & 1)))
238 | }
239 | }
240 | }
241 |
242 | // GBVideoSwitchBank
243 | func (g *Video) SwitchBank(value byte) {
244 | value &= 1
245 | g.VRAM.Bank = uint16(value)
246 | }
247 |
248 | // GBVideoProcessDots
249 | func (g *Video) ProcessDots(cyclesLate uint64) {
250 | if g.Mode() != 3 {
251 | return
252 | }
253 |
254 | oldX := 0
255 | g.X = HORIZONTAL_PIXELS
256 | g.Renderer.drawRange(oldX, g.X, g.Ly)
257 | }
258 |
259 | // mode0 = HBlank
260 | // 204 cycles
261 | func (g *Video) EndMode0(cyclesLate uint64) {
262 | if g.frameskipCounter <= 0 {
263 | g.Renderer.finishScanline(g.Ly)
264 | }
265 |
266 | lyc := g.io[GB_REG_LYC]
267 | g.Ly++
268 | g.io[GB_REG_LY] = byte(g.Ly)
269 |
270 | oldStat := g.Stat
271 | name, callback, after := scheduler.EndMode2, g.EndMode2, uint64(MODE_2_LENGTH)
272 | if g.Ly < VERTICAL_PIXELS {
273 | g.setMode(2)
274 | } else {
275 | g.setMode(1)
276 | name, callback, after = scheduler.EndMode1, g.EndMode1, HORIZONTAL_LENGTH
277 |
278 | g.scheduler.DescheduleEvent(scheduler.UpdateFrame)
279 | g.scheduler.ScheduleEvent(scheduler.UpdateFrame, g.updateFrameCount, 0)
280 |
281 | if !statIRQAsserted(oldStat) && statIRQAsserted(g.Stat) {
282 | g.io[GB_REG_IF] = util.SetBit8(g.io[GB_REG_IF], 1, true)
283 | }
284 | g.io[GB_REG_IF] = util.SetBit8(g.io[GB_REG_IF], 0, true)
285 | }
286 |
287 | if !statIRQAsserted(oldStat) && statIRQAsserted(g.Stat) {
288 | g.io[GB_REG_IF] = util.SetBit8(g.io[GB_REG_IF], 1, true)
289 | }
290 |
291 | // LYC stat is delayed 1 T-cycle
292 | oldStat = g.Stat
293 | g.Stat = util.SetBit8(g.Stat, 2, lyc == g.io[GB_REG_LY])
294 | if !statIRQAsserted(oldStat) && statIRQAsserted(g.Stat) {
295 | g.io[GB_REG_IF] = util.SetBit8(g.io[GB_REG_IF], 1, true)
296 | }
297 |
298 | g.updateIRQs()
299 | g.scheduler.ScheduleEvent(name, callback, after-cyclesLate)
300 | }
301 |
302 | // mode1 = VBlank
303 | func (g *Video) EndMode1(cyclesLate uint64) {
304 | if !util.Bit(g.LCDC, Enable) {
305 | return
306 | }
307 |
308 | lyc := g.io[GB_REG_LYC]
309 | g.Ly++
310 | switch g.Ly {
311 | case VERTICAL_TOTAL_PIXELS + 1:
312 | g.Ly, g.io[GB_REG_LY] = 0, 0
313 | g.setMode(2)
314 | defer g.scheduler.ScheduleEvent(scheduler.EndMode2, g.EndMode2, MODE_2_LENGTH-cyclesLate)
315 | case VERTICAL_TOTAL_PIXELS:
316 | g.io[GB_REG_LY] = 0
317 | defer g.scheduler.ScheduleEvent(scheduler.EndMode1, g.EndMode1, HORIZONTAL_LENGTH-8-cyclesLate)
318 | case VERTICAL_TOTAL_PIXELS - 1:
319 | g.io[GB_REG_LY] = byte(g.Ly)
320 | defer g.scheduler.ScheduleEvent(scheduler.EndMode1, g.EndMode1, 8-cyclesLate)
321 | default:
322 | g.io[GB_REG_LY] = byte(g.Ly)
323 | defer g.scheduler.ScheduleEvent(scheduler.EndMode1, g.EndMode1, HORIZONTAL_LENGTH-cyclesLate)
324 | }
325 |
326 | oldStat := g.Stat
327 | g.Stat = util.SetBit8(g.Stat, 2, lyc == g.io[GB_REG_LY])
328 | if !statIRQAsserted(oldStat) && statIRQAsserted(g.Stat) {
329 | g.io[GB_REG_IF] = util.SetBit8(g.io[GB_REG_IF], 1, true)
330 | g.updateIRQs()
331 | }
332 | }
333 |
334 | // mode2 = [mode0 -> mode2 -> mode3] -> [mode0 -> mode2 -> mode3] -> ...
335 | // 80 cycles
336 | func (g *Video) EndMode2(cyclesLate uint64) {
337 | oldStat := g.Stat
338 | g.X = -(int(g.io[GB_REG_SCX]) & 7)
339 | g.setMode(3)
340 | g.scheduler.ScheduleEvent(scheduler.EndMode3, g.EndMode3, MODE_3_LENGTH+uint64(g.Renderer.objMax*6)-cyclesLate)
341 | if !statIRQAsserted(oldStat) && statIRQAsserted(g.Stat) {
342 | g.io[GB_REG_IF] = util.SetBit8(g.io[GB_REG_IF], 1, true)
343 | g.updateIRQs()
344 | }
345 | }
346 |
347 | // mode3 = [mode0 -> mode2 -> mode3] -> [mode0 -> mode2 -> mode3] -> ...
348 | // 172 cycles
349 | func (g *Video) EndMode3(cyclesLate uint64) {
350 | oldStat := g.Stat
351 | g.ProcessDots(cyclesLate)
352 | g.hdma()
353 | g.setMode(0)
354 | g.scheduler.ScheduleEvent(scheduler.EndMode0, g.EndMode0, MODE_0_LENGTH-uint64(g.Renderer.objMax*6)-cyclesLate)
355 | if !statIRQAsserted(oldStat) && statIRQAsserted(g.Stat) {
356 | g.io[GB_REG_IF] = util.SetBit8(g.io[GB_REG_IF], 1, true)
357 | g.updateIRQs()
358 | }
359 | }
360 |
361 | // _updateFrameCount
362 | func (g *Video) updateFrameCount(_ uint64) {
363 | if !util.Bit(g.LCDC, Enable) {
364 | g.scheduler.ScheduleEvent(scheduler.UpdateFrame, g.updateFrameCount, TOTAL_LENGTH)
365 | }
366 |
367 | g.frameskipCounter--
368 | if g.frameskipCounter < 0 {
369 | g.Renderer.finishFrame()
370 | g.frameskipCounter = g.frameskip
371 | }
372 | g.FrameCounter++
373 | }
374 |
375 | func (g *Video) Mode() byte {
376 | return g.Stat & 0x3
377 | }
378 |
379 | func (g *Video) setMode(mode byte) {
380 | g.Stat = (g.Stat & 0xfc) | mode
381 | }
382 |
383 | // GBVideoWriteLCDC
384 | func (g *Video) WriteLCDC(old, value byte) {
385 | if !util.Bit(old, Enable) && util.Bit(value, Enable) {
386 | g.scheduler.ScheduleEvent(scheduler.EndMode2, g.EndMode2, MODE_2_LENGTH-5)
387 | g.Ly = 0
388 | g.io[GB_REG_LY] = 0
389 | oldStat := g.Stat
390 | g.setMode(0)
391 | g.Stat = util.SetBit8(g.Stat, 2, byte(g.Ly) == g.io[GB_REG_LYC])
392 | if !statIRQAsserted(oldStat) && statIRQAsserted(g.Stat) {
393 | g.io[GB_REG_IF] = util.SetBit8(g.io[GB_REG_IF], 1, true)
394 | g.updateIRQs()
395 | }
396 | g.Renderer.writePalette(0, g.Palette[0])
397 |
398 | g.scheduler.DescheduleEvent(scheduler.UpdateFrame)
399 | }
400 | if util.Bit(old, Enable) && !util.Bit(value, Enable) {
401 | g.setMode(0)
402 | g.Ly = 0
403 | g.io[GB_REG_LY] = 0
404 | g.Renderer.writePalette(0, Color(g.dmgPalette[0]))
405 |
406 | g.scheduler.DescheduleEvent(scheduler.EndMode0)
407 | g.scheduler.DescheduleEvent(scheduler.UpdateFrame)
408 | g.scheduler.ScheduleEvent(scheduler.UpdateFrame, g.updateFrameCount, TOTAL_LENGTH)
409 | }
410 | }
411 |
412 | // GBVideoWriteSTAT
413 | func (g *Video) WriteSTAT(value byte) {
414 | oldStat := g.Stat
415 | g.Stat = (g.Stat & 0x7) | (value & 0x78)
416 | if !util.Bit(g.LCDC, Enable) || g.Renderer.Model >= util.GB_MODEL_CGB {
417 | return
418 | }
419 | if !statIRQAsserted(oldStat) && g.Mode() < 3 {
420 | g.io[GB_REG_IF] = util.SetBit8(g.io[GB_REG_IF], 1, true)
421 | g.updateIRQs()
422 | }
423 | }
424 |
425 | // GBVideoWriteLYC
426 | func (g *Video) WriteLYC(value byte) {
427 | oldStat := g.Stat
428 | if util.Bit(g.LCDC, Enable) {
429 | g.Stat = util.SetBit8(g.Stat, 2, value == byte(g.Ly))
430 | if !statIRQAsserted(oldStat) && statIRQAsserted(g.Stat) {
431 | g.io[GB_REG_IF] = util.SetBit8(g.io[GB_REG_IF], 1, true)
432 | g.updateIRQs()
433 | }
434 | }
435 | }
436 |
437 | func statIRQAsserted(stat byte) bool {
438 | if util.Bit(stat, 6) && util.Bit(stat, 2) {
439 | return true
440 | }
441 | switch stat & 0x3 {
442 | case 0, 1, 2:
443 | return util.Bit(stat, 3+int(stat&0x3))
444 | }
445 | return false
446 | }
447 |
--------------------------------------------------------------------------------
/pkg/gbc/optable.go:
--------------------------------------------------------------------------------
1 | package gbc
2 |
3 | const (
4 | INS_ADC = iota
5 | INS_AND
6 | INS_ADD
7 | INS_CP
8 | INS_DEC
9 | INS_INC
10 | INS_OR
11 | INS_SBC
12 | INS_SUB
13 | INS_XOR
14 | INS_BIT
15 | INS_RES
16 | INS_SET
17 | INS_SWAP
18 | INS_RL
19 | INS_RLA
20 | INS_RLC
21 | INS_RLCA
22 | INS_RR
23 | INS_RRA
24 | INS_RRC
25 | INS_RRCA
26 | INS_SLA
27 | INS_SRA
28 | INS_SRL
29 | INS_LD
30 | INS_CALL
31 | INS_JP
32 | INS_JR
33 | INS_RET
34 | INS_RETI
35 | INS_RST
36 | INS_POP
37 | INS_PUSH
38 | INS_CCF
39 | INS_CPL
40 | INS_DAA
41 | INS_DI
42 | INS_EI
43 | INS_HALT
44 | INS_NOP
45 | INS_SCF
46 | INS_STOP
47 | INS_PREFIX
48 | INS_NONE
49 | INS_LDH
50 | )
51 |
52 | const (
53 | OP_NONE = iota
54 | OP_BC
55 | OP_d16
56 | OP_BC_PAREN
57 | OP_A
58 | OP_AF
59 | OP_B
60 | OP_d8
61 | OP_a16_PAREN
62 | OP_SP
63 | OP_SP_PLUS_r8
64 | OP_HL
65 | OP_C
66 | OP_C_PAREN
67 | OP_DE
68 | OP_DE_PAREN
69 | OP_D
70 | OP_r8
71 | OP_a8_PAREN
72 | OP_a16
73 | OP_E
74 | OP_NZ
75 | OP_NC
76 | OP_H
77 | OP_L
78 | OP_Z
79 | OP_HLPLUS_PAREN
80 | OP_HLMINUS_PAREN
81 | OP_HL_PAREN
82 | )
83 |
84 | type Inst struct {
85 | id int
86 | Operand1, Operand2 int
87 | Cycle1, Cycle2 int // cond is true(1)/false(2)
88 | Handler func(*GBC, int, int)
89 | }
90 |
91 | var nilOpcode = Inst{id: INS_NONE}
92 |
93 | var gbz80insts [256]Inst = [256]Inst{
94 | /* 0x0x */ {INS_NOP, 0, 0, 1, 1, nop}, {INS_LD, BC, 0, 3, 3, ld16i}, {INS_LD, BC, A, 2, 2, ldm16r}, {INS_INC, BC, 0, 2, 2, inc16}, {INS_INC, B, 0, 1, 1, inc8}, {INS_DEC, B, 0, 1, 1, dec8}, {INS_LD, B, 0, 2, 2, ld8i}, {INS_RLCA, 0, 0, 1, 1, rlca}, {INS_LD, OP_a16_PAREN, OP_SP, 5, 5, op0x08}, {INS_ADD, HL, BC, 2, 2, addHL}, {INS_LD, A, BC, 2, 2, ld8m}, {INS_DEC, BC, 0, 2, 2, dec16}, {INS_INC, C, 0, 1, 1, inc8}, {INS_DEC, C, 0, 1, 1, dec8}, {INS_LD, C, 0, 2, 2, ld8i}, {INS_RRCA, 0, 0, 1, 1, rrca},
95 | /* 0x1x */ {INS_STOP, 0, 0, 1, 1, stop}, {INS_LD, DE, 0, 3, 3, ld16i}, {INS_LD, DE, A, 2, 2, ldm16r}, {INS_INC, DE, 0, 2, 2, inc16}, {INS_INC, D, 0, 1, 1, inc8}, {INS_DEC, D, 0, 1, 1, dec8}, {INS_LD, D, 0, 2, 2, ld8i}, {INS_RLA, 0, 0, 1, 1, rla}, {INS_JR, 0, 0, 0, 0, jr}, {INS_ADD, HL, DE, 2, 2, addHL}, {INS_LD, A, DE, 2, 2, ld8m}, {INS_DEC, DE, 0, 2, 2, dec16}, {INS_INC, E, 0, 1, 1, inc8}, {INS_DEC, E, 0, 1, 1, dec8}, {INS_LD, E, 0, 2, 2, ld8i}, {INS_RRA, 0, 0, 1, 1, rra},
96 | /* 0x2x */ {INS_JR, flagZ, 0, 0, 0, jrncc}, {INS_LD, HL, 0, 3, 3, ld16i}, {INS_LD, HLI, A, 2, 2, ldm16r}, {INS_INC, HL, 0, 2, 2, inc16}, {INS_INC, H, 0, 1, 1, inc8}, {INS_DEC, H, 0, 1, 1, dec8}, {INS_LD, H, 0, 2, 2, ld8i}, {INS_DAA, 0, 0, 1, 1, daa}, {INS_JR, flagZ, 0, 0, 0, jrcc}, {INS_ADD, HL, HL, 2, 2, addHL}, {INS_LD, A, HLI, 2, 2, ld8m}, {INS_DEC, HL, 0, 2, 2, dec16}, {INS_INC, L, 0, 1, 1, inc8}, {INS_DEC, L, 0, 1, 1, dec8}, {INS_LD, L, 0, 2, 2, ld8i}, {INS_CPL, 0, 0, 1, 1, cpl},
97 | /* 0x3x */ {INS_JR, flagC, 0, 0, 0, jrncc}, {INS_LD, SP, 0, 3, 3, ld16i}, {INS_LD, HLD, A, 2, 2, ldm16r}, {INS_INC, SP, 0, 2, 2, inc16}, {INS_INC, 0, 0, 2, 2, incHL}, {INS_DEC, 0, 0, 2, 2, decHL}, {INS_LD, OP_HL_PAREN, OP_d8, 2, 2, op0x36}, {INS_SCF, 0, 0, 1, 1, scf}, {INS_JR, flagC, 0, 0, 0, jrcc}, {INS_ADD, HL, SP, 2, 2, addHL}, {INS_LD, A, HLD, 2, 2, ld8m}, {INS_DEC, SP, 0, 2, 2, dec16}, {INS_INC, A, 0, 1, 1, inc8}, {INS_DEC, A, 0, 1, 1, dec8}, {INS_LD, A, 0, 2, 2, ld8i}, {INS_CCF, 0, 0, 1, 1, ccf},
98 | /* 0x4x */ {INS_LD, B, B, 1, 1, ld8r}, {INS_LD, B, C, 1, 1, ld8r}, {INS_LD, B, D, 1, 1, ld8r}, {INS_LD, B, E, 1, 1, ld8r}, {INS_LD, B, H, 1, 1, ld8r}, {INS_LD, B, L, 1, 1, ld8r}, {INS_LD, B, HL, 2, 2, ld8m}, {INS_LD, B, A, 1, 1, ld8r}, {INS_LD, C, B, 1, 1, ld8r}, {INS_LD, C, C, 1, 1, ld8r}, {INS_LD, C, D, 1, 1, ld8r}, {INS_LD, C, E, 1, 1, ld8r}, {INS_LD, C, H, 1, 1, ld8r}, {INS_LD, C, L, 1, 1, ld8r}, {INS_LD, C, HL, 2, 2, ld8m}, {INS_LD, C, A, 1, 1, ld8r},
99 | /* 0x5x */ {INS_LD, D, B, 1, 1, ld8r}, {INS_LD, D, C, 1, 1, ld8r}, {INS_LD, D, D, 1, 1, ld8r}, {INS_LD, D, E, 1, 1, ld8r}, {INS_LD, D, H, 1, 1, ld8r}, {INS_LD, D, L, 1, 1, ld8r}, {INS_LD, D, HL, 2, 2, ld8m}, {INS_LD, D, A, 1, 1, ld8r}, {INS_LD, E, B, 1, 1, ld8r}, {INS_LD, E, C, 1, 1, ld8r}, {INS_LD, E, D, 1, 1, ld8r}, {INS_LD, E, E, 1, 1, ld8r}, {INS_LD, E, H, 1, 1, ld8r}, {INS_LD, E, L, 1, 1, ld8r}, {INS_LD, E, HL, 2, 2, ld8m}, {INS_LD, E, A, 1, 1, ld8r},
100 | /* 0x6x */ {INS_LD, H, B, 1, 1, ld8r}, {INS_LD, H, C, 1, 1, ld8r}, {INS_LD, H, D, 1, 1, ld8r}, {INS_LD, H, E, 1, 1, ld8r}, {INS_LD, H, H, 1, 1, ld8r}, {INS_LD, H, L, 1, 1, ld8r}, {INS_LD, H, HL, 2, 2, ld8m}, {INS_LD, H, A, 1, 1, ld8r}, {INS_LD, L, B, 1, 1, ld8r}, {INS_LD, L, C, 1, 1, ld8r}, {INS_LD, L, D, 1, 1, ld8r}, {INS_LD, L, E, 1, 1, ld8r}, {INS_LD, L, H, 1, 1, ld8r}, {INS_LD, L, L, 1, 1, ld8r}, {INS_LD, L, HL, 2, 2, ld8m}, {INS_LD, L, A, 1, 1, ld8r},
101 | /* 0x7x */ {INS_LD, 0, B, 2, 2, ldHLR8}, {INS_LD, 0, C, 2, 2, ldHLR8}, {INS_LD, 0, D, 2, 2, ldHLR8}, {INS_LD, 0, E, 2, 2, ldHLR8}, {INS_LD, 0, H, 2, 2, ldHLR8}, {INS_LD, 0, L, 2, 2, ldHLR8}, {INS_HALT, 0, 0, 1, 1, halt}, {INS_LD, 0, A, 2, 2, ldHLR8}, {INS_LD, A, B, 1, 1, ld8r}, {INS_LD, A, C, 1, 1, ld8r}, {INS_LD, A, D, 1, 1, ld8r}, {INS_LD, A, E, 1, 1, ld8r}, {INS_LD, A, H, 1, 1, ld8r}, {INS_LD, A, L, 1, 1, ld8r}, {INS_LD, A, HL, 2, 2, ld8m}, {INS_LD, A, A, 1, 1, ld8r},
102 | /* 0x8x */ {INS_ADD, 0, B, 1, 1, add8}, {INS_ADD, 0, C, 1, 1, add8}, {INS_ADD, 0, D, 1, 1, add8}, {INS_ADD, 0, E, 1, 1, add8}, {INS_ADD, 0, H, 1, 1, add8}, {INS_ADD, 0, L, 1, 1, add8}, {INS_ADD, OP_A, OP_HL_PAREN, 2, 2, addaHL}, {INS_ADD, 0, A, 1, 1, add8}, {INS_ADC, 0, B, 1, 1, adc8}, {INS_ADC, 0, C, 1, 1, adc8}, {INS_ADC, 0, D, 1, 1, adc8}, {INS_ADC, 0, E, 1, 1, adc8}, {INS_ADC, 0, H, 1, 1, adc8}, {INS_ADC, 0, L, 1, 1, adc8}, {INS_ADC, 0, 0, 2, 2, adcaHL}, {INS_ADC, 0, A, 1, 1, adc8},
103 | /* 0x9x */ {INS_SUB, 0, B, 1, 1, sub8}, {INS_SUB, 0, C, 1, 1, sub8}, {INS_SUB, 0, D, 1, 1, sub8}, {INS_SUB, 0, E, 1, 1, sub8}, {INS_SUB, 0, H, 1, 1, sub8}, {INS_SUB, 0, L, 1, 1, sub8}, {INS_SUB, 0, 0, 2, 2, subaHL}, {INS_SUB, 0, A, 1, 1, sub8}, {INS_SBC, 0, B, 1, 1, sbc8}, {INS_SBC, 0, C, 1, 1, sbc8}, {INS_SBC, 0, D, 1, 1, sbc8}, {INS_SBC, 0, E, 1, 1, sbc8}, {INS_SBC, 0, H, 1, 1, sbc8}, {INS_SBC, 0, L, 1, 1, sbc8}, {INS_SBC, 0, 0, 2, 2, sbcaHL}, {INS_SBC, 0, A, 1, 1, sbc8},
104 | /* 0xax */ {INS_AND, A, B, 1, 1, and8}, {INS_AND, A, C, 1, 1, and8}, {INS_AND, A, D, 1, 1, and8}, {INS_AND, A, E, 1, 1, and8}, {INS_AND, A, H, 1, 1, and8}, {INS_AND, A, L, 1, 1, and8}, {INS_AND, OP_HL_PAREN, OP_NONE, 2, 2, andaHL}, {INS_AND, A, A, 1, 1, and8}, {INS_XOR, 0, B, 1, 1, xor8}, {INS_XOR, 0, C, 1, 1, xor8}, {INS_XOR, 0, D, 1, 1, xor8}, {INS_XOR, 0, E, 1, 1, xor8}, {INS_XOR, 0, H, 1, 1, xor8}, {INS_XOR, 0, L, 1, 1, xor8}, {INS_XOR, OP_HL_PAREN, OP_NONE, 2, 2, xoraHL}, {INS_XOR, 0, A, 1, 1, xor8},
105 | /* 0xbx */ {INS_OR, A, B, 1, 1, or8}, {INS_OR, A, C, 1, 1, or8}, {INS_OR, A, D, 1, 1, or8}, {INS_OR, A, E, 1, 1, or8}, {INS_OR, A, H, 1, 1, or8}, {INS_OR, A, L, 1, 1, or8}, {INS_OR, OP_HL_PAREN, OP_NONE, 2, 2, oraHL}, {INS_OR, A, A, 1, 1, or8}, {INS_CP, 0, B, 1, 1, cp}, {INS_CP, 0, C, 1, 1, cp}, {INS_CP, 0, D, 1, 1, cp}, {INS_CP, 0, E, 1, 1, cp}, {INS_CP, 0, H, 1, 1, cp}, {INS_CP, 0, L, 1, 1, cp}, {INS_CP, OP_HL_PAREN, OP_NONE, 2, 2, cpaHL}, {INS_CP, 0, A, 1, 1, cp},
106 | /* 0xcx */ {INS_RET, flagZ, 0, 2, 2, retncc}, {INS_POP, C, B, 2, 2, pop}, {INS_JP, flagZ, 0, 1, 1, jpncc}, {INS_JP, 0, 0, 2, 2, jp}, {INS_CALL, flagZ, 0, 0, 0, callncc}, {INS_PUSH, B, C, 2, 2, push}, {INS_ADD, OP_A, OP_d8, 2, 2, addu8}, {INS_RST, 0x00, 0, 4, 4, rst}, {INS_RET, flagZ, 0, 2, 2, retcc}, {INS_RET, 0, 0, 4, 4, ret}, {INS_JP, flagZ, 0, 1, 1, jpcc}, {INS_PREFIX, 0, 0, 0, 0, prefixCB}, {INS_CALL, flagZ, 0, 0, 0, callcc}, {INS_CALL, 0, 0, 0, 0, call}, {INS_ADC, 0, 0, 2, 2, adcu8}, {INS_RST, 0x08, 0, 4, 4, rst},
107 | /* 0xdx */ {INS_RET, flagC, 0, 2, 2, retncc}, {INS_POP, E, D, 2, 2, pop}, {INS_JP, flagC, 0, 1, 1, jpncc}, nilOpcode, {INS_CALL, flagC, 0, 0, 0, callncc}, {INS_PUSH, D, E, 2, 2, push}, {INS_SUB, 0, 0, 2, 2, subu8}, {INS_RST, 0x10, 0, 4, 4, rst}, {INS_RET, flagC, 0, 2, 2, retcc}, {INS_RETI, 0, 0, 4, 4, reti}, {INS_JP, flagC, 0, 1, 1, jpcc}, nilOpcode, {INS_CALL, flagC, 0, 0, 0, callcc}, nilOpcode, {INS_SBC, 0, 0, 2, 2, sbcu8}, {INS_RST, 0x18, 0, 4, 4, rst},
108 | /* 0xex */ {INS_LDH, OP_a8_PAREN, OP_A, 2, 2, op0xe0}, {INS_POP, L, H, 2, 2, pop}, {INS_LD, OP_C_PAREN, OP_A, 2, 2, op0xe2}, nilOpcode, nilOpcode, {INS_PUSH, H, L, 2, 2, push}, {INS_AND, OP_d8, OP_NONE, 2, 2, andu8}, {INS_RST, 0x20, 0, 4, 4, rst}, {INS_ADD, 0, 0, 4, 4, addSPi8}, {INS_JP, 0, 0, 1, 1, jpHL}, {INS_LD, OP_a16_PAREN, OP_A, 2, 2, op0xea}, nilOpcode, nilOpcode, nilOpcode, {INS_XOR, OP_d8, OP_NONE, 2, 2, xoru8}, {INS_RST, 0x28, 0, 4, 4, rst},
109 | /* 0xfx */ {INS_LDH, OP_A, OP_a8_PAREN, 2, 2, op0xf0}, {INS_POP, 0, 0, 2, 2, popAF}, {INS_LD, OP_A, OP_C_PAREN, 2, 2, op0xf2}, {INS_DI, 0, 0, 1, 1, di}, nilOpcode, {INS_PUSH, A, F, 2, 2, pushAF}, {INS_OR, OP_d8, OP_NONE, 2, 2, oru8}, {INS_RST, 0x30, 0, 4, 4, rst}, {INS_LD, OP_HL, OP_SP_PLUS_r8, 3, 3, op0xf8}, {INS_LD, OP_SP, OP_HL, 2, 2, op0xf9}, {INS_LD, OP_A, OP_a16_PAREN, 2, 2, ldau16}, {INS_EI, 0, 0, 1, 1, ei}, nilOpcode, nilOpcode, {INS_CP, OP_d8, OP_NONE, 2, 2, cpu8}, {INS_RST, 0x38, 0, 4, 4, rst},
110 | }
111 |
112 | var gbz80instsCb [256]Inst = [256]Inst{
113 | /* 0x0x */ {INS_RLC, B, 0, 2, 2, rlc}, {INS_RLC, C, 0, 2, 2, rlc}, {INS_RLC, D, 0, 2, 2, rlc}, {INS_RLC, E, 0, 2, 2, rlc}, {INS_RLC, H, 0, 2, 2, rlc}, {INS_RLC, L, 0, 2, 2, rlc}, {INS_RLC, 0, 0, 3, 3, rlcHL}, {INS_RLC, A, 0, 2, 2, rlc}, {INS_RRC, B, 0, 2, 2, rrc}, {INS_RRC, C, 0, 2, 2, rrc}, {INS_RRC, D, 0, 2, 2, rrc}, {INS_RRC, E, 0, 2, 2, rrc}, {INS_RRC, H, 0, 2, 2, rrc}, {INS_RRC, L, 0, 2, 2, rrc}, {INS_RRC, 0, 0, 3, 3, rrcHL}, {INS_RRC, A, 0, 2, 2, rrc},
114 | /* 0x1x */ {INS_RL, 0, B, 2, 2, rl}, {INS_RL, 0, C, 2, 2, rl}, {INS_RL, 0, D, 2, 2, rl}, {INS_RL, 0, E, 2, 2, rl}, {INS_RL, 0, H, 2, 2, rl}, {INS_RL, 0, L, 2, 2, rl}, {INS_RL, 0, 0, 3, 3, rlHL}, {INS_RL, 0, A, 2, 2, rl}, {INS_RR, B, 0, 2, 2, rr}, {INS_RR, C, 0, 2, 2, rr}, {INS_RR, D, 0, 2, 2, rr}, {INS_RR, E, 0, 2, 2, rr}, {INS_RR, H, 0, 2, 2, rr}, {INS_RR, L, 0, 2, 2, rr}, {INS_RR, 0, 0, 3, 3, rrHL}, {INS_RR, A, 0, 2, 2, rr},
115 | /* 0x2x */ {INS_SLA, B, 0, 2, 2, sla}, {INS_SLA, C, 0, 2, 2, sla}, {INS_SLA, D, 0, 2, 2, sla}, {INS_SLA, E, 0, 2, 2, sla}, {INS_SLA, H, 0, 2, 2, sla}, {INS_SLA, L, 0, 2, 2, sla}, {INS_SLA, 0, 0, 3, 3, slaHL}, {INS_SLA, A, 0, 2, 2, sla}, {INS_SRA, B, 0, 2, 2, sra}, {INS_SRA, C, 0, 2, 2, sra}, {INS_SRA, D, 0, 2, 2, sra}, {INS_SRA, E, 0, 2, 2, sra}, {INS_SRA, H, 0, 2, 2, sra}, {INS_SRA, L, 0, 2, 2, sra}, {INS_SRA, 0, 0, 3, 3, sraHL}, {INS_SRA, A, 0, 2, 2, sra},
116 | /* 0x3x */ {INS_SWAP, 0, B, 2, 2, swap}, {INS_SWAP, 0, C, 2, 2, swap}, {INS_SWAP, 0, D, 2, 2, swap}, {INS_SWAP, 0, E, 2, 2, swap}, {INS_SWAP, 0, H, 2, 2, swap}, {INS_SWAP, 0, L, 2, 2, swap}, {INS_SWAP, 0, 0, 3, 3, swapHL}, {INS_SWAP, 0, A, 2, 2, swap}, {INS_SRL, B, 0, 2, 2, srl}, {INS_SRL, C, 0, 2, 2, srl}, {INS_SRL, D, 0, 2, 2, srl}, {INS_SRL, E, 0, 2, 2, srl}, {INS_SRL, H, 0, 2, 2, srl}, {INS_SRL, L, 0, 2, 2, srl}, {INS_SRL, 0, 0, 3, 3, srlHL}, {INS_SRL, A, 0, 2, 2, srl},
117 |
118 | /* 0x4x */ {INS_BIT, 0, B, 2, 2, bit}, {INS_BIT, 0, C, 2, 2, bit}, {INS_BIT, 0, D, 2, 2, bit}, {INS_BIT, 0, E, 2, 2, bit}, {INS_BIT, 0, H, 2, 2, bit}, {INS_BIT, 0, L, 2, 2, bit}, {INS_BIT, 0, 0, 3, 3, bitHL}, {INS_BIT, 0, A, 2, 2, bit}, {INS_BIT, 1, B, 2, 2, bit}, {INS_BIT, 1, C, 2, 2, bit}, {INS_BIT, 1, D, 2, 2, bit}, {INS_BIT, 1, E, 2, 2, bit}, {INS_BIT, 1, H, 2, 2, bit}, {INS_BIT, 1, L, 2, 2, bit}, {INS_BIT, 1, 0, 3, 3, bitHL}, {INS_BIT, 1, A, 2, 2, bit},
119 | /* 0x5x */ {INS_BIT, 2, B, 2, 2, bit}, {INS_BIT, 2, C, 2, 2, bit}, {INS_BIT, 2, D, 2, 2, bit}, {INS_BIT, 2, E, 2, 2, bit}, {INS_BIT, 2, H, 2, 2, bit}, {INS_BIT, 2, L, 2, 2, bit}, {INS_BIT, 2, 0, 3, 3, bitHL}, {INS_BIT, 2, A, 2, 2, bit}, {INS_BIT, 3, B, 2, 2, bit}, {INS_BIT, 3, C, 2, 2, bit}, {INS_BIT, 3, D, 2, 2, bit}, {INS_BIT, 3, E, 2, 2, bit}, {INS_BIT, 3, H, 2, 2, bit}, {INS_BIT, 3, L, 2, 2, bit}, {INS_BIT, 3, 0, 3, 3, bitHL}, {INS_BIT, 3, A, 2, 2, bit},
120 | /* 0x6x */ {INS_BIT, 4, B, 2, 2, bit}, {INS_BIT, 4, C, 2, 2, bit}, {INS_BIT, 4, D, 2, 2, bit}, {INS_BIT, 4, E, 2, 2, bit}, {INS_BIT, 4, H, 2, 2, bit}, {INS_BIT, 4, L, 2, 2, bit}, {INS_BIT, 4, 0, 3, 3, bitHL}, {INS_BIT, 4, A, 2, 2, bit}, {INS_BIT, 5, B, 2, 2, bit}, {INS_BIT, 5, C, 2, 2, bit}, {INS_BIT, 5, D, 2, 2, bit}, {INS_BIT, 5, E, 2, 2, bit}, {INS_BIT, 5, H, 2, 2, bit}, {INS_BIT, 5, L, 2, 2, bit}, {INS_BIT, 5, 0, 3, 3, bitHL}, {INS_BIT, 5, A, 2, 2, bit},
121 | /* 0x7x */ {INS_BIT, 6, B, 2, 2, bit}, {INS_BIT, 6, C, 2, 2, bit}, {INS_BIT, 6, D, 2, 2, bit}, {INS_BIT, 6, E, 2, 2, bit}, {INS_BIT, 6, H, 2, 2, bit}, {INS_BIT, 6, L, 2, 2, bit}, {INS_BIT, 6, 0, 3, 3, bitHL}, {INS_BIT, 6, A, 2, 2, bit}, {INS_BIT, 7, B, 2, 2, bit}, {INS_BIT, 7, C, 2, 2, bit}, {INS_BIT, 7, D, 2, 2, bit}, {INS_BIT, 7, E, 2, 2, bit}, {INS_BIT, 7, H, 2, 2, bit}, {INS_BIT, 7, L, 2, 2, bit}, {INS_BIT, 7, 0, 3, 3, bitHL}, {INS_BIT, 7, A, 2, 2, bit},
122 |
123 | /* 0x8x */ {INS_RES, 0, B, 2, 2, res}, {INS_RES, 0, C, 2, 2, res}, {INS_RES, 0, D, 2, 2, res}, {INS_RES, 0, E, 2, 2, res}, {INS_RES, 0, H, 2, 2, res}, {INS_RES, 0, L, 2, 2, res}, {INS_RES, 0, 0, 3, 3, resHL}, {INS_RES, 0, A, 2, 2, res}, {INS_RES, 1, B, 2, 2, res}, {INS_RES, 1, C, 2, 2, res}, {INS_RES, 1, D, 2, 2, res}, {INS_RES, 1, E, 2, 2, res}, {INS_RES, 1, H, 2, 2, res}, {INS_RES, 1, L, 2, 2, res}, {INS_RES, 1, 0, 3, 3, resHL}, {INS_RES, 1, A, 2, 2, res},
124 | /* 0x9x */ {INS_RES, 2, B, 2, 2, res}, {INS_RES, 2, C, 2, 2, res}, {INS_RES, 2, D, 2, 2, res}, {INS_RES, 2, E, 2, 2, res}, {INS_RES, 2, H, 2, 2, res}, {INS_RES, 2, L, 2, 2, res}, {INS_RES, 2, 0, 3, 3, resHL}, {INS_RES, 2, A, 2, 2, res}, {INS_RES, 3, B, 2, 2, res}, {INS_RES, 3, C, 2, 2, res}, {INS_RES, 3, D, 2, 2, res}, {INS_RES, 3, E, 2, 2, res}, {INS_RES, 3, H, 2, 2, res}, {INS_RES, 3, L, 2, 2, res}, {INS_RES, 3, 0, 3, 3, resHL}, {INS_RES, 3, A, 2, 2, res},
125 | /* 0xax */ {INS_RES, 4, B, 2, 2, res}, {INS_RES, 4, C, 2, 2, res}, {INS_RES, 4, D, 2, 2, res}, {INS_RES, 4, E, 2, 2, res}, {INS_RES, 4, H, 2, 2, res}, {INS_RES, 4, L, 2, 2, res}, {INS_RES, 4, 0, 3, 3, resHL}, {INS_RES, 4, A, 2, 2, res}, {INS_RES, 5, B, 2, 2, res}, {INS_RES, 5, C, 2, 2, res}, {INS_RES, 5, D, 2, 2, res}, {INS_RES, 5, E, 2, 2, res}, {INS_RES, 5, H, 2, 2, res}, {INS_RES, 5, L, 2, 2, res}, {INS_RES, 5, 0, 3, 3, resHL}, {INS_RES, 5, A, 2, 2, res},
126 | /* 0xbx */ {INS_RES, 6, B, 2, 2, res}, {INS_RES, 6, C, 2, 2, res}, {INS_RES, 6, D, 2, 2, res}, {INS_RES, 6, E, 2, 2, res}, {INS_RES, 6, H, 2, 2, res}, {INS_RES, 6, L, 2, 2, res}, {INS_RES, 6, 0, 3, 3, resHL}, {INS_RES, 6, A, 2, 2, res}, {INS_RES, 7, B, 2, 2, res}, {INS_RES, 7, C, 2, 2, res}, {INS_RES, 7, D, 2, 2, res}, {INS_RES, 7, E, 2, 2, res}, {INS_RES, 7, H, 2, 2, res}, {INS_RES, 7, L, 2, 2, res}, {INS_RES, 7, 0, 3, 3, resHL}, {INS_RES, 7, A, 2, 2, res},
127 |
128 | /* 0xcx */ {INS_SET, 0, B, 2, 2, set}, {INS_SET, 0, C, 2, 2, set}, {INS_SET, 0, D, 2, 2, set}, {INS_SET, 0, E, 2, 2, set}, {INS_SET, 0, H, 2, 2, set}, {INS_SET, 0, L, 2, 2, set}, {INS_SET, 0, 0, 3, 3, setHL}, {INS_SET, 0, A, 2, 2, set}, {INS_SET, 1, B, 2, 2, set}, {INS_SET, 1, C, 2, 2, set}, {INS_SET, 1, D, 2, 2, set}, {INS_SET, 1, E, 2, 2, set}, {INS_SET, 1, H, 2, 2, set}, {INS_SET, 1, L, 2, 2, set}, {INS_SET, 1, 0, 3, 3, setHL}, {INS_SET, 1, A, 2, 2, set},
129 | /* 0xdx */ {INS_SET, 2, B, 2, 2, set}, {INS_SET, 2, C, 2, 2, set}, {INS_SET, 2, D, 2, 2, set}, {INS_SET, 2, E, 2, 2, set}, {INS_SET, 2, H, 2, 2, set}, {INS_SET, 2, L, 2, 2, set}, {INS_SET, 2, 0, 3, 3, setHL}, {INS_SET, 2, A, 2, 2, set}, {INS_SET, 3, B, 2, 2, set}, {INS_SET, 3, C, 2, 2, set}, {INS_SET, 3, D, 2, 2, set}, {INS_SET, 3, E, 2, 2, set}, {INS_SET, 3, H, 2, 2, set}, {INS_SET, 3, L, 2, 2, set}, {INS_SET, 3, 0, 3, 3, setHL}, {INS_SET, 3, A, 2, 2, set},
130 | /* 0xex */ {INS_SET, 4, B, 2, 2, set}, {INS_SET, 4, C, 2, 2, set}, {INS_SET, 4, D, 2, 2, set}, {INS_SET, 4, E, 2, 2, set}, {INS_SET, 4, H, 2, 2, set}, {INS_SET, 4, L, 2, 2, set}, {INS_SET, 4, 0, 3, 3, setHL}, {INS_SET, 4, A, 2, 2, set}, {INS_SET, 5, B, 2, 2, set}, {INS_SET, 5, C, 2, 2, set}, {INS_SET, 5, D, 2, 2, set}, {INS_SET, 5, E, 2, 2, set}, {INS_SET, 5, H, 2, 2, set}, {INS_SET, 5, L, 2, 2, set}, {INS_SET, 5, 0, 3, 3, setHL}, {INS_SET, 5, A, 2, 2, set},
131 | /* 0xfx */ {INS_SET, 6, B, 2, 2, set}, {INS_SET, 6, C, 2, 2, set}, {INS_SET, 6, D, 2, 2, set}, {INS_SET, 6, E, 2, 2, set}, {INS_SET, 6, H, 2, 2, set}, {INS_SET, 6, L, 2, 2, set}, {INS_SET, 6, 0, 3, 3, setHL}, {INS_SET, 6, A, 2, 2, set}, {INS_SET, 7, B, 2, 2, set}, {INS_SET, 7, C, 2, 2, set}, {INS_SET, 7, D, 2, 2, set}, {INS_SET, 7, E, 2, 2, set}, {INS_SET, 7, H, 2, 2, set}, {INS_SET, 7, L, 2, 2, set}, {INS_SET, 7, 0, 3, 3, setHL}, {INS_SET, 7, A, 2, 2, set},
132 | }
133 |
--------------------------------------------------------------------------------
/pkg/gbc/video/renderer.go:
--------------------------------------------------------------------------------
1 | package video
2 |
3 | import (
4 | "github.com/pokemium/worldwide/pkg/util"
5 | )
6 |
7 | // GBVideoRenderer & GBVideoSoftwareRenderer
8 | type Renderer struct {
9 | // GBVideoRenderer
10 | g *Video
11 | disableBG, disableOBJ, disableWIN bool
12 | highlightBG bool
13 | highlightOBJ [MAX_OBJ]bool
14 | highlightWIN bool
15 | highlightColor uint16
16 | highlightAmount byte
17 |
18 | // GBVideoSoftwareRenderer
19 | // Renderer.row[i] -> Renderer.Lookup[i] -> Renderer.palette[i] -> outputBuffer
20 | outputBuffer [256 * 256]Color
21 | outputBufferStride int
22 |
23 | // each element represents palette color
24 | // x = 0 or 1 or 2 or 3
25 | // x -> BGP or BGP0
26 | // 1*4 + x -> BGP1
27 | // 2*4 + x -> BGP2
28 | // ...
29 | // 7*4 + x -> BGP7
30 | // 8*4 + x -> OBP0
31 | // 9*4 + x -> OBP1
32 | row [HORIZONTAL_PIXELS + 8]uint16
33 |
34 | Palette [64 * 3]Color
35 |
36 | // palette color(Renderer.row element) -> Renderer.palette index
37 | Lookup [64 * 3]byte
38 |
39 | wy, wx, currentWy, currentWx byte
40 | lastY, lastX int
41 | hasWindow bool
42 |
43 | lastHighlightAmount byte
44 | Model util.GBModel
45 | obj [MAX_LINE_OBJ]Sprite
46 | objMax int
47 |
48 | sgbBorders bool
49 | sgbRenderMode int
50 | sgbAttributes []byte
51 | }
52 |
53 | func NewRenderer(g *Video) *Renderer {
54 | r := &Renderer{
55 | g: g,
56 | highlightColor: 0x7fff,
57 | outputBufferStride: 160,
58 | lastY: VERTICAL_PIXELS,
59 | }
60 |
61 | for i := byte(0); i < 192; i++ {
62 | r.Lookup[i] = i
63 | }
64 |
65 | return r
66 | }
67 |
68 | // GBVideoSoftwareRendererUpdateWindow
69 | func (r *Renderer) updateWindow(before, after bool, oldWy byte) {
70 | if r.lastY >= VERTICAL_PIXELS || !(after || before) {
71 | return
72 | }
73 | if !r.hasWindow && r.lastX == HORIZONTAL_PIXELS {
74 | return
75 | }
76 | if r.lastY >= int(oldWy) {
77 | if !after {
78 | r.currentWy = byte(int(r.currentWy) - r.lastY)
79 | r.hasWindow = true
80 | } else if !before {
81 | if !r.hasWindow {
82 | r.currentWy = byte(r.lastY - int(r.wy))
83 | if r.lastY >= int(r.wy) && r.lastX > int(r.wx) {
84 | r.currentWy++
85 | }
86 | } else {
87 | r.currentWy += byte(r.lastY)
88 | }
89 | } else if r.wy != oldWy {
90 | r.currentWy += oldWy - r.wy
91 | r.hasWindow = true
92 | }
93 | }
94 | }
95 |
96 | // writeVideoRegister / GBVideoSoftwareRendererWriteVideoRegister
97 | // this is called from GBIOWrite/GBVideoWritePalette/etc...
98 | func (r *Renderer) WriteVideoRegister(offset byte, value byte) byte {
99 | wasWindow := r.inWindow()
100 | wy := r.wy
101 |
102 | switch offset {
103 | case GB_REG_LCDC:
104 | r.g.LCDC = value
105 | r.updateWindow(wasWindow, r.inWindow(), wy)
106 | case GB_REG_WY:
107 | r.wy = value
108 | r.updateWindow(wasWindow, r.inWindow(), wy)
109 | case GB_REG_WX:
110 | r.wx = value
111 | r.updateWindow(wasWindow, r.inWindow(), wy)
112 | case GB_REG_BGP:
113 | r.Lookup[0] = value & 3
114 | r.Lookup[1] = (value >> 2) & 3
115 | r.Lookup[2] = (value >> 4) & 3
116 | r.Lookup[3] = (value >> 6) & 3
117 | r.Lookup[PAL_HIGHLIGHT_BG+0] = PAL_HIGHLIGHT + (value & 3)
118 | r.Lookup[PAL_HIGHLIGHT_BG+1] = PAL_HIGHLIGHT + ((value >> 2) & 3)
119 | r.Lookup[PAL_HIGHLIGHT_BG+2] = PAL_HIGHLIGHT + ((value >> 4) & 3)
120 | r.Lookup[PAL_HIGHLIGHT_BG+3] = PAL_HIGHLIGHT + ((value >> 6) & 3)
121 | case GB_REG_OBP0:
122 | r.Lookup[PAL_OBJ+0] = value & 3
123 | r.Lookup[PAL_OBJ+1] = (value >> 2) & 3
124 | r.Lookup[PAL_OBJ+2] = (value >> 4) & 3
125 | r.Lookup[PAL_OBJ+3] = (value >> 6) & 3
126 | r.Lookup[PAL_HIGHLIGHT_OBJ+0] = PAL_HIGHLIGHT + (value & 3)
127 | r.Lookup[PAL_HIGHLIGHT_OBJ+1] = PAL_HIGHLIGHT + ((value >> 2) & 3)
128 | r.Lookup[PAL_HIGHLIGHT_OBJ+2] = PAL_HIGHLIGHT + ((value >> 4) & 3)
129 | r.Lookup[PAL_HIGHLIGHT_OBJ+3] = PAL_HIGHLIGHT + ((value >> 6) & 3)
130 | case GB_REG_OBP1:
131 | r.Lookup[PAL_OBJ+4] = value & 3
132 | r.Lookup[PAL_OBJ+5] = (value >> 2) & 3
133 | r.Lookup[PAL_OBJ+6] = (value >> 4) & 3
134 | r.Lookup[PAL_OBJ+7] = (value >> 6) & 3
135 | r.Lookup[PAL_HIGHLIGHT_OBJ+4] = PAL_HIGHLIGHT + (value & 3)
136 | r.Lookup[PAL_HIGHLIGHT_OBJ+5] = PAL_HIGHLIGHT + ((value >> 2) & 3)
137 | r.Lookup[PAL_HIGHLIGHT_OBJ+6] = PAL_HIGHLIGHT + ((value >> 4) & 3)
138 | r.Lookup[PAL_HIGHLIGHT_OBJ+7] = PAL_HIGHLIGHT + ((value >> 6) & 3)
139 | }
140 |
141 | return value
142 | }
143 |
144 | // writePalette / GBVideoSoftwareRendererWritePalette
145 | // GBVideoWritePalette calls this
146 | func (r *Renderer) writePalette(index int, value Color) {
147 | color := value
148 | if r.Model&util.GB_MODEL_SGB != 0 {
149 | if index < 0x10 && index != 0 && (index&3) == 0 {
150 | color = r.Palette[0]
151 | } else if index >= PAL_SGB_BORDER && (index&0xf) == 0 {
152 | color = r.Palette[0]
153 | } else if index > PAL_HIGHLIGHT && index < PAL_HIGHLIGHT_OBJ && (index&3) == 0 {
154 | color = r.Palette[PAL_HIGHLIGHT_BG]
155 | }
156 | }
157 |
158 | r.Palette[index] = color
159 | if index < PAL_SGB_BORDER && (index < PAL_OBJ || (index&3) != 0) {
160 | r.Palette[index+PAL_HIGHLIGHT] = color
161 | }
162 | }
163 |
164 | // drawRange / GBVideoSoftwareRendererDrawRange
165 | // by row
166 | func (r *Renderer) drawRange(startX, endX, y int) {
167 | r.lastY, r.lastX = y, endX
168 | if startX >= endX {
169 | return
170 | }
171 |
172 | mapIdx := GB_BASE_MAP // 0x9800
173 | if util.Bit(r.g.LCDC, TileMap) {
174 | mapIdx += GB_SIZE_MAP // 0x9c00
175 | }
176 |
177 | if r.disableBG {
178 | for x := startX; x < endX; x++ {
179 | r.row[x] = 0
180 | }
181 | }
182 |
183 | if util.Bit(r.g.LCDC, BgEnable) || r.Model >= util.GB_MODEL_CGB {
184 | wy, wx := int(r.wy)+int(r.currentWy), int(r.wx)+int(r.currentWx)-7
185 | if util.Bit(r.g.LCDC, Window) && wy == y && wx <= endX {
186 | r.hasWindow = true
187 | }
188 | scx, scy := int(r.g.io[GB_REG_SCX]), int(r.g.io[GB_REG_SCY])
189 | if util.Bit(r.g.LCDC, Window) && r.hasWindow && wx <= endX && !r.disableWIN {
190 | if wx > 0 && !r.disableBG {
191 | // bg -> window
192 | r.drawBackground(mapIdx, startX, wx, scx, scy+y, r.highlightBG)
193 |
194 | // fallthrough and draw window
195 | }
196 |
197 | mapIdx = GB_BASE_MAP
198 | if util.Bit(r.g.LCDC, WindowTileMap) {
199 | mapIdx += GB_SIZE_MAP // 0x9c00
200 | }
201 | r.drawBackground(mapIdx, wx, endX, -wx, y-wy, r.highlightWIN)
202 | } else if !r.disableBG {
203 | r.drawBackground(mapIdx, startX, endX, scx, scy+y, r.highlightBG)
204 | }
205 | } else if !r.disableBG {
206 | for x := startX; x < endX; x++ {
207 | r.row[x] = 0
208 | }
209 | }
210 |
211 | if startX == 0 {
212 | r.cleanOAM(y)
213 | }
214 | if util.Bit(r.g.LCDC, ObjEnable) && !r.disableOBJ {
215 | for i := 0; i < r.objMax; i++ {
216 | r.drawObj(r.obj[i], startX, endX, y)
217 | }
218 | }
219 |
220 | highlightAmount := (r.highlightAmount + 6) >> 4
221 | if r.lastHighlightAmount != highlightAmount {
222 | r.lastHighlightAmount = highlightAmount
223 | for i := 0; i < PAL_SGB_BORDER; i++ {
224 | if i >= PAL_OBJ && i&3 == 0 {
225 | continue
226 | }
227 | r.Palette[i+PAL_HIGHLIGHT] = r.Palette[i]
228 | }
229 | }
230 |
231 | sgbOffset := 0
232 | if (r.Model&util.GB_MODEL_SGB != 0) && r.sgbBorders {
233 | sgbOffset = r.outputBufferStride*40 + 48
234 | }
235 |
236 | row := r.outputBuffer[r.outputBufferStride*y+sgbOffset:]
237 | x, p := startX, 0
238 | switch r.sgbRenderMode {
239 | case 0:
240 | if r.Model&util.GB_MODEL_SGB != 0 {
241 | p = int(r.sgbAttributes[(startX>>5)+5*(y>>3)])
242 | p >>= 6 - ((x / 4) & 0x6)
243 | p &= 3
244 | p <<= 2
245 | }
246 | for ; x < ((startX+7) & ^7) && x < endX; x++ {
247 | row[x] = r.Palette[p|int(r.Lookup[r.row[x]&OBJ_PRIO_MASK])]
248 | }
249 |
250 | for ; x+7 < (endX & ^7); x += 8 {
251 | if (r.Model & util.GB_MODEL_SGB) != 0 {
252 | p = int(r.sgbAttributes[(x>>5)+5*(y>>3)])
253 | p >>= 6 - ((x / 4) & 0x6)
254 | p &= 3
255 | p <<= 2
256 | }
257 | row[x+0] = r.Palette[p|int(r.Lookup[r.row[x]&OBJ_PRIO_MASK])]
258 | row[x+1] = r.Palette[p|int(r.Lookup[r.row[x+1]&OBJ_PRIO_MASK])]
259 | row[x+2] = r.Palette[p|int(r.Lookup[r.row[x+2]&OBJ_PRIO_MASK])]
260 | row[x+3] = r.Palette[p|int(r.Lookup[r.row[x+3]&OBJ_PRIO_MASK])]
261 | row[x+4] = r.Palette[p|int(r.Lookup[r.row[x+4]&OBJ_PRIO_MASK])]
262 | row[x+5] = r.Palette[p|int(r.Lookup[r.row[x+5]&OBJ_PRIO_MASK])]
263 | row[x+6] = r.Palette[p|int(r.Lookup[r.row[x+6]&OBJ_PRIO_MASK])]
264 | row[x+7] = r.Palette[p|int(r.Lookup[r.row[x+7]&OBJ_PRIO_MASK])]
265 | }
266 | if (r.Model & util.GB_MODEL_SGB) != 0 {
267 | p = int(r.sgbAttributes[(x>>5)+5*(y>>3)])
268 | p >>= 6 - ((x / 4) & 0x6)
269 | p &= 3
270 | p <<= 2
271 | }
272 | for ; x < endX; x++ {
273 | row[x] = r.Palette[p|int(r.Lookup[r.row[x]&OBJ_PRIO_MASK])]
274 | }
275 | case 2:
276 | for ; x < ((startX+7) & ^7) && x < endX; x++ {
277 | row[x] = 0
278 | }
279 | for ; x+7 < (endX & ^7); x += 8 {
280 | row[x] = 0
281 | row[x+1] = 0
282 | row[x+2] = 0
283 | row[x+3] = 0
284 | row[x+4] = 0
285 | row[x+5] = 0
286 | row[x+6] = 0
287 | row[x+7] = 0
288 | }
289 | for ; x < endX; x++ {
290 | row[x] = 0
291 | }
292 | case 3:
293 | for ; x < ((startX+7) & ^7) && x < endX; x++ {
294 | row[x] = r.Palette[0]
295 | }
296 | for ; x+7 < (endX & ^7); x += 8 {
297 | row[x] = r.Palette[0]
298 | row[x+1] = r.Palette[0]
299 | row[x+2] = r.Palette[0]
300 | row[x+3] = r.Palette[0]
301 | row[x+4] = r.Palette[0]
302 | row[x+5] = r.Palette[0]
303 | row[x+6] = r.Palette[0]
304 | row[x+7] = r.Palette[0]
305 | }
306 | for ; x < endX; x++ {
307 | row[x] = r.Palette[0]
308 | }
309 | }
310 | }
311 |
312 | // finishScanline / GBVideoSoftwareRendererFinishScanline
313 | func (r *Renderer) finishScanline(y int) {
314 | r.lastX, r.currentWx = 0, 0
315 | }
316 |
317 | // finishFrame / GBVideoSoftwareRendererFinishFrame
318 | func (r *Renderer) finishFrame() {
319 | if !util.Bit(r.g.LCDC, Enable) {
320 | r.clearScreen()
321 | }
322 | r.lastY, r.lastX = VERTICAL_PIXELS, 0
323 | r.currentWy, r.currentWx = 0, 0
324 | r.hasWindow = false
325 | }
326 |
327 | // GBVideoSoftwareRendererDrawBackground
328 | // by row
329 | func (r *Renderer) drawBackground(mapIdx, startX, endX, sx, sy int, highlight bool) {
330 | vramIdx := 0
331 | attrIdx := mapIdx + GB_SIZE_VRAM_BANK0 // for CGB
332 | if !util.Bit(r.g.LCDC, TileData) {
333 | vramIdx += 0x1000
334 | }
335 |
336 | topY := ((sy >> 3) & 0x1F) * 0x20
337 | bottomY := sy & 7
338 | if startX < 0 {
339 | startX = 0
340 | }
341 |
342 | x := 0
343 | if ((startX + sx) & 7) > 0 {
344 | startX2 := startX + 8 - ((startX + sx) & 7)
345 | for x := startX; x < startX2; x++ {
346 | localData := vramIdx
347 | localY := bottomY
348 | topX, bottomX := ((x+sx)>>3)&0x1F, 7-((x+sx)&7)
349 | bgTile := 0
350 | if util.Bit(r.g.LCDC, TileData) {
351 | // 0x8000-0x8800 [0, 255]
352 | bgTile = int(r.g.VRAM.Buffer[mapIdx+topX+topY])
353 | } else {
354 | // 0x8800-0x97ff [-128, 127]
355 | bgTile = int(int8(r.g.VRAM.Buffer[mapIdx+topX+topY]))
356 | }
357 |
358 | p := uint16(0)
359 | if highlight {
360 | p = PAL_HIGHLIGHT_BG
361 | }
362 |
363 | if r.Model >= util.GB_MODEL_CGB {
364 | attrs := r.g.VRAM.Buffer[attrIdx+topX+topY]
365 | p |= uint16(attrs&0x7) * 4
366 | if util.Bit(attrs, ObjAttrPriority) && util.Bit(r.g.LCDC, BgEnable) {
367 | p |= OBJ_PRIORITY
368 | }
369 | if util.Bit(attrs, ObjAttrBank) {
370 | localData += GB_SIZE_VRAM_BANK0
371 | }
372 | if util.Bit(attrs, ObjAttrYFlip) {
373 | localY = 7 - bottomY
374 | }
375 | if util.Bit(attrs, ObjAttrXFlip) {
376 | bottomX = 7 - bottomX
377 | }
378 | }
379 | tileDataLower := r.g.VRAM.Buffer[localData+(bgTile*8+localY)*2]
380 | tileDataUpper := r.g.VRAM.Buffer[localData+(bgTile*8+localY)*2+1]
381 | tileDataUpper >>= bottomX
382 | tileDataLower >>= bottomX
383 | r.row[x] = p | uint16((tileDataUpper&1)<<1) | uint16(tileDataLower&1)
384 | }
385 | startX = startX2
386 | }
387 |
388 | // by tile row
389 | for x = startX; x < endX; x += 8 {
390 | localData := vramIdx
391 | localY := bottomY
392 | topX := ((x + sx) >> 3) & 0x1F
393 |
394 | bgTile := 0
395 | if util.Bit(r.g.LCDC, TileData) {
396 | // 0x8000-0x8800 [0, 255]
397 | bgTile = int(r.g.VRAM.Buffer[mapIdx+topX+topY])
398 | } else {
399 | // 0x8800-0x97ff [-128, 127]
400 | bgTile = int(int8(r.g.VRAM.Buffer[mapIdx+topX+topY]))
401 | }
402 |
403 | p := uint16(PAL_BG)
404 | if highlight {
405 | p = PAL_HIGHLIGHT_BG
406 | }
407 |
408 | if r.Model >= util.GB_MODEL_CGB {
409 | attrs := r.g.VRAM.Buffer[attrIdx+topX+topY]
410 | p |= uint16(attrs&0x7) * 4
411 | if util.Bit(attrs, ObjAttrPriority) && util.Bit(r.g.LCDC, BgEnable) {
412 | p |= OBJ_PRIORITY
413 | }
414 | if util.Bit(attrs, ObjAttrBank) {
415 | localData += GB_SIZE_VRAM_BANK0
416 | }
417 | if util.Bit(attrs, ObjAttrYFlip) {
418 | localY = 7 - bottomY
419 | }
420 | if util.Bit(attrs, ObjAttrXFlip) {
421 | tileDataLower := r.g.VRAM.Buffer[localData+(bgTile*8+localY)*2]
422 | tileDataUpper := r.g.VRAM.Buffer[localData+(bgTile*8+localY)*2+1]
423 | r.row[x+0] = p | uint16((tileDataUpper&1)<<1) | uint16(tileDataLower&1)
424 | r.row[x+1] = p | uint16(tileDataUpper&2) | uint16((tileDataLower&2)>>1)
425 | r.row[x+2] = p | uint16((tileDataUpper&4)>>1) | uint16((tileDataLower&4)>>2)
426 | r.row[x+3] = p | uint16((tileDataUpper&8)>>2) | uint16((tileDataLower&8)>>3)
427 | r.row[x+4] = p | uint16((tileDataUpper&16)>>3) | uint16((tileDataLower&16)>>4)
428 | r.row[x+5] = p | uint16((tileDataUpper&32)>>4) | uint16((tileDataLower&32)>>5)
429 | r.row[x+6] = p | uint16((tileDataUpper&64)>>5) | uint16((tileDataLower&64)>>6)
430 | r.row[x+7] = p | uint16((tileDataUpper&128)>>6) | uint16((tileDataLower&128)>>7)
431 | continue
432 | }
433 | }
434 |
435 | tileDataLower := r.g.VRAM.Buffer[localData+(bgTile*8+localY)*2]
436 | tileDataUpper := r.g.VRAM.Buffer[localData+(bgTile*8+localY)*2+1]
437 | r.row[x+7] = p | uint16((tileDataUpper&1)<<1) | uint16(tileDataLower&1)
438 | r.row[x+6] = p | uint16(tileDataUpper&2) | uint16((tileDataLower&2)>>1)
439 | r.row[x+5] = p | uint16((tileDataUpper&4)>>1) | uint16((tileDataLower&4)>>2)
440 | r.row[x+4] = p | uint16((tileDataUpper&8)>>2) | uint16((tileDataLower&8)>>3)
441 | r.row[x+3] = p | uint16((tileDataUpper&16)>>3) | uint16((tileDataLower&16)>>4)
442 | r.row[x+2] = p | uint16((tileDataUpper&32)>>4) | uint16((tileDataLower&32)>>5)
443 | r.row[x+1] = p | uint16((tileDataUpper&64)>>5) | uint16((tileDataLower&64)>>6)
444 | r.row[x+0] = p | uint16((tileDataUpper&128)>>6) | uint16((tileDataLower&128)>>7)
445 | }
446 | }
447 |
448 | // GBVideoSoftwareRendererDrawObj
449 | func (r *Renderer) drawObj(obj Sprite, startX, endX, y int) {
450 | objX := int(obj.obj.x)
451 | ix := objX - 8
452 | if endX < ix || startX >= ix+8 {
453 | return
454 | }
455 | if objX < endX {
456 | endX = objX
457 | }
458 | if objX-8 > startX {
459 | startX = objX - 8
460 | }
461 | if startX < 0 {
462 | startX = 0
463 | }
464 |
465 | vramIdx := 0x0
466 | tileOffset, bottomY := 0, 0
467 | objY := int(obj.obj.y)
468 | if util.Bit(obj.obj.attr, ObjAttrYFlip) {
469 | bottomY = 7 - ((y - objY - 16) & 7)
470 | if util.Bit(r.g.LCDC, ObjSize) && y-objY < -8 {
471 | tileOffset++
472 | }
473 | } else {
474 | bottomY = (y - objY - 16) & 7
475 | if util.Bit(r.g.LCDC, ObjSize) && y-objY >= -8 {
476 | tileOffset++
477 | }
478 | }
479 | if util.Bit(r.g.LCDC, ObjSize) && obj.obj.tile&1 == 1 {
480 | tileOffset--
481 | }
482 |
483 | mask, mask2 := uint(0x60), uint(OBJ_PRIORITY/3)
484 | if util.Bit(obj.obj.attr, ObjAttrPriority) {
485 | mask, mask2 = 0x63, 0
486 | }
487 |
488 | p := uint16(PAL_OBJ)
489 | if r.highlightOBJ[obj.index] {
490 | p = PAL_HIGHLIGHT_OBJ
491 | }
492 |
493 | if r.Model >= util.GB_MODEL_CGB {
494 | p |= uint16(obj.obj.attr&0x07) * 4
495 | if util.Bit(obj.obj.attr, ObjAttrBank) {
496 | vramIdx += GB_SIZE_VRAM_BANK0
497 | }
498 | if !util.Bit(r.g.LCDC, BgEnable) {
499 | mask, mask2 = 0x60, OBJ_PRIORITY/3
500 | }
501 | } else {
502 | p |= (uint16((obj.obj.attr>>ObjAttrPalette)&1) + 8) * 4 // 8x4 or 9x4
503 | }
504 |
505 | bottomX, x, objTile := 0, startX, int(obj.obj.tile)+tileOffset
506 | if (x-objX)&7 != 0 {
507 | for ; x < endX; x++ {
508 | if util.Bit(obj.obj.attr, ObjAttrXFlip) {
509 | bottomX = (x - objX) & 7
510 | } else {
511 | bottomX = 7 - ((x - objX) & 7)
512 | }
513 | tileDataLower := r.g.VRAM.Buffer[vramIdx+(objTile*8+bottomY)*2]
514 | tileDataUpper := r.g.VRAM.Buffer[vramIdx+(objTile*8+bottomY)*2+1]
515 | tileDataUpper >>= bottomX
516 | tileDataLower >>= bottomX
517 | current := r.row[x]
518 | if ((tileDataUpper|tileDataLower)&1 > 0) && (uint(current)&mask == 0) && (uint(current)&mask2) <= OBJ_PRIORITY {
519 | r.row[x] = p | uint16((tileDataUpper&1)<<1) | uint16(tileDataLower&1)
520 | }
521 | }
522 | } else if util.Bit(obj.obj.attr, ObjAttrXFlip) {
523 | tileDataLower := r.g.VRAM.Buffer[vramIdx+(objTile*8+bottomY)*2]
524 | tileDataUpper := r.g.VRAM.Buffer[vramIdx+(objTile*8+bottomY)*2+1]
525 | current := r.row[x]
526 | if ((tileDataUpper|tileDataLower)&1) != 0 && (uint(current)&mask == 0) && (uint(current)&mask2) <= OBJ_PRIORITY {
527 | r.row[x] = p | uint16((tileDataUpper&1)<<1) | uint16(tileDataLower&1)
528 | }
529 | current = r.row[x+1]
530 | if ((tileDataUpper|tileDataLower)&2) != 0 && (uint(current)&mask == 0) && (uint(current)&mask2) <= OBJ_PRIORITY {
531 | r.row[x+1] = p | uint16(tileDataUpper&2) | uint16((tileDataLower&2)>>1)
532 | }
533 | current = r.row[x+2]
534 | if ((tileDataUpper|tileDataLower)&4) != 0 && (uint(current)&mask == 0) && (uint(current)&mask2) <= OBJ_PRIORITY {
535 | r.row[x+2] = p | uint16((tileDataUpper&4)>>1) | uint16((tileDataLower&4)>>2)
536 | }
537 | current = r.row[x+3]
538 | if ((tileDataUpper|tileDataLower)&8) != 0 && (uint(current)&mask == 0) && (uint(current)&mask2) <= OBJ_PRIORITY {
539 | r.row[x+3] = p | uint16((tileDataUpper&8)>>2) | uint16((tileDataLower&8)>>3)
540 | }
541 | current = r.row[x+4]
542 | if ((tileDataUpper|tileDataLower)&16) != 0 && (uint(current)&mask == 0) && (uint(current)&mask2) <= OBJ_PRIORITY {
543 | r.row[x+4] = p | uint16((tileDataUpper&16)>>3) | uint16((tileDataLower&16)>>4)
544 | }
545 | current = r.row[x+5]
546 | if ((tileDataUpper|tileDataLower)&32) != 0 && (uint(current)&mask == 0) && (uint(current)&mask2) <= OBJ_PRIORITY {
547 | r.row[x+5] = p | uint16((tileDataUpper&32)>>4) | uint16((tileDataLower&32)>>5)
548 | }
549 | current = r.row[x+6]
550 | if ((tileDataUpper|tileDataLower)&64) != 0 && (uint(current)&mask == 0) && (uint(current)&mask2) <= OBJ_PRIORITY {
551 | r.row[x+6] = p | uint16((tileDataUpper&64)>>5) | uint16((tileDataLower&64)>>6)
552 | }
553 | current = r.row[x+7]
554 | if ((tileDataUpper|tileDataLower)&128) != 0 && (uint(current)&mask == 0) && (uint(current)&mask2) <= OBJ_PRIORITY {
555 | r.row[x+7] = p | uint16((tileDataUpper&128)>>6) | uint16((tileDataLower&128)>>7)
556 | }
557 | } else {
558 | tileDataLower := r.g.VRAM.Buffer[vramIdx+(objTile*8+bottomY)*2]
559 | tileDataUpper := r.g.VRAM.Buffer[vramIdx+(objTile*8+bottomY)*2+1]
560 | current := r.row[x+7]
561 | if ((tileDataUpper|tileDataLower)&1) != 0 && (uint(current)&mask) == 0 && (uint(current)&mask2) <= OBJ_PRIORITY {
562 | r.row[x+7] = p | uint16((tileDataUpper&1)<<1) | uint16(tileDataLower&1)
563 | }
564 | current = r.row[x+6]
565 | if ((tileDataUpper|tileDataLower)&2) != 0 && (uint(current)&mask) == 0 && (uint(current)&mask2) <= OBJ_PRIORITY {
566 | r.row[x+6] = p | uint16(tileDataUpper&2) | uint16((tileDataLower&2)>>1)
567 | }
568 | current = r.row[x+5]
569 | if ((tileDataUpper|tileDataLower)&4) != 0 && (uint(current)&mask) == 0 && (uint(current)&mask2) <= OBJ_PRIORITY {
570 | r.row[x+5] = p | uint16((tileDataUpper&4)>>1) | uint16((tileDataLower&4)>>2)
571 | }
572 | current = r.row[x+4]
573 | if ((tileDataUpper|tileDataLower)&8) != 0 && (uint(current)&mask) == 0 && (uint(current)&mask2) <= OBJ_PRIORITY {
574 | r.row[x+4] = p | uint16((tileDataUpper&8)>>2) | uint16((tileDataLower&8)>>3)
575 | }
576 | current = r.row[x+3]
577 | if ((tileDataUpper|tileDataLower)&16) != 0 && (uint(current)&mask) == 0 && (uint(current)&mask2) <= OBJ_PRIORITY {
578 | r.row[x+3] = p | uint16((tileDataUpper&16)>>3) | uint16((tileDataLower&16)>>4)
579 | }
580 | current = r.row[x+2]
581 | if ((tileDataUpper|tileDataLower)&32) != 0 && (uint(current)&mask) == 0 && (uint(current)&mask2) <= OBJ_PRIORITY {
582 | r.row[x+2] = p | uint16((tileDataUpper&32)>>4) | uint16((tileDataLower&32)>>5)
583 | }
584 | current = r.row[x+1]
585 | if ((tileDataUpper|tileDataLower)&64) != 0 && (uint(current)&mask) == 0 && (uint(current)&mask2) <= OBJ_PRIORITY {
586 | r.row[x+1] = p | uint16((tileDataUpper&64)>>5) | uint16((tileDataLower&64)>>6)
587 | }
588 | current = r.row[x]
589 | if ((tileDataUpper|tileDataLower)&128) != 0 && (uint(current)&mask) == 0 && (uint(current)&mask2) <= OBJ_PRIORITY {
590 | r.row[x] = p | uint16((tileDataUpper&128)>>6) | uint16((tileDataLower&128)>>7)
591 | }
592 | }
593 | }
594 |
595 | // _cleanOAM
596 | func (r *Renderer) cleanOAM(y int) {
597 | spriteHeight := 8
598 | if util.Bit(r.g.LCDC, ObjSize) {
599 | spriteHeight = 16
600 | }
601 |
602 | o := 0
603 | for i := 0; i < MAX_OBJ && o < MAX_LINE_OBJ; i++ {
604 | oy := int(r.g.Oam.Objs[i].y)
605 | if y < oy-16 || y >= oy-16+spriteHeight {
606 | continue
607 | }
608 |
609 | r.obj[o].obj = *r.g.Oam.Objs[i]
610 | r.obj[o].index = int8(i)
611 | o++
612 | if o == 10 {
613 | break
614 | }
615 | }
616 | r.objMax = o
617 | }
618 |
619 | // _inWindow
620 | func (r *Renderer) inWindow() bool {
621 | return util.Bit(r.g.LCDC, Window) && HORIZONTAL_PIXELS+7 > r.wx
622 | }
623 |
624 | // _clearScreen
625 | func (r *Renderer) clearScreen() {
626 | if r.Model&util.GB_MODEL_SGB != 0 {
627 | return
628 | }
629 |
630 | for y := 0; y < VERTICAL_PIXELS; y++ {
631 | row := r.outputBuffer[r.outputBufferStride*y:]
632 | for x := 0; x < HORIZONTAL_PIXELS; x += 4 {
633 | row[x+0] = r.Palette[0]
634 | row[x+1] = r.Palette[0]
635 | row[x+2] = r.Palette[0]
636 | row[x+3] = r.Palette[0]
637 | }
638 | }
639 | }
640 |
--------------------------------------------------------------------------------