Compare commits
167 Commits
Author | SHA1 | Date |
---|---|---|
Maximilian Mader | e3c8f1c1d4 | |
Lior Halphon | 56deb4b92e | |
Lior Halphon | eceb2e4830 | |
Lior Halphon | 0dbfaef4cf | |
Lior Halphon | 88f5b22bf6 | |
Lior Halphon | d9b8e829a5 | |
Lior Halphon | 856a2b0ebe | |
Lior Halphon | 004c20d8e2 | |
Lior Halphon | 3c4bfd2a1b | |
Lior Halphon | faccdd3e9b | |
Lior Halphon | e466c3c5b1 | |
Lior Halphon | 7071032288 | |
Lior Halphon | 887a8104f5 | |
Lior Halphon | a773297b3a | |
Lior Halphon | 63a858d767 | |
Lior Halphon | 1065a40d8f | |
Lior Halphon | f2429e1c25 | |
Lior Halphon | 96d127e160 | |
Lior Halphon | 1b38e8c932 | |
Lior Halphon | 52a4c09855 | |
Lior Halphon | 6a24598266 | |
Lior Halphon | aaf9a76b67 | |
Lior Halphon | 58df8144ec | |
Lior Halphon | 16913f925b | |
Lior Halphon | 9a765820cc | |
Lior Halphon | fd6b734fd0 | |
Lior Halphon | 9ae2c9fd54 | |
Lior Halphon | 8f8b7f6b33 | |
Lior Halphon | ec4c1948f5 | |
Lior Halphon | 22f8ab6509 | |
Lior Halphon | 517f455486 | |
Lior Halphon | 9b5dc9eca7 | |
Lior Halphon | b932f6699e | |
Lior Halphon | 20e9b1c655 | |
Lior Halphon | 3fbeb61c09 | |
Lior Halphon | ab4fa3a478 | |
Lior Halphon | 4d90504688 | |
Lior Halphon | d41c188cfd | |
Lior Halphon | 24796acccf | |
Lior Halphon | 979d32faed | |
Lior Halphon | 197a475fab | |
radimerry | 426d3d3a37 | |
Lior Halphon | 4f91b19a94 | |
Lior Halphon | abf6e5632c | |
Lior Halphon | eb60dbce0d | |
Lior Halphon | 12891c641b | |
Lior Halphon | 6bd7b96ed5 | |
Lior Halphon | 95f5eeb40b | |
Ricardo Maurizio Paul | c79e67b8cc | |
Lior Halphon | cdfcc4ca2d | |
Lior Halphon | 6055092249 | |
Lior Halphon | 5cc845d715 | |
Lior Halphon | 706135113c | |
Lior Halphon | 8c86cff486 | |
Lior Halphon | bb836662dd | |
Lior Halphon | 87fdf91e0c | |
Lior Halphon | f866284b49 | |
Lior Halphon | 4521bb4767 | |
Lior Halphon | a68f749c3a | |
Lior Halphon | cb73e0b91a | |
Lior Halphon | 6337e3e43a | |
Lior Halphon | ac29b4391e | |
Lior Halphon | 69a5ed3396 | |
Lior Halphon | 36e2896ec7 | |
Lior Halphon | bef1529bb2 | |
Lior Halphon | 851d44869f | |
Lior Halphon | 18126994ff | |
Lior Halphon | 51cf4c638c | |
Ricardo Maurizio Paul | de21e8d628 | |
Lior Halphon | bfdab8f246 | |
Lior Halphon | b2edcc9543 | |
Lior Halphon | 2a034d4ebe | |
Lior Halphon | 339de0db96 | |
Anders | 9c271a637d | |
Lior Halphon | 019f262531 | |
Nikos Chantziaras | 9e8f918b27 | |
Lior Halphon | b31bd58642 | |
offtkp | dc16104cfd | |
Lior Halphon | 79945c8c18 | |
Lior Halphon | 9c7bed97d5 | |
Lior Halphon | 86a1977034 | |
Lior Halphon | 9fe965bcc2 | |
Lior Halphon | b5e271386a | |
Lior Halphon | ef15c9b160 | |
Lior Halphon | d713ba85c7 | |
Lior Halphon | ab109da683 | |
Lior Halphon | 5e119548e9 | |
Lior Halphon | 0925b06555 | |
Lior Halphon | 965e623637 | |
Lior Halphon | 7350843cca | |
Lior Halphon | c78a003712 | |
Lior Halphon | a621803e82 | |
Lior Halphon | 777013e998 | |
Lior Halphon | 2c635c7a87 | |
Lior Halphon | 641f26e13e | |
Lior Halphon | 8073e3d39e | |
Lior Halphon | 4d74719d56 | |
Lior Halphon | f52152b2c9 | |
Lior Halphon | 586459bb74 | |
Lior Halphon | 7c8b9cf05a | |
Lior Halphon | a48f251039 | |
Lior Halphon | 9a2e8e1acf | |
Lior Halphon | f02bb2f0e6 | |
Lior Halphon | 3c6a46830d | |
Lior Halphon | 4c6bc91ded | |
Lior Halphon | a4209b47d0 | |
Lior Halphon | efe31cefc9 | |
Lior Halphon | c730ba767b | |
Lior Halphon | f8a105e8d0 | |
Lior Halphon | 97c758ba75 | |
Lior Halphon | 4e27558ac2 | |
Lior Halphon | 850e7bb78c | |
Lior Halphon | b5eea012cc | |
Lior Halphon | 6a8db89ae5 | |
Lior Halphon | 1c6ecc2e14 | |
Lior Halphon | e7236deb11 | |
Lior Halphon | ba5416ee5b | |
Lior Halphon | 320aff1d1e | |
Lior Halphon | 864f0927be | |
Lior Halphon | 7c5704621a | |
Lior Halphon | 37ca174f37 | |
Lior Halphon | 76b881c2e1 | |
Lior Halphon | 967fdadd7c | |
Lior Halphon | 1a41957b3c | |
Lior Halphon | ad1f019893 | |
Lior Halphon | 941afee3ba | |
Lior Halphon | dbb14d7040 | |
Lior Halphon | 44ee6dc73f | |
Lior Halphon | a7f7530eed | |
Lior Halphon | 4bebd2bc33 | |
Lior Halphon | 3a2d028efa | |
Lior Halphon | 9e3ad31df1 | |
Lior Halphon | 26656de44f | |
Lior Halphon | 81e2ec08e0 | |
Lior Halphon | aa5a279116 | |
Lior Halphon | 0ab7bf7749 | |
Lior Halphon | 196aaaa7ed | |
Lior Halphon | 8676a7c7bc | |
Lior Halphon | f810a2cd60 | |
Lior Halphon | 582a5588ba | |
Lior Halphon | 56b14c67aa | |
Lior Halphon | 95153af1d6 | |
Maximilian Mader | 13e0b90b47 | |
Lior Halphon | ee03b1e433 | |
Lior Halphon | cce36f1754 | |
Lior Halphon | e903333c7e | |
Lior Halphon | ab75858c86 | |
Lior Halphon | b45761146f | |
Lior Halphon | 3133687e68 | |
Lior Halphon | dbe9035c55 | |
Lior Halphon | 5088bd0959 | |
Lior Halphon | e922b3fc4a | |
orbea | 7c9ab0fd46 | |
Lior Halphon | eaeeb49612 | |
Lior Halphon | b92dd51101 | |
orbea | adfc329cdf | |
orbea | 5cf71b406e | |
Lior Halphon | d92148b461 | |
Lior Halphon | ffa53eda20 | |
Lior Halphon | 4ce8e77796 | |
Lior Halphon | 20cbc896a1 | |
orbea | fefb81ab65 | |
Lior Halphon | ec012cf9f8 | |
Lior Halphon | c4a14ac4db | |
Lior Halphon | b1187919d3 | |
Lior Halphon | 2c71ca789f | |
Lior Halphon | 8df572f92e |
42
BESS.md
|
@ -176,6 +176,48 @@ The length of this block is 0x11 bytes long and it follows the following structu
|
|||
| 0x0E | Scheduled alarm time days (16-bit) |
|
||||
| 0x10 | Alarm enabled flag (8-bits, either 0 or 1) |
|
||||
|
||||
#### TPP1 block
|
||||
The TPP1 block uses the `'TPP1'` identifier, and is an optional block that is used while emulating a TPP1 cartridge to store RTC information. This block can be omitted if the ROM header does not specify the inclusion of a RTC.
|
||||
|
||||
The length of this block is 0x11 bytes long and it follows the following structure:
|
||||
|
||||
| Offset | Content |
|
||||
|--------|-------------------------------------------------------|
|
||||
| 0x00 | UNIX timestamp at the time of the save state (64-bit) |
|
||||
| 0x08 | The current RTC data (4 bytes) |
|
||||
| 0x0C | The latched RTC data (4 bytes) |
|
||||
| 0x10 | The value of the MR4 register (8-bits) |
|
||||
|
||||
|
||||
#### MBC7 block
|
||||
The MBC7 block uses the `'MBC7'` identifier, and is an optional block that is used while emulating an MBC7 cartridge to store the EEPROM communication state and motion control state.
|
||||
|
||||
The length of this block is 0xA bytes long and it follows the following structure:
|
||||
|
||||
| Offset | Content |
|
||||
|--------|-------------------------------------------------------|
|
||||
| 0x00 | Flags (8-bits) |
|
||||
| 0x01 | Argument bits left (8-bits) |
|
||||
| 0x02 | Current EEPROM command (16-bits) |
|
||||
| 0x04 | Pending bits to read (16-bits) |
|
||||
| 0x06 | Latched gyro X value (16-bits) |
|
||||
| 0x08 | Latched gyro Y value (16-bits) |
|
||||
|
||||
The meaning of the individual bits in flags are:
|
||||
* Bit 0: Latch ready; set after writing `0x55` to `0xAX0X` and reset after writing `0xAA` to `0xAX1X`
|
||||
* Bit 1: EEPROM DO line
|
||||
* Bit 2: EEPROM DI line
|
||||
* Bit 3: EEPROM CLK line
|
||||
* Bit 4: EEPROM CS line
|
||||
* Bit 5: EEPROM write enable; set after an `EWEN` command, reset after an `EWDS` command
|
||||
* Bits 6-7: Unused.
|
||||
|
||||
The current EEPROM command field has bits pushed to its LSB first, padded with zeros. For example, if the ROM clocked a single `1` bit, this field should contain `0b1`; if the ROM later clocks a `0` bit, this field should contain `0b10`.
|
||||
|
||||
If the currently transmitted command has an argument, the "Argument bits left" field should contain the number argument bits remaining. Otherwise, it should contain 0.
|
||||
|
||||
The "Pending bits to read" field contains the pending bits waiting to be shifted into the DO signal, MSB first, padded with ones.
|
||||
|
||||
#### SGB block
|
||||
|
||||
The SGB block uses the `'SGB '` identifier, and is an optional block that is only used while emulating an SGB or SGB2 *and* SGB commands enabled. Implementations must not save this block on other models or when SGB commands are disabled, and should assume SGB commands are disabled if this block is missing.
|
||||
|
|
Before Width: | Height: | Size: 479 B After Width: | Height: | Size: 477 B |
|
@ -1,5 +1,7 @@
|
|||
; SameBoy CGB bootstrap ROM
|
||||
; Todo: use friendly names for HW registers instead of magic numbers
|
||||
|
||||
INCLUDE "hardware.inc"
|
||||
|
||||
SECTION "BootCode", ROM0[$0]
|
||||
Start:
|
||||
; Init stack pointer
|
||||
|
@ -7,13 +9,6 @@ Start:
|
|||
|
||||
; Clear memory VRAM
|
||||
call ClearMemoryPage8000
|
||||
ld a, 2
|
||||
ld c, $70
|
||||
ld [c], a
|
||||
; Clear RAM Bank 2 (Like the original boot ROM)
|
||||
ld h, $D0
|
||||
call ClearMemoryPage
|
||||
ld [c], a
|
||||
|
||||
; Clear OAM
|
||||
ld h, $fe
|
||||
|
@ -39,18 +34,19 @@ ENDC
|
|||
; Clear title checksum
|
||||
ldh [TitleChecksum], a
|
||||
|
||||
; Init Audio
|
||||
ld a, $80
|
||||
ldh [$26], a
|
||||
ldh [$11], a
|
||||
ldh [rNR52], a
|
||||
ldh [rNR11], a
|
||||
ld a, $f3
|
||||
ldh [$12], a
|
||||
ldh [$25], a
|
||||
ldh [rNR12], a
|
||||
ldh [rNR51], a
|
||||
ld a, $77
|
||||
ldh [$24], a
|
||||
ldh [rNR50], a
|
||||
|
||||
; Init BG palette
|
||||
ld a, $fc
|
||||
ldh [$47], a
|
||||
ldh [rBGP], a
|
||||
|
||||
; Load logo from ROM.
|
||||
; A nibble represents a 4-pixels line, 2 bytes represent a 4x4 tile, scaled to 8x8.
|
||||
|
@ -72,14 +68,14 @@ ENDC
|
|||
|
||||
; Clear the second VRAM bank
|
||||
ld a, 1
|
||||
ldh [$4F], a
|
||||
ldh [rVBK], a
|
||||
call ClearMemoryPage8000
|
||||
call LoadTileset
|
||||
|
||||
ld b, 3
|
||||
IF DEF(FAST)
|
||||
xor a
|
||||
ldh [$4F], a
|
||||
ldh [rVBK], a
|
||||
ELSE
|
||||
; Load Tilemap
|
||||
ld hl, $98C2
|
||||
|
@ -129,11 +125,11 @@ ELSE
|
|||
push af
|
||||
; Switch to second VRAM Bank
|
||||
ld a, 1
|
||||
ldh [$4F], a
|
||||
ldh [rVBK], a
|
||||
ld [hl], 8
|
||||
; Switch to back first VRAM Bank
|
||||
xor a
|
||||
ldh [$4F], a
|
||||
ldh [rVBK], a
|
||||
pop af
|
||||
ldi [hl], a
|
||||
ret
|
||||
|
@ -187,7 +183,7 @@ ENDC
|
|||
|
||||
; Turn on LCD
|
||||
ld a, $91
|
||||
ldh [$40], a
|
||||
ldh [rLCDC], a
|
||||
|
||||
IF !DEF(FAST)
|
||||
call DoIntroAnimation
|
||||
|
@ -220,12 +216,15 @@ ENDC
|
|||
IF DEF(AGB)
|
||||
ld b, 1
|
||||
ENDC
|
||||
jr BootGame
|
||||
|
||||
; Will be filled with NOPs
|
||||
HDMAData:
|
||||
db $D0, $00, $98, $A0, $12
|
||||
db $D0, $00, $80, $00, $40
|
||||
|
||||
SECTION "BootGame", ROM0[$fe]
|
||||
BootGame:
|
||||
ldh [$50], a
|
||||
ldh [rBANK], a ; unmap boot ROM
|
||||
|
||||
SECTION "MoreStuff", ROM0[$200]
|
||||
; Game Palettes Data
|
||||
|
@ -610,9 +609,9 @@ WaitBFrames:
|
|||
ret
|
||||
|
||||
PlaySound:
|
||||
ldh [$13], a
|
||||
ldh [rNR13], a
|
||||
ld a, $87
|
||||
ldh [$14], a
|
||||
ldh [rNR14], a
|
||||
ret
|
||||
|
||||
ClearMemoryPage8000:
|
||||
|
@ -763,7 +762,7 @@ ReadTrademarkSymbol:
|
|||
DoIntroAnimation:
|
||||
; Animate the intro
|
||||
ld a, 1
|
||||
ldh [$4F], a
|
||||
ldh [rVBK], a
|
||||
ld d, 26
|
||||
.animationLoop
|
||||
ld b, 2
|
||||
|
@ -835,8 +834,6 @@ IF !DEF(FAST)
|
|||
res 2, b
|
||||
.redNotMaxed
|
||||
|
||||
; add de, bc
|
||||
; ld [hli], de
|
||||
ld a, e
|
||||
add c
|
||||
ld [hli], a
|
||||
|
@ -854,12 +851,19 @@ IF !DEF(FAST)
|
|||
dec b
|
||||
jr nz, .fadeLoop
|
||||
ENDC
|
||||
ld a, 1
|
||||
ld a, 2
|
||||
ldh [rSVBK], a
|
||||
; Clear RAM Bank 2 (Like the original boot ROM)
|
||||
ld hl, $D000
|
||||
call ClearMemoryPage
|
||||
inc a
|
||||
call ClearVRAMViaHDMA
|
||||
call _ClearVRAMViaHDMA
|
||||
call ClearVRAMViaHDMA ; A = $40, so it's bank 0
|
||||
ld a, $ff
|
||||
ldh [$00], a
|
||||
xor a
|
||||
ldh [rSVBK], a
|
||||
cpl
|
||||
ldh [rJOYP], a
|
||||
|
||||
; Final values for CGB mode
|
||||
ld d, a
|
||||
|
@ -871,7 +875,7 @@ ENDC
|
|||
call z, EmulateDMG
|
||||
bit 7, a
|
||||
|
||||
ldh [$4C], a
|
||||
ldh [rKEY0], a ; write CGB compatibility byte, CGB mode
|
||||
ldh a, [TitleChecksum]
|
||||
ld b, a
|
||||
|
||||
|
@ -901,7 +905,7 @@ ENDC
|
|||
|
||||
.emulateDMGForCGBGame
|
||||
call EmulateDMG
|
||||
ldh [$4C], a
|
||||
ldh [rKEY0], a ; write $04, DMG emulation mode
|
||||
ld a, $1
|
||||
ret
|
||||
|
||||
|
@ -915,7 +919,7 @@ GetKeyComboPalette:
|
|||
|
||||
EmulateDMG:
|
||||
ld a, 1
|
||||
ldh [$6C], a ; DMG Emulation
|
||||
ldh [rOPRI], a ; DMG Emulation sprite priority
|
||||
call GetPaletteIndex
|
||||
bit 7, a
|
||||
call nz, LoadDMGTilemap
|
||||
|
@ -1052,7 +1056,7 @@ LoadPalettesFromHRAM:
|
|||
|
||||
LoadBGPalettes:
|
||||
ld e, 0
|
||||
ld c, $68
|
||||
ld c, LOW(rBGPI)
|
||||
|
||||
LoadPalettes:
|
||||
ld a, $80
|
||||
|
@ -1067,9 +1071,10 @@ LoadPalettes:
|
|||
ret
|
||||
|
||||
ClearVRAMViaHDMA:
|
||||
ldh [$4F], a
|
||||
ldh [rVBK], a
|
||||
ld hl, HDMAData
|
||||
_ClearVRAMViaHDMA:
|
||||
call WaitFrame ; Wait for vblank
|
||||
ld c, $51
|
||||
ld b, 5
|
||||
.loop
|
||||
|
@ -1083,8 +1088,8 @@ _ClearVRAMViaHDMA:
|
|||
; clobbers AF and HL
|
||||
GetInputPaletteIndex:
|
||||
ld a, $20 ; Select directions
|
||||
ldh [$00], a
|
||||
ldh a, [$00]
|
||||
ldh [rJOYP], a
|
||||
ldh a, [rJOYP]
|
||||
cpl
|
||||
and $F
|
||||
ret z ; No direction keys pressed, no palette
|
||||
|
@ -1098,8 +1103,8 @@ GetInputPaletteIndex:
|
|||
; c = 1: Right, 2: Left, 3: Up, 4: Down
|
||||
|
||||
ld a, $10 ; Select buttons
|
||||
ldh [$00], a
|
||||
ldh a, [$00]
|
||||
ldh [rJOYP], a
|
||||
ldh a, [rJOYP]
|
||||
cpl
|
||||
rla
|
||||
rla
|
||||
|
@ -1223,10 +1228,6 @@ LoadDMGTilemap:
|
|||
pop af
|
||||
ret
|
||||
|
||||
HDMAData:
|
||||
db $88, $00, $98, $A0, $12
|
||||
db $88, $00, $80, $00, $40
|
||||
|
||||
BootEnd:
|
||||
IF BootEnd > $900
|
||||
FAIL "BootROM overflowed: {BootEnd}"
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
; SameBoy DMG bootstrap ROM
|
||||
; Todo: use friendly names for HW registers instead of magic numbers
|
||||
|
||||
INCLUDE "hardware.inc"
|
||||
|
||||
SECTION "BootCode", ROM0[$0]
|
||||
Start:
|
||||
; Init stack pointer
|
||||
|
@ -15,17 +17,17 @@ Start:
|
|||
|
||||
; Init Audio
|
||||
ld a, $80
|
||||
ldh [$26], a
|
||||
ldh [$11], a
|
||||
ldh [rNR52], a
|
||||
ldh [rNR11], a
|
||||
ld a, $f3
|
||||
ldh [$12], a
|
||||
ldh [$25], a
|
||||
ldh [rNR12], a
|
||||
ldh [rNR51], a
|
||||
ld a, $77
|
||||
ldh [$24], a
|
||||
ldh [rNR50], a
|
||||
|
||||
; Init BG palette
|
||||
ld a, $54
|
||||
ldh [$47], a
|
||||
ldh [rBGP], a
|
||||
|
||||
; Load logo from ROM.
|
||||
; A nibble represents a 4-pixels line, 2 bytes represent a 4x4 tile, scaled to 8x8.
|
||||
|
@ -70,11 +72,11 @@ Start:
|
|||
.tilemapDone
|
||||
|
||||
ld a, 30
|
||||
ldh [$ff42], a
|
||||
ldh [rSCY], a
|
||||
|
||||
; Turn on LCD
|
||||
ld a, $91
|
||||
ldh [$40], a
|
||||
ldh [rLCDC], a
|
||||
|
||||
ld d, (-119) & $FF
|
||||
ld c, 15
|
||||
|
@ -84,7 +86,7 @@ Start:
|
|||
ld a, d
|
||||
sra a
|
||||
sra a
|
||||
ldh [$ff42], a
|
||||
ldh [rSCY], a
|
||||
ld a, d
|
||||
add c
|
||||
ld d, a
|
||||
|
@ -92,12 +94,12 @@ Start:
|
|||
cp 8
|
||||
jr nz, .noPaletteChange
|
||||
ld a, $A8
|
||||
ldh [$47], a
|
||||
ldh [rBGP], a
|
||||
.noPaletteChange
|
||||
dec c
|
||||
jr nz, .animate
|
||||
ld a, $fc
|
||||
ldh [$47], a
|
||||
ldh [rBGP], a
|
||||
|
||||
; Play first sound
|
||||
ld a, $83
|
||||
|
@ -167,9 +169,9 @@ WaitBFrames:
|
|||
ret
|
||||
|
||||
PlaySound:
|
||||
ldh [$13], a
|
||||
ldh [rNR13], a
|
||||
ld a, $87
|
||||
ldh [$14], a
|
||||
ldh [rNR14], a
|
||||
ret
|
||||
|
||||
|
||||
|
@ -178,4 +180,4 @@ db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c
|
|||
|
||||
SECTION "BootGame", ROM0[$fe]
|
||||
BootGame:
|
||||
ldh [$50], a
|
||||
ldh [rBANK], a ; unmap boot ROM
|
|
@ -0,0 +1,919 @@
|
|||
;*
|
||||
;* Gameboy Hardware definitions
|
||||
;*
|
||||
;* Based on Jones' hardware.inc
|
||||
;* And based on Carsten Sorensen's ideas.
|
||||
;*
|
||||
;* Rev 1.1 - 15-Jul-97 : Added define check
|
||||
;* Rev 1.2 - 18-Jul-97 : Added revision check macro
|
||||
;* Rev 1.3 - 19-Jul-97 : Modified for RGBASM V1.05
|
||||
;* Rev 1.4 - 27-Jul-97 : Modified for new subroutine prefixes
|
||||
;* Rev 1.5 - 15-Aug-97 : Added _HRAM, PAD, CART defines
|
||||
;* : and Nintendo Logo
|
||||
;* Rev 1.6 - 30-Nov-97 : Added rDIV, rTIMA, rTMA, & rTAC
|
||||
;* Rev 1.7 - 31-Jan-98 : Added _SCRN0, _SCRN1
|
||||
;* Rev 1.8 - 15-Feb-98 : Added rSB, rSC
|
||||
;* Rev 1.9 - 16-Feb-98 : Converted I/O registers to $FFXX format
|
||||
;* Rev 2.0 - : Added GBC registers
|
||||
;* Rev 2.1 - : Added MBC5 & cart RAM enable/disable defines
|
||||
;* Rev 2.2 - : Fixed NR42,NR43, & NR44 equates
|
||||
;* Rev 2.3 - : Fixed incorrect _HRAM equate
|
||||
;* Rev 2.4 - 27-Apr-13 : Added some cart defines (AntonioND)
|
||||
;* Rev 2.5 - 03-May-15 : Fixed format (AntonioND)
|
||||
;* Rev 2.6 - 09-Apr-16 : Added GBC OAM and cart defines (AntonioND)
|
||||
;* Rev 2.7 - 19-Jan-19 : Added rPCMXX (ISSOtm)
|
||||
;* Rev 2.8 - 03-Feb-19 : Added audio registers flags (Álvaro Cuesta)
|
||||
;* Rev 2.9 - 28-Feb-20 : Added utility rP1 constants
|
||||
;* Rev 3.0 - 27-Aug-20 : Register ordering, byte-based sizes, OAM additions, general cleanup (Blitter Object)
|
||||
|
||||
; If all of these are already defined, don't do it again.
|
||||
|
||||
IF !DEF(HARDWARE_INC)
|
||||
HARDWARE_INC = 1
|
||||
|
||||
rev_Check_hardware_inc : MACRO
|
||||
;NOTE: REVISION NUMBER CHANGES MUST BE ADDED
|
||||
;TO SECOND PARAMETER IN FOLLOWING LINE.
|
||||
IF \1 > 3.0 ;PUT REVISION NUMBER HERE
|
||||
WARN "Version \1 or later of 'hardware.inc' is required."
|
||||
ENDC
|
||||
ENDM
|
||||
|
||||
_VRAM EQU $8000 ; $8000->$9FFF
|
||||
_VRAM8000 EQU _VRAM
|
||||
_VRAM8800 EQU _VRAM+$800
|
||||
_VRAM9000 EQU _VRAM+$1000
|
||||
_SCRN0 EQU $9800 ; $9800->$9BFF
|
||||
_SCRN1 EQU $9C00 ; $9C00->$9FFF
|
||||
_SRAM EQU $A000 ; $A000->$BFFF
|
||||
_RAM EQU $C000 ; $C000->$CFFF / $C000->$DFFF
|
||||
_RAMBANK EQU $D000 ; $D000->$DFFF
|
||||
_OAMRAM EQU $FE00 ; $FE00->$FE9F
|
||||
_IO EQU $FF00 ; $FF00->$FF7F,$FFFF
|
||||
_AUD3WAVERAM EQU $FF30 ; $FF30->$FF3F
|
||||
_HRAM EQU $FF80 ; $FF80->$FFFE
|
||||
|
||||
; *** MBC5 Equates ***
|
||||
|
||||
rRAMG EQU $0000 ; $0000->$1fff
|
||||
rROMB0 EQU $2000 ; $2000->$2fff
|
||||
rROMB1 EQU $3000 ; $3000->$3fff - If more than 256 ROM banks are present.
|
||||
rRAMB EQU $4000 ; $4000->$5fff - Bit 3 enables rumble (if present)
|
||||
|
||||
|
||||
;***************************************************************************
|
||||
;*
|
||||
;* Custom registers
|
||||
;*
|
||||
;***************************************************************************
|
||||
|
||||
; --
|
||||
; -- P1 ($FF00)
|
||||
; -- Register for reading joy pad info. (R/W)
|
||||
; --
|
||||
rP1 EQU $FF00
|
||||
|
||||
P1F_5 EQU %00100000 ; P15 out port, set to 0 to get buttons
|
||||
P1F_4 EQU %00010000 ; P14 out port, set to 0 to get dpad
|
||||
P1F_3 EQU %00001000 ; P13 in port
|
||||
P1F_2 EQU %00000100 ; P12 in port
|
||||
P1F_1 EQU %00000010 ; P11 in port
|
||||
P1F_0 EQU %00000001 ; P10 in port
|
||||
|
||||
P1F_GET_DPAD EQU P1F_5
|
||||
P1F_GET_BTN EQU P1F_4
|
||||
P1F_GET_NONE EQU P1F_4 | P1F_5
|
||||
|
||||
|
||||
; --
|
||||
; -- SB ($FF01)
|
||||
; -- Serial Transfer Data (R/W)
|
||||
; --
|
||||
rSB EQU $FF01
|
||||
|
||||
|
||||
; --
|
||||
; -- SC ($FF02)
|
||||
; -- Serial I/O Control (R/W)
|
||||
; --
|
||||
rSC EQU $FF02
|
||||
|
||||
|
||||
; --
|
||||
; -- DIV ($FF04)
|
||||
; -- Divider register (R/W)
|
||||
; --
|
||||
rDIV EQU $FF04
|
||||
|
||||
|
||||
; --
|
||||
; -- TIMA ($FF05)
|
||||
; -- Timer counter (R/W)
|
||||
; --
|
||||
rTIMA EQU $FF05
|
||||
|
||||
|
||||
; --
|
||||
; -- TMA ($FF06)
|
||||
; -- Timer modulo (R/W)
|
||||
; --
|
||||
rTMA EQU $FF06
|
||||
|
||||
|
||||
; --
|
||||
; -- TAC ($FF07)
|
||||
; -- Timer control (R/W)
|
||||
; --
|
||||
rTAC EQU $FF07
|
||||
|
||||
TACF_START EQU %00000100
|
||||
TACF_STOP EQU %00000000
|
||||
TACF_4KHZ EQU %00000000
|
||||
TACF_16KHZ EQU %00000011
|
||||
TACF_65KHZ EQU %00000010
|
||||
TACF_262KHZ EQU %00000001
|
||||
|
||||
|
||||
; --
|
||||
; -- IF ($FF0F)
|
||||
; -- Interrupt Flag (R/W)
|
||||
; --
|
||||
rIF EQU $FF0F
|
||||
|
||||
|
||||
; --
|
||||
; -- AUD1SWEEP/NR10 ($FF10)
|
||||
; -- Sweep register (R/W)
|
||||
; --
|
||||
; -- Bit 6-4 - Sweep Time
|
||||
; -- Bit 3 - Sweep Increase/Decrease
|
||||
; -- 0: Addition (frequency increases???)
|
||||
; -- 1: Subtraction (frequency increases???)
|
||||
; -- Bit 2-0 - Number of sweep shift (# 0-7)
|
||||
; -- Sweep Time: (n*7.8ms)
|
||||
; --
|
||||
rNR10 EQU $FF10
|
||||
rAUD1SWEEP EQU rNR10
|
||||
|
||||
AUD1SWEEP_UP EQU %00000000
|
||||
AUD1SWEEP_DOWN EQU %00001000
|
||||
|
||||
|
||||
; --
|
||||
; -- AUD1LEN/NR11 ($FF11)
|
||||
; -- Sound length/Wave pattern duty (R/W)
|
||||
; --
|
||||
; -- Bit 7-6 - Wave Pattern Duty (00:12.5% 01:25% 10:50% 11:75%)
|
||||
; -- Bit 5-0 - Sound length data (# 0-63)
|
||||
; --
|
||||
rNR11 EQU $FF11
|
||||
rAUD1LEN EQU rNR11
|
||||
|
||||
|
||||
; --
|
||||
; -- AUD1ENV/NR12 ($FF12)
|
||||
; -- Envelope (R/W)
|
||||
; --
|
||||
; -- Bit 7-4 - Initial value of envelope
|
||||
; -- Bit 3 - Envelope UP/DOWN
|
||||
; -- 0: Decrease
|
||||
; -- 1: Range of increase
|
||||
; -- Bit 2-0 - Number of envelope sweep (# 0-7)
|
||||
; --
|
||||
rNR12 EQU $FF12
|
||||
rAUD1ENV EQU rNR12
|
||||
|
||||
|
||||
; --
|
||||
; -- AUD1LOW/NR13 ($FF13)
|
||||
; -- Frequency low byte (W)
|
||||
; --
|
||||
rNR13 EQU $FF13
|
||||
rAUD1LOW EQU rNR13
|
||||
|
||||
|
||||
; --
|
||||
; -- AUD1HIGH/NR14 ($FF14)
|
||||
; -- Frequency high byte (W)
|
||||
; --
|
||||
; -- Bit 7 - Initial (when set, sound restarts)
|
||||
; -- Bit 6 - Counter/consecutive selection
|
||||
; -- Bit 2-0 - Frequency's higher 3 bits
|
||||
; --
|
||||
rNR14 EQU $FF14
|
||||
rAUD1HIGH EQU rNR14
|
||||
|
||||
|
||||
; --
|
||||
; -- AUD2LEN/NR21 ($FF16)
|
||||
; -- Sound Length; Wave Pattern Duty (R/W)
|
||||
; --
|
||||
; -- see AUD1LEN for info
|
||||
; --
|
||||
rNR21 EQU $FF16
|
||||
rAUD2LEN EQU rNR21
|
||||
|
||||
|
||||
; --
|
||||
; -- AUD2ENV/NR22 ($FF17)
|
||||
; -- Envelope (R/W)
|
||||
; --
|
||||
; -- see AUD1ENV for info
|
||||
; --
|
||||
rNR22 EQU $FF17
|
||||
rAUD2ENV EQU rNR22
|
||||
|
||||
|
||||
; --
|
||||
; -- AUD2LOW/NR23 ($FF18)
|
||||
; -- Frequency low byte (W)
|
||||
; --
|
||||
rNR23 EQU $FF18
|
||||
rAUD2LOW EQU rNR23
|
||||
|
||||
|
||||
; --
|
||||
; -- AUD2HIGH/NR24 ($FF19)
|
||||
; -- Frequency high byte (W)
|
||||
; --
|
||||
; -- see AUD1HIGH for info
|
||||
; --
|
||||
rNR24 EQU $FF19
|
||||
rAUD2HIGH EQU rNR24
|
||||
|
||||
|
||||
; --
|
||||
; -- AUD3ENA/NR30 ($FF1A)
|
||||
; -- Sound on/off (R/W)
|
||||
; --
|
||||
; -- Bit 7 - Sound ON/OFF (1=ON,0=OFF)
|
||||
; --
|
||||
rNR30 EQU $FF1A
|
||||
rAUD3ENA EQU rNR30
|
||||
|
||||
|
||||
; --
|
||||
; -- AUD3LEN/NR31 ($FF1B)
|
||||
; -- Sound length (R/W)
|
||||
; --
|
||||
; -- Bit 7-0 - Sound length
|
||||
; --
|
||||
rNR31 EQU $FF1B
|
||||
rAUD3LEN EQU rNR31
|
||||
|
||||
|
||||
; --
|
||||
; -- AUD3LEVEL/NR32 ($FF1C)
|
||||
; -- Select output level
|
||||
; --
|
||||
; -- Bit 6-5 - Select output level
|
||||
; -- 00: 0/1 (mute)
|
||||
; -- 01: 1/1
|
||||
; -- 10: 1/2
|
||||
; -- 11: 1/4
|
||||
; --
|
||||
rNR32 EQU $FF1C
|
||||
rAUD3LEVEL EQU rNR32
|
||||
|
||||
|
||||
; --
|
||||
; -- AUD3LOW/NR33 ($FF1D)
|
||||
; -- Frequency low byte (W)
|
||||
; --
|
||||
; -- see AUD1LOW for info
|
||||
; --
|
||||
rNR33 EQU $FF1D
|
||||
rAUD3LOW EQU rNR33
|
||||
|
||||
|
||||
; --
|
||||
; -- AUD3HIGH/NR34 ($FF1E)
|
||||
; -- Frequency high byte (W)
|
||||
; --
|
||||
; -- see AUD1HIGH for info
|
||||
; --
|
||||
rNR34 EQU $FF1E
|
||||
rAUD3HIGH EQU rNR34
|
||||
|
||||
|
||||
; --
|
||||
; -- AUD4LEN/NR41 ($FF20)
|
||||
; -- Sound length (R/W)
|
||||
; --
|
||||
; -- Bit 5-0 - Sound length data (# 0-63)
|
||||
; --
|
||||
rNR41 EQU $FF20
|
||||
rAUD4LEN EQU rNR41
|
||||
|
||||
|
||||
; --
|
||||
; -- AUD4ENV/NR42 ($FF21)
|
||||
; -- Envelope (R/W)
|
||||
; --
|
||||
; -- see AUD1ENV for info
|
||||
; --
|
||||
rNR42 EQU $FF21
|
||||
rAUD4ENV EQU rNR42
|
||||
|
||||
|
||||
; --
|
||||
; -- AUD4POLY/NR43 ($FF22)
|
||||
; -- Polynomial counter (R/W)
|
||||
; --
|
||||
; -- Bit 7-4 - Selection of the shift clock frequency of the (scf)
|
||||
; -- polynomial counter (0000-1101)
|
||||
; -- freq=drf*1/2^scf (not sure)
|
||||
; -- Bit 3 - Selection of the polynomial counter's step
|
||||
; -- 0: 15 steps
|
||||
; -- 1: 7 steps
|
||||
; -- Bit 2-0 - Selection of the dividing ratio of frequencies (drf)
|
||||
; -- 000: f/4 001: f/8 010: f/16 011: f/24
|
||||
; -- 100: f/32 101: f/40 110: f/48 111: f/56 (f=4.194304 Mhz)
|
||||
; --
|
||||
rNR43 EQU $FF22
|
||||
rAUD4POLY EQU rNR43
|
||||
|
||||
|
||||
; --
|
||||
; -- AUD4GO/NR44 ($FF23)
|
||||
; --
|
||||
; -- Bit 7 - Inital
|
||||
; -- Bit 6 - Counter/consecutive selection
|
||||
; --
|
||||
rNR44 EQU $FF23
|
||||
rAUD4GO EQU rNR44
|
||||
|
||||
|
||||
; --
|
||||
; -- AUDVOL/NR50 ($FF24)
|
||||
; -- Channel control / ON-OFF / Volume (R/W)
|
||||
; --
|
||||
; -- Bit 7 - Vin->SO2 ON/OFF (Vin??)
|
||||
; -- Bit 6-4 - SO2 output level (volume) (# 0-7)
|
||||
; -- Bit 3 - Vin->SO1 ON/OFF (Vin??)
|
||||
; -- Bit 2-0 - SO1 output level (volume) (# 0-7)
|
||||
; --
|
||||
rNR50 EQU $FF24
|
||||
rAUDVOL EQU rNR50
|
||||
|
||||
AUDVOL_VIN_LEFT EQU %10000000 ; SO2
|
||||
AUDVOL_VIN_RIGHT EQU %00001000 ; SO1
|
||||
|
||||
|
||||
; --
|
||||
; -- AUDTERM/NR51 ($FF25)
|
||||
; -- Selection of Sound output terminal (R/W)
|
||||
; --
|
||||
; -- Bit 7 - Output sound 4 to SO2 terminal
|
||||
; -- Bit 6 - Output sound 3 to SO2 terminal
|
||||
; -- Bit 5 - Output sound 2 to SO2 terminal
|
||||
; -- Bit 4 - Output sound 1 to SO2 terminal
|
||||
; -- Bit 3 - Output sound 4 to SO1 terminal
|
||||
; -- Bit 2 - Output sound 3 to SO1 terminal
|
||||
; -- Bit 1 - Output sound 2 to SO1 terminal
|
||||
; -- Bit 0 - Output sound 0 to SO1 terminal
|
||||
; --
|
||||
rNR51 EQU $FF25
|
||||
rAUDTERM EQU rNR51
|
||||
|
||||
; SO2
|
||||
AUDTERM_4_LEFT EQU %10000000
|
||||
AUDTERM_3_LEFT EQU %01000000
|
||||
AUDTERM_2_LEFT EQU %00100000
|
||||
AUDTERM_1_LEFT EQU %00010000
|
||||
; SO1
|
||||
AUDTERM_4_RIGHT EQU %00001000
|
||||
AUDTERM_3_RIGHT EQU %00000100
|
||||
AUDTERM_2_RIGHT EQU %00000010
|
||||
AUDTERM_1_RIGHT EQU %00000001
|
||||
|
||||
|
||||
; --
|
||||
; -- AUDENA/NR52 ($FF26)
|
||||
; -- Sound on/off (R/W)
|
||||
; --
|
||||
; -- Bit 7 - All sound on/off (sets all audio regs to 0!)
|
||||
; -- Bit 3 - Sound 4 ON flag (read only)
|
||||
; -- Bit 2 - Sound 3 ON flag (read only)
|
||||
; -- Bit 1 - Sound 2 ON flag (read only)
|
||||
; -- Bit 0 - Sound 1 ON flag (read only)
|
||||
; --
|
||||
rNR52 EQU $FF26
|
||||
rAUDENA EQU rNR52
|
||||
|
||||
AUDENA_ON EQU %10000000
|
||||
AUDENA_OFF EQU %00000000 ; sets all audio regs to 0!
|
||||
|
||||
|
||||
; --
|
||||
; -- LCDC ($FF40)
|
||||
; -- LCD Control (R/W)
|
||||
; --
|
||||
rLCDC EQU $FF40
|
||||
|
||||
LCDCF_OFF EQU %00000000 ; LCD Control Operation
|
||||
LCDCF_ON EQU %10000000 ; LCD Control Operation
|
||||
LCDCF_WIN9800 EQU %00000000 ; Window Tile Map Display Select
|
||||
LCDCF_WIN9C00 EQU %01000000 ; Window Tile Map Display Select
|
||||
LCDCF_WINOFF EQU %00000000 ; Window Display
|
||||
LCDCF_WINON EQU %00100000 ; Window Display
|
||||
LCDCF_BG8800 EQU %00000000 ; BG & Window Tile Data Select
|
||||
LCDCF_BG8000 EQU %00010000 ; BG & Window Tile Data Select
|
||||
LCDCF_BG9800 EQU %00000000 ; BG Tile Map Display Select
|
||||
LCDCF_BG9C00 EQU %00001000 ; BG Tile Map Display Select
|
||||
LCDCF_OBJ8 EQU %00000000 ; OBJ Construction
|
||||
LCDCF_OBJ16 EQU %00000100 ; OBJ Construction
|
||||
LCDCF_OBJOFF EQU %00000000 ; OBJ Display
|
||||
LCDCF_OBJON EQU %00000010 ; OBJ Display
|
||||
LCDCF_BGOFF EQU %00000000 ; BG Display
|
||||
LCDCF_BGON EQU %00000001 ; BG Display
|
||||
; "Window Character Data Select" follows BG
|
||||
|
||||
|
||||
; --
|
||||
; -- STAT ($FF41)
|
||||
; -- LCDC Status (R/W)
|
||||
; --
|
||||
rSTAT EQU $FF41
|
||||
|
||||
STATF_LYC EQU %01000000 ; LYC=LY Coincidence (Selectable)
|
||||
STATF_MODE10 EQU %00100000 ; Mode 10
|
||||
STATF_MODE01 EQU %00010000 ; Mode 01 (V-Blank)
|
||||
STATF_MODE00 EQU %00001000 ; Mode 00 (H-Blank)
|
||||
STATF_LYCF EQU %00000100 ; Coincidence Flag
|
||||
STATF_HBL EQU %00000000 ; H-Blank
|
||||
STATF_VBL EQU %00000001 ; V-Blank
|
||||
STATF_OAM EQU %00000010 ; OAM-RAM is used by system
|
||||
STATF_LCD EQU %00000011 ; Both OAM and VRAM used by system
|
||||
STATF_BUSY EQU %00000010 ; When set, VRAM access is unsafe
|
||||
|
||||
|
||||
; --
|
||||
; -- SCY ($FF42)
|
||||
; -- Scroll Y (R/W)
|
||||
; --
|
||||
rSCY EQU $FF42
|
||||
|
||||
|
||||
; --
|
||||
; -- SCY ($FF43)
|
||||
; -- Scroll X (R/W)
|
||||
; --
|
||||
rSCX EQU $FF43
|
||||
|
||||
|
||||
; --
|
||||
; -- LY ($FF44)
|
||||
; -- LCDC Y-Coordinate (R)
|
||||
; --
|
||||
; -- Values range from 0->153. 144->153 is the VBlank period.
|
||||
; --
|
||||
rLY EQU $FF44
|
||||
|
||||
|
||||
; --
|
||||
; -- LYC ($FF45)
|
||||
; -- LY Compare (R/W)
|
||||
; --
|
||||
; -- When LY==LYC, STATF_LYCF will be set in STAT
|
||||
; --
|
||||
rLYC EQU $FF45
|
||||
|
||||
|
||||
; --
|
||||
; -- DMA ($FF46)
|
||||
; -- DMA Transfer and Start Address (W)
|
||||
; --
|
||||
rDMA EQU $FF46
|
||||
|
||||
|
||||
; --
|
||||
; -- BGP ($FF47)
|
||||
; -- BG Palette Data (W)
|
||||
; --
|
||||
; -- Bit 7-6 - Intensity for %11
|
||||
; -- Bit 5-4 - Intensity for %10
|
||||
; -- Bit 3-2 - Intensity for %01
|
||||
; -- Bit 1-0 - Intensity for %00
|
||||
; --
|
||||
rBGP EQU $FF47
|
||||
|
||||
|
||||
; --
|
||||
; -- OBP0 ($FF48)
|
||||
; -- Object Palette 0 Data (W)
|
||||
; --
|
||||
; -- See BGP for info
|
||||
; --
|
||||
rOBP0 EQU $FF48
|
||||
|
||||
|
||||
; --
|
||||
; -- OBP1 ($FF49)
|
||||
; -- Object Palette 1 Data (W)
|
||||
; --
|
||||
; -- See BGP for info
|
||||
; --
|
||||
rOBP1 EQU $FF49
|
||||
|
||||
|
||||
; --
|
||||
; -- WY ($FF4A)
|
||||
; -- Window Y Position (R/W)
|
||||
; --
|
||||
; -- 0 <= WY <= 143
|
||||
; -- When WY = 0, the window is displayed from the top edge of the LCD screen.
|
||||
; --
|
||||
rWY EQU $FF4A
|
||||
|
||||
|
||||
; --
|
||||
; -- WX ($FF4B)
|
||||
; -- Window X Position (R/W)
|
||||
; --
|
||||
; -- 7 <= WX <= 166
|
||||
; -- When WX = 7, the window is displayed from the left edge of the LCD screen.
|
||||
; -- Values of 0-6 and 166 are unreliable due to hardware bugs.
|
||||
; --
|
||||
rWX EQU $FF4B
|
||||
|
||||
|
||||
; --
|
||||
; -- SPEED ($FF4D)
|
||||
; -- Select CPU Speed (R/W)
|
||||
; --
|
||||
rKEY1 EQU $FF4D
|
||||
rSPD EQU rKEY1
|
||||
|
||||
KEY1F_DBLSPEED EQU %10000000 ; 0=Normal Speed, 1=Double Speed (R)
|
||||
KEY1F_PREPARE EQU %00000001 ; 0=No, 1=Prepare (R/W)
|
||||
|
||||
|
||||
; --
|
||||
; -- VBK ($FF4F)
|
||||
; -- Select Video RAM Bank (R/W)
|
||||
; --
|
||||
; -- Bit 0 - Bank Specification (0: Specify Bank 0; 1: Specify Bank 1)
|
||||
; --
|
||||
rVBK EQU $FF4F
|
||||
|
||||
|
||||
; --
|
||||
; -- HDMA1 ($FF51)
|
||||
; -- High byte for Horizontal Blanking/General Purpose DMA source address (W)
|
||||
; -- CGB Mode Only
|
||||
; --
|
||||
rHDMA1 EQU $FF51
|
||||
|
||||
|
||||
; --
|
||||
; -- HDMA2 ($FF52)
|
||||
; -- Low byte for Horizontal Blanking/General Purpose DMA source address (W)
|
||||
; -- CGB Mode Only
|
||||
; --
|
||||
rHDMA2 EQU $FF52
|
||||
|
||||
|
||||
; --
|
||||
; -- HDMA3 ($FF53)
|
||||
; -- High byte for Horizontal Blanking/General Purpose DMA destination address (W)
|
||||
; -- CGB Mode Only
|
||||
; --
|
||||
rHDMA3 EQU $FF53
|
||||
|
||||
|
||||
; --
|
||||
; -- HDMA4 ($FF54)
|
||||
; -- Low byte for Horizontal Blanking/General Purpose DMA destination address (W)
|
||||
; -- CGB Mode Only
|
||||
; --
|
||||
rHDMA4 EQU $FF54
|
||||
|
||||
|
||||
; --
|
||||
; -- HDMA5 ($FF55)
|
||||
; -- Transfer length (in tiles minus 1)/mode/start for Horizontal Blanking, General Purpose DMA (R/W)
|
||||
; -- CGB Mode Only
|
||||
; --
|
||||
rHDMA5 EQU $FF55
|
||||
|
||||
HDMA5F_MODE_GP EQU %00000000 ; General Purpose DMA (W)
|
||||
HDMA5F_MODE_HBL EQU %10000000 ; HBlank DMA (W)
|
||||
|
||||
; -- Once DMA has started, use HDMA5F_BUSY to check when the transfer is complete
|
||||
HDMA5F_BUSY EQU %10000000 ; 0=Busy (DMA still in progress), 1=Transfer complete (R)
|
||||
|
||||
|
||||
; --
|
||||
; -- RP ($FF56)
|
||||
; -- Infrared Communications Port (R/W)
|
||||
; --
|
||||
rRP EQU $FF56
|
||||
|
||||
|
||||
; --
|
||||
; -- BCPS ($FF68)
|
||||
; -- Background Color Palette Specification (R/W)
|
||||
; --
|
||||
rBCPS EQU $FF68
|
||||
|
||||
BCPSF_AUTOINC EQU %10000000 ; Auto Increment (0=Disabled, 1=Increment after Writing)
|
||||
|
||||
|
||||
; --
|
||||
; -- BCPD ($FF69)
|
||||
; -- Background Color Palette Data (R/W)
|
||||
; --
|
||||
rBCPD EQU $FF69
|
||||
|
||||
|
||||
; --
|
||||
; -- OCPS ($FF6A)
|
||||
; -- Object Color Palette Specification (R/W)
|
||||
; --
|
||||
rOCPS EQU $FF6A
|
||||
|
||||
OCPSF_AUTOINC EQU %10000000 ; Auto Increment (0=Disabled, 1=Increment after Writing)
|
||||
|
||||
|
||||
; --
|
||||
; -- OCPD ($FF6B)
|
||||
; -- Object Color Palette Data (R/W)
|
||||
; --
|
||||
rOCPD EQU $FF6B
|
||||
|
||||
|
||||
; --
|
||||
; -- SMBK/SVBK ($FF70)
|
||||
; -- Select Main RAM Bank (R/W)
|
||||
; --
|
||||
; -- Bit 2-0 - Bank Specification (0,1: Specify Bank 1; 2-7: Specify Banks 2-7)
|
||||
; --
|
||||
rSVBK EQU $FF70
|
||||
rSMBK EQU rSVBK
|
||||
|
||||
|
||||
; --
|
||||
; -- PCM12 ($FF76)
|
||||
; -- Sound channel 1&2 PCM amplitude (R)
|
||||
; --
|
||||
; -- Bit 7-4 - Copy of sound channel 2's PCM amplitude
|
||||
; -- Bit 3-0 - Copy of sound channel 1's PCM amplitude
|
||||
; --
|
||||
rPCM12 EQU $FF76
|
||||
|
||||
|
||||
; --
|
||||
; -- PCM34 ($FF77)
|
||||
; -- Sound channel 3&4 PCM amplitude (R)
|
||||
; --
|
||||
; -- Bit 7-4 - Copy of sound channel 4's PCM amplitude
|
||||
; -- Bit 3-0 - Copy of sound channel 3's PCM amplitude
|
||||
; --
|
||||
rPCM34 EQU $FF77
|
||||
|
||||
|
||||
; --
|
||||
; -- IE ($FFFF)
|
||||
; -- Interrupt Enable (R/W)
|
||||
; --
|
||||
rIE EQU $FFFF
|
||||
|
||||
IEF_HILO EQU %00010000 ; Transition from High to Low of Pin number P10-P13
|
||||
IEF_SERIAL EQU %00001000 ; Serial I/O transfer end
|
||||
IEF_TIMER EQU %00000100 ; Timer Overflow
|
||||
IEF_LCDC EQU %00000010 ; LCDC (see STAT)
|
||||
IEF_VBLANK EQU %00000001 ; V-Blank
|
||||
|
||||
|
||||
;***************************************************************************
|
||||
;*
|
||||
;* Flags common to multiple sound channels
|
||||
;*
|
||||
;***************************************************************************
|
||||
|
||||
; --
|
||||
; -- Square wave duty cycle
|
||||
; --
|
||||
; -- Can be used with AUD1LEN and AUD2LEN
|
||||
; -- See AUD1LEN for more info
|
||||
; --
|
||||
AUDLEN_DUTY_12_5 EQU %00000000 ; 12.5%
|
||||
AUDLEN_DUTY_25 EQU %01000000 ; 25%
|
||||
AUDLEN_DUTY_50 EQU %10000000 ; 50%
|
||||
AUDLEN_DUTY_75 EQU %11000000 ; 75%
|
||||
|
||||
|
||||
; --
|
||||
; -- Audio envelope flags
|
||||
; --
|
||||
; -- Can be used with AUD1ENV, AUD2ENV, AUD4ENV
|
||||
; -- See AUD1ENV for more info
|
||||
; --
|
||||
AUDENV_UP EQU %00001000
|
||||
AUDENV_DOWN EQU %00000000
|
||||
|
||||
|
||||
; --
|
||||
; -- Audio trigger flags
|
||||
; --
|
||||
; -- Can be used with AUD1HIGH, AUD2HIGH, AUD3HIGH
|
||||
; -- See AUD1HIGH for more info
|
||||
; --
|
||||
|
||||
AUDHIGH_RESTART EQU %10000000
|
||||
AUDHIGH_LENGTH_ON EQU %01000000
|
||||
AUDHIGH_LENGTH_OFF EQU %00000000
|
||||
|
||||
|
||||
;***************************************************************************
|
||||
;*
|
||||
;* CPU values on bootup (a=type, b=qualifier)
|
||||
;*
|
||||
;***************************************************************************
|
||||
|
||||
BOOTUP_A_DMG EQU $01 ; Dot Matrix Game
|
||||
BOOTUP_A_CGB EQU $11 ; Color GameBoy
|
||||
BOOTUP_A_MGB EQU $FF ; Mini GameBoy (Pocket GameBoy)
|
||||
|
||||
; if a=BOOTUP_A_CGB, bit 0 in b can be checked to determine if real CGB or
|
||||
; other system running in GBC mode
|
||||
BOOTUP_B_CGB EQU %00000000
|
||||
BOOTUP_B_AGB EQU %00000001 ; GBA, GBA SP, Game Boy Player, or New GBA SP
|
||||
|
||||
|
||||
;***************************************************************************
|
||||
;*
|
||||
;* Cart related
|
||||
;*
|
||||
;***************************************************************************
|
||||
|
||||
; $0143 Color GameBoy compatibility code
|
||||
CART_COMPATIBLE_DMG EQU $00
|
||||
CART_COMPATIBLE_DMG_GBC EQU $80
|
||||
CART_COMPATIBLE_GBC EQU $C0
|
||||
|
||||
; $0146 GameBoy/Super GameBoy indicator
|
||||
CART_INDICATOR_GB EQU $00
|
||||
CART_INDICATOR_SGB EQU $03
|
||||
|
||||
; $0147 Cartridge type
|
||||
CART_ROM EQU $00
|
||||
CART_ROM_MBC1 EQU $01
|
||||
CART_ROM_MBC1_RAM EQU $02
|
||||
CART_ROM_MBC1_RAM_BAT EQU $03
|
||||
CART_ROM_MBC2 EQU $05
|
||||
CART_ROM_MBC2_BAT EQU $06
|
||||
CART_ROM_RAM EQU $08
|
||||
CART_ROM_RAM_BAT EQU $09
|
||||
CART_ROM_MMM01 EQU $0B
|
||||
CART_ROM_MMM01_RAM EQU $0C
|
||||
CART_ROM_MMM01_RAM_BAT EQU $0D
|
||||
CART_ROM_MBC3_BAT_RTC EQU $0F
|
||||
CART_ROM_MBC3_RAM_BAT_RTC EQU $10
|
||||
CART_ROM_MBC3 EQU $11
|
||||
CART_ROM_MBC3_RAM EQU $12
|
||||
CART_ROM_MBC3_RAM_BAT EQU $13
|
||||
CART_ROM_MBC5 EQU $19
|
||||
CART_ROM_MBC5_BAT EQU $1A
|
||||
CART_ROM_MBC5_RAM_BAT EQU $1B
|
||||
CART_ROM_MBC5_RUMBLE EQU $1C
|
||||
CART_ROM_MBC5_RAM_RUMBLE EQU $1D
|
||||
CART_ROM_MBC5_RAM_BAT_RUMBLE EQU $1E
|
||||
CART_ROM_MBC7_RAM_BAT_GYRO EQU $22
|
||||
CART_ROM_POCKET_CAMERA EQU $FC
|
||||
CART_ROM_BANDAI_TAMA5 EQU $FD
|
||||
CART_ROM_HUDSON_HUC3 EQU $FE
|
||||
CART_ROM_HUDSON_HUC1 EQU $FF
|
||||
|
||||
; $0148 ROM size
|
||||
; these are kilobytes
|
||||
CART_ROM_32K EQU $00 ; 2 banks
|
||||
CART_ROM_64K EQU $01 ; 4 banks
|
||||
CART_ROM_128K EQU $02 ; 8 banks
|
||||
CART_ROM_256K EQU $03 ; 16 banks
|
||||
CART_ROM_512K EQU $04 ; 32 banks
|
||||
CART_ROM_1024K EQU $05 ; 64 banks
|
||||
CART_ROM_2048K EQU $06 ; 128 banks
|
||||
CART_ROM_4096K EQU $07 ; 256 banks
|
||||
CART_ROM_8192K EQU $08 ; 512 banks
|
||||
CART_ROM_1152K EQU $52 ; 72 banks
|
||||
CART_ROM_1280K EQU $53 ; 80 banks
|
||||
CART_ROM_1536K EQU $54 ; 96 banks
|
||||
|
||||
; $0149 SRAM size
|
||||
; these are kilobytes
|
||||
CART_SRAM_NONE EQU 0
|
||||
CART_SRAM_2K EQU 1 ; 1 incomplete bank
|
||||
CART_SRAM_8K EQU 2 ; 1 bank
|
||||
CART_SRAM_32K EQU 3 ; 4 banks
|
||||
CART_SRAM_128K EQU 4 ; 16 banks
|
||||
|
||||
CART_SRAM_ENABLE EQU $0A
|
||||
CART_SRAM_DISABLE EQU $00
|
||||
|
||||
; $014A Destination code
|
||||
CART_DEST_JAPANESE EQU $00
|
||||
CART_DEST_NON_JAPANESE EQU $01
|
||||
|
||||
|
||||
;***************************************************************************
|
||||
;*
|
||||
;* Keypad related
|
||||
;*
|
||||
;***************************************************************************
|
||||
|
||||
PADF_DOWN EQU $80
|
||||
PADF_UP EQU $40
|
||||
PADF_LEFT EQU $20
|
||||
PADF_RIGHT EQU $10
|
||||
PADF_START EQU $08
|
||||
PADF_SELECT EQU $04
|
||||
PADF_B EQU $02
|
||||
PADF_A EQU $01
|
||||
|
||||
PADB_DOWN EQU $7
|
||||
PADB_UP EQU $6
|
||||
PADB_LEFT EQU $5
|
||||
PADB_RIGHT EQU $4
|
||||
PADB_START EQU $3
|
||||
PADB_SELECT EQU $2
|
||||
PADB_B EQU $1
|
||||
PADB_A EQU $0
|
||||
|
||||
|
||||
;***************************************************************************
|
||||
;*
|
||||
;* Screen related
|
||||
;*
|
||||
;***************************************************************************
|
||||
|
||||
SCRN_X EQU 160 ; Width of screen in pixels
|
||||
SCRN_Y EQU 144 ; Height of screen in pixels
|
||||
SCRN_X_B EQU 20 ; Width of screen in bytes
|
||||
SCRN_Y_B EQU 18 ; Height of screen in bytes
|
||||
|
||||
SCRN_VX EQU 256 ; Virtual width of screen in pixels
|
||||
SCRN_VY EQU 256 ; Virtual height of screen in pixels
|
||||
SCRN_VX_B EQU 32 ; Virtual width of screen in bytes
|
||||
SCRN_VY_B EQU 32 ; Virtual height of screen in bytes
|
||||
|
||||
|
||||
;***************************************************************************
|
||||
;*
|
||||
;* OAM related
|
||||
;*
|
||||
;***************************************************************************
|
||||
|
||||
; OAM attributes
|
||||
; each entry in OAM RAM is 4 bytes (sizeof_OAM_ATTRS)
|
||||
RSRESET
|
||||
OAMA_Y RB 1 ; y pos
|
||||
OAMA_X RB 1 ; x pos
|
||||
OAMA_TILEID RB 1 ; tile id
|
||||
OAMA_FLAGS RB 1 ; flags (see below)
|
||||
sizeof_OAM_ATTRS RB 0
|
||||
|
||||
OAM_COUNT EQU 40 ; number of OAM entries in OAM RAM
|
||||
|
||||
; flags
|
||||
OAMF_PRI EQU %10000000 ; Priority
|
||||
OAMF_YFLIP EQU %01000000 ; Y flip
|
||||
OAMF_XFLIP EQU %00100000 ; X flip
|
||||
OAMF_PAL0 EQU %00000000 ; Palette number; 0,1 (DMG)
|
||||
OAMF_PAL1 EQU %00010000 ; Palette number; 0,1 (DMG)
|
||||
OAMF_BANK0 EQU %00000000 ; Bank number; 0,1 (GBC)
|
||||
OAMF_BANK1 EQU %00001000 ; Bank number; 0,1 (GBC)
|
||||
|
||||
OAMF_PALMASK EQU %00000111 ; Palette (GBC)
|
||||
|
||||
OAMB_PRI EQU 7 ; Priority
|
||||
OAMB_YFLIP EQU 6 ; Y flip
|
||||
OAMB_XFLIP EQU 5 ; X flip
|
||||
OAMB_PAL1 EQU 4 ; Palette number; 0,1 (DMG)
|
||||
OAMB_BANK1 EQU 3 ; Bank number; 0,1 (GBC)
|
||||
|
||||
|
||||
;*
|
||||
;* Nintendo scrolling logo
|
||||
;* (Code won't work on a real GameBoy)
|
||||
;* (if next lines are altered.)
|
||||
NINTENDO_LOGO : MACRO
|
||||
DB $CE,$ED,$66,$66,$CC,$0D,$00,$0B,$03,$73,$00,$83,$00,$0C,$00,$0D
|
||||
DB $00,$08,$11,$1F,$88,$89,$00,$0E,$DC,$CC,$6E,$E6,$DD,$DD,$D9,$99
|
||||
DB $BB,$BB,$67,$63,$6E,$0E,$EC,$CC,$DD,$DC,$99,$9F,$BB,$B9,$33,$3E
|
||||
ENDM
|
||||
|
||||
; SameBoy additions
|
||||
rKEY0 EQU $FF4C
|
||||
rBANK EQU $FF50
|
||||
rOPRI EQU $FF6C
|
||||
|
||||
rJOYP EQU rP1
|
||||
rBGPI EQU rBCPS
|
||||
rBGPD EQU rBCPD
|
||||
rOBPI EQU rOCPS
|
||||
rOBPD EQU rOCPD
|
||||
|
||||
ENDC ;HARDWARE_INC
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
; SameBoy SGB bootstrap ROM
|
||||
; Todo: use friendly names for HW registers instead of magic numbers
|
||||
|
||||
INCLUDE "hardware.inc"
|
||||
|
||||
SECTION "BootCode", ROM0[$0]
|
||||
Start:
|
||||
; Init stack pointer
|
||||
|
@ -15,17 +17,17 @@ Start:
|
|||
|
||||
; Init Audio
|
||||
ld a, $80
|
||||
ldh [$26], a
|
||||
ldh [$11], a
|
||||
ldh [rNR52], a
|
||||
ldh [rNR11], a
|
||||
ld a, $f3
|
||||
ldh [$12], a
|
||||
ldh [$25], a
|
||||
ldh [rNR12], a
|
||||
ldh [rNR51], a
|
||||
ld a, $77
|
||||
ldh [$24], a
|
||||
ldh [rNR50], a
|
||||
|
||||
; Init BG palette to white
|
||||
ld a, $0
|
||||
ldh [$47], a
|
||||
ldh [rBGP], a
|
||||
|
||||
; Load logo from ROM.
|
||||
; A nibble represents a 4-pixels line, 2 bytes represent a 4x4 tile, scaled to 8x8.
|
||||
|
@ -71,10 +73,10 @@ Start:
|
|||
|
||||
; Turn on LCD
|
||||
ld a, $91
|
||||
ldh [$40], a
|
||||
ldh [rLCDC], a
|
||||
|
||||
ld a, $f1 ; Packet magic, increases by 2 for every packet
|
||||
ldh [$80], a
|
||||
ldh [_HRAM], a
|
||||
ld hl, $104 ; Header start
|
||||
|
||||
xor a
|
||||
|
@ -86,7 +88,7 @@ Start:
|
|||
ld a, $30
|
||||
ld [c], a
|
||||
|
||||
ldh a, [$80]
|
||||
ldh a, [_HRAM]
|
||||
call SendByte
|
||||
push hl
|
||||
ld b, $e
|
||||
|
@ -117,9 +119,9 @@ Start:
|
|||
ld [c], a
|
||||
|
||||
; Update command
|
||||
ldh a, [$80]
|
||||
ldh a, [_HRAM]
|
||||
add 2
|
||||
ldh [$80], a
|
||||
ldh [_HRAM], a
|
||||
|
||||
ld a, $58
|
||||
cp l
|
||||
|
@ -135,7 +137,7 @@ Start:
|
|||
|
||||
; Init BG palette
|
||||
ld a, $fc
|
||||
ldh [$47], a
|
||||
ldh [rBGP], a
|
||||
|
||||
; Set registers to match the original SGB boot
|
||||
IF DEF(SGB2)
|
||||
|
@ -210,4 +212,4 @@ db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c
|
|||
|
||||
SECTION "BootGame", ROM0[$fe]
|
||||
BootGame:
|
||||
ldh [$50], a
|
||||
ldh [rBANK], a
|
|
@ -5,6 +5,7 @@
|
|||
#import <Carbon/Carbon.h>
|
||||
#import <JoyKit/JoyKit.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
#import <mach-o/dyld.h>
|
||||
|
||||
#define UPDATE_SERVER "https://sameboy.github.io"
|
||||
|
||||
|
@ -31,10 +32,14 @@ static uint32_t color_to_int(NSColor *color)
|
|||
UPDATE_FAILED,
|
||||
} _updateState;
|
||||
NSString *_downloadDirectory;
|
||||
AuthorizationRef _auth;
|
||||
}
|
||||
|
||||
- (void) applicationDidFinishLaunching:(NSNotification *)notification
|
||||
{
|
||||
// Refresh icon if launched via a software update
|
||||
[NSApplication sharedApplication].applicationIconImage = [NSImage imageNamed:@"AppIcon"];
|
||||
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
for (unsigned i = 0; i < GBButtonCount; i++) {
|
||||
if ([[defaults objectForKey:button_to_preference_name(i, 0)] isKindOfClass:[NSString class]]) {
|
||||
|
@ -71,6 +76,68 @@ static uint32_t color_to_int(NSColor *color)
|
|||
|
||||
@"GBMBC7JoystickOverride": @NO,
|
||||
@"GBMBC7AllowMouse": @YES,
|
||||
|
||||
// Default themes
|
||||
@"GBThemes": @{
|
||||
@"Desert": @{
|
||||
@"BrightnessBias": @0.0,
|
||||
@"Colors": @[@0xff302f3e, @0xff576674, @0xff839ba4, @0xffb1d0d2, @0xffb7d7d8],
|
||||
@"DisabledLCDColor": @YES,
|
||||
@"HueBias": @0.10087773904382469,
|
||||
@"HueBiasStrength": @0.062142056772908363,
|
||||
@"Manual": @NO,
|
||||
},
|
||||
@"Evening": @{
|
||||
@"BrightnessBias": @-0.10168700106441975,
|
||||
@"Colors": @[@0xff362601, @0xff695518, @0xff899853, @0xffa6e4ae, @0xffa9eebb],
|
||||
@"DisabledLCDColor": @YES,
|
||||
@"HueBias": @0.60027079191058874,
|
||||
@"HueBiasStrength": @0.33816297305747867,
|
||||
@"Manual": @NO,
|
||||
},
|
||||
@"Fog": @{
|
||||
@"BrightnessBias": @0.0,
|
||||
@"Colors": @[@0xff373c34, @0xff737256, @0xff9da386, @0xffc3d2bf, @0xffc7d8c6],
|
||||
@"DisabledLCDColor": @YES,
|
||||
@"HueBias": @0.55750435756972117,
|
||||
@"HueBiasStrength": @0.18424738545816732,
|
||||
@"Manual": @NO,
|
||||
},
|
||||
@"Magic Eggplant": @{
|
||||
@"BrightnessBias": @0.0,
|
||||
@"Colors": @[@0xff3c2136, @0xff942e84, @0xffc7699d, @0xfff1e4b0, @0xfff6f9b2],
|
||||
@"DisabledLCDColor": @YES,
|
||||
@"HueBias": @0.87717878486055778,
|
||||
@"HueBiasStrength": @0.65018052788844627,
|
||||
@"Manual": @NO,
|
||||
},
|
||||
@"Radioactive Pea": @{
|
||||
@"BrightnessBias": @-0.48079556772908372,
|
||||
@"Colors": @[@0xff215200, @0xff1f7306, @0xff169e34, @0xff03ceb8, @0xff00d4d1],
|
||||
@"DisabledLCDColor": @YES,
|
||||
@"HueBias": @0.3795131972111554,
|
||||
@"HueBiasStrength": @0.34337649402390436,
|
||||
@"Manual": @NO,
|
||||
},
|
||||
@"Seaweed": @{
|
||||
@"BrightnessBias": @-0.28532744023904377,
|
||||
@"Colors": @[@0xff3f0015, @0xff426532, @0xff58a778, @0xff95e0df, @0xffa0e7ee],
|
||||
@"DisabledLCDColor": @YES,
|
||||
@"HueBias": @0.2694067480079681,
|
||||
@"HueBiasStrength": @0.51565612549800799,
|
||||
@"Manual": @NO,
|
||||
},
|
||||
@"Twilight": @{
|
||||
@"BrightnessBias": @-0.091789093625498031,
|
||||
@"Colors": @[@0xff3f0015, @0xff461286, @0xff6254bd, @0xff97d3e9, @0xffa0e7ee],
|
||||
@"DisabledLCDColor": @YES,
|
||||
@"HueBias": @0.0,
|
||||
@"HueBiasStrength": @0.49710532868525897,
|
||||
@"Manual": @NO,
|
||||
},
|
||||
},
|
||||
|
||||
@"NSToolbarItemForcesStandardSize": @YES, // Forces Monterey to resepect toolbar item sizes
|
||||
}];
|
||||
|
||||
[JOYController startOnRunLoop:[NSRunLoop currentRunLoop] withOptions:@{
|
||||
|
@ -154,6 +221,10 @@ static uint32_t color_to_int(NSColor *color)
|
|||
#ifndef UPDATE_SUPPORT
|
||||
[_preferencesWindow.toolbar removeItemAtIndex:4];
|
||||
#endif
|
||||
if (@available(macOS 11.0, *)) {
|
||||
[_preferencesWindow.toolbar insertItemWithItemIdentifier:NSToolbarFlexibleSpaceItemIdentifier atIndex:0];
|
||||
[_preferencesWindow.toolbar insertItemWithItemIdentifier:NSToolbarFlexibleSpaceItemIdentifier atIndex:_preferencesWindow.toolbar.items.count];
|
||||
}
|
||||
}
|
||||
[_preferencesWindow makeKeyAndOrderFront:self];
|
||||
}
|
||||
|
@ -297,8 +368,55 @@ static uint32_t color_to_int(NSColor *color)
|
|||
[self.updateWindow performClose:sender];
|
||||
}
|
||||
|
||||
- (bool)executePath:(NSString *)path withArguments:(NSArray <NSString *> *)arguments
|
||||
{
|
||||
if (!_auth) {
|
||||
NSTask *task = [[NSTask alloc] init];
|
||||
task.launchPath = path;
|
||||
task.arguments = arguments;
|
||||
[task launch];
|
||||
[task waitUntilExit];
|
||||
return task.terminationStatus == 0 && task.terminationReason == NSTaskTerminationReasonExit;
|
||||
}
|
||||
|
||||
char *argv[arguments.count + 1];
|
||||
argv[arguments.count] = NULL;
|
||||
for (unsigned i = 0; i < arguments.count; i++) {
|
||||
argv[i] = (char *)arguments[i].UTF8String;
|
||||
}
|
||||
|
||||
return AuthorizationExecuteWithPrivileges(_auth, path.UTF8String, kAuthorizationFlagDefaults, argv, NULL) == errAuthorizationSuccess;
|
||||
}
|
||||
|
||||
- (void)deauthorize
|
||||
{
|
||||
if (_auth) {
|
||||
AuthorizationFree(_auth, kAuthorizationFlagDefaults);
|
||||
_auth = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)installUpdate:(id)sender
|
||||
{
|
||||
bool needsAuthorization = false;
|
||||
if ([self executePath:@"/usr/sbin/spctl" withArguments:@[@"--status"]]) { // Succeeds when GateKeeper is on
|
||||
// GateKeeper is on, we need to --add ourselves as root, else we might get a GateKeeper crash
|
||||
needsAuthorization = true;
|
||||
}
|
||||
else if (access(_dyld_get_image_name(0), W_OK)) {
|
||||
// We don't have write access, so we need authorization to update as root
|
||||
needsAuthorization = true;
|
||||
}
|
||||
|
||||
if (needsAuthorization && !_auth) {
|
||||
AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagPreAuthorize | kAuthorizationFlagInteractionAllowed, &_auth);
|
||||
// Make sure we can modify the bundle
|
||||
if (![self executePath:@"/usr/sbin/chown" withArguments:@[@"-R", [NSString stringWithFormat:@"%d:%d", getuid(), getgid()], [NSBundle mainBundle].bundlePath]]) {
|
||||
[self deauthorize];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[self.updateProgressSpinner startAnimation:nil];
|
||||
self.updateProgressButton.title = @"Cancel";
|
||||
self.updateProgressButton.enabled = true;
|
||||
|
@ -317,8 +435,8 @@ static uint32_t color_to_int(NSColor *color)
|
|||
appropriateForURL:[[NSBundle mainBundle] bundleURL]
|
||||
create:true
|
||||
error:nil] path];
|
||||
NSTask *unzipTask;
|
||||
if (!_downloadDirectory) {
|
||||
[self deauthorize];
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
self.updateProgressButton.enabled = false;
|
||||
self.updateProgressLabel.stringValue = @"Failed to extract update.";
|
||||
|
@ -330,12 +448,14 @@ static uint32_t color_to_int(NSColor *color)
|
|||
return;
|
||||
}
|
||||
|
||||
NSTask *unzipTask;
|
||||
unzipTask = [[NSTask alloc] init];
|
||||
unzipTask.launchPath = @"/usr/bin/unzip";
|
||||
unzipTask.arguments = @[location.path, @"-d", _downloadDirectory];
|
||||
[unzipTask launch];
|
||||
[unzipTask waitUntilExit];
|
||||
if (unzipTask.terminationStatus != 0 || unzipTask.terminationReason != NSTaskTerminationReasonExit) {
|
||||
[self deauthorize];
|
||||
[[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil];
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
self.updateProgressButton.enabled = false;
|
||||
|
@ -380,6 +500,7 @@ static uint32_t color_to_int(NSColor *color)
|
|||
NSError *error = nil;
|
||||
[[NSFileManager defaultManager] moveItemAtPath:contentsPath toPath:contentsTempPath error:&error];
|
||||
if (error) {
|
||||
[self deauthorize];
|
||||
[[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil];
|
||||
_downloadDirectory = nil;
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
|
@ -394,6 +515,7 @@ static uint32_t color_to_int(NSColor *color)
|
|||
}
|
||||
[[NSFileManager defaultManager] moveItemAtPath:updateContentsPath toPath:contentsPath error:&error];
|
||||
if (error) {
|
||||
[self deauthorize];
|
||||
[[NSFileManager defaultManager] moveItemAtPath:contentsTempPath toPath:contentsPath error:nil];
|
||||
[[NSFileManager defaultManager] removeItemAtPath:_downloadDirectory error:nil];
|
||||
_downloadDirectory = nil;
|
||||
|
@ -440,6 +562,14 @@ static uint32_t color_to_int(NSColor *color)
|
|||
}
|
||||
}
|
||||
|
||||
- (void)orderFrontAboutPanel:(id)sender
|
||||
{
|
||||
// NSAboutPanelOptionApplicationIcon is not available prior to 10.13, but the key is still there and working.
|
||||
[[NSApplication sharedApplication] orderFrontStandardAboutPanelWithOptions:@{
|
||||
@"ApplicationIcon": [NSImage imageNamed:@"Icon"]
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (_downloadDirectory) {
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14868" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14868"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="Document">
|
||||
<connections>
|
||||
<outlet property="audioFormatButton" destination="knX-AW-zt5" id="fKt-eI-H0y"/>
|
||||
<outlet property="audioRecordingAccessoryView" destination="c22-O7-iKe" id="XD8-Gi-qOC"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customView id="c22-O7-iKe">
|
||||
<rect key="frame" x="0.0" y="0.0" width="354" height="36"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Atq-RE-328">
|
||||
<rect key="frame" x="18" y="10" width="56" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Format:" id="dso-NS-JlD">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="knX-AW-zt5">
|
||||
<rect key="frame" x="81" y="4" width="256" height="25"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Apple AIFF" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="1" imageScaling="proportionallyDown" inset="2" selectedItem="M3Z-UN-VKZ" id="tLM-Di-Dy3">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<menu key="menu" id="gqn-SL-AA5">
|
||||
<items>
|
||||
<menuItem title="Apple AIFF" state="on" tag="1" id="M3Z-UN-VKZ"/>
|
||||
<menuItem title="RIFF WAVE" tag="2" id="zA0-Np-4XD"/>
|
||||
<menuItem title="Raw PCM (Stereo 96KHz, 16-bit LE)" id="r9J-4k-XH5"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="audioFormatChanged:" target="-2" id="I1k-d9-afp"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="75" y="19"/>
|
||||
</customView>
|
||||
</objects>
|
||||
</document>
|
BIN
Cocoa/CPU.png
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
Cocoa/CPU@2x.png
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 304 B |
After Width: | Height: | Size: 586 B |
After Width: | Height: | Size: 311 B |
After Width: | Height: | Size: 605 B |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 483 B |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 870 B |
After Width: | Height: | Size: 252 B |
After Width: | Height: | Size: 456 B |
After Width: | Height: | Size: 255 B |
After Width: | Height: | Size: 454 B |
|
@ -6,8 +6,10 @@
|
|||
#include "GBOSDView.h"
|
||||
|
||||
@class GBCheatWindowController;
|
||||
@class GBPaletteView;
|
||||
@class GBObjectView;
|
||||
|
||||
@interface Document : NSDocument <NSWindowDelegate, GBImageViewDelegate, NSTableViewDataSource, NSTableViewDelegate, NSSplitViewDelegate>
|
||||
@interface Document : NSDocument <NSWindowDelegate, GBImageViewDelegate, NSSplitViewDelegate>
|
||||
@property (nonatomic, readonly) GB_gameboy_t *gb;
|
||||
@property (nonatomic, strong) IBOutlet GBView *view;
|
||||
@property (nonatomic, strong) IBOutlet NSTextView *consoleOutput;
|
||||
|
@ -29,8 +31,8 @@
|
|||
@property (nonatomic, strong) IBOutlet NSTabView *vramTabView;
|
||||
@property (nonatomic, strong) IBOutlet NSPanel *vramWindow;
|
||||
@property (nonatomic, strong) IBOutlet NSTextField *vramStatusLabel;
|
||||
@property (nonatomic, strong) IBOutlet NSTableView *paletteTableView;
|
||||
@property (nonatomic, strong) IBOutlet NSTableView *objectsTableView;
|
||||
@property (nonatomic, strong) IBOutlet GBPaletteView *paletteView;
|
||||
@property (nonatomic, strong) IBOutlet GBObjectView *objectView;
|
||||
@property (nonatomic, strong) IBOutlet NSPanel *printerFeedWindow;
|
||||
@property (nonatomic, strong) IBOutlet NSImageView *feedImageView;
|
||||
@property (nonatomic, strong) IBOutlet NSTextView *debuggerSideViewInput;
|
||||
|
@ -51,7 +53,13 @@
|
|||
@property (strong) IBOutlet NSSegmentedControl *gbsNextPrevButton;
|
||||
@property (strong) IBOutlet GBVisualizerView *gbsVisualizer;
|
||||
@property (strong) IBOutlet GBOSDView *osdView;
|
||||
@property (readonly) GB_oam_info_t *oamInfo;
|
||||
@property uint8_t oamCount;
|
||||
@property uint8_t oamHeight;
|
||||
@property (strong) IBOutlet NSView *audioRecordingAccessoryView;
|
||||
@property (strong) IBOutlet NSPopUpButton *audioFormatButton;
|
||||
|
||||
+ (NSImage *) imageFromData:(NSData *)data width:(NSUInteger) width height:(NSUInteger) height scale:(double) scale;
|
||||
-(uint8_t) readMemory:(uint16_t) addr;
|
||||
-(void) writeMemory:(uint16_t) addr value:(uint8_t)value;
|
||||
-(void) performAtomicBlock: (void (^)())block;
|
||||
|
|
422
Cocoa/Document.m
|
@ -1,16 +1,42 @@
|
|||
#include <AVFoundation/AVFoundation.h>
|
||||
#include <CoreAudio/CoreAudio.h>
|
||||
#include <Core/gb.h>
|
||||
#include "GBAudioClient.h"
|
||||
#include "Document.h"
|
||||
#include "AppDelegate.h"
|
||||
#include "HexFiend/HexFiend.h"
|
||||
#include "GBMemoryByteArray.h"
|
||||
#include "GBWarningPopover.h"
|
||||
#include "GBCheatWindowController.h"
|
||||
#include "GBTerminalTextFieldCell.h"
|
||||
#include "BigSurToolbar.h"
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <CoreAudio/CoreAudio.h>
|
||||
#import <Core/gb.h>
|
||||
#import "GBAudioClient.h"
|
||||
#import "Document.h"
|
||||
#import "AppDelegate.h"
|
||||
#import "HexFiend/HexFiend.h"
|
||||
#import "GBMemoryByteArray.h"
|
||||
#import "GBWarningPopover.h"
|
||||
#import "GBCheatWindowController.h"
|
||||
#import "GBTerminalTextFieldCell.h"
|
||||
#import "BigSurToolbar.h"
|
||||
#import "GBPaletteEditorController.h"
|
||||
#import "GBObjectView.h"
|
||||
#import "GBPaletteView.h"
|
||||
|
||||
@implementation NSString (relativePath)
|
||||
|
||||
- (NSString *)pathRelativeToDirectory:(NSString *)directory
|
||||
{
|
||||
NSMutableArray<NSString *> *baseComponents = [[directory pathComponents] mutableCopy];
|
||||
NSMutableArray<NSString *> *selfComponents = [[self pathComponents] mutableCopy];
|
||||
|
||||
while (baseComponents.count) {
|
||||
if (![baseComponents.firstObject isEqualToString:selfComponents.firstObject]) {
|
||||
break;
|
||||
}
|
||||
|
||||
[baseComponents removeObjectAtIndex:0];
|
||||
[selfComponents removeObjectAtIndex:0];
|
||||
}
|
||||
while (baseComponents.count) {
|
||||
[baseComponents removeObjectAtIndex:0];
|
||||
[selfComponents insertObject:@".." atIndex:0];
|
||||
}
|
||||
return [selfComponents componentsJoinedByString:@"/"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#define GB_MODEL_PAL_BIT_OLD 0x1000
|
||||
|
||||
|
@ -46,10 +72,7 @@ enum model {
|
|||
AVCaptureConnection *cameraConnection;
|
||||
AVCaptureStillImageOutput *cameraOutput;
|
||||
|
||||
GB_oam_info_t oamInfo[40];
|
||||
uint16_t oamCount;
|
||||
uint8_t oamHeight;
|
||||
bool oamUpdating;
|
||||
GB_oam_info_t _oamInfo[40];
|
||||
|
||||
NSMutableData *currentPrinterImageData;
|
||||
enum {GBAccessoryNone, GBAccessoryPrinter, GBAccessoryWorkboy, GBAccessoryLinkCable} accessory;
|
||||
|
@ -78,6 +101,9 @@ enum model {
|
|||
Document *slave;
|
||||
signed linkOffset;
|
||||
bool linkCableBit;
|
||||
|
||||
NSSavePanel *_audioSavePanel;
|
||||
bool _isRecordingAudio;
|
||||
}
|
||||
|
||||
@property GBAudioClient *audioClient;
|
||||
|
@ -105,7 +131,7 @@ static void boot_rom_load(GB_gameboy_t *gb, GB_boot_rom_t type)
|
|||
[self loadBootROM: type];
|
||||
}
|
||||
|
||||
static void vblank(GB_gameboy_t *gb)
|
||||
static void vblank(GB_gameboy_t *gb, GB_vblank_type_t type)
|
||||
{
|
||||
Document *self = (__bridge Document *)GB_get_user_data(gb);
|
||||
[self vblank];
|
||||
|
@ -454,7 +480,6 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
|||
- (void) run
|
||||
{
|
||||
assert(!master);
|
||||
running = true;
|
||||
[self preRun];
|
||||
if (slave) {
|
||||
[slave preRun];
|
||||
|
@ -501,8 +526,8 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
|||
[_audioClient stop];
|
||||
_audioClient = nil;
|
||||
self.view.mouseHidingEnabled = false;
|
||||
GB_save_battery(&gb, [[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"sav"].path UTF8String]);
|
||||
GB_save_cheats(&gb, [[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"cht"].path UTF8String]);
|
||||
GB_save_battery(&gb, self.savPath.UTF8String);
|
||||
GB_save_cheats(&gb, self.chtPath.UTF8String);
|
||||
unsigned time_to_alarm = GB_time_to_alarm(&gb);
|
||||
|
||||
if (time_to_alarm) {
|
||||
|
@ -533,6 +558,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
|||
return;
|
||||
}
|
||||
if (running) return;
|
||||
running = true;
|
||||
[[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil] start];
|
||||
}
|
||||
|
||||
|
@ -907,7 +933,12 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
|||
for (NSView *view in [_mainWindow.contentView.subviews copy]) {
|
||||
[view removeFromSuperview];
|
||||
}
|
||||
[[NSBundle mainBundle] loadNibNamed:@"GBS" owner:self topLevelObjects:nil];
|
||||
if (@available(macOS 11, *)) {
|
||||
[[NSBundle mainBundle] loadNibNamed:@"GBS11" owner:self topLevelObjects:nil];
|
||||
}
|
||||
else {
|
||||
[[NSBundle mainBundle] loadNibNamed:@"GBS" owner:self topLevelObjects:nil];
|
||||
}
|
||||
[_mainWindow setContentSize:self.gbsPlayerView.bounds.size];
|
||||
_mainWindow.styleMask &= ~NSWindowStyleMaskResizable;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{ // Cocoa is weird, no clue why it's needed
|
||||
|
@ -944,28 +975,107 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
|||
}
|
||||
}
|
||||
|
||||
- (bool)isCartContainer
|
||||
{
|
||||
return [self.fileName.pathExtension.lowercaseString isEqualToString:@"gbcart"];
|
||||
}
|
||||
|
||||
- (NSString *)savPath
|
||||
{
|
||||
if (self.isCartContainer) {
|
||||
return [self.fileName stringByAppendingPathComponent:@"battery.sav"];
|
||||
}
|
||||
|
||||
return [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"sav"].path;
|
||||
}
|
||||
|
||||
- (NSString *)chtPath
|
||||
{
|
||||
if (self.isCartContainer) {
|
||||
return [self.fileName stringByAppendingPathComponent:@"cheats.cht"];
|
||||
}
|
||||
|
||||
return [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"cht"].path;
|
||||
}
|
||||
|
||||
- (NSString *)saveStatePath:(unsigned)index
|
||||
{
|
||||
if (self.isCartContainer) {
|
||||
return [self.fileName stringByAppendingPathComponent:[NSString stringWithFormat:@"state.s%u", index]];
|
||||
}
|
||||
return [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%u", index]].path;
|
||||
}
|
||||
|
||||
- (NSString *)romPath
|
||||
{
|
||||
NSString *fileName = self.fileName;
|
||||
if (self.isCartContainer) {
|
||||
NSArray *paths = [[NSString stringWithContentsOfFile:[fileName stringByAppendingPathComponent:@"rom.gbl"]
|
||||
encoding:NSUTF8StringEncoding
|
||||
error:nil] componentsSeparatedByString:@"\n"];
|
||||
fileName = nil;
|
||||
bool needsRebuild = false;
|
||||
for (NSString *path in paths) {
|
||||
NSURL *url = [NSURL URLWithString:path relativeToURL:self.fileURL];
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:url.path]) {
|
||||
if (fileName && ![fileName isEqualToString:url.path]) {
|
||||
needsRebuild = true;
|
||||
break;
|
||||
}
|
||||
fileName = url.path;
|
||||
}
|
||||
else {
|
||||
needsRebuild = true;
|
||||
}
|
||||
}
|
||||
if (fileName && needsRebuild) {
|
||||
[[NSString stringWithFormat:@"%@\n%@\n%@",
|
||||
[fileName pathRelativeToDirectory:self.fileName],
|
||||
fileName,
|
||||
[[NSURL fileURLWithPath:fileName].fileReferenceURL.absoluteString substringFromIndex:strlen("file://")]]
|
||||
writeToFile:[self.fileName stringByAppendingPathComponent:@"rom.gbl"]
|
||||
atomically:false
|
||||
encoding:NSUTF8StringEncoding
|
||||
error:nil];
|
||||
}
|
||||
}
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
- (int) loadROM
|
||||
{
|
||||
__block int ret = 0;
|
||||
NSString *fileName = self.romPath;
|
||||
if (!fileName) {
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
[alert setMessageText:@"Could not locate the ROM referenced by this Game Boy Cartridge"];
|
||||
[alert setAlertStyle:NSAlertStyleCritical];
|
||||
[alert runModal];
|
||||
return 1;
|
||||
}
|
||||
|
||||
NSString *rom_warnings = [self captureOutputForBlock:^{
|
||||
GB_debugger_clear_symbols(&gb);
|
||||
if ([[[self.fileType pathExtension] lowercaseString] isEqualToString:@"isx"]) {
|
||||
ret = GB_load_isx(&gb, self.fileURL.path.UTF8String);
|
||||
GB_load_battery(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"ram"].path.UTF8String);
|
||||
if ([[[fileName pathExtension] lowercaseString] isEqualToString:@"isx"]) {
|
||||
ret = GB_load_isx(&gb, fileName.UTF8String);
|
||||
if (!self.isCartContainer) {
|
||||
GB_load_battery(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"ram"].path.UTF8String);
|
||||
}
|
||||
}
|
||||
else if ([[[self.fileType pathExtension] lowercaseString] isEqualToString:@"gbs"]) {
|
||||
else if ([[[fileName pathExtension] lowercaseString] isEqualToString:@"gbs"]) {
|
||||
__block GB_gbs_info_t info;
|
||||
ret = GB_load_gbs(&gb, self.fileURL.path.UTF8String, &info);
|
||||
ret = GB_load_gbs(&gb, fileName.UTF8String, &info);
|
||||
[self prepareGBSInterface:&info];
|
||||
}
|
||||
else {
|
||||
ret = GB_load_rom(&gb, [self.fileURL.path UTF8String]);
|
||||
ret = GB_load_rom(&gb, [fileName UTF8String]);
|
||||
}
|
||||
GB_load_battery(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"sav"].path.UTF8String);
|
||||
GB_load_cheats(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"cht"].path.UTF8String);
|
||||
GB_load_battery(&gb, self.savPath.UTF8String);
|
||||
GB_load_cheats(&gb, self.chtPath.UTF8String);
|
||||
[self.cheatWindowController cheatsUpdated];
|
||||
GB_debugger_load_symbol_file(&gb, [[[NSBundle mainBundle] pathForResource:@"registers" ofType:@"sym"] UTF8String]);
|
||||
GB_debugger_load_symbol_file(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:@"sym"].path.UTF8String);
|
||||
GB_debugger_load_symbol_file(&gb, [[fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sym"].UTF8String);
|
||||
}];
|
||||
if (ret) {
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
|
@ -1032,7 +1142,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
|||
return !GB_debugger_is_stopped(&gb);
|
||||
}
|
||||
else if ([anItem action] == @selector(reset:) && anItem.tag != MODEL_NONE) {
|
||||
[(NSMenuItem*)anItem setState:anItem.tag == current_model];
|
||||
[(NSMenuItem *)anItem setState:anItem.tag == current_model];
|
||||
}
|
||||
else if ([anItem action] == @selector(interrupt:)) {
|
||||
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]) {
|
||||
|
@ -1040,26 +1150,29 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
|||
}
|
||||
}
|
||||
else if ([anItem action] == @selector(disconnectAllAccessories:)) {
|
||||
[(NSMenuItem*)anItem setState:accessory == GBAccessoryNone];
|
||||
[(NSMenuItem *)anItem setState:accessory == GBAccessoryNone];
|
||||
}
|
||||
else if ([anItem action] == @selector(connectPrinter:)) {
|
||||
[(NSMenuItem*)anItem setState:accessory == GBAccessoryPrinter];
|
||||
[(NSMenuItem *)anItem setState:accessory == GBAccessoryPrinter];
|
||||
}
|
||||
else if ([anItem action] == @selector(connectWorkboy:)) {
|
||||
[(NSMenuItem*)anItem setState:accessory == GBAccessoryWorkboy];
|
||||
[(NSMenuItem *)anItem setState:accessory == GBAccessoryWorkboy];
|
||||
}
|
||||
else if ([anItem action] == @selector(connectLinkCable:)) {
|
||||
[(NSMenuItem*)anItem setState:[(NSMenuItem *)anItem representedObject] == master ||
|
||||
[(NSMenuItem *)anItem setState:[(NSMenuItem *)anItem representedObject] == master ||
|
||||
[(NSMenuItem *)anItem representedObject] == slave];
|
||||
}
|
||||
else if ([anItem action] == @selector(toggleCheats:)) {
|
||||
[(NSMenuItem*)anItem setState:GB_cheats_enabled(&gb)];
|
||||
[(NSMenuItem *)anItem setState:GB_cheats_enabled(&gb)];
|
||||
}
|
||||
else if ([anItem action] == @selector(toggleDisplayBackground:)) {
|
||||
[(NSMenuItem*)anItem setState:!GB_is_background_rendering_disabled(&gb)];
|
||||
[(NSMenuItem *)anItem setState:!GB_is_background_rendering_disabled(&gb)];
|
||||
}
|
||||
else if ([anItem action] == @selector(toggleDisplayObjects:)) {
|
||||
[(NSMenuItem*)anItem setState:!GB_is_object_rendering_disabled(&gb)];
|
||||
[(NSMenuItem *)anItem setState:!GB_is_object_rendering_disabled(&gb)];
|
||||
}
|
||||
else if ([anItem action] == @selector(toggleAudioRecording:)) {
|
||||
[(NSMenuItem *)anItem setTitle:_isRecordingAudio? @"Stop Audio Recording" : @"Start Audio Recording…"];
|
||||
}
|
||||
|
||||
return [super validateUserInterfaceItem:anItem];
|
||||
|
@ -1128,9 +1241,8 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
|||
[self.consoleWindow orderFront:nil];
|
||||
}
|
||||
pending_console_output = nil;
|
||||
}
|
||||
}
|
||||
[console_output_lock unlock];
|
||||
|
||||
}
|
||||
|
||||
- (void) log: (const char *) string withAttributes: (GB_log_attributes) attributes
|
||||
|
@ -1255,6 +1367,10 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
|||
|
||||
- (char *) getDebuggerInput
|
||||
{
|
||||
bool isPlaying = _audioClient.isPlaying;
|
||||
if (isPlaying) {
|
||||
[_audioClient stop];
|
||||
}
|
||||
[audioLock lock];
|
||||
[audioLock signal];
|
||||
[audioLock unlock];
|
||||
|
@ -1273,6 +1389,9 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
|||
[self.debuggerSideView setString:@""];
|
||||
}
|
||||
});
|
||||
if (isPlaying) {
|
||||
[_audioClient start];
|
||||
}
|
||||
if ((id) input == [NSNull null]) {
|
||||
return NULL;
|
||||
}
|
||||
|
@ -1297,7 +1416,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
|||
{
|
||||
bool __block success = false;
|
||||
[self performAtomicBlock:^{
|
||||
success = GB_save_state(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]].path.UTF8String) == 0;
|
||||
success = GB_save_state(&gb, [self saveStatePath:[sender tag]].UTF8String) == 0;
|
||||
}];
|
||||
|
||||
if (!success) {
|
||||
|
@ -1335,8 +1454,8 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
|||
|
||||
- (IBAction)loadState:(id)sender
|
||||
{
|
||||
int ret = [self loadStateFile:[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag]]].path.UTF8String noErrorOnNotFound:true];
|
||||
if (ret == ENOENT) {
|
||||
int ret = [self loadStateFile:[self saveStatePath:[sender tag]].UTF8String noErrorOnNotFound:true];
|
||||
if (ret == ENOENT && !self.isCartContainer) {
|
||||
[self loadStateFile:[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"sn%ld", (long)[sender tag]]].path.UTF8String noErrorOnNotFound:false];
|
||||
}
|
||||
}
|
||||
|
@ -1390,28 +1509,21 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
|||
|
||||
+ (NSImage *) imageFromData:(NSData *)data width:(NSUInteger) width height:(NSUInteger) height scale:(double) scale
|
||||
{
|
||||
CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef) data);
|
||||
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
|
||||
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast;
|
||||
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
|
||||
|
||||
CGImageRef iref = CGImageCreate(width,
|
||||
height,
|
||||
8,
|
||||
32,
|
||||
4 * width,
|
||||
colorSpaceRef,
|
||||
bitmapInfo,
|
||||
provider,
|
||||
NULL,
|
||||
true,
|
||||
renderingIntent);
|
||||
CGDataProviderRelease(provider);
|
||||
CGColorSpaceRelease(colorSpaceRef);
|
||||
|
||||
NSImage *ret = [[NSImage alloc] initWithCGImage:iref size:NSMakeSize(width * scale, height * scale)];
|
||||
CGImageRelease(iref);
|
||||
|
||||
NSImage *ret = [[NSImage alloc] initWithSize:NSMakeSize(width * scale, height * scale)];
|
||||
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
|
||||
pixelsWide:width
|
||||
pixelsHigh:height
|
||||
bitsPerSample:8
|
||||
samplesPerPixel:3
|
||||
hasAlpha:false
|
||||
isPlanar:false
|
||||
colorSpaceName:NSDeviceRGBColorSpace
|
||||
bitmapFormat:0
|
||||
bytesPerRow:4 * width
|
||||
bitsPerPixel:32];
|
||||
memcpy(rep.bitmapData, data.bytes, data.length);
|
||||
[ret addRepresentation:rep];
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -1475,13 +1587,9 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
|||
case 2:
|
||||
/* OAM */
|
||||
{
|
||||
oamCount = GB_get_oam_info(&gb, oamInfo, &oamHeight);
|
||||
_oamCount = GB_get_oam_info(&gb, _oamInfo, &_oamHeight);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (!oamUpdating) {
|
||||
oamUpdating = true;
|
||||
[self.objectsTableView reloadData];
|
||||
oamUpdating = false;
|
||||
}
|
||||
[self.objectView reloadData:self];
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
@ -1490,7 +1598,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
|||
/* Palettes */
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.paletteTableView reloadData];
|
||||
[self.paletteView reloadData:self];
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
@ -1757,14 +1865,14 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
|||
window_rect.origin.y += window_rect.size.height;
|
||||
switch ([sender selectedSegment]) {
|
||||
case 0:
|
||||
case 2:
|
||||
window_rect.size.height = 384 + height_diff + 48;
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
window_rect.size.height = 512 + height_diff + 48;
|
||||
break;
|
||||
case 3:
|
||||
window_rect.size.height = 20 * 16 + height_diff + 34;
|
||||
window_rect.size.height = 24 * 16 + height_diff;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -1796,7 +1904,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
|||
uint8_t *vram = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_VRAM, NULL, NULL);
|
||||
|
||||
if (map_type == GB_MAP_9C00 || (map_type == GB_MAP_AUTO && lcdc & 0x08)) {
|
||||
map_base = 0x1c00;
|
||||
map_base = 0x1C00;
|
||||
}
|
||||
|
||||
if (tileset_type == GB_TILESET_AUTO) {
|
||||
|
@ -1837,79 +1945,9 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
|||
}
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
|
||||
- (GB_oam_info_t *)oamInfo
|
||||
{
|
||||
if (tableView == self.paletteTableView) {
|
||||
return 16; /* 8 BG palettes, 8 OBJ palettes*/
|
||||
}
|
||||
else if (tableView == self.objectsTableView) {
|
||||
return oamCount;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
|
||||
{
|
||||
NSUInteger columnIndex = [[tableView tableColumns] indexOfObject:tableColumn];
|
||||
if (tableView == self.paletteTableView) {
|
||||
if (columnIndex == 0) {
|
||||
return [NSString stringWithFormat:@"%s %u", row >= 8 ? "Object" : "Background", (unsigned)(row & 7)];
|
||||
}
|
||||
|
||||
uint8_t *palette_data = GB_get_direct_access(&gb, row >= 8? GB_DIRECT_ACCESS_OBP : GB_DIRECT_ACCESS_BGP, NULL, NULL);
|
||||
|
||||
uint16_t index = columnIndex - 1 + (row & 7) * 4;
|
||||
return @((palette_data[(index << 1) + 1] << 8) | palette_data[(index << 1)]);
|
||||
}
|
||||
else if (tableView == self.objectsTableView) {
|
||||
switch (columnIndex) {
|
||||
case 0:
|
||||
return [Document imageFromData:[NSData dataWithBytesNoCopy:oamInfo[row].image
|
||||
length:64 * 4 * 2
|
||||
freeWhenDone:false]
|
||||
width:8
|
||||
height:oamHeight
|
||||
scale:16.0/oamHeight];
|
||||
case 1:
|
||||
return @((unsigned)oamInfo[row].x - 8);
|
||||
case 2:
|
||||
return @((unsigned)oamInfo[row].y - 16);
|
||||
case 3:
|
||||
return [NSString stringWithFormat:@"$%02x", oamInfo[row].tile];
|
||||
case 4:
|
||||
return [NSString stringWithFormat:@"$%04x", 0x8000 + oamInfo[row].tile * 0x10];
|
||||
case 5:
|
||||
return [NSString stringWithFormat:@"$%04x", oamInfo[row].oam_addr];
|
||||
case 6:
|
||||
if (GB_is_cgb(&gb)) {
|
||||
return [NSString stringWithFormat:@"%c%c%c%d%d",
|
||||
oamInfo[row].flags & 0x80? 'P' : '-',
|
||||
oamInfo[row].flags & 0x40? 'Y' : '-',
|
||||
oamInfo[row].flags & 0x20? 'X' : '-',
|
||||
oamInfo[row].flags & 0x08? 1 : 0,
|
||||
oamInfo[row].flags & 0x07];
|
||||
}
|
||||
return [NSString stringWithFormat:@"%c%c%c%d",
|
||||
oamInfo[row].flags & 0x80? 'P' : '-',
|
||||
oamInfo[row].flags & 0x40? 'Y' : '-',
|
||||
oamInfo[row].flags & 0x20? 'X' : '-',
|
||||
oamInfo[row].flags & 0x10? 1 : 0];
|
||||
case 7:
|
||||
return oamInfo[row].obscured_by_line_limit? @"Dropped: Too many objects in line": @"";
|
||||
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
|
||||
{
|
||||
return tableView == self.objectsTableView;
|
||||
}
|
||||
|
||||
- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row
|
||||
{
|
||||
return false;
|
||||
return _oamInfo;
|
||||
}
|
||||
|
||||
- (IBAction)showVRAMViewer:(id)sender
|
||||
|
@ -2148,6 +2186,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
|||
bool wasRunning = self->running;
|
||||
Document *partner = master ?: slave;
|
||||
if (partner) {
|
||||
wasRunning |= partner->running;
|
||||
[self stop];
|
||||
partner->master = nil;
|
||||
partner->slave = nil;
|
||||
|
@ -2342,4 +2381,95 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
|
|||
GB_set_object_rendering_disabled(&gb, !GB_is_object_rendering_disabled(&gb));
|
||||
}
|
||||
|
||||
- (IBAction)newCartridgeInstance:(id)sender
|
||||
{
|
||||
bool shouldResume = running;
|
||||
[self stop];
|
||||
NSSavePanel *savePanel = [NSSavePanel savePanel];
|
||||
[savePanel setAllowedFileTypes:@[@"gbcart"]];
|
||||
[savePanel beginSheetModalForWindow:self.mainWindow completionHandler:^(NSInteger result) {
|
||||
if (result == NSModalResponseOK) {
|
||||
[savePanel orderOut:self];
|
||||
NSString *romPath = self.romPath;
|
||||
[[NSFileManager defaultManager] trashItemAtURL:savePanel.URL resultingItemURL:nil error:nil];
|
||||
[[NSFileManager defaultManager] createDirectoryAtURL:savePanel.URL withIntermediateDirectories:false attributes:nil error:nil];
|
||||
[[NSString stringWithFormat:@"%@\n%@\n%@",
|
||||
[romPath pathRelativeToDirectory:savePanel.URL.path],
|
||||
romPath,
|
||||
[[NSURL fileURLWithPath:romPath].fileReferenceURL.absoluteString substringFromIndex:strlen("file://")]
|
||||
] writeToURL:[savePanel.URL URLByAppendingPathComponent:@"rom.gbl"] atomically:false encoding:NSUTF8StringEncoding error:nil];
|
||||
[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:savePanel.URL display:true completionHandler:nil];
|
||||
}
|
||||
if (shouldResume) {
|
||||
[self start];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (IBAction)toggleAudioRecording:(id)sender
|
||||
{
|
||||
|
||||
bool shouldResume = running;
|
||||
[self stop];
|
||||
if (_isRecordingAudio) {
|
||||
_isRecordingAudio = false;
|
||||
int error = GB_stop_audio_recording(&gb);
|
||||
if (error) {
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
[alert setMessageText:[NSString stringWithFormat:@"Could not finalize recording: %s", strerror(error)]];
|
||||
[alert setAlertStyle:NSAlertStyleCritical];
|
||||
[alert runModal];
|
||||
}
|
||||
else {
|
||||
[self.osdView displayText:@"Audio recording ended"];
|
||||
}
|
||||
if (shouldResume) {
|
||||
[self start];
|
||||
}
|
||||
return;
|
||||
}
|
||||
_audioSavePanel = [NSSavePanel savePanel];
|
||||
if (!self.audioRecordingAccessoryView) {
|
||||
[[NSBundle mainBundle] loadNibNamed:@"AudioRecordingAccessoryView" owner:self topLevelObjects:nil];
|
||||
}
|
||||
_audioSavePanel.accessoryView = self.audioRecordingAccessoryView;
|
||||
[self audioFormatChanged:self.audioFormatButton];
|
||||
|
||||
[_audioSavePanel beginSheetModalForWindow:self.mainWindow completionHandler:^(NSInteger result) {
|
||||
if (result == NSModalResponseOK) {
|
||||
[_audioSavePanel orderOut:self];
|
||||
int error = GB_start_audio_recording(&gb, _audioSavePanel.URL.fileSystemRepresentation, self.audioFormatButton.selectedTag);
|
||||
if (error) {
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
[alert setMessageText:[NSString stringWithFormat:@"Could not start recording: %s", strerror(error)]];
|
||||
[alert setAlertStyle:NSAlertStyleCritical];
|
||||
[alert runModal];
|
||||
}
|
||||
else {
|
||||
[self.osdView displayText:@"Audio recording started"];
|
||||
_isRecordingAudio = true;
|
||||
}
|
||||
}
|
||||
if (shouldResume) {
|
||||
[self start];
|
||||
}
|
||||
_audioSavePanel = nil;
|
||||
}];
|
||||
}
|
||||
|
||||
- (IBAction)audioFormatChanged:(NSPopUpButton *)sender
|
||||
{
|
||||
switch ((GB_audio_format_t)sender.selectedTag) {
|
||||
case GB_AUDIO_FORMAT_RAW:
|
||||
_audioSavePanel.allowedFileTypes = @[@"raw", @"pcm"];
|
||||
break;
|
||||
case GB_AUDIO_FORMAT_AIFF:
|
||||
_audioSavePanel.allowedFileTypes = @[@"aiff", @"aif", @"aifc"];
|
||||
break;
|
||||
case GB_AUDIO_FORMAT_WAV:
|
||||
_audioSavePanel.allowedFileTypes = @[@"wav"];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -25,9 +25,9 @@
|
|||
<outlet property="memoryBankItem" destination="bWC-FW-IYP" id="Lf2-dh-z32"/>
|
||||
<outlet property="memoryView" destination="8hr-8o-3rN" id="fF0-rh-8ND"/>
|
||||
<outlet property="memoryWindow" destination="mRm-dL-mCj" id="VPR-lu-vtI"/>
|
||||
<outlet property="objectsTableView" destination="TOc-XJ-w9w" id="O4R-4Z-9hU"/>
|
||||
<outlet property="objectView" destination="fIM-GT-QXJ" id="jzs-q8-Z2U"/>
|
||||
<outlet property="osdView" destination="MX4-l2-7NE" id="Am7-fq-uvu"/>
|
||||
<outlet property="paletteTableView" destination="gfC-d3-dmq" id="fTC-eL-Qg3"/>
|
||||
<outlet property="paletteView" destination="ZuP-AU-0pA" id="ef6-27-Bci"/>
|
||||
<outlet property="printerFeedWindow" destination="NdE-0B-WCf" id="yVK-cS-NOJ"/>
|
||||
<outlet property="tilemapImageView" destination="LlK-tV-bjv" id="nSY-Xd-BjZ"/>
|
||||
<outlet property="tilemapMapButton" destination="YIJ-Qc-SIZ" id="BB7-Gg-7XP"/>
|
||||
|
@ -341,17 +341,17 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<tabView fixedFrame="YES" drawsBackground="NO" type="noTabsNoBorder" initialItem="pXb-od-Wb1" translatesAutoresizingMaskIntoConstraints="NO" id="AZz-Mh-rPA">
|
||||
<rect key="frame" x="0.0" y="24" width="512" height="408"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="432"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<tabViewItems>
|
||||
<tabViewItem label="Tileset" identifier="1" id="pXb-od-Wb1">
|
||||
<view key="view" ambiguous="YES" id="lCG-Gt-XMF">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="432"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="QcQ-ex-36R" customClass="GBImageView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="384"/>
|
||||
<rect key="frame" x="0.0" y="24" width="512" height="384"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="axesIndependently" id="pXc-O8-jg5"/>
|
||||
<connections>
|
||||
|
@ -360,7 +360,7 @@
|
|||
</connections>
|
||||
</imageView>
|
||||
<popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="TLv-xS-X5K">
|
||||
<rect key="frame" x="4" y="388" width="128" height="17"/>
|
||||
<rect key="frame" x="4" y="412" width="128" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="roundRect" title="None" bezelStyle="roundedRect" alignment="left" controlSize="mini" lineBreakMode="truncatingTail" state="on" borderStyle="border" focusRingType="none" imageScaling="proportionallyDown" inset="2" selectedItem="G8p-CH-PlV" id="1jI-s4-4YY">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
|
@ -392,7 +392,7 @@
|
|||
</connections>
|
||||
</popUpButton>
|
||||
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fL6-2S-Rgd">
|
||||
<rect key="frame" x="452" y="388" width="56" height="17"/>
|
||||
<rect key="frame" x="452" y="412" width="56" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="roundRect" title="Grid" bezelStyle="roundedRect" alignment="center" controlSize="mini" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="pDn-9a-Se6">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES" changeBackground="YES" changeGray="YES"/>
|
||||
|
@ -407,11 +407,11 @@
|
|||
</tabViewItem>
|
||||
<tabViewItem label="Tilemap" identifier="2" id="kaY-Wy-Yt1">
|
||||
<view key="view" id="c6j-cM-dDx">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="432"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DhM-Em-hj7">
|
||||
<rect key="frame" x="385" y="388" width="63" height="17"/>
|
||||
<rect key="frame" x="385" y="412" width="63" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="roundRect" title="Scrolling" bezelStyle="roundedRect" alignment="center" controlSize="mini" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="P2E-5t-BN9">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES" changeBackground="YES" changeGray="YES"/>
|
||||
|
@ -422,7 +422,7 @@
|
|||
</connections>
|
||||
</button>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LlK-tV-bjv" customClass="GBImageView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="384"/>
|
||||
<rect key="frame" x="0.0" y="24" width="512" height="384"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="axesIndependently" id="RvP-El-ILj"/>
|
||||
<connections>
|
||||
|
@ -431,7 +431,7 @@
|
|||
</connections>
|
||||
</imageView>
|
||||
<popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="loB-0k-Qff">
|
||||
<rect key="frame" x="4" y="388" width="128" height="17"/>
|
||||
<rect key="frame" x="4" y="412" width="128" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="roundRect" title="Effective Palettes" bezelStyle="roundedRect" alignment="left" controlSize="mini" lineBreakMode="truncatingTail" state="on" borderStyle="border" focusRingType="none" imageScaling="proportionallyDown" inset="2" selectedItem="oUH-Sa-Ec1" id="Eij-Cp-URa">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
|
@ -464,7 +464,7 @@
|
|||
</connections>
|
||||
</popUpButton>
|
||||
<popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="YIJ-Qc-SIZ">
|
||||
<rect key="frame" x="135" y="388" width="96" height="17"/>
|
||||
<rect key="frame" x="135" y="412" width="96" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="roundRect" title="Effective Tilemap" bezelStyle="roundedRect" alignment="left" controlSize="mini" lineBreakMode="truncatingTail" state="on" borderStyle="border" focusRingType="none" imageScaling="proportionallyDown" inset="2" selectedItem="XRF-Vj-3gs" id="3W1-Db-wDn">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
|
@ -482,7 +482,7 @@
|
|||
</connections>
|
||||
</popUpButton>
|
||||
<popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="k4c-Vg-MBu">
|
||||
<rect key="frame" x="235" y="388" width="96" height="17"/>
|
||||
<rect key="frame" x="235" y="412" width="96" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="roundRect" title="Effective Tileset" bezelStyle="roundedRect" alignment="left" controlSize="mini" lineBreakMode="truncatingTail" state="on" borderStyle="border" focusRingType="none" imageScaling="proportionallyDown" inset="2" selectedItem="CRe-dX-rzY" id="h53-sb-Odg">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
|
@ -504,250 +504,45 @@
|
|||
</tabViewItem>
|
||||
<tabViewItem label="Objects" identifier="" id="a08-eg-Maw">
|
||||
<view key="view" id="EiO-p0-3xn">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="432"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<scrollView fixedFrame="YES" borderType="none" autohidesScrollers="YES" horizontalLineScroll="20" horizontalPageScroll="10" verticalLineScroll="20" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="krD-gH-o5I">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
|
||||
<scrollView fixedFrame="YES" borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Vhq-K4-baH">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="432"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" id="3VT-AA-xVT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
|
||||
<clipView key="contentView" ambiguous="YES" id="JYu-CM-49p">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="432"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="none" alternatingRowBackgroundColors="YES" columnReordering="NO" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="18" headerView="of1-KC-dXC" id="TOc-XJ-w9w">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="391"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<size key="intercellSpacing" width="3" height="2"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
|
||||
<tableColumns>
|
||||
<tableColumn width="32" minWidth="32" maxWidth="1000" id="hRp-Kh-nWC">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<imageCell key="dataCell" scrollable="YES" lineBreakMode="clipping" refusesFirstResponder="YES" alignment="left" id="Jhk-KW-Hoc" customClass="GBImageCell"/>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
<tableColumn width="28" minWidth="28" maxWidth="1000" id="Vrl-in-npm">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center" title="X">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" id="czf-Bn-nZN">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
<tableColumn width="28" minWidth="28" maxWidth="1000" id="636-Td-Zcm">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center" title="Y">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" id="zh6-hI-Ss8">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
<tableColumn width="28" minWidth="28" maxWidth="1000" id="vMj-ya-pGG">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center" title="Tile">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" id="0s8-eF-rgd">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
<tableColumn width="68" minWidth="40" maxWidth="1000" id="U5B-eh-aer">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center" title="Tile Addr.">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" id="PtW-wF-c0o">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
<tableColumn width="68" minWidth="40" maxWidth="1000" id="LSg-Sc-Sdr">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center" title="OAM Addr.">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" id="a7k-83-iPE">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
<tableColumn width="65" minWidth="40" maxWidth="1000" id="S9B-Hc-6Ee">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center" title="Attributes">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" id="sJa-oU-QZp">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
<tableColumn width="168" minWidth="40" maxWidth="1000" id="8fQ-EC-E5K">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" selectable="YES" editable="YES" id="YSL-O0-ZwU">
|
||||
<font key="font" metaFont="miniSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
</tableColumns>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="-2" id="ypl-SU-g5n"/>
|
||||
<outlet property="delegate" destination="-2" id="HzE-pT-cX5"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<view ambiguous="YES" id="fIM-GT-QXJ" customClass="GBObjectView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="706"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</view>
|
||||
</subviews>
|
||||
<nil key="backgroundColor"/>
|
||||
<edgeInsets key="contentInsets" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
|
||||
</clipView>
|
||||
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="n53-qA-NpY">
|
||||
<rect key="frame" x="0.0" y="392" width="512" height="16"/>
|
||||
<edgeInsets key="contentInsets" left="0.0" right="0.0" top="0.0" bottom="0.0"/>
|
||||
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="zUB-xg-cXq">
|
||||
<rect key="frame" x="-100" y="-100" width="512" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="mqp-NY-g8d">
|
||||
<rect key="frame" x="224" y="17" width="15" height="102"/>
|
||||
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="jMe-gO-ERw">
|
||||
<rect key="frame" x="496" y="0.0" width="16" height="432"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<tableHeaderView key="headerView" id="of1-KC-dXC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</tableHeaderView>
|
||||
</scrollView>
|
||||
</subviews>
|
||||
</view>
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Palettes" identifier="" id="vLb-Nh-UYE">
|
||||
<view key="view" id="qIg-ci-WTA">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="432"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<scrollView fixedFrame="YES" borderType="none" horizontalLineScroll="20" horizontalPageScroll="10" verticalLineScroll="20" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" verticalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="iSs-Ow-wwb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
|
||||
<customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ZuP-AU-0pA" customClass="GBPaletteView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="432"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<clipView key="contentView" ambiguous="YES" id="bP9-su-zQw">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnReordering="NO" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="18" id="gfC-d3-dmq">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="408"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<size key="intercellSpacing" width="3" height="2"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<tableViewGridLines key="gridStyleMask" dashed="YES"/>
|
||||
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
|
||||
<tableColumns>
|
||||
<tableColumn width="125" minWidth="40" maxWidth="1000" id="Aje-FQ-fUw">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" refusesFirstResponder="YES" title="Text Cell" id="Ygb-xo-X8v">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
<tableColumn width="93" minWidth="40" maxWidth="1000" id="4EN-0j-lda">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" refusesFirstResponder="YES" title="Text Cell" id="3a2-A4-XQW" customClass="GBColorCell">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
<tableColumn width="93" minWidth="10" maxWidth="3.4028234663852886e+38" id="syl-os-nSf">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" refusesFirstResponder="YES" alignment="left" title="Text Cell" id="3ql-bn-AxP" customClass="GBColorCell">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
<tableColumn width="93" minWidth="10" maxWidth="3.4028234663852886e+38" id="Qw3-u2-c1s">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" refusesFirstResponder="YES" title="Text Cell" id="9at-mH-PWc" customClass="GBColorCell">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
<tableColumn width="93" minWidth="10" maxWidth="3.4028234663852886e+38" id="gTl-gN-qLn">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" refusesFirstResponder="YES" alignment="left" title="Text Cell" id="dY8-Zu-d4t" customClass="GBColorCell">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
</tableColumn>
|
||||
</tableColumns>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="-2" id="F2f-nK-QTN"/>
|
||||
<outlet property="delegate" destination="-2" id="7hn-MM-DDD"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
</subviews>
|
||||
</clipView>
|
||||
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="OS3-sw-bjv">
|
||||
<rect key="frame" x="-100" y="-100" width="510" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="4HA-2m-8TZ">
|
||||
<rect key="frame" x="-100" y="-100" width="15" height="102"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
</customView>
|
||||
</subviews>
|
||||
</view>
|
||||
</tabViewItem>
|
||||
|
@ -840,7 +635,7 @@
|
|||
<rect key="frame" x="16" y="174" width="154" height="19"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="To value:" id="Ycx-oE-aA4">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
|
@ -971,7 +766,7 @@
|
|||
<rect key="frame" x="16" y="204" width="152" height="19"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Change byte at address:" id="xwa-TF-eY1">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
|
@ -986,7 +781,7 @@
|
|||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="none" alternatingRowBackgroundColors="YES" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" headerView="pvX-uJ-qK5" id="tA3-8T-bxb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="398" height="249"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="398" height="257"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<size key="intercellSpacing" width="3" height="2"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -998,7 +793,7 @@
|
|||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<buttonCell key="dataCell" type="bevel" bezelStyle="regularSquare" image="NSStopProgressFreestandingTemplate" imagePosition="only" inset="2" id="5xh-hN-jHH">
|
||||
<buttonCell key="dataCell" type="inline" bezelStyle="inline" image="NSStopProgressFreestandingTemplate" imagePosition="only" alignment="center" imageScaling="proportionallyDown" inset="2" id="5xh-hN-jHH">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
|
@ -1051,7 +846,7 @@
|
|||
</subviews>
|
||||
</clipView>
|
||||
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="3Hg-LL-VqH">
|
||||
<rect key="frame" x="1" y="258" width="398" height="16"/>
|
||||
<rect key="frame" x="1" y="119" width="223" height="15"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="zET-KH-qF4">
|
||||
|
@ -1059,7 +854,7 @@
|
|||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<tableHeaderView key="headerView" id="pvX-uJ-qK5">
|
||||
<rect key="frame" x="0.0" y="0.0" width="398" height="25"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="398" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</tableHeaderView>
|
||||
</scrollView>
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBColorCell : NSTextFieldCell
|
||||
|
||||
@end
|
|
@ -1,49 +0,0 @@
|
|||
#import "GBColorCell.h"
|
||||
|
||||
static inline double scale_channel(uint8_t x)
|
||||
{
|
||||
x &= 0x1f;
|
||||
return x / 31.0;
|
||||
}
|
||||
|
||||
@implementation GBColorCell
|
||||
{
|
||||
NSInteger _integerValue;
|
||||
}
|
||||
|
||||
- (void)setObjectValue:(id)objectValue
|
||||
{
|
||||
_integerValue = [objectValue integerValue];
|
||||
uint8_t r = _integerValue & 0x1F,
|
||||
g = (_integerValue >> 5) & 0x1F,
|
||||
b = (_integerValue >> 10) & 0x1F;
|
||||
super.objectValue = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"$%04x", (uint16_t)(_integerValue & 0x7FFF)] attributes:@{
|
||||
NSForegroundColorAttributeName: r * 3 + g * 4 + b * 2 > 120? [NSColor blackColor] : [NSColor whiteColor],
|
||||
NSFontAttributeName: [NSFont userFixedPitchFontOfSize:12]
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSInteger)integerValue
|
||||
{
|
||||
return _integerValue;
|
||||
}
|
||||
|
||||
- (int)intValue
|
||||
{
|
||||
return (int)_integerValue;
|
||||
}
|
||||
|
||||
|
||||
- (NSColor *) backgroundColor
|
||||
{
|
||||
/* Todo: color correction */
|
||||
uint16_t color = self.integerValue;
|
||||
return [NSColor colorWithRed:scale_channel(color) green:scale_channel(color >> 5) blue:scale_channel(color >> 10) alpha:1.0];
|
||||
}
|
||||
|
||||
- (BOOL)drawsBackground
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@end
|
|
@ -1,5 +0,0 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface GBImageCell : NSImageCell
|
||||
|
||||
@end
|
|
@ -1,10 +0,0 @@
|
|||
#import "GBImageCell.h"
|
||||
|
||||
@implementation GBImageCell
|
||||
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
|
||||
{
|
||||
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
|
||||
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
|
||||
[super drawWithFrame:cellFrame inView:controlView];
|
||||
}
|
||||
@end
|
|
@ -10,18 +10,18 @@
|
|||
}
|
||||
@end
|
||||
|
||||
@implementation GBImageView
|
||||
{
|
||||
NSTrackingArea *trackingArea;
|
||||
}
|
||||
@interface GBGridView : NSView
|
||||
@end
|
||||
|
||||
@implementation GBGridView
|
||||
|
||||
- (void)drawRect:(NSRect)dirtyRect
|
||||
{
|
||||
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
|
||||
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
|
||||
[super drawRect:dirtyRect];
|
||||
CGFloat y_ratio = self.frame.size.height / self.image.size.height;
|
||||
CGFloat x_ratio = self.frame.size.width / self.image.size.width;
|
||||
for (GBImageViewGridConfiguration *conf in self.verticalGrids) {
|
||||
GBImageView *parent = (GBImageView *)self.superview;
|
||||
|
||||
CGFloat y_ratio = parent.frame.size.height / parent.image.size.height;
|
||||
CGFloat x_ratio = parent.frame.size.width / parent.image.size.width;
|
||||
for (GBImageViewGridConfiguration *conf in parent.verticalGrids) {
|
||||
[conf.color set];
|
||||
for (CGFloat y = conf.size * y_ratio; y < self.frame.size.height; y += conf.size * y_ratio) {
|
||||
NSBezierPath *line = [NSBezierPath bezierPath];
|
||||
|
@ -32,7 +32,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
for (GBImageViewGridConfiguration *conf in self.horizontalGrids) {
|
||||
for (GBImageViewGridConfiguration *conf in parent.horizontalGrids) {
|
||||
[conf.color set];
|
||||
for (CGFloat x = conf.size * x_ratio; x < self.frame.size.width; x += conf.size * x_ratio) {
|
||||
NSBezierPath *line = [NSBezierPath bezierPath];
|
||||
|
@ -43,11 +43,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
if (self.displayScrollRect) {
|
||||
if (parent.displayScrollRect) {
|
||||
NSBezierPath *path = [NSBezierPath bezierPathWithRect:CGRectInfinite];
|
||||
for (unsigned x = 0; x < 2; x++) {
|
||||
for (unsigned y = 0; y < 2; y++) {
|
||||
NSRect rect = self.scrollRect;
|
||||
NSRect rect = parent.scrollRect;
|
||||
rect.origin.x *= x_ratio;
|
||||
rect.origin.y *= y_ratio;
|
||||
rect.size.width *= x_ratio;
|
||||
|
@ -56,7 +56,7 @@
|
|||
|
||||
rect.origin.x -= self.frame.size.width * x;
|
||||
rect.origin.y += self.frame.size.height * y;
|
||||
|
||||
|
||||
|
||||
NSBezierPath *subpath = [NSBezierPath bezierPathWithRect:rect];
|
||||
[path appendBezierPath:subpath];
|
||||
|
@ -72,36 +72,62 @@
|
|||
[path stroke];
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation GBImageView
|
||||
{
|
||||
NSTrackingArea *_trackingArea;
|
||||
GBGridView *_gridView;
|
||||
NSRect _scrollRect;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder
|
||||
{
|
||||
self = [super initWithCoder:coder];
|
||||
self.wantsLayer = true;
|
||||
_gridView = [[GBGridView alloc] initWithFrame:self.bounds];
|
||||
_gridView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
||||
[self addSubview:_gridView];
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void)viewWillDraw
|
||||
{
|
||||
[super viewWillDraw];
|
||||
for (CALayer *layer in self.layer.sublayers) {
|
||||
layer.magnificationFilter = kCAFilterNearest;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setHorizontalGrids:(NSArray *)horizontalGrids
|
||||
{
|
||||
self->_horizontalGrids = horizontalGrids;
|
||||
[self setNeedsDisplay];
|
||||
[_gridView setNeedsDisplay:true];
|
||||
}
|
||||
|
||||
- (void)setVerticalGrids:(NSArray *)verticalGrids
|
||||
{
|
||||
self->_verticalGrids = verticalGrids;
|
||||
[self setNeedsDisplay];
|
||||
[_gridView setNeedsDisplay:true];
|
||||
}
|
||||
|
||||
- (void)setDisplayScrollRect:(bool)displayScrollRect
|
||||
{
|
||||
self->_displayScrollRect = displayScrollRect;
|
||||
[self setNeedsDisplay];
|
||||
[_gridView setNeedsDisplay:true];
|
||||
}
|
||||
|
||||
- (void)updateTrackingAreas
|
||||
{
|
||||
if (trackingArea != nil) {
|
||||
[self removeTrackingArea:trackingArea];
|
||||
if (_trackingArea != nil) {
|
||||
[self removeTrackingArea:_trackingArea];
|
||||
}
|
||||
|
||||
trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds]
|
||||
_trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds]
|
||||
options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingMouseMoved
|
||||
owner:self
|
||||
userInfo:nil];
|
||||
[self addTrackingArea:trackingArea];
|
||||
[self addTrackingArea:_trackingArea];
|
||||
}
|
||||
|
||||
- (void)mouseExited:(NSEvent *)theEvent
|
||||
|
@ -124,4 +150,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
- (void)setScrollRect:(NSRect)scrollRect
|
||||
{
|
||||
if (memcmp(&scrollRect, &_scrollRect, sizeof(scrollRect)) != 0) {
|
||||
_scrollRect = scrollRect;
|
||||
[_gridView setNeedsDisplay:true];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSRect)scrollRect
|
||||
{
|
||||
return _scrollRect;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
#import "Document.h"
|
||||
|
||||
@interface GBObjectView : NSView
|
||||
- (void)reloadData:(Document *)document;
|
||||
@end
|
|
@ -0,0 +1,124 @@
|
|||
#import "GBObjectView.h"
|
||||
|
||||
@interface GBObjectViewItem : NSObject
|
||||
@property IBOutlet NSView *view;
|
||||
@property IBOutlet NSImageView *image;
|
||||
@property IBOutlet NSTextField *oamAddress;
|
||||
@property IBOutlet NSTextField *position;
|
||||
@property IBOutlet NSTextField *attributes;
|
||||
@property IBOutlet NSTextField *tile;
|
||||
@property IBOutlet NSTextField *tileAddress;
|
||||
@property IBOutlet NSImageView *warningIcon;
|
||||
@property IBOutlet NSBox *verticalLine;
|
||||
@end
|
||||
|
||||
@implementation GBObjectViewItem
|
||||
{
|
||||
@public
|
||||
uint32_t _lastImageData[128];
|
||||
uint8_t _lastHeight;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation GBObjectView
|
||||
{
|
||||
NSMutableArray<GBObjectViewItem *> *_items;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder
|
||||
{
|
||||
self = [super initWithCoder:coder];
|
||||
_items = [NSMutableArray array];
|
||||
CGFloat height = self.frame.size.height;
|
||||
for (unsigned i = 0; i < 40; i++) {
|
||||
GBObjectViewItem *item = [[GBObjectViewItem alloc] init];
|
||||
[_items addObject:item];
|
||||
[[NSBundle mainBundle] loadNibNamed:@"GBObjectViewItem" owner:item topLevelObjects:nil];
|
||||
item.view.hidden = true;
|
||||
[self addSubview:item.view];
|
||||
[item.view setFrameOrigin:NSMakePoint((i % 4) * 120, height - (i / 4 * 68) - 68)];
|
||||
item.oamAddress.toolTip = @"OAM address";
|
||||
item.position.toolTip = @"Position";
|
||||
item.attributes.toolTip = @"Attributes";
|
||||
item.tile.toolTip = @"Tile index";
|
||||
item.tileAddress.toolTip = @"Tile address";
|
||||
item.warningIcon.toolTip = @"Dropped: too many objects in line";
|
||||
if ((i % 4) == 3) {
|
||||
[item.verticalLine removeFromSuperview];
|
||||
}
|
||||
item.view.autoresizingMask = NSViewMaxXMargin | NSViewMinYMargin;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)reloadData:(Document *)document
|
||||
{
|
||||
GB_oam_info_t *info = document.oamInfo;
|
||||
uint8_t length = document.oamCount;
|
||||
bool cgb = GB_is_cgb(document.gb);
|
||||
uint8_t height = document.oamHeight;
|
||||
for (unsigned i = 0; i < 40; i++) {
|
||||
GBObjectViewItem *item = _items[i];
|
||||
if (i >= length) {
|
||||
item.view.hidden = true;
|
||||
}
|
||||
else {
|
||||
item.view.hidden = false;
|
||||
item.oamAddress.stringValue = [NSString stringWithFormat:@"$%04X", info[i].oam_addr];
|
||||
item.position.stringValue = [NSString stringWithFormat:@"(%d, %d)",
|
||||
((signed)(unsigned)info[i].x) - 8,
|
||||
((signed)(unsigned)info[i].y) - 16];
|
||||
item.tile.stringValue = [NSString stringWithFormat:@"$%02X", info[i].tile];
|
||||
item.tileAddress.stringValue = [NSString stringWithFormat:@"$%04X", 0x8000 + info[i].tile * 0x10];
|
||||
item.warningIcon.hidden = !info[i].obscured_by_line_limit;
|
||||
if (cgb) {
|
||||
item.attributes.stringValue = [NSString stringWithFormat:@"%c%c%c%d%d",
|
||||
info[i].flags & 0x80? 'P' : '-',
|
||||
info[i].flags & 0x40? 'Y' : '-',
|
||||
info[i].flags & 0x20? 'X' : '-',
|
||||
info[i].flags & 0x08? 1 : 0,
|
||||
info[i].flags & 0x07];
|
||||
}
|
||||
else {
|
||||
item.attributes.stringValue = [NSString stringWithFormat:@"%c%c%c%d",
|
||||
info[i].flags & 0x80? 'P' : '-',
|
||||
info[i].flags & 0x40? 'Y' : '-',
|
||||
info[i].flags & 0x20? 'X' : '-',
|
||||
info[i].flags & 0x10? 1 : 0];
|
||||
}
|
||||
size_t imageSize = 8 * 4 * height;
|
||||
if (height == item->_lastHeight && memcmp(item->_lastImageData, info[i].image, imageSize) == 0) {
|
||||
continue;
|
||||
}
|
||||
memcpy(item->_lastImageData, info[i].image, imageSize);
|
||||
item->_lastHeight = height;
|
||||
item.image.image = [Document imageFromData:[NSData dataWithBytesNoCopy:info[i].image
|
||||
length:64 * 4 * 2
|
||||
freeWhenDone:false]
|
||||
width:8
|
||||
height:height
|
||||
scale:32.0 / height];
|
||||
}
|
||||
}
|
||||
|
||||
NSRect frame = self.frame;
|
||||
CGFloat newHeight = MAX(68 * ((length + 3) / 4), self.superview.frame.size.height);
|
||||
frame.origin.y -= newHeight - frame.size.height;
|
||||
frame.size.height = newHeight;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
- (void)drawRect:(NSRect)dirtyRect
|
||||
{
|
||||
if (@available(macOS 10.14, *)) {
|
||||
[[NSColor alternatingContentBackgroundColors].lastObject setFill];
|
||||
}
|
||||
else {
|
||||
[[NSColor colorWithDeviceWhite:0.96 alpha:1] setFill];
|
||||
}
|
||||
NSRect frame = self.frame;
|
||||
for (unsigned i = 1; i <= 5; i++) {
|
||||
NSRectFill(NSMakeRect(0, frame.size.height - i * 68 * 2, frame.size.width, 68));
|
||||
}
|
||||
}
|
||||
@end
|
|
@ -0,0 +1,94 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14868" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14868"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="GBObjectViewItem">
|
||||
<connections>
|
||||
<outlet property="attributes" destination="AVH-lh-aVu" id="F5l-vc-9YB"/>
|
||||
<outlet property="image" destination="GWF-Vk-eLx" id="cQa-lM-SqU"/>
|
||||
<outlet property="oamAddress" destination="HiB-x7-HCb" id="LLX-HU-t00"/>
|
||||
<outlet property="position" destination="bxJ-ig-rox" id="woq-X2-lCK"/>
|
||||
<outlet property="tile" destination="Kco-1J-bKc" id="C8u-jH-bCh"/>
|
||||
<outlet property="tileAddress" destination="ptE-vl-9HD" id="9K3-Oq-1vs"/>
|
||||
<outlet property="verticalLine" destination="Q91-eq-oeo" id="6kc-qh-cFx"/>
|
||||
<outlet property="view" destination="c22-O7-iKe" id="50f-xf-9Gg"/>
|
||||
<outlet property="warningIcon" destination="dQV-cO-218" id="Rxi-Wq-Upl"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customView id="c22-O7-iKe">
|
||||
<rect key="frame" x="0.0" y="0.0" width="120" height="68"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Kco-1J-bKc">
|
||||
<rect key="frame" x="44" y="4" width="58" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" selectable="YES" alignment="left" title="$32" id="Aps-81-wR7">
|
||||
<font key="font" size="11" name="Menlo-Regular"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ptE-vl-9HD">
|
||||
<rect key="frame" x="0.0" y="4" width="44" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" selectable="YES" alignment="center" title="$8320" id="t2G-E2-vt5">
|
||||
<font key="font" size="11" name="Menlo-Regular"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bxJ-ig-rox">
|
||||
<rect key="frame" x="44" y="36" width="76" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" selectable="YES" title="(32,16)" id="oac-yZ-h47">
|
||||
<font key="font" size="11" name="Menlo-Regular"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="AVH-lh-aVu">
|
||||
<rect key="frame" x="44" y="20" width="76" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" selectable="YES" title="---04" id="tJX-6t-5Kx">
|
||||
<font key="font" size="11" name="Menlo-Regular"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="HiB-x7-HCb">
|
||||
<rect key="frame" x="44" y="52" width="76" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" selectable="YES" alignment="left" title="$FE32" id="ysm-jq-PKy">
|
||||
<font key="font" size="11" name="Menlo-Bold"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" id="GWF-Vk-eLx" customClass="GBImageView">
|
||||
<rect key="frame" x="2" y="24" width="40" height="40"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" id="AJz-KH-eeo"/>
|
||||
</imageView>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dQV-cO-218">
|
||||
<rect key="frame" x="100" y="4" width="16" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NSCaution" id="TQB-Vp-9Jm"/>
|
||||
</imageView>
|
||||
<box horizontalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="Q91-eq-oeo">
|
||||
<rect key="frame" x="116" y="4" width="5" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
</box>
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="132" y="149"/>
|
||||
</customView>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="NSCaution" width="32" height="32"/>
|
||||
</resources>
|
||||
</document>
|
|
@ -0,0 +1,6 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
#import "Document.h"
|
||||
|
||||
@interface GBPaletteView : NSView
|
||||
- (void)reloadData:(Document *)document;
|
||||
@end
|
|
@ -0,0 +1,91 @@
|
|||
#import "GBPaletteView.h"
|
||||
|
||||
@interface GBPaletteViewItem : NSObject
|
||||
@property IBOutlet NSView *view;
|
||||
@property (strong) IBOutlet NSTextField *label;
|
||||
@property (strong) IBOutlet NSTextField *color0;
|
||||
@property (strong) IBOutlet NSTextField *color1;
|
||||
@property (strong) IBOutlet NSTextField *color2;
|
||||
@property (strong) IBOutlet NSTextField *color3;
|
||||
@end
|
||||
|
||||
@implementation GBPaletteViewItem
|
||||
@end
|
||||
|
||||
@implementation GBPaletteView
|
||||
{
|
||||
NSMutableArray<NSTextField *> *_colors;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder
|
||||
{
|
||||
self = [super initWithCoder:coder];
|
||||
_colors = [NSMutableArray array];
|
||||
CGFloat height = self.frame.size.height;
|
||||
for (unsigned i = 0; i < 16; i++) {
|
||||
GBPaletteViewItem *item = [[GBPaletteViewItem alloc] init];
|
||||
[[NSBundle mainBundle] loadNibNamed:@"GBPaletteViewRow" owner:item topLevelObjects:nil];
|
||||
[self addSubview:item.view];
|
||||
[item.view setFrameOrigin:NSMakePoint(0, height - (i * 24) - 24)];
|
||||
item.label.stringValue = [NSString stringWithFormat:@"%@ %d", i < 8? @"Background" : @"Object", i % 8];
|
||||
item.view.autoresizingMask = NSViewMaxXMargin | NSViewMinYMargin;
|
||||
[_colors addObject:item.color0];
|
||||
[_colors addObject:item.color1];
|
||||
[_colors addObject:item.color2];
|
||||
[_colors addObject:item.color3];
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)reloadData:(Document *)document
|
||||
{
|
||||
GB_gameboy_t *gb = document.gb;
|
||||
uint8_t *bg = GB_get_direct_access(gb, GB_DIRECT_ACCESS_BGP, NULL, NULL);
|
||||
uint8_t *obj = GB_get_direct_access(gb, GB_DIRECT_ACCESS_OBP, NULL, NULL);
|
||||
|
||||
for (unsigned i = 0; i < 4 * 8 * 2; i++) {
|
||||
uint8_t index = i % (4 * 8);
|
||||
uint8_t *palette = i >= 4 * 8 ? obj : bg;
|
||||
uint16_t color = (palette[(index << 1) + 1] << 8) | palette[(index << 1)];
|
||||
uint32_t nativeColor = GB_convert_rgb15(gb, color, false);
|
||||
|
||||
uint8_t r = color & 0x1F,
|
||||
g = (color >> 5) & 0x1F,
|
||||
b = (color >> 10) & 0x1F;
|
||||
|
||||
NSTextField *field = _colors[i];
|
||||
field.stringValue = [NSString stringWithFormat:@"$%04X", color];
|
||||
field.textColor = r * 3 + g * 4 + b * 2 > 120? [NSColor blackColor] : [NSColor whiteColor];
|
||||
field.toolTip = [NSString stringWithFormat:@"Red: %d, Green: %d, Blue: %d", r, g, b];
|
||||
field.backgroundColor = [NSColor colorWithRed:(nativeColor & 0xFF) / 255.0
|
||||
green:((nativeColor >> 8) & 0xFF) / 255.0
|
||||
blue:((nativeColor >> 16) & 0xFF) / 255.0
|
||||
alpha:1.0];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)drawRect:(NSRect)dirtyRect
|
||||
{
|
||||
NSRect frame = self.frame;
|
||||
if (@available(macOS 10.14, *)) {
|
||||
[[NSColor alternatingContentBackgroundColors].lastObject setFill];
|
||||
}
|
||||
else {
|
||||
[[NSColor colorWithDeviceWhite:0.96 alpha:1] setFill];
|
||||
}
|
||||
for (unsigned i = 1; i <= 8; i++) {
|
||||
NSRectFill(NSMakeRect(0, frame.size.height - i * 24 * 2, frame.size.width, 24));
|
||||
}
|
||||
|
||||
if (@available(macOS 10.14, *)) {
|
||||
[[NSColor alternatingContentBackgroundColors].firstObject setFill];
|
||||
}
|
||||
else {
|
||||
[[NSColor controlBackgroundColor] setFill];
|
||||
}
|
||||
for (unsigned i = 0; i < 8; i++) {
|
||||
NSRectFill(NSMakeRect(0, frame.size.height - i * 24 * 2 - 24, frame.size.width, 24));
|
||||
}
|
||||
}
|
||||
@end
|
|
@ -0,0 +1,74 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14868" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14868"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="GBPaletteViewItem">
|
||||
<connections>
|
||||
<outlet property="color0" destination="ypt-t4-Mf3" id="Bam-dX-hHk"/>
|
||||
<outlet property="color1" destination="KkX-Z8-Sqi" id="uCl-UT-PWu"/>
|
||||
<outlet property="color2" destination="jDk-Ej-4yI" id="Xjs-el-m3s"/>
|
||||
<outlet property="color3" destination="7PI-YE-fTk" id="7J8-aH-LEO"/>
|
||||
<outlet property="label" destination="NOK-yI-LKh" id="AK3-g5-H7a"/>
|
||||
<outlet property="view" destination="c22-O7-iKe" id="DPx-8k-YQB"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customView id="c22-O7-iKe">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ypt-t4-Mf3">
|
||||
<rect key="frame" x="131" y="0.0" width="96" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" borderStyle="border" alignment="center" title="$7FFF" drawsBackground="YES" id="sKu-Uy-2LG">
|
||||
<font key="font" size="13" name="Menlo-Regular"/>
|
||||
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KkX-Z8-Sqi">
|
||||
<rect key="frame" x="226" y="0.0" width="96" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" borderStyle="border" alignment="center" title="$7FFF" drawsBackground="YES" id="9LH-TF-W1L">
|
||||
<font key="font" size="13" name="Menlo-Regular"/>
|
||||
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="jDk-Ej-4yI">
|
||||
<rect key="frame" x="321" y="0.0" width="96" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" borderStyle="border" alignment="center" title="$7FFF" drawsBackground="YES" id="arE-i5-zCC">
|
||||
<font key="font" size="13" name="Menlo-Regular"/>
|
||||
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7PI-YE-fTk">
|
||||
<rect key="frame" x="416" y="0.0" width="96" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" borderStyle="border" alignment="center" title="$7FFF" drawsBackground="YES" id="ZbU-nE-FsE">
|
||||
<font key="font" size="13" name="Menlo-Regular"/>
|
||||
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="NOK-yI-LKh">
|
||||
<rect key="frame" x="4" y="0.0" width="121" height="20"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Background 0" id="qM4-cY-SDE">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="139" y="31"/>
|
||||
</customView>
|
||||
</objects>
|
||||
</document>
|
|
@ -65,7 +65,7 @@
|
|||
|
||||
- (NSWindowToolbarStyle)toolbarStyle
|
||||
{
|
||||
return NSWindowToolbarStylePreference;
|
||||
return NSWindowToolbarStyleExpanded;
|
||||
}
|
||||
|
||||
- (void)close
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qxJ-pH-d0y">
|
||||
<rect key="frame" x="61.5" y="127" width="39" height="23"/>
|
||||
<rect key="frame" x="63.5" y="128" width="38" height="23"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="Play" imagePosition="only" alignment="center" alternateImage="Pause" state="on" borderStyle="border" focusRingType="none" inset="2" id="3ZK-br-UrS">
|
||||
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
|
||||
|
@ -54,37 +54,14 @@
|
|||
<action selector="togglePause:" target="-2" id="AUe-I7-nOK"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0yD-Sp-Ilo">
|
||||
<rect key="frame" x="19.5" y="127" width="38" height="23"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="Rewind" imagePosition="only" alignment="center" state="on" borderStyle="border" focusRingType="none" inset="2" id="ZIF-TP-Fqn">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="changeGBSTrack:" target="-2" id="jug-AS-bW7"/>
|
||||
</connections>
|
||||
</button>
|
||||
<popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="I1T-VS-Vse">
|
||||
<rect key="frame" x="106" y="127" width="131" height="23"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="border" focusRingType="none" imageScaling="proportionallyDown" inset="2" id="YJh-dI-A5D">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="Knp-Ok-Pb4"/>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="changeGBSTrack:" target="-2" id="HET-AT-CfQ"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<segmentedControl verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="SRS-M5-VVL">
|
||||
<rect key="frame" x="240.5" y="127" width="72" height="23"/>
|
||||
<rect key="frame" x="244.5" y="128" width="68" height="23"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<segmentedCell key="cell" borderStyle="border" alignment="left" style="texturedRounded" trackingMode="momentary" id="cmq-I8-cFL">
|
||||
<font key="font" metaFont="system"/>
|
||||
<segments>
|
||||
<segment toolTip="Previous Track" image="Previous" width="33"/>
|
||||
<segment toolTip="Next Track" image="Next" width="32" tag="1"/>
|
||||
<segment toolTip="Previous Track" image="Previous" width="31"/>
|
||||
<segment toolTip="Next Track" image="Next" width="30" tag="1"/>
|
||||
</segments>
|
||||
</segmentedCell>
|
||||
<connections>
|
||||
|
@ -114,6 +91,29 @@
|
|||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0yD-Sp-Ilo">
|
||||
<rect key="frame" x="20.5" y="128" width="38" height="23"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="Rewind" imagePosition="only" alignment="center" state="on" borderStyle="border" focusRingType="none" inset="2" id="ZIF-TP-Fqn">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="changeGBSTrack:" target="-2" id="jug-AS-bW7"/>
|
||||
</connections>
|
||||
</button>
|
||||
<popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="I1T-VS-Vse">
|
||||
<rect key="frame" x="107" y="127" width="131" height="25"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" alignment="center" lineBreakMode="truncatingTail" borderStyle="border" focusRingType="none" imageScaling="proportionallyDown" inset="2" id="YJh-dI-A5D">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="Knp-Ok-Pb4"/>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="changeGBSTrack:" target="-2" id="HET-AT-CfQ"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="67" y="292.5"/>
|
||||
</customView>
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14868" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14868"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="Document">
|
||||
<connections>
|
||||
<outlet property="gbsAuthor" destination="gaD-ZH-Beh" id="2i7-BD-bJ2"/>
|
||||
<outlet property="gbsCopyright" destination="2dl-dH-E3J" id="LnT-Vb-pN6"/>
|
||||
<outlet property="gbsNextPrevButton" destination="SRS-M5-VVL" id="YEN-01-wRX"/>
|
||||
<outlet property="gbsPlayPauseButton" destination="qxJ-pH-d0y" id="qk8-8I-9u5"/>
|
||||
<outlet property="gbsPlayerView" destination="c22-O7-iKe" id="A1w-e5-EQE"/>
|
||||
<outlet property="gbsRewindButton" destination="0yD-Sp-Ilo" id="FgR-xd-JW5"/>
|
||||
<outlet property="gbsTitle" destination="H3v-X3-48q" id="DCl-wL-oy8"/>
|
||||
<outlet property="gbsTracks" destination="I1T-VS-Vse" id="Vk4-GP-RjB"/>
|
||||
<outlet property="gbsVisualizer" destination="Q3o-bK-DIN" id="1YC-C5-Je6"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customView id="c22-O7-iKe">
|
||||
<rect key="frame" x="0.0" y="0.0" width="332" height="221"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="H3v-X3-48q">
|
||||
<rect key="frame" x="18" y="192" width="296" height="19"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="Title" id="BwZ-Zj-sP6">
|
||||
<font key="font" metaFont="systemBold" size="16"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="gaD-ZH-Beh">
|
||||
<rect key="frame" x="18" y="166" width="296" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="Author" id="IgT-r1-T38">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qxJ-pH-d0y">
|
||||
<rect key="frame" x="57" y="124" width="50" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" bezelStyle="rounded" image="Play" imagePosition="only" alignment="center" alternateImage="Pause" state="on" borderStyle="border" focusRingType="none" inset="2" id="3ZK-br-UrS">
|
||||
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="togglePause:" target="-2" id="AUe-I7-nOK"/>
|
||||
</connections>
|
||||
</button>
|
||||
<popUpButton focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="I1T-VS-Vse">
|
||||
<rect key="frame" x="105" y="127" width="141" height="25"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" alignment="center" lineBreakMode="truncatingTail" borderStyle="border" focusRingType="none" imageScaling="proportionallyDown" inset="2" id="YJh-dI-A5D">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="Knp-Ok-Pb4"/>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="changeGBSTrack:" target="-2" id="HET-AT-CfQ"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<segmentedControl verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="SRS-M5-VVL">
|
||||
<rect key="frame" x="247" y="129" width="68" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<segmentedCell key="cell" borderStyle="border" alignment="left" style="rounded" trackingMode="momentary" id="cmq-I8-cFL">
|
||||
<font key="font" metaFont="system"/>
|
||||
<segments>
|
||||
<segment toolTip="Previous Track" image="Previous" width="31"/>
|
||||
<segment toolTip="Next Track" image="Next" width="30" tag="1"/>
|
||||
</segments>
|
||||
</segmentedCell>
|
||||
<connections>
|
||||
<action selector="gbsNextPrevPushed:" target="-2" id="roN-Iy-tDQ"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
<box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="b9A-cd-ias">
|
||||
<rect key="frame" x="0.0" y="117" width="332" height="5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
</box>
|
||||
<customView appearanceType="darkAqua" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tRy-Gw-IaG" customClass="GBOptionalVisualEffectView">
|
||||
<rect key="frame" x="0.0" y="24" width="332" height="95"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Q3o-bK-DIN" customClass="GBVisualizerView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="332" height="95"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
</customView>
|
||||
</subviews>
|
||||
</customView>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="2dl-dH-E3J">
|
||||
<rect key="frame" x="18" y="5" width="296" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="clipping" alignment="center" title="Copyright" id="nM9-oF-OV9">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0yD-Sp-Ilo">
|
||||
<rect key="frame" x="13" y="124" width="50" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" bezelStyle="rounded" image="Rewind" imagePosition="only" alignment="center" state="on" borderStyle="border" focusRingType="none" inset="2" id="ZIF-TP-Fqn">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="changeGBSTrack:" target="-2" id="jug-AS-bW7"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="67" y="292.5"/>
|
||||
</customView>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="Next" width="16" height="10"/>
|
||||
<image name="Pause" width="10" height="10"/>
|
||||
<image name="Play" width="10" height="10"/>
|
||||
<image name="Previous" width="16" height="10"/>
|
||||
<image name="Rewind" width="10" height="10"/>
|
||||
</resources>
|
||||
</document>
|
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 9.3 KiB |
|
@ -88,6 +88,24 @@
|
|||
<key>NSDocumentClass</key>
|
||||
<string>Document</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>gbcart</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>ColorCartridge</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Game Boy Cartridge</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array/>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>1</integer>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>Document</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>SameBoy</string>
|
||||
|
@ -112,7 +130,7 @@
|
|||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.9</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2015-2021 Lior Halphon</string>
|
||||
<string>Copyright © 2015-2022 Lior Halphon</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
|
BIN
Cocoa/Joypad.png
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 740 B |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 427 B |
After Width: | Height: | Size: 710 B |
|
@ -30,7 +30,7 @@
|
|||
|
||||
<h1>SameBoy</h1>
|
||||
<h2>MIT License</h2>
|
||||
<h3>Copyright © 2015-2021 Lior Halphon</h3>
|
||||
<h3>Copyright © 2015-2022 Lior Halphon</h3>
|
||||
|
||||
<p>Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<menuItem title="About SameBoy" id="5kV-Vb-QxS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
|
||||
<action selector="orderFrontAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
|
||||
|
@ -91,6 +91,12 @@
|
|||
</menu>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="m54-Is-iLE"/>
|
||||
<menuItem title="New Cartridge Instance…" keyEquivalent="n" id="Vld-be-NZu">
|
||||
<connections>
|
||||
<action selector="newCartridgeInstance:" target="-1" id="GJc-xU-ZEr"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="vQH-Yd-TH4"/>
|
||||
<menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
|
||||
<connections>
|
||||
<action selector="performClose:" target="-1" id="HmO-Ls-i7Q"/>
|
||||
|
@ -364,9 +370,14 @@
|
|||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="DPb-Sh-5tg"/>
|
||||
<menuItem title="Mute Sound" keyEquivalent="m" id="1UK-8n-QPP">
|
||||
<menuItem title="Start Audio Recording…" keyEquivalent="A" id="1UK-8n-QPP">
|
||||
<connections>
|
||||
<action selector="mute:" target="-1" id="YE5-mi-Yzd"/>
|
||||
<action selector="toggleAudioRecording:" target="-1" id="YE5-mi-Yzd"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Mute Sound" keyEquivalent="m" id="zo0-Rh-wTu">
|
||||
<connections>
|
||||
<action selector="mute:" target="-1" id="eK3-ea-ExJ"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
|
|
|
@ -11,6 +11,13 @@ static NSImage * (*imageNamed)(Class self, SEL _cmd, NSString *name);
|
|||
|
||||
+ (NSImage *)imageNamedWithDark:(NSImageName)name
|
||||
{
|
||||
if (@available(macOS 11.0, *)) {
|
||||
if (![name containsString:@"~solid"]) {
|
||||
NSImage *solid = [self imageNamed:[name stringByAppendingString:@"~solid"]];
|
||||
[solid setTemplate:true];
|
||||
if (solid) return solid;
|
||||
}
|
||||
}
|
||||
NSImage *light = imageNamed(self, _cmd, name);
|
||||
if (@available(macOS 10.14, *)) {
|
||||
NSImage *dark = imageNamed(self, _cmd, [name stringByAppendingString:@"~dark"]);
|
||||
|
|
BIN
Cocoa/Next.png
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 158 B |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 257 B |
BIN
Cocoa/Pause.png
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 107 B |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 131 B |
BIN
Cocoa/Play.png
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 144 B |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 221 B |
|
@ -52,7 +52,7 @@
|
|||
<action selector="switchPreferencesTab:" target="-2" id="Tio-D7-PaA"/>
|
||||
</connections>
|
||||
</toolbarItem>
|
||||
<toolbarItem implicitItemIdentifier="EB7AB5BC-57B0-4639-A037-E28226866C80" label="Updates" paletteLabel="Updates" tag="4" image="AppIcon" autovalidates="NO" selectable="YES" id="lMV-68-ntz">
|
||||
<toolbarItem implicitItemIdentifier="EB7AB5BC-57B0-4639-A037-E28226866C80" label="Updates" paletteLabel="Updates" tag="4" image="Updates" autovalidates="NO" selectable="YES" id="lMV-68-ntz">
|
||||
<connections>
|
||||
<action selector="switchPreferencesTab:" target="-2" id="bfU-hc-FnN"/>
|
||||
</connections>
|
||||
|
@ -222,7 +222,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="lxk-db-Sxv">
|
||||
<rect key="frame" x="30" y="165" width="262" height="22"/>
|
||||
<rect key="frame" x="30" y="165" width="262" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Disabled" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="iHP-Yz-fiH" id="aQ6-HN-7Aj">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
|
@ -251,7 +251,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Iwr-eI-SD1">
|
||||
<rect key="frame" x="30" y="114" width="262" height="22"/>
|
||||
<rect key="frame" x="30" y="114" width="262" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Greyscale" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Ajr-5r-iIk" id="rEU-jh-m3j">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
|
@ -281,7 +281,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="R9D-FV-bpd">
|
||||
<rect key="frame" x="30" y="60" width="262" height="25"/>
|
||||
<rect key="frame" x="30" y="60" width="262" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Never" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="1" imageScaling="proportionallyDown" inset="2" selectedItem="heL-AV-0az" id="DY9-2D-h1L">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
|
@ -326,11 +326,11 @@
|
|||
<point key="canvasLocation" x="-501" y="236.5"/>
|
||||
</customView>
|
||||
<customView id="ymk-46-SX7">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="375"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="427"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="w9w-yX-KxB">
|
||||
<rect key="frame" x="18" y="283" width="284" height="17"/>
|
||||
<rect key="frame" x="18" y="335" width="284" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Rewinding duration:" id="JaO-5h-ugl">
|
||||
<font key="font" metaFont="system"/>
|
||||
|
@ -339,7 +339,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="o3Z-34-FJk">
|
||||
<rect key="frame" x="18" y="228" width="280" height="17"/>
|
||||
<rect key="frame" x="18" y="280" width="280" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Real Time Clock emulation:" id="Qoi-ub-YtI">
|
||||
<font key="font" metaFont="system"/>
|
||||
|
@ -348,7 +348,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MI2-ql-f6M">
|
||||
<rect key="frame" x="18" y="338" width="284" height="17"/>
|
||||
<rect key="frame" x="18" y="390" width="284" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Boot ROMs location:" id="nj0-Cb-gEA">
|
||||
<font key="font" metaFont="system"/>
|
||||
|
@ -357,7 +357,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Wg8-hJ-df9">
|
||||
<rect key="frame" x="18" y="160" width="284" height="17"/>
|
||||
<rect key="frame" x="18" y="212" width="284" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Game Boy revision:" id="GIA-ep-SBi">
|
||||
<font key="font" metaFont="system"/>
|
||||
|
@ -366,7 +366,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LFw-Uk-cPR">
|
||||
<rect key="frame" x="30" y="127" width="262" height="26"/>
|
||||
<rect key="frame" x="30" y="179" width="262" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="DMG-CPU B" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="2" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" selectedItem="aXT-sE-m5Z" id="FuX-Hc-uO7">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
|
@ -385,7 +385,7 @@
|
|||
</connections>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MAq-1X-Gpo">
|
||||
<rect key="frame" x="16" y="50" width="284" height="17"/>
|
||||
<rect key="frame" x="16" y="102" width="284" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Game Boy Color revision:" id="edD-t7-vwk">
|
||||
<font key="font" metaFont="system"/>
|
||||
|
@ -394,19 +394,19 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dlD-sk-SHO">
|
||||
<rect key="frame" x="28" y="17" width="262" height="26"/>
|
||||
<rect key="frame" x="28" y="69" width="262" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="CPU-CGB E" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="517" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" selectedItem="3lF-1Q-2SS" id="Edt-1S-dXz">
|
||||
<popUpButtonCell key="cell" type="push" title="CPU CGB E" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="517" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" selectedItem="3lF-1Q-2SS" id="Edt-1S-dXz">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" autoenablesItems="NO" id="bbF-hB-Hv7">
|
||||
<items>
|
||||
<menuItem title="CPU-CGB 0 (Experimental)" tag="512" id="2Uk-u3-6Gw"/>
|
||||
<menuItem title="CPU-CGB A (Experimental)" tag="513" id="axv-yk-RWM"/>
|
||||
<menuItem title="CPU-CGB B (Experimental)" tag="514" id="NtJ-oo-IM2"/>
|
||||
<menuItem title="CPU-CGB C (Experimental)" tag="515" id="9YL-u8-12z"/>
|
||||
<menuItem title="CPU-CGB D" tag="516" id="c76-oF-fkU"/>
|
||||
<menuItem title="CPU-CGB E" state="on" tag="517" id="3lF-1Q-2SS"/>
|
||||
<menuItem title="CPU CGB 0 (Experimental)" tag="512" id="2Uk-u3-6Gw"/>
|
||||
<menuItem title="CPU CGB A (Experimental)" tag="513" id="axv-yk-RWM"/>
|
||||
<menuItem title="CPU CGB B (Experimental)" tag="514" id="NtJ-oo-IM2"/>
|
||||
<menuItem title="CPU CGB C (Experimental)" tag="515" id="9YL-u8-12z"/>
|
||||
<menuItem title="CPU CGB D" tag="516" id="c76-oF-fkU"/>
|
||||
<menuItem title="CPU CGB E" state="on" tag="517" id="3lF-1Q-2SS"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
|
@ -415,7 +415,7 @@
|
|||
</connections>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tAa-0A-0fP">
|
||||
<rect key="frame" x="18" y="105" width="284" height="17"/>
|
||||
<rect key="frame" x="18" y="157" width="284" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Super Game Boy model:" id="d0g-rk-FK0">
|
||||
<font key="font" metaFont="system"/>
|
||||
|
@ -424,11 +424,11 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="mdm-eW-ia1">
|
||||
<rect key="frame" x="12" y="183" width="296" height="5"/>
|
||||
<rect key="frame" x="12" y="235" width="296" height="5"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
</box>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dza-T7-RkX">
|
||||
<rect key="frame" x="28" y="72" width="262" height="26"/>
|
||||
<rect key="frame" x="28" y="124" width="262" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Super Game Boy (NTSC)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="4" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" selectedItem="x5A-7f-ef9" id="2Mt-ci-bB0">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
|
@ -446,7 +446,7 @@
|
|||
</connections>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tFf-H1-XUL">
|
||||
<rect key="frame" x="30" y="199" width="262" height="22"/>
|
||||
<rect key="frame" x="30" y="251" width="262" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Sync to system clock" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="arp-Qi-Xix" id="uRs-Ag-Sbw">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
|
@ -465,7 +465,7 @@
|
|||
</connections>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wC4-aJ-mhQ">
|
||||
<rect key="frame" x="30" y="305" width="262" height="26"/>
|
||||
<rect key="frame" x="30" y="357" width="262" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Use built-in boot ROMs" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Tnm-SR-ZEm" id="T3Y-Ln-Onl">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
|
@ -489,7 +489,7 @@
|
|||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7fg-Ww-JjR">
|
||||
<rect key="frame" x="30" y="250" width="262" height="26"/>
|
||||
<rect key="frame" x="30" y="302" width="262" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Disabled" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="lxQ-4n-kEv" id="lvb-QF-0Ht">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
|
@ -512,8 +512,33 @@
|
|||
<action selector="rewindLengthChanged:" target="QvC-M9-y7g" id="5NQ-1T-RNc"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5r5-qY-b8h">
|
||||
<rect key="frame" x="18" y="47" width="280" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Game Boy Advance revision:" id="R5q-dJ-NvD">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3Dl-S6-O7c">
|
||||
<rect key="frame" x="28" y="17" width="262" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="CPU AGB A (GBA, GB Player)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="519" imageScaling="proportionallyDown" inset="2" autoenablesItems="NO" selectedItem="8bk-wP-Cbr" id="EhC-I2-5Th">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" autoenablesItems="NO" id="b2b-jo-SrI">
|
||||
<items>
|
||||
<menuItem title="CPU AGB 0 (Early GBA)" tag="518" enabled="NO" id="75z-Yy-XaY"/>
|
||||
<menuItem title="CPU AGB A (GBA, GB Player)" state="on" tag="519" id="8bk-wP-Cbr"/>
|
||||
<menuItem title="CPU AGB B (GBA SP)" tag="520" enabled="NO" id="jIE-v4-768"/>
|
||||
<menuItem title="CPU AGB E (Late GBA SP, GB Player)" tag="521" enabled="NO" id="w7s-kh-HaZ"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="-501" y="667.5"/>
|
||||
<point key="canvasLocation" x="-501" y="693.5"/>
|
||||
</customView>
|
||||
<customView id="Zn1-Y5-RbR">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="201"/>
|
||||
|
@ -765,7 +790,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ogs-xG-b4b">
|
||||
<rect key="frame" x="30" y="58" width="262" height="22"/>
|
||||
<rect key="frame" x="30" y="58" width="262" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Never" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="jki-7x-bnM" id="o9b-MH-8kd">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
|
@ -854,7 +879,7 @@
|
|||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
|
||||
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" id="5Al-aC-tq8">
|
||||
<rect key="frame" x="1" y="1" width="158" height="316"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView focusRingType="none" verticalHuggingPriority="750" ambiguous="YES" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="sourceList" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" id="ZVn-bk-duk">
|
||||
<rect key="frame" x="0.0" y="0.0" width="158" height="316"/>
|
||||
|
@ -1088,12 +1113,12 @@
|
|||
</customObject>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="AppIcon" width="128" height="128"/>
|
||||
<image name="CPU" width="32" height="32"/>
|
||||
<image name="Display" width="32" height="32"/>
|
||||
<image name="Joypad" width="32" height="32"/>
|
||||
<image name="NSAddTemplate" width="11" height="11"/>
|
||||
<image name="NSRemoveTemplate" width="11" height="11"/>
|
||||
<image name="Speaker" width="32" height="32"/>
|
||||
<image name="Updates" width="32" height="32"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 148 B |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 266 B |
BIN
Cocoa/Rewind.png
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 147 B |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 215 B |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 548 B |
After Width: | Height: | Size: 905 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 428 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 366 B |
After Width: | Height: | Size: 1021 B |
324
Core/apu.c
|
@ -2,6 +2,7 @@
|
|||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include "gb.h"
|
||||
|
||||
static const uint8_t duties[] = {
|
||||
|
@ -21,7 +22,7 @@ static void refresh_channel(GB_gameboy_t *gb, unsigned index, unsigned cycles_of
|
|||
|
||||
bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index)
|
||||
{
|
||||
if (gb->model >= GB_MODEL_AGB) {
|
||||
if (gb->model > GB_MODEL_CGB_E) {
|
||||
/* On the AGB, mixing is done digitally, so there are no per-channel
|
||||
DACs. Instead, all channels are summed digital regardless of
|
||||
whatever the DAC state would be on a CGB or earlier model. */
|
||||
|
@ -68,14 +69,8 @@ static uint8_t agb_bias_for_channel(GB_gameboy_t *gb, unsigned index)
|
|||
|
||||
static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsigned cycles_offset)
|
||||
{
|
||||
if (gb->model >= GB_MODEL_AGB && index == GB_WAVE) {
|
||||
/* For some reason, channel 3 is inverted on the AGB */
|
||||
value ^= 0xF;
|
||||
}
|
||||
|
||||
if (value == 0 && gb->apu.samples[index] == 0) return;
|
||||
|
||||
if (gb->model >= GB_MODEL_AGB) {
|
||||
|
||||
if (gb->model > GB_MODEL_CGB_E) {
|
||||
/* On the AGB, because no analog mixing is done, the behavior of NR51 is a bit different.
|
||||
A channel that is not connected to a terminal is idenitcal to a connected channel
|
||||
playing PCM sample 0. */
|
||||
|
@ -85,21 +80,26 @@ static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsign
|
|||
unsigned right_volume = (gb->io_registers[GB_IO_NR50] & 7) + 1;
|
||||
unsigned left_volume = ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1;
|
||||
|
||||
if (index == GB_WAVE) {
|
||||
/* For some reason, channel 3 is inverted on the AGB */
|
||||
value ^= 0xF;
|
||||
}
|
||||
|
||||
GB_sample_t output;
|
||||
uint8_t bias = agb_bias_for_channel(gb, index);
|
||||
|
||||
if (gb->io_registers[GB_IO_NR51] & (1 << index)) {
|
||||
output.right = (0xf - value * 2 + bias) * right_volume;
|
||||
output.right = (0xF - value * 2 + bias) * right_volume;
|
||||
}
|
||||
else {
|
||||
output.right = 0xf * right_volume;
|
||||
output.right = 0xF * right_volume;
|
||||
}
|
||||
|
||||
if (gb->io_registers[GB_IO_NR51] & (0x10 << index)) {
|
||||
output.left = (0xf - value * 2 + bias) * left_volume;
|
||||
output.left = (0xF - value * 2 + bias) * left_volume;
|
||||
}
|
||||
else {
|
||||
output.left = 0xf * left_volume;
|
||||
output.left = 0xF * left_volume;
|
||||
}
|
||||
|
||||
if (*(uint32_t *)&(gb->apu_output.current_sample[index]) != *(uint32_t *)&output) {
|
||||
|
@ -111,6 +111,8 @@ static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsign
|
|||
return;
|
||||
}
|
||||
|
||||
if (value == 0 && gb->apu.samples[index] == 0) return;
|
||||
|
||||
if (!GB_apu_is_DAC_enabled(gb, index)) {
|
||||
value = gb->apu.samples[index];
|
||||
}
|
||||
|
@ -127,7 +129,7 @@ static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsign
|
|||
if (gb->io_registers[GB_IO_NR51] & (0x10 << index)) {
|
||||
left_volume = ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1;
|
||||
}
|
||||
GB_sample_t output = {(0xf - value * 2) * left_volume, (0xf - value * 2) * right_volume};
|
||||
GB_sample_t output = {(0xF - value * 2) * left_volume, (0xF - value * 2) * right_volume};
|
||||
if (*(uint32_t *)&(gb->apu_output.current_sample[index]) != *(uint32_t *)&output) {
|
||||
refresh_channel(gb, index, cycles_offset);
|
||||
gb->apu_output.current_sample[index] = output;
|
||||
|
@ -145,7 +147,7 @@ static signed interference(GB_gameboy_t *gb)
|
|||
/* These aren't scientifically measured, but based on ear based on several recordings */
|
||||
signed ret = 0;
|
||||
if (gb->halted) {
|
||||
if (gb->model != GB_MODEL_AGB) {
|
||||
if (gb->model <= GB_MODEL_CGB_E) {
|
||||
ret -= MAX_CH_AMP / 5;
|
||||
}
|
||||
else {
|
||||
|
@ -154,7 +156,7 @@ static signed interference(GB_gameboy_t *gb)
|
|||
}
|
||||
if (gb->io_registers[GB_IO_LCDC] & 0x80) {
|
||||
ret += MAX_CH_AMP / 7;
|
||||
if ((gb->io_registers[GB_IO_STAT] & 3) == 3 && gb->model != GB_MODEL_AGB) {
|
||||
if ((gb->io_registers[GB_IO_STAT] & 3) == 3 && gb->model <= GB_MODEL_CGB_E) {
|
||||
ret += MAX_CH_AMP / 14;
|
||||
}
|
||||
else if ((gb->io_registers[GB_IO_STAT] & 3) == 1) {
|
||||
|
@ -166,7 +168,7 @@ static signed interference(GB_gameboy_t *gb)
|
|||
ret += MAX_CH_AMP / 10;
|
||||
}
|
||||
|
||||
if (GB_is_cgb(gb) && gb->model < GB_MODEL_AGB && (gb->io_registers[GB_IO_RP] & 1)) {
|
||||
if (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_E && (gb->io_registers[GB_IO_RP] & 1)) {
|
||||
ret += MAX_CH_AMP / 10;
|
||||
}
|
||||
|
||||
|
@ -186,7 +188,7 @@ static void render(GB_gameboy_t *gb)
|
|||
unrolled for (unsigned i = 0; i < GB_N_CHANNELS; i++) {
|
||||
double multiplier = CH_STEP;
|
||||
|
||||
if (gb->model < GB_MODEL_AGB) {
|
||||
if (gb->model <= GB_MODEL_CGB_E) {
|
||||
if (!GB_apu_is_DAC_enabled(gb, i)) {
|
||||
gb->apu_output.dac_discharge[i] -= ((double) DAC_DECAY_SPEED) / gb->apu_output.sample_rate;
|
||||
if (gb->apu_output.dac_discharge[i] < 0) {
|
||||
|
@ -223,6 +225,8 @@ static void render(GB_gameboy_t *gb)
|
|||
gb->apu_output.last_update[i] = 0;
|
||||
}
|
||||
gb->apu_output.cycles_since_render = 0;
|
||||
|
||||
if (gb->sgb && gb->sgb->intro_animation < GB_SGB_INTRO_ANIMATION_LENGTH) return;
|
||||
|
||||
GB_sample_t filtered_output = gb->apu_output.highpass_mode?
|
||||
(GB_sample_t) {output.left - gb->apu_output.highpass_diff.left,
|
||||
|
@ -243,18 +247,14 @@ static void render(GB_gameboy_t *gb)
|
|||
unsigned left_volume = 0;
|
||||
unsigned right_volume = 0;
|
||||
unrolled for (unsigned i = GB_N_CHANNELS; i--;) {
|
||||
if (gb->apu.is_active[i]) {
|
||||
if (GB_apu_is_DAC_enabled(gb, i)) {
|
||||
if (mask & 1) {
|
||||
left_volume += (gb->io_registers[GB_IO_NR50] & 7) * CH_STEP * 0xF;
|
||||
left_volume += ((gb->io_registers[GB_IO_NR50] & 7) + 1) * CH_STEP * 0xF;
|
||||
}
|
||||
if (mask & 0x10) {
|
||||
right_volume += ((gb->io_registers[GB_IO_NR50] >> 4) & 7) * CH_STEP * 0xF;
|
||||
right_volume += (((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1) * CH_STEP * 0xF;
|
||||
}
|
||||
}
|
||||
else {
|
||||
left_volume += gb->apu_output.current_sample[i].left * CH_STEP;
|
||||
right_volume += gb->apu_output.current_sample[i].right * CH_STEP;
|
||||
}
|
||||
mask >>= 1;
|
||||
}
|
||||
gb->apu_output.highpass_diff = (GB_double_sample_t)
|
||||
|
@ -279,11 +279,29 @@ static void render(GB_gameboy_t *gb)
|
|||
}
|
||||
assert(gb->apu_output.sample_callback);
|
||||
gb->apu_output.sample_callback(gb, &filtered_output);
|
||||
if (unlikely(gb->apu_output.output_file)) {
|
||||
#ifdef GB_BIG_ENDIAN
|
||||
if (gb->apu_output.output_format == GB_AUDIO_FORMAT_WAV) {
|
||||
filtered_output.left = LE16(filtered_output.left);
|
||||
filtered_output.right = LE16(filtered_output.right);
|
||||
}
|
||||
#endif
|
||||
if (fwrite(&filtered_output, sizeof(filtered_output), 1, gb->apu_output.output_file) != 1) {
|
||||
fclose(gb->apu_output.output_file);
|
||||
gb->apu_output.output_file = NULL;
|
||||
gb->apu_output.output_error = errno;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void update_square_sample(GB_gameboy_t *gb, unsigned index)
|
||||
{
|
||||
if (gb->apu.square_channels[index].sample_surpressed) return;
|
||||
if (gb->apu.square_channels[index].sample_surpressed) {
|
||||
if (gb->model > GB_MODEL_CGB_E) {
|
||||
update_sample(gb, index, gb->apu.samples[index], 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t duty = gb->io_registers[index == GB_SQUARE_1? GB_IO_NR11 :GB_IO_NR21] >> 6;
|
||||
update_sample(gb, index,
|
||||
|
@ -528,7 +546,7 @@ void GB_apu_div_event(GB_gameboy_t *gb)
|
|||
if (gb->apu.wave_channel.length_enabled) {
|
||||
if (gb->apu.wave_channel.pulse_length) {
|
||||
if (!--gb->apu.wave_channel.pulse_length) {
|
||||
if (gb->apu.is_active[GB_WAVE] && gb->model == GB_MODEL_AGB) {
|
||||
if (gb->apu.is_active[GB_WAVE] && gb->model > GB_MODEL_CGB_E) {
|
||||
if (gb->apu.wave_channel.sample_countdown == 0) {
|
||||
gb->apu.wave_channel.current_sample_byte =
|
||||
gb->io_registers[GB_IO_WAV_START + (((gb->apu.wave_channel.current_sample_index + 1) & 0xF) >> 1)];
|
||||
|
@ -607,7 +625,7 @@ void GB_apu_run(GB_gameboy_t *gb, bool force)
|
|||
(gb->apu.apu_cycles > 0x1000) ||
|
||||
(gb->apu_output.sample_cycles >= clock_rate) ||
|
||||
(gb->apu.square_sweep_calculate_countdown || gb->apu.channel_1_restart_hold) ||
|
||||
(gb->model < GB_MODEL_AGB && (gb->apu.wave_channel.bugged_read_countdown || (gb->apu.wave_channel.enable && gb->apu.wave_channel.pulsed)))) {
|
||||
(gb->model <= GB_MODEL_CGB_E && (gb->apu.wave_channel.bugged_read_countdown || (gb->apu.wave_channel.enable && gb->apu.wave_channel.pulsed)))) {
|
||||
force = true;
|
||||
}
|
||||
if (!force) {
|
||||
|
@ -690,6 +708,14 @@ void GB_apu_run(GB_gameboy_t *gb, bool force)
|
|||
unrolled for (unsigned i = GB_SQUARE_1; i <= GB_SQUARE_2; i++) {
|
||||
if (gb->apu.is_active[i]) {
|
||||
uint16_t cycles_left = cycles;
|
||||
if (unlikely(gb->apu.square_channels[i].delay)) {
|
||||
if (gb->apu.square_channels[i].delay < cycles_left) {
|
||||
gb->apu.square_channels[i].delay = 0;
|
||||
}
|
||||
else {
|
||||
gb->apu.square_channels[i].delay -= cycles_left;
|
||||
}
|
||||
}
|
||||
while (unlikely(cycles_left > gb->apu.square_channels[i].sample_countdown)) {
|
||||
cycles_left -= gb->apu.square_channels[i].sample_countdown + 1;
|
||||
gb->apu.square_channels[i].sample_countdown = (gb->apu.square_channels[i].sample_length ^ 0x7FF) * 2 + 1;
|
||||
|
@ -699,7 +725,7 @@ void GB_apu_run(GB_gameboy_t *gb, bool force)
|
|||
if (cycles_left == 0 && gb->apu.samples[i] == 0) {
|
||||
gb->apu.pcm_mask[0] &= i == GB_SQUARE_1? 0xF0 : 0x0F;
|
||||
}
|
||||
|
||||
gb->apu.square_channels[i].did_tick = true;
|
||||
update_square_sample(gb, i);
|
||||
}
|
||||
if (cycles_left) {
|
||||
|
@ -726,7 +752,7 @@ void GB_apu_run(GB_gameboy_t *gb, bool force)
|
|||
gb->apu.wave_channel.wave_form_just_read = false;
|
||||
}
|
||||
}
|
||||
else if (gb->apu.wave_channel.enable && gb->apu.wave_channel.pulsed && gb->model < GB_MODEL_AGB) {
|
||||
else if (gb->apu.wave_channel.enable && gb->apu.wave_channel.pulsed && gb->model <= GB_MODEL_CGB_E) {
|
||||
uint16_t cycles_left = cycles;
|
||||
while (unlikely(cycles_left > gb->apu.wave_channel.sample_countdown)) {
|
||||
cycles_left -= gb->apu.wave_channel.sample_countdown + 1;
|
||||
|
@ -807,6 +833,8 @@ void GB_apu_init(GB_gameboy_t *gb)
|
|||
gb->apu.skip_div_event = GB_SKIP_DIV_EVENT_SKIP;
|
||||
gb->apu.div_divider = 1;
|
||||
}
|
||||
gb->apu.square_channels[GB_SQUARE_1].sample_countdown = -1;
|
||||
gb->apu.square_channels[GB_SQUARE_2].sample_countdown = -1;
|
||||
}
|
||||
|
||||
uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg)
|
||||
|
@ -844,7 +872,7 @@ uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg)
|
|||
if (!GB_is_cgb(gb) && !gb->apu.wave_channel.wave_form_just_read) {
|
||||
return 0xFF;
|
||||
}
|
||||
if (gb->model == GB_MODEL_AGB) {
|
||||
if (gb->model > GB_MODEL_CGB_E) {
|
||||
return 0xFF;
|
||||
}
|
||||
reg = GB_IO_WAV_START + gb->apu.wave_channel.current_sample_index / 2;
|
||||
|
@ -959,7 +987,7 @@ static inline uint16_t effective_channel4_counter(GB_gameboy_t *gb)
|
|||
effective_counter |= 0x10;
|
||||
}
|
||||
break;
|
||||
case GB_MODEL_AGB:
|
||||
case GB_MODEL_AGB_A:
|
||||
/* TODO: AGBs are not affected, but AGSes are. They don't seem to follow a simple
|
||||
pattern like the other revisions. */
|
||||
/* For the most part, AGS seems to do:
|
||||
|
@ -987,7 +1015,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||
}
|
||||
|
||||
if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END && gb->apu.is_active[GB_WAVE]) {
|
||||
if ((!GB_is_cgb(gb) && !gb->apu.wave_channel.wave_form_just_read) || gb->model == GB_MODEL_AGB) {
|
||||
if ((!GB_is_cgb(gb) && !gb->apu.wave_channel.wave_form_just_read) || gb->model > GB_MODEL_CGB_E) {
|
||||
return;
|
||||
}
|
||||
reg = GB_IO_WAV_START + gb->apu.wave_channel.current_sample_index / 2;
|
||||
|
@ -1002,7 +1030,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||
/* These registers affect the output of all 4 channels (but not the output of the PCM registers).*/
|
||||
/* We call update_samples with the current value so the APU output is updated with the new outputs */
|
||||
for (unsigned i = GB_N_CHANNELS; i--;) {
|
||||
update_sample(gb, i, gb->apu.samples[i], 0);
|
||||
int8_t sample = gb->apu.samples[i];
|
||||
gb->apu.samples[i] = 0x10; // Invalidate to force update
|
||||
update_sample(gb, i, sample, 0);
|
||||
}
|
||||
break;
|
||||
case GB_IO_NR52: {
|
||||
|
@ -1051,9 +1081,9 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||
case GB_IO_NR11:
|
||||
case GB_IO_NR21: {
|
||||
unsigned index = reg == GB_IO_NR21? GB_SQUARE_2: GB_SQUARE_1;
|
||||
gb->apu.square_channels[index].pulse_length = (0x40 - (value & 0x3f));
|
||||
gb->apu.square_channels[index].pulse_length = (0x40 - (value & 0x3F));
|
||||
if (!gb->apu.global_enable) {
|
||||
value &= 0x3f;
|
||||
value &= 0x3F;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -1080,6 +1110,19 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||
case GB_IO_NR13:
|
||||
case GB_IO_NR23: {
|
||||
unsigned index = reg == GB_IO_NR23? GB_SQUARE_2: GB_SQUARE_1;
|
||||
if (gb->apu.is_active[index]) {
|
||||
/* On an AGB, as well as on CGB C and earlier (TODO: Tested: 0, B and C), it behaves slightly different on
|
||||
double speed. */
|
||||
if (gb->model == GB_MODEL_CGB_E || gb->model == GB_MODEL_CGB_D || gb->apu.square_channels[index].sample_countdown & 1) {
|
||||
if (gb->apu.square_channels[index].did_tick &&
|
||||
gb->apu.square_channels[index].sample_countdown >> 1 == (gb->apu.square_channels[index].sample_length ^ 0x7FF)) {
|
||||
gb->apu.square_channels[index].current_sample_index--;
|
||||
gb->apu.square_channels[index].current_sample_index &= 7;
|
||||
gb->apu.square_channels[index].sample_surpressed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gb->apu.square_channels[index].sample_length &= ~0xFF;
|
||||
gb->apu.square_channels[index].sample_length |= value & 0xFF;
|
||||
break;
|
||||
|
@ -1096,7 +1139,8 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||
/* On an AGB, as well as on CGB C and earlier (TODO: Tested: 0, B and C), it behaves slightly different on
|
||||
double speed. */
|
||||
if (gb->model == GB_MODEL_CGB_E || gb->model == GB_MODEL_CGB_D || gb->apu.square_channels[index].sample_countdown & 1) {
|
||||
if (gb->apu.square_channels[index].sample_countdown >> 1 == (gb->apu.square_channels[index].sample_length ^ 0x7FF)) {
|
||||
if (gb->apu.square_channels[index].did_tick &&
|
||||
gb->apu.square_channels[index].sample_countdown >> 1 == (gb->apu.square_channels[index].sample_length ^ 0x7FF)) {
|
||||
gb->apu.square_channels[index].current_sample_index--;
|
||||
gb->apu.square_channels[index].current_sample_index &= 7;
|
||||
gb->apu.square_channels[index].sample_surpressed = false;
|
||||
|
@ -1112,16 +1156,27 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||
turning the APU off. */
|
||||
gb->apu.square_channels[index].envelope_clock.locked = false;
|
||||
gb->apu.square_channels[index].envelope_clock.clock = false;
|
||||
gb->apu.square_channels[index].did_tick = false;
|
||||
bool force_unsurpressed = false;
|
||||
if (!gb->apu.is_active[index]) {
|
||||
gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + 6 - gb->apu.lf_div;
|
||||
if (gb->model == GB_MODEL_CGB_E || gb->model == GB_MODEL_CGB_D) {
|
||||
if (!(value & 4) && !(((gb->apu.square_channels[index].sample_countdown - gb->apu.square_channels[index].delay) / 2) & 0x400)) {
|
||||
gb->apu.square_channels[index].current_sample_index++;
|
||||
gb->apu.square_channels[index].current_sample_index &= 0x7;
|
||||
force_unsurpressed = true;
|
||||
}
|
||||
}
|
||||
gb->apu.square_channels[index].delay = 6 - gb->apu.lf_div;
|
||||
gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + gb->apu.square_channels[index].delay;
|
||||
if (gb->model <= GB_MODEL_CGB_C && gb->apu.lf_div) {
|
||||
gb->apu.square_channels[index].sample_countdown += 2;
|
||||
gb->apu.square_channels[index].delay += 2;
|
||||
}
|
||||
}
|
||||
else {
|
||||
unsigned extra_delay = 0;
|
||||
if (gb->model == GB_MODEL_CGB_E || gb->model == GB_MODEL_CGB_D) {
|
||||
if (!(value & 4) && !(((gb->apu.square_channels[index].sample_countdown - 1) / 2) & 0x400)) {
|
||||
if (!(value & 4) && !(((gb->apu.square_channels[index].sample_countdown - 1 - gb->apu.square_channels[index].delay) / 2) & 0x400)) {
|
||||
gb->apu.square_channels[index].current_sample_index++;
|
||||
gb->apu.square_channels[index].current_sample_index &= 0x7;
|
||||
gb->apu.square_channels[index].sample_surpressed = false;
|
||||
|
@ -1134,9 +1189,11 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||
}
|
||||
}
|
||||
/* Timing quirk: if already active, sound starts 2 (2MHz) ticks earlier.*/
|
||||
gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + 4 - gb->apu.lf_div + extra_delay;
|
||||
gb->apu.square_channels[index].delay = 4 - gb->apu.lf_div + extra_delay;
|
||||
gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + gb->apu.square_channels[index].delay;
|
||||
if (gb->model <= GB_MODEL_CGB_C && gb->apu.lf_div) {
|
||||
gb->apu.square_channels[index].sample_countdown += 2;
|
||||
gb->apu.square_channels[index].delay += 2;
|
||||
}
|
||||
}
|
||||
gb->apu.square_channels[index].current_volume = gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] >> 4;
|
||||
|
@ -1152,7 +1209,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||
if ((gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] & 0xF8) != 0 && !gb->apu.is_active[index]) {
|
||||
gb->apu.is_active[index] = true;
|
||||
update_sample(gb, index, 0, 0);
|
||||
gb->apu.square_channels[index].sample_surpressed = true;
|
||||
gb->apu.square_channels[index].sample_surpressed = true && !force_unsurpressed;
|
||||
}
|
||||
if (gb->apu.square_channels[index].pulse_length == 0) {
|
||||
gb->apu.square_channels[index].pulse_length = 0x40;
|
||||
|
@ -1219,7 +1276,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||
gb->apu.wave_channel.pulsed = false;
|
||||
if (gb->apu.is_active[GB_WAVE]) {
|
||||
// Todo: I assume this happens on pre-CGB models; test this with an audible test
|
||||
if (gb->apu.wave_channel.sample_countdown == 0 && gb->model < GB_MODEL_AGB) {
|
||||
if (gb->apu.wave_channel.sample_countdown == 0 && gb->model <= GB_MODEL_CGB_E) {
|
||||
gb->apu.wave_channel.current_sample_byte = gb->io_registers[GB_IO_WAV_START + (gb->pc & 0xF)];
|
||||
}
|
||||
else if (gb->apu.wave_channel.wave_form_just_read && gb->model <= GB_MODEL_CGB_C) {
|
||||
|
@ -1315,7 +1372,7 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
|
|||
/* Noise Channel */
|
||||
|
||||
case GB_IO_NR41: {
|
||||
gb->apu.noise_channel.pulse_length = (0x40 - (value & 0x3f));
|
||||
gb->apu.noise_channel.pulse_length = (0x40 - (value & 0x3F));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1498,6 +1555,11 @@ void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, double cycles_per_sample)
|
|||
gb->apu_output.highpass_rate = pow(0.999958, cycles_per_sample);
|
||||
}
|
||||
|
||||
unsigned GB_get_sample_rate(GB_gameboy_t *gb)
|
||||
{
|
||||
return gb->apu_output.sample_rate;
|
||||
}
|
||||
|
||||
void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback)
|
||||
{
|
||||
gb->apu_output.sample_callback = callback;
|
||||
|
@ -1512,3 +1574,179 @@ void GB_set_interference_volume(GB_gameboy_t *gb, double volume)
|
|||
{
|
||||
gb->apu_output.interference_volume = volume;
|
||||
}
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint32_t format_chunk; // = BE32('FORM')
|
||||
uint32_t size; // = BE32(file size - 8)
|
||||
uint32_t format; // = BE32('AIFC')
|
||||
|
||||
uint32_t fver_chunk; // = BE32('FVER')
|
||||
uint32_t fver_size; // = BE32(4)
|
||||
uint32_t fver;
|
||||
|
||||
uint32_t comm_chunk; // = BE32('COMM')
|
||||
uint32_t comm_size; // = BE32(0x18)
|
||||
|
||||
uint16_t channels; // = BE16(2)
|
||||
uint32_t samples_per_channel; // = BE32(total number of samples / 2)
|
||||
uint16_t bit_depth; // = BE16(16)
|
||||
uint16_t frequency_exponent;
|
||||
uint64_t frequency_significand;
|
||||
uint32_t compression_type; // = 'NONE' (BE) or 'twos' (LE)
|
||||
uint16_t compression_name; // = 0
|
||||
|
||||
uint32_t ssnd_chunk; // = BE32('SSND')
|
||||
uint32_t ssnd_size; // = BE32(length of samples - 8)
|
||||
uint32_t ssnd_offset; // = 0
|
||||
uint32_t ssnd_block; // = 0
|
||||
} aiff_header_t;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint32_t marker; // = BE32('RIFF')
|
||||
uint32_t size; // = LE32(file size - 8)
|
||||
uint32_t type; // = BE32('WAVE')
|
||||
|
||||
uint32_t fmt_chunk; // = BE32('fmt ')
|
||||
uint32_t fmt_size; // = LE16(16)
|
||||
uint16_t format; // = LE16(1)
|
||||
uint16_t channels; // = LE16(2)
|
||||
uint32_t sample_rate; // = LE32(sample_rate)
|
||||
uint32_t byte_rate; // = LE32(sample_rate * 4)
|
||||
uint16_t frame_size; // = LE32(4)
|
||||
uint16_t bit_depth; // = LE16(16)
|
||||
|
||||
uint32_t data_chunk; // = BE32('data')
|
||||
uint32_t data_size; // = LE32(length of samples)
|
||||
} wav_header_t;
|
||||
|
||||
|
||||
int GB_start_audio_recording(GB_gameboy_t *gb, const char *path, GB_audio_format_t format)
|
||||
{
|
||||
if (gb->apu_output.sample_rate == 0) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
if (gb->apu_output.output_file) {
|
||||
GB_stop_audio_recording(gb);
|
||||
}
|
||||
gb->apu_output.output_file = fopen(path, "wb");
|
||||
if (!gb->apu_output.output_file) return errno;
|
||||
|
||||
gb->apu_output.output_format = format;
|
||||
switch (format) {
|
||||
case GB_AUDIO_FORMAT_RAW:
|
||||
return 0;
|
||||
case GB_AUDIO_FORMAT_AIFF: {
|
||||
aiff_header_t header = {0,};
|
||||
if (fwrite(&header, sizeof(header), 1, gb->apu_output.output_file) != 1) {
|
||||
fclose(gb->apu_output.output_file);
|
||||
gb->apu_output.output_file = NULL;
|
||||
return errno;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
case GB_AUDIO_FORMAT_WAV: {
|
||||
wav_header_t header = {0,};
|
||||
if (fwrite(&header, sizeof(header), 1, gb->apu_output.output_file) != 1) {
|
||||
fclose(gb->apu_output.output_file);
|
||||
gb->apu_output.output_file = NULL;
|
||||
return errno;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
fclose(gb->apu_output.output_file);
|
||||
gb->apu_output.output_file = NULL;
|
||||
return EINVAL;
|
||||
}
|
||||
}
|
||||
int GB_stop_audio_recording(GB_gameboy_t *gb)
|
||||
{
|
||||
if (!gb->apu_output.output_file) {
|
||||
int ret = gb->apu_output.output_error ?: -1;
|
||||
gb->apu_output.output_error = 0;
|
||||
return ret;
|
||||
}
|
||||
gb->apu_output.output_error = 0;
|
||||
switch (gb->apu_output.output_format) {
|
||||
case GB_AUDIO_FORMAT_RAW:
|
||||
break;
|
||||
case GB_AUDIO_FORMAT_AIFF: {
|
||||
size_t file_size = ftell(gb->apu_output.output_file);
|
||||
size_t frames = (file_size - sizeof(aiff_header_t)) / sizeof(GB_sample_t);
|
||||
aiff_header_t header = {
|
||||
.format_chunk = BE32('FORM'),
|
||||
.size = BE32(file_size - 8),
|
||||
.format = BE32('AIFC'),
|
||||
|
||||
.fver_chunk = BE32('FVER'),
|
||||
.fver_size = BE32(4),
|
||||
.fver = BE32(0xA2805140),
|
||||
|
||||
.comm_chunk = BE32('COMM'),
|
||||
.comm_size = BE32(0x18),
|
||||
.channels = BE16(2),
|
||||
.samples_per_channel = BE32(frames),
|
||||
.bit_depth = BE16(16),
|
||||
#ifdef GB_BIG_ENDIAN
|
||||
.compression_type = 'NONE',
|
||||
#else
|
||||
.compression_type = 'twos',
|
||||
#endif
|
||||
.compression_name = 0,
|
||||
.ssnd_chunk = BE32('SSND'),
|
||||
.ssnd_size = BE32(frames * sizeof(GB_sample_t) - 8),
|
||||
.ssnd_offset = 0,
|
||||
.ssnd_block = 0,
|
||||
};
|
||||
|
||||
uint64_t significand = gb->apu_output.sample_rate;
|
||||
uint16_t exponent = 0x403E;
|
||||
while ((int64_t)significand > 0) {
|
||||
significand <<= 1;
|
||||
exponent--;
|
||||
}
|
||||
header.frequency_exponent = BE16(exponent);
|
||||
header.frequency_significand = BE64(significand);
|
||||
|
||||
fseek(gb->apu_output.output_file, 0, SEEK_SET);
|
||||
if (fwrite(&header, sizeof(header), 1, gb->apu_output.output_file) != 1) {
|
||||
gb->apu_output.output_error = errno;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GB_AUDIO_FORMAT_WAV: {
|
||||
size_t file_size = ftell(gb->apu_output.output_file);
|
||||
size_t frames = (file_size - sizeof(wav_header_t)) / sizeof(GB_sample_t);
|
||||
wav_header_t header = {
|
||||
.marker = BE32('RIFF'),
|
||||
.size = LE32(file_size - 8),
|
||||
.type = BE32('WAVE'),
|
||||
|
||||
.fmt_chunk = BE32('fmt '),
|
||||
.fmt_size = LE16(16),
|
||||
.format = LE16(1),
|
||||
.channels = LE16(2),
|
||||
.sample_rate = LE32(gb->apu_output.sample_rate),
|
||||
.byte_rate = LE32(gb->apu_output.sample_rate * 4),
|
||||
.frame_size = LE32(4),
|
||||
.bit_depth = LE16(16),
|
||||
|
||||
.data_chunk = BE32('data'),
|
||||
.data_size = LE32(frames * sizeof(GB_sample_t)),
|
||||
};
|
||||
|
||||
fseek(gb->apu_output.output_file, 0, SEEK_SET);
|
||||
if (fwrite(&header, sizeof(header), 1, gb->apu_output.output_file) != 1) {
|
||||
gb->apu_output.output_error = errno;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(gb->apu_output.output_file);
|
||||
gb->apu_output.output_file = NULL;
|
||||
|
||||
int ret = gb->apu_output.output_error;
|
||||
gb->apu_output.output_error = 0;
|
||||
return ret;
|
||||
}
|
||||
|
|
17
Core/apu.h
|
@ -3,6 +3,7 @@
|
|||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include "defs.h"
|
||||
|
||||
#ifdef GB_INTERNAL
|
||||
|
@ -88,6 +89,8 @@ typedef struct
|
|||
uint16_t sample_length; // From NRX3, NRX4, in APU ticks
|
||||
bool length_enabled; // NRX4
|
||||
GB_envelope_clock_t envelope_clock;
|
||||
uint8_t delay; // Hack for CGB D/E phantom step due to how sample_countdown is implemented in SameBoy
|
||||
bool did_tick;
|
||||
} square_channels[2];
|
||||
|
||||
struct {
|
||||
|
@ -140,6 +143,12 @@ typedef enum {
|
|||
GB_HIGHPASS_MAX
|
||||
} GB_highpass_mode_t;
|
||||
|
||||
typedef enum {
|
||||
GB_AUDIO_FORMAT_RAW, // Native endian
|
||||
GB_AUDIO_FORMAT_AIFF, // Native endian
|
||||
GB_AUDIO_FORMAT_WAV,
|
||||
} GB_audio_format_t;
|
||||
|
||||
typedef struct {
|
||||
unsigned sample_rate;
|
||||
|
||||
|
@ -160,14 +169,20 @@ typedef struct {
|
|||
|
||||
double interference_volume;
|
||||
double interference_highpass;
|
||||
|
||||
FILE *output_file;
|
||||
GB_audio_format_t output_format;
|
||||
int output_error;
|
||||
} GB_apu_output_t;
|
||||
|
||||
void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate);
|
||||
unsigned GB_get_sample_rate(GB_gameboy_t *gb);
|
||||
void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, double cycles_per_sample); /* Cycles are in 8MHz units */
|
||||
void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode);
|
||||
void GB_set_interference_volume(GB_gameboy_t *gb, double volume);
|
||||
void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback);
|
||||
|
||||
int GB_start_audio_recording(GB_gameboy_t *gb, const char *path, GB_audio_format_t format);
|
||||
int GB_stop_audio_recording(GB_gameboy_t *gb);
|
||||
#ifdef GB_INTERNAL
|
||||
internal bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index);
|
||||
internal void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value);
|
||||
|
|
355
Core/debugger.c
|
@ -155,12 +155,20 @@ static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer
|
|||
return output;
|
||||
}
|
||||
|
||||
static GB_symbol_map_t *get_symbol_map(GB_gameboy_t *gb, uint16_t bank)
|
||||
{
|
||||
if (bank >= gb->n_symbol_maps) {
|
||||
return NULL;
|
||||
}
|
||||
return gb->bank_symbols[bank];
|
||||
}
|
||||
|
||||
static const char *debugger_value_to_string(GB_gameboy_t *gb, value_t value, bool prefer_name)
|
||||
{
|
||||
if (!value.has_bank) return value_to_string(gb, value.value, prefer_name);
|
||||
|
||||
static __thread char output[256];
|
||||
const GB_bank_symbol_t *symbol = GB_map_find_symbol(gb->bank_symbols[value.bank], value.value);
|
||||
const GB_bank_symbol_t *symbol = GB_map_find_symbol(get_symbol_map(gb, value.bank), value.value);
|
||||
|
||||
if (symbol && (value.value - symbol->addr > 0x1000 || symbol->addr == 0) ) {
|
||||
symbol = NULL;
|
||||
|
@ -786,22 +794,6 @@ static bool finish(GB_gameboy_t *gb, char *arguments, char *modifiers, const deb
|
|||
return false;
|
||||
}
|
||||
|
||||
static bool stack_leak_detection(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
|
||||
{
|
||||
NO_MODIFIERS
|
||||
STOPPED_ONLY
|
||||
|
||||
if (strlen(lstrip(arguments))) {
|
||||
print_usage(gb, command);
|
||||
return true;
|
||||
}
|
||||
|
||||
gb->debug_stopped = false;
|
||||
gb->stack_leak_detection = true;
|
||||
gb->debug_call_depth = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
|
||||
{
|
||||
NO_MODIFIERS
|
||||
|
@ -911,13 +903,14 @@ static char *symbol_completer(GB_gameboy_t *gb, const char *string, uintptr_t *_
|
|||
|
||||
size_t length = strlen(symbol_prefix);
|
||||
while (context->bank < 0x200) {
|
||||
if (gb->bank_symbols[context->bank] == NULL ||
|
||||
context->symbol >= gb->bank_symbols[context->bank]->n_symbols) {
|
||||
GB_symbol_map_t *map = get_symbol_map(gb, context->bank);
|
||||
if (map == NULL ||
|
||||
context->symbol >= map->n_symbols) {
|
||||
context->bank++;
|
||||
context->symbol = 0;
|
||||
continue;
|
||||
}
|
||||
const char *candidate = gb->bank_symbols[context->bank]->symbols[context->symbol++].name;
|
||||
const char *candidate = map->symbols[context->symbol++].name;
|
||||
if (memcmp(symbol_prefix, candidate, length) == 0) {
|
||||
return strdup(candidate + length);
|
||||
}
|
||||
|
@ -1537,16 +1530,22 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
|
|||
}
|
||||
else {
|
||||
static const char *const mapper_names[] = {
|
||||
[GB_MBC1] = "MBC1",
|
||||
[GB_MBC2] = "MBC2",
|
||||
[GB_MBC3] = "MBC3",
|
||||
[GB_MBC5] = "MBC5",
|
||||
[GB_MBC7] = "MBC7",
|
||||
[GB_HUC1] = "HUC-1",
|
||||
[GB_HUC3] = "HUC-3",
|
||||
[GB_MBC1] = "MBC1",
|
||||
[GB_MBC2] = "MBC2",
|
||||
[GB_MBC3] = "MBC3",
|
||||
[GB_MBC5] = "MBC5",
|
||||
[GB_MBC7] = "MBC7",
|
||||
[GB_MMM01] = "MMM01",
|
||||
[GB_HUC1] = "HUC-1",
|
||||
[GB_HUC3] = "HUC-3",
|
||||
[GB_CAMERA] = "MAC-GBD",
|
||||
|
||||
};
|
||||
GB_log(gb, "%s\n", mapper_names[cartridge->mbc_type]);
|
||||
}
|
||||
if (cartridge->mbc_type == GB_MMM01 || cartridge->mbc_type == GB_MBC1) {
|
||||
GB_log(gb, "Current mapped ROM0 bank: %x\n", gb->mbc_rom0_bank);
|
||||
}
|
||||
GB_log(gb, "Current mapped ROM bank: %x\n", gb->mbc_rom_bank);
|
||||
if (cartridge->has_ram) {
|
||||
GB_log(gb, "Current mapped RAM bank: %x\n", gb->mbc_ram_bank);
|
||||
|
@ -1650,6 +1649,29 @@ static bool palettes(GB_gameboy_t *gb, char *arguments, char *modifiers, const d
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool dma(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
|
||||
{
|
||||
NO_MODIFIERS
|
||||
if (strlen(lstrip(arguments))) {
|
||||
print_usage(gb, command);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!GB_is_dma_active(gb)) {
|
||||
GB_log(gb, "DMA is inactive\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (gb->dma_current_dest == 0xFF) {
|
||||
GB_log(gb, "DMA warming up\n"); // Shouldn't actually happen, as it only lasts 2 T-cycles
|
||||
return true;
|
||||
}
|
||||
|
||||
GB_log(gb, "Next DMA write: [$FE%02X] = [$%04X]\n", gb->dma_current_dest, gb->dma_current_src);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
|
||||
{
|
||||
NO_MODIFIERS
|
||||
|
@ -1691,8 +1713,13 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
|
|||
GB_log(gb, "Glitched line 0 OAM mode (%d cycles to next event)\n", -gb->display_cycles / 2);
|
||||
}
|
||||
else if (gb->mode_for_interrupt == 3) {
|
||||
signed pixel = gb->position_in_line > 160? (int8_t) gb->position_in_line : gb->position_in_line;
|
||||
GB_log(gb, "Rendering pixel (%d/160)\n", pixel);
|
||||
if (((uint8_t)(gb->position_in_line + 16) < 8)) {
|
||||
GB_log(gb, "Adjusting for scrolling (%d/%d)\n", gb->position_in_line & 7, gb->io_registers[GB_IO_SCX] & 7);
|
||||
}
|
||||
else {
|
||||
signed pixel = gb->position_in_line > 160? (int8_t) gb->position_in_line : gb->position_in_line;
|
||||
GB_log(gb, "Rendering pixel (%d/160)\n", pixel);
|
||||
}
|
||||
}
|
||||
else {
|
||||
GB_log(gb, "Sleeping (%d cycles to next event)\n", -gb->display_cycles / 2);
|
||||
|
@ -1708,54 +1735,60 @@ static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
|
|||
static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
|
||||
{
|
||||
NO_MODIFIERS
|
||||
if (strlen(lstrip(arguments))) {
|
||||
print_usage(gb, command);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
GB_log(gb, "Current state: ");
|
||||
if (!gb->apu.global_enable) {
|
||||
GB_log(gb, "Disabled\n");
|
||||
}
|
||||
else {
|
||||
GB_log(gb, "Enabled\n");
|
||||
for (uint8_t channel = 0; channel < GB_N_CHANNELS; channel++) {
|
||||
GB_log(gb, "CH%u is %s, DAC %s; current sample = 0x%x\n", channel + 1,
|
||||
gb->apu.is_active[channel] ? "active " : "inactive",
|
||||
GB_apu_is_DAC_enabled(gb, channel) ? "active " : "inactive",
|
||||
gb->apu.samples[channel]);
|
||||
const char *stripped = lstrip(arguments);
|
||||
if (strlen(stripped)) {
|
||||
if (stripped[0] != 0 && (stripped[0] < '1' || stripped[0] > '5')) {
|
||||
print_usage(gb, command);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
GB_log(gb, "SO1 (left output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x07);
|
||||
if (gb->io_registers[GB_IO_NR51] & 0x0f) {
|
||||
for (uint8_t channel = 0, mask = 0x01; channel < GB_N_CHANNELS; channel++, mask <<= 1) {
|
||||
if (gb->io_registers[GB_IO_NR51] & mask) {
|
||||
GB_log(gb, " CH%u", channel + 1);
|
||||
if (stripped[0] == 0 || stripped[0] == '5') {
|
||||
GB_log(gb, "Current state: ");
|
||||
if (!gb->apu.global_enable) {
|
||||
GB_log(gb, "Disabled\n");
|
||||
}
|
||||
else {
|
||||
GB_log(gb, "Enabled\n");
|
||||
for (uint8_t channel = 0; channel < GB_N_CHANNELS; channel++) {
|
||||
GB_log(gb, "CH%u is %s, DAC %s; current sample = 0x%x\n", channel + 1,
|
||||
gb->apu.is_active[channel] ? "active " : "inactive",
|
||||
GB_apu_is_DAC_enabled(gb, channel) ? "active " : "inactive",
|
||||
gb->apu.samples[channel]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
GB_log(gb, " no channels");
|
||||
}
|
||||
GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": "");
|
||||
|
||||
GB_log(gb, "SO2 (right output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x70 >> 4);
|
||||
if (gb->io_registers[GB_IO_NR51] & 0xf0) {
|
||||
for (uint8_t channel = 0, mask = 0x10; channel < GB_N_CHANNELS; channel++, mask <<= 1) {
|
||||
if (gb->io_registers[GB_IO_NR51] & mask) {
|
||||
GB_log(gb, " CH%u", channel + 1);
|
||||
GB_log(gb, "SO1 (left output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x07);
|
||||
if (gb->io_registers[GB_IO_NR51] & 0x0F) {
|
||||
for (uint8_t channel = 0, mask = 0x01; channel < GB_N_CHANNELS; channel++, mask <<= 1) {
|
||||
if (gb->io_registers[GB_IO_NR51] & mask) {
|
||||
GB_log(gb, " CH%u", channel + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
GB_log(gb, " no channels");
|
||||
}
|
||||
GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": "");
|
||||
|
||||
GB_log(gb, "SO2 (right output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x70 >> 4);
|
||||
if (gb->io_registers[GB_IO_NR51] & 0xF0) {
|
||||
for (uint8_t channel = 0, mask = 0x10; channel < GB_N_CHANNELS; channel++, mask <<= 1) {
|
||||
if (gb->io_registers[GB_IO_NR51] & mask) {
|
||||
GB_log(gb, " CH%u", channel + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
GB_log(gb, " no channels");
|
||||
}
|
||||
GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": "");
|
||||
}
|
||||
else {
|
||||
GB_log(gb, " no channels");
|
||||
}
|
||||
GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": "");
|
||||
|
||||
|
||||
for (uint8_t channel = GB_SQUARE_1; channel <= GB_SQUARE_2; channel++) {
|
||||
if (stripped[0] != 0 && stripped[0] != ('1') + channel) continue;
|
||||
|
||||
GB_log(gb, "\nCH%u:\n", channel + 1);
|
||||
GB_log(gb, " Current volume: %u, current sample length: %u APU ticks (next in %u ticks)\n",
|
||||
gb->apu.square_channels[channel].current_volume,
|
||||
|
@ -1795,50 +1828,53 @@ static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
|
|||
}
|
||||
}
|
||||
|
||||
if (stripped[0] == 0 || stripped[0] == '3') {
|
||||
GB_log(gb, "\nCH3:\n");
|
||||
GB_log(gb, " Wave:");
|
||||
for (uint8_t i = 0; i < 16; i++) {
|
||||
GB_log(gb, "%s%X", i % 2? "" : " ", gb->io_registers[GB_IO_WAV_START + i] >> 4);
|
||||
GB_log(gb, "%X", gb->io_registers[GB_IO_WAV_START + i] & 0xF);
|
||||
}
|
||||
GB_log(gb, "\n");
|
||||
GB_log(gb, " Current position: %u\n", gb->apu.wave_channel.current_sample_index);
|
||||
|
||||
GB_log(gb, "\nCH3:\n");
|
||||
GB_log(gb, " Wave:");
|
||||
for (uint8_t i = 0; i < 16; i++) {
|
||||
GB_log(gb, "%s%X", i % 2? "" : " ", gb->io_registers[GB_IO_WAV_START + i] >> 4);
|
||||
GB_log(gb, "%X", gb->io_registers[GB_IO_WAV_START + i] & 0xF);
|
||||
}
|
||||
GB_log(gb, "\n");
|
||||
GB_log(gb, " Current position: %u\n", gb->apu.wave_channel.current_sample_index);
|
||||
GB_log(gb, " Volume %s (right-shifted %u times)\n",
|
||||
gb->apu.wave_channel.shift > 4? "" : (const char *[]){"100%", "50%", "25%", "", "muted"}[gb->apu.wave_channel.shift],
|
||||
gb->apu.wave_channel.shift);
|
||||
|
||||
GB_log(gb, " Volume %s (right-shifted %u times)\n",
|
||||
gb->apu.wave_channel.shift > 4? "" : (const char *[]){"100%", "50%", "25%", "", "muted"}[gb->apu.wave_channel.shift],
|
||||
gb->apu.wave_channel.shift);
|
||||
GB_log(gb, " Current sample length: %u APU ticks (next in %u ticks)\n",
|
||||
gb->apu.wave_channel.sample_length ^ 0x7FF,
|
||||
gb->apu.wave_channel.sample_countdown);
|
||||
|
||||
GB_log(gb, " Current sample length: %u APU ticks (next in %u ticks)\n",
|
||||
gb->apu.wave_channel.sample_length ^ 0x7ff,
|
||||
gb->apu.wave_channel.sample_countdown);
|
||||
|
||||
if (gb->apu.wave_channel.length_enabled) {
|
||||
GB_log(gb, " Channel will end in %u 256 Hz ticks\n",
|
||||
gb->apu.wave_channel.pulse_length);
|
||||
if (gb->apu.wave_channel.length_enabled) {
|
||||
GB_log(gb, " Channel will end in %u 256 Hz ticks\n",
|
||||
gb->apu.wave_channel.pulse_length);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
GB_log(gb, "\nCH4:\n");
|
||||
GB_log(gb, " Current volume: %u, current internal counter: 0x%04x (next increase in %u ticks)\n",
|
||||
gb->apu.noise_channel.current_volume,
|
||||
gb->apu.noise_channel.counter,
|
||||
gb->apu.noise_channel.counter_countdown);
|
||||
if (stripped[0] == 0 || stripped[0] == '4') {
|
||||
GB_log(gb, "\nCH4:\n");
|
||||
GB_log(gb, " Current volume: %u, current internal counter: 0x%04x (next increase in %u ticks)\n",
|
||||
gb->apu.noise_channel.current_volume,
|
||||
gb->apu.noise_channel.counter,
|
||||
gb->apu.noise_channel.counter_countdown);
|
||||
|
||||
GB_log(gb, " %u 256 Hz ticks till next volume %screase (out of %u)\n",
|
||||
gb->apu.noise_channel.volume_countdown,
|
||||
gb->io_registers[GB_IO_NR42] & 8 ? "in" : "de",
|
||||
gb->io_registers[GB_IO_NR42] & 7);
|
||||
GB_log(gb, " %u 256 Hz ticks till next volume %screase (out of %u)\n",
|
||||
gb->apu.noise_channel.volume_countdown,
|
||||
gb->io_registers[GB_IO_NR42] & 8 ? "in" : "de",
|
||||
gb->io_registers[GB_IO_NR42] & 7);
|
||||
|
||||
GB_log(gb, " LFSR in %u-step mode, current value ",
|
||||
gb->apu.noise_channel.narrow? 7 : 15);
|
||||
for (uint16_t lfsr = gb->apu.noise_channel.lfsr, i = 15; i--; lfsr <<= 1) {
|
||||
GB_log(gb, "%u%s", (lfsr >> 14) & 1, i%4 ? "" : " ");
|
||||
}
|
||||
GB_log(gb, " LFSR in %u-step mode, current value ",
|
||||
gb->apu.noise_channel.narrow? 7 : 15);
|
||||
for (uint16_t lfsr = gb->apu.noise_channel.lfsr, i = 15; i--; lfsr <<= 1) {
|
||||
GB_log(gb, "%u%s", (lfsr >> 14) & 1, i%4 ? "" : " ");
|
||||
}
|
||||
|
||||
if (gb->apu.noise_channel.length_enabled) {
|
||||
GB_log(gb, " Channel will end in %u 256 Hz ticks\n",
|
||||
gb->apu.noise_channel.pulse_length);
|
||||
if (gb->apu.noise_channel.length_enabled) {
|
||||
GB_log(gb, " Channel will end in %u 256 Hz ticks\n",
|
||||
gb->apu.noise_channel.pulse_length);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1878,9 +1914,9 @@ static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug
|
|||
break;
|
||||
}
|
||||
}
|
||||
mask = (0xf << (shift_amount - 1)) & 0xf;
|
||||
mask = (0xF << (shift_amount - 1)) & 0xF;
|
||||
|
||||
for (int8_t cur_val = 0xf & mask; cur_val >= 0; cur_val -= shift_amount) {
|
||||
for (int8_t cur_val = 0xF & mask; cur_val >= 0; cur_val -= shift_amount) {
|
||||
for (uint8_t i = 0; i < 32; i++) {
|
||||
uint8_t sample = i & 1?
|
||||
(gb->io_registers[GB_IO_WAV_START + i / 2] & 0xF) :
|
||||
|
@ -1901,6 +1937,8 @@ static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug
|
|||
static bool undo(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
|
||||
{
|
||||
NO_MODIFIERS
|
||||
STOPPED_ONLY
|
||||
|
||||
if (strlen(lstrip(arguments))) {
|
||||
print_usage(gb, command);
|
||||
return true;
|
||||
|
@ -1931,46 +1969,45 @@ static const debugger_command_t commands[] = {
|
|||
{"next", 1, next, "Run the next instruction, skipping over function calls"},
|
||||
{"step", 1, step, "Run the next instruction, stepping into function calls"},
|
||||
{"finish", 1, finish, "Run until the current function returns"},
|
||||
{"undo", 1, undo, "Reverts the last command"},
|
||||
{"backtrace", 2, backtrace, "Displays the current call stack"},
|
||||
{"bt", 2, }, /* Alias */
|
||||
{"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected"},
|
||||
{"ticks", 2, ticks, "Displays the number of CPU ticks since the last time 'ticks' was" HELP_NEWLINE
|
||||
"used"},
|
||||
{"undo", 1, undo, "Revert the last command"},
|
||||
{"registers", 1, registers, "Print values of processor registers and other important registers"},
|
||||
{"cartridge", 2, mbc, "Displays information about the MBC and cartridge"},
|
||||
{"mbc", 3, }, /* Alias */
|
||||
{"apu", 3, apu, "Displays information about the current state of the audio chip"},
|
||||
{"wave", 3, wave, "Prints a visual representation of the wave RAM." HELP_NEWLINE
|
||||
"Modifiers can be used for a (f)ull print (the default)," HELP_NEWLINE
|
||||
"a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)", .modifiers_completer = wave_completer},
|
||||
{"lcd", 3, lcd, "Displays information about the current state of the LCD controller"},
|
||||
{"palettes", 3, palettes, "Displays the current CGB palettes"},
|
||||
{"softbreak", 2, softbreak, "Enables or disables software breakpoints", "(on|off)", .argument_completer = on_off_completer},
|
||||
{"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE
|
||||
"Can also modify the condition of existing breakpoints." HELP_NEWLINE
|
||||
"If the j modifier is used, the breakpoint will occur just before" HELP_NEWLINE
|
||||
"jumping to the target.",
|
||||
"<expression>[ if <condition expression>]", "j",
|
||||
.argument_completer = symbol_completer, .modifiers_completer = j_completer},
|
||||
{"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[<expression>]", .argument_completer = symbol_completer},
|
||||
{"watch", 1, watch, "Add a new watchpoint at the specified address/expression." HELP_NEWLINE
|
||||
"Can also modify the condition and type of existing watchpoints." HELP_NEWLINE
|
||||
"Default watchpoint type is write-only.",
|
||||
"<expression>[ if <condition expression>]", "(r|w|rw)",
|
||||
.argument_completer = symbol_completer, .modifiers_completer = rw_completer
|
||||
},
|
||||
{"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[<expression>]", .argument_completer = symbol_completer},
|
||||
{"list", 1, list, "List all set breakpoints and watchpoints"},
|
||||
{"backtrace", 2, backtrace, "Display the current call stack"},
|
||||
{"bt", 2, }, /* Alias */
|
||||
{"print", 1, print, "Evaluate and print an expression" HELP_NEWLINE
|
||||
"Use modifier to format as an address (a, default) or as a number in" HELP_NEWLINE
|
||||
"decimal (d), hexadecimal (x), octal (o) or binary (b).",
|
||||
"<expression>", "format", .argument_completer = symbol_completer, .modifiers_completer = format_completer},
|
||||
"Use modifier to format as an address (a, default) or as a number in" HELP_NEWLINE
|
||||
"decimal (d), hexadecimal (x), octal (o) or binary (b).",
|
||||
"<expression>", "format", .argument_completer = symbol_completer, .modifiers_completer = format_completer},
|
||||
{"eval", 2, }, /* Alias */
|
||||
{"examine", 2, examine, "Examine values at address", "<expression>", "count", .argument_completer = symbol_completer},
|
||||
{"x", 1, }, /* Alias */
|
||||
{"disassemble", 1, disassemble, "Disassemble instructions at address", "<expression>", "count", .argument_completer = symbol_completer},
|
||||
|
||||
{"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE
|
||||
"Can also modify the condition of existing breakpoints." HELP_NEWLINE
|
||||
"If the j modifier is used, the breakpoint will occur just before" HELP_NEWLINE
|
||||
"jumping to the target.",
|
||||
"<expression>[ if <condition expression>]", "j",
|
||||
.argument_completer = symbol_completer, .modifiers_completer = j_completer},
|
||||
{"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[<expression>]", .argument_completer = symbol_completer},
|
||||
{"watch", 1, watch, "Add a new watchpoint at the specified address/expression." HELP_NEWLINE
|
||||
"Can also modify the condition and type of existing watchpoints." HELP_NEWLINE
|
||||
"Default watchpoint type is write-only.",
|
||||
"<expression>[ if <condition expression>]", "(r|w|rw)",
|
||||
.argument_completer = symbol_completer, .modifiers_completer = rw_completer
|
||||
},
|
||||
{"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[<expression>]", .argument_completer = symbol_completer},
|
||||
{"softbreak", 2, softbreak, "Enable or disable software breakpoints", "(on|off)", .argument_completer = on_off_completer},
|
||||
{"list", 1, list, "List all set breakpoints and watchpoints"},
|
||||
{"ticks", 2, ticks, "Display the number of CPU ticks since the last time 'ticks' was" HELP_NEWLINE
|
||||
"used"},
|
||||
{"cartridge", 2, mbc, "Display information about the MBC and cartridge"},
|
||||
{"mbc", 3, }, /* Alias */
|
||||
{"apu", 3, apu, "Display information about the current state of the audio processing unit", "[channel (1-4, 5 for NR5x)]"},
|
||||
{"wave", 3, wave, "Print a visual representation of the wave RAM." HELP_NEWLINE
|
||||
"Modifiers can be used for a (f)ull print (the default)," HELP_NEWLINE
|
||||
"a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)", .modifiers_completer = wave_completer},
|
||||
{"lcd", 3, lcd, "Display information about the current state of the LCD controller"},
|
||||
{"palettes", 3, palettes, "Display the current CGB palettes"},
|
||||
{"dma", 3, dma, "Display the current OAM DMA status"},
|
||||
|
||||
{"help", 1, help, "List available commands or show help for the specified command", "[<command>]"},
|
||||
{NULL,}, /* Null terminator */
|
||||
|
@ -2038,19 +2075,7 @@ void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr)
|
|||
{
|
||||
/* Called just after the CPU calls a function/enters an interrupt/etc... */
|
||||
|
||||
if (gb->stack_leak_detection) {
|
||||
if (gb->debug_call_depth >= sizeof(gb->sp_for_call_depth) / sizeof(gb->sp_for_call_depth[0])) {
|
||||
GB_log(gb, "Potential stack overflow detected (Functions nest too much). \n");
|
||||
gb->debug_stopped = true;
|
||||
}
|
||||
else {
|
||||
gb->sp_for_call_depth[gb->debug_call_depth] = gb->sp;
|
||||
gb->addr_for_call_depth[gb->debug_call_depth] = gb->pc;
|
||||
}
|
||||
}
|
||||
|
||||
if (gb->backtrace_size < sizeof(gb->backtrace_sps) / sizeof(gb->backtrace_sps[0])) {
|
||||
|
||||
while (gb->backtrace_size) {
|
||||
if (gb->backtrace_sps[gb->backtrace_size - 1] < gb->sp) {
|
||||
gb->backtrace_size--;
|
||||
|
@ -2075,21 +2100,6 @@ void GB_debugger_ret_hook(GB_gameboy_t *gb)
|
|||
|
||||
gb->debug_call_depth--;
|
||||
|
||||
if (gb->stack_leak_detection) {
|
||||
if (gb->debug_call_depth < 0) {
|
||||
GB_log(gb, "Function finished without a stack leak.\n");
|
||||
gb->debug_stopped = true;
|
||||
}
|
||||
else {
|
||||
if (gb->sp != gb->sp_for_call_depth[gb->debug_call_depth]) {
|
||||
GB_log(gb, "Stack leak detected for function %s!\n", value_to_string(gb, gb->addr_for_call_depth[gb->debug_call_depth], true));
|
||||
GB_log(gb, "SP is $%04x, should be $%04x.\n", gb->sp,
|
||||
gb->sp_for_call_depth[gb->debug_call_depth]);
|
||||
gb->debug_stopped = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (gb->backtrace_size) {
|
||||
if (gb->backtrace_sps[gb->backtrace_size - 1] <= gb->sp) {
|
||||
gb->backtrace_size--;
|
||||
|
@ -2390,7 +2400,6 @@ next_command:
|
|||
if (gb->debug_stopped && !gb->debug_disable) {
|
||||
gb->debug_next_command = false;
|
||||
gb->debug_fin_command = false;
|
||||
gb->stack_leak_detection = false;
|
||||
input = gb->input_callback(gb);
|
||||
|
||||
if (input == NULL) {
|
||||
|
@ -2419,7 +2428,12 @@ void GB_debugger_handle_async_commands(GB_gameboy_t *gb)
|
|||
|
||||
void GB_debugger_add_symbol(GB_gameboy_t *gb, uint16_t bank, uint16_t address, const char *symbol)
|
||||
{
|
||||
bank &= 0x1FF;
|
||||
if (bank >= gb->n_symbol_maps) {
|
||||
gb->bank_symbols = realloc(gb->bank_symbols, (bank + 1) * sizeof(*gb->bank_symbols));
|
||||
while (bank >= gb->n_symbol_maps) {
|
||||
gb->bank_symbols[gb->n_symbol_maps++] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!gb->bank_symbols[bank]) {
|
||||
gb->bank_symbols[bank] = GB_map_alloc();
|
||||
|
@ -2461,7 +2475,7 @@ void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path)
|
|||
|
||||
void GB_debugger_clear_symbols(GB_gameboy_t *gb)
|
||||
{
|
||||
for (unsigned i = sizeof(gb->bank_symbols) / sizeof(gb->bank_symbols[0]); i--;) {
|
||||
for (unsigned i = gb->n_symbol_maps; i--;) {
|
||||
if (gb->bank_symbols[i]) {
|
||||
GB_map_free(gb->bank_symbols[i]);
|
||||
gb->bank_symbols[i] = 0;
|
||||
|
@ -2474,15 +2488,20 @@ void GB_debugger_clear_symbols(GB_gameboy_t *gb)
|
|||
gb->reversed_symbol_map.buckets[i] = next;
|
||||
}
|
||||
}
|
||||
gb->n_symbol_maps = 0;
|
||||
if (gb->bank_symbols) {
|
||||
free(gb->bank_symbols);
|
||||
gb->bank_symbols = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr)
|
||||
{
|
||||
uint16_t bank = bank_for_addr(gb, addr);
|
||||
|
||||
const GB_bank_symbol_t *symbol = GB_map_find_symbol(gb->bank_symbols[bank], addr);
|
||||
const GB_bank_symbol_t *symbol = GB_map_find_symbol(get_symbol_map(gb, bank), addr);
|
||||
if (symbol) return symbol;
|
||||
if (bank != 0) return GB_map_find_symbol(gb->bank_symbols[0], addr); /* Maybe the symbol incorrectly uses bank 0? */
|
||||
if (bank != 0) return GB_map_find_symbol(get_symbol_map(gb, 0), addr); /* Maybe the symbol incorrectly uses bank 0? */
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
|
490
Core/display.c
|
@ -9,27 +9,31 @@
|
|||
|
||||
static inline unsigned fifo_size(GB_fifo_t *fifo)
|
||||
{
|
||||
return (fifo->write_end - fifo->read_end) & (GB_FIFO_LENGTH - 1);
|
||||
return fifo->size;
|
||||
}
|
||||
|
||||
static void fifo_clear(GB_fifo_t *fifo)
|
||||
{
|
||||
fifo->read_end = fifo->write_end = 0;
|
||||
fifo->read_end = fifo->size = 0;
|
||||
}
|
||||
|
||||
static GB_fifo_item_t *fifo_pop(GB_fifo_t *fifo)
|
||||
{
|
||||
assert(fifo->size);
|
||||
GB_fifo_item_t *ret = &fifo->fifo[fifo->read_end];
|
||||
fifo->read_end++;
|
||||
fifo->read_end &= (GB_FIFO_LENGTH - 1);
|
||||
fifo->size--;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint8_t palette, bool bg_priority, bool flip_x)
|
||||
{
|
||||
assert(fifo->size == 0);
|
||||
fifo->size = 8;
|
||||
if (!flip_x) {
|
||||
unrolled for (unsigned i = 8; i--;) {
|
||||
fifo->fifo[fifo->write_end] = (GB_fifo_item_t) {
|
||||
unrolled for (unsigned i = 0; i < 8; i++) {
|
||||
fifo->fifo[i] = (GB_fifo_item_t) {
|
||||
(lower >> 7) | ((upper >> 7) << 1),
|
||||
palette,
|
||||
0,
|
||||
|
@ -37,14 +41,11 @@ static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint
|
|||
};
|
||||
lower <<= 1;
|
||||
upper <<= 1;
|
||||
|
||||
fifo->write_end++;
|
||||
fifo->write_end &= (GB_FIFO_LENGTH - 1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
unrolled for (unsigned i = 8; i--;) {
|
||||
fifo->fifo[fifo->write_end] = (GB_fifo_item_t) {
|
||||
unrolled for (unsigned i = 0; i < 8; i++) {
|
||||
fifo->fifo[i] = (GB_fifo_item_t) {
|
||||
(lower & 1) | ((upper & 1) << 1),
|
||||
palette,
|
||||
0,
|
||||
|
@ -52,19 +53,15 @@ static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint
|
|||
};
|
||||
lower >>= 1;
|
||||
upper >>= 1;
|
||||
|
||||
fifo->write_end++;
|
||||
fifo->write_end &= (GB_FIFO_LENGTH - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void fifo_overlay_object_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint8_t palette, bool bg_priority, uint8_t priority, bool flip_x)
|
||||
{
|
||||
while (fifo_size(fifo) < 8) {
|
||||
fifo->fifo[fifo->write_end] = (GB_fifo_item_t) {0,};
|
||||
fifo->write_end++;
|
||||
fifo->write_end &= (GB_FIFO_LENGTH - 1);
|
||||
while (fifo->size < GB_FIFO_LENGTH) {
|
||||
fifo->fifo[(fifo->read_end + fifo->size) & (GB_FIFO_LENGTH - 1)] = (GB_fifo_item_t) {0,};
|
||||
fifo->size++;
|
||||
}
|
||||
|
||||
uint8_t flip_xor = flip_x? 0: 0x7;
|
||||
|
@ -109,7 +106,7 @@ typedef struct __attribute__((packed)) {
|
|||
uint8_t flags;
|
||||
} object_t;
|
||||
|
||||
void GB_display_vblank(GB_gameboy_t *gb)
|
||||
void GB_display_vblank(GB_gameboy_t *gb, GB_vblank_type_t type)
|
||||
{
|
||||
gb->vblank_just_occured = true;
|
||||
gb->cycles_since_vblank_callback = 0;
|
||||
|
@ -159,18 +156,18 @@ void GB_display_vblank(GB_gameboy_t *gb)
|
|||
GB_borrow_sgb_border(gb);
|
||||
uint32_t border_colors[16 * 4];
|
||||
|
||||
if (!gb->has_sgb_border && GB_is_cgb(gb) && gb->model != GB_MODEL_AGB) {
|
||||
if (!gb->has_sgb_border && GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_E) {
|
||||
uint16_t colors[] = {
|
||||
0x2095, 0x5129, 0x1EAF, 0x1EBA, 0x4648,
|
||||
0x30DA, 0x69AD, 0x2B57, 0x2B5D, 0x632C,
|
||||
0x1050, 0x3C84, 0x0E07, 0x0E18, 0x2964,
|
||||
};
|
||||
unsigned index = gb->rom? gb->rom[0x14e] % 5 : 0;
|
||||
unsigned index = gb->rom? gb->rom[0x14E] % 5 : 0;
|
||||
if (gb->model == GB_MODEL_CGB_0) {
|
||||
index = 1; // CGB 0 was only available in Indigo!
|
||||
index = 1; // CGB 0 was only available in indigo!
|
||||
}
|
||||
else if (gb->model == GB_MODEL_CGB_A) {
|
||||
index = 0; // CGB 0 was only available in Indigo!
|
||||
index = 0; // CGB A was only available in red!
|
||||
}
|
||||
gb->borrowed_border.palette[0] = LE16(colors[index]);
|
||||
gb->borrowed_border.palette[10] = LE16(colors[5 + index]);
|
||||
|
@ -214,7 +211,7 @@ void GB_display_vblank(GB_gameboy_t *gb)
|
|||
GB_handle_rumble(gb);
|
||||
|
||||
if (gb->vblank_callback) {
|
||||
gb->vblank_callback(gb);
|
||||
gb->vblank_callback(gb, type);
|
||||
}
|
||||
GB_timing_sync(gb);
|
||||
}
|
||||
|
@ -277,7 +274,7 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border)
|
|||
b = scale_channel_with_curve_sgb(b);
|
||||
}
|
||||
else {
|
||||
bool agb = gb->model == GB_MODEL_AGB;
|
||||
bool agb = gb->model > GB_MODEL_CGB_E;
|
||||
r = agb? scale_channel_with_curve_agb(r) : scale_channel_with_curve(r);
|
||||
g = agb? scale_channel_with_curve_agb(g) : scale_channel_with_curve(g);
|
||||
b = agb? scale_channel_with_curve_agb(b) : scale_channel_with_curve(b);
|
||||
|
@ -331,10 +328,10 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border)
|
|||
uint8_t old_min = MIN(r, MIN(g, b));
|
||||
uint8_t new_min = MIN(new_r, MIN(new_g, new_b));
|
||||
|
||||
if (new_min != 0xff) {
|
||||
new_r = 0xff - (0xff - new_r) * (0xff - old_min) / (0xff - new_min);
|
||||
new_g = 0xff - (0xff - new_g) * (0xff - old_min) / (0xff - new_min);
|
||||
new_b = 0xff - (0xff - new_b) * (0xff - old_min) / (0xff - new_min);
|
||||
if (new_min != 0xFF) {
|
||||
new_r = 0xFF - (0xFF - new_r) * (0xFF - old_min) / (0xFF - new_min);
|
||||
new_g = 0xFF - (0xFF - new_g) * (0xFF - old_min) / (0xFF - new_min);
|
||||
new_b = 0xFF - (0xFF - new_b) * (0xFF - old_min) / (0xFF - new_min);
|
||||
}
|
||||
}
|
||||
r = new_r;
|
||||
|
@ -397,6 +394,9 @@ void GB_set_light_temperature(GB_gameboy_t *gb, double temperature)
|
|||
void GB_STAT_update(GB_gameboy_t *gb)
|
||||
{
|
||||
if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) return;
|
||||
if (GB_is_dma_active(gb) && (gb->io_registers[GB_IO_STAT] & 3) == 2) {
|
||||
gb->io_registers[GB_IO_STAT] &= ~3;
|
||||
}
|
||||
|
||||
bool previous_interrupt_line = gb->stat_interrupt_line;
|
||||
/* Set LY=LYC bit */
|
||||
|
@ -433,20 +433,19 @@ void GB_STAT_update(GB_gameboy_t *gb)
|
|||
|
||||
void GB_lcd_off(GB_gameboy_t *gb)
|
||||
{
|
||||
gb->cycles_for_line = 0;
|
||||
gb->display_state = 0;
|
||||
gb->display_cycles = 0;
|
||||
/* When the LCD is disabled, state is constant */
|
||||
|
||||
if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3)) {
|
||||
gb->hdma_on = true;
|
||||
}
|
||||
|
||||
/* When the LCD is off, LY is 0 and STAT mode is 0. */
|
||||
gb->io_registers[GB_IO_LY] = 0;
|
||||
gb->io_registers[GB_IO_STAT] &= ~3;
|
||||
if (gb->hdma_on_hblank) {
|
||||
gb->hdma_on_hblank = false;
|
||||
gb->hdma_on = false;
|
||||
|
||||
/* Todo: is this correct? */
|
||||
gb->hdma_steps_left = 0xff;
|
||||
}
|
||||
|
||||
|
||||
gb->oam_read_blocked = false;
|
||||
gb->vram_read_blocked = false;
|
||||
|
@ -465,32 +464,60 @@ void GB_lcd_off(GB_gameboy_t *gb)
|
|||
}
|
||||
}
|
||||
|
||||
static inline uint8_t oam_read(GB_gameboy_t *gb, uint8_t addr)
|
||||
{
|
||||
if (unlikely(gb->oam_ppu_blocked)) {
|
||||
return 0xFF;
|
||||
}
|
||||
if (unlikely(gb->dma_current_dest <= 0xA0 && gb->dma_current_dest > 0)) { // TODO: what happens in the last and first M cycles?
|
||||
if (gb->hdma_in_progress) {
|
||||
return GB_read_oam(gb, (gb->hdma_current_src & ~1) | (addr & 1));
|
||||
}
|
||||
if (gb->dma_current_dest != 0xA0) {
|
||||
return gb->oam[(gb->dma_current_dest & ~1) | (addr & 1)];
|
||||
}
|
||||
}
|
||||
return gb->oam[addr];
|
||||
}
|
||||
|
||||
static void add_object_from_index(GB_gameboy_t *gb, unsigned index)
|
||||
{
|
||||
if (likely(!GB_is_dma_active(gb) || gb->halted || gb->stopped)) {
|
||||
gb->mode2_y_bus = oam_read(gb, index * 4);
|
||||
gb->mode2_x_bus = oam_read(gb, index * 4 + 1);
|
||||
}
|
||||
|
||||
if (gb->n_visible_objs == 10) return;
|
||||
|
||||
/* TODO: It appears that DMA blocks PPU access to OAM, but it needs verification. */
|
||||
if (gb->dma_steps_left && (gb->dma_cycles >= 0 || gb->is_dma_restarting)) {
|
||||
if (unlikely(GB_is_dma_active(gb) && (gb->halted || gb->stopped))) {
|
||||
if (gb->model < GB_MODEL_CGB_E) {
|
||||
return;
|
||||
}
|
||||
/* CGB-0 to CGB-D: Halted DMA blocks Mode 2;
|
||||
Pre-CGB: Unit specific behavior, some units read FFs, some units read using
|
||||
several different corruption pattterns. For simplicity, we emulate
|
||||
FFs. */
|
||||
}
|
||||
|
||||
if (unlikely(gb->oam_ppu_blocked)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gb->oam_ppu_blocked) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* This reverse sorts the visible objects by location and priority */
|
||||
object_t *objects = (object_t *) &gb->oam;
|
||||
bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0;
|
||||
signed y = objects[index].y - 16;
|
||||
signed y = gb->mode2_y_bus - 16;
|
||||
/* This reverse sorts the visible objects by location and priority */
|
||||
if (y <= gb->current_line && y + (height_16? 16 : 8) > gb->current_line) {
|
||||
unsigned j = 0;
|
||||
for (; j < gb->n_visible_objs; j++) {
|
||||
if (gb->obj_comparators[j] <= objects[index].x) break;
|
||||
if (gb->objects_x[j] <= gb->mode2_x_bus) break;
|
||||
}
|
||||
memmove(gb->visible_objs + j + 1, gb->visible_objs + j, gb->n_visible_objs - j);
|
||||
memmove(gb->obj_comparators + j + 1, gb->obj_comparators + j, gb->n_visible_objs - j);
|
||||
memmove(gb->objects_x + j + 1, gb->objects_x + j, gb->n_visible_objs - j);
|
||||
memmove(gb->objects_y + j + 1, gb->objects_y + j, gb->n_visible_objs - j);
|
||||
gb->visible_objs[j] = index;
|
||||
gb->obj_comparators[j] = objects[index].x;
|
||||
gb->objects_x[j] = gb->mode2_x_bus;
|
||||
gb->objects_y[j] = gb->mode2_y_bus;
|
||||
gb->n_visible_objs++;
|
||||
}
|
||||
}
|
||||
|
@ -529,22 +556,37 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
|
|||
const GB_fifo_item_t *oam_fifo_item = NULL;
|
||||
bool draw_oam = false;
|
||||
bool bg_enabled = true, bg_priority = false;
|
||||
|
||||
// Rendering (including scrolling adjustment) does not occur as long as an object at x=0 is pending
|
||||
if (gb->n_visible_objs != 0 &&
|
||||
(gb->io_registers[GB_IO_LCDC] & 2 || GB_is_cgb(gb)) &&
|
||||
gb->objects_x[gb->n_visible_objs - 1] == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fifo_size(&gb->bg_fifo)) {
|
||||
fifo_item = fifo_pop(&gb->bg_fifo);
|
||||
bg_priority = fifo_item->bg_priority;
|
||||
|
||||
if (fifo_size(&gb->oam_fifo)) {
|
||||
oam_fifo_item = fifo_pop(&gb->oam_fifo);
|
||||
if (oam_fifo_item->pixel && (gb->io_registers[GB_IO_LCDC] & 2) && unlikely(!gb->objects_disabled)) {
|
||||
draw_oam = true;
|
||||
bg_priority |= oam_fifo_item->bg_priority;
|
||||
}
|
||||
if (unlikely(gb->wx_triggered && !fifo_size(&gb->bg_fifo))) return;
|
||||
|
||||
fifo_item = fifo_pop(&gb->bg_fifo);
|
||||
bg_priority = fifo_item->bg_priority;
|
||||
|
||||
if (fifo_size(&gb->oam_fifo)) {
|
||||
oam_fifo_item = fifo_pop(&gb->oam_fifo);
|
||||
if (oam_fifo_item->pixel && (gb->io_registers[GB_IO_LCDC] & 2) && unlikely(!gb->objects_disabled)) {
|
||||
draw_oam = true;
|
||||
bg_priority |= oam_fifo_item->bg_priority;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!fifo_item) return;
|
||||
// (gb->position_in_line + 16 < 8) is (gb->position_in_line < -8) in unsigned logic
|
||||
if (((uint8_t)(gb->position_in_line + 16) < 8)) {
|
||||
if ((gb->position_in_line & 7) == (gb->io_registers[GB_IO_SCX] & 7)) {
|
||||
gb->position_in_line = -8;
|
||||
}
|
||||
else if (gb->position_in_line == (uint8_t) -9) {
|
||||
gb->position_in_line = -16;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Drop pixels for scrollings */
|
||||
if (gb->position_in_line >= 160 || (gb->disable_rendering && !gb->sgb)) {
|
||||
|
@ -641,6 +683,23 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
|
|||
gb->window_is_being_fetched = false;
|
||||
}
|
||||
|
||||
static inline void dma_sync(GB_gameboy_t *gb, unsigned *cycles)
|
||||
{
|
||||
if (unlikely(GB_is_dma_active(gb))) {
|
||||
unsigned offset = *cycles - gb->display_cycles; // Time passed in 8MHz ticks
|
||||
if (offset) {
|
||||
*cycles = gb->display_cycles;
|
||||
if (!gb->cgb_double_speed) {
|
||||
offset >>= 1; // Convert to T-cycles
|
||||
}
|
||||
unsigned old = gb->dma_cycles;
|
||||
gb->dma_cycles = offset;
|
||||
GB_dma_run(gb);
|
||||
gb->dma_cycles = old - offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* All verified CGB timings are based on CGB CPU E. CGB CPUs >= D are known to have
|
||||
slightly different timings than CPUs <= C.
|
||||
|
||||
|
@ -651,7 +710,45 @@ static inline uint8_t fetcher_y(GB_gameboy_t *gb)
|
|||
return gb->wx_triggered? gb->window_y : gb->current_line + gb->io_registers[GB_IO_SCY];
|
||||
}
|
||||
|
||||
static void advance_fetcher_state_machine(GB_gameboy_t *gb)
|
||||
static inline uint8_t vram_read(GB_gameboy_t *gb, uint16_t addr)
|
||||
{
|
||||
if (unlikely(gb->vram_ppu_blocked)) {
|
||||
return 0xFF;
|
||||
}
|
||||
if (unlikely(gb->hdma_in_progress)) {
|
||||
gb->addr_for_hdma_conflict = addr;
|
||||
return 0;
|
||||
}
|
||||
// TODO: what if both?
|
||||
else if (unlikely(gb->dma_current_dest <= 0xA0 && gb->dma_current_dest > 0 && (gb->dma_current_src & 0xE000) == 0x8000)) { // TODO: what happens in the last and first M cycles?
|
||||
// DMAing from VRAM!
|
||||
/* TODO: AGS has its own, very different pattern, but AGS is not currently a supported model */
|
||||
/* TODO: Research this when researching odd modes */
|
||||
/* TODO: probably not 100% on the first few reads during halt/stop modes*/
|
||||
unsigned offset = 1 - (gb->halted || gb->stopped);
|
||||
if (GB_is_cgb(gb)) {
|
||||
if (gb->dma_ppu_vram_conflict) {
|
||||
addr = (gb->dma_ppu_vram_conflict_addr & 0x1FFF) | (addr & 0x2000);
|
||||
}
|
||||
else if (gb->dma_cycles_modulo && !gb->halted && !gb->stopped) {
|
||||
addr &= 0x2000;
|
||||
addr |= ((gb->dma_current_src - offset) & 0x1FFF);
|
||||
}
|
||||
else {
|
||||
addr &= 0x2000 | ((gb->dma_current_src - offset) & 0x1FFF);
|
||||
gb->dma_ppu_vram_conflict_addr = addr;
|
||||
gb->dma_ppu_vram_conflict = !gb->halted && !gb->stopped;
|
||||
}
|
||||
}
|
||||
else {
|
||||
addr |= ((gb->dma_current_src - offset) & 0x1FFF);
|
||||
}
|
||||
gb->oam[gb->dma_current_dest - offset] = gb->vram[(addr & 0x1FFF) | (gb->cgb_vram_bank? 0x2000 : 0)];
|
||||
}
|
||||
return gb->vram[addr];
|
||||
}
|
||||
|
||||
static void advance_fetcher_state_machine(GB_gameboy_t *gb, unsigned *cycles)
|
||||
{
|
||||
typedef enum {
|
||||
GB_FETCHER_GET_TILE,
|
||||
|
@ -673,6 +770,7 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
|
|||
};
|
||||
switch (fetcher_state_machine[gb->fetcher_state & 7]) {
|
||||
case GB_FETCHER_GET_TILE: {
|
||||
dma_sync(gb, cycles);
|
||||
uint16_t map = 0x1800;
|
||||
|
||||
if (!(gb->io_registers[GB_IO_LCDC] & 0x20)) {
|
||||
|
@ -694,6 +792,9 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
|
|||
if (gb->wx_triggered) {
|
||||
x = gb->window_tile_x;
|
||||
}
|
||||
else if ((uint8_t)(gb->position_in_line + 16) < 8) {
|
||||
x = gb->io_registers[GB_IO_SCX] >> 3;
|
||||
}
|
||||
else {
|
||||
/* TODO: There is some CGB timing error around here.
|
||||
Adjusting SCX by 7 or less shouldn't have an effect on a CGB,
|
||||
|
@ -705,23 +806,18 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
|
|||
gb->fetcher_y = y;
|
||||
}
|
||||
gb->last_tile_index_address = map + x + y / 8 * 32;
|
||||
gb->current_tile = gb->vram[gb->last_tile_index_address];
|
||||
if (gb->vram_ppu_blocked) {
|
||||
gb->current_tile = 0xFF;
|
||||
}
|
||||
gb->current_tile = vram_read(gb, gb->last_tile_index_address);
|
||||
if (GB_is_cgb(gb)) {
|
||||
/* The CGB actually accesses both the tile index AND the attributes in the same T-cycle.
|
||||
This probably means the CGB has a 16-bit data bus for the VRAM. */
|
||||
gb->current_tile_attributes = gb->vram[gb->last_tile_index_address + 0x2000];
|
||||
if (gb->vram_ppu_blocked) {
|
||||
gb->current_tile_attributes = 0xFF;
|
||||
}
|
||||
gb->current_tile_attributes = vram_read(gb, gb->last_tile_index_address + 0x2000);
|
||||
}
|
||||
}
|
||||
gb->fetcher_state++;
|
||||
break;
|
||||
|
||||
case GB_FETCHER_GET_TILE_DATA_LOWER: {
|
||||
dma_sync(gb, cycles);
|
||||
bool use_glitched = false;
|
||||
bool cgb_d_glitch = false;
|
||||
if (gb->tile_sel_glitch) {
|
||||
|
@ -746,29 +842,22 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
|
|||
}
|
||||
if (!use_glitched) {
|
||||
gb->current_tile_data[0] =
|
||||
gb->vram[tile_address + ((y & 7) ^ y_flip) * 2];
|
||||
if (gb->vram_ppu_blocked) {
|
||||
gb->current_tile_data[0] = 0xFF;
|
||||
}
|
||||
vram_read(gb, tile_address + ((y & 7) ^ y_flip) * 2);
|
||||
|
||||
}
|
||||
if ((gb->io_registers[GB_IO_LCDC] & 0x10) && gb->tile_sel_glitch) {
|
||||
gb->data_for_sel_glitch =
|
||||
gb->vram[tile_address + ((y & 7) ^ y_flip) * 2];
|
||||
if (gb->vram_ppu_blocked) {
|
||||
gb->data_for_sel_glitch = 0xFF;
|
||||
}
|
||||
vram_read(gb, tile_address + ((y & 7) ^ y_flip) * 2);
|
||||
}
|
||||
else if (cgb_d_glitch) {
|
||||
gb->data_for_sel_glitch = gb->vram[gb->current_tile * 0x10 + ((y & 7) ^ y_flip) * 2];
|
||||
if (gb->vram_ppu_blocked) {
|
||||
gb->data_for_sel_glitch = 0xFF;
|
||||
}
|
||||
gb->data_for_sel_glitch = vram_read(gb, gb->current_tile * 0x10 + ((y & 7) ^ y_flip) * 2);
|
||||
}
|
||||
}
|
||||
gb->fetcher_state++;
|
||||
break;
|
||||
|
||||
case GB_FETCHER_GET_TILE_DATA_HIGH: {
|
||||
dma_sync(gb, cycles);
|
||||
/* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */
|
||||
|
||||
bool use_glitched = false;
|
||||
|
@ -796,27 +885,20 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
|
|||
gb->last_tile_data_address = tile_address + ((y & 7) ^ y_flip) * 2 + 1 - cgb_d_glitch;
|
||||
if (!use_glitched) {
|
||||
gb->current_tile_data[1] =
|
||||
gb->vram[gb->last_tile_data_address];
|
||||
if (gb->vram_ppu_blocked) {
|
||||
gb->current_tile_data[1] = 0xFF;
|
||||
}
|
||||
vram_read(gb, gb->last_tile_data_address);
|
||||
}
|
||||
if ((gb->io_registers[GB_IO_LCDC] & 0x10) && gb->tile_sel_glitch) {
|
||||
gb->data_for_sel_glitch = gb->vram[gb->last_tile_data_address];
|
||||
if (gb->vram_ppu_blocked) {
|
||||
gb->data_for_sel_glitch = 0xFF;
|
||||
}
|
||||
gb->data_for_sel_glitch = vram_read(gb, gb->last_tile_data_address);
|
||||
|
||||
}
|
||||
else if (cgb_d_glitch) {
|
||||
gb->data_for_sel_glitch = gb->vram[gb->current_tile * 0x10 + ((y & 7) ^ y_flip) * 2 + 1];
|
||||
if (gb->vram_ppu_blocked) {
|
||||
gb->data_for_sel_glitch = 0xFF;
|
||||
}
|
||||
gb->data_for_sel_glitch = vram_read(gb, gb->current_tile * 0x10 + ((y & 7) ^ y_flip) * 2 + 1);
|
||||
|
||||
}
|
||||
}
|
||||
if (gb->wx_triggered) {
|
||||
gb->window_tile_x++;
|
||||
gb->window_tile_x &= 0x1f;
|
||||
gb->window_tile_x &= 0x1F;
|
||||
}
|
||||
|
||||
// fallthrough
|
||||
|
@ -842,25 +924,19 @@ static void advance_fetcher_state_machine(GB_gameboy_t *gb)
|
|||
}
|
||||
}
|
||||
|
||||
static uint16_t get_object_line_address(GB_gameboy_t *gb, const object_t *object)
|
||||
static uint16_t get_object_line_address(GB_gameboy_t *gb, uint8_t y, uint8_t tile, uint8_t flags)
|
||||
{
|
||||
/* TODO: what does the PPU read if DMA is active? */
|
||||
if (gb->oam_ppu_blocked) {
|
||||
static const object_t blocked = {0xFF, 0xFF, 0xFF, 0xFF};
|
||||
object = &blocked;
|
||||
}
|
||||
|
||||
bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0; /* Todo: Which T-cycle actually reads this? */
|
||||
uint8_t tile_y = (gb->current_line - object->y) & (height_16? 0xF : 7);
|
||||
uint8_t tile_y = (gb->current_line - y) & (height_16? 0xF : 7);
|
||||
|
||||
if (object->flags & 0x40) { /* Flip Y */
|
||||
if (flags & 0x40) { /* Flip Y */
|
||||
tile_y ^= height_16? 0xF : 7;
|
||||
}
|
||||
|
||||
/* Todo: I'm not 100% sure an access to OAM can't trigger the OAM bug while we're accessing this */
|
||||
uint16_t line_address = (height_16? object->tile & 0xFE : object->tile) * 0x10 + tile_y * 2;
|
||||
uint16_t line_address = (height_16? tile & 0xFE : tile) * 0x10 + tile_y * 2;
|
||||
|
||||
if (gb->cgb_mode && (object->flags & 0x8)) { /* Use VRAM bank 2 */
|
||||
if (gb->cgb_mode && (flags & 0x8)) { /* Use VRAM bank 2 */
|
||||
line_address += 0x2000;
|
||||
}
|
||||
return line_address;
|
||||
|
@ -932,7 +1008,7 @@ static void render_line(GB_gameboy_t *gb)
|
|||
const object_t *object = &objects[object_index];
|
||||
gb->n_visible_objs--;
|
||||
|
||||
uint16_t line_address = get_object_line_address(gb, object);
|
||||
uint16_t line_address = get_object_line_address(gb, object->y, object->tile, object->flags);
|
||||
uint8_t data0 = gb->vram[line_address];
|
||||
uint8_t data1 = gb->vram[line_address + 1];
|
||||
if (gb->n_visible_objs == 0) {
|
||||
|
@ -1096,7 +1172,7 @@ static void render_line_sgb(GB_gameboy_t *gb)
|
|||
const object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]];
|
||||
gb->n_visible_objs--;
|
||||
|
||||
uint16_t line_address = get_object_line_address(gb, object);
|
||||
uint16_t line_address = get_object_line_address(gb, object->y, object->tile, object->flags);
|
||||
uint8_t data0 = gb->vram[line_address];
|
||||
uint8_t data1 = gb->vram[line_address + 1];
|
||||
if (object->flags & 0x20) {
|
||||
|
@ -1213,9 +1289,11 @@ object_buffer_pointer++\
|
|||
|
||||
static inline uint16_t mode3_batching_length(GB_gameboy_t *gb)
|
||||
{
|
||||
if (gb->position_in_line != (uint8_t)-16) return 0;
|
||||
if (gb->model & GB_MODEL_NO_SFC_BIT) return 0;
|
||||
if (gb->hdma_on) return 0;
|
||||
if (gb->dma_steps_left) return 0;
|
||||
if (gb->stopped) return 0;
|
||||
if (GB_is_dma_active(gb)) return 0;
|
||||
if (gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20) && (gb->io_registers[GB_IO_WX] < 8 || gb->io_registers[GB_IO_WX] == 166)) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -1236,23 +1314,47 @@ static inline uint16_t mode3_batching_length(GB_gameboy_t *gb)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static inline uint8_t x_for_object_match(GB_gameboy_t *gb)
|
||||
{
|
||||
uint8_t ret = gb->position_in_line + 8;
|
||||
if (ret > (uint8_t)-16) return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
TODO: It seems that the STAT register's mode bits are always "late" by 4 T-cycles.
|
||||
The PPU logic can be greatly simplified if that delay is simply emulated.
|
||||
*/
|
||||
void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
|
||||
{
|
||||
if (unlikely((gb->io_registers[GB_IO_LCDC] & 0x80) && (signed)(gb->cycles_for_line * 2 + cycles + gb->display_cycles) > LINE_LENGTH * 2)) {
|
||||
unsigned first_batch = (LINE_LENGTH * 2 - gb->cycles_for_line * 2 + gb->display_cycles);
|
||||
GB_display_run(gb, first_batch, force);
|
||||
cycles -= first_batch;
|
||||
if (gb->display_state == 22) {
|
||||
gb->io_registers[GB_IO_STAT] &= ~3;
|
||||
gb->mode_for_interrupt = 0;
|
||||
GB_STAT_update(gb);
|
||||
}
|
||||
gb->display_state = 9;
|
||||
gb->display_cycles = 0;
|
||||
}
|
||||
if (unlikely(gb->delayed_glitch_hblank_interrupt && cycles && gb->current_line < LINES)) {
|
||||
gb->delayed_glitch_hblank_interrupt = false;
|
||||
gb->mode_for_interrupt = 0;
|
||||
GB_STAT_update(gb);
|
||||
gb->mode_for_interrupt = 3;
|
||||
}
|
||||
gb->cycles_since_vblank_callback += cycles / 2;
|
||||
|
||||
/* The PPU does not advance while in STOP mode on the DMG */
|
||||
if (gb->stopped && !GB_is_cgb(gb)) {
|
||||
if (gb->cycles_since_vblank_callback >= LCDC_PERIOD) {
|
||||
GB_display_vblank(gb);
|
||||
GB_display_vblank(gb, GB_VBLANK_TYPE_ARTIFICIAL);
|
||||
}
|
||||
return;
|
||||
}
|
||||
object_t *objects = (object_t *) &gb->oam;
|
||||
|
||||
|
||||
GB_BATCHABLE_STATE_MACHINE(gb, display, cycles, 2, !force) {
|
||||
GB_STATE(gb, display, 1);
|
||||
GB_STATE(gb, display, 2);
|
||||
|
@ -1262,7 +1364,7 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
|
|||
GB_STATE(gb, display, 6);
|
||||
GB_STATE(gb, display, 7);
|
||||
GB_STATE(gb, display, 8);
|
||||
// GB_STATE(gb, display, 9);
|
||||
GB_STATE(gb, display, 9);
|
||||
GB_STATE(gb, display, 10);
|
||||
GB_STATE(gb, display, 11);
|
||||
GB_STATE(gb, display, 12);
|
||||
|
@ -1277,7 +1379,6 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
|
|||
GB_STATE(gb, display, 22);
|
||||
GB_STATE(gb, display, 23);
|
||||
GB_STATE(gb, display, 24);
|
||||
GB_STATE(gb, display, 25);
|
||||
GB_STATE(gb, display, 26);
|
||||
GB_STATE(gb, display, 27);
|
||||
GB_STATE(gb, display, 28);
|
||||
|
@ -1295,6 +1396,7 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
|
|||
GB_STATE(gb, display, 40);
|
||||
GB_STATE(gb, display, 41);
|
||||
GB_STATE(gb, display, 42);
|
||||
GB_STATE(gb, display, 43);
|
||||
}
|
||||
|
||||
if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) {
|
||||
|
@ -1302,7 +1404,7 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
|
|||
if (gb->cycles_since_vblank_callback < LCDC_PERIOD) {
|
||||
GB_SLEEP(gb, display, 1, LCDC_PERIOD - gb->cycles_since_vblank_callback);
|
||||
}
|
||||
GB_display_vblank(gb);
|
||||
GB_display_vblank(gb, GB_VBLANK_TYPE_LCD_OFF);
|
||||
gb->cgb_repeated_a_frame = true;
|
||||
}
|
||||
return;
|
||||
|
@ -1318,6 +1420,7 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
|
|||
gb->current_line = 0;
|
||||
gb->window_y = -1;
|
||||
gb->wy_triggered = false;
|
||||
gb->position_in_line = -16;
|
||||
|
||||
gb->ly_for_comparison = 0;
|
||||
gb->io_registers[GB_IO_STAT] &= ~3;
|
||||
|
@ -1337,6 +1440,7 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
|
|||
GB_SLEEP(gb, display, 34, 2);
|
||||
|
||||
gb->n_visible_objs = 0;
|
||||
gb->orig_n_visible_objs = 0;
|
||||
gb->cycles_for_line += 8; // Mode 0 is shorter on the first line 0, so we augment cycles_for_line by 8 extra cycles.
|
||||
|
||||
gb->io_registers[GB_IO_STAT] &= ~3;
|
||||
|
@ -1363,7 +1467,47 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
|
|||
gb->wx_triggered = false;
|
||||
gb->wx166_glitch = false;
|
||||
goto mode_3_start;
|
||||
|
||||
|
||||
// Mode 3 abort, state 9
|
||||
display9: {
|
||||
// TODO: Timing of things in this scenario is almost completely untested
|
||||
if (gb->current_line < LINES && !GB_is_sgb(gb) && !gb->disable_rendering) {
|
||||
GB_log(gb, "The ROM is preventing line %d from fully rendering, this could damage a real device's LCD display.\n", gb->current_line);
|
||||
uint32_t *dest = NULL;
|
||||
if (gb->border_mode != GB_BORDER_ALWAYS) {
|
||||
dest = gb->screen + gb->lcd_x + gb->current_line * WIDTH;
|
||||
}
|
||||
else {
|
||||
dest = gb->screen + gb->lcd_x + gb->current_line * BORDERED_WIDTH + (BORDERED_WIDTH - WIDTH) / 2 + (BORDERED_HEIGHT - LINES) / 2 * BORDERED_WIDTH;
|
||||
}
|
||||
uint32_t color = GB_is_cgb(gb)? GB_convert_rgb15(gb, 0x7FFF, false) : gb->background_palettes_rgb[4];
|
||||
while (gb->lcd_x < 160) {
|
||||
*(dest++) = color;
|
||||
gb->lcd_x++;
|
||||
}
|
||||
}
|
||||
gb->n_visible_objs = gb->orig_n_visible_objs;
|
||||
gb->current_line++;
|
||||
gb->cycles_for_line = 0;
|
||||
if (gb->current_line != LINES) {
|
||||
gb->cycles_for_line = 2;
|
||||
GB_SLEEP(gb, display, 28, 2);
|
||||
gb->io_registers[GB_IO_LY] = gb->current_line;
|
||||
if (gb->position_in_line >= 156 && gb->position_in_line < (uint8_t)-16) {
|
||||
gb->delayed_glitch_hblank_interrupt = true;
|
||||
}
|
||||
GB_STAT_update(gb);
|
||||
gb->position_in_line = -15;
|
||||
goto mode_3_start;
|
||||
}
|
||||
else {
|
||||
if (gb->position_in_line >= 156 && gb->position_in_line < (uint8_t)-16) {
|
||||
gb->delayed_glitch_hblank_interrupt = true;
|
||||
}
|
||||
gb->position_in_line = -16;
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
/* Lines 0 - 143 */
|
||||
gb->window_y = -1;
|
||||
|
@ -1405,8 +1549,9 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
|
|||
gb->mode_for_interrupt = -1;
|
||||
GB_STAT_update(gb);
|
||||
gb->n_visible_objs = 0;
|
||||
gb->orig_n_visible_objs = 0;
|
||||
|
||||
if (!gb->dma_steps_left && !gb->oam_ppu_blocked) {
|
||||
if (!GB_is_dma_active(gb) && !gb->oam_ppu_blocked) {
|
||||
GB_BATCHPOINT(gb, display, 5, 80);
|
||||
}
|
||||
for (gb->oam_search_index = 0; gb->oam_search_index < 40; gb->oam_search_index++) {
|
||||
|
@ -1427,7 +1572,7 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
|
|||
}
|
||||
}
|
||||
gb->cycles_for_line = MODE2_LENGTH + 4;
|
||||
|
||||
gb->orig_n_visible_objs = gb->n_visible_objs;
|
||||
gb->accessed_oam_row = -1;
|
||||
gb->io_registers[GB_IO_STAT] &= ~3;
|
||||
gb->io_registers[GB_IO_STAT] |= 3;
|
||||
|
@ -1462,11 +1607,7 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
|
|||
fifo_clear(&gb->oam_fifo);
|
||||
/* Fill the FIFO with 8 pixels of "junk", it's going to be dropped anyway. */
|
||||
fifo_push_bg_row(&gb->bg_fifo, 0, 0, 0, false, false);
|
||||
/* Todo: find out actual access time of SCX */
|
||||
gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8;
|
||||
gb->lcd_x = 0;
|
||||
|
||||
gb->extra_penalty_for_object_at_0 = MIN((gb->io_registers[GB_IO_SCX] & 7), 5);
|
||||
|
||||
/* The actual rendering cycle */
|
||||
gb->fetcher_state = 0;
|
||||
|
@ -1555,20 +1696,19 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
|
|||
/* Handle objects */
|
||||
/* When the object enabled bit is off, this proccess is skipped entirely on the DMG, but not on the CGB.
|
||||
On the CGB, this bit is checked only when the pixel is actually popped from the FIFO. */
|
||||
|
||||
|
||||
while (gb->n_visible_objs != 0 &&
|
||||
(gb->position_in_line < 160 || gb->position_in_line >= (uint8_t)(-8)) &&
|
||||
gb->obj_comparators[gb->n_visible_objs - 1] < (uint8_t)(gb->position_in_line + 8)) {
|
||||
gb->objects_x[gb->n_visible_objs - 1] < x_for_object_match(gb)) {
|
||||
gb->n_visible_objs--;
|
||||
}
|
||||
|
||||
gb->during_object_fetch = true;
|
||||
while (gb->n_visible_objs != 0 &&
|
||||
(gb->io_registers[GB_IO_LCDC] & 2 || GB_is_cgb(gb)) &&
|
||||
gb->obj_comparators[gb->n_visible_objs - 1] == (uint8_t)(gb->position_in_line + 8)) {
|
||||
gb->objects_x[gb->n_visible_objs - 1] == x_for_object_match(gb)) {
|
||||
|
||||
while (gb->fetcher_state < 5 || fifo_size(&gb->bg_fifo) == 0) {
|
||||
advance_fetcher_state_machine(gb);
|
||||
advance_fetcher_state_machine(gb, &cycles);
|
||||
gb->cycles_for_line++;
|
||||
GB_SLEEP(gb, display, 27, 1);
|
||||
if (gb->object_fetch_aborted) {
|
||||
|
@ -1576,20 +1716,8 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
|
|||
}
|
||||
}
|
||||
|
||||
/* Todo: Measure if penalty occurs before or after waiting for the fetcher. */
|
||||
if (gb->extra_penalty_for_object_at_0 != 0) {
|
||||
if (gb->obj_comparators[gb->n_visible_objs - 1] == 0) {
|
||||
gb->cycles_for_line += gb->extra_penalty_for_object_at_0;
|
||||
GB_SLEEP(gb, display, 28, gb->extra_penalty_for_object_at_0);
|
||||
gb->extra_penalty_for_object_at_0 = 0;
|
||||
if (gb->object_fetch_aborted) {
|
||||
goto abort_fetching_object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: Can this be deleted? { */
|
||||
advance_fetcher_state_machine(gb);
|
||||
advance_fetcher_state_machine(gb, &cycles);
|
||||
gb->cycles_for_line++;
|
||||
GB_SLEEP(gb, display, 41, 1);
|
||||
if (gb->object_fetch_aborted) {
|
||||
|
@ -1597,18 +1725,28 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
|
|||
}
|
||||
/* } */
|
||||
|
||||
advance_fetcher_state_machine(gb);
|
||||
|
||||
gb->cycles_for_line += 3;
|
||||
GB_SLEEP(gb, display, 20, 3);
|
||||
advance_fetcher_state_machine(gb, &cycles);
|
||||
dma_sync(gb, &cycles);
|
||||
gb->mode2_y_bus = oam_read(gb, gb->visible_objs[gb->n_visible_objs - 1] * 4 + 2);
|
||||
gb->object_flags = oam_read(gb, gb->visible_objs[gb->n_visible_objs - 1] * 4 + 3);
|
||||
|
||||
gb->cycles_for_line += 2;
|
||||
GB_SLEEP(gb, display, 20, 2);
|
||||
if (gb->object_fetch_aborted) {
|
||||
goto abort_fetching_object;
|
||||
}
|
||||
|
||||
gb->object_low_line_address = get_object_line_address(gb, &objects[gb->visible_objs[gb->n_visible_objs - 1]]);
|
||||
/* TODO: timing not verified */
|
||||
dma_sync(gb, &cycles);
|
||||
gb->object_low_line_address = get_object_line_address(gb,
|
||||
gb->objects_y[gb->n_visible_objs - 1],
|
||||
gb->mode2_y_bus,
|
||||
gb->object_flags);
|
||||
gb->object_tile_data[0] = vram_read(gb, gb->object_low_line_address);
|
||||
|
||||
|
||||
gb->cycles_for_line++;
|
||||
GB_SLEEP(gb, display, 39, 1);
|
||||
gb->cycles_for_line += 2;
|
||||
GB_SLEEP(gb, display, 39, 2);
|
||||
if (gb->object_fetch_aborted) {
|
||||
goto abort_fetching_object;
|
||||
}
|
||||
|
@ -1616,24 +1754,28 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
|
|||
gb->during_object_fetch = false;
|
||||
gb->cycles_for_line++;
|
||||
GB_SLEEP(gb, display, 40, 1);
|
||||
|
||||
/* TODO: timing not verified */
|
||||
dma_sync(gb, &cycles);
|
||||
gb->object_tile_data[1] = vram_read(gb, get_object_line_address(gb,
|
||||
gb->objects_y[gb->n_visible_objs - 1],
|
||||
gb->mode2_y_bus,
|
||||
gb->object_flags) + 1);
|
||||
|
||||
const object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]];
|
||||
|
||||
uint16_t line_address = get_object_line_address(gb, object);
|
||||
|
||||
uint8_t palette = (object->flags & 0x10) ? 1 : 0;
|
||||
uint8_t palette = (gb->object_flags & 0x10) ? 1 : 0;
|
||||
if (gb->cgb_mode) {
|
||||
palette = object->flags & 0x7;
|
||||
palette = gb->object_flags & 0x7;
|
||||
}
|
||||
fifo_overlay_object_row(&gb->oam_fifo,
|
||||
gb->vram_ppu_blocked? 0xFF : gb->vram[gb->object_low_line_address],
|
||||
gb->vram_ppu_blocked? 0xFF : gb->vram[line_address + 1],
|
||||
gb->object_tile_data[0],
|
||||
gb->object_tile_data[1],
|
||||
palette,
|
||||
object->flags & 0x80,
|
||||
gb->object_flags & 0x80,
|
||||
gb->object_priority == GB_OBJECT_PRIORITY_INDEX? gb->visible_objs[gb->n_visible_objs - 1] : 0,
|
||||
object->flags & 0x20);
|
||||
gb->object_flags & 0x20);
|
||||
|
||||
gb->data_for_sel_glitch = gb->vram_ppu_blocked? 0xFF : gb->vram[line_address + 1];
|
||||
gb->data_for_sel_glitch = gb->vram_ppu_blocked? 0xFF : gb->vram[gb->object_low_line_address + 1];
|
||||
gb->n_visible_objs--;
|
||||
}
|
||||
|
||||
|
@ -1642,14 +1784,15 @@ abort_fetching_object:
|
|||
gb->during_object_fetch = false;
|
||||
|
||||
render_pixel_if_possible(gb);
|
||||
advance_fetcher_state_machine(gb);
|
||||
advance_fetcher_state_machine(gb, &cycles);
|
||||
|
||||
if (gb->position_in_line == 160) break;
|
||||
gb->cycles_for_line++;
|
||||
GB_SLEEP(gb, display, 21, 1);
|
||||
}
|
||||
skip_slow_mode_3:
|
||||
|
||||
gb->position_in_line = -16;
|
||||
|
||||
/* TODO: This seems incorrect (glitches Tesserae), verify further */
|
||||
/*
|
||||
if (gb->fetcher_state == 4 || gb->fetcher_state == 5) {
|
||||
|
@ -1712,17 +1855,25 @@ skip_slow_mode_3:
|
|||
GB_SLEEP(gb, display, 33, 2);
|
||||
gb->cgb_palettes_blocked = !gb->cgb_double_speed;
|
||||
|
||||
if (gb->hdma_on_hblank && !gb->halted && !gb->stopped) {
|
||||
gb->hdma_on = true;
|
||||
}
|
||||
|
||||
gb->cycles_for_line += 2;
|
||||
GB_SLEEP(gb, display, 36, 2);
|
||||
gb->cgb_palettes_blocked = false;
|
||||
|
||||
gb->cycles_for_line += 8;
|
||||
GB_SLEEP(gb, display, 25, 8);
|
||||
|
||||
if (gb->hdma_on_hblank) {
|
||||
gb->hdma_starting = true;
|
||||
if (gb->cycles_for_line > LINE_LENGTH - 2) {
|
||||
gb->cycles_for_line = 0;
|
||||
GB_SLEEP(gb, display, 43, LINE_LENGTH - gb->cycles_for_line);
|
||||
goto display9;
|
||||
}
|
||||
|
||||
{
|
||||
uint16_t cycles_for_line = gb->cycles_for_line;
|
||||
gb->cycles_for_line = 0;
|
||||
GB_SLEEP(gb, display, 11, LINE_LENGTH - cycles_for_line - 2);
|
||||
}
|
||||
GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line - 2);
|
||||
/*
|
||||
TODO: Verify double speed timing
|
||||
TODO: Timing differs on a DMG
|
||||
|
@ -1731,6 +1882,7 @@ skip_slow_mode_3:
|
|||
(gb->io_registers[GB_IO_WY] == gb->current_line)) {
|
||||
gb->wy_triggered = true;
|
||||
}
|
||||
gb->cycles_for_line = 0;
|
||||
GB_SLEEP(gb, display, 31, 2);
|
||||
if (gb->current_line != LINES - 1) {
|
||||
gb->mode_for_interrupt = 2;
|
||||
|
@ -1739,7 +1891,7 @@ skip_slow_mode_3:
|
|||
// Todo: unverified timing
|
||||
gb->current_lcd_line++;
|
||||
if (gb->current_lcd_line == LINES && GB_is_sgb(gb)) {
|
||||
GB_display_vblank(gb);
|
||||
GB_display_vblank(gb, GB_VBLANK_TYPE_NORMAL_FRAME);
|
||||
}
|
||||
|
||||
if (gb->icd_hreset_callback) {
|
||||
|
@ -1760,6 +1912,10 @@ skip_slow_mode_3:
|
|||
gb->io_registers[GB_IO_IF] |= 2;
|
||||
}
|
||||
GB_SLEEP(gb, display, 12, 2);
|
||||
if (gb->delayed_glitch_hblank_interrupt) {
|
||||
gb->delayed_glitch_hblank_interrupt = false;
|
||||
gb->mode_for_interrupt = 0;
|
||||
}
|
||||
gb->ly_for_comparison = gb->current_line;
|
||||
GB_STAT_update(gb);
|
||||
GB_SLEEP(gb, display, 24, 1);
|
||||
|
@ -1777,13 +1933,13 @@ skip_slow_mode_3:
|
|||
|
||||
if (gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON) {
|
||||
if (GB_is_cgb(gb)) {
|
||||
GB_display_vblank(gb);
|
||||
GB_display_vblank(gb, GB_VBLANK_TYPE_NORMAL_FRAME);
|
||||
gb->frame_skip_state = GB_FRAMESKIP_FIRST_FRAME_SKIPPED;
|
||||
}
|
||||
else {
|
||||
if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) {
|
||||
gb->is_odd_frame ^= true;
|
||||
GB_display_vblank(gb);
|
||||
GB_display_vblank(gb, GB_VBLANK_TYPE_NORMAL_FRAME);
|
||||
}
|
||||
gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED;
|
||||
}
|
||||
|
@ -1791,7 +1947,7 @@ skip_slow_mode_3:
|
|||
else {
|
||||
if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) {
|
||||
gb->is_odd_frame ^= true;
|
||||
GB_display_vblank(gb);
|
||||
GB_display_vblank(gb, GB_VBLANK_TYPE_NORMAL_FRAME);
|
||||
}
|
||||
if (gb->frame_skip_state == GB_FRAMESKIP_FIRST_FRAME_SKIPPED) {
|
||||
gb->cgb_repeated_a_frame = true;
|
||||
|
@ -1918,7 +2074,7 @@ void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette
|
|||
}
|
||||
|
||||
if (map_type == GB_MAP_9C00 || (map_type == GB_MAP_AUTO && gb->io_registers[GB_IO_LCDC] & 0x08)) {
|
||||
map = 0x1c00;
|
||||
map = 0x1C00;
|
||||
}
|
||||
|
||||
if (tileset_type == GB_TILESET_AUTO) {
|
||||
|
@ -1971,9 +2127,9 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *object_h
|
|||
for (signed y = 0; y < LINES; y++) {
|
||||
object_t *object = (object_t *) &gb->oam;
|
||||
uint8_t objects_in_line = 0;
|
||||
bool obscured = false;
|
||||
for (uint8_t i = 0; i < 40; i++, object++) {
|
||||
signed object_y = object->y - 16;
|
||||
bool obscured = false;
|
||||
// Is object not in this line?
|
||||
if (object_y > y || object_y + *object_height <= y) continue;
|
||||
if (++objects_in_line == 11) obscured = true;
|
||||
|
|
|
@ -5,12 +5,18 @@
|
|||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
GB_VBLANK_TYPE_NORMAL_FRAME, // An actual Vblank-triggered frame
|
||||
GB_VBLANK_TYPE_LCD_OFF, // An artificial frame pushed while the LCD was off
|
||||
GB_VBLANK_TYPE_ARTIFICIAL, // An artificial frame pushed for some other reason
|
||||
} GB_vblank_type_t;
|
||||
|
||||
#ifdef GB_INTERNAL
|
||||
internal void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force);
|
||||
internal void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index);
|
||||
internal void GB_STAT_update(GB_gameboy_t *gb);
|
||||
internal void GB_lcd_off(GB_gameboy_t *gb);
|
||||
internal void GB_display_vblank(GB_gameboy_t *gb);
|
||||
internal void GB_display_vblank(GB_gameboy_t *gb, GB_vblank_type_t type);
|
||||
#define GB_display_sync(gb) GB_display_run(gb, 0, true)
|
||||
|
||||
enum {
|
||||
|
|
143
Core/gb.c
|
@ -121,7 +121,7 @@ static void load_default_border(GB_gameboy_t *gb)
|
|||
}
|
||||
#endif
|
||||
|
||||
if (gb->model == GB_MODEL_AGB) {
|
||||
if (gb->model > GB_MODEL_CGB_E) {
|
||||
#include "graphics/agb_border.inc"
|
||||
LOAD_BORDER();
|
||||
}
|
||||
|
@ -216,6 +216,7 @@ void GB_free(GB_gameboy_t *gb)
|
|||
GB_remove_cheat(gb, gb->cheats[0]);
|
||||
}
|
||||
#endif
|
||||
GB_stop_audio_recording(gb);
|
||||
memset(gb, 0, sizeof(*gb));
|
||||
}
|
||||
|
||||
|
@ -437,12 +438,12 @@ int GB_load_gbs_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size
|
|||
if (gb->gbs_header.load_address) {
|
||||
// Generate interrupt handlers
|
||||
for (unsigned i = 0; i <= (has_interrupts? 0x50 : 0x38); i += 8) {
|
||||
gb->rom[i] = 0xc3; // jp $XXXX
|
||||
gb->rom[i] = 0xC3; // jp $XXXX
|
||||
gb->rom[i + 1] = (LE16(gb->gbs_header.load_address) + i);
|
||||
gb->rom[i + 2] = (LE16(gb->gbs_header.load_address) + i) >> 8;
|
||||
}
|
||||
for (unsigned i = has_interrupts? 0x58 : 0x40; i <= 0x60; i += 8) {
|
||||
gb->rom[i] = 0xc9; // ret
|
||||
gb->rom[i] = 0xC9; // ret
|
||||
}
|
||||
|
||||
// Generate entry
|
||||
|
@ -703,7 +704,7 @@ error:
|
|||
|
||||
void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size)
|
||||
{
|
||||
gb->rom_size = (size + 0x3fff) & ~0x3fff;
|
||||
gb->rom_size = (size + 0x3FFF) & ~0x3FFF;
|
||||
while (gb->rom_size & (gb->rom_size - 1)) {
|
||||
gb->rom_size |= gb->rom_size >> 1;
|
||||
gb->rom_size++;
|
||||
|
@ -715,7 +716,7 @@ void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t siz
|
|||
free(gb->rom);
|
||||
}
|
||||
gb->rom = malloc(gb->rom_size);
|
||||
memset(gb->rom, 0xff, gb->rom_size);
|
||||
memset(gb->rom, 0xFF, gb->rom_size);
|
||||
memcpy(gb->rom, buffer, size);
|
||||
GB_configure_cart(gb);
|
||||
gb->tried_loading_sgb_border = false;
|
||||
|
@ -1128,7 +1129,7 @@ exit:
|
|||
return;
|
||||
}
|
||||
|
||||
uint8_t GB_run(GB_gameboy_t *gb)
|
||||
unsigned GB_run(GB_gameboy_t *gb)
|
||||
{
|
||||
gb->vblank_just_occured = false;
|
||||
|
||||
|
@ -1219,10 +1220,10 @@ void GB_set_lcd_line_callback(GB_gameboy_t *gb, GB_lcd_line_callback_t callback)
|
|||
gb->lcd_line_callback = callback;
|
||||
}
|
||||
|
||||
const GB_palette_t GB_PALETTE_GREY = {{{0x00, 0x00, 0x00}, {0x55, 0x55, 0x55}, {0xaa, 0xaa, 0xaa}, {0xff, 0xff, 0xff}, {0xff, 0xff, 0xff}}};
|
||||
const GB_palette_t GB_PALETTE_DMG = {{{0x08, 0x18, 0x10}, {0x39, 0x61, 0x39}, {0x84, 0xa5, 0x63}, {0xc6, 0xde, 0x8c}, {0xd2, 0xe6, 0xa6}}};
|
||||
const GB_palette_t GB_PALETTE_MGB = {{{0x07, 0x10, 0x0e}, {0x3a, 0x4c, 0x3a}, {0x81, 0x8d, 0x66}, {0xc2, 0xce, 0x93}, {0xcf, 0xda, 0xac}}};
|
||||
const GB_palette_t GB_PALETTE_GBL = {{{0x0a, 0x1c, 0x15}, {0x35, 0x78, 0x62}, {0x56, 0xb4, 0x95}, {0x7f, 0xe2, 0xc3}, {0x91, 0xea, 0xd0}}};
|
||||
const GB_palette_t GB_PALETTE_GREY = {{{0x00, 0x00, 0x00}, {0x55, 0x55, 0x55}, {0xAA, 0xAA, 0xAA}, {0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF}}};
|
||||
const GB_palette_t GB_PALETTE_DMG = {{{0x08, 0x18, 0x10}, {0x39, 0x61, 0x39}, {0x84, 0xA5, 0x63}, {0xC6, 0xDE, 0x8C}, {0xD2, 0xE6, 0xA6}}};
|
||||
const GB_palette_t GB_PALETTE_MGB = {{{0x07, 0x10, 0x0E}, {0x3A, 0x4C, 0x3A}, {0x81, 0x8D, 0x66}, {0xC2, 0xCE, 0x93}, {0xCF, 0xDA, 0xAC}}};
|
||||
const GB_palette_t GB_PALETTE_GBL = {{{0x0A, 0x1C, 0x15}, {0x35, 0x78, 0x62}, {0x56, 0xB4, 0x95}, {0x7F, 0xE2, 0xC3}, {0x91, 0xEA, 0xD0}}};
|
||||
|
||||
static void update_dmg_palette(GB_gameboy_t *gb)
|
||||
{
|
||||
|
@ -1293,26 +1294,38 @@ void GB_set_serial_transfer_bit_end_callback(GB_gameboy_t *gb, GB_serial_transfe
|
|||
|
||||
bool GB_serial_get_data_bit(GB_gameboy_t *gb)
|
||||
{
|
||||
if (!(gb->io_registers[GB_IO_SC] & 0x80)) {
|
||||
/* Disabled serial returns 0 bits */
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gb->io_registers[GB_IO_SC] & 1) {
|
||||
/* Internal Clock */
|
||||
GB_log(gb, "Serial read request while using internal clock. \n");
|
||||
return 0xFF;
|
||||
return true;
|
||||
}
|
||||
return gb->io_registers[GB_IO_SB] & 0x80;
|
||||
}
|
||||
|
||||
void GB_serial_set_data_bit(GB_gameboy_t *gb, bool data)
|
||||
{
|
||||
if (!(gb->io_registers[GB_IO_SC] & 0x80)) {
|
||||
/* Serial disabled */
|
||||
return;
|
||||
}
|
||||
|
||||
if (gb->io_registers[GB_IO_SC] & 1) {
|
||||
/* Internal Clock */
|
||||
GB_log(gb, "Serial write request while using internal clock. \n");
|
||||
return;
|
||||
}
|
||||
|
||||
gb->io_registers[GB_IO_SB] <<= 1;
|
||||
gb->io_registers[GB_IO_SB] |= data;
|
||||
gb->serial_count++;
|
||||
if (gb->serial_count == 8) {
|
||||
gb->io_registers[GB_IO_IF] |= 8;
|
||||
gb->io_registers[GB_IO_SC] &= ~0x80;
|
||||
gb->serial_count = 0;
|
||||
}
|
||||
}
|
||||
|
@ -1378,7 +1391,7 @@ static void reset_ram(GB_gameboy_t *gb)
|
|||
switch (gb->model) {
|
||||
case GB_MODEL_MGB:
|
||||
case GB_MODEL_CGB_E:
|
||||
case GB_MODEL_AGB: /* Unverified */
|
||||
case GB_MODEL_AGB_A: /* Unverified */
|
||||
for (unsigned i = 0; i < gb->ram_size; i++) {
|
||||
gb->ram[i] = GB_random();
|
||||
}
|
||||
|
@ -1442,7 +1455,7 @@ static void reset_ram(GB_gameboy_t *gb)
|
|||
case GB_MODEL_CGB_C:
|
||||
case GB_MODEL_CGB_D:
|
||||
case GB_MODEL_CGB_E:
|
||||
case GB_MODEL_AGB:
|
||||
case GB_MODEL_AGB_A:
|
||||
for (unsigned i = 0; i < sizeof(gb->hram); i++) {
|
||||
gb->hram[i] = GB_random();
|
||||
}
|
||||
|
@ -1475,8 +1488,8 @@ static void reset_ram(GB_gameboy_t *gb)
|
|||
case GB_MODEL_CGB_C:
|
||||
case GB_MODEL_CGB_D:
|
||||
case GB_MODEL_CGB_E:
|
||||
case GB_MODEL_AGB:
|
||||
/* Zero'd out by boot ROM anyway, extra OAM no accessible */
|
||||
case GB_MODEL_AGB_A:
|
||||
/* Zero'd out by boot ROM anyway */
|
||||
break;
|
||||
|
||||
case GB_MODEL_DMG_B:
|
||||
|
@ -1509,7 +1522,7 @@ static void reset_ram(GB_gameboy_t *gb)
|
|||
case GB_MODEL_CGB_C:
|
||||
case GB_MODEL_CGB_D:
|
||||
case GB_MODEL_CGB_E:
|
||||
case GB_MODEL_AGB:
|
||||
case GB_MODEL_AGB_A:
|
||||
/* Initialized by CGB-A and newer, 0s in CGB-0 */
|
||||
break;
|
||||
case GB_MODEL_MGB: {
|
||||
|
@ -1589,7 +1602,7 @@ static void request_boot_rom(GB_gameboy_t *gb)
|
|||
case GB_MODEL_CGB_E:
|
||||
type = GB_BOOT_ROM_CGB;
|
||||
break;
|
||||
case GB_MODEL_AGB:
|
||||
case GB_MODEL_AGB_A:
|
||||
type = GB_BOOT_ROM_AGB;
|
||||
break;
|
||||
}
|
||||
|
@ -1609,7 +1622,8 @@ void GB_reset(GB_gameboy_t *gb)
|
|||
gb->model = model;
|
||||
gb->version = GB_STRUCT_VERSION;
|
||||
|
||||
gb->mbc_rom_bank = 1;
|
||||
GB_reset_mbc(gb);
|
||||
|
||||
gb->last_rtc_second = time(NULL);
|
||||
gb->cgb_ram_bank = 1;
|
||||
gb->io_registers[GB_IO_JOYP] = 0xCF;
|
||||
|
@ -1631,16 +1645,15 @@ void GB_reset(GB_gameboy_t *gb)
|
|||
}
|
||||
reset_ram(gb);
|
||||
|
||||
/* The serial interrupt always occur on the 0xF7th cycle of every 0x100 cycle since boot. */
|
||||
gb->serial_cycles = 0x100-0xF7;
|
||||
gb->serial_mask = 0x80;
|
||||
gb->io_registers[GB_IO_SC] = 0x7E;
|
||||
|
||||
/* These are not deterministic, but 00 (CGB) and FF (DMG) are the most common initial values by far */
|
||||
gb->io_registers[GB_IO_DMA] = gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = GB_is_cgb(gb)? 0x00 : 0xFF;
|
||||
|
||||
gb->accessed_oam_row = -1;
|
||||
|
||||
|
||||
gb->dma_current_dest = 0xA1;
|
||||
|
||||
if (GB_is_hle_sgb(gb)) {
|
||||
if (!gb->sgb) {
|
||||
gb->sgb = malloc(sizeof(*gb->sgb));
|
||||
|
@ -1905,49 +1918,49 @@ void GB_get_rom_title(GB_gameboy_t *gb, char *title)
|
|||
uint32_t GB_get_rom_crc32(GB_gameboy_t *gb)
|
||||
{
|
||||
static const uint32_t table[] = {
|
||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
|
||||
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
|
||||
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
|
||||
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
|
||||
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
|
||||
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
|
||||
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
|
||||
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
|
||||
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
|
||||
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
|
||||
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
|
||||
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
|
||||
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
|
||||
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
|
||||
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
|
||||
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
|
||||
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
|
||||
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
|
||||
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
|
||||
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
||||
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
|
||||
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
|
||||
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
|
||||
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
|
||||
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
|
||||
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
|
||||
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
|
||||
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
||||
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
|
||||
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
|
||||
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
|
||||
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
|
||||
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
|
||||
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
|
||||
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
|
||||
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
|
||||
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
|
||||
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
|
||||
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
|
||||
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
||||
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
|
||||
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
|
||||
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
|
||||
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F,
|
||||
0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
|
||||
0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2,
|
||||
0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
|
||||
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9,
|
||||
0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
|
||||
0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C,
|
||||
0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
|
||||
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423,
|
||||
0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
|
||||
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106,
|
||||
0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
|
||||
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D,
|
||||
0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
|
||||
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
|
||||
0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
|
||||
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7,
|
||||
0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
|
||||
0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA,
|
||||
0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
|
||||
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,
|
||||
0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
|
||||
0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84,
|
||||
0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
|
||||
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB,
|
||||
0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
|
||||
0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E,
|
||||
0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
|
||||
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55,
|
||||
0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
|
||||
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28,
|
||||
0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
|
||||
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F,
|
||||
0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
|
||||
0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
|
||||
0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
|
||||
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69,
|
||||
0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
|
||||
0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC,
|
||||
0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
|
||||
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693,
|
||||
0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
|
||||
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
|
||||
};
|
||||
|
||||
const uint8_t *byte = gb->rom;
|
||||
|
|
207
Core/gb.h
|
@ -36,6 +36,8 @@
|
|||
#define GB_MODEL_PAL_BIT 0x40
|
||||
#define GB_MODEL_NO_SFC_BIT 0x80
|
||||
|
||||
#define GB_REWIND_FRAMES_PER_KEY 255
|
||||
|
||||
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
#define GB_BIG_ENDIAN
|
||||
#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
|
@ -44,6 +46,17 @@
|
|||
#error Unable to detect endianess
|
||||
#endif
|
||||
|
||||
#ifdef GB_BIG_ENDIAN
|
||||
#define GB_REGISTER_ORDER a, f, \
|
||||
b, c, \
|
||||
d, e, \
|
||||
h, l
|
||||
#else
|
||||
#define GB_REGISTER_ORDER f, a, \
|
||||
c, b, \
|
||||
e, d, \
|
||||
l, h
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
struct GB_color_s {
|
||||
|
@ -102,7 +115,11 @@ typedef enum {
|
|||
GB_MODEL_CGB_C = 0x203,
|
||||
GB_MODEL_CGB_D = 0x204,
|
||||
GB_MODEL_CGB_E = 0x205,
|
||||
GB_MODEL_AGB = 0x206,
|
||||
// GB_MODEL_AGB_0 = 0x206,
|
||||
GB_MODEL_AGB_A = 0x207,
|
||||
GB_MODEL_AGB = GB_MODEL_AGB_A,
|
||||
//GB_MODEL_AGB_B = 0x208
|
||||
//GB_MODEL_AGB_E = 0x209
|
||||
} GB_model_t;
|
||||
|
||||
enum {
|
||||
|
@ -145,7 +162,7 @@ enum {
|
|||
|
||||
/* Missing */
|
||||
|
||||
GB_IO_IF = 0x0f, // Interrupt Flag (R/W)
|
||||
GB_IO_IF = 0x0F, // Interrupt Flag (R/W)
|
||||
|
||||
/* Sound */
|
||||
GB_IO_NR10 = 0x10, // Channel 1 Sweep register (R/W)
|
||||
|
@ -158,11 +175,11 @@ enum {
|
|||
GB_IO_NR22 = 0x17, // Channel 2 Volume Envelope (R/W)
|
||||
GB_IO_NR23 = 0x18, // Channel 2 Frequency lo data (W)
|
||||
GB_IO_NR24 = 0x19, // Channel 2 Frequency hi data (R/W)
|
||||
GB_IO_NR30 = 0x1a, // Channel 3 Sound on/off (R/W)
|
||||
GB_IO_NR31 = 0x1b, // Channel 3 Sound Length
|
||||
GB_IO_NR32 = 0x1c, // Channel 3 Select output level (R/W)
|
||||
GB_IO_NR33 = 0x1d, // Channel 3 Frequency's lower data (W)
|
||||
GB_IO_NR34 = 0x1e, // Channel 3 Frequency's higher data (R/W)
|
||||
GB_IO_NR30 = 0x1A, // Channel 3 Sound on/off (R/W)
|
||||
GB_IO_NR31 = 0x1B, // Channel 3 Sound Length
|
||||
GB_IO_NR32 = 0x1C, // Channel 3 Select output level (R/W)
|
||||
GB_IO_NR33 = 0x1D, // Channel 3 Frequency's lower data (W)
|
||||
GB_IO_NR34 = 0x1E, // Channel 3 Frequency's higher data (R/W)
|
||||
/* NR40 does not exist */
|
||||
GB_IO_NR41 = 0x20, // Channel 4 Sound Length (R/W)
|
||||
GB_IO_NR42 = 0x21, // Channel 4 Volume Envelope (R/W)
|
||||
|
@ -175,7 +192,7 @@ enum {
|
|||
/* Missing */
|
||||
|
||||
GB_IO_WAV_START = 0x30, // Wave pattern start
|
||||
GB_IO_WAV_END = 0x3f, // Wave pattern end
|
||||
GB_IO_WAV_END = 0x3F, // Wave pattern end
|
||||
|
||||
/* Graphics */
|
||||
GB_IO_LCDC = 0x40, // LCD Control (R/W)
|
||||
|
@ -188,17 +205,17 @@ enum {
|
|||
GB_IO_BGP = 0x47, // BG Palette Data (R/W) - Non CGB Mode Only
|
||||
GB_IO_OBP0 = 0x48, // Object Palette 0 Data (R/W) - Non CGB Mode Only
|
||||
GB_IO_OBP1 = 0x49, // Object Palette 1 Data (R/W) - Non CGB Mode Only
|
||||
GB_IO_WY = 0x4a, // Window Y Position (R/W)
|
||||
GB_IO_WX = 0x4b, // Window X Position minus 7 (R/W)
|
||||
GB_IO_WY = 0x4A, // Window Y Position (R/W)
|
||||
GB_IO_WX = 0x4B, // Window X Position minus 7 (R/W)
|
||||
// Controls DMG mode and PGB mode
|
||||
GB_IO_KEY0 = 0x4c,
|
||||
GB_IO_KEY0 = 0x4C,
|
||||
|
||||
/* General CGB features */
|
||||
GB_IO_KEY1 = 0x4d, // CGB Mode Only - Prepare Speed Switch
|
||||
GB_IO_KEY1 = 0x4D, // CGB Mode Only - Prepare Speed Switch
|
||||
|
||||
/* Missing */
|
||||
|
||||
GB_IO_VBK = 0x4f, // CGB Mode Only - VRAM Bank
|
||||
GB_IO_VBK = 0x4F, // CGB Mode Only - VRAM Bank
|
||||
GB_IO_BANK = 0x50, // Write to disable the BIOS mapping
|
||||
|
||||
/* CGB DMA */
|
||||
|
@ -213,16 +230,17 @@ enum {
|
|||
|
||||
/* Missing */
|
||||
|
||||
/* CGB Paletts */
|
||||
/* CGB Palettes */
|
||||
GB_IO_BGPI = 0x68, // CGB Mode Only - Background Palette Index
|
||||
GB_IO_BGPD = 0x69, // CGB Mode Only - Background Palette Data
|
||||
GB_IO_OBPI = 0x6a, // CGB Mode Only - Object Palette Index
|
||||
GB_IO_OBPD = 0x6b, // CGB Mode Only - Object Palette Data
|
||||
GB_IO_OPRI = 0x6c, // Affects object priority (X based or index based)
|
||||
GB_IO_OBPI = 0x6A, // CGB Mode Only - Object Palette Index
|
||||
GB_IO_OBPD = 0x6B, // CGB Mode Only - Object Palette Data
|
||||
GB_IO_OPRI = 0x6C, // Affects object priority (X based or index based)
|
||||
|
||||
/* Missing */
|
||||
|
||||
GB_IO_SVBK = 0x70, // CGB Mode Only - WRAM Bank
|
||||
GB_IO_PSM = 0x71, // Palette Selection Mode, controls the PSW and key combo
|
||||
GB_IO_PSWX = 0x72, // X position of the palette switching window
|
||||
GB_IO_PSWY = 0x73, // Y position of the palette switching window
|
||||
GB_IO_PSW = 0x74, // Key combo to trigger the palette switching window
|
||||
|
@ -265,7 +283,7 @@ typedef enum {
|
|||
#endif
|
||||
#endif
|
||||
|
||||
typedef void (*GB_vblank_callback_t)(GB_gameboy_t *gb);
|
||||
typedef void (*GB_vblank_callback_t)(GB_gameboy_t *gb, GB_vblank_type_t type);
|
||||
typedef void (*GB_log_callback_t)(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes);
|
||||
typedef char *(*GB_input_callback_t)(GB_gameboy_t *gb);
|
||||
typedef uint32_t (*GB_rgb_encode_callback_t)(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b);
|
||||
|
@ -293,11 +311,11 @@ typedef struct {
|
|||
bool bg_priority; // For object FIFO – the BG priority bit. For the BG FIFO – the CGB attributes priority bit
|
||||
} GB_fifo_item_t;
|
||||
|
||||
#define GB_FIFO_LENGTH 16
|
||||
#define GB_FIFO_LENGTH 8
|
||||
typedef struct {
|
||||
GB_fifo_item_t fifo[GB_FIFO_LENGTH];
|
||||
uint8_t read_end;
|
||||
uint8_t write_end;
|
||||
uint8_t size;
|
||||
} GB_fifo_t;
|
||||
|
||||
typedef struct {
|
||||
|
@ -335,17 +353,7 @@ typedef union {
|
|||
pc;
|
||||
};
|
||||
struct {
|
||||
#ifdef GB_BIG_ENDIAN
|
||||
uint8_t a, f,
|
||||
b, c,
|
||||
d, e,
|
||||
h, l;
|
||||
#else
|
||||
uint8_t f, a,
|
||||
c, b,
|
||||
e, d,
|
||||
l, h;
|
||||
#endif
|
||||
uint8_t GB_REGISTER_ORDER;
|
||||
};
|
||||
} GB_registers_t;
|
||||
|
||||
|
@ -368,7 +376,7 @@ struct GB_gameboy_internal_s {
|
|||
/* The version field makes sure we don't load save state files with a completely different structure.
|
||||
This happens when struct fields are removed/resized in an backward incompatible manner. */
|
||||
uint32_t version;
|
||||
);
|
||||
)
|
||||
|
||||
GB_SECTION(core_state,
|
||||
/* Registers */
|
||||
|
@ -383,17 +391,7 @@ struct GB_gameboy_internal_s {
|
|||
pc;
|
||||
};
|
||||
struct {
|
||||
#ifdef GB_BIG_ENDIAN
|
||||
uint8_t a, f,
|
||||
b, c,
|
||||
d, e,
|
||||
h, l;
|
||||
#else
|
||||
uint8_t f, a,
|
||||
c, b,
|
||||
e, d,
|
||||
l, h;
|
||||
#endif
|
||||
uint8_t GB_REGISTER_ORDER;
|
||||
};
|
||||
};
|
||||
uint8_t ime;
|
||||
|
@ -414,37 +412,37 @@ struct GB_gameboy_internal_s {
|
|||
/* Misc state */
|
||||
bool infrared_input;
|
||||
GB_printer_t printer;
|
||||
uint8_t extra_oam[0xff00 - 0xfea0];
|
||||
uint8_t extra_oam[0xFF00 - 0xFEA0];
|
||||
uint32_t ram_size; // Different between CGB and DMG
|
||||
GB_workboy_t workboy;
|
||||
|
||||
int32_t ir_sensor;
|
||||
bool effective_ir_input;
|
||||
uint16_t address_bus;
|
||||
);
|
||||
)
|
||||
|
||||
/* DMA and HDMA */
|
||||
GB_SECTION(dma,
|
||||
bool hdma_on;
|
||||
bool hdma_on_hblank;
|
||||
uint8_t hdma_steps_left;
|
||||
int16_t hdma_cycles; // in 8MHz units
|
||||
uint16_t hdma_current_src, hdma_current_dest;
|
||||
|
||||
uint8_t dma_steps_left;
|
||||
uint8_t dma_current_dest;
|
||||
uint8_t last_dma_read;
|
||||
uint16_t dma_current_src;
|
||||
int16_t dma_cycles;
|
||||
bool is_dma_restarting;
|
||||
uint8_t dma_and_pattern;
|
||||
bool dma_skip_write;
|
||||
uint8_t last_opcode_read; /* Required to emulte HDMA reads from Exxx */
|
||||
bool hdma_starting;
|
||||
);
|
||||
uint16_t dma_cycles;
|
||||
int8_t dma_cycles_modulo;
|
||||
bool dma_ppu_vram_conflict;
|
||||
uint16_t dma_ppu_vram_conflict_addr;
|
||||
uint8_t hdma_open_bus; /* Required to emulate HDMA reads from Exxx */
|
||||
bool allow_hdma_on_wake;
|
||||
)
|
||||
|
||||
/* MBC */
|
||||
GB_SECTION(mbc,
|
||||
uint16_t mbc_rom_bank;
|
||||
uint16_t mbc_rom0_bank; /* For multicart mappings . */
|
||||
uint8_t mbc_ram_bank;
|
||||
uint32_t mbc_ram_size;
|
||||
bool mbc_ram_enable;
|
||||
|
@ -469,29 +467,45 @@ struct GB_gameboy_internal_s {
|
|||
uint8_t rom_bank_low;
|
||||
uint8_t rom_bank_high:1;
|
||||
uint8_t ram_bank:4;
|
||||
} mbc5;
|
||||
} mbc5; // Also used for GB_CAMERA
|
||||
|
||||
struct {
|
||||
uint8_t rom_bank;
|
||||
uint16_t x_latch;
|
||||
uint16_t y_latch;
|
||||
bool latch_ready:1;
|
||||
bool eeprom_do:1;
|
||||
bool eeprom_di:1;
|
||||
bool eeprom_clk:1;
|
||||
bool eeprom_cs:1;
|
||||
uint16_t eeprom_command:11;
|
||||
uint16_t read_bits;
|
||||
uint8_t bits_countdown:5;
|
||||
bool secondary_ram_enable:1;
|
||||
bool eeprom_write_enabled:1;
|
||||
} mbc7;
|
||||
struct {
|
||||
uint8_t rom_bank;
|
||||
uint16_t x_latch;
|
||||
uint16_t y_latch;
|
||||
bool latch_ready:1;
|
||||
bool eeprom_do:1;
|
||||
bool eeprom_di:1;
|
||||
bool eeprom_clk:1;
|
||||
bool eeprom_cs:1;
|
||||
uint16_t eeprom_command:11;
|
||||
uint16_t read_bits;
|
||||
uint8_t argument_bits_left:5;
|
||||
bool secondary_ram_enable:1;
|
||||
bool eeprom_write_enabled:1;
|
||||
} mbc7;
|
||||
|
||||
struct {
|
||||
uint8_t rom_bank_low:5;
|
||||
uint8_t rom_bank_mid:2;
|
||||
bool mbc1_mode:1;
|
||||
|
||||
uint8_t rom_bank_mask:4;
|
||||
uint8_t rom_bank_high:2;
|
||||
uint8_t ram_bank_low:2;
|
||||
|
||||
uint8_t ram_bank_high:2;
|
||||
uint8_t ram_bank_mask:2;
|
||||
|
||||
bool locked:1;
|
||||
bool mbc1_mode_disable:1;
|
||||
bool multiplex_mode:1;
|
||||
} mmm01;
|
||||
|
||||
struct {
|
||||
uint8_t bank_low:6;
|
||||
uint8_t bank_high:3;
|
||||
bool mode:1;
|
||||
bool ir_mode:1;
|
||||
bool ir_mode;
|
||||
} huc1;
|
||||
|
||||
struct {
|
||||
|
@ -513,20 +527,17 @@ struct GB_gameboy_internal_s {
|
|||
uint8_t mode;
|
||||
} tpp1;
|
||||
};
|
||||
uint16_t mbc_rom0_bank; /* For some MBC1 wirings. */
|
||||
bool camera_registers_mapped;
|
||||
uint8_t camera_registers[0x36];
|
||||
uint8_t rumble_strength;
|
||||
bool cart_ir;
|
||||
|
||||
);
|
||||
|
||||
)
|
||||
|
||||
/* HRAM and HW Registers */
|
||||
GB_SECTION(hram,
|
||||
uint8_t hram[0xFFFF - 0xFF80];
|
||||
uint8_t io_registers[0x80];
|
||||
);
|
||||
)
|
||||
|
||||
/* Timing */
|
||||
GB_SECTION(timing,
|
||||
|
@ -534,8 +545,8 @@ struct GB_gameboy_internal_s {
|
|||
GB_UNIT(div);
|
||||
uint16_t div_counter;
|
||||
uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */
|
||||
uint16_t serial_cycles;
|
||||
uint16_t serial_length;
|
||||
bool serial_master_clock;
|
||||
uint8_t serial_mask;
|
||||
uint8_t double_speed_alignment;
|
||||
uint8_t serial_count;
|
||||
int32_t speed_switch_halt_countdown;
|
||||
|
@ -546,12 +557,12 @@ struct GB_gameboy_internal_s {
|
|||
bool lcd_disabled_outside_of_vblank;
|
||||
int32_t allowed_pending_cycles;
|
||||
uint16_t mode3_batching_length;
|
||||
);
|
||||
)
|
||||
|
||||
/* APU */
|
||||
GB_SECTION(apu,
|
||||
GB_apu_t apu;
|
||||
);
|
||||
)
|
||||
|
||||
/* RTC */
|
||||
GB_SECTION(rtc,
|
||||
|
@ -559,7 +570,7 @@ struct GB_gameboy_internal_s {
|
|||
uint64_t last_rtc_second;
|
||||
uint32_t rtc_cycles;
|
||||
uint8_t tpp1_mr4;
|
||||
);
|
||||
)
|
||||
|
||||
/* Video Display */
|
||||
GB_SECTION(video,
|
||||
|
@ -570,7 +581,6 @@ struct GB_gameboy_internal_s {
|
|||
uint8_t object_palettes_data[0x40];
|
||||
uint8_t position_in_line;
|
||||
bool stat_interrupt_line;
|
||||
uint8_t effective_scx;
|
||||
uint8_t window_y;
|
||||
/* The LCDC will skip the first frame it renders after turning it on.
|
||||
On the CGB, a frame is not skipped if the previous frame was skipped as well.
|
||||
|
@ -588,7 +598,6 @@ struct GB_gameboy_internal_s {
|
|||
bool vram_read_blocked;
|
||||
bool oam_write_blocked;
|
||||
bool vram_write_blocked;
|
||||
bool fifo_insertion_glitch;
|
||||
uint8_t current_line;
|
||||
uint16_t ly_for_comparison;
|
||||
GB_fifo_t bg_fifo, oam_fifo;
|
||||
|
@ -602,11 +611,19 @@ struct GB_gameboy_internal_s {
|
|||
bool wx166_glitch;
|
||||
bool wx_triggered;
|
||||
uint8_t visible_objs[10];
|
||||
uint8_t obj_comparators[10];
|
||||
uint8_t objects_x[10];
|
||||
uint8_t objects_y[10];
|
||||
uint8_t object_tile_data[2];
|
||||
uint8_t mode2_y_bus;
|
||||
// They're the same bus
|
||||
union {
|
||||
uint8_t mode2_x_bus;
|
||||
uint8_t object_flags;
|
||||
};
|
||||
uint8_t n_visible_objs;
|
||||
uint8_t orig_n_visible_objs;
|
||||
uint8_t oam_search_index;
|
||||
uint8_t accessed_oam_row;
|
||||
uint8_t extra_penalty_for_object_at_0;
|
||||
uint8_t mode_for_interrupt;
|
||||
bool lyc_interrupt_line;
|
||||
bool cgb_palettes_blocked;
|
||||
|
@ -626,7 +643,8 @@ struct GB_gameboy_internal_s {
|
|||
uint16_t last_tile_index_address;
|
||||
bool cgb_repeated_a_frame;
|
||||
uint8_t data_for_sel_glitch;
|
||||
);
|
||||
bool delayed_glitch_hblank_interrupt;
|
||||
)
|
||||
|
||||
/* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */
|
||||
/* This data is reserved on reset and must come last in the struct */
|
||||
|
@ -715,8 +733,6 @@ struct GB_gameboy_internal_s {
|
|||
void *nontrivial_jump_state;
|
||||
bool non_trivial_jump_breakpoint_occured;
|
||||
|
||||
/* SLD (Todo: merge with backtrace) */
|
||||
bool stack_leak_detection;
|
||||
signed debug_call_depth;
|
||||
uint16_t sp_for_call_depth[0x200]; /* Should be much more than enough */
|
||||
uint16_t addr_for_call_depth[0x200];
|
||||
|
@ -734,7 +750,8 @@ struct GB_gameboy_internal_s {
|
|||
struct GB_watchpoint_s *watchpoints;
|
||||
|
||||
/* Symbol tables */
|
||||
GB_symbol_map_t *bank_symbols[0x200];
|
||||
GB_symbol_map_t **bank_symbols;
|
||||
size_t n_symbol_maps;
|
||||
GB_reversed_symbol_map_t reversed_symbol_map;
|
||||
|
||||
/* Ticks command */
|
||||
|
@ -746,7 +763,6 @@ struct GB_gameboy_internal_s {
|
|||
const char *undo_label;
|
||||
|
||||
/* Rewind */
|
||||
#define GB_REWIND_FRAMES_PER_KEY 255
|
||||
size_t rewind_buffer_length;
|
||||
struct {
|
||||
uint8_t *key_state;
|
||||
|
@ -774,7 +790,7 @@ struct GB_gameboy_internal_s {
|
|||
bool disable_rendering;
|
||||
uint8_t boot_rom[0x900];
|
||||
bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank
|
||||
uint8_t cycles_since_run; // How many cycles have passed since the last call to GB_run(), in 8MHz units
|
||||
unsigned cycles_since_run; // How many cycles have passed since the last call to GB_run(), in 8MHz units
|
||||
double clock_multiplier;
|
||||
GB_rumble_mode_t rumble_mode;
|
||||
uint32_t rumble_on_cycles;
|
||||
|
@ -784,9 +800,12 @@ struct GB_gameboy_internal_s {
|
|||
bool wx_just_changed;
|
||||
bool tile_sel_glitch;
|
||||
bool disable_oam_corruption; // For safe memory reads
|
||||
bool in_dma_read;
|
||||
bool hdma_in_progress;
|
||||
uint16_t addr_for_hdma_conflict;
|
||||
|
||||
GB_gbs_header_t gbs_header;
|
||||
);
|
||||
)
|
||||
};
|
||||
|
||||
#ifndef GB_INTERNAL
|
||||
|
@ -814,7 +833,7 @@ void GB_reset(GB_gameboy_t *gb);
|
|||
void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model);
|
||||
|
||||
/* Returns the time passed, in 8MHz ticks. */
|
||||
uint8_t GB_run(GB_gameboy_t *gb);
|
||||
unsigned GB_run(GB_gameboy_t *gb);
|
||||
/* Returns the time passed since the last frame, in nanoseconds */
|
||||
uint64_t GB_run_frame(GB_gameboy_t *gb);
|
||||
|
||||
|
|
|
@ -59,7 +59,10 @@ void GB_update_joyp(GB_gameboy_t *gb)
|
|||
/* Todo: This assumes the keys *always* bounce, which is incorrect when emulating an SGB */
|
||||
if (previous_state != (gb->io_registers[GB_IO_JOYP] & 0xF)) {
|
||||
/* The joypad interrupt DOES occur on CGB (Tested on CGB-E), unlike what some documents say. */
|
||||
gb->io_registers[GB_IO_IF] |= 0x10;
|
||||
if (!(gb->io_registers[GB_IO_IF] & 0x10)) {
|
||||
gb->joyp_accessed = true;
|
||||
gb->io_registers[GB_IO_IF] |= 0x10;
|
||||
}
|
||||
}
|
||||
|
||||
gb->io_registers[GB_IO_JOYP] |= 0xC0;
|
||||
|
@ -72,7 +75,10 @@ void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value)
|
|||
gb->io_registers[GB_IO_JOYP] |= value & 0xF;
|
||||
|
||||
if (previous_state & ~(gb->io_registers[GB_IO_JOYP] & 0xF)) {
|
||||
gb->io_registers[GB_IO_IF] |= 0x10;
|
||||
if (!(gb->io_registers[GB_IO_IF] & 0x10)) {
|
||||
gb->joyp_accessed = true;
|
||||
gb->io_registers[GB_IO_IF] |= 0x10;
|
||||
}
|
||||
}
|
||||
gb->io_registers[GB_IO_JOYP] |= 0xC0;
|
||||
}
|
||||
|
|
173
Core/mbc.c
|
@ -5,42 +5,41 @@
|
|||
|
||||
const GB_cartridge_t GB_cart_defs[256] = {
|
||||
// From http://gbdev.gg8.se/wiki/articles/The_Cartridge_Header#0147_-_Cartridge_Type
|
||||
/* MBC SUBTYPE RAM BAT. RTC RUMB. */
|
||||
{ GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 00h ROM ONLY
|
||||
{ GB_MBC1 , GB_STANDARD_MBC, false, false, false, false}, // 01h MBC1
|
||||
{ GB_MBC1 , GB_STANDARD_MBC, true , false, false, false}, // 02h MBC1+RAM
|
||||
{ GB_MBC1 , GB_STANDARD_MBC, true , true , false, false}, // 03h MBC1+RAM+BATTERY
|
||||
/* MBC RAM BAT. RTC RUMB. */
|
||||
{ GB_NO_MBC, false, false, false, false}, // 00h ROM ONLY
|
||||
{ GB_MBC1 , false, false, false, false}, // 01h MBC1
|
||||
{ GB_MBC1 , true , false, false, false}, // 02h MBC1+RAM
|
||||
{ GB_MBC1 , true , true , false, false}, // 03h MBC1+RAM+BATTERY
|
||||
[5] =
|
||||
{ GB_MBC2 , GB_STANDARD_MBC, true , false, false, false}, // 05h MBC2
|
||||
{ GB_MBC2 , GB_STANDARD_MBC, true , true , false, false}, // 06h MBC2+BATTERY
|
||||
{ GB_MBC2 , true , false, false, false}, // 05h MBC2
|
||||
{ GB_MBC2 , true , true , false, false}, // 06h MBC2+BATTERY
|
||||
[8] =
|
||||
{ GB_NO_MBC, GB_STANDARD_MBC, true , false, false, false}, // 08h ROM+RAM
|
||||
{ GB_NO_MBC, GB_STANDARD_MBC, true , true , false, false}, // 09h ROM+RAM+BATTERY
|
||||
{ GB_NO_MBC, true , false, false, false}, // 08h ROM+RAM
|
||||
{ GB_NO_MBC, true , true , false, false}, // 09h ROM+RAM+BATTERY
|
||||
[0xB] =
|
||||
/* Todo: Not supported yet */
|
||||
{ GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Bh MMM01
|
||||
{ GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Ch MMM01+RAM
|
||||
{ GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Dh MMM01+RAM+BATTERY
|
||||
{ GB_MMM01 , false, false, false, false}, // 0Bh MMM01
|
||||
{ GB_MMM01 , true , false, false, false}, // 0Ch MMM01+RAM
|
||||
{ GB_MMM01 , true , true , false, false}, // 0Dh MMM01+RAM+BATTERY
|
||||
[0xF] =
|
||||
{ GB_MBC3 , GB_STANDARD_MBC, false, true, true , false}, // 0Fh MBC3+TIMER+BATTERY
|
||||
{ GB_MBC3 , GB_STANDARD_MBC, true , true, true , false}, // 10h MBC3+TIMER+RAM+BATTERY
|
||||
{ GB_MBC3 , GB_STANDARD_MBC, false, false, false, false}, // 11h MBC3
|
||||
{ GB_MBC3 , GB_STANDARD_MBC, true , false, false, false}, // 12h MBC3+RAM
|
||||
{ GB_MBC3 , GB_STANDARD_MBC, true , true , false, false}, // 13h MBC3+RAM+BATTERY
|
||||
{ GB_MBC3 , false, true, true , false}, // 0Fh MBC3+TIMER+BATTERY
|
||||
{ GB_MBC3 , true , true, true , false}, // 10h MBC3+TIMER+RAM+BATTERY
|
||||
{ GB_MBC3 , false, false, false, false}, // 11h MBC3
|
||||
{ GB_MBC3 , true , false, false, false}, // 12h MBC3+RAM
|
||||
{ GB_MBC3 , true , true , false, false}, // 13h MBC3+RAM+BATTERY
|
||||
[0x19] =
|
||||
{ GB_MBC5 , GB_STANDARD_MBC, false, false, false, false}, // 19h MBC5
|
||||
{ GB_MBC5 , GB_STANDARD_MBC, true , false, false, false}, // 1Ah MBC5+RAM
|
||||
{ GB_MBC5 , GB_STANDARD_MBC, true , true , false, false}, // 1Bh MBC5+RAM+BATTERY
|
||||
{ GB_MBC5 , GB_STANDARD_MBC, false, false, false, true }, // 1Ch MBC5+RUMBLE
|
||||
{ GB_MBC5 , GB_STANDARD_MBC, true , false, false, true }, // 1Dh MBC5+RUMBLE+RAM
|
||||
{ GB_MBC5 , GB_STANDARD_MBC, true , true , false, true }, // 1Eh MBC5+RUMBLE+RAM+BATTERY
|
||||
{ GB_MBC5 , false, false, false, false}, // 19h MBC5
|
||||
{ GB_MBC5 , true , false, false, false}, // 1Ah MBC5+RAM
|
||||
{ GB_MBC5 , true , true , false, false}, // 1Bh MBC5+RAM+BATTERY
|
||||
{ GB_MBC5 , false, false, false, true }, // 1Ch MBC5+RUMBLE
|
||||
{ GB_MBC5 , true , false, false, true }, // 1Dh MBC5+RUMBLE+RAM
|
||||
{ GB_MBC5 , true , true , false, true }, // 1Eh MBC5+RUMBLE+RAM+BATTERY
|
||||
[0x22] =
|
||||
{ GB_MBC7 , GB_STANDARD_MBC, true, true, false, false}, // 22h MBC7+ACCEL+EEPROM
|
||||
{ GB_MBC7 , true, true, false, false}, // 22h MBC7+ACCEL+EEPROM
|
||||
[0xFC] =
|
||||
{ GB_MBC5 , GB_CAMERA , true , true , false, false}, // FCh POCKET CAMERA
|
||||
{ GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // FDh BANDAI TAMA5 (Todo: Not supported)
|
||||
{ GB_HUC3 , GB_STANDARD_MBC, true , true , true, false}, // FEh HuC3
|
||||
{ GB_HUC1 , GB_STANDARD_MBC, true , true , false, false}, // FFh HuC1+RAM+BATTERY
|
||||
{ GB_CAMERA, true , true , false, false}, // FCh POCKET CAMERA
|
||||
{ GB_NO_MBC, false, false, false, false}, // FDh BANDAI TAMA5 (Todo: Not supported)
|
||||
{ GB_HUC3 , true , true , true, false}, // FEh HuC3
|
||||
{ GB_HUC1 , true , true , false, false}, // FFh HuC1+RAM+BATTERY
|
||||
};
|
||||
|
||||
void GB_update_mbc_mappings(GB_gameboy_t *gb)
|
||||
|
@ -97,22 +96,51 @@ void GB_update_mbc_mappings(GB_gameboy_t *gb)
|
|||
}
|
||||
break;
|
||||
case GB_MBC5:
|
||||
case GB_CAMERA:
|
||||
gb->mbc_rom_bank = gb->mbc5.rom_bank_low | (gb->mbc5.rom_bank_high << 8);
|
||||
gb->mbc_ram_bank = gb->mbc5.ram_bank;
|
||||
break;
|
||||
case GB_MBC7:
|
||||
gb->mbc_rom_bank = gb->mbc7.rom_bank;
|
||||
break;
|
||||
case GB_HUC1:
|
||||
if (gb->huc1.mode == 0) {
|
||||
gb->mbc_rom_bank = gb->huc1.bank_low | (gb->mbc1.bank_high << 6);
|
||||
gb->mbc_ram_bank = 0;
|
||||
case GB_MMM01:
|
||||
if (gb->mmm01.locked) {
|
||||
if (gb->mmm01.multiplex_mode) {
|
||||
gb->mbc_rom0_bank = (gb->mmm01.rom_bank_low & (gb->mmm01.rom_bank_mask << 1)) |
|
||||
((gb->mmm01.mbc1_mode? 0 : gb->mmm01.ram_bank_low) << 5) |
|
||||
(gb->mmm01.rom_bank_high << 7);
|
||||
gb->mbc_rom_bank = gb->mmm01.rom_bank_low |
|
||||
(gb->mmm01.ram_bank_low << 5) |
|
||||
(gb->mmm01.rom_bank_high << 7);
|
||||
gb->mbc_ram_bank = gb->mmm01.rom_bank_mid | (gb->mmm01.ram_bank_high << 2);
|
||||
}
|
||||
else {
|
||||
gb->mbc_rom0_bank = (gb->mmm01.rom_bank_low & (gb->mmm01.rom_bank_mask << 1)) |
|
||||
(gb->mmm01.rom_bank_mid << 5) |
|
||||
(gb->mmm01.rom_bank_high << 7);
|
||||
gb->mbc_rom_bank = gb->mmm01.rom_bank_low |
|
||||
(gb->mmm01.rom_bank_mid << 5) |
|
||||
(gb->mmm01.rom_bank_high << 7);
|
||||
if (gb->mmm01.mbc1_mode) {
|
||||
gb->mbc_ram_bank = gb->mmm01.ram_bank_low | (gb->mmm01.ram_bank_high << 2);
|
||||
}
|
||||
else {
|
||||
gb->mbc_ram_bank = gb->mmm01.ram_bank_low | (gb->mmm01.ram_bank_high << 2);
|
||||
}
|
||||
}
|
||||
if (gb->mbc_rom_bank == gb->mbc_rom0_bank) {
|
||||
gb->mbc_rom_bank++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
gb->mbc_rom_bank = gb->huc1.bank_low;
|
||||
gb->mbc_ram_bank = gb->huc1.bank_high;
|
||||
gb->mbc_rom_bank = -1;
|
||||
gb->mbc_rom0_bank = -2;
|
||||
}
|
||||
break;
|
||||
case GB_HUC1:
|
||||
gb->mbc_rom_bank = gb->huc1.bank_low;
|
||||
gb->mbc_ram_bank = gb->huc1.bank_high;
|
||||
break;
|
||||
case GB_HUC3:
|
||||
gb->mbc_rom_bank = gb->huc3.rom_bank;
|
||||
gb->mbc_ram_bank = gb->huc3.ram_bank;
|
||||
|
@ -129,22 +157,44 @@ void GB_update_mbc_mappings(GB_gameboy_t *gb)
|
|||
void GB_configure_cart(GB_gameboy_t *gb)
|
||||
{
|
||||
gb->cartridge_type = &GB_cart_defs[gb->rom[0x147]];
|
||||
if (gb->rom[0x147] == 0xbc &&
|
||||
gb->rom[0x149] == 0xc1 &&
|
||||
gb->rom[0x14a] == 0x65) {
|
||||
static const GB_cartridge_t tpp1 = {GB_TPP1, GB_STANDARD_MBC, true, true, true, true};
|
||||
if (gb->cartridge_type->mbc_type == GB_MMM01) {
|
||||
uint8_t *temp = malloc(0x8000);
|
||||
memcpy(temp, gb->rom, 0x8000);
|
||||
memmove(gb->rom, gb->rom + 0x8000, gb->rom_size - 0x8000);
|
||||
memcpy(gb->rom + gb->rom_size - 0x8000, temp, 0x8000);
|
||||
free(temp);
|
||||
}
|
||||
else {
|
||||
const GB_cartridge_t *maybe_mmm01_type = &GB_cart_defs[gb->rom[gb->rom_size - 0x8000 + 0x147]];
|
||||
if (memcmp(gb->rom + 0x104, gb->rom + gb->rom_size - 0x8000 + 0x104, 0x30) == 0) {
|
||||
if (maybe_mmm01_type->mbc_type == GB_MMM01) {
|
||||
gb->cartridge_type = maybe_mmm01_type;
|
||||
}
|
||||
else if(gb->rom[gb->rom_size - 0x8000 + 0x147] == 0x11) {
|
||||
GB_log(gb, "ROM header reports MBC3, but it appears to be an MMM01 ROM. Assuming cartridge uses MMM01.");
|
||||
gb->cartridge_type = &GB_cart_defs[0xB];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gb->rom[0x147] == 0xBC &&
|
||||
gb->rom[0x149] == 0xC1 &&
|
||||
gb->rom[0x14A] == 0x65) {
|
||||
static const GB_cartridge_t tpp1 = {GB_TPP1, true, true, true, true};
|
||||
gb->cartridge_type = &tpp1;
|
||||
gb->tpp1.rom_bank = 1;
|
||||
}
|
||||
|
||||
if (gb->rom[0x147] == 0 && gb->rom_size > 0x8000) {
|
||||
GB_log(gb, "ROM header reports no MBC, but file size is over 32Kb. Assuming cartridge uses MBC3.\n");
|
||||
gb->cartridge_type = &GB_cart_defs[0x11];
|
||||
if (gb->cartridge_type->mbc_type != GB_MMM01) {
|
||||
if (gb->rom[0x147] == 0 && gb->rom_size > 0x8000) {
|
||||
GB_log(gb, "ROM header reports no MBC, but file size is over 32Kb. Assuming cartridge uses MBC3.\n");
|
||||
gb->cartridge_type = &GB_cart_defs[0x11];
|
||||
}
|
||||
else if (gb->rom[0x147] != 0 && memcmp(gb->cartridge_type, &GB_cart_defs[0], sizeof(GB_cart_defs[0])) == 0) {
|
||||
GB_log(gb, "Cartridge type %02x is not yet supported.\n", gb->rom[0x147]);
|
||||
}
|
||||
}
|
||||
else if (gb->rom[0x147] != 0 && memcmp(gb->cartridge_type, &GB_cart_defs[0], sizeof(GB_cart_defs[0])) == 0) {
|
||||
GB_log(gb, "Cartridge type %02x is not yet supported.\n", gb->rom[0x147]);
|
||||
}
|
||||
|
||||
|
||||
if (gb->mbc_ram) {
|
||||
free(gb->mbc_ram);
|
||||
gb->mbc_ram = NULL;
|
||||
|
@ -165,7 +215,12 @@ void GB_configure_cart(GB_gameboy_t *gb)
|
|||
}
|
||||
else {
|
||||
static const unsigned ram_sizes[256] = {0, 0x800, 0x2000, 0x8000, 0x20000, 0x10000};
|
||||
gb->mbc_ram_size = ram_sizes[gb->rom[0x149]];
|
||||
if (gb->cartridge_type->mbc_type == GB_MMM01) {
|
||||
gb->mbc_ram_size = ram_sizes[gb->rom[gb->rom_size - 0x8000 + 0x149]];
|
||||
}
|
||||
else {
|
||||
gb->mbc_ram_size = ram_sizes[gb->rom[0x149]];
|
||||
}
|
||||
}
|
||||
|
||||
if (gb->mbc_ram_size) {
|
||||
|
@ -193,16 +248,28 @@ void GB_configure_cart(GB_gameboy_t *gb)
|
|||
}
|
||||
}
|
||||
|
||||
/* Set MBC5's bank to 1 correctly */
|
||||
if (gb->cartridge_type->mbc_type == GB_MBC5) {
|
||||
gb->mbc5.rom_bank_low = 1;
|
||||
GB_reset_mbc(gb);
|
||||
}
|
||||
|
||||
void GB_reset_mbc(GB_gameboy_t *gb)
|
||||
{
|
||||
if (gb->cartridge_type->mbc_type == GB_MMM01) {
|
||||
gb->mbc_rom_bank = -1;
|
||||
gb->mbc_rom0_bank = -2;
|
||||
gb->mmm01.ram_bank_mask = -1;
|
||||
}
|
||||
|
||||
/* Initial MBC7 state */
|
||||
if (gb->cartridge_type->mbc_type == GB_MBC7) {
|
||||
else if (gb->cartridge_type->mbc_type == GB_MBC5 ||
|
||||
gb->cartridge_type->mbc_type == GB_CAMERA) {
|
||||
gb->mbc5.rom_bank_low = 1;
|
||||
gb->mbc_rom_bank = 1;
|
||||
}
|
||||
else if (gb->cartridge_type->mbc_type == GB_MBC7) {
|
||||
gb->mbc7.x_latch = gb->mbc7.y_latch = 0x8000;
|
||||
gb->mbc7.latch_ready = true;
|
||||
gb->mbc7.read_bits = -1;
|
||||
gb->mbc7.eeprom_do = true;
|
||||
}
|
||||
else {
|
||||
gb->mbc_rom_bank = 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,14 +11,12 @@ typedef struct {
|
|||
GB_MBC3,
|
||||
GB_MBC5,
|
||||
GB_MBC7,
|
||||
GB_MMM01,
|
||||
GB_HUC1,
|
||||
GB_HUC3,
|
||||
GB_TPP1,
|
||||
} mbc_type;
|
||||
enum {
|
||||
GB_STANDARD_MBC,
|
||||
GB_CAMERA,
|
||||
} mbc_subtype;
|
||||
} mbc_type;
|
||||
bool has_ram;
|
||||
bool has_battery;
|
||||
bool has_rtc;
|
||||
|
@ -26,9 +24,10 @@ typedef struct {
|
|||
} GB_cartridge_t;
|
||||
|
||||
#ifdef GB_INTERNAL
|
||||
extern const GB_cartridge_t GB_cart_defs[256];
|
||||
internal extern const GB_cartridge_t GB_cart_defs[256];
|
||||
internal void GB_update_mbc_mappings(GB_gameboy_t *gb);
|
||||
internal void GB_configure_cart(GB_gameboy_t *gb);
|
||||
internal void GB_reset_mbc(GB_gameboy_t *gb);
|
||||
#endif
|
||||
|
||||
#endif /* MBC_h */
|
||||
|
|
516
Core/memory.c
|
@ -95,7 +95,7 @@ void GB_trigger_oam_bug(GB_gameboy_t *gb, uint16_t address)
|
|||
|
||||
if (address >= 0xFE00 && address < 0xFF00) {
|
||||
GB_display_sync(gb);
|
||||
if (gb->accessed_oam_row != 0xff && gb->accessed_oam_row >= 8) {
|
||||
if (gb->accessed_oam_row != 0xFF && gb->accessed_oam_row >= 8) {
|
||||
uint16_t *base = (uint16_t *)(gb->oam + gb->accessed_oam_row);
|
||||
base[0] = bitwise_glitch(base[0],
|
||||
base[-4],
|
||||
|
@ -197,7 +197,7 @@ void GB_trigger_oam_bug_read(GB_gameboy_t *gb, uint16_t address)
|
|||
if (GB_is_cgb(gb)) return;
|
||||
|
||||
if (address >= 0xFE00 && address < 0xFF00) {
|
||||
if (gb->accessed_oam_row != 0xff && gb->accessed_oam_row >= 8) {
|
||||
if (gb->accessed_oam_row != 0xFF && gb->accessed_oam_row >= 8) {
|
||||
if ((gb->accessed_oam_row & 0x18) == 0x10) {
|
||||
oam_bug_secondary_read_corruption(gb);
|
||||
}
|
||||
|
@ -251,15 +251,16 @@ void GB_trigger_oam_bug_read(GB_gameboy_t *gb, uint16_t address)
|
|||
|
||||
static bool is_addr_in_dma_use(GB_gameboy_t *gb, uint16_t addr)
|
||||
{
|
||||
if (!gb->dma_steps_left || (gb->dma_cycles < 0 && !gb->is_dma_restarting) || addr >= 0xfe00) return false;
|
||||
if (addr >= 0xfe00) return false;
|
||||
if (!GB_is_dma_active(gb) || addr >= 0xFE00 || gb->hdma_in_progress) return false;
|
||||
if (gb->dma_current_dest == 0xFF || gb->dma_current_dest == 0x0) return false; // Warm up
|
||||
if (addr >= 0xFE00) return false;
|
||||
if (gb->dma_current_src == addr) return false; // Shortcut for DMA access flow
|
||||
if (gb->dma_current_src > 0xe000 && (gb->dma_current_src & ~0x2000) == addr) return false;
|
||||
if (gb->dma_current_src >= 0xE000 && (gb->dma_current_src & ~0x2000) == addr) return false;
|
||||
if (GB_is_cgb(gb)) {
|
||||
if (addr >= 0xe000) {
|
||||
if (addr >= 0xC000) {
|
||||
return bus_for_addr(gb, gb->dma_current_src) != GB_BUS_VRAM;
|
||||
}
|
||||
if (gb->dma_current_src >= 0xe000) {
|
||||
if (gb->dma_current_src >= 0xE000) {
|
||||
return bus_for_addr(gb, addr) != GB_BUS_VRAM;
|
||||
}
|
||||
}
|
||||
|
@ -291,8 +292,18 @@ static uint8_t read_mbc_rom(GB_gameboy_t *gb, uint16_t addr)
|
|||
|
||||
static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr)
|
||||
{
|
||||
GB_display_sync(gb);
|
||||
if (unlikely(gb->vram_read_blocked)) {
|
||||
if (likely(!GB_is_dma_active(gb))) {
|
||||
/* Prevent syncing from a DMA read. Batching doesn't happen during DMA anyway. */
|
||||
GB_display_sync(gb);
|
||||
}
|
||||
else {
|
||||
if ((gb->dma_current_dest & 0xE000) == 0x8000) {
|
||||
// TODO: verify conflict behavior
|
||||
return gb->vram[(addr & 0x1FFF) + (gb->cgb_vram_bank? 0x2000 : 0)];
|
||||
}
|
||||
}
|
||||
|
||||
if (unlikely(gb->vram_read_blocked && !gb->in_dma_read)) {
|
||||
return 0xFF;
|
||||
}
|
||||
if (unlikely(gb->display_state == 22 && GB_is_cgb(gb) && !gb->cgb_double_speed)) {
|
||||
|
@ -372,14 +383,14 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr)
|
|||
}
|
||||
}
|
||||
else if ((!gb->mbc_ram_enable) &&
|
||||
gb->cartridge_type->mbc_subtype != GB_CAMERA &&
|
||||
gb->cartridge_type->mbc_type != GB_CAMERA &&
|
||||
gb->cartridge_type->mbc_type != GB_HUC1 &&
|
||||
gb->cartridge_type->mbc_type != GB_HUC3) {
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) {
|
||||
return 0xc0 | gb->effective_ir_input;
|
||||
return 0xC0 | gb->effective_ir_input;
|
||||
}
|
||||
|
||||
if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type != GB_HUC3 &&
|
||||
|
@ -403,8 +414,8 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr)
|
|||
return 0xFF;
|
||||
}
|
||||
|
||||
if (gb->cartridge_type->mbc_subtype == GB_CAMERA && gb->mbc_ram_bank == 0 && addr >= 0xa100 && addr < 0xaf00) {
|
||||
return GB_camera_read_image(gb, addr - 0xa100);
|
||||
if (gb->cartridge_type->mbc_type == GB_CAMERA && gb->mbc_ram_bank == 0 && addr >= 0xA100 && addr < 0xAF00) {
|
||||
return GB_camera_read_image(gb, addr - 0xA100);
|
||||
}
|
||||
|
||||
uint8_t effective_bank = gb->mbc_ram_bank;
|
||||
|
@ -462,13 +473,45 @@ static inline void sync_ppu_if_needed(GB_gameboy_t *gb, uint8_t register_accesse
|
|||
}
|
||||
}
|
||||
|
||||
static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
|
||||
internal uint8_t GB_read_oam(GB_gameboy_t *gb, uint8_t addr)
|
||||
{
|
||||
|
||||
if (gb->hdma_on) {
|
||||
return gb->last_opcode_read;
|
||||
if (addr < 0xA0) {
|
||||
return gb->oam[addr];
|
||||
}
|
||||
|
||||
switch (gb->model) {
|
||||
case GB_MODEL_CGB_E:
|
||||
case GB_MODEL_AGB_A:
|
||||
return (addr & 0xF0) | (addr >> 4);
|
||||
|
||||
case GB_MODEL_CGB_D:
|
||||
if (addr >= 0xC0) {
|
||||
addr |= 0xF0;
|
||||
}
|
||||
return gb->extra_oam[addr - 0xA0];
|
||||
|
||||
case GB_MODEL_CGB_C:
|
||||
case GB_MODEL_CGB_B:
|
||||
case GB_MODEL_CGB_A:
|
||||
case GB_MODEL_CGB_0:
|
||||
addr &= ~0x18;
|
||||
return gb->extra_oam[addr - 0xA0];
|
||||
|
||||
case GB_MODEL_DMG_B:
|
||||
case GB_MODEL_MGB:
|
||||
case GB_MODEL_SGB_NTSC:
|
||||
case GB_MODEL_SGB_PAL:
|
||||
case GB_MODEL_SGB_NTSC_NO_SFC:
|
||||
case GB_MODEL_SGB_PAL_NO_SFC:
|
||||
case GB_MODEL_SGB2:
|
||||
case GB_MODEL_SGB2_NO_SFC:
|
||||
return 0;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
|
||||
{
|
||||
if (addr < 0xFE00) {
|
||||
return read_banked_ram(gb, addr);
|
||||
}
|
||||
|
@ -479,12 +522,12 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
|
|||
if (!gb->disable_oam_corruption) {
|
||||
GB_trigger_oam_bug_read(gb, addr);
|
||||
}
|
||||
return 0xff;
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
if ((gb->dma_steps_left && (gb->dma_cycles > 0 || gb->is_dma_restarting))) {
|
||||
if (GB_is_dma_active(gb)) {
|
||||
/* Todo: Does reading from OAM during DMA causes the OAM bug? */
|
||||
return 0xff;
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
if (gb->oam_read_blocked) {
|
||||
|
@ -492,20 +535,20 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
|
|||
if (addr < 0xFEA0) {
|
||||
uint16_t *oam = (uint16_t *)gb->oam;
|
||||
if (gb->accessed_oam_row == 0) {
|
||||
oam[(addr & 0xf8) >> 1] =
|
||||
oam[(addr & 0xF8) >> 1] =
|
||||
oam[0] = bitwise_glitch_read(oam[0],
|
||||
oam[(addr & 0xf8) >> 1],
|
||||
oam[(addr & 0xff) >> 1]);
|
||||
oam[(addr & 0xF8) >> 1],
|
||||
oam[(addr & 0xFF) >> 1]);
|
||||
|
||||
for (unsigned i = 2; i < 8; i++) {
|
||||
gb->oam[i] = gb->oam[(addr & 0xf8) + i];
|
||||
gb->oam[i] = gb->oam[(addr & 0xF8) + i];
|
||||
}
|
||||
}
|
||||
else if (gb->accessed_oam_row == 0xa0) {
|
||||
else if (gb->accessed_oam_row == 0xA0) {
|
||||
uint8_t target = (addr & 7) | 0x98;
|
||||
uint16_t a = oam[0x9c >> 1],
|
||||
uint16_t a = oam[0x9C >> 1],
|
||||
b = oam[target >> 1],
|
||||
c = oam[(addr & 0xf8) >> 1];
|
||||
c = oam[(addr & 0xF8) >> 1];
|
||||
switch (addr & 7) {
|
||||
case 0:
|
||||
case 1:
|
||||
|
@ -520,7 +563,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
|
|||
case 2:
|
||||
case 3: {
|
||||
/* Probably instance specific */
|
||||
c = oam[(addr & 0xfe) >> 1];
|
||||
c = oam[(addr & 0xFE) >> 1];
|
||||
|
||||
// MGB only: oam[target >> 1] = bitwise_glitch_read(a, b, c);
|
||||
oam[target >> 1] = (a & b) | (a & c) | (b & c);
|
||||
|
@ -538,54 +581,15 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
|
|||
}
|
||||
|
||||
for (unsigned i = 0; i < 8; i++) {
|
||||
gb->oam[(addr & 0xf8) + i] = gb->oam[0x98 + i];
|
||||
gb->oam[(addr & 0xF8) + i] = gb->oam[0x98 + i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
if (addr < 0xFEA0) {
|
||||
return gb->oam[addr & 0xFF];
|
||||
}
|
||||
|
||||
if (gb->oam_read_blocked) {
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
switch (gb->model) {
|
||||
case GB_MODEL_CGB_E:
|
||||
case GB_MODEL_AGB:
|
||||
return (addr & 0xF0) | ((addr >> 4) & 0xF);
|
||||
|
||||
case GB_MODEL_CGB_D:
|
||||
if (addr > 0xfec0) {
|
||||
addr |= 0xf0;
|
||||
}
|
||||
return gb->extra_oam[addr - 0xfea0];
|
||||
|
||||
case GB_MODEL_CGB_C:
|
||||
case GB_MODEL_CGB_B:
|
||||
case GB_MODEL_CGB_A:
|
||||
case GB_MODEL_CGB_0:
|
||||
addr &= ~0x18;
|
||||
return gb->extra_oam[addr - 0xfea0];
|
||||
|
||||
case GB_MODEL_DMG_B:
|
||||
case GB_MODEL_MGB:
|
||||
case GB_MODEL_SGB_NTSC:
|
||||
case GB_MODEL_SGB_PAL:
|
||||
case GB_MODEL_SGB_NTSC_NO_SFC:
|
||||
case GB_MODEL_SGB_PAL_NO_SFC:
|
||||
case GB_MODEL_SGB2:
|
||||
case GB_MODEL_SGB2_NO_SFC:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (addr < 0xFF00) {
|
||||
return 0;
|
||||
return GB_read_oam(gb, addr);
|
||||
}
|
||||
|
||||
if (addr < 0xFF80) {
|
||||
|
@ -688,7 +692,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
|
|||
if (gb->model != GB_MODEL_CGB_E) {
|
||||
ret |= 0x10;
|
||||
}
|
||||
if (((gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 && gb->effective_ir_input) && gb->model != GB_MODEL_AGB) {
|
||||
if (((gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 && gb->effective_ir_input) && gb->model <= GB_MODEL_CGB_E) {
|
||||
ret &= ~2;
|
||||
}
|
||||
return ret;
|
||||
|
@ -739,21 +743,21 @@ uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr)
|
|||
GB_debugger_test_read_watchpoint(gb, addr);
|
||||
}
|
||||
if (unlikely(is_addr_in_dma_use(gb, addr))) {
|
||||
if (GB_is_cgb(gb) && bus_for_addr(gb, addr) == GB_BUS_MAIN && gb->dma_current_src >= 0xe000) {
|
||||
if (GB_is_cgb(gb) && bus_for_addr(gb, addr) == GB_BUS_MAIN && gb->dma_current_src >= 0xE000) {
|
||||
/* This is cart specific! Everdrive 7X on a CGB-A or 0 behaves differently. */
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
if (GB_is_cgb(gb) && bus_for_addr(gb, gb->dma_current_src) != GB_BUS_RAM && addr >= 0xc000) {
|
||||
if (GB_is_cgb(gb) && bus_for_addr(gb, gb->dma_current_src) != GB_BUS_RAM && addr >= 0xC000) {
|
||||
// TODO: this should probably affect the DMA dest as well
|
||||
addr = (gb->dma_current_src & 0x1000) | (addr & 0xFFF) | 0xC000;
|
||||
addr = ((gb->dma_current_src - 1) & 0x1000) | (addr & 0xFFF) | 0xC000;
|
||||
}
|
||||
else if (GB_is_cgb(gb) && gb->dma_current_src >= 0xe000 && addr > 0xc000) {
|
||||
else if (GB_is_cgb(gb) && gb->dma_current_src >= 0xE000 && addr >= 0xC000) {
|
||||
// TODO: this should probably affect the DMA dest as well
|
||||
addr = (gb->dma_current_src & 0x1000) | (addr & 0xFFF) | 0xC000;
|
||||
addr = ((gb->dma_current_src - 1) & 0x1000) | (addr & 0xFFF) | 0xC000;
|
||||
}
|
||||
else {
|
||||
addr = gb->dma_current_src;
|
||||
addr = (gb->dma_current_src - 1);
|
||||
}
|
||||
}
|
||||
uint8_t data = read_map[addr >> 12](gb, addr);
|
||||
|
@ -823,7 +827,16 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
value &= 7;
|
||||
}
|
||||
gb->mbc5.ram_bank = value;
|
||||
gb->camera_registers_mapped = (value & 0x10) && gb->cartridge_type->mbc_subtype == GB_CAMERA;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case GB_CAMERA:
|
||||
switch (addr & 0xF000) {
|
||||
case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break;
|
||||
case 0x2000: case 0x3000: gb->mbc5.rom_bank_low = value; break;
|
||||
case 0x4000: case 0x5000:
|
||||
gb->mbc5.ram_bank = value;
|
||||
gb->camera_registers_mapped = (value & 0x10);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
@ -834,12 +847,46 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
case 0x4000: case 0x5000: gb->mbc7.secondary_ram_enable = value == 0x40; break;
|
||||
}
|
||||
break;
|
||||
case GB_MMM01:
|
||||
switch (addr & 0xF000) {
|
||||
case 0x0000: case 0x1000:
|
||||
gb->mbc_ram_enable = (value & 0xF) == 0xA;
|
||||
if (!gb->mmm01.locked) {
|
||||
gb->mmm01.ram_bank_mask = value >> 4;
|
||||
gb->mmm01.locked = value & 0x40;
|
||||
}
|
||||
break;
|
||||
case 0x2000: case 0x3000:
|
||||
if (!gb->mmm01.locked) {
|
||||
gb->mmm01.rom_bank_mid = value >> 5;
|
||||
}
|
||||
gb->mmm01.rom_bank_low &= (gb->mmm01.rom_bank_mask << 1);
|
||||
gb->mmm01.rom_bank_low |= ~(gb->mmm01.rom_bank_mask << 1) & value;
|
||||
break;
|
||||
case 0x4000: case 0x5000:
|
||||
gb->mmm01.ram_bank_low = value | ~gb->mmm01.ram_bank_mask;
|
||||
if (!gb->mmm01.locked) {
|
||||
gb->mmm01.ram_bank_high = value >> 2;
|
||||
gb->mmm01.rom_bank_high = value >> 4;
|
||||
gb->mmm01.mbc1_mode_disable = value & 0x40;
|
||||
}
|
||||
break;
|
||||
case 0x6000: case 0x7000:
|
||||
if (!gb->mmm01.mbc1_mode_disable) {
|
||||
gb->mmm01.mbc1_mode = (value & 1);
|
||||
}
|
||||
if (!gb->mmm01.locked) {
|
||||
gb->mmm01.rom_bank_mask = value >> 2;
|
||||
gb->mmm01.multiplex_mode = value & 0x40;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case GB_HUC1:
|
||||
switch (addr & 0xF000) {
|
||||
case 0x0000: case 0x1000: gb->huc1.ir_mode = (value & 0xF) == 0xE; break;
|
||||
case 0x2000: case 0x3000: gb->huc1.bank_low = value; break;
|
||||
case 0x4000: case 0x5000: gb->huc1.bank_high = value; break;
|
||||
case 0x6000: case 0x7000: gb->huc1.mode = value; break;
|
||||
}
|
||||
break;
|
||||
case GB_HUC3:
|
||||
|
@ -941,15 +988,15 @@ static bool huc3_write(GB_gameboy_t *gb, uint8_t value)
|
|||
gb->huc3.days &= ~(0xF << ((gb->huc3.access_index - 3) * 4));
|
||||
gb->huc3.days |= ((value & 0xF) << ((gb->huc3.access_index - 3) * 4));
|
||||
}
|
||||
else if (gb->huc3.access_index >= 0x58 && gb->huc3.access_index <= 0x5a) {
|
||||
else if (gb->huc3.access_index >= 0x58 && gb->huc3.access_index <= 0x5A) {
|
||||
gb->huc3.alarm_minutes &= ~(0xF << ((gb->huc3.access_index - 0x58) * 4));
|
||||
gb->huc3.alarm_minutes |= ((value & 0xF) << ((gb->huc3.access_index - 0x58) * 4));
|
||||
}
|
||||
else if (gb->huc3.access_index >= 0x5b && gb->huc3.access_index <= 0x5e) {
|
||||
gb->huc3.alarm_days &= ~(0xF << ((gb->huc3.access_index - 0x5b) * 4));
|
||||
gb->huc3.alarm_days |= ((value & 0xF) << ((gb->huc3.access_index - 0x5b) * 4));
|
||||
else if (gb->huc3.access_index >= 0x5B && gb->huc3.access_index <= 0x5E) {
|
||||
gb->huc3.alarm_days &= ~(0xF << ((gb->huc3.access_index - 0x5B) * 4));
|
||||
gb->huc3.alarm_days |= ((value & 0xF) << ((gb->huc3.access_index - 0x5B) * 4));
|
||||
}
|
||||
else if (gb->huc3.access_index == 0x5f) {
|
||||
else if (gb->huc3.access_index == 0x5F) {
|
||||
gb->huc3.alarm_enabled = value & 1;
|
||||
}
|
||||
else {
|
||||
|
@ -1024,7 +1071,7 @@ static void write_mbc7_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
gb->mbc7.eeprom_do = gb->mbc7.read_bits >> 15;
|
||||
gb->mbc7.read_bits <<= 1;
|
||||
gb->mbc7.read_bits |= 1;
|
||||
if (gb->mbc7.bits_countdown == 0) {
|
||||
if (gb->mbc7.argument_bits_left == 0) {
|
||||
/* Not transferring extra bits for a command*/
|
||||
gb->mbc7.eeprom_command <<= 1;
|
||||
gb->mbc7.eeprom_command |= gb->mbc7.eeprom_di;
|
||||
|
@ -1055,7 +1102,7 @@ static void write_mbc7_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
if (gb->mbc7.eeprom_write_enabled) {
|
||||
((uint16_t *)gb->mbc_ram)[gb->mbc7.eeprom_command & 0x7F] = 0;
|
||||
}
|
||||
gb->mbc7.bits_countdown = 16;
|
||||
gb->mbc7.argument_bits_left = 16;
|
||||
// We still need to process this command, don't erase eeprom_command
|
||||
break;
|
||||
case 0xC:
|
||||
|
@ -1083,7 +1130,7 @@ static void write_mbc7_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
if (gb->mbc7.eeprom_write_enabled) {
|
||||
memset(gb->mbc_ram, 0, gb->mbc_ram_size);
|
||||
}
|
||||
gb->mbc7.bits_countdown = 16;
|
||||
gb->mbc7.argument_bits_left = 16;
|
||||
// We still need to process this command, don't erase eeprom_command
|
||||
break;
|
||||
}
|
||||
|
@ -1091,10 +1138,10 @@ static void write_mbc7_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
}
|
||||
else {
|
||||
// We're shifting in extra bits for a WRITE/WRAL command
|
||||
gb->mbc7.bits_countdown--;
|
||||
gb->mbc7.argument_bits_left--;
|
||||
gb->mbc7.eeprom_do = true;
|
||||
if (gb->mbc7.eeprom_di) {
|
||||
uint16_t bit = LE16(1 << gb->mbc7.bits_countdown);
|
||||
uint16_t bit = LE16(1 << gb->mbc7.argument_bits_left);
|
||||
if (gb->mbc7.eeprom_command & 0x100) {
|
||||
// WRITE
|
||||
((uint16_t *)gb->mbc_ram)[gb->mbc7.eeprom_command & 0x7F] |= bit;
|
||||
|
@ -1106,7 +1153,7 @@ static void write_mbc7_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
}
|
||||
}
|
||||
}
|
||||
if (gb->mbc7.bits_countdown == 0) { // We're done
|
||||
if (gb->mbc7.argument_bits_left == 0) { // We're done
|
||||
gb->mbc7.eeprom_command = 0;
|
||||
gb->mbc7.read_bits = (gb->mbc7.eeprom_command & 0x100)? 0xFF : 0x3FFF; // Emulate some time to settle
|
||||
}
|
||||
|
@ -1194,6 +1241,40 @@ static void write_banked_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
gb->ram[(addr & 0x0FFF) + gb->cgb_ram_bank * 0x1000] = value;
|
||||
}
|
||||
|
||||
static void write_oam(GB_gameboy_t *gb, uint8_t addr, uint8_t value)
|
||||
{
|
||||
if (addr < 0xA0) {
|
||||
gb->oam[addr] = value;
|
||||
return;
|
||||
}
|
||||
switch (gb->model) {
|
||||
case GB_MODEL_CGB_D:
|
||||
if (addr >= 0xC0) {
|
||||
addr |= 0xF0;
|
||||
}
|
||||
gb->extra_oam[addr - 0xA0] = value;
|
||||
break;
|
||||
case GB_MODEL_CGB_C:
|
||||
case GB_MODEL_CGB_B:
|
||||
case GB_MODEL_CGB_A:
|
||||
case GB_MODEL_CGB_0:
|
||||
addr &= ~0x18;
|
||||
gb->extra_oam[addr - 0xA0] = value;
|
||||
break;
|
||||
case GB_MODEL_CGB_E:
|
||||
case GB_MODEL_AGB_A:
|
||||
case GB_MODEL_DMG_B:
|
||||
case GB_MODEL_MGB:
|
||||
case GB_MODEL_SGB_NTSC:
|
||||
case GB_MODEL_SGB_PAL:
|
||||
case GB_MODEL_SGB_NTSC_NO_SFC:
|
||||
case GB_MODEL_SGB_PAL_NO_SFC:
|
||||
case GB_MODEL_SGB2:
|
||||
case GB_MODEL_SGB2_NO_SFC:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
||||
{
|
||||
if (addr < 0xFE00) {
|
||||
|
@ -1209,54 +1290,24 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
return;
|
||||
}
|
||||
|
||||
if ((gb->dma_steps_left && (gb->dma_cycles > 0 || gb->is_dma_restarting))) {
|
||||
if (GB_is_dma_active(gb)) {
|
||||
/* Todo: Does writing to OAM during DMA causes the OAM bug? */
|
||||
return;
|
||||
}
|
||||
|
||||
if (GB_is_cgb(gb)) {
|
||||
if (addr < 0xFEA0) {
|
||||
gb->oam[addr & 0xFF] = value;
|
||||
return;
|
||||
}
|
||||
switch (gb->model) {
|
||||
case GB_MODEL_CGB_D:
|
||||
if (addr > 0xfec0) {
|
||||
addr |= 0xf0;
|
||||
}
|
||||
gb->extra_oam[addr - 0xfea0] = value;
|
||||
break;
|
||||
case GB_MODEL_CGB_C:
|
||||
case GB_MODEL_CGB_B:
|
||||
case GB_MODEL_CGB_A:
|
||||
case GB_MODEL_CGB_0:
|
||||
addr &= ~0x18;
|
||||
gb->extra_oam[addr - 0xfea0] = value;
|
||||
break;
|
||||
case GB_MODEL_CGB_E:
|
||||
case GB_MODEL_AGB:
|
||||
break;
|
||||
case GB_MODEL_DMG_B:
|
||||
case GB_MODEL_MGB:
|
||||
case GB_MODEL_SGB_NTSC:
|
||||
case GB_MODEL_SGB_PAL:
|
||||
case GB_MODEL_SGB_NTSC_NO_SFC:
|
||||
case GB_MODEL_SGB_PAL_NO_SFC:
|
||||
case GB_MODEL_SGB2:
|
||||
case GB_MODEL_SGB2_NO_SFC:
|
||||
unreachable();
|
||||
}
|
||||
write_oam(gb, addr, value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (addr < 0xFEA0) {
|
||||
if (gb->accessed_oam_row == 0xa0) {
|
||||
if (gb->accessed_oam_row == 0xA0) {
|
||||
for (unsigned i = 0; i < 8; i++) {
|
||||
if ((i & 6) != (addr & 6)) {
|
||||
gb->oam[(addr & 0xf8) + i] = gb->oam[0x98 + i];
|
||||
gb->oam[(addr & 0xF8) + i] = gb->oam[0x98 + i];
|
||||
}
|
||||
else {
|
||||
gb->oam[(addr & 0xf8) + i] = bitwise_glitch(gb->oam[(addr & 0xf8) + i], gb->oam[0x9c], gb->oam[0x98 + i]);
|
||||
gb->oam[(addr & 0xF8) + i] = bitwise_glitch(gb->oam[(addr & 0xF8) + i], gb->oam[0x9C], gb->oam[0x98 + i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1265,13 +1316,13 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
|
||||
if (gb->accessed_oam_row == 0) {
|
||||
gb->oam[0] = bitwise_glitch(gb->oam[0],
|
||||
gb->oam[(addr & 0xf8)],
|
||||
gb->oam[(addr & 0xfe)]);
|
||||
gb->oam[(addr & 0xF8)],
|
||||
gb->oam[(addr & 0xFE)]);
|
||||
gb->oam[1] = bitwise_glitch(gb->oam[1],
|
||||
gb->oam[(addr & 0xf8) + 1],
|
||||
gb->oam[(addr & 0xfe) | 1]);
|
||||
gb->oam[(addr & 0xF8) + 1],
|
||||
gb->oam[(addr & 0xFE) | 1]);
|
||||
for (unsigned i = 2; i < 8; i++) {
|
||||
gb->oam[i] = gb->oam[(addr & 0xf8) + i];
|
||||
gb->oam[i] = gb->oam[(addr & 0xF8) + i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1370,24 +1421,17 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
|
||||
case GB_IO_LCDC:
|
||||
if ((value & 0x80) && !(gb->io_registers[GB_IO_LCDC] & 0x80)) {
|
||||
if (value & 0x80) {
|
||||
// LCD turned on
|
||||
if (!gb->lcd_disabled_outside_of_vblank &&
|
||||
(gb->cycles_since_vblank_callback > 10 * 456 || GB_is_sgb(gb))) {
|
||||
// Trigger a vblank here so we don't exceed LCDC_PERIOD
|
||||
GB_display_vblank(gb);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// LCD turned off
|
||||
if (gb->current_line < 144) {
|
||||
// ROM might be repeatedly disabling LCDC outside of vblank, avoid callback spam
|
||||
gb->lcd_disabled_outside_of_vblank = true;
|
||||
}
|
||||
// LCD turned on
|
||||
if (!gb->lcd_disabled_outside_of_vblank &&
|
||||
(gb->cycles_since_vblank_callback > 10 * 456 || GB_is_sgb(gb))) {
|
||||
// Trigger a vblank here so we don't exceed LCDC_PERIOD
|
||||
GB_display_vblank(gb, GB_VBLANK_TYPE_ARTIFICIAL);
|
||||
}
|
||||
|
||||
gb->display_cycles = 0;
|
||||
gb->display_state = 0;
|
||||
gb->double_speed_alignment = 0;
|
||||
gb->cycles_for_line = 0;
|
||||
if (GB_is_sgb(gb)) {
|
||||
gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED;
|
||||
}
|
||||
|
@ -1459,19 +1503,12 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
return;
|
||||
|
||||
case GB_IO_DMA:
|
||||
if (gb->dma_steps_left) {
|
||||
/* This is not correct emulation, since we're not really delaying the second DMA.
|
||||
One write that should have happened in the first DMA will not happen. However,
|
||||
since that byte will be overwritten by the second DMA before it can actually be
|
||||
read, it doesn't actually matter. */
|
||||
gb->is_dma_restarting = true;
|
||||
}
|
||||
gb->dma_and_pattern = 0xFF;
|
||||
gb->dma_cycles = -7;
|
||||
gb->dma_current_dest = 0;
|
||||
gb->dma_cycles = 0;
|
||||
gb->dma_cycles_modulo = 2;
|
||||
gb->dma_current_dest = 0xFF;
|
||||
gb->dma_current_src = value << 8;
|
||||
gb->dma_steps_left = 0xa0;
|
||||
gb->io_registers[GB_IO_DMA] = value;
|
||||
GB_STAT_update(gb);
|
||||
return;
|
||||
case GB_IO_SVBK:
|
||||
if (gb->cgb_mode || (GB_is_cgb(gb) && !gb->boot_rom_finished)) {
|
||||
|
@ -1531,6 +1568,10 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
gb->hdma_current_src &= 0xF0;
|
||||
gb->hdma_current_src |= value << 8;
|
||||
}
|
||||
/* Range 0xE*** like 0xF*** and can't overflow (with 0x800 bytes) to anything meaningful */
|
||||
if (gb->hdma_current_src >= 0xE000) {
|
||||
gb->hdma_current_src |= 0xF000;
|
||||
}
|
||||
return;
|
||||
case GB_IO_HDMA2:
|
||||
if (gb->cgb_mode) {
|
||||
|
@ -1546,7 +1587,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
return;
|
||||
case GB_IO_HDMA4:
|
||||
if (gb->cgb_mode) {
|
||||
gb->hdma_current_dest &= 0x1F00;
|
||||
gb->hdma_current_dest &= 0xFF00;
|
||||
gb->hdma_current_dest |= value & 0xF0;
|
||||
}
|
||||
return;
|
||||
|
@ -1558,38 +1599,31 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
}
|
||||
gb->hdma_on = (value & 0x80) == 0;
|
||||
gb->hdma_on_hblank = (value & 0x80) != 0;
|
||||
if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0) {
|
||||
if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0 && gb->display_state != 7) {
|
||||
gb->hdma_on = true;
|
||||
}
|
||||
gb->io_registers[GB_IO_HDMA5] = value;
|
||||
gb->hdma_steps_left = (gb->io_registers[GB_IO_HDMA5] & 0x7F) + 1;
|
||||
/* Todo: Verify this. Gambatte's DMA tests require this. */
|
||||
if (gb->hdma_current_dest + (gb->hdma_steps_left << 4) > 0xFFFF) {
|
||||
gb->hdma_steps_left = (0x10000 - gb->hdma_current_dest) >> 4;
|
||||
}
|
||||
gb->hdma_cycles = -12;
|
||||
return;
|
||||
|
||||
/* Todo: what happens when starting a transfer during a transfer?
|
||||
What happens when starting a transfer during external clock?
|
||||
*/
|
||||
/* TODO: What happens when starting a transfer during external clock?
|
||||
TODO: When a cable is connected, the clock of the other side affects "zombie" serial clocking */
|
||||
case GB_IO_SC:
|
||||
gb->serial_count = 0;
|
||||
if (!gb->cgb_mode) {
|
||||
value |= 2;
|
||||
}
|
||||
if (gb->serial_master_clock) {
|
||||
GB_serial_master_edge(gb);
|
||||
}
|
||||
gb->io_registers[GB_IO_SC] = value | (~0x83);
|
||||
gb->serial_mask = gb->cgb_mode && (value & 2)? 4 : 0x80;
|
||||
if ((value & 0x80) && (value & 0x1) ) {
|
||||
gb->serial_length = gb->cgb_mode && (value & 2)? 16 : 512;
|
||||
gb->serial_count = 0;
|
||||
/* Todo: This is probably incorrect for CGB's faster clock mode. */
|
||||
gb->serial_cycles &= 0xFF;
|
||||
if (gb->serial_transfer_bit_start_callback) {
|
||||
gb->serial_transfer_bit_start_callback(gb, gb->io_registers[GB_IO_SB] & 0x80);
|
||||
}
|
||||
}
|
||||
else {
|
||||
gb->serial_length = 0;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
case GB_IO_RP: {
|
||||
|
@ -1656,89 +1690,149 @@ void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
|
||||
if (unlikely(is_addr_in_dma_use(gb, addr))) {
|
||||
bool oam_write = addr >= 0xFE00;
|
||||
if (GB_is_cgb(gb) && bus_for_addr(gb, addr) == GB_BUS_MAIN && gb->dma_current_src >= 0xe000) {
|
||||
if (GB_is_cgb(gb) && bus_for_addr(gb, addr) == GB_BUS_MAIN && gb->dma_current_src >= 0xE000) {
|
||||
/* This is cart specific! Everdrive 7X on a CGB-A or 0 behaves differently. */
|
||||
return;
|
||||
}
|
||||
|
||||
if (GB_is_cgb(gb) && bus_for_addr(gb, gb->dma_current_src) != GB_BUS_RAM && addr >= 0xc000) {
|
||||
if (GB_is_cgb(gb) && (gb->dma_current_src < 0xC000 || gb->dma_current_src >= 0xE000) && addr >= 0xC000) {
|
||||
// TODO: this should probably affect the DMA dest as well
|
||||
addr = (gb->dma_current_src & 0x1000) | (addr & 0xFFF) | 0xC000;
|
||||
addr = ((gb->dma_current_src - 1) & 0x1000) | (addr & 0xFFF) | 0xC000;
|
||||
goto write;
|
||||
}
|
||||
else if (GB_is_cgb(gb) && gb->dma_current_src >= 0xe000 && addr > 0xc000) {
|
||||
else if (GB_is_cgb(gb) && gb->dma_current_src >= 0xE000 && addr >= 0xC000) {
|
||||
// TODO: this should probably affect the DMA dest as well
|
||||
addr = (gb->dma_current_src & 0x1000) | (addr & 0xFFF) | 0xC000;
|
||||
addr = ((gb->dma_current_src - 1) & 0x1000) | (addr & 0xFFF) | 0xC000;
|
||||
}
|
||||
else {
|
||||
addr = gb->dma_current_src;
|
||||
addr = (gb->dma_current_src - 1);
|
||||
}
|
||||
if (GB_is_cgb(gb) || addr > 0xc000) {
|
||||
gb->dma_and_pattern = addr < 0xc000? 0x00 : 0xFF;
|
||||
if ((gb->model < GB_MODEL_CGB_0 || gb->model == GB_MODEL_CGB_B) && addr > 0xc000) {
|
||||
gb->dma_and_pattern = value;
|
||||
if (GB_is_cgb(gb) || addr >= 0xA000) {
|
||||
if (addr < 0xA000) {
|
||||
gb->oam[gb->dma_current_dest - 1] = 0;
|
||||
}
|
||||
else if ((gb->model < GB_MODEL_CGB_C || gb->model > GB_MODEL_CGB_E) && addr > 0xc000 && !oam_write) {
|
||||
gb->dma_skip_write = true;
|
||||
gb->oam[gb->dma_current_dest] = value;
|
||||
else if ((gb->model < GB_MODEL_CGB_0 || gb->model == GB_MODEL_CGB_B)) {
|
||||
gb->oam[gb->dma_current_dest - 1] &= value;
|
||||
}
|
||||
if (gb->model < GB_MODEL_CGB_E || addr >= 0xc000) return;
|
||||
else if ((gb->model < GB_MODEL_CGB_C || gb->model > GB_MODEL_CGB_E) && !oam_write) {
|
||||
gb->oam[gb->dma_current_dest - 1] = value;
|
||||
}
|
||||
if (gb->model < GB_MODEL_CGB_E || addr >= 0xA000) return;
|
||||
}
|
||||
}
|
||||
write:
|
||||
write_map[addr >> 12](gb, addr, value);
|
||||
}
|
||||
|
||||
bool GB_is_dma_active(GB_gameboy_t *gb)
|
||||
{
|
||||
return gb->dma_current_dest != 0xA1;
|
||||
}
|
||||
|
||||
void GB_dma_run(GB_gameboy_t *gb)
|
||||
{
|
||||
while (unlikely(gb->dma_cycles >= 4 && gb->dma_steps_left)) {
|
||||
/* Todo: measure this value */
|
||||
gb->dma_cycles -= 4;
|
||||
gb->dma_steps_left--;
|
||||
if (gb->dma_skip_write) {
|
||||
gb->dma_skip_write = false;
|
||||
if (gb->dma_current_dest == 0xA1) return;
|
||||
if (unlikely(gb->halted || gb->stopped)) return;
|
||||
signed cycles = gb->dma_cycles + gb->dma_cycles_modulo;
|
||||
gb->in_dma_read = true;
|
||||
while (unlikely(cycles >= 4)) {
|
||||
cycles -= 4;
|
||||
if (gb->dma_current_dest >= 0xA0) {
|
||||
gb->dma_current_dest++;
|
||||
if (gb->display_state == 8) {
|
||||
gb->io_registers[GB_IO_STAT] |= 2;
|
||||
GB_STAT_update(gb);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (unlikely(gb->hdma_in_progress && (gb->hdma_steps_left > 1 || (gb->hdma_current_dest & 0xF) != 0xF))) {
|
||||
gb->dma_current_dest++;
|
||||
}
|
||||
else if (gb->dma_current_src < 0xe000) {
|
||||
gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src) & gb->dma_and_pattern;
|
||||
else if (gb->dma_current_src < 0xE000) {
|
||||
gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src);
|
||||
}
|
||||
else {
|
||||
if (GB_is_cgb(gb)) {
|
||||
gb->oam[gb->dma_current_dest++] = gb->dma_and_pattern;
|
||||
gb->oam[gb->dma_current_dest++] = 0xFF;
|
||||
}
|
||||
else {
|
||||
gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src & ~0x2000) & gb->dma_and_pattern;
|
||||
gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src & ~0x2000);
|
||||
}
|
||||
}
|
||||
gb->dma_and_pattern = 0xFF;
|
||||
|
||||
/* dma_current_src must be the correct value during GB_read_memory */
|
||||
gb->dma_current_src++;
|
||||
if (!gb->dma_steps_left) {
|
||||
gb->is_dma_restarting = false;
|
||||
}
|
||||
gb->dma_ppu_vram_conflict = false;
|
||||
}
|
||||
gb->in_dma_read = false;
|
||||
gb->dma_cycles_modulo = cycles;
|
||||
gb->dma_cycles = 0;
|
||||
}
|
||||
|
||||
void GB_hdma_run(GB_gameboy_t *gb)
|
||||
{
|
||||
if (likely(!gb->hdma_on)) return;
|
||||
|
||||
while (gb->hdma_cycles >= 0x4) {
|
||||
gb->hdma_cycles -= 0x4;
|
||||
|
||||
GB_write_memory(gb, 0x8000 | (gb->hdma_current_dest++ & 0x1FFF), GB_read_memory(gb, (gb->hdma_current_src++)));
|
||||
unsigned cycles = gb->cgb_double_speed? 4 : 2;
|
||||
/* This is a bit cart, revision and unit specific. TODO: what if PC is in cart RAM? */
|
||||
if (gb->model < GB_MODEL_CGB_D || gb->pc > 0x8000) {
|
||||
gb->hdma_open_bus = 0xFF;
|
||||
}
|
||||
gb->addr_for_hdma_conflict = 0xFFFF;
|
||||
uint16_t vram_base = gb->cgb_vram_bank? 0x2000 : 0;
|
||||
gb->hdma_in_progress = true;
|
||||
GB_advance_cycles(gb, cycles);
|
||||
while (gb->hdma_on) {
|
||||
uint8_t byte = gb->hdma_open_bus;
|
||||
gb->addr_for_hdma_conflict = 0xFFFF;
|
||||
|
||||
if ((gb->hdma_current_dest & 0xf) == 0) {
|
||||
if (--gb->hdma_steps_left == 0) {
|
||||
if (gb->hdma_current_src < 0x8000 ||
|
||||
(gb->hdma_current_src & 0xE000) == 0xC000 ||
|
||||
(gb->hdma_current_src & 0xE000) == 0xA000) {
|
||||
byte = GB_read_memory(gb, gb->hdma_current_src);
|
||||
}
|
||||
if (unlikely(GB_is_dma_active(gb)) && (gb->dma_cycles_modulo == 2 || gb->cgb_double_speed)) {
|
||||
write_oam(gb, gb->hdma_current_src, byte);
|
||||
}
|
||||
gb->hdma_current_src++;
|
||||
GB_advance_cycles(gb, cycles);
|
||||
if (gb->addr_for_hdma_conflict == 0xFFFF /* || (gb->model >= GB_MODEL_AGB_B && gb->cgb_double_speed) */) {
|
||||
uint16_t addr = (gb->hdma_current_dest++ & 0x1FFF);
|
||||
gb->vram[vram_base + addr] = byte;
|
||||
// TODO: vram_write_blocked might not be the correct timing
|
||||
if (gb->vram_write_blocked /* && gb->model < GB_MODEL_AGB_B */) {
|
||||
gb->vram[(vram_base ^ 0x2000) + addr] = byte;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (gb->model == GB_MODEL_CGB_E || gb->cgb_double_speed) {
|
||||
/*
|
||||
These corruptions revision (unit?) specific in single speed. They happen only on my CGB-E.
|
||||
*/
|
||||
gb->addr_for_hdma_conflict &= 0x1FFF;
|
||||
// TODO: there are *some* scenarions in single speed mode where this write doesn't happen. What's the logic?
|
||||
uint16_t addr = (gb->hdma_current_dest & gb->addr_for_hdma_conflict & 0x1FFF);
|
||||
gb->vram[vram_base + addr] = byte;
|
||||
// TODO: vram_write_blocked might not be the correct timing
|
||||
if (gb->vram_write_blocked /* && gb->model < GB_MODEL_AGB_B */) {
|
||||
gb->vram[(vram_base ^ 0x2000) + addr] = byte;
|
||||
}
|
||||
}
|
||||
gb->hdma_current_dest++;
|
||||
}
|
||||
gb->hdma_open_bus = 0xFF;
|
||||
|
||||
if ((gb->hdma_current_dest & 0xF) == 0) {
|
||||
if (--gb->hdma_steps_left == 0 || gb->hdma_current_dest == 0) {
|
||||
gb->hdma_on = false;
|
||||
gb->hdma_on_hblank = false;
|
||||
gb->hdma_starting = false;
|
||||
gb->io_registers[GB_IO_HDMA5] &= 0x7F;
|
||||
break;
|
||||
}
|
||||
if (gb->hdma_on_hblank) {
|
||||
else if (gb->hdma_on_hblank) {
|
||||
gb->hdma_on = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
gb->hdma_in_progress = false; // TODO: timing? (affects VRAM reads)
|
||||
if (!gb->cgb_double_speed) {
|
||||
GB_advance_cycles(gb, 2);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,8 +13,10 @@ uint8_t GB_safe_read_memory(GB_gameboy_t *gb, uint16_t addr); // Without side ef
|
|||
void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value);
|
||||
#ifdef GB_INTERNAL
|
||||
internal void GB_dma_run(GB_gameboy_t *gb);
|
||||
internal bool GB_is_dma_active(GB_gameboy_t *gb);
|
||||
internal void GB_hdma_run(GB_gameboy_t *gb);
|
||||
internal void GB_trigger_oam_bug(GB_gameboy_t *gb, uint16_t address);
|
||||
internal uint8_t GB_read_oam(GB_gameboy_t *gb, uint8_t addr);
|
||||
#endif
|
||||
|
||||
#endif /* memory_h */
|
||||
|
|
|
@ -22,8 +22,8 @@ static void handle_command(GB_gameboy_t *gb)
|
|||
gb->printer.status = 6; /* Printing */
|
||||
uint32_t image[gb->printer.image_offset];
|
||||
uint8_t palette = gb->printer.command_data[2];
|
||||
uint32_t colors[4] = {gb->rgb_encode_callback(gb, 0xff, 0xff, 0xff),
|
||||
gb->rgb_encode_callback(gb, 0xaa, 0xaa, 0xaa),
|
||||
uint32_t colors[4] = {gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF),
|
||||
gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA),
|
||||
gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55),
|
||||
gb->rgb_encode_callback(gb, 0x00, 0x00, 0x00)};
|
||||
for (unsigned i = 0; i < gb->printer.image_offset; i++) {
|
||||
|
|
|
@ -17,7 +17,7 @@ static uint8_t *state_compress(const uint8_t *prev, const uint8_t *data, size_t
|
|||
|
||||
while (uncompressed_size) {
|
||||
if (prev_mode) {
|
||||
if (*data == *prev && COUNTER != 0xffff) {
|
||||
if (*data == *prev && COUNTER != 0xFFFF) {
|
||||
COUNTER++;
|
||||
data++;
|
||||
prev++;
|
||||
|
@ -35,7 +35,7 @@ static uint8_t *state_compress(const uint8_t *prev, const uint8_t *data, size_t
|
|||
}
|
||||
}
|
||||
else {
|
||||
if (*data != *prev && COUNTER != 0xffff) {
|
||||
if (*data != *prev && COUNTER != 0xFFFF) {
|
||||
COUNTER++;
|
||||
DATA = *data;
|
||||
data_pos++;
|
||||
|
|
|
@ -119,6 +119,28 @@ typedef struct __attribute__((packed)){
|
|||
GB_huc3_rtc_time_t data;
|
||||
} BESS_HUC3_t;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
BESS_block_t header;
|
||||
|
||||
// Flags
|
||||
bool latch_ready:1;
|
||||
bool eeprom_do:1;
|
||||
bool eeprom_di:1;
|
||||
bool eeprom_clk:1;
|
||||
bool eeprom_cs:1;
|
||||
bool eeprom_write_enabled:1;
|
||||
uint8_t padding:2;
|
||||
|
||||
uint8_t argument_bits_left;
|
||||
|
||||
uint16_t eeprom_command;
|
||||
uint16_t read_bits;
|
||||
|
||||
uint16_t x_latch;
|
||||
uint16_t y_latch;
|
||||
|
||||
} BESS_MBC7_t;
|
||||
|
||||
typedef struct __attribute__((packed)){
|
||||
BESS_block_t header;
|
||||
uint64_t last_rtc_second;
|
||||
|
@ -232,8 +254,14 @@ static size_t bess_size_for_cartridge(const GB_cartridge_t *cart)
|
|||
return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t) + (cart->has_rtc? sizeof(BESS_RTC_t) : 0);
|
||||
case GB_MBC5:
|
||||
return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t);
|
||||
case GB_CAMERA:
|
||||
return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t);
|
||||
case GB_MBC7:
|
||||
return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t) + sizeof(BESS_MBC7_t);
|
||||
case GB_MMM01:
|
||||
return sizeof(BESS_block_t) + 8 * sizeof(BESS_MBC_pair_t);
|
||||
case GB_HUC1:
|
||||
return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t);
|
||||
return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t);
|
||||
case GB_HUC3:
|
||||
return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t) + sizeof(BESS_HUC3_t);
|
||||
case GB_TPP1:
|
||||
|
@ -268,7 +296,7 @@ size_t GB_get_save_state_size(GB_gameboy_t *gb)
|
|||
+ sizeof(BESS_CORE_t)
|
||||
+ sizeof(BESS_XOAM_t)
|
||||
+ (gb->sgb? sizeof(BESS_SGB_t) : 0)
|
||||
+ bess_size_for_cartridge(gb->cartridge_type) // MBC & RTC/HUC3/TPP1 block
|
||||
+ bess_size_for_cartridge(gb->cartridge_type) // MBC & RTC/HUC3/TPP1/MBC7 block
|
||||
+ sizeof(BESS_block_t) // END block
|
||||
+ sizeof(BESS_footer_t);
|
||||
}
|
||||
|
@ -323,7 +351,7 @@ static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t
|
|||
case GB_MODEL_CGB_C: return true;
|
||||
case GB_MODEL_CGB_D: return true;
|
||||
case GB_MODEL_CGB_E: return true;
|
||||
case GB_MODEL_AGB: return true;
|
||||
case GB_MODEL_AGB_A: return true;
|
||||
}
|
||||
if ((gb->model & GB_MODEL_FAMILY_MASK) == (save->model & GB_MODEL_FAMILY_MASK)) {
|
||||
save->model = gb->model;
|
||||
|
@ -340,10 +368,8 @@ static void sanitize_state(GB_gameboy_t *gb)
|
|||
GB_palette_changed(gb, true, i * 2);
|
||||
}
|
||||
|
||||
gb->bg_fifo.read_end &= 0xF;
|
||||
gb->bg_fifo.write_end &= 0xF;
|
||||
gb->oam_fifo.read_end &= 0xF;
|
||||
gb->oam_fifo.write_end &= 0xF;
|
||||
gb->bg_fifo.read_end &= GB_FIFO_LENGTH - 1;
|
||||
gb->oam_fifo.read_end &= GB_FIFO_LENGTH - 1;
|
||||
gb->last_tile_index_address &= 0x1FFF;
|
||||
gb->window_tile_x &= 0x1F;
|
||||
|
||||
|
@ -372,11 +398,6 @@ static void sanitize_state(GB_gameboy_t *gb)
|
|||
if (!GB_is_cgb(gb)) {
|
||||
gb->current_tile_attributes = 0;
|
||||
}
|
||||
|
||||
if ((unsigned)gb->dma_current_dest + (unsigned)gb->dma_steps_left >= 0xa0) {
|
||||
gb->dma_current_dest = 0;
|
||||
gb->dma_steps_left = 0;
|
||||
}
|
||||
|
||||
gb->object_low_line_address &= gb->vram_size & ~1;
|
||||
if (gb->lcd_x > gb->position_in_line) {
|
||||
|
@ -411,7 +432,7 @@ static int save_bess_mbc_block(GB_gameboy_t *gb, virtual_file_t *file)
|
|||
{
|
||||
|
||||
BESS_block_t mbc_block = {BE32('MBC '), 0};
|
||||
BESS_MBC_pair_t pairs[4];
|
||||
BESS_MBC_pair_t pairs[8];
|
||||
switch (gb->cartridge_type->mbc_type) {
|
||||
default:
|
||||
case GB_NO_MBC: return 0;
|
||||
|
@ -440,20 +461,41 @@ static int save_bess_mbc_block(GB_gameboy_t *gb, virtual_file_t *file)
|
|||
pairs[3] = (BESS_MBC_pair_t){LE16(0x4000), gb->mbc5.ram_bank};
|
||||
mbc_block.size = 4 * sizeof(pairs[0]);
|
||||
break;
|
||||
case GB_CAMERA:
|
||||
pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->mbc_ram_enable? 0xA : 0x0};
|
||||
pairs[1] = (BESS_MBC_pair_t){LE16(0x2000), gb->mbc5.rom_bank_low};
|
||||
pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->mbc5.ram_bank};
|
||||
mbc_block.size = 3 * sizeof(pairs[0]);
|
||||
break;
|
||||
case GB_MBC7:
|
||||
pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->mbc_ram_enable? 0xA : 0x0};
|
||||
pairs[1] = (BESS_MBC_pair_t){LE16(0x2000), gb->mbc7.rom_bank};
|
||||
pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->mbc7.secondary_ram_enable? 0x40 : 0};
|
||||
mbc_block.size = 3 * sizeof(pairs[0]);
|
||||
break;
|
||||
case GB_MMM01:
|
||||
pairs[0] = (BESS_MBC_pair_t){LE16(0x2000), (gb->mmm01.rom_bank_low & (gb->mmm01.rom_bank_mask << 1)) | (gb->mmm01.rom_bank_mid << 5)};
|
||||
pairs[1] = (BESS_MBC_pair_t){LE16(0x6000), gb->mmm01.mbc1_mode | (gb->mmm01.rom_bank_mask << 2) | (gb->mmm01.multiplex_mode << 6)};
|
||||
pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->mmm01.ram_bank_low | (gb->mmm01.ram_bank_high << 2) | (gb->mmm01.rom_bank_high << 4) | (gb->mmm01.mbc1_mode_disable << 6)};
|
||||
pairs[3] = (BESS_MBC_pair_t){LE16(0x0000), (gb->mbc_ram_enable? 0xA : 0x0) | (gb->mmm01.ram_bank_mask << 4) | (gb->mmm01.locked << 6)};
|
||||
/* For compatibility with emulators that inaccurately emulate MMM01, and also require two writes per register */
|
||||
pairs[4] = (BESS_MBC_pair_t){LE16(0x2000), (gb->mmm01.rom_bank_low & ~(gb->mmm01.rom_bank_mask << 1))};
|
||||
pairs[5] = pairs[1];
|
||||
pairs[6] = pairs[2];
|
||||
pairs[7] = pairs[3];
|
||||
mbc_block.size = 8 * sizeof(pairs[0]);
|
||||
break;
|
||||
case GB_HUC1:
|
||||
pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->huc1.ir_mode? 0xE : 0x0};
|
||||
pairs[1] = (BESS_MBC_pair_t){LE16(0x2000), gb->huc1.bank_low};
|
||||
pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->huc1.bank_high};
|
||||
pairs[3] = (BESS_MBC_pair_t){LE16(0x6000), gb->huc1.mode};
|
||||
mbc_block.size = 4 * sizeof(pairs[0]);
|
||||
|
||||
mbc_block.size = 3 * sizeof(pairs[0]);
|
||||
case GB_HUC3:
|
||||
pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->huc3.mode};
|
||||
pairs[1] = (BESS_MBC_pair_t){LE16(0x2000), gb->huc3.rom_bank};
|
||||
pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->huc3.ram_bank};
|
||||
mbc_block.size = 3 * sizeof(pairs[0]);
|
||||
break;
|
||||
|
||||
case GB_TPP1:
|
||||
pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->tpp1.rom_bank};
|
||||
pairs[1] = (BESS_MBC_pair_t){LE16(0x0001), gb->tpp1.rom_bank >> 8};
|
||||
|
@ -476,6 +518,14 @@ static int save_bess_mbc_block(GB_gameboy_t *gb, virtual_file_t *file)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static const uint8_t *get_header_bank(GB_gameboy_t *gb)
|
||||
{
|
||||
if (gb->cartridge_type->mbc_type == GB_MMM01) {
|
||||
return gb->rom + gb->rom_size - 0x8000;
|
||||
}
|
||||
return gb->rom;
|
||||
}
|
||||
|
||||
static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool append_bess)
|
||||
{
|
||||
if (file->write(file, GB_GET_SECTION(gb, header), GB_SECTION_SIZE(header)) != GB_SECTION_SIZE(header)) goto error;
|
||||
|
@ -545,11 +595,13 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe
|
|||
goto error;
|
||||
}
|
||||
|
||||
if (file->write(file, gb->rom + 0x134, 0x10) != 0x10) {
|
||||
const uint8_t *bank = get_header_bank(gb);
|
||||
|
||||
if (file->write(file, bank + 0x134, 0x10) != 0x10) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (file->write(file, gb->rom + 0x14e, 2) != 2) {
|
||||
if (file->write(file, bank + 0x14E, 2) != 2) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
|
@ -581,7 +633,7 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe
|
|||
case GB_MODEL_CGB_C: bess_core.full_model = BE32('CCC '); break;
|
||||
case GB_MODEL_CGB_D: bess_core.full_model = BE32('CCD '); break;
|
||||
case GB_MODEL_CGB_E: bess_core.full_model = BE32('CCE '); break;
|
||||
case GB_MODEL_AGB: bess_core.full_model = BE32('CA '); break; // SameBoy doesn't emulate a specific AGB revision yet
|
||||
case GB_MODEL_AGB_A: bess_core.full_model = BE32('CAA '); break;
|
||||
}
|
||||
|
||||
bess_core.pc = LE16(gb->pc);
|
||||
|
@ -685,6 +737,30 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe
|
|||
}
|
||||
}
|
||||
|
||||
if (gb->cartridge_type ->mbc_type == GB_MBC7) {
|
||||
BESS_MBC7_t bess_mbc7 = {
|
||||
.latch_ready = gb->mbc7.latch_ready,
|
||||
.eeprom_do = gb->mbc7.eeprom_do,
|
||||
.eeprom_di = gb->mbc7.eeprom_di,
|
||||
.eeprom_clk = gb->mbc7.eeprom_clk,
|
||||
.eeprom_cs = gb->mbc7.eeprom_cs,
|
||||
.eeprom_write_enabled = gb->mbc7.eeprom_write_enabled,
|
||||
|
||||
.argument_bits_left = gb->mbc7.argument_bits_left,
|
||||
|
||||
.eeprom_command = LE16(gb->mbc7.eeprom_command),
|
||||
.read_bits = LE16(gb->mbc7.read_bits),
|
||||
|
||||
.x_latch = LE16(gb->mbc7.x_latch),
|
||||
.y_latch = LE16(gb->mbc7.y_latch),
|
||||
};
|
||||
bess_mbc7.header = (BESS_block_t){BE32('MBC7'), LE32(sizeof(bess_mbc7) - sizeof(bess_mbc7.header))};
|
||||
|
||||
if (file->write(file, &bess_mbc7, sizeof(bess_mbc7)) != sizeof(bess_mbc7)) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
bool needs_sgb_padding = false;
|
||||
if (gb->sgb) {
|
||||
/* BESS SGB */
|
||||
|
@ -970,6 +1046,11 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo
|
|||
// Interrupts
|
||||
GB_write_memory(&save, 0xFF00 + GB_IO_IF, core.io_registers[GB_IO_IF]);
|
||||
|
||||
/* Required to be compatible with both SameBoy 0.14.x AND BGB */
|
||||
if (GB_is_cgb(&save) && !save.cgb_mode && save.cgb_ram_bank == 7) {
|
||||
save.cgb_ram_bank = 1;
|
||||
}
|
||||
|
||||
break;
|
||||
case BE32('NAME'):
|
||||
if (LE32(block.size) > sizeof(emulator_name) - 1) {
|
||||
|
@ -983,7 +1064,8 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo
|
|||
BESS_INFO_t bess_info = {0,};
|
||||
if (LE32(block.size) != sizeof(bess_info) - sizeof(block)) goto parse_error;
|
||||
if (file->read(file, &bess_info.header + 1, LE32(block.size)) != LE32(block.size)) goto error;
|
||||
if (memcmp(bess_info.title, gb->rom + 0x134, sizeof(bess_info.title))) {
|
||||
const uint8_t *bank = get_header_bank(gb);
|
||||
if (memcmp(bess_info.title, bank + 0x134, sizeof(bess_info.title))) {
|
||||
char ascii_title[0x11] = {0,};
|
||||
for (unsigned i = 0; i < 0x10; i++) {
|
||||
if (bess_info.title[i] < 0x20 || bess_info.title[i] > 0x7E) break;
|
||||
|
@ -991,7 +1073,7 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo
|
|||
}
|
||||
GB_log(gb, "Save state was made on another ROM: '%s'\n", ascii_title);
|
||||
}
|
||||
else if (memcmp(bess_info.checksum, gb->rom + 0x14E, 2)) {
|
||||
else if (memcmp(bess_info.checksum, bank + 0x14E, 2)) {
|
||||
GB_log(gb, "Save state was potentially made on another revision of the same ROM.\n");
|
||||
}
|
||||
break;
|
||||
|
@ -1004,7 +1086,12 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo
|
|||
case BE32('MBC '):
|
||||
if (!found_core) goto parse_error;
|
||||
if (LE32(block.size) % 3 != 0) goto parse_error;
|
||||
if (LE32(block.size) > 0x1000) goto parse_error;
|
||||
if (LE32(block.size) > 0x1000) goto parse_error;
|
||||
/* Inject some default writes, as some emulators omit them */
|
||||
if (gb->cartridge_type->mbc_type == GB_MMM01) {
|
||||
GB_write_memory(&save, 0x6000, 0x30);
|
||||
GB_write_memory(&save, 0x4000, 0x70);
|
||||
}
|
||||
for (unsigned i = LE32(block.size); i > 0; i -= 3) {
|
||||
BESS_MBC_pair_t pair;
|
||||
file->read(file, &pair, sizeof(pair));
|
||||
|
@ -1063,6 +1150,29 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo
|
|||
save.rtc_latched.data[i ^ 3] = bess_tpp1.latched_rtc_data[i];
|
||||
}
|
||||
save.tpp1_mr4 = bess_tpp1.mr4;
|
||||
break;
|
||||
case BE32('MBC7'):
|
||||
if (!found_core) goto parse_error;
|
||||
BESS_MBC7_t bess_mbc7;
|
||||
if (LE32(block.size) != sizeof(bess_mbc7) - sizeof(block)) goto parse_error;
|
||||
if (file->read(file, &bess_mbc7.header + 1, LE32(block.size)) != LE32(block.size)) goto error;
|
||||
if (gb->cartridge_type->mbc_type != GB_MBC7) break;
|
||||
|
||||
save.mbc7.latch_ready = bess_mbc7.latch_ready;
|
||||
save.mbc7.eeprom_do = bess_mbc7.eeprom_do;
|
||||
save.mbc7.eeprom_di = bess_mbc7.eeprom_di;
|
||||
save.mbc7.eeprom_clk = bess_mbc7.eeprom_clk;
|
||||
save.mbc7.eeprom_cs = bess_mbc7.eeprom_cs;
|
||||
save.mbc7.eeprom_write_enabled = bess_mbc7.eeprom_write_enabled;
|
||||
|
||||
save.mbc7.argument_bits_left = bess_mbc7.argument_bits_left;
|
||||
|
||||
save.mbc7.eeprom_command = LE16(bess_mbc7.eeprom_command);
|
||||
save.mbc7.read_bits = LE16(bess_mbc7.read_bits);
|
||||
|
||||
save.mbc7.x_latch = LE16(bess_mbc7.x_latch);
|
||||
save.mbc7.y_latch = LE16(bess_mbc7.y_latch);
|
||||
|
||||
break;
|
||||
case BE32('SGB '):
|
||||
if (!found_core) goto parse_error;
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
as anonymous enums inside unions */
|
||||
#define GB_SECTION(name, ...) __attribute__ ((aligned (8))) __VA_ARGS__
|
||||
#else
|
||||
#define GB_SECTION(name, ...) union __attribute__ ((aligned (8))) {uint8_t name##_section_start; struct {__VA_ARGS__};}; uint8_t name##_section_end[0]
|
||||
#define GB_SECTION(name, ...) union __attribute__ ((aligned (8))) {uint8_t name##_section_start; struct {__VA_ARGS__};}; uint8_t name##_section_end[0];
|
||||
#define GB_SECTION_OFFSET(name) (offsetof(GB_gameboy_t, name##_section_start))
|
||||
#define GB_SECTION_SIZE(name) (offsetof(GB_gameboy_t, name##_section_end) - offsetof(GB_gameboy_t, name##_section_start))
|
||||
#define GB_GET_SECTION(gb, name) ((void*)&((gb)->name##_section_start))
|
||||
|
|
|
@ -165,7 +165,7 @@ static void command_ready(GB_gameboy_t *gb)
|
|||
return;
|
||||
}
|
||||
memcpy(&gb->sgb->received_header[index * 14], &gb->sgb->command[2], 14);
|
||||
if (gb->sgb->command[0] == 0xfb) {
|
||||
if (gb->sgb->command[0] == 0xFB) {
|
||||
if (gb->sgb->received_header[0x42] != 3 || gb->sgb->received_header[0x47] != 0x33) {
|
||||
gb->sgb->disable_commands = true;
|
||||
for (unsigned i = 0; i < sizeof(palette_assignments) / sizeof(palette_assignments[0]); i++) {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
typedef struct GB_sgb_s GB_sgb_t;
|
||||
typedef struct {
|
||||
uint8_t tiles[0x100 * 8 * 4];
|
||||
#ifdef GB_INTERNAL
|
||||
union {
|
||||
struct {
|
||||
uint16_t map[32 * 32];
|
||||
|
@ -14,6 +15,9 @@ typedef struct {
|
|||
};
|
||||
uint16_t raw_data[0x440];
|
||||
};
|
||||
#else
|
||||
uint16_t raw_data[0x440];
|
||||
#endif
|
||||
} GB_sgb_border_t;
|
||||
|
||||
#ifdef GB_INTERNAL
|
||||
|
|
|
@ -23,6 +23,7 @@ typedef enum {
|
|||
GB_CONFLICT_WX,
|
||||
GB_CONFLICT_CGB_LCDC,
|
||||
GB_CONFLICT_NR10,
|
||||
GB_CONFLICT_CGB_SCX,
|
||||
} conflict_t;
|
||||
|
||||
/* Todo: How does double speed mode affect these? */
|
||||
|
@ -35,9 +36,7 @@ static const conflict_t cgb_conflict_map[0x80] = {
|
|||
[GB_IO_OBP0] = GB_CONFLICT_PALETTE_CGB,
|
||||
[GB_IO_OBP1] = GB_CONFLICT_PALETTE_CGB,
|
||||
[GB_IO_NR10] = GB_CONFLICT_NR10,
|
||||
[GB_IO_SCX] = GB_CONFLICT_WRITE_CPU, // TODO: Similar to BGP, there's some time travelling involved
|
||||
|
||||
/* Todo: most values not verified, and probably differ between revisions */
|
||||
[GB_IO_SCX] = GB_CONFLICT_CGB_SCX,
|
||||
};
|
||||
|
||||
/* Todo: verify on an MGB */
|
||||
|
@ -145,6 +144,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
/* The DMG STAT-write bug is basically the STAT register being read as FF for a single T-cycle */
|
||||
case GB_CONFLICT_STAT_DMG:
|
||||
GB_advance_cycles(gb, gb->pending_cycles);
|
||||
GB_display_sync(gb);
|
||||
/* State 7 is the edge between HBlank and OAM mode, and it behaves a bit weird.
|
||||
The OAM interrupt seems to be blocked by HBlank interrupts in that case, despite
|
||||
the timing not making much sense for that.
|
||||
|
@ -206,7 +206,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
|
||||
uint8_t old_value = GB_read_memory(gb, addr);
|
||||
GB_advance_cycles(gb, gb->pending_cycles - 2);
|
||||
|
||||
GB_display_sync(gb);
|
||||
if (gb->model != GB_MODEL_MGB && gb->position_in_line == 0 && (old_value & 2) && !(value & 2)) {
|
||||
old_value &= ~2;
|
||||
}
|
||||
|
@ -242,7 +242,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
break;
|
||||
|
||||
case GB_CONFLICT_CGB_LCDC:
|
||||
if ((value ^ gb->io_registers[GB_IO_LCDC]) & 0x10) {
|
||||
if ((~value & gb->io_registers[GB_IO_LCDC]) & 0x10) {
|
||||
// Todo: This is difference is because my timing is off in one of the models
|
||||
if (gb->model > GB_MODEL_CGB_C) {
|
||||
GB_advance_cycles(gb, gb->pending_cycles);
|
||||
|
@ -277,6 +277,7 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
GB_advance_cycles(gb, gb->pending_cycles);
|
||||
if (gb->model <= GB_MODEL_CGB_C) {
|
||||
// TODO: Double speed mode? This logic is also a bit weird, it needs more tests
|
||||
GB_apu_run(gb, true);
|
||||
if (gb->apu.square_sweep_calculate_countdown > 3 && gb->apu.enable_zombie_calculate_stepping) {
|
||||
gb->apu.square_sweep_calculate_countdown -= 2;
|
||||
}
|
||||
|
@ -289,6 +290,19 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
GB_write_memory(gb, addr, value);
|
||||
gb->pending_cycles = 4;
|
||||
break;
|
||||
|
||||
case GB_CONFLICT_CGB_SCX:
|
||||
if (gb->cgb_double_speed) {
|
||||
GB_advance_cycles(gb, gb->pending_cycles - 2);
|
||||
GB_write_memory(gb, addr, value);
|
||||
gb->pending_cycles = 6;
|
||||
}
|
||||
else {
|
||||
GB_advance_cycles(gb, gb->pending_cycles);
|
||||
GB_write_memory(gb, addr, value);
|
||||
gb->pending_cycles = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
gb->address_bus = addr;
|
||||
}
|
||||
|
@ -346,6 +360,7 @@ static void enter_stop_mode(GB_gameboy_t *gb)
|
|||
gb->div_cycles = -4; // Emulate the CPU-side DIV-reset signal being held
|
||||
}
|
||||
gb->stopped = true;
|
||||
gb->allow_hdma_on_wake = (gb->io_registers[GB_IO_STAT] & 3);
|
||||
gb->oam_ppu_blocked = !gb->oam_read_blocked;
|
||||
gb->vram_ppu_blocked = !gb->vram_read_blocked;
|
||||
gb->cgb_palettes_ppu_blocked = !gb->cgb_palettes_blocked;
|
||||
|
@ -354,6 +369,12 @@ static void enter_stop_mode(GB_gameboy_t *gb)
|
|||
static void leave_stop_mode(GB_gameboy_t *gb)
|
||||
{
|
||||
gb->stopped = false;
|
||||
if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0 && gb->allow_hdma_on_wake) {
|
||||
gb->hdma_on = true;
|
||||
}
|
||||
// TODO: verify this
|
||||
gb->dma_cycles = 4;
|
||||
GB_dma_run(gb);
|
||||
gb->oam_ppu_blocked = false;
|
||||
gb->vram_ppu_blocked = false;
|
||||
gb->cgb_palettes_ppu_blocked = false;
|
||||
|
@ -372,6 +393,9 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode)
|
|||
bool interrupt_pending = (gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F);
|
||||
// When entering with IF&IE, the 2nd byte of STOP is actually executed
|
||||
if (!exit_by_joyp) {
|
||||
if (!immediate_exit) {
|
||||
GB_dma_run(gb);
|
||||
}
|
||||
enter_stop_mode(gb);
|
||||
}
|
||||
|
||||
|
@ -414,8 +438,10 @@ static void stop(GB_gameboy_t *gb, uint8_t opcode)
|
|||
if (immediate_exit) {
|
||||
leave_stop_mode(gb);
|
||||
if (!interrupt_pending) {
|
||||
GB_dma_run(gb);
|
||||
gb->halted = true;
|
||||
gb->just_halted = true;
|
||||
gb->allow_hdma_on_wake = (gb->io_registers[GB_IO_STAT] & 3);
|
||||
}
|
||||
else {
|
||||
gb->speed_switch_halt_countdown = 0;
|
||||
|
@ -858,7 +884,7 @@ static void ld_##dhl##_##y(GB_gameboy_t *gb, uint8_t opcode) \
|
|||
cycle_write(gb, gb->hl, gb->y); \
|
||||
}
|
||||
|
||||
LD_X_Y(b,c) LD_X_Y(b,d) LD_X_Y(b,e) LD_X_Y(b,h) LD_X_Y(b,l) LD_X_DHL(b) LD_X_Y(b,a)
|
||||
LD_X_Y(b,c) LD_X_Y(b,d) LD_X_Y(b,e) LD_X_Y(b,h) LD_X_Y(b,l) LD_X_DHL(b) LD_X_Y(b,a)
|
||||
LD_X_Y(c,b) LD_X_Y(c,d) LD_X_Y(c,e) LD_X_Y(c,h) LD_X_Y(c,l) LD_X_DHL(c) LD_X_Y(c,a)
|
||||
LD_X_Y(d,b) LD_X_Y(d,c) LD_X_Y(d,e) LD_X_Y(d,h) LD_X_Y(d,l) LD_X_DHL(d) LD_X_Y(d,a)
|
||||
LD_X_Y(e,b) LD_X_Y(e,c) LD_X_Y(e,d) LD_X_Y(e,h) LD_X_Y(e,l) LD_X_DHL(e) LD_X_Y(e,a)
|
||||
|
@ -1004,7 +1030,6 @@ static void halt(GB_gameboy_t *gb, uint8_t opcode)
|
|||
gb->pending_cycles = 0;
|
||||
GB_advance_cycles(gb, 4);
|
||||
|
||||
gb->halted = true;
|
||||
/* Despite what some online documentations say, the HALT bug also happens on a CGB, in both CGB and DMG modes. */
|
||||
if (((gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F) != 0)) {
|
||||
if (gb->ime) {
|
||||
|
@ -1016,6 +1041,10 @@ static void halt(GB_gameboy_t *gb, uint8_t opcode)
|
|||
gb->halt_bug = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
gb->halted = true;
|
||||
gb->allow_hdma_on_wake = (gb->io_registers[GB_IO_STAT] & 3);
|
||||
}
|
||||
gb->just_halted = true;
|
||||
}
|
||||
|
||||
|
@ -1357,7 +1386,7 @@ static void rlc_r(GB_gameboy_t *gb, uint8_t opcode)
|
|||
if (carry) {
|
||||
gb->af |= GB_CARRY_FLAG;
|
||||
}
|
||||
if (!(value << 1)) {
|
||||
if (value == 0) {
|
||||
gb->af |= GB_ZERO_FLAG;
|
||||
}
|
||||
}
|
||||
|
@ -1571,10 +1600,6 @@ static opcode_t *opcodes[256] = {
|
|||
};
|
||||
void GB_cpu_run(GB_gameboy_t *gb)
|
||||
{
|
||||
if (gb->hdma_on) {
|
||||
GB_advance_cycles(gb, 4);
|
||||
return;
|
||||
}
|
||||
if (gb->stopped) {
|
||||
GB_timing_sync(gb);
|
||||
GB_advance_cycles(gb, 4);
|
||||
|
@ -1612,16 +1637,27 @@ void GB_cpu_run(GB_gameboy_t *gb)
|
|||
/* Wake up from HALT mode without calling interrupt code. */
|
||||
if (gb->halted && !effective_ime && interrupt_queue) {
|
||||
gb->halted = false;
|
||||
if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0 && gb->allow_hdma_on_wake) {
|
||||
gb->hdma_on = true;
|
||||
}
|
||||
gb->dma_cycles = 4;
|
||||
GB_dma_run(gb);
|
||||
gb->speed_switch_halt_countdown = 0;
|
||||
}
|
||||
|
||||
/* Call interrupt */
|
||||
else if (effective_ime && interrupt_queue) {
|
||||
gb->halted = false;
|
||||
if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0 && gb->allow_hdma_on_wake) {
|
||||
gb->hdma_on = true;
|
||||
}
|
||||
// TODO: verify the timing!
|
||||
gb->dma_cycles = 4;
|
||||
GB_dma_run(gb);
|
||||
gb->speed_switch_halt_countdown = 0;
|
||||
uint16_t call_addr = gb->pc;
|
||||
|
||||
gb->last_opcode_read = cycle_read(gb, gb->pc++);
|
||||
cycle_read(gb, gb->pc++);
|
||||
cycle_oam_bug_pc(gb);
|
||||
gb->pc--;
|
||||
GB_trigger_oam_bug(gb, gb->sp); /* Todo: test T-cycle timing */
|
||||
|
@ -1660,22 +1696,19 @@ void GB_cpu_run(GB_gameboy_t *gb)
|
|||
}
|
||||
/* Run mode */
|
||||
else if (!gb->halted) {
|
||||
gb->last_opcode_read = cycle_read(gb, gb->pc++);
|
||||
uint8_t opcode = gb->hdma_open_bus = cycle_read(gb, gb->pc++);
|
||||
if (unlikely(gb->hdma_on)) {
|
||||
GB_hdma_run(gb);
|
||||
}
|
||||
if (unlikely(gb->execution_callback)) {
|
||||
gb->execution_callback(gb, gb->pc - 1, gb->last_opcode_read);
|
||||
gb->execution_callback(gb, gb->pc - 1, opcode);
|
||||
}
|
||||
if (unlikely(gb->halt_bug)) {
|
||||
gb->pc--;
|
||||
gb->halt_bug = false;
|
||||
}
|
||||
opcodes[gb->last_opcode_read](gb, gb->last_opcode_read);
|
||||
opcodes[opcode](gb, opcode);
|
||||
}
|
||||
|
||||
flush_pending_cycles(gb);
|
||||
|
||||
if (gb->hdma_starting) {
|
||||
gb->hdma_starting = false;
|
||||
gb->hdma_on = true;
|
||||
gb->hdma_cycles = -8;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -519,7 +519,7 @@ static void ld_da8_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
|
|||
{
|
||||
(*pc)++;
|
||||
uint8_t addr = GB_read_memory(gb, (*pc)++);
|
||||
const char *symbol = GB_debugger_name_for_address(gb, 0xff00 + addr);
|
||||
const char *symbol = GB_debugger_name_for_address(gb, 0xFF00 + addr);
|
||||
if (symbol) {
|
||||
GB_log(gb, "LDH [%s & $FF], a ; =$%02x\n", symbol, addr);
|
||||
}
|
||||
|
@ -532,7 +532,7 @@ static void ld_a_da8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
|
|||
{
|
||||
(*pc)++;
|
||||
uint8_t addr = GB_read_memory(gb, (*pc)++);
|
||||
const char *symbol = GB_debugger_name_for_address(gb, 0xff00 + addr);
|
||||
const char *symbol = GB_debugger_name_for_address(gb, 0xFF00 + addr);
|
||||
if (symbol) {
|
||||
GB_log(gb, "LDH a, [%s & $FF] ; =$%02x\n", symbol, addr);
|
||||
}
|
||||
|
|
|
@ -44,6 +44,9 @@ const GB_bank_symbol_t *GB_map_find_symbol(GB_symbol_map_t *map, uint16_t addr)
|
|||
index--;
|
||||
}
|
||||
if (index < map->n_symbols) {
|
||||
while (index && map->symbols[index].addr == map->symbols[index - 1].addr) {
|
||||
index--;
|
||||
}
|
||||
return &map->symbols[index];
|
||||
}
|
||||
return NULL;
|
||||
|
|
123
Core/timing.c
|
@ -108,23 +108,32 @@ void GB_timing_sync(GB_gameboy_t *gb)
|
|||
#endif
|
||||
|
||||
#define IR_DECAY 31500
|
||||
#define IR_THRESHOLD 19900
|
||||
#define IR_MAX IR_THRESHOLD * 2 + IR_DECAY
|
||||
#define IR_WARMUP 19900
|
||||
#define IR_THRESHOLD 240
|
||||
#define IR_MAX IR_THRESHOLD * 2 + IR_DECAY + 268
|
||||
|
||||
static void ir_run(GB_gameboy_t *gb, uint32_t cycles)
|
||||
{
|
||||
if ((gb->model == GB_MODEL_AGB || !gb->cgb_mode) && gb->cartridge_type->mbc_type != GB_HUC1 && gb->cartridge_type->mbc_type != GB_HUC3) return;
|
||||
if (gb->infrared_input || gb->cart_ir || (gb->io_registers[GB_IO_RP] & 1)) {
|
||||
/* TODO: the way this thing works makes the CGB IR port behave inaccurately when used together with HUC1/3 IR ports*/
|
||||
if ((gb->model > GB_MODEL_CGB_E || !gb->cgb_mode) && gb->cartridge_type->mbc_type != GB_HUC1 && gb->cartridge_type->mbc_type != GB_HUC3) return;
|
||||
bool is_sensing = (gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 ||
|
||||
(gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.ir_mode) ||
|
||||
(gb->cartridge_type->mbc_type == GB_HUC3 && gb->huc3.mode == 0xE);
|
||||
if (is_sensing && (gb->infrared_input || gb->cart_ir || (gb->io_registers[GB_IO_RP] & 1))) {
|
||||
gb->ir_sensor += cycles;
|
||||
if (gb->ir_sensor > IR_MAX) {
|
||||
gb->ir_sensor = IR_MAX;
|
||||
}
|
||||
|
||||
gb->effective_ir_input = gb->ir_sensor >= IR_THRESHOLD && gb->ir_sensor <= IR_THRESHOLD + IR_DECAY;
|
||||
gb->effective_ir_input = gb->ir_sensor >= IR_WARMUP + IR_THRESHOLD && gb->ir_sensor <= IR_WARMUP + IR_THRESHOLD + IR_DECAY;
|
||||
}
|
||||
else {
|
||||
if (gb->ir_sensor <= cycles) {
|
||||
gb->ir_sensor = 0;
|
||||
unsigned target = is_sensing? IR_WARMUP : 0;
|
||||
if (gb->ir_sensor < target) {
|
||||
gb->ir_sensor += cycles;
|
||||
}
|
||||
else if (gb->ir_sensor <= target + cycles) {
|
||||
gb->ir_sensor = target;
|
||||
}
|
||||
else {
|
||||
gb->ir_sensor -= cycles;
|
||||
|
@ -154,6 +163,42 @@ static void increase_tima(GB_gameboy_t *gb)
|
|||
}
|
||||
}
|
||||
|
||||
void GB_serial_master_edge(GB_gameboy_t *gb)
|
||||
{
|
||||
if (unlikely(gb->printer_callback && (gb->printer.command_state || gb->printer.bits_received))) {
|
||||
gb->printer.idle_time += 1 << gb->serial_mask;
|
||||
}
|
||||
|
||||
gb->serial_master_clock ^= true;
|
||||
|
||||
if (!gb->serial_master_clock && (gb->io_registers[GB_IO_SC] & 0x81) == 0x81) {
|
||||
gb->serial_count++;
|
||||
if (gb->serial_count == 8) {
|
||||
gb->serial_count = 0;
|
||||
gb->io_registers[GB_IO_SC] &= ~0x80;
|
||||
gb->io_registers[GB_IO_IF] |= 8;
|
||||
}
|
||||
|
||||
gb->io_registers[GB_IO_SB] <<= 1;
|
||||
|
||||
if (gb->serial_transfer_bit_end_callback) {
|
||||
gb->io_registers[GB_IO_SB] |= gb->serial_transfer_bit_end_callback(gb);
|
||||
}
|
||||
else {
|
||||
gb->io_registers[GB_IO_SB] |= 1;
|
||||
}
|
||||
|
||||
if (gb->serial_count) {
|
||||
/* Still more bits to send */
|
||||
if (gb->serial_transfer_bit_start_callback) {
|
||||
gb->serial_transfer_bit_start_callback(gb, gb->io_registers[GB_IO_SB] & 0x80);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void GB_set_internal_div_counter(GB_gameboy_t *gb, uint16_t value)
|
||||
{
|
||||
/* TIMA increases when a specific high-bit becomes a low-bit. */
|
||||
|
@ -162,6 +207,10 @@ void GB_set_internal_div_counter(GB_gameboy_t *gb, uint16_t value)
|
|||
increase_tima(gb);
|
||||
}
|
||||
|
||||
if (triggers & gb->serial_mask) {
|
||||
GB_serial_master_edge(gb);
|
||||
}
|
||||
|
||||
/* TODO: Can switching to double speed mode trigger an event? */
|
||||
uint16_t apu_bit = gb->cgb_double_speed? 0x2000 : 0x1000;
|
||||
if (triggers & apu_bit) {
|
||||
|
@ -199,53 +248,6 @@ static void timers_run(GB_gameboy_t *gb, uint8_t cycles)
|
|||
}
|
||||
}
|
||||
|
||||
static void advance_serial(GB_gameboy_t *gb, uint8_t cycles)
|
||||
{
|
||||
if (unlikely(gb->printer_callback && (gb->printer.command_state || gb->printer.bits_received))) {
|
||||
gb->printer.idle_time += cycles;
|
||||
}
|
||||
if (likely(gb->serial_length == 0)) {
|
||||
gb->serial_cycles += cycles;
|
||||
return;
|
||||
}
|
||||
|
||||
while (cycles > gb->serial_length) {
|
||||
advance_serial(gb, gb->serial_length);
|
||||
cycles -= gb->serial_length;
|
||||
}
|
||||
|
||||
uint16_t previous_serial_cycles = gb->serial_cycles;
|
||||
gb->serial_cycles += cycles;
|
||||
if ((gb->serial_cycles & gb->serial_length) != (previous_serial_cycles & gb->serial_length)) {
|
||||
gb->serial_count++;
|
||||
if (gb->serial_count == 8) {
|
||||
gb->serial_length = 0;
|
||||
gb->serial_count = 0;
|
||||
gb->io_registers[GB_IO_SC] &= ~0x80;
|
||||
gb->io_registers[GB_IO_IF] |= 8;
|
||||
}
|
||||
|
||||
gb->io_registers[GB_IO_SB] <<= 1;
|
||||
|
||||
if (gb->serial_transfer_bit_end_callback) {
|
||||
gb->io_registers[GB_IO_SB] |= gb->serial_transfer_bit_end_callback(gb);
|
||||
}
|
||||
else {
|
||||
gb->io_registers[GB_IO_SB] |= 1;
|
||||
}
|
||||
|
||||
if (gb->serial_length) {
|
||||
/* Still more bits to send */
|
||||
if (gb->serial_transfer_bit_start_callback) {
|
||||
gb->serial_transfer_bit_start_callback(gb, gb->io_registers[GB_IO_SB] & 0x80);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
void GB_set_rtc_mode(GB_gameboy_t *gb, GB_rtc_mode_t mode)
|
||||
{
|
||||
if (gb->rtc_mode != mode) {
|
||||
|
@ -384,12 +386,9 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
|
|||
}
|
||||
gb->apu.pcm_mask[0] = gb->apu.pcm_mask[1] = 0xFF; // Sort of hacky, but too many cross-component interactions to do it right
|
||||
// Affected by speed boost
|
||||
gb->dma_cycles += cycles;
|
||||
gb->dma_cycles = cycles;
|
||||
|
||||
timers_run(gb, cycles);
|
||||
if (unlikely(!gb->stopped)) {
|
||||
advance_serial(gb, cycles); // TODO: Verify what happens in STOP mode
|
||||
}
|
||||
|
||||
if (unlikely(gb->speed_switch_halt_countdown)) {
|
||||
gb->speed_switch_halt_countdown -= cycles;
|
||||
|
@ -420,7 +419,6 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
|
|||
if (likely(gb->io_registers[GB_IO_LCDC] & 0x80)) {
|
||||
gb->double_speed_alignment += cycles;
|
||||
}
|
||||
gb->hdma_cycles += cycles;
|
||||
gb->apu_output.sample_cycles += cycles * gb->apu_output.sample_rate;
|
||||
gb->cycles_since_last_sync += cycles;
|
||||
gb->cycles_since_run += cycles;
|
||||
|
@ -428,12 +426,11 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
|
|||
gb->rumble_on_cycles += gb->rumble_strength & 3;
|
||||
gb->rumble_off_cycles += (gb->rumble_strength & 3) ^ 3;
|
||||
|
||||
if (unlikely(!gb->stopped)) { // TODO: Verify what happens in STOP mode
|
||||
GB_dma_run(gb);
|
||||
GB_hdma_run(gb);
|
||||
}
|
||||
GB_apu_run(gb, false);
|
||||
GB_display_run(gb, cycles, false);
|
||||
if (unlikely(!gb->stopped)) { // TODO: Verify what happens in STOP mode
|
||||
GB_dma_run(gb);
|
||||
}
|
||||
ir_run(gb, cycles);
|
||||
rtc_run(gb, cycles);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ internal void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t
|
|||
internal bool GB_timing_sync_turbo(GB_gameboy_t *gb); /* Returns true if should skip frame */
|
||||
internal void GB_timing_sync(GB_gameboy_t *gb);
|
||||
internal void GB_set_internal_div_counter(GB_gameboy_t *gb, uint16_t value);
|
||||
internal void GB_serial_master_edge(GB_gameboy_t *gb);
|
||||
enum {
|
||||
GB_TIMA_RUNNING = 0,
|
||||
GB_TIMA_RELOADING = 1,
|
||||
|
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 411 B |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 28 KiB |