From 6c38295d6277646d02c56704d9b96abf0ffeb9b6 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Sun, 27 Mar 2022 15:23:21 +0200 Subject: [PATCH] new naomi network protocol. vblank event. initd rumble support new vblank event, used by cheats, lua and naomi net new udp net protocol for naomi. rx/tx on vblank on emu thread. input: rumble power configurable (Issue #158) ui: rumble intensity slider, enable/disable upnp aica: hook to consume midi out decode midi out to simulate rumble for initd upnp can now be disabled --- core/cfg/option.cpp | 3 + core/cfg/option.h | 2 + core/cheats.cpp | 86 ++- core/cheats.h | 1 + core/emulator.cpp | 3 +- core/emulator.h | 1 + core/hw/aica/aica_if.h | 1 + core/hw/aica/sgc_if.cpp | 78 +- core/hw/naomi/naomi.cpp | 38 +- core/hw/naomi/naomi.h | 2 + core/hw/naomi/naomi_cart.cpp | 1 + core/hw/naomi/naomi_m3comm.cpp | 195 +++-- core/hw/naomi/naomi_m3comm.h | 11 +- core/hw/pvr/Renderer_if.cpp | 2 - core/input/gamepad_device.cpp | 1 + core/input/gamepad_device.h | 18 +- core/input/mapping.cpp | 2 + core/input/mapping.h | 1 + core/lua/lua.cpp | 10 +- core/lua/lua.h | 2 - core/network/ggpo.cpp | 7 +- core/network/naomi_network.cpp | 708 +++++------------- core/network/naomi_network.h | 245 ++++-- core/network/net_handshake.cpp | 2 +- core/network/picoppp.cpp | 2 +- core/rend/gui.cpp | 28 +- core/rend/vulkan/vk_context_lr.cpp | 2 +- core/sdl/sdl_gamepad.h | 17 +- core/serialize.h | 3 +- .../src/main/jni/src/android_gamepad.h | 1 + shell/libretro/option.cpp | 2 + tests/src/serialize_test.cpp | 2 +- 32 files changed, 664 insertions(+), 813 deletions(-) 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()); }