├── 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 | ![logo](./logo.png) 2 | 3 | # 🌏 worldwide 4 | ![Go](https://github.com/pokemium/worldwide/workflows/Go/badge.svg) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/pokemium/worldwide)](https://goreportcard.com/report/github.com/pokemium/worldwide) 6 | [![GitHub stars](https://img.shields.io/github/stars/pokemium/worldwide)](https://github.com/pokemium/worldwide/stargazers) 7 | [![GitHub license](https://img.shields.io/github/license/pokemium/worldwide)](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 | ![logo](./logo.png) 11 | 12 | # 🌏 worldwide 13 | ![Go](https://github.com/pokemium/worldwide/workflows/Go/badge.svg) 14 | [![Go Report Card](https://goreportcard.com/badge/github.com/pokemium/worldwide)](https://goreportcard.com/report/github.com/pokemium/worldwide) 15 | [![GitHub stars](https://img.shields.io/github/stars/pokemium/worldwide)](https://github.com/pokemium/worldwide/stargazers) 16 | [![GitHub license](https://img.shields.io/github/license/pokemium/worldwide)](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 | --------------------------------------------------------------------------------