naomi: f355 multiboard support WIP

This commit is contained in:
Flyinghead 2023-01-11 22:31:36 +01:00
parent 4f0d3a958d
commit 7e4572a86a
33 changed files with 1291 additions and 478 deletions

View File

@ -797,6 +797,8 @@ target_sources(${PROJECT_NAME} PRIVATE
core/hw/naomi/m1cartridge.h
core/hw/naomi/m4cartridge.cpp
core/hw/naomi/m4cartridge.h
core/hw/naomi/multiboard.h
core/hw/naomi/multiboard.cpp
core/hw/naomi/naomi_cart.cpp
core/hw/naomi/naomi_cart.h
core/hw/naomi/naomi_flashrom.cpp

View File

@ -1,120 +1,4 @@
/*
reicast build options
Reicast can support a lot of stuff, and this is an attempt
to organize the build time options
Option categories
BUILD_* - BUILD_COMPILER, etc...
definitions about the build machine
HOST_*
definitions about the host machine
FEAT_*
definitions about the features that this build targets
This is higly related to HOST_*, but it's for options that might
or might not be avaiable depending on the target host, or that
features that are irrelevant of the host
Eg, Alsa, Pulse Audio and OSS might sense as HOST dedinitions
but it usually makes more sense to detect them as runtime. In
that context, HOST_ALSA makes no sense because the host might
or might not have alsa installed/ running
MMU makes no sense as a HOST definition at all, so it should
be FEAT_HAS_MMU
TARGET_*
A preconfigured default. Eg TARGET_WIN86.
Naming of options, option values, and how to use them
for options that makes sense to have a list of values
{CATEGORY}_{OPTION}
{OPTION}_{VALUE}
eg.
BUILD_COMPILER == COMPILER_GCC, HOST_CPU != CPU_X64, ...
for options that are boolean
{CATEGORY}_IS_{OPTION} or {CATEGORY}_HAS_{OPTION}
Evaluates to 0 or 1
If an configuration cannot be neatly split into a set of
of orthogonal options, then it makes sense to break things
to "sets" or have a hierarchy of options.
Example
-------
In the beggining it made sense to have an audio backend
per operating system. It made sense to have it depend
on HOST_OS and seleect DirectSound or alsa.
// no option needed
Then, as android was introduced, which also uses OS_LINUX
atm, the audio could have been made an option. It could be
a HOST_* option, or FEAT_* one. I'd prefer FEAT_*.
FEAT_* makes more sense as future wise we might want
to support multiple backends.
FEAT_AUDIO_BACKEND
AUDIO_BACKEND_NONE
AUDIO_BACKEND_DS
AUDIO_BACKEND_ALSA
AUDIO_BACKEND_ANDROID
Used like
#if FEAT_AUDIO_BACKEND == AUDIO_BACKEND_DS ....
At some point, we might have multiple audio backends that
can be compiled in and autodetected/selected at runtime.
In that case, it might make sense to have the options like
FEAT_HAS_ALSA
FEAT_HAS_DIRECTSOUND
FEAT_HAS_ANDROID_AUDIO
or
FEAT_HAS_AUDIO_ALSA
FEAT_HAS_AUDIO_DS
FEAT_HAS_AUDIO_ANDROID
The none option might or might not make sense. In this
case it can be removed, as it should always be avaiable.
Guidelines
----------
General rule of thumb, don't overcomplicate things. Start
with a simple option, and then make it more complicated
as new uses apply (see the example above)
Don't use too long names, don't use too cryptic names.
Most team developers should be able to understand or
figure out most of the acronyms used.
Try to be consistent on the acronyms across all definitions
Code shouldn't depend on build level options whenever possible
Generally, the file should compile even if the option/module is
disabled. This makes makefiles etc much easier to write
TARGET_* options should generally only be used in this file
The current source is *not* good example of these guidelines
We'll try to be smart and figure out some options/defaults on this file
but this shouldn't get too complicated
*/
#pragma once
//#define STRICT_MODE
#ifndef STRICT_MODE
#define FAST_MMU
@ -226,6 +110,10 @@
#define USE_GGPO
#endif
#if !defined(__ANDROID__) && !defined(TARGET_IPHONE) && !defined(TARGET_UWP) && !defined(__SWITCH__) && !defined(LIBRETRO)
#define NAOMI_MULTIBOARD
#endif
// TARGET PLATFORM
#define RAM_SIZE_MAX (32*1024*1024)

View File

@ -146,6 +146,7 @@ Option<bool> GGPOChat("GGPOChat", true, "network");
Option<bool> GGPOChatTimeoutToggle("GGPOChatTimeoutToggle", true, "network");
Option<int> GGPOChatTimeout("GGPOChatTimeout", 10, "network");
Option<bool> NetworkOutput("NetworkOutput", false, "network");
Option<int> MultiboardSlaves("MultiboardSlaves", 1, "network");
#ifdef SUPPORT_DISPMANX
Option<bool> DispmanxMaintainAspect("maintain_aspect", true, "dispmanx");

View File

@ -507,6 +507,7 @@ extern Option<bool> GGPOChat;
extern Option<bool> GGPOChatTimeoutToggle;
extern Option<int> GGPOChatTimeout;
extern Option<bool> NetworkOutput;
extern Option<int> MultiboardSlaves;
#ifdef SUPPORT_DISPMANX
extern Option<bool> DispmanxMaintainAspect;

View File

@ -345,6 +345,11 @@ static void loadSpecialSettings()
INFO_LOG(BOOT, "Enabling specific JVS setup for game %s", naomi_game_id);
settings.input.JammaSetup = JVS::_18Wheeler;
}
else if (!strcmp("F355 CHALLENGE JAPAN", naomi_game_id))
{
INFO_LOG(BOOT, "Enabling specific JVS setup for game %s", naomi_game_id);
settings.input.JammaSetup = JVS::F355;
}
else if (!strcmp("INU NO OSANPO", naomi_game_id)) // Dog Walking
{
INFO_LOG(BOOT, "Enabling specific JVS setup for game %s", naomi_game_id);
@ -458,6 +463,10 @@ void Emulator::init()
int getGamePlatform(const char *path)
{
if (settings.naomi.slave)
// Multiboard slave
return DC_PLATFORM_NAOMI;
if (path == NULL)
// Dreamcast BIOS
return DC_PLATFORM_DREAMCAST;
@ -547,13 +556,16 @@ void Emulator::loadGame(const char *path, LoadProgress *progress)
// Reload the BIOS in case a game-specific region is set
naomi_cart_LoadBios(path);
}
mcfg_DestroyDevices();
mcfg_CreateDevices();
if (settings.platform.isNaomi()) {
// Must be done after the maple devices are created and EEPROM is accessible
naomi_cart_ConfigureEEPROM();
// and reload settings so that eeprom-based settings can be overridden
loadGameSpecificSettings();
if (!settings.naomi.slave)
{
mcfg_DestroyDevices();
mcfg_CreateDevices();
if (settings.platform.isNaomi()) {
// Must be done after the maple devices are created and EEPROM is accessible
naomi_cart_ConfigureEEPROM();
// and reload settings so that eeprom-based settings can be overridden
loadGameSpecificSettings();
}
}
cheatManager.reset(settings.content.gameId);
if (cheatManager.isWidescreen())
@ -567,7 +579,7 @@ void Emulator::loadGame(const char *path, LoadProgress *progress)
{
if (config::GGPOEnable)
dc_loadstate(-1);
else if (config::AutoLoadState && !NaomiNetworkSupported())
else if (config::AutoLoadState && !NaomiNetworkSupported() && !settings.naomi.multiboard)
dc_loadstate(config::SavestateSlot);
}
EventManager::event(Event::Start);
@ -625,9 +637,13 @@ void Emulator::unloadGame()
stop();
if (state == Loaded || state == Error)
{
if (state == Loaded && config::AutoSaveState && !settings.content.path.empty())
if (state == Loaded && config::AutoSaveState && !settings.content.path.empty() && !settings.naomi.multiboard)
dc_savestate(config::SavestateSlot);
dc_reset(true);
try {
dc_reset(true);
} catch (const FlycastException& e) {
ERROR_LOG(COMMON, "%s", e.what());
}
config::Settings::instance().reset();
config::Settings::instance().load(false);
@ -656,7 +672,8 @@ void Emulator::term()
}
}
void Emulator::stop() {
void Emulator::stop()
{
if (state != Running)
return;
state = Loaded;
@ -780,7 +797,8 @@ void EventManager::registerEvent(Event event, Callback callback, void *param)
callbacks.insert({ event, { std::make_pair(callback, param) } });
}
void EventManager::unregisterEvent(Event event, Callback callback, void *param) {
void EventManager::unregisterEvent(Event event, Callback callback, void *param)
{
auto it = callbacks.find(event);
if (it == callbacks.end())
return;
@ -792,7 +810,8 @@ void EventManager::unregisterEvent(Event event, Callback callback, void *param)
it->second.erase(it2);
}
void EventManager::broadcastEvent(Event event) {
void EventManager::broadcastEvent(Event event)
{
auto it = callbacks.find(event);
if (it == callbacks.end())
return;

View File

@ -187,10 +187,10 @@ static void fixUpDCFlash()
static bool nvmem_load()
{
bool rc;
bool rc = true;
if (settings.platform.isConsole())
rc = sys_nvmem->Load(getRomPrefix(), "%nvmem.bin", "nvram");
else
else if (!settings.naomi.slave)
rc = sys_nvmem->Load(hostfs::getArcadeFlashPath() + ".nvmem");
if (!rc)
INFO_LOG(FLASHROM, "flash/nvmem is missing, will create new file...");
@ -228,6 +228,8 @@ bool LoadRomFiles()
void SaveRomFiles()
{
if (settings.naomi.slave)
return;
if (settings.platform.isConsole())
sys_nvmem->Save(getRomPrefix(), "nvmem.bin", "nvmem");
else
@ -369,10 +371,7 @@ T DYNACALL ReadMem_area0(u32 paddr)
default:
// G2 Ext area
if (System == DC_PLATFORM_NAOMI || System == DC_PLATFORM_NAOMI2)
{
INFO_LOG(MEMORY, "Read<%d> from G2 Ext area not implemented @ %08x", sz, addr);
return (T)0;
}
return (T)g2ext_readMem(addr, sz);
else if (config::EmulateBBA)
return (T)bba_ReadMem(addr, sz);
else
@ -485,7 +484,7 @@ void DYNACALL WriteMem_area0(u32 paddr, T data)
default:
// G2 Ext area
if (System == DC_PLATFORM_NAOMI || System == DC_PLATFORM_NAOMI2)
INFO_LOG(MEMORY, "Write<%d> to G2 Ext area not implemented @ %08x: %x", sz, addr, (u32)data);
g2ext_writeMem(addr, data, sz);
else if (config::EmulateBBA)
bba_WriteMem(addr, data, sz);
return;

View File

@ -457,19 +457,73 @@ protected:
}
};
class jvs_837_13844_racing : public jvs_837_13844_motor_board
{
public:
jvs_837_13844_racing(u8 node_id, maple_naomi_jamma *parent, int first_player = 0)
: jvs_837_13844_motor_board(node_id, parent, first_player)
{
}
protected:
u8 process(u8 in) override
{
in = ~in;
// E0: stop motor
// E3: roll right
// EB: roll left
// Dn: set wheel high-order?
// Cn: set wheel low-order?
// 18 wheeler: ff, fe, 3f, 49, 67
// d8, c0, e0, d0
// 4b, 4a, 9f, 4b, 4d, 4e, 4f, 45, 45, 4f, a3, 4d, ..., 9f, 4c,
u8 out = 0;
switch (in)
{
case 0xf0:
testMode = true;
break;
case 0xff:
testMode = false;
break;
case 0xf1:
out = 0x10;
break;
default:
break;
}
if (testMode)
out = in;
// reverse
out = (out & 0xF0) >> 4 | (out & 0x0F) << 4;
out = (out & 0xCC) >> 2 | (out & 0x33) << 2;
out = (out & 0xAA) >> 1 | (out & 0x55) << 1;
return out;
}
private:
bool testMode = false; // TODO serialize
};
// 18 Wheeler: fake the drive board and limit the wheel analog value
class jvs_837_13844_18wheeler : public jvs_837_13844_motor_board
class jvs_837_13844_18wheeler : public jvs_837_13844_racing
{
public:
jvs_837_13844_18wheeler(u8 node_id, maple_naomi_jamma *parent, int first_player = 0)
: jvs_837_13844_motor_board(node_id, parent, first_player)
: jvs_837_13844_racing(node_id, parent, first_player)
{
}
protected:
void read_digital_in(const u32 *buttons, u16 *v) override
{
jvs_837_13844_motor_board::read_digital_in(buttons, v);
jvs_837_13844_racing::read_digital_in(buttons, v);
if (buttons[0] & NAOMI_BTN2_KEY)
{
gear = -1;
@ -507,43 +561,14 @@ protected:
u16 read_analog_axis(int player_num, int player_axis, bool inverted) override
{
u16 v = jvs_837_13844_motor_board::read_analog_axis(player_num, player_axis, inverted);
u16 v = jvs_837_13844_racing::read_analog_axis(player_num, player_axis, inverted);
if (player_axis == 0)
return std::min<u16>(0xefff, std::max<u16>(0x1000, v));
else
return v;
}
u8 process(u8 in) override
{
in = ~in;
switch (in)
{
case 0xf0:
testMode = true;
break;
case 0xff:
testMode = false;
break;
default:
break;
}
u8 out = 0;
if (testMode)
out = in;
// reverse
out = (out & 0xF0) >> 4 | (out & 0x0F) << 4;
out = (out & 0xCC) >> 2 | (out & 0x33) << 2;
out = (out & 0xAA) >> 1 | (out & 0x55) << 1;
return out;
}
private:
bool testMode = false; // TODO serialize
int gear = 0; // 0: low, 1: high, -1: reverse
bool transitionWait = false;
};
@ -757,72 +782,78 @@ private:
maple_naomi_jamma::maple_naomi_jamma()
{
switch (settings.input.JammaSetup)
if (!settings.naomi.slave)
{
case JVS::Default:
default:
io_boards.push_back(std::unique_ptr<jvs_837_13551>(new jvs_837_13551(1, this)));
break;
case JVS::FourPlayers:
io_boards.push_back(std::unique_ptr<jvs_837_13551_4P>(new jvs_837_13551_4P(1, this)));
break;
case JVS::RotaryEncoders:
io_boards.push_back(std::unique_ptr<jvs_837_13938>(new jvs_837_13938(1, this)));
io_boards.push_back(std::unique_ptr<jvs_837_13551>(new jvs_837_13551(2, this)));
break;
case JVS::OutTrigger:
io_boards.push_back(std::unique_ptr<jvs_837_13938>(new jvs_837_13938(1, this)));
io_boards.push_back(std::unique_ptr<jvs_837_13551_noanalog>(new jvs_837_13551_noanalog(2, this)));
break;
case JVS::SegaMarineFishing:
io_boards.push_back(std::unique_ptr<jvs_837_13844>(new jvs_837_13844(1, this)));
break;
case JVS::DualIOBoards4P:
if (!strcmp(naomi_game_id, "VIRTUA ATHLETE"))
{
// reverse the board order so that P1 is P1
io_boards.push_back(std::unique_ptr<jvs_837_13551>(new jvs_837_13551(1, this, 2)));
io_boards.push_back(std::unique_ptr<jvs_837_13551>(new jvs_837_13551(2, this, 0)));
}
else
switch (settings.input.JammaSetup)
{
case JVS::Default:
default:
io_boards.push_back(std::unique_ptr<jvs_837_13551>(new jvs_837_13551(1, this)));
io_boards.push_back(std::unique_ptr<jvs_837_13551>(new jvs_837_13551(2, this, 2)));
break;
case JVS::FourPlayers:
io_boards.push_back(std::unique_ptr<jvs_837_13551_4P>(new jvs_837_13551_4P(1, this)));
break;
case JVS::RotaryEncoders:
io_boards.push_back(std::unique_ptr<jvs_837_13938>(new jvs_837_13938(1, this)));
io_boards.push_back(std::unique_ptr<jvs_837_13551>(new jvs_837_13551(2, this)));
break;
case JVS::OutTrigger:
io_boards.push_back(std::unique_ptr<jvs_837_13938>(new jvs_837_13938(1, this)));
io_boards.push_back(std::unique_ptr<jvs_837_13551_noanalog>(new jvs_837_13551_noanalog(2, this)));
break;
case JVS::SegaMarineFishing:
io_boards.push_back(std::unique_ptr<jvs_837_13844>(new jvs_837_13844(1, this)));
break;
case JVS::DualIOBoards4P:
if (!strcmp(naomi_game_id, "VIRTUA ATHLETE"))
{
// reverse the board order so that P1 is P1
io_boards.push_back(std::unique_ptr<jvs_837_13551>(new jvs_837_13551(1, this, 2)));
io_boards.push_back(std::unique_ptr<jvs_837_13551>(new jvs_837_13551(2, this, 0)));
}
else
{
io_boards.push_back(std::unique_ptr<jvs_837_13551>(new jvs_837_13551(1, this)));
io_boards.push_back(std::unique_ptr<jvs_837_13551>(new jvs_837_13551(2, this, 2)));
}
break;
case JVS::LightGun:
io_boards.push_back(std::unique_ptr<jvs_namco_jyu>(new jvs_namco_jyu(1, this)));
break;
case JVS::LightGunAsAnalog:
// Regular board sending lightgun coords as axis 0/1
io_boards.push_back(std::unique_ptr<jvs_837_13551>(new jvs_837_13551(1, this)));
io_boards.back()->lightgun_as_analog = true;
break;
case JVS::Mazan:
io_boards.push_back(std::unique_ptr<jvs_namco_fcb>(new jvs_namco_fcb(1, this)));
io_boards.push_back(std::unique_ptr<jvs_namco_fcb>(new jvs_namco_fcb(2, this)));
break;
case JVS::GunSurvivor:
io_boards.push_back(std::unique_ptr<jvs_namco_fca>(new jvs_namco_fca(1, this)));
break;
case JVS::DogWalking:
io_boards.push_back(std::unique_ptr<jvs_837_13844_encoders>(new jvs_837_13844_encoders(1, this)));
break;
case JVS::TouchDeUno:
io_boards.push_back(std::unique_ptr<jvs_837_13844_touch>(new jvs_837_13844_touch(1, this)));
break;
case JVS::WorldKicks:
io_boards.push_back(std::unique_ptr<jvs_namco_v226>(new jvs_namco_v226(1, this)));
break;
case JVS::WorldKicksPCB:
io_boards.push_back(std::unique_ptr<jvs_namco_v226_pcb>(new jvs_namco_v226_pcb(1, this)));
break;
case JVS::WaveRunnerGP:
io_boards.push_back(std::unique_ptr<jvs_837_13844_wrungp>(new jvs_837_13844_wrungp(1, this)));
break;
case JVS::_18Wheeler:
io_boards.push_back(std::unique_ptr<jvs_837_13844_18wheeler>(new jvs_837_13844_18wheeler(1, this)));
break;
case JVS::F355:
io_boards.push_back(std::unique_ptr<jvs_837_13844_racing>(new jvs_837_13844_racing(1, this)));
break;
}
break;
case JVS::LightGun:
io_boards.push_back(std::unique_ptr<jvs_namco_jyu>(new jvs_namco_jyu(1, this)));
break;
case JVS::LightGunAsAnalog:
// Regular board sending lightgun coords as axis 0/1
io_boards.push_back(std::unique_ptr<jvs_837_13551>(new jvs_837_13551(1, this)));
io_boards.back()->lightgun_as_analog = true;
break;
case JVS::Mazan:
io_boards.push_back(std::unique_ptr<jvs_namco_fcb>(new jvs_namco_fcb(1, this)));
io_boards.push_back(std::unique_ptr<jvs_namco_fcb>(new jvs_namco_fcb(2, this)));
break;
case JVS::GunSurvivor:
io_boards.push_back(std::unique_ptr<jvs_namco_fca>(new jvs_namco_fca(1, this)));
break;
case JVS::DogWalking:
io_boards.push_back(std::unique_ptr<jvs_837_13844_encoders>(new jvs_837_13844_encoders(1, this)));
break;
case JVS::TouchDeUno:
io_boards.push_back(std::unique_ptr<jvs_837_13844_touch>(new jvs_837_13844_touch(1, this)));
break;
case JVS::WorldKicks:
io_boards.push_back(std::unique_ptr<jvs_namco_v226>(new jvs_namco_v226(1, this)));
break;
case JVS::WorldKicksPCB:
io_boards.push_back(std::unique_ptr<jvs_namco_v226_pcb>(new jvs_namco_v226_pcb(1, this)));
break;
case JVS::WaveRunnerGP:
io_boards.push_back(std::unique_ptr<jvs_837_13844_wrungp>(new jvs_837_13844_wrungp(1, this)));
break;
case JVS::_18Wheeler:
io_boards.push_back(std::unique_ptr<jvs_837_13844_18wheeler>(new jvs_837_13844_18wheeler(1, this)));
break;
}
std::string eeprom_file = hostfs::getArcadeFlashPath() + ".eeprom";

View File

@ -22,11 +22,11 @@
enum
{
AW_EPR_OFFSETL_addr = 0x00,
AW_EPR_OFFSETH_addr = 0x04,
AW_MPR_RECORD_INDEX_addr = 0x0C,
AW_MPR_FIRST_FILE_INDEX_addr = 0x10,
AW_MPR_FILE_OFFSETL_addr = 0x14,
AW_MPR_FILE_OFFSETH_addr = 0x18,
AW_PIO_DATA_addr = 0x80,
AW_EPR_OFFSETL_addr = 0x5f7000,
AW_EPR_OFFSETH_addr = 0x5f7004,
AW_MPR_RECORD_INDEX_addr = 0x5f700C,
AW_MPR_FIRST_FILE_INDEX_addr = 0x5f7010,
AW_MPR_FILE_OFFSETL_addr = 0x5f7014,
AW_MPR_FILE_OFFSETH_addr = 0x5f7018,
AW_PIO_DATA_addr = 0x5f7080,
};

View File

@ -166,20 +166,8 @@ ROM board internal layouts:
u32 AWCartridge::ReadMem(u32 address, u32 size) {
verify(size != 1);
switch(address & 255)
switch (address)
{
// case AW_EPR_OFFSETH_addr:
// break;
// case AW_EPR_OFFSETL_addr:
// break;
// case AW_MPR_RECORD_INDEX_addr:
// break;
// case AW_MPR_FIRST_FILE_INDEX_addr:
// break;
// case AW_MPR_FILE_OFFSETH_addr:
// break;
// case AW_MPR_FILE_OFFSETL_addr:
// break;
case AW_PIO_DATA_addr:
{
u32 roffset = epr_offset & 0x3ffffff;
@ -197,7 +185,7 @@ u32 AWCartridge::ReadMem(u32 address, u32 size) {
void AWCartridge::WriteMem(u32 address, u32 data, u32 size)
{
switch(address & 255)
switch (address)
{
case AW_EPR_OFFSETH_addr:
epr_offset = (epr_offset & 0x0000ffff) | (data << 16);

View File

@ -33,7 +33,7 @@ public:
u32 data = NaomiCartridge::ReadMem(address, size);
if ((address & 0xff) == (NAOMI_ROM_OFFSETH_addr & 0xff))
if (address == NAOMI_ROM_OFFSETH_addr)
// indicates that security PIC is present
data |= 0x2000;

View File

@ -0,0 +1,245 @@
/*
Copyright 2023 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#include "multiboard.h"
static Multiboard *multiboard;
#ifdef NAOMI_MULTIBOARD
#include "cfg/cfg.h"
#ifndef _WIN32
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <errno.h>
#endif
#include "oslib/oslib.h"
#include "naomi_cart.h"
#include "naomi_roms.h"
#include "cfg/option.h"
#include "hw/sh4/sh4_sched.h"
#include <thread>
#include <chrono>
constexpr int SyncCycles = 500000;
static int schedCallback(int tag, int cycles, int jitter)
{
multiboard->syncWait();
return SyncCycles;
}
Multiboard::Multiboard()
{
sharedMem = nullptr;
boardId = cfgLoadInt("naomi", "BoardId", 0);
#ifdef _WIN32
const char *FileName = "Local\\naomi_multiboard_mem";
if (isMaster())
mapFile = CreateFileMapping(
INVALID_HANDLE_VALUE, // use paging file
NULL, // default security
PAGE_READWRITE, // read/write access
0, // max. object size (high-order)
sizeof(SharedMemory), // max. object size (low-order)
FileName); // name of mapping object
else
mapFile = OpenFileMapping(
FILE_MAP_ALL_ACCESS, // read/write access
FALSE, // do not inherit the name
FileName); // name of mapping object
if (mapFile == NULL)
{
ERROR_LOG(NAOMI, "Could not open/create file mapping (%d)", GetLastError());
}
else
{
INFO_LOG(NAOMI, "Created shared memory: \"%s\"", FileName);
sharedMem = (SharedMemory *) MapViewOfFile(mapFile, // handle to map object
FILE_MAP_ALL_ACCESS, // read/write permission
0,
0,
sizeof(SharedMemory));
if (sharedMem == nullptr)
{
ERROR_LOG(NAOMI, "Could not map view of file (%d)", GetLastError());
CloseHandle(mapFile);
mapFile = NULL;
}
}
#else
#define SHARED_MEM_FILE "/naomi_multiboard_mem"
int fd = shm_open(SHARED_MEM_FILE, O_RDWR | (isMaster() ? O_CREAT : 0), 0644);
if (fd < 0)
ERROR_LOG(NAOMI, "Can't open mapped file. errno %d", errno);
else
{
if (isMaster() && ftruncate(fd, sizeof(SharedMemory)))
ERROR_LOG(NAOMI, "Can't ftruncate mapped file. errno %d", errno);
sharedMem = (SharedMemory *)mmap(nullptr, sizeof(SharedMemory), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
}
#endif
if (sharedMem == nullptr)
throw FlycastException("Cannot initialize Naomi multiboard shared memory");
if (isMaster())
{
new (sharedMem) SharedMemory();
sharedMem->boardReady[1] = sharedMem->boardReady[2] = sharedMem->boardReady[3] = true;
sharedMem->boardSynced[1] = sharedMem->boardSynced[2] = sharedMem->boardSynced[3] = true;
}
multiboard = this;
schedId = sh4_sched_register(0, schedCallback);
sh4_sched_request(schedId, SyncCycles);
}
void Multiboard::startSlave()
{
if (isSlave() || slaveStarted)
return;
int slaves;
if (!strcmp("f355", CurrentCartridge->game->name))
slaves = 3;
else if (config::MultiboardSlaves >= 2)
slaves = CurrentCartridge->game->multiboard;
else
slaves = 1;
boardCount = slaves + 1;
for (int i = 0; i < boardCount; i++)
sharedMem->boardReady[i] = false;
int x = cfgLoadInt("window", "left", (1920 - 640) / 2);
int y = cfgLoadInt("window", "top", (1080 - 480) / 2);
for (int i = 0; i < slaves; i++)
{
std::string region = "config:Dreamcast.Region=" + std::to_string(config::Region);
std::string board = "naomi:BoardId=" + std::to_string(i + 1);
int slaveX = x;
int slaveY = y + 480;
if (slaves == 2) {
slaveX = i == 0 ? x - 640 : x + 640;
slaveY = y;
}
else if (slaves == 3)
slaveX = i == 1 ? x - 640 : i == 2 ? x + 640 : x;
std::string left = "window:left=" + std::to_string(slaveX);
std::string top = "window:top=" + std::to_string(slaveY);
const char *args[] = {
"-config", board.c_str(),
"-config", region.c_str(),
"-config", left.c_str(),
"-config", top.c_str(),
CurrentCartridge->game->bios == nullptr ? "naomi" : CurrentCartridge->game->bios
};
os_RunInstance(ARRAY_SIZE(args), args);
}
slaveStarted = true;
}
void Multiboard::syncWait()
{
if (isMaster() && !slaveStarted)
return;
{
std::unique_lock<IpcMutex> lock(sharedMem->mutex);
sharedMem->boardReady[boardId] = true;
sharedMem->boardSynced[boardId] = false;
sharedMem->cond.notify_all();
}
do {
if (isSlave() && sharedMem->exit) {
NOTICE_LOG(NAOMI, "Slave exiting");
throw FlycastException("Slave exit");
}
{
std::unique_lock<IpcMutex> lock(sharedMem->mutex);
bool allReady = true;
for (const auto& ready : sharedMem->boardReady)
if (!ready) {
allReady = false;
break;
}
if (allReady) {
sharedMem->boardSynced[boardId] = true;
sharedMem->cond.notify_all();
break;
}
if (sharedMem->cond.wait_for(lock, std::chrono::seconds(5)) == std::cv_status::timeout) {
ERROR_LOG(NAOMI, "Time out waiting for multiboard vsync. Slave %d", isSlave());
return;
}
}
} while (true);
if (isMaster())
{
do {
{
std::unique_lock<IpcMutex> lock(sharedMem->mutex);
bool allSynced = true;
for (const auto& synced : sharedMem->boardSynced)
if (!synced) {
allSynced = false;
break;
}
if (allSynced)
{
for (int i = 0; i < boardCount; i++)
sharedMem->boardReady[i] = false;
break;
}
if (sharedMem->cond.wait_for(lock, std::chrono::seconds(5)) == std::cv_status::timeout) {
ERROR_LOG(NAOMI, "Time out waiting for multiboard vsync");
break;
}
}
} while (true);
}
}
Multiboard::~Multiboard()
{
if (schedId != -1)
sh4_sched_unregister(schedId);
multiboard = nullptr;
if (sharedMem != nullptr)
{
sharedMem->exit = true;
sharedMem->cond.notify_all();
if (isMaster())
sharedMem->~SharedMemory();
}
#ifdef _WIN32
if (sharedMem != nullptr)
UnmapViewOfFile(sharedMem);
if (mapFile != NULL)
CloseHandle(mapFile);
#else
if (sharedMem != nullptr)
munmap(sharedMem, sizeof(SharedMemory));
shm_unlink(SHARED_MEM_FILE);
#endif
}
#endif // NAOMI_MULTIBOARD

554
core/hw/naomi/multiboard.h Normal file
View File

@ -0,0 +1,554 @@
/*
Copyright 2023 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "types.h"
#ifdef NAOMI_MULTIBOARD
#include "naomi_regs.h"
#include "hw/holly/sb.h"
#include "hw/sh4/sh4_mem.h"
#include <atomic>
#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
class IpcMutex
{
HANDLE mutex;
public:
using native_handle_type = HANDLE;
IpcMutex()
{
SECURITY_ATTRIBUTES secattr{ sizeof(SECURITY_ATTRIBUTES) };
secattr.bInheritHandle = TRUE;
mutex = CreateMutex(&secattr, FALSE, NULL);
if (mutex == NULL)
throw std::runtime_error("CreateMutex failed");
}
IpcMutex(const IpcMutex&) = delete;
~IpcMutex() {
CloseHandle(mutex);
}
void lock() {
WaitForSingleObject(mutex, INFINITE);
}
void unlock() {
ReleaseMutex(mutex);
}
native_handle_type native_handle() {
return mutex;
}
IpcMutex& operator=(const IpcMutex&) = delete;
};
class IpcConditionVariable
{
HANDLE semaphore;
IpcMutex mutex;
int waiters = 0;
std::cv_status waitMs(std::unique_lock<IpcMutex>& lock, DWORD msecs)
{
mutex.lock();
waiters++;
mutex.unlock();
// The unlock/wait/lock should be atomical so this implementation isn't compliant
lock.unlock();
DWORD rc = WaitForSingleObject(semaphore, msecs);
lock.lock();
if (rc == WAIT_ABANDONED || rc == WAIT_FAILED)
throw std::runtime_error("Semaphore wait failure");
return rc == WAIT_TIMEOUT ? std::cv_status::timeout : std::cv_status::no_timeout;
}
public:
IpcConditionVariable()
{
SECURITY_ATTRIBUTES secattr{ sizeof(SECURITY_ATTRIBUTES) };
secattr.bInheritHandle = TRUE;
semaphore = CreateSemaphore(&secattr, 0, std::numeric_limits<LONG>::max(), NULL);
if (semaphore == NULL)
throw std::runtime_error("Semaphore create failed");
}
~IpcConditionVariable()
{
CloseHandle(semaphore);
}
void notify_all()
{
mutex.lock();
ReleaseSemaphore(semaphore, waiters, NULL);
waiters = 0;
mutex.unlock();
}
void wait(std::unique_lock<IpcMutex>& lock)
{
waitMs(lock, INFINITE);
}
template<class Rep, class Period>
std::cv_status wait_for(std::unique_lock<IpcMutex>& lock,
const std::chrono::duration<Rep, Period>& rel_time)
{
return waitMs(lock, std::chrono::duration_cast<std::chrono::milliseconds>(rel_time).count());
}
IpcConditionVariable& operator=(const IpcConditionVariable&) = delete;
};
#else // _!WIN32
#include <pthread.h>
class IpcMutex
{
pthread_mutex_t mutex;
public:
using native_handle_type = pthread_mutex_t*;
IpcMutex()
{
pthread_mutexattr_t mattr;
pthread_mutexattr_init(&mattr);
pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&mutex, &mattr);
pthread_mutexattr_destroy(&mattr);
}
IpcMutex(const IpcMutex&) = delete;
~IpcMutex() {
pthread_mutex_destroy(&mutex);
}
void lock() {
pthread_mutex_lock(&mutex);
}
void unlock() {
pthread_mutex_unlock(&mutex);
}
native_handle_type native_handle() {
return &mutex;
}
IpcMutex& operator=(const IpcMutex&) = delete;
};
class IpcConditionVariable
{
pthread_cond_t cond;
public:
IpcConditionVariable()
{
pthread_condattr_t cvattr;
pthread_condattr_init(&cvattr);
pthread_condattr_setpshared(&cvattr, PTHREAD_PROCESS_SHARED);
pthread_cond_init(&cond, &cvattr);
pthread_condattr_destroy(&cvattr);
}
IpcConditionVariable(const IpcConditionVariable&) = delete;
~IpcConditionVariable() {
pthread_cond_destroy(&cond);
}
void notify_all() {
pthread_cond_broadcast(&cond);
}
void wait(std::unique_lock<IpcMutex>& lock) {
pthread_cond_wait(&cond, lock.mutex()->native_handle());
}
template<class Rep, class Period>
std::cv_status wait_for(std::unique_lock<IpcMutex>& lock,
const std::chrono::duration<Rep, Period>& rel_time)
{
timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
std::chrono::seconds seconds = std::chrono::duration_cast<std::chrono::seconds>(rel_time);
ts.tv_sec += seconds.count();
auto nanoTime = rel_time - seconds;
ts.tv_nsec += std::chrono::nanoseconds(nanoTime).count();
if (ts.tv_nsec >= 1000000000) {
ts.tv_sec++;
ts.tv_nsec -= 1000000000;
}
int rc = pthread_cond_timedwait(&cond, lock.mutex()->native_handle(), &ts);
return rc == ETIMEDOUT ? std::cv_status::timeout : std::cv_status::no_timeout;
}
IpcConditionVariable& operator=(const IpcConditionVariable&) = delete;
};
#endif // _!WIN32
class Multiboard
{
public:
static constexpr u32 G1_BASE = 0x05F7080;
static constexpr u32 G2_BASE = 0x1010000;
Multiboard();
~Multiboard();
u32 readG1(u32 addr, u32 size)
{
switch (addr)
{
case NAOMI_MBOARD_OFFSET_addr:
return offset;
case NAOMI_MBOARD_DATA_addr:
{
u32 bank = (sharedMem->status & 1) ? 0 : 0x80000;
u32 addr;
if (isMaster())
{
bank = 0x80000 - bank;
addr = offset + bank / 2;
}
else
{
addr = offset + (boardId - 1) * 0x10000 + bank / 2;
}
u16 data = sharedMem->data[addr & (MEM_SIZE - 1)];
DEBUG_LOG(NAOMI, "read NAOMI_COMM_DATA[%x]: %x (pc = %x)", addr, data, p_sh4rcb->cntx.pc);
offset++;
return data;
}
case 0x5f7074:
DEBUG_LOG(NAOMI, "5F7074 read: %d", reg74); // loops from 0 to ff
return reg74 & 0xff;
case 0x5f706C:
DEBUG_LOG(NAOMI, "5F706C read");
return 0; // written to C4 afterwards & 7 except if == 7
case G1_BASE + 0x08:
DEBUG_LOG(NAOMI, "5F7088 read");
return 0x80; // loops until bit 7 is set
case G1_BASE + 0x10:
DEBUG_LOG(NAOMI, "5F7090 read");
return 0x60; // ? or 0x61 or 0x62
case G1_BASE + 0x14:
DEBUG_LOG(NAOMI, "5F7094 read");
return 0x43; // set to 43 before
default:
DEBUG_LOG(NAOMI, "Unknown G1 register read<%d>: %x (pc = %x)", size, addr, p_sh4rcb->cntx.pc);
return 0xFFFF;
}
}
void writeG1(u32 addr, u32 size, u32 data)
{
switch (addr)
{
case NAOMI_MBOARD_OFFSET_addr:
DEBUG_LOG(NAOMI, "NAOMI_COMM_OFFSET = %x (pc = %x)", data, p_sh4rcb->cntx.pc);
offset = data;
break;
case NAOMI_MBOARD_DATA_addr:
{
DEBUG_LOG(NAOMI, "NAOMI_COMM_DATA[%x] = %x (pc = °%x)", offset, data, p_sh4rcb->cntx.pc);
u32 bank = (sharedMem->status & 1) ? 0 : 0x80000;
u32 addr;
if (isMaster())
{
bank = 0x80000 - bank;
addr = offset + bank / 2;
}
else
{
addr = offset + (boardId - 1) * 0x10000 + bank / 2;
}
sharedMem->data[addr & (MEM_SIZE - 1)] = data;
offset++;
}
break;
case 0x5f7070:
DEBUG_LOG(NAOMI, "5F7070 written: %d", data);
break;
case 0x5f7074:
DEBUG_LOG(NAOMI, "5F7074 written: %d", data);
reg74 = data;
startSlave();
break;
case 0x5f7058: // Set to 0 before DMA operation from multiboard
break;
case NAOMI_MBOARD_STATUS_addr:
// Set to 4 after writing most packets
if (isSlave())
{
if ((data & 4) != 0)
sharedMem->status.fetch_or(0x10 << boardId);
//else
// sharedMem->status.fetch_and(~(0x10 << boardId));
}
break;
default:
DEBUG_LOG(NAOMI, "Unknown G1 register written<%d>: %x = %x (pc = %x)", size, addr, data, p_sh4rcb->cntx.pc);
break;
}
}
u32 readG2Ext(u32 addr, u32 size)
{
//DEBUG_LOG(NAOMI, "g2ext_readMem<%d> %x (pc = %x)", size, addr, p_sh4rcb->cntx.pc);
switch (addr)
{
case G2_BASE + 0x08:
return 0x80; // loops until bit 7 is set
case G2_BASE + 0x10: // similar to 5F7090
return 0x60;
case G2_BASE + 0x14: // similar to 5F7094
return 0x43;
case G2_BASE + 0x94:
return 0; // ?
case G2_BASE + 0x98:
return 0; // ?
case G2_BASE + 0x9c: // similar to 5F7074
return isMaster() ? reg9c : 0;
case G2_BASE + 0xc0: // similar to 5F706C. need to match!
return 0;
case 0x1008000: // status reg
{
verify(size == 2);
// seems to determine if acting as master or slave
if (isSlave())
return 0;
u16 v = 0xff00 | sharedMem->status;
DEBUG_LOG(NAOMI, "g2ext_readMem status_reg %x", v);
return v;
}
default:
if ((addr - 0x1020000) < MEM_SIZE * 2)
{
u32 bank = (sharedMem->status & 1) ? 0x80000 : 0;
DEBUG_LOG(NAOMI, "g2ext_readMem<%d> %x -> %x", size, addr, sharedMem->data[(addr - 0x1020000 + bank) / 2]);
verify(size >= 2);
u32 offset;
if (isSlave())
{
return 0;
bank = 0x80000 - bank;
if (addr >= 0x1040000) {
INFO_LOG(NAOMI, "Read shared mem out of bound for slave: %x", addr);
break;
}
offset = (addr - 0x1020000 + bank + (boardId - 1) * 0x20000) / 2;
}
else
offset = (addr - 0x1020000 + bank) / 2;
if (size == 2)
return sharedMem->data[offset];
else
return *(u32 *)&sharedMem->data[offset];
}
break;
}
return 0;
}
void writeG2Ext(u32 addr, u32 size, u32 data)
{
//DEBUG_LOG(NAOMI, "g2ext_writeMem<%d> %x = %x", size, addr, data);
switch (addr) {
case 0x1008000: // status reg
verify(size == 2);
if (isMaster())
sharedMem->status = data;
DEBUG_LOG(NAOMI, "g2ext_writeMem status_reg %x", data);
break;
case G2_BASE + 0x9c:
reg9c = data;
break;
case G2_BASE + 0xa0: // LEDs
DEBUG_LOG(NAOMI, "G2 leds %x", data);
break;
default:
if ((addr - 0x1020000) < MEM_SIZE * 2)
{
DEBUG_LOG(NAOMI, "g2ext_writeMem<%d> %x: %x", size, addr, data);
verify(size >= 2);
u32 bank = (sharedMem->status & 1) ? 0x80000 : 0;
if (isSlave())
{
break;
bank = 0x80000 - bank;
if (addr >= 0x1040000) {
INFO_LOG(NAOMI, "Write shared mem out of bound for slave: %x", addr);
break;
}
u32 offset = (addr - 0x1020000 + bank + (boardId - 1) * 0x20000) / 2;
if (size == 2)
sharedMem->data[offset] = data;
else
*(u32 *)&sharedMem->data[offset] = data;
}
else
{
u32 offset = (addr - 0x1020000 + bank) / 2;
if ((sharedMem->status & 2) != 0 || addr >= 0x1040000)
{
if (size == 2)
sharedMem->data[offset] = data;
else
*(u32 *)&sharedMem->data[offset] = data;
}
if (addr < 0x1040000) // FIXME this is weird
{
if ((sharedMem->status & 4) != 0)
{
if (size == 2)
sharedMem->data[offset + 0x10000] = data;
else
*(u32 *)&sharedMem->data[offset + 0x10000] = data;
}
if (sharedMem->status & 8)
{
if (size == 2)
sharedMem->data[offset + 0x20000] = data;
else
*(u32 *)&sharedMem->data[offset + 0x20000] = data;
}
}
}
}
break;
}
}
bool dmaStart()
{
if (isMaster())
return false;
DEBUG_LOG(NAOMI, "Multiboard DMA start addr %08X len %d", SB_GDSTAR, SB_GDLEN);
verify(1 == SB_GDDIR);
u32 start = SB_GDSTAR & 0x1FFFFFE0;
u32 len = (SB_GDLEN + 31) & ~31;
u32 bank = (sharedMem->status & 1) ? 0 : 0x80000;
u32 *src = (u32 *)&sharedMem->data[(boardId - 1) * 0x10000 + bank / 2];
WriteMemBlock_nommu_ptr(start, src, len);
SB_GDSTARD = start + len;
SB_GDLEND = len;
return true;
}
void reset()
{
if (isSlave())
sharedMem->status.fetch_and(~(0x10 << boardId));
}
void syncWait();
private:
bool isMaster() const { return boardId == 0; }
bool isSlave() const { return boardId != 0; }
void startSlave();
static constexpr size_t MEM_SIZE = 0x100000 / sizeof(u16);
struct SharedMemory
{
std::atomic<u16> status;
IpcMutex mutex;
IpcConditionVariable cond;
std::atomic<bool> boardReady[4];
std::atomic<bool> boardSynced[4];
std::atomic<bool> exit;
u16 data[MEM_SIZE];
};
int boardId = 0;
u32 offset = 0;
u16 reg74 = 0;
u32 reg9c = 0;
SharedMemory *sharedMem;
#ifdef _WIN32
HANDLE mapFile;
#endif
int boardCount = 0;
bool slaveStarted = false;
int schedId;
};
#else // !NAOMI_MULTIBOARD
class Multiboard
{
public:
u32 readG1(u32 addr, u32 size) {
return 0;
}
void writeG1(u32 addr, u32 size, u32 data) { }
u32 readG2Ext(u32 addr, u32 size) {
return 0;
}
void writeG2Ext(u32 addr, u32 size, u32 data) { }
bool dmaStart() {
return false;
}
void reset() { }
void syncWait() { }
};
#endif // !NAOMI_MULTIBOARD

View File

@ -18,9 +18,8 @@
#include "serialize.h"
#include "network/output.h"
//#define NAOMI_COMM
static NaomiM3Comm m3comm;
Multiboard *multiboard;
static const u32 BoardID = 0x980055AA;
static u32 GSerialBuffer, BSerialBuffer;
@ -31,12 +30,6 @@ static int BControl, BCmd, BLastCmd;
static int GControl, GCmd, GLastCmd;
static int SerStep, SerStep2;
#ifdef NAOMI_COMM
u32 CommOffset;
u32* CommSharedMem;
HANDLE CommMapFile=INVALID_HANDLE_VALUE;
#endif
/*
El numero de serie solo puede contener:
0-9 (0x30-0x39)
@ -45,6 +38,7 @@ J-N (0x4A-0x4E)
P-Z (0x50-0x5A)
*/
static u8 BSerial[]="\xB7"/*CRC1*/"\x19"/*CRC2*/"0123234437897584372973927387463782196719782697849162342198671923649";
//static u8 BSerial[]="\x09\xa1 0000000000000000\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; // default from mame
static u8 GSerial[]="\xB7"/*CRC1*/"\x19"/*CRC2*/"0123234437897584372973927387463782196719782697849162342198671923649";
static u8 midiTxBuf[4];
@ -278,11 +272,11 @@ static void NaomiGameIDProcessCmd()
void NaomiGameIDWrite(const u16 Data)
{
int Dat=Data&0x01;
int Clk=Data&0x02;
int Rst=Data&0x04;
int Sta=Data&0x08;
int Cmd=Data&0x10;
int Dat=Data&0x01; // mame: SDA
int Clk=Data&0x02; // mame: SCL
int Rst=Data&0x04; // mame: CS
int Sta=Data&0x08; // mame: RST
int Cmd=Data&0x10; // mame: unused...
if(Rst)
{
@ -395,7 +389,7 @@ void naomi_process(u32 command, u32 offsetl, u32 parameterl, u32 parameterh)
u32 ReadMem_naomi(u32 address, u32 size)
{
verify(size != 1);
// verify(size != 1);
if (unlikely(CurrentCartridge == NULL))
{
INFO_LOG(NAOMI, "called without cartridge");
@ -432,7 +426,10 @@ static void Naomi_DmaStart(u32 addr, u32 data)
return;
}
if (!m3comm.DmaStart(addr, data) && CurrentCartridge != NULL)
if (multiboard != nullptr && multiboard->dmaStart())
{
}
else if (!m3comm.DmaStart(addr, data) && CurrentCartridge != NULL)
{
DEBUG_LOG(NAOMI, "NAOMI-DMA start addr %08X len %d", SB_GDSTAR, SB_GDLEN);
verify(1 == SB_GDDIR);
@ -477,67 +474,15 @@ static void Naomi_DmaEnable(u32 addr, u32 data)
void naomi_reg_Init()
{
#ifdef NAOMI_COMM
CommMapFile = CreateFileMapping(
INVALID_HANDLE_VALUE, // use paging file
NULL, // default security
PAGE_READWRITE, // read/write access
0, // max. object size
0x1000*4, // buffer size
L"Global\\nullDC_103_naomi_comm"); // name of mapping object
if (CommMapFile == NULL || CommMapFile==INVALID_HANDLE_VALUE)
{
_tprintf(TEXT("Could not create file mapping object (%d).\nTrying to open existing one\n"), GetLastError());
CommMapFile=OpenFileMapping(
FILE_MAP_ALL_ACCESS, // read/write access
FALSE, // do not inherit the name
L"Global\\nullDC_103_naomi_comm"); // name of mapping object
}
if (CommMapFile == NULL || CommMapFile==INVALID_HANDLE_VALUE)
{
_tprintf(TEXT("Could not open existing file either\n"), GetLastError());
CommMapFile=INVALID_HANDLE_VALUE;
}
else
{
printf("NAOMI: Created \"Global\\nullDC_103_naomi_comm\"\n");
CommSharedMem = (u32*) MapViewOfFile(CommMapFile, // handle to map object
FILE_MAP_ALL_ACCESS, // read/write permission
0,
0,
0x1000*4);
if (CommSharedMem == NULL)
{
_tprintf(TEXT("Could not map view of file (%d).\n"),
GetLastError());
CloseHandle(CommMapFile);
CommMapFile=INVALID_HANDLE_VALUE;
}
else
printf("NAOMI: Mapped CommSharedMem\n");
}
#endif
NaomiInit();
networkOutput.init();
}
void naomi_reg_Term()
{
#ifdef NAOMI_COMM
if (CommSharedMem)
{
UnmapViewOfFile(CommSharedMem);
}
if (CommMapFile!=INVALID_HANDLE_VALUE)
{
CloseHandle(CommMapFile);
}
#endif
if (multiboard != nullptr)
delete multiboard;
multiboard = nullptr;
m3comm.closeNetwork();
networkOutput.term();
}
@ -573,7 +518,18 @@ void naomi_reg_Reset(bool hard)
reg_dimm_status = 0x11;
m3comm.closeNetwork();
if (hard)
{
naomi_cart_Close();
if (multiboard != nullptr)
{
delete multiboard;
multiboard = nullptr;
}
if (settings.naomi.multiboard)
multiboard = new Multiboard();
}
else if (multiboard != nullptr)
multiboard->reset();
}
static u8 aw_maple_devs;

View File

@ -3,6 +3,7 @@
*/
#pragma once
#include "types.h"
#include "multiboard.h"
void naomi_reg_Init();
void naomi_reg_Term();
@ -30,3 +31,21 @@ void initMidiForceFeedback();
u32 libExtDevice_ReadMem_A0_006(u32 addr, u32 size);
void libExtDevice_WriteMem_A0_006(u32 addr, u32 data, u32 size);
extern Multiboard *multiboard;
//Area 0 , 0x01000000- 0x01FFFFFF [G2 Ext. Device]
static inline u32 g2ext_readMem(u32 addr, u32 size)
{
if (multiboard != nullptr)
return multiboard->readG2Ext(addr, size);
INFO_LOG(NAOMI, "Unhandled G2 Ext read<%d> at %x", size, addr);
return 0;
}
static inline void g2ext_writeMem(u32 addr, u32 data, u32 size)
{
if (multiboard != nullptr)
multiboard->writeG2Ext(addr, size, data);
else
INFO_LOG(NAOMI, "Unhandled G2 Ext write<%d> at %x: %x", size, addr, data);
}

View File

@ -63,7 +63,7 @@ static bool loadBios(const char *filename, Archive *child_archive, Archive *pare
return false;
}
struct BIOS_t *bios = &BIOS[biosid];
const BIOS_t *bios = &BIOS[biosid];
std::string arch_name(filename);
std::string path = hostfs::findNaomiBios(arch_name + ".zip");
@ -164,7 +164,7 @@ static bool loadBios(const char *filename, Archive *child_archive, Archive *pare
return found_region;
}
static Game *FindGame(const char *filename)
static const Game *FindGame(const char *filename)
{
std::string gameName = get_file_basename(filename);
size_t folder_pos = get_last_slash_pos(gameName);
@ -180,7 +180,14 @@ static Game *FindGame(const char *filename)
void naomi_cart_LoadBios(const char *filename)
{
Game *game = FindGame(filename);
if (settings.naomi.slave)
{
if (!loadBios(filename, nullptr, nullptr, config::Region))
throw NaomiCartException(std::string("Error: cannot load BIOS ") + filename);
bios_loaded = true;
return;
}
const Game *game = FindGame(filename);
if (game == nullptr)
return;
@ -211,7 +218,7 @@ void naomi_cart_LoadBios(const char *filename)
static void loadMameRom(const char *filename, LoadProgress *progress)
{
Game *game = FindGame(filename);
const Game *game = FindGame(filename);
if (game == NULL)
throw NaomiCartException("Unknown game");
@ -268,6 +275,7 @@ static void loadMameRom(const char *filename, LoadProgress *progress)
}
CurrentCartridge->SetKey(game->key);
NaomiGameInputs = game->inputs;
CurrentCartridge->game = game;
MD5Sum md5;
@ -565,6 +573,11 @@ void naomi_cart_LoadRom(const char* file, LoadProgress *progress)
{
naomi_cart_Close();
if (settings.naomi.slave)
{
CurrentCartridge = new NaomiCartridge(0);
return;
}
std::string extension = get_file_extension(file);
if (extension == "zip" || extension == "7z")
@ -625,22 +638,30 @@ void naomi_cart_Close()
int naomi_cart_GetPlatform(const char *path)
{
Game *game = FindGame(path);
if (game == NULL)
settings.naomi.multiboard = false;
const Game *game = FindGame(path);
if (game == nullptr)
return DC_PLATFORM_NAOMI;
else if (game->cart_type == AW)
return DC_PLATFORM_ATOMISWAVE;
else if (game->bios != nullptr && !strcmp("naomi2", game->bios))
return DC_PLATFORM_NAOMI2;
else
{
#ifdef NAOMI_MULTIBOARD
if (game->multiboard > 0)
settings.naomi.multiboard = true;
#endif
return DC_PLATFORM_NAOMI;
}
}
Cartridge::Cartridge(u32 size)
{
RomPtr = (u8 *)malloc(size);
RomSize = size;
memset(RomPtr, 0xFF, RomSize);
if (size != 0)
memset(RomPtr, 0xFF, RomSize);
}
Cartridge::~Cartridge()
@ -719,33 +740,33 @@ void* NaomiCartridge::GetDmaPtr(u32& size)
u32 NaomiCartridge::ReadMem(u32 address, u32 size)
{
verify(size!=1);
// verify(size != 1); not true anymore with multiboard
switch(address & 255)
switch (address)
{
case 0x3c: // 5f703c: DIMM COMMAND
case NAOMI_DIMM_COMMAND:
//DEBUG_LOG(NAOMI, "DIMM COMMAND read<%d>", size);
return 0xffff; //reg_dimm_command
case 0x40: // 5f7040: DIMM OFFSETL
case NAOMI_DIMM_OFFSETL:
DEBUG_LOG(NAOMI, "DIMM OFFSETL read<%d>", size);
return reg_dimm_offsetl;
case 0x44: // 5f7044: DIMM PARAMETERL
case NAOMI_DIMM_PARAMETERL:
DEBUG_LOG(NAOMI, "DIMM PARAMETERL read<%d>", size);
return reg_dimm_parameterl;
case 0x48: // 5f7048: DIMM PARAMETERH
case NAOMI_DIMM_PARAMETERH:
DEBUG_LOG(NAOMI, "DIMM PARAMETERH read<%d>", size);
return reg_dimm_parameterh;
case 0x4c: // 5f704c: DIMM STATUS
case NAOMI_DIMM_STATUS:
DEBUG_LOG(NAOMI, "DIMM STATUS read<%d>: %x", size, reg_dimm_status);
return reg_dimm_status;
case NAOMI_ROM_OFFSETH_addr&255:
return RomPioOffset>>16 | (RomPioAutoIncrement << 15);
case NAOMI_ROM_OFFSETH_addr:
return RomPioOffset >> 16 | (RomPioAutoIncrement << 15);
case NAOMI_ROM_OFFSETL_addr&255:
return RomPioOffset&0xFFFF;
case NAOMI_ROM_OFFSETL_addr:
return RomPioOffset & 0xFFFF;
case NAOMI_ROM_DATA_addr & 255:
case NAOMI_ROM_DATA_addr:
{
u32 rv = 0;
Read(RomPioOffset, 2, &rv);
@ -755,51 +776,40 @@ u32 NaomiCartridge::ReadMem(u32 address, u32 size)
return rv;
}
case NAOMI_DMA_COUNT_addr&255:
return (u16) DmaCount;
case NAOMI_DMA_COUNT_addr:
return (u16)DmaCount;
case NAOMI_BOARDID_READ_addr&255:
return NaomiGameIDRead()?0x8000:0x0000;
case NAOMI_BOARDID_READ_addr:
return NaomiGameIDRead() ? 0x8000 : 0x0000;
//What should i do to emulate 'nothing' ?
case NAOMI_COMM_OFFSET_addr&255:
#ifdef NAOMI_COMM
DEBUG_LOG(NAOMI, "naomi COMM offs READ: %X, %d", address, size);
return CommOffset;
#endif
case NAOMI_COMM_DATA_addr&255:
#ifdef NAOMI_COMM
DEBUG_LOG(NAOMI, "naomi COMM data read: %X, %d", CommOffset, size);
if (CommSharedMem)
{
return CommSharedMem[CommOffset&0xF];
}
#endif
return 1;
case NAOMI_DMA_OFFSETH_addr:
return DmaOffset >> 16;
case NAOMI_DMA_OFFSETL_addr:
return DmaOffset & 0xFFFF;
case NAOMI_DMA_OFFSETH_addr&255:
return DmaOffset>>16;
case NAOMI_DMA_OFFSETL_addr&255:
return DmaOffset&0xFFFF;
case NAOMI_BOARDID_WRITE_addr&255:
case NAOMI_BOARDID_WRITE_addr:
DEBUG_LOG(NAOMI, "naomi ReadBoardId: %X, %d", address, size);
return 1;
default:
break;
}
DEBUG_LOG(NAOMI, "naomi?WTF? ReadMem: %X, %d", address, size);
if (multiboard != nullptr)
return multiboard->readG1(address, size);
return 0xFFFF;
if (address == NAOMI_MBOARD_DATA_addr || address == NAOMI_MBOARD_OFFSET_addr)
return 1;
else {
DEBUG_LOG(NAOMI, "naomiCart::ReadMem<%d> unknown: %08x", size, address);
return 0xFFFF;
}
}
void NaomiCartridge::WriteMem(u32 address, u32 data, u32 size)
{
switch(address & 255)
switch (address)
{
case 0x3c: // 5f703c: DIMM COMMAND
case NAOMI_DIMM_COMMAND:
if (0x1E03 == data)
{
/*
@ -811,20 +821,20 @@ void NaomiCartridge::WriteMem(u32 address, u32 data, u32 size)
DEBUG_LOG(NAOMI, "DIMM COMMAND Write<%d>: %x", size, data);
return;
case 0x40: // 5f7040: DIMM OFFSETL
case NAOMI_DIMM_OFFSETL:
reg_dimm_offsetl = data;
DEBUG_LOG(NAOMI, "DIMM OFFSETL Write<%d>: %x", size, data);
return;
case 0x44: // 5f7044: DIMM PARAMETERL
case NAOMI_DIMM_PARAMETERL:
reg_dimm_parameterl = data;
DEBUG_LOG(NAOMI, "DIMM PARAMETERL Write<%d>: %x", size, data);
return;
case 0x48: // 5f7048: DIMM PARAMETERH
case NAOMI_DIMM_PARAMETERH:
reg_dimm_parameterh = data;
DEBUG_LOG(NAOMI, "DIMM PARAMETERH Write<%d>: %x", size, data);
return;
case 0x4C: // 5f704c: DIMM STATUS
case NAOMI_DIMM_STATUS:
DEBUG_LOG(NAOMI, "DIMM STATUS Write<%d>: %x", size, data);
if (data&0x100)
{
@ -841,76 +851,63 @@ void NaomiCartridge::WriteMem(u32 address, u32 data, u32 size)
return;
//These are known to be valid on normal ROMs and DIMM board
case NAOMI_ROM_OFFSETH_addr&255:
case NAOMI_ROM_OFFSETH_addr:
RomPioAutoIncrement = (data & 0x8000) != 0;
RomPioOffset&=0x0000ffff;
RomPioOffset|=(data<<16)&0x7fff0000;
RomPioOffset &= 0x0000ffff;
RomPioOffset |= (data << 16) & 0x7fff0000;
PioOffsetChanged(RomPioOffset);
return;
case NAOMI_ROM_OFFSETL_addr&255:
RomPioOffset&=0xffff0000;
RomPioOffset|=data;
case NAOMI_ROM_OFFSETL_addr:
RomPioOffset &= 0xffff0000;
RomPioOffset |= data;
PioOffsetChanged(RomPioOffset);
return;
case NAOMI_ROM_DATA_addr&255:
case NAOMI_ROM_DATA_addr:
Write(RomPioOffset, size, data);
if (RomPioAutoIncrement)
RomPioOffset += 2;
return;
case NAOMI_DMA_OFFSETH_addr&255:
DmaOffset&=0x0000ffff;
DmaOffset|=(data&0x7fff)<<16;
case NAOMI_DMA_OFFSETH_addr:
DmaOffset &= 0x0000ffff;
DmaOffset |= (data & 0x7fff) << 16;
DmaOffsetChanged(DmaOffset);
return;
case NAOMI_DMA_OFFSETL_addr&255:
DmaOffset&=0xffff0000;
DmaOffset|=data;
case NAOMI_DMA_OFFSETL_addr:
DmaOffset &= 0xffff0000;
DmaOffset |= data;
DmaOffsetChanged(DmaOffset);
return;
case NAOMI_DMA_COUNT_addr&255:
{
DmaCount=data;
}
case NAOMI_DMA_COUNT_addr:
DmaCount = data;
return;
case NAOMI_BOARDID_WRITE_addr&255:
case NAOMI_BOARDID_WRITE_addr:
NaomiGameIDWrite((u16)data);
return;
//What should i do to emulate 'nothing' ?
case NAOMI_COMM_OFFSET_addr&255:
#ifdef NAOMI_COMM
DEBUG_LOG(NAOMI, "naomi COMM ofset Write: %X <= %X, %d", address, data, size);
CommOffset=data&0xFFFF;
#endif
return;
case NAOMI_COMM_DATA_addr&255:
#ifdef NAOMI_COMM
DEBUG_LOG(NAOMI, "naomi COMM data Write: %X <= %X, %d", CommOffset, data, size);
if (CommSharedMem)
{
CommSharedMem[CommOffset&0xF]=data;
}
#endif
return;
//This should be valid
case NAOMI_BOARDID_READ_addr&255:
case NAOMI_BOARDID_READ_addr:
DEBUG_LOG(NAOMI, "naomi WriteMem: %X <= %X, %d", address, data, size);
return;
case NAOMI_LED_addr & 0xff:
case NAOMI_LED_addr:
DEBUG_LOG(NAOMI, "LED %d %d %d %d %d %d %d %d", (data >> 7) & 1, (data >> 6) & 1, (data >> 5) & 1, (data >> 4) & 1,
(data >> 3) & 1, (data >> 2) & 1, (data >> 1) & 1, data & 1);
return;
default: break;
default:
break;
}
DEBUG_LOG(NAOMI, "naomi?WTF? WriteMem: %X <= %X, %d", address, data, size);
if (multiboard != nullptr)
multiboard->writeG1(address, size, data);
else
DEBUG_LOG(NAOMI, "naomiCart::WriteMem<%d>: unknown %08x <= %x", size, address, data);
}
void NaomiCartridge::Serialize(Serializer& ser) const

View File

@ -43,6 +43,8 @@ struct RomBootID
// Note: this structure is copied to system RAM by the BIOS at location 0c01f400
};
struct Game;
class Cartridge
{
public:
@ -67,6 +69,8 @@ public:
virtual void SetKeyData(u8 *key_data) { }
virtual bool GetBootId(RomBootID *bootId) = 0;
const Game *game;
protected:
u8* RomPtr;
u32 RomSize;

View File

@ -105,17 +105,17 @@ void NaomiM3Comm::sendNetwork()
u32 NaomiM3Comm::ReadMem(u32 address, u32 size)
{
switch (address & 255)
switch (address)
{
case NAOMI_COMM2_CTRL_addr & 255:
case NAOMI_COMM2_CTRL_addr:
//DEBUG_LOG(NAOMI, "NAOMI_COMM2_CTRL read");
return comm_ctrl;
case NAOMI_COMM2_OFFSET_addr & 255:
case NAOMI_COMM2_OFFSET_addr:
//DEBUG_LOG(NAOMI, "NAOMI_COMM2_OFFSET read");
return comm_offset;
case NAOMI_COMM2_DATA_addr & 255:
case NAOMI_COMM2_DATA_addr:
{
u16 value;
if (comm_ctrl & COMM_CTRL_CPU_RAM)
@ -129,11 +129,11 @@ u32 NaomiM3Comm::ReadMem(u32 address, u32 size)
return value;
}
case NAOMI_COMM2_STATUS0_addr & 255:
case NAOMI_COMM2_STATUS0_addr:
DEBUG_LOG(NAOMI, "NAOMI_COMM2_STATUS0 read %x", comm_status0);
return comm_status0;
case NAOMI_COMM2_STATUS1_addr & 255:
case NAOMI_COMM2_STATUS1_addr:
DEBUG_LOG(NAOMI, "NAOMI_COMM2_STATUS1 read %x", comm_status1);
return comm_status1;
@ -170,9 +170,9 @@ void NaomiM3Comm::connectedState()
void NaomiM3Comm::WriteMem(u32 address, u32 data, u32 size)
{
switch (address & 255)
switch (address)
{
case NAOMI_COMM2_CTRL_addr & 255:
case NAOMI_COMM2_CTRL_addr:
// bit 0: access RAM is 0 - communication RAM / 1 - M68K RAM
// bit 1: comm RAM bank (seems R/O for SH4)
// bit 5: M68K Reset
@ -192,12 +192,12 @@ void NaomiM3Comm::WriteMem(u32 address, u32 data, u32 size)
DEBUG_LOG(NAOMI, "NAOMI_COMM2_CTRL = %x", comm_ctrl);
return;
case NAOMI_COMM2_OFFSET_addr & 255:
case NAOMI_COMM2_OFFSET_addr:
comm_offset = (u16)data;
//DEBUG_LOG(NAOMI, "NAOMI_COMM2_OFFSET set to %x", comm_offset);
return;
case NAOMI_COMM2_DATA_addr & 255:
case NAOMI_COMM2_DATA_addr:
DEBUG_LOG(NAOMI, "NAOMI_COMM2_DATA written @ %04x %04x", comm_offset, (u16)data);
data = swap16(data);
if (comm_ctrl & COMM_CTRL_CPU_RAM)
@ -207,12 +207,12 @@ void NaomiM3Comm::WriteMem(u32 address, u32 data, u32 size)
comm_offset += 2;
return;
case NAOMI_COMM2_STATUS0_addr & 255:
case NAOMI_COMM2_STATUS0_addr:
comm_status0 = (u16)data;
//DEBUG_LOG(NAOMI, "NAOMI_COMM2_STATUS0 set to %x", comm_status0);
return;
case NAOMI_COMM2_STATUS1_addr & 255:
case NAOMI_COMM2_STATUS1_addr:
comm_status1 = (u16)data;
//DEBUG_LOG(NAOMI, "NAOMI_COMM2_STATUS1 set to %x", comm_status1);
return;

View File

@ -19,9 +19,17 @@ enum
NAOMI_COMM2_STATUS0_addr = 0x5F7024,
NAOMI_COMM2_STATUS1_addr = 0x5F7028,
NAOMI_DIMM_COMMAND = 0x5f703c,
NAOMI_DIMM_OFFSETL = 0x5f7040,
NAOMI_DIMM_PARAMETERL = 0x5f7044,
NAOMI_DIMM_PARAMETERH = 0x5f7048,
NAOMI_DIMM_STATUS = 0x5f704C,
NAOMI_LED_addr = 0x5F7068,
NAOMI_BOARDID_WRITE_addr = 0x5F7078,
NAOMI_BOARDID_READ_addr = 0x5F707C,
NAOMI_COMM_OFFSET_addr = 0x5F7050,
NAOMI_COMM_DATA_addr = 0x5F7054,
NAOMI_MBOARD_OFFSET_addr = 0x5F7050,
NAOMI_MBOARD_DATA_addr = 0x5F7054,
NAOMI_MBOARD_STATUS_addr = 0x5F705C,
};

View File

@ -24,7 +24,7 @@
#include "naomi_roms_eeprom.h"
#include "naomi_roms_input.h"
BIOS_t BIOS[] =
const BIOS_t BIOS[] =
{
{
"airlbios",
@ -175,7 +175,7 @@ BIOS_t BIOS[] =
}
};
Game Games[] =
const Game Games[] =
{
// Naomi M1 Roms
// Giant Gram 2000 (JPN, USA, EXP, KOR, AUS)
@ -825,7 +825,7 @@ Game Games[] =
"Airline Pilots (World, Rev B)",
0x0b000000,
0x28070e41,
"naomi",
"airlbios",
M2,
ROT0,
{
@ -859,7 +859,7 @@ Game Games[] =
"Airline Pilots (Japan, Rev A)",
0x0b000000,
0x28070e41,
"naomi",
"airlbios",
M2,
ROT0,
{
@ -1676,8 +1676,10 @@ Game Games[] =
{ NULL, 0, 0 },
},
NULL,
&f355_inputs
nullptr,
&f355_inputs,
nullptr,
3,
},
// Ferrari F355 Challenge (twin)
{
@ -1713,7 +1715,11 @@ Game Games[] =
{ "mpr-22846.ic20s", 0xa000000, 0x800000, 0xd4148f39 },
{ "mpr-22847.ic21s", 0xa800000, 0x800000, 0x955ad42e },
{ NULL, 0, 0 },
}
},
nullptr,
&f355_inputs,
nullptr,
3,
},
// Ferrari F355 Challenge (twin/deluxe, preview)
{
@ -1749,7 +1755,11 @@ Game Games[] =
{ "rom20.ic20s", 0xa000000, 0x800000, 0xd4148f39 },
{ "rom21.ic21s", 0xa800000, 0x800000, 0x955ad42e },
{ NULL, 0, 0 },
}
},
nullptr,
&f355_inputs,
nullptr,
3,
},
// Ferrari F355 Challenge 2 (twin)
{
@ -1786,7 +1796,11 @@ Game Games[] =
{ "mpr-23397.ic20s", 0xa000000, 0x800000 },
{ "mpr-23398.ic21s", 0xa800000, 0x800000 },
{ NULL, 0, 0 },
}
},
nullptr,
&f355_inputs,
nullptr,
3,
},
// Giant Gram: All Japan Pro Wrestling 2 (JPN, USA, EXP, KOR, AUS)
{

View File

@ -63,7 +63,7 @@ struct BIOS_t
u32 src_offset; // For copy
} blobs[MAX_GAME_FILES];
};
extern BIOS_t BIOS[];
extern const BIOS_t BIOS[];
struct InputDescriptors;
@ -89,5 +89,6 @@ struct Game
const char *gdrom_name;
InputDescriptors *inputs;
u8 *eeprom_dump;
int multiboard;
};
extern Game Games[];
extern const Game Games[];

View File

@ -361,32 +361,28 @@ static InputDescriptors marine_fishing_inputs = {
static InputDescriptors f355_inputs = {
{
{ NAOMI_BTN0_KEY, "ASSIST SC" },
{ NAOMI_BTN1_KEY, "ASSIST TC" },
{ NAOMI_BTN2_KEY, "ASSIST ABS" },
{ NAOMI_BTN3_KEY, "ASSIST IBS", 0, NAOMI_BTN1_KEY },
{ NAOMI_BTN4_KEY, "WING SHIFT L", 0, NAOMI_DOWN_KEY },
{ NAOMI_BTN5_KEY, "WING SHIFT R", 0, NAOMI_UP_KEY },
{ NAOMI_UP_KEY, "ASSIST SC" },
{ NAOMI_DOWN_KEY, "ASSIST TC" },
{ NAOMI_LEFT_KEY, "ASSIST ABS" },
{ NAOMI_RIGHT_KEY, "ASSIST IBS" },
// Manual gearshift (Deluxe only)
// L R
// U 1 3 5
//
// D 2 4 6
{ NAOMI_UP_KEY, "SPEED SHIFT UP" },
{ NAOMI_DOWN_KEY, "SPEED SHIFT DOWN" },
{ NAOMI_LEFT_KEY, "SPEED SHIFT LEFT" },
{ NAOMI_RIGHT_KEY, "SPEED SHIFT RIGHT" },
{ NAOMI_BTN0_KEY, "WING SHIFT L", 0, NAOMI_BTN1_KEY },
{ NAOMI_BTN1_KEY, "WING SHIFT R", 0, NAOMI_BTN0_KEY },
// manual gear shift on P2 DPad
// L R
// U 2 1
// 4 3
// D 6 5
NAO_START_DESC
NAO_BASE_BTN_DESC
},
{
{ "HANDLE", Full, 0 },
{ "ACCEL", Half, 4 },
{ "BRAKE", Half, 5 },
{ "CLUTCH", Full, 2 }, // Deluxe only
{ "unused", Full, 4 },
{ "HANDLE", Full, 0 },
},
};

View File

@ -85,7 +85,7 @@ bool GamepadDevice::handleButtonInput(int port, DreamcastKey key, bool pressed)
break;
case EMU_BTN_FFORWARD:
if (pressed && !gui_is_open())
settings.input.fastForwardMode = !settings.input.fastForwardMode && !settings.network.online;
settings.input.fastForwardMode = !settings.input.fastForwardMode && !settings.network.online && !settings.naomi.multiboard;
break;
case EMU_BTN_INSERT_CARD:
if (pressed && settings.platform.isNaomi())

View File

@ -294,6 +294,21 @@ std::vector<std::string> find_system_data_dirs()
return dirs;
}
static const char *selfPath;
void os_RunInstance(int argc, const char *argv[])
{
if (fork() == 0)
{
std::vector<char *> localArgs;
localArgs.push_back((char *)selfPath);
for (int i = 0; i < argc; i++)
localArgs.push_back((char *)argv[i]);
localArgs.push_back(nullptr);
execv(selfPath, &localArgs[0]);
}
}
#if defined(USE_BREAKPAD)
static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded)
{
@ -306,6 +321,7 @@ static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor,
int main(int argc, char* argv[])
{
selfPath = argv[0];
#if defined(__SWITCH__)
socketInitializeDefault();
nxlinkStdio();

View File

@ -370,6 +370,16 @@ void SetNaomiNetworkConfig(int node)
{
configure_maxspeed_flash(node != -1, node == 0);
}
else if (!strcmp("F355 CHALLENGE JAPAN", naomi_game_id))
{
// FIXME need default flash
write_naomi_flash(0x230, node == -1 ? 0 : node == 0 ? 1 : 2);
if (node != -1)
// car number (0 to 7)
write_naomi_flash(0x231, node);
// 0x233: cabinet type (0 deluxe, 1 twin)
write_naomi_flash(0x233, config::MultiboardSlaves >= 2 ? 0 : 1);
}
}
bool NaomiNetworkSupported()
@ -378,6 +388,7 @@ bool NaomiNetworkSupported()
"ALIEN FRONT", "MOBILE SUIT GUNDAM JAPAN", "MOBILE SUIT GUNDAM DELUXE JAPAN", " BIOHAZARD GUN SURVIVOR2",
"HEAVY METAL JAPAN", "OUTTRIGGER JAPAN", "SLASHOUT JAPAN VERSION", "SPAWN JAPAN",
"SPIKERS BATTLE JAPAN VERSION", "VIRTUAL-ON ORATORIO TANGRAM", "WAVE RUNNER GP", "WORLD KICKS",
"F355 CHALLENGE JAPAN",
// Naomi 2
"CLUB KART IN JAPAN", "INITIAL D", "INITIAL D Ver.2", "INITIAL D Ver.3", "THE KING OF ROUTE66"
};

View File

@ -31,6 +31,12 @@ int flycast_init(int argc, char* argv[])
{
return 69;
}
if (cfgLoadInt("naomi", "BoardId", 0) != 0)
{
settings.naomi.multiboard = true;
settings.naomi.slave = true;
}
config::Settings::instance().reset();
LogManager::Shutdown();
if (!cfgOpen())

View File

@ -13,6 +13,7 @@ void os_SetupInput();
void os_TermInput();
void os_InstallFaultHandler();
void os_UninstallFaultHandler();
void os_RunInstance(int argc, const char *argv[]);
#ifdef _MSC_VER
#include <intrin.h>

View File

@ -448,7 +448,7 @@ void gui_plot_render_time(int width, int height)
void gui_open_settings()
{
std::lock_guard<std::mutex> lock(guiMutex);
if (gui_state == GuiState::Closed)
if (gui_state == GuiState::Closed && !settings.naomi.slave)
{
if (!ggpo::active())
{
@ -515,7 +515,7 @@ static void gui_display_commands()
ImGui::Begin("##commands", NULL, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize);
bool loadSaveStateDisabled = settings.content.path.empty() || settings.network.online;
bool loadSaveStateDisabled = settings.content.path.empty() || settings.network.online || settings.naomi.multiboard;
if (loadSaveStateDisabled)
{
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
@ -2235,6 +2235,11 @@ static void gui_display_settings()
OptionCheckbox("Enable UPnP", config::EnableUPnP, "Automatically configure your network router for netplay");
OptionCheckbox("Broadcast Digital Outputs", config::NetworkOutput, "Broadcast digital outputs and force-feedback state on TCP port 8000. "
"Compatible with the \"-output network\" MAME option. Arcade games only.");
ImGui::Text("Multiboard Screens:");
//OptionRadioButton<int>("Disabled", config::MultiboardSlaves, 0, "Multiboard disabled (when optional)");
OptionRadioButton<int>("1 (Twin)", config::MultiboardSlaves, 1, "One screen configuration (F355 Twin)");
ImGui::SameLine();
OptionRadioButton<int>("3 (Deluxe)", config::MultiboardSlaves, 2, "Three screens configuration");
}
ImGui::Spacing();
header("Other");
@ -2797,7 +2802,7 @@ void gui_display_ui()
return;
if (gui_state == GuiState::Main)
{
if (!settings.content.path.empty())
if (!settings.content.path.empty() || settings.naomi.slave)
{
#ifndef __ANDROID__
commandLineStart = true;

View File

@ -651,13 +651,16 @@ void sdl_window_create()
void sdl_window_destroy()
{
#ifndef __SWITCH__
get_window_state();
cfgSaveInt("window", "left", windowPos.x);
cfgSaveInt("window", "top", windowPos.y);
cfgSaveInt("window", "width", windowPos.w);
cfgSaveInt("window", "height", windowPos.h);
cfgSaveBool("window", "maximized", window_maximized);
cfgSaveBool("window", "fullscreen", window_fullscreen);
if (!settings.naomi.slave)
{
get_window_state();
cfgSaveInt("window", "left", windowPos.x);
cfgSaveInt("window", "top", windowPos.y);
cfgSaveInt("window", "width", windowPos.w);
cfgSaveInt("window", "height", windowPos.h);
cfgSaveBool("window", "maximized", window_maximized);
cfgSaveBool("window", "fullscreen", window_fullscreen);
}
#endif
termRenderApi();
SDL_DestroyWindow(window);

View File

@ -197,6 +197,7 @@ enum class JVS {
LightGunAsAnalog,
WaveRunnerGP,
_18Wheeler,
F355
};
enum class RenderType {
@ -304,6 +305,13 @@ struct settings_t
u8 vmu[16];
} md5;
} network;
struct
{
bool multiboard;
bool slave;
} naomi;
bool disableRenderer;
};

View File

@ -963,3 +963,31 @@ void os_DoEvents()
}
#endif
}
void os_RunInstance(int argc, const char *argv[])
{
char exePath[MAX_PATH];
GetModuleFileNameA(NULL, exePath, sizeof(exePath));
std::string cmdLine(exePath);
for (int i = 0; i < argc; i++)
{
cmdLine += ' ';
cmdLine += argv[i];
}
STARTUPINFO startupInfo{};
startupInfo.cb = sizeof(STARTUPINFO);
PROCESS_INFORMATION processInfo{};
BOOL rc = CreateProcessA(exePath, (char *)cmdLine.c_str(), nullptr, nullptr, true, 0, nullptr, nullptr, &startupInfo, &processInfo);
if (rc)
{
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
}
else
{
WARN_LOG(BOOT, "Cannot launch Flycast instance: error %d", GetLastError());
}
}

View File

@ -106,14 +106,6 @@ extern "C" int SDL_main(int argc, char *argv[])
if (!file_exists(config_dir))
config_dir = std::string(home) + "/Library/Application Support/Flycast/";
/* Different config folder for multiple instances */
int instanceNumber = (int)[[NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.flyinghead.Flycast"] count];
if (instanceNumber > 1)
{
config_dir += std::to_string(instanceNumber) + "/";
[[NSApp dockTile] setBadgeLabel:@(instanceNumber).stringValue];
}
mkdir(config_dir.c_str(), 0755); // create the directory if missing
set_user_config_dir(config_dir);
add_system_data_dir(config_dir);
@ -182,3 +174,20 @@ std::string os_Locale(){
std::string os_PrecomposedString(std::string string){
return [[[NSString stringWithUTF8String:string.c_str()] precomposedStringWithCanonicalMapping] UTF8String];
}
void os_RunInstance(int argc, const char *argv[])
{
if (fork() == 0)
{
std::vector<char *> localArgs;
NSArray *arguments = [[NSProcessInfo processInfo] arguments];
const char *selfPath = [[arguments objectAtIndex:0] UTF8String];
localArgs.push_back((char *)selfPath);
for (int i = 0; i < argc; i++)
localArgs.push_back((char *)argv[i]);
localArgs.push_back(nullptr);
execv(selfPath, &localArgs[0]);
ERROR_LOG(BOOT, "Error %d launching Flycast instance %s", errno, selfPath);
die("execv failed");
}
}

View File

@ -3243,3 +3243,5 @@ void gui_display_notification(const char *msg, int duration)
retromsg.frames = duration / 17;
environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE, &retromsg);
}
void os_RunInstance(int argc, const char *argv[]) { }

View File

@ -119,6 +119,7 @@ Option<int> GGPODelay("", 0);
Option<bool> NetworkStats("", false);
Option<int> GGPOAnalogAxes("", 0);
Option<bool> NetworkOutput(CORE_OPTION_NAME "_network_output", false);
Option<int> MultiboardSlaves("", 0);
// Maple