naomi: use boot rom id to configure eeprom

Make eeprom from scratch if it doesn't exist based on boot id info.
Override eeprom settings to make game happy
Change region to supported one
Get rid of per-rom region info in rom list
New option to configure Naomi games in free play
This commit is contained in:
Flyinghead 2022-02-18 17:35:20 +01:00
parent a92c2899c8
commit ecc03e3ebc
16 changed files with 347 additions and 385 deletions

View File

@ -36,6 +36,7 @@ Option<bool> ForceWindowsCE("Dreamcast.ForceWindowsCE");
Option<bool> AutoLoadState("Dreamcast.AutoLoadState");
Option<bool> AutoSaveState("Dreamcast.AutoSaveState");
Option<int> SavestateSlot("Dreamcast.SavestateSlot");
Option<bool> ForceFreePlay("ForceFreePlay", true);
// Sound

View File

@ -355,6 +355,7 @@ extern Option<bool> ForceWindowsCE;
extern Option<bool> AutoLoadState;
extern Option<bool> AutoSaveState;
extern Option<int> SavestateSlot;
extern Option<bool> ForceFreePlay;
// Sound

View File

@ -512,6 +512,9 @@ void Emulator::loadGame(const char *path, LoadProgress *progress)
naomi_cart_LoadBios(path);
}
mcfg_CreateDevices();
if (settings.platform.isNaomi())
// Must be done after the maple devices are created and EEPROM is accessible
naomi_cart_ConfigureEEPROM();
cheatManager.reset(settings.content.gameId);
if (cheatManager.isWidescreen())
{

View File

@ -410,18 +410,53 @@ void AWCartridge::AdvancePtr(u32 size)
dma_offset += size;
}
std::string AWCartridge::GetGameId()
struct AtomiswaveBootID
{
if (RomSize < 0x30 + 0x20)
return "(ROM too small)";
char boardName[16]; // SYSTEM_X_APP
char vendorName[32];
char gameTitle[32];
char year[4];
char month[2];
char day[2];
u32 _unkn0;
u32 _unkn1;
u32 _unkn2;
u32 gamePC;
u32 _unkn3;
u32 testPC;
};
dma_offset = 0x30;
u32 limit = 0x20;
char *name = (char *)GetDmaPtr(limit);
std::string game_id(name, limit);
while (!game_id.empty() && game_id.back() == ' ')
game_id.pop_back();
return game_id;
bool AWCartridge::GetBootId(RomBootID *bootId)
{
if (RomSize < sizeof(AtomiswaveBootID))
return false;
AtomiswaveBootID awBootId;
u8 *p = (u8 *)&awBootId;
u32 size = sizeof(AtomiswaveBootID);
dma_offset = 0;
recalc_dma_offset(EPR);
while (size > 0)
{
u32 chunkSize = size;
void *src = GetDmaPtr(chunkSize);
if (chunkSize == 0)
return false;
memcpy(p, src, chunkSize);
p += chunkSize;
size -= chunkSize;
AdvancePtr(chunkSize);
}
memset(bootId, 0, sizeof(RomBootID));
memcpy(bootId->boardName, awBootId.boardName, sizeof(bootId->boardName));
memcpy(bootId->vendorName, awBootId.vendorName, sizeof(bootId->vendorName));
memcpy(bootId->gameTitle[0], awBootId.gameTitle, sizeof(bootId->gameTitle[0]));
bootId->gamePC = awBootId.gamePC;
bootId->testPC = awBootId.testPC;
bootId->year = atoi(std::string(awBootId.year, awBootId.year + 4).c_str());
bootId->month = atoi(std::string(awBootId.month, awBootId.month + 2).c_str());
bootId->day = atoi(std::string(awBootId.day, awBootId.day + 2).c_str());
return true;
}
void AWCartridge::Serialize(Serializer& ser) const

View File

@ -25,7 +25,7 @@ public:
void *GetDmaPtr(u32 &size) override;
void AdvancePtr(u32 size) override;
std::string GetGameId() override;
bool GetBootId(RomBootID *bootId) override;
void SetKey(u32 key) override;
void Serialize(Serializer& ser) const override;

View File

@ -273,26 +273,21 @@ M4Cartridge::~M4Cartridge()
free(m_key_data);
}
std::string M4Cartridge::GetGameId()
bool M4Cartridge::GetBootId(RomBootID *bootId)
{
if (RomSize < 0x30 + 0x20)
return "(ROM too small)";
std::string game_id;
if (RomPtr[0] == 'N' && RomPtr[1] == 'A')
game_id = std::string((char *)(RomPtr + 0x30), 0x20);
else
if (RomSize < sizeof(RomBootID))
return false;
RomBootID *pBootId = (RomBootID *)RomPtr;
if (memcmp(pBootId->boardName, "NAOMI", 5))
{
rom_cur_address = 0;
enc_reset();
enc_fill();
game_id = std::string((char *)(buffer + 0x30), 0x20);
pBootId = (RomBootID *)buffer;
}
memcpy(bootId, pBootId, sizeof(RomBootID));
while (!game_id.empty() && game_id.back() == ' ')
game_id.pop_back();
return game_id;
return true;
}
void M4Cartridge::Serialize(Serializer& ser) const

View File

@ -44,9 +44,9 @@ public:
void* GetDmaPtr(u32 &size) override;
void AdvancePtr(u32 size) override;
std::string GetGameId() override;
void Serialize(Serializer& ser) const override;
void Deserialize(Deserializer& deser) override;
bool GetBootId(RomBootID *bootId) override;
void SetKey(u32 key) override { this->m4id = key; }
void SetKeyData(u8 *key_data) override { this->m_key_data = key_data; }

View File

@ -38,6 +38,7 @@
#include "oslib/oslib.h"
#include "serialize.h"
#include "card_reader.h"
#include "naomi_flashrom.h"
Cartridge *CurrentCartridge;
bool bios_loaded = false;
@ -188,12 +189,9 @@ void naomi_cart_LoadBios(const char *filename)
const char *bios = "naomi";
if (game->bios != nullptr)
bios = game->bios;
u32 region_flag = std::min((int)config::Region, (int)game->region_flag);
if (game->region_flag == REGION_EXPORT_ONLY)
region_flag = REGION_EXPORT;
if (!loadBios(bios, archive.get(), parent_archive.get(), region_flag))
if (!loadBios(bios, archive.get(), parent_archive.get(), config::Region))
{
WARN_LOG(NAOMI, "Warning: Region %d bios not found in %s", region_flag, bios);
WARN_LOG(NAOMI, "Warning: Region %d bios not found in %s", (int)config::Region, bios);
if (!loadBios(bios, archive.get(), parent_archive.get(), -1))
{
// If a specific BIOS is needed for this game, fail.
@ -206,7 +204,7 @@ void naomi_cart_LoadBios(const char *filename)
bios_loaded = true;
}
static void naomi_cart_LoadZip(const char *filename, LoadProgress *progress)
static void loadMameRom(const char *filename, LoadProgress *progress)
{
Game *game = FindGame(filename);
if (game == NULL)
@ -396,19 +394,8 @@ static void naomi_cart_LoadZip(const char *filename, LoadProgress *progress)
}
md5.getDigest(settings.network.md5.game);
}
strcpy(naomi_game_id, CurrentCartridge->GetGameId().c_str());
if (naomi_game_id[0] == '\0')
strcpy(naomi_game_id, game->name);
NOTICE_LOG(NAOMI, "NAOMI GAME ID [%s]", naomi_game_id);
if (CurrentCartridge->GetGameId() == "INITIAL D"
|| CurrentCartridge->GetGameId() == "INITIAL D Ver.2"
|| CurrentCartridge->GetGameId() == "INITIAL D Ver.3"
|| CurrentCartridge->GetGameId() == "INITIAL D CYCRAFT")
{
card_reader::initialDCardReader.init();
}
// Default game name if ROM boot id isn't found
strcpy(naomi_game_id, game->name);
} catch (...) {
delete CurrentCartridge;
@ -417,18 +404,8 @@ static void naomi_cart_LoadZip(const char *filename, LoadProgress *progress)
}
}
void naomi_cart_LoadRom(const char* file, LoadProgress *progress)
static void loadDecryptedRom(const char* file, LoadProgress *progress)
{
naomi_cart_Close();
std::string extension = get_file_extension(file);
if (extension == "zip" || extension == "7z")
{
naomi_cart_LoadZip(file, progress);
return;
}
// Try to load BIOS from naomi.zip
if (!loadBios("naomi", NULL, NULL, config::Region))
{
@ -446,6 +423,7 @@ void naomi_cart_LoadRom(const char* file, LoadProgress *progress)
std::vector<u32> fsize;
u32 romSize = 0;
std::string extension = get_file_extension(file);
if (extension == "lst")
{
// LST file
@ -576,8 +554,48 @@ void naomi_cart_LoadRom(const char* file, LoadProgress *progress)
DEBUG_LOG(NAOMI, "Legacy ROM loaded successfully");
CurrentCartridge = new DecryptedCartridge(romBase, romSize);
strcpy(naomi_game_id, CurrentCartridge->GetGameId().c_str());
NOTICE_LOG(NAOMI, "NAOMI GAME ID [%s]", naomi_game_id);
}
void naomi_cart_LoadRom(const char* file, LoadProgress *progress)
{
naomi_cart_Close();
std::string extension = get_file_extension(file);
if (extension == "zip" || extension == "7z")
loadMameRom(file, progress);
else
loadDecryptedRom(file, progress);
RomBootID bootId;
if (CurrentCartridge->GetBootId(&bootId))
{
std::string gameId = trim_trailing_ws(std::string(bootId.gameTitle[0], &bootId.gameTitle[0][32]));
strcpy(naomi_game_id, gameId.c_str());
NOTICE_LOG(NAOMI, "NAOMI GAME ID [%s] region %x players %x vertical %x", naomi_game_id, (u8)bootId.country, bootId.cabinet, bootId.vertical);
if (gameId == "INITIAL D"
|| gameId == "INITIAL D Ver.2"
|| gameId == "INITIAL D Ver.3"
|| gameId == "INITIAL D CYCRAFT")
{
card_reader::initialDCardReader.init();
}
}
else
NOTICE_LOG(NAOMI, "NAOMI GAME ID [%s]", naomi_game_id);
}
void naomi_cart_ConfigureEEPROM()
{
if (!settings.platform.isNaomi())
return;
RomBootID bootId;
if (CurrentCartridge->GetBootId(&bootId))
configure_naomi_eeprom(&bootId);
else
WARN_LOG(NAOMI, "Can't read ROM boot ID");
}
void naomi_cart_Close()
@ -649,19 +667,25 @@ void* Cartridge::GetPtr(u32 offset, u32& size)
return &RomPtr[offset];
}
std::string Cartridge::GetGameId() {
if (RomSize < 0x30 + 0x20)
return "(ROM too small)";
std::string game_id((char *)RomPtr + 0x30, 0x20);
if (game_id == "AWNAOMI " && RomSize >= 0xFF50)
bool NaomiCartridge::GetBootId(RomBootID *bootId)
{
if (RomSize < sizeof(RomBootID))
return false;
u8 *p = (u8 *)bootId;
u32 size = sizeof(RomBootID);
DmaOffset = 0;
while (size > 0)
{
game_id = std::string((char *)RomPtr + 0xFF30, 0x20);
u32 chunkSize = size;
void *src = GetDmaPtr(chunkSize);
if (chunkSize == 0)
return false;
memcpy(p, src, chunkSize);
p += chunkSize;
size -= chunkSize;
AdvancePtr(chunkSize);
}
while (!game_id.empty() && game_id.back() == ' ')
game_id.pop_back();
return game_id;
return true;
}
void* NaomiCartridge::GetDmaPtr(u32& size)
@ -694,7 +718,7 @@ u32 NaomiCartridge::ReadMem(u32 address, u32 size)
case 0x48: // 5f7048: DIMM PARAMETERH
DEBUG_LOG(NAOMI, "DIMM PARAMETERH read<%d>", size);
return reg_dimm_parameterh;
case 0x04C: // 5f704c: DIMM STATUS
case 0x4c: // 5f704c: DIMM STATUS
DEBUG_LOG(NAOMI, "DIMM STATUS read<%d>: %x", size, reg_dimm_status);
return reg_dimm_status;
@ -965,16 +989,21 @@ u16 M2Cartridge::ReadCipheredData(u32 offset)
}
std::string M2Cartridge::GetGameId()
bool M2Cartridge::GetBootId(RomBootID *bootId)
{
std::string game_id = NaomiCartridge::GetGameId();
if ((game_id.size() < 2 || ((u8)game_id[0] == 0xff && (u8)game_id[1] == 0xff)) && RomSize >= 0x800050)
if (RomSize < sizeof(RomBootID))
return false;
RomBootID *pBootId = (RomBootID *)RomPtr;
if ((pBootId->gameTitle[0][0] == '\0'
|| ((u8)pBootId->gameTitle[0][0] == 0xff && (u8)pBootId->gameTitle[0][1] == 0xff)))
{
game_id = std::string((char *)RomPtr + 0x800030, 0x20);
while (!game_id.empty() && game_id.back() == ' ')
game_id.pop_back();
if (RomSize < 0x800000 + sizeof(RomBootID))
return false;
pBootId = (RomBootID *)(RomPtr + 0x800000);
}
return game_id;
memcpy(bootId, pBootId, sizeof(RomBootID));
return true;
}
void M2Cartridge::Serialize(Serializer& ser) const {

View File

@ -5,6 +5,44 @@
#include "types.h"
#include "emulator.h"
struct RomBootID
{
char boardName[16];
char vendorName[32];
char gameTitle[8][32];
u16 year;
u8 month;
u8 day;
char gameID[4]; // copied to eeprom[3]
u16 romMode; // != 0 => offset | 0x20000000
u16 romWaitFlag; // != 0 => configure G1 BUS
u32 romWait[8]; // init values for SB_G1RRC, SB_G1RWC, SB_G1FRC, SB_G1FWC, SB_G1CRC, SB_G1CWC, SB_G1GDRC, SB_G1GDWC
u16 romID[22][3]; // M2/M4-type ROM checksums
u8 coinFlag[8][16];// Init instructions for EEPROM for each region
// b0: if 0, use default BIOS settings, if 1, use settings below
// b1: bit 0: vertical monitor, bit 1: forces attract mode sound off
// b2: coin chute type: 0 common, 1; individual
// b3: default coin setting (1-28)
// b4: coin 1 rate (for manual coin setting)
// b5: coin 2 rate (for manual coin setting)
// b6: credit rate (for manual coin setting)
// b7: bonus credit rate (for manual coin setting)
// b8-15: coin sequences
char sequence[8][32];// text ("CREDIT TO START", "CREDIT TO CONTINUE", ...)
u32 gameLoad[8][3]; // regions to copy from ROM to system RAM: offset, RAM address, length (offset=0xffffffff means end of list)
u32 testLoad[8][3]; // same for game test mode
u32 gamePC; // main game entry point
u32 testPC; // test mode entry point
u8 country; // supported regions bitmap
u8 cabinet; // supported # of players bitmap: b0 1 player, b1 2 players, ...
u8 resolution; // 0: 31kHz, 1: 15kHz
u8 vertical; // b0: horizontal mode, b1: vertical mode
u8 serialID; // if 1, check the ROM/DIMM board serial# eeprom
u8 _res[211]; // _res[210]: if 0xFF then the header is unencrypted. if not then the header is encrypted starting at offset 0x010.
// Note: this structure is copied to system RAM by the BIOS at location 0c01f400
};
class Cartridge
{
public:
@ -23,11 +61,11 @@ public:
virtual void* GetPtr(u32 offset, u32& size);
virtual void* GetDmaPtr(u32 &size) = 0;
virtual void AdvancePtr(u32 size) = 0;
virtual std::string GetGameId();
virtual void Serialize(Serializer& ser) const {}
virtual void Deserialize(Deserializer& deser) {}
virtual void SetKey(u32 key) { }
virtual void SetKeyData(u8 *key_data) { }
virtual bool GetBootId(RomBootID *bootId) = 0;
protected:
u8* RomPtr;
@ -45,6 +83,7 @@ public:
void AdvancePtr(u32 size) override {}
void Serialize(Serializer& ser) const override;
void Deserialize(Deserializer& deser) override;
bool GetBootId(RomBootID *bootId) override;
void SetKey(u32 key) override { this->key = key; }
@ -75,7 +114,7 @@ public:
void Serialize(Serializer& ser) const override;
void Deserialize(Deserializer& deser) override;
void* GetDmaPtr(u32& size) override;
std::string GetGameId() override;
bool GetBootId(RomBootID *bootId) override;
private:
u8 naomi_cart_ram[64 * 1024];
@ -91,6 +130,7 @@ void naomi_cart_LoadRom(const char* file, LoadProgress *progress);
void naomi_cart_Close();
int naomi_cart_GetPlatform(const char *path);
void naomi_cart_LoadBios(const char *filename);
void naomi_cart_ConfigureEEPROM();
extern char naomi_game_id[];
extern u8 *naomi_default_eeprom;

View File

@ -22,6 +22,7 @@
#include "hw/flashrom/flashrom.h"
#include "hw/holly/sb_mem.h"
#include "hw/maple/maple_devs.h"
#include "cfg/option.h"
extern WritableChip *sys_nvmem;
@ -88,8 +89,7 @@ void write_naomi_flash(u32 addr, u8 value)
// eeprom layout:
// Offset Size
// 0 2 CRC of bytes [2,17]
// 2 1 size of record (16, sometimes 1, ignored)
// 3 15 data
// 2 16 data
// 18 18 same record repeated
// 36 2 CRC of bytes [44,44+size[
// 38 1 record size
@ -98,10 +98,27 @@ void write_naomi_flash(u32 addr, u8 value)
// 44 size*2 Record data, repeated twice
//
// The first record contains naomi bios settings, and the second one game-specific settings
// common settings:
// 2 b0 vertical screen
// b4 attract mode sound
// 3 Game serial ID (4 chars)
// 7 unknown (9 or x18)
// 8 b0: coin chute type (0 common, 1 individual)
// b4-5: cabinet type (0: 1P, 10: 2P, 20: 2P, 30: 4P)
// 9 coin setting (-1), 27 is manual
// 10 coin to credit
// 11 chute 1 multiplier
// 12 chute 2 multiplier
// 13 bonus adder (coins)
// 14 coin sequence: b0-3 seq 1, b4-7 seq 2
// 15 coin sequence: b0-3 seq 3, b4-7 seq 4
// 16 coin sequence: b0-3 seq 5, b4-7 seq 6
// 17 coin sequence: b0-3 seq 7, b4-7 seq 8
//
void write_naomi_eeprom(u32 offset, u8 value)
{
if (offset >= 3 && offset < 20)
if (offset >= 2 && offset < 18)
{
EEPROM[offset] = value;
EEPROM[offset + 18] = value;
@ -121,3 +138,129 @@ void write_naomi_eeprom(u32 offset, u8 value)
else
WARN_LOG(NAOMI, "EEPROM record doesn't exist or is too short");
}
static u8 readEeprom(u32 offset)
{
return EEPROM[offset & 127];
}
static bool initEeprom(const RomBootID *bootId)
{
if (!memcmp(bootId->gameID, &EEPROM[3], sizeof(bootId->gameID)))
return false;
NOTICE_LOG(NAOMI, "Initializing Naomi EEPROM for game %.32s", bootId->gameTitle[0]);
for (int i = 0; i < 4; i++)
write_naomi_eeprom(3 + i, bootId->gameID[i]);
write_naomi_eeprom(7, 9); // FIXME 9 or 0x18?
if (bootId->cabinet & 8)
write_naomi_eeprom(8, 30);
else if (bootId->cabinet & 4)
write_naomi_eeprom(8, 20);
else if (bootId->cabinet & 2)
write_naomi_eeprom(8, 10);
else
write_naomi_eeprom(8, 0);
if (bootId->coinFlag[0][1] == 1)
{
// ROM-specific defaults
write_naomi_eeprom(2, bootId->coinFlag[0][3] | (((bootId->coinFlag[0][3] & 2) ^ 2) << 3));
write_naomi_eeprom(9, bootId->coinFlag[0][3] - 1);
write_naomi_eeprom(10, bootId->coinFlag[0][6]);
write_naomi_eeprom(11, bootId->coinFlag[0][4]);
write_naomi_eeprom(12, bootId->coinFlag[0][5]);
write_naomi_eeprom(13, bootId->coinFlag[0][7]);
write_naomi_eeprom(14, bootId->coinFlag[0][8] | (bootId->coinFlag[0][9] << 4));
write_naomi_eeprom(15, bootId->coinFlag[0][10] | (bootId->coinFlag[0][11] << 4));
write_naomi_eeprom(16, bootId->coinFlag[0][12] | (bootId->coinFlag[0][13] << 4));
write_naomi_eeprom(17, bootId->coinFlag[0][14] | (bootId->coinFlag[0][15] << 4));
}
else
{
// BIOS defaults
write_naomi_eeprom(2, (bootId->vertical & 2) ? 0x11 : 0x10);
write_naomi_eeprom(9, 0);
write_naomi_eeprom(10, 1);
write_naomi_eeprom(11, 1);
write_naomi_eeprom(12, 1);
write_naomi_eeprom(13, 0);
write_naomi_eeprom(14, 0x11);
write_naomi_eeprom(15, 0x11);
write_naomi_eeprom(16, 0x11);
write_naomi_eeprom(17, 0x11);
}
return true;
}
void configure_naomi_eeprom(const RomBootID *bootId)
{
initEeprom(bootId);
// Horizontal / vertical screen orientation
if (bootId->vertical & 2)
{
NOTICE_LOG(NAOMI, "EEPROM: vertical monitor orientation");
write_naomi_eeprom(2, readEeprom(2) | 1);
config::Rotate90.override(true);
}
else if (bootId->vertical & 1)
{
NOTICE_LOG(NAOMI, "EEPROM: horizontal monitor orientation");
write_naomi_eeprom(2, readEeprom(2) & ~1);
}
// Number of players
if (bootId->cabinet != 0)
{
int nPlayers = readEeprom(8) >> 4; // 0 to 3
if (!(bootId->cabinet & (1 << nPlayers)))
{
u8 coinChute = readEeprom(8) & 1;
if (bootId->cabinet & 8)
{
NOTICE_LOG(NAOMI, "EEPROM: 4-player cabinet");
write_naomi_eeprom(8, 0x30 | coinChute);
}
else if (bootId->cabinet & 4)
{
NOTICE_LOG(NAOMI, "EEPROM: 3-player cabinet");
write_naomi_eeprom(8, 0x20 | coinChute);
}
else if (bootId->cabinet & 2)
{
NOTICE_LOG(NAOMI, "EEPROM: 2-player cabinet");
write_naomi_eeprom(8, 0x10 | coinChute);
}
else if (bootId->cabinet & 1)
{
NOTICE_LOG(NAOMI, "EEPROM: 1-player cabinet");
write_naomi_eeprom(8, 0x00 | coinChute);
}
}
}
// Region
if (bootId->country != 0 && (bootId->country & (1 << config::Region)) == 0)
{
if (bootId->country & 2)
{
NOTICE_LOG(NAOMI, "Forcing region USA");
config::Region.override(1);
}
else if (bootId->country & 4)
{
NOTICE_LOG(NAOMI, "Forcing region Export");
config::Region.override(2);
}
else if (bootId->country & 1)
{
NOTICE_LOG(NAOMI, "Forcing region Japan");
config::Region.override(0);
}
else if (bootId->country & 8)
{
NOTICE_LOG(NAOMI, "Forcing region Korea");
config::Region.override(3);
}
naomi_cart_LoadBios(settings.content.path.c_str());
}
// Coin settings
if (config::ForceFreePlay)
write_naomi_eeprom(9, 27 - 1);
}

View File

@ -20,6 +20,8 @@
*/
#pragma once
#include "types.h"
#include "naomi_cart.h"
void write_naomi_flash(u32 addr, u8 value);
void write_naomi_eeprom(u32 offset, u8 value);
void configure_naomi_eeprom(const RomBootID *bootId);

File diff suppressed because it is too large Load Diff

View File

@ -44,15 +44,6 @@ enum CartridgeType {
GD
};
enum RegionType {
REGION_JAPAN = 0,
REGION_USA = 1,
REGION_EXPORT = 2,
REGION_KOREA = 3,
REGION_AUSTRALIA = 4,
REGION_EXPORT_ONLY = 5
};
enum RotationType {
ROT0 = 0,
ROT270 = 3,
@ -85,7 +76,6 @@ struct Game
u32 key;
const char *bios;
CartridgeType cart_type;
RegionType region_flag;
RotationType rotation_flag;
struct
{

View File

@ -1378,6 +1378,7 @@ static void gui_display_settings()
ImGui::SameLine();
OptionCheckbox("Save", config::AutoSaveState,
"Save the state of the game when stopping");
OptionCheckbox("Naomi Free Play", config::ForceFreePlay, "Configure Naomi games in Free Play mode.");
ImGui::PopStyleVar();
ImGui::EndTabItem();

View File

@ -212,6 +212,20 @@ struct retro_core_option_v2_definition option_defs_us[] = {
},
"disabled",
},
{
CORE_OPTION_NAME "_force_freeplay",
"Set NAOMI Games to Free Play",
NULL,
"Modify to coin settings of the game to free play.",
NULL,
"system",
{
{ "disabled", NULL },
{ "enabled", NULL },
{ NULL, NULL },
},
"enabled",
},
{
CORE_OPTION_NAME "_internal_resolution",
"Internal Resolution",

View File

@ -37,6 +37,7 @@ Option<bool> ForceWindowsCE(CORE_OPTION_NAME "_force_wince");
Option<bool> AutoLoadState("");
Option<bool> AutoSaveState("");
Option<int> SavestateSlot("");
Option<bool> ForceFreePlay(CORE_OPTION_NAME "_force_freeplay", true);
// Sound