forked from remogatto/sms
/
main.go
217 lines (188 loc) · 5.41 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
package main
import (
"flag"
"fmt"
"github.com/0xe2-0x9a-0x9b/Go-SDL/sdl"
"github.com/remogatto/z80"
"github.com/remogatto/application"
"log"
"os"
"time"
)
// drainTicker drains the remaining ticks from the given tick.
func drainTicker(ticker *time.Ticker) {
loop:
for {
select {
case <-ticker.C:
default:
break loop
}
}
}
// emulatorLoop sends a cmdRenderFrame command to the rendering backend
// (displayLoop) each 1/50 second.
type emulatorLoop struct {
ticker *time.Ticker
sms *SMS
pause, terminate chan int
pauseEmulation chan int
}
// newEmulatorLoop returns a new emulatorLoop instance.
func newEmulatorLoop(displayLoop DisplayLoop) *emulatorLoop {
emulatorLoop := &emulatorLoop{
ticker: time.NewTicker(time.Duration(1e9 / 50)), // 50 Hz
sms: newSMS(displayLoop),
pause: make(chan int),
terminate: make(chan int),
pauseEmulation: make(chan int),
}
if flag.Arg(0) == "" {
return nil
}
emulatorLoop.sms.loadRom(flag.Arg(0))
return emulatorLoop
}
// Pause returns the pause channel of the loop.
// If a value is sent to this channel, the loop will be paused.
func (l *emulatorLoop) Pause() chan int {
return l.pause
}
// Terminate returns the terminate channel of the loop.
// If a value is sent to this channel, the loop will be terminated.
func (l *emulatorLoop) Terminate() chan int {
return l.terminate
}
// Run runs emulatorLoop.
// The loop sends a cmdRenderFrame command to the sms command channel
// each time it receives a value from the ticker.
func (l *emulatorLoop) Run() {
for {
select {
case <-l.pause:
l.ticker.Stop()
l.pause <- 0
case <-l.terminate:
l.terminate <- 0
case <-l.ticker.C:
l.sms.command <- cmdRenderFrame{}
case <-l.pauseEmulation:
if l.sms.paused {
l.ticker.Stop()
drainTicker(l.ticker)
} else {
l.ticker = time.NewTicker(time.Duration(1e9 / 50))
}
l.pauseEmulation <- 0
}
}
}
// commandLoop receives commands from the sms command channel and
// forward them to the emulatorLoop or to the displayLoop.
type commandLoop struct {
pause, terminate chan int
emulatorLoop *emulatorLoop
displayLoop DisplayLoop
}
// newCommandLoop returns a commandLoop instance.
func newCommandLoop(emulatorLoop *emulatorLoop, displayLoop DisplayLoop) *commandLoop {
return &commandLoop{
emulatorLoop: emulatorLoop,
displayLoop: displayLoop,
pause: make(chan int),
terminate: make(chan int),
}
}
// Pause returns the pause channel of the loop.
// If a value is sent to this channel, the loop will be paused.
func (l *commandLoop) Pause() chan int {
return l.pause
}
// Terminate returns the terminate channel of the loop.
// If a value is sent to this channel, the loop will be terminated.
func (l *commandLoop) Terminate() chan int {
return l.terminate
}
// Run runs the commandLoop.
// The loop waits for commands sent to sms.command channel.
func (l *commandLoop) Run() {
for {
select {
case <-l.pause:
l.pause <- 0
case <-l.terminate:
l.terminate <- 0
case _cmd := <-l.emulatorLoop.sms.command:
switch cmd := _cmd.(type) {
case cmdRenderFrame:
l.displayLoop.Display() <- l.emulatorLoop.sms.frame()
case cmdLoadRom:
l.emulatorLoop.sms.loadRom(cmd.fileName)
case cmdJoypadEvent:
l.emulatorLoop.sms.joypad(cmd.value, cmd.event)
case cmdPauseEmulation:
l.emulatorLoop.pauseEmulation <- 0
<-l.emulatorLoop.pauseEmulation
cmd.paused <- l.emulatorLoop.sms.paused
case cmdShowCurrentInstruction:
prevAddr := z80.PreviousInstruction(l.emulatorLoop.sms.memory, l.emulatorLoop.sms.cpu.PC())
res, address, shift := z80.Disassemble(l.emulatorLoop.sms.memory, prevAddr, 0)
if res != "shift " {
application.Logf("0x%04x %s\n", l.emulatorLoop.sms.cpu.PC(), res)
}
res, address, shift = z80.Disassemble(l.emulatorLoop.sms.memory, l.emulatorLoop.sms.cpu.PC(), 0)
if res != "shift " {
application.Logf("0x%04x %s\n", l.emulatorLoop.sms.cpu.PC(), res)
}
for i := 0; i < 20; i++ {
oldAddress := address
res, address, shift = z80.Disassemble(l.emulatorLoop.sms.memory, address, shift)
if res != "shift " {
application.Logf("0x%04x %s\n", oldAddress, res)
}
// address++
}
}
}
}
}
// usage shows sms executable usage.
func usage() {
fmt.Fprintf(os.Stderr, "SMS - A Sega Master System emulator written in Go\n\n")
fmt.Fprintf(os.Stderr, "Usage:\n\n")
fmt.Fprintf(os.Stderr, "\tsms [options] game.sms\n\n")
fmt.Fprintf(os.Stderr, "Options are:\n\n")
flag.PrintDefaults()
}
func main() {
verbose := flag.Bool("verbose", false, "verbose mode")
fullScreen := flag.Bool("fullscreen", false, "go fullscreen")
help := flag.Bool("help", false, "Show usage")
flag.Usage = usage
flag.Parse()
if *help {
usage()
return
}
application.Verbose = *verbose
if sdl.Init(sdl.INIT_EVERYTHING) != 0 {
log.Fatal(sdl.GetError())
}
screen := newSDL2xScreen(*fullScreen)
sdlLoop := newSDLLoop(screen)
emulatorLoop := newEmulatorLoop(sdlLoop)
if emulatorLoop == nil {
usage()
return
}
commandLoop := newCommandLoop(emulatorLoop, sdlLoop)
inputLoop := newInputLoop(emulatorLoop.sms)
application.Register("Emulator loop", emulatorLoop)
application.Register("Command loop", commandLoop)
application.Register("SDL render loop", sdlLoop)
application.Register("SDL input loop", inputLoop)
exitCh := make(chan bool)
application.Run(exitCh)
<-exitCh
sdl.Quit()
}