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
This commit is contained in:
parent
55e613fae5
commit
6c38295d62
|
|
@ -17,6 +17,7 @@
|
|||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "option.h"
|
||||
#include "network/naomi_network.h"
|
||||
|
||||
namespace config {
|
||||
|
||||
|
|
@ -117,7 +118,9 @@ Option<bool> NetworkEnable("Enable", false, "network");
|
|||
Option<bool> ActAsServer("ActAsServer", false, "network");
|
||||
OptionString DNS("DNS", "46.101.91.123", "network");
|
||||
OptionString NetworkServer("server", "", "network");
|
||||
Option<int> LocalPort("LocalPort", NaomiNetwork::SERVER_PORT, "network");
|
||||
Option<bool> EmulateBBA("EmulateBBA", false, "network");
|
||||
Option<bool> EnableUPnP("EnableUPnP", true, "network");
|
||||
Option<bool> GGPOEnable("GGPO", false, "network");
|
||||
Option<int> GGPODelay("GGPODelay", 0, "network");
|
||||
Option<bool> NetworkStats("Stats", true, "network");
|
||||
|
|
|
|||
|
|
@ -462,7 +462,9 @@ extern Option<bool> NetworkEnable;
|
|||
extern Option<bool> ActAsServer;
|
||||
extern OptionString DNS;
|
||||
extern OptionString NetworkServer;
|
||||
extern Option<int> LocalPort;
|
||||
extern Option<bool> EmulateBBA;
|
||||
extern Option<bool> EnableUPnP;
|
||||
extern Option<bool> GGPOEnable;
|
||||
extern Option<int> GGPODelay;
|
||||
extern Option<bool> NetworkStats;
|
||||
|
|
|
|||
|
|
@ -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 == "")
|
||||
|
|
|
|||
|
|
@ -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[];
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ enum class Event {
|
|||
Resume,
|
||||
Terminate,
|
||||
LoadState,
|
||||
VBlank,
|
||||
};
|
||||
|
||||
class EventManager
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<s32 ALFOWS>
|
||||
enum class LFOType
|
||||
{
|
||||
Sawtooth,
|
||||
Square,
|
||||
Triangle,
|
||||
Random
|
||||
};
|
||||
|
||||
template<LFOType Type>
|
||||
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<s32 PLFOWS>
|
||||
template<LFOType Type>
|
||||
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<EG_Attack>;
|
||||
AEG_STEP_LUT[EG_Decay1] = &AegStep<EG_Decay1>;
|
||||
AEG_STEP_LUT[EG_Decay2] = &AegStep<EG_Decay2>;
|
||||
AEG_STEP_LUT[EG_Release] = &AegStep<EG_Release>;
|
||||
|
||||
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<EG_Attack>;
|
||||
FEG_STEP_LUT[EG_Decay1] = &FegStep<EG_Decay1>;
|
||||
FEG_STEP_LUT[EG_Decay2] = &FegStep<EG_Decay2>;
|
||||
FEG_STEP_LUT[EG_Release] = &FegStep<EG_Release>;
|
||||
|
||||
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<LFOType::Sawtooth>;
|
||||
ALFOWS_CALC[(int)LFOType::Square] = &CalcAlfo<LFOType::Square>;
|
||||
ALFOWS_CALC[(int)LFOType::Triangle] = &CalcAlfo<LFOType::Triangle>;
|
||||
ALFOWS_CALC[(int)LFOType::Random] = &CalcAlfo<LFOType::Random>;
|
||||
|
||||
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<LFOType::Sawtooth>;
|
||||
PLFOWS_CALC[(int)LFOType::Square] = &CalcPlfo<LFOType::Square>;
|
||||
PLFOWS_CALC[(int)LFOType::Triangle] = &CalcPlfo<LFOType::Triangle>;
|
||||
PLFOWS_CALC[(int)LFOType::Random] = &CalcPlfo<LFOType::Random>;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -581,6 +581,7 @@ void naomi_cart_LoadRom(const char* file, LoadProgress *progress)
|
|||
|| gameId == "INITIAL D CYCRAFT")
|
||||
{
|
||||
card_reader::initialDCardReader.init();
|
||||
initdFFBInit();
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -18,69 +18,86 @@
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
//
|
||||
// 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 <chrono>
|
||||
#include <memory>
|
||||
|
||||
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<u8[]> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::thread>(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<std::chrono::milliseconds>(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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,10 +20,6 @@
|
|||
*/
|
||||
#pragma once
|
||||
#include "types.h"
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
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<bool> network_stopping{ false };
|
||||
std::unique_ptr<std::thread> thread;
|
||||
std::mutex mem_mutex;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -345,6 +345,7 @@ bool GamepadDevice::find_mapping(int system /* = settings.platform.system */)
|
|||
if (cloneMapping)
|
||||
input_mapper = std::make_shared<InputMapping>(*input_mapper);
|
||||
perGameMapping = perGame;
|
||||
rumblePower = input_mapper->rumblePower;
|
||||
return true;
|
||||
}
|
||||
if (!perGame)
|
||||
|
|
|
|||
|
|
@ -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<GamepadDevice>& gamepad);
|
||||
|
||||
|
|
@ -95,7 +108,8 @@ protected:
|
|||
std::string _name;
|
||||
std::string _unique_id;
|
||||
std::shared_ptr<InputMapping> input_mapper;
|
||||
bool _rumble_enabled = true;
|
||||
bool rumbleEnabled = false;
|
||||
int rumblePower = 100;
|
||||
|
||||
private:
|
||||
bool handleButtonInput(int port, DreamcastKey key, bool pressed);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -760,8 +760,11 @@ std::future<bool> 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)
|
||||
|
|
|
|||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "naomi_network.h"
|
||||
|
||||
#include "types.h"
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#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 <chrono>
|
||||
#include <thread>
|
||||
|
||||
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<std::mutex> 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<std::mutex> 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<std::mutex> 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<bool> 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:
|
||||
|
|
|
|||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include "types.h"
|
||||
#include <cstdint>
|
||||
#include <atomic>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include "net_platform.h"
|
||||
#include "miniupnp.h"
|
||||
#include "rend/gui.h"
|
||||
#include "cfg/option.h"
|
||||
#include "emulator.h"
|
||||
#include <atomic>
|
||||
#include <future>
|
||||
|
||||
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<bool> 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<bool> 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<Slave> 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<bool> 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<bool> networkStopping{ false };
|
||||
MiniUPnP miniupnp;
|
||||
|
||||
static const uint16_t SERVER_PORT = 37391;
|
||||
sockaddr_in nextPeer;
|
||||
std::vector<u8> receivedData;
|
||||
u16 packetNumber = 0;
|
||||
bool _startNow = false;
|
||||
|
||||
// Server stuff
|
||||
struct Slave
|
||||
{
|
||||
int state;
|
||||
sockaddr_in addr;
|
||||
};
|
||||
std::vector<Slave> slaves;
|
||||
|
||||
// Client stuff
|
||||
u32 serverIp;
|
||||
|
||||
public:
|
||||
static constexpr u16 SERVER_PORT = 37391;
|
||||
};
|
||||
extern NaomiNetwork naomiNetwork;
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ public:
|
|||
}
|
||||
|
||||
void stop() override {
|
||||
naomiNetwork.terminate();
|
||||
naomiNetwork.shutdown();
|
||||
}
|
||||
|
||||
bool canStartNow() override {
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ bool VkCreateDevice(retro_vulkan_context* context, VkInstance instance, VkPhysic
|
|||
bool getMemReq2Supported = false;
|
||||
VulkanContext::Instance()->dedicatedAllocationSupported = false;
|
||||
std::vector<const char *> 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())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -62,7 +62,8 @@ public:
|
|||
V24 = 819,
|
||||
V25 = 820,
|
||||
V26 = 821,
|
||||
Current = V26,
|
||||
V27 = 822,
|
||||
Current = V27,
|
||||
|
||||
Next = Current + 1,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ public:
|
|||
if (id == VIRTUAL_GAMEPAD_ID)
|
||||
{
|
||||
input_mapper = std::make_shared<IdentityInputMapping>();
|
||||
rumbleEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -107,7 +107,9 @@ Option<bool> NetworkEnable("", false);
|
|||
Option<bool> ActAsServer("", false);
|
||||
OptionString DNS("", "46.101.91.123");
|
||||
OptionString NetworkServer("", "");
|
||||
Option<int> LocalPort("", 0);
|
||||
Option<bool> EmulateBBA("", false); // TODO
|
||||
Option<bool> EnableUPnP("", true); // TODO
|
||||
Option<bool> GGPOEnable("", false);
|
||||
Option<int> GGPODelay("", 0);
|
||||
Option<bool> NetworkStats("", false);
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ TEST_F(SerializeTest, SizeTest)
|
|||
std::vector<char> data(30000000);
|
||||
Serializer ser(data.data(), data.size());
|
||||
dc_serialize(ser);
|
||||
ASSERT_EQ(28191587u, ser.size());
|
||||
ASSERT_EQ(28191595u, ser.size());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue