-
Notifications
You must be signed in to change notification settings - Fork 8
/
ndsemu.go
366 lines (322 loc) · 9.31 KB
/
ndsemu.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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
package main
import (
"flag"
"fmt"
"io/ioutil"
"ndsemu/e2d"
"ndsemu/emu/hw"
log "ndsemu/emu/logger"
"ndsemu/homebrew"
"os"
"os/signal"
"path/filepath"
"runtime/debug"
"runtime/pprof"
"strings"
"time"
"github.com/veandco/go-sdl2/sdl"
)
type CpuNum int
const (
CpuNds9 CpuNum = 0
CpuNds7 CpuNum = 1
)
/*
* NDS9: ARM946E-S, architecture ARMv5TE, 66Mhz
* NDS7: ARM7TDMI, architecture ARMv4T, 33Mhz
*
*/
const cFirmwareDefault = "bios/firmware.bin"
var (
skipBiosArg = flag.Bool("s", false, "skip bios and run immediately")
flagDebug = flag.Bool("debug", false, "run with debugger")
cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
flagLogging = flag.String("log", "", "enable logging for specified modules")
flagJit = flag.Bool("jit", false, "use JIT for emulation (unstable, eats memory)")
flagVsync = flag.Bool("vsync", true, "run at normal speed (60 FPS)")
flagFirmware = flag.String("firmware", cFirmwareDefault, "specify the firwmare file to use")
flagHbrewFat = flag.String("homebrew-fat", "", "FAT image to be mounted for homebrew ROM")
nds7 *NDS7
nds9 *NDS9
KeyState = make([]uint8, 256)
)
func init() {
// For now, disable GC during JIT. This is required because the Go runtime
// doesn't like generated code on the Go stack without a stackmap.
// Use a hack because we need to do this before any goroutine is started
for _, arg := range os.Args {
if arg == "-jit" || arg == "-jit=true" {
debug.SetGCPercent(-1)
}
}
}
func main() {
sdl.Main(main1)
}
func main1() {
flag.Parse()
// Check whether there is a local firmware copy, otherwise
// create one (to handle read/write)
if (*flagFirmware)[0] != '/' {
bindir, _ := filepath.Abs(filepath.Dir(os.Args[0]))
*flagFirmware = filepath.Join(bindir, *flagFirmware)
}
if _, err := os.Stat(*flagFirmware); err != nil {
log.ModEmu.FatalZ("cannot open firmware").Error("err", err).End()
}
firstboot := false
fwsav := *flagFirmware + ".sav"
if _, err := os.Stat(fwsav); err != nil {
fw, err := ioutil.ReadFile(*flagFirmware)
if err != nil {
log.ModEmu.FatalZ("cannot load firwmare:").Error("err", err).End()
}
err = ioutil.WriteFile(fwsav, fw, 0777)
if err != nil {
log.ModEmu.FatalZ("cannot save firwmare:").Error("err", err).End()
}
firstboot = true
}
Emu = NewNDSEmulator(fwsav, *flagJit)
// Check if the NDS ROM is homebrew. If so, directly load it into slot2
// like PassMe does.
if len(flag.Args()) > 0 {
if hbrew, _ := homebrew.Detect(flag.Arg(0)); hbrew {
if err := Emu.Hw.Sl2.MapCartFile(flag.Arg(0)); err != nil {
log.ModEmu.FatalZ(err.Error()).End()
}
if len(flag.Args()) > 1 {
log.ModEmu.FatalZ("slot2 ROM specified but slot1 ROM is homebrew").End()
}
// FIXME: also load the ROM in slot1. Theoretically, for a full
// Passme emulation, the ROM in slot1 should be patched by PassMe,
// but it looks like the firmware we're using doesn't need it.
if err := Emu.Hw.Gc.MapCartFile(flag.Arg(0)); err != nil {
log.ModEmu.FatalZ(err.Error()).End()
}
// See if we are asked to load a FAT image as well. If so, we concatenate it
// to the ROM, and then do a DLDI patch to make libfat find it.
if *flagHbrewFat != "" {
if err := Emu.Hw.Sl2.HomebrewMapFatFile(*flagHbrewFat); err != nil {
log.ModEmu.FatalZ(err.Error()).End()
}
if err := homebrew.FcsrPatchDldi(Emu.Hw.Sl2.Rom); err != nil {
log.ModEmu.FatalZ(err.Error()).End()
}
}
// Activate IDEAS-compatibile debug output on both CPUs
// (use a special SWI to write messages in console)
homebrew.ActivateIdeasDebug(nds9.Cpu)
homebrew.ActivateIdeasDebug(nds7.Cpu)
} else if strings.HasSuffix(flag.Arg(0), ".nds") {
// Map Slot1 cart file (NDS ROM)
if err := Emu.Hw.Gc.MapCartFile(flag.Arg(0)); err != nil {
log.ModEmu.FatalZ(err.Error()).End()
}
// Map save file for Slot1
if err := Emu.Hw.Bkp.MapSaveFile(flag.Arg(0) + ".sav"); err != nil {
log.ModEmu.FatalZ(err.Error()).End()
}
// If specified, map Slot2 cart file (GBA ROM)
if len(flag.Args()) > 1 {
if err := Emu.Hw.Sl2.MapCartFile(flag.Arg(1)); err != nil {
log.ModEmu.FatalZ(err.Error()).End()
}
}
if *flagHbrewFat != "" {
log.ModEmu.FatalZ("cannot specify -homebrew-fat for non-homebrew ROM").End()
}
} else if strings.HasSuffix(flag.Arg(0), ".gba") {
if err := Emu.Hw.Sl2.MapCartFile(flag.Arg(0)); err != nil {
log.ModEmu.FatalZ(err.Error()).End()
}
if len(flag.Args()) > 1 {
log.ModEmu.FatalZ("cannot specify multiple ROMs after GBA rom").End()
}
if *flagHbrewFat != "" {
log.ModEmu.FatalZ("cannot specify -homebrew-fat for non-homebrew ROM").End()
}
} else {
log.ModEmu.FatalZ("unrecognized ROM type").String("rom", flag.Arg(0)).End()
}
}
if err := Emu.Hw.Ff.MapFirmwareFile(fwsav); err != nil {
log.ModEmu.FatalZ(err.Error()).End()
}
if firstboot {
Emu.Hw.Rtc.ResetDefaults()
}
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
<-c
f, err := os.Create("ram.dump")
if err == nil {
f.Write(Emu.Mem.Ram[:])
f.Close()
}
f, err = os.Create("wram.dump")
if err == nil {
f.Write(Emu.Hw.Mc.wram[:])
f.Write(Emu.Mem.Wram[:])
f.Close()
}
for i := 0; i < len(Emu.Hw.Mc.vram); i++ {
char := 'a' + i
f, err = os.Create(fmt.Sprintf("vram-%c.dump", char))
if err == nil {
f.Write(Emu.Hw.Mc.vram[i][:])
f.Close()
}
}
f, err = os.Create("vram-bg-a.dump")
if err == nil {
v := Emu.Hw.Mc.VramLinearBank(0, e2d.VramLinearBG, 0)
v.Dump(f)
v = Emu.Hw.Mc.VramLinearBank(0, e2d.VramLinearBG, 256*1024)
v.Dump(f)
f.Close()
}
f, err = os.Create("vram-bg-b.dump")
if err == nil {
v := Emu.Hw.Mc.VramLinearBank(1, e2d.VramLinearBG, 0)
v.Dump(f)
f.Truncate(128 * 1024)
f.Close()
}
f, err = os.Create("vram-bgextpal-a.dump")
if err == nil {
v := Emu.Hw.Mc.VramLinearBank(0, e2d.VramLinearBGExtPal, 0)
v.Dump(f)
f.Close()
}
f, err = os.Create("vram-bgextpal-b.dump")
if err == nil {
v := Emu.Hw.Mc.VramLinearBank(1, e2d.VramLinearBGExtPal, 0)
v.Dump(f)
f.Close()
}
f, err = os.Create("oam.dump")
if err == nil {
f.Write(Emu.Mem.OamRam[:])
f.Close()
}
f, err = os.Create("texture.dump")
if err == nil {
texbank := Emu.Hw.Mc.VramTextureBank()
for i := 0; i < 16; i++ {
f.Write(texbank.Slots[i])
}
f.Close()
}
f, err = os.Create("texpal.dump")
if err == nil {
texbank := Emu.Hw.Mc.VramTexturePaletteBank()
for i := 0; i < 8; i++ {
f.Write(texbank.Slots[i])
}
f.Close()
}
if *cpuprofile != "" {
pprof.StopCPUProfile()
}
os.Exit(1)
}()
if *skipBiosArg {
if err := InjectGamecard(Emu.Hw.Gc, Emu.Mem); err != nil {
fmt.Println(err)
return
}
// Shared wram: map everything to ARM7
Emu.Hw.Mc.WramCnt.Write8(0, 3)
// Set post-boot flag to 1
nds9.misc.PostFlg.Value = 1
nds7.misc7.PostFlg.Value = 1
nds9.Irq.Ime.Value = 0x1
nds7.Irq.Ime.Value = 0x1
nds9.Irq.Ie.Value = uint32(IrqIpcRecvFifo | IrqTimers | IrqVBlank)
nds7.Irq.Ie.Value = uint32(IrqIpcRecvFifo | IrqTimers | IrqVBlank)
// VRAM: map everything in "LCDC mode"
Emu.Hw.Mc.VramCntA.Write8(0, 0x80)
Emu.Hw.Mc.VramCntB.Write8(0, 0x80)
Emu.Hw.Mc.VramCntC.Write8(0, 0x80)
Emu.Hw.Mc.VramCntD.Write8(0, 0x80)
Emu.Hw.Mc.VramCntE.Write8(0, 0x80)
Emu.Hw.Mc.VramCntF.Write8(0, 0x80)
Emu.Hw.Mc.VramCntG.Write8(0, 0x80)
Emu.Hw.Mc.VramCntH.Write8(0, 0x80)
Emu.Hw.Mc.VramCntI.Write8(0, 0x80)
// Gamecard: skip directly to key2 status
Emu.Hw.Gc.stat = gcStatusKey2
nds9.Cp15.ConfigureControlReg(0x52078, 0x00FF085)
}
if *flagDebug {
Emu.StartDebugger()
}
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
log.ModEmu.FatalZ(err.Error())
}
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
if *flagLogging != "" {
var modmask log.ModuleMask
for _, modname := range strings.Split(*flagLogging, ",") {
if modname == "all" {
modmask |= log.ModuleMaskAll
} else if m, found := log.ModuleByName(modname); found {
modmask |= m.Mask()
} else {
log.ModEmu.FatalZ("invalid module name").String("name", modname).End()
}
}
log.EnableDebugModules(modmask)
}
hwout := hw.NewOutput(hw.OutputConfig{
Title: "NDSEmu - Nintendo DS Emulator",
Width: 256,
Height: 192 + 90 + 192,
FramePerSecond: 60,
NumBackBuffers: 3,
EnforceSpeed: *flagVsync,
AudioFrequency: cAudioFreq,
AudioChannels: 2,
AudioSampleSigned: true,
})
hwout.EnableVideo(true)
hwout.EnableAudio(true)
var fprof *os.File
profiling := 0
KeyState = hw.GetKeyboardState()
for hwout.Poll() {
if KeyState[hw.SCANCODE_P] != 0 {
time.Sleep(1 * time.Second)
}
if KeyState[hw.SCANCODE_L] != 0 && profiling == 0 {
fprof, _ = os.Create("profile.dump")
pprof.StartCPUProfile(fprof)
profiling = Emu.framecount
}
if profiling > 0 && profiling <= Emu.framecount-120 {
pprof.StopCPUProfile()
fprof.Close()
fprof = nil
profiling = 0
log.ModEmu.Warnf("profile dumped")
}
x, y, btn := hwout.GetMouseState()
y -= 192 + 90
pendown := btn&hw.MouseButtonLeft != 0
Emu.Hw.Key.SetPenDown(pendown)
Emu.Hw.Tsc.SetPen(pendown, x, y)
v, a := hwout.BeginFrame()
exit := Emu.RunOneFrame(v, ([]int16)(a))
hwout.EndFrame(v, a)
if exit {
fmt.Println("System was powered off")
break
}
}
}