mirror of
https://github.com/libretro/Genesis-Plus-GX-Wide.git
synced 2024-11-27 02:20:22 +00:00
Resync with non-wide
This commit is contained in:
parent
04e9a4d9c8
commit
e7878ba7a3
@ -68,6 +68,9 @@ include:
|
||||
file: '/ios9.yml'
|
||||
|
||||
################################## CONSOLES ################################
|
||||
# PlayStation3
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/psl1ght-static.yml'
|
||||
|
||||
# PlayStation Vita
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
@ -225,6 +228,12 @@ libretro-build-tvos-arm64:
|
||||
- .core-defs
|
||||
|
||||
################################### CONSOLES #################################
|
||||
# PlayStation3
|
||||
libretro-build-psl1ght:
|
||||
extends:
|
||||
- .libretro-psl1ght-static-retroarch-master
|
||||
- .core-defs
|
||||
|
||||
# PlayStation Vita
|
||||
libretro-build-vita:
|
||||
extends:
|
||||
|
@ -49,7 +49,8 @@ Genesis Plus GX 1.7.5 (xx/xx/xxxx) (Eke-Eke)
|
||||
* fixed byte access to font data registers
|
||||
* fixed pending level 1 interrupts when GFX interrupt is disabled (fixes random freezes when exiting "Batman Returns" option menu)
|
||||
* fixed CDD seek command again (Final Fight CD freeze with model 2 BIOS)
|
||||
* fixed CDD status reported during seek/access time (sound effect synchronization issue in Bari Arm)
|
||||
* fixed CDD status reported during seek/access time (fixes sound effect synchronization issue in Bari Arm)
|
||||
* fixed CDD position reset when disc is stopped (fixes random freezes in Spiderman vs Kingpin when switching between audio tracks)
|
||||
* fixed word access to CDD control register (fixes spurious audio track playback on startup with Mode 1 patched games using MSU-MD driver)
|
||||
* fixed CD communication registers state on peripheral reset (fixes SUB-CPU side initialization in MSU-MD sample demo and some Mode 1 patched games using MSU-MD driver)
|
||||
* fixed 32x32 pixels stamp index masking during GFX operation (fixes graphics rotation/scaling effects in "Chuck Rock II - Son of Chuck")
|
||||
@ -68,6 +69,7 @@ Genesis Plus GX 1.7.5 (xx/xx/xxxx) (Eke-Eke)
|
||||
* improved console region auto-detection for a few PAL-only games (The Smurfs Travel the World & Williams Arcade's Greatest Hits)
|
||||
* improved I2C EEPROM boards emulation accuracy
|
||||
* improved SVP memory handlers accuracy (fixes Virtua Racing debug mode)
|
||||
* fixed Realtec mapper behavior on soft-reset and with TMSS hardware
|
||||
* fixed Game Genie / Pro Action Replay lock-on support when Mega CD hardware is enabled
|
||||
* fixed Game Genie / Pro Action Replay lock-on support with games larger than 8MB
|
||||
* fixed SRAM support in Triple Play 96 & Triple Play - Gold Edition
|
||||
@ -109,6 +111,8 @@ Genesis Plus GX 1.7.5 (xx/xx/xxxx) (Eke-Eke)
|
||||
* fixed 68k undocumented behaviors for ABCD/SBCD/NBCD instructions (thanks to Flamewing for his test ROM)
|
||||
* fixed 68k timing of BTST Dn,#Imm instruction (verified by Flamewing in original microcode)
|
||||
* fixed 68k timings of ANDI.L #Imm,Dn, ADDQ.W #Imm,An and TAS instructions (cf. Yacht.txt)
|
||||
* fixed 68k timings of BCHG, BCLR, BTST Dn,#Imm and Dn,Dm instructions when bit number is less than 16 (cf. Yacht.txt)
|
||||
* fixed 68k timings of CHK, TRAP, TRAPV, LINEA and LINEF exceptions (cf. Yacht.txt)
|
||||
* fixed 68K DIVU instruction timings for SUB-CPU
|
||||
* fixed Z80 interrupt duration (Bomb on Basic City music running too fast)
|
||||
* fixed Z80 SP register initialization on power-on for Master System & Game Gear
|
||||
@ -149,6 +153,7 @@ Genesis Plus GX 1.7.5 (xx/xx/xxxx) (Eke-Eke)
|
||||
* fixed Master System II extended video modes sprite parsing (fixes Mega Man 2 demo)
|
||||
* fixed Game Gear display rendering regression when left/right borders were disabled
|
||||
* fixed 68k cycles delay on invalid VRAM writes (fixes "Microcosm" intro loop)
|
||||
* fixed address/code potential corruption by one-instruction execution delay after HV interrupts activation (fixes Pugsy's Pyramids stage boss)
|
||||
* optimized tile caching
|
||||
|
||||
[Core/Sound]
|
||||
@ -168,6 +173,7 @@ Genesis Plus GX 1.7.5 (xx/xx/xxxx) (Eke-Eke)
|
||||
* fixed YM2612 potential issue with SSG-EG inverted attenuation level on Key OFF
|
||||
* fixed YM2413 carrier/modulator phase reset after channel Key ON (fixes Japanese Master System BIOS music)
|
||||
* fixed YM2413 intruments ROM (verified on YM2413B die)
|
||||
* disabled PSG output on Mark III hardware when FM output is enabled (verified with real FM sound unit hardware)
|
||||
|
||||
[Gamecube/Wii]
|
||||
---------------
|
||||
|
12
README.md
12
README.md
@ -12,17 +12,17 @@ Genesis Plus GX is an open-source Sega 8/16 bit emulator focused on accuracy and
|
||||
|
||||
----
|
||||
|
||||
The source code, initially based on Genesis Plus 1.2a by [Charles MacDonald](http://www.techno-junk.org/ ) has been heavily modified & enhanced, with respect to original goals and design, in order to improve emulation accuracy as well as adding support for new peripherals, cartridge or console hardware and many other exciting [features](https://bitbucket.org/eke/genesis-plus-gx/src/master/wiki/Features.md).
|
||||
The source code, initially based on Genesis Plus 1.2a by [Charles MacDonald](http://www.techno-junk.org/ ) has been heavily modified & enhanced, with respect to original goals and design, in order to improve emulation accuracy as well as adding support for new peripherals, cartridge or console hardware and many other exciting [features](https://github.com/ekeeke/Genesis-Plus-GX/blob/master/wiki/Features.md).
|
||||
|
||||
The result is that Genesis Plus GX is now more a continuation of the original project than a simple port, providing very accurate emulation and [100% compatibility](https://bitbucket.org/eke/genesis-plus-gx/src/master/wiki/Compatibility.md) with Genesis / Mega Drive, Sega/Mega CD, Master System, Game Gear & SG-1000 released software (including all unlicensed or pirate known dumps), also emulating backwards compatibility modes when available. All the people who contributed (directly or indirectly) to this project are listed on the [Credits](https://bitbucket.org/eke/genesis-plus-gx/src/master/wiki/Credits.md) page.
|
||||
The result is that Genesis Plus GX is now more a continuation of the original project than a simple port, providing very accurate emulation and [100% compatibility](https://github.com/ekeeke/Genesis-Plus-GX/blob/master/wiki/Compatibility.md) with Genesis / Mega Drive, Sega/Mega CD, Master System, Game Gear & SG-1000 released software (including all unlicensed or pirate known dumps), also emulating backwards compatibility modes when available. All the people who contributed (directly or indirectly) to this project are listed on the [Credits](https://github.com/ekeeke/Genesis-Plus-GX/blob/master/wiki/Credits.md) page.
|
||||
|
||||
----
|
||||
|
||||
Multi-platform sourcecode (core), which is made available for use under a specific non-commercial [license](https://bitbucket.org/eke/genesis-plus-gx/src/master/LICENSE.txt), is maintained on [Bitbucket](https://bitbucket.org/eke/genesis-plus-gx/src/) / [Github](https://github.com/ekeeke/Genesis-Plus-GX) so that other Genesis Plus ports can benefit of it, as I really wish this emulator becomes a reference for _portable_ and _accurate_ Sega 8/16-bit emulation. If you ported this emulator to other platforms or need help porting it, feel free to contact me.
|
||||
Multi-platform sourcecode (core), which is made available for use under a specific non-commercial [license](https://github.com/ekeeke/Genesis-Plus-GX/blob/master/LICENSE.txt), is maintained on [Bitbucket](https://bitbucket.org/eke/genesis-plus-gx/src/) / [Github](https://github.com/ekeeke/Genesis-Plus-GX) so that other Genesis Plus ports can benefit of it, as I really wish this emulator becomes a reference for _portable_ and _accurate_ Sega 8/16-bit emulation. If you ported this emulator to other platforms or need help porting it, feel free to contact me.
|
||||
|
||||
----
|
||||
|
||||
Latest official Gamecube / Wii standalone port (screenshots below) is available [here](https://bitbucket.org/eke/genesis-plus-gx/downloads). Be sure to check the included user manual first. A [startup guide](https://bitbucket.org/eke/genesis-plus-gx/src/master/wiki/Getting%20Started.md) and a [FAQ](https://bitbucket.org/eke/genesis-plus-gx/src/master/wiki/Frequently%20Asked%20Questions.md) are also available.
|
||||
Latest official Gamecube / Wii standalone port (screenshots below) is available [here](https://github.com/ekeeke/Genesis-Plus-GX/tree/master/builds). Be sure to check the included [user manual](https://github.com/ekeeke/Genesis-Plus-GX/blob/master/gx/docs/README.pdf) first. A [startup guide](https://github.com/ekeeke/Genesis-Plus-GX/blob/master/wiki/Getting%20Started.md) and a [FAQ](https://github.com/ekeeke/Genesis-Plus-GX/blob/master/wiki/Frequently%20Asked%20Questions.md) are also available.
|
||||
|
||||
![MainMenu.png](https://bitbucket.org/repo/7AjE6M/images/3565283297-MainMenu.png)
|
||||
![menu_load.png](https://bitbucket.org/repo/7AjE6M/images/164055790-menu_load.png)
|
||||
@ -32,9 +32,7 @@ Latest official Gamecube / Wii standalone port (screenshots below) is available
|
||||
|
||||
----
|
||||
|
||||
You can also test latest compiled builds for Gamecube / Wii and Retroarch (Windows 32-bit version only) by downloading them from [here](https://bitbucket.org/eke/genesis-plus-gx/src/master/builds/).
|
||||
|
||||
|
||||
You can also test latest compiled builds for Gamecube / Wii and Retroarch (Windows 32-bit version only) by downloading them from [here](https://github.com/ekeeke/Genesis-Plus-GX/tree/master/builds).
|
||||
|
||||
----
|
||||
|
||||
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 3.8 MiB After Width: | Height: | Size: 3.8 MiB |
Binary file not shown.
Before Width: | Height: | Size: 4.0 MiB After Width: | Height: | Size: 4.0 MiB |
@ -2,7 +2,7 @@
|
||||
* Genesis Plus
|
||||
* Mega Drive cartridge hardware support
|
||||
*
|
||||
* Copyright (C) 2007-2021 Eke-Eke (Genesis Plus GX)
|
||||
* Copyright (C) 2007-2023 Eke-Eke (Genesis Plus GX)
|
||||
*
|
||||
* Many cartridge protections were initially documented by Haze
|
||||
* (http://haze.mameworld.info/)
|
||||
@ -71,6 +71,7 @@ static uint32 mapper_flashkit_r(uint32 address);
|
||||
static uint32 mapper_smw_64_r(uint32 address);
|
||||
static void mapper_smw_64_w(uint32 address, uint32 data);
|
||||
static void mapper_realtec_w(uint32 address, uint32 data);
|
||||
static uint32 mapper_realtec_r(uint32 address);
|
||||
static void mapper_seganet_w(uint32 address, uint32 data);
|
||||
static void mapper_32k_w(uint32 data);
|
||||
static void mapper_64k_w(uint32 data);
|
||||
@ -377,20 +378,20 @@ void md_cart_init(void)
|
||||
zbank_memory_map[i].write = zbank_unused_w;
|
||||
}
|
||||
|
||||
/* support for Quackshot REV 01 (real) dump */
|
||||
if (strstr(rominfo.product,"00004054-01") && (cart.romsize == 0x80000))
|
||||
/* support for Quackshot REV A original ROM dump (512KB) */
|
||||
if (strstr(rominfo.product,"00004054-01") && (cart.romsize == 0x80000) && (rominfo.checksum == 0xa4b3))
|
||||
{
|
||||
/* $000000-$0fffff: first 256K mirrored (A18 not connected to ROM chip, A19 not decoded) */
|
||||
/* $000000-$0fffff: lower 256KB mirrored (VA18 and VA19 not connected to ROM chip) */
|
||||
for (i=0x00; i<0x10; i++)
|
||||
{
|
||||
/* $200000-$3fffff: mirror of $000000-$1fffff (A21 not decoded) */
|
||||
/* $200000-$3fffff: mirror of $000000-$1fffff (VA21 not connected to ROM chip) */
|
||||
m68k.memory_map[i].base = m68k.memory_map[i + 0x20].base = cart.rom + ((i & 0x03) << 16);
|
||||
}
|
||||
|
||||
/* $100000-$1fffff: second 256K mirrored (A20 connected to ROM chip A18) */
|
||||
/* $100000-$1fffff: upper 256KB mirrored (VA20 connected to ROM chip A19) */
|
||||
for (i=0x10; i<0x20; i++)
|
||||
{
|
||||
/* $200000-$3fffff: mirror of $000000-$1fffff (A21 not decoded) */
|
||||
/* $200000-$3fffff: mirror of $000000-$1fffff (VA21 not connected to ROM chip) */
|
||||
m68k.memory_map[i].base = m68k.memory_map[i + 0x20].base = cart.rom + 0x40000 + ((i & 0x03) << 16);
|
||||
}
|
||||
}
|
||||
@ -503,15 +504,12 @@ void md_cart_init(void)
|
||||
memcpy(cart.rom + 0x400000 + i*0x2000, cart.rom + 0x7e000, 0x2000);
|
||||
}
|
||||
|
||||
/* Boot ROM (8KB mirrored) is mapped to $000000-$3FFFFF */
|
||||
for (i=0x00; i<0x40; i++)
|
||||
{
|
||||
m68k.memory_map[i].base = cart.rom + 0x400000;
|
||||
}
|
||||
/* specific read handler for ROM header area */
|
||||
m68k.memory_map[0].read16 = mapper_realtec_r;
|
||||
}
|
||||
|
||||
/* detect specific mappers */
|
||||
if (strstr(rominfo.consoletype,"SEGA SSF"))
|
||||
else if (strstr(rominfo.consoletype,"SEGA SSF"))
|
||||
{
|
||||
/* Everdrive extended SSF mapper */
|
||||
cart.hw.time_w = mapper_512k_w;
|
||||
@ -803,8 +801,23 @@ void md_cart_reset(int hard_reset)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* Realtec mapper */
|
||||
if (cart.hw.realtec)
|
||||
{
|
||||
/* Boot ROM (8KB mirrored) is mapped to $000000-$3FFFFF */
|
||||
for (i=0x00; i<0x40; i++)
|
||||
{
|
||||
m68k.memory_map[i].base = cart.rom + 0x400000;
|
||||
}
|
||||
|
||||
/* Reset mapper */
|
||||
cart.hw.regs[0] = 0;
|
||||
cart.hw.regs[1] = 0;
|
||||
cart.hw.regs[2] = 0;
|
||||
}
|
||||
|
||||
/* reset cartridge mapping */
|
||||
if (cart.hw.bankshift)
|
||||
else if (cart.hw.bankshift)
|
||||
{
|
||||
for (i=0x00; i<0x40; i++)
|
||||
{
|
||||
@ -1615,8 +1628,8 @@ static uint32 mapper_smw_64_r(uint32 address)
|
||||
}
|
||||
|
||||
/*
|
||||
Realtec ROM bankswitch (Earth Defend, Balloon Boy & Funny World, Whac-A-Critter)
|
||||
(Note: register usage is inverted in TascoDlx documentation)
|
||||
Realtec ROM bankswitch (Earth Defend, Balloon Boy & Funny World, Whac-A-Critter, Tom Clown)
|
||||
Verified with real cartridge hardware (slightly different from behavior described in TascoDlx documentation)
|
||||
*/
|
||||
static void mapper_realtec_w(uint32 address, uint32 data)
|
||||
{
|
||||
@ -1624,34 +1637,47 @@ static void mapper_realtec_w(uint32 address, uint32 data)
|
||||
{
|
||||
case 0x402000:
|
||||
{
|
||||
/* number of mapped 64k blocks (the written value is a number of 128k blocks) */
|
||||
cart.hw.regs[2] = data << 1;
|
||||
/* fixed ROM bank size */
|
||||
/* when bit 0 is set, ROM A16 pin is forced to value configured in register below (connected to VA17 otherwise) */
|
||||
/* when bit 1 is set, ROM A17 pin is forced to value configured in register below (connected to VA18 otherwise) */
|
||||
/* other bits have no effect */
|
||||
cart.hw.regs[1] = (data & 3) << 1;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x404000:
|
||||
{
|
||||
/* 00000xxx */
|
||||
cart.hw.regs[0] = data & 7;
|
||||
/* fixed ROM bank selection (4 x 128KB banks) */
|
||||
/* bit 0 corresponds to ROM A16 pin value when forced */
|
||||
/* bit 1 corresponds to ROM A17 pin value when forced */
|
||||
/* other bits have no effect */
|
||||
cart.hw.regs[2] = (data & 3) << 1;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x400000:
|
||||
{
|
||||
/* 00000yy1 */
|
||||
cart.hw.regs[1] = data & 6;
|
||||
|
||||
/* ensure mapped size is not null */
|
||||
if (cart.hw.regs[2])
|
||||
/* ROM access enable */
|
||||
/* when bit 0 is set, ROM A17/A16 pins are set according to above registers and ROM A15/A12 pins are connected to VA16-VA13 (forced to 1 on reset) */
|
||||
/* other bits habe no effect */
|
||||
if (data & 0x01)
|
||||
{
|
||||
/* once ROM access is enabled, ROM mapping can not be modified until next reset */
|
||||
if (!cart.hw.regs[0])
|
||||
{
|
||||
/* mapped start address is 00yy xxx0 0000 0000 0000 0000 */
|
||||
uint32 base = (cart.hw.regs[0] << 1) | (cart.hw.regs[1] << 3);
|
||||
|
||||
/* selected blocks are mirrored into the whole cartridge area */
|
||||
int i;
|
||||
for (i=0x00; i<0x40; i++)
|
||||
{
|
||||
m68k.memory_map[i].base = &cart.rom[(base + (i % cart.hw.regs[2])) << 16];
|
||||
/* 0x000000-0x07ffff mapped area is mirrored in 4MB cartridge range (VA21-VA19 not connected) */
|
||||
uint32 base = i & 7;
|
||||
|
||||
/* adjust 64k mapped area ROM base address according to fixed ROM bank configuration (see above) */
|
||||
base = (base & ~cart.hw.regs[1]) | (cart.hw.regs[2] & cart.hw.regs[1]);
|
||||
|
||||
m68k.memory_map[i].base = &cart.rom[base << 16];
|
||||
}
|
||||
|
||||
cart.hw.regs[0] = data;
|
||||
}
|
||||
}
|
||||
return;
|
||||
@ -1659,6 +1685,19 @@ static void mapper_realtec_w(uint32 address, uint32 data)
|
||||
}
|
||||
}
|
||||
|
||||
static uint32 mapper_realtec_r(uint32 address)
|
||||
{
|
||||
/* /VRES is asserted to bypass TMSS licensing screen when first read access to cartridge ROM header is detected */
|
||||
if ((address == 0x100) && (m68k.memory_map[0].base = cart.base))
|
||||
{
|
||||
/* asserting /VRES from cartridge should only reset 68k CPU (TODO: confirm this on real hardware) */
|
||||
m68k_pulse_reset();
|
||||
}
|
||||
|
||||
/* default ROM area read handler */
|
||||
return *(uint16 *)(m68k.memory_map[0].base + (address & 0xfffe));
|
||||
}
|
||||
|
||||
/* Game no Kanzume Otokuyou ROM Mapper */
|
||||
static void mapper_seganet_w(uint32 address, uint32 data)
|
||||
{
|
||||
@ -1959,23 +1998,24 @@ static uint32 mapper_128k_radica_r(uint32 address)
|
||||
|
||||
|
||||
/*
|
||||
Custom logic (ST 16S25HB1 PAL) used in Micro Machines US cartridge (SR16V1.1 board)
|
||||
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
/VRES is asserted after write access to 0xA14101 (TMSS bank-shift register)
|
||||
with D0=1 (cartridge ROM access enabled instead of TMSS Boot ROM) being detected
|
||||
Custom logic (ST 16S25HB1 PAL) used in Micro Machines USA cartridge (SR16V1.1 board)
|
||||
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
/VRES is asserted to bypass TMSS security checks when write access to 0xA141xx
|
||||
with D0=1 is detected (access to cartridge ROM enabled instead of TMSS Boot ROM)
|
||||
*/
|
||||
static void mapper_sr16v1_w(uint32 address, uint32 data)
|
||||
{
|
||||
/* 0xA10000-0xA1FFFF address range is mapped to I/O and Control registers */
|
||||
/* default I/O and Control registers write handler */
|
||||
ctrl_io_write_byte(address, data);
|
||||
|
||||
/* cartridge uses /LWR, /AS and VA1-VA18 (only VA8-VA17 required to decode access to TMSS bank-shift register) */
|
||||
/* cartridge uses /LWR, /AS and VA1-VA18 (only VA8-VA17 are required to decode access to TMSS bankswitch register) */
|
||||
if ((address & 0xff01) == 0x4101)
|
||||
{
|
||||
/* cartridge ROM is enabled when D0=1 */
|
||||
/* check if cartridge ROM is enabled (D0=1) */
|
||||
if (data & 0x01)
|
||||
{
|
||||
gen_reset(0);
|
||||
/* asserting /VRES from cartridge should only reset 68k CPU (TODO: confirm this on real hardware) */
|
||||
m68k_pulse_reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
* Genesis Plus
|
||||
* Mega Drive cartridge hardware support
|
||||
*
|
||||
* Copyright (C) 2007-2021 Eke-Eke (Genesis Plus GX)
|
||||
* Copyright (C) 2007-2023 Eke-Eke (Genesis Plus GX)
|
||||
*
|
||||
* Most cartridge protections were initially documented by Haze
|
||||
* (http://haze.mameworld.info/)
|
||||
|
@ -2,7 +2,10 @@
|
||||
* Genesis Plus
|
||||
* SG-1000, Master System & Game Gear cartridge hardware support
|
||||
*
|
||||
* Copyright (C) 2007-2022 Eke-Eke (Genesis Plus GX)
|
||||
* Copyright (C) 2007-2023 Eke-Eke (Genesis Plus GX)
|
||||
*
|
||||
* Credits to Ben Sittler and Omar Cornut at smspower.org for Korean mappers
|
||||
* reverse-engineering and description
|
||||
*
|
||||
* Redistribution and use of this code or any derivative works are permitted
|
||||
* provided that the following conditions are met:
|
||||
@ -52,10 +55,14 @@
|
||||
#define MAPPER_MULTI_16K (0x14)
|
||||
#define MAPPER_KOREA (0x15)
|
||||
#define MAPPER_KOREA_16K (0x16)
|
||||
#define MAPPER_MULTI_2x16K_V1 (0x17)
|
||||
#define MAPPER_MULTI_2x16K_V2 (0x18)
|
||||
#define MAPPER_MULTI_32K_16K (0x19)
|
||||
#define MAPPER_KOREA_8K (0x20)
|
||||
#define MAPPER_MSX (0x21)
|
||||
#define MAPPER_MSX_NEMESIS (0x22)
|
||||
#define MAPPER_MULTI_4X8K (0x23)
|
||||
#define MAPPER_MULTI_8K (0x23)
|
||||
#define MAPPER_MULTI_4x8K (0x24)
|
||||
#define MAPPER_MULTI_32K (0x40)
|
||||
|
||||
typedef struct
|
||||
@ -94,10 +101,10 @@ static const rominfo_t game_list[] =
|
||||
/* games using "Korean" mappers */
|
||||
{0x445525E2, 0, 0, 0, MAPPER_MSX, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Penguin Adventure (KR) */
|
||||
{0x83F0EEDE, 0, 0, 0, MAPPER_MSX, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Street Master (KR) */
|
||||
{0xA05258F5, 0, 0, 0, MAPPER_MSX, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Won-Si-In (KR) */
|
||||
{0xA05258F5, 0, 0, 0, MAPPER_MSX, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Wonsiin (KR) */
|
||||
{0x06965ED9, 0, 0, 0, MAPPER_MSX, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* F-1 Spirit - The way to Formula-1 (KR) */
|
||||
{0x77EFE84A, 0, 0, 0, MAPPER_MSX, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Cyborg Z (KR) */
|
||||
{0xF89AF3CC, 0, 0, 0, MAPPER_MSX, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Knightmare II - The Maze of Galious (KR) */
|
||||
{0xF89AF3CC, 0, 0, 0, MAPPER_MSX, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Knightmare II: The Maze of Galious (KR) */
|
||||
{0x9195C34C, 0, 0, 0, MAPPER_MSX, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Super Boy 3 (KR) */
|
||||
{0xE316C06D, 0, 0, 0, MAPPER_MSX_NEMESIS, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Nemesis (KR) */
|
||||
{0x0A77FA5E, 0, 0, 0, MAPPER_MSX, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Nemesis 2 (KR) */
|
||||
@ -120,8 +127,22 @@ static const rominfo_t game_list[] =
|
||||
{0xFBA94148, 0, 0, 0, MAPPER_MULTI_32K, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Hi-Com 8-in-1 The Best Game Collection (Vol. 1) (KR) */
|
||||
{0x8333C86E, 0, 0, 0, MAPPER_MULTI_32K, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Hi-Com 8-in-1 The Best Game Collection (Vol. 2) (KR) */
|
||||
{0x00E9809F, 0, 0, 0, MAPPER_MULTI_32K, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Hi-Com 8-in-1 The Best Game Collection (Vol. 3) (KR) */
|
||||
{0xBA5EC0E3, 0, 0, 0, MAPPER_MULTI_4X8K, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* 128 Hap (KR) */
|
||||
{0x380D7400, 0, 0, 0, MAPPER_MULTI_4X8K, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Game Mo-eumjip 188 Hap (KR) */
|
||||
{0xBA5EC0E3, 0, 0, 0, MAPPER_MULTI_4x8K, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* 128 Hap (KR) */
|
||||
{0x380D7400, 0, 0, 0, MAPPER_MULTI_4x8K, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Game Mo-eumjip 188 Hap [v0] (KR) */
|
||||
{0xC76601E0, 0, 0, 0, MAPPER_MULTI_4x8K, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Game Mo-eumjip 188 Hap [v1] (KR) */
|
||||
{0x38B3A72F, 0, 0, 0, MAPPER_MULTI_8K, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Game Chongjiphap 200 (KR).sms */
|
||||
{0xD3056492, 0, 0, 0, MAPPER_MULTI_8K, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Super Game 270 Hap ~ Jaemissneun-270 (KR) */
|
||||
{0xAB07ECD4, 0, 0, 0, MAPPER_MULTI_8K, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Super Game World 260 Hap (KR) */
|
||||
{0x0CDE0938, 0, 0, 0, MAPPER_MULTI_8K, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Super Game World 30 Hap (KR) */
|
||||
{0xC29BB8CD, 0, 0, 0, MAPPER_MULTI_8K, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Super Game World 75 Hap (KR) */
|
||||
{0x660BF6EC, 0, 0, 0, MAPPER_MULTI_8K, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Super Multi Game - Super 75 in 1 (KR) */
|
||||
{0xEB7790DE, 0, 0, 0, MAPPER_MULTI_8K, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Super Multi Game - Super 125 in 1 (KR) */
|
||||
{0xE6AD4D4B, 0, 0, 0, MAPPER_MULTI_8K, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Super World 30 Hap (KR) */
|
||||
{0xEDB13847, 0, 0, 0, MAPPER_MULTI_2x16K_V1, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Super Game 45 (KR) */
|
||||
{0xA841C0B7, 0, 0, 0, MAPPER_MULTI_2x16K_V2, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Super Game 52 Hap (KR) */
|
||||
{0x4E202AA2, 0, 0, 0, MAPPER_MULTI_2x16K_V2, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Super Game 180 (KR) */
|
||||
{0xBA5D2776, 0, 0, 0, MAPPER_MULTI_2x16K_V2, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Super Game 200 (KR) */
|
||||
{0xC0AC6956, 0, 0, 0, MAPPER_MULTI_32K_16K, SYSTEM_SMS, REGION_JAPAN_NTSC}, /* Pigu-Wang 7 Hap - Jaemiiss-neun Game Mo-eumjip (KR) */
|
||||
|
||||
/* games using Codemaster mapper */
|
||||
{0x29822980, 0, 0, 0, MAPPER_CODIES, SYSTEM_SMS2, REGION_EUROPE}, /* Cosmic Spacehead */
|
||||
@ -145,6 +166,7 @@ static const rominfo_t game_list[] =
|
||||
|
||||
/* games using serial EEPROM */
|
||||
{0x36EBCD6D, 0, 0, 0, MAPPER_93C46, SYSTEM_GG, REGION_USA}, /* Majors Pro Baseball */
|
||||
{0x2DA8E943, 0, 0, 0, MAPPER_93C46, SYSTEM_GG, REGION_USA}, /* Pro Yakyuu GG League */
|
||||
{0x3D8D0DD6, 0, 0, 0, MAPPER_93C46, SYSTEM_GG, REGION_USA}, /* World Series Baseball [v0] */
|
||||
{0xBB38CFD7, 0, 0, 0, MAPPER_93C46, SYSTEM_GG, REGION_USA}, /* World Series Baseball [v1] */
|
||||
{0x578A8A38, 0, 0, 0, MAPPER_93C46, SYSTEM_GG, REGION_USA}, /* World Series Baseball '95 */
|
||||
@ -160,22 +182,24 @@ static const rominfo_t game_list[] =
|
||||
{0x092F29D6, 0, 0, 0, MAPPER_RAM_8K, SYSTEM_SG, REGION_JAPAN_NTSC}, /* The Castle (J) */
|
||||
|
||||
/* games requiring SG-1000 II 8K RAM extension adapter (type A) */
|
||||
{0x16F240D3, 0, 0, 0, MAPPER_RAM_8K_EXT1, SYSTEM_SGII, REGION_JAPAN_NTSC}, /* Adventure Island [DahJee] (TW) */
|
||||
{0xCE5648C3, 0, 0, 0, MAPPER_RAM_8K_EXT1, SYSTEM_SGII, REGION_JAPAN_NTSC}, /* Bomberman Special [DahJee] (TW) */
|
||||
{0x223397A1, 0, 0, 0, MAPPER_RAM_8K_EXT1, SYSTEM_SGII, REGION_JAPAN_NTSC}, /* King's Valley (TW) */
|
||||
{0x281D2888, 0, 0, 0, MAPPER_RAM_8K_EXT1, SYSTEM_SGII, REGION_JAPAN_NTSC}, /* Knightmare (TW) */
|
||||
{0x281D2888, 0, 0, 0, MAPPER_RAM_8K_EXT1, SYSTEM_SGII, REGION_JAPAN_NTSC}, /* Knightmare [Jumbo] (TW) */
|
||||
{0x306D5F78, 0, 0, 0, MAPPER_RAM_8K_EXT1, SYSTEM_SGII, REGION_JAPAN_NTSC}, /* Rally-X [DahJee] (TW) */
|
||||
{0x29E047CC, 0, 0, 0, MAPPER_RAM_8K_EXT1, SYSTEM_SGII, REGION_JAPAN_NTSC}, /* Road Fighter (TW) */
|
||||
{0x5CBD1163, 0, 0, 0, MAPPER_RAM_8K_EXT1, SYSTEM_SGII, REGION_JAPAN_NTSC}, /* Tank Battalion (TW) */
|
||||
{0x40414556, 0, 0, 0, MAPPER_RAM_8K_EXT1, SYSTEM_SGII, REGION_JAPAN_NTSC}, /* The Goonies (TW) */
|
||||
{0x2E7166D5, 0, 0, 0, MAPPER_RAM_8K_EXT1, SYSTEM_SGII, REGION_JAPAN_NTSC}, /* The Legend of Kage (TW) */
|
||||
{0xC550B4F0, 0, 0, 0, MAPPER_RAM_8K_EXT1, SYSTEM_SGII, REGION_JAPAN_NTSC}, /* TwinBee (TW) */
|
||||
{0xFC87463C, 0, 0, 0, MAPPER_RAM_8K_EXT1, SYSTEM_SGII, REGION_JAPAN_NTSC}, /* Yie Ar Kung-Fu II (TW) */
|
||||
{0xDF7CBFA5, 0, 0, 0, MAPPER_RAM_8K_EXT1, SYSTEM_SGII, REGION_JAPAN_NTSC}, /* Pippols (TW) */
|
||||
{0xE0816BB7, 0, 0, 0, MAPPER_RAM_8K_EXT1, SYSTEM_SGII, REGION_JAPAN_NTSC}, /* Star Soldier (TW) */
|
||||
{0xE0816BB7, 0, 0, 0, MAPPER_RAM_8K_EXT1, SYSTEM_SGII, REGION_JAPAN_NTSC}, /* Star Soldier [DahJee] (TW) */
|
||||
|
||||
/* games requiring SG-1000 II 8K RAM extension adapter (type B) */
|
||||
{0x69FC1494, 0, 0, 0, MAPPER_NONE, SYSTEM_SGII_RAM_EXT, REGION_JAPAN_NTSC}, /* Bomberman Special (TW) */
|
||||
{0xFFC4EE3F, 0, 0, 0, MAPPER_NONE, SYSTEM_SGII_RAM_EXT, REGION_JAPAN_NTSC}, /* Magical Kid Wiz (TW) */
|
||||
{0x2E366CCF, 0, 0, 0, MAPPER_NONE, SYSTEM_SGII_RAM_EXT, REGION_JAPAN_NTSC}, /* The Castle (TW) */
|
||||
{0x2E366CCF, 0, 0, 0, MAPPER_NONE, SYSTEM_SGII_RAM_EXT, REGION_JAPAN_NTSC}, /* The Castle [MSX] (TW) */
|
||||
{0xAAAC12CF, 0, 0, 0, MAPPER_NONE, SYSTEM_SGII_RAM_EXT, REGION_JAPAN_NTSC}, /* Rally-X (TW) */
|
||||
{0xD2EDD329, 0, 0, 0, MAPPER_NONE, SYSTEM_SGII_RAM_EXT, REGION_JAPAN_NTSC}, /* Road Fighter (TW) */
|
||||
|
||||
@ -427,7 +451,11 @@ static void write_mapper_korea_8k(unsigned int address, unsigned char data);
|
||||
static void write_mapper_korea_16k(unsigned int address, unsigned char data);
|
||||
static void write_mapper_msx(unsigned int address, unsigned char data);
|
||||
static void write_mapper_multi_16k(unsigned int address, unsigned char data);
|
||||
static void write_mapper_multi_2x16k_v1(unsigned int address, unsigned char data);
|
||||
static void write_mapper_multi_2x16k_v2(unsigned int address, unsigned char data);
|
||||
static void write_mapper_multi_32k_16k(unsigned int address, unsigned char data);
|
||||
static void write_mapper_multi_32k(unsigned int address, unsigned char data);
|
||||
static void write_mapper_multi_8k(unsigned int address, unsigned char data);
|
||||
static void write_mapper_multi_4x8k(unsigned int address, unsigned char data);
|
||||
static void write_mapper_93c46(unsigned int address, unsigned char data);
|
||||
static void write_mapper_terebi(unsigned int address, unsigned char data);
|
||||
@ -616,7 +644,8 @@ void sms_cart_reset(void)
|
||||
case MAPPER_KOREA_8K:
|
||||
case MAPPER_MSX:
|
||||
case MAPPER_MSX_NEMESIS:
|
||||
case MAPPER_MULTI_4X8K:
|
||||
case MAPPER_MULTI_4x8K:
|
||||
case MAPPER_MULTI_8K:
|
||||
cart_rom.fcr[0] = 0;
|
||||
cart_rom.fcr[1] = 0;
|
||||
cart_rom.fcr[2] = 0;
|
||||
@ -737,6 +766,24 @@ void sms_cart_switch(uint8 mode)
|
||||
}
|
||||
}
|
||||
|
||||
int sms_cart_ram_size(void)
|
||||
{
|
||||
if ((cart_rom.mapper == MAPPER_RAM_8K) || (cart_rom.mapper == MAPPER_RAM_8K_EXT1))
|
||||
{
|
||||
/* 8KB on-board RAM */
|
||||
return 0x2000;
|
||||
}
|
||||
|
||||
if (cart_rom.mapper == MAPPER_RAM_2K)
|
||||
{
|
||||
/* 2KB on-board RAM */
|
||||
return 0x800;
|
||||
}
|
||||
|
||||
/* no on-board RAM by default */
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sms_cart_region_detect(void)
|
||||
{
|
||||
int i = sizeof(game_list) / sizeof(rominfo_t) - 1;
|
||||
@ -837,7 +884,8 @@ int sms_cart_context_load(uint8 *state)
|
||||
case MAPPER_KOREA_8K:
|
||||
case MAPPER_MSX:
|
||||
case MAPPER_MSX_NEMESIS:
|
||||
case MAPPER_MULTI_4X8K:
|
||||
case MAPPER_MULTI_4x8K:
|
||||
case MAPPER_MULTI_8K:
|
||||
cart_rom.fcr[0] = 0;
|
||||
cart_rom.fcr[1] = 0;
|
||||
cart_rom.fcr[2] = 0;
|
||||
@ -1011,7 +1059,7 @@ static void mapper_reset(void)
|
||||
/* "Nemesis" mapper specific */
|
||||
if (slot.mapper == MAPPER_MSX_NEMESIS)
|
||||
{
|
||||
/* first 8KB page is mapped to last 8KB ROM bank */
|
||||
/* first 8KB page ($0000-$1FFF) is mapped to last 8KB ROM bank */
|
||||
for (i = 0x00; i < 0x08; i++)
|
||||
{
|
||||
z80_readmap[i] = &slot.rom[(0x0f << 13) | ((i & 0x07) << 10)];
|
||||
@ -1026,12 +1074,26 @@ static void mapper_reset(void)
|
||||
else
|
||||
{
|
||||
/* 16KB pages */
|
||||
if ((slot.mapper == MAPPER_MULTI_2x16K_V1) || (slot.mapper == MAPPER_MULTI_2x16K_V2))
|
||||
{
|
||||
mapper_16k_w(1,slot.fcr[1]);
|
||||
mapper_16k_w(2,slot.fcr[2]);
|
||||
}
|
||||
else if (slot.mapper == MAPPER_MULTI_32K_16K)
|
||||
{
|
||||
mapper_16k_w(1,slot.fcr[1]);
|
||||
mapper_16k_w(2,slot.fcr[2]);
|
||||
mapper_16k_w(3,slot.fcr[3]);
|
||||
}
|
||||
else
|
||||
{
|
||||
mapper_16k_w(0,slot.fcr[0]);
|
||||
mapper_16k_w(1,slot.fcr[1]);
|
||||
mapper_16k_w(2,slot.fcr[2]);
|
||||
mapper_16k_w(3,slot.fcr[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* reset Z80 memory handlers */
|
||||
switch (slot.mapper)
|
||||
@ -1073,12 +1135,32 @@ static void mapper_reset(void)
|
||||
z80_writemem = write_mapper_multi_16k;
|
||||
break;
|
||||
|
||||
case MAPPER_MULTI_2x16K_V1:
|
||||
z80_readmem = read_mapper_default;
|
||||
z80_writemem = write_mapper_multi_2x16k_v1;
|
||||
break;
|
||||
|
||||
case MAPPER_MULTI_2x16K_V2:
|
||||
z80_readmem = read_mapper_default;
|
||||
z80_writemem = write_mapper_multi_2x16k_v2;
|
||||
break;
|
||||
|
||||
case MAPPER_MULTI_32K_16K:
|
||||
z80_readmem = read_mapper_default;
|
||||
z80_writemem = write_mapper_multi_32k_16k;
|
||||
break;
|
||||
|
||||
case MAPPER_MULTI_32K:
|
||||
z80_readmem = read_mapper_default;
|
||||
z80_writemem = write_mapper_multi_32k;
|
||||
break;
|
||||
|
||||
case MAPPER_MULTI_4X8K:
|
||||
case MAPPER_MULTI_8K:
|
||||
z80_readmem = read_mapper_default;
|
||||
z80_writemem = write_mapper_multi_8k;
|
||||
break;
|
||||
|
||||
case MAPPER_MULTI_4x8K:
|
||||
z80_readmem = read_mapper_default;
|
||||
z80_writemem = write_mapper_multi_4x8k;
|
||||
break;
|
||||
@ -1128,6 +1210,17 @@ static void mapper_8k_w(int offset, unsigned char data)
|
||||
{
|
||||
z80_readmap[i] = &page[(i & 0x07) << 10];
|
||||
}
|
||||
|
||||
/* Multi Korean mapper specific */
|
||||
if (slot.mapper == MAPPER_MULTI_8K)
|
||||
{
|
||||
/* $2000-$3FFF is mirror of $A000-$BFFF */
|
||||
for (i = 0x08; i < 0x10; i++)
|
||||
{
|
||||
z80_readmap[i] = z80_readmap[0x20 + i];
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1225,7 +1318,7 @@ static void mapper_16k_w(int offset, unsigned char data)
|
||||
case 1: /* cartridge ROM bank (16KB) at $0000-$3FFF */
|
||||
{
|
||||
/* first 1KB is not fixed (CODEMASTER or MULTI mappers only) */
|
||||
if ((slot.mapper == MAPPER_CODIES) || (slot.mapper == MAPPER_MULTI_16K))
|
||||
if (slot.mapper >= MAPPER_CODIES)
|
||||
{
|
||||
z80_readmap[0] = &slot.rom[(page << 14)];
|
||||
}
|
||||
@ -1268,13 +1361,66 @@ static void mapper_16k_w(int offset, unsigned char data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Multi Korean mappers specific */
|
||||
else if (slot.mapper == MAPPER_MULTI_2x16K_V1)
|
||||
{
|
||||
if (slot.fcr[0] != 0x01)
|
||||
{
|
||||
/* $8000-$BFFF is not mapped to cartridge ROM (unused area) */
|
||||
for (i = 0x20; i < 0x30; i++)
|
||||
{
|
||||
z80_readmap[i] = cart.rom + 0x510400;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* $8000-$9FFF is mirror of $6000-$7FFF */
|
||||
for (i = 0x20; i < 0x28; i++)
|
||||
{
|
||||
z80_readmap[i] = z80_readmap[i - 0x08];
|
||||
}
|
||||
|
||||
/* $A000-$BFFF is mirror of $4000-$5FFF */
|
||||
for (i = 0x28; i < 0x30; i++)
|
||||
{
|
||||
z80_readmap[i] = z80_readmap[i - 0x18];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (slot.mapper == MAPPER_MULTI_2x16K_V2)
|
||||
{
|
||||
if (slot.fcr[0] != 0x03)
|
||||
{
|
||||
/* $8000-$BFFF is not mapped to cartridge ROM (unused area) */
|
||||
for (i = 0x20; i < 0x30; i++)
|
||||
{
|
||||
z80_readmap[i] = cart.rom + 0x510400;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* $8000-$9FFF is mirror of $6000-$7FFF */
|
||||
for (i = 0x20; i < 0x28; i++)
|
||||
{
|
||||
z80_readmap[i] = z80_readmap[i - 0x08];
|
||||
}
|
||||
|
||||
/* $A000-$BFFF is mirror of $4000-$5FFF */
|
||||
for (i = 0x28; i < 0x30; i++)
|
||||
{
|
||||
z80_readmap[i] = z80_readmap[i - 0x18];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 3: /* cartridge ROM bank (16KB) at $8000-$BFFF */
|
||||
{
|
||||
/* check that external RAM (16KB) is not mapped at $8000-$BFFF (SEGA mapper only) */
|
||||
if ((slot.fcr[0] & 0x08)) break;
|
||||
if ((slot.fcr[0] & 0x08) && (slot.mapper != MAPPER_MULTI_32K_16K)) break;
|
||||
|
||||
/* first 8KB */
|
||||
for (i = 0x20; i < 0x28; i++)
|
||||
@ -1389,6 +1535,94 @@ static void write_mapper_multi_16k(unsigned int address, unsigned char data)
|
||||
z80_writemap[address >> 10][address & 0x03FF] = data;
|
||||
}
|
||||
|
||||
static void write_mapper_multi_2x16k_v1(unsigned int address, unsigned char data)
|
||||
{
|
||||
if (address == 0xFFFE)
|
||||
{
|
||||
/* save mapper configuration to unused register */
|
||||
slot.fcr[0] = (data >> 5) & 0x03;
|
||||
|
||||
if (slot.fcr[0] & 0x02)
|
||||
{
|
||||
data &= 0x1e;
|
||||
mapper_16k_w(1,data);
|
||||
mapper_16k_w(2,data+1);
|
||||
}
|
||||
else
|
||||
{
|
||||
data &= 0x1f;
|
||||
mapper_16k_w(1,0x00);
|
||||
mapper_16k_w(2,data);
|
||||
}
|
||||
}
|
||||
|
||||
z80_writemap[address >> 10][address & 0x03FF] = data;
|
||||
}
|
||||
|
||||
static void write_mapper_multi_2x16k_v2(unsigned int address, unsigned char data)
|
||||
{
|
||||
if (address == 0xBFFC)
|
||||
{
|
||||
/* save mapper configuration to unused register */
|
||||
slot.fcr[0] = (data >> 6) & 0x03;
|
||||
|
||||
switch (slot.fcr[0])
|
||||
{
|
||||
case 0x00:
|
||||
{
|
||||
data &= 0x3e;
|
||||
mapper_16k_w(1,data);
|
||||
mapper_16k_w(2,data+1);
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x01:
|
||||
{
|
||||
data &= 0x3f;
|
||||
mapper_16k_w(1,data);
|
||||
mapper_16k_w(2,data);
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
data &= 0x3f;
|
||||
mapper_16k_w(1,0x20);
|
||||
mapper_16k_w(2,data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
z80_writemap[address >> 10][address & 0x03FF] = data;
|
||||
}
|
||||
|
||||
static void write_mapper_multi_32k_16k(unsigned int address, unsigned char data)
|
||||
{
|
||||
z80_writemap[address >> 10][address & 0x03FF] = data;
|
||||
|
||||
address &= 0xBFEF;
|
||||
|
||||
if (address == 0xBFE5)
|
||||
{
|
||||
/* save 16K bank index to unused register */
|
||||
slot.fcr[0] = (data & 0x3f) << 1;
|
||||
mapper_16k_w(1,slot.fcr[0]);
|
||||
mapper_16k_w(2,slot.fcr[0]+1);
|
||||
mapper_16k_w(3,slot.fcr[0]+1);
|
||||
}
|
||||
else if (address == 0xBFEE)
|
||||
{
|
||||
data &= 0x1f;
|
||||
mapper_16k_w(2,slot.fcr[0]+data);
|
||||
}
|
||||
else if (address == 0xBFEF)
|
||||
{
|
||||
data &= 0x1f;
|
||||
mapper_16k_w(3,slot.fcr[0]+data);
|
||||
}
|
||||
}
|
||||
|
||||
static void write_mapper_multi_32k(unsigned int address, unsigned char data)
|
||||
{
|
||||
if (address == 0xFFFF)
|
||||
@ -1399,6 +1633,35 @@ static void write_mapper_multi_32k(unsigned int address, unsigned char data)
|
||||
z80_writemap[address >> 10][address & 0x03FF] = data;
|
||||
}
|
||||
|
||||
static void write_mapper_multi_8k(unsigned int address, unsigned char data)
|
||||
{
|
||||
if (address == 0x0000)
|
||||
{
|
||||
mapper_8k_w(0,data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (address == 0x0100)
|
||||
{
|
||||
mapper_8k_w(2,data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (address == 0x0200)
|
||||
{
|
||||
mapper_8k_w(1,data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (address == 0x0300)
|
||||
{
|
||||
mapper_8k_w(3,data);
|
||||
return;
|
||||
}
|
||||
|
||||
z80_writemap[address >> 10][address & 0x03FF] = data;
|
||||
}
|
||||
|
||||
static void write_mapper_multi_4x8k(unsigned int address, unsigned char data)
|
||||
{
|
||||
if (address == 0x2000)
|
||||
|
@ -2,7 +2,7 @@
|
||||
* Genesis Plus
|
||||
* SG-1000, Master System & Game Gear cartridge hardware support
|
||||
*
|
||||
* Copyright (C) 2007-2022 Eke-Eke (Genesis Plus GX)
|
||||
* Copyright (C) 2007-2023 Eke-Eke (Genesis Plus GX)
|
||||
*
|
||||
* Redistribution and use of this code or any derivative works are permitted
|
||||
* provided that the following conditions are met:
|
||||
@ -47,6 +47,7 @@
|
||||
extern void sms_cart_init(void);
|
||||
extern void sms_cart_reset(void);
|
||||
extern void sms_cart_switch(uint8 mode);
|
||||
extern int sms_cart_ram_size(void);
|
||||
extern int sms_cart_region_detect(void);
|
||||
extern int sms_cart_context_save(uint8 *state);
|
||||
extern int sms_cart_context_load(uint8 *state);
|
||||
|
@ -189,7 +189,7 @@ void cd_cart_init(void)
|
||||
else
|
||||
{
|
||||
/* enable 512K backup RAM cartridge when booting from CD (Mode 2) */
|
||||
scd.cartridge.id = 6;
|
||||
scd.cartridge.id = cart_size;
|
||||
}
|
||||
|
||||
/* RAM cartridge enabled ? */
|
||||
|
@ -2,7 +2,7 @@
|
||||
* Genesis Plus
|
||||
* CD drive processor & CD-DA fader
|
||||
*
|
||||
* Copyright (C) 2012-2022 Eke-Eke (Genesis Plus GX)
|
||||
* Copyright (C) 2012-2023 Eke-Eke (Genesis Plus GX)
|
||||
*
|
||||
* Redistribution and use of this code or any derivative works are permitted
|
||||
* provided that the following conditions are met:
|
||||
@ -1948,6 +1948,10 @@ void cdd_process(void)
|
||||
/* update reported drive status */
|
||||
scd.regs[0x38>>1].byte.h = cdd.status;
|
||||
|
||||
/* do not update RS1-RS8 if disc is stopped */
|
||||
if ((cdd.status == CD_STOP) || (cdd.status > CD_PAUSE))
|
||||
break;
|
||||
|
||||
/* check if RS1 indicated invalid track infos (during seeking) */
|
||||
if (scd.regs[0x38>>1].byte.l == 0x0f)
|
||||
{
|
||||
@ -1997,11 +2001,14 @@ void cdd_process(void)
|
||||
scd.regs[0x36>>1].byte.h = 0x01;
|
||||
|
||||
/* RS1-RS8 ignored, expects 0x0 (CD_STOP) in RS0 once */
|
||||
scd.regs[0x38>>1].w = CD_STOP << 8;
|
||||
scd.regs[0x38>>1].w = (CD_STOP << 8) | 0x0f;
|
||||
scd.regs[0x3a>>1].w = 0x0000;
|
||||
scd.regs[0x3c>>1].w = 0x0000;
|
||||
scd.regs[0x3e>>1].w = 0x0000;
|
||||
scd.regs[0x40>>1].w = ~CD_STOP & 0x0f;
|
||||
scd.regs[0x40>>1].w = ~(CD_STOP + 0x0f) & 0x0f;
|
||||
|
||||
/* reset drive position */
|
||||
cdd.index = cdd.lba = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2114,8 +2121,8 @@ void cdd_process(void)
|
||||
/* Fixes a few games hanging because they expect data to be read with some delay */
|
||||
/* Wolf Team games (Annet Futatabi, Aisle Lord, Cobra Command, Earnest Evans, Road Avenger & Time Gal) need at least 11 interrupts delay */
|
||||
/* Space Adventure Cobra (2nd morgue scene) needs at least 13 interrupts delay (incl. seek time, so 11 is OK) */
|
||||
/* By default, at least one interrupt latency is required by current emulation model (BIOS hangs otherwise) */
|
||||
cdd.latency = 1 + 10*config.cd_latency;
|
||||
/* By default, at least two interrupts latency is required by current emulation model (BIOS hangs otherwise) */
|
||||
cdd.latency = 2 + 9*config.cd_latency;
|
||||
}
|
||||
|
||||
/* CD drive seek time */
|
||||
@ -2172,7 +2179,7 @@ void cdd_process(void)
|
||||
scd.regs[0x3a>>1].w = 0x0000;
|
||||
scd.regs[0x3c>>1].w = 0x0000;
|
||||
scd.regs[0x3e>>1].w = 0x0000;
|
||||
scd.regs[0x40>>1].w = ~(CD_SEEK + 0xf) & 0x0f;
|
||||
scd.regs[0x40>>1].w = ~(CD_SEEK + 0x0f) & 0x0f;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2238,7 +2245,7 @@ void cdd_process(void)
|
||||
scd.regs[0x3a>>1].w = 0x0000;
|
||||
scd.regs[0x3c>>1].w = 0x0000;
|
||||
scd.regs[0x3e>>1].w = 0x0000;
|
||||
scd.regs[0x40>>1].w = ~(CD_SEEK + 0xf) & 0x0f;
|
||||
scd.regs[0x40>>1].w = ~(CD_SEEK + 0x0f) & 0x0f;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2303,11 +2310,14 @@ void cdd_process(void)
|
||||
cdd.status = cdd.loaded ? CD_TOC : NO_DISC;
|
||||
|
||||
/* RS1-RS8 ignored, expects CD_STOP in RS0 once */
|
||||
scd.regs[0x38>>1].w = CD_STOP << 8;
|
||||
scd.regs[0x38>>1].w = (CD_STOP << 8) | 0x0f;
|
||||
scd.regs[0x3a>>1].w = 0x0000;
|
||||
scd.regs[0x3c>>1].w = 0x0000;
|
||||
scd.regs[0x3e>>1].w = 0x0000;
|
||||
scd.regs[0x40>>1].w = ~CD_STOP & 0x0f;
|
||||
scd.regs[0x40>>1].w = ~(CD_STOP + 0x0f) & 0x0f;
|
||||
|
||||
/* reset drive position */
|
||||
cdd.index = cdd.lba = 0;
|
||||
|
||||
#ifdef CD_TRAY_CALLBACK
|
||||
CD_TRAY_CALLBACK
|
||||
@ -2322,11 +2332,14 @@ void cdd_process(void)
|
||||
|
||||
/* update status (RS1-RS8 ignored) */
|
||||
cdd.status = CD_OPEN;
|
||||
scd.regs[0x38>>1].w = CD_OPEN << 8;
|
||||
scd.regs[0x38>>1].w = (CD_OPEN << 8) | 0x0f;
|
||||
scd.regs[0x3a>>1].w = 0x0000;
|
||||
scd.regs[0x3c>>1].w = 0x0000;
|
||||
scd.regs[0x3e>>1].w = 0x0000;
|
||||
scd.regs[0x40>>1].w = ~CD_OPEN & 0x0f;
|
||||
scd.regs[0x40>>1].w = ~(CD_OPEN + 0x0f) & 0x0f;
|
||||
|
||||
/* reset drive position */
|
||||
cdd.index = cdd.lba = 0;
|
||||
|
||||
#ifdef CD_TRAY_CALLBACK
|
||||
CD_TRAY_CALLBACK
|
||||
|
@ -2,7 +2,7 @@
|
||||
* Genesis Plus
|
||||
* CD drive processor & CD-DA fader
|
||||
*
|
||||
* Copyright (C) 2012-2022 Eke-Eke (Genesis Plus GX)
|
||||
* Copyright (C) 2012-2023 Eke-Eke (Genesis Plus GX)
|
||||
*
|
||||
* Redistribution and use of this code or any derivative works are permitted
|
||||
* provided that the following conditions are met:
|
||||
|
@ -65,6 +65,7 @@ extern uint8 zram[0x2000];
|
||||
extern uint32 zbank;
|
||||
extern uint8 zstate;
|
||||
extern uint8 pico_current;
|
||||
extern uint8_t cart_size;
|
||||
|
||||
/* Function prototypes */
|
||||
extern void gen_init(void);
|
||||
|
@ -2,7 +2,7 @@
|
||||
* Genesis Plus
|
||||
* XE-1AP analog controller support
|
||||
*
|
||||
* Copyright (C) 2011-2022 Eke-Eke (Genesis Plus GX)
|
||||
* Copyright (C) 2011-2023 Eke-Eke (Genesis Plus GX)
|
||||
*
|
||||
* Redistribution and use of this code or any derivative works are permitted
|
||||
* provided that the following conditions are met:
|
||||
@ -96,12 +96,12 @@ INLINE unsigned char xe_1ap_read(int index)
|
||||
case 9: /* CH3 low (Throttle vertical or horizontal direction) */
|
||||
data = input.analog[port+1][0] & 0x0F;
|
||||
break;
|
||||
case 10: /* N/A */
|
||||
data = 0x0F;
|
||||
break;
|
||||
case 11: /* A B A' B' buttons status (active low) */
|
||||
data = (~input.pad[port] >> 6) & 0x0F;
|
||||
break;
|
||||
default: /* N/A */
|
||||
data = 0x0F;
|
||||
break;
|
||||
}
|
||||
|
||||
/* TL indicates current data cycle (0=1st cycle, 1=2nd cycle, etc) */
|
||||
|
@ -711,6 +711,7 @@ void vdp_dma_update(unsigned int cycles)
|
||||
/* Check if DMA is finished */
|
||||
if (!dma_length)
|
||||
{
|
||||
/* DMA source address registers are incremented during DMA (even DMA Fill) */
|
||||
if (config.vdp_fix_dma_boundary_bug) {
|
||||
/*
|
||||
* NOTICE: VDP has a hardware bug where DMA transfer source address is not incremented properly,
|
||||
|
@ -1042,7 +1042,6 @@ void color_update_m4(int index, unsigned int data)
|
||||
|
||||
case SYSTEM_SG:
|
||||
case SYSTEM_SGII:
|
||||
case SYSTEM_SGII_RAM_EXT:
|
||||
{
|
||||
/* Fixed TMS99xx palette */
|
||||
if (index & 0x0F)
|
||||
@ -1541,6 +1540,7 @@ void render_bg_m5(int line)
|
||||
pf_row_mask = playfield_row_mask;
|
||||
pf_shift = playfield_shift;
|
||||
|
||||
|
||||
/* Window & Plane A */
|
||||
a = (reg[18] & 0x1F) << 3;
|
||||
w = (reg[18] >> 7) & 1;
|
||||
@ -1907,7 +1907,7 @@ void render_bg_m5_vs(int line)
|
||||
/* Enhanced function that allows each cell to be vscrolled individually, instead of being limited to 2-cell */
|
||||
void render_bg_m5_vs_enhanced(int line)
|
||||
{
|
||||
int column, v_offset;
|
||||
int column;
|
||||
int start_real, end_real;
|
||||
uint32 atex, atbuf, *src, *dst;
|
||||
uint32 v_line, next_v_line, *nt;
|
||||
@ -1915,6 +1915,10 @@ void render_bg_m5_vs_enhanced(int line)
|
||||
uint32 *vs;
|
||||
int a, w, start = 0, end;
|
||||
uint32 shift, index;
|
||||
|
||||
/* Vertical scroll offset */
|
||||
int v_offset = 0;
|
||||
|
||||
/* Common data */
|
||||
uint32 xscroll = *(uint32 *)&vram[hscb + ((line & hscroll_mask) << 2)];
|
||||
if (
|
||||
@ -3009,11 +3013,14 @@ void render_bg_m5_vs(int line)
|
||||
|
||||
void render_bg_m5_vs_enhanced(int line)
|
||||
{
|
||||
int column, start, end, v_offset;
|
||||
int column, start, end;
|
||||
uint32 atex, atbuf, *src, *dst;
|
||||
uint32 shift, index, v_line, next_v_line, *nt;
|
||||
uint8 *lb;
|
||||
|
||||
/* Vertical scroll offset */
|
||||
int v_offset = 0;
|
||||
|
||||
/* Scroll Planes common data */
|
||||
uint32 xscroll = *(uint32 *)&vram[hscb + ((line & hscroll_mask) << 2)];
|
||||
if (
|
||||
@ -4730,7 +4737,6 @@ void parse_satb_m5(int line)
|
||||
object_info->xpos = p[link + 3];
|
||||
else
|
||||
object_info->xpos = p[link + 3] & 0x1ff;
|
||||
|
||||
object_info->ypos = ypos;
|
||||
object_info->size = size & 0x0f;
|
||||
|
||||
@ -5013,7 +5019,7 @@ void render_line(int line)
|
||||
/* Left-most column blanking */
|
||||
if (reg[0] & 0x20)
|
||||
{
|
||||
if (system_hw >= SYSTEM_MARKIII)
|
||||
if (system_hw > SYSTEM_SGII)
|
||||
{
|
||||
memset(&linebuf[0][0x20], 0x40, 8);
|
||||
}
|
||||
|
Binary file not shown.
Binary file not shown.
@ -3,7 +3,7 @@
|
||||
*
|
||||
* Genesis Plus GX menu
|
||||
*
|
||||
* Copyright Eke-Eke (2009-2022)
|
||||
* Copyright Eke-Eke (2009-2023)
|
||||
*
|
||||
* Redistribution and use of this code or any derivative works are permitted
|
||||
* provided that the following conditions are met:
|
||||
@ -1662,7 +1662,7 @@ static void systemmenu ()
|
||||
|
||||
case 12: /*** Main 68k Overclock ***/
|
||||
{
|
||||
GUI_OptionBox(m,0,"Main 68k Overclock Ratio",(void *)&config.m68k_overclock,0.1,1.0,3.0,0);
|
||||
GUI_OptionBox(m,0,"Main 68k Overclock Ratio",(void *)&config.m68k_overclock,0.1,1.0,7.0,0);
|
||||
if (config.m68k_overclock > 1.0)
|
||||
sprintf (items[12].text, "Main 68k Overclock: %1.1fx", config.m68k_overclock);
|
||||
else
|
||||
@ -1672,7 +1672,7 @@ static void systemmenu ()
|
||||
|
||||
case 13: /*** Sub 68k Overclock ***/
|
||||
{
|
||||
GUI_OptionBox(m,0,"Sub 68k Overclock Ratio",(void *)&config.s68k_overclock,0.1,1.0,3.0,0);
|
||||
GUI_OptionBox(m,0,"Sub 68k Overclock Ratio",(void *)&config.s68k_overclock,0.1,1.0,4.0,0);
|
||||
if (config.s68k_overclock > 1.0)
|
||||
sprintf (items[13].text, "Sub 68k Overclock: %1.1fx", config.s68k_overclock);
|
||||
else
|
||||
@ -1682,7 +1682,7 @@ static void systemmenu ()
|
||||
|
||||
case 14: /*** Z80 Overclock ***/
|
||||
{
|
||||
GUI_OptionBox(m,0,"Z80 Overclock Ratio",(void *)&config.z80_overclock,0.1,1.0,3.0,0);
|
||||
GUI_OptionBox(m,0,"Z80 Overclock Ratio",(void *)&config.z80_overclock,0.1,1.0,15.0,0);
|
||||
if (config.z80_overclock > 1.0)
|
||||
sprintf (items[14].text, "Z80 Overclock: %1.1fx", config.z80_overclock);
|
||||
else
|
||||
|
@ -3,7 +3,7 @@
|
||||
*
|
||||
* Genesis Plus GX menus
|
||||
*
|
||||
* Copyright Eke-Eke (2009-2022)
|
||||
* Copyright Eke-Eke (2009-2023)
|
||||
*
|
||||
* Redistribution and use of this code or any derivative works are permitted
|
||||
* provided that the following conditions are met:
|
||||
|
@ -3,7 +3,7 @@
|
||||
*
|
||||
* Genesis Plus GX input support
|
||||
*
|
||||
* Copyright Eke-Eke (2007-2022)
|
||||
* Copyright Eke-Eke (2007-2023)
|
||||
*
|
||||
* Redistribution and use of this code or any derivative works are permitted
|
||||
* provided that the following conditions are met:
|
||||
@ -1773,35 +1773,54 @@ void gx_input_UpdateMenu(void)
|
||||
/* Check if inputs update are disabled */
|
||||
if (inputs_disabled) return;
|
||||
|
||||
int i;
|
||||
s8 x, y;
|
||||
u16 pp, hp;
|
||||
#ifdef HW_RVL
|
||||
u32 pw, hw, pwu, hwu;
|
||||
#endif
|
||||
|
||||
/* PAD status update */
|
||||
PAD_ScanPads();
|
||||
|
||||
/* Check all PAD ports */
|
||||
for (i=0; i<4; i++)
|
||||
{
|
||||
/* PAD pressed keys */
|
||||
s16 pp = PAD_ButtonsDown(0);
|
||||
pp = PAD_ButtonsDown(i);
|
||||
|
||||
/* PAD held keys (direction/selection) */
|
||||
s16 hp = PAD_ButtonsHeld(0) & PAD_BUTTONS_HELD;
|
||||
/* PAD held keys (direction/selection only) */
|
||||
hp = PAD_ButtonsHeld(i) & PAD_BUTTONS_HELD;
|
||||
|
||||
/* PAD analog sticks (handled as PAD held direction keys) */
|
||||
s8 x = PAD_StickX(0);
|
||||
s8 y = PAD_StickY(0);
|
||||
/* PAD analog sticks (handled as held direction keys) */
|
||||
x = PAD_StickX(i);
|
||||
y = PAD_StickY(i);
|
||||
if (x > ANALOG_SENSITIVITY) hp |= PAD_BUTTON_RIGHT;
|
||||
else if (x < -ANALOG_SENSITIVITY) hp |= PAD_BUTTON_LEFT;
|
||||
else if (y > ANALOG_SENSITIVITY) hp |= PAD_BUTTON_UP;
|
||||
else if (y < -ANALOG_SENSITIVITY) hp |= PAD_BUTTON_DOWN;
|
||||
|
||||
/* Ignore other ports once first used PAD controlled is found */
|
||||
if (pp || hp) break;
|
||||
}
|
||||
|
||||
#ifdef HW_RVL
|
||||
/* WPAD status update */
|
||||
WPAD_ScanPads();
|
||||
WPADData *data = WPAD_Data(0);
|
||||
|
||||
/* Check all WPAD ports */
|
||||
for (i=0; i<4; i++)
|
||||
{
|
||||
/* WPAD data */
|
||||
WPADData *data = WPAD_Data(i);
|
||||
|
||||
/* WPAD pressed keys */
|
||||
u32 pw = data->btns_d;
|
||||
pw = data->btns_d;
|
||||
|
||||
/* WPAD held keys (direction/selection) */
|
||||
u32 hw = data->btns_h & WPAD_BUTTONS_HELD;
|
||||
/* WPAD held keys (direction/selection only) */
|
||||
hw = data->btns_h & WPAD_BUTTONS_HELD;
|
||||
|
||||
/* WPAD analog sticks (handled as PAD held direction keys) */
|
||||
/* WPAD analog sticks (handled as held direction keys) */
|
||||
x = wpad_StickX(data, 0);
|
||||
y = wpad_StickY(data, 0);
|
||||
if (x > ANALOG_SENSITIVITY) hp |= PAD_BUTTON_RIGHT;
|
||||
@ -1809,9 +1828,14 @@ void gx_input_UpdateMenu(void)
|
||||
else if (y > ANALOG_SENSITIVITY) hp |= PAD_BUTTON_UP;
|
||||
else if (y < -ANALOG_SENSITIVITY) hp |= PAD_BUTTON_DOWN;
|
||||
|
||||
/* WiiU GamePad status */
|
||||
u32 pwu = 0;
|
||||
u32 hwu = 0;
|
||||
/* Wiimote orientation */
|
||||
WPAD_IR(i, &m_input.ir);
|
||||
|
||||
/* Ignore other ports once first used WPAD controller is found */
|
||||
if (m_input.ir.valid || pw || hw) break;
|
||||
}
|
||||
|
||||
/* Check WiiU GamePad status */
|
||||
if (WiiDRC_Inited())
|
||||
{
|
||||
WiiDRC_ScanPads();
|
||||
@ -1830,7 +1854,11 @@ void gx_input_UpdateMenu(void)
|
||||
else if (y > ANALOG_SENSITIVITY) hp |= PAD_BUTTON_UP;
|
||||
else if (y < -ANALOG_SENSITIVITY) hp |= PAD_BUTTON_DOWN;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
pwu = 0;
|
||||
hwu = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* check if any direction/selection key is being held or just being pressed/released */
|
||||
@ -1860,7 +1888,6 @@ void gx_input_UpdateMenu(void)
|
||||
|
||||
#ifdef HW_RVL
|
||||
/* Wiimote & Classic Controller direction keys */
|
||||
WPAD_IR(0, &m_input.ir);
|
||||
if (m_input.ir.valid)
|
||||
{
|
||||
/* Wiimote is handled vertically */
|
||||
|
@ -3,7 +3,7 @@
|
||||
*
|
||||
* Genesis Plus GX input support
|
||||
*
|
||||
* Copyright Eke-Eke (2007-2022)
|
||||
* Copyright Eke-Eke (2007-2023)
|
||||
*
|
||||
* Redistribution and use of this code or any derivative works are permitted
|
||||
* provided that the following conditions are met:
|
||||
|
@ -1,14 +1,14 @@
|
||||
import re
|
||||
|
||||
# 0: full struct; 1: up to & including first []; 2: content between first {}
|
||||
p_struct = re.compile(r'(struct\s*[a-zA-Z0-9_\s]+\[])\s*'
|
||||
r'(?:(?:\/\*(?:.|[\r\n])*?\*\/|\/\/.*[\r\n]+)\s*)*'
|
||||
# 0: full struct; 1: up to & including first []; 2 & 3: comments; 4: content between first {}
|
||||
p_struct = re.compile(r'(\bstruct\b\s*[a-zA-Z0-9_\s]+\[])\s*' # 1st capturing group
|
||||
r'(?:(?=(\/\*(?:.|[\r\n])*?\*\/|\/\/.*[\r\n]+))\2\s*)*' # 2nd capturing group
|
||||
r'=\s*' # =
|
||||
r'(?:(?:\/\*(?:.|[\r\n])*?\*\/|\/\/.*[\r\n]+)\s*)*'
|
||||
r'(?:(?=(\/\*(?:.|[\r\n])*?\*\/|\/\/.*[\r\n]+))\3\s*)*' # 3rd capturing group
|
||||
r'{((?:.|[\r\n])*?)\{\s*NULL,\s*NULL,\s*NULL\s*(?:.|[\r\n])*?},?(?:.|[\r\n])*?};') # captures full struct, it's beginning and it's content
|
||||
# 0: type name[]; 1: type; 2: name
|
||||
p_type_name = re.compile(r'(retro_core_option_[a-zA-Z0-9_]+)\s*'
|
||||
r'(option_cats([a-z_]{0,8})|option_defs([a-z_]{0,8}))\s*\[]')
|
||||
p_type_name = re.compile(r'(\bretro_core_option_[a-zA-Z0-9_]+)\s*'
|
||||
r'(\boption_cats([a-z_]{0,8})|\boption_defs([a-z_]*))\s*\[]')
|
||||
# 0: full option; 1: key; 2: description; 3: additional info; 4: key/value pairs
|
||||
p_option = re.compile(r'{\s*' # opening braces
|
||||
r'(?:(?:\/\*(?:.|[\r\n])*?\*\/|\/\/.*[\r\n]+|#.*[\r\n]+)\s*)*'
|
||||
@ -76,9 +76,9 @@ p_key_value = re.compile(r'{\s*' # opening braces
|
||||
|
||||
p_masked = re.compile(r'([A-Z_][A-Z0-9_]+)\s*(\"(?:"\s*"|\\\s*|.)*\")')
|
||||
|
||||
p_intl = re.compile(r'(struct retro_core_option_definition \*option_defs_intl\[RETRO_LANGUAGE_LAST]) = {'
|
||||
p_intl = re.compile(r'(\bstruct retro_core_option_definition \*option_defs_intl\[RETRO_LANGUAGE_LAST]) = {'
|
||||
r'((?:.|[\r\n])*?)};')
|
||||
p_set = re.compile(r'static INLINE void libretro_set_core_options\(retro_environment_t environ_cb\)'
|
||||
p_set = re.compile(r'\bstatic INLINE void libretro_set_core_options\(retro_environment_t environ_cb\)'
|
||||
r'(?:.|[\r\n])*?};?\s*#ifdef __cplusplus\s*}\s*#endif')
|
||||
|
||||
p_yaml = re.compile(r'"project_id": "[0-9]+".*\s*'
|
||||
|
@ -134,13 +134,12 @@ def is_viable_non_dupe(text: str, comparison) -> bool:
|
||||
|
||||
|
||||
def is_viable_value(text: str) -> bool:
|
||||
"""text must be longer than 2 ('""'), not 'NULL' and text.lower() not in
|
||||
{'"enabled"', '"disabled"', '"true"', '"false"', '"on"', '"off"'}.
|
||||
"""text must be longer than 2 ('""') and not 'NULL'.
|
||||
|
||||
:param text: String to be tested.
|
||||
:return: bool
|
||||
"""
|
||||
return 2 < len(text) and text != 'NULL' and text.lower() not in ON_OFFS
|
||||
return 2 < len(text) and text != 'NULL'
|
||||
|
||||
|
||||
def create_non_dupe(base_name: str, opt_num: int, comparison) -> str:
|
||||
@ -183,17 +182,17 @@ def get_texts(text: str) -> dict:
|
||||
if lang not in just_string:
|
||||
hash_n_string[lang] = {}
|
||||
just_string[lang] = set()
|
||||
|
||||
is_v2 = False
|
||||
is_v2_definition = 'retro_core_option_v2_definition' == struct_type_name[0]
|
||||
pre_name = ''
|
||||
# info texts format
|
||||
p = cor.p_info
|
||||
if 'retro_core_option_v2_definition' == struct_type_name[0]:
|
||||
is_v2 = True
|
||||
elif 'retro_core_option_v2_category' == struct_type_name[0]:
|
||||
if 'retro_core_option_v2_category' == struct_type_name[0]:
|
||||
# prepend category labels, as they can be the same as option labels
|
||||
pre_name = 'CATEGORY_'
|
||||
# categories have different info texts format
|
||||
p = cor.p_info_cat
|
||||
|
||||
struct_content = struct.group(2)
|
||||
struct_content = struct.group(4)
|
||||
# 0: full option; 1: key; 2: description; 3: additional info; 4: key/value pairs
|
||||
struct_options = cor.p_option.finditer(struct_content)
|
||||
for opt, option in enumerate(struct_options):
|
||||
@ -219,7 +218,7 @@ def get_texts(text: str) -> dict:
|
||||
if option.group(3):
|
||||
infos = option.group(3)
|
||||
option_info = p.finditer(infos)
|
||||
if is_v2:
|
||||
if is_v2_definition:
|
||||
desc1 = next(option_info).group(1)
|
||||
if is_viable_non_dupe(desc1, just_string[lang]):
|
||||
just_string[lang].add(desc1)
|
||||
@ -248,16 +247,21 @@ def get_texts(text: str) -> dict:
|
||||
else:
|
||||
raise ValueError(f'Too few arguments in struct {struct_type_name[1]} option {option.group(1)}!')
|
||||
|
||||
# group 4:
|
||||
# group 4: key/value pairs
|
||||
if option.group(4):
|
||||
for j, kv_set in enumerate(cor.p_key_value.finditer(option.group(4))):
|
||||
set_key, set_value = kv_set.group(1, 2)
|
||||
if not is_viable_value(set_value):
|
||||
if not is_viable_value(set_key):
|
||||
continue
|
||||
# use the key if value not available
|
||||
set_value = set_key
|
||||
if not is_viable_value(set_value):
|
||||
continue
|
||||
# re.fullmatch(r'(?:[+-][0-9]+)+', value[1:-1])
|
||||
if set_value not in just_string[lang] and not re.sub(r'[+-]', '', set_value[1:-1]).isdigit():
|
||||
|
||||
# add only if non-dupe, not translated by RetroArch directly & not purely numeric
|
||||
if set_value not in just_string[lang]\
|
||||
and set_value.lower() not in ON_OFFS\
|
||||
and not re.sub(r'[+-]', '', set_value[1:-1]).isdigit():
|
||||
clean_key = set_key[1:-1]
|
||||
clean_key = remove_special_chars(clean_key).upper().replace(' ', '_')
|
||||
m_h = create_non_dupe(re.sub(r'__+', '_', f"OPTION_VAL_{clean_key}"), opt, hash_n_string[lang])
|
||||
@ -298,8 +302,12 @@ def h2json(file_paths: dict) -> dict:
|
||||
for file_lang in file_paths:
|
||||
if not os.path.isfile(file_paths[file_lang]):
|
||||
continue
|
||||
|
||||
jsons[file_lang] = file_paths[file_lang][:-2] + '.json'
|
||||
file_path = file_paths[file_lang]
|
||||
try:
|
||||
jsons[file_lang] = file_path[:file_path.rindex('.')] + '.json'
|
||||
except ValueError:
|
||||
print(f"File {file_path} has incorrect format! File ending missing?")
|
||||
continue
|
||||
|
||||
p = cor.p_masked
|
||||
|
||||
@ -397,11 +405,11 @@ def get_crowdin_client(dir_path: str) -> str:
|
||||
return jar_path
|
||||
|
||||
|
||||
def create_intl_file(localisation_file_path: str, intl_dir_path: str, text: str, file_path: str) -> None:
|
||||
def create_intl_file(intl_file_path: str, localisations_path: str, text: str, file_path: str) -> None:
|
||||
"""Creates 'libretro_core_options_intl.h' from Crowdin translations.
|
||||
|
||||
:param localisation_file_path: Path to 'libretro_core_options_intl.h'
|
||||
:param intl_dir_path: Path to the intl/<core_name> directory.
|
||||
:param intl_file_path: Path to 'libretro_core_options_intl.h'
|
||||
:param localisations_path: Path to the intl/<core_name> directory.
|
||||
:param text: Content of the 'libretro_core_options.h' being translated.
|
||||
:param file_path: Path to the '_us.h' file, containing the original English texts.
|
||||
:return: None
|
||||
@ -497,10 +505,11 @@ def create_intl_file(localisation_file_path: str, intl_dir_path: str, text: str,
|
||||
'extern "C" {\n' \
|
||||
'#endif\n'
|
||||
|
||||
if os.path.isfile(localisation_file_path):
|
||||
if os.path.isfile(intl_file_path):
|
||||
# copy top of the file for re-use
|
||||
with open(localisation_file_path, 'r', encoding='utf-8') as intl: # libretro_core_options_intl.h
|
||||
with open(intl_file_path, 'r', encoding='utf-8') as intl: # libretro_core_options_intl.h
|
||||
in_text = intl.read()
|
||||
# attempt 1: find the distinct comment header
|
||||
intl_start = re.search(re.escape('/*\n'
|
||||
' ********************************\n'
|
||||
' * Core Option Definitions\n'
|
||||
@ -509,19 +518,22 @@ def create_intl_file(localisation_file_path: str, intl_dir_path: str, text: str,
|
||||
if intl_start:
|
||||
out_txt = in_text[:intl_start.end(0)]
|
||||
else:
|
||||
# attempt 2: if no comment header present, find c++ compiler instruction (it is kind of a must)
|
||||
intl_start = re.search(re.escape('#ifdef __cplusplus\n'
|
||||
'extern "C" {\n'
|
||||
'#endif\n'), in_text)
|
||||
if intl_start:
|
||||
out_txt = in_text[:intl_start.end(0)]
|
||||
# if all attempts fail, use default from above
|
||||
|
||||
# only write to file, if there is anything worthwhile to write!
|
||||
overwrite = False
|
||||
|
||||
# iterate through localisation files
|
||||
files = {}
|
||||
for file in os.scandir(intl_dir_path):
|
||||
for file in os.scandir(localisations_path):
|
||||
files[file.name] = {'is_file': file.is_file(), 'path': file.path}
|
||||
|
||||
for file in sorted(files): # intl/<core_name>/_*
|
||||
if files[file]['is_file'] \
|
||||
and file.startswith('_') \
|
||||
@ -532,6 +544,7 @@ def create_intl_file(localisation_file_path: str, intl_dir_path: str, text: str,
|
||||
struct_groups = cor.p_struct.finditer(text)
|
||||
lang_low = os.path.splitext(file)[0].lower()
|
||||
lang_up = lang_low.upper()
|
||||
# mark each language's section with a comment, for readability
|
||||
out_txt = out_txt + f'/* RETRO_LANGUAGE{lang_up} */\n\n' # /* RETRO_LANGUAGE_NM */
|
||||
|
||||
# copy adjusted translations (makros)
|
||||
@ -544,22 +557,22 @@ def create_intl_file(localisation_file_path: str, intl_dir_path: str, text: str,
|
||||
if 3 > len(struct_type_name): # no language specifier
|
||||
new_decl = re.sub(re.escape(struct_type_name[1]), struct_type_name[1] + lang_low, declaration)
|
||||
else:
|
||||
new_decl = re.sub(re.escape(struct_type_name[2]), lang_low, declaration)
|
||||
if '_us' != struct_type_name[2]:
|
||||
# only use _us constructs - other languages present in the source file are not important
|
||||
continue
|
||||
new_decl = re.sub(re.escape(struct_type_name[2]), lang_low, declaration)
|
||||
|
||||
p = cor.p_info
|
||||
if 'retro_core_option_v2_category' == struct_type_name[0]:
|
||||
p = cor.p_info_cat
|
||||
p = (cor.p_info_cat if 'retro_core_option_v2_category' == struct_type_name[0] else cor.p_info)
|
||||
offset_construct = construct.start(0)
|
||||
# append localised construct name and ' = {'
|
||||
start = construct.end(1) - offset_construct
|
||||
end = construct.start(2) - offset_construct
|
||||
end = construct.start(4) - offset_construct
|
||||
out_txt = out_txt + new_decl + construct.group(0)[start:end]
|
||||
|
||||
content = construct.group(2)
|
||||
# insert macros
|
||||
content = construct.group(4)
|
||||
new_content = cor.p_option.sub(replace_option, content)
|
||||
|
||||
start = construct.end(2) - offset_construct
|
||||
start = construct.end(4) - offset_construct
|
||||
# append macro-filled content and close the construct
|
||||
out_txt = out_txt + new_content + construct.group(0)[start:] + '\n'
|
||||
|
||||
# for v2
|
||||
@ -574,7 +587,7 @@ def create_intl_file(localisation_file_path: str, intl_dir_path: str, text: str,
|
||||
|
||||
# only write to file, if there is anything worthwhile to write!
|
||||
if overwrite:
|
||||
with open(localisation_file_path, 'w', encoding='utf-8') as intl:
|
||||
with open(intl_file_path, 'w', encoding='utf-8') as intl:
|
||||
intl.write(out_txt + '\n#ifdef __cplusplus\n'
|
||||
'}\n#endif\n'
|
||||
'\n#endif')
|
||||
@ -585,7 +598,7 @@ def create_intl_file(localisation_file_path: str, intl_dir_path: str, text: str,
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
if os.path.isfile(sys.argv[1]):
|
||||
if os.path.isfile(sys.argv[1]) or sys.argv[1].endswith('.h'):
|
||||
_temp = os.path.dirname(sys.argv[1])
|
||||
else:
|
||||
_temp = sys.argv[1]
|
||||
|
@ -6,8 +6,8 @@
|
||||
"files":
|
||||
[
|
||||
{
|
||||
"source": "/intl/_core_name_/_us.json",
|
||||
"dest": "/_core_name_/core_options.json",
|
||||
"translation": "/intl/_core_name_/_%two_letters_code%.json",
|
||||
"source": "/_core_name_/_us.json",
|
||||
"dest": "/_core_name_/_core_name_.json",
|
||||
"translation": "/_core_name_/_%two_letters_code%.json",
|
||||
},
|
||||
]
|
||||
|
@ -2,10 +2,9 @@
|
||||
|
||||
import core_option_translation as t
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
if t.os.path.isfile(t.sys.argv[1]):
|
||||
if t.os.path.isfile(t.sys.argv[1]) or t.sys.argv[1].endswith('.h'):
|
||||
_temp = t.os.path.dirname(t.sys.argv[1])
|
||||
else:
|
||||
_temp = t.sys.argv[1]
|
||||
|
@ -29,8 +29,8 @@ if __name__ == '__main__':
|
||||
crowdin_config = re.sub(r'"api_token": "_secret_"',
|
||||
f'"api_token": "{API_KEY}"',
|
||||
crowdin_config, 1)
|
||||
crowdin_config = re.sub(r'/_core_name_/',
|
||||
f'/{CORE_NAME}/'
|
||||
crowdin_config = re.sub(r'/_core_name_',
|
||||
f'/{CORE_NAME}'
|
||||
, crowdin_config)
|
||||
with open(YAML_PATH, 'w') as crowdin_config_file:
|
||||
crowdin_config_file.write(crowdin_config)
|
||||
@ -68,8 +68,8 @@ if __name__ == '__main__':
|
||||
crowdin_config, 1)
|
||||
|
||||
# TODO this is NOT safe!
|
||||
crowdin_config = re.sub(re.escape(f'/{CORE_NAME}/'),
|
||||
'/_core_name_/',
|
||||
crowdin_config = re.sub(re.escape(f'/{CORE_NAME}'),
|
||||
'/_core_name_',
|
||||
crowdin_config)
|
||||
|
||||
with open(YAML_PATH, 'w') as crowdin_config_file:
|
||||
@ -84,8 +84,8 @@ if __name__ == '__main__':
|
||||
crowdin_config, 1)
|
||||
|
||||
# TODO this is NOT safe!
|
||||
crowdin_config = re.sub(re.escape(f'/{CORE_NAME}/'),
|
||||
'/_core_name_/',
|
||||
crowdin_config = re.sub(re.escape(f'/{CORE_NAME}'),
|
||||
'/_core_name_',
|
||||
crowdin_config)
|
||||
|
||||
with open(YAML_PATH, 'w') as crowdin_config_file:
|
||||
|
@ -2,10 +2,9 @@
|
||||
|
||||
import core_option_translation as t
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
if t.os.path.isfile(t.sys.argv[1]):
|
||||
if t.os.path.isfile(t.sys.argv[1]) or t.sys.argv[1].endswith('.h'):
|
||||
_temp = t.os.path.dirname(t.sys.argv[1])
|
||||
else:
|
||||
_temp = t.sys.argv[1]
|
||||
|
@ -29,8 +29,8 @@ if __name__ == '__main__':
|
||||
crowdin_config = re.sub(r'"api_token": "_secret_"',
|
||||
f'"api_token": "{API_KEY}"',
|
||||
crowdin_config, 1)
|
||||
crowdin_config = re.sub(r'/_core_name_/',
|
||||
f'/{CORE_NAME}/'
|
||||
crowdin_config = re.sub(r'/_core_name_',
|
||||
f'/{CORE_NAME}'
|
||||
, crowdin_config)
|
||||
with open(YAML_PATH, 'w') as crowdin_config_file:
|
||||
crowdin_config_file.write(crowdin_config)
|
||||
@ -68,8 +68,8 @@ if __name__ == '__main__':
|
||||
crowdin_config, 1)
|
||||
|
||||
# TODO this is NOT safe!
|
||||
crowdin_config = re.sub(re.escape(f'/{CORE_NAME}/'),
|
||||
'/_core_name_/',
|
||||
crowdin_config = re.sub(re.escape(f'/{CORE_NAME}'),
|
||||
'/_core_name_',
|
||||
crowdin_config)
|
||||
|
||||
with open(YAML_PATH, 'w') as crowdin_config_file:
|
||||
@ -84,8 +84,8 @@ if __name__ == '__main__':
|
||||
crowdin_config, 1)
|
||||
|
||||
# TODO this is NOT safe!
|
||||
crowdin_config = re.sub(re.escape(f'/{CORE_NAME}/'),
|
||||
'/_core_name_/',
|
||||
crowdin_config = re.sub(re.escape(f'/{CORE_NAME}'),
|
||||
'/_core_name_',
|
||||
crowdin_config)
|
||||
|
||||
with open(YAML_PATH, 'w') as crowdin_config_file:
|
||||
|
@ -21,8 +21,8 @@ if __name__ == '__main__':
|
||||
print('Please provide Crowdin API Token and core name!')
|
||||
raise e
|
||||
|
||||
DIR_PATH = t.os.path.dirname(t.os.path.realpath(__file__))
|
||||
YAML_PATH = t.os.path.join(DIR_PATH, 'crowdin.yaml')
|
||||
DIR_PATH = os.path.dirname(os.path.realpath(__file__))
|
||||
YAML_PATH = os.path.join(DIR_PATH, 'crowdin.yaml')
|
||||
|
||||
# Apply Crowdin API Key
|
||||
with open(YAML_PATH, 'r') as crowdin_config_file:
|
||||
@ -30,8 +30,8 @@ if __name__ == '__main__':
|
||||
crowdin_config = re.sub(r'"api_token": "_secret_"',
|
||||
f'"api_token": "{API_KEY}"',
|
||||
crowdin_config, 1)
|
||||
crowdin_config = re.sub(r'/_core_name_/',
|
||||
f'/{CORE_NAME}/'
|
||||
crowdin_config = re.sub(r'/_core_name_',
|
||||
f'/{CORE_NAME}'
|
||||
, crowdin_config)
|
||||
with open(YAML_PATH, 'w') as crowdin_config_file:
|
||||
crowdin_config_file.write(crowdin_config)
|
||||
@ -39,22 +39,22 @@ if __name__ == '__main__':
|
||||
try:
|
||||
# Download Crowdin CLI
|
||||
jar_name = 'crowdin-cli.jar'
|
||||
jar_path = t.os.path.join(DIR_PATH, jar_name)
|
||||
jar_path = os.path.join(DIR_PATH, jar_name)
|
||||
crowdin_cli_file = 'crowdin-cli.zip'
|
||||
crowdin_cli_url = 'https://downloads.crowdin.com/cli/v3/' + crowdin_cli_file
|
||||
crowdin_cli_path = t.os.path.join(DIR_PATH, crowdin_cli_file)
|
||||
crowdin_cli_path = os.path.join(DIR_PATH, crowdin_cli_file)
|
||||
|
||||
if not os.path.isfile(t.os.path.join(DIR_PATH, jar_name)):
|
||||
if not os.path.isfile(os.path.join(DIR_PATH, jar_name)):
|
||||
print('download crowdin-cli.jar')
|
||||
urllib.request.urlretrieve(crowdin_cli_url, crowdin_cli_path)
|
||||
with zipfile.ZipFile(crowdin_cli_path, 'r') as zip_ref:
|
||||
jar_dir = t.os.path.join(DIR_PATH, zip_ref.namelist()[0])
|
||||
jar_dir = os.path.join(DIR_PATH, zip_ref.namelist()[0])
|
||||
for file in zip_ref.namelist():
|
||||
if file.endswith(jar_name):
|
||||
jar_file = file
|
||||
break
|
||||
zip_ref.extract(jar_file, path=DIR_PATH)
|
||||
os.rename(t.os.path.join(DIR_PATH, jar_file), jar_path)
|
||||
os.rename(os.path.join(DIR_PATH, jar_file), jar_path)
|
||||
os.remove(crowdin_cli_path)
|
||||
shutil.rmtree(jar_dir)
|
||||
|
||||
@ -74,24 +74,37 @@ if __name__ == '__main__':
|
||||
crowdin_config = re.sub(r'"api_token": ".*?"', '"api_token": "_secret_"', crowdin_config, 1)
|
||||
|
||||
# TODO this is NOT safe!
|
||||
crowdin_config = re.sub(re.escape(f'/{CORE_NAME}/'),
|
||||
'/_core_name_/',
|
||||
crowdin_config = re.sub(re.escape(f'/{CORE_NAME}'),
|
||||
'/_core_name_',
|
||||
crowdin_config)
|
||||
|
||||
with open(YAML_PATH, 'w') as crowdin_config_file:
|
||||
crowdin_config_file.write(crowdin_config)
|
||||
|
||||
with open('intl/translation_workflow.py', 'r') as workflow:
|
||||
with open('intl/upload_workflow.py', 'r') as workflow:
|
||||
workflow_config = workflow.read()
|
||||
workflow_config = workflow_config.replace(
|
||||
"subprocess.run(['python3', 'intl/core_option_translation.py', dir_path, core_name])",
|
||||
"subprocess.run(['python3', 'intl/crowdin_prep.py', dir_path, core_name])"
|
||||
)
|
||||
workflow_config = workflow_config.replace(
|
||||
"subprocess.run(['python3', 'intl/initial_sync.py', api_key, core_name])\n",
|
||||
"subprocess.run(['python3', 'intl/initial_sync.py', api_key, core_name])",
|
||||
"subprocess.run(['python3', 'intl/crowdin_source_upload.py', api_key, core_name])"
|
||||
)
|
||||
with open('intl/upload_workflow.py', 'w') as workflow:
|
||||
workflow.write(workflow_config)
|
||||
|
||||
with open('intl/download_workflow.py', 'r') as workflow:
|
||||
workflow_config = workflow.read()
|
||||
workflow_config = workflow_config.replace(
|
||||
"subprocess.run(['python3', 'intl/core_option_translation.py', dir_path, core_name])",
|
||||
"subprocess.run(['python3', 'intl/crowdin_prep.py', dir_path, core_name])"
|
||||
)
|
||||
workflow_config = workflow_config.replace(
|
||||
"subprocess.run(['python3', 'intl/initial_sync.py', api_key, core_name])",
|
||||
"subprocess.run(['python3', 'intl/crowdin_translation_download.py', api_key, core_name])"
|
||||
)
|
||||
with open('intl/translation_workflow.py', 'w') as workflow:
|
||||
with open('intl/download_workflow.py', 'w') as workflow:
|
||||
workflow.write(workflow_config)
|
||||
|
||||
except Exception as e:
|
||||
@ -103,8 +116,8 @@ if __name__ == '__main__':
|
||||
crowdin_config, 1)
|
||||
|
||||
# TODO this is NOT safe!
|
||||
crowdin_config = re.sub(re.escape(f'/{CORE_NAME}/'),
|
||||
'/_core_name_/',
|
||||
crowdin_config = re.sub(re.escape(f'/{CORE_NAME}'),
|
||||
'/_core_name_',
|
||||
crowdin_config)
|
||||
|
||||
with open(YAML_PATH, 'w') as crowdin_config_file:
|
||||
|
30
intl/remove_initial_cycle.py
Normal file
30
intl/remove_initial_cycle.py
Normal file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
with open('intl/upload_workflow.py', 'r') as workflow:
|
||||
workflow_config = workflow.read()
|
||||
|
||||
workflow_config = workflow_config.replace(
|
||||
"subprocess.run(['python3', 'intl/core_option_translation.py', dir_path, core_name])",
|
||||
"subprocess.run(['python3', 'intl/crowdin_prep.py', dir_path, core_name])"
|
||||
)
|
||||
workflow_config = workflow_config.replace(
|
||||
"subprocess.run(['python3', 'intl/initial_sync.py', api_key, core_name])",
|
||||
"subprocess.run(['python3', 'intl/crowdin_source_upload.py', api_key, core_name])"
|
||||
)
|
||||
with open('intl/upload_workflow.py', 'w') as workflow:
|
||||
workflow.write(workflow_config)
|
||||
|
||||
|
||||
with open('intl/download_workflow.py', 'r') as workflow:
|
||||
workflow_config = workflow.read()
|
||||
|
||||
workflow_config = workflow_config.replace(
|
||||
"subprocess.run(['python3', 'intl/core_option_translation.py', dir_path, core_name])",
|
||||
"subprocess.run(['python3', 'intl/crowdin_prep.py', dir_path, core_name])"
|
||||
)
|
||||
workflow_config = workflow_config.replace(
|
||||
"subprocess.run(['python3', 'intl/initial_sync.py', api_key, core_name])",
|
||||
"subprocess.run(['python3', 'intl/crowdin_translation_download.py', api_key, core_name])"
|
||||
)
|
||||
with open('intl/download_workflow.py', 'w') as workflow:
|
||||
workflow.write(workflow_config)
|
@ -286,6 +286,11 @@ enum retro_language
|
||||
RETRO_LANGUAGE_INDONESIAN = 24,
|
||||
RETRO_LANGUAGE_SWEDISH = 25,
|
||||
RETRO_LANGUAGE_UKRAINIAN = 26,
|
||||
RETRO_LANGUAGE_CZECH = 27,
|
||||
RETRO_LANGUAGE_CATALAN_VALENCIA = 28,
|
||||
RETRO_LANGUAGE_CATALAN = 29,
|
||||
RETRO_LANGUAGE_BRITISH_ENGLISH = 30,
|
||||
RETRO_LANGUAGE_HUNGARIAN = 31,
|
||||
RETRO_LANGUAGE_LAST,
|
||||
|
||||
/* Ensure sizeof(enum) == sizeof(int) */
|
||||
@ -1756,6 +1761,12 @@ enum retro_mod
|
||||
* the frontend is attempting to call retro_run().
|
||||
*/
|
||||
|
||||
#define RETRO_ENVIRONMENT_GET_SAVESTATE_CONTEXT (72 | RETRO_ENVIRONMENT_EXPERIMENTAL)
|
||||
/* int * --
|
||||
* Tells the core about the context the frontend is asking for savestate.
|
||||
* (see enum retro_savestate_context)
|
||||
*/
|
||||
|
||||
/* VFS functionality */
|
||||
|
||||
/* File paths:
|
||||
@ -2993,6 +3004,35 @@ enum retro_pixel_format
|
||||
RETRO_PIXEL_FORMAT_UNKNOWN = INT_MAX
|
||||
};
|
||||
|
||||
enum retro_savestate_context
|
||||
{
|
||||
/* Standard savestate written to disk. */
|
||||
RETRO_SAVESTATE_CONTEXT_NORMAL = 0,
|
||||
|
||||
/* Savestate where you are guaranteed that the same instance will load the save state.
|
||||
* You can store internal pointers to code or data.
|
||||
* It's still a full serialization and deserialization, and could be loaded or saved at any time.
|
||||
* It won't be written to disk or sent over the network.
|
||||
*/
|
||||
RETRO_SAVESTATE_CONTEXT_RUNAHEAD_SAME_INSTANCE = 1,
|
||||
|
||||
/* Savestate where you are guaranteed that the same emulator binary will load that savestate.
|
||||
* You can skip anything that would slow down saving or loading state but you can not store internal pointers.
|
||||
* It won't be written to disk or sent over the network.
|
||||
* Example: "Second Instance" runahead
|
||||
*/
|
||||
RETRO_SAVESTATE_CONTEXT_RUNAHEAD_SAME_BINARY = 2,
|
||||
|
||||
/* Savestate used within a rollback netplay feature.
|
||||
* You should skip anything that would unnecessarily increase bandwidth usage.
|
||||
* It won't be written to disk but it will be sent over the network.
|
||||
*/
|
||||
RETRO_SAVESTATE_CONTEXT_ROLLBACK_NETPLAY = 3,
|
||||
|
||||
/* Ensure sizeof() == sizeof(int). */
|
||||
RETRO_SAVESTATE_CONTEXT_UNKNOWN = INT_MAX
|
||||
};
|
||||
|
||||
struct retro_message
|
||||
{
|
||||
const char *msg; /* Message to be displayed. */
|
||||
|
@ -139,6 +139,7 @@ static uint8_t brm_format[0x40] =
|
||||
0x53,0x45,0x47,0x41,0x5f,0x43,0x44,0x5f,0x52,0x4f,0x4d,0x00,0x01,0x00,0x00,0x00,
|
||||
0x52,0x41,0x4d,0x5f,0x43,0x41,0x52,0x54,0x52,0x49,0x44,0x47,0x45,0x5f,0x5f,0x5f
|
||||
};
|
||||
uint8_t cart_size;
|
||||
|
||||
static bool is_running = 0;
|
||||
static uint8_t temp[0x10000];
|
||||
@ -199,8 +200,8 @@ static bool libretro_supports_bitmasks = false;
|
||||
|
||||
#define SOUND_FREQUENCY 44100
|
||||
|
||||
/* Hide the EQ settings for now */
|
||||
/*#define HAVE_EQ*/
|
||||
/*EQ settings*/
|
||||
#define HAVE_EQ
|
||||
|
||||
/* Frameskipping Support */
|
||||
|
||||
@ -1338,7 +1339,9 @@ static void check_variables(bool first_run)
|
||||
bool update_frameskip = false;
|
||||
struct retro_variable var = {0};
|
||||
|
||||
var.key = CORE_NAME "_bram";
|
||||
if (first_run)
|
||||
{
|
||||
var.key = CORE_NAME "_system_bram";
|
||||
environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var);
|
||||
{
|
||||
if (!var.value || !strcmp(var.value, "per bios"))
|
||||
@ -1357,6 +1360,106 @@ static void check_variables(bool first_run)
|
||||
strlcpy(CD_BRAM_JP, newpath, sizeof(CD_BRAM_JP));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (first_run)
|
||||
{
|
||||
var.key = CORE_NAME "_cart_size";
|
||||
environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var);
|
||||
{
|
||||
if (var.value && !strcmp(var.value, "disabled"))
|
||||
cart_size = 0xff;
|
||||
else if (var.value && !strcmp(var.value, "128k"))
|
||||
cart_size = 1;
|
||||
else if (var.value && !strcmp(var.value, "256k"))
|
||||
cart_size = 2;
|
||||
else if (var.value && !strcmp(var.value, "512k"))
|
||||
cart_size = 3;
|
||||
else if (var.value && !strcmp(var.value, "1meg"))
|
||||
cart_size = 4;
|
||||
else if (var.value && !strcmp(var.value, "2meg"))
|
||||
cart_size = 5;
|
||||
else if (var.value && !strcmp(var.value, "4meg"))
|
||||
cart_size = 6;
|
||||
}
|
||||
}
|
||||
|
||||
if (first_run)
|
||||
{
|
||||
var.key = CORE_NAME "_cart_bram";
|
||||
environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var);
|
||||
{
|
||||
if ((!var.value || !strcmp(var.value, "per cart")) && cart_size == 1)
|
||||
{
|
||||
fill_pathname_join(CART_BRAM, save_dir, "128Kbit_cart.brm", sizeof(CART_BRAM));
|
||||
}
|
||||
else if ((!var.value || !strcmp(var.value, "per cart")) && cart_size == 2)
|
||||
{
|
||||
fill_pathname_join(CART_BRAM, save_dir, "256Kbit_cart.brm", sizeof(CART_BRAM));
|
||||
}
|
||||
else if ((!var.value || !strcmp(var.value, "per cart")) && cart_size == 3)
|
||||
{
|
||||
fill_pathname_join(CART_BRAM, save_dir, "512Kbit_cart.brm", sizeof(CART_BRAM));
|
||||
}
|
||||
else if ((!var.value || !strcmp(var.value, "per cart")) && cart_size == 4)
|
||||
{
|
||||
fill_pathname_join(CART_BRAM, save_dir, "1Mbit_cart.brm", sizeof(CART_BRAM));
|
||||
}
|
||||
else if ((!var.value || !strcmp(var.value, "per cart")) && cart_size == 5)
|
||||
{
|
||||
fill_pathname_join(CART_BRAM, save_dir, "2Mbit_cart.brm", sizeof(CART_BRAM));
|
||||
}
|
||||
else if ((!var.value || !strcmp(var.value, "per cart")) && cart_size == 6)
|
||||
{
|
||||
fill_pathname_join(CART_BRAM, save_dir, "4Mbit_cart.brm", sizeof(CART_BRAM));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cart_size == 1)
|
||||
{
|
||||
char newpath[4096];
|
||||
fill_pathname_join(newpath, save_dir, g_rom_name, sizeof(newpath));
|
||||
strlcat(newpath, "_128Kbit_cart.brm", sizeof(newpath));
|
||||
strlcpy(CART_BRAM, newpath, sizeof(CART_BRAM));
|
||||
}
|
||||
else if (cart_size == 2)
|
||||
{
|
||||
char newpath[4096];
|
||||
fill_pathname_join(newpath, save_dir, g_rom_name, sizeof(newpath));
|
||||
strlcat(newpath, "_256Kbit_cart.brm", sizeof(newpath));
|
||||
strlcpy(CART_BRAM, newpath, sizeof(CART_BRAM));
|
||||
}
|
||||
else if (cart_size == 3)
|
||||
{
|
||||
char newpath[4096];
|
||||
fill_pathname_join(newpath, save_dir, g_rom_name, sizeof(newpath));
|
||||
strlcat(newpath, "_512Kbit_cart.brm", sizeof(newpath));
|
||||
strlcpy(CART_BRAM, newpath, sizeof(CART_BRAM));
|
||||
}
|
||||
else if (cart_size == 4)
|
||||
{
|
||||
char newpath[4096];
|
||||
fill_pathname_join(newpath, save_dir, g_rom_name, sizeof(newpath));
|
||||
strlcat(newpath, "_1Mbit_cart.brm", sizeof(newpath));
|
||||
strlcpy(CART_BRAM, newpath, sizeof(CART_BRAM));
|
||||
}
|
||||
else if (cart_size == 5)
|
||||
{
|
||||
char newpath[4096];
|
||||
fill_pathname_join(newpath, save_dir, g_rom_name, sizeof(newpath));
|
||||
strlcat(newpath, "_2Mbit_cart.brm", sizeof(newpath));
|
||||
strlcpy(CART_BRAM, newpath, sizeof(CART_BRAM));
|
||||
}
|
||||
else if (cart_size == 6)
|
||||
{
|
||||
char newpath[4096];
|
||||
fill_pathname_join(newpath, save_dir, g_rom_name, sizeof(newpath));
|
||||
strlcat(newpath, "_4Mbit_cart.brm", sizeof(newpath));
|
||||
strlcpy(CART_BRAM, newpath, sizeof(CART_BRAM));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var.key = CORE_NAME "_system_hw";
|
||||
environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var);
|
||||
@ -1664,7 +1767,7 @@ static void check_variables(bool first_run)
|
||||
if (var.value && !strcmp(var.value, "low-pass"))
|
||||
config.filter = 1;
|
||||
|
||||
#if HAVE_EQ
|
||||
#ifdef HAVE_EQ
|
||||
else if (var.value && !strcmp(var.value, "EQ"))
|
||||
config.filter = 2;
|
||||
#endif
|
||||
@ -1679,7 +1782,7 @@ static void check_variables(bool first_run)
|
||||
config.lp_range = (!var.value) ? 0x9999 : ((atoi(var.value) * 65536) / 100);
|
||||
}
|
||||
|
||||
#if HAVE_EQ
|
||||
#ifdef HAVE_EQ
|
||||
var.key = CORE_NAME "_audio_eq_low";
|
||||
environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var);
|
||||
{
|
||||
@ -2852,7 +2955,7 @@ void retro_set_environment(retro_environment_t cb)
|
||||
|
||||
static const struct retro_system_content_info_override content_overrides[] = {
|
||||
{
|
||||
"mdx|md|smd|gen|bms|sms|gg|sg|68k|sgd", /* extensions */
|
||||
"mdx|md|bin|smd|gen|bms|sms|gg|sg|68k|sgd", /* extensions */
|
||||
#if defined(LOW_MEMORY)
|
||||
true, /* need_fullpath */
|
||||
#else
|
||||
@ -3307,7 +3410,6 @@ bool retro_load_game(const struct retro_game_info *info)
|
||||
fill_pathname_join(CD_BIOS_EU, dir, "bios_CD_E.bin", sizeof(CD_BIOS_EU));
|
||||
fill_pathname_join(CD_BIOS_US, dir, "bios_CD_U.bin", sizeof(CD_BIOS_US));
|
||||
fill_pathname_join(CD_BIOS_JP, dir, "bios_CD_J.bin", sizeof(CD_BIOS_JP));
|
||||
fill_pathname_join(CART_BRAM, dir, "cart.brm", sizeof(CART_BRAM));
|
||||
|
||||
check_variables(true);
|
||||
|
||||
@ -3545,37 +3647,44 @@ size_t retro_get_memory_size(unsigned id)
|
||||
{
|
||||
case RETRO_MEMORY_SAVE_RAM:
|
||||
{
|
||||
/* return 0 if SRAM is disabled */
|
||||
if (!sram.on)
|
||||
return 0;
|
||||
|
||||
/* if emulation is not running, we assume the frontend is requesting SRAM size for loading */
|
||||
/* if emulation is not running, we assume the frontend is requesting SRAM size for loading so max supported size is returned */
|
||||
if (!is_running)
|
||||
{
|
||||
/* max supported size is returned */
|
||||
return 0x10000;
|
||||
}
|
||||
|
||||
/* otherwise, we assume this is for saving and we need to check if SRAM data has been modified */
|
||||
/* otherwise, we assume this is for saving and we return the size of SRAM data that has actually been modified */
|
||||
/* this is obviously not %100 safe since the frontend could still be trying to load SRAM while emulation is running */
|
||||
/* a better solution would be that the frontend itself checks if data has been modified before writing it to a file */
|
||||
for (i=0xffff; i>=0; i--)
|
||||
{
|
||||
if (sram.sram[i] != 0xff)
|
||||
{
|
||||
/* only save modified size */
|
||||
return (i+1);
|
||||
|
||||
/* return 0 if SRAM is not modified */
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case RETRO_MEMORY_SYSTEM_RAM:
|
||||
if (system_hw == SYSTEM_SG)
|
||||
return 0x00400;
|
||||
{
|
||||
/* 16-bit hardware */
|
||||
if ((system_hw & SYSTEM_PBC) == SYSTEM_MD)
|
||||
return 0x10000; /* 64KB internal RAM */
|
||||
|
||||
/* get 8-bit cartrige on-board RAM size */
|
||||
i = sms_cart_ram_size();
|
||||
|
||||
if (i > 0)
|
||||
return i + 0x2000; /* on-board RAM size + max 8KB internal RAM */
|
||||
else if (system_hw == SYSTEM_SGII)
|
||||
return 0x00800;
|
||||
else if (system_hw == SYSTEM_SGII_RAM_EXT || system_hw == SYSTEM_SMS || system_hw == SYSTEM_SMS2 || system_hw == SYSTEM_GG || system_hw == SYSTEM_GGMS || system_hw == SYSTEM_PBC)
|
||||
return 0x02000;
|
||||
return 0x0800; /* 2KB internal RAM */
|
||||
else if (system_hw == SYSTEM_SG)
|
||||
return 0x0400; /* 1KB internal RAM */
|
||||
else
|
||||
return 0x10000;
|
||||
return 0x2000; /* 8KB internal RAM */
|
||||
}
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ extern "C" {
|
||||
|
||||
#if defined(M68K_OVERCLOCK_SHIFT) || defined(Z80_OVERCLOCK_SHIFT)
|
||||
#define HAVE_OVERCLOCK
|
||||
#define HAVE_EQ
|
||||
#endif
|
||||
|
||||
/*
|
||||
@ -146,8 +147,8 @@ struct retro_core_option_v2_definition option_defs_us[] = {
|
||||
"disabled"
|
||||
},
|
||||
{
|
||||
CORE_NAME "_bram",
|
||||
"CD System BRAM",
|
||||
CORE_NAME "_system_bram",
|
||||
"CD System BRAM (Requires Restart)",
|
||||
NULL,
|
||||
"When running Sega CD/Mega-CD content, specifies whether to share a single save file between all games from a specific region (Per-BIOS) or to create a separate save file for each game (Per-Game). Note that the Sega CD/Mega-CD has limited internal storage, sufficient only for a handful of titles. To avoid running out of space, the 'Per-Game' setting is recommended.",
|
||||
NULL,
|
||||
@ -159,6 +160,39 @@ struct retro_core_option_v2_definition option_defs_us[] = {
|
||||
},
|
||||
"per bios"
|
||||
},
|
||||
{
|
||||
CORE_NAME "_cart_bram",
|
||||
"CD Backup Cart BRAM (Requires Restart)",
|
||||
NULL,
|
||||
"When running Sega CD/Mega-CD content, specifies whether to share a single backup ram cart for all games (Per-Cart) or to create a separate backup ram cart for each game (Per-Game).",
|
||||
NULL,
|
||||
"system",
|
||||
{
|
||||
{ "per cart", "Per-Cart" },
|
||||
{ "per game", "Per-Game" },
|
||||
{ NULL, NULL },
|
||||
},
|
||||
"per cart"
|
||||
},
|
||||
{
|
||||
CORE_NAME "_cart_size",
|
||||
"CD Backup Cart BRAM Size (Requires Restart)",
|
||||
NULL,
|
||||
"Sets the backup ram cart size when running Sega CD/Mega-CD content. Useful when setting the backup ram cart to Per-Game to avoid multiple larger cart sizes.",
|
||||
NULL,
|
||||
"system",
|
||||
{
|
||||
{ "disabled", "Disabled" },
|
||||
{ "128k", "128Kbit" },
|
||||
{ "256k", "256Kbit" },
|
||||
{ "512k", "512Kbit" },
|
||||
{ "1meg", "1Mbit" },
|
||||
{ "2meg", "2Mbit" },
|
||||
{ "4meg", "4Mbit" },
|
||||
{ NULL, NULL },
|
||||
},
|
||||
"4meg"
|
||||
},
|
||||
{
|
||||
CORE_NAME "_add_on",
|
||||
"CD add-on (MD mode) (Requires Restart)",
|
||||
@ -457,7 +491,7 @@ struct retro_core_option_v2_definition option_defs_us[] = {
|
||||
{
|
||||
{ "disabled", NULL },
|
||||
{ "low-pass", "Low-Pass" },
|
||||
#if HAVE_EQ
|
||||
#ifdef HAVE_EQ
|
||||
{ "EQ", NULL },
|
||||
#endif
|
||||
{ NULL, NULL },
|
||||
@ -842,7 +876,7 @@ struct retro_core_option_v2_definition option_defs_us[] = {
|
||||
CORE_NAME "_enhanced_vscroll_limit",
|
||||
"Enhanced per-tile vertical scroll limit",
|
||||
NULL,
|
||||
"Only when Enchance per-tile vertical scroll is enabled. Adjusts the limit of the vertical scroll enhancement. When the vscroll difference between neighbouring tiles is bigger than this limit, the enhancement is disabled.",
|
||||
"Only when Enhanced per-tile vertical scroll is enabled. Adjusts the limit of the vertical scroll enhancement. When the vscroll difference between neighbouring tiles is bigger than this limit, the enhancement is disabled.",
|
||||
NULL,
|
||||
"hacks",
|
||||
{
|
||||
@ -1434,6 +1468,11 @@ struct retro_core_options_v2 *options_intl[RETRO_LANGUAGE_LAST] = {
|
||||
&options_id, /* RETRO_LANGUAGE_INDONESIAN */
|
||||
&options_sv, /* RETRO_LANGUAGE_SWEDISH */
|
||||
&options_uk, /* RETRO_LANGUAGE_UKRAINIAN */
|
||||
&options_cs, /* RETRO_LANGUAGE_CZECH */
|
||||
&options_val, /* RETRO_LANGUAGE_CATALAN_VALENCIA */
|
||||
&options_ca, /* RETRO_LANGUAGE_CATALAN */
|
||||
&options_en, /* RETRO_LANGUAGE_BRITISH_ENGLISH */
|
||||
&options_hu, /* RETRO_LANGUAGE_HUNGARIAN */
|
||||
};
|
||||
#endif
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
64
wiki/Compatibility.md
Normal file
64
wiki/Compatibility.md
Normal file
@ -0,0 +1,64 @@
|
||||
|
||||
Genesis Plus GX is able to accurately emulate various console hardware models, which makes it compatible with 100% of SG-1000, Master System, Game Gear, Genesis / Mega Drive & Sega/Mega CD released software, including demos, unlicensed & pirate games.
|
||||
|
||||
This means that all these games have been verified to be working without any graphical glitches or lock-up but off course, it is impossible for me to test all games *from start to end* so feel free to report an [issue](https://bitbucket.org/eke/genesis-plus-gx/issues?status=new&status=open) if you found a specific game not running accurately with this emulator.
|
||||
|
||||
On a side note, make sure you are always using good dumps: ROM files labeled as bad, "fixed" or hacked dumps (b1, f1, h1, ...), are known to cause issues, even on real hardware.
|
||||
|
||||
## Sega/Mega CD games ##
|
||||
|
||||
Sega/Mega CD games are only compatible with Genesis / Mega Drive hardware. Sega/Mega CD add-on is automatically enabled when a valid CD image file is detected.
|
||||
|
||||
|
||||
## Genesis / Mega Drive games ##
|
||||
|
||||
Genesis / Mega Drive games are only compatible with Genesis / Mega Drive hardware. Some games might require different hardware to be enabled (PICO games or games only working with CD unit attached) and are auto-detected.
|
||||
|
||||
|
||||
## Game Gear games ##
|
||||
|
||||
Game Gear games are only compatible with Game Gear hardware.
|
||||
|
||||
|
||||
## Master System games ##
|
||||
|
||||
Master System games are compatible with Game Gear and Genesis hardware, using backwards compatibility mode. However, the following games are known to be working only on specific hardware:
|
||||
|
||||
### _games requiring Master System BIOS to initialize VDP registers_ ###
|
||||
* California Games II
|
||||
* Speed Ball
|
||||
|
||||
### _games incompatible with Genesis VDP_ ###
|
||||
* Bart vs. The Space Mutants (palette issue)
|
||||
* Earthworm Jim (zoomed sprites issue)
|
||||
* X-Men - Mojo World (palette issue)
|
||||
|
||||
### _games requiring legacy VDP modes (not supported by Genesis VDP or Game Gear with custom BIOS)_ ###
|
||||
|
||||
* Dr Hello
|
||||
* F-1 Spirit - The way to Formula-1
|
||||
* F16 Fighter
|
||||
* F16 Fighting Falcon
|
||||
* FA Tetris
|
||||
* Flashpoint
|
||||
* Loretta no Shouzou - Sherlock Holmes
|
||||
* Penguin Adventure
|
||||
* Street Master
|
||||
* Super Boy II
|
||||
* The Three Dragon Story
|
||||
* Wonsiin
|
||||
* Cyborg Z
|
||||
|
||||
### _games requiring extended VDP modes (Master System II & Game Gear only)_ ###
|
||||
* Cosmic Spacehead
|
||||
* Excellent Dizzy Collection, The [Proto]
|
||||
* Fantastic Dizzy
|
||||
* Micro Machines
|
||||
|
||||
### _games requiring original VDP limitation (Mark III & Master System I only)_ ###
|
||||
* Y'S (Japanese version)
|
||||
|
||||
|
||||
## SG-1000 games ##
|
||||
|
||||
SG-1000 games are compatible with Mark-III and Master System hardware, using backwards compatibility mode (a custom BIOS, not available, is required on Game Gear). In order to display the correct colour palette, it is however recommended to use SG-1000 hardware.
|
45
wiki/Credits.md
Normal file
45
wiki/Credits.md
Normal file
@ -0,0 +1,45 @@
|
||||
This project would not have been possible without these people:
|
||||
|
||||
## Genesis Plus core ##
|
||||
* current version (improved emulation accuracy, new features, compatibility fixes & various cores modification) by [eke-eke](http://gxdev.wordpress.com/)
|
||||
* based on the original 1.2a version by [Charles MacDonald](http://www.techno-junk.org/)
|
||||
* original Z80 core by Juergen Buchmueller ([MAME](http://mamedev.org/release.html))
|
||||
* original Musashi 68k core by Karl Stenerud ([MAME](http://mamedev.org/release.html))
|
||||
* original YM2612 & YM2413 core by Jarek Burczynski and Tatsuyuki Satoh ([MAME](http://mamedev.org/release.html))
|
||||
* SVP core by [Notaz](http://notaz.gp2x.de/svp.php)
|
||||
* Blip Buffer library & NTSC Video filter by Shay Green ([Blargg](http://www.fly.net/~ant/libs))
|
||||
* 3-Band EQ implementation by [Neil C ](http://www.musicdsp.org/archive.php?classid=3#236)
|
||||
* [libtremor](http://www.freshports.org/audio/libtremor) by xiph.org
|
||||
|
||||
### additional thanks to ###
|
||||
* [Nemesis], for his tests and documentation on YM2612 & VDP chips.
|
||||
* Tasco Deluxe for his work around the SVP chip and for his documentation of Realtec mapper used in a few unlicensed games.
|
||||
* [Bart Trzynadlowski](http://www.trzy.org/) for his documentation of the "Super Street Fighter II" mapper and some 68000 undocumented aspects.
|
||||
* [Jorge Cwik](http://pasti.fxatari.com/68kdocs/) for his work on some 68000 undocumented timings and instruction prefetch.
|
||||
* [Haze](http://mamedev.emulab.it/haze/) for having found and documented many unlicensed game protections.
|
||||
* Notaz & Stephane Dallongeville for sharing the source code of their respective emulators, Picodrive and Gens, which were also great sources of inspiration.
|
||||
* Steve Snake, author of [Kega](http://www.eidolons-inn.net/tiki-index.php?page=Kega) and AamirM, author of [Regen](http://aamirm.hacking-cult.org/), for sharing some of their findings.
|
||||
* Charles Mac Donald, again, for his excellent documentation about the Sega Genesis hardware and for having designed such a great emulator in the first place (your VDP renderer rules).
|
||||
* [Spritesmind](http://www.spritesmind.net/_GenDev/forum/) & [SMS Power](http://www.smspower.org/) forums members for their technical help.
|
||||
|
||||
|
||||
## Gamecube/Wii ports ##
|
||||
|
||||
* current version, Wii port, generic GUI design & coding by eke-eke
|
||||
* based on the original Game Cube port from Softdev, Honkeykong & Markcube
|
||||
* original icon, logo & button design by [LowLines](http://llref.emutalk.net/)
|
||||
* credit picture by [Orioto](http://orioto.deviantart.com/)
|
||||
* memory card icon design by [Brakken](http://www.tehskeen.net)
|
||||
* [libFAT](http://chishm.drunkencoders.com/libfat/) by Chism
|
||||
* [wiiuse](http://www.wiiuse.net/) library by Michael Laforest (para)
|
||||
* [libDI](http://hackmii.com/2008/08/libdi-and-the-dvdx-installer/) by Erant
|
||||
* [libOGC](http://www.devkitpro.org/category/libogc/) by Shagkur & many others
|
||||
* asndlib and OGG player by [Hermes](http://www.entuwii.net/foro/viewtopic.php?f=6&t=87) incl. some fixes by [Tantric](http://code.google.com/u/dborth/)
|
||||
* [libpng](http://www.libpng.org/pub/png/libpng.html) by various authors
|
||||
|
||||
### additional thanks to ###
|
||||
* Softdev for all his great work and inspiration
|
||||
* Wintermute for [devkitpro](http://www.devkitpro.org/) & [devkitPPC](http://www.devkitpro.org/category/devkitppc/)
|
||||
* [Tmbinc](http://debugmo.de/) for having opened the way to the Game Cube scene
|
||||
* [Twiizer team](http://hackmii.com/) for all their contribution to the Wii scene
|
||||
* Brakken & former Tehskeen forum members for their feedback, support and donations
|
109
wiki/Features.md
Normal file
109
wiki/Features.md
Normal file
@ -0,0 +1,109 @@
|
||||
|
||||
Genesis Plus main goal is to provide the most complete & accurate emulation of the Sega Genesis/Megadrive hardware.
|
||||
|
||||
The original emulation core from [Charles Mac Donald](http://cgfm2.emuviews.com/) has been largely modified to improve overall accuracy and therefore compatibility, as well as adding emulation of various peripherals, cartridge and system hardware.
|
||||
|
||||
![sega_megadrive_2.thumbnail.jpg](https://bitbucket.org/repo/7AjE6M/images/4007718275-sega_megadrive_2.thumbnail.jpg)
|
||||
|
||||
|
||||
## Very Accurate & Full Speed Sega 8-bit / 16-bit emulation ##
|
||||
* accurate emulation of SG-1000, Mark-III, Master System (I & II), Game Gear, Genesis / Mega Drive, Sega / Mega CD hardware models (incl. backwards compatibility modes)
|
||||
* NTSC (60Hz) & PAL (50Hz) video hardware emulation
|
||||
* highly accurate 68000 & Z80 CPU emulation & synchronization
|
||||
* highly accurate VDP emulation (all rendering modes, mid-line changes, undocumented registers,…) & timings (HBLANK, DMA, FIFO, HV interrupts,…)
|
||||
* sample-accurate YM2612,YM2413, SN76489, & RF5C164 PCM sound chips emulation
|
||||
* cycle-accurate sound chips synchronization with 68000/Z80 CPU
|
||||
* cycle-accurate 68000 & Z80 CPU synchronization
|
||||
* optimized Main-CPU / Sub-CPU synchronization (Sega/Mega CD)
|
||||
* accurate CDD, CDC & GFX chip emulation (Sega/Mega CD)
|
||||
* accurate CD-DA fader emulation (Sega/Mega CD)
|
||||
* Mode 1 cartridge support (Sega/Mega CD)
|
||||
* Audio CD & CD+G support (Sega/Mega CD)
|
||||
* high-quality audio resampling using Blip Buffer
|
||||
* basic hardware latency emulation (VDP/68k, Z80/68k)
|
||||
* full overscan area emulation (horizontal & vertical color borders)
|
||||
* optional Game Gear extended screen mode
|
||||
* optional Game Gear LCD ghosting filter
|
||||
* optional Blargg's NTSC filters
|
||||
* optional BOOT ROM support (Master System, Game Gear, Genesis / Mega Drive)
|
||||
* optional TMSS hardware emulation (Genesis / Mega Drive)
|
||||
* preliminary PICO emulation
|
||||
* support for raw (.bin, .gen, .md, .sms, .gg & .sg) and interleaved (.smd & .mdx) ROM files
|
||||
* support for various CD image file formats (CUE+BIN, ISO+WAV & ISO+OGG)
|
||||
* support for subcodes external files (SUB)
|
||||
|
||||
|
||||
![street.gif](https://bitbucket.org/repo/7AjE6M/images/607524734-street.gif)
|
||||
|
||||
## Support for various peripherals ##
|
||||
* 2-buttons, 3-buttons & 6-buttons Control Pads
|
||||
* Sega Team Player & EA 4-Way Play multitaps
|
||||
* Master Tap
|
||||
* Sega Mouse
|
||||
* Sega Paddle Control
|
||||
* Sega Sports Pad
|
||||
* Sega Graphics Board
|
||||
* Terebi Oekaki tablet
|
||||
* Sega Light Phaser
|
||||
* Sega Menacer
|
||||
* Konami Justifiers
|
||||
* Sega Activator
|
||||
* XE-1AP analog controller
|
||||
|
||||
|
||||
![menacer.jpg](https://bitbucket.org/repo/7AjE6M/images/4221166085-menacer.jpg)
|
||||
|
||||
## Support for various cartridges extra hardware ##
|
||||
* SVP DSP (Virtua Racing)
|
||||
* J-Cart adapter (Micro Machines & Pete Sampras series, Super Skidmarks)
|
||||
* Backup RAM (max. 64KB)
|
||||
* I2C (24Cxx), SPI (95xxx) & MicroWire (93C46) EEPROMs
|
||||
* RAM cart (max. 512KB) (Sega/Mega CD)
|
||||
* “official” ROM bankswitch hardware (Super Street Fighter 2)
|
||||
* “official” backup RAM bankswitch hardware (Phantasy Star 4, Legend of Thor, Sonic the Hedgehog 3)
|
||||
* all known unlicensed/pirate cartridges bankswitch & copy protection hardware
|
||||
* all known Master System & Game Gear cartridge “mappers” (incl. unlicensed Korean ones)
|
||||
* Game Genie & Action Replay hardware emulation
|
||||
* Sonic & Knuckles “Lock-On” hardware emulation
|
||||
* support for ROM image up to 10MB (Ultimate MK3 hack)
|
||||
|
||||
![vracing.png](https://bitbucket.org/repo/7AjE6M/images/802538951-vracing.png)
|
||||
|
||||
## Gamecube/Wii generic features ##
|
||||
|
||||
* fully featured & optimized Graphical User Interface
|
||||
* 48 kHz stereo sound
|
||||
* optimized GX video rendering engine
|
||||
* perfect audio/video/input synchronization
|
||||
* 50/60 Hz video output support
|
||||
* original low-resolution video modes support (interlaced & non-interlaced)
|
||||
* high-resolution interlaced (480i/576i) & progressive (480p/576p) video modes support
|
||||
* hardware bilinear filtering
|
||||
* configurable BIOS & Lock-on ROM files
|
||||
* configurable sound mixer (FM/PSG levels) and filtering (Low-Pass filter & 3-Band equalizer)
|
||||
* configurable NTSC filter
|
||||
* independently configurable region mode, VDP mode & Master Clock
|
||||
* 1~4 Players support
|
||||
* automatic Backup RAM and State files loading/saving
|
||||
* automatic game files loading
|
||||
* game files loading history
|
||||
* load files from SD/SDHC or DVD
|
||||
* support for zipped ROM files
|
||||
* game internal header information display
|
||||
* internal game screenshots
|
||||
* Game Genie & Pro Action Replay codes support through .pat files
|
||||
* cartridge "hot-swap"
|
||||
* automatic disc swap
|
||||
|
||||
|
||||
## Wii extra features ##
|
||||
* up to 8 Players support
|
||||
* Wii Remote, Nunchuk & Classic controllers support
|
||||
* Wii Remote IR support & calibration for light guns
|
||||
* Wii U Pro Controller support
|
||||
* USB mouse support for mouse emulation
|
||||
* USB drive support (IOS58 is required for USB2)
|
||||
* configurable hardware “Trap” filter & Gamma correction
|
||||
* "Wiiflow" plugin compatibility
|
||||
|
||||
![wiimote.jpg](https://bitbucket.org/repo/7AjE6M/images/2853669073-wiimote.jpg)
|
119
wiki/Frequently Asked Questions.md
Normal file
119
wiki/Frequently Asked Questions.md
Normal file
@ -0,0 +1,119 @@
|
||||
----
|
||||
|
||||
**_The emulator does not start and hangs on a black screen when loaded on my Wii._**
|
||||
|
||||
Preferably use the Homebrew Channel to launch this program on your Wii, using the provided meta.xml file.
|
||||
|
||||
If you installed the emulator as a standalone channel or are using a different loader, you might have modified or deleted required system files (IOS), which could prevent the application from starting. Also note that some loaders are not compatible with compressed dols, instead of provided boot.dol, use genplus_wii.dol, which is not compressed.
|
||||
|
||||
Lastly, if you have unsupported devices connected to the Game Cube ports or USB ports, you might want to remove them.
|
||||
|
||||
----
|
||||
|
||||
**_The emulator loads fine but randomly freeze (no input response) or crashes (DSI exception screen) in the menu or during game._**
|
||||
|
||||
Use the latest version of Homebrew Channel to launch the emulator.
|
||||
|
||||
----
|
||||
|
||||
**_When I try to load game files from my SD card or USB drive, no files are found or an error message occurs._**
|
||||
|
||||
Make sure you have at least one FAT (FAT16 or FAT32) partition on your device.
|
||||
|
||||
----
|
||||
|
||||
**_My USB2 compatible drive does not seem to be detected, is that normal ?_**
|
||||
|
||||
USB2 compatibility requires IOS58 to be installed in your system (it usually came with recent System Menu updates). You must also make sure the Homebrew Channel is using this IOS (reinstall HBC if it doesn't) to load the emulator.
|
||||
|
||||
Please note that you cannot use both USB ports at the same time.
|
||||
|
||||
----
|
||||
|
||||
**_I can not load game files from the DVD, an error message occurs saying it can not be mounted or no valid DVD was found._**
|
||||
|
||||
First, make sure the DVD has been formatted in the expected format (ISO9960/Joliet). Then, your console must be able to read burned DVD: on a non-chipped Wii, you should use the Homebrew Channel and the provided meta.xml file, which enables full access to the DVD drive, without requiring a modchip.
|
||||
|
||||
Please note that on most recent Wii models, it is impossible to enable DVD access anymore.
|
||||
|
||||
----
|
||||
|
||||
**_Well, I can access my device but I can't see all my game files, what is happening ?_**
|
||||
|
||||
Because of memory limitations, the emulator only lists up to 1000 game files per directory. This should be more than enough for anybody but if you have a larger "collection", it is highly advised to arrange your files within subdirectories, which also increase the speed and the usability of the file browser.
|
||||
|
||||
----
|
||||
|
||||
**_When I load a game, nothing happen. I can return to the emulator menu but the game does not play or make the emulator crash._**
|
||||
|
||||
You probably tried to load an unsupported game file. The emulator only supports valid Genesis / Mega Drive (.bin, .gen, .md, .smd, .mdx), Master System (.sms), Game Gear (.gg), SG-1000 (.sg) ROM files or Sega/Mega CD image files (.bin, .iso, .cue). ROM files (not CD image files !) can be zipped but make sure there is only one file per .zip archive. Also note that .rar or .7z files are NOT supported.
|
||||
|
||||
Lastly, make sure you are only using verified dumps: bad dumps, hacked or fixed image files are subject to crash, even on real hardware, so just get ride of them.
|
||||
|
||||
----
|
||||
|
||||
**_I'm trying to load a CD game but I got an error message telling me the file is too big_**
|
||||
|
||||
This happen if the file you are trying to load is not recognized as a valid Sega / Mega CD image. Please make sure you are only loading valid .iso or .bin files (or a .cue file pointing to a valid .iso / .bin file). Compressed image (such as .rar, .zip, .7z, etc) are NOT supported for CD emulation, you must decompress them first. Also make sure the game you are trying to load is really a Sega / Mega CD game (you wouldn't believe the amount of people trying to load Saturn games !)
|
||||
|
||||
----
|
||||
|
||||
**_I can hear the sound of the game I loaded but nothing is displayed but a black or distorted screen._**
|
||||
|
||||
You are most likely using invalid video settings. Try deleting the configuration file (config.ini) and restart the emulator. If that does not help, try changing the following options, Display and TV Mode, and see if that fixes the issue.
|
||||
|
||||
----
|
||||
|
||||
**_I try to load a CD game but there is no audio at all, only sound effects most of the time._**
|
||||
|
||||
It means you did not load a CD image with valid CD-DA tracks informations. You can confirm that by entering the CD Boot menu when resetting the game (press A to enter Boot menu) and seeing the number of tracks is stuck to 99 (max). Keep in mind that if you are using an ISO file with external audio files, only 44100hz stereo 16-bit WAV or OGG audio files are supported and you must either name them accordingly or use an associated CUE file. When using a BIN file, an associated CUE file is needed if you want to hear audio tracks. Have a look in the README for more details about supported CD image & audio tracks format and how you should load them. Also note that many games will lock or crash at some points if the expected number and lengths of audio tracks is not respected.
|
||||
|
||||
----
|
||||
|
||||
**_Well, I can hear audio tracks and the CD Boot menu show them all but the sound is buggy/scratched/distorted._**
|
||||
|
||||
It is most likely due to audio files themselves. Play them on a computer with another media player to check if there are correct. Many CD rips you can find online are either corrupt or incomplete, you might want to use verified good disc dumps.
|
||||
|
||||
----
|
||||
|
||||
**_When playing CD games, I randomly experiment some slowdowns or short freezes with audio and/or video._**
|
||||
|
||||
Sega/Mega CD emulation is quite CPU consuming and all in all, there is only short time allowed to the emulator to read data from the disc image. Since disc images are continuously accessed from the loading device (SD, USB, DVD), slow or inconsistent device accesses will likely cause bottlenecks in emulator processing. There is no much solutions beside using faster devices and, for USB drives, to make sure IOS58 is loaded when the emulator starts and USB2 mode is enabled.
|
||||
|
||||
----
|
||||
|
||||
**_How can I use my Classic Controller, Nunchuk, etc ? It is connected to my Wii Remote but it does not respond in games._**
|
||||
|
||||
You must first configure the type of controller you want to use for each player. Go to controller settings, pick one of the player and change the affected device. Don't forget to change it back if you disconnect the expansion controller.
|
||||
|
||||
----
|
||||
|
||||
**_How can I play with the gun in Snatcher ? I successfully calibrated it using the wiimote when the game is started but it does not aim correctly during the game_**
|
||||
|
||||
The calibration routine in Snatcher is kinda bugged, or more exactly, it worked with a real gun because the original Justifiers were relatively imprecise so you wouldn't really notice if it doesn't really aim the center of the screen. The solution is simply not to use the calibration option AT ALL, the default configuration works fine in-game and aims perfectly where the wiimote is pointing.
|
||||
|
||||
----
|
||||
|
||||
**_My second Wii remote controller is not detected by the emulator. How can i play 2 players games ?_**
|
||||
|
||||
Any additional Wii remote must be permanently registered in your Wii system settings, otherwise it cannot be detected by homebrew applications. Go back to the Wii system menu and register each Wii remote using the red SYNC buttons, as explained in your Wii user manual.
|
||||
|
||||
----
|
||||
|
||||
**_I'm trying to use ??? feature and have put all the required files as described in the User Manual but it doesn't seem to work. What am I doing wrong ?_**
|
||||
|
||||
Make sure you disable the "Hide Known Extension" feature in your OS then verify again if the required files have the proper name and extension.
|
||||
|
||||
----
|
||||
|
||||
**_How can I play 32X games in this emulator ?_**
|
||||
|
||||
You can't. This program currently only emulates SG-1000, Master System, Game Gear, Genesis / Mega Drive & Sega/Mega CD hardware, including existing backward compatibility modes. Other systems might be added in the future but it's actually not high priority.
|
||||
|
||||
----
|
||||
|
||||
**_Hey, I have a very original idea. What about adding a cool feature like (insert anything) ? I think the emulator would be perfect with that feature._**
|
||||
|
||||
You can always make an enhancement request in the Issue page on this site. However, if something has not been implemented yet, it has very likely a good reason not to be. Some stuff need time to work on, while others are simply no interest to me. As a general rule, I prefer focusing myself on the emulation core rather than features only designed to make the emulator interface "cooler" or "easier" to use.
|
||||
|
||||
----
|
64
wiki/Getting Started.md
Normal file
64
wiki/Getting Started.md
Normal file
@ -0,0 +1,64 @@
|
||||
|
||||
## How to start the emulator ##
|
||||
|
||||
Gamecube and Wii applications usually come in the form of .dol files.
|
||||
|
||||
Once you have downloaded and decompressed the archive, you should get two versions of the emulator:
|
||||
|
||||
* genplus_cube.dol is the application running in Gamecube mode.
|
||||
|
||||
This can be loaded on a Game Cube by using various methods: see http://www.gc-linux.org/wiki/Getting_Started#Booting_Code for more details.
|
||||
|
||||
* genplus_wii.dol is the application running in Wii mode.
|
||||
|
||||
The easiest way to run the emulator on a Wii is to install the [Homebrew Channel](http://hbc.hackmii.com/). Once you are done, simply copy the /apps directory (included with this release) and its content to the root of your SD card or USB drive.
|
||||
|
||||
There are other ways to run dol files on the Wii like building a dedicated channel or using an alternate DOL loader. Feel free to visit http://www.wiibrew.org for additional information.
|
||||
|
||||
----
|
||||
## How to use the emulator ##
|
||||
|
||||
Genesis Plus GX supports Mega Drive / Genesis, Master System, Game Gear & SG-1000 ROM files and Sega/Mega CD image files. Supported ROM files must come in the form of RAW .bin, .ison .gen, .md, .smd, .mdx, .sms, .gg & .sg files. Compressed .zip files (for ROM files only) are also supported as long as they contain a single ROM file in one of the supported format.
|
||||
|
||||
|
||||
To play a game, you first need to load a ROM file from one of the supported devices: you can either load it from DVD, SD card or USB drive (Wii only).
|
||||
|
||||
|
||||
There is a limit of 1000 ROM files per directory so it's strongly advised to create subdirectories. Reducing the number of ROM files per directory also improves menu interface speed and usability.
|
||||
|
||||
|
||||
For Sega / Mega CD emulation, original BIOS files are needed for each console region: the emulator expects BIOS ROM files to be respectively named BIOS_CD_J.bin, BIOS_CD_U.bin and BIOS_CD_E.bin and placed in /genplus/bios/ directory on the default FAT device. It should not matter what BIOS versions you are using but it is recommended, for best compatibility, to use Model 1 BIOS ROM image files.
|
||||
|
||||
### from SD ###
|
||||
|
||||
The SD card should be formatted to FAT (FAT16 or FAT32). If not found, the emulator automatically creates a directory named “/genplus” at the root of your SD card, as well as subdirectories required by the emulator to store miscellaneous files (cheat, save & screenshot files).
|
||||
|
||||
|
||||
By default, the emulator will look for files in the sd:/genplus/roms directory but you can place them anywhere you want, the menu keeping trace of the last accessed directory for each device.
|
||||
|
||||
|
||||
### from USB (Wii only) ###
|
||||
|
||||
The USB drive should have at least one partition formatted to FAT (FAT16 or FAT32), other file systems (NTFS, EXT2, etc) are not supported. If no SD card is inserted when the emulator starts, it automatically creates a directory named “/genplus” at the root of your USB drive partition, as well as subdirectories required by the emulator to store miscellaneous files (cheat, save & screenshot files).
|
||||
|
||||
|
||||
By default, the emulator will look for files in the usb:/genplus/roms directory but you can place them anywhere you want, the menu keeping trace of the last accessed directory for each device and for each file types.
|
||||
|
||||
|
||||
To use an USB2 drive, you must have IOS58 installed (it should be automatically installed with System Menu 4.3 update). You should also load the emulator through the Homebrew Channel, using the provided meta.xml file and make sure Homebrew Channel is using IOS58 as default IOS. If not, you might need to reinstall Homebrew Channel after having installed IOS58.
|
||||
|
||||
|
||||
### from DVD ###
|
||||
|
||||
The DVD should be formatted using ISO9660/Joliet (refer to the user manual of your DVD Burning software for more details). The Game Cube Mini-DVD drive allows up to 1.35GB of data while the Wii DVD drive allows up to 4.7GB of data (simple-layer).
|
||||
|
||||
|
||||
By default, the emulator will look for files at the root of your DVD but you can place them anywhere you want, the menu keeping trace of the last accessed directory for each device and for each file types.
|
||||
|
||||
|
||||
To use DVD on a non-chipped Wii, you should load the emulator through the Homebrew Channel and use the provided meta.xml file, in order to allow full access to the DVD drive.
|
||||
|
||||
|
||||
|
||||
----
|
||||
*Please report to the included [User Manual](https://github.com/ekeeke/Genesis-Plus-GX/blob/master/gx/docs/README.pdf) for additional informations.*
|
Loading…
Reference in New Issue
Block a user