diff --git a/core/cfg/option.cpp b/core/cfg/option.cpp
index 8420e4de5..426ac8ff0 100644
--- a/core/cfg/option.cpp
+++ b/core/cfg/option.cpp
@@ -17,6 +17,7 @@
along with Flycast. If not, see .
*/
#include "option.h"
+#include "network/naomi_network.h"
namespace config {
@@ -117,7 +118,9 @@ Option NetworkEnable("Enable", false, "network");
Option ActAsServer("ActAsServer", false, "network");
OptionString DNS("DNS", "46.101.91.123", "network");
OptionString NetworkServer("server", "", "network");
+Option LocalPort("LocalPort", NaomiNetwork::SERVER_PORT, "network");
Option EmulateBBA("EmulateBBA", false, "network");
+Option EnableUPnP("EnableUPnP", true, "network");
Option GGPOEnable("GGPO", false, "network");
Option GGPODelay("GGPODelay", 0, "network");
Option NetworkStats("Stats", true, "network");
diff --git a/core/cfg/option.h b/core/cfg/option.h
index 1e9bfd275..9d2076c5e 100644
--- a/core/cfg/option.h
+++ b/core/cfg/option.h
@@ -462,7 +462,9 @@ extern Option NetworkEnable;
extern Option ActAsServer;
extern OptionString DNS;
extern OptionString NetworkServer;
+extern Option LocalPort;
extern Option EmulateBBA;
+extern Option EnableUPnP;
extern Option GGPOEnable;
extern Option GGPODelay;
extern Option NetworkStats;
diff --git a/core/cheats.cpp b/core/cheats.cpp
index 78c03095d..f52ea9b21 100644
--- a/core/cheats.cpp
+++ b/core/cheats.cpp
@@ -25,6 +25,7 @@
#include "reios/reios.h"
#include "cfg/cfg.h"
#include "cfg/ini.h"
+#include "emulator.h"
const WidescreenCheat CheatManager::widescreen_cheats[] =
{
@@ -315,6 +316,20 @@ const WidescreenCheat CheatManager::naomi_widescreen_cheats[] =
};
CheatManager cheatManager;
+static void vblankCallback(Event event, void *param)
+{
+ ((CheatManager *)param)->apply();
+}
+
+void CheatManager::setActive(bool active)
+{
+ this->active = active;
+ if (active || widescreen_cheat != nullptr)
+ EventManager::listen(Event::VBlank, vblankCallback, this);
+ else
+ EventManager::unlisten(Event::VBlank, vblankCallback, this);
+}
+
void CheatManager::loadCheatFile(const std::string& filename)
{
#ifndef LIBRETRO
@@ -360,7 +375,7 @@ void CheatManager::loadCheatFile(const std::string& filename)
if (cheat.type != Cheat::Type::disabled)
cheats.push_back(cheat);
}
- active = !cheats.empty();
+ setActive(!cheats.empty());
INFO_LOG(COMMON, "%d cheats loaded", (int)cheats.size());
cfgSaveStr("cheats", gameId, filename);
#endif
@@ -368,10 +383,11 @@ void CheatManager::loadCheatFile(const std::string& filename)
void CheatManager::reset(const std::string& gameId)
{
+ widescreen_cheat = nullptr;
if (this->gameId != gameId)
{
cheats.clear();
- active = false;
+ setActive(false);
this->gameId = gameId;
#ifndef LIBRETRO
std::string cheatFile = cfgLoadStr("cheats", gameId, "");
@@ -380,51 +396,51 @@ void CheatManager::reset(const std::string& gameId)
#endif
if (gameId == "VF4 FINAL TUNED JAPAN")
{
- active = true;
+ setActive(true);
cheats.emplace_back(Cheat::Type::setValue, "Skip DIMM version check", true, 16, 0x000205c6, 9);
}
}
- widescreen_cheat = nullptr;
- if (!config::WidescreenGameHacks)
- return;
- if (settings.platform.isConsole())
+ if (config::WidescreenGameHacks)
{
- for (int i = 0; widescreen_cheats[i].game_id != nullptr; i++)
+ if (settings.platform.isConsole())
{
- if (!strcmp(gameId.c_str(), widescreen_cheats[i].game_id)
- && (widescreen_cheats[i].area_or_version == nullptr
- || !strncmp(ip_meta.area_symbols, widescreen_cheats[i].area_or_version, sizeof(ip_meta.area_symbols))
- || !strncmp(ip_meta.product_version, widescreen_cheats[i].area_or_version, sizeof(ip_meta.product_version))))
+ for (int i = 0; widescreen_cheats[i].game_id != nullptr; i++)
{
- widescreen_cheat = &widescreen_cheats[i];
- NOTICE_LOG(COMMON, "Applying widescreen hack to game %s", gameId.c_str());
- break;
+ if (!strcmp(gameId.c_str(), widescreen_cheats[i].game_id)
+ && (widescreen_cheats[i].area_or_version == nullptr
+ || !strncmp(ip_meta.area_symbols, widescreen_cheats[i].area_or_version, sizeof(ip_meta.area_symbols))
+ || !strncmp(ip_meta.product_version, widescreen_cheats[i].area_or_version, sizeof(ip_meta.product_version))))
+ {
+ widescreen_cheat = &widescreen_cheats[i];
+ NOTICE_LOG(COMMON, "Applying widescreen hack to game %s", gameId.c_str());
+ break;
+ }
}
}
- }
- else
- {
- std::string romName = get_file_basename(settings.content.path);
- size_t folder_pos = get_last_slash_pos(romName);
- if (folder_pos != std::string::npos)
- romName = romName.substr(folder_pos + 1);
+ else
+ {
+ std::string romName = get_file_basename(settings.content.path);
+ size_t folder_pos = get_last_slash_pos(romName);
+ if (folder_pos != std::string::npos)
+ romName = romName.substr(folder_pos + 1);
- for (int i = 0; naomi_widescreen_cheats[i].game_id != nullptr; i++)
- {
- if (!strcmp(gameId.c_str(), naomi_widescreen_cheats[i].game_id)
- && (naomi_widescreen_cheats[i].area_or_version == nullptr
- || !strcmp(romName.c_str(), naomi_widescreen_cheats[i].area_or_version)))
+ for (int i = 0; naomi_widescreen_cheats[i].game_id != nullptr; i++)
{
- widescreen_cheat = &naomi_widescreen_cheats[i];
- NOTICE_LOG(COMMON, "Applying widescreen hack to game %s", gameId.c_str());
- break;
+ if (!strcmp(gameId.c_str(), naomi_widescreen_cheats[i].game_id)
+ && (naomi_widescreen_cheats[i].area_or_version == nullptr
+ || !strcmp(romName.c_str(), naomi_widescreen_cheats[i].area_or_version)))
+ {
+ widescreen_cheat = &naomi_widescreen_cheats[i];
+ NOTICE_LOG(COMMON, "Applying widescreen hack to game %s", gameId.c_str());
+ break;
+ }
}
}
+ if (widescreen_cheat != nullptr)
+ for (size_t i = 0; i < ARRAY_SIZE(widescreen_cheat->addresses) && widescreen_cheat->addresses[i] != 0; i++)
+ verify(widescreen_cheat->addresses[i] < RAM_SIZE);
}
- if (widescreen_cheat == nullptr)
- return;
- for (size_t i = 0; i < ARRAY_SIZE(widescreen_cheat->addresses) && widescreen_cheat->addresses[i] != 0; i++)
- verify(widescreen_cheat->addresses[i] < RAM_SIZE);
+ setActive(active);
}
u32 CheatManager::readRam(u32 addr, u32 bits)
@@ -756,7 +772,7 @@ void CheatManager::addGameSharkCheat(const std::string& name, const std::string&
throw FlycastException("Unsupported cheat type");
}
}
- active = !cheats.empty();
+ setActive(!cheats.empty());
#ifndef LIBRETRO
std::string path = cfgLoadStr("cheats", gameId, "");
if (path == "")
diff --git a/core/cheats.h b/core/cheats.h
index 5e091e947..a3fbdc997 100644
--- a/core/cheats.h
+++ b/core/cheats.h
@@ -81,6 +81,7 @@ public:
private:
u32 readRam(u32 addr, u32 bits);
void writeRam(u32 addr, u32 value, u32 bits);
+ void setActive(bool active);
static const WidescreenCheat widescreen_cheats[];
static const WidescreenCheat naomi_widescreen_cheats[];
diff --git a/core/emulator.cpp b/core/emulator.cpp
index 77cbc17cf..52656209a 100644
--- a/core/emulator.cpp
+++ b/core/emulator.cpp
@@ -38,7 +38,6 @@
#include "hw/mem/mem_watch.h"
#include "network/net_handshake.h"
#include "rend/gui.h"
-#include "lua/lua.h"
#include "network/naomi_network.h"
#include "serialize.h"
#include "hw/pvr/pvr.h"
@@ -831,7 +830,7 @@ bool Emulator::render()
void Emulator::vblank()
{
- lua::vblank();
+ EventManager::event(Event::VBlank);
// Time out if a frame hasn't been rendered for 50 ms
if (sh4_sched_now64() - startTime <= 10000000)
return;
diff --git a/core/emulator.h b/core/emulator.h
index 4946c32cc..cb85b2874 100644
--- a/core/emulator.h
+++ b/core/emulator.h
@@ -45,6 +45,7 @@ enum class Event {
Resume,
Terminate,
LoadState,
+ VBlank,
};
class EventManager
diff --git a/core/hw/aica/aica_if.h b/core/hw/aica/aica_if.h
index 98b267666..532f26858 100644
--- a/core/hw/aica/aica_if.h
+++ b/core/hw/aica/aica_if.h
@@ -13,6 +13,7 @@ void WriteMem_aica_reg(u32 addr,u32 data,u32 sz);
void aica_Init();
void aica_Reset(bool hard);
void aica_Term();
+void aica_setMidiReceiver(void (*handler)(u8 data));
void aica_sb_Init();
void aica_sb_Reset(bool hard);
diff --git a/core/hw/aica/sgc_if.cpp b/core/hw/aica/sgc_if.cpp
index 6e44c5ffb..b87170f93 100755
--- a/core/hw/aica/sgc_if.cpp
+++ b/core/hw/aica/sgc_if.cpp
@@ -127,6 +127,8 @@ static const s32 qtable[32] = {
0x1C00,0x1D00,0x1E00,0x1F00
};
+static void (*midiReceiver)(u8 data);
+
//Remove the fractional part by chopping..
static SampleType FPs(SampleType a, int bits) {
return a >> bits;
@@ -779,7 +781,6 @@ struct ChannelEx
FEG.ReleaseRate = FEG_SPS[EG_EffRate(base_rate, ccd->FRR)];
}
- //WHEE :D!
void RegWrite(u32 offset, int size)
{
switch(offset)
@@ -1024,7 +1025,7 @@ void StreamStep(ChannelEx* ch)
else
{
CA = ch->loop.LSA;
- key_printf("[%d]LPCTL : Looping LSA %x LEA %x", ch->ChannelNumber, ch->loop.LSA, ch->loop.LEA);
+ key_printf("[%d]LPCTL : Looping LSA %x LEA %x AEG %x", ch->ChannelNumber, ch->loop.LSA, ch->loop.LEA, ch->AEG.GetValue());
}
}
@@ -1040,52 +1041,60 @@ void StreamStep(ChannelEx* ch)
}
-template
+enum class LFOType
+{
+ Sawtooth,
+ Square,
+ Triangle,
+ Random
+};
+
+template
void CalcAlfo(ChannelEx* ch)
{
u32 rv;
- switch(ALFOWS)
+ switch(Type)
{
- case 0: // Sawtooth
+ case LFOType::Sawtooth:
rv=ch->lfo.state;
break;
- case 1: // Square
+ case LFOType::Square:
rv=ch->lfo.state&0x80?255:0;
break;
- case 2: // Triangle
+ case LFOType::Triangle:
rv=(ch->lfo.state&0x7f)^(ch->lfo.state&0x80 ? 0x7F:0);
rv<<=1;
break;
- case 3:// Random ! .. not :p
+ case LFOType::Random: // ... not so much
rv=(ch->lfo.state>>3)^(ch->lfo.state<<3)^(ch->lfo.state&0xE3);
break;
}
ch->lfo.alfo=rv>>ch->lfo.alfo_shft;
}
-template
+template
void CalcPlfo(ChannelEx* ch)
{
u32 rv;
- switch(PLFOWS)
+ switch(Type)
{
- case 0: // sawtooth
+ case LFOType::Sawtooth:
rv = ch->lfo.state;
break;
- case 1: // square
+ case LFOType::Square:
rv = ch->lfo.state & 0x80 ? 0xff : 0;
break;
- case 2: // triangle
+ case LFOType::Triangle:
rv = (ch->lfo.state & 0x7f) ^ (ch->lfo.state & 0x80 ? 0x7F : 0);
rv <<= 1;
break;
- case 3:// random ! .. not :p
+ case LFOType::Random:
rv = (ch->lfo.state >> 3) ^ (ch->lfo.state << 3) ^ (ch->lfo.state & 0xE3);
break;
}
@@ -1220,25 +1229,25 @@ static void staticinitialise()
STREAM_INITAL_STEP_LUT[3]=&StepDecodeSampleInitial<3>;
STREAM_INITAL_STEP_LUT[4]=&StepDecodeSampleInitial<-1>;
- AEG_STEP_LUT[0]=&AegStep<0>;
- AEG_STEP_LUT[1]=&AegStep<1>;
- AEG_STEP_LUT[2]=&AegStep<2>;
- AEG_STEP_LUT[3]=&AegStep<3>;
+ AEG_STEP_LUT[EG_Attack] = &AegStep;
+ AEG_STEP_LUT[EG_Decay1] = &AegStep;
+ AEG_STEP_LUT[EG_Decay2] = &AegStep;
+ AEG_STEP_LUT[EG_Release] = &AegStep;
- FEG_STEP_LUT[0]=&FegStep<0>;
- FEG_STEP_LUT[1]=&FegStep<1>;
- FEG_STEP_LUT[2]=&FegStep<2>;
- FEG_STEP_LUT[3]=&FegStep<3>;
+ FEG_STEP_LUT[EG_Attack] = &FegStep;
+ FEG_STEP_LUT[EG_Decay1] = &FegStep;
+ FEG_STEP_LUT[EG_Decay2] = &FegStep;
+ FEG_STEP_LUT[EG_Release] = &FegStep;
- ALFOWS_CALC[0]=&CalcAlfo<0>;
- ALFOWS_CALC[1]=&CalcAlfo<1>;
- ALFOWS_CALC[2]=&CalcAlfo<2>;
- ALFOWS_CALC[3]=&CalcAlfo<3>;
+ ALFOWS_CALC[(int)LFOType::Sawtooth] = &CalcAlfo;
+ ALFOWS_CALC[(int)LFOType::Square] = &CalcAlfo;
+ ALFOWS_CALC[(int)LFOType::Triangle] = &CalcAlfo;
+ ALFOWS_CALC[(int)LFOType::Random] = &CalcAlfo;
- PLFOWS_CALC[0]=&CalcPlfo<0>;
- PLFOWS_CALC[1]=&CalcPlfo<1>;
- PLFOWS_CALC[2]=&CalcPlfo<2>;
- PLFOWS_CALC[3]=&CalcPlfo<3>;
+ PLFOWS_CALC[(int)LFOType::Sawtooth] = &CalcPlfo;
+ PLFOWS_CALC[(int)LFOType::Square] = &CalcPlfo;
+ PLFOWS_CALC[(int)LFOType::Triangle] = &CalcPlfo;
+ PLFOWS_CALC[(int)LFOType::Random] = &CalcPlfo;
}
ChannelEx ChannelEx::Chans[64];
@@ -1312,6 +1321,7 @@ void sgc_Init()
beepCounter = 0;
dsp::init();
+ midiReceiver = nullptr;
}
void sgc_Term()
@@ -1375,6 +1385,10 @@ void WriteCommonReg8(u32 reg,u32 data)
state.RBP = (CommonData->RBP * 2048) & ARAM_MASK;
state.dirty = true;
}
+ else if (reg == 0x280c) { // MOBUF
+ if (midiReceiver != nullptr)
+ midiReceiver(data);
+ }
}
void vmuBeep(int on, int period)
@@ -1766,3 +1780,7 @@ void channel_deserialize(Deserializer& deser)
deser.skip(4); // samples_gen
}
}
+
+void aica_setMidiReceiver(void (*handler)(u8 data)) {
+ midiReceiver = handler;
+}
diff --git a/core/hw/naomi/naomi.cpp b/core/hw/naomi/naomi.cpp
index 6a9b7f574..32ed12b12 100644
--- a/core/hw/naomi/naomi.cpp
+++ b/core/hw/naomi/naomi.cpp
@@ -9,12 +9,12 @@
#include "hw/maple/maple_cfg.h"
#include "hw/sh4/sh4_sched.h"
#include "hw/sh4/modules/dmac.h"
+#include "hw/aica/aica_if.h"
#include "naomi.h"
#include "naomi_cart.h"
#include "naomi_regs.h"
#include "naomi_m3comm.h"
-#include "network/naomi_network.h"
#include "serialize.h"
//#define NAOMI_COMM
@@ -46,6 +46,9 @@ P-Z (0x50-0x5A)
static u8 BSerial[]="\xB7"/*CRC1*/"\x19"/*CRC2*/"0123234437897584372973927387463782196719782697849162342198671923649";
static u8 GSerial[]="\xB7"/*CRC1*/"\x19"/*CRC2*/"0123234437897584372973927387463782196719782697849162342198671923649";
+static u8 midiTxBuf[4];
+static u32 midiTxBufIndex;
+
static unsigned int ShiftCRC(unsigned int CRC,unsigned int rounds)
{
const unsigned int Magic=0x10210000;
@@ -537,7 +540,6 @@ void naomi_reg_Term()
}
#endif
m3comm.closeNetwork();
- naomiNetwork.terminate();
}
void naomi_reg_Reset(bool hard)
@@ -570,7 +572,6 @@ void naomi_reg_Reset(bool hard)
reg_dimm_parameterh = 0;
reg_dimm_status = 0x11;
m3comm.closeNetwork();
- naomiNetwork.terminate();
if (hard)
naomi_cart_Close();
}
@@ -686,6 +687,8 @@ void naomi_Serialize(Serializer& ser)
ser << aw_maple_devs;
ser << coin_chute_time;
ser << aw_ram_test_skipped;
+ ser << midiTxBuf;
+ ser << midiTxBufIndex;
// TODO serialize m3comm?
}
void naomi_Deserialize(Deserializer& deser)
@@ -727,4 +730,33 @@ void naomi_Deserialize(Deserializer& deser)
deser >> coin_chute_time;
deser >> aw_ram_test_skipped;
}
+ if (deser.version() >= Deserializer::V27)
+ {
+ deser >> midiTxBuf;
+ deser >> midiTxBufIndex;
+ }
+ else
+ {
+ midiTxBufIndex = 0;
+ }
+}
+
+static void initFFBMidiReceiver(u8 data)
+{
+ if (data & 0x80)
+ midiTxBufIndex = 0;
+ midiTxBuf[midiTxBufIndex] = data;
+ if (midiTxBufIndex == 3 && ((midiTxBuf[0] ^ midiTxBuf[1] ^ midiTxBuf[2]) & 0x7f) == midiTxBuf[3])
+ {
+ // decoding from FFB Arcade Plugin (by Boomslangnz)
+ // https://github.com/Boomslangnz/FFBArcadePlugin/blob/master/Game%20Files/Demul.cpp
+ if (midiTxBuf[0] == 0x85 && midiTxBuf[1] == 0x3f)
+ MapleConfigMap::UpdateVibration(0, std::max(0.f, (float)(midiTxBuf[2] - 1) / 24.f), 0.f, 5);
+ }
+ midiTxBufIndex = (midiTxBufIndex + 1) % ARRAY_SIZE(midiTxBuf);
+}
+
+void initdFFBInit()
+{
+ aica_setMidiReceiver(initFFBMidiReceiver);
}
diff --git a/core/hw/naomi/naomi.h b/core/hw/naomi/naomi.h
index cc7d2c076..514d4c67f 100644
--- a/core/hw/naomi/naomi.h
+++ b/core/hw/naomi/naomi.h
@@ -25,3 +25,5 @@ extern u32 reg_dimm_offsetl;
extern u32 reg_dimm_parameterl;
extern u32 reg_dimm_parameterh;
extern u32 reg_dimm_status;
+
+void initdFFBInit();
diff --git a/core/hw/naomi/naomi_cart.cpp b/core/hw/naomi/naomi_cart.cpp
index 824f1b423..d57d15543 100644
--- a/core/hw/naomi/naomi_cart.cpp
+++ b/core/hw/naomi/naomi_cart.cpp
@@ -581,6 +581,7 @@ void naomi_cart_LoadRom(const char* file, LoadProgress *progress)
|| gameId == "INITIAL D CYCRAFT")
{
card_reader::initialDCardReader.init();
+ initdFFBInit();
}
}
else
diff --git a/core/hw/naomi/naomi_m3comm.cpp b/core/hw/naomi/naomi_m3comm.cpp
index 014f3430e..1bc68aeec 100644
--- a/core/hw/naomi/naomi_m3comm.cpp
+++ b/core/hw/naomi/naomi_m3comm.cpp
@@ -18,69 +18,86 @@
You should have received a copy of the GNU General Public License
along with flycast. If not, see .
*/
+//
+// Optical communication board (837-13691)
+// Ring topology
+// 10 Mbps
+// Max packet size 0x4000
+//
#include "naomi_m3comm.h"
#include "naomi_regs.h"
#include "hw/holly/sb.h"
#include "hw/sh4/sh4_mem.h"
#include "network/naomi_network.h"
+#include "emulator.h"
#include
+#include
+
+constexpr u16 COMM_CTRL_CPU_RAM = 1 << 0;
+constexpr u16 COMM_CTRL_RESET = 1 << 5; // rising edge
+constexpr u16 COMM_CTRL_G1DMA = 1 << 14; // active low
+
+struct CommBoardStat
+{
+ u16 transmode; // communication mode (0: master, positive value: slave)
+ u16 totalnode; // Total number of nodes (same value is entered in upper and lower 8 bits)
+ u16 nodeID; // Local node ID (the same value is entered in the upper and lower 8 bits)
+ u16 transcnt; // counter (value increases by 1 per frame)
+ u16 cts; // CTS timer value (for debugging)
+ u16 dma_rx_addr; // DMA receive address (for debugging)
+ u16 dma_rx_size; // DMA receive size (for debugging)
+ u16 dma_tx_addr; // DMA transmit address (for debugging)
+ u16 dma_tx_size; // DMA transmission size (for debugging)
+ u16 dummy[7];
+};
static inline u16 swap16(u16 w)
{
return (w >> 8) | (w << 8);
}
+static void vblankCallback(Event event, void *param) {
+ ((NaomiM3Comm *)param)->vblank();
+}
+
void NaomiM3Comm::closeNetwork()
{
- network_stopping = true;
+ EventManager::unlisten(Event::VBlank, vblankCallback, this);
naomiNetwork.shutdown();
- if (thread && thread->joinable())
- thread->join();
}
void NaomiM3Comm::connectNetwork()
{
+ gui_display_notification("Network started", 5000);
packet_number = 0;
- if (naomiNetwork.syncNetwork())
- {
- slot_count = naomiNetwork.slotCount();
- slot_id = naomiNetwork.slotId();
- connectedState(true);
- }
- else
- {
- connectedState(false);
- network_stopping = true;
- naomiNetwork.shutdown();
- }
+ slot_count = naomiNetwork.getSlotCount();
+ slot_id = naomiNetwork.getSlotId();
+ connectedState(true);
+ EventManager::listen(Event::VBlank, vblankCallback, this);
}
-void NaomiM3Comm::receiveNetwork()
+bool NaomiM3Comm::receiveNetwork()
{
const u32 slot_size = swap16(*(u16*)&m68k_ram[0x204]);
const u32 packet_size = slot_size * slot_count;
std::unique_ptr buf(new u8[packet_size]);
- if (naomiNetwork.receive(buf.get(), packet_size))
- {
- packet_number += slot_count - 1;
- *(u16*)&comm_ram[6] = swap16(packet_number);
- std::unique_lock lock(mem_mutex);
- memcpy(&comm_ram[0x100 + slot_size], buf.get(), packet_size);
- }
+ u16 packetNumber;
+ if (!naomiNetwork.receive(buf.get(), packet_size, &packetNumber))
+ return false;
+
+ *(u16*)&comm_ram[6] = swap16(packetNumber);
+ memcpy(&comm_ram[0x100 + slot_size], buf.get(), packet_size);
+
+ return true;
}
void NaomiM3Comm::sendNetwork()
{
- if (naomiNetwork.hasToken())
- {
- const u32 packet_size = swap16(*(u16*)&m68k_ram[0x204]) * slot_count;
- std::unique_lock lock(mem_mutex);
- naomiNetwork.send(&comm_ram[0x100], packet_size);
- packet_number++;
- *(u16*)&comm_ram[6] = swap16(packet_number);
- }
+ const u32 packet_size = swap16(*(u16*)&m68k_ram[0x204]) * slot_count;
+ naomiNetwork.send(&comm_ram[0x100], packet_size, packet_number);
+ packet_number++;
}
u32 NaomiM3Comm::ReadMem(u32 address, u32 size)
@@ -98,13 +115,13 @@ u32 NaomiM3Comm::ReadMem(u32 address, u32 size)
case NAOMI_COMM2_DATA_addr & 255:
{
u16 value;
- if (comm_ctrl & 1)
+ if (comm_ctrl & COMM_CTRL_CPU_RAM)
value = *(u16*)&m68k_ram[comm_offset];
else
// TODO u16 *commram = (u16*)membank("comm_ram")->base();
value = *(u16*)&comm_ram[comm_offset];
value = swap16(value);
- DEBUG_LOG(NAOMI, "NAOMI_COMM2_DATA %s read @ %04x: %x", (comm_ctrl & 1) ? "m68k ram" : "comm ram", comm_offset, value);
+ DEBUG_LOG(NAOMI, "NAOMI_COMM2_DATA %s read @ %04x: %x", (comm_ctrl & COMM_CTRL_CPU_RAM) ? "m68k ram" : "comm ram", comm_offset, value);
comm_offset += 2;
return value;
}
@@ -127,7 +144,7 @@ void NaomiM3Comm::connectedState(bool success)
{
if (!success)
return;
-
+ verify(slot_count >= 2);
memset(&comm_ram[0xf000], 0, 16);
comm_ram[0xf000] = 1;
comm_ram[0xf001] = 1;
@@ -136,39 +153,16 @@ void NaomiM3Comm::connectedState(bool success)
u32 slot_size = swap16(*(u16*)&m68k_ram[0x204]);
- memset(&comm_ram[0], 0, 32);
- // 80000
- comm_ram[0] = 0;
- comm_ram[1] = slot_id == 0 ? 0 : 1;
- // 80002
- comm_ram[2] = 0x01;
- comm_ram[3] = 0x01;
- // 80004
- if (slot_id == 0)
- {
- comm_ram[4] = 0;
- comm_ram[5] = 0;
- }
- else
- {
- comm_ram[4] = 1;
- comm_ram[5] = 1;
- }
- // 80006: packet number
- comm_ram[6] = 0;
- comm_ram[7] = 0;
- // 80008
- comm_ram[8] = slot_id == 0 ? 0x78 : 0x73;
- comm_ram[9] = slot_id == 0 ? 0x30 : 0xa2;
- // 8000A
- *(u16 *)(comm_ram + 10) = 0x100 + slot_size; // offset of recvd data
- // 8000C
- *(u16 *)(comm_ram + 12) = slot_size * slot_count; // recvd data size
- // 8000E
- *(u16 *)(comm_ram + 14) = 0x100; // offset of sent data
- // 80010
- *(u16 *)(comm_ram + 16) = 0x80 + slot_size * slot_count; // sent data size
- // FIXME wrungp uses 100, others 80
+ CommBoardStat& stat = *(CommBoardStat *)&comm_ram[0];
+ memset(&stat, 0, sizeof(stat));
+ stat.transmode = swap16(slot_id == 0 ? 0 : 1);
+ stat.totalnode = slot_count | (slot_count << 8);
+ stat.nodeID = slot_id | (slot_id << 8);
+ stat.cts = swap16(slot_id == 0 ? 0x7830 : 0x73a2);
+ stat.dma_rx_addr = swap16(0x100 + slot_size);
+ stat.dma_rx_size = swap16(slot_size * slot_count);
+ stat.dma_tx_addr = swap16(0x100);
+ stat.dma_tx_size = swap16(slot_size * slot_count);
comm_status0 = 0xff01; // But 1 at connect time before f000 is read
comm_status1 = (slot_count << 8) | slot_id;
@@ -183,20 +177,19 @@ void NaomiM3Comm::WriteMem(u32 address, u32 data, u32 size)
// bit 1: comm RAM bank (seems R/O for SH4)
// bit 5: M68K Reset
// bit 6: ???
- // bit 7: might be M68K IRQ 5 or 2
+ // bit 7: might be M68K IRQ 5 or 2 - set to 0 by nlCbIntr()
// bit 14: G1 DMA bus master 0 - active / 1 - disabled
// bit 15: 0 - enable / 1 - disable this device ???
- if (data & (1 << 5))
+ if ((comm_ctrl & COMM_CTRL_RESET) == 0 && (data & COMM_CTRL_RESET) != 0)
{
DEBUG_LOG(NAOMI, "NAOMI_COMM2_CTRL m68k reset");
memset(&comm_ram[0], 0, 32);
comm_status0 = 0; // varies...
comm_status1 = 0;
- if (!thread || !thread->joinable())
- startThread();
+ connectNetwork();
}
- comm_ctrl = (u16)(data & ~(1 << 5));
- //DEBUG_LOG(NAOMI, "NAOMI_COMM2_CTRL set to %x", comm_ctrl);
+ comm_ctrl = (u16)data;
+ DEBUG_LOG(NAOMI, "NAOMI_COMM2_CTRL = %x", comm_ctrl);
return;
case NAOMI_COMM2_OFFSET_addr & 255:
@@ -207,7 +200,7 @@ void NaomiM3Comm::WriteMem(u32 address, u32 data, u32 size)
case NAOMI_COMM2_DATA_addr & 255:
DEBUG_LOG(NAOMI, "NAOMI_COMM2_DATA written @ %04x %04x", comm_offset, (u16)data);
data = swap16(data);
- if (comm_ctrl & 1)
+ if (comm_ctrl & COMM_CTRL_CPU_RAM)
*(u16*)&m68k_ram[comm_offset] = (u16)data;
else
*(u16*)&comm_ram[comm_offset] = (u16)data;
@@ -232,11 +225,10 @@ void NaomiM3Comm::WriteMem(u32 address, u32 data, u32 size)
bool NaomiM3Comm::DmaStart(u32 addr, u32 data)
{
- if (comm_ctrl & 0x4000)
+ if (comm_ctrl & COMM_CTRL_G1DMA)
return false;
DEBUG_LOG(NAOMI, "NaomiM3Comm: DMA addr %08X <-> %04x len %d %s", SB_GDSTAR, comm_offset, SB_GDLEN, SB_GDDIR == 0 ? "OUT" : "IN");
- std::unique_lock lock(mem_mutex);
if (SB_GDDIR == 0)
{
// Network write
@@ -246,7 +238,8 @@ bool NaomiM3Comm::DmaStart(u32 addr, u32 data)
else
{
// Network read
- if (SB_GDLEN == 32 && (comm_ctrl & 1) == 0)
+ /*
+ if (SB_GDLEN == 32 && (comm_ctrl & COMM_CTRL_CPU_RAM) == 0)
{
char buf[32 * 5 + 1];
buf[0] = 0;
@@ -257,42 +250,30 @@ bool NaomiM3Comm::DmaStart(u32 addr, u32 data)
}
DEBUG_LOG(NAOMI, "Comm RAM read @%x: %s", comm_offset, buf);
}
+ */
for (u32 i = 0; i < SB_GDLEN; i++)
WriteMem8_nommu(SB_GDSTAR + i, comm_ram[comm_offset++]);
}
return true;
}
-void NaomiM3Comm::startThread()
+void NaomiM3Comm::vblank()
{
- network_stopping = false;
- thread = std::unique_ptr(new std::thread([this]() {
- using the_clock = std::chrono::high_resolution_clock;
+ if ((comm_ctrl & COMM_CTRL_RESET) == 0 || comm_status1 == 0)
+ return;
- connectNetwork();
-
- the_clock::time_point token_time = the_clock::now();
-
- while (!network_stopping)
- {
- naomiNetwork.pipeSlaves();
- receiveNetwork();
-
- if (slot_id == 0 && naomiNetwork.hasToken())
- {
- const auto target_duration = std::chrono::milliseconds(10);
- auto duration = the_clock::now() - token_time;
- if (duration < target_duration)
- {
- DEBUG_LOG(NAOMI, "Sleeping for %ld ms", (long)std::chrono::duration_cast(target_duration - duration).count());
- std::this_thread::sleep_for(target_duration - duration);
- }
- token_time = the_clock::now();
- }
-
- sendNetwork();
-
- }
- DEBUG_LOG(NAOMI, "Network thread exiting");
- }));
+ using the_clock = std::chrono::high_resolution_clock;
+ the_clock::time_point start = the_clock::now();
+ try {
+ bool received = false;
+ do {
+ received = receiveNetwork();
+ } while (!received && the_clock::now() - start < std::chrono::milliseconds(100));
+ if (!received)
+ INFO_LOG(NETWORK, "No data received");
+ sendNetwork();
+ } catch (const FlycastException& e) {
+ comm_status0 = 0;
+ comm_status1 = 0;
+ }
}
diff --git a/core/hw/naomi/naomi_m3comm.h b/core/hw/naomi/naomi_m3comm.h
index 962b1a1f6..900988352 100644
--- a/core/hw/naomi/naomi_m3comm.h
+++ b/core/hw/naomi/naomi_m3comm.h
@@ -20,10 +20,6 @@
*/
#pragma once
#include "types.h"
-#include
-#include
-#include
-#include
class NaomiM3Comm
{
@@ -33,13 +29,13 @@ public:
bool DmaStart(u32 addr, u32 data);
void closeNetwork();
+ void vblank();
private:
void connectNetwork();
- void receiveNetwork();
+ bool receiveNetwork();
void sendNetwork();
void connectedState(bool success);
- void startThread();
u16 comm_ctrl = 0xC000;
u16 comm_offset = 0;
@@ -51,7 +47,4 @@ private:
int slot_count = 0;
int slot_id = 0;
- std::atomic network_stopping{ false };
- std::unique_ptr thread;
- std::mutex mem_mutex;
};
diff --git a/core/hw/pvr/Renderer_if.cpp b/core/hw/pvr/Renderer_if.cpp
index 8fca23174..7f012999e 100644
--- a/core/hw/pvr/Renderer_if.cpp
+++ b/core/hw/pvr/Renderer_if.cpp
@@ -1,6 +1,5 @@
#include "Renderer_if.h"
#include "spg.h"
-#include "cheats.h"
#include "hw/pvr/pvr_mem.h"
#include "rend/TexCache.h"
#include "cfg/option.h"
@@ -276,7 +275,6 @@ void rend_vblank()
}
render_called = false;
check_framebuffer_write();
- cheatManager.apply();
emu.vblank();
}
diff --git a/core/input/gamepad_device.cpp b/core/input/gamepad_device.cpp
index 232b79c60..6944c7574 100644
--- a/core/input/gamepad_device.cpp
+++ b/core/input/gamepad_device.cpp
@@ -345,6 +345,7 @@ bool GamepadDevice::find_mapping(int system /* = settings.platform.system */)
if (cloneMapping)
input_mapper = std::make_shared(*input_mapper);
perGameMapping = perGame;
+ rumblePower = input_mapper->rumblePower;
return true;
}
if (!perGame)
diff --git a/core/input/gamepad_device.h b/core/input/gamepad_device.h
index ceaf66a94..058396124 100644
--- a/core/input/gamepad_device.h
+++ b/core/input/gamepad_device.h
@@ -56,7 +56,20 @@ public:
virtual void rumble(float power, float inclination, u32 duration_ms) {}
virtual void update_rumble() {}
- bool is_rumble_enabled() const { return _rumble_enabled; }
+ bool is_rumble_enabled() const { return rumbleEnabled; }
+ int get_rumble_power() const { return rumblePower; }
+ void set_rumble_power(int power) {
+ if (power != this->rumblePower)
+ {
+ this->rumblePower = power;
+ if (input_mapper != nullptr)
+ {
+ input_mapper->rumblePower = power;
+ input_mapper->set_dirty();
+ save_mapping();
+ }
+ }
+ }
static void Register(const std::shared_ptr& gamepad);
@@ -95,7 +108,8 @@ protected:
std::string _name;
std::string _unique_id;
std::shared_ptr input_mapper;
- bool _rumble_enabled = true;
+ bool rumbleEnabled = false;
+ int rumblePower = 100;
private:
bool handleButtonInput(int port, DreamcastKey key, bool pressed);
diff --git a/core/input/mapping.cpp b/core/input/mapping.cpp
index 41d5507c7..e310e6261 100644
--- a/core/input/mapping.cpp
+++ b/core/input/mapping.cpp
@@ -186,6 +186,7 @@ void InputMapping::load(FILE* fp)
dz = std::min(dz, 100);
dz = std::max(dz, 0);
this->dead_zone = (float)dz / 100.f;
+ this->rumblePower = mf.get_int("emulator", "rumble_power", this->rumblePower);
version = mf.get_int("emulator", "version", 1);
if (version < 3)
@@ -413,6 +414,7 @@ bool InputMapping::save(const std::string& name)
mf.set("emulator", "mapping_name", this->name);
mf.set_int("emulator", "dead_zone", (int)std::round(this->dead_zone * 100.f));
+ mf.set_int("emulator", "rumble_power", this->rumblePower);
mf.set_int("emulator", "version", 3);
int bindIndex = 0;
diff --git a/core/input/mapping.h b/core/input/mapping.h
index f7d0864da..c7a6a8e27 100644
--- a/core/input/mapping.h
+++ b/core/input/mapping.h
@@ -44,6 +44,7 @@ public:
std::string name;
float dead_zone = 0.1f;
+ int rumblePower = 100;
int version = 3;
DreamcastKey get_button_id(u32 port, u32 code)
diff --git a/core/lua/lua.cpp b/core/lua/lua.cpp
index 86581cecd..af667a1da 100644
--- a/core/lua/lua.cpp
+++ b/core/lua/lua.cpp
@@ -68,6 +68,9 @@ static void emuEventCallback(Event event, void *)
case Event::LoadState:
key = "loadState";
break;
+ case Event::VBlank:
+ key = "vblank";
+ break;
}
if (v[key].isFunction())
v[key]();
@@ -90,11 +93,6 @@ static void eventCallback(const char *tag)
}
}
-void vblank()
-{
- eventCallback("vblank");
-}
-
void overlay()
{
eventCallback("overlay");
@@ -624,6 +622,7 @@ void init()
EventManager::listen(Event::Pause, emuEventCallback);
EventManager::listen(Event::Terminate, emuEventCallback);
EventManager::listen(Event::LoadState, emuEventCallback);
+ EventManager::listen(Event::VBlank, emuEventCallback);
doExec(initFile);
}
@@ -637,6 +636,7 @@ void term()
EventManager::unlisten(Event::Pause, emuEventCallback);
EventManager::unlisten(Event::Terminate, emuEventCallback);
EventManager::unlisten(Event::LoadState, emuEventCallback);
+ EventManager::unlisten(Event::VBlank, emuEventCallback);
lua_close(L);
L = nullptr;
}
diff --git a/core/lua/lua.h b/core/lua/lua.h
index 759adb603..28806c57b 100644
--- a/core/lua/lua.h
+++ b/core/lua/lua.h
@@ -26,7 +26,6 @@ namespace lua
void init();
void term();
void exec(const std::string& path);
-void vblank();
void overlay();
#else
@@ -34,7 +33,6 @@ void overlay();
inline static void init() {}
inline static void term() {}
inline static void exec(const std::string& path) {}
-inline static void vblank() {}
inline static void overlay() {}
#endif
diff --git a/core/network/ggpo.cpp b/core/network/ggpo.cpp
index 3e8361492..df0d8290b 100644
--- a/core/network/ggpo.cpp
+++ b/core/network/ggpo.cpp
@@ -760,8 +760,11 @@ std::future startNetwork()
#ifdef SYNC_TEST
startSession(0, 0);
#else
- miniupnp.Init();
- miniupnp.AddPortMapping(SERVER_PORT, false);
+ if (config::EnableUPnP)
+ {
+ miniupnp.Init();
+ miniupnp.AddPortMapping(SERVER_PORT, false);
+ }
try {
if (config::ActAsServer)
diff --git a/core/network/naomi_network.cpp b/core/network/naomi_network.cpp
index 4d7d59a91..d2047adc5 100644
--- a/core/network/naomi_network.cpp
+++ b/core/network/naomi_network.cpp
@@ -1,72 +1,30 @@
/*
- Created on: Apr 12, 2020
+ Copyright 2022 flyinghead
- Copyright 2020 flyinghead
+ This file is part of Flycast.
- This file is part of flycast.
-
- flycast is free software: you can redistribute it and/or modify
+ 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,
+ 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 .
+ along with Flycast. If not, see .
*/
#include "naomi_network.h"
-
-#include "types.h"
-#include
-#include
-#include
-#include "rend/gui.h"
#include "hw/naomi/naomi_cart.h"
#include "hw/naomi/naomi_flashrom.h"
#include "cfg/option.h"
-#include "emulator.h"
-
-#ifdef _MSC_VER
-#if defined(_WIN64)
-typedef __int64 ssize_t;
-#else
-typedef long ssize_t;
-#endif
-#endif
+#include
+#include
NaomiNetwork naomiNetwork;
-sock_t NaomiNetwork::createAndBind(int protocol)
-{
- sock_t sock = socket(AF_INET, protocol == IPPROTO_TCP ? SOCK_STREAM : SOCK_DGRAM, protocol);
- if (!VALID(sock))
- {
- ERROR_LOG(NETWORK, "Cannot create server socket");
- return sock;
- }
- int option = 1;
- setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&option, sizeof(option));
-
- struct sockaddr_in serveraddr;
- memset(&serveraddr, 0, sizeof(serveraddr));
- serveraddr.sin_family = AF_INET;
- serveraddr.sin_port = htons(SERVER_PORT);
-
- if (::bind(sock, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
- {
- ERROR_LOG(NETWORK, "NaomiServer: bind() failed. errno=%d", get_last_error());
- closeSocket(sock);
- }
- else
- set_non_blocking(sock);
-
- return sock;
-}
-
bool NaomiNetwork::init()
{
if (!config::NetworkEnable)
@@ -76,140 +34,48 @@ bool NaomiNetwork::init()
if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0)
{
ERROR_LOG(NETWORK, "WSAStartup failed. errno=%d", get_last_error());
- return false;
+ throw Exception("WSAStartup failed");
}
#endif
- if (config::ActAsServer)
+ if (config::EnableUPnP)
{
miniupnp.Init();
- miniupnp.AddPortMapping(SERVER_PORT, true);
- return createBeaconSocket() && createServerSocket();
+ miniupnp.AddPortMapping(config::LocalPort, true);
}
- else
- return true;
-}
-bool NaomiNetwork::createServerSocket()
-{
- if (VALID(server_sock))
- return true;
+ createSocket();
- server_sock = createAndBind(IPPROTO_TCP);
- if (!VALID(server_sock))
- return false;
-
- if (listen(server_sock, 5) < 0)
- {
- ERROR_LOG(NETWORK, "NaomiServer: listen() failed. errno=%d", get_last_error());
- closeSocket(server_sock);
- return false;
- }
return true;
}
-bool NaomiNetwork::createBeaconSocket()
+void NaomiNetwork::createSocket()
{
- if (!VALID(beacon_sock))
- beacon_sock = createAndBind(IPPROTO_UDP);
+ sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (sock == INVALID_SOCKET)
+ {
+ ERROR_LOG(NETWORK, "Socket creation failed: errno %d", get_last_error());
+ throw Exception("Socket creation failed");
+ }
+ int option = 1;
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&option, sizeof(option));
- return VALID(beacon_sock);
-}
+ sockaddr_in serveraddr{};
+ serveraddr.sin_family = AF_INET;
+ serveraddr.sin_port = htons(config::LocalPort);
-void NaomiNetwork::processBeacon()
-{
- // Receive broadcast queries on beacon socket and reply
- struct sockaddr_in addr;
- socklen_t addrlen = sizeof(addr);
- memset(&addr, 0, sizeof(addr));
- char buf[6];
- ssize_t n;
- do {
- memset(buf, '\0', sizeof(buf));
- if ((n = recvfrom(beacon_sock, buf, sizeof(buf), 0, (struct sockaddr *)&addr, &addrlen)) == -1)
- {
- if (get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK)
- WARN_LOG(NETWORK, "NaomiServer: Error receiving datagram. errno=%d", get_last_error());
- }
- else
- {
- DEBUG_LOG(NETWORK, "NaomiServer: beacon received %ld bytes", (long)n);
- if (n == sizeof(buf) && !strncmp(buf, "flycast", n))
- sendto(beacon_sock, buf, n, 0, (const struct sockaddr *)&addr, addrlen);
- }
- } while (n != -1);
-}
+ if (::bind(sock, (sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
+ {
+ ERROR_LOG(NETWORK, "NaomiServer: bind() failed. errno=%d", get_last_error());
+ closesocket(sock);
-bool NaomiNetwork::findServer()
-{
- // Automatically find the adhoc server on the local network using broadcast
- sock_t sockfd = socket(AF_INET, SOCK_DGRAM, 0);
- if (!VALID(sockfd))
- {
- ERROR_LOG(NETWORK, "Datagram socket creation error. errno=%d", get_last_error());
- return false;
- }
+ throw Exception("Socket bind failed");
+ }
+ set_non_blocking(sock);
// Allow broadcast packets to be sent
int broadcast = 1;
- if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, (const char *)&broadcast, sizeof(broadcast)) == -1)
- {
- ERROR_LOG(NETWORK, "setsockopt(SO_BROADCAST) failed. errno=%d", get_last_error());
- closesocket(sockfd);
- return false;
- }
-
- // Set a 500ms timeout on recv call
- if (!set_recv_timeout(sockfd, 500))
- {
- ERROR_LOG(NETWORK, "setsockopt(SO_RCVTIMEO) failed. errno=%d", get_last_error());
- closesocket(sockfd);
- return false;
- }
-
- struct sockaddr_in addr;
- addr.sin_family = AF_INET; // host byte order
- addr.sin_port = htons(SERVER_PORT); // short, network byte order
- addr.sin_addr.s_addr = INADDR_BROADCAST;
- memset(addr.sin_zero, '\0', sizeof(addr.sin_zero));
-
- struct sockaddr server_addr;
-
- for (int i = 0; i < 3; i++)
- {
- if (sendto(sockfd, "flycast", 6, 0, (struct sockaddr *)&addr, sizeof addr) == -1)
- {
- WARN_LOG(NETWORK, "Send datagram failed. errno=%d", get_last_error());
- std::this_thread::sleep_for(std::chrono::milliseconds(100));
- continue;
- }
-
- char buf[6];
- memset(&server_addr, '\0', sizeof(server_addr));
- socklen_t addrlen = sizeof(server_addr);
- if (recvfrom(sockfd, buf, sizeof(buf), 0, &server_addr, &addrlen) == -1)
- {
- if (get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK)
- WARN_LOG(NETWORK, "Recv datagram failed. errno=%d", get_last_error());
- else
- INFO_LOG(NETWORK, "Recv datagram timeout. i=%d", i);
- continue;
- }
- server_ip = ((struct sockaddr_in *)&server_addr)->sin_addr;
- char addressBuffer[INET_ADDRSTRLEN];
- inet_ntop(AF_INET, &server_ip, addressBuffer, INET_ADDRSTRLEN);
- server_name = addressBuffer;
- break;
- }
- closesocket(sockfd);
- if (server_ip.s_addr == INADDR_NONE)
- {
- WARN_LOG(NETWORK, "Network Error: Can't find ad-hoc server on local network");
- gui_display_notification("No server found", 8000);
- return false;
- }
- INFO_LOG(NETWORK, "Found ad-hoc server at %s", server_name.c_str());
-
- return true;
+ if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (const char *)&broadcast, sizeof(broadcast)) == -1)
+ WARN_LOG(NETWORK, "setsockopt(SO_BROADCAST) failed. errno=%d", get_last_error());
}
bool NaomiNetwork::startNetwork()
@@ -217,10 +83,9 @@ bool NaomiNetwork::startNetwork()
if (!init())
return false;
- slot_id = 0;
- slot_count = 0;
+ slotId = 0;
+ slotCount = 0;
slaves.clear();
- got_token = false;
using namespace std::chrono;
const auto timeout = seconds(20);
@@ -232,416 +97,191 @@ bool NaomiNetwork::startNetwork()
while (steady_clock::now() - start_time < timeout)
{
- if (network_stopping)
- {
- for (auto& slave : slaves)
- if (VALID(slave.socket))
- closeSocket(slave.socket);
+ if (networkStopping)
return false;
- }
+
std::string notif = slaves.empty() ? "Waiting for players..."
: std::to_string(slaves.size()) + " player(s) connected. Waiting...";
gui_display_notification(notif.c_str(), timeout.count() * 2000);
- processBeacon();
+ poll();
- struct sockaddr_in src_addr;
- socklen_t addr_len = sizeof(src_addr);
- memset(&src_addr, 0, addr_len);
- sock_t clientSock = accept(server_sock, (struct sockaddr *)&src_addr, &addr_len);
- if (!VALID(clientSock))
- {
- if (get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK)
- perror("accept");
- }
- else
- {
- NOTICE_LOG(NETWORK, "Slave connection accepted");
- set_non_blocking(clientSock);
- set_tcp_nodelay(clientSock);
- std::lock_guard lock(mutex);
- slaves.emplace_back(clientSock);
- }
- const auto now = steady_clock::now();
- u32 waiting_slaves = 0;
- for (auto& slave : slaves)
- {
- if (slave.state == ClientState::Waiting)
- waiting_slaves++;
- else if (slave.state == ClientState::Connected)
- {
- char buffer[8];
- ssize_t l = ::recv(slave.socket, buffer, sizeof(buffer), 0);
- if (l < (int)sizeof(buffer) && get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK)
- {
- // error
- INFO_LOG(NETWORK, "Slave socket recv error. errno=%d", get_last_error());
- closeSocket(slave.socket);
- }
- else if (l == -1 && now - slave.state_time > milliseconds(100))
- {
- // timeout
- INFO_LOG(NETWORK, "Slave socket Connected timeout");
- closeSocket(slave.socket);
- }
- else if (l == (int)sizeof(buffer))
- {
- if (memcmp(buffer, naomi_game_id, sizeof(buffer)))
- {
- // wrong game
- WARN_LOG(NETWORK, "Wrong game id received: %.8s", buffer);
- closeSocket(slave.socket);
- }
- else
- {
- slave.set_state(ClientState::Waiting);
- waiting_slaves++;
- }
- }
- }
- }
- {
- std::lock_guard lock(mutex);
- slaves.erase(std::remove_if(slaves.begin(),
- slaves.end(),
- [](const Slave& slave){ return !VALID(slave.socket); }),
- slaves.end());
- }
- if (waiting_slaves == 3 || (start_now && !slaves.empty() && waiting_slaves == slaves.size()))
+ if (slaves.size() == 3 || (_startNow && !slaves.empty()))
break;
- std::this_thread::sleep_for(milliseconds(100));
+ std::this_thread::sleep_for(milliseconds(20));
}
- slot_id = 0;
- slot_count = slaves.size() + 1;
- u8 buf[2] = { (u8)slot_count, 0 };
- int slot_num = 1;
- {
- for (auto& slave : slaves)
- {
- buf[1] = { (u8)slot_num };
- slot_num++;
- ::send(slave.socket, (const char *)buf, 2, 0);
- slave.set_state(ClientState::Starting);
- }
- }
- NOTICE_LOG(NETWORK, "Master starting: %zd slaves", slaves.size());
if (!slaves.empty())
{
+ NOTICE_LOG(NETWORK, "Master starting: %zd slaves", slaves.size());
+ _startNow = true;
+ slotCount = slaves.size() + 1;
+ Packet packet(Start);
+ packet.start.nodeCount = slotCount;
+ for (auto& slave : slaves)
+ send(&slave.addr, &packet, packet.size());
+
+ nextPeer = slaves[0].addr;
+
gui_display_notification("Starting game", 2000);
SetNaomiNetworkConfig(0);
return true;
}
- else
- {
- gui_display_notification("No player connected", 8000);
- return false;
- }
+ gui_display_notification("No player connected", 8000);
}
else
{
+ serverIp = INADDR_BROADCAST;
+ u16 serverPort = SERVER_PORT;
if (!config::NetworkServer.get().empty())
{
- struct addrinfo *resultAddr;
- if (getaddrinfo(config::NetworkServer.get().c_str(), 0, nullptr, &resultAddr))
- WARN_LOG(NETWORK, "Server %s is unknown", config::NetworkServer.get().c_str());
+ auto pos = config::NetworkServer.get().find_last_of(':');
+ std::string server;
+ if (pos != std::string::npos)
+ {
+ serverPort = atoi(config::NetworkServer.get().substr(pos + 1).c_str());
+ server = config::NetworkServer.get().substr(0, pos);
+ }
else
- for (struct addrinfo *ptr = resultAddr; ptr != nullptr; ptr = ptr->ai_next)
+ server = config::NetworkServer;
+ addrinfo *resultAddr;
+ if (getaddrinfo(server.c_str(), 0, nullptr, &resultAddr))
+ WARN_LOG(NETWORK, "Server %s is unknown", server.c_str());
+ else
+ {
+ for (addrinfo *ptr = resultAddr; ptr != nullptr; ptr = ptr->ai_next)
if (ptr->ai_family == AF_INET)
{
- server_ip = ((sockaddr_in *)ptr->ai_addr)->sin_addr;
+ serverIp = ((sockaddr_in *)ptr->ai_addr)->sin_addr.s_addr;
break;
}
+ freeaddrinfo(resultAddr);
+ }
}
NOTICE_LOG(NETWORK, "Connecting to server");
gui_display_notification("Connecting to server", 10000);
steady_clock::time_point start_time = steady_clock::now();
- while (!network_stopping && steady_clock::now() - start_time < timeout)
+ while (!networkStopping && !_startNow && steady_clock::now() - start_time < timeout)
{
- if (server_ip.s_addr == INADDR_NONE && !findServer())
- continue;
-
- client_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- struct sockaddr_in src_addr;
- src_addr.sin_family = AF_INET;
- src_addr.sin_addr = server_ip;
- src_addr.sin_port = htons(SERVER_PORT);
- if (::connect(client_sock, (struct sockaddr *)&src_addr, sizeof(src_addr)) < 0)
+ if (slotId == 0)
{
- ERROR_LOG(NETWORK, "Socket connect failed");
- closeSocket(client_sock);
- std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ Packet packet(SyncReq);
+ sockaddr_in serverAddr{};
+ serverAddr.sin_family = AF_INET;
+ serverAddr.sin_port = htons(serverPort);
+ serverAddr.sin_addr.s_addr = serverIp;
+ send(&serverAddr, &packet, packet.size());
+ }
+ std::this_thread::sleep_for(milliseconds(10));
+ poll();
+ }
+ if (!networkStopping && _startNow)
+ {
+ SetNaomiNetworkConfig(slotId);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool NaomiNetwork::receive(const sockaddr_in *addr, const Packet *packet, u32 size)
+{
+ DEBUG_LOG(NETWORK, "Received port %d pckt %d size %x", ntohs(addr->sin_port), packet->type, size - (u32)packet->size(0));
+ switch (packet->type)
+ {
+ case SyncReq:
+ if (config::ActAsServer && !_startNow)
+ {
+ Slave *slave = nullptr;
+ for (auto& s : slaves)
+ if (s.addr.sin_port == addr->sin_port && s.addr.sin_addr.s_addr == addr->sin_addr.s_addr)
+ {
+ slave = &s;
+ break;
+ }
+ if (slave == nullptr)
+ {
+ slaves.push_back(Slave());
+ slave = &slaves.back();
+ slave->state = 0; // unused
+ slave->addr = *addr;
+ }
+ Packet reply(SyncReply);
+ reply.sync.nodeId = (u16)(slave - &slaves[0] + 1);
+ if (slave - &slaves[0] + 1 < (int)slaves.size())
+ {
+ Slave *nextSlave = &slaves[slave - &slaves[0] + 1];
+ reply.sync.nextNodeIp = nextSlave->addr.sin_addr.s_addr;
+ reply.sync.nextNodePort = nextSlave->addr.sin_port;
}
else
{
- gui_display_notification("Waiting for server to start", 10000);
- set_tcp_nodelay(client_sock);
- ::send(client_sock, naomi_game_id, 8, 0);
- set_recv_timeout(client_sock, (int)std::chrono::milliseconds(timeout * 2).count());
- u8 buf[2];
- if (::recv(client_sock, (char *)buf, 2, 0) < 2)
- {
- ERROR_LOG(NETWORK, "recv failed: errno=%d", get_last_error());
- closeSocket(client_sock);
- gui_display_notification("Server failed to start", 10000);
-
- return false;
- }
- slot_count = buf[0];
- slot_id = buf[1];
- got_token = slot_id == 1;
- set_non_blocking(client_sock);
- std::string notif = "Connected as slot " + std::to_string(slot_id);
- gui_display_notification(notif.c_str(), 2000);
- SetNaomiNetworkConfig(slot_id);
-
-
- return true;
+ //FIXME local ip?
+ reply.sync.nextNodeIp = 0;
+ reply.sync.nextNodePort = htons(config::LocalPort);
}
- }
- return false;
- }
-}
+ send(addr, &reply, reply.size());
-bool NaomiNetwork::syncNetwork()
-{
- using namespace std::chrono;
- const auto timeout = seconds(10);
-
- if (config::ActAsServer)
- {
- steady_clock::time_point start_time = steady_clock::now();
-
- bool all_slaves_ready = false;
- while (steady_clock::now() - start_time < timeout && !all_slaves_ready)
- {
- all_slaves_ready = true;
- for (auto& slave : slaves)
- if (slave.state != ClientState::Ready)
- {
- char buf[4];
- ssize_t l = ::recv(slave.socket, buf, sizeof(buf), 0);
- if (l < 4 && get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK)
- {
- INFO_LOG(NETWORK, "Socket recv failed. errno=%d", get_last_error());
- closeSocket(slave.socket);
- return false;
- }
- if (l == 4)
- {
- if (memcmp(buf, "REDY", 4))
- {
- INFO_LOG(NETWORK, "Synchronization failed");
- closeSocket(slave.socket);
- return false;
- }
- slave.set_state(ClientState::Ready);
- }
- else
- all_slaves_ready = false;
- }
- if (network_stopping)
- return false;
- }
- std::this_thread::sleep_for(std::chrono::milliseconds(100));
- for (auto& slave : slaves)
- {
- ssize_t l = ::send(slave.socket, "GO!!", 4, 0);
- if (l < 4)
+ if (reply.sync.nodeId > 1)
{
- INFO_LOG(NETWORK, "Socket send failed. errno=%d", get_last_error());
- closeSocket(slave.socket);
- return false;
+ // notify previous slave of nextNode change
+ reply.sync.nextNodeIp = addr->sin_addr.s_addr;
+ reply.sync.nextNodePort = addr->sin_port;
+ reply.sync.nodeId--;
+ slave = &slaves[reply.sync.nodeId - 1];
+ send(&slave->addr, &reply, reply.size());
}
- slave.set_state(ClientState::Online);
}
- gui_display_notification("Network started", 5000);
+ break;
+ case SyncReply:
+ if (!config::ActAsServer && !_startNow)
+ {
+ serverIp = addr->sin_addr.s_addr;
+ slotId = packet->sync.nodeId;
+ nextPeer.sin_family = AF_INET;
+ nextPeer.sin_port = packet->sync.nextNodePort;
+ nextPeer.sin_addr.s_addr = packet->sync.nextNodeIp == 0 ? addr->sin_addr.s_addr : packet->sync.nextNodeIp;
+ std::string notif = "Connected as slot " + std::to_string(slotId);
+ gui_display_notification(notif.c_str(), 2000);
+ }
+ break;
+
+ case Start:
+ if (!_startNow)
+ {
+ slotCount = packet->start.nodeCount;
+ sendAck(addr);
+ _startNow = true;
+ }
+ break;
+
+ case Data:
+ if (!receivedData.empty())
+ INFO_LOG(NETWORK, "Received packet overwritten");
+ receivedData.resize(size - packet->size(0));
+ memcpy(receivedData.data(), packet->data.payload, receivedData.size());
+ packetNumber = packet->data.packetNumber;
+ // TODO? sendAck(peer, port);
return true;
+
+ case Ack:
+ break;
+
+ case NAck:
+ WARN_LOG(NETWORK, "NAK received");
+ throw Exception("NAK received");
+ break;
+
+ default:
+ WARN_LOG(NETWORK, "Unknown packet type %d", packet->type);
+ throw Exception("Unknown packet type ");
+ break;
}
- else
- {
- // Tell master we're ready
- ssize_t l = ::send(client_sock, "REDY", 4 ,0);
- if (l < 4)
- {
- WARN_LOG(NETWORK, "Socket send failed. errno=%d", get_last_error());
- closeSocket(client_sock);
- return false;
- }
- steady_clock::time_point start_time = steady_clock::now();
- while (steady_clock::now() - start_time < timeout)
- {
- // Wait for the go
- char buf[4];
- l = ::recv(client_sock, buf, sizeof(buf), 0);
- if (l < 4 && get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK)
- {
- INFO_LOG(NETWORK, "Socket recv failed. errno=%d", get_last_error());
- closeSocket(client_sock);
- return false;
- }
- else if (l == 4)
- {
- if (memcmp(buf, "GO!!", 4))
- {
- INFO_LOG(NETWORK, "Synchronization failed");
- closeSocket(client_sock);
- return false;
- }
- gui_display_notification("Network started", 5000);
- return true;
- }
- if (network_stopping)
- {
- closeSocket(client_sock);
- return false;
- }
- std::this_thread::sleep_for(std::chrono::milliseconds(20));
- }
- INFO_LOG(NETWORK, "Socket recv timeout");
- closeSocket(client_sock);
- return false;
- }
-}
-
-void NaomiNetwork::pipeSlaves()
-{
- if (!isMaster() || slot_count < 3)
- return;
- char buf[16384];
- for (auto it = slaves.begin(); it != slaves.end() - 1; it++)
- {
- if (!VALID(it->socket) || !VALID((it + 1)->socket))
- // TODO keep link on
- continue;
- ssize_t l = ::recv(it->socket, buf, sizeof(buf), 0);
- if (l <= 0)
- {
- if (get_last_error() == L_EAGAIN || get_last_error() == L_EWOULDBLOCK)
- continue;
- WARN_LOG(NETWORK, "pipeSlaves: receive failed. errno=%d", get_last_error());
- closeSocket(it->socket);
- continue;
- }
- ssize_t l2 = ::send((it + 1)->socket, buf, l, 0);
- if (l2 != l)
- {
- WARN_LOG(NETWORK, "pipeSlaves: send failed. errno=%d", get_last_error());
- closeSocket((it + 1)->socket);
- }
- }
-}
-
-bool NaomiNetwork::receive(u8 *data, u32 size)
-{
- sock_t sockfd = INVALID_SOCKET;
- if (isMaster())
- sockfd = slaves.empty() ? INVALID_SOCKET : slaves.back().socket;
- else
- sockfd = client_sock;
- if (!VALID(sockfd))
- return false;
-
- ssize_t received = 0;
- while (received != size)
- {
- ssize_t l = ::recv(sockfd, (char*)(data + received), size - received, 0);
- if (l <= 0)
- {
- if (get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK)
- {
- WARN_LOG(NETWORK, "receiveNetwork: read failed. errno=%d", get_last_error());
- if (isMaster())
- {
- closeSocket(slaves.back().socket);
- got_token = false;
- }
- else
- shutdown();
- return false;
- }
- else if (received == 0)
- return false;
- }
- else
- received += l;
- if (network_stopping)
- return false;
- }
- DEBUG_LOG(NETWORK, "[%d] Received %d bytes", slot_id, size);
- got_token = true;
- return true;
-}
-
-void NaomiNetwork::send(u8 *data, u32 size)
-{
- if (!got_token)
- return;
-
- sock_t sockfd;
- if (isMaster())
- sockfd = slaves.empty() ? INVALID_SOCKET : slaves.front().socket;
- else
- sockfd = client_sock;
- if (!VALID(sockfd))
- return;
-
- if (::send(sockfd, (const char *)data, size, 0) < size)
- {
- if (get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK)
- {
- WARN_LOG(NETWORK, "send failed. errno=%d", get_last_error());
- if (isMaster())
- closeSocket(slaves.front().socket);
- else
- shutdown();
- }
- return;
- }
- else
- {
- DEBUG_LOG(NETWORK, "[%d] Sent %d bytes", slot_id, size);
- got_token = false;
- }
-}
-
-void NaomiNetwork::shutdown()
-{
- network_stopping = true;
- {
- std::lock_guard lock(mutex);
- for (auto& slave : slaves)
- closeSocket(slave.socket);
- }
- if (VALID(client_sock))
- closeSocket(client_sock);
- emu.setNetworkState(false);
-}
-
-void NaomiNetwork::terminate()
-{
- shutdown();
- if (config::ActAsServer)
- miniupnp.Term();
- if (VALID(beacon_sock))
- closeSocket(beacon_sock);
- if (VALID(server_sock))
- closeSocket(server_sock);
-}
-
-std::future NaomiNetwork::startNetworkAsync()
-{
- network_stopping = false;
- start_now = false;
- return std::async(std::launch::async, [this] {
- bool res = startNetwork();
- emu.setNetworkState(res);
- return res;
- });
+ return false;
}
// Sets the game network config using MIE eeprom or bbsram:
diff --git a/core/network/naomi_network.h b/core/network/naomi_network.h
index 273f4ed75..c2de9b1e0 100644
--- a/core/network/naomi_network.h
+++ b/core/network/naomi_network.h
@@ -1,94 +1,211 @@
/*
- Created on: Apr 12, 2020
+ Copyright 2022 flyinghead
- Copyright 2020 flyinghead
+ This file is part of Flycast.
- This file is part of flycast.
-
- flycast is free software: you can redistribute it and/or modify
+ 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,
+ 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 .
+ along with Flycast. If not, see .
*/
#pragma once
#include "types.h"
-#include
-#include
-#include
-#include
-#include
#include "net_platform.h"
#include "miniupnp.h"
+#include "rend/gui.h"
+#include "cfg/option.h"
+#include "emulator.h"
+#include
+#include
class NaomiNetwork
{
public:
- NaomiNetwork() {
-#ifdef _WIN32
- server_ip.S_un.S_addr = INADDR_NONE;
-#else
- server_ip.s_addr = INADDR_NONE;
-#endif
+ class Exception : public FlycastException
+ {
+ public:
+ Exception(const std::string& reason) : FlycastException(reason) {}
+ };
+
+ ~NaomiNetwork() { shutdown(); }
+
+ std::future startNetworkAsync()
+ {
+ networkStopping = false;
+ _startNow = false;
+ return std::async(std::launch::async, [this] {
+ bool res = startNetwork();
+ emu.setNetworkState(res);
+ return res;
+ });
+ }
+
+ void shutdown()
+ {
+ emu.setNetworkState(false);
+ closesocket(sock);
+ sock = INVALID_SOCKET;
+ }
+
+ bool receive(u8 *data, u32 size, u16 *packetNumber)
+ {
+ poll();
+ if (receivedData.empty())
+ return false;
+
+ size = std::min(size, (u32)receivedData.size());
+ memcpy(data, receivedData.data(), size);
+ receivedData.erase(receivedData.begin(), receivedData.begin() + size);
+ *packetNumber = this->packetNumber;
+
+ return true;
+ }
+
+ void send(u8 *data, u32 size, u16 packetNumber)
+ {
+ verify(size < sizeof(Packet::data.payload));
+ Packet packet(Data);
+ memcpy(packet.data.payload, data, size);
+ packet.data.packetNumber = packetNumber;
+ send(&nextPeer, &packet, packet.size(size));
+ }
+
+ int getSlotCount() const { return slotCount; }
+ int getSlotId() const { return slotId; }
+ void startNow() {
+ if (config::ActAsServer)
+ _startNow = true;
}
- ~NaomiNetwork() { terminate(); }
- std::future startNetworkAsync();
- void startNow() { start_now = true; }
- bool syncNetwork();
- void pipeSlaves();
- bool receive(u8 *data, u32 size);
- void send(u8 *data, u32 size);
- void shutdown(); // thread-safe
- void terminate(); // thread-safe
- int slotCount() const { return slot_count; }
- int slotId() const { return slot_id; }
- bool hasToken() const { return got_token; }
private:
- bool init();
- bool createServerSocket();
- bool createBeaconSocket();
- bool startNetwork();
- void processBeacon();
- bool findServer();
- sock_t createAndBind(int protocol);
- bool isMaster() const { return slot_id == 0; }
- void closeSocket(sock_t& socket) const { closesocket(socket); socket = INVALID_SOCKET; }
-
- struct in_addr server_ip;
- std::string server_name;
- // server stuff
- sock_t server_sock = INVALID_SOCKET;
- sock_t beacon_sock = INVALID_SOCKET;
- enum class ClientState { Connected, Waiting, Starting, Ready, Online };
- struct Slave {
- Slave(sock_t socket)
- : state(ClientState::Connected), state_time(std::chrono::steady_clock::now()), socket(socket) {}
- void set_state(ClientState state) { this->state = state; this->state_time = std::chrono::steady_clock::now(); }
- ClientState state;
- std::chrono::steady_clock::time_point state_time;
- sock_t socket;
+ enum PacketType : u16 {
+ SyncReq,
+ SyncReply,
+ Start,
+ Data,
+ Ack,
+ NAck
};
- std::vector slaves;
- bool start_now = false;
- // client stuff
- sock_t client_sock = INVALID_SOCKET;
- // common stuff
- int slot_count = 0;
- int slot_id = 0;
- bool got_token = false;
- std::atomic network_stopping{ false };
- std::mutex mutex;
+
+ #pragma pack(push, 1)
+ struct Packet
+ {
+ Packet(PacketType type = SyncReq) : type(type) {}
+
+ PacketType type;
+ union {
+ struct {
+ u16 nodeId;
+ u16 nextNodePort;
+ u32 nextNodeIp;
+ } sync;
+ struct {
+ u16 nodeCount;
+ } start;
+ struct {
+ u16 packetNumber;
+ u8 payload[0x4000];
+ } data;
+ };
+
+ size_t size(size_t dataSize = 0) const
+ {
+ size_t sz = sizeof(type);
+ switch (type) {
+ case SyncReq:
+ case SyncReply:
+ sz += sizeof(sync);
+ break;
+ case Start:
+ sz += sizeof(start);
+ break;
+ case Data:
+ sz += sizeof(data.packetNumber) + dataSize;
+ break;
+ default:
+ break;
+ }
+ return sz;
+ }
+ };
+ #pragma pack(pop)
+
+ bool init();
+
+ void createSocket();
+
+ bool startNetwork();
+
+ void poll()
+ {
+ Packet packet;
+ sockaddr_in addr;
+ while (true)
+ {
+ socklen_t len = sizeof(addr);
+ int rc = recvfrom(sock, (char *)&packet, sizeof(packet), 0, (sockaddr *)&addr, &len);
+ if (rc == -1)
+ {
+ int error = get_last_error();
+ if (error == EWOULDBLOCK || error == EAGAIN)
+ break;
+ throw Exception("Receive error: errno " + std::to_string(error));
+ }
+ if (rc < (int)packet.size(0))
+ throw Exception("Receive error: truncated packet");
+ receive(&addr, &packet, rc);
+ }
+ }
+
+ bool receive(const sockaddr_in *addr, const Packet *packet, u32 size);
+
+ void sendAck(const sockaddr_in *addr, bool ack = true)
+ {
+ Packet packet(ack ? Ack : NAck);
+ send(addr, &packet, packet.size());
+ }
+
+ void send(const sockaddr_in *addr, const Packet *packet, u32 size)
+ {
+ ssize_t rc = sendto(sock, (const char *)packet, size, 0,
+ (sockaddr *)addr, sizeof(*addr));
+ if (rc != size)
+ throw Exception("Send failed: errno " + std::to_string(get_last_error()));
+ DEBUG_LOG(NETWORK, "Sent port %d pckt %d size %x", ntohs(addr->sin_port), packet->type, size - (u32)packet->size(0));
+ }
+
+ sock_t sock;
+ int slotCount = 0;
+ int slotId = 0;
+ std::atomic networkStopping{ false };
MiniUPnP miniupnp;
- static const uint16_t SERVER_PORT = 37391;
+ sockaddr_in nextPeer;
+ std::vector receivedData;
+ u16 packetNumber = 0;
+ bool _startNow = false;
+
+ // Server stuff
+ struct Slave
+ {
+ int state;
+ sockaddr_in addr;
+ };
+ std::vector slaves;
+
+ // Client stuff
+ u32 serverIp;
+
+public:
+ static constexpr u16 SERVER_PORT = 37391;
};
extern NaomiNetwork naomiNetwork;
diff --git a/core/network/net_handshake.cpp b/core/network/net_handshake.cpp
index fcda2b4bd..8bf78d33a 100644
--- a/core/network/net_handshake.cpp
+++ b/core/network/net_handshake.cpp
@@ -46,7 +46,7 @@ public:
}
void stop() override {
- naomiNetwork.terminate();
+ naomiNetwork.shutdown();
}
bool canStartNow() override {
diff --git a/core/network/picoppp.cpp b/core/network/picoppp.cpp
index cb1b47337..7fea7cddf 100644
--- a/core/network/picoppp.cpp
+++ b/core/network/picoppp.cpp
@@ -873,7 +873,7 @@ static void *pico_thread_func(void *)
std::async(std::launch::async, [ports]() {
// Initialize miniupnpc and map network ports
MiniUPnP upnp;
- if (ports != nullptr)
+ if (ports != nullptr && config::EnableUPnP)
{
if (!upnp.Init())
WARN_LOG(MODEM, "UPNP Init failed");
diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp
index 99cedee82..1335c60dd 100644
--- a/core/rend/gui.cpp
+++ b/core/rend/gui.cpp
@@ -1390,12 +1390,13 @@ static void gui_display_settings()
header("Physical Devices");
{
ImGui::Columns(4, "physicalDevices", false);
- ImGui::Text("System");
+ ImVec4 gray{ 0.5f, 0.5f, 0.5f, 1.f };
+ ImGui::TextColored(gray, "System");
ImGui::SetColumnWidth(-1, ImGui::CalcTextSize("System").x + ImGui::GetStyle().FramePadding.x * 2.0f + ImGui::GetStyle().ItemSpacing.x);
ImGui::NextColumn();
- ImGui::Text("Name");
+ ImGui::TextColored(gray, "Name");
ImGui::NextColumn();
- ImGui::Text("Port");
+ ImGui::TextColored(gray, "Port");
ImGui::SetColumnWidth(-1, ImGui::CalcTextSize("None").x * 1.6f + ImGui::GetStyle().FramePadding.x * 2.0f + ImGui::GetFrameHeight()
+ ImGui::GetStyle().ItemInnerSpacing.x + ImGui::GetStyle().ItemSpacing.x);
ImGui::NextColumn();
@@ -1445,7 +1446,16 @@ static void gui_display_settings()
ImGui::SameLine();
OptionSlider("Haptic", config::VirtualGamepadVibration, 0, 60);
}
+ else
#endif
+ if (gamepad->is_rumble_enabled())
+ {
+ ImGui::SameLine(0, 16 * scaling);
+ int power = gamepad->get_rumble_power();
+ ImGui::SetNextItemWidth(150 * scaling);
+ if (ImGui::SliderInt("Rumble", &power, 0, 100))
+ gamepad->set_rumble_power(power);
+ }
ImGui::NextColumn();
ImGui::PopID();
}
@@ -1951,6 +1961,7 @@ static void gui_display_settings()
"Enable networking for supported Naomi games");
if (config::GGPOEnable)
{
+ config::NetworkEnable = false;
OptionCheckbox("Play as Player 1", config::ActAsServer,
"Deselect to play as player 2");
char server_name[256];
@@ -1983,10 +1994,19 @@ static void gui_display_settings()
strcpy(server_name, config::NetworkServer.get().c_str());
ImGui::InputText("Server", server_name, sizeof(server_name), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr);
ImGui::SameLine();
- ShowHelpMarker("The server to connect to. Leave blank to find a server automatically");
+ ShowHelpMarker("The server to connect to. Leave blank to find a server automatically on the default port");
config::NetworkServer.set(server_name);
}
+ char localPort[256];
+ sprintf(localPort, "%d", (int)config::LocalPort);
+ ImGui::InputText("Local Port", localPort, sizeof(localPort), ImGuiInputTextFlags_CharsDecimal, nullptr, nullptr);
+ ImGui::SameLine();
+ ShowHelpMarker("The local UDP port to use");
+ config::LocalPort.set(atoi(localPort));
}
+ OptionCheckbox("Enable UPnP", config::EnableUPnP);
+ ImGui::SameLine();
+ ShowHelpMarker("Automatically configure your network router for netplay");
}
ImGui::Spacing();
header("Other");
diff --git a/core/rend/vulkan/vk_context_lr.cpp b/core/rend/vulkan/vk_context_lr.cpp
index bcd0c95ee..661c13b81 100644
--- a/core/rend/vulkan/vk_context_lr.cpp
+++ b/core/rend/vulkan/vk_context_lr.cpp
@@ -127,7 +127,7 @@ bool VkCreateDevice(retro_vulkan_context* context, VkInstance instance, VkPhysic
bool getMemReq2Supported = false;
VulkanContext::Instance()->dedicatedAllocationSupported = false;
std::vector deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
- for (int i = 0; i < num_required_device_extensions; i++)
+ for (unsigned i = 0; i < num_required_device_extensions; i++)
deviceExtensions.push_back(required_device_extensions[i]);
for (const auto& property : physicalDevice.enumerateDeviceExtensionProperties())
{
diff --git a/core/sdl/sdl_gamepad.h b/core/sdl/sdl_gamepad.h
index 48506e878..2be1df345 100644
--- a/core/sdl/sdl_gamepad.h
+++ b/core/sdl/sdl_gamepad.h
@@ -177,9 +177,9 @@ public:
INFO_LOG(INPUT, "using custom mapping '%s'", input_mapper->name.c_str());
#if SDL_VERSION_ATLEAST(2, 0, 18)
- sdl_has_rumble = SDL_JoystickHasRumble(sdl_joystick);
+ rumbleEnabled = SDL_JoystickHasRumble(sdl_joystick);
#else
- sdl_has_rumble = (SDL_JoystickRumble(sdl_joystick, 1, 1, 1) != -1);
+ rumbleEnabled = (SDL_JoystickRumble(sdl_joystick, 1, 1, 1) != -1);
#endif
}
@@ -192,17 +192,18 @@ public:
void rumble(float power, float inclination, u32 duration_ms) override
{
- if (sdl_has_rumble)
+ if (rumbleEnabled)
{
vib_inclination = inclination * power;
vib_stop_time = os_GetSeconds() + duration_ms / 1000.0;
- SDL_JoystickRumble(sdl_joystick, (Uint16)(power * 65535), (Uint16)(power * 65535), duration_ms);
+ Uint16 intensity = (Uint16)std::min(power * rumblePower * 65535.f / 100.f, 65535.f);
+ SDL_JoystickRumble(sdl_joystick, intensity, intensity, duration_ms);
}
}
void update_rumble() override
{
- if (!sdl_has_rumble)
+ if (!rumbleEnabled)
return;
if (vib_inclination > 0)
{
@@ -210,7 +211,10 @@ public:
if (rem_time <= 0)
vib_inclination = 0;
else
- SDL_JoystickRumble(sdl_joystick, (Uint16)(vib_inclination * rem_time * 65535), (Uint16)(vib_inclination * rem_time * 65535), rem_time);
+ {
+ Uint16 intensity = (Uint16)std::min(vib_inclination * rem_time * 65535.f * rumblePower / 100.f, 65535.f);
+ SDL_JoystickRumble(sdl_joystick, intensity, intensity, rem_time);
+ }
}
}
@@ -371,7 +375,6 @@ public:
private:
SDL_Joystick* sdl_joystick;
SDL_JoystickID sdl_joystick_instance;
- bool sdl_has_rumble = false;
float vib_inclination = 0;
double vib_stop_time = 0;
SDL_GameController *sdl_controller = nullptr;
diff --git a/core/serialize.h b/core/serialize.h
index b7d7c91a0..0ca8c552d 100644
--- a/core/serialize.h
+++ b/core/serialize.h
@@ -62,7 +62,8 @@ public:
V24 = 819,
V25 = 820,
V26 = 821,
- Current = V26,
+ V27 = 822,
+ Current = V27,
Next = Current + 1,
};
diff --git a/shell/android-studio/flycast/src/main/jni/src/android_gamepad.h b/shell/android-studio/flycast/src/main/jni/src/android_gamepad.h
index 30821ef30..7c59505ec 100644
--- a/shell/android-studio/flycast/src/main/jni/src/android_gamepad.h
+++ b/shell/android-studio/flycast/src/main/jni/src/android_gamepad.h
@@ -105,6 +105,7 @@ public:
if (id == VIRTUAL_GAMEPAD_ID)
{
input_mapper = std::make_shared();
+ rumbleEnabled = true;
}
else
{
diff --git a/shell/libretro/option.cpp b/shell/libretro/option.cpp
index 327ade47a..5fe96b3cd 100644
--- a/shell/libretro/option.cpp
+++ b/shell/libretro/option.cpp
@@ -107,7 +107,9 @@ Option NetworkEnable("", false);
Option ActAsServer("", false);
OptionString DNS("", "46.101.91.123");
OptionString NetworkServer("", "");
+Option LocalPort("", 0);
Option EmulateBBA("", false); // TODO
+Option EnableUPnP("", true); // TODO
Option GGPOEnable("", false);
Option GGPODelay("", 0);
Option NetworkStats("", false);
diff --git a/tests/src/serialize_test.cpp b/tests/src/serialize_test.cpp
index d036b43db..833fe2690 100644
--- a/tests/src/serialize_test.cpp
+++ b/tests/src/serialize_test.cpp
@@ -31,7 +31,7 @@ TEST_F(SerializeTest, SizeTest)
std::vector data(30000000);
Serializer ser(data.data(), data.size());
dc_serialize(ser);
- ASSERT_EQ(28191587u, ser.size());
+ ASSERT_EQ(28191595u, ser.size());
}