├── .gitignore ├── LICENSE.md ├── README.md ├── apu.go ├── b ├── bits.go ├── cart.go ├── cmd └── dmgo │ └── main.go ├── cpu.go ├── dbg.go ├── dmgo.go ├── errEmu.go ├── gbs.go ├── go.mod ├── go.sum ├── lcd.go ├── mbc.go ├── mem.go ├── ops.go ├── pack.go ├── profiling ├── common.go ├── profiling_block.go ├── profiling_cpu.go ├── profiling_disabled.go ├── profiling_live.go └── profiling_mem.go ├── snap.go ├── tga.go └── txt.go /.gitignore: -------------------------------------------------------------------------------- 1 | glide.lock 2 | vendor 3 | 4 | tools 5 | build 6 | build-dev 7 | r 8 | 9 | *.gb 10 | *.gbc 11 | *.gbs 12 | *.sav 13 | 14 | *.exe 15 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 theinternetftw 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dmgo - a gameboy emulator in go 2 | 3 | My other emulators: 4 | [famigo](https://github.com/theinternetftw/famigo), 5 | [vcsgo](https://github.com/theinternetftw/vcsgo), 6 | [segmago](https://github.com/theinternetftw/segmago), and 7 | [a1go](https://github.com/theinternetftw/a1go). 8 | 9 | #### Features: 10 | * Audio! 11 | * Saved game support! 12 | * Quicksave/Quickload, too! 13 | * All major [MBCs](http://gbdev.gg8.se/wiki/articles/Memory_Bank_Controllers) suppported! 14 | * Glitches are relatively rare but still totally happen! 15 | * Graphical and auditory cross-platform support! 16 | 17 | #### Dependencies: 18 | 19 | * You can compile on windows with no C dependencies. 20 | * Other platforms should do whatever the [ebiten](https://github.com/hajimehoshi/ebiten) page says, which is what's currently under the hood. 21 | 22 | #### Compile instructions 23 | 24 | * If you have go version >= 1.18, `go build ./cmd/dmgo` should be enough. 25 | * The interested can also see my build script `b` for profiling and such. 26 | * Non-windows users will need ebiten's dependencies. 27 | 28 | #### Important Notes: 29 | 30 | * Keybindings are currently hardcoded to WSAD / JK / TY (arrowpad, ab, start/select) 31 | * Saved games use/expect a slightly different naming convention than usual: romfilename.gb.sav 32 | * Quicksave/Quickload is done by pressing m or l (make or load quicksave), followed by a number key 33 | -------------------------------------------------------------------------------- /apu.go: -------------------------------------------------------------------------------- 1 | package dmgo 2 | 3 | import "fmt" 4 | 5 | type apu struct { 6 | // not marshalled in snapshot 7 | buffer apuCircleBuf 8 | 9 | LeftSample uint32 10 | RightSample uint32 11 | NumSamples uint32 12 | 13 | LastLeft float64 14 | LastRight float64 15 | LastCorrectedLeft float64 16 | LastCorrectedRight float64 17 | 18 | // everything else marshalled 19 | 20 | AllSoundsOn bool 21 | 22 | SweepTimeCounter int 23 | EnvTimeCounter int 24 | LengthTimeCounter int 25 | 26 | Sounds [4]sound 27 | 28 | // cart chip sounds. never used by any game? 29 | VInToLeftSpeaker bool 30 | VInToRightSpeaker bool 31 | 32 | RightSpeakerVolume byte // right=S01 in docs 33 | LeftSpeakerVolume byte // left=S02 in docs 34 | } 35 | 36 | func (apu *apu) init() { 37 | apu.Sounds[0].SoundType = squareSoundType 38 | apu.Sounds[1].SoundType = squareSoundType 39 | apu.Sounds[2].SoundType = waveSoundType 40 | apu.Sounds[3].SoundType = noiseSoundType 41 | 42 | apu.Sounds[3].PolyFeedbackReg = 0x01 43 | } 44 | 45 | const ( 46 | apuCircleBufSize = 16 * 512 * 4 // must be power of two 47 | samplesPerSecond = 44100 48 | clocksPerSecond = 4194304 / 2 // Max divider setting is 2MHz 49 | clocksPerSample = clocksPerSecond / samplesPerSecond 50 | ) 51 | 52 | // NOTE: size must be power of 2 53 | type apuCircleBuf struct { 54 | writeIndex uint 55 | readIndex uint 56 | buf [apuCircleBufSize]byte 57 | } 58 | 59 | func (c *apuCircleBuf) write(bytes []byte) (writeCount int) { 60 | for _, b := range bytes { 61 | if c.full() { 62 | return writeCount 63 | } 64 | c.buf[c.mask(c.writeIndex)] = b 65 | c.writeIndex++ 66 | writeCount++ 67 | } 68 | return writeCount 69 | } 70 | func (c *apuCircleBuf) read(preSizedBuf []byte) []byte { 71 | readCount := 0 72 | for i := range preSizedBuf { 73 | if c.size() == 0 { 74 | break 75 | } 76 | preSizedBuf[i] = c.buf[c.mask(c.readIndex)] 77 | c.readIndex++ 78 | readCount++ 79 | } 80 | return preSizedBuf[:readCount] 81 | } 82 | func (c *apuCircleBuf) mask(i uint) uint { return i & (uint(len(c.buf)) - 1) } 83 | func (c *apuCircleBuf) size() uint { return c.writeIndex - c.readIndex } 84 | func (c *apuCircleBuf) full() bool { return c.size() == uint(len(c.buf)) } 85 | 86 | func (apu *apu) readSoundBuffer(toFill []byte) []byte { 87 | if int(apu.buffer.size()) < len(toFill) { 88 | // fmt.Println("audSize:", apu.buffer.size(), "len(toFill)", len(toFill), "buf[0]", apu.buffer.buf[0]) 89 | } 90 | if int(apu.buffer.size()) < len(toFill) { 91 | fmt.Println("[apu] readSoundBuffer() underflow!") 92 | // stretch sound to fill buffer to avoid click 93 | for int(apu.buffer.size()) < len(toFill) { 94 | apu.genSample() 95 | } 96 | } 97 | return apu.buffer.read(toFill) 98 | } 99 | 100 | func (apu *apu) genSample() { 101 | apu.runFreqCycle() 102 | 103 | leftSam, rightSam := uint32(0), uint32(0) 104 | if apu.AllSoundsOn { 105 | 106 | // IMPORTANT TODO: 107 | // probably have to reintroduce wave bias fix 108 | // from float-land. should probably center at 109 | // 7.5 and let the end dc blocker get it around zero? 110 | 111 | left0, right0 := apu.Sounds[0].getSample() 112 | left1, right1 := apu.Sounds[1].getSample() 113 | left2, right2 := apu.Sounds[2].getSample() 114 | left3, right3 := apu.Sounds[3].getSample() 115 | leftSam += uint32(left0 + left1 + left2 + left3) 116 | rightSam += uint32(right0 + right1 + right2 + right3) 117 | leftSam *= uint32(apu.LeftSpeakerVolume + 1) 118 | rightSam *= uint32(apu.RightSpeakerVolume + 1) 119 | // will need to div by 4*8*15 120 | } 121 | apu.LeftSample += leftSam 122 | apu.RightSample += rightSam 123 | apu.NumSamples++ 124 | 125 | if apu.NumSamples >= clocksPerSample { 126 | left := float64(apu.LeftSample) / float64(apu.NumSamples) 127 | right := float64(apu.RightSample) / float64(apu.NumSamples) 128 | left /= 4 * 8 * 15 129 | right /= 4 * 8 * 15 130 | 131 | // dc blocker to center waveform 132 | correctedLeft := left - apu.LastLeft + 0.995*apu.LastCorrectedLeft 133 | apu.LastCorrectedLeft = correctedLeft 134 | apu.LastLeft = left 135 | left = correctedLeft 136 | 137 | correctedRight := right - apu.LastRight + 0.995*apu.LastCorrectedRight 138 | apu.LastCorrectedRight = correctedRight 139 | apu.LastRight = right 140 | right = correctedRight 141 | 142 | iSampleL, iSampleR := int16(left*32767.0), int16(right*32767.0) 143 | apu.buffer.write([]byte{ 144 | byte(iSampleL & 0xff), 145 | byte(iSampleL >> 8), 146 | byte(iSampleR & 0xff), 147 | byte(iSampleR >> 8), 148 | }) 149 | 150 | apu.LeftSample = 0 151 | apu.RightSample = 0 152 | apu.NumSamples = 0 153 | } 154 | } 155 | 156 | func (apu *apu) runCycle(cs *cpuState) { 157 | 158 | apu.LengthTimeCounter++ 159 | if apu.LengthTimeCounter >= 16384 { 160 | apu.runLengthCycle() 161 | apu.LengthTimeCounter = 0 162 | 163 | apu.SweepTimeCounter++ 164 | if apu.SweepTimeCounter >= 2 { 165 | apu.Sounds[0].runSweepCycle() 166 | apu.SweepTimeCounter = 0 167 | } 168 | 169 | apu.EnvTimeCounter++ 170 | if apu.EnvTimeCounter >= 4 { 171 | apu.runEnvCycle() 172 | apu.EnvTimeCounter = 0 173 | } 174 | } 175 | 176 | if apu.LengthTimeCounter&1 == 0 && !apu.buffer.full() { 177 | apu.genSample() 178 | } 179 | } 180 | 181 | func (apu *apu) runFreqCycle() { 182 | apu.Sounds[0].runFreqCycle() 183 | apu.Sounds[1].runFreqCycle() 184 | apu.Sounds[2].runFreqCycle() 185 | apu.Sounds[3].runFreqCycle() 186 | } 187 | func (apu *apu) runLengthCycle() { 188 | apu.Sounds[0].runLengthCycle() 189 | apu.Sounds[1].runLengthCycle() 190 | apu.Sounds[2].runLengthCycle() 191 | apu.Sounds[3].runLengthCycle() 192 | } 193 | func (apu *apu) runEnvCycle() { 194 | apu.Sounds[0].runEnvCycle() 195 | apu.Sounds[1].runEnvCycle() 196 | apu.Sounds[2].runEnvCycle() 197 | apu.Sounds[3].runEnvCycle() 198 | } 199 | 200 | type envDir bool 201 | 202 | var ( 203 | envUp = envDir(true) 204 | envDown = envDir(false) 205 | ) 206 | 207 | type sweepDir bool 208 | 209 | var ( 210 | sweepUp = sweepDir(false) 211 | sweepDown = sweepDir(true) 212 | ) 213 | 214 | const ( 215 | squareSoundType = 0 216 | waveSoundType = 1 217 | noiseSoundType = 2 218 | ) 219 | 220 | type sound struct { 221 | SoundType uint8 222 | 223 | On bool 224 | RightSpeakerOn bool // S01 in docs 225 | LeftSpeakerOn bool // S02 in docs 226 | 227 | EnvelopeDirection envDir 228 | EnvelopeStartVal byte 229 | EnvelopeSweepVal byte 230 | CurrentEnvelope byte 231 | EnvelopeCounter byte 232 | 233 | T uint32 234 | FreqDivider uint32 235 | FreqReg uint16 236 | 237 | SweepCounter byte 238 | SweepDirection sweepDir 239 | SweepTime byte 240 | SweepShift byte 241 | 242 | LengthData uint16 243 | CurrentLength uint16 244 | 245 | WaveDuty byte 246 | WaveDutySeqCounter byte 247 | 248 | WaveOutLvl byte // sound[2] only 249 | WavePatternRAM [16]byte 250 | WavePatternCursor byte 251 | 252 | PolyFeedbackReg uint16 // sound[3] only 253 | PolyDivisorShift byte 254 | PolyDivisorBase byte 255 | Poly7BitMode bool 256 | PolySample byte 257 | 258 | PlaysContinuously bool 259 | RestartRequested bool 260 | } 261 | 262 | func (sound *sound) runFreqCycle() { 263 | 264 | sound.T += 2 // currently called at 2MHz, so tick twice 265 | 266 | if sound.T >= sound.FreqDivider { 267 | sound.T = 0 268 | switch sound.SoundType { 269 | case squareSoundType: 270 | sound.WaveDutySeqCounter = (sound.WaveDutySeqCounter + 1) & 7 271 | case waveSoundType: 272 | sound.WavePatternCursor = (sound.WavePatternCursor + 1) & 31 273 | case noiseSoundType: 274 | sound.updatePolyCounter() 275 | } 276 | } 277 | } 278 | 279 | func (sound *sound) updatePolyCounter() { 280 | newHigh := (sound.PolyFeedbackReg & 0x01) ^ ((sound.PolyFeedbackReg >> 1) & 0x01) 281 | sound.PolyFeedbackReg >>= 1 282 | sound.PolyFeedbackReg &^= 1 << 14 283 | sound.PolyFeedbackReg |= newHigh << 14 284 | if sound.Poly7BitMode { 285 | sound.PolyFeedbackReg &^= 1 << 6 286 | sound.PolyFeedbackReg |= newHigh << 6 287 | } 288 | if sound.PolyFeedbackReg&0x01 == 0 { 289 | sound.PolySample = 1 290 | } else { 291 | sound.PolySample = 0 292 | } 293 | } 294 | 295 | func (sound *sound) runLengthCycle() { 296 | if sound.CurrentLength > 0 && !sound.PlaysContinuously { 297 | sound.CurrentLength-- 298 | if sound.CurrentLength == 0 { 299 | sound.On = false 300 | } 301 | } 302 | if sound.RestartRequested { 303 | sound.On = true 304 | sound.RestartRequested = false 305 | if sound.LengthData == 0 { 306 | if sound.SoundType == waveSoundType { 307 | sound.LengthData = 256 308 | } else { 309 | sound.LengthData = 64 310 | } 311 | } 312 | sound.CurrentLength = sound.LengthData 313 | sound.CurrentEnvelope = sound.EnvelopeStartVal 314 | sound.SweepCounter = 0 315 | sound.WavePatternCursor = 0 316 | sound.PolyFeedbackReg = 0xffff 317 | } 318 | } 319 | 320 | func (sound *sound) runSweepCycle() { 321 | if sound.SweepTime != 0 { 322 | if sound.SweepCounter < sound.SweepTime { 323 | sound.SweepCounter++ 324 | } else { 325 | sound.SweepCounter = 0 326 | var nextFreq uint16 327 | if sound.SweepDirection == sweepUp { 328 | nextFreq = sound.FreqReg + (sound.FreqReg >> uint16(sound.SweepShift)) 329 | } else { 330 | nextFreq = sound.FreqReg - (sound.FreqReg >> uint16(sound.SweepShift)) 331 | } 332 | if nextFreq > 2047 { 333 | sound.On = false 334 | } else { 335 | sound.FreqReg = nextFreq 336 | sound.updateFreq() 337 | } 338 | } 339 | } 340 | } 341 | 342 | func (sound *sound) runEnvCycle() { 343 | // more complicated, see GBSOUND 344 | if sound.EnvelopeSweepVal != 0 { 345 | if sound.EnvelopeCounter < sound.EnvelopeSweepVal { 346 | sound.EnvelopeCounter++ 347 | } else { 348 | sound.EnvelopeCounter = 0 349 | if sound.EnvelopeDirection == envUp { 350 | if sound.CurrentEnvelope < 0x0f { 351 | sound.CurrentEnvelope++ 352 | } 353 | } else { 354 | if sound.CurrentEnvelope > 0x00 { 355 | sound.CurrentEnvelope-- 356 | } 357 | } 358 | } 359 | } 360 | } 361 | 362 | var dutyCycleTable = [4][8]byte{ 363 | {0, 0, 0, 0, 0, 0, 0, 1}, 364 | {1, 0, 0, 0, 0, 0, 0, 1}, 365 | {1, 0, 0, 0, 0, 1, 1, 1}, 366 | {0, 1, 1, 1, 1, 1, 1, 0}, 367 | } 368 | 369 | func (sound *sound) inDutyCycle() bool { 370 | sel := sound.WaveDuty 371 | counter := sound.WaveDutySeqCounter 372 | return dutyCycleTable[sel][counter] == 1 373 | } 374 | 375 | func (sound *sound) getSample() (byte, byte) { 376 | sample := byte(0) 377 | if sound.On { 378 | switch sound.SoundType { 379 | case squareSoundType: 380 | vol := sound.CurrentEnvelope 381 | if sound.inDutyCycle() { 382 | sample = vol 383 | } else { 384 | sample = 0 385 | } 386 | case waveSoundType: 387 | if sound.WaveOutLvl > 0 { 388 | sampleByte := sound.WavePatternRAM[sound.WavePatternCursor/2] 389 | if sound.WavePatternCursor&1 == 0 { 390 | sample = sampleByte >> 4 391 | } else { 392 | sample = sampleByte & 0x0f 393 | } 394 | } 395 | case noiseSoundType: 396 | if sound.FreqDivider > 0 { 397 | vol := sound.CurrentEnvelope 398 | sample = vol * sound.PolySample 399 | } 400 | } 401 | } 402 | 403 | left, right := byte(0), byte(0) 404 | if sound.LeftSpeakerOn { 405 | left = sample 406 | } 407 | if sound.RightSpeakerOn { 408 | right = sample 409 | } 410 | return left, right 411 | } 412 | 413 | func (sound *sound) updateFreq() { 414 | switch sound.SoundType { 415 | case waveSoundType: 416 | sound.FreqDivider = 2 * (2048 - uint32(sound.FreqReg)) 417 | case noiseSoundType: 418 | divider := uint32(8) 419 | if sound.PolyDivisorBase > 0 { 420 | if sound.PolyDivisorShift < 14 { 421 | divider = uint32(sound.PolyDivisorBase) << uint32(sound.PolyDivisorShift+4) 422 | } else { 423 | divider = 0 // invalid shift value - disable audio 424 | } 425 | } 426 | sound.FreqDivider = divider 427 | case squareSoundType: 428 | sound.FreqDivider = 4 * (2048 - uint32(sound.FreqReg)) // 32 mul for freq, div by 8 for duty seq 429 | } 430 | } 431 | 432 | func (sound *sound) writeWaveOnOffReg(val byte) { 433 | sound.On = val&0x80 != 0 434 | } 435 | 436 | func (sound *sound) writeWavePatternValue(addr uint16, val byte) { 437 | sound.WavePatternRAM[addr] = val 438 | } 439 | 440 | func (sound *sound) writePolyCounterReg(val byte) { 441 | sound.Poly7BitMode = val&0x08 != 0 442 | sound.PolyDivisorShift = val >> 4 443 | sound.PolyDivisorBase = val & 0x07 444 | } 445 | func (sound *sound) readPolyCounterReg() byte { 446 | val := byte(0) 447 | if sound.Poly7BitMode { 448 | val |= 0x08 449 | } 450 | val |= sound.PolyDivisorShift << 4 451 | val |= sound.PolyDivisorBase 452 | return val 453 | } 454 | 455 | func (sound *sound) writeWaveOutLvlReg(val byte) { 456 | sound.WaveOutLvl = (val >> 5) & 0x03 457 | } 458 | func (sound *sound) readWaveOutLvlReg() byte { 459 | return (sound.WaveOutLvl << 5) | 0x9f 460 | } 461 | 462 | func (sound *sound) writeLengthDataReg(val byte) { 463 | switch sound.SoundType { 464 | case waveSoundType: 465 | sound.LengthData = 256 - uint16(val) 466 | case noiseSoundType: 467 | sound.LengthData = 64 - uint16(val&0x3f) 468 | default: 469 | panic("writeLengthData: unexpected sound type") 470 | } 471 | } 472 | func (sound *sound) readLengthDataReg() byte { 473 | switch sound.SoundType { 474 | case waveSoundType: 475 | return byte(256 - sound.LengthData) 476 | case noiseSoundType: 477 | return byte(64 - sound.LengthData) 478 | default: 479 | panic("writeLengthData: unexpected sound type") 480 | } 481 | } 482 | func (sound *sound) writeLenDutyReg(val byte) { 483 | sound.LengthData = 64 - uint16(val&0x3f) 484 | sound.WaveDuty = val >> 6 485 | } 486 | func (sound *sound) readLenDutyReg() byte { 487 | return (sound.WaveDuty << 6) | 0x3f 488 | } 489 | 490 | func (sound *sound) writeSweepReg(val byte) { 491 | sound.SweepTime = (val >> 4) & 0x07 492 | sound.SweepShift = val & 0x07 493 | if val&0x08 != 0 { 494 | sound.SweepDirection = sweepDown 495 | } else { 496 | sound.SweepDirection = sweepUp 497 | } 498 | } 499 | func (sound *sound) readSweepReg() byte { 500 | val := sound.SweepTime << 4 501 | val |= sound.SweepShift 502 | if sound.SweepDirection == sweepDown { 503 | val |= 0x08 504 | } 505 | return val | 0x80 506 | } 507 | 508 | func (sound *sound) writeSoundEnvReg(val byte) { 509 | sound.EnvelopeStartVal = val >> 4 510 | if sound.EnvelopeStartVal == 0 { 511 | sound.On = false 512 | } 513 | if val&0x08 != 0 { 514 | sound.EnvelopeDirection = envUp 515 | } else { 516 | sound.EnvelopeDirection = envDown 517 | } 518 | sound.EnvelopeSweepVal = val & 0x07 519 | } 520 | func (sound *sound) readSoundEnvReg() byte { 521 | val := sound.EnvelopeStartVal<<4 | sound.EnvelopeSweepVal 522 | if sound.EnvelopeDirection == envUp { 523 | val |= 0x08 524 | } 525 | return val 526 | } 527 | 528 | func (sound *sound) writeFreqLowReg(val byte) { 529 | sound.FreqReg &^= 0x00ff 530 | sound.FreqReg |= uint16(val) 531 | sound.updateFreq() 532 | } 533 | func (sound *sound) readFreqLowReg() byte { 534 | return 0xff 535 | } 536 | 537 | func (sound *sound) writeFreqHighReg(val byte) { 538 | if val&0x80 != 0 { 539 | sound.RestartRequested = true 540 | } 541 | sound.PlaysContinuously = val&0x40 == 0 542 | sound.FreqReg &^= 0xff00 543 | sound.FreqReg |= uint16(val&0x07) << 8 544 | sound.updateFreq() 545 | } 546 | func (sound *sound) readFreqHighReg() byte { 547 | val := byte(0xff) 548 | if sound.PlaysContinuously { 549 | val &^= 0x40 // continuous == 0, uses length == 1 550 | } 551 | return val 552 | } 553 | 554 | func (apu *apu) writeVolumeReg(val byte) { 555 | apu.VInToLeftSpeaker = val&0x80 != 0 556 | apu.VInToRightSpeaker = val&0x08 != 0 557 | apu.RightSpeakerVolume = (val >> 4) & 0x07 558 | apu.LeftSpeakerVolume = val & 0x07 559 | } 560 | func (apu *apu) readVolumeReg() byte { 561 | val := apu.RightSpeakerVolume<<4 | apu.LeftSpeakerVolume 562 | if apu.VInToLeftSpeaker { 563 | val |= 0x80 564 | } 565 | if apu.VInToRightSpeaker { 566 | val |= 0x08 567 | } 568 | return val 569 | } 570 | 571 | func (apu *apu) writeSpeakerSelectReg(val byte) { 572 | boolsFromByte(val, 573 | &apu.Sounds[3].LeftSpeakerOn, 574 | &apu.Sounds[2].LeftSpeakerOn, 575 | &apu.Sounds[1].LeftSpeakerOn, 576 | &apu.Sounds[0].LeftSpeakerOn, 577 | &apu.Sounds[3].RightSpeakerOn, 578 | &apu.Sounds[2].RightSpeakerOn, 579 | &apu.Sounds[1].RightSpeakerOn, 580 | &apu.Sounds[0].RightSpeakerOn, 581 | ) 582 | } 583 | func (apu *apu) readSpeakerSelectReg() byte { 584 | return byteFromBools( 585 | apu.Sounds[3].LeftSpeakerOn, 586 | apu.Sounds[2].LeftSpeakerOn, 587 | apu.Sounds[1].LeftSpeakerOn, 588 | apu.Sounds[0].LeftSpeakerOn, 589 | apu.Sounds[3].RightSpeakerOn, 590 | apu.Sounds[2].RightSpeakerOn, 591 | apu.Sounds[1].RightSpeakerOn, 592 | apu.Sounds[0].RightSpeakerOn, 593 | ) 594 | } 595 | 596 | func (apu *apu) writeSoundOnOffReg(val byte) { 597 | // sound on off shows sounds 1-4 status in 598 | // lower bits, but writing does not 599 | // change them. 600 | boolsFromByte(val, 601 | &apu.AllSoundsOn, 602 | nil, nil, nil, nil, nil, nil, nil, 603 | ) 604 | } 605 | func (apu *apu) readSoundOnOffReg() byte { 606 | return byteFromBools( 607 | apu.AllSoundsOn, 608 | true, true, true, 609 | apu.Sounds[3].On, 610 | apu.Sounds[2].On, 611 | apu.Sounds[1].On, 612 | apu.Sounds[0].On, 613 | ) 614 | } 615 | -------------------------------------------------------------------------------- /b: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | projectname=dmgo 4 | cmd_pkg_dir=./cmd/$projectname 5 | 6 | echo "$projectname buildscript" 7 | echo 8 | echo " optional args:" 9 | echo " all - build for all platforms." 10 | echo " release - build in the build_release folder (otherwise builds in build_dev) and" 11 | echo " adds the \"release\" build tag." 12 | echo " any other arg - inserted as a build tag." 13 | echo 14 | echo " useful tags: release, profiling_cpu, profiling_mem, profiling_block, profiling_live" 15 | echo 16 | 17 | echo "running fmt, vet, etc..." 18 | echo 19 | goimports -w *.go cmd/*/*.go 20 | go vet . ./cmd/* 21 | 22 | build_folder="build_dev" 23 | while [ "$#" -ne 0 ]; do 24 | case "$1" in 25 | "all") build_all_platforms=1 ;; 26 | "release") build_folder="build_release" ;& # fallthrough 27 | *) build_tags="$build_tags$1," ;; 28 | esac 29 | shift 30 | done 31 | 32 | mkdir -p "$build_folder" 33 | if [ $build_tags ]; then 34 | build_tags="-tags $build_tags" 35 | fi 36 | if [ $build_all_platforms ]; then 37 | set -x 38 | env GOOS=windows GOARCH=amd64 go build $build_tags -o $build_folder/$projectname-win-x64.exe $cmd_pkg_dir 39 | env GOOS=linux GOARCH=amd64 go build $build_tags -o $build_folder/$projectname-linux-x64 $cmd_pkg_dir 40 | env GOOS=darwin GOARCH=amd64 go build $build_tags -o $build_folder/$projectname-mac-x64 $cmd_pkg_dir 41 | env GOOS=linux GOARCH=arm GOARM=6 go build $build_tags -o $build_folder/$projectname-rpi $cmd_pkg_dir 42 | env GOOS=linux GOARCH=arm GOARM=7 go build $build_tags -o $build_folder/$projectname-rpi2 $cmd_pkg_dir 43 | else 44 | set -x 45 | cd $build_folder # to avoid -o option, which requires manually setting a different file extension on windows 46 | go build $build_tags ../$cmd_pkg_dir 47 | fi 48 | 49 | -------------------------------------------------------------------------------- /bits.go: -------------------------------------------------------------------------------- 1 | package dmgo 2 | 3 | func boolBit(b bool, bNum byte) byte { 4 | if !b { 5 | return 0 6 | } 7 | return 1 << bNum 8 | } 9 | 10 | func ifBool(b bool, fn func()) { 11 | if b { 12 | fn() 13 | } 14 | } 15 | func byteFromBools(b7, b6, b5, b4, b3, b2, b1, b0 bool) byte { 16 | var result byte 17 | ifBool(b7, func() { result |= 0x80 }) 18 | ifBool(b6, func() { result |= 0x40 }) 19 | ifBool(b5, func() { result |= 0x20 }) 20 | ifBool(b4, func() { result |= 0x10 }) 21 | ifBool(b3, func() { result |= 0x08 }) 22 | ifBool(b2, func() { result |= 0x04 }) 23 | ifBool(b1, func() { result |= 0x02 }) 24 | ifBool(b0, func() { result |= 0x01 }) 25 | return result 26 | } 27 | 28 | func ifBoolPtrNotNil(bptr *bool, fn func()) { 29 | if bptr != nil { 30 | fn() 31 | } 32 | } 33 | func boolsFromByte(val byte, b7, b6, b5, b4, b3, b2, b1, b0 *bool) { 34 | ifBoolPtrNotNil(b7, func() { *b7 = val&0x80 > 0 }) 35 | ifBoolPtrNotNil(b6, func() { *b6 = val&0x40 > 0 }) 36 | ifBoolPtrNotNil(b5, func() { *b5 = val&0x20 > 0 }) 37 | ifBoolPtrNotNil(b4, func() { *b4 = val&0x10 > 0 }) 38 | ifBoolPtrNotNil(b3, func() { *b3 = val&0x08 > 0 }) 39 | ifBoolPtrNotNil(b2, func() { *b2 = val&0x04 > 0 }) 40 | ifBoolPtrNotNil(b1, func() { *b1 = val&0x02 > 0 }) 41 | ifBoolPtrNotNil(b0, func() { *b0 = val&0x01 > 0 }) 42 | } 43 | -------------------------------------------------------------------------------- /cart.go: -------------------------------------------------------------------------------- 1 | package dmgo 2 | 3 | import "fmt" 4 | 5 | // CartInfo represents a dmg cart header 6 | type CartInfo struct { 7 | // Title is the game title (11 or 16 chars) 8 | Title string 9 | // ManufacturerCode is a mysterious optional 4-char code 10 | ManufacturerCode string 11 | // CGBFlag describes if it's CGB, DMG, or both-supported 12 | CGBFlag byte 13 | // NewLicenseeCode is used to indicate the publisher 14 | NewLicenseeCode string 15 | // SGBFlag indicates SGB support 16 | SGBFlag byte 17 | // CartridgeType indicates MBC-type, accessories, etc 18 | CartridgeType byte 19 | // ROMSizeCode indicates the size of the ROM 20 | ROMSizeCode byte 21 | // RAMSizeCode indicates the size of the RAM 22 | RAMSizeCode byte 23 | // DestinationCode shows if the game is meant for Japan or not 24 | DestinationCode byte 25 | // OldLicenseeCode is the pre-SGB way to indicate the publisher. 26 | // 0x33 indicates the NewLicenseeCode is used instead. 27 | // SGB will not function if the old code is not 0x33. 28 | OldLicenseeCode byte 29 | // MaskRomVersion is the version of the game cart. Usually 0x00. 30 | MaskRomVersion byte 31 | // HeaderChecksum is a checksum of the header which must be correct for the game to run 32 | HeaderChecksum byte 33 | } 34 | 35 | // GetRAMSize decodes the ram size code into an actual size 36 | func (ci *CartInfo) GetRAMSize() uint { 37 | if ci.CartridgeType == 5 || ci.CartridgeType == 6 { 38 | return 512 39 | } 40 | codeSizeMap := map[byte]uint{ 41 | 0x00: 0, 42 | 0x01: 2 * 1024, 43 | 0x02: 8 * 1024, 44 | 0x03: 32 * 1024, 45 | 0x04: 128 * 1024, 46 | 0x05: 64 * 1024, 47 | } 48 | if size, ok := codeSizeMap[ci.RAMSizeCode]; ok { 49 | return size 50 | } 51 | panic(fmt.Sprintf("unknown RAM size code 0x%02x", ci.RAMSizeCode)) 52 | } 53 | 54 | // GetROMSize decodes the ROM size code into an actual size 55 | func (ci *CartInfo) GetROMSize() uint { 56 | codeSizeMap := map[byte]uint{ 57 | 0x00: 32 * 1024, // no banking 58 | 0x01: 64 * 1024, // 4 banks 59 | 0x02: 128 * 1024, // 8 banks 60 | 0x03: 256 * 1024, // 16 banks 61 | 0x04: 512 * 1024, // 32 banks 62 | 0x05: 1024 * 1024, // 64 banks (only 63 used by MBC1) 63 | 0x06: 2048 * 1024, // 128 banks (only 125 used by MBC1) 64 | 0x07: 4096 * 1024, // 256 banks 65 | 0x08: 8192 * 1024, // 512 banks 66 | 0x52: 1152 * 1024, // 72 banks 67 | 0x53: 1280 * 1024, // 80 banks 68 | 0x54: 1536 * 1024, // 96 banks 69 | } 70 | if size, ok := codeSizeMap[ci.ROMSizeCode]; ok { 71 | return size 72 | } 73 | panic(fmt.Sprintf("unknown ROM size code 0x%02x", ci.RAMSizeCode)) 74 | } 75 | 76 | func (ci *CartInfo) cgbOnly() bool { return ci.CGBFlag == 0xc0 } 77 | func (ci *CartInfo) cgbOptional() bool { return ci.CGBFlag == 0x80 } 78 | 79 | // ParseCartInfo parses a dmg cart header 80 | func ParseCartInfo(cartBytes []byte) *CartInfo { 81 | cart := CartInfo{} 82 | 83 | cart.CGBFlag = cartBytes[0x143] 84 | if cart.CGBFlag >= 0x80 { 85 | cart.Title = string(cartBytes[0x134:0x13f]) 86 | cart.ManufacturerCode = string(cartBytes[0x13f:0x143]) 87 | } else { 88 | cart.Title = string(cartBytes[0x134:0x144]) 89 | } 90 | cart.Title = stripZeroes(cart.Title) 91 | cart.SGBFlag = cartBytes[0x146] 92 | cart.CartridgeType = cartBytes[0x147] 93 | cart.ROMSizeCode = cartBytes[0x148] 94 | cart.RAMSizeCode = cartBytes[0x149] 95 | cart.DestinationCode = cartBytes[0x14a] 96 | cart.OldLicenseeCode = cartBytes[0x14b] 97 | if cart.OldLicenseeCode == 0x33 { 98 | cart.NewLicenseeCode = string(cartBytes[0x144:0x146]) 99 | } 100 | cart.MaskRomVersion = cartBytes[0x14c] 101 | cart.HeaderChecksum = cartBytes[0x14d] 102 | 103 | return &cart 104 | } 105 | 106 | func stripZeroes(s string) string { 107 | cursor := len(s) 108 | for cursor > 0 && s[cursor-1] == '\x00' { 109 | cursor-- 110 | } 111 | return s[:cursor] 112 | } 113 | -------------------------------------------------------------------------------- /cmd/dmgo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/theinternetftw/dmgo" 5 | "github.com/theinternetftw/dmgo/profiling" 6 | "github.com/theinternetftw/glimmer" 7 | 8 | "archive/zip" 9 | "bytes" 10 | "fmt" 11 | "io/ioutil" 12 | "os" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | func main() { 18 | 19 | defer profiling.Start().Stop() 20 | 21 | assert(len(os.Args) == 2, "usage: ./dmgo ROM_FILENAME") 22 | cartFilename := os.Args[1] 23 | 24 | var cartBytes []byte 25 | var err error 26 | if strings.HasSuffix(cartFilename, ".zip") { 27 | cartBytes = readZipFileOrDie(cartFilename) 28 | } else { 29 | cartBytes, err = ioutil.ReadFile(cartFilename) 30 | dieIf(err) 31 | } 32 | 33 | assert(len(cartBytes) > 3, "cannot parse, file is too small") 34 | 35 | // TODO: config file instead 36 | devMode := fileExists("devmode") 37 | 38 | var emu dmgo.Emulator 39 | windowTitle := "dmgo" 40 | 41 | fileMagic := string(cartBytes[:3]) 42 | if fileMagic == "GBS" { 43 | // nsf(e) file 44 | emu = dmgo.NewGbsPlayer(cartBytes, devMode) 45 | } else { 46 | // rom file 47 | 48 | cartInfo := dmgo.ParseCartInfo(cartBytes) 49 | if devMode { 50 | fmt.Printf("Game title: %q\n", cartInfo.Title) 51 | fmt.Printf("Cart type: %d\n", cartInfo.CartridgeType) 52 | fmt.Printf("Cart RAM size: %d\n", cartInfo.GetRAMSize()) 53 | fmt.Printf("Cart ROM size: %d\n", cartInfo.GetROMSize()) 54 | } 55 | 56 | emu = dmgo.NewEmulator(cartBytes, devMode) 57 | windowTitle = fmt.Sprintf("dmgo - %q", cartInfo.Title) 58 | } 59 | 60 | snapshotPrefix := cartFilename + ".snapshot" 61 | saveFilename := cartFilename + ".sav" 62 | 63 | if saveFile, err := ioutil.ReadFile(saveFilename); err == nil { 64 | err = emu.SetCartRAM(saveFile) 65 | if err != nil { 66 | fmt.Println("error loading savefile,", err) 67 | } else { 68 | fmt.Println("loaded save!") 69 | } 70 | } 71 | 72 | glimmer.InitDisplayLoop(glimmer.InitDisplayLoopOptions{ 73 | WindowTitle: windowTitle, 74 | RenderWidth: 160, RenderHeight: 144, 75 | WindowWidth: 160 * 4, WindowHeight: 144 * 4, 76 | InitCallback: func(sharedState *glimmer.WindowState) { 77 | 78 | audio, audioErr := glimmer.OpenAudioBuffer(glimmer.OpenAudioBufferOptions{ 79 | OutputBufDuration: 25 * time.Millisecond, 80 | SamplesPerSecond: 44100, 81 | BitsPerSample: 16, 82 | ChannelCount: 2, 83 | }) 84 | dieIf(audioErr) 85 | 86 | session := sessionState{ 87 | snapshotPrefix: snapshotPrefix, 88 | saveFilename: saveFilename, 89 | frameTimer: glimmer.MakeFrameTimer(), 90 | lastSaveTime: time.Now(), 91 | lastInputPollTime: time.Now(), 92 | audio: audio, 93 | emu: emu, 94 | } 95 | 96 | runEmu(&session, sharedState) 97 | }, 98 | }) 99 | } 100 | 101 | type sessionState struct { 102 | snapshotMode rune 103 | snapshotPrefix string 104 | saveFilename string 105 | audio *glimmer.AudioBuffer 106 | latestInput dmgo.Input 107 | frameTimer glimmer.FrameTimer 108 | lastSaveTime time.Time 109 | lastInputPollTime time.Time 110 | ticksSincePollingInput int 111 | lastSaveRAM []byte 112 | emu dmgo.Emulator 113 | currentNumFrames int 114 | audioBytesProduced int 115 | } 116 | 117 | func runEmu(session *sessionState, window *glimmer.WindowState) { 118 | 119 | dbgKeyState := make([]bool, 256) 120 | 121 | var audioChunkBuf []byte 122 | audioToGen := session.audio.GetPrevCallbackReadLen() 123 | 124 | session.lastSaveRAM = session.emu.GetCartRAM() 125 | 126 | for { 127 | session.ticksSincePollingInput++ 128 | if session.ticksSincePollingInput == 100 { 129 | session.ticksSincePollingInput = 0 130 | now := time.Now() 131 | 132 | inputDiff := now.Sub(session.lastInputPollTime) 133 | 134 | if inputDiff > 8*time.Millisecond { 135 | session.lastInputPollTime = now 136 | 137 | window.InputMutex.Lock() 138 | var numDown rune 139 | { 140 | bDown := window.CharIsDown('b') 141 | session.latestInput = dmgo.Input{ 142 | Joypad: dmgo.Joypad{ 143 | Sel: bDown || window.CharIsDown('t'), 144 | Start: bDown || window.CharIsDown('y'), 145 | Up: window.CharIsDown('w'), 146 | Down: window.CharIsDown('s'), 147 | Left: window.CharIsDown('a'), 148 | Right: window.CharIsDown('d'), 149 | A: bDown || window.CharIsDown('k'), 150 | B: bDown || window.CharIsDown('j'), 151 | }, 152 | } 153 | 154 | numDown = 'x' 155 | for r := '0'; r <= '9'; r++ { 156 | if window.CharIsDown(r) { 157 | numDown = r 158 | break 159 | } 160 | } 161 | if window.CharIsDown('m') { 162 | session.snapshotMode = 'm' 163 | } else if window.CharIsDown('l') { 164 | session.snapshotMode = 'l' 165 | } 166 | 167 | window.CopyKeyCharArray(dbgKeyState) 168 | } 169 | window.InputMutex.Unlock() 170 | 171 | if numDown > '0' && numDown <= '9' { 172 | snapFilename := session.snapshotPrefix + string(numDown) 173 | if session.snapshotMode == 'm' { 174 | session.snapshotMode = 'x' 175 | snapshot := session.emu.MakeSnapshot() 176 | ioutil.WriteFile(snapFilename, snapshot, os.FileMode(0644)) 177 | } else if session.snapshotMode == 'l' { 178 | session.snapshotMode = 'x' 179 | snapBytes, err := ioutil.ReadFile(snapFilename) 180 | if err != nil { 181 | fmt.Println("failed to load snapshot:", err) 182 | continue 183 | } 184 | newEmu, err := session.emu.LoadSnapshot(snapBytes) 185 | if err != nil { 186 | fmt.Println("failed to load snapshot:", err) 187 | continue 188 | } 189 | session.emu = newEmu 190 | } 191 | } 192 | session.emu.UpdateInput(session.latestInput) 193 | session.emu.UpdateDbgKeyState(dbgKeyState) 194 | } 195 | } 196 | 197 | if session.emu.InDevMode() { 198 | session.emu.DbgStep() 199 | } else { 200 | session.emu.Step() 201 | } 202 | bufInfo := session.emu.GetSoundBufferInfo() 203 | if bufInfo.IsValid && bufInfo.UsedSize >= audioToGen { 204 | if cap(audioChunkBuf) < audioToGen { 205 | audioChunkBuf = make([]byte, audioToGen) 206 | } 207 | session.audio.Write(session.emu.ReadSoundBuffer(audioChunkBuf[:audioToGen])) 208 | } 209 | 210 | if session.emu.FlipRequested() { 211 | window.RenderMutex.Lock() 212 | copy(window.Pix, session.emu.Framebuffer()) 213 | window.RenderMutex.Unlock() 214 | 215 | session.frameTimer.MarkRenderComplete() 216 | 217 | session.currentNumFrames++ 218 | 219 | session.audio.WaitForPlaybackIfAhead() 220 | 221 | audioToGen = session.audio.GetPrevCallbackReadLen() 222 | 223 | session.frameTimer.MarkFrameComplete() 224 | 225 | // if session.currentNumFrames&0x3f == 0 { 226 | // fmt.Println("[dmgo] max waited for audio:", session.audio.GetMaxWaited(), "buf modifier now:", session.audio.GetNextReqModifier()) 227 | // } 228 | 229 | // if session.emu.InDevMode() { 230 | // session.frameTimer.PrintStatsEveryXFrames(60 * 5) 231 | // } 232 | 233 | if time.Now().Sub(session.lastSaveTime) > 5*time.Second { 234 | ram := session.emu.GetCartRAM() 235 | if len(ram) > 0 && !bytes.Equal(ram, session.lastSaveRAM) { 236 | ioutil.WriteFile(session.saveFilename, ram, os.FileMode(0644)) 237 | session.lastSaveTime = time.Now() 238 | session.lastSaveRAM = ram 239 | } 240 | } 241 | } 242 | } 243 | } 244 | 245 | func assert(test bool, msg string) { 246 | if !test { 247 | fmt.Println(msg) 248 | os.Exit(1) 249 | } 250 | } 251 | 252 | func dieIf(err error) { 253 | if err != nil { 254 | fmt.Println(err) 255 | os.Exit(1) 256 | } 257 | } 258 | 259 | func readZipFileOrDie(filename string) []byte { 260 | zipReader, err := zip.OpenReader(filename) 261 | dieIf(err) 262 | 263 | // TODO: make list of filenames, sort abc, grab first alpha-sorted file with .gb or .gbc in name 264 | f := zipReader.File[0] 265 | fmt.Printf("unzipping first file found: %q\n", f.FileHeader.Name) 266 | cartReader, err := f.Open() 267 | dieIf(err) 268 | cartBytes, err := ioutil.ReadAll(cartReader) 269 | dieIf(err) 270 | 271 | cartReader.Close() 272 | zipReader.Close() 273 | return cartBytes 274 | } 275 | 276 | func fileExists(path string) bool { 277 | _, err := os.Stat(path) 278 | return !os.IsNotExist(err) 279 | } 280 | -------------------------------------------------------------------------------- /cpu.go: -------------------------------------------------------------------------------- 1 | package dmgo 2 | 3 | // TODO: handle HALT hardware bug (see TCAGBD) 4 | func (cs *cpuState) handleInterrupts() bool { 5 | 6 | var intFlag *bool 7 | var intAddr uint16 8 | if cs.VBlankInterruptEnabled && cs.VBlankIRQ { 9 | intFlag, intAddr = &cs.VBlankIRQ, 0x0040 10 | } else if cs.LCDStatInterruptEnabled && cs.LCDStatIRQ { 11 | intFlag, intAddr = &cs.LCDStatIRQ, 0x0048 12 | } else if cs.TimerInterruptEnabled && cs.TimerIRQ { 13 | intFlag, intAddr = &cs.TimerIRQ, 0x0050 14 | } else if cs.SerialInterruptEnabled && cs.SerialIRQ { 15 | intFlag, intAddr = &cs.SerialIRQ, 0x0058 16 | } else if cs.JoypadInterruptEnabled && cs.JoypadIRQ { 17 | intFlag, intAddr = &cs.JoypadIRQ, 0x0060 18 | } 19 | 20 | if intFlag != nil { 21 | if cs.InterruptMasterEnable { 22 | cs.InterruptMasterEnable = false 23 | *intFlag = false 24 | cs.runCycles(8) 25 | cs.pushOp16(cs.PC) 26 | cs.PC = intAddr 27 | } 28 | return true 29 | } 30 | return false 31 | } 32 | 33 | func (cs *cpuState) getZeroFlag() bool { return cs.F&0x80 > 0 } 34 | func (cs *cpuState) getSubFlag() bool { return cs.F&0x40 > 0 } 35 | func (cs *cpuState) getHalfCarryFlag() bool { return cs.F&0x20 > 0 } 36 | func (cs *cpuState) getCarryFlag() bool { return cs.F&0x10 > 0 } 37 | 38 | func (cs *cpuState) setFlags(flags uint16) { 39 | 40 | zero := flags & 0xf000 41 | sub := flags & 0xf00 42 | halfCarry := flags & 0xf0 43 | carry := flags & 0xf 44 | 45 | if zero == 0x1000 { 46 | cs.F |= 0x80 47 | } else if zero == 0x0000 { 48 | cs.F &^= 0x80 49 | } 50 | if sub == 0x100 { 51 | cs.F |= 0x40 52 | } else if sub == 0x000 { 53 | cs.F &^= 0x40 54 | } 55 | if halfCarry == 0x10 { 56 | cs.F |= 0x20 57 | } else if halfCarry == 0x00 { 58 | cs.F &^= 0x20 59 | } 60 | if carry == 1 { 61 | cs.F |= 0x10 62 | } else if carry == 0 { 63 | cs.F &^= 0x10 64 | } 65 | } 66 | 67 | func (cs *cpuState) getAF() uint16 { return (uint16(cs.A) << 8) | uint16(cs.F) } 68 | func (cs *cpuState) getBC() uint16 { return (uint16(cs.B) << 8) | uint16(cs.C) } 69 | func (cs *cpuState) getDE() uint16 { return (uint16(cs.D) << 8) | uint16(cs.E) } 70 | func (cs *cpuState) getHL() uint16 { return (uint16(cs.H) << 8) | uint16(cs.L) } 71 | 72 | func (cs *cpuState) setAF(val uint16) { 73 | cs.A = byte(val >> 8) 74 | cs.F = byte(val) &^ 0x0f 75 | } 76 | func (cs *cpuState) setBC(val uint16) { cs.B, cs.C = byte(val>>8), byte(val) } 77 | func (cs *cpuState) setDE(val uint16) { cs.D, cs.E = byte(val>>8), byte(val) } 78 | func (cs *cpuState) setHL(val uint16) { cs.H, cs.L = byte(val>>8), byte(val) } 79 | 80 | func (cs *cpuState) setSP(val uint16) { cs.SP = val } 81 | func (cs *cpuState) setPC(val uint16) { cs.PC = val } 82 | -------------------------------------------------------------------------------- /dbg.go: -------------------------------------------------------------------------------- 1 | package dmgo 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | ) 8 | 9 | const ( 10 | dbgStateNewCmd int = iota 11 | dbgStateInCmd 12 | dbgStateRunWithBreakpoints 13 | dbgStateRunNoBreakpoints 14 | ) 15 | 16 | const ( 17 | breakOpChange int = iota 18 | breakOpEq 19 | breakOpNeq 20 | 21 | // TODO: add these once we parse vals/args into ints and such 22 | // breakOpGt 23 | // breakOpGte 24 | // breakOpLt 25 | // breakOpLte 26 | ) 27 | 28 | var breakOpsMap = map[string]int{ 29 | "change": breakOpChange, 30 | "=": breakOpEq, 31 | "==": breakOpEq, 32 | "!=": breakOpNeq, 33 | 34 | // ">": breakOpGt, 35 | // ">=": breakOpGte, 36 | // "<": breakOpLt, 37 | // "<=": breakOpLte, 38 | } 39 | 40 | type breakpoint struct { 41 | fieldPath string 42 | breakVal string 43 | op int 44 | } 45 | 46 | type debugger struct { 47 | keysJustPressed []rune 48 | keys [256]bool 49 | lineBuf []byte 50 | state int 51 | breakpoints []breakpoint 52 | } 53 | 54 | func lookupValue(root reflect.Value, lookups []string) (reflect.Value, bool) { 55 | v := root 56 | t := root.Type() 57 | for i := range lookups { 58 | if t.Kind() != reflect.Struct { 59 | fmt.Println("field", lookups[i], "is not a struct but field name lookup was asked for") 60 | } 61 | _, ok := t.FieldByName(lookups[i]) 62 | if !ok { 63 | fmt.Println("field", lookups[i], "not found") 64 | return reflect.Value{}, false 65 | } 66 | v = v.FieldByName(lookups[i]) 67 | t = v.Type() 68 | } 69 | return v, true 70 | } 71 | func getField(emu Emulator, path string) (reflect.Value, bool) { 72 | root := reflect.Indirect(reflect.ValueOf(emu)) 73 | return lookupValue(root, strings.Split(path, ".")) 74 | } 75 | func getMethod(emu Emulator, path string) (reflect.Value, bool) { 76 | root := reflect.Indirect(reflect.ValueOf(emu)) 77 | v := root 78 | parts := strings.Split(path, ".") 79 | if len(parts) > 1 { 80 | var ok bool 81 | v, ok = lookupValue(root, parts[:len(parts)-1]) 82 | if !ok { 83 | return reflect.Value{}, false 84 | } 85 | } 86 | t := v.Type() 87 | var ok bool 88 | _, ok = t.MethodByName(parts[len(parts)-1]) 89 | if ok { 90 | return v.MethodByName(parts[len(parts)-1]), true 91 | } 92 | // also allow pointer receivers 93 | v = v.Addr() 94 | t = v.Type() 95 | _, ok = t.MethodByName(parts[len(parts)-1]) 96 | if ok { 97 | return v.MethodByName(parts[len(parts)-1]), true 98 | } 99 | fmt.Println("method not found or private") 100 | return reflect.Value{}, false 101 | } 102 | 103 | func strIndexOf(strs []string, str string) int { 104 | for i := range strs { 105 | if strs[i] == str { 106 | return i 107 | } 108 | } 109 | return -1 110 | } 111 | 112 | var dbgCmdMap = map[string]func(*debugger, Emulator, []string){ 113 | "run": func(d *debugger, emu Emulator, arg []string) { 114 | if len(d.breakpoints) > 0 { 115 | d.state = dbgStateRunWithBreakpoints 116 | } else { 117 | d.state = dbgStateRunNoBreakpoints 118 | } 119 | }, 120 | "x": func(d *debugger, emu Emulator, arg []string) { 121 | if len(arg) == 0 { 122 | fmt.Println("usage: x FIELD_PATH") 123 | return 124 | } 125 | if v, ok := getField(emu, arg[0]); ok { 126 | fmt.Println(v) 127 | } 128 | }, 129 | "break": func(d *debugger, emu Emulator, arg []string) { 130 | if len(arg) == 0 { 131 | fmt.Println("usage: break FIELD_NAME OP [VAL]") 132 | return 133 | } 134 | v, field_ok := getField(emu, arg[0]) 135 | if !field_ok { 136 | return 137 | } 138 | opStr := "change" 139 | if len(arg) > 1 { 140 | opStr = arg[1] 141 | } 142 | op, op_ok := breakOpsMap[opStr] 143 | if !op_ok { 144 | fmt.Println("bad OP arg for break") 145 | return 146 | } 147 | var valStr string 148 | if op != breakOpChange { 149 | if len(arg) < 3 { 150 | fmt.Println("need val for break op of", opStr) 151 | return 152 | } 153 | valStr = arg[2] 154 | } else { 155 | valStr = fmt.Sprintf("%v", v) // change works like != lastVal 156 | } 157 | bp := breakpoint{fieldPath: arg[0], op: op, breakVal: valStr} 158 | d.breakpoints = append(d.breakpoints, bp) 159 | }, 160 | "call": func(d *debugger, emu Emulator, arg []string) { 161 | if len(arg) == 0 { 162 | fmt.Println("usage: call METHOD_PATH") 163 | return 164 | } 165 | if len(arg) > 1 { 166 | fmt.Println("method args not yet impl") 167 | return 168 | } 169 | if v, ok := getMethod(emu, arg[0]); ok { 170 | results := v.Call([]reflect.Value{}) 171 | if len(results) > 0 { 172 | fmt.Println(results) 173 | } 174 | } 175 | }, 176 | } 177 | 178 | func (d *debugger) step(emu Emulator) { 179 | if d.state == dbgStateRunNoBreakpoints { 180 | emu.Step() 181 | } else if d.state == dbgStateRunWithBreakpoints { 182 | for i := range d.breakpoints { 183 | bp := &d.breakpoints[i] 184 | f, ok := getField(emu, bp.fieldPath) 185 | if !ok { 186 | fmt.Println("couldn't find field listed in breakpoint, something screwy's going on...") 187 | d.state = dbgStateNewCmd 188 | return 189 | } 190 | valStr := fmt.Sprintf("%v", f) 191 | // fmt.Println("checking bp for", bp.fieldPath, "val is", valStr) 192 | switch bp.op { 193 | case breakOpChange: 194 | if valStr != bp.breakVal { 195 | fmt.Println("hit breakpoint:", bp.fieldPath, "changed from", bp.breakVal, "to", valStr) 196 | bp.breakVal = valStr 197 | d.state = dbgStateNewCmd 198 | return 199 | } 200 | case breakOpEq: 201 | if valStr == bp.breakVal { 202 | fmt.Println("hit breakpoint:", f, "==", valStr) 203 | d.state = dbgStateNewCmd 204 | return 205 | } 206 | case breakOpNeq: 207 | if valStr != bp.breakVal { 208 | fmt.Println("hit breakpoint:", f, "!=", bp.breakVal, "- now", valStr) 209 | d.state = dbgStateNewCmd 210 | return 211 | } 212 | default: 213 | fmt.Println("unexpected bp op, something screwy's going on...") 214 | d.state = dbgStateNewCmd 215 | return 216 | } 217 | } 218 | emu.Step() 219 | } else if d.state == dbgStateNewCmd { 220 | d.lineBuf = d.lineBuf[:0] 221 | d.state = dbgStateInCmd 222 | fmt.Printf("\n> ") 223 | } else if d.state == dbgStateInCmd { 224 | for _, r := range d.keysJustPressed { 225 | switch r { 226 | case '\b': 227 | if len(d.lineBuf) > 0 { 228 | d.lineBuf = d.lineBuf[:len(d.lineBuf)-1] 229 | fmt.Print("\b \b") 230 | } 231 | case '\n': 232 | fmt.Println() 233 | d.state = dbgStateNewCmd 234 | fields := strings.Fields(string(d.lineBuf)) 235 | if len(fields) > 0 { 236 | if cmd, ok := dbgCmdMap[fields[0]]; ok { 237 | cmd(d, emu, fields[1:]) 238 | } else { 239 | fmt.Println("unknown cmd") 240 | } 241 | } 242 | default: 243 | d.lineBuf = append(d.lineBuf, byte(r)) 244 | fmt.Printf("%c", r) 245 | } 246 | } 247 | d.keysJustPressed = d.keysJustPressed[:0] 248 | } 249 | } 250 | 251 | func (d *debugger) updateInput(keys []bool) { 252 | for i := range d.keys { 253 | if keys[i] && !d.keys[i] { 254 | if rune(i) == '`' { 255 | d.state = dbgStateNewCmd 256 | } else { 257 | d.keysJustPressed = append(d.keysJustPressed, rune(i)) 258 | } 259 | } 260 | d.keys[i] = keys[i] 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /dmgo.go: -------------------------------------------------------------------------------- 1 | package dmgo 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type cpuState struct { 8 | // everything here marshalled for snapshot 9 | 10 | PC uint16 11 | SP uint16 12 | A, F, B, C, D, E, H, L byte 13 | Mem mem 14 | 15 | Title string 16 | HeaderChecksum byte 17 | 18 | LCD lcd 19 | APU apu 20 | 21 | InHaltMode bool 22 | InStopMode bool 23 | 24 | OAMDMAActive bool 25 | OAMDMAIndex uint16 26 | OAMDMASource uint16 27 | 28 | CGBMode bool 29 | FastMode bool 30 | SpeedSwitchPrepped bool 31 | 32 | IRDataReadEnable bool 33 | IRSendDataEnable bool 34 | 35 | InterruptMasterEnable bool 36 | MasterEnableRequested bool 37 | 38 | VBlankInterruptEnabled bool 39 | LCDStatInterruptEnabled bool 40 | TimerInterruptEnabled bool 41 | SerialInterruptEnabled bool 42 | JoypadInterruptEnabled bool 43 | DummyEnableBits [3]bool 44 | 45 | VBlankIRQ bool 46 | LCDStatIRQ bool 47 | TimerIRQ bool 48 | SerialIRQ bool 49 | JoypadIRQ bool 50 | 51 | SerialTransferData byte 52 | SerialTransferStartFlag bool 53 | SerialTransferClockIsInternal bool 54 | SerialFastMode bool 55 | SerialClock uint16 56 | SerialBitsTransferred byte 57 | 58 | TimerOn bool 59 | TimerLag int 60 | TimerModuloReg byte 61 | TimerCounterReg byte 62 | TimerFreqSelector byte 63 | TimerDivCycles uint16 // div reg is top 8 bits of this 64 | 65 | Joypad Joypad 66 | 67 | Steps uint 68 | Cycles uint 69 | 70 | devMode bool 71 | debugger debugger 72 | } 73 | 74 | func (cs *cpuState) SetDevMode(b bool) { cs.devMode = b } 75 | func (cs *cpuState) InDevMode() bool { return cs.devMode } 76 | 77 | func (cs *cpuState) runSerialCycle() { 78 | if !cs.SerialTransferStartFlag { 79 | cs.SerialBitsTransferred = 0 80 | cs.SerialClock = 0 81 | return 82 | } 83 | if !cs.SerialTransferClockIsInternal { 84 | // no real link cable, so wait forever 85 | // (hopefully til game times out transfer) 86 | return 87 | } 88 | cs.SerialClock++ 89 | if cs.SerialClock == 512 { // 8192Hz 90 | cs.SerialClock = 0 91 | cs.SerialTransferData <<= 1 92 | // emulate a disconnected cable 93 | cs.SerialTransferData |= 0x01 94 | cs.SerialBitsTransferred++ 95 | if cs.SerialBitsTransferred == 8 { 96 | cs.SerialBitsTransferred = 0 97 | cs.SerialClock = 0 98 | cs.SerialIRQ = true 99 | } 100 | } 101 | } 102 | 103 | // NOTE: timer is more complicated than this. 104 | // See TCAGBD 105 | func (cs *cpuState) runTimerCycle() { 106 | 107 | cs.TimerDivCycles++ 108 | 109 | if !cs.TimerOn { 110 | return 111 | } 112 | if cs.TimerLag > 0 { 113 | cs.TimerLag-- 114 | if cs.TimerLag == 0 && cs.TimerCounterReg == 0 { 115 | cs.TimerCounterReg = cs.TimerModuloReg 116 | cs.TimerIRQ = true 117 | } 118 | } 119 | 120 | cycleCount := [...]uint16{ 121 | 1024, 16, 64, 256, 122 | }[cs.TimerFreqSelector] 123 | if cs.TimerDivCycles&(cycleCount-1) == 0 { 124 | cs.TimerCounterReg++ 125 | if cs.TimerCounterReg == 0 { 126 | cs.TimerLag = 4 127 | } 128 | } 129 | } 130 | 131 | func (cs *cpuState) readTimerControlReg() byte { 132 | return 0xf8 | boolBit(cs.TimerOn, 2) | cs.TimerFreqSelector 133 | } 134 | func (cs *cpuState) writeTimerControlReg(val byte) { 135 | cs.TimerOn = val&0x04 != 0 136 | cs.TimerFreqSelector = val & 0x03 137 | } 138 | 139 | func (cs *cpuState) readSerialControlReg() byte { 140 | return byteFromBools( 141 | cs.SerialTransferStartFlag, 142 | true, 143 | true, 144 | true, 145 | true, 146 | true, 147 | cs.SerialFastMode, 148 | cs.SerialTransferClockIsInternal, 149 | ) 150 | } 151 | func (cs *cpuState) writeSerialControlReg(val byte) { 152 | cs.SerialTransferStartFlag = val&0x80 != 0 153 | cs.SerialTransferClockIsInternal = val&0x01 != 0 154 | if cs.CGBMode { 155 | cs.SerialFastMode = val&0x02 != 0 156 | } 157 | } 158 | 159 | // Joypad represents the buttons on a gameboy 160 | type Joypad struct { 161 | Sel bool 162 | Start bool 163 | Up bool 164 | Down bool 165 | Left bool 166 | Right bool 167 | A bool 168 | B bool 169 | readMask byte 170 | } 171 | 172 | func (jp *Joypad) writeJoypadReg(val byte) { 173 | jp.readMask = (val >> 4) & 0x03 174 | } 175 | func (jp *Joypad) readJoypadReg() byte { 176 | val := 0xc0 | (jp.readMask << 4) | 0x0f 177 | if jp.readMask&0x01 == 0 { 178 | val &^= boolBit(jp.Down, 3) 179 | val &^= boolBit(jp.Up, 2) 180 | val &^= boolBit(jp.Left, 1) 181 | val &^= boolBit(jp.Right, 0) 182 | } 183 | if jp.readMask&0x02 == 0 { 184 | val &^= boolBit(jp.Start, 3) 185 | val &^= boolBit(jp.Sel, 2) 186 | val &^= boolBit(jp.B, 1) 187 | val &^= boolBit(jp.A, 0) 188 | } 189 | return val 190 | } 191 | 192 | func (cs *cpuState) updateJoypad(newJP Joypad) { 193 | lastVal := cs.Joypad.readJoypadReg() & 0x0f 194 | 195 | mask := cs.Joypad.readMask 196 | cs.Joypad = newJP 197 | cs.Joypad.readMask = mask 198 | 199 | newVal := cs.Joypad.readJoypadReg() & 0x0f 200 | // this is correct behavior. it only triggers irq 201 | // if it goes from no-buttons-pressed to any-pressed. 202 | if lastVal == 0x0f && newVal < lastVal { 203 | cs.JoypadIRQ = true 204 | } 205 | } 206 | 207 | func (cs *cpuState) writeIRPortReg(val byte) { 208 | cs.IRDataReadEnable = val&0xc0 == 0xc0 209 | cs.IRSendDataEnable = val&0x01 == 0x01 210 | } 211 | func (cs *cpuState) readIRPortReg() byte { 212 | out := byte(0) 213 | if cs.IRDataReadEnable { 214 | out |= 0xc2 // no data received 215 | } 216 | if cs.IRSendDataEnable { 217 | out |= 0x01 218 | } 219 | return out 220 | } 221 | 222 | func (cs *cpuState) writeInterruptEnableReg(val byte) { 223 | boolsFromByte(val, 224 | &cs.DummyEnableBits[2], 225 | &cs.DummyEnableBits[1], 226 | &cs.DummyEnableBits[0], 227 | &cs.JoypadInterruptEnabled, 228 | &cs.SerialInterruptEnabled, 229 | &cs.TimerInterruptEnabled, 230 | &cs.LCDStatInterruptEnabled, 231 | &cs.VBlankInterruptEnabled, 232 | ) 233 | } 234 | func (cs *cpuState) readInterruptEnableReg() byte { 235 | return byteFromBools( 236 | cs.DummyEnableBits[2], 237 | cs.DummyEnableBits[1], 238 | cs.DummyEnableBits[0], 239 | cs.JoypadInterruptEnabled, 240 | cs.SerialInterruptEnabled, 241 | cs.TimerInterruptEnabled, 242 | cs.LCDStatInterruptEnabled, 243 | cs.VBlankInterruptEnabled, 244 | ) 245 | } 246 | 247 | func (cs *cpuState) writeInterruptFlagReg(val byte) { 248 | boolsFromByte(val, 249 | nil, nil, nil, 250 | &cs.JoypadIRQ, 251 | &cs.SerialIRQ, 252 | &cs.TimerIRQ, 253 | &cs.LCDStatIRQ, 254 | &cs.VBlankIRQ, 255 | ) 256 | } 257 | func (cs *cpuState) readInterruptFlagReg() byte { 258 | return byteFromBools( 259 | true, true, true, 260 | cs.JoypadIRQ, 261 | cs.SerialIRQ, 262 | cs.TimerIRQ, 263 | cs.LCDStatIRQ, 264 | cs.VBlankIRQ, 265 | ) 266 | } 267 | 268 | func newState(cart []byte, devMode bool) *cpuState { 269 | cartInfo := ParseCartInfo(cart) 270 | state := cpuState{ 271 | Title: cartInfo.Title, 272 | HeaderChecksum: cartInfo.HeaderChecksum, 273 | Mem: mem{ 274 | cart: cart, 275 | CartRAM: make([]byte, cartInfo.GetRAMSize()), 276 | InternalRAMBankNumber: 1, 277 | mbc: makeMBC(cartInfo), 278 | }, 279 | CGBMode: cartInfo.cgbOptional() || cartInfo.cgbOnly(), 280 | devMode: devMode, 281 | } 282 | state.init() 283 | return &state 284 | } 285 | 286 | func (cs *cpuState) init() { 287 | if cs.CGBMode { 288 | cs.setAF(0x1180) 289 | cs.setBC(0x0000) 290 | cs.setDE(0xff56) 291 | cs.setHL(0x000d) 292 | } else { 293 | cs.setAF(0x01b0) 294 | cs.setBC(0x0013) 295 | cs.setDE(0x00d8) 296 | cs.setHL(0x014d) 297 | } 298 | cs.setSP(0xfffe) 299 | cs.setPC(0x0100) 300 | 301 | cs.TimerDivCycles = 0xabcc 302 | 303 | cs.LCD.init(cs) 304 | cs.APU.init() 305 | 306 | cs.Mem.mbc.Init(&cs.Mem) 307 | 308 | cs.initIORegs() 309 | 310 | cs.APU.Sounds[0].RestartRequested = false 311 | cs.APU.Sounds[1].RestartRequested = false 312 | cs.APU.Sounds[2].RestartRequested = false 313 | cs.APU.Sounds[3].RestartRequested = false 314 | 315 | cs.initVRAM() 316 | cs.VBlankIRQ = true 317 | } 318 | 319 | func (cs *cpuState) initIORegs() { 320 | cs.write(0xff10, 0x80) 321 | cs.write(0xff11, 0xbf) 322 | cs.write(0xff12, 0xf3) 323 | cs.write(0xff14, 0xbf) 324 | cs.write(0xff17, 0x3f) 325 | cs.write(0xff19, 0xbf) 326 | cs.write(0xff1a, 0x7f) 327 | cs.write(0xff1b, 0xff) 328 | cs.write(0xff1c, 0x9f) 329 | cs.write(0xff1e, 0xbf) 330 | cs.write(0xff20, 0xff) 331 | cs.write(0xff23, 0xbf) 332 | cs.write(0xff24, 0x77) 333 | cs.write(0xff25, 0xf3) 334 | cs.write(0xff26, 0xf1) 335 | 336 | cs.write(0xff40, 0x91) 337 | cs.write(0xff47, 0xfc) 338 | cs.write(0xff48, 0xff) 339 | cs.write(0xff49, 0xff) 340 | } 341 | 342 | func (cs *cpuState) initVRAM() { 343 | nibbleLookup := []byte{ 344 | 0x00, 0x03, 0x0c, 0x0f, 0x30, 0x33, 0x3c, 0x3f, 345 | 0xc0, 0xc3, 0xcc, 0xcf, 0xf0, 0xf3, 0xfc, 0xff, 346 | } 347 | 348 | hdrTileData := []byte{} 349 | for i := 0x104; i < 0x104+48; i++ { 350 | packed := cs.read(uint16(i)) 351 | b1, b2 := nibbleLookup[packed>>4], nibbleLookup[packed&0x0f] 352 | hdrTileData = append(hdrTileData, b1, 0, b1, 0, b2, 0, b2, 0) 353 | } 354 | 355 | // append boot rom tile data 356 | hdrTileData = append(hdrTileData, 357 | 0x3c, 0x00, 0x42, 0x00, 0xb9, 0x00, 0xa5, 0x00, 0xb9, 0x00, 0xa5, 0x00, 0x42, 0x00, 0x3c, 0x00, 358 | ) 359 | 360 | bootTileMap := []byte{ 361 | 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 362 | 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 363 | 0x00, 0x00, 0x00, 0x00, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 364 | } 365 | 366 | for i := range hdrTileData { 367 | cs.write(uint16(0x8010+i), hdrTileData[i]) 368 | } 369 | for i := range bootTileMap { 370 | cs.write(uint16(0x9900+i), bootTileMap[i]) 371 | } 372 | } 373 | 374 | func (cs *cpuState) runOAMDMACycle() { 375 | i := cs.OAMDMAIndex 376 | addr := cs.OAMDMASource 377 | cs.write(0xfe00+i, cs.read(addr+i)) 378 | cs.OAMDMAIndex++ 379 | if cs.OAMDMAIndex == 0xa0 { 380 | cs.OAMDMAActive = false 381 | } 382 | } 383 | 384 | func (cs *cpuState) runCycles(numCycles uint) { 385 | // Things that speed up to match fast mode 386 | for i := uint(0); i < numCycles; i++ { 387 | cs.Cycles++ 388 | cs.runTimerCycle() 389 | cs.runSerialCycle() 390 | if cs.OAMDMAActive { 391 | cs.runOAMDMACycle() 392 | } 393 | } 394 | if cs.FastMode { 395 | numCycles >>= 1 396 | } 397 | // Things that don't speed up with fast mode 398 | for i := uint(0); i < numCycles; i++ { 399 | cs.APU.runCycle(cs) 400 | cs.LCD.runCycle(cs) 401 | } 402 | } 403 | 404 | func (cs *cpuState) readSpeedSwitchReg() byte { 405 | return byteFromBools(cs.FastMode, 406 | true, true, true, 407 | true, true, true, 408 | cs.SpeedSwitchPrepped, 409 | ) 410 | } 411 | func (cs *cpuState) writeSpeedSwitchReg(val byte) { 412 | cs.SpeedSwitchPrepped = val&0x01 == 0x01 413 | } 414 | func (cs *cpuState) handleSpeedSwitching() { 415 | // TODO: accurate timing 416 | if cs.SpeedSwitchPrepped { 417 | cs.SpeedSwitchPrepped = false 418 | cs.FastMode = !cs.FastMode 419 | } 420 | } 421 | 422 | // Emulator exposes the public facing fns for an emulation session 423 | type Emulator interface { 424 | Step() 425 | 426 | Framebuffer() []byte 427 | FlipRequested() bool 428 | 429 | UpdateInput(input Input) 430 | ReadSoundBuffer([]byte) []byte 431 | GetSoundBufferInfo() SoundBufferInfo 432 | 433 | GetCartRAM() []byte 434 | SetCartRAM([]byte) error 435 | 436 | MakeSnapshot() []byte 437 | LoadSnapshot([]byte) (Emulator, error) 438 | 439 | InDevMode() bool 440 | SetDevMode(b bool) 441 | UpdateDbgKeyState([]bool) 442 | DbgStep() 443 | } 444 | 445 | func (cs *cpuState) UpdateDbgKeyState(keys []bool) { 446 | cs.debugger.updateInput(keys) 447 | } 448 | 449 | func (cs *cpuState) MakeSnapshot() []byte { 450 | return cs.makeSnapshot() 451 | } 452 | 453 | func (cs *cpuState) LoadSnapshot(snapBytes []byte) (Emulator, error) { 454 | return cs.loadSnapshot(snapBytes) 455 | } 456 | 457 | // NewEmulator creates an emulation session 458 | func NewEmulator(cart []byte, devMode bool) Emulator { 459 | return newState(cart, devMode) 460 | } 461 | 462 | // Input covers all outside info sent to the Emulator 463 | type Input struct { 464 | Joypad Joypad 465 | } 466 | 467 | // ReadSoundBuffer returns a 44100hz * 16bit * 2ch sound buffer. 468 | // A pre-sized buffer must be provided, which is returned resized 469 | // if the buffer was less full than the length requested. 470 | func (cs *cpuState) ReadSoundBuffer(toFill []byte) []byte { 471 | return cs.APU.readSoundBuffer(toFill) 472 | } 473 | 474 | // SoundBufferInfo gives info about the sound buffer. IsValid is used to 475 | // handle Emulator impls that don't have sound, e.g. errEmu 476 | type SoundBufferInfo struct { 477 | UsedSize int 478 | IsValid bool 479 | } 480 | 481 | // GetSoundBufferLen gets the current size of the filled sound buffer. 482 | func (cs *cpuState) GetSoundBufferInfo() SoundBufferInfo { 483 | return SoundBufferInfo{ 484 | IsValid: true, 485 | UsedSize: int(cs.APU.buffer.size()), 486 | } 487 | } 488 | 489 | // GetCartRAM returns the current state of external RAM 490 | func (cs *cpuState) GetCartRAM() []byte { 491 | return append([]byte{}, cs.Mem.CartRAM...) 492 | } 493 | 494 | // SetCartRAM attempts to set the RAM, returning error if size not correct 495 | func (cs *cpuState) SetCartRAM(ram []byte) error { 496 | if len(cs.Mem.CartRAM) == len(ram) { 497 | copy(cs.Mem.CartRAM, ram) 498 | return nil 499 | } 500 | // TODO: better checks if possible (e.g. real format, cart title/checksum, etc.) 501 | return fmt.Errorf("ram size mismatch") 502 | } 503 | 504 | func (cs *cpuState) UpdateInput(input Input) { 505 | cs.updateJoypad(input.Joypad) 506 | } 507 | 508 | // Framebuffer returns the current state of the lcd screen 509 | func (cs *cpuState) Framebuffer() []byte { 510 | return cs.LCD.framebuffer[:] 511 | } 512 | 513 | // FlipRequested indicates if a draw request is pending 514 | // and clears it before returning 515 | func (cs *cpuState) FlipRequested() bool { 516 | val := cs.LCD.FlipRequested 517 | cs.LCD.FlipRequested = false 518 | return val 519 | } 520 | 521 | var lastSP = int(-1) 522 | 523 | func (cs *cpuState) debugLineOnStackChange() { 524 | if lastSP != int(cs.SP) { 525 | lastSP = int(cs.SP) 526 | fmt.Println(cs.DebugStatusLine()) 527 | } 528 | } 529 | 530 | // Step steps the emulator one instruction 531 | func (cs *cpuState) Step() { 532 | cs.step() 533 | } 534 | func (cs *cpuState) DbgStep() { 535 | cs.debugger.step(cs) 536 | } 537 | 538 | var hitTarget = false 539 | 540 | func (cs *cpuState) step() { 541 | ieAndIfFlagMatch := cs.handleInterrupts() 542 | if cs.InHaltMode { 543 | if ieAndIfFlagMatch { 544 | cs.runCycles(4) 545 | cs.InHaltMode = false 546 | } else { 547 | cs.runCycles(4) 548 | return 549 | } 550 | } 551 | 552 | // cs.debugLineOnStackChange() 553 | // if cs.Steps&0x2ffff == 0 { 554 | // if cs.PC == 0x4d19 { 555 | // hitTarget = true 556 | // } 557 | // if hitTarget { 558 | // fmt.Println(cs.DebugStatusLine()) 559 | // } 560 | // fmt.Fprintln(os.Stderr, cs.DebugStatusLine()) 561 | 562 | // TODO: correct behavior, i.e. only resume on 563 | // button press if not about to switch speeds. 564 | if cs.InStopMode { 565 | cs.TimerDivCycles = 0 566 | cs.handleSpeedSwitching() 567 | cs.runCycles(4) 568 | cs.InStopMode = false 569 | } 570 | 571 | // this is here to lag behind the request by 572 | // one instruction. 573 | if cs.MasterEnableRequested { 574 | cs.MasterEnableRequested = false 575 | cs.InterruptMasterEnable = true 576 | } 577 | 578 | cs.Steps++ 579 | 580 | cs.stepOpcode() 581 | } 582 | 583 | func fatalErr(v ...interface{}) { 584 | fmt.Println(v...) 585 | panic("fatalErr()") 586 | } 587 | -------------------------------------------------------------------------------- /errEmu.go: -------------------------------------------------------------------------------- 1 | package dmgo 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | type errEmu struct { 9 | textDisplay textDisplay 10 | screen [160 * 144 * 4]byte 11 | flipRequested bool 12 | 13 | devMode bool 14 | } 15 | 16 | // NewErrEmu returns an emulator that only shows an error message 17 | func NewErrEmu(msg string) Emulator { 18 | emu := errEmu{} 19 | emu.textDisplay = textDisplay{w: 160, h: 144, screen: emu.screen[:]} 20 | os.Stderr.Write([]byte(msg + "\n")) 21 | emu.textDisplay.newline() 22 | emu.textDisplay.writeString(msg) 23 | emu.flipRequested = true 24 | return &emu 25 | } 26 | 27 | func (e *errEmu) GetCartRAM() []byte { return []byte{} } 28 | func (e *errEmu) SetCartRAM([]byte) error { 29 | return fmt.Errorf("save not implemented for errEmu") 30 | } 31 | func (e *errEmu) MakeSnapshot() []byte { return nil } 32 | func (e *errEmu) LoadSnapshot([]byte) (Emulator, error) { 33 | return nil, fmt.Errorf("snapshots not implemented for errEmu") 34 | } 35 | func (e *errEmu) ReadSoundBuffer(toFill []byte) []byte { return nil } 36 | func (e *errEmu) GetSoundBufferInfo() SoundBufferInfo { return SoundBufferInfo{} } 37 | func (e *errEmu) UpdateInput(input Input) {} 38 | func (e *errEmu) Step() {} 39 | 40 | func (e *errEmu) Framebuffer() []byte { return e.screen[:] } 41 | func (e *errEmu) FlipRequested() bool { 42 | result := e.flipRequested 43 | e.flipRequested = false 44 | return result 45 | } 46 | 47 | func (e *errEmu) SetDevMode(b bool) { e.devMode = b } 48 | func (e *errEmu) InDevMode() bool { return e.devMode } 49 | func (e *errEmu) UpdateDbgKeyState(b []bool) {} 50 | func (e *errEmu) DbgStep() {} 51 | -------------------------------------------------------------------------------- /gbs.go: -------------------------------------------------------------------------------- 1 | package dmgo 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | type gbsPlayer struct { 12 | cpuState 13 | Hdr gbsHeader 14 | CurrentSong byte 15 | CurrentSongStart time.Time 16 | Paused bool 17 | PauseStartTime time.Time 18 | TextDisplay textDisplay 19 | DbgScreen [160 * 144 * 4]byte 20 | 21 | devMode bool 22 | } 23 | 24 | func (gp *gbsPlayer) SetDevMode(b bool) { gp.devMode = b } 25 | func (gp *gbsPlayer) InDevMode() bool { return gp.devMode } 26 | 27 | func (gp *gbsPlayer) GetCartRAM() []byte { return nil } 28 | func (gp *gbsPlayer) SetCartRAM(ram []byte) error { 29 | return fmt.Errorf("saves not implemented for GBSs") 30 | } 31 | func (gp *gbsPlayer) MakeSnapshot() []byte { return nil } 32 | func (gp *gbsPlayer) LoadSnapshot(snapBytes []byte) (Emulator, error) { 33 | return nil, fmt.Errorf("snapshots not implemented for GBSs") 34 | } 35 | 36 | type gbsHeader struct { 37 | Magic [3]byte 38 | Version byte 39 | NumSongs byte 40 | StartSong byte 41 | LoadAddr uint16 42 | InitAddr uint16 43 | PlayAddr uint16 44 | StackPtr uint16 45 | TimerModulo byte 46 | TimerControl byte 47 | TitleString [32]byte 48 | AuthorString [32]byte 49 | CopyrightString [32]byte 50 | } 51 | 52 | func parseGbs(gbs []byte) (gbsHeader, []byte, error) { 53 | hdr := gbsHeader{} 54 | if err := readStructLE(gbs, &hdr); err != nil { 55 | return gbsHeader{}, nil, fmt.Errorf("gbs player error\n%s", err.Error()) 56 | } 57 | if hdr.Version != 1 { 58 | return gbsHeader{}, nil, fmt.Errorf("gbs player error\nunsupported gbs version: %v", hdr.Version) 59 | } 60 | data := gbs[0x70:] 61 | return hdr, data, nil 62 | } 63 | 64 | func readStructLE(structBytes []byte, iface interface{}) error { 65 | return binary.Read(bytes.NewReader(structBytes), binary.LittleEndian, iface) 66 | } 67 | 68 | // NewGbsPlayer creates an gbsPlayer session 69 | func NewGbsPlayer(gbs []byte, devMode bool) Emulator { 70 | 71 | var hdr gbsHeader 72 | var data []byte 73 | var err error 74 | hdr, data, err = parseGbs(gbs) 75 | if err != nil { 76 | return NewErrEmu(fmt.Sprintf("gbs player error\n%s", err.Error())) 77 | } 78 | 79 | cart := append(make([]byte, hdr.LoadAddr), data...) 80 | paddingNeeded := len(cart) % 16 * 1024 81 | if paddingNeeded != 0 { 82 | cart = append(cart, make([]byte, paddingNeeded)...) 83 | } 84 | 85 | gp := gbsPlayer{ 86 | cpuState: cpuState{ 87 | Mem: mem{ 88 | mbc: &gbsMBC{}, 89 | cart: cart, 90 | CartRAM: make([]byte, 8192), 91 | InternalRAMBankNumber: 1, 92 | }, 93 | }, 94 | devMode: devMode, 95 | Hdr: hdr, 96 | } 97 | gp.TextDisplay = textDisplay{w: 160, h: 144, screen: gp.DbgScreen[:]} 98 | 99 | gp.devPrintln("uses timer:", gp.usesTimer()) 100 | 101 | if gp.Hdr.TimerControl&0x80 > 0 { 102 | gp.devPrintln("GBC Speed Requested") 103 | gp.FastMode = true 104 | } 105 | 106 | if gp.usesTimer() { 107 | gp.TimerFreqSelector = gp.Hdr.TimerControl & 0x03 108 | gp.TimerModuloReg = gp.Hdr.TimerModulo 109 | gp.TimerOn = true 110 | } 111 | 112 | gp.init() 113 | 114 | gp.patchRsts() 115 | 116 | gp.initTune(gp.Hdr.StartSong - 1) 117 | 118 | gp.updateScreen() 119 | 120 | return &gp 121 | } 122 | 123 | func (gp *gbsPlayer) devPrintln(s ...interface{}) { 124 | if gp.InDevMode() { 125 | fmt.Println(s...) 126 | } 127 | } 128 | 129 | func (gp *gbsPlayer) usesTimer() bool { 130 | return gp.Hdr.TimerControl&0x04 == 0x04 131 | } 132 | 133 | func (gp *gbsPlayer) patchRsts() { 134 | addrs := []uint16{0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38} 135 | for _, addr := range addrs { 136 | newAddr := gp.Hdr.LoadAddr + addr 137 | patch := []byte{0xcd, byte(newAddr), byte(newAddr >> 8)} 138 | copy(gp.Mem.cart[addr:addr+3], patch) 139 | } 140 | } 141 | 142 | func (gp *gbsPlayer) initTune(songNum byte) { 143 | 144 | gp.F = 0 145 | gp.B, gp.C = 0, 0 146 | gp.D, gp.E = 0, 0 147 | gp.H, gp.L = 0, 0 148 | 149 | for i := uint16(0xa000); i < 0xfe00; i++ { 150 | gp.write(i, 0) 151 | } 152 | 153 | for i := uint16(0xff80); i < 0xffff; i++ { 154 | gp.write(i, 0) 155 | } 156 | 157 | gp.initIORegs() 158 | 159 | gp.APU.Sounds[0].RestartRequested = false 160 | gp.APU.Sounds[1].RestartRequested = false 161 | gp.APU.Sounds[2].RestartRequested = false 162 | gp.APU.Sounds[3].RestartRequested = false 163 | 164 | gp.A = songNum 165 | 166 | // force a call to INIT 167 | gp.SP = gp.Hdr.StackPtr 168 | gp.pushOp16(0x0130) 169 | gp.PC = gp.Hdr.InitAddr 170 | for gp.PC != 0x0130 { 171 | gp.Step() 172 | } 173 | 174 | gp.CurrentSong = songNum 175 | gp.CurrentSongStart = time.Now() 176 | } 177 | 178 | func (gp *gbsPlayer) updateScreen() { 179 | 180 | gp.TextDisplay.clearScreen() 181 | 182 | gp.TextDisplay.setPos(0, 1) 183 | 184 | gp.TextDisplay.writeString("GBS Player\n\n") 185 | gp.TextDisplay.writeString(string(gp.Hdr.TitleString[:]) + "\n") 186 | gp.TextDisplay.writeString(string(gp.Hdr.AuthorString[:]) + "\n") 187 | 188 | copyStr := string(gp.Hdr.CopyrightString[:]) 189 | copyParts := strings.SplitN(copyStr, " ", 2) 190 | if len(copyParts) > 1 { 191 | // almost always improves presentation 192 | gp.TextDisplay.writeString(copyParts[0] + "\n") 193 | gp.TextDisplay.writeString(copyParts[1] + "\n") 194 | } else { 195 | gp.TextDisplay.writeString(copyStr + "\n") 196 | } 197 | 198 | gp.TextDisplay.newline() 199 | 200 | gp.TextDisplay.writeString(fmt.Sprintf("Track %02d/%02d\n", gp.CurrentSong+1, gp.Hdr.NumSongs)) 201 | 202 | nowTime := int(time.Now().Sub(gp.CurrentSongStart).Seconds()) 203 | nowTimeStr := fmt.Sprintf("%02d:%02d", nowTime/60, nowTime%60) 204 | 205 | gp.TextDisplay.writeString(fmt.Sprintf("%s", nowTimeStr)) 206 | 207 | if gp.Paused { 208 | gp.TextDisplay.writeString(" *PAUSED*\n") 209 | } else { 210 | gp.TextDisplay.newline() 211 | } 212 | } 213 | 214 | func (gp *gbsPlayer) prevSong() { 215 | if gp.CurrentSong > 0 { 216 | gp.CurrentSong-- 217 | gp.initTune(gp.CurrentSong) 218 | gp.updateScreen() 219 | } 220 | } 221 | func (gp *gbsPlayer) nextSong() { 222 | if gp.CurrentSong < gp.Hdr.NumSongs-1 { 223 | gp.CurrentSong++ 224 | gp.initTune(gp.CurrentSong) 225 | gp.updateScreen() 226 | } 227 | } 228 | func (gp *gbsPlayer) togglePause() { 229 | gp.Paused = !gp.Paused 230 | if gp.Paused { 231 | gp.PauseStartTime = time.Now() 232 | } else { 233 | gp.CurrentSongStart = gp.CurrentSongStart.Add(time.Now().Sub(gp.PauseStartTime)) 234 | } 235 | gp.updateScreen() 236 | } 237 | 238 | var lastInput time.Time 239 | 240 | func (gp *gbsPlayer) UpdateInput(input Input) { 241 | now := time.Now() 242 | if now.Sub(lastInput).Seconds() > 0.20 { 243 | if input.Joypad.Left { 244 | gp.prevSong() 245 | lastInput = now 246 | } 247 | if input.Joypad.Right { 248 | gp.nextSong() 249 | lastInput = now 250 | } 251 | if input.Joypad.Start { 252 | gp.togglePause() 253 | lastInput = now 254 | } 255 | } 256 | } 257 | 258 | var lastScreenUpdate time.Time 259 | 260 | func (gp *gbsPlayer) DbgStep() { 261 | gp.cpuState.debugger.step(gp) 262 | } 263 | func (gp *gbsPlayer) Step() { 264 | if !gp.Paused { 265 | 266 | now := time.Now() 267 | if now.Sub(lastScreenUpdate) >= 100*time.Millisecond { 268 | lastScreenUpdate = now 269 | gp.updateScreen() 270 | } 271 | 272 | doPlayCall := false 273 | if gp.PC == 0x0130 { 274 | if gp.usesTimer() { 275 | if gp.TimerIRQ { 276 | gp.TimerIRQ = false 277 | doPlayCall = true 278 | } 279 | } else { 280 | if gp.VBlankIRQ { 281 | gp.VBlankIRQ = false 282 | doPlayCall = true 283 | } 284 | } 285 | } 286 | 287 | if doPlayCall { 288 | gp.SP = gp.Hdr.StackPtr 289 | gp.pushOp16(0x0130) 290 | gp.PC = gp.Hdr.PlayAddr 291 | } 292 | 293 | if gp.PC != 0x0130 { 294 | gp.step() 295 | } else { 296 | gp.runCycles(4) 297 | } 298 | } 299 | } 300 | 301 | func (gp *gbsPlayer) ReadSoundBuffer(toFill []byte) []byte { 302 | return gp.APU.readSoundBuffer(toFill) 303 | } 304 | 305 | func (gp *gbsPlayer) Framebuffer() []byte { 306 | return gp.DbgScreen[:] 307 | } 308 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/theinternetftw/dmgo 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/pkg/profile v1.2.1 7 | github.com/theinternetftw/glimmer v0.1.1 8 | golang.org/x/sys v0.15.0 9 | ) 10 | 11 | require ( 12 | github.com/ebitengine/oto/v3 v3.2.0-alpha.2.0.20231021101548-b794c0292b2b // indirect 13 | github.com/ebitengine/purego v0.6.0-alpha.2 // indirect 14 | github.com/hajimehoshi/ebiten/v2 v2.6.3 // indirect 15 | github.com/jezek/xgb v1.1.1 // indirect 16 | golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63 // indirect 17 | golang.org/x/image v0.14.0 // indirect 18 | golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a // indirect 19 | golang.org/x/sync v0.5.0 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/ebitengine/oto/v3 v3.2.0-alpha.2.0.20231021101548-b794c0292b2b h1:gi77VfxDFdIxm2lHa4lmKcrKi3CRft2R+FHijvoQXRg= 2 | github.com/ebitengine/oto/v3 v3.2.0-alpha.2.0.20231021101548-b794c0292b2b/go.mod h1:JtMbxJHZBDXfS8BmVYwzWk9Z6r7jsjwsHzOuZrEkfs4= 3 | github.com/ebitengine/purego v0.6.0-alpha.2 h1:lYSvMtNBEjNGAzqPC5WP7bHUOxkFU3L+JZMdxK7krkw= 4 | github.com/ebitengine/purego v0.6.0-alpha.2/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= 5 | github.com/hajimehoshi/ebiten/v2 v2.6.3 h1:xJ5klESxhflZbPUx3GdIPoITzgPgamsyv8aZCVguXGI= 6 | github.com/hajimehoshi/ebiten/v2 v2.6.3/go.mod h1:TZtorL713an00UW4LyvMeKD8uXWnuIuCPtlH11b0pgI= 7 | github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4= 8 | github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= 9 | github.com/pkg/profile v1.2.1 h1:F++O52m40owAmADcojzM+9gyjmMOY/T4oYJkgFDH8RE= 10 | github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= 11 | github.com/theinternetftw/glimmer v0.1.1 h1:hCkEr9pYx/n2VfdICj+l8XHHRswiSyzrYSvXQHSfDSU= 12 | github.com/theinternetftw/glimmer v0.1.1/go.mod h1:uFx+kJyGJ2revp3lzpzKlfjp0p37AE/VSYdrh3Gj/gY= 13 | golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63 h1:3AGKexOYqL+ztdWdkB1bDwXgPBuTS/S8A4WzuTvJ8Cg= 14 | golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0= 15 | golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= 16 | golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= 17 | golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a h1:sYbmY3FwUWCBTodZL1S3JUuOvaW6kM2o+clDzzDNBWg= 18 | golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a/go.mod h1:Ede7gF0KGoHlj822RtphAHK1jLdrcuRBZg0sF1Q+SPc= 19 | golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= 20 | golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 21 | golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= 22 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 23 | -------------------------------------------------------------------------------- /lcd.go: -------------------------------------------------------------------------------- 1 | package dmgo 2 | 3 | import ( 4 | "sort" 5 | ) 6 | 7 | type lcd struct { 8 | // not marshalled in snapshot 9 | framebuffer [160 * 144 * 4]byte 10 | 11 | // everything else marshalled 12 | 13 | FlipRequested bool // for whatever really draws the fb 14 | 15 | PastFirstFrame bool 16 | 17 | VideoRAM [0x4000]byte 18 | HighBankActive bool 19 | 20 | CGBMode bool 21 | BGPaletteRAM [64]byte 22 | BGPaletteRAMIndex byte 23 | BGPaletteRAMAutoIncrement bool 24 | SpritePaletteRAM [64]byte 25 | SpritePaletteRAMIndex byte 26 | SpritePaletteRAMAutoIncrement bool 27 | 28 | OAM [160]byte 29 | OAMForScanline []oamEntry 30 | 31 | // for oam sprite priority 32 | BGMask [160]bool 33 | BGPriorityMask [160]bool 34 | SpriteMask [160]bool 35 | 36 | ScrollY byte 37 | ScrollX byte 38 | WindowY byte 39 | WindowX byte 40 | 41 | PassedWindowY bool 42 | 43 | BackgroundPaletteReg byte 44 | ObjectPalette0Reg byte 45 | ObjectPalette1Reg byte 46 | 47 | HBlankInterrupt bool 48 | VBlankInterrupt bool 49 | OAMInterrupt bool 50 | LYCInterrupt bool 51 | 52 | LWY byte 53 | LYReg byte 54 | LYCReg byte 55 | 56 | InVBlank bool 57 | InHBlank bool 58 | AccessingOAM bool 59 | ReadingData bool 60 | 61 | // control bits 62 | DisplayOn bool 63 | UseUpperWindowTileMap bool 64 | DisplayWindow bool 65 | UseLowerBGAndWindowTileData bool 66 | UseUpperBGTileMap bool 67 | BigSprites bool 68 | DisplaySprites bool 69 | BGWindowMasterEnable bool 70 | BGWindowPrioritiesActive bool 71 | 72 | CyclesSinceLYInc uint 73 | CyclesSinceVBlankStart uint 74 | 75 | StatIRQSignal bool 76 | } 77 | 78 | func (lcd *lcd) writeBGPaletteRAMIndexReg(val byte) { 79 | lcd.BGPaletteRAMIndex = val & 0x3f 80 | lcd.BGPaletteRAMAutoIncrement = val&0x80 != 0 81 | } 82 | func (lcd *lcd) readBGPaletteRAMIndexReg() byte { 83 | out := lcd.BGPaletteRAMIndex 84 | if lcd.BGPaletteRAMAutoIncrement { 85 | out |= 0x80 86 | } 87 | return out 88 | } 89 | 90 | func (lcd *lcd) writeBGPaletteRAMDataReg(val byte) { 91 | if !lcd.DisplayOn || !lcd.ReadingData { 92 | lcd.BGPaletteRAM[lcd.BGPaletteRAMIndex] = val 93 | } 94 | if lcd.BGPaletteRAMAutoIncrement { 95 | lcd.BGPaletteRAMIndex = (lcd.BGPaletteRAMIndex + 1) & 0x3f 96 | } 97 | } 98 | func (lcd *lcd) readBGPaletteRAMDataReg() byte { 99 | if !lcd.DisplayOn || !lcd.ReadingData { 100 | return lcd.BGPaletteRAM[lcd.BGPaletteRAMIndex] 101 | } 102 | return 0xff 103 | } 104 | 105 | func (lcd *lcd) writeSpritePaletteRAMIndexReg(val byte) { 106 | lcd.SpritePaletteRAMIndex = val & 0x3f 107 | lcd.SpritePaletteRAMAutoIncrement = val&0x80 != 0 108 | } 109 | func (lcd *lcd) readSpritePaletteRAMIndexReg() byte { 110 | out := lcd.SpritePaletteRAMIndex 111 | if lcd.SpritePaletteRAMAutoIncrement { 112 | out |= 0x80 113 | } 114 | return out 115 | } 116 | 117 | func (lcd *lcd) writeSpritePaletteRAMDataReg(val byte) { 118 | if !lcd.DisplayOn || !lcd.ReadingData { 119 | lcd.SpritePaletteRAM[lcd.SpritePaletteRAMIndex] = val 120 | } 121 | if lcd.SpritePaletteRAMAutoIncrement { 122 | lcd.SpritePaletteRAMIndex = (lcd.SpritePaletteRAMIndex + 1) & 0x3f 123 | } 124 | } 125 | func (lcd *lcd) readSpritePaletteRAMDataReg() byte { 126 | if !lcd.ReadingData { 127 | return lcd.SpritePaletteRAM[lcd.SpritePaletteRAMIndex] 128 | } 129 | return 0xff 130 | } 131 | 132 | var lastOAMWarningCycles uint 133 | var lastOAMWarningLine byte 134 | 135 | func (lcd *lcd) writeOAM(addr uint16, val byte) { 136 | if !lcd.DisplayOn || (!lcd.AccessingOAM && !lcd.ReadingData) { 137 | lcd.OAM[addr] = val 138 | } else { 139 | if lcd.CyclesSinceLYInc != lastOAMWarningCycles || lcd.LYReg != lastOAMWarningLine { 140 | lastOAMWarningCycles = lcd.CyclesSinceLYInc 141 | lastOAMWarningLine = lcd.LYReg 142 | // TODO: figure out if this is nominal 143 | //fmt.Println("TOUCHED OAM DURING USE: CyclesSinceLYInc", lcd.CyclesSinceLYInc, "LYReg", lcd.LYReg) 144 | } 145 | } 146 | } 147 | func (lcd *lcd) readOAM(addr uint16) byte { 148 | if !lcd.DisplayOn || (!lcd.AccessingOAM && !lcd.ReadingData) { 149 | return lcd.OAM[addr] 150 | } 151 | return 0xff 152 | } 153 | 154 | func (lcd *lcd) init(cs *cpuState) { 155 | lcd.CGBMode = cs.CGBMode 156 | lcd.BGWindowPrioritiesActive = !lcd.CGBMode 157 | lcd.BGWindowMasterEnable = lcd.CGBMode 158 | lcd.AccessingOAM = true // at start of line 159 | } 160 | 161 | func (lcd *lcd) writeVideoRAM(addr uint16, val byte) { 162 | if !lcd.DisplayOn || !lcd.ReadingData { 163 | if lcd.HighBankActive { 164 | addr += 0x2000 165 | } 166 | lcd.VideoRAM[addr] = val 167 | } 168 | } 169 | func (lcd *lcd) readVideoRAM(addr uint16) byte { 170 | if !lcd.DisplayOn || !lcd.ReadingData { 171 | if lcd.HighBankActive { 172 | addr += 0x2000 173 | } 174 | return lcd.VideoRAM[addr] 175 | } 176 | return 0xff 177 | } 178 | 179 | func (lcd *lcd) writeBankReg(val byte) { 180 | lcd.HighBankActive = val&0x01 != 0 181 | } 182 | func (lcd *lcd) readBankReg() byte { 183 | if lcd.HighBankActive { 184 | return 0x01 185 | } 186 | return 0x00 187 | } 188 | 189 | func (cs *cpuState) updateStatIRQ() { 190 | lastSignal := cs.LCD.StatIRQSignal 191 | cs.LCD.StatIRQSignal = (cs.LCD.LYCInterrupt && cs.LCD.LYReg == cs.LCD.LYCReg) || 192 | (cs.LCD.HBlankInterrupt && cs.LCD.InHBlank) || 193 | (cs.LCD.OAMInterrupt && cs.LCD.AccessingOAM) || 194 | ((cs.LCD.VBlankInterrupt || cs.LCD.OAMInterrupt) && cs.LCD.InVBlank) 195 | if !lastSignal && cs.LCD.StatIRQSignal { // rising edge only 196 | cs.LCDStatIRQ = true 197 | } 198 | } 199 | 200 | func (lcd *lcd) startHBlankAndDoRender(cs *cpuState) { 201 | lcd.ReadingData = false 202 | lcd.InHBlank = true 203 | lcd.renderScanline() 204 | cs.updateStatIRQ() 205 | 206 | cs.runHblankDMA() 207 | } 208 | 209 | func (lcd *lcd) startReadData() { 210 | lcd.parseOAMForScanline(lcd.LYReg) 211 | lcd.AccessingOAM = false 212 | lcd.ReadingData = true 213 | } 214 | 215 | func (lcd *lcd) handleHBlankEnd(cs *cpuState) { 216 | lcd.CyclesSinceLYInc = 0 217 | lcd.InHBlank = false 218 | lcd.LYReg++ 219 | if lcd.isWindowVisible() { 220 | lcd.LWY++ 221 | } 222 | 223 | if lcd.LYReg == 144 && !lcd.InVBlank { 224 | lcd.InVBlank = true 225 | cs.VBlankIRQ = true 226 | 227 | if lcd.PastFirstFrame { 228 | lcd.FlipRequested = true 229 | } else { 230 | lcd.PastFirstFrame = true 231 | } 232 | } 233 | } 234 | 235 | func (lcd *lcd) handleVBlank(cs *cpuState) { 236 | lcd.CyclesSinceVBlankStart += 4 237 | if lcd.CyclesSinceVBlankStart == 456*10 { 238 | lcd.LYReg = 0 239 | lcd.PassedWindowY = false 240 | lcd.InVBlank = false 241 | lcd.CyclesSinceLYInc = 0 242 | lcd.CyclesSinceVBlankStart = 0 243 | } 244 | // NOTE: TCAGBD claims the oam flag triggers this as well 245 | cs.updateStatIRQ() 246 | } 247 | 248 | func (lcd *lcd) startAccessingOAM() { 249 | lcd.AccessingOAM = true 250 | if lcd.LYReg == lcd.WindowY { 251 | lcd.PassedWindowY = true 252 | lcd.LWY = lcd.LYReg - lcd.WindowY 253 | } 254 | } 255 | 256 | func (lcd *lcd) runCycle(cs *cpuState) { 257 | if !lcd.DisplayOn { 258 | return 259 | } 260 | 261 | lcd.CyclesSinceLYInc++ 262 | if (lcd.CyclesSinceLYInc & 3) == 0 { 263 | switch lcd.CyclesSinceLYInc { 264 | case 4: 265 | if !lcd.InVBlank { 266 | lcd.startAccessingOAM() 267 | } 268 | cs.updateStatIRQ() 269 | case 80: 270 | if lcd.AccessingOAM { 271 | lcd.startReadData() 272 | } 273 | case 252: 274 | if lcd.ReadingData { 275 | lcd.startHBlankAndDoRender(cs) 276 | } 277 | case 456: 278 | lcd.handleHBlankEnd(cs) 279 | } 280 | if lcd.InVBlank { 281 | lcd.handleVBlank(cs) 282 | } 283 | } 284 | } 285 | 286 | type tileAttrs struct { 287 | bgPaletteNum byte 288 | xFlip, yFlip bool 289 | useHighBank bool 290 | hasPriority bool 291 | } 292 | 293 | func (lcd *lcd) getTilePixel(tdataAddr uint16, attr tileAttrs, tileNum, x, y byte) byte { 294 | if tdataAddr == 0x0800 { // 0x8000 relative 295 | tileNum = byte(int(int8(tileNum)) + 128) 296 | } 297 | mapBitY, mapBitX := y&0x07, x&0x07 298 | if attr.xFlip { 299 | mapBitX = 7 - mapBitX 300 | } 301 | if attr.yFlip { 302 | mapBitY = 7 - mapBitY 303 | } 304 | if attr.useHighBank { 305 | tdataAddr += 0x2000 306 | } 307 | dataByteL := lcd.VideoRAM[tdataAddr+(uint16(tileNum)<<4)+(uint16(mapBitY)<<1)] 308 | dataByteH := lcd.VideoRAM[tdataAddr+(uint16(tileNum)<<4)+(uint16(mapBitY)<<1)+1] 309 | dataBitL := (dataByteL >> (7 - mapBitX)) & 0x1 310 | dataBitH := (dataByteH >> (7 - mapBitX)) & 0x1 311 | return (dataBitH << 1) | dataBitL 312 | } 313 | func (lcd *lcd) getTileNum(tmapAddr uint16, x, y byte) byte { 314 | tileNumY, tileNumX := uint16(y>>3), uint16(x>>3) 315 | tileNum := lcd.VideoRAM[tmapAddr+tileNumY*32+tileNumX] 316 | return tileNum 317 | } 318 | func (lcd *lcd) getTileAttrs(tmapAddr uint16, x, y byte) tileAttrs { 319 | if !lcd.CGBMode { 320 | return tileAttrs{} 321 | } 322 | attr := tileAttrs{} 323 | tileNumY, tileNumX := uint16(y>>3), uint16(x>>3) 324 | attrByte := lcd.VideoRAM[0x2000+tmapAddr+tileNumY*32+tileNumX] 325 | attr.bgPaletteNum = attrByte & 0x07 326 | boolsFromByte(attrByte, 327 | &attr.hasPriority, 328 | &attr.yFlip, 329 | &attr.xFlip, 330 | nil, 331 | &attr.useHighBank, 332 | nil, 333 | nil, 334 | nil, 335 | ) 336 | return attr 337 | } 338 | 339 | func (lcd *lcd) DumpTiles() { 340 | tileAttrs := tileAttrs{} 341 | pixData := []byte{} 342 | for i := 0; i < len(lcd.VideoRAM)/16; i += 16 { 343 | dataAddr := uint16(i / 256 * (16 * 256)) 344 | for y := 0; y < 8; y++ { 345 | for j := 0; j < 16; j++ { 346 | for x := 0; x < 8; x++ { 347 | tileNum := byte(i + j) 348 | pix := lcd.getTilePixel(dataAddr, tileAttrs, tileNum, byte(x), byte(y)) 349 | r, g, b := lcd.applyBGPalettes(tileAttrs, pix) 350 | pixData = append(pixData, []byte{r, g, b}...) 351 | } 352 | } 353 | } 354 | } 355 | writeTgaRGB("tiledata.tga", 16*8, len(pixData)/(16*8*3), pixData) 356 | } 357 | 358 | func (lcd *lcd) getBGPixel(x, y byte) (byte, tileAttrs) { 359 | mapAddr := lcd.getBGTileMapAddr() 360 | dataAddr := lcd.getBGAndWindowTileDataAddr() 361 | tileNum := lcd.getTileNum(mapAddr, x, y) 362 | tileAttrs := lcd.getTileAttrs(mapAddr, x, y) 363 | return lcd.getTilePixel(dataAddr, tileAttrs, tileNum, x, y), tileAttrs 364 | } 365 | 366 | func (lcd *lcd) getWindowPixel(x, y byte) (byte, tileAttrs) { 367 | mapAddr := lcd.getWindowTileMapAddr() 368 | dataAddr := lcd.getBGAndWindowTileDataAddr() 369 | tileNum := lcd.getTileNum(mapAddr, x, y) 370 | tileAttrs := lcd.getTileAttrs(mapAddr, x, y) 371 | return lcd.getTilePixel(dataAddr, tileAttrs, tileNum, x, y), tileAttrs 372 | } 373 | 374 | func (lcd *lcd) getSpritePixel(e *oamEntry, x, y byte) (byte, byte, byte, bool) { 375 | tileX := byte(int16(x) - e.x) 376 | tileY := byte(int16(y) - e.y) 377 | if e.xFlip() { 378 | tileX = 7 - tileX 379 | } 380 | if e.yFlip() { 381 | tileY = e.height - 1 - tileY 382 | } 383 | tileNum := e.tileNum 384 | if e.height == 16 { 385 | tileNum &^= 0x01 386 | if tileY >= 8 { 387 | tileNum++ 388 | } 389 | } 390 | tileAttrs := tileAttrs{ 391 | useHighBank: e.cgbUseHighBank(), 392 | } 393 | rawPixel := lcd.getTilePixel(0x0000, tileAttrs, tileNum, tileX, tileY) // addr 8000 relative 394 | if rawPixel == 0 { 395 | return 0, 0, 0, false // transparent 396 | } 397 | r, g, b := lcd.applySpritePalettes(e, rawPixel) 398 | return r, g, b, true 399 | } 400 | 401 | func cgbToRGB(cgbColor uint16) (byte, byte, byte) { 402 | r := byte(cgbColor&0x1f) << 3 403 | g := byte(cgbColor>>5) << 3 404 | b := byte(cgbColor>>10) << 3 405 | // TODO: accurate CGB color mixing 406 | return r, g, b 407 | } 408 | 409 | func (lcd *lcd) applySpritePalettes(e *oamEntry, rawPixel byte) (byte, byte, byte) { 410 | if lcd.CGBMode { 411 | palNum := e.cgbPalNumber() 412 | cVal := uint16(lcd.SpritePaletteRAM[8*palNum+2*rawPixel]) 413 | cVal |= uint16(lcd.SpritePaletteRAM[8*palNum+2*rawPixel+1]) << 8 414 | return cgbToRGB(cVal) 415 | } 416 | palReg := lcd.ObjectPalette0Reg 417 | if e.palSelector() { 418 | palReg = lcd.ObjectPalette1Reg 419 | } 420 | return lcd.applyCustomPalette((palReg >> (rawPixel * 2)) & 0x03) 421 | } 422 | 423 | var standardPalette = [][]byte{ 424 | {0x00, 0x00, 0x00}, 425 | {0x55, 0x55, 0x55}, 426 | {0xaa, 0xaa, 0xaa}, 427 | {0xff, 0xff, 0xff}, 428 | } 429 | 430 | func (lcd *lcd) applyCustomPalette(val byte) (byte, byte, byte) { 431 | // TODO: actual custom palette choices stored in lcd 432 | outVal := standardPalette[3-val] 433 | return outVal[0], outVal[1], outVal[2] 434 | } 435 | 436 | // 0x8000 relative 437 | func (lcd *lcd) getBGTileMapAddr() uint16 { 438 | if lcd.UseUpperBGTileMap { 439 | return 0x1c00 440 | } 441 | return 0x1800 442 | } 443 | 444 | // 0x8000 relative 445 | func (lcd *lcd) getWindowTileMapAddr() uint16 { 446 | if lcd.UseUpperWindowTileMap { 447 | return 0x1c00 448 | } 449 | return 0x1800 450 | } 451 | 452 | // 0x8000 relative 453 | func (lcd *lcd) getBGAndWindowTileDataAddr() uint16 { 454 | if lcd.UseLowerBGAndWindowTileData { 455 | return 0x0000 456 | } 457 | return 0x0800 458 | } 459 | 460 | type oamEntry struct { 461 | y int16 462 | x int16 463 | height byte 464 | tileNum byte 465 | flagsByte byte 466 | } 467 | 468 | func (e *oamEntry) behindBG() bool { return e.flagsByte&0x80 != 0 } 469 | func (e *oamEntry) yFlip() bool { return e.flagsByte&0x40 != 0 } 470 | func (e *oamEntry) xFlip() bool { return e.flagsByte&0x20 != 0 } 471 | func (e *oamEntry) palSelector() bool { return e.flagsByte&0x10 != 0 } 472 | 473 | func (e *oamEntry) cgbUseHighBank() bool { return e.flagsByte&0x08 != 0 } 474 | func (e *oamEntry) cgbPalNumber() byte { return e.flagsByte & 0x07 } 475 | 476 | func yInSprite(y byte, spriteY int16, height int) bool { 477 | return int16(y) >= spriteY && int16(y) < spriteY+int16(height) 478 | } 479 | func (lcd *lcd) parseOAMForScanline(scanline byte) { 480 | height := 8 481 | if lcd.BigSprites { 482 | height = 16 483 | } 484 | 485 | // reslice so we don't realloc 486 | lcd.OAMForScanline = lcd.OAMForScanline[:0] 487 | 488 | // search all sprites, limit total found to 10 per scanline 489 | for i := 0; len(lcd.OAMForScanline) < 10 && i < 40; i++ { 490 | addr := i * 4 491 | spriteY := int16(lcd.OAM[addr]) - 16 492 | if yInSprite(scanline, spriteY, height) { 493 | lcd.OAMForScanline = append(lcd.OAMForScanline, oamEntry{ 494 | y: spriteY, 495 | x: int16(lcd.OAM[addr+1]) - 8, 496 | height: byte(height), 497 | tileNum: lcd.OAM[addr+2], 498 | flagsByte: lcd.OAM[addr+3], 499 | }) 500 | } 501 | } 502 | 503 | // NOTE: Pandocs suggest that on DMG, x coord is first sort priority, 504 | // oam index second, and that may be true for object draw sort order, 505 | // but dkland suggests indexes reign supreme for the total number of 506 | // drawable sprites. In that game they set x to zero to disable, and 507 | // dk is never drawn below those sprites because his sprites are 508 | // always at the front of the oam table. 509 | // 510 | // NOTE 2: After watching The Ultimate Game Boy talk, which is highly 511 | // recommended, my opinion here has solidified. There it's suggested 512 | // that the only thing that happens in oam search is the selection 513 | // of the top ten, and the ten are decided on based on scanline test 514 | // alone (well, Michael also suggests that an x != 0 test is made, 515 | // but he's wrong about other things in the talk, so I'm holding out 516 | // until I see evidence of this. It would make the setting of x to 517 | // zero in disabled dkland sprites make more sense, though). 518 | 519 | // resort to x-coord order (DMG only) 520 | if !lcd.CGBMode { 521 | sort.Stable(sortableOAM(lcd.OAMForScanline)) 522 | } 523 | } 524 | 525 | type sortableOAM []oamEntry 526 | 527 | func (s sortableOAM) Less(i, j int) bool { return s[i].x < s[j].x } 528 | func (s sortableOAM) Len() int { return len(s) } 529 | func (s sortableOAM) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 530 | 531 | func (lcd *lcd) isWindowVisible() bool { 532 | return lcd.WindowY <= 143 && lcd.WindowX <= 166 533 | } 534 | 535 | func (lcd *lcd) renderScanline() { 536 | if lcd.LYReg >= 144 { 537 | return 538 | } 539 | lcd.fillScanline(0) 540 | 541 | y := lcd.LYReg 542 | 543 | for i := 0; i < 160; i++ { 544 | lcd.BGMask[i] = false 545 | lcd.BGPriorityMask[i] = false 546 | lcd.SpriteMask[i] = false 547 | } 548 | 549 | if lcd.BGWindowMasterEnable { 550 | 551 | mightDrawWindow := lcd.DisplayWindow && lcd.PassedWindowY 552 | winStartX := int(lcd.WindowX) - 7 553 | 554 | bgEndX := 160 555 | if mightDrawWindow && winStartX < bgEndX { 556 | bgEndX = winStartX 557 | } 558 | 559 | bgY := y + lcd.ScrollY 560 | for x := 0; x < bgEndX; x++ { 561 | bgX := byte(x) + lcd.ScrollX 562 | pixel, attrs := lcd.getBGPixel(bgX, bgY) 563 | if pixel != 0 { 564 | lcd.BGMask[x] = true 565 | } 566 | if attrs.hasPriority { 567 | lcd.BGPriorityMask[x] = true 568 | } 569 | r, g, b := lcd.applyBGPalettes(attrs, pixel) 570 | lcd.setFramebufferPixel(byte(x), y, r, g, b) 571 | } 572 | 573 | if mightDrawWindow { 574 | x := winStartX 575 | if x < 0 { 576 | x = 0 577 | } 578 | for ; x < 160; x++ { 579 | pixel, attrs := lcd.getWindowPixel(byte(x-winStartX), lcd.LWY) 580 | if pixel != 0 { 581 | lcd.BGMask[x] = true 582 | } 583 | if attrs.hasPriority { 584 | lcd.BGPriorityMask[x] = true 585 | } 586 | r, g, b := lcd.applyBGPalettes(attrs, pixel) 587 | lcd.setFramebufferPixel(byte(x), y, r, g, b) 588 | } 589 | } 590 | } 591 | 592 | if lcd.DisplaySprites { 593 | for i := range lcd.OAMForScanline { 594 | e := &lcd.OAMForScanline[i] 595 | lcd.renderSpriteAtScanline(e, y) 596 | } 597 | } 598 | } 599 | 600 | func (lcd *lcd) applyBGPalettes(attrs tileAttrs, rawPixel byte) (byte, byte, byte) { 601 | if lcd.CGBMode { 602 | cVal := uint16(lcd.BGPaletteRAM[8*attrs.bgPaletteNum+2*rawPixel]) 603 | cVal |= uint16(lcd.BGPaletteRAM[8*attrs.bgPaletteNum+2*rawPixel+1]) << 8 604 | return cgbToRGB(cVal) 605 | } 606 | palettedPixel := (lcd.BackgroundPaletteReg >> (rawPixel * 2)) & 0x03 607 | return lcd.applyCustomPalette(palettedPixel) 608 | } 609 | 610 | func (lcd *lcd) renderSpriteAtScanline(e *oamEntry, y byte) { 611 | startX := byte(0) 612 | if e.x > 0 { 613 | startX = byte(e.x) 614 | } 615 | endX := byte(e.x + 8) 616 | for x := startX; x < endX && x < 160; x++ { 617 | if !lcd.SpriteMask[x] { 618 | if r, g, b, a := lcd.getSpritePixel(e, x, y); a { 619 | lcd.SpriteMask[x] = true 620 | hideSprite := lcd.BGWindowPrioritiesActive && (lcd.BGPriorityMask[x] || e.behindBG()) && lcd.BGMask[x] 621 | if !hideSprite { 622 | lcd.setFramebufferPixel(x, y, r, g, b) 623 | } 624 | } 625 | } 626 | } 627 | } 628 | 629 | func (lcd *lcd) getFramebufferPixel(xByte, yByte byte) (byte, byte, byte) { 630 | x, y := int(xByte), int(yByte) 631 | yIdx := y * 160 * 4 632 | r := lcd.framebuffer[yIdx+x*4+0] 633 | g := lcd.framebuffer[yIdx+x*4+1] 634 | b := lcd.framebuffer[yIdx+x*4+2] 635 | return r, g, b 636 | } 637 | func (lcd *lcd) setFramebufferPixel(xByte, yByte, r, g, b byte) { 638 | x, y := int(xByte), int(yByte) 639 | yIdx := y * 160 * 4 640 | lcd.framebuffer[yIdx+x*4+0] = r 641 | lcd.framebuffer[yIdx+x*4+1] = g 642 | lcd.framebuffer[yIdx+x*4+2] = b 643 | lcd.framebuffer[yIdx+x*4+3] = 0xff 644 | } 645 | func (lcd *lcd) fillScanline(pixel byte) { 646 | r, g, b := lcd.applyBGPalettes(tileAttrs{}, pixel) 647 | yIdx := int(lcd.LYReg) * 160 * 4 648 | for x := 0; x < 160; x++ { 649 | lcd.framebuffer[yIdx+x*4+0] = r 650 | lcd.framebuffer[yIdx+x*4+1] = g 651 | lcd.framebuffer[yIdx+x*4+2] = b 652 | lcd.framebuffer[yIdx+x*4+3] = 0xff 653 | } 654 | } 655 | 656 | func (lcd *lcd) writeScrollY(val byte) { 657 | lcd.ScrollY = val 658 | } 659 | func (lcd *lcd) writeScrollX(val byte) { 660 | lcd.ScrollX = val 661 | } 662 | func (lcd *lcd) writeLycReg(val byte) { 663 | lcd.LYCReg = val 664 | } 665 | func (lcd *lcd) writeLyReg(val byte) { 666 | lcd.LYReg = val 667 | } 668 | func (lcd *lcd) writeBackgroundPaletteReg(val byte) { 669 | lcd.BackgroundPaletteReg = val 670 | } 671 | func (lcd *lcd) writeObjectPalette0Reg(val byte) { 672 | lcd.ObjectPalette0Reg = val 673 | } 674 | func (lcd *lcd) writeObjectPalette1Reg(val byte) { 675 | lcd.ObjectPalette1Reg = val 676 | } 677 | func (lcd *lcd) writeWindowY(val byte) { 678 | lcd.WindowY = val 679 | } 680 | func (lcd *lcd) writeWindowX(val byte) { 681 | lcd.WindowX = val 682 | } 683 | 684 | func (lcd *lcd) writeControlReg(val byte) { 685 | bgBit := &lcd.BGWindowMasterEnable 686 | if lcd.CGBMode { 687 | bgBit = &lcd.BGWindowPrioritiesActive 688 | } 689 | boolsFromByte(val, 690 | &lcd.DisplayOn, 691 | &lcd.UseUpperWindowTileMap, 692 | &lcd.DisplayWindow, 693 | &lcd.UseLowerBGAndWindowTileData, 694 | &lcd.UseUpperBGTileMap, 695 | &lcd.BigSprites, 696 | &lcd.DisplaySprites, 697 | bgBit, 698 | ) 699 | 700 | if !lcd.DisplayOn { 701 | lcd.PastFirstFrame = false 702 | lcd.LYReg = 0 703 | } 704 | } 705 | func (lcd *lcd) readControlReg() byte { 706 | bgBit := lcd.BGWindowMasterEnable 707 | if lcd.CGBMode { 708 | bgBit = lcd.BGWindowPrioritiesActive 709 | } 710 | return byteFromBools( 711 | lcd.DisplayOn, 712 | lcd.UseUpperWindowTileMap, 713 | lcd.DisplayWindow, 714 | lcd.UseLowerBGAndWindowTileData, 715 | lcd.UseUpperBGTileMap, 716 | lcd.BigSprites, 717 | lcd.DisplaySprites, 718 | bgBit, 719 | ) 720 | } 721 | 722 | func (lcd *lcd) writeStatusReg(val byte) { 723 | boolsFromByte(val, 724 | nil, 725 | &lcd.LYCInterrupt, 726 | &lcd.OAMInterrupt, 727 | &lcd.VBlankInterrupt, 728 | &lcd.HBlankInterrupt, 729 | nil, 730 | nil, 731 | nil, 732 | ) 733 | } 734 | func (lcd *lcd) readStatusReg() byte { 735 | return byteFromBools( 736 | true, // bit 7 always set 737 | lcd.LYCInterrupt, 738 | lcd.OAMInterrupt, 739 | lcd.VBlankInterrupt, 740 | lcd.HBlankInterrupt, 741 | lcd.DisplayOn && (lcd.LYReg == lcd.LYCReg), 742 | lcd.DisplayOn && (lcd.AccessingOAM || lcd.ReadingData), 743 | lcd.DisplayOn && (lcd.InVBlank || lcd.ReadingData), 744 | ) 745 | } 746 | -------------------------------------------------------------------------------- /mbc.go: -------------------------------------------------------------------------------- 1 | package dmgo 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | // TODO: differentiate between those with 10 | // batteries (can save) vs those without 11 | // so we don't leak .sav files for those 12 | // who don't need em. What about flash 13 | // mem that doesn't need battery? That 14 | // used ever? 15 | // NOTE: bgb warns when carts have RAM 16 | // but list a cartType that ostensibly 17 | // doesn't. Real gameboys don't care, 18 | // do we? 19 | func makeMBC(cartInfo *CartInfo) mbc { 20 | switch cartInfo.CartridgeType { 21 | case 0: 22 | return &nullMBC{} 23 | case 1, 2, 3: 24 | return &mbc1{} 25 | case 5, 6: 26 | return &mbc2{} 27 | case 8, 9: 28 | return &nullMBC{} // but this time with RAM 29 | case 11, 12, 13: 30 | panic("MMM01 mapper requested. Not implemented!") 31 | case 15, 16, 17, 18, 19: 32 | return &mbc3{} 33 | case 25, 26, 27, 28, 29, 30: 34 | return &mbc5{} 35 | default: 36 | panic(fmt.Sprintf("makeMBC: unknown cart type %v", cartInfo.CartridgeType)) 37 | } 38 | } 39 | 40 | type mbc interface { 41 | Init(mem *mem) 42 | // Read reads via the MBC 43 | Read(mem *mem, addr uint16) byte 44 | // Write writes via the MBC 45 | Write(mem *mem, addr uint16, val byte) 46 | 47 | // for debug 48 | GetROMBankNumber() int 49 | GetRAMBankNumber() int 50 | 51 | ROMBankOffset() uint 52 | RAMBankOffset() uint 53 | 54 | Marshal() marshalledMBC 55 | } 56 | 57 | type marshalledMBC struct { 58 | Name string 59 | Data []byte 60 | } 61 | 62 | func unmarshalMBC(m marshalledMBC) (mbc, error) { 63 | switch m.Name { 64 | case "nullMBC": 65 | return &nullMBC{}, nil 66 | case "mbc1": 67 | var mbc1 mbc1 68 | if err := json.Unmarshal(m.Data, &mbc1); err != nil { 69 | return nil, err 70 | } 71 | return &mbc1, nil 72 | case "mbc2": 73 | var mbc2 mbc2 74 | if err := json.Unmarshal(m.Data, &mbc2); err != nil { 75 | return nil, err 76 | } 77 | return &mbc2, nil 78 | case "mbc3": 79 | var mbc3 mbc3 80 | if err := json.Unmarshal(m.Data, &mbc3); err != nil { 81 | return nil, err 82 | } 83 | return &mbc3, nil 84 | case "mbc5": 85 | var mbc5 mbc5 86 | if err := json.Unmarshal(m.Data, &mbc5); err != nil { 87 | return nil, err 88 | } 89 | return &mbc5, nil 90 | default: 91 | return nil, fmt.Errorf("state contained unknown mbc %q", m.Name) 92 | } 93 | } 94 | 95 | type bankNumbers struct { 96 | ROMBankNumber uint16 97 | RAMBankNumber uint16 98 | MaxRAMBank uint16 99 | MaxROMBank uint16 100 | } 101 | 102 | func (bn *bankNumbers) GetROMBankNumber() int { 103 | return int(bn.ROMBankNumber) 104 | } 105 | func (bn *bankNumbers) GetRAMBankNumber() int { 106 | return int(bn.RAMBankNumber) 107 | } 108 | func (bn *bankNumbers) setROMBankNumber(bankNum uint16) { 109 | // ran into this in dkland2, which will write trash 110 | // past the amount of rom they have. I figure they 111 | // don't have the lines hooked up, so let's only 112 | // "hook up" lines that are actually addressable 113 | topBit := uint16(0x8000) 114 | for bn.MaxROMBank < topBit { 115 | bankNum &^= topBit 116 | topBit >>= 1 117 | } 118 | bn.ROMBankNumber = bankNum 119 | } 120 | func (bn *bankNumbers) setRAMBankNumber(bankNum uint16) { 121 | topBit := uint16(0x8000) 122 | for bn.MaxRAMBank < topBit { 123 | bankNum &^= topBit 124 | topBit >>= 1 125 | } 126 | bn.RAMBankNumber = bankNum 127 | } 128 | func (bn *bankNumbers) init(mem *mem) { 129 | bn.MaxROMBank = uint16(len(mem.cart)/0x4000 - 1) 130 | bn.MaxRAMBank = uint16(len(mem.CartRAM)/0x2000 - 1) 131 | } 132 | func (bn *bankNumbers) ROMBankOffset() uint { 133 | return uint(bn.ROMBankNumber) * 0x4000 134 | } 135 | func (bn *bankNumbers) RAMBankOffset() uint { 136 | return uint(bn.RAMBankNumber) * 0x2000 137 | } 138 | 139 | type nullMBC struct { 140 | bankNumbers 141 | } 142 | 143 | func (mbc *nullMBC) Init(mem *mem) { 144 | mbc.bankNumbers.init(mem) 145 | mbc.ROMBankNumber = 1 // set up a flat map 146 | } 147 | func (mbc *nullMBC) Read(mem *mem, addr uint16) byte { 148 | switch { 149 | case addr < 0x8000: 150 | return mem.cart[addr] 151 | case addr >= 0xa000 && addr < 0xc000: 152 | localAddr := uint(addr - 0xa000) 153 | if int(localAddr) < len(mem.CartRAM) { 154 | return mem.CartRAM[localAddr] 155 | } 156 | return 0xff 157 | default: 158 | panic(fmt.Sprintf("nullMBC: not implemented: read at %x\n", addr)) 159 | } 160 | } 161 | func (mbc *nullMBC) Write(mem *mem, addr uint16, val byte) { 162 | localAddr := uint(addr - 0xa000) 163 | if int(localAddr) < len(mem.CartRAM) { 164 | mem.CartRAM[localAddr] = val 165 | } 166 | } 167 | func (mbc *nullMBC) Marshal() marshalledMBC { 168 | return marshalledMBC{Name: "nullMBC"} 169 | } 170 | 171 | const ( 172 | bankingModeRAM = iota 173 | bankingModeROM 174 | ) 175 | 176 | type mbc1 struct { 177 | bankNumbers 178 | 179 | RAMEnabled bool 180 | BankingMode int 181 | } 182 | 183 | func (mbc *mbc1) Init(mem *mem) { 184 | mbc.bankNumbers.init(mem) 185 | mbc.ROMBankNumber = 1 // can't go lower 186 | } 187 | 188 | func (mbc *mbc1) Read(mem *mem, addr uint16) byte { 189 | switch { 190 | case addr < 0x4000: 191 | return mem.cart[addr] 192 | case addr >= 0x4000 && addr < 0x8000: 193 | localAddr := uint(addr-0x4000) + mbc.ROMBankOffset() 194 | if localAddr >= uint(len(mem.cart)) { 195 | panic(fmt.Sprintf("mbc1: bad rom local addr: 0x%06x, bank number: %d\r\n", localAddr, mbc.ROMBankNumber)) 196 | } 197 | return mem.cart[localAddr] 198 | case addr >= 0xa000 && addr < 0xc000: 199 | localAddr := uint(addr-0xa000) + mbc.RAMBankOffset() 200 | if mbc.RAMEnabled && int(localAddr) < len(mem.CartRAM) { 201 | return mem.CartRAM[localAddr] 202 | } 203 | return 0xff 204 | default: 205 | panic(fmt.Sprintf("mbc1: not implemented: read at %x\n", addr)) 206 | } 207 | } 208 | 209 | func (mbc *mbc1) Write(mem *mem, addr uint16, val byte) { 210 | switch { 211 | case addr < 0x2000: 212 | mbc.RAMEnabled = val&0x0f == 0x0a 213 | case addr >= 0x2000 && addr < 0x4000: 214 | bankNum := uint16(val & 0x1f) 215 | if bankNum == 0 { 216 | // No bank 0 selection. This also disallows any bank 217 | // with 0 for the bottom 5 bits, i.e. no 0x20, 0x40, 218 | // or 0x60 banks. Trying to select them will select 219 | // 0x21, 0x41, or 0x61. Thus a max of 125 banks, 220 | // 128-3, for MBC1 221 | bankNum = 1 222 | } 223 | bankNum = (mbc.ROMBankNumber &^ 0x1f) | bankNum 224 | mbc.setROMBankNumber(bankNum) 225 | case addr >= 0x4000 && addr < 0x6000: 226 | valBits := uint16(val & 0x03) 227 | if mbc.BankingMode == bankingModeRAM { 228 | mbc.setRAMBankNumber(valBits) 229 | } else { // ROM mode 230 | bankNum := (valBits << 5) | (mbc.ROMBankNumber & 0x1f) 231 | mbc.setROMBankNumber(bankNum) 232 | } 233 | case addr >= 0x6000 && addr < 0x8000: 234 | // NOTE: do those two bits from the RAM number need to 235 | // be passed over to the ROM number after the banking 236 | // mode changes? (and vice versa?) 237 | if (val&0x01) > 0 && mbc.BankingMode != bankingModeRAM { 238 | mbc.BankingMode = bankingModeRAM 239 | mbc.setROMBankNumber(mbc.ROMBankNumber & 0x1f) 240 | } else { 241 | mbc.BankingMode = bankingModeROM 242 | mbc.setRAMBankNumber(0) 243 | } 244 | case addr >= 0xa000 && addr < 0xc000: 245 | localAddr := uint(addr-0xa000) + mbc.RAMBankOffset() 246 | if mbc.RAMEnabled && int(localAddr) < len(mem.CartRAM) { 247 | mem.CartRAM[localAddr] = val 248 | } 249 | default: 250 | panic(fmt.Sprintf("mbc1: not implemented: write at %x\n", addr)) 251 | } 252 | } 253 | 254 | func (mbc *mbc1) Marshal() marshalledMBC { 255 | rawJSON, err := json.Marshal(mbc) 256 | if err != nil { 257 | panic(err) 258 | } 259 | return marshalledMBC{ 260 | Name: "mbc1", 261 | Data: rawJSON, 262 | } 263 | } 264 | 265 | type mbc2 struct { 266 | bankNumbers 267 | 268 | RAMEnabled bool 269 | } 270 | 271 | func (mbc *mbc2) Init(mem *mem) { 272 | mbc.bankNumbers.init(mem) 273 | mbc.ROMBankNumber = 1 // can't go lower 274 | } 275 | 276 | func (mbc *mbc2) Read(mem *mem, addr uint16) byte { 277 | switch { 278 | case addr < 0x4000: 279 | return mem.cart[addr] 280 | case addr >= 0x4000 && addr < 0x8000: 281 | localAddr := uint(addr-0x4000) + mbc.ROMBankOffset() 282 | if localAddr >= uint(len(mem.cart)) { 283 | panic(fmt.Sprintf("mbc2: bad rom local addr: 0x%06x, bank number: %d\r\n", localAddr, mbc.ROMBankNumber)) 284 | } 285 | return mem.cart[localAddr] 286 | case addr >= 0xa000 && addr < 0xc000: 287 | localAddr := uint(addr - 0xa000) 288 | if mbc.RAMEnabled && int(localAddr) < len(mem.CartRAM) { 289 | // 4-bit ram (FIXME: pull high nibble down or up?) 290 | return mem.CartRAM[localAddr] & 0x0f 291 | } 292 | return 0xff 293 | default: 294 | panic(fmt.Sprintf("mbc2: not implemented: read at %x\n", addr)) 295 | } 296 | } 297 | 298 | func (mbc *mbc2) Write(mem *mem, addr uint16, val byte) { 299 | switch { 300 | case addr < 0x2000: 301 | if addr&0x0100 > 0 { 302 | // nop, this bit must be zero 303 | } else { 304 | mbc.RAMEnabled = val&0x0f == 0x0a 305 | } 306 | case addr >= 0x2000 && addr < 0x4000: 307 | if addr&0x0100 == 0 { 308 | // nop, this bit must be one 309 | } else { 310 | // 16 rom banks 311 | bankNum := uint16(val & 0x0f) 312 | if bankNum == 0 { 313 | // no bank 0 selection. 314 | bankNum = 1 315 | } 316 | mbc.setROMBankNumber(bankNum) 317 | } 318 | case addr >= 0x4000 && addr < 0x8000: 319 | // nop 320 | case addr >= 0xa000 && addr < 0xc000: 321 | localAddr := uint(addr - 0xa000) 322 | if mbc.RAMEnabled && int(localAddr) < len(mem.CartRAM) { 323 | // 4-bit RAM 324 | mem.CartRAM[localAddr] = val & 0x0f 325 | } 326 | default: 327 | panic(fmt.Sprintf("mbc2: not implemented: write at %x\n", addr)) 328 | } 329 | } 330 | 331 | func (mbc *mbc2) Marshal() marshalledMBC { 332 | rawJSON, err := json.Marshal(mbc) 333 | if err != nil { 334 | panic(err) 335 | } 336 | return marshalledMBC{ 337 | Name: "mbc2", 338 | Data: rawJSON, 339 | } 340 | } 341 | 342 | type mbc3 struct { 343 | bankNumbers 344 | 345 | RAMEnabled bool 346 | 347 | Seconds byte 348 | Minutes byte 349 | Hours byte 350 | Days uint16 351 | 352 | LatchedSeconds byte 353 | LatchedMinutes byte 354 | LatchedHours byte 355 | LatchedDays uint16 356 | DayCarry bool 357 | 358 | TimerStopped bool 359 | TimerLatched bool 360 | 361 | TimeAtLastSet time.Time 362 | } 363 | 364 | func (mbc *mbc3) Init(mem *mem) { 365 | mbc.bankNumbers.init(mem) 366 | mbc.ROMBankNumber = 1 // can't go lower 367 | 368 | // NOTE: could load this/vals/carry from save or something 369 | // here. Considering .sav is a simple format (just the RAM 370 | // proper), should make e.g. an additional .rtc file for this 371 | mbc.TimeAtLastSet = time.Now() 372 | } 373 | 374 | func (mbc *mbc3) updateTimer() { 375 | if mbc.TimerStopped { 376 | return 377 | } 378 | oldUnix := time.Unix( 379 | int64(mbc.Seconds)+ 380 | int64(mbc.Minutes)*60+ 381 | int64(mbc.Hours)*60*60+ 382 | int64(mbc.Days)*60*60*24, 0) 383 | 384 | ticked := time.Now().Sub(mbc.TimeAtLastSet) 385 | mbc.TimeAtLastSet = time.Now() 386 | 387 | newTotalSeconds := oldUnix.Add(ticked).Unix() 388 | mbc.Seconds = byte(newTotalSeconds % 60) 389 | 390 | newTotalMinutes := (newTotalSeconds - int64(mbc.Seconds)/60) 391 | mbc.Minutes = byte(newTotalMinutes % 60) 392 | 393 | newTotalHours := (newTotalMinutes - int64(mbc.Minutes)/60) 394 | mbc.Hours = byte(newTotalHours % 24) 395 | 396 | newTotalDays := (newTotalHours - int64(mbc.Hours)/24) 397 | mbc.Days = uint16(newTotalDays) 398 | 399 | if newTotalDays > 511 { 400 | mbc.DayCarry = true 401 | } 402 | } 403 | 404 | func (mbc *mbc3) updateLatch() { 405 | mbc.updateTimer() 406 | mbc.LatchedSeconds = mbc.Seconds 407 | mbc.LatchedMinutes = mbc.Minutes 408 | mbc.LatchedHours = mbc.Hours 409 | mbc.LatchedDays = mbc.Days 410 | } 411 | 412 | func (mbc *mbc3) Read(mem *mem, addr uint16) byte { 413 | switch { 414 | case addr < 0x4000: 415 | return mem.cart[addr] 416 | case addr >= 0x4000 && addr < 0x8000: 417 | localAddr := uint(addr-0x4000) + mbc.ROMBankOffset() 418 | if localAddr >= uint(len(mem.cart)) { 419 | panic(fmt.Sprintf("mbc3: bad rom local addr: 0x%06x, bank number: %d\r\n", localAddr, mbc.ROMBankNumber)) 420 | } 421 | return mem.cart[localAddr] 422 | case addr >= 0xa000 && addr < 0xc000: 423 | switch mbc.RAMBankNumber { 424 | case 0, 1, 2, 3: 425 | localAddr := uint(addr-0xa000) + mbc.RAMBankOffset() 426 | if mbc.RAMEnabled && int(localAddr) < len(mem.CartRAM) { 427 | return mem.CartRAM[localAddr] 428 | } 429 | return 0xff 430 | case 8: 431 | return mbc.LatchedSeconds 432 | case 9: 433 | return mbc.LatchedMinutes 434 | case 10: 435 | return mbc.LatchedHours 436 | case 11: 437 | return byte(mbc.LatchedDays) 438 | case 12: 439 | return boolBit(mbc.DayCarry, 7) | boolBit(mbc.TimerStopped, 6) | (byte(mbc.LatchedDays>>7) & 0x01) 440 | } 441 | // might need a default of return 0xff here 442 | } 443 | panic(fmt.Sprintf("mbc3: not implemented: read at %x\n", addr)) 444 | } 445 | 446 | func (mbc *mbc3) Write(mem *mem, addr uint16, val byte) { 447 | switch { 448 | case addr < 0x2000: 449 | mbc.RAMEnabled = val&0x0f == 0x0a 450 | case addr >= 0x2000 && addr < 0x4000: 451 | bankNum := uint16(val &^ 0x80) // 7bit selector 452 | if bankNum == 0 { 453 | // no bank 0 selection. 454 | bankNum = 1 455 | } 456 | mbc.setROMBankNumber(bankNum) 457 | case addr >= 0x4000 && addr < 0x6000: 458 | switch val { 459 | case 0, 1, 2, 3: 460 | mbc.setRAMBankNumber(uint16(val)) 461 | case 8, 9, 10, 11, 12: 462 | // sidestep the bank set semantics for the rtc regs 463 | mbc.RAMBankNumber = uint16(val) 464 | default: 465 | // all others nop 466 | // NOTE: or should they e.g. select a bank that returns all 0xff's? 467 | } 468 | case addr >= 0x6000 && addr < 0x8000: 469 | switch { 470 | case val&0x01 == 0: 471 | mbc.TimerLatched = false 472 | case val&0x01 == 1 && !mbc.TimerLatched: 473 | mbc.TimerLatched = true 474 | mbc.updateTimer() 475 | } 476 | case addr >= 0xa000 && addr < 0xc000: 477 | switch mbc.RAMBankNumber { 478 | case 0, 1, 2, 3: 479 | localAddr := uint(addr-0xa000) + mbc.RAMBankOffset() 480 | if mbc.RAMEnabled && int(localAddr) < len(mem.CartRAM) { 481 | mem.CartRAM[localAddr] = val 482 | } 483 | case 8: 484 | mbc.updateTimer() 485 | mbc.Seconds = val 486 | case 9: 487 | mbc.updateTimer() 488 | mbc.Minutes = val 489 | case 10: 490 | mbc.updateTimer() 491 | mbc.Hours = val 492 | case 11: 493 | mbc.updateTimer() 494 | mbc.Days = uint16(val) 495 | case 12: 496 | mbc.updateTimer() 497 | mbc.Days &^= 0x0100 498 | mbc.Days |= uint16(val&0x01) << 8 499 | mbc.TimerStopped = val&(1<<6) > 0 500 | mbc.DayCarry = val&(1<<7) > 0 501 | default: 502 | // nop 503 | } 504 | default: 505 | panic(fmt.Sprintf("mbc3: not implemented: write at %x\n", addr)) 506 | } 507 | } 508 | 509 | func (mbc *mbc3) Marshal() marshalledMBC { 510 | rawJSON, err := json.Marshal(mbc) 511 | if err != nil { 512 | panic(err) 513 | } 514 | return marshalledMBC{ 515 | Name: "mbc3", 516 | Data: rawJSON, 517 | } 518 | } 519 | 520 | type mbc5 struct { 521 | bankNumbers 522 | 523 | RAMEnabled bool 524 | } 525 | 526 | func (mbc *mbc5) Init(mem *mem) { 527 | mbc.bankNumbers.init(mem) 528 | 529 | // NOTE: can do bank 0 now, but still start with 1 530 | mbc.ROMBankNumber = 1 531 | } 532 | 533 | func (mbc *mbc5) Read(mem *mem, addr uint16) byte { 534 | switch { 535 | case addr < 0x4000: 536 | return mem.cart[addr] 537 | case addr >= 0x4000 && addr < 0x8000: 538 | localAddr := uint(addr-0x4000) + mbc.ROMBankOffset() 539 | if localAddr >= uint(len(mem.cart)) { 540 | panic(fmt.Sprintf("mbc5: bad rom local addr: 0x%06x, bank number: %d\r\n", localAddr, mbc.ROMBankNumber)) 541 | } 542 | return mem.cart[localAddr] 543 | case addr >= 0xa000 && addr < 0xc000: 544 | localAddr := uint(addr-0xa000) + mbc.RAMBankOffset() 545 | if mbc.RAMEnabled && int(localAddr) < len(mem.CartRAM) { 546 | return mem.CartRAM[localAddr] 547 | } 548 | return 0xff 549 | default: 550 | panic(fmt.Sprintf("mbc5: not implemented: read at %x\n", addr)) 551 | } 552 | } 553 | 554 | func (mbc *mbc5) Write(mem *mem, addr uint16, val byte) { 555 | switch { 556 | case addr < 0x2000: 557 | mbc.RAMEnabled = val&0x0f == 0x0a 558 | case addr >= 0x2000 && addr < 0x3000: 559 | mbc.setROMBankNumber((mbc.ROMBankNumber &^ 0xff) | uint16(val)) 560 | case addr >= 0x3000 && addr < 0x4000: 561 | // NOTE: TCAGBD says that games that don't use the 9th bit 562 | // can use this to set the lower eight! I'll wait until I 563 | // see a game try to do that before impl'ing 564 | mbc.setROMBankNumber((mbc.ROMBankNumber &^ 0x100) | uint16(val&0x01)<<8) 565 | case addr >= 0x4000 && addr < 0x6000: 566 | mbc.setRAMBankNumber(uint16(val & 0x0f)) 567 | case addr >= 0x6000 && addr < 0x8000: 568 | // nop? 569 | case addr >= 0xa000 && addr < 0xc000: 570 | localAddr := uint(addr-0xa000) + mbc.RAMBankOffset() 571 | if mbc.RAMEnabled && int(localAddr) < len(mem.CartRAM) { 572 | mem.CartRAM[localAddr] = val 573 | } 574 | default: 575 | panic(fmt.Sprintf("mbc5: not implemented: write at %x\n", addr)) 576 | } 577 | } 578 | 579 | func (mbc *mbc5) Marshal() marshalledMBC { 580 | rawJSON, err := json.Marshal(mbc) 581 | if err != nil { 582 | panic(err) 583 | } 584 | return marshalledMBC{ 585 | Name: "mbc5", 586 | Data: rawJSON, 587 | } 588 | } 589 | 590 | type gbsMBC struct { 591 | bankNumbers 592 | } 593 | 594 | func (mbc *gbsMBC) Init(mem *mem) { 595 | mbc.bankNumbers.init(mem) 596 | mbc.ROMBankNumber = 1 // can't go lower 597 | } 598 | 599 | func (mbc *gbsMBC) Read(mem *mem, addr uint16) byte { 600 | switch { 601 | case addr < 0x4000: 602 | return mem.cart[addr] 603 | case addr >= 0x4000 && addr < 0x8000: 604 | localAddr := uint(addr-0x4000) + mbc.ROMBankOffset() 605 | if localAddr >= uint(len(mem.cart)) { 606 | panic(fmt.Sprintf("gbsMBC: bad rom local addr: 0x%06x, bank number: %d\r\n", localAddr, mbc.ROMBankNumber)) 607 | } 608 | return mem.cart[localAddr] 609 | case addr >= 0xa000 && addr < 0xc000: 610 | return mem.CartRAM[addr-0xa000] 611 | default: 612 | panic(fmt.Sprintf("gbsMBC: not implemented: read at %x\n", addr)) 613 | } 614 | } 615 | 616 | func (mbc *gbsMBC) Write(mem *mem, addr uint16, val byte) { 617 | switch { 618 | case addr < 0x2000: 619 | // nop 620 | case addr >= 0x2000 && addr < 0x4000: 621 | // 16 rom banks 622 | bankNum := uint16(val & 0x0f) 623 | mbc.setROMBankNumber(bankNum) 624 | case addr >= 0x4000 && addr < 0x8000: 625 | // nop 626 | case addr >= 0xa000 && addr < 0xc000: 627 | localAddr := uint(addr - 0xa000) 628 | mem.CartRAM[localAddr] = val 629 | default: 630 | panic(fmt.Sprintf("gbsMBC: not implemented: write at %x\n", addr)) 631 | } 632 | } 633 | 634 | func (mbc *gbsMBC) Marshal() marshalledMBC { 635 | rawJSON, err := json.Marshal(mbc) 636 | if err != nil { 637 | panic(err) 638 | } 639 | return marshalledMBC{ 640 | Name: "gbsMBC", 641 | Data: rawJSON, 642 | } 643 | } 644 | -------------------------------------------------------------------------------- /mem.go: -------------------------------------------------------------------------------- 1 | package dmgo 2 | 3 | import "fmt" 4 | 5 | type mem struct { 6 | // not marshalled in snapshot 7 | cart []byte 8 | 9 | // everything else marshalled 10 | 11 | CartRAM []byte 12 | InternalRAM [0x8000]byte 13 | InternalRAMBankNumber uint16 14 | HighInternalRAM [0x7f]byte 15 | mbc mbc 16 | 17 | // cgb dma 18 | DMASource uint16 19 | DMASourceReg uint16 20 | DMADest uint16 21 | DMADestReg uint16 22 | DMALength uint16 23 | DMAHblankMode bool 24 | DMAInProgress bool 25 | } 26 | 27 | func (mem *mem) mbcRead(addr uint16) byte { 28 | return mem.mbc.Read(mem, addr) 29 | } 30 | func (mem *mem) mbcWrite(addr uint16, val byte) { 31 | mem.mbc.Write(mem, addr, val) 32 | } 33 | 34 | func (cs *cpuState) writeDMASourceHigh(val byte) { 35 | cs.Mem.DMASourceReg = (cs.Mem.DMASourceReg &^ 0xff00) | (uint16(val) << 8) 36 | } 37 | func (cs *cpuState) readDMASourceHigh() byte { 38 | return byte(cs.Mem.DMASourceReg >> 8) 39 | } 40 | 41 | func (cs *cpuState) writeDMASourceLow(val byte) { 42 | cs.Mem.DMASourceReg = (cs.Mem.DMASourceReg &^ 0xff) | uint16(val) 43 | } 44 | func (cs *cpuState) readDMASourceLow() byte { 45 | return byte(cs.Mem.DMASourceReg) 46 | } 47 | 48 | func (cs *cpuState) writeDMADestHigh(val byte) { 49 | val = (val &^ 0xe0) | 0x80 50 | cs.Mem.DMADestReg = (cs.Mem.DMADestReg &^ 0xff00) | (uint16(val) << 8) 51 | } 52 | func (cs *cpuState) readDMADestHigh() byte { 53 | return byte(cs.Mem.DMADestReg >> 8) 54 | } 55 | 56 | func (cs *cpuState) writeDMADestLow(val byte) { 57 | cs.Mem.DMADestReg = (cs.Mem.DMADestReg &^ 0xff) | uint16(val) 58 | } 59 | func (cs *cpuState) readDMADestLow() byte { 60 | return byte(cs.Mem.DMADestReg) 61 | } 62 | 63 | func (cs *cpuState) writeDMAControlReg(val byte) { 64 | cs.Mem.DMALength = (uint16(val&0x7f) + 1) << 4 65 | cs.Mem.DMAHblankMode = val&0x80 != 0 66 | if cs.Mem.DMAInProgress && (val&0x80 == 0) { 67 | cs.Mem.DMAHblankMode = false 68 | cs.Mem.DMAInProgress = false 69 | return 70 | } 71 | cs.Mem.DMAInProgress = true 72 | cs.Mem.DMASource = (cs.Mem.DMASourceReg & 0xfff0) 73 | cs.Mem.DMADest = (cs.Mem.DMADestReg & 0x1ff0) | 0x8000 74 | if !cs.Mem.DMAHblankMode { 75 | for cs.Mem.DMAInProgress { 76 | cs.runDMACycle() 77 | } 78 | } 79 | } 80 | func (cs *cpuState) readDMAControlReg() byte { 81 | out := byte((cs.Mem.DMALength>>4)-1) & 0x7f 82 | if !cs.Mem.DMAInProgress { 83 | out |= 0x80 84 | } 85 | return out 86 | } 87 | 88 | func (cs *cpuState) runDMACycle() { 89 | cs.write(cs.Mem.DMADest, cs.read(cs.Mem.DMASource)) 90 | cs.write(cs.Mem.DMADest+1, cs.read(cs.Mem.DMASource+1)) 91 | if cs.CGBMode { 92 | cs.runCycles(8) 93 | } else { 94 | cs.runCycles(4) 95 | } 96 | cs.Mem.DMASource += 2 97 | cs.Mem.DMADest += 2 98 | cs.Mem.DMALength -= 2 99 | if cs.Mem.DMALength == 0 { 100 | cs.Mem.DMAInProgress = false 101 | } 102 | } 103 | func (cs *cpuState) runHblankDMA() { 104 | if cs.Mem.DMAInProgress && cs.Mem.DMAHblankMode { 105 | for i := 0; cs.Mem.DMAInProgress && i < 8; i++ { 106 | cs.runDMACycle() 107 | } 108 | } 109 | } 110 | 111 | func (cs *cpuState) read(addr uint16) byte { 112 | var val byte 113 | switch { 114 | 115 | case addr < 0x8000: 116 | val = cs.Mem.mbcRead(addr) 117 | 118 | case addr >= 0x8000 && addr < 0xa000: 119 | val = cs.LCD.readVideoRAM(addr - 0x8000) 120 | 121 | case addr >= 0xa000 && addr < 0xc000: 122 | val = cs.Mem.mbcRead(addr) 123 | 124 | case addr >= 0xc000 && addr < 0xfe00: 125 | ramAddr := (addr - 0xc000) & 0x1fff // 8kb with wraparound 126 | if ramAddr >= 0x1000 { 127 | ramAddr = (ramAddr - 0x1000) + 0x1000*cs.Mem.InternalRAMBankNumber 128 | } 129 | val = cs.Mem.InternalRAM[ramAddr] 130 | 131 | case addr >= 0xfe00 && addr < 0xfea0: 132 | val = cs.LCD.readOAM(addr - 0xfe00) 133 | 134 | case addr >= 0xfea0 && addr < 0xff00: 135 | val = 0xff // (empty mem, but can be more complicated, see TCAGBD) 136 | 137 | case addr == 0xff00: 138 | val = cs.Joypad.readJoypadReg() 139 | case addr == 0xff01: 140 | val = cs.SerialTransferData 141 | case addr == 0xff02: 142 | val = cs.readSerialControlReg() 143 | 144 | case addr == 0xff03: 145 | val = 0xff // unmapped bytes 146 | 147 | case addr == 0xff04: 148 | val = byte(cs.TimerDivCycles >> 8) 149 | case addr == 0xff05: 150 | val = cs.TimerCounterReg 151 | case addr == 0xff06: 152 | val = cs.TimerModuloReg 153 | case addr == 0xff07: 154 | val = cs.readTimerControlReg() 155 | 156 | case addr >= 0xff08 && addr < 0xff0f: 157 | val = 0xff // unmapped bytes 158 | 159 | case addr == 0xff0f: 160 | val = cs.readInterruptFlagReg() 161 | 162 | case addr == 0xff10: 163 | val = cs.APU.Sounds[0].readSweepReg() 164 | case addr == 0xff11: 165 | val = cs.APU.Sounds[0].readLenDutyReg() 166 | case addr == 0xff12: 167 | val = cs.APU.Sounds[0].readSoundEnvReg() 168 | case addr == 0xff13: 169 | val = cs.APU.Sounds[0].readFreqLowReg() 170 | case addr == 0xff14: 171 | val = cs.APU.Sounds[0].readFreqHighReg() 172 | 173 | case addr == 0xff15: 174 | val = 0xff // unmapped bytes 175 | case addr == 0xff16: 176 | val = cs.APU.Sounds[1].readLenDutyReg() 177 | case addr == 0xff17: 178 | val = cs.APU.Sounds[1].readSoundEnvReg() 179 | case addr == 0xff18: 180 | val = cs.APU.Sounds[1].readFreqLowReg() 181 | case addr == 0xff19: 182 | val = cs.APU.Sounds[1].readFreqHighReg() 183 | 184 | case addr == 0xff1a: 185 | val = boolBit(cs.APU.Sounds[2].On, 7) | 0x7f 186 | case addr == 0xff1b: 187 | val = cs.APU.Sounds[2].readLengthDataReg() 188 | case addr == 0xff1c: 189 | val = cs.APU.Sounds[2].readWaveOutLvlReg() 190 | case addr == 0xff1d: 191 | val = cs.APU.Sounds[2].readFreqLowReg() 192 | case addr == 0xff1e: 193 | val = cs.APU.Sounds[2].readFreqHighReg() 194 | 195 | case addr == 0xff1f: 196 | val = 0xff // unmapped bytes 197 | case addr == 0xff20: 198 | val = cs.APU.Sounds[3].readLengthDataReg() 199 | case addr == 0xff21: 200 | val = cs.APU.Sounds[3].readSoundEnvReg() 201 | case addr == 0xff22: 202 | val = cs.APU.Sounds[3].readPolyCounterReg() 203 | case addr == 0xff23: 204 | val = cs.APU.Sounds[3].readFreqHighReg() 205 | 206 | case addr == 0xff24: 207 | val = cs.APU.readVolumeReg() 208 | case addr == 0xff25: 209 | val = cs.APU.readSpeakerSelectReg() 210 | case addr == 0xff26: 211 | val = cs.APU.readSoundOnOffReg() 212 | 213 | case addr >= 0xff27 && addr < 0xff30: 214 | val = 0xff // unmapped bytes 215 | 216 | case addr >= 0xff30 && addr < 0xff40: 217 | val = cs.APU.Sounds[2].WavePatternRAM[addr-0xff30] 218 | 219 | case addr == 0xff40: 220 | val = cs.LCD.readControlReg() 221 | case addr == 0xff41: 222 | val = cs.LCD.readStatusReg() 223 | case addr == 0xff42: 224 | val = cs.LCD.ScrollY 225 | case addr == 0xff43: 226 | val = cs.LCD.ScrollX 227 | case addr == 0xff44: 228 | val = cs.LCD.LYReg 229 | case addr == 0xff45: 230 | val = cs.LCD.LYCReg 231 | 232 | case addr == 0xff46: 233 | val = 0xff // oam DMA reg, write-only 234 | 235 | case addr == 0xff47: 236 | val = cs.LCD.BackgroundPaletteReg 237 | case addr == 0xff48: 238 | val = cs.LCD.ObjectPalette0Reg 239 | case addr == 0xff49: 240 | val = cs.LCD.ObjectPalette1Reg 241 | case addr == 0xff4a: 242 | val = cs.LCD.WindowY 243 | case addr == 0xff4b: 244 | val = cs.LCD.WindowX 245 | 246 | case addr == 0xff4c: 247 | val = 0xff // unmapped bytes 248 | 249 | case addr == 0xff4d: 250 | if cs.CGBMode { 251 | val = cs.readSpeedSwitchReg() 252 | } 253 | 254 | case addr == 0xff4e: 255 | val = 0xff // unmapped bytes 256 | 257 | case addr == 0xff4f: 258 | if cs.CGBMode { 259 | val = cs.LCD.readBankReg() 260 | } else { 261 | val = 0xff // unmapped bytes 262 | } 263 | 264 | case addr == 0xff50: 265 | val = 0xff // unmapped bytes 266 | 267 | case addr == 0xff51: 268 | if cs.CGBMode { 269 | val = cs.readDMASourceHigh() 270 | } 271 | case addr == 0xff52: 272 | if cs.CGBMode { 273 | val = cs.readDMASourceLow() 274 | } 275 | case addr == 0xff53: 276 | if cs.CGBMode { 277 | val = cs.readDMADestHigh() 278 | } 279 | case addr == 0xff54: 280 | if cs.CGBMode { 281 | val = cs.readDMADestLow() 282 | } 283 | case addr == 0xff55: 284 | if cs.CGBMode { 285 | val = cs.readDMAControlReg() 286 | } 287 | 288 | case addr == 0xff56: 289 | if cs.CGBMode { 290 | val = cs.readIRPortReg() 291 | } 292 | 293 | case addr >= 0xff57 && addr < 0xff68: 294 | val = 0xff // unmapped bytes 295 | 296 | case addr == 0xff68: 297 | if cs.CGBMode { 298 | val = cs.LCD.readBGPaletteRAMIndexReg() 299 | } else { 300 | val = 0xff 301 | } 302 | case addr == 0xff69: 303 | if cs.CGBMode { 304 | val = cs.LCD.readBGPaletteRAMDataReg() 305 | } else { 306 | val = 0xff 307 | } 308 | case addr == 0xff6a: 309 | if cs.CGBMode { 310 | val = cs.LCD.readSpritePaletteRAMIndexReg() 311 | } else { 312 | val = 0xff 313 | } 314 | case addr == 0xff6b: 315 | if cs.CGBMode { 316 | val = cs.LCD.readSpritePaletteRAMDataReg() 317 | } else { 318 | val = 0xff 319 | } 320 | 321 | case addr >= 0xff6c && addr < 0xff70: 322 | val = 0xff // unmapped bytes 323 | 324 | case addr == 0xff70: 325 | if cs.CGBMode { 326 | val = byte(cs.Mem.InternalRAMBankNumber) 327 | } 328 | 329 | case addr >= 0xff71 && addr < 0xff80: 330 | val = 0xff // unmapped bytes 331 | 332 | case addr >= 0xff80 && addr < 0xffff: 333 | val = cs.Mem.HighInternalRAM[addr-0xff80] 334 | case addr == 0xffff: 335 | val = cs.readInterruptEnableReg() 336 | 337 | default: 338 | cs.stepErr(fmt.Sprintf("not implemented: read at %x\n", addr)) 339 | } 340 | return val 341 | } 342 | 343 | func (cs *cpuState) read16(addr uint16) uint16 { 344 | high := uint16(cs.read(addr + 1)) 345 | low := uint16(cs.read(addr)) 346 | return (high << 8) | low 347 | } 348 | 349 | func (cs *cpuState) write(addr uint16, val byte) { 350 | switch { 351 | 352 | case addr < 0x8000: 353 | cs.Mem.mbcWrite(addr, val) 354 | 355 | case addr >= 0x8000 && addr < 0xa000: 356 | cs.LCD.writeVideoRAM(addr-0x8000, val) 357 | 358 | case addr >= 0xa000 && addr < 0xc000: 359 | cs.Mem.mbcWrite(addr, val) 360 | 361 | case addr >= 0xc000 && addr < 0xfe00: 362 | ramAddr := (addr - 0xc000) & 0x1fff // 8kb with wraparound 363 | if ramAddr >= 0x1000 { 364 | ramAddr = (ramAddr - 0x1000) + 0x1000*cs.Mem.InternalRAMBankNumber 365 | } 366 | cs.Mem.InternalRAM[ramAddr] = val 367 | case addr >= 0xfe00 && addr < 0xfea0: 368 | cs.LCD.writeOAM(addr-0xfe00, val) 369 | case addr >= 0xfea0 && addr < 0xff00: 370 | // empty, nop (can be more complicated, see TCAGBD) 371 | 372 | case addr == 0xff00: 373 | cs.Joypad.writeJoypadReg(val) 374 | case addr == 0xff01: 375 | cs.SerialTransferData = val 376 | case addr == 0xff02: 377 | cs.writeSerialControlReg(val) 378 | 379 | case addr == 0xff03: 380 | // nop (unmapped bytes) 381 | 382 | case addr == 0xff04: 383 | cs.TimerDivCycles = 0 384 | case addr == 0xff05: 385 | cs.TimerCounterReg = val 386 | case addr == 0xff06: 387 | cs.TimerModuloReg = val 388 | case addr == 0xff07: 389 | cs.writeTimerControlReg(val) 390 | 391 | case addr >= 0xff08 && addr < 0xff0f: 392 | // nop (unmapped bytes) 393 | 394 | case addr == 0xff0f: 395 | cs.writeInterruptFlagReg(val) 396 | 397 | case addr == 0xff10: 398 | cs.APU.Sounds[0].writeSweepReg(val) 399 | case addr == 0xff11: 400 | cs.APU.Sounds[0].writeLenDutyReg(val) 401 | case addr == 0xff12: 402 | cs.APU.Sounds[0].writeSoundEnvReg(val) 403 | case addr == 0xff13: 404 | cs.APU.Sounds[0].writeFreqLowReg(val) 405 | case addr == 0xff14: 406 | cs.APU.Sounds[0].writeFreqHighReg(val) 407 | 408 | case addr == 0xff15: 409 | // nop (unmapped bytes) 410 | 411 | case addr == 0xff16: 412 | cs.APU.Sounds[1].writeLenDutyReg(val) 413 | case addr == 0xff17: 414 | cs.APU.Sounds[1].writeSoundEnvReg(val) 415 | case addr == 0xff18: 416 | cs.APU.Sounds[1].writeFreqLowReg(val) 417 | case addr == 0xff19: 418 | cs.APU.Sounds[1].writeFreqHighReg(val) 419 | 420 | case addr == 0xff1a: 421 | cs.APU.Sounds[2].writeWaveOnOffReg(val) 422 | case addr == 0xff1b: 423 | cs.APU.Sounds[2].writeLengthDataReg(val) 424 | case addr == 0xff1c: 425 | cs.APU.Sounds[2].writeWaveOutLvlReg(val) 426 | case addr == 0xff1d: 427 | cs.APU.Sounds[2].writeFreqLowReg(val) 428 | case addr == 0xff1e: 429 | cs.APU.Sounds[2].writeFreqHighReg(val) 430 | 431 | case addr == 0xff1f: 432 | // nop (unmapped bytes) 433 | 434 | case addr == 0xff20: 435 | cs.APU.Sounds[3].writeLengthDataReg(val) 436 | case addr == 0xff21: 437 | cs.APU.Sounds[3].writeSoundEnvReg(val) 438 | case addr == 0xff22: 439 | cs.APU.Sounds[3].writePolyCounterReg(val) 440 | case addr == 0xff23: 441 | cs.APU.Sounds[3].writeFreqHighReg(val) // noise channel uses control bits, freq ignored 442 | 443 | case addr == 0xff24: 444 | cs.APU.writeVolumeReg(val) 445 | case addr == 0xff25: 446 | cs.APU.writeSpeakerSelectReg(val) 447 | case addr == 0xff26: 448 | cs.APU.writeSoundOnOffReg(val) 449 | 450 | case addr >= 0xff27 && addr < 0xff30: 451 | // nop (unmapped bytes) 452 | 453 | case addr >= 0xff30 && addr < 0xff40: 454 | cs.APU.Sounds[2].writeWavePatternValue(addr-0xff30, val) 455 | 456 | case addr == 0xff40: 457 | cs.LCD.writeControlReg(val) 458 | case addr == 0xff41: 459 | cs.LCD.writeStatusReg(val) 460 | case addr == 0xff42: 461 | cs.LCD.writeScrollY(val) 462 | case addr == 0xff43: 463 | cs.LCD.writeScrollX(val) 464 | case addr == 0xff44: 465 | // nop? pandocs says something 466 | // about "resetting the counter", 467 | // bgb seems to do nothing. doesn't 468 | // reset LYReg, doesn't change any 469 | // counter I see... 470 | case addr == 0xff45: 471 | cs.LCD.writeLycReg(val) 472 | case addr == 0xff46: 473 | cs.OAMDMAIndex = 0 474 | cs.OAMDMAActive = true 475 | cs.OAMDMASource = uint16(val) << 8 476 | case addr == 0xff47: 477 | cs.LCD.writeBackgroundPaletteReg(val) 478 | case addr == 0xff48: 479 | cs.LCD.writeObjectPalette0Reg(val) 480 | case addr == 0xff49: 481 | cs.LCD.writeObjectPalette1Reg(val) 482 | case addr == 0xff4a: 483 | cs.LCD.writeWindowY(val) 484 | case addr == 0xff4b: 485 | cs.LCD.writeWindowX(val) 486 | 487 | case addr == 0xff4c: 488 | // empty, nop (can be more complicated, see TCAGBD) 489 | 490 | case addr == 0xff4d: 491 | if cs.CGBMode { 492 | cs.writeSpeedSwitchReg(val) 493 | } 494 | 495 | case addr == 0xff4e: 496 | // empty, nop (can be more complicated, see TCAGBD) 497 | 498 | case addr == 0xff4f: 499 | if cs.CGBMode { 500 | cs.LCD.writeBankReg(val) 501 | } 502 | 503 | case addr == 0xff50: 504 | // empty, nop (can be more complicated, see TCAGBD) 505 | 506 | case addr == 0xff51: 507 | if cs.CGBMode { 508 | cs.writeDMASourceHigh(val) 509 | } 510 | case addr == 0xff52: 511 | if cs.CGBMode { 512 | cs.writeDMASourceLow(val) 513 | } 514 | case addr == 0xff53: 515 | if cs.CGBMode { 516 | cs.writeDMADestHigh(val) 517 | } 518 | case addr == 0xff54: 519 | if cs.CGBMode { 520 | cs.writeDMADestLow(val) 521 | } 522 | case addr == 0xff55: 523 | if cs.CGBMode { 524 | cs.writeDMAControlReg(val) 525 | } 526 | 527 | case addr == 0xff56: 528 | if cs.CGBMode { 529 | cs.writeIRPortReg(val) 530 | } 531 | 532 | case addr >= 0xff57 && addr < 0xff68: 533 | // empty, nop (can be more complicated, see TCAGBD) 534 | 535 | case addr == 0xff68: 536 | if cs.CGBMode { 537 | cs.LCD.writeBGPaletteRAMIndexReg(val) 538 | } 539 | case addr == 0xff69: 540 | if cs.CGBMode { 541 | cs.LCD.writeBGPaletteRAMDataReg(val) 542 | } 543 | case addr == 0xff6a: 544 | if cs.CGBMode { 545 | cs.LCD.writeSpritePaletteRAMIndexReg(val) 546 | } 547 | case addr == 0xff6b: 548 | if cs.CGBMode { 549 | cs.LCD.writeSpritePaletteRAMDataReg(val) 550 | } 551 | 552 | case addr >= 0xff6c && addr < 0xff70: 553 | // empty, nop (can be more complicated, see TCAGBD) 554 | 555 | case addr == 0xff70: 556 | if cs.CGBMode { 557 | if val&0x07 == 0 { 558 | val = 1 559 | } 560 | cs.Mem.InternalRAMBankNumber = uint16(val) & 0x07 561 | } 562 | 563 | case addr >= 0xff71 && addr < 0xff80: 564 | // empty, nop (can be more complicated, see TCAGBD) 565 | 566 | case addr >= 0xff80 && addr < 0xffff: 567 | cs.Mem.HighInternalRAM[addr-0xff80] = val 568 | case addr == 0xffff: 569 | cs.writeInterruptEnableReg(val) 570 | default: 571 | cs.stepErr(fmt.Sprintf("not implemented: write(0x%04x, %v)\n", addr, val)) 572 | } 573 | } 574 | 575 | func (cs *cpuState) write16(addr uint16, val uint16) { 576 | cs.write(addr, byte(val)) 577 | cs.write(addr+1, byte(val>>8)) 578 | } 579 | -------------------------------------------------------------------------------- /ops.go: -------------------------------------------------------------------------------- 1 | package dmgo 2 | 3 | import "fmt" 4 | 5 | func (cs *cpuState) setOp8(reg *uint8, val uint8, flags uint16) { 6 | *reg = val 7 | cs.setFlags(flags) 8 | } 9 | 10 | func (cs *cpuState) setALUOp(val uint8, flags uint16) { 11 | cs.A = val 12 | cs.setFlags(flags) 13 | } 14 | 15 | func setOpA(cs *cpuState, val uint8) { cs.A = val } 16 | func setOpB(cs *cpuState, val uint8) { cs.B = val } 17 | func setOpC(cs *cpuState, val uint8) { cs.C = val } 18 | func setOpD(cs *cpuState, val uint8) { cs.D = val } 19 | func setOpE(cs *cpuState, val uint8) { cs.E = val } 20 | func setOpL(cs *cpuState, val uint8) { cs.L = val } 21 | func setOpH(cs *cpuState, val uint8) { cs.H = val } 22 | 23 | func (cs *cpuState) setOp16(cycles uint, setFn func(uint16), val uint16, flags uint16) { 24 | cs.runCycles(cycles) 25 | setFn(val) 26 | cs.setFlags(flags) 27 | } 28 | 29 | func (cs *cpuState) jmpRel8(test bool, relAddr int8) { 30 | if test { 31 | cs.runCycles(4) 32 | cs.PC = uint16(int(cs.PC) + int(relAddr)) 33 | } 34 | } 35 | func (cs *cpuState) jmpAbs16(test bool, addr uint16) { 36 | if test { 37 | cs.runCycles(4) 38 | cs.PC = addr 39 | } 40 | } 41 | 42 | func (cs *cpuState) jmpCall(test bool, addr uint16) { 43 | if test { 44 | cs.pushOp16(cs.PC) 45 | cs.PC = addr 46 | } 47 | } 48 | func (cs *cpuState) jmpRet(test bool) { 49 | cs.runCycles(4) 50 | if test { 51 | cs.popOp16(cs.setPC) 52 | cs.runCycles(4) 53 | } 54 | } 55 | 56 | // reminder: flags == zero, addsub, halfcarry, carry 57 | // set all: 0x1111 58 | // clear all: 0x0000 59 | // ignore all: 0x2222 60 | 61 | func zFlag(val uint8) uint16 { 62 | if val == 0 { 63 | return 0x1000 64 | } 65 | return 0x0000 66 | } 67 | 68 | // half carry 69 | func hFlagAdd(val, addend uint8) uint16 { 70 | // 4th to 5th bit carry 71 | if int(val&0x0f)+int(addend&0x0f) >= 0x10 { 72 | return 0x10 73 | } 74 | return 0x00 75 | } 76 | 77 | // half carry 78 | func hFlagAdc(val, addend, fReg uint8) uint16 { 79 | carry := (fReg >> 4) & 0x01 80 | // 4th to 5th bit carry 81 | if int(carry)+int(val&0x0f)+int(addend&0x0f) >= 0x10 { 82 | return 0x10 83 | } 84 | return 0x00 85 | } 86 | 87 | // half carry 16 88 | func hFlagAdd16(val, addend uint16) uint16 { 89 | // 12th to 13th bit carry 90 | if int(val&0x0fff)+int(addend&0x0fff) >= 0x1000 { 91 | return 0x10 92 | } 93 | return 0x00 94 | } 95 | 96 | // half carry 97 | func hFlagSub(val, subtrahend uint8) uint16 { 98 | if int(val&0xf)-int(subtrahend&0xf) < 0 { 99 | return 0x10 100 | } 101 | return 0x00 102 | } 103 | 104 | // half carry 105 | func hFlagSbc(val, subtrahend, fReg uint8) uint16 { 106 | carry := (fReg >> 4) & 0x01 107 | if int(val&0xf)-int(subtrahend&0xf)-int(carry) < 0 { 108 | return 0x10 109 | } 110 | return 0x00 111 | } 112 | 113 | // carry 114 | func cFlagAdd(val, addend uint8) uint16 { 115 | if int(val)+int(addend) > 0xff { 116 | return 0x1 117 | } 118 | return 0x0 119 | } 120 | 121 | // carry 122 | func cFlagAdc(val, addend, fReg uint8) uint16 { 123 | carry := (fReg >> 4) & 0x01 124 | if int(carry)+int(val)+int(addend) > 0xff { 125 | return 0x1 126 | } 127 | return 0x0 128 | } 129 | 130 | // carry 16 131 | func cFlagAdd16(val, addend uint16) uint16 { 132 | if int(val)+int(addend) > 0xffff { 133 | return 0x1 134 | } 135 | return 0x0 136 | } 137 | 138 | // carry 139 | func cFlagSub(val, subtrahend uint8) uint16 { 140 | if int(val)-int(subtrahend) < 0 { 141 | return 0x1 142 | } 143 | return 0x0 144 | } 145 | func cFlagSbc(val, subtrahend, fReg uint8) uint16 { 146 | carry := (fReg >> 4) & 0x01 147 | if int(val)-int(subtrahend)-int(carry) < 0 { 148 | return 0x1 149 | } 150 | return 0x0 151 | } 152 | 153 | func (cs *cpuState) pushOp16(val uint16) { 154 | cs.runCycles(4) 155 | // Can't use cpuWrite16 b/c push goes in opposite order. 156 | cs.cpuWrite(cs.SP-1, byte(val>>8)) 157 | cs.cpuWrite(cs.SP-2, byte(val)) 158 | cs.SP -= 2 159 | } 160 | func (cs *cpuState) popOp16(setFn func(val uint16)) { 161 | setFn(cs.cpuRead16(cs.SP)) 162 | cs.SP += 2 163 | } 164 | 165 | func (cs *cpuState) incOpReg(reg *byte) { 166 | val := *reg 167 | *reg = val + 1 168 | cs.setFlags(zFlag(val+1) | hFlagAdd(val, 1) | 0x0002) 169 | } 170 | func (cs *cpuState) incOpHL() { 171 | val := cs.cpuRead(cs.getHL()) 172 | cs.cpuWrite(cs.getHL(), val+1) 173 | cs.setFlags(zFlag(val+1) | hFlagAdd(val, 1) | 0x0002) 174 | } 175 | 176 | func (cs *cpuState) decOpReg(reg *byte) { 177 | val := *reg 178 | *reg = val - 1 179 | cs.setFlags(zFlag(val-1) | hFlagSub(val, 1) | 0x0102) 180 | } 181 | func (cs *cpuState) decOpHL() { 182 | val := cs.cpuRead(cs.getHL()) 183 | cs.cpuWrite(cs.getHL(), val-1) 184 | cs.setFlags(zFlag(val-1) | hFlagSub(val, 1) | 0x0102) 185 | } 186 | 187 | func (cs *cpuState) daaOp() { 188 | 189 | newCarryFlag := uint16(0) 190 | if cs.getSubFlag() { 191 | diff := byte(0) 192 | if cs.getHalfCarryFlag() { 193 | diff += 0x06 194 | } 195 | if cs.getCarryFlag() { 196 | newCarryFlag = 0x0001 197 | diff += 0x60 198 | } 199 | cs.A -= diff 200 | } else { 201 | diff := byte(0) 202 | if cs.A&0x0f > 0x09 || cs.getHalfCarryFlag() { 203 | diff += 0x06 204 | } 205 | if cs.A > 0x99 || cs.getCarryFlag() { 206 | newCarryFlag = 0x0001 207 | diff += 0x60 208 | } 209 | cs.A += diff 210 | } 211 | 212 | cs.setFlags(zFlag(cs.A) | 0x0200 | newCarryFlag) 213 | } 214 | 215 | func (cs *cpuState) ifToString() string { 216 | out := []byte(" ") 217 | if cs.VBlankIRQ { 218 | out[0] = 'V' 219 | } 220 | if cs.LCDStatIRQ { 221 | out[1] = 'L' 222 | } 223 | if cs.TimerIRQ { 224 | out[2] = 'T' 225 | } 226 | if cs.SerialIRQ { 227 | out[3] = 'S' 228 | } 229 | if cs.JoypadIRQ { 230 | out[4] = 'J' 231 | } 232 | return string(out) 233 | } 234 | func (cs *cpuState) ieToString() string { 235 | out := []byte(" ") 236 | if cs.VBlankInterruptEnabled { 237 | out[0] = 'V' 238 | } 239 | if cs.LCDStatInterruptEnabled { 240 | out[1] = 'L' 241 | } 242 | if cs.TimerInterruptEnabled { 243 | out[2] = 'T' 244 | } 245 | if cs.SerialInterruptEnabled { 246 | out[3] = 'S' 247 | } 248 | if cs.JoypadInterruptEnabled { 249 | out[4] = 'J' 250 | } 251 | return string(out) 252 | } 253 | func (cs *cpuState) imeToString() string { 254 | if cs.InterruptMasterEnable { 255 | return "1" 256 | } 257 | return "0" 258 | } 259 | func (cs *cpuState) DebugStatusLine() string { 260 | 261 | return fmt.Sprintf("Step:%08d, ", cs.Steps) + 262 | fmt.Sprintf("Cycles:%08d, ", cs.Cycles) + 263 | fmt.Sprintf("(*PC)[0:2]:%02x%02x%02x, ", cs.read(cs.PC), cs.read(cs.PC+1), cs.read(cs.PC+2)) + 264 | fmt.Sprintf("(*SP):%04x, ", cs.read16(cs.SP)) + 265 | fmt.Sprintf("[PC:%04x ", cs.PC) + 266 | fmt.Sprintf("SP:%04x ", cs.SP) + 267 | fmt.Sprintf("AF:%04x ", cs.getAF()) + 268 | fmt.Sprintf("BC:%04x ", cs.getBC()) + 269 | fmt.Sprintf("DE:%04x ", cs.getDE()) + 270 | fmt.Sprintf("HL:%04x ", cs.getHL()) + 271 | fmt.Sprintf("IME:%v ", cs.imeToString()) + 272 | fmt.Sprintf("IE:%v ", cs.ieToString()) + 273 | fmt.Sprintf("IF:%v ", cs.ifToString()) + 274 | fmt.Sprintf("LY:%02x ", cs.LCD.LYReg) + 275 | fmt.Sprintf("LYC:%02x ", cs.LCD.LYCReg) + 276 | fmt.Sprintf("LC:%02x ", cs.LCD.readControlReg()) + 277 | fmt.Sprintf("LS:%02x ", cs.LCD.readStatusReg()) + 278 | fmt.Sprintf("ROM:%d]", cs.Mem.mbc.GetROMBankNumber()) 279 | } 280 | 281 | func addOpA(cs *cpuState, val byte) { 282 | cs.setALUOp(cs.A+val, (zFlag(cs.A+val) | hFlagAdd(cs.A, val) | cFlagAdd(cs.A, val))) 283 | } 284 | func adcOpA(cs *cpuState, val byte) { 285 | carry := (cs.F >> 4) & 0x01 286 | cs.setALUOp(cs.A+val+carry, (zFlag(cs.A+val+carry) | hFlagAdc(cs.A, val, cs.F) | cFlagAdc(cs.A, val, cs.F))) 287 | } 288 | func subOpA(cs *cpuState, val byte) { 289 | cs.setALUOp(cs.A-val, (zFlag(cs.A-val) | 0x100 | hFlagSub(cs.A, val) | cFlagSub(cs.A, val))) 290 | } 291 | func sbcOpA(cs *cpuState, val byte) { 292 | carry := (cs.F >> 4) & 0x01 293 | cs.setALUOp(cs.A-val-carry, (zFlag(cs.A-val-carry) | 0x100 | hFlagSbc(cs.A, val, cs.F) | cFlagSbc(cs.A, val, cs.F))) 294 | } 295 | func andOpA(cs *cpuState, val byte) { 296 | cs.setALUOp(cs.A&val, (zFlag(cs.A&val) | 0x010)) 297 | } 298 | func xorOpA(cs *cpuState, val byte) { 299 | cs.setALUOp(cs.A^val, zFlag(cs.A^val)) 300 | } 301 | func orOpA(cs *cpuState, val byte) { 302 | cs.setALUOp(cs.A|val, zFlag(cs.A|val)) 303 | } 304 | func cpOp(cs *cpuState, val byte) { 305 | cs.setFlags(zFlag(cs.A-val) | hFlagSub(cs.A, val) | cFlagSub(cs.A, val) | 0x0100) 306 | } 307 | 308 | func (cs *cpuState) callOp(callAddr uint16) { 309 | cs.pushOp16(cs.PC) 310 | cs.PC = callAddr 311 | } 312 | 313 | // NOTE: should be the relevant bits only 314 | func (cs *cpuState) getRegFromOpBits(opBits byte) *byte { 315 | switch opBits { 316 | case 0: 317 | return &cs.B 318 | case 1: 319 | return &cs.C 320 | case 2: 321 | return &cs.D 322 | case 3: 323 | return &cs.E 324 | case 4: 325 | return &cs.H 326 | case 5: 327 | return &cs.L 328 | case 6: 329 | return nil // (hl) 330 | case 7: 331 | return &cs.A 332 | } 333 | panic("getRegFromOpBits: unknown bits passed") 334 | } 335 | 336 | func (cs *cpuState) getValFromOpBits(opcode byte) byte { 337 | if reg := cs.getRegFromOpBits(opcode & 0x07); reg != nil { 338 | return *reg 339 | } 340 | return cs.cpuRead(cs.getHL()) 341 | } 342 | 343 | // opcode >> 3 344 | var isSimpleOp = []bool{ 345 | false, false, false, false, false, false, false, false, 346 | true, true, true, true, true, true, false, true, 347 | true, true, true, true, true, true, true, true, 348 | false, false, false, false, false, false, false, false, 349 | } 350 | 351 | // opcode >> 3 352 | var simpleOpFnTable = []func(*cpuState, byte){ 353 | nil, nil, nil, nil, nil, nil, nil, nil, 354 | setOpB, setOpC, setOpD, setOpE, setOpH, setOpL, nil, setOpA, 355 | addOpA, adcOpA, subOpA, sbcOpA, andOpA, xorOpA, orOpA, cpOp, 356 | } 357 | 358 | func (cs *cpuState) cpuRead(addr uint16) byte { 359 | cs.runCycles(4) 360 | return cs.read(addr) 361 | } 362 | 363 | func (cs *cpuState) cpuWrite(addr uint16, val byte) { 364 | cs.runCycles(4) 365 | cs.write(addr, val) 366 | } 367 | 368 | func (cs *cpuState) cpuRead16(addr uint16) uint16 { 369 | lsb := cs.cpuRead(addr) 370 | msb := cs.cpuRead(addr + 1) 371 | return (uint16(msb) << 8) | uint16(lsb) 372 | } 373 | 374 | func (cs *cpuState) cpuWrite16(addr uint16, val uint16) { 375 | cs.cpuWrite(addr, byte(val)) 376 | cs.cpuWrite(addr+1, byte(val>>8)) 377 | } 378 | 379 | func (cs *cpuState) cpuReadAndIncPC() byte { 380 | val := cs.cpuRead(cs.PC) 381 | cs.PC++ 382 | return val 383 | } 384 | 385 | func (cs *cpuState) cpuReadAndIncPC16() uint16 { 386 | lsb := cs.cpuRead(cs.PC) 387 | cs.PC++ 388 | msb := cs.cpuRead(cs.PC) 389 | cs.PC++ 390 | return (uint16(msb) << 8) | uint16(lsb) 391 | } 392 | 393 | func (cs *cpuState) stepOpcode() { 394 | 395 | opcode := cs.read(cs.PC) // no runCycles, because we're acting like this was prefetched 396 | cs.PC++ 397 | 398 | // simple cases [ ld R, R_OR_(HL) or ALU_OP R_OR_(HL) ] 399 | sel := opcode >> 3 400 | if isSimpleOp[sel] { 401 | val := cs.getValFromOpBits(opcode) 402 | simpleOpFnTable[sel](cs, val) 403 | cs.runCycles(4) // to cover the last execute step / next prefetch of opcodes 404 | return 405 | } 406 | 407 | // complex cases 408 | switch opcode { 409 | 410 | case 0x00: // nop 411 | // my work here is done.jpg 412 | case 0x01: // ld bc, n16 413 | cs.setBC(cs.cpuReadAndIncPC16()) 414 | case 0x02: // ld (bc), a 415 | cs.cpuWrite(cs.getBC(), cs.A) 416 | case 0x03: // inc bc 417 | cs.runCycles(4) 418 | cs.setBC(cs.getBC() + 1) 419 | case 0x04: // inc b 420 | cs.incOpReg(&cs.B) 421 | case 0x05: // dec b 422 | cs.decOpReg(&cs.B) 423 | case 0x06: // ld b, n8 424 | cs.B = cs.cpuReadAndIncPC() 425 | case 0x07: // rlca 426 | cs.rlcaOp() 427 | 428 | case 0x08: // ld (a16), sp 429 | cs.cpuWrite16(cs.cpuReadAndIncPC16(), cs.SP) 430 | case 0x09: // add hl, bc 431 | v1, v2 := cs.getHL(), cs.getBC() 432 | cs.setOp16(4, cs.setHL, v1+v2, (0x2000 | hFlagAdd16(v1, v2) | cFlagAdd16(v1, v2))) 433 | case 0x0a: // ld a, (bc) 434 | cs.A = cs.cpuRead(cs.getBC()) 435 | case 0x0b: // dec bc 436 | cs.runCycles(4) 437 | cs.setBC(cs.getBC() - 1) 438 | case 0x0c: // inc c 439 | cs.incOpReg(&cs.C) 440 | case 0x0d: // dec c 441 | cs.decOpReg(&cs.C) 442 | case 0x0e: // ld c, n8 443 | cs.C = cs.cpuReadAndIncPC() 444 | case 0x0f: // rrca 445 | cs.rrcaOp() 446 | 447 | case 0x10: // stop 448 | cs.InStopMode = true 449 | case 0x11: // ld de, n16 450 | cs.setDE(cs.cpuReadAndIncPC16()) 451 | case 0x12: // ld (de), a 452 | cs.cpuWrite(cs.getDE(), cs.A) 453 | case 0x13: // inc de 454 | cs.runCycles(4) 455 | cs.setDE(cs.getDE() + 1) 456 | case 0x14: // inc d 457 | cs.incOpReg(&cs.D) 458 | case 0x15: // dec d 459 | cs.decOpReg(&cs.D) 460 | case 0x16: // ld d, n8 461 | cs.D = cs.cpuReadAndIncPC() 462 | case 0x17: // rla 463 | cs.rlaOp() 464 | 465 | case 0x18: // jr r8 466 | cs.jmpRel8(true, int8(cs.cpuReadAndIncPC())) 467 | case 0x19: // add hl, de 468 | v1, v2 := cs.getHL(), cs.getDE() 469 | cs.setOp16(4, cs.setHL, v1+v2, (0x2000 | hFlagAdd16(v1, v2) | cFlagAdd16(v1, v2))) 470 | case 0x1a: // ld a, (de) 471 | cs.A = cs.cpuRead(cs.getDE()) 472 | case 0x1b: // dec de 473 | cs.runCycles(4) 474 | cs.setDE(cs.getDE() - 1) 475 | case 0x1c: // inc e 476 | cs.incOpReg(&cs.E) 477 | case 0x1d: // dec e 478 | cs.decOpReg(&cs.E) 479 | case 0x1e: // ld e, n8 480 | cs.E = cs.cpuReadAndIncPC() 481 | case 0x1f: // rra 482 | cs.rraOp() 483 | 484 | case 0x20: // jr nz, r8 485 | cs.jmpRel8(!cs.getZeroFlag(), int8(cs.cpuReadAndIncPC())) 486 | case 0x21: // ld hl, n16 487 | cs.setHL(cs.cpuReadAndIncPC16()) 488 | case 0x22: // ld (hl++), a 489 | cs.cpuWrite(cs.getHL(), cs.A) 490 | cs.setHL(cs.getHL() + 1) 491 | case 0x23: // inc hl 492 | cs.runCycles(4) 493 | cs.setHL(cs.getHL() + 1) 494 | case 0x24: // inc h 495 | cs.incOpReg(&cs.H) 496 | case 0x25: // dec h 497 | cs.decOpReg(&cs.H) 498 | case 0x26: // ld h, n8 499 | cs.H = cs.cpuReadAndIncPC() 500 | case 0x27: // daa 501 | cs.daaOp() 502 | 503 | case 0x28: // jr z, r8 504 | cs.jmpRel8(cs.getZeroFlag(), int8(cs.cpuReadAndIncPC())) 505 | case 0x29: // add hl, hl 506 | v1, v2 := cs.getHL(), cs.getHL() 507 | cs.setOp16(4, cs.setHL, v1+v2, (0x2000 | hFlagAdd16(v1, v2) | cFlagAdd16(v1, v2))) 508 | case 0x2a: // ld a, (hl++) 509 | cs.A = cs.cpuRead(cs.getHL()) 510 | cs.setHL(cs.getHL() + 1) 511 | case 0x2b: // dec hl 512 | cs.runCycles(4) 513 | cs.setHL(cs.getHL() - 1) 514 | case 0x2c: // inc l 515 | cs.incOpReg(&cs.L) 516 | case 0x2d: // dec l 517 | cs.decOpReg(&cs.L) 518 | case 0x2e: // ld l, n8 519 | cs.L = cs.cpuReadAndIncPC() 520 | case 0x2f: // cpl 521 | cs.setOp8(&cs.A, ^cs.A, 0x2112) 522 | 523 | case 0x30: // jr nc, r8 524 | cs.jmpRel8(!cs.getCarryFlag(), int8(cs.cpuReadAndIncPC())) 525 | case 0x31: // ld sp, n16 526 | cs.SP = cs.cpuReadAndIncPC16() 527 | case 0x32: // ld (hl--) a 528 | cs.cpuWrite(cs.getHL(), cs.A) 529 | cs.setHL(cs.getHL() - 1) 530 | case 0x33: // inc sp 531 | cs.runCycles(4) 532 | cs.SP++ 533 | case 0x34: // inc (hl) 534 | cs.incOpHL() 535 | case 0x35: // dec (hl) 536 | cs.decOpHL() 537 | case 0x36: // ld (hl) n8 538 | cs.cpuWrite(cs.getHL(), cs.cpuReadAndIncPC()) 539 | case 0x37: // scf 540 | cs.setFlags(0x2001) 541 | 542 | case 0x38: // jr c, r8 543 | cs.jmpRel8(cs.getCarryFlag(), int8(cs.cpuReadAndIncPC())) 544 | case 0x39: // add hl, sp 545 | v1, v2 := cs.getHL(), cs.SP 546 | cs.setOp16(4, cs.setHL, v1+v2, (0x2000 | hFlagAdd16(v1, v2) | cFlagAdd16(v1, v2))) 547 | case 0x3a: // ld a, (hl--) 548 | cs.A = cs.cpuRead(cs.getHL()) 549 | cs.setHL(cs.getHL() - 1) 550 | case 0x3b: // dec sp 551 | cs.runCycles(4) 552 | cs.SP-- 553 | case 0x3c: // inc a 554 | cs.incOpReg(&cs.A) 555 | case 0x3d: // dec a 556 | cs.decOpReg(&cs.A) 557 | case 0x3e: // ld a, n8 558 | cs.A = cs.cpuReadAndIncPC() 559 | case 0x3f: // ccf 560 | carry := uint16((cs.F>>4)&0x01) ^ 0x01 561 | cs.setFlags(0x2000 | carry) 562 | 563 | case 0x70: // ld (hl), b 564 | cs.cpuWrite(cs.getHL(), cs.B) 565 | case 0x71: // ld (hl), c 566 | cs.cpuWrite(cs.getHL(), cs.C) 567 | case 0x72: // ld (hl), d 568 | cs.cpuWrite(cs.getHL(), cs.D) 569 | case 0x73: // ld (hl), e 570 | cs.cpuWrite(cs.getHL(), cs.E) 571 | case 0x74: // ld (hl), h 572 | cs.cpuWrite(cs.getHL(), cs.H) 573 | case 0x75: // ld (hl), l 574 | cs.cpuWrite(cs.getHL(), cs.L) 575 | case 0x76: // halt 576 | cs.InHaltMode = true 577 | case 0x77: // ld (hl), a 578 | cs.cpuWrite(cs.getHL(), cs.A) 579 | 580 | case 0xc0: // ret nz 581 | cs.jmpRet(!cs.getZeroFlag()) 582 | case 0xc1: // pop bc 583 | cs.popOp16(cs.setBC) 584 | case 0xc2: // jp nz, a16 585 | cs.jmpAbs16(!cs.getZeroFlag(), cs.cpuReadAndIncPC16()) 586 | case 0xc3: // jp a16 587 | cs.runCycles(4) 588 | cs.PC = cs.cpuReadAndIncPC16() 589 | case 0xc4: // call nz, a16 590 | cs.jmpCall(!cs.getZeroFlag(), cs.cpuReadAndIncPC16()) 591 | case 0xc5: // push bc 592 | cs.pushOp16(cs.getBC()) 593 | case 0xc6: // add a, n8 594 | addOpA(cs, cs.cpuReadAndIncPC()) 595 | case 0xc7: // rst 00h 596 | cs.callOp(0x0000) 597 | 598 | case 0xc8: // ret z 599 | cs.jmpRet(cs.getZeroFlag()) 600 | case 0xc9: // ret 601 | cs.popOp16(cs.setPC) 602 | cs.runCycles(4) 603 | case 0xca: // jp z, a16 604 | cs.jmpAbs16(cs.getZeroFlag(), cs.cpuReadAndIncPC16()) 605 | case 0xcb: // extended opcode prefix 606 | cs.stepExtendedOpcode() 607 | case 0xcc: // call z, a16 608 | cs.jmpCall(cs.getZeroFlag(), cs.cpuReadAndIncPC16()) 609 | case 0xcd: // call a16 610 | cs.callOp(cs.cpuReadAndIncPC16()) 611 | case 0xce: // adc a, n8 612 | adcOpA(cs, cs.cpuReadAndIncPC()) 613 | case 0xcf: // rst 08h 614 | cs.callOp(0x0008) 615 | 616 | case 0xd0: // ret nc 617 | cs.jmpRet(!cs.getCarryFlag()) 618 | case 0xd1: // pop de 619 | cs.popOp16(cs.setDE) 620 | case 0xd2: // jp nc, a16 621 | cs.jmpAbs16(!cs.getCarryFlag(), cs.cpuReadAndIncPC16()) 622 | case 0xd3: 623 | cs.illegalOpcode(opcode) 624 | case 0xd4: // call nc, a16 625 | cs.jmpCall(!cs.getCarryFlag(), cs.cpuReadAndIncPC16()) 626 | case 0xd5: // push de 627 | cs.pushOp16(cs.getDE()) 628 | case 0xd6: // sub n8 629 | subOpA(cs, cs.cpuReadAndIncPC()) 630 | case 0xd7: // rst 10h 631 | cs.callOp(0x0010) 632 | 633 | case 0xd8: // ret c 634 | cs.jmpRet(cs.getCarryFlag()) 635 | case 0xd9: // reti 636 | cs.popOp16(cs.setPC) 637 | cs.runCycles(4) 638 | cs.MasterEnableRequested = true 639 | case 0xda: // jp c, a16 640 | cs.jmpAbs16(cs.getCarryFlag(), cs.cpuReadAndIncPC16()) 641 | case 0xdb: 642 | cs.illegalOpcode(opcode) 643 | case 0xdc: // call c, a16 644 | cs.jmpCall(cs.getCarryFlag(), cs.cpuReadAndIncPC16()) 645 | case 0xdd: 646 | cs.illegalOpcode(opcode) 647 | case 0xde: // sbc n8 648 | sbcOpA(cs, cs.cpuReadAndIncPC()) 649 | case 0xdf: // rst 18h 650 | cs.callOp(0x0018) 651 | 652 | case 0xe0: // ld (0xFF00 + n8), a 653 | val := cs.cpuReadAndIncPC() 654 | cs.cpuWrite(0xff00+uint16(val), cs.A) 655 | case 0xe1: // pop hl 656 | cs.popOp16(cs.setHL) 657 | case 0xe2: // ld (0xFF00 + c), a 658 | val := cs.C 659 | cs.cpuWrite(0xff00+uint16(val), cs.A) 660 | case 0xe3: 661 | cs.illegalOpcode(opcode) 662 | case 0xe4: 663 | cs.illegalOpcode(opcode) 664 | case 0xe5: // push hl 665 | cs.pushOp16(cs.getHL()) 666 | case 0xe6: // and n8 667 | andOpA(cs, cs.cpuReadAndIncPC()) 668 | case 0xe7: // rst 20h 669 | cs.callOp(0x0020) 670 | 671 | case 0xe8: // add sp, r8 672 | v1, v2 := cs.SP, uint16(int8(cs.cpuReadAndIncPC())) 673 | cs.setOp16(8, cs.setSP, v1+v2, (hFlagAdd(byte(v1), byte(v2)) | cFlagAdd(byte(v1), byte(v2)))) 674 | case 0xe9: // jp hl (also written jp (hl)) 675 | cs.PC = cs.getHL() 676 | case 0xea: // ld (a16), a 677 | cs.cpuWrite(cs.cpuReadAndIncPC16(), cs.A) 678 | case 0xeb: 679 | cs.illegalOpcode(opcode) 680 | case 0xec: 681 | cs.illegalOpcode(opcode) 682 | case 0xed: 683 | cs.illegalOpcode(opcode) 684 | case 0xee: // xor n8 685 | xorOpA(cs, cs.cpuReadAndIncPC()) 686 | case 0xef: // rst 28h 687 | cs.callOp(0x0028) 688 | 689 | case 0xf0: // ld a, (0xFF00 + n8) 690 | val := cs.cpuReadAndIncPC() 691 | cs.A = cs.cpuRead(0xff00 + uint16(val)) 692 | case 0xf1: // pop af 693 | cs.popOp16(cs.setAF) 694 | case 0xf2: // ld a, (0xFF00 + c) 695 | val := cs.C 696 | cs.A = cs.cpuRead(0xff00 + uint16(val)) 697 | case 0xf3: // di 698 | cs.InterruptMasterEnable = false 699 | case 0xf4: 700 | cs.illegalOpcode(opcode) 701 | case 0xf5: // push af 702 | cs.pushOp16(cs.getAF()) 703 | case 0xf6: // or n8 704 | orOpA(cs, cs.cpuReadAndIncPC()) 705 | case 0xf7: // rst 30h 706 | cs.callOp(0x0030) 707 | 708 | case 0xf8: // ld hl, sp+r8 709 | v1, v2 := cs.SP, uint16(int8(cs.cpuReadAndIncPC())) 710 | cs.setOp16(4, cs.setHL, v1+v2, (hFlagAdd(byte(v1), byte(v2)) | cFlagAdd(byte(v1), byte(v2)))) 711 | case 0xf9: // ld sp, hl 712 | cs.runCycles(4) 713 | cs.SP = cs.getHL() 714 | case 0xfa: // ld a, (a16) 715 | cs.A = cs.cpuRead(cs.cpuReadAndIncPC16()) 716 | case 0xfb: // ei 717 | cs.MasterEnableRequested = true 718 | case 0xfc: 719 | cs.illegalOpcode(opcode) 720 | case 0xfd: 721 | cs.illegalOpcode(opcode) 722 | case 0xfe: // cp a, n8 723 | cpOp(cs, cs.cpuReadAndIncPC()) 724 | case 0xff: // rst 38h 725 | cs.callOp(0x0038) 726 | 727 | default: 728 | cs.stepErr(fmt.Sprintf("Unknown Opcode: 0x%02x\r\n", opcode)) 729 | } 730 | 731 | cs.runCycles(4) // to cover the last execute step / next prefetch of opcodes 732 | } 733 | 734 | func (cs *cpuState) illegalOpcode(opcode uint8) { 735 | cs.stepErr(fmt.Sprintf("illegal opcode %02x", opcode)) 736 | } 737 | 738 | func (cs *cpuState) stepExtendedOpcode() { 739 | 740 | extOpcode := cs.cpuReadAndIncPC() 741 | 742 | switch extOpcode & 0xf8 { 743 | 744 | case 0x00: // rlc R_OR_(HL) 745 | cs.extSetOp(extOpcode, cs.rlcOp) 746 | case 0x08: // rrc R_OR_(HL) 747 | cs.extSetOp(extOpcode, cs.rrcOp) 748 | case 0x10: // rl R_OR_(HL) 749 | cs.extSetOp(extOpcode, cs.rlOp) 750 | case 0x18: // rr R_OR_(HL) 751 | cs.extSetOp(extOpcode, cs.rrOp) 752 | case 0x20: // sla R_OR_(HL) 753 | cs.extSetOp(extOpcode, cs.slaOp) 754 | case 0x28: // sra R_OR_(HL) 755 | cs.extSetOp(extOpcode, cs.sraOp) 756 | case 0x30: // swap R_OR_(HL) 757 | cs.extSetOp(extOpcode, cs.swapOp) 758 | case 0x38: // srl R_OR_(HL) 759 | cs.extSetOp(extOpcode, cs.srlOp) 760 | 761 | case 0x40: // bit 0, R_OR_(HL) 762 | cs.bitOp(extOpcode, 0) 763 | case 0x48: // bit 1, R_OR_(HL) 764 | cs.bitOp(extOpcode, 1) 765 | case 0x50: // bit 2, R_OR_(HL) 766 | cs.bitOp(extOpcode, 2) 767 | case 0x58: // bit 3, R_OR_(HL) 768 | cs.bitOp(extOpcode, 3) 769 | case 0x60: // bit 4, R_OR_(HL) 770 | cs.bitOp(extOpcode, 4) 771 | case 0x68: // bit 5, R_OR_(HL) 772 | cs.bitOp(extOpcode, 5) 773 | case 0x70: // bit 6, R_OR_(HL) 774 | cs.bitOp(extOpcode, 6) 775 | case 0x78: // bit 7, R_OR_(HL) 776 | cs.bitOp(extOpcode, 7) 777 | 778 | case 0x80: // res 0, R_OR_(HL) 779 | cs.bitResOp(extOpcode, 0) 780 | case 0x88: // res 1, R_OR_(HL) 781 | cs.bitResOp(extOpcode, 1) 782 | case 0x90: // res 2, R_OR_(HL) 783 | cs.bitResOp(extOpcode, 2) 784 | case 0x98: // res 3, R_OR_(HL) 785 | cs.bitResOp(extOpcode, 3) 786 | case 0xa0: // res 4, R_OR_(HL) 787 | cs.bitResOp(extOpcode, 4) 788 | case 0xa8: // res 5, R_OR_(HL) 789 | cs.bitResOp(extOpcode, 5) 790 | case 0xb0: // res 6, R_OR_(HL) 791 | cs.bitResOp(extOpcode, 6) 792 | case 0xb8: // res 6, R_OR_(HL) 793 | cs.bitResOp(extOpcode, 7) 794 | 795 | case 0xc0: // set 0, R_OR_(HL) 796 | cs.bitSetOp(extOpcode, 0) 797 | case 0xc8: // set 1, R_OR_(HL) 798 | cs.bitSetOp(extOpcode, 1) 799 | case 0xd0: // set 2, R_OR_(HL) 800 | cs.bitSetOp(extOpcode, 2) 801 | case 0xd8: // set 3, R_OR_(HL) 802 | cs.bitSetOp(extOpcode, 3) 803 | case 0xe0: // set 4, R_OR_(HL) 804 | cs.bitSetOp(extOpcode, 4) 805 | case 0xe8: // set 5, R_OR_(HL) 806 | cs.bitSetOp(extOpcode, 5) 807 | case 0xf0: // set 6, R_OR_(HL) 808 | cs.bitSetOp(extOpcode, 6) 809 | case 0xf8: // set 7, R_OR_(HL) 810 | cs.bitSetOp(extOpcode, 7) 811 | } 812 | } 813 | 814 | func (cs *cpuState) extSetOp(opcode byte, 815 | opFn func(val byte) (result byte, flags uint16)) { 816 | 817 | if reg := cs.getRegFromOpBits(opcode & 0x07); reg != nil { 818 | result, flags := opFn(*reg) 819 | cs.setOp8(reg, result, flags) 820 | } else { 821 | result, flags := opFn(cs.cpuRead(cs.getHL())) 822 | cs.cpuWrite(cs.getHL(), result) 823 | cs.setFlags(flags) 824 | } 825 | } 826 | 827 | func (cs *cpuState) swapOp(val byte) (byte, uint16) { 828 | result := val>>4 | (val&0x0f)<<4 829 | return result, zFlag(result) 830 | } 831 | 832 | func (cs *cpuState) rlaOp() { 833 | result, flags := cs.rlOp(cs.A) 834 | cs.setALUOp(result, flags&^0x1000) // rla is 000c, unlike other rl's 835 | } 836 | func (cs *cpuState) rlOp(val byte) (byte, uint16) { 837 | result, carry := (val<<1)|((cs.F>>4)&0x01), (val >> 7) 838 | return result, (zFlag(result) | uint16(carry)) 839 | } 840 | 841 | func (cs *cpuState) rraOp() { 842 | result, flags := cs.rrOp(cs.A) 843 | cs.setALUOp(result, flags&^0x1000) // rra is 000c, unlike other rr's 844 | } 845 | func (cs *cpuState) rrOp(val byte) (byte, uint16) { 846 | result, carry := ((cs.F<<3)&0x80)|(val>>1), (val & 0x01) 847 | return result, (zFlag(result) | uint16(carry)) 848 | } 849 | 850 | func (cs *cpuState) rlcaOp() { 851 | result, flags := cs.rlcOp(cs.A) 852 | cs.setALUOp(result, flags&^0x1000) // rlca is 000c, unlike other rlc's 853 | } 854 | func (cs *cpuState) rlcOp(val byte) (byte, uint16) { 855 | result, carry := (val<<1)|(val>>7), val>>7 856 | return result, (zFlag(result) | uint16(carry)) 857 | } 858 | 859 | func (cs *cpuState) rrcaOp() { 860 | result, flags := cs.rrcOp(cs.A) 861 | cs.setALUOp(result, flags&^0x1000) // rrca is 000c, unlike other rrc's 862 | } 863 | func (cs *cpuState) rrcOp(val byte) (byte, uint16) { 864 | result, carry := (val<<7)|(val>>1), (val & 0x01) 865 | return result, (zFlag(result) | uint16(carry)) 866 | } 867 | 868 | func (cs *cpuState) srlOp(val byte) (byte, uint16) { 869 | result, carry := val>>1, val&0x01 870 | return result, (zFlag(result) | uint16(carry)) 871 | } 872 | 873 | func (cs *cpuState) slaOp(val byte) (byte, uint16) { 874 | result, carry := val<<1, val>>7 875 | return result, (zFlag(result) | uint16(carry)) 876 | } 877 | 878 | func (cs *cpuState) sraOp(val byte) (byte, uint16) { 879 | result, carry := (val&0x80)|(val>>1), val&0x01 880 | return result, (zFlag(result) | uint16(carry)) 881 | } 882 | 883 | func (cs *cpuState) bitOp(opcode byte, bitNum uint8) { 884 | val := cs.getValFromOpBits(opcode) 885 | cs.setFlags(zFlag(val&(1< currentSnapshotVersion { 37 | return nil, fmt.Errorf("this version of dmgo is too old to open this snapshot") 38 | } 39 | 40 | // NOTE: what about external RAM? Doesn't this overwrite .sav files with whatever's in the snapshot? 41 | 42 | return cs.convertLatestSnapshot(&snap) 43 | } 44 | 45 | func (cs *cpuState) convertLatestSnapshot(snap *snapshot) (*cpuState, error) { 46 | var err error 47 | var newState cpuState 48 | if err = json.Unmarshal(snap.State, &newState); err != nil { 49 | return nil, err 50 | } 51 | if newState.Mem.mbc, err = unmarshalMBC(snap.MBC); err != nil { 52 | return nil, err 53 | } 54 | newState.Mem.cart = cs.Mem.cart 55 | 56 | newState.devMode = cs.devMode 57 | 58 | return &newState, nil 59 | } 60 | 61 | var snapshotConverters = map[int]func(map[string]interface{}) error{ 62 | 63 | // NOTE: Be careful with the json here. use/read the pack.go functions. 64 | // golang's json marshalling can sometimes do the unexpected, e.g. byte 65 | // slices must be packed as base64 strings. 66 | 67 | // NOTE: If an MBC ever has incompatible changes, the marshalledMBC will have to 68 | // be passed through all these conv fns as well. 69 | 70 | // NOTE: If new field can be zero, no need for converter. 71 | 72 | // added 2017-03-01 73 | 1: func(state map[string]interface{}) error { 74 | 75 | if vramStr, lcd, err := followJSON(state, "LCD", "VideoRAM"); err == nil { 76 | if vram, err := getByteSliceFromJSON(vramStr); err == nil { 77 | newVRAM := [0x4000]byte{} 78 | copy(newVRAM[:], vram) 79 | lcd["VideoRAM"] = newVRAM 80 | } else { 81 | return fmt.Errorf("could not convert old v1 snapshot: %v", err) 82 | } 83 | } else { 84 | return fmt.Errorf("could not convert old v1 snapshot: %v", err) 85 | } 86 | 87 | if ramStr, mem, err := followJSON(state, "Mem", "InternalRAM"); err == nil { 88 | if ram, err := getByteSliceFromJSON(ramStr); err == nil { 89 | var newRAM [0x8000]byte 90 | copy(newRAM[:], ram) 91 | mem["InternalRAM"] = newRAM 92 | mem["InternalRAMBankNumber"] = 1 93 | } else { 94 | return fmt.Errorf("could not convert old v1 snapshot: %v", err) 95 | } 96 | } else { 97 | return fmt.Errorf("could not convert old v1 snapshot: %v", err) 98 | } 99 | 100 | return nil 101 | }, 102 | 103 | // added 2018-12-21 104 | 2: func(state map[string]interface{}) error { 105 | if apu, _, err := followJSON(state, "APU"); err == nil { 106 | if apuMap, ok := apu.(map[string]interface{}); ok { 107 | apuMap["EnvTimeCounter"] = 0 108 | apuMap["SweepTimeCounter"] = 0 109 | apuMap["LengthTimeCounter"] = 0 110 | } else { 111 | return fmt.Errorf("could not convert old v2 snapshot: apu var is of unknown type") 112 | } 113 | } else { 114 | return fmt.Errorf("could not convert old v2 snapshot: %v", err) 115 | } 116 | 117 | if sounds, _, err := followJSON(state, "APU", "Sounds"); err == nil { 118 | if soundsArr, ok := sounds.([]interface{}); ok { 119 | for _, sound := range soundsArr { 120 | if soundMap, ok := sound.(map[string]interface{}); ok { 121 | soundMap["T"] = 0 122 | soundMap["PolySample"] = 0 123 | } else { 124 | return fmt.Errorf("could not convert old v2 snapshot: Sound var is of unknown type") 125 | } 126 | } 127 | } else { 128 | return fmt.Errorf("could not convert old v2 snapshot: Sounds var is of unknown type") 129 | } 130 | } else { 131 | return fmt.Errorf("could not convert old v2 snapshot: %v", err) 132 | } 133 | return nil 134 | }, 135 | } 136 | 137 | func (cs *cpuState) convertOldSnapshot(snap *snapshot) (*cpuState, error) { 138 | 139 | var state map[string]interface{} 140 | if err := json.Unmarshal(snap.State, &state); err != nil { 141 | return nil, fmt.Errorf("json unpack err: %v", err) 142 | } 143 | 144 | for i := snap.Version; i < currentSnapshotVersion; i++ { 145 | if converterFn, ok := snapshotConverters[i]; !ok { 146 | return nil, fmt.Errorf("could not find converter for snapshot version: %v", i) 147 | } else if err := converterFn(state); err != nil { 148 | return nil, fmt.Errorf("error converting snapshot version %v: %v", i, err) 149 | } 150 | } 151 | 152 | var err error 153 | if snap.State, err = json.Marshal(state); err != nil { 154 | return nil, fmt.Errorf("json pack err: %v", err) 155 | } 156 | 157 | return cs.convertLatestSnapshot(snap) 158 | } 159 | 160 | func (cs *cpuState) makeSnapshot() []byte { 161 | var err error 162 | var csJSON []byte 163 | var snapJSON []byte 164 | if csJSON, err = json.Marshal(cs); err != nil { 165 | panic(err) 166 | } 167 | snap := snapshot{ 168 | Version: currentSnapshotVersion, 169 | Info: infoString, 170 | State: json.RawMessage(csJSON), 171 | MBC: cs.Mem.mbc.Marshal(), 172 | } 173 | if snapJSON, err = json.Marshal(&snap); err != nil { 174 | panic(err) 175 | } 176 | buf := &bytes.Buffer{} 177 | writer := gzip.NewWriter(buf) 178 | writer.Write(snapJSON) 179 | writer.Close() 180 | return buf.Bytes() 181 | } 182 | -------------------------------------------------------------------------------- /tga.go: -------------------------------------------------------------------------------- 1 | package dmgo 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | func die(s string) { 9 | fmt.Fprintln(os.Stderr, s) 10 | os.Exit(1) 11 | } 12 | 13 | func writeTgaRGB(fname string, w int, h int, rgbData []byte) { 14 | if w*h*3 != len(rgbData) { 15 | fmt.Fprintf(os.Stderr, "writeTgaRGB(): bad sizes, %v*%v*3 != %v\n", w, h, len(rgbData)) 16 | } 17 | 18 | outb := []byte{ 19 | 0, 0, 2, 0, 0, 0, 0, 0, // main hdr + color map info (unused) 20 | 0, 0, 0, 0, // img origin 21 | byte(w), byte(w >> 8), 22 | byte(h), byte(h >> 8), 23 | 24, // 24 bbp 24 | 0x20, // top to bottom, left to right ordering, no alpha 25 | } 26 | // switch to BGR, that it is mandated is the only bad thing about tga 27 | for i := 0; i < len(rgbData); i += 3 { 28 | outb = append(outb, rgbData[i+2]) 29 | outb = append(outb, rgbData[i+1]) 30 | outb = append(outb, rgbData[i]) 31 | } 32 | 33 | os.WriteFile(fname, outb, 0777) 34 | } 35 | 36 | func writeTgaRGBA(fname string, w int, h int, rgbaData []byte) { 37 | if w*h*4 != len(rgbaData) { 38 | fmt.Fprintf(os.Stderr, "writeTgaRGBA(): bad sizes, %v*%v*4 != %v\n", w, h, len(rgbaData)) 39 | } 40 | 41 | outb := []byte{ 42 | 0, 0, 2, 0, 0, 0, 0, 0, // main hdr + color map info (unused) 43 | 0, 0, 0, 0, // img origin 44 | byte(w), byte(w >> 8), 45 | byte(h), byte(h >> 8), 46 | 32, // 24 bbp 47 | 0x28, // top to bottom, left to right ordering, 8-bit alpha 48 | } 49 | // switch to BGRA, that it is mandated is the only bad thing about tga 50 | for i := 0; i < len(rgbaData); i += 4 { 51 | outb = append(outb, rgbaData[i+2]) 52 | outb = append(outb, rgbaData[i+1]) 53 | outb = append(outb, rgbaData[i]) 54 | outb = append(outb, rgbaData[i+3]) 55 | } 56 | 57 | os.WriteFile(fname, outb, 0777) 58 | } 59 | -------------------------------------------------------------------------------- /txt.go: -------------------------------------------------------------------------------- 1 | package dmgo 2 | 3 | type textDisplay struct { 4 | x, y int 5 | w, h int 6 | screen []byte // w*h*4 7 | } 8 | 9 | func (t *textDisplay) newline() { 10 | t.x = 0 11 | t.y += 8 12 | if t.y >= t.h { 13 | t.y = 0 14 | } 15 | } 16 | 17 | func (t *textDisplay) advanceChar() { 18 | t.x += 8 19 | if t.x >= t.w { 20 | t.newline() 21 | } 22 | } 23 | 24 | func (t *textDisplay) setPos(x, y int) { 25 | t.x = x * 8 26 | t.y = y * 8 27 | } 28 | 29 | func (t *textDisplay) clearLine() { 30 | startX, startY := t.x, t.y 31 | for t.y == startY { 32 | t.writeChar(' ') 33 | } 34 | t.x, t.y = startX, startY 35 | } 36 | 37 | func (t *textDisplay) clearScreen() { 38 | for i := 0; i < len(t.screen); i++ { 39 | t.screen[i] = 0 40 | } 41 | } 42 | 43 | func (t *textDisplay) writeString(str string) { 44 | for _, char := range str { 45 | if char == '\x00' { 46 | continue 47 | } 48 | t.writeChar(char) 49 | } 50 | } 51 | 52 | func (t *textDisplay) writeChar(char rune) { 53 | if char == '\n' { 54 | t.newline() 55 | } else { 56 | if char == '\t' { 57 | char = ' ' 58 | } 59 | if char >= 32 { 60 | if char >= 'a' && char <= 'z' { 61 | char = char - 'a' + 'A' 62 | } 63 | fontChr, ok := dbgFont[char] 64 | if !ok { 65 | fontChr = dbgFont['?'] 66 | } 67 | for i := 0; i < 7; i++ { 68 | line := t.screen[(t.y+i)*t.w*4+t.x*4:] 69 | chrLine := fontChr[i*7 : i*7+7] 70 | for j := 0; j < 7; j++ { 71 | col := byte(0) 72 | if chrLine[j] == 1 { 73 | col = 0xff 74 | } 75 | line[j*4+0], line[j*4+1] = col, col 76 | line[j*4+2], line[j*4+3] = col, col 77 | } 78 | } 79 | t.advanceChar() 80 | } 81 | } 82 | } 83 | 84 | var dbgFont = map[rune][7 * 7]byte{ 85 | '0': { 86 | 1, 1, 1, 1, 1, 1, 1, 87 | 1, 0, 0, 0, 0, 1, 1, 88 | 1, 0, 0, 0, 1, 0, 1, 89 | 1, 0, 0, 1, 0, 0, 1, 90 | 1, 0, 1, 0, 0, 0, 1, 91 | 1, 1, 0, 0, 0, 0, 1, 92 | 1, 1, 1, 1, 1, 1, 1, 93 | }, 94 | '1': { 95 | 0, 0, 0, 1, 0, 0, 0, 96 | 0, 0, 0, 1, 0, 0, 0, 97 | 0, 0, 0, 1, 0, 0, 0, 98 | 0, 0, 0, 1, 0, 0, 0, 99 | 0, 0, 0, 1, 0, 0, 0, 100 | 0, 0, 0, 1, 0, 0, 0, 101 | 0, 0, 0, 1, 0, 0, 0, 102 | }, 103 | '2': { 104 | 1, 1, 1, 1, 1, 1, 1, 105 | 0, 0, 0, 0, 0, 0, 1, 106 | 0, 0, 0, 0, 0, 0, 1, 107 | 1, 1, 1, 1, 1, 1, 1, 108 | 1, 0, 0, 0, 0, 0, 0, 109 | 1, 0, 0, 0, 0, 0, 0, 110 | 1, 1, 1, 1, 1, 1, 1, 111 | }, 112 | '3': { 113 | 1, 1, 1, 1, 1, 1, 1, 114 | 0, 0, 0, 0, 0, 0, 1, 115 | 0, 0, 0, 0, 0, 0, 1, 116 | 1, 1, 1, 1, 1, 1, 1, 117 | 0, 0, 0, 0, 0, 0, 1, 118 | 0, 0, 0, 0, 0, 0, 1, 119 | 1, 1, 1, 1, 1, 1, 1, 120 | }, 121 | '4': { 122 | 1, 0, 0, 0, 0, 0, 1, 123 | 1, 0, 0, 0, 0, 0, 1, 124 | 1, 0, 0, 0, 0, 0, 1, 125 | 1, 1, 1, 1, 1, 1, 1, 126 | 0, 0, 0, 0, 0, 0, 1, 127 | 0, 0, 0, 0, 0, 0, 1, 128 | 0, 0, 0, 0, 0, 0, 1, 129 | }, 130 | '5': { 131 | 1, 1, 1, 1, 1, 1, 1, 132 | 1, 0, 0, 0, 0, 0, 0, 133 | 1, 0, 0, 0, 0, 0, 0, 134 | 1, 1, 1, 1, 1, 1, 1, 135 | 0, 0, 0, 0, 0, 0, 1, 136 | 0, 0, 0, 0, 0, 0, 1, 137 | 1, 1, 1, 1, 1, 1, 1, 138 | }, 139 | '6': { 140 | 1, 1, 1, 1, 1, 1, 1, 141 | 1, 0, 0, 0, 0, 0, 0, 142 | 1, 0, 0, 0, 0, 0, 0, 143 | 1, 1, 1, 1, 1, 1, 1, 144 | 1, 0, 0, 0, 0, 0, 1, 145 | 1, 0, 0, 0, 0, 0, 1, 146 | 1, 1, 1, 1, 1, 1, 1, 147 | }, 148 | '7': { 149 | 1, 1, 1, 1, 1, 1, 1, 150 | 0, 0, 0, 0, 0, 1, 0, 151 | 0, 0, 0, 0, 1, 0, 0, 152 | 0, 0, 0, 1, 0, 0, 0, 153 | 0, 0, 1, 0, 0, 0, 0, 154 | 0, 1, 0, 0, 0, 0, 0, 155 | 1, 0, 0, 0, 0, 0, 0, 156 | }, 157 | '8': { 158 | 1, 1, 1, 1, 1, 1, 1, 159 | 1, 0, 0, 0, 0, 0, 1, 160 | 1, 0, 0, 0, 0, 0, 1, 161 | 1, 1, 1, 1, 1, 1, 1, 162 | 1, 0, 0, 0, 0, 0, 1, 163 | 1, 0, 0, 0, 0, 0, 1, 164 | 1, 1, 1, 1, 1, 1, 1, 165 | }, 166 | '9': { 167 | 1, 1, 1, 1, 1, 1, 1, 168 | 1, 0, 0, 0, 0, 0, 1, 169 | 1, 0, 0, 0, 0, 0, 1, 170 | 1, 1, 1, 1, 1, 1, 1, 171 | 0, 0, 0, 0, 0, 0, 1, 172 | 0, 0, 0, 0, 0, 0, 1, 173 | 1, 1, 1, 1, 1, 1, 1, 174 | }, 175 | 'A': { 176 | 1, 1, 1, 1, 1, 1, 1, 177 | 1, 0, 0, 0, 0, 0, 1, 178 | 1, 0, 0, 0, 0, 0, 1, 179 | 1, 1, 1, 1, 1, 1, 1, 180 | 1, 0, 0, 0, 0, 0, 1, 181 | 1, 0, 0, 0, 0, 0, 1, 182 | 1, 0, 0, 0, 0, 0, 1, 183 | }, 184 | 'B': { 185 | 1, 1, 1, 1, 1, 1, 0, 186 | 1, 0, 0, 0, 0, 0, 1, 187 | 1, 0, 0, 0, 0, 0, 1, 188 | 1, 1, 1, 1, 1, 1, 0, 189 | 1, 0, 0, 0, 0, 0, 1, 190 | 1, 0, 0, 0, 0, 0, 1, 191 | 1, 1, 1, 1, 1, 1, 0, 192 | }, 193 | 'C': { 194 | 1, 1, 1, 1, 1, 1, 1, 195 | 1, 0, 0, 0, 0, 0, 0, 196 | 1, 0, 0, 0, 0, 0, 0, 197 | 1, 0, 0, 0, 0, 0, 0, 198 | 1, 0, 0, 0, 0, 0, 0, 199 | 1, 0, 0, 0, 0, 0, 0, 200 | 1, 1, 1, 1, 1, 1, 1, 201 | }, 202 | 'D': { 203 | 1, 1, 1, 1, 1, 0, 0, 204 | 1, 0, 0, 0, 0, 1, 0, 205 | 1, 0, 0, 0, 0, 0, 1, 206 | 1, 0, 0, 0, 0, 0, 1, 207 | 1, 0, 0, 0, 0, 0, 1, 208 | 1, 0, 0, 0, 0, 1, 0, 209 | 1, 1, 1, 1, 1, 0, 0, 210 | }, 211 | 'E': { 212 | 1, 1, 1, 1, 1, 1, 1, 213 | 1, 0, 0, 0, 0, 0, 0, 214 | 1, 0, 0, 0, 0, 0, 0, 215 | 1, 1, 1, 1, 1, 1, 1, 216 | 1, 0, 0, 0, 0, 0, 0, 217 | 1, 0, 0, 0, 0, 0, 0, 218 | 1, 1, 1, 1, 1, 1, 1, 219 | }, 220 | 'F': { 221 | 1, 1, 1, 1, 1, 1, 1, 222 | 1, 0, 0, 0, 0, 0, 0, 223 | 1, 0, 0, 0, 0, 0, 0, 224 | 1, 1, 1, 1, 1, 1, 1, 225 | 1, 0, 0, 0, 0, 0, 0, 226 | 1, 0, 0, 0, 0, 0, 0, 227 | 1, 0, 0, 0, 0, 0, 0, 228 | }, 229 | 'G': { 230 | 1, 1, 1, 1, 1, 1, 1, 231 | 1, 0, 0, 0, 0, 0, 0, 232 | 1, 0, 0, 0, 0, 0, 0, 233 | 1, 0, 0, 1, 1, 1, 1, 234 | 1, 0, 0, 0, 0, 0, 1, 235 | 1, 0, 0, 0, 0, 0, 1, 236 | 1, 1, 1, 1, 1, 1, 1, 237 | }, 238 | 'H': { 239 | 1, 0, 0, 0, 0, 0, 1, 240 | 1, 0, 0, 0, 0, 0, 1, 241 | 1, 0, 0, 0, 0, 0, 1, 242 | 1, 1, 1, 1, 1, 1, 1, 243 | 1, 0, 0, 0, 0, 0, 1, 244 | 1, 0, 0, 0, 0, 0, 1, 245 | 1, 0, 0, 0, 0, 0, 1, 246 | }, 247 | 'I': { 248 | 1, 1, 1, 1, 1, 1, 1, 249 | 0, 0, 0, 1, 0, 0, 0, 250 | 0, 0, 0, 1, 0, 0, 0, 251 | 0, 0, 0, 1, 0, 0, 0, 252 | 0, 0, 0, 1, 0, 0, 0, 253 | 0, 0, 0, 1, 0, 0, 0, 254 | 1, 1, 1, 1, 1, 1, 1, 255 | }, 256 | 'J': { 257 | 1, 1, 1, 1, 1, 1, 1, 258 | 0, 0, 0, 0, 1, 0, 0, 259 | 0, 0, 0, 0, 1, 0, 0, 260 | 0, 0, 0, 0, 1, 0, 0, 261 | 0, 0, 0, 0, 1, 0, 0, 262 | 1, 0, 0, 0, 1, 0, 0, 263 | 1, 1, 1, 1, 1, 0, 0, 264 | }, 265 | 'K': { 266 | 1, 0, 0, 0, 0, 1, 0, 267 | 1, 0, 0, 0, 1, 0, 0, 268 | 1, 0, 0, 1, 0, 0, 0, 269 | 1, 1, 1, 0, 0, 0, 0, 270 | 1, 0, 0, 1, 0, 0, 0, 271 | 1, 0, 0, 0, 1, 0, 0, 272 | 1, 0, 0, 0, 0, 1, 0, 273 | }, 274 | 'L': { 275 | 1, 0, 0, 0, 0, 0, 0, 276 | 1, 0, 0, 0, 0, 0, 0, 277 | 1, 0, 0, 0, 0, 0, 0, 278 | 1, 0, 0, 0, 0, 0, 0, 279 | 1, 0, 0, 0, 0, 0, 0, 280 | 1, 0, 0, 0, 0, 0, 0, 281 | 1, 1, 1, 1, 1, 1, 1, 282 | }, 283 | 'M': { 284 | 1, 1, 1, 1, 1, 1, 1, 285 | 1, 0, 0, 1, 0, 0, 1, 286 | 1, 0, 0, 1, 0, 0, 1, 287 | 1, 0, 0, 1, 0, 0, 1, 288 | 1, 0, 0, 1, 0, 0, 1, 289 | 1, 0, 0, 1, 0, 0, 1, 290 | 1, 0, 0, 1, 0, 0, 1, 291 | }, 292 | 'N': { 293 | 1, 0, 0, 0, 0, 0, 1, 294 | 1, 1, 0, 0, 0, 0, 1, 295 | 1, 0, 1, 0, 0, 0, 1, 296 | 1, 0, 0, 1, 0, 0, 1, 297 | 1, 0, 0, 0, 1, 0, 1, 298 | 1, 0, 0, 0, 0, 1, 1, 299 | 1, 0, 0, 0, 0, 0, 1, 300 | }, 301 | 'O': { 302 | 1, 1, 1, 1, 1, 1, 1, 303 | 1, 0, 0, 0, 0, 0, 1, 304 | 1, 0, 0, 0, 0, 0, 1, 305 | 1, 0, 0, 0, 0, 0, 1, 306 | 1, 0, 0, 0, 0, 0, 1, 307 | 1, 0, 0, 0, 0, 0, 1, 308 | 1, 1, 1, 1, 1, 1, 1, 309 | }, 310 | 'P': { 311 | 1, 1, 1, 1, 1, 1, 1, 312 | 1, 0, 0, 0, 0, 0, 1, 313 | 1, 0, 0, 0, 0, 0, 1, 314 | 1, 1, 1, 1, 1, 1, 1, 315 | 1, 0, 0, 0, 0, 0, 0, 316 | 1, 0, 0, 0, 0, 0, 0, 317 | 1, 0, 0, 0, 0, 0, 0, 318 | }, 319 | 'Q': { 320 | 1, 1, 1, 1, 1, 1, 1, 321 | 1, 0, 0, 0, 0, 0, 1, 322 | 1, 0, 0, 0, 0, 0, 1, 323 | 1, 0, 0, 0, 0, 0, 1, 324 | 1, 0, 0, 0, 1, 0, 1, 325 | 1, 0, 0, 0, 0, 1, 0, 326 | 1, 1, 1, 1, 1, 0, 1, 327 | }, 328 | 'R': { 329 | 1, 1, 1, 1, 1, 1, 1, 330 | 1, 0, 0, 0, 0, 0, 1, 331 | 1, 0, 0, 0, 0, 0, 1, 332 | 1, 1, 1, 1, 1, 1, 1, 333 | 1, 0, 0, 0, 1, 0, 0, 334 | 1, 0, 0, 0, 0, 1, 0, 335 | 1, 0, 0, 0, 0, 0, 1, 336 | }, 337 | 'S': { 338 | 1, 1, 1, 1, 1, 1, 1, 339 | 1, 0, 0, 0, 0, 0, 0, 340 | 1, 0, 0, 0, 0, 0, 0, 341 | 1, 1, 1, 1, 1, 1, 1, 342 | 0, 0, 0, 0, 0, 0, 1, 343 | 0, 0, 0, 0, 0, 0, 1, 344 | 1, 1, 1, 1, 1, 1, 1, 345 | }, 346 | 'T': { 347 | 1, 1, 1, 1, 1, 1, 1, 348 | 0, 0, 0, 1, 0, 0, 0, 349 | 0, 0, 0, 1, 0, 0, 0, 350 | 0, 0, 0, 1, 0, 0, 0, 351 | 0, 0, 0, 1, 0, 0, 0, 352 | 0, 0, 0, 1, 0, 0, 0, 353 | 0, 0, 0, 1, 0, 0, 0, 354 | }, 355 | 'U': { 356 | 1, 0, 0, 0, 0, 0, 1, 357 | 1, 0, 0, 0, 0, 0, 1, 358 | 1, 0, 0, 0, 0, 0, 1, 359 | 1, 0, 0, 0, 0, 0, 1, 360 | 1, 0, 0, 0, 0, 0, 1, 361 | 1, 0, 0, 0, 0, 0, 1, 362 | 1, 1, 1, 1, 1, 1, 1, 363 | }, 364 | 'V': { 365 | 1, 0, 0, 0, 0, 0, 1, 366 | 1, 0, 0, 0, 0, 0, 1, 367 | 1, 0, 0, 0, 0, 0, 1, 368 | 1, 0, 0, 0, 0, 0, 1, 369 | 0, 1, 0, 0, 0, 1, 0, 370 | 0, 0, 1, 0, 1, 0, 0, 371 | 0, 0, 0, 1, 0, 0, 0, 372 | }, 373 | 'W': { 374 | 1, 0, 0, 0, 0, 0, 1, 375 | 1, 0, 0, 0, 0, 0, 1, 376 | 1, 0, 0, 0, 0, 0, 1, 377 | 1, 0, 0, 1, 0, 0, 1, 378 | 1, 0, 0, 1, 0, 0, 1, 379 | 1, 0, 0, 1, 0, 0, 1, 380 | 1, 1, 1, 1, 1, 1, 1, 381 | }, 382 | 'X': { 383 | 1, 0, 0, 0, 0, 0, 1, 384 | 0, 1, 0, 0, 0, 1, 0, 385 | 0, 0, 1, 0, 1, 0, 0, 386 | 0, 0, 0, 1, 0, 0, 0, 387 | 0, 0, 1, 0, 1, 0, 0, 388 | 0, 1, 0, 0, 0, 1, 0, 389 | 1, 0, 0, 0, 0, 0, 1, 390 | }, 391 | 'Y': { 392 | 1, 0, 0, 0, 0, 0, 1, 393 | 0, 1, 0, 0, 0, 1, 0, 394 | 0, 0, 1, 0, 1, 0, 0, 395 | 0, 0, 0, 1, 0, 0, 0, 396 | 0, 0, 0, 1, 0, 0, 0, 397 | 0, 0, 0, 1, 0, 0, 0, 398 | 0, 0, 0, 1, 0, 0, 0, 399 | }, 400 | 'Z': { 401 | 1, 1, 1, 1, 1, 1, 1, 402 | 0, 0, 0, 0, 0, 1, 0, 403 | 0, 0, 0, 0, 1, 0, 0, 404 | 0, 0, 0, 1, 0, 0, 0, 405 | 0, 0, 1, 0, 0, 0, 0, 406 | 0, 1, 0, 0, 0, 0, 0, 407 | 1, 1, 1, 1, 1, 1, 1, 408 | }, 409 | ' ': { 410 | 0, 0, 0, 0, 0, 0, 0, 411 | 0, 0, 0, 0, 0, 0, 0, 412 | 0, 0, 0, 0, 0, 0, 0, 413 | 0, 0, 0, 0, 0, 0, 0, 414 | 0, 0, 0, 0, 0, 0, 0, 415 | 0, 0, 0, 0, 0, 0, 0, 416 | 0, 0, 0, 0, 0, 0, 0, 417 | }, 418 | '.': { 419 | 0, 0, 0, 0, 0, 0, 0, 420 | 0, 0, 0, 0, 0, 0, 0, 421 | 0, 0, 0, 0, 0, 0, 0, 422 | 0, 0, 0, 0, 0, 0, 0, 423 | 0, 0, 0, 0, 0, 0, 0, 424 | 0, 0, 1, 1, 0, 0, 0, 425 | 0, 0, 1, 1, 0, 0, 0, 426 | }, 427 | ',': { 428 | 0, 0, 0, 0, 0, 0, 0, 429 | 0, 0, 0, 0, 0, 0, 0, 430 | 0, 0, 0, 0, 0, 0, 0, 431 | 0, 0, 0, 0, 0, 0, 0, 432 | 0, 0, 0, 0, 0, 0, 0, 433 | 0, 1, 0, 0, 0, 0, 0, 434 | 1, 0, 0, 0, 0, 0, 0, 435 | }, 436 | '?': { 437 | 1, 1, 1, 1, 1, 1, 1, 438 | 0, 0, 0, 0, 0, 0, 1, 439 | 0, 0, 0, 0, 0, 0, 1, 440 | 0, 0, 0, 1, 1, 1, 1, 441 | 0, 0, 0, 1, 0, 0, 0, 442 | 0, 0, 0, 0, 0, 0, 0, 443 | 0, 0, 0, 1, 0, 0, 0, 444 | }, 445 | ':': { 446 | 0, 0, 1, 1, 0, 0, 0, 447 | 0, 0, 1, 1, 0, 0, 0, 448 | 0, 0, 0, 0, 0, 0, 0, 449 | 0, 0, 0, 0, 0, 0, 0, 450 | 0, 0, 0, 0, 0, 0, 0, 451 | 0, 0, 1, 1, 0, 0, 0, 452 | 0, 0, 1, 1, 0, 0, 0, 453 | }, 454 | '!': { 455 | 0, 0, 0, 1, 0, 0, 0, 456 | 0, 0, 0, 1, 0, 0, 0, 457 | 0, 0, 0, 1, 0, 0, 0, 458 | 0, 0, 0, 1, 0, 0, 0, 459 | 0, 0, 0, 1, 0, 0, 0, 460 | 0, 0, 0, 0, 0, 0, 0, 461 | 0, 0, 0, 1, 0, 0, 0, 462 | }, 463 | '/': { 464 | 0, 0, 0, 0, 0, 0, 1, 465 | 0, 0, 0, 0, 0, 1, 0, 466 | 0, 0, 0, 0, 1, 0, 0, 467 | 0, 0, 0, 1, 0, 0, 0, 468 | 0, 0, 1, 0, 0, 0, 0, 469 | 0, 1, 0, 0, 0, 0, 0, 470 | 1, 0, 0, 0, 0, 0, 0, 471 | }, 472 | '*': { 473 | 1, 0, 0, 1, 0, 0, 1, 474 | 0, 1, 0, 1, 0, 1, 0, 475 | 0, 0, 1, 1, 1, 0, 0, 476 | 0, 0, 0, 1, 0, 0, 0, 477 | 0, 0, 1, 1, 1, 0, 0, 478 | 0, 1, 0, 1, 0, 1, 0, 479 | 1, 0, 0, 1, 0, 0, 1, 480 | }, 481 | '"': { 482 | 0, 1, 1, 0, 1, 1, 0, 483 | 0, 1, 1, 0, 1, 1, 0, 484 | 0, 0, 0, 0, 0, 0, 0, 485 | 0, 0, 0, 0, 0, 0, 0, 486 | 0, 0, 0, 0, 0, 0, 0, 487 | 0, 0, 0, 0, 0, 0, 0, 488 | 0, 0, 0, 0, 0, 0, 0, 489 | }, 490 | '\'': { 491 | 0, 0, 1, 1, 0, 0, 0, 492 | 0, 0, 1, 1, 0, 0, 0, 493 | 0, 0, 0, 0, 0, 0, 0, 494 | 0, 0, 0, 0, 0, 0, 0, 495 | 0, 0, 0, 0, 0, 0, 0, 496 | 0, 0, 0, 0, 0, 0, 0, 497 | 0, 0, 0, 0, 0, 0, 0, 498 | }, 499 | '-': { 500 | 0, 0, 0, 0, 0, 0, 0, 501 | 0, 0, 0, 0, 0, 0, 0, 502 | 0, 0, 0, 0, 0, 0, 0, 503 | 1, 1, 1, 1, 1, 1, 1, 504 | 0, 0, 0, 0, 0, 0, 0, 505 | 0, 0, 0, 0, 0, 0, 0, 506 | 0, 0, 0, 0, 0, 0, 0, 507 | }, 508 | '+': { 509 | 0, 0, 0, 1, 0, 0, 0, 510 | 0, 0, 0, 1, 0, 0, 0, 511 | 0, 0, 0, 1, 0, 0, 0, 512 | 1, 1, 1, 1, 1, 1, 1, 513 | 0, 0, 0, 1, 0, 0, 0, 514 | 0, 0, 0, 1, 0, 0, 0, 515 | 0, 0, 0, 1, 0, 0, 0, 516 | }, 517 | '=': { 518 | 0, 0, 0, 0, 0, 0, 0, 519 | 0, 0, 0, 0, 0, 0, 0, 520 | 1, 1, 1, 1, 1, 1, 1, 521 | 0, 0, 0, 0, 0, 0, 0, 522 | 1, 1, 1, 1, 1, 1, 1, 523 | 0, 0, 0, 0, 0, 0, 0, 524 | 0, 0, 0, 0, 0, 0, 0, 525 | }, 526 | '#': { 527 | 0, 0, 1, 0, 1, 0, 0, 528 | 0, 0, 1, 0, 1, 0, 0, 529 | 1, 1, 1, 1, 1, 1, 1, 530 | 0, 0, 1, 0, 1, 0, 0, 531 | 1, 1, 1, 1, 1, 1, 1, 532 | 0, 0, 1, 0, 1, 0, 0, 533 | 0, 0, 1, 0, 1, 0, 0, 534 | }, 535 | '@': { 536 | 1, 1, 1, 1, 1, 1, 1, 537 | 1, 0, 0, 0, 0, 0, 1, 538 | 1, 0, 1, 1, 1, 0, 1, 539 | 1, 0, 1, 0, 1, 0, 1, 540 | 1, 0, 1, 1, 1, 1, 1, 541 | 1, 0, 0, 0, 0, 0, 0, 542 | 1, 1, 1, 1, 1, 1, 0, 543 | }, 544 | '$': { 545 | 0, 0, 0, 1, 0, 0, 0, 546 | 0, 1, 1, 1, 1, 1, 0, 547 | 0, 1, 0, 1, 0, 0, 0, 548 | 0, 1, 1, 1, 1, 1, 0, 549 | 0, 0, 0, 1, 0, 1, 0, 550 | 0, 1, 1, 1, 1, 1, 0, 551 | 0, 0, 0, 1, 0, 0, 0, 552 | }, 553 | '%': { 554 | 0, 0, 0, 0, 0, 0, 0, 555 | 0, 1, 1, 0, 0, 1, 0, 556 | 0, 1, 1, 0, 1, 0, 0, 557 | 0, 0, 0, 1, 0, 0, 0, 558 | 0, 0, 1, 0, 1, 1, 0, 559 | 0, 1, 0, 0, 1, 1, 0, 560 | 0, 0, 0, 0, 0, 0, 0, 561 | }, 562 | '^': { 563 | 0, 0, 0, 0, 0, 0, 0, 564 | 0, 0, 0, 0, 0, 0, 0, 565 | 0, 0, 0, 1, 0, 0, 0, 566 | 0, 0, 1, 0, 1, 0, 0, 567 | 0, 1, 0, 0, 0, 1, 0, 568 | 0, 0, 0, 0, 0, 0, 0, 569 | 0, 0, 0, 0, 0, 0, 0, 570 | }, 571 | '&': { 572 | 0, 0, 1, 1, 1, 0, 0, 573 | 0, 0, 1, 0, 1, 0, 0, 574 | 0, 0, 1, 0, 1, 0, 0, 575 | 0, 0, 1, 1, 0, 0, 0, 576 | 0, 1, 0, 0, 1, 0, 1, 577 | 0, 1, 0, 0, 0, 1, 0, 578 | 0, 1, 1, 1, 1, 0, 1, 579 | }, 580 | '(': { 581 | 0, 0, 0, 0, 1, 0, 0, 582 | 0, 0, 0, 1, 0, 0, 0, 583 | 0, 0, 1, 0, 0, 0, 0, 584 | 0, 0, 1, 0, 0, 0, 0, 585 | 0, 0, 1, 0, 0, 0, 0, 586 | 0, 0, 0, 1, 0, 0, 0, 587 | 0, 0, 0, 0, 1, 0, 0, 588 | }, 589 | ')': { 590 | 0, 0, 1, 0, 0, 0, 0, 591 | 0, 0, 0, 1, 0, 0, 0, 592 | 0, 0, 0, 0, 1, 0, 0, 593 | 0, 0, 0, 0, 1, 0, 0, 594 | 0, 0, 0, 0, 1, 0, 0, 595 | 0, 0, 0, 1, 0, 0, 0, 596 | 0, 0, 1, 0, 0, 0, 0, 597 | }, 598 | '[': { 599 | 0, 0, 1, 1, 1, 0, 0, 600 | 0, 0, 1, 0, 0, 0, 0, 601 | 0, 0, 1, 0, 0, 0, 0, 602 | 0, 0, 1, 0, 0, 0, 0, 603 | 0, 0, 1, 0, 0, 0, 0, 604 | 0, 0, 1, 0, 0, 0, 0, 605 | 0, 0, 1, 1, 1, 0, 0, 606 | }, 607 | ']': { 608 | 0, 0, 1, 1, 1, 0, 0, 609 | 0, 0, 0, 0, 1, 0, 0, 610 | 0, 0, 0, 0, 1, 0, 0, 611 | 0, 0, 0, 0, 1, 0, 0, 612 | 0, 0, 0, 0, 1, 0, 0, 613 | 0, 0, 0, 0, 1, 0, 0, 614 | 0, 0, 1, 1, 1, 0, 0, 615 | }, 616 | '}': { 617 | 0, 0, 1, 1, 1, 0, 0, 618 | 0, 0, 0, 0, 1, 0, 0, 619 | 0, 0, 0, 0, 1, 0, 0, 620 | 0, 0, 0, 0, 1, 1, 0, 621 | 0, 0, 0, 0, 1, 0, 0, 622 | 0, 0, 0, 0, 1, 0, 0, 623 | 0, 0, 1, 1, 1, 0, 0, 624 | }, 625 | '{': { 626 | 0, 0, 1, 1, 1, 0, 0, 627 | 0, 0, 1, 0, 0, 0, 0, 628 | 0, 0, 1, 0, 0, 0, 0, 629 | 0, 1, 1, 0, 0, 0, 0, 630 | 0, 0, 1, 0, 0, 0, 0, 631 | 0, 0, 1, 0, 0, 0, 0, 632 | 0, 0, 1, 1, 1, 0, 0, 633 | }, 634 | '_': { 635 | 0, 0, 0, 0, 0, 0, 0, 636 | 0, 0, 0, 0, 0, 0, 0, 637 | 0, 0, 0, 0, 0, 0, 0, 638 | 0, 0, 0, 0, 0, 0, 0, 639 | 0, 0, 0, 0, 0, 0, 0, 640 | 0, 0, 0, 0, 0, 0, 0, 641 | 1, 1, 1, 1, 1, 1, 1, 642 | }, 643 | '|': { 644 | 0, 0, 0, 1, 0, 0, 0, 645 | 0, 0, 0, 1, 0, 0, 0, 646 | 0, 0, 0, 1, 0, 0, 0, 647 | 0, 0, 0, 1, 0, 0, 0, 648 | 0, 0, 0, 1, 0, 0, 0, 649 | 0, 0, 0, 1, 0, 0, 0, 650 | 0, 0, 0, 1, 0, 0, 0, 651 | }, 652 | '\\': { 653 | 1, 0, 0, 0, 0, 0, 0, 654 | 0, 1, 0, 0, 0, 0, 0, 655 | 0, 0, 1, 0, 0, 0, 0, 656 | 0, 0, 0, 1, 0, 0, 0, 657 | 0, 0, 0, 0, 1, 0, 0, 658 | 0, 0, 0, 0, 0, 1, 0, 659 | 0, 0, 0, 0, 0, 0, 1, 660 | }, 661 | ';': { 662 | 0, 0, 1, 1, 0, 0, 0, 663 | 0, 0, 1, 1, 0, 0, 0, 664 | 0, 0, 0, 0, 0, 0, 0, 665 | 0, 0, 0, 0, 0, 0, 0, 666 | 0, 0, 0, 0, 0, 0, 0, 667 | 0, 0, 0, 1, 0, 0, 0, 668 | 0, 0, 1, 0, 0, 0, 0, 669 | }, 670 | '<': { 671 | 0, 0, 0, 0, 1, 0, 0, 672 | 0, 0, 0, 1, 0, 0, 0, 673 | 0, 0, 1, 0, 0, 0, 0, 674 | 0, 1, 0, 0, 0, 0, 0, 675 | 0, 0, 1, 0, 0, 0, 0, 676 | 0, 0, 0, 1, 0, 0, 0, 677 | 0, 0, 0, 0, 1, 0, 0, 678 | }, 679 | '>': { 680 | 0, 1, 0, 0, 0, 0, 0, 681 | 0, 0, 1, 0, 0, 0, 0, 682 | 0, 0, 0, 1, 0, 0, 0, 683 | 0, 0, 0, 0, 1, 0, 0, 684 | 0, 0, 0, 1, 0, 0, 0, 685 | 0, 0, 1, 0, 0, 0, 0, 686 | 0, 1, 0, 0, 0, 0, 0, 687 | }, 688 | '`': { 689 | 0, 0, 1, 0, 0, 0, 0, 690 | 0, 0, 0, 1, 0, 0, 0, 691 | 0, 0, 0, 0, 0, 0, 0, 692 | 0, 0, 0, 0, 0, 0, 0, 693 | 0, 0, 0, 0, 0, 0, 0, 694 | 0, 0, 0, 0, 0, 0, 0, 695 | 0, 0, 0, 0, 0, 0, 0, 696 | }, 697 | '~': { 698 | 0, 0, 0, 0, 0, 0, 0, 699 | 0, 0, 0, 0, 0, 0, 0, 700 | 0, 1, 1, 0, 0, 1, 0, 701 | 1, 0, 0, 1, 1, 0, 0, 702 | 0, 0, 0, 0, 0, 0, 0, 703 | 0, 0, 0, 0, 0, 0, 0, 704 | 0, 0, 0, 0, 0, 0, 0, 705 | }, 706 | } 707 | --------------------------------------------------------------------------------