Here's a video of the current menu interface (sadly lacking an animated raccoon mascot for the loading screns). You can read about R0 and R1 in the GR thread in the Cartridge subforum.
https://cdn.discordapp.com/attachments/ ... 200527.mp4
It works on every retail dump I can make or rip from compilations. Since Wiz 'n' Liz, Zero Tolerance and Hard Wired work, I'm confident that the hardware and software logic I'm using to switch control to games is appropriate. (If there any other freely available retail titles of the era other than these three, I'm interested.)
However, when it comes to homebrew games, demoscene productions and Sonic 1,2,3/K mods, compatibility is only around 50%. I've made some spreadsheets showing the compatibility on the current version of my board and menu system. (They also serve as a rough guide for me to tell which mod/game is which, so I apologise for the crudeness of the notes. )
These are all on a PAL Model 1 Mega Drive with TMSS.
Sonic mods compatibility table:
https://docs.google.com/spreadsheets/d/ ... a7/pubhtml
Demo prods compatibility table:
https://docs.google.com/spreadsheets/d/ ... U0/pubhtml
Homebrew compatibility table:
https://docs.google.com/spreadsheets/d/ ... MF/pubhtml
Very briefly: the GR board has 8 megabytes of Flash memory which I treat as two 4 megabyte banks ( 0) bootable menu + 1) game writing area ) switched by a PIC. To change from the system RAM-resident menu software to the installed game image, I perform the following steps:
- Write to TIME (which is mapped to a bi-directional register on the cart which the PIC can access) to signal that a bank switch is required. The installed image is now in 0x000000-0x3FFFFF.
- The contents of 0x000000.l are copied to a7.
- The contents of 0x000004.l are copied to a0, then jmp (a0)
This should be all that's necessary, and for the most part it is. I expected that games would not hold any assumptions about the state of the system upon boot, but it seems that some games do - certain titles wouldn't start, some would have broken controls, others garbage graphics. One example is the title screen of Sonic's Fun & Easy Adventure which assumes both RAM (spinning SEGA is garbage) and VRAM state (title screen garbage tiles) are zeros. You can simulate this in an emulator if you prepend something that fills these areas with nonzeroes before booting SF&EA.
To fix that, I added a small 'anti-bootstrap' that tries to clean up after the GR menu before booting:
- The VRAM, VSRAM and CRAM are set to zero.
- The RAM-resident menu copies anti-bootstrap to the end of RAM overwriting the stack and jumps to it.
- The anti-bootstrap clears all system RAM before itself to zero.
- Game boots.
The idea is hopefully to have all of the RAM I can set to zero, except the anti-bootstrap itself which lives where the stack would, so would be overwritten by games without being read.
Unfortunately, even with the anti-bootstrap, compatibility is still around 50% for non-retail games. I'm not sure why this is. I suspect that it could be one of the following:
- Interrupt state. The GR menu can't use interrupts because I'm constantly bankswitching out the ROM-resident menu firmware (for writing the game image to Flash), so I disable all interrupts immediately and use Vcounter wait loops for timing. Do games expect interrupts to begin enabled?
- VDP registers. I don't clean these up because it would be surely really silly for a game to assume anything about these? Is there a 'best' state to leave the VDP in? I've added a mode where if you hold Start, my cart performs the TMSS sequence only and then jumps to the anti-bootstrap without copying anything to VRAM or setting VDP state which does fix some demos. (This is the 'emergency boot' in the spreadsheets.)
- Z80 state. I only interact with the Z80 enough to silence the PSG and FM and to write an infinite loop to Z80 RAM space. Is there a 'best' state to leave the Z80 in?
I'm not thrilled about bending over backwards for certain games. I'd rather have my cart not provide too much assistance to the target game, so folks would write their software properly. :p Tanglewood (demo and itch.io bundle version), Phantom Gear, Curse of Illmore and Pringles all work great.
However, one common factor in the games that don't work is that they all seem to use SGDK - this is both demo prods and homebrew games. If a game doesn't use SGDK, chances are it'll work on my cart. Every RetroSouls game on Itchio except Misplaced fails to boot.
Given the above, I feel it's my cart that's doing something wrong rather than the games.
- Does SGDK have any SRAM handling by default? If the default simplest library startup begins with a routine for detecting SRAM size for example, it's possible that it's getting confused. Sonic mods like Classic Heroes and Dr. Yundong can detect that the GR cart has no SRAM though. There's nothing I can do about this, but it would explain the faults.
- Does SGDK have any specific VDP state it expects? When most games that don't work attempt to boot, there's a momentary frame where a row of small glitchy squares (like 2x2, bigger than cramdots) appear in a horizontal line for a single frame. Sometimes a row of green ]]]]] characters will appear on screen until I reset the system.
Also it's possible that these games are all just NTSC only, but they seem to work under mednafen in 50 Hz mode.
Apologies for the rambling thread, but I thought you might be interested in my progress. Any thoughts on my approach would be very welcome. I can send the source and a compiled menu rom image (with the GR cart interaction simulated) if anybody wants to see that.
The anti-bootstrap/boot sequence looks like this:
Code: Select all
; The RAM resident code is compiled to run from $FF0000.
; It'll be copied into position by the entry point.
; At the address of $FF0000+CODELENGTH, the static RAM memory
; map with my global variables in begins.
rorg RAM_START
game_raccoon_ram_resident_assembled_block_begin:
; game_raccoon_start_emergency:
; Do the handshake without affecting VDP, then boot game immediately.
game_raccoon_start_emergency:
move.w #GAME_RACCOON_TIME_WRITE_MAGIC_NUMBER_GR_HANDSHAKE,d0
TIME_write_d0_w
emergency_handshake_loop_until_deisolated:
GAME_RACCOON_isolation_test
bne emergency_handshake_loop_until_deisolated
jmp game_raccoon_game_partition_boot_sequence
; game_raccoon_game_partition_boot_sequence:
; Signal to the PIC that we want to boot the currently installed game,
; wait for deisolation, install the anti-bootstrap sequence at the very
; end of RAM, jump to anti-bootstrap, erase all Game Raccoon RAM-resident
; and VRAM-resident data, and jump to game entry point.
game_raccoon_game_partition_boot_sequence:
move.w #GAME_RACCOON_TIME_WRITE_MAGIC_NUMBER_BOOT_CURRENT_GAME_PARTITION,d0
TIME_write_d0_w
game_raccoon_game_partition_boot_sequence_loop_until_deisolated:
GAME_RACCOON_isolation_test
bne game_raccoon_game_partition_boot_sequence_loop_until_deisolated
; Alright, we've switched to the game partition!
; We CANNOT return to the boot partition by any means now.
; All ROM-resident data is inaccessible.
; Clear VDP state and VRAM.
jsr vdp_clear_all_cram
jsr vdp_clear_all_vram
jsr vdp_clear_all_vsram
; Write a zero to TIME which may help compatibility with some homebrew.
; This will be ignored by the PIC.
move.w #0,d0
TIME_write_d0_w
; Copy the anti-bootstrap to the end of RAM where the stack lives,
; destroying the stack as we go.
; The anti-bootstrap erases as much as it can and then finally
; jumps to the new game ROM image.
lea game_raccoon_anti_bootstrap_start,a0
lea GAME_RACCOON_ANTI_BOOTSTRAP_DESTINATION,a1
move.w #(GAME_RACCOON_ANTI_BOOTSTRAP_BLOCK_COPY_LENGTH_LONGS-1),d0
game_raccoon_anti_bootstrap_copy_next_long:
move.l (a0)+,(a1)+
dbf d0,game_raccoon_anti_bootstrap_copy_next_long
jmp GAME_RACCOON_ANTI_BOOTSTRAP_DESTINATION
; Anti-bootstrap:
; ---------------
; RAM Layout:
; START END
; [RAM-resident Game Raccoon stuff] [RAM variables] [Stack]
; After anti-bootstrap installation:
; [RAM-resident Game Raccoon stuff] [RAM variables] [Anti-bootstrap]
; After anti-bootstrap execution:
; [Anti-bootstrap]
; The new game should immediately clobber the anti-bootstrap's lingering stuff
; when it starts.
align 4
game_raccoon_anti_bootstrap_start:
; Erase the Game Raccoon RAM-resident part up until the
; game_raccoon_anti_bootstrap_erase_next_long label.
lea RAM_START,a0
move.w #(GAME_RACCOON_ANTI_BOOTSTRAP_ERASE_LENGTH_LONGS-1),d0
move.l #0,d1
jmp GAME_RACCOON_ANTI_BOOTSTRAP_DESTINATION+game_raccoon_anti_bootstrap_erase_next_long-game_raccoon_anti_bootstrap_start
align 4
game_raccoon_anti_bootstrap_erase_next_long:
move.l d1,(a0)+
dbf d0,game_raccoon_anti_bootstrap_erase_next_long
; Everything is erased except the previous two lines
; and the following three. HIT IT!
move.l (CART_STACK_POSITION),a7 ; Load the stack pointer starting value from the cart.
move.l (CART_ENTRY_POINT),a0 ; And jump to the entry point given in the cart image header!
jmp (a0)
game_raccoon_anti_bootstrap_end = *
GAME_RACCOON_ANTI_BOOTSTRAP_BLOCK_COPY_LENGTH_LONGS = game_raccoon_anti_bootstrap_end-game_raccoon_anti_bootstrap_start
GAME_RACCOON_ANTI_BOOTSTRAP_DESTINATION = (RAM_END-(GAME_RACCOON_ANTI_BOOTSTRAP_BLOCK_COPY_LENGTH_LONGS*4))
GAME_RACCOON_ANTI_BOOTSTRAP_ERASE_LENGTH_LONGS = (((GAME_RACCOON_ANTI_BOOTSTRAP_DESTINATION+(game_raccoon_anti_bootstrap_erase_next_long-game_raccoon_anti_bootstrap_start))-RAM_START)/4)
; Anti-bootstrap stuff end.
Code: Select all
macro z80_bus_request_engage
move.w #$0100,(MM_Z80_BUS_REQUEST)
endm
macro z80_wait_bus_available
l\? btst #0,(MM_Z80_BUS_REQUEST)
bne l\?
endm
macro z80_bus_request_disengage
move.w #$0000,(MM_Z80_BUS_REQUEST)
endm
macro z80_reset_engage
move.w #$0000,(MM_Z80_RESET)
endm
macro z80_reset_disengage
move.w #$0100,(MM_Z80_RESET)
endm
z80_initialise:
z80_bus_request_engage
z80_reset_disengage
z80_wait_bus_available
lea MM_Z80_MEMORY_SPACE,a0
move.b #$C3,(a0)+ ; Put a [jp $0000] at $0000.
move.b #$00,(a0)+
move.b #$00,(a0)
z80_reset_engage
z80_bus_request_disengage
z80_reset_disengage
rts
; psg_fm_initialise:
; Invalidates all registers.
; The sound chips are on the Z80 bus, so we request it as above
; then send the values we need.
psg_fm_initialise:
z80_bus_request_engage
z80_wait_bus_available
; Silence the SN76489, by setting attenuation of all channels to F (full attenuation = silence).
lea MM_SN76489_PSG,a0
move.b #(PSG_LATCH_BIT|PSG_CHANNEL_0|PSG_TYPE_VOLUME_DATA|$F),(a0)
nop
move.b #(PSG_LATCH_BIT|PSG_CHANNEL_1|PSG_TYPE_VOLUME_DATA|$F),(a0)
nop
move.b #(PSG_LATCH_BIT|PSG_CHANNEL_2|PSG_TYPE_VOLUME_DATA|$F),(a0)
nop
move.b #(PSG_LATCH_BIT|PSG_CHANNEL_3|PSG_TYPE_VOLUME_DATA|$F),(a0)
; Silence the YM2612:
lea MM_YM2612_ADDRESS_P1,a0
lea MM_YM2612_DATA_P1,a1
; For each of the six channels (lower three bits), note off all four operators (upper four bits).
YM2612_wait
move.b #YM2612_28_NOTE_ON_CONTROL,(a0)
YM2612_wait
move.b #%00000000|YM2612_28_CHANNEL_0,(a1)
YM2612_wait
move.b #%00000000|YM2612_28_CHANNEL_1,(a1)
YM2612_wait
move.b #%00000000|YM2612_28_CHANNEL_2,(a1)
YM2612_wait
move.b #%00000000|YM2612_28_CHANNEL_3,(a1)
YM2612_wait
move.b #%00000000|YM2612_28_CHANNEL_4,(a1)
YM2612_wait
move.b #%00000000|YM2612_28_CHANNEL_5,(a1)
; For each of the three channels in each of the two parts, set
; the TL of each operator within each channel to 127.
move.w #(2-1),d0 ; Two parts.
psg_fm_initialise_next_part:
move.b #$40,d1 ; 40h is the data slot for the TLs of all operators across all channels.
move.w #(16-1),d2 ; 16 entries across TL struct.
psg_fm_initialise_next_targeted_data_slot:
YM2612_wait
move.b d1,(a0) ; Write to ADDRESS_P* the data slot we're targeting.
addi.b #1,d1 ; Advance targeted data slot to next across the TL row.
YM2612_wait
move.b #127,(a1) ; Write to DATA_P* the maximum TL (minimum volume) value to the current slot.
dbf d2,psg_fm_initialise_next_targeted_data_slot
lea MM_YM2612_ADDRESS_P2,a0 ; Swap to the other Part.
lea MM_YM2612_DATA_P2,a1
dbf d0,psg_fm_initialise_next_part
z80_bus_request_disengage
rts