// Thunder: A Rolling Thunder Emulator
//
// by James Hammons
-// (C) 2004, 2014 Underground Software
+// (C) 2004, 2023 Underground Software
//
// JLH = James Hammons <jlhamm@acm.org>
//
// JLH 08/12/2009 Stabilized emulation so that it works
// JLH 04/04/2014 Converted to SDL 2
// JLH 04/17/2014 Removed a metric fuck-tonne of cruft, added YM2151 & MCU
+// JLH 01/13/2023 Finally fixed the sprite lag problem :-D
//
-#define THUNDER_VERSION "1.2.0"
+#define THUNDER_VERSION "1.2.1"
#include <SDL2/SDL.h>
#include <string>
#include "video.h"
#include "ym2151.h"
-
#define ROM1 "rt3-1b.9c"
#define ROM2 "rt3-2b.12c"
#define ROM3 "rt3-3.12d"
#define PROM5 "mb7112e.6u"
#define MCUROM "rt1-mcu.bin"
-
// Global defines
uint8_t gram1[0x10000], grom1[0x10000], grom2[0x10000];
// MCU inputs
Byte input1, input2, input3, input4, input5;
+// Copy sprites flag
+bool copySprites = false;
+
// Function prototypes
uint8_t MCUReadMemory(uint16_t address);
void MCUWriteMemory(uint16_t address, uint8_t data);
-
//
// Read a byte from memory
//
return grom1[addr];
}
-
//
// Write a byte to memory
//
void MainWriteMemory(uint16_t address, uint8_t data)
{
- extern bool disasm;
-
if (address == 0x6000)
SpawnSound(GAMESOUND, gram1[0x6200], 0); // Do voice chan 1
if (address == 0x6400)
gram1[address] = data;
if (address == 0x5FF2)
- CopySprites();
+ copySprites = true;
if (address == 0x8800)
charBankSwitch = false; // Char banksw1
if (address == 0x8C00)
charBankSwitch = true; // Char banksw2
- if (address == 0x8400) // Frame go strobe? VBlank acknowledge?
- {
-// BlitChar(charROM, gram1);
-
- // IRQ Ack (may also be frame go...)
- ClearLineOfCurrentV6809(V6809_ASSERT_LINE_IRQ);
-#if 1
- if (disasm)
- WriteLog("WriteMem: CPU #1 Acknowledging IRQ...\n", data);
-#endif
- }
+ if (address == 0x8400) // Frame go strobe? VBlank acknowledge?
+ ClearLineOfCurrentV6809(V6809_LINE_IRQ); // IRQ Ack
}
-
//
// Read a byte from memory (2nd processor)
//
return grom2[address];
}
-
//
// Write a byte to memory (2nd processor)
//
void SubWriteMemory(uint16_t address, uint8_t data)
{
- extern bool disasm;
-
// Set sprite data bank switch
if (address == 0xD803)
banksw2 = (uint32_t)(data & 0x03) << 13;
gram1[address - 0x2000] = data;
if (address == 0x1FF2)
- CopySprites();
+ copySprites = true;
if (address == 0x8800)
- {
- // IRQ Ack (may also be frame go...)
- ClearLineOfCurrentV6809(V6809_ASSERT_LINE_IRQ);
-#if 1
- if (disasm)
- WriteLog("WriteMem: CPU #2 Acknowledging IRQ...\n", data);
-#endif
- }
+ ClearLineOfCurrentV6809(V6809_LINE_IRQ); // IRQ Ack
}
-
uint8_t MCUReadMemory(uint16_t address)
{
if (address < 0x20)
return mcuMem[address];
}
-
void MCUWriteMemory(uint16_t address, uint8_t data)
{
static uint8_t ymRegister;
mcuMem[address] = data;
}
-
uint8_t V63701ReadPort1(void)
{
// printf("V63701ReadPort1: Read $%02X...\n", input3.byte);
return input3.byte;
}
-
uint8_t V63701ReadPort2(void)
{
return 0xFF;
}
-
void V63701WritePort1(uint8_t data)
{
// printf("V63701WritePort1: Wrote $%02X...\n", data);
}
-
void V63701WritePort2(uint8_t data)
{
// printf("V63701WritePort2: Wrote $%02X...\n", data);
}
-
//
// Generic Load file into image space
// (No error checking performed! Responsibility of caller!)
return false;
}
- fread(&mem[address], 1, length, file);
+ size_t ignored = fread(&mem[address], 1, length, file);
fclose(file);
return true;
}
-
//
// Read color PROMs
//
bool ReadColorPROMs(void)
{
- FILE * file1 = fopen("./ROMs/"PROM3, "rb");
+ FILE * file1 = fopen("./ROMs/" PROM3, "rb");
if (file1)
{
fclose(file1);
}
- file1 = fopen("./ROMs/"PROM4, "rb");
+ file1 = fopen("./ROMs/" PROM4, "rb");
if (file1)
{
fclose(file1);
}
- file1 = fopen("./ROMs/"PROM1, "rb");
- FILE * file2 = fopen("./ROMs/"PROM2, "rb");
+ file1 = fopen("./ROMs/" PROM1, "rb");
+ FILE * file2 = fopen("./ROMs/" PROM2, "rb");
// If open was successful...
if (file1 && file2)
// PROM5 has the following in it (tile address decoder):
// 00: 00 20 40 60 02 22 42 62 04 24 44 64 06 26 46 66
- // 10: 88 A8 C8 E8 8A AA CA EA 8C AC CC EC 8E AE CE EE
+ // 10: 88 A8 C8 E8 8A AA CA EA 8C AC CC EC 8E AE CE EE
if (!file1)
{
return true;
}
-
//
// Unpack font data
//
bool UnpackFonts(void)
{
// 0x4000 $800 chars
- FILE * file1 = fopen("./ROMs/"ROM7, "rb");
- FILE * file2 = fopen("./ROMs/"ROM8, "rb");
+ FILE * file1 = fopen("./ROMs/" ROM7, "rb");
+ FILE * file2 = fopen("./ROMs/" ROM8, "rb");
if (!file1 || !file2)
{
- printf("Could not open either "ROM7" or "ROM8"!\n");
+ printf("Could not open either " ROM7 " or " ROM8 "!\n");
return false;
}
fclose(file1);
fclose(file2);
- file1 = fopen("./ROMs/"ROM5, "rb");
- file2 = fopen("./ROMs/"ROM6, "rb");
+ file1 = fopen("./ROMs/" ROM5, "rb");
+ file2 = fopen("./ROMs/" ROM6, "rb");
if (!file1 || !file2)
{
- printf("Could not open either "ROM5" or "ROM6"!\n");
+ printf("Could not open either " ROM5 " or " ROM6 "!\n");
return false;
}
return true;
}
-
//
// Main loop
//
{
InitLog("thunder.log");
- extern bool disasm; // From 'V6809.CPP'
-
bool running; // CPU running state flag...
SDL_Event event; // SDL "event"
uint32_t ticks, oldTicks;
uint8_t frameTickCount = 0;
- printf("THUNDER v"THUNDER_VERSION" by James Hammons\n");
- printf("Serial #20149417 / Prerelease\n");
- printf("© 2003, 2014 Underground Software\n\n");
+ printf("THUNDER v" THUNDER_VERSION " by James Hammons\n");
+ printf("Serial #20230113 / Prerelease\n");
+ printf("© 2003, 2023 Underground Software\n\n");
printf("This emulator is free software. If you paid for it you were RIPPED OFF\n\n");
// SDL_WM_SetCaption("Thunder v"THUNDER_VERSION" ", "Thunder");
memset(&cpu1, 0, sizeof(V6809REGS));
cpu1.RdMem = MainReadMemory;
cpu1.WrMem = MainWriteMemory;
- cpu1.cpuFlags |= V6809_ASSERT_LINE_RESET;
+ cpu1.cpuFlags |= V6809_LINE_RESET;
memset(&cpu2, 0, sizeof(V6809REGS));
cpu2.RdMem = SubReadMemory;
cpu2.WrMem = SubWriteMemory;
- cpu2.cpuFlags |= V6809_ASSERT_LINE_RESET;
+ cpu2.cpuFlags |= V6809_LINE_RESET;
memset(&mcu, 0, sizeof(V63701REGS));
mcu.RdMem = MCUReadMemory;
mcu.WrMem = MCUWriteMemory;
- mcu.cpuFlags |= V63701_ASSERT_LINE_RESET;
+ mcu.cpuFlags |= V63701_LINE_RESET;
running = true;
InitGUI(); // Reset # of coins
// Set up DIP switches...
- input4.bit.b0 = 1; // DSW B-0: Contiues (1 = 6, 0 = 3)
+ input4.bit.b0 = 1; // DSW B-0: Continues (1 = 6, 0 = 3)
input4.bit.b1 = 1; // DSW B-2: ???
input4.bit.b2 = 1; // DSW B-4: Difficulty (1 = normal, 0 = easy)
input4.bit.b3 = 1; // DSW B-6: Bonus lives (70K/200K = 1, 100K/300K = 0)
WriteLog("About to set up audio...\n");
InitSound();
-//memset(scrBuffer, 0xFF, VIRTUAL_SCREEN_WIDTH*VIRTUAL_SCREEN_HEIGHT*sizeof(uint32_t));
-//RenderScreenBuffer();
-
WriteLog("About to enter main loop...\n");
while (running)
{
// Dipswitches are presented to the main CPUs as 0 or 1 at locations
// $423D - $425B by the MCU
-//testing... (works)
-//gram1[0x423D] = 1;
- //gram1[0x423D] = self_test; // Reset DSW1-1
-// gram1[0x4268] = 0; // Reset Video test
-
// SDL key handling...
SDL_Event event;
#endif
// We can do this here because we're not executing the cores yet.
- cpu1.cpuFlags |= V6809_ASSERT_LINE_IRQ;
- cpu2.cpuFlags |= V6809_ASSERT_LINE_IRQ;
- mcu.cpuFlags |= V63701_ASSERT_LINE_IRQ;
+ cpu1.cpuFlags |= V6809_LINE_IRQ;
+ cpu2.cpuFlags |= V6809_LINE_IRQ;
+ mcu.cpuFlags |= V63701_LINE_IRQ;
// while (cpu1.clock < 25000)
// 1.538 MHz = 25633.333... cycles per frame (1/60 s)
// 25600 cycles/frame
// 40 works, until it doesn't... :-P
// 640 * 40
// 800 * 32
+// 20 seems to work... :-)
// Interesting, putting IRQs at 30 Hz makes it run at the correct speed. Still hangs in the demo, though.
- for(int i=0; i<640; i++)
+// for(int i=0; i<640; i++)
+ for(int i=0; i<1280; i++)
{
// Gay, but what are ya gonna do?
// There's better ways, such as keeping track of when slave writes to master, etc...
- Execute6809(&cpu1, 40);
- Execute6809(&cpu2, 40);
+// Execute6809(&cpu1, 40);
+// Execute6809(&cpu2, 40);
+ Execute6809(&cpu1, 20);
+ Execute6809(&cpu2, 20);
// MCU runs at 1,536,000 Hz
// 1536000 / 60 / 640 == 40
- Execute63701(&mcu, 40);
+// Execute63701(&mcu, 40);
+ Execute63701(&mcu, 20);
}
BlitChar(charROM, gram1);
+ // Make sure that the sprite RAM lags by one frame... :-)
+ if (copySprites)
+ {
+ CopySprites();
+ copySprites = false;
+ }
+
frameTickCount++;
if (frameTickCount == 3)
return 1;
}
-
-#if 0
-/*
-Hitachi uC runs at 6.144 MHz
-YM2151 runs at 3.579580 MHz
-
-
-Rolling Thunder Memory map
---------------------------
-Most of the decoding is done by custom chips (CUS47 and CUS41), so the memory
-map is inferred by program behaviour. The customs also handle internally irq
-and watchdog.
-
-The main CPU memory map is the same in all games because CUS47 is used by all
-games. The sub CPU and sound CPU, on the other hand, change because CUS41 is
-replaced by other chips.
-
-All RAM is shared between main and sub CPU, except for sound RAM which is
-shared between main and sound CPU; the portion of object RAM that is overlapped
-by sound RAM is used exclusively by the sub CPU.
-
-MAIN CPU:
-
-Address Dir Data Name Description
-------------------- --- -------- --------- -----------------------
-000x xxxx xxxx xxxx R/W xxxxxxxx SCROLL0 tilemap 0/1 RAM (shared with sub CPU)
-001x xxxx xxxx xxxx R/W xxxxxxxx SCROLL1 tilemap 2/3 RAM (shared with sub CPU)
-0100 00xx xxxx xxxx R/W xxxxxxxx SOUND sound RAM (through CUS30, shared with MCU)
-0100 0000 xxxx xxxx R/W xxxxxxxx portion holding the sound wave data
-0100 0001 00xx xxxx R/W xxxxxxxx portion holding the sound registers
-010x xxxx xxxx xxxx R/W xxxxxxxx OBJECT work RAM (shared with sub CPU) [1]
-0101 1xxx xxxx xxxx R/W xxxxxxxx portion holding sprite registers
-011x xxxx xxxx xxxx R xxxxxxxx ROM 9D program ROM (banked) [2]
-1xxx xxxx xxxx xxxx R xxxxxxxx ROM 9C program ROM
-1000 00-- ---- ---- W -------- watchdog reset (RES generated by CUS47)
-1000 01-- ---- ---- W -------- main CPU irq acknowledge (IRQ generated by CUS47)
-1000 1x-- ---- ---- W -------- BANK tile gfx bank select (data is in A10) (latch in CUS47)
-1001 00-- ---- -x0x W xxxxxxxx LATCH0 tilemap 0/1 X scroll + priority
-1001 00-- ---- -x10 W xxxxxxxx LATCH0 tilemap 0/1 Y scroll
-1001 00-- ---- --11 W ------xx BAMNKM ROM 9D bank select
-1001 01-- ---- -x0x W xxxxxxxx LATCH1 tilemap 2/3 X scroll + priority
-1001 01-- ---- -x10 W xxxxxxxx LATCH1 tilemap 2/3 Y scroll
-1001 01-- ---- --11 W ------xx BAMNKS ROM 12D bank select
-1100 00-- ---- ---- W xxxxxxxx BACKCOLOR background color
-
-[1] Note that this is partially overlapped by sound RAM
-[2] In Rolling Thunder and others, replaced by the ROM/voice expansion board
-
-
-SUB CPU:
-
-Address Dir Data Name Description
-------------------- --- -------- --------- -----------------------
-000x xxxx xxxx xxxx R/W xxxxxxxx SUBOBJ work RAM (shared with main CPU)
-0001 1xxx xxxx xxxx R/W xxxxxxxx portion holding sprite registers
-001x xxxx xxxx xxxx R/W xxxxxxxx SUBSCR0 tilemap 0/1 RAM (shared with main CPU)
-010x xxxx xxxx xxxx R/W xxxxxxxx SUBSCR1 tilemap 2/3 RAM (shared with main CPU)
-011x xxxx xxxx xxxx R xxxxxxxx ROM 12D program ROM (banked) [1]
-1xxx xxxx xxxx xxxx R xxxxxxxx ROM 12C program ROM
-1000 0--- ---- ---- W -------- watchdog reset (MRESET generated by CUS41)
-1000 1--- ---- ---- W -------- main CPU irq acknowledge (generated by CUS41)
-1101 0--- ---- -x0x W xxxxxxxx LATCH0 tilemap 0/1 X scroll + priority
-1101 0--- ---- -x10 W xxxxxxxx LATCH0 tilemap 0/1 Y scroll
-1101 0--- ---- --11 W ------xx BAMNKM ROM 9D bank select
-1101 1--- ---- -x0x W xxxxxxxx LATCH1 tilemap 2/3 X scroll + priority
-1101 1--- ---- -x10 W xxxxxxxx LATCH1 tilemap 2/3 Y scroll
-1101 1--- ---- --11 W ------xx BAMNKS ROM 12D bank select
-
-[1] Only used by Rolling Thunder
-
-
-MCU:
-
-Address Dir Data Name Description
-------------------- --- -------- --------- -----------------------
-0000 0000 xxxx xxxx MCU internal registers, timers, ports and RAM
-0001 xxxx xxxx xxxx R/W xxxxxxxx RAM 3F sound RAM (through CUS30, partially shared with main CPU)
-0001 0000 xxxx xxxx R/W xxxxxxxx portion holding the sound wave data
-0001 0001 00xx xxxx R/W xxxxxxxx portion holding the sound registers
-0010 0--- --00 ---x R/W xxxxxxxx YMCS YM2151
-0010 0--- --01 ---- n.c.
-0010 0--- --10 ---- R xxxxxxxx PORTA switch inputs
-0010 0--- --11 ---- R xxxxxxxx PORTB dip switches
-01xx xxxx xxxx xxxx R xxxxxxxx ROM 6B program ROM (lower half)
-10xx xxxx xxxx xxxx R xxxxxxxx ROM 6B program ROM (upper half)
-1011 0--- ---- ---- W unknown (CUS41)
-1011 1--- ---- ---- W unknown (CUS41)
-1111 xxxx xxxx xxxx R xxxxxxxx MCU internal ROM
-
-
-Notes:
------
-- There are two watchdogs, one per CPU (or maybe three). Handling them
- separately is necessary to allow entering service mode without manually
- resetting in rthunder and genpeitd: only one of the CPUs stops writing to
- the watchdog.
-
-- The sprite hardware buffers spriteram: the program writes the sprite list to
- offsets 4-9 of every 16-byte block, then at the end writes to offset 0x1ff2
- of sprite RAM to signal the chip that the list is complete. The chip will
- copy the list from 4-9 to 10-15 and use it from there. This has not been
- verified on the real hardware, but it is the most logical way of doing it.
- Emulating this behaviour and not using an external buffer is important in
- rthunder: when you insert a coin, the whole sprite RAM is cleared, but 0x1ff2
- is not written to. If we buffered spriteram to an external buffer, this would
- cause dangling sprites because the buffer would not be updated.
-
-- spriteram buffering fixes sprite lag, but causes a glitch in rthunder when
- entering a door. The *closed* door is made of tiles, but the *moving* door is
- made of sprites. Since sprites are delayed by 1 frame, when you enter a door
- there is one frame where neither the tile-based closed door nor the
- sprite-based moving door is shown, so it flickers. This behavior has been
- confirmed on a real PCB.
-
-TODO:
-----
-- The two unknown writes for the MCU are probably watchdog reset and irq acknowledge,
- but they don't seem to work as expected. During the first few frames they are
- written out of order and hooking them up in the usual way causes the MCU to
- stop receiving interrupts.
-*/
-#endif
-