From 275abe066e3b5944c4e7103af6e513ebbf89fbb1 Mon Sep 17 00:00:00 2001 From: "Benjamin C. Wiley Sittler" Date: Sun, 26 Feb 2023 10:00:28 -0500 Subject: [PATCH] Add MAPPER_GG_Turbo_9_in_1_8000_4000 for "Turbo 9 in 1 [Street Fighter 2] (Unl)" GG multicart. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is mostly working. One game (Choplifter) does not start, and there are glitches after restoring a save state that uses SMS-GG mode (glitchiness varies by game. some show wrong graphics, others reset, still others are fine.) I qualified the name of Turbo 9 in 1 the first menu entry since there are apparently a bunch of very similarly-named cartridges with different menus/games, and the label description only makes sense when you are looking at the physical cartridge It's an unlicensed Game Gear multicart with 9 games. The label says "TURBO 9 IN 1" The box also says: COLOR VIDEO GAME SELECT GAMES ON SCREEN GEAR GAME [where you might expect GAME GEAR; maybe it is a typo?] The label and box also have a game listing in both English and traditional Chinese. Transcription/translation errors are my own: ``` 1. STREET FIGHTER III / 街頭霸王Ⅲ (i.e. "street bully III"/"Street Fighter III") 2. CHAMPION SOCCER / 世界杯足球 (i.e. "world cup football") 3. CHOPLIFTER / 直昇機救人 (i.e. "helicopter rescue") 4. HANG ON II / 高速電單車Ⅱ (i.e. "high-speed motorcycle II") 5. SUPER BUBBLE BOBBLE / 泡泡龍 (i.e. "bubble dragon"/"Bubble Bobble") 6. SUPER MARIO 2 / 孖寶兄弟2 (i.e. "twin brothers 2") 7. COLUMNS / 寶石方塊 (i.e. "gem cube") 8. MY HERO / 熱血硬派 (i.e. "hot-blooded tough guy") 9. GALAGA 93 / 蜜蜂機93 (i.e. "bee machine 93") ``` Some character choices suggest a possible Hong Kong origin, e.g. 電單車 for motorcycle and 街頭霸王 for Street Fighter. The mapper is an extended version of Codemasters allowing initial "base page" offset selection. Only Jang Pung II uses the mapper after that point. The menu has a single screen: ``` G.G. SCREEN SELECT┆ PUSH 2. START GAME┆ PUSH ↑. ↓. SELECT┆ ┆ 01.STREET FIGHTER 2 [0x8000=0x10, 0x4000=0x11]; it's 256K SMS Jang Pung II [SMS-GG] (KR) 02.CHAMPION SOCCER┆ [0x8000=0x00, 0x4000=0x11]; it's 32K SG-1000 Champion Soccer modified to coexist with the menu code 03.CHOPLIFTER ┆ [0x8000=0x02, 0x4000=0x11]; it's 32K SG-1000 Choplifter (JP,AU) →04.HANG ON 2 ┆ [0x8000=0x04, 0x4000=0x11]; it's 32K SMS Hang On (EU,AU,BR,DE,IT) 05.SUPER BUBBLE BOBBLE [0x8000=0x06, 0x4000=0x11]; it's 32K SMS Super Bubble Bobble (KR) 06.SUPER MARIO ┆ [0x8000=0x0e, 0x4000=0x11]; it's 32K SMS Super Boy II (KR) (32K) 07.COLUMNS ┆ [0x8000=0x0a, 0x4000=0x11]; it's 32K GG Columns [v0] (JP) 08.MY HERO ┆ [0x8000=0x0c, 0x4000=0x11]; it's 32K SMS My Hero (US,EU,BR,PT,DE,IT) 09.GALAGA ┆ [0x8000=0x08, 0x4000=0x11]; it's 32K SG-1000 Sega-Galaga with the "Sega" removed ``` NOTE: the menu can operate to some extent in both native GG mode and in SMS-GG mode. In native GG mode some text is cut off. I've added a "┆" indicator to show the first column that is cut off. It is unclear how the cartridge decides whether to switch from native GG mode (which is the power-on default) to SMS-GG mode, but it appears to happen at the moment 0x11 is written to 0x4000 *unless* 0x0a was the most recent value previously written to 0x4000. No mechanism is known for programmatically returning the mapper to its power-on default state. --- meka/compat.txt | 3 ++- meka/meka.nam | 1 + meka/srcs/machine.cpp | 24 +++++++++++++++++ meka/srcs/mappers.cpp | 63 +++++++++++++++++++++++++++++++++++++++++++ meka/srcs/mappers.h | 2 ++ meka/srcs/saves.cpp | 29 +++++++++++++++++--- 6 files changed, 118 insertions(+), 4 deletions(-) diff --git a/meka/compat.txt b/meka/compat.txt index ab16dc0f..c8444dd1 100644 --- a/meka/compat.txt +++ b/meka/compat.txt @@ -1454,6 +1454,7 @@ Tom and Jerry: The Movie (JP) Ok Torarete Tamaruka!? (JP) Ok True Lies Ok + Turbo 9 in 1 [Street Fighter 2] *Ok Ultimate Soccer Ok Urban Strike Ok Vampire - Master of Darkness (US) Ok @@ -1498,7 +1499,7 @@ Zoop (US) Ok Zoop [Proto] (US) Ok ----------------------------------------------------------------------------- - 517 games tested - 506 are "Ok" - Compatibility rate: 97.63% + 518 games tested - 507 are "Ok" - Compatibility rate: 97.88% ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- diff --git a/meka/meka.nam b/meka/meka.nam index 77c032bf..2e43eca5 100644 --- a/meka/meka.nam +++ b/meka/meka.nam @@ -1332,6 +1332,7 @@ GG a1453efa D9F631F59C7933C3 Tom and Jerry: The Movie/COUNTRY=JP,BR/PRODUCT_N GG 5cd33ff2 DC1F080D96A53D78 Tom and Jerry: The Movie/COUNTRY=US,EU/PRODUCT_NO=2434 GG 5bcf9b97 F9471D41510253BC Torarete Tamaruka!?/COUNTRY=JP/PRODUCT_NO=G-3348 GG 5173b02a B31BDB3827A8440C True Lies/COUNTRY=US,EU,JP/PRODUCT_NO=T-81318,T-81318-50,T-81167 +GG 763b5d62 BD832687D3D0244C Turbo 9 in 1 [Street Fighter 2]/EMU_MAPPER=30 GG 820965a3 78192012DB30BF73 Ultimate Soccer/COUNTRY=JP,EU,BR/PRODUCT_NO=G-3333,075300 GG 185e9fc1 07D77087D26DD517 Urban Strike/COUNTRY=US/PRODUCT_NO=T-100048 GG 7ec64025 84C269594C15F3A4 Vampire - Master of Darkness/COUNTRY=US/PRODUCT_NO=2437/COMMENT=US version of "In the Wake of Vampire" (Japan) and "Master of Darkness" (Europe). diff --git a/meka/srcs/machine.cpp b/meka/srcs/machine.cpp index f23e934f..3f267760 100644 --- a/meka/srcs/machine.cpp +++ b/meka/srcs/machine.cpp @@ -22,6 +22,7 @@ #include "tvtype.h" #include "sound/fmunit.h" #include "sound/psg.h" +#include "app_game.h" //----------------------------------------------------------------------------- // Data @@ -196,6 +197,9 @@ void Machine_Set_Handler_MemRW(void) case MAPPER_SMS_Korean_MSX_32KB_2000: WrZ80 = WrZ80_NoHook = Write_Mapper_SMS_Korean_MSX_32KB_2000; break; + case MAPPER_GG_Turbo_9_in_1_8000_4000: + WrZ80 = WrZ80_NoHook = Write_Mapper_GG_Turbo_9_in_1_8000_4000; + break; } } @@ -485,6 +489,26 @@ void Machine_Set_Mapping (void) g_machine.mapper_regs[0] = 0; break; + case MAPPER_GG_Turbo_9_in_1_8000_4000: + Map_8k_ROM(0, 0x00 & tsms.Pages_Mask_8k); + Map_8k_ROM(1, 0x01 & tsms.Pages_Mask_8k); + Map_8k_ROM(2, 0x02 & tsms.Pages_Mask_8k); + Map_8k_ROM(3, 0x03 & tsms.Pages_Mask_8k); + Map_8k_ROM(4, 0x00 & tsms.Pages_Mask_8k); + Map_8k_ROM(5, 0x01 & tsms.Pages_Mask_8k); + Map_8k_RAM(6, 0); + Map_8k_RAM(7, 0); + g_machine.mapper_regs_count = 3; + for (int i = 0; i != MAPPER_REGS_MAX; i++) + g_machine.mapper_regs[i] = 0; + g_machine.mapper_regs[0] = 0x80; + g_machine.mapper_regs[2] = 1; + drv_set(DRV_GG); + gamebox_resize_all(); + VDP_UpdateLineLimits(); + Video_GameMode_UpdateBounds(); + break; + case MAPPER_SC3000_Survivors_Multicart: g_machine.mapper_regs_count = 1; for (int i = 0; i != MAPPER_REGS_MAX; i++) diff --git a/meka/srcs/mappers.cpp b/meka/srcs/mappers.cpp index 00bf3386..703ff79e 100644 --- a/meka/srcs/mappers.cpp +++ b/meka/srcs/mappers.cpp @@ -14,6 +14,9 @@ #include "shared.h" #include "mappers.h" #include "eeprom.h" +#include "vdp.h" +#include "video.h" +#include "app_game.h" //----------------------------------------------------------------------------- // Data @@ -952,6 +955,66 @@ WRITE_FUNC (Write_Mapper_SMS_Korean_MSX_32KB_2000) Write_Error (Addr, Value); } +// Mapper #30 +// Turbo 9 in 1 +WRITE_FUNC(Write_Mapper_GG_Turbo_9_in_1_8000_4000) +{ + if (Addr == 0x8000 || Addr == 0x4000) // Configurable segment ----------------------------------------------- + { + if (Addr == 0x8000) { + g_machine.mapper_regs[1] = Value; + } + if (Addr == 0x4000) { + g_machine.mapper_regs[2] = Value; + } + if ((g_machine.mapper_regs[2] == 0x11) && (g_machine.mapper_regs[0] == 0x80)) { + // use of 0x80 to signal the initial state is a Meka + // extension but does not conflict with the menu code's + // use of the mapper + g_machine.mapper_regs[0] = g_machine.mapper_regs[1]; + g_machine.mapper_regs[1] = 0; + g_machine.mapper_regs[2] = 1; + } else if ((g_machine.mapper_regs[2] == 0x11) && (g_machine.mapper_regs[1] == 0x80)) { + // use of 0x80 to return to the initial state is a Meka + // extension but does not conflict with the menu code's + // use of the mapper, nor does it conflict with the one + // game that uses the mapper + g_machine.mapper_regs[0] = 0x80; + g_machine.mapper_regs[1] = 0; + g_machine.mapper_regs[2] = 1; + } + Map_8k_ROM(0, (g_machine.mapper_regs[0] * 2) & tsms.Pages_Mask_8k); + Map_8k_ROM(1, (g_machine.mapper_regs[0] * 2 + 1) & tsms.Pages_Mask_8k); + Map_8k_ROM(2, ((g_machine.mapper_regs[0] + g_machine.mapper_regs[2]) * 2) & tsms.Pages_Mask_8k); + Map_8k_ROM(3, ((g_machine.mapper_regs[0] + g_machine.mapper_regs[2]) * 2 + 1) & tsms.Pages_Mask_8k); + Map_8k_ROM(4, ((g_machine.mapper_regs[0] + g_machine.mapper_regs[1]) * 2) & tsms.Pages_Mask_8k); + Map_8k_ROM(5, ((g_machine.mapper_regs[0] + g_machine.mapper_regs[1]) * 2 + 1) & tsms.Pages_Mask_8k); + // It is unclear how the cartridge decides whether to switch + // from native GG mode (which is the power-on default) to + // SMS-GG mode, but it appears to happen at the moment 0x11 is + // written to 0x4000 *unless* 0x0a was the most recent value + // previously written to 0x4000. + if ((g_machine.mapper_regs[0] != 0x80) && (g_machine.mapper_regs[0] != 0x0a)) { + drv_set(DRV_SMS); + } else { + drv_set(DRV_GG); + } + gamebox_resize_all(); + VDP_UpdateLineLimits(); + Video_GameMode_UpdateBounds(); + return; + } + + switch (Addr >> 13) + { + // RAM [0xC000] = [0xE000] ------------------------------------------------ + case 6: Mem_Pages[6][Addr] = Value; return; + case 7: Mem_Pages[7][Addr] = Value; return; + } + + Write_Error(Addr, Value); +} + // Based on MSX ASCII 8KB mapper? http://bifi.msxnet.org/msxnet/tech/megaroms.html#ascii8 // - This mapper requires 4 registers to save bank switching state. // However, all other mappers so far used only 3 registers, stored as 3 bytes. diff --git a/meka/srcs/mappers.h b/meka/srcs/mappers.h index 3711266f..82d97d5d 100644 --- a/meka/srcs/mappers.h +++ b/meka/srcs/mappers.h @@ -50,6 +50,7 @@ #define MAPPER_SMS_Korean_MD_FFF5 (25) // Registers at 0xFFF5 and 0xFFFF (Jaemiissneun Game Mo-eumjip 42/65 Hap [SMS-MD], Pigu Wang Hap ~ Jaemiiss-neun Game Mo-eumjip [SMS-MD]) #define MAPPER_SMS_Korean_MD_FFFA (26) // Registers at 0xFFFA and 0xFFFF (Game Jiphap 30 Hap [SMS-MD]) #define MAPPER_SMS_Korean_MSX_32KB_2000 (27) // Register at 0x2000 (2 Hap in 1 (Moai-ui bomul, David-2)) +#define MAPPER_GG_Turbo_9_in_1_8000_4000 (30) // Registers at 0x8000 and 0x4000 (Turbo 9 in 1) #define READ_FUNC(_NAME) u8 _NAME(register u16 Addr) #define WRITE_FUNC(_NAME) void _NAME(register u16 Addr, register u8 Value) @@ -96,6 +97,7 @@ WRITE_FUNC (Write_Mapper_SMS_Korean_MD_FFF0); WRITE_FUNC (Write_Mapper_SMS_Korean_MD_FFF5); WRITE_FUNC (Write_Mapper_SMS_Korean_MD_FFFA); WRITE_FUNC (Write_Mapper_SMS_Korean_MSX_32KB_2000); +WRITE_FUNC (Write_Mapper_GG_Turbo_9_in_1_8000_4000); //----------------------------------------------------------------------------- void Out_SC3000_SurvivorsMulticarts_DataWrite(u8 v); diff --git a/meka/srcs/saves.cpp b/meka/srcs/saves.cpp index 09bb14b8..ece5838f 100644 --- a/meka/srcs/saves.cpp +++ b/meka/srcs/saves.cpp @@ -26,6 +26,7 @@ void Load_Game_Fixup(void) { int i; u8 b; + bool sms_gg_mode_in_mapper = false; // CPU #ifdef MARAT_Z80 @@ -144,13 +145,33 @@ void Load_Game_Fixup(void) case MAPPER_SMS_Korean_MSX_32KB_2000: WrZ80_NoHook(0x2000, g_machine.mapper_regs[0]); break; + case MAPPER_GG_Turbo_9_in_1_8000_4000: + if (1) { + unsigned int base_page_16k = g_machine.mapper_regs[0]; + unsigned int page_8000_offset_16k = g_machine.mapper_regs[1]; + unsigned int page_4000_offset_16k = g_machine.mapper_regs[2]; + // use of 0x80 to return to the initial state is a Meka + // extension but does not conflict with the menu code's + // use of the mapper, nor does it conflict with the one + // game that uses the mapper + WrZ80_NoHook(0x8000, 0x80); + WrZ80_NoHook(0x4000, 0x11); + WrZ80_NoHook(0x8000, base_page_16k); + WrZ80_NoHook(0x4000, 0x11); + WrZ80_NoHook(0x4000, page_4000_offset_16k); + WrZ80_NoHook(0x8000, page_8000_offset_16k); + sms_gg_mode_in_mapper = true; + } + break; } } // VDP/Graphic related - tsms.VDP_Video_Change |= VDP_VIDEO_CHANGE_ALL; - VDP_UpdateLineLimits(); - // FALSE!!! // tsms.VDP_Line = 224; + if (!sms_gg_mode_in_mapper) { + tsms.VDP_Video_Change |= VDP_VIDEO_CHANGE_ALL; + VDP_UpdateLineLimits(); + // FALSE!!! // tsms.VDP_Line = 224; + } // Rewrite all VDP registers (we can do that since it has zero side-effect) for (i = 0; i < 16; i ++) @@ -339,6 +360,7 @@ int Save_Game_MSV(FILE *f) case MAPPER_SMS_Korean_MD_FFF5: case MAPPER_SMS_Korean_MD_FFFA: case MAPPER_SMS_Korean_MSX_32KB_2000: + case MAPPER_GG_Turbo_9_in_1_8000_4000: default: fwrite (RAM, 0x2000, 1, f); // Do not use g_driver->ram because of g_driver video mode change break; @@ -518,6 +540,7 @@ int Load_Game_MSV(FILE *f) case MAPPER_SMS_Korean_MD_FFF5: case MAPPER_SMS_Korean_MD_FFFA: case MAPPER_SMS_Korean_MSX_32KB_2000: + case MAPPER_GG_Turbo_9_in_1_8000_4000: default: fread (RAM, 0x2000, 1, f); // Do not use g_driver->ram because of g_driver video mode change break;