diff --git a/src/NDS.cpp b/src/NDS.cpp index 2ff8ffe2ce..5262059c16 100644 --- a/src/NDS.cpp +++ b/src/NDS.cpp @@ -176,6 +176,7 @@ bool RunningGame; void DivDone(u32 param); void SqrtDone(u32 param); void RunTimer(u32 tid, s32 cycles); +void UpdateWifiTimings(); void SetWifiWaitCnt(u16 val); void SetGBASlotTimings(); @@ -892,9 +893,7 @@ bool DoSavestate(Savestate* file) InitTimings(); SetGBASlotTimings(); - u16 tmp = WifiWaitCnt; - WifiWaitCnt = 0xFFFF; - SetWifiWaitCnt(tmp); // force timing table update + UpdateWifiTimings(); } for (int i = 0; i < 8; i++) @@ -918,6 +917,9 @@ bool DoSavestate(Savestate* file) if (!file->Saving) { GPU::SetPowerCnt(PowerControl9); + + SPU::SetPowerCnt(PowerControl7 & 0x0001); + Wifi::SetPowerCnt(PowerControl7 & 0x0002); } #ifdef JIT_ENABLED @@ -1198,6 +1200,25 @@ void ScheduleEvent(u32 id, bool periodic, s32 delay, void (*func)(u32), u32 para Reschedule(evt->Timestamp); } +void ScheduleEvent(u32 id, u64 timestamp, void (*func)(u32), u32 param) +{ + if (SchedListMask & (1<Timestamp = timestamp; + evt->Func = func; + evt->Param = param; + + SchedListMask |= (1<Timestamp); +} + void CancelEvent(u32 id) { SchedListMask &= ~(1<>3) & 0x3], (val & 0x20) ? 4 : 10); + } + else + { + SetARM7RegionTimings(0x04800, 0x04808, Mem7_Wifi0, 32, 1, 1); + SetARM7RegionTimings(0x04808, 0x04810, Mem7_Wifi1, 32, 1, 1); + } +} + void SetWifiWaitCnt(u16 val) { if (WifiWaitCnt == val) return; WifiWaitCnt = val; - - const int ntimings[4] = {10, 8, 6, 18}; - SetARM7RegionTimings(0x04800, 0x04808, Mem7_Wifi0, 16, ntimings[val & 0x3], (val & 0x4) ? 4 : 6); - SetARM7RegionTimings(0x04808, 0x04810, Mem7_Wifi1, 16, ntimings[(val>>3) & 0x3], (val & 0x20) ? 4 : 10); + UpdateWifiTimings(); } void SetGBASlotTimings() @@ -1941,8 +1976,8 @@ void debug(u32 param) //for (int i = 0; i < 9; i++) // printf("VRAM %c: %02X\n", 'A'+i, GPU::VRAMCNT[i]); - /*FILE* - shit = fopen("debug/construct.bin", "wb"); + FILE* + shit = fopen("debug/inazuma.bin", "wb"); fwrite(ARM9->ITCM, 0x8000, 1, shit); for (u32 i = 0x02000000; i < 0x02400000; i+=4) { @@ -1954,9 +1989,14 @@ void debug(u32 param) u32 val = ARM7Read32(i); fwrite(&val, 4, 1, shit); } - fclose(shit);*/ + for (u32 i = 0x06000000; i < 0x06040000; i+=4) + { + u32 val = ARM7Read32(i); + fwrite(&val, 4, 1, shit); + } + fclose(shit); - FILE* + /*FILE* shit = fopen("debug/directboot9.bin", "wb"); for (u32 i = 0x02000000; i < 0x04000000; i+=4) { @@ -1970,7 +2010,7 @@ void debug(u32 param) u32 val = DSi::ARM7Read32(i); fwrite(&val, 4, 1, shit); } - fclose(shit); + fclose(shit);*/ } @@ -2396,6 +2436,7 @@ u8 ARM7Read8(u32 addr) case 0x04800000: if (addr < 0x04810000) { + if (!(PowerControl7 & (1<<1))) return 0; if (addr & 0x1) return Wifi::Read(addr-1) >> 8; return Wifi::Read(addr) & 0xFF; } @@ -2460,6 +2501,7 @@ u16 ARM7Read16(u32 addr) case 0x04800000: if (addr < 0x04810000) { + if (!(PowerControl7 & (1<<1))) return 0; return Wifi::Read(addr); } break; @@ -2523,6 +2565,7 @@ u32 ARM7Read32(u32 addr) case 0x04800000: if (addr < 0x04810000) { + if (!(PowerControl7 & (1<<1))) return 0; return Wifi::Read(addr) | (Wifi::Read(addr+2) << 16); } break; @@ -2614,7 +2657,8 @@ void ARM7Write8(u32 addr, u8 val) return; } - if (ARM7->R[15] > 0x00002F30) // ARM7 BIOS bug + //if (ARM7->R[15] > 0x00002F30) // ARM7 BIOS bug + if (addr >= 0x01000000) printf("unknown arm7 write8 %08X %02X @ %08X\n", addr, val, ARM7->R[15]); } @@ -2662,6 +2706,7 @@ void ARM7Write16(u32 addr, u16 val) case 0x04800000: if (addr < 0x04810000) { + if (!(PowerControl7 & (1<<1))) return; Wifi::Write(addr, val); return; } @@ -2691,7 +2736,8 @@ void ARM7Write16(u32 addr, u16 val) return; } - printf("unknown arm7 write16 %08X %04X @ %08X\n", addr, val, ARM7->R[15]); + if (addr >= 0x01000000) + printf("unknown arm7 write16 %08X %04X @ %08X\n", addr, val, ARM7->R[15]); } void ARM7Write32(u32 addr, u32 val) @@ -2738,6 +2784,7 @@ void ARM7Write32(u32 addr, u32 val) case 0x04800000: if (addr < 0x04810000) { + if (!(PowerControl7 & (1<<1))) return; Wifi::Write(addr, val & 0xFFFF); Wifi::Write(addr+2, val >> 16); return; @@ -2771,7 +2818,8 @@ void ARM7Write32(u32 addr, u32 val) return; } - printf("unknown arm7 write32 %08X %08X @ %08X\n", addr, val, ARM7->R[15]); + if (addr >= 0x01000000) + printf("unknown arm7 write32 %08X %08X @ %08X\n", addr, val, ARM7->R[15]); } bool ARM7GetMemRegion(u32 addr, bool write, MemRegion* region) @@ -2931,7 +2979,8 @@ u8 ARM9IORead8(u32 addr) return (u8)(emuID[idx]); } - printf("unknown ARM9 IO read8 %08X %08X\n", addr, ARM9->R[15]); + if ((addr & 0xFFFFF000) != 0x04004000) + printf("unknown ARM9 IO read8 %08X %08X\n", addr, ARM9->R[15]); return 0; } @@ -3077,7 +3126,8 @@ u16 ARM9IORead16(u32 addr) return GPU3D::Read16(addr); } - printf("unknown ARM9 IO read16 %08X %08X\n", addr, ARM9->R[15]); + if ((addr & 0xFFFFF000) != 0x04004000) + printf("unknown ARM9 IO read16 %08X %08X\n", addr, ARM9->R[15]); return 0; } @@ -3220,7 +3270,8 @@ u32 ARM9IORead32(u32 addr) return GPU3D::Read32(addr); } - printf("unknown ARM9 IO read32 %08X %08X\n", addr, ARM9->R[15]); + if ((addr & 0xFFFFF000) != 0x04004000) + printf("unknown ARM9 IO read32 %08X %08X\n", addr, ARM9->R[15]); return 0; } @@ -3748,6 +3799,7 @@ u8 ARM7IORead8(u32 addr) case 0x04000241: return WRAMCnt; case 0x04000300: return PostFlag7; + case 0x04000304: return PowerControl7; } if (addr >= 0x04000400 && addr < 0x04000520) @@ -3755,7 +3807,8 @@ u8 ARM7IORead8(u32 addr) return SPU::Read8(addr); } - printf("unknown ARM7 IO read8 %08X %08X\n", addr, ARM7->R[15]); + if ((addr & 0xFFFFF000) != 0x04004000) + printf("unknown ARM7 IO read8 %08X %08X\n", addr, ARM7->R[15]); return 0; } @@ -3830,7 +3883,9 @@ u16 ARM7IORead16(u32 addr) case 0x040001C2: return SPI::ReadData(); case 0x04000204: return ExMemCnt[1]; - case 0x04000206: return WifiWaitCnt; + case 0x04000206: + if (!(PowerControl7 & (1<<1))) return 0; + return WifiWaitCnt; case 0x04000208: return IME[1]; case 0x04000210: return IE[1] & 0xFFFF; @@ -3846,7 +3901,8 @@ u16 ARM7IORead16(u32 addr) return SPU::Read16(addr); } - printf("unknown ARM7 IO read16 %08X %08X\n", addr, ARM7->R[15]); + if ((addr & 0xFFFFF000) != 0x04004000) + printf("unknown ARM7 IO read16 %08X %08X\n", addr, ARM7->R[15]); return 0; } @@ -3912,6 +3968,7 @@ u32 ARM7IORead32(u32 addr) case 0x04000210: return IE[1]; case 0x04000214: return IF[1]; + case 0x04000304: return PowerControl7; case 0x04000308: return ARM7BIOSProt; case 0x04100000: @@ -3945,7 +4002,8 @@ u32 ARM7IORead32(u32 addr) return SPU::Read32(addr); } - printf("unknown ARM7 IO read32 %08X %08X\n", addr, ARM7->R[15]); + if ((addr & 0xFFFFF000) != 0x04004000) + printf("unknown ARM7 IO read32 %08X %08X\n", addr, ARM7->R[15]); return 0; } @@ -4140,6 +4198,7 @@ void ARM7IOWrite16(u32 addr, u16 val) return; } case 0x04000206: + if (!(PowerControl7 & (1<<1))) return; SetWifiWaitCnt(val); return; @@ -4155,7 +4214,15 @@ void ARM7IOWrite16(u32 addr, u16 val) PostFlag7 = val & 0x01; return; - case 0x04000304: PowerControl7 = val; return; + case 0x04000304: + { + u16 change = PowerControl7 ^ val; + PowerControl7 = val & 0x0003; + SPU::SetPowerCnt(val & 0x0001); + Wifi::SetPowerCnt(val & 0x0002); + if (change & 0x0002) UpdateWifiTimings(); + } + return; case 0x04000308: if (ARM7BIOSProt == 0) @@ -4277,7 +4344,15 @@ void ARM7IOWrite32(u32 addr, u32 val) case 0x04000210: IE[1] = val; UpdateIRQ(1); return; case 0x04000214: IF[1] &= ~val; UpdateIRQ(1); return; - case 0x04000304: PowerControl7 = val & 0xFFFF; return; + case 0x04000304: + { + u16 change = PowerControl7 ^ val; + PowerControl7 = val & 0x0003; + SPU::SetPowerCnt(val & 0x0001); + Wifi::SetPowerCnt(val & 0x0002); + if (change & 0x0002) UpdateWifiTimings(); + } + return; case 0x04000308: if (ARM7BIOSProt == 0) diff --git a/src/NDS.h b/src/NDS.h index 80a1c6d7b6..8f408a031d 100644 --- a/src/NDS.h +++ b/src/NDS.h @@ -263,6 +263,7 @@ void SetLidClosed(bool closed); void MicInputFrame(s16* data, int samples); void ScheduleEvent(u32 id, bool periodic, s32 delay, void (*func)(u32), u32 param); +void ScheduleEvent(u32 id, u64 timestamp, void (*func)(u32), u32 param); void CancelEvent(u32 id); void debug(u32 p); diff --git a/src/NDSCart.cpp b/src/NDSCart.cpp index 35418ebb94..cdc26ef17e 100644 --- a/src/NDSCart.cpp +++ b/src/NDSCart.cpp @@ -1584,6 +1584,9 @@ bool LoadROM(const u8* romdata, u32 romlen) if (CartInserted) EjectCart(); + memset(&Header, 0, sizeof(Header)); + memset(&Banner, 0, sizeof(Banner)); + CartROMSize = 0x200; while (CartROMSize < romlen) CartROMSize <<= 1; @@ -1603,13 +1606,13 @@ bool LoadROM(const u8* romdata, u32 romlen) memcpy(&Header, CartROM, sizeof(Header)); - if (!Header.BannerOffset) - { - memset(&Banner, 0, sizeof(Banner)); - } - else + u8 unitcode = Header.UnitCode; + bool dsi = (unitcode & 0x02) != 0; + + size_t bannersize = dsi ? 0x23C0 : 0xA40; + if (Header.BannerOffset >= 0x200 && Header.BannerOffset < (CartROMSize - bannersize)) { - memcpy(&Banner, CartROM + Header.BannerOffset, sizeof(Banner)); + memcpy(&Banner, CartROM + Header.BannerOffset, bannersize); } printf("Game code: %.4s\n", Header.GameCode); @@ -1619,9 +1622,6 @@ bool LoadROM(const u8* romdata, u32 romlen) (u32)Header.GameCode[1] << 8 | (u32)Header.GameCode[0]; - u8 unitcode = Header.UnitCode; - bool dsi = (unitcode & 0x02) != 0; - u32 arm9base = Header.ARM9ROMOffset; bool homebrew = (arm9base < 0x4000) || (gamecode == 0x23232323); diff --git a/src/Platform.h b/src/Platform.h index 4106977ba8..56f2c2e110 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -32,6 +32,10 @@ void DeInit(); void StopEmu(); +// instance ID, for local multiplayer +int InstanceID(); +std::string InstanceFileSuffix(); + // configuration values enum ConfigEntry @@ -77,7 +81,6 @@ enum ConfigEntry Firm_Color, Firm_Message, Firm_MAC, - Firm_RandomizeMAC, AudioBitrate, }; @@ -156,8 +159,16 @@ void WriteGBASave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen // packet type: DS-style TX header (12 bytes) + original 802.11 frame bool MP_Init(); void MP_DeInit(); -int MP_SendPacket(u8* data, int len); -int MP_RecvPacket(u8* data, bool block); +void MP_Begin(); +void MP_End(); +int MP_SendPacket(u8* data, int len, u64 timestamp); +int MP_RecvPacket(u8* data, u64* timestamp); +int MP_SendCmd(u8* data, int len, u64 timestamp); +int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid); +int MP_SendAck(u8* data, int len, u64 timestamp); +int MP_RecvHostPacket(u8* data, u64* timestamp); +u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask); + // LAN comm interface // packet type: Ethernet (802.3) diff --git a/src/SPI.cpp b/src/SPI.cpp index 6ecb86c419..e990b3a751 100644 --- a/src/SPI.cpp +++ b/src/SPI.cpp @@ -215,7 +215,8 @@ void LoadDefaultFirmware() // wifi access points // TODO: WFC ID?? - FILE* f = Platform::OpenLocalFile("wfcsettings.bin", "rb"); + FILE* f = Platform::OpenLocalFile("wfcsettings.bin"+Platform::InstanceFileSuffix(), "rb"); + if (!f) f = Platform::OpenLocalFile("wfcsettings.bin", "rb"); if (f) { u32 apdata = userdata - 0xA00; @@ -259,7 +260,7 @@ void LoadDefaultFirmware() } } -void LoadFirmwareFromFile(FILE* f) +void LoadFirmwareFromFile(FILE* f, bool makecopy) { fseek(f, 0, SEEK_END); @@ -271,7 +272,9 @@ void LoadFirmwareFromFile(FILE* f) fread(Firmware, 1, FirmwareLength, f); // take a backup - std::string fwBackupPath = FirmwarePath + ".bak"; + std::string fwBackupPath; + if (!makecopy) fwBackupPath = FirmwarePath + ".bak"; + else fwBackupPath = FirmwarePath; FILE* bf = Platform::OpenLocalFile(fwBackupPath, "rb"); if (!bf) { @@ -333,15 +336,24 @@ void Reset() else FirmwarePath = Platform::GetConfigString(Platform::FirmwarePath); + bool makecopy = false; + std::string origpath = FirmwarePath; + FirmwarePath += Platform::InstanceFileSuffix(); + FILE* f = Platform::OpenLocalFile(FirmwarePath, "rb"); if (!f) + { + f = Platform::OpenLocalFile(origpath, "rb"); + makecopy = true; + } + if (!f) { printf("Firmware not found! Generating default firmware.\n"); FirmwarePath = ""; } else { - LoadFirmwareFromFile(f); + LoadFirmwareFromFile(f, makecopy); fclose(f); } } @@ -385,28 +397,28 @@ void Reset() *(u16*)&Firmware[userdata+0x72] = CRC16(&Firmware[userdata], 0x70, 0xFFFF); - if (firmoverride) + //if (firmoverride) { u8 mac[6]; - bool rep; + bool rep = false; + + memcpy(mac, &Firmware[0x36], 6); - if (Platform::GetConfigBool(Platform::Firm_RandomizeMAC)) + if (firmoverride) + rep = Platform::GetConfigArray(Platform::Firm_MAC, mac); + + int inst = Platform::InstanceID(); + if (inst > 0) { - mac[0] = 0x00; - mac[1] = 0x09; - mac[2] = 0xBF; - mac[3] = rand()&0xFF; - mac[4] = rand()&0xFF; - mac[5] = rand()&0xFF; rep = true; - } - else - { - rep = Platform::GetConfigArray(Platform::Firm_MAC, mac); + mac[3] += inst; + mac[4] += inst*0x44; + mac[5] += inst*0x10; } if (rep) { + mac[0] &= 0xFC; // ensure the MAC isn't a broadcast MAC memcpy(&Firmware[0x36], mac, 6); *(u16*)&Firmware[0x2A] = CRC16(&Firmware[0x2C], *(u16*)&Firmware[0x2C], 0x0000); @@ -593,7 +605,12 @@ void Write(u8 val, u32 hold) } else { - FILE* f = Platform::OpenLocalFile("wfcsettings.bin", "wb"); + char wfcfile[50] = {0}; + int inst = Platform::InstanceID(); + if (inst > 0) snprintf(wfcfile, 49, "wfcsettings.bin", Platform::InstanceID()); + else strncpy(wfcfile, "wfcsettings.bin", 49); + + FILE* f = Platform::OpenLocalFile(wfcfile, "wb"); if (f) { u32 cutoff = 0x7F400 & FirmwareMask; diff --git a/src/SPU.cpp b/src/SPU.cpp index cba05586d1..9f245a2066 100644 --- a/src/SPU.cpp +++ b/src/SPU.cpp @@ -184,6 +184,12 @@ void DoSavestate(Savestate* file) } +void SetPowerCnt(u32 val) +{ + // TODO +} + + void SetInterpolation(int type) { InterpType = type; diff --git a/src/SPU.h b/src/SPU.h index 1e20c117ef..1f28c2f800 100644 --- a/src/SPU.h +++ b/src/SPU.h @@ -31,6 +31,8 @@ void Stop(); void DoSavestate(Savestate* file); +void SetPowerCnt(u32 val); + // 0=none 1=linear 2=cosine 3=cubic void SetInterpolation(int type); diff --git a/src/Wifi.cpp b/src/Wifi.cpp index 4e3bc17d25..c2614e73c7 100644 --- a/src/Wifi.cpp +++ b/src/Wifi.cpp @@ -26,43 +26,59 @@ #include "ARM.h" #include "GPU.h" + namespace Wifi { //#define WIFI_LOG printf #define WIFI_LOG(...) {} +#define PRINT_MAC(pf, mac) printf("%s: %02X:%02X:%02X:%02X:%02X:%02X\n", pf, (mac)[0], (mac)[1], (mac)[2], (mac)[3], (mac)[4], (mac)[5]); + u8 RAM[0x2000]; u16 IO[0x1000>>1]; #define IOPORT(x) IO[(x)>>1] +#define IOPORT8(x) ((u8*)IO)[x] + +// destination MACs for MP frames +const u8 MPCmdMAC[6] = {0x03, 0x09, 0xBF, 0x00, 0x00, 0x00}; +const u8 MPReplyMAC[6] = {0x03, 0x09, 0xBF, 0x00, 0x00, 0x10}; +const u8 MPAckMAC[6] = {0x03, 0x09, 0xBF, 0x00, 0x00, 0x03}; + +const int kTimerInterval = 8; +const u32 kTimeCheckMask = ~(kTimerInterval - 1); + +bool Enabled; +bool PowerOn; + +s32 TimerError; u16 Random; +// general, always-on microsecond counter +u64 USTimestamp; + u64 USCounter; u64 USCompare; bool BlockBeaconIRQ14; u32 CmdCounter; -u16 BBCnt; -u8 BBWrite; u8 BBRegs[0x100]; u8 BBRegsRO[0x100]; u8 RFVersion; -u16 RFCnt; -u16 RFData1; -u16 RFData2; u32 RFRegs[0x40]; struct TXSlot { + bool Valid; u16 Addr; u16 Length; u8 Rate; u8 CurPhase; - u32 CurPhaseTime; + int CurPhaseTime; u32 HalfwordTimeMask; }; @@ -70,16 +86,17 @@ TXSlot TXSlots[6]; u8 RXBuffer[2048]; u32 RXBufferPtr; -u32 RXTime; +int RXTime; u32 RXHalfwordTimeMask; -u16 RXEndAddr; u32 ComStatus; // 0=waiting for packets 1=receiving 2=sending u32 TXCurSlot; u32 RXCounter; int MPReplyTimer; -int MPNumReplies; +u16 MPClientMask, MPClientFail; + +u8 MPClientReplies[15*1024]; bool MPInited; bool LANInited; @@ -87,6 +104,11 @@ bool LANInited; int USUntilPowerOn; bool ForcePowerOn; +// MULTIPLAYER SYNC APPARATUS +bool IsMPClient; +u64 NextSync; // for clients: timestamp for next sync point +u64 RXTimestamp; + // multiplayer host TX sequence: // 1. preamble // 2. IRQ7 @@ -112,21 +134,26 @@ bool ForcePowerOn; // 4 = switching from TX to RX // 5 = MP host data sent, waiting for replies (RFPINS=0x0084) // 6 = RX -// 7 = ?? +// 7 = switching from RX reply to TX ack // 8 = MP client sending reply, MP host sending ack (RFPINS=0x0046) // 9 = idle // wifi TODO: -// * power saving -// * RXSTAT, multiplay reply errors +// * RXSTAT // * TX errors (if applicable) bool Init() { - MPInited = false; - LANInited = false; + //MPInited = false; + //LANInited = false; + + Platform::MP_Init(); + MPInited = true; + + Platform::LAN_Init(); + LANInited = true; WifiAP::Init(); @@ -148,6 +175,9 @@ void Reset() memset(RAM, 0, 0x2000); memset(IO, 0, 0x1000); + Enabled = false; + PowerOn = false; + Random = 1; memset(BBRegs, 0, 0x100); @@ -200,19 +230,39 @@ void Reset() memset(&IOPORT(0x018), 0xFF, 6); memset(&IOPORT(0x020), 0xFF, 6); + // TODO: find out what the initial values are + IOPORT(W_PowerUS) = 0x0001; + + USTimestamp = 0; + USCounter = 0; USCompare = 0; BlockBeaconIRQ14 = false; + memset(TXSlots, 0, sizeof(TXSlots)); ComStatus = 0; TXCurSlot = -1; RXCounter = 0; + memset(RXBuffer, 0, sizeof(RXBuffer)); + RXBufferPtr = 0; + RXTime = 0; + RXHalfwordTimeMask = 0xFFFFFFFF; + MPReplyTimer = 0; - MPNumReplies = 0; + MPClientMask = 0; + MPClientFail = 0; + memset(MPClientReplies, 0, sizeof(MPClientReplies)); CmdCounter = 0; + USUntilPowerOn = 0; + ForcePowerOn = false; + + IsMPClient = false; + NextSync = 0; + RXTimestamp = 0; + WifiAP::Reset(); } @@ -228,8 +278,13 @@ void DoSavestate(Savestate* file) file->VarArray(RAM, 0x2000); file->VarArray(IO, 0x1000); + file->Bool32(&Enabled); + file->Bool32(&PowerOn); + file->Var16(&Random); + file->Var32((u32*)&TimerError); + file->VarArray(BBRegs, 0x100); file->VarArray(BBRegsRO, 0x100); @@ -240,17 +295,107 @@ void DoSavestate(Savestate* file) file->Var64(&USCompare); file->Bool32(&BlockBeaconIRQ14); + file->Var32(&CmdCounter); + + file->Var64(&USTimestamp); + + for (int i = 0; i < 6; i++) + { + TXSlot* slot = &TXSlots[i]; + + file->Bool32(&slot->Valid); + file->Var16(&slot->Addr); + file->Var16(&slot->Length); + file->Var8(&slot->Rate); + file->Var8(&slot->CurPhase); + file->Var32((u32*)&slot->CurPhaseTime); + file->Var32(&slot->HalfwordTimeMask); + } + + file->VarArray(RXBuffer, sizeof(RXBuffer)); + file->Var32(&RXBufferPtr); + file->Var32((u32*)&RXTime); + file->Var32(&RXHalfwordTimeMask); + file->Var32(&ComStatus); file->Var32(&TXCurSlot); file->Var32(&RXCounter); file->Var32((u32*)&MPReplyTimer); - file->Var32((u32*)&MPNumReplies); + file->Var16(&MPClientMask); + file->Var16(&MPClientFail); - file->Var32(&CmdCounter); + file->VarArray(MPClientReplies, sizeof(MPClientReplies)); + + file->Var32((u32*)&USUntilPowerOn); + file->Bool32(&ForcePowerOn); + + file->Bool32(&IsMPClient); + file->Var64(&NextSync); + file->Var64(&RXTimestamp); } +void ScheduleTimer(bool first) +{ + if (first) TimerError = 0; + + s32 cycles = 33513982 * kTimerInterval; + cycles -= TimerError; + s32 delay = (cycles + 999999) / 1000000; + TimerError = (delay * 1000000) - cycles; + + NDS::ScheduleEvent(NDS::Event_Wifi, !first, delay, USTimer, 0); +} + +void UpdatePowerOn() +{ + bool on = Enabled; + + if (NDS::ConsoleType == 1) + { + // TODO for DSi: + // * W_POWER_US doesn't work (atleast on DWM-W024) + // * other registers like GPIO_WIFI may also control wifi power/clock + // * turning wifi off via POWCNT2 while sending breaks further attempts at sending frames + } + else + { + on = on && ((IOPORT(W_PowerUS) & 0x1) == 0); + } + + if (on == PowerOn) + return; + + PowerOn = on; + if (on) + { + printf("WIFI: ON\n"); + + ScheduleTimer(true); + + Platform::MP_Begin(); + } + else + { + printf("WIFI: OFF\n"); + + NDS::CancelEvent(NDS::Event_Wifi); + + Platform::MP_End(); + } +} + +void SetPowerCnt(u32 val) +{ + Enabled = val & (1<<1); + UpdatePowerOn(); +} + + +void PowerDown(); +void StartTX_Beacon(); + void SetIRQ(u32 irq) { u32 oldflags = IOPORT(W_IF) & IOPORT(W_IE); @@ -269,7 +414,8 @@ void SetIRQ13() if (!(IOPORT(W_PowerTX) & 0x0002)) { IOPORT(0x034) = 0x0002; - // TODO: 03C + //PowerDown(); + // FIXME!! IOPORT(W_RFPins) = 0x0046; IOPORT(W_RFStatus) = 9; } @@ -318,20 +464,32 @@ void SetIRQ15() void SetStatus(u32 status) { - // TODO, eventually: states 2/4, also find out what state 7 is + // TODO, eventually: states 2/4/7 u16 rfpins[10] = {0x04, 0x84, 0, 0x46, 0, 0x84, 0x87, 0, 0x46, 0x04}; IOPORT(W_RFStatus) = status; IOPORT(W_RFPins) = rfpins[status]; } -bool MACEqual(u8* a, u8* b) +void PowerDown() { - return (*(u32*)&a[0] == *(u32*)&b[0]) && (*(u16*)&a[4] == *(u16*)&b[4]); + IOPORT(W_TXReqRead) &= ~0x000F; + IOPORT(W_PowerState) |= 0x0200; + + // if the RF hardware is powered down while still sending or receiving, + // the current frame is completed before going idle + if (!ComStatus) + { + SetStatus(9); + } } -// TODO: set RFSTATUS/RFPINS +bool MACEqual(const u8* a, const u8* b) +{ + return (*(u32*)&a[0] == *(u32*)&b[0]) && (*(u16*)&a[4] == *(u16*)&b[4]); +} + int PreambleLen(int rate) { @@ -340,6 +498,16 @@ int PreambleLen(int rate) return 192; } +u32 NumClients(u16 bitmask) +{ + u32 ret = 0; + for (int i = 1; i < 16; i++) + { + if (bitmask & (1<Addr + 0x4]; @@ -347,6 +515,19 @@ void IncrementTXCount(TXSlot* slot) *(u16*)&RAM[slot->Addr + 0x4] = cnt; } +void ReportMPReplyErrors(u16 clientfail) +{ + // TODO: do these trigger any IRQ? + + for (int i = 1; i < 16; i++) + { + if (!(clientfail & (1<Addr = (IOPORT(W_TXSlotCmd) & 0x0FFF) << 1; slot->Length = *(u16*)&RAM[slot->Addr + 0xA] & 0x3FFF; @@ -401,9 +583,11 @@ void StartTX_Beacon() IOPORT(W_TXBusy) |= 0x0010; } -// TODO eventually: there is a small delay to firing TX void FireTX() { + if (!(IOPORT(W_RXCnt) & 0x8000)) + return; + u16 txbusy = IOPORT(W_TXBusy); u16 txreq = IOPORT(W_TXReqRead); @@ -443,24 +627,89 @@ void FireTX() } } +void SendMPDefaultReply() +{ + u8 reply[12 + 32]; + + *(u16*)&reply[0xA] = 28; // length + + // rate + //if (TXSlots[1].Rate == 2) reply[0x8] = 0x14; + //else reply[0x8] = 0xA; + // TODO + reply[0x8] = 0x14; + + *(u16*)&reply[0xC + 0x00] = 0x0158; + *(u16*)&reply[0xC + 0x02] = 0x00F0;//0; // TODO?? + *(u16*)&reply[0xC + 0x04] = IOPORT(W_BSSID0); + *(u16*)&reply[0xC + 0x06] = IOPORT(W_BSSID1); + *(u16*)&reply[0xC + 0x08] = IOPORT(W_BSSID2); + *(u16*)&reply[0xC + 0x0A] = IOPORT(W_MACAddr0); + *(u16*)&reply[0xC + 0x0C] = IOPORT(W_MACAddr1); + *(u16*)&reply[0xC + 0x0E] = IOPORT(W_MACAddr2); + *(u16*)&reply[0xC + 0x10] = 0x0903; + *(u16*)&reply[0xC + 0x12] = 0x00BF; + *(u16*)&reply[0xC + 0x14] = 0x1000; + *(u16*)&reply[0xC + 0x16] = IOPORT(W_TXSeqNo) << 4; + *(u32*)&reply[0xC + 0x18] = 0; + + int txlen = Platform::MP_SendReply(reply, 12+28, USTimestamp, IOPORT(W_AIDLow)); + WIFI_LOG("wifi: sent %d/40 bytes of MP default reply\n", txlen); +} + void SendMPReply(u16 clienttime, u16 clientmask) { TXSlot* slot = &TXSlots[5]; // mark the last packet as success. dunno what the MSB is, it changes. + //if (slot->Valid) if (IOPORT(W_TXSlotReply2) & 0x8000) *(u16*)&RAM[slot->Addr] = 0x0001; + // CHECKME!! + // can the transfer rate for MP replies be set, or is it determined from the CMD transfer rate? + // how does it work for default empty replies? + slot->Rate = 2; + IOPORT(W_TXSlotReply2) = IOPORT(W_TXSlotReply1); IOPORT(W_TXSlotReply1) = 0; + if (!(IOPORT(W_TXSlotReply2) & 0x8000)) + { + slot->Valid = false; + } + else + { + slot->Valid = true; + + slot->Addr = (IOPORT(W_TXSlotReply2) & 0x0FFF) << 1; + slot->Length = *(u16*)&RAM[slot->Addr + 0xA] & 0x3FFF; + + // the packet is entirely ignored if it lasts longer than the maximum reply time + u32 duration = PreambleLen(slot->Rate) + (slot->Length * (slot->Rate==2 ? 4:8)); + if (duration > clienttime) + slot->Valid = false; + } + + //if (RAM[slot->Addr+4] > 0) + // printf("REPLY RETRY COUNTER %d (%04X)\n", RAM[slot->Addr+4], IOPORT(W_TXSlotReply2)); + // this seems to be set upon IRQ0 // TODO: how does it behave if the packet addr is changed before it gets sent? (maybe just not possible) - if (IOPORT(W_TXSlotReply2) & 0x8000) + if (slot->Valid) { - slot->Addr = (IOPORT(W_TXSlotReply2) & 0x0FFF) << 1; //*(u16*)&RAM[slot->Addr + 0x4] = 0x0001; IncrementTXCount(slot); + + slot->CurPhase = 0; + int txlen = Platform::MP_SendReply(&RAM[slot->Addr], 12 + slot->Length, USTimestamp, IOPORT(W_AIDLow)); + WIFI_LOG("wifi: sent %d/%d bytes of MP reply\n", txlen, 12 + slot->Length); + } + else + { + slot->CurPhase = 10; + + SendMPDefaultReply(); } u16 clientnum = 0; @@ -470,43 +719,12 @@ void SendMPReply(u16 clienttime, u16 clientmask) clientnum++; } - slot->CurPhase = 0; - slot->CurPhaseTime = 16 + ((clienttime + 10) * clientnum); + slot->CurPhaseTime = 16 + ((clienttime + 10) * clientnum) + PreambleLen(slot->Rate); IOPORT(W_TXBusy) |= 0x0080; } -void SendMPDefaultReply() -{ - u8 reply[12 + 32]; - - *(u16*)&reply[0xA] = 28; // length - - // rate - //if (TXSlots[1].Rate == 2) reply[0x8] = 0x14; - //else reply[0x8] = 0xA; - // TODO - reply[0x8] = 0x14; - - *(u16*)&reply[0xC + 0x00] = 0x0158; - *(u16*)&reply[0xC + 0x02] = 0x00F0;//0; // TODO?? - *(u16*)&reply[0xC + 0x04] = IOPORT(W_BSSID0); - *(u16*)&reply[0xC + 0x06] = IOPORT(W_BSSID1); - *(u16*)&reply[0xC + 0x08] = IOPORT(W_BSSID2); - *(u16*)&reply[0xC + 0x0A] = IOPORT(W_MACAddr0); - *(u16*)&reply[0xC + 0x0C] = IOPORT(W_MACAddr1); - *(u16*)&reply[0xC + 0x0E] = IOPORT(W_MACAddr2); - *(u16*)&reply[0xC + 0x10] = 0x0903; - *(u16*)&reply[0xC + 0x12] = 0x00BF; - *(u16*)&reply[0xC + 0x14] = 0x1000; - *(u16*)&reply[0xC + 0x16] = IOPORT(W_TXSeqNo) << 4; - *(u32*)&reply[0xC + 0x18] = 0; - - int txlen = Platform::MP_SendPacket(reply, 12+28); - WIFI_LOG("wifi: sent %d/40 bytes of MP default reply\n", txlen); -} - -void SendMPAck() +void SendMPAck(u16 clientfail) { u8 ack[12 + 32]; @@ -516,41 +734,32 @@ void SendMPAck() if (TXSlots[1].Rate == 2) ack[0x8] = 0x14; else ack[0x8] = 0xA; - *(u16*)&ack[0xC + 0x00] = 0x0218; - *(u16*)&ack[0xC + 0x02] = 0; - *(u16*)&ack[0xC + 0x04] = 0x0903; - *(u16*)&ack[0xC + 0x06] = 0x00BF; - *(u16*)&ack[0xC + 0x08] = 0x0300; - *(u16*)&ack[0xC + 0x0A] = IOPORT(W_BSSID0); - *(u16*)&ack[0xC + 0x0C] = IOPORT(W_BSSID1); - *(u16*)&ack[0xC + 0x0E] = IOPORT(W_BSSID2); - *(u16*)&ack[0xC + 0x10] = IOPORT(W_MACAddr0); - *(u16*)&ack[0xC + 0x12] = IOPORT(W_MACAddr1); - *(u16*)&ack[0xC + 0x14] = IOPORT(W_MACAddr2); - *(u16*)&ack[0xC + 0x16] = IOPORT(W_TXSeqNo) << 4; - *(u16*)&ack[0xC + 0x18] = 0x0033; // ??? - *(u16*)&ack[0xC + 0x1A] = 0; - *(u32*)&ack[0xC + 0x1C] = 0; - - int txlen = Platform::MP_SendPacket(ack, 12+32); - WIFI_LOG("wifi: sent %d/44 bytes of MP ack, %d %d\n", txlen, ComStatus, RXTime); -} - -u32 NumClients(u16 bitmask) -{ - u32 ret = 0; - for (int i = 1; i < 16; i++) - { - if (bitmask & (1<CurPhaseTime--; + slot->CurPhaseTime -= kTimerInterval; if (slot->CurPhaseTime > 0) { if (slot->CurPhase == 1) @@ -560,19 +769,28 @@ bool ProcessTX(TXSlot* slot, int num) } else if (slot->CurPhase == 2) { - MPReplyTimer--; - if (MPReplyTimer == 0 && MPNumReplies > 0) + MPReplyTimer -= kTimerInterval; + if (MPReplyTimer <= 0 && MPClientMask != 0) { - if (CheckRX(true)) + int nclient = 1; + while (!(MPClientMask & (1 << nclient))) nclient++; + + u32 curclient = 1 << nclient; + + /*if (CheckRX(1)) { - ComStatus |= 0x1; - } + // we received a reply, mark it as such + // TODO: is any received packet considered a good reply? + // hardware probably requires a specific frame-control and/or destination MAC - // TODO: properly handle reply errors - // also, if the reply is too big to fit within its window, what happens? + MPClientFail &= ~curclient; + } + else printf("REPLY %04X NOT RECEIVED\n");*/ + if (!(MPClientFail & curclient)) + MPClientReplyRX(nclient); - MPReplyTimer = 10 + IOPORT(W_CmdReplyTime); - MPNumReplies--; + MPReplyTimer += 10 + IOPORT(W_CmdReplyTime); + MPClientMask &= ~curclient; } } @@ -592,31 +810,16 @@ bool ProcessTX(TXSlot* slot, int num) SetStatus(8); - // if no reply is configured, send a default empty reply - if (!(IOPORT(W_TXSlotReply2) & 0x8000)) - { - SendMPDefaultReply(); - - slot->Addr = 0; - slot->Length = 28; - slot->Rate = 2; // TODO - slot->CurPhase = 4; - slot->CurPhaseTime = 28*4; - slot->HalfwordTimeMask = 0xFFFFFFFF; - IOPORT(W_TXSeqNo) = (IOPORT(W_TXSeqNo) + 1) & 0x0FFF; - break; - } + //slot->Addr = (IOPORT(W_TXSlotReply2) & 0x0FFF) << 1; + //slot->Length = *(u16*)&RAM[slot->Addr + 0xA] & 0x3FFF; - slot->Addr = (IOPORT(W_TXSlotReply2) & 0x0FFF) << 1; - slot->Length = *(u16*)&RAM[slot->Addr + 0xA] & 0x3FFF; + /*u8 rate = RAM[slot->Addr + 0x8]; + if (rate == 0x14) slot->Rate = 2; + else slot->Rate = 1;*/ // TODO: duration should be set by hardware // doesn't seem to be important //RAM[slot->Addr + 0xC + 2] = 0x00F0; - - u8 rate = RAM[slot->Addr + 0x8]; - if (rate == 0x14) slot->Rate = 2; - else slot->Rate = 1; } else SetStatus(3); @@ -625,17 +828,32 @@ bool ProcessTX(TXSlot* slot, int num) if (slot->Rate == 2) { len *= 4; - slot->HalfwordTimeMask = 0x7; + slot->HalfwordTimeMask = 0x7 & kTimeCheckMask; } else { len *= 8; - slot->HalfwordTimeMask = 0xF; + slot->HalfwordTimeMask = 0xF & kTimeCheckMask; } slot->CurPhase = 1; slot->CurPhaseTime = len; + u16 framectl = *(u16*)&RAM[slot->Addr + 0xC]; + if (framectl & (1<<14)) + { + // WEP frame + // TODO: what happens when sending a WEP frame while WEP processing is off? + // TODO: some form of actual WEP processing? + // for now we just set the WEP FCS to a nonzero value, because some games require it + + if (IOPORT(W_WEPCnt) & (1<<15)) + { + u32 wep_fcs = (slot->Addr + 0xC + slot->Length - 7) & ~0x1; + *(u32*)&RAM[wep_fcs] = 0x22334466; + } + } + u64 oldts; if (num == 4) { @@ -644,28 +862,66 @@ bool ProcessTX(TXSlot* slot, int num) *(u64*)&RAM[slot->Addr + 0xC + 24] = USCounter; } - //u32 noseqno = 0; - //if (num == 1) noseqno = (IOPORT(W_TXSlotCmd) & 0x4000); + u32 noseqno = 0; + if (num == 1) noseqno = (IOPORT(W_TXSlotCmd) & 0x4000); - //if (!noseqno) + if (!noseqno) { *(u16*)&RAM[slot->Addr + 0xC + 22] = IOPORT(W_TXSeqNo) << 4; IOPORT(W_TXSeqNo) = (IOPORT(W_TXSeqNo) + 1) & 0x0FFF; } + if ((num != 5) && (RAM[slot->Addr+4] > 0)) + printf("SLOT %d RETRY COUNTER %d\n", RAM[slot->Addr+4]); + // set TX addr IOPORT(W_RXTXAddr) = slot->Addr >> 1; - // send - int txlen = Platform::MP_SendPacket(&RAM[slot->Addr], 12 + slot->Length); - WIFI_LOG("wifi: sent %d/%d bytes of slot%d packet, addr=%04X, framectl=%04X, %04X %04X\n", - txlen, slot->Length+12, num, slot->Addr, *(u16*)&RAM[slot->Addr + 0xC], - *(u16*)&RAM[slot->Addr + 0x24], *(u16*)&RAM[slot->Addr + 0x26]); + if (num == 1) + { + // send + int txlen = Platform::MP_SendCmd(&RAM[slot->Addr], 12 + slot->Length, USTimestamp); + WIFI_LOG("wifi: sent %d/%d bytes of slot%d packet, addr=%04X, framectl=%04X, %04X %04X\n", + txlen, slot->Length+12, num, slot->Addr, *(u16*)&RAM[slot->Addr + 0xC], + *(u16*)&RAM[slot->Addr + 0x24], *(u16*)&RAM[slot->Addr + 0x26]); + } + else if (num == 5) + { + // send + /*int txlen = Platform::MP_SendReply(&RAM[slot->Addr], 12 + slot->Length, USTimestamp, IOPORT(W_AIDLow)); + WIFI_LOG("wifi: sent %d/%d bytes of slot%d packet, addr=%04X, framectl=%04X, %04X %04X\n", + txlen, slot->Length+12, num, slot->Addr, *(u16*)&RAM[slot->Addr + 0xC], + *(u16*)&RAM[slot->Addr + 0x24], *(u16*)&RAM[slot->Addr + 0x26]);*/ + } + else //if (num != 5) + { + // send + int txlen = Platform::MP_SendPacket(&RAM[slot->Addr], 12 + slot->Length, USTimestamp); + WIFI_LOG("wifi: sent %d/%d bytes of slot%d packet, addr=%04X, framectl=%04X, %04X %04X\n", + txlen, slot->Length+12, num, slot->Addr, *(u16*)&RAM[slot->Addr + 0xC], + *(u16*)&RAM[slot->Addr + 0x24], *(u16*)&RAM[slot->Addr + 0x26]); + } // if the packet is being sent via LOC1..3, send it to the AP // any packet sent via CMD/REPLY/BEACON isn't going to have much use outside of local MP if (num == 0 || num == 2 || num == 3) + { + if ((framectl & 0x00FF) == 0x0010) + { + u16 aid = *(u16*)&RAM[slot->Addr + 0xC + 24 + 4]; + if (aid) printf("[HOST] syncing client %04X, sync=%016llX\n", aid, USTimestamp); + } + else if ((framectl & 0x00FF) == 0x00C0) + { + if (IsMPClient) + { + printf("[CLIENT] deauth\n"); + IsMPClient = false; + } + } + WifiAP::SendPacket(&RAM[slot->Addr], 12 + slot->Length); + } if (num == 4) { @@ -674,10 +930,25 @@ bool ProcessTX(TXSlot* slot, int num) } break; + case 10: // preamble done (default empty MP reply) + { + SetIRQ(7); + SetStatus(8); + + //SendMPDefaultReply(); + + //slot->Addr = 0; + //slot->Length = 28; + slot->CurPhase = 4; + slot->CurPhaseTime = 28*4; + slot->HalfwordTimeMask = 0xFFFFFFFF; + } + break; + case 1: // transmit done { - // for the MP reply slot, this is set later - if (num != 5) + // for the MP CMD and reply slots, this is set later + if (num != 1 && num != 5) *(u16*)&RAM[slot->Addr] = 0x0001; RAM[slot->Addr + 5] = 0; @@ -690,12 +961,21 @@ bool ProcessTX(TXSlot* slot, int num) } SetStatus(5); - u16 clientmask = *(u16*)&RAM[slot->Addr + 12 + 24 + 2]; - MPNumReplies = NumClients(clientmask); - MPReplyTimer = 16; + u16 clientmask = *(u16*)&RAM[slot->Addr + 12 + 24 + 2] & 0xFFFE; + //MPNumReplies = NumClients(clientmask); + MPReplyTimer = 16 + PreambleLen(slot->Rate); + MPClientMask = clientmask; + MPClientFail = clientmask; + u16 res = 0; + if (clientmask) + res = Platform::MP_RecvReplies(MPClientReplies, USTimestamp, clientmask); + MPClientFail &= ~res; + + // TODO: 112 likely includes the ack preamble, which needs adjusted + // for long-preamble settings slot->CurPhase = 2; - slot->CurPhaseTime = 112 + ((10 + IOPORT(W_CmdReplyTime)) * MPNumReplies); + slot->CurPhaseTime = 112 + ((10 + IOPORT(W_CmdReplyTime)) * NumClients(clientmask)); break; } @@ -750,7 +1030,10 @@ bool ProcessTX(TXSlot* slot, int num) if (slot->Rate == 2) slot->CurPhaseTime = 32 * 4; else slot->CurPhaseTime = 32 * 8; - SendMPAck(); + ReportMPReplyErrors(MPClientFail); + + // send + SendMPAck(MPClientFail); slot->CurPhase = 3; } @@ -762,11 +1045,15 @@ bool ProcessTX(TXSlot* slot, int num) IOPORT(W_TXBusy) &= ~(1<<1); IOPORT(W_TXSlotCmd) &= 0x7FFF; // confirmed - // seems this is set to indicate which clients failed to reply - *(u16*)&RAM[slot->Addr + 0x2] = 0; + if (!MPClientFail) + *(u16*)&RAM[slot->Addr] = 0x0001; + else + *(u16*)&RAM[slot->Addr] = 0x0005; + + // this is set to indicate which clients failed to reply + *(u16*)&RAM[slot->Addr + 0x2] = MPClientFail; IncrementTXCount(slot); - SetIRQ(12); IOPORT(W_TXSeqNo) = (IOPORT(W_TXSeqNo) + 1) & 0x0FFF; if (IOPORT(W_TXStatCnt) & 0x2000) @@ -776,12 +1063,19 @@ bool ProcessTX(TXSlot* slot, int num) } SetStatus(1); + // TODO: retry the whole cycle if some clients failed to respond + // AND if there is enough time left in CMDCOUNT + // (games seem to always configure CMDCOUNT such that there is no time for retries) + SetIRQ(12); + FireTX(); } return true; case 4: // MP default reply transfer finished { + IOPORT(W_TXSeqNo) = (IOPORT(W_TXSeqNo) + 1) & 0x0FFF; + IOPORT(W_TXBusy) &= ~0x80; SetStatus(1); FireTX(); @@ -804,98 +1098,403 @@ inline void IncrementRXAddr(u16& addr, u16 inc = 2) } } -bool CheckRX(bool block) +void StartRX() { + u16 framelen = *(u16*)&RXBuffer[8]; + RXTime = framelen; + + u16 txrate = *(u16*)&RXBuffer[6]; + if (txrate == 0x14) + { + RXTime *= 4; + RXHalfwordTimeMask = 0x7 & kTimeCheckMask; + } + else + { + RXTime *= 8; + RXHalfwordTimeMask = 0xF & kTimeCheckMask; + } + + u16 addr = IOPORT(W_RXBufWriteCursor) << 1; + IncrementRXAddr(addr, 12); + IOPORT(W_RXTXAddr) = addr >> 1; + + RXBufferPtr = 12; + + SetIRQ(6); + SetStatus(6); + ComStatus |= 1; +} + +void FinishRX() +{ + ComStatus &= ~0x1; + RXCounter = 0; + + if (!ComStatus) + { + if (IOPORT(W_PowerState) & 0x0300) + SetStatus(9); + else + SetStatus(1); + } + + // TODO: RX stats + + u16 framectl = *(u16*)&RXBuffer[12]; + + // check the frame's destination address + // note: the hardware always checks the first address field, regardless of the frame type/etc + // similarly, the second address field is used to send acks to non-broadcast frames + + u8* dstmac = &RXBuffer[12 + 4]; + if (!(dstmac[0] & 0x01)) + { + if (!MACEqual(dstmac, (u8*)&IOPORT(W_MACAddr0))) + return; + } + + // reject the frame if it's a WEP frame and WEP is off + // TODO: check if sending WEP frames with WEP off works at all? + + if (framectl & (1<<14)) + { + if (!(IOPORT(W_WEPCnt) & (1<<15))) + return; + } + + // apply RX filtering + // TODO: + // * RXFILTER bits 0, 9, 10, 12 not fully understood + // * port 0D8 also affects reception of frames + // * MP CMD frames with a duplicate sequence number are ignored + + u16 rxflags = 0x0010; + + switch ((framectl >> 2) & 0x3) + { + case 0: // management + { + u8* bssid = &RXBuffer[12 + 16]; + if (MACEqual(bssid, (u8*)&IOPORT(W_BSSID0))) + rxflags |= 0x8000; + + u16 subtype = (framectl >> 4) & 0xF; + if (subtype == 0x8) // beacon + { + if (!(rxflags & 0x8000)) + { + if (!(IOPORT(W_RXFilter) & (1<<0))) + return; + } + + rxflags |= 0x0001; + } + else if ((subtype <= 0x5) || + (subtype >= 0xA && subtype <= 0xC)) + { + if (!(rxflags & 0x8000)) + { + // CHECKME! + if (!(IOPORT(W_RXFilter) & (3<<9))) + return; + } + } + } + break; + + case 1: // control + { + if ((framectl & 0xF0) == 0xA0) // PS-poll + { + u8* bssid = &RXBuffer[12 + 4]; + if (MACEqual(bssid, (u8*)&IOPORT(W_BSSID0))) + rxflags |= 0x8000; + + if (!(rxflags & 0x8000)) + { + if (!(IOPORT(W_RXFilter) & (1<<11))) + return; + } + + rxflags |= 0x0005; + } + else + return; + } + break; + + case 2: // data + { + u16 fromto = (framectl >> 8) & 0x3; + if (IOPORT(W_RXFilter2) & (1<> 4) & 0xF) + { + case 0x0: break; + + case 0x1: + if ((rxflags & 0xF) == 0xD) + { + if (!(rxfilter & (1<<7))) return; + } + else if ((rxflags & 0xF) != 0xE) + { + if (!(rxfilter & (1<<1))) return; + } + break; + + case 0x2: + if ((rxflags & 0xF) != 0xC) + { + if (!(rxfilter & (1<<2))) return; + } + break; + + case 0x3: + if (!(rxfilter & (1<<3))) return; + break; + + case 0x4: break; + + case 0x5: + if ((rxflags & 0xF) == 0xF) + { + if (!(rxfilter & (1<<8))) return; + } + else + { + if (!(rxfilter & (1<<4))) return; + } + break; + + case 0x6: + if (!(rxfilter & (1<<5))) return; + break; + + case 0x7: + if (!(rxfilter & (1<<6))) return; + break; + + default: + return; + } + } + break; + } + + // build the RX header + + u16 headeraddr = IOPORT(W_RXBufWriteCursor) << 1; + *(u16*)&RAM[headeraddr] = rxflags; + IncrementRXAddr(headeraddr); + *(u16*)&RAM[headeraddr] = 0x0040; // ??? + IncrementRXAddr(headeraddr, 4); + *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[6]; // TX rate + IncrementRXAddr(headeraddr); + *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[8]; // frame length + IncrementRXAddr(headeraddr); + *(u16*)&RAM[headeraddr] = 0x4080; // RSSI + + // signal successful reception + + u16 addr = IOPORT(W_RXTXAddr) << 1; + if (addr & 0x2) IncrementRXAddr(addr); + IOPORT(W_RXBufWriteCursor) = (addr & ~0x3) >> 1; + + SetIRQ(0); + + if ((rxflags & 0x800F) == 0x800C) + { + // reply to CMD frames + + u16 clientmask = *(u16*)&RXBuffer[0xC + 26]; + if (IOPORT(W_AIDLow) && (clientmask & (1 << IOPORT(W_AIDLow)))) + { + SendMPReply(*(u16*)&RXBuffer[0xC + 24], clientmask); + } + else + { + // send a blank + // this is just so the host can have something to receive, instead of hitting a timeout + // in the case this client wasn't ready to send a reply + // TODO: also send this if we have RX disabled + + Platform::MP_SendReply(nullptr, 0, USTimestamp, 0); + } + } + else if ((rxflags & 0x800F) == 0x8001) + { + // when receiving a beacon with the right BSSID, the beacon's timestamp + // is copied to USCOUNTER + + u32 len = *(u16*)&RXBuffer[8]; + u16 txrate = *(u16*)&RXBuffer[6]; + len *= ((txrate==0x14) ? 4 : 8); + len -= 76; // CHECKME: is this offset fixed? + + u64 timestamp = *(u64*)&RXBuffer[12 + 24]; + timestamp += (u64)len; + + USCounter = timestamp; + } +} + +void MPClientReplyRX(int client) +{ + if (IOPORT(W_PowerState) & 0x0300) + return; + + if (!(IOPORT(W_RXCnt) & 0x8000)) + return; + + if (IOPORT(W_RXBufBegin) == IOPORT(W_RXBufEnd)) + return; + + int framelen; + u8 txrate; + + u8* reply = &MPClientReplies[(client-1)*1024]; + framelen = *(u16*)&reply[10]; + + txrate = reply[8]; + + // TODO: what are the maximum crop values? + u16 framectl = *(u16*)&reply[12]; + if (framectl & (1<<14)) + { + framelen -= (IOPORT(W_RXLenCrop) >> 7) & 0x1FE; + if (framelen > 24) memmove(&RXBuffer[12+24], &RXBuffer[12+28], framelen); + } + else + framelen -= (IOPORT(W_RXLenCrop) << 1) & 0x1FE; + + if (framelen < 0) framelen = 0; + + // TODO rework RX system so we don't need this (by reading directly into MPClientReplies) + memcpy(RXBuffer, reply, 12+framelen); + + *(u16*)&RXBuffer[6] = txrate; + *(u16*)&RXBuffer[8] = framelen; + + RXTimestamp = 0; + StartRX(); +} + +bool CheckRX(int type) // 0=regular 1=MP replies 2=MP host frames +{ + if (IOPORT(W_PowerState) & 0x0300) + return false; + if (!(IOPORT(W_RXCnt) & 0x8000)) return false; if (IOPORT(W_RXBufBegin) == IOPORT(W_RXBufEnd)) return false; - u16 framelen; + int rxlen; + int framelen; u16 framectl; u8 txrate; - bool bssidmatch; - u16 rxflags; + u64 timestamp; for (;;) { - int rxlen = Platform::MP_RecvPacket(RXBuffer, block); - if (rxlen == 0) rxlen = WifiAP::RecvPacket(RXBuffer); - if (rxlen == 0) return false; + timestamp = 0; + + if (type == 0) + { + rxlen = Platform::MP_RecvPacket(RXBuffer, ×tamp); + if (rxlen <= 0) + rxlen = WifiAP::RecvPacket(RXBuffer); + } + else + { + rxlen = Platform::MP_RecvHostPacket(RXBuffer, ×tamp); + if (rxlen < 0) + { + // host is gone + // TODO: make this more resilient + IsMPClient = false; + } + } + + if (rxlen <= 0) return false; if (rxlen < 12+24) continue; framelen = *(u16*)&RXBuffer[10]; if (framelen != rxlen-12) { - printf("bad frame length\n"); + printf("bad frame length %d/%d\n", framelen, rxlen-12); continue; } - framelen -= 4; framectl = *(u16*)&RXBuffer[12+0]; txrate = RXBuffer[8]; - u32 a_src, a_dst, a_bss; - rxflags = 0x0010; - switch (framectl & 0x000C) + // TODO: what are the maximum crop values? + if (framectl & (1<<14)) { - case 0x0000: // management - a_src = 10; - a_dst = 4; - a_bss = 16; - if ((framectl & 0x00F0) == 0x0080) - rxflags |= 0x0001; - break; - - case 0x0004: // control - printf("blarg\n"); - continue; - - case 0x0008: // data - switch (framectl & 0x0300) - { - case 0x0000: // STA to STA - a_src = 10; - a_dst = 4; - a_bss = 16; - break; - case 0x0100: // STA to DS - a_src = 10; - a_dst = 16; - a_bss = 4; - break; - case 0x0200: // DS to STA - a_src = 16; - a_dst = 4; - a_bss = 10; - break; - case 0x0300: // DS to DS - printf("blarg\n"); - continue; - } - // TODO: those also trigger on other framectl values - // like 0208 -> C - framectl &= 0xE7FF; - if (framectl == 0x0228) rxflags |= 0x000C; // MP host frame - else if (framectl == 0x0218) rxflags |= 0x000D; // MP ack frame - else if (framectl == 0x0118) rxflags |= 0x000E; // MP reply frame - else if (framectl == 0x0158) rxflags |= 0x000F; // empty MP reply frame - else rxflags |= 0x0008; - break; + framelen -= (IOPORT(W_RXLenCrop) >> 7) & 0x1FE; + if (framelen > 24) memmove(&RXBuffer[12+24], &RXBuffer[12+28], framelen); } + else + framelen -= (IOPORT(W_RXLenCrop) << 1) & 0x1FE; - if (MACEqual(&RXBuffer[12 + a_src], (u8*)&IOPORT(W_MACAddr0))) - continue; // oops. we received a packet we just sent. - - bssidmatch = MACEqual(&RXBuffer[12 + a_bss], (u8*)&IOPORT(W_BSSID0)); - //if (!(IOPORT(W_BSSID0) & 0x0001) && !(RXBuffer[12 + a_bss] & 0x01) && - if (!MACEqual(&RXBuffer[12 + a_dst], (u8*)&IOPORT(W_MACAddr0)) && - !(RXBuffer[12 + a_dst] & 0x01)) - { - printf("received packet %04X but it didn't pass the MAC check\n", framectl); - continue; - } + if (framelen < 0) framelen = 0; break; } @@ -903,37 +1502,64 @@ bool CheckRX(bool block) WIFI_LOG("wifi: received packet FC:%04X SN:%04X CL:%04X RXT:%d CMT:%d\n", framectl, *(u16*)&RXBuffer[12+4+6+6+6], *(u16*)&RXBuffer[12+4+6+6+6+2+2], framelen*4, IOPORT(W_CmdReplyTime)); - // make RX header - - if (bssidmatch) rxflags |= 0x8000; - - *(u16*)&RXBuffer[0] = rxflags; - *(u16*)&RXBuffer[2] = 0x0040; // ??? *(u16*)&RXBuffer[6] = txrate; *(u16*)&RXBuffer[8] = framelen; - *(u16*)&RXBuffer[10] = 0x4080; // min/max RSSI. dunno - RXTime = framelen; + bool macgood = (RXBuffer[12 + 4] & 0x01) || MACEqual(&RXBuffer[12 + 4], (u8*)&IOPORT(W_MACAddr0)); - if (txrate == 0x14) + if (((framectl & 0x00FF) == 0x0010) && timestamp && macgood) { - RXTime *= 4; - RXHalfwordTimeMask = 0x7; + // if receiving an association response: get the sync value from the host + + u16 aid = *(u16*)&RXBuffer[12+24+4]; + + if (aid) + { + printf("[CLIENT %01X] host sync=%016llX\n", aid&0xF, timestamp); + + IsMPClient = true; + USTimestamp = timestamp; + NextSync = RXTimestamp + (framelen * (txrate==0x14 ? 4:8)); + } + + RXTimestamp = 0; + StartRX(); } - else + else if (((framectl & 0x00FF) == 0x00C0) && timestamp && macgood && IsMPClient) { - RXTime *= 8; - RXHalfwordTimeMask = 0xF; + IsMPClient = false; + NextSync = 0; + + RXTimestamp = 0; + StartRX(); } + else if (macgood && IsMPClient) + { + // if we are being a MP client, we need to delay this frame until we reach the + // timestamp it came with + // we also need to determine how far we can run after having received this frame - u16 addr = IOPORT(W_RXBufWriteCursor) << 1; - IncrementRXAddr(addr, 12); - IOPORT(W_RXTXAddr) = addr >> 1; + RXTimestamp = timestamp; + if (RXTimestamp < USTimestamp) RXTimestamp = USTimestamp; + NextSync = RXTimestamp + (framelen * (txrate==0x14 ? 4:8)); - RXBufferPtr = 12; + if (MACEqual(&RXBuffer[12 + 4], MPCmdMAC)) + { + u16 clienttime = *(u16*)&RXBuffer[12+24]; + u16 clientmask = *(u16*)&RXBuffer[12+26]; + + // include the MP reply time window + NextSync += 112 + ((clienttime + 10) * NumClients(clientmask)); + } + } + else + { + // otherwise, just start receiving this frame now + + RXTimestamp = 0; + StartRX(); + } - SetIRQ(6); - SetStatus(6); return true; } @@ -942,7 +1568,7 @@ void MSTimer() { if (IOPORT(W_USCompareCnt)) { - if (USCounter == USCompare) + if ((USCounter & ~0x3FF) == USCompare) { BlockBeaconIRQ14 = false; SetIRQ14(0); @@ -964,16 +1590,34 @@ void MSTimer() void USTimer(u32 param) { - WifiAP::USTimer(); + USTimestamp += kTimerInterval; + + if (IsMPClient && (!ComStatus)) + { + if (RXTimestamp && (USTimestamp >= RXTimestamp)) + { + RXTimestamp = 0; + StartRX(); + } + + if (USTimestamp >= NextSync) + { + // TODO: not do this every tick if it fails to receive a frame! + CheckRX(2); + } + } + + if (!(USTimestamp & 0x3FF & kTimeCheckMask)) + WifiAP::MSTimer(); bool switchOffPowerSaving = false; if (USUntilPowerOn < 0) { - USUntilPowerOn++; + USUntilPowerOn += kTimerInterval; - switchOffPowerSaving = USUntilPowerOn == 0 && (IOPORT(W_PowerUnk) & 0x0001 || ForcePowerOn); + switchOffPowerSaving = (USUntilPowerOn >= 0) && (IOPORT(W_PowerUnk) & 0x0001 || ForcePowerOn); } - if (USUntilPowerOn == 0 && (IOPORT(W_PowerState) & 0x0002 || switchOffPowerSaving)) + if ((USUntilPowerOn >= 0) && (IOPORT(W_PowerState) & 0x0002 || switchOffPowerSaving)) { IOPORT(W_PowerState) = 0; IOPORT(W_RFPins) = 1; @@ -983,35 +1627,50 @@ void USTimer(u32 param) if (IOPORT(W_USCountCnt)) { - USCounter++; + USCounter += kTimerInterval; u32 uspart = (USCounter & 0x3FF); if (IOPORT(W_USCompareCnt)) { u32 beaconus = (IOPORT(W_BeaconCount1) << 10) | (0x3FF - uspart); - if (beaconus == IOPORT(W_PreBeacon)) SetIRQ15(); + if ((beaconus & kTimeCheckMask) == (IOPORT(W_PreBeacon) & kTimeCheckMask)) + SetIRQ15(); } - if (!uspart) MSTimer(); + if (!(uspart & kTimeCheckMask)) + MSTimer(); } if (IOPORT(W_CmdCountCnt) & 0x0001) { if (CmdCounter > 0) { - CmdCounter--; + if (CmdCounter < kTimerInterval) + CmdCounter = 0; + else + CmdCounter -= kTimerInterval; } } if (IOPORT(W_ContentFree) != 0) - IOPORT(W_ContentFree)--; + { + if (IOPORT(W_ContentFree) < kTimerInterval) + IOPORT(W_ContentFree) = 0; + else + IOPORT(W_ContentFree) -= kTimerInterval; + } - if (!(IOPORT(W_PowerState) & 0x300)) + if (ComStatus == 0) { - if (ComStatus == 0) + u16 txbusy = IOPORT(W_TXBusy); + if (txbusy) { - u16 txbusy = IOPORT(W_TXBusy); - if (txbusy) + if (IOPORT(W_PowerState) & 0x0300) + { + ComStatus = 0; + TXCurSlot = -1; + } + else { ComStatus = 0x2; if (txbusy & 0x0080) TXCurSlot = 5; @@ -1021,105 +1680,94 @@ void USTimer(u32 param) else if (txbusy & 0x0002) TXCurSlot = 1; else if (txbusy & 0x0001) TXCurSlot = 0; } - else + } + else + { + if ((!IsMPClient) || (USTimestamp > NextSync)) { - if ((!(RXCounter & 0x1FF))) + if ((!(RXCounter & 0x1FF & kTimeCheckMask)) && (!ComStatus)) { - if (CheckRX(false)) - ComStatus = 0x1; + CheckRX(0); } - - RXCounter++; } + + RXCounter += kTimerInterval; } + } - if (ComStatus & 0x2) + if (ComStatus & 0x2) + { + bool finished = ProcessTX(&TXSlots[TXCurSlot], TXCurSlot); + if (finished) { - bool finished = ProcessTX(&TXSlots[TXCurSlot], TXCurSlot); - if (finished) + if (IOPORT(W_PowerState) & 0x0300) { - // transfer finished, see if there's another slot to do - // checkme: priority order of beacon/reply - // TODO: for CMD, check CMDCOUNT - u16 txbusy = IOPORT(W_TXBusy); - if (txbusy & 0x0080) TXCurSlot = 5; - else if (txbusy & 0x0010) TXCurSlot = 4; - else if (txbusy & 0x0008) TXCurSlot = 3; - else if (txbusy & 0x0004) TXCurSlot = 2; - else if (txbusy & 0x0002) TXCurSlot = 1; - else if (txbusy & 0x0001) TXCurSlot = 0; - else - { - TXCurSlot = -1; - ComStatus = 0; - RXCounter = 0; - } + IOPORT(W_TXBusy) = 0; + SetStatus(9); + } + + // transfer finished, see if there's another slot to do + // checkme: priority order of beacon/reply + // TODO: for CMD, check CMDCOUNT + u16 txbusy = IOPORT(W_TXBusy); + if (txbusy & 0x0080) TXCurSlot = 5; + else if (txbusy & 0x0010) TXCurSlot = 4; + else if (txbusy & 0x0008) TXCurSlot = 3; + else if (txbusy & 0x0004) TXCurSlot = 2; + else if (txbusy & 0x0002) TXCurSlot = 1; + else if (txbusy & 0x0001) TXCurSlot = 0; + else + { + TXCurSlot = -1; + ComStatus = 0; + RXCounter = 0; } } - if (ComStatus & 0x1) + } + if (ComStatus & 0x1) + { + RXTime -= kTimerInterval; + if (!(RXTime & RXHalfwordTimeMask)) { - RXTime--; - if (!(RXTime & RXHalfwordTimeMask)) - { - u16 addr = IOPORT(W_RXTXAddr) << 1; - if (addr < 0x1FFF) *(u16*)&RAM[addr] = *(u16*)&RXBuffer[RXBufferPtr]; + u16 addr = IOPORT(W_RXTXAddr) << 1; + if (addr < 0x1FFF) *(u16*)&RAM[addr] = *(u16*)&RXBuffer[RXBufferPtr]; - IncrementRXAddr(addr); - RXBufferPtr += 2; + IncrementRXAddr(addr); + IOPORT(W_RXTXAddr) = addr >> 1; + RXBufferPtr += 2; - if (RXTime == 0) // finished receiving + if (RXTime <= 0) // finished receiving + { + FinishRX(); + } + else if (addr == (IOPORT(W_RXBufReadCursor) << 1)) + { + // TODO: properly check the crossing of the read cursor + // (for example, if it is outside of the RX buffer) + + printf("wifi: RX buffer full (buf=%04X/%04X rd=%04X wr=%04X rxtx=%04X power=%04X com=%d rxcnt=%04X filter=%04X/%04X frame=%04X/%04X len=%d)\n", + (IOPORT(W_RXBufBegin)>>1)&0xFFF, (IOPORT(W_RXBufEnd)>>1)&0xFFF, + IOPORT(W_RXBufReadCursor), IOPORT(W_RXBufWriteCursor), + IOPORT(W_RXTXAddr), IOPORT(W_PowerState), ComStatus, + IOPORT(W_RXCnt), IOPORT(W_RXFilter), IOPORT(W_RXFilter2), + *(u16*)&RXBuffer[0], *(u16*)&RXBuffer[12], *(u16*)&RXBuffer[8]); + RXTime = 0; + SetStatus(1); + if (TXCurSlot == 0xFFFFFFFF) { - if (addr & 0x2) IncrementRXAddr(addr); - - // copy the RX header - u16 headeraddr = IOPORT(W_RXBufWriteCursor) << 1; - *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[0]; IncrementRXAddr(headeraddr); - *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[2]; IncrementRXAddr(headeraddr, 4); - *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[6]; IncrementRXAddr(headeraddr); - *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[8]; IncrementRXAddr(headeraddr); - *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[10]; - - IOPORT(W_RXBufWriteCursor) = (addr & ~0x3) >> 1; - - SetIRQ(0); - SetStatus(1); - - WIFI_LOG("wifi: finished receiving packet %04X\n", *(u16*)&RXBuffer[12]); - ComStatus &= ~0x1; RXCounter = 0; - - if ((RXBuffer[0] & 0x0F) == 0x0C) - { - u16 clientmask = *(u16*)&RXBuffer[0xC + 26]; - if (IOPORT(W_AIDLow) && (RXBuffer[0xC + 4] & 0x01) && (clientmask & (1 << IOPORT(W_AIDLow)))) - { - SendMPReply(*(u16*)&RXBuffer[0xC + 24], *(u16*)&RXBuffer[0xC + 26]); - } - } } - - if (addr == (IOPORT(W_RXBufReadCursor) << 1)) + // TODO: proper error management + if ((!ComStatus) && (IOPORT(W_PowerState) & 0x0300)) { - printf("wifi: RX buffer full\n"); - RXTime = 0; - SetStatus(1); - if (TXCurSlot == 0xFFFFFFFF) - { - ComStatus &= ~0x1; - RXCounter = 0; - } - // TODO: proper error management + SetStatus(9); } - - IOPORT(W_RXTXAddr) = addr >> 1; } } } - // TODO: make it more accurate, eventually - // in the DS, the wifi system has its own 22MHz clock and doesn't use the system clock - NDS::ScheduleEvent(NDS::Event_Wifi, true, 33, USTimer, 0); + ScheduleTimer(false); } @@ -1157,15 +1805,13 @@ void RFTransfer_Type3() } -// TODO: wifi waitstates - u16 Read(u32 addr) -{//printf("WIFI READ %08X\n", addr); +{ if (addr >= 0x04810000) return 0; addr &= 0x7FFE; - //printf("WIFI: read %08X\n", addr); + if (addr >= 0x4000 && addr < 0x6000) { return *(u16*)&RAM[addr & 0x1FFE]; @@ -1213,7 +1859,6 @@ u16 Read(u32 addr) if (activeread) { u32 rdaddr = IOPORT(W_RXBufReadAddr); - u16 ret = *(u16*)&RAM[rdaddr]; rdaddr += 2; @@ -1243,6 +1888,20 @@ u16 Read(u32 addr) case W_TXBusy: return IOPORT(W_TXBusy) & 0x001F; // no bit for MP replies. odd + + case W_CMDStat0: + case W_CMDStat1: + case W_CMDStat2: + case W_CMDStat3: + case W_CMDStat4: + case W_CMDStat5: + case W_CMDStat6: + case W_CMDStat7: + { + u16 ret = IOPORT(addr&0xFFF); + IOPORT(addr&0xFFF) = 0; + return ret; + } } //printf("WIFI: read %08X\n", addr); @@ -1250,12 +1909,12 @@ u16 Read(u32 addr) } void Write(u32 addr, u16 val) -{//printf("WIFI WRITE %08X %04X\n", addr, val); +{ if (addr >= 0x04810000) return; addr &= 0x7FFE; - //printf("WIFI: write %08X %04X\n", addr, val); + if (addr >= 0x4000 && addr < 0x6000) { *(u16*)&RAM[addr & 0x1FFE] = val; @@ -1290,9 +1949,7 @@ void Write(u32 addr, u16 val) { //printf("mode reset shutdown %08x\n", NDS::ARM7->R[15]); IOPORT(0x27C) = 0x000A; - IOPORT(W_RFPins) = 0x0004; - IOPORT(W_RFStatus) = 9; - IOPORT(W_PowerState) |= 0x200; + PowerDown(); } if (val & 0x2000) @@ -1353,6 +2010,13 @@ void Write(u32 addr, u16 val) printf("wifi: force-setting IF %04X\n", val); return; + case W_AIDLow: + IOPORT(W_AIDLow) = val & 0x000F; + return; + case W_AIDFull: + IOPORT(W_AIDFull) = val & 0x07FF; + return; + case W_PowerState: //printf("writing power state %x %08x\n", val, NDS::ARM7->R[15]); IOPORT(W_PowerState) |= val & 0x0002; @@ -1376,6 +2040,7 @@ void Write(u32 addr, u16 val) return; case W_PowerForce: //if ((val&0x8001)==0x8000) printf("WIFI: forcing power %04X\n", val); + val &= 0x8001; //printf("writing power force %x %08x\n", val, NDS::ARM7->R[15]); if (val == 0x8001) @@ -1384,8 +2049,7 @@ void Write(u32 addr, u16 val) IOPORT(0x034) = 0x0002; IOPORT(W_PowerState) = 0x0200; IOPORT(W_TXReqRead) = 0; - IOPORT(W_RFPins) = 0x0046; - IOPORT(W_RFStatus) = 9; + PowerDown(); } if (val == 1 && IOPORT(W_PowerState) & 0x0002) { @@ -1403,29 +2067,9 @@ void Write(u32 addr, u16 val) } break; case W_PowerUS: - // schedule timer event when the clock is enabled - // TODO: check whether this resets USCOUNT (and also which other events can reset it) - if ((IOPORT(W_PowerUS) & 0x0001) && !(val & 0x0001)) - { - printf("WIFI ON\n"); - NDS::ScheduleEvent(NDS::Event_Wifi, false, 33, USTimer, 0); - if (!MPInited) - { - Platform::MP_Init(); - MPInited = true; - } - if (!LANInited) - { - Platform::LAN_Init(); - LANInited = true; - } - } - else if (!(IOPORT(W_PowerUS) & 0x0001) && (val & 0x0001)) - { - printf("WIFI OFF\n"); - NDS::CancelEvent(NDS::Event_Wifi); - } - break; + IOPORT(W_PowerUS) = val & 0x0003; + UpdatePowerOn(); + return; case W_PowerUnk: val &= 0x0003; //printf("writing power unk %x\n", val); @@ -1486,6 +2130,10 @@ void Write(u32 addr, u16 val) IOPORT(W_TXSlotReply2) = IOPORT(W_TXSlotReply1); IOPORT(W_TXSlotReply1) = 0; } + if (val & 0x8000) + { + FireTX(); + } val &= 0xFF0E; if (val & 0x7FFF) printf("wifi: unknown RXCNT bits set %04X\n", val); break; @@ -1570,6 +2218,7 @@ void Write(u32 addr, u16 val) case W_TXSlotCmd: // checkme: is it possible to cancel a queued transfer that hasn't started yet // by clearing bit15 here? + // TODO: "W_TXBUF_CMD.Bit15 can be set ONLY while W_CMD_COUNT is non-zero." IOPORT(addr&0xFFF) = val; FireTX(); return; @@ -1597,13 +2246,12 @@ void Write(u32 addr, u16 val) case 0x214: case 0x268: return; - + default: //printf("WIFI unk: write %08X %04X\n", addr, val); break; } - //printf("WIFI: write %08X %04X\n", addr, val); IOPORT(addr&0xFFF) = val; } diff --git a/src/Wifi.h b/src/Wifi.h index 2e15673884..b9594f45aa 100644 --- a/src/Wifi.h +++ b/src/Wifi.h @@ -93,6 +93,7 @@ enum W_CmdTotalTime = 0x0C0, W_CmdReplyTime = 0x0C4, W_RXFilter = 0x0D0, + W_RXLenCrop = 0x0DA, W_RXFilter2 = 0x0E0, W_USCountCnt = 0x0E8, @@ -136,12 +137,43 @@ enum W_TXErrorCount = 0x1C0, W_RXCount = 0x1C4, + W_CMDStat0 = 0x1D0, + W_CMDStat1 = 0x1D2, + W_CMDStat2 = 0x1D4, + W_CMDStat3 = 0x1D6, + W_CMDStat4 = 0x1D8, + W_CMDStat5 = 0x1DA, + W_CMDStat6 = 0x1DC, + W_CMDStat7 = 0x1DE, + W_TXSeqNo = 0x210, W_RFStatus = 0x214, W_IFSet = 0x21C, W_RXTXAddr = 0x268, }; +enum +{ + Event_RXCheck = 0, + Event_IRQ15, + Event_MSTimer, + Event_RFWakeup, + Event_RX, + Event_TX, + Event_MPClientSync, + Event_RF, + Event_BB, + + Event_MAX +}; + +struct SchedEvent +{ + void (*Func)(u32 param); + u64 Timestamp; + u32 Param; +}; + extern bool MPInited; @@ -151,7 +183,7 @@ void DeInit(); void Reset(); void DoSavestate(Savestate* file); -void StartTX_Beacon(); +void SetPowerCnt(u32 val); void USTimer(u32 param); diff --git a/src/WifiAP.cpp b/src/WifiAP.cpp index e083c74dd3..5351639608 100644 --- a/src/WifiAP.cpp +++ b/src/WifiAP.cpp @@ -91,7 +91,7 @@ void DeInit() void Reset() { // random starting point for the counter - USCounter = 0x428888017ULL; + USCounter = 0x428888000ULL; SeqNo = 0x0120; BeaconDue = false; @@ -115,18 +115,6 @@ bool MACIsBroadcast(u8* a) } -void USTimer() -{ - USCounter++; - - u32 chk = (u32)USCounter; - if (!(chk & 0x1FFFF)) - { - // send beacon every 128ms - BeaconDue = true; - } -} - void MSTimer() { USCounter += 0x400; diff --git a/src/WifiAP.h b/src/WifiAP.h index e88132da8a..e5ca1ed7a8 100644 --- a/src/WifiAP.h +++ b/src/WifiAP.h @@ -33,7 +33,6 @@ bool Init(); void DeInit(); void Reset(); -void USTimer(); void MSTimer(); // packet format: 12-byte TX header + original 802.11 frame diff --git a/src/frontend/qt_sdl/AudioSettingsDialog.cpp b/src/frontend/qt_sdl/AudioSettingsDialog.cpp index a9b3ade7e7..4beefaf3bd 100644 --- a/src/frontend/qt_sdl/AudioSettingsDialog.cpp +++ b/src/frontend/qt_sdl/AudioSettingsDialog.cpp @@ -67,6 +67,20 @@ AudioSettingsDialog::AudioSettingsDialog(QWidget* parent) : QDialog(parent), ui( bool iswav = (Config::MicInputType == 3); ui->txtMicWavPath->setEnabled(iswav); ui->btnMicWavBrowse->setEnabled(iswav); + + int inst = Platform::InstanceID(); + if (inst > 0) + { + ui->lblInstanceNum->setText(QString("Configuring settings for instance %1").arg(inst+1)); + ui->cbInterpolation->setEnabled(false); + ui->cbBitrate->setEnabled(false); + for (QAbstractButton* btn : grpMicMode->buttons()) + btn->setEnabled(false); + ui->txtMicWavPath->setEnabled(false); + ui->btnMicWavBrowse->setEnabled(false); + } + else + ui->lblInstanceNum->hide(); } AudioSettingsDialog::~AudioSettingsDialog() diff --git a/src/frontend/qt_sdl/AudioSettingsDialog.ui b/src/frontend/qt_sdl/AudioSettingsDialog.ui index d7cfadd68a..8fc38d926a 100644 --- a/src/frontend/qt_sdl/AudioSettingsDialog.ui +++ b/src/frontend/qt_sdl/AudioSettingsDialog.ui @@ -7,7 +7,7 @@ 0 0 482 - 256 + 301 @@ -23,6 +23,13 @@ QLayout::SetFixedSize + + + + Configuring settings for instance X + + + @@ -76,7 +83,7 @@ - <html><head/><body><p>The bitrate of audio playback. If set to "Automatic" this will be 10-bit for DS mode and 16-bit for DSi mode.</p></body></html> + <html><head/><body><p>The bitrate of audio playback. If set to "Automatic" this will be 10-bit for DS mode and 16-bit for DSi mode.</p></body></html> diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index 3089c32ff0..5f1c490f48 100644 --- a/src/frontend/qt_sdl/CMakeLists.txt +++ b/src/frontend/qt_sdl/CMakeLists.txt @@ -17,6 +17,7 @@ set(SOURCES_QT_SDL AudioSettingsDialog.cpp FirmwareSettingsDialog.cpp PathSettingsDialog.cpp + MPSettingsDialog.cpp WifiSettingsDialog.cpp InterfaceSettingsDialog.cpp ROMInfoDialog.cpp @@ -25,6 +26,7 @@ set(SOURCES_QT_SDL Input.cpp LAN_PCap.cpp LAN_Socket.cpp + LocalMP.cpp OSD.cpp OSD_shaders.h font.h @@ -112,6 +114,8 @@ if (PORTABLE) endif() if (APPLE) + target_sources(melonDS PRIVATE sem_timedwait.cpp) + # Copy icon into the bundle set(RESOURCE_FILES "${CMAKE_SOURCE_DIR}/res/melon.icns") target_sources(melonDS PUBLIC "${RESOURCE_FILES}") diff --git a/src/frontend/qt_sdl/Config.cpp b/src/frontend/qt_sdl/Config.cpp index 7a0ec698ba..a8df8ee57b 100644 --- a/src/frontend/qt_sdl/Config.cpp +++ b/src/frontend/qt_sdl/Config.cpp @@ -106,9 +106,10 @@ int FirmwareBirthdayDay; int FirmwareFavouriteColour; std::string FirmwareMessage; std::string FirmwareMAC; -bool RandomizeMAC; -bool SocketBindAnyAddr; +int MPAudioMode; +int MPRecvTimeout; + std::string LANDevice; bool DirectLAN; @@ -141,200 +142,196 @@ bool DSiBatteryCharging; const char* kConfigFile = "melonDS.ini"; +const char* kUniqueConfigFile = "melonDS.%d.ini"; ConfigEntry ConfigFile[] = { - {"Key_A", 0, &KeyMapping[0], -1}, - {"Key_B", 0, &KeyMapping[1], -1}, - {"Key_Select", 0, &KeyMapping[2], -1}, - {"Key_Start", 0, &KeyMapping[3], -1}, - {"Key_Right", 0, &KeyMapping[4], -1}, - {"Key_Left", 0, &KeyMapping[5], -1}, - {"Key_Up", 0, &KeyMapping[6], -1}, - {"Key_Down", 0, &KeyMapping[7], -1}, - {"Key_R", 0, &KeyMapping[8], -1}, - {"Key_L", 0, &KeyMapping[9], -1}, - {"Key_X", 0, &KeyMapping[10], -1}, - {"Key_Y", 0, &KeyMapping[11], -1}, - - {"Joy_A", 0, &JoyMapping[0], -1}, - {"Joy_B", 0, &JoyMapping[1], -1}, - {"Joy_Select", 0, &JoyMapping[2], -1}, - {"Joy_Start", 0, &JoyMapping[3], -1}, - {"Joy_Right", 0, &JoyMapping[4], -1}, - {"Joy_Left", 0, &JoyMapping[5], -1}, - {"Joy_Up", 0, &JoyMapping[6], -1}, - {"Joy_Down", 0, &JoyMapping[7], -1}, - {"Joy_R", 0, &JoyMapping[8], -1}, - {"Joy_L", 0, &JoyMapping[9], -1}, - {"Joy_X", 0, &JoyMapping[10], -1}, - {"Joy_Y", 0, &JoyMapping[11], -1}, - - {"HKKey_Lid", 0, &HKKeyMapping[HK_Lid], -1}, - {"HKKey_Mic", 0, &HKKeyMapping[HK_Mic], -1}, - {"HKKey_Pause", 0, &HKKeyMapping[HK_Pause], -1}, - {"HKKey_Reset", 0, &HKKeyMapping[HK_Reset], -1}, - {"HKKey_FastForward", 0, &HKKeyMapping[HK_FastForward], -1}, - {"HKKey_FastForwardToggle", 0, &HKKeyMapping[HK_FastForwardToggle], -1}, - {"HKKey_FullscreenToggle", 0, &HKKeyMapping[HK_FullscreenToggle], -1}, - {"HKKey_SwapScreens", 0, &HKKeyMapping[HK_SwapScreens], -1}, - {"HKKey_SolarSensorDecrease", 0, &HKKeyMapping[HK_SolarSensorDecrease], -1}, - {"HKKey_SolarSensorIncrease", 0, &HKKeyMapping[HK_SolarSensorIncrease], -1}, - {"HKKey_FrameStep", 0, &HKKeyMapping[HK_FrameStep], -1}, - - {"HKJoy_Lid", 0, &HKJoyMapping[HK_Lid], -1}, - {"HKJoy_Mic", 0, &HKJoyMapping[HK_Mic], -1}, - {"HKJoy_Pause", 0, &HKJoyMapping[HK_Pause], -1}, - {"HKJoy_Reset", 0, &HKJoyMapping[HK_Reset], -1}, - {"HKJoy_FastForward", 0, &HKJoyMapping[HK_FastForward], -1}, - {"HKJoy_FastForwardToggle", 0, &HKJoyMapping[HK_FastForwardToggle], -1}, - {"HKJoy_FullscreenToggle", 0, &HKJoyMapping[HK_FullscreenToggle], -1}, - {"HKJoy_SwapScreens", 0, &HKJoyMapping[HK_SwapScreens], -1}, - {"HKJoy_SolarSensorDecrease", 0, &HKJoyMapping[HK_SolarSensorDecrease], -1}, - {"HKJoy_SolarSensorIncrease", 0, &HKJoyMapping[HK_SolarSensorIncrease], -1}, - {"HKJoy_FrameStep", 0, &HKJoyMapping[HK_FrameStep], -1}, - - {"JoystickID", 0, &JoystickID, 0}, - - {"WindowWidth", 0, &WindowWidth, 256}, - {"WindowHeight", 0, &WindowHeight, 384}, - {"WindowMax", 1, &WindowMaximized, false}, - - {"ScreenRotation", 0, &ScreenRotation, 0}, - {"ScreenGap", 0, &ScreenGap, 0}, - {"ScreenLayout", 0, &ScreenLayout, 0}, - {"ScreenSwap", 1, &ScreenSwap, false}, - {"ScreenSizing", 0, &ScreenSizing, 0}, - {"IntegerScaling", 1, &IntegerScaling, false}, - {"ScreenAspectTop",0, &ScreenAspectTop,0}, - {"ScreenAspectBot",0, &ScreenAspectBot,0}, - {"ScreenFilter", 1, &ScreenFilter, true}, - - {"ScreenUseGL", 1, &ScreenUseGL, false}, - {"ScreenVSync", 1, &ScreenVSync, false}, - {"ScreenVSyncInterval", 0, &ScreenVSyncInterval, 1}, - - {"3DRenderer", 0, &_3DRenderer, 0}, - {"Threaded3D", 1, &Threaded3D, true}, - - {"GL_ScaleFactor", 0, &GL_ScaleFactor, 1}, - {"GL_BetterPolygons", 1, &GL_BetterPolygons, false}, - - {"LimitFPS", 1, &LimitFPS, true}, + {"Key_A", 0, &KeyMapping[0], -1, true}, + {"Key_B", 0, &KeyMapping[1], -1, true}, + {"Key_Select", 0, &KeyMapping[2], -1, true}, + {"Key_Start", 0, &KeyMapping[3], -1, true}, + {"Key_Right", 0, &KeyMapping[4], -1, true}, + {"Key_Left", 0, &KeyMapping[5], -1, true}, + {"Key_Up", 0, &KeyMapping[6], -1, true}, + {"Key_Down", 0, &KeyMapping[7], -1, true}, + {"Key_R", 0, &KeyMapping[8], -1, true}, + {"Key_L", 0, &KeyMapping[9], -1, true}, + {"Key_X", 0, &KeyMapping[10], -1, true}, + {"Key_Y", 0, &KeyMapping[11], -1, true}, + + {"Joy_A", 0, &JoyMapping[0], -1, true}, + {"Joy_B", 0, &JoyMapping[1], -1, true}, + {"Joy_Select", 0, &JoyMapping[2], -1, true}, + {"Joy_Start", 0, &JoyMapping[3], -1, true}, + {"Joy_Right", 0, &JoyMapping[4], -1, true}, + {"Joy_Left", 0, &JoyMapping[5], -1, true}, + {"Joy_Up", 0, &JoyMapping[6], -1, true}, + {"Joy_Down", 0, &JoyMapping[7], -1, true}, + {"Joy_R", 0, &JoyMapping[8], -1, true}, + {"Joy_L", 0, &JoyMapping[9], -1, true}, + {"Joy_X", 0, &JoyMapping[10], -1, true}, + {"Joy_Y", 0, &JoyMapping[11], -1, true}, + + {"HKKey_Lid", 0, &HKKeyMapping[HK_Lid], -1, true}, + {"HKKey_Mic", 0, &HKKeyMapping[HK_Mic], -1, true}, + {"HKKey_Pause", 0, &HKKeyMapping[HK_Pause], -1, true}, + {"HKKey_Reset", 0, &HKKeyMapping[HK_Reset], -1, true}, + {"HKKey_FastForward", 0, &HKKeyMapping[HK_FastForward], -1, true}, + {"HKKey_FastForwardToggle", 0, &HKKeyMapping[HK_FastForwardToggle], -1, true}, + {"HKKey_FullscreenToggle", 0, &HKKeyMapping[HK_FullscreenToggle], -1, true}, + {"HKKey_SwapScreens", 0, &HKKeyMapping[HK_SwapScreens], -1, true}, + {"HKKey_SolarSensorDecrease", 0, &HKKeyMapping[HK_SolarSensorDecrease], -1, true}, + {"HKKey_SolarSensorIncrease", 0, &HKKeyMapping[HK_SolarSensorIncrease], -1, true}, + {"HKKey_FrameStep", 0, &HKKeyMapping[HK_FrameStep], -1, true}, + + {"HKJoy_Lid", 0, &HKJoyMapping[HK_Lid], -1, true}, + {"HKJoy_Mic", 0, &HKJoyMapping[HK_Mic], -1, true}, + {"HKJoy_Pause", 0, &HKJoyMapping[HK_Pause], -1, true}, + {"HKJoy_Reset", 0, &HKJoyMapping[HK_Reset], -1, true}, + {"HKJoy_FastForward", 0, &HKJoyMapping[HK_FastForward], -1, true}, + {"HKJoy_FastForwardToggle", 0, &HKJoyMapping[HK_FastForwardToggle], -1, true}, + {"HKJoy_FullscreenToggle", 0, &HKJoyMapping[HK_FullscreenToggle], -1, true}, + {"HKJoy_SwapScreens", 0, &HKJoyMapping[HK_SwapScreens], -1, true}, + {"HKJoy_SolarSensorDecrease", 0, &HKJoyMapping[HK_SolarSensorDecrease], -1, true}, + {"HKJoy_SolarSensorIncrease", 0, &HKJoyMapping[HK_SolarSensorIncrease], -1, true}, + {"HKJoy_FrameStep", 0, &HKJoyMapping[HK_FrameStep], -1, true}, + + {"JoystickID", 0, &JoystickID, 0, true}, + + {"WindowWidth", 0, &WindowWidth, 256, true}, + {"WindowHeight", 0, &WindowHeight, 384, true}, + {"WindowMax", 1, &WindowMaximized, false, true}, + + {"ScreenRotation", 0, &ScreenRotation, 0, true}, + {"ScreenGap", 0, &ScreenGap, 0, true}, + {"ScreenLayout", 0, &ScreenLayout, 0, true}, + {"ScreenSwap", 1, &ScreenSwap, false, true}, + {"ScreenSizing", 0, &ScreenSizing, 0, true}, + {"IntegerScaling", 1, &IntegerScaling, false, true}, + {"ScreenAspectTop",0, &ScreenAspectTop,0, true}, + {"ScreenAspectBot",0, &ScreenAspectBot,0, true}, + {"ScreenFilter", 1, &ScreenFilter, true, true}, + + {"ScreenUseGL", 1, &ScreenUseGL, false, false}, + {"ScreenVSync", 1, &ScreenVSync, false, false}, + {"ScreenVSyncInterval", 0, &ScreenVSyncInterval, 1, false}, + + {"3DRenderer", 0, &_3DRenderer, 0, false}, + {"Threaded3D", 1, &Threaded3D, true, false}, + + {"GL_ScaleFactor", 0, &GL_ScaleFactor, 1, false}, + {"GL_BetterPolygons", 1, &GL_BetterPolygons, false, false}, + + {"LimitFPS", 1, &LimitFPS, true, false}, {"AudioSync", 1, &AudioSync, false}, - {"ShowOSD", 1, &ShowOSD, true}, + {"ShowOSD", 1, &ShowOSD, true, false}, - {"ConsoleType", 0, &ConsoleType, 0}, - {"DirectBoot", 1, &DirectBoot, true}, + {"ConsoleType", 0, &ConsoleType, 0, false}, + {"DirectBoot", 1, &DirectBoot, true, false}, #ifdef JIT_ENABLED - {"JIT_Enable", 1, &JIT_Enable, false}, - {"JIT_MaxBlockSize", 0, &JIT_MaxBlockSize, 32}, - {"JIT_BranchOptimisations", 1, &JIT_BranchOptimisations, true}, - {"JIT_LiteralOptimisations", 1, &JIT_LiteralOptimisations, true}, + {"JIT_Enable", 1, &JIT_Enable, false, false}, + {"JIT_MaxBlockSize", 0, &JIT_MaxBlockSize, 32, false}, + {"JIT_BranchOptimisations", 1, &JIT_BranchOptimisations, true, false}, + {"JIT_LiteralOptimisations", 1, &JIT_LiteralOptimisations, true, false}, #ifdef __APPLE__ - {"JIT_FastMemory", 1, &JIT_FastMemory, false}, + {"JIT_FastMemory", 1, &JIT_FastMemory, false, false}, #else - {"JIT_FastMemory", 1, &JIT_FastMemory, true}, + {"JIT_FastMemory", 1, &JIT_FastMemory, true, false}, #endif #endif - {"ExternalBIOSEnable", 1, &ExternalBIOSEnable, false}, - - {"BIOS9Path", 2, &BIOS9Path, (std::string)""}, - {"BIOS7Path", 2, &BIOS7Path, (std::string)""}, - {"FirmwarePath", 2, &FirmwarePath, (std::string)""}, - - {"DSiBIOS9Path", 2, &DSiBIOS9Path, (std::string)""}, - {"DSiBIOS7Path", 2, &DSiBIOS7Path, (std::string)""}, - {"DSiFirmwarePath", 2, &DSiFirmwarePath, (std::string)""}, - {"DSiNANDPath", 2, &DSiNANDPath, (std::string)""}, - - {"DLDIEnable", 1, &DLDIEnable, false}, - {"DLDISDPath", 2, &DLDISDPath, (std::string)"dldi.bin"}, - {"DLDISize", 0, &DLDISize, 0}, - {"DLDIReadOnly", 1, &DLDIReadOnly, false}, - {"DLDIFolderSync", 1, &DLDIFolderSync, false}, - {"DLDIFolderPath", 2, &DLDIFolderPath, (std::string)""}, - - {"DSiSDEnable", 1, &DSiSDEnable, false}, - {"DSiSDPath", 2, &DSiSDPath, (std::string)"dsisd.bin"}, - {"DSiSDSize", 0, &DSiSDSize, 0}, - {"DSiSDReadOnly", 1, &DSiSDReadOnly, false}, - {"DSiSDFolderSync", 1, &DSiSDFolderSync, false}, - {"DSiSDFolderPath", 2, &DSiSDFolderPath, (std::string)""}, - - {"FirmwareOverrideSettings", 1, &FirmwareOverrideSettings, false}, - {"FirmwareUsername", 2, &FirmwareUsername, (std::string)"melonDS"}, - {"FirmwareLanguage", 0, &FirmwareLanguage, 1}, - {"FirmwareBirthdayMonth", 0, &FirmwareBirthdayMonth, 1}, - {"FirmwareBirthdayDay", 0, &FirmwareBirthdayDay, 1}, - {"FirmwareFavouriteColour", 0, &FirmwareFavouriteColour, 0}, - {"FirmwareMessage", 2, &FirmwareMessage, (std::string)""}, - {"FirmwareMAC", 2, &FirmwareMAC, (std::string)""}, - {"RandomizeMAC", 1, &RandomizeMAC, false}, - - {"SockBindAnyAddr", 1, &SocketBindAnyAddr, false}, - {"LANDevice", 2, &LANDevice, (std::string)""}, - {"DirectLAN", 1, &DirectLAN, false}, - - {"SavStaRelocSRAM", 1, &SavestateRelocSRAM, false}, - - {"AudioInterp", 0, &AudioInterp, 0}, - {"AudioBitrate", 0, &AudioBitrate, 0}, - {"AudioVolume", 0, &AudioVolume, 256}, - {"MicInputType", 0, &MicInputType, 1}, - {"MicWavPath", 2, &MicWavPath, (std::string)""}, - - {"LastROMFolder", 2, &LastROMFolder, (std::string)""}, - - {"RecentROM_0", 2, &RecentROMList[0], (std::string)""}, - {"RecentROM_1", 2, &RecentROMList[1], (std::string)""}, - {"RecentROM_2", 2, &RecentROMList[2], (std::string)""}, - {"RecentROM_3", 2, &RecentROMList[3], (std::string)""}, - {"RecentROM_4", 2, &RecentROMList[4], (std::string)""}, - {"RecentROM_5", 2, &RecentROMList[5], (std::string)""}, - {"RecentROM_6", 2, &RecentROMList[6], (std::string)""}, - {"RecentROM_7", 2, &RecentROMList[7], (std::string)""}, - {"RecentROM_8", 2, &RecentROMList[8], (std::string)""}, - {"RecentROM_9", 2, &RecentROMList[9], (std::string)""}, - - {"SaveFilePath", 2, &SaveFilePath, (std::string)""}, - {"SavestatePath", 2, &SavestatePath, (std::string)""}, - {"CheatFilePath", 2, &CheatFilePath, (std::string)""}, - - {"EnableCheats", 1, &EnableCheats, false}, - - {"MouseHide", 1, &MouseHide, false}, - {"MouseHideSeconds", 0, &MouseHideSeconds, 5}, - {"PauseLostFocus", 1, &PauseLostFocus, false}, - - {"DSBatteryLevelOkay", 1, &DSBatteryLevelOkay, true}, - {"DSiBatteryLevel", 0, &DSiBatteryLevel, 0xF}, - {"DSiBatteryCharging", 1, &DSiBatteryCharging, true}, - - {"", -1, nullptr, 0} + {"ExternalBIOSEnable", 1, &ExternalBIOSEnable, false, false}, + + {"BIOS9Path", 2, &BIOS9Path, (std::string)"", false}, + {"BIOS7Path", 2, &BIOS7Path, (std::string)"", false}, + {"FirmwarePath", 2, &FirmwarePath, (std::string)"", false}, + + {"DSiBIOS9Path", 2, &DSiBIOS9Path, (std::string)"", false}, + {"DSiBIOS7Path", 2, &DSiBIOS7Path, (std::string)"", false}, + {"DSiFirmwarePath", 2, &DSiFirmwarePath, (std::string)"", false}, + {"DSiNANDPath", 2, &DSiNANDPath, (std::string)"", false}, + + {"DLDIEnable", 1, &DLDIEnable, false, false}, + {"DLDISDPath", 2, &DLDISDPath, (std::string)"dldi.bin", false}, + {"DLDISize", 0, &DLDISize, 0, false}, + {"DLDIReadOnly", 1, &DLDIReadOnly, false, false}, + {"DLDIFolderSync", 1, &DLDIFolderSync, false, false}, + {"DLDIFolderPath", 2, &DLDIFolderPath, (std::string)"", false}, + + {"DSiSDEnable", 1, &DSiSDEnable, false, false}, + {"DSiSDPath", 2, &DSiSDPath, (std::string)"dsisd.bin", false}, + {"DSiSDSize", 0, &DSiSDSize, 0, false}, + {"DSiSDReadOnly", 1, &DSiSDReadOnly, false, false}, + {"DSiSDFolderSync", 1, &DSiSDFolderSync, false, false}, + {"DSiSDFolderPath", 2, &DSiSDFolderPath, (std::string)"", false}, + + {"FirmwareOverrideSettings", 1, &FirmwareOverrideSettings, false, true}, + {"FirmwareUsername", 2, &FirmwareUsername, (std::string)"melonDS", true}, + {"FirmwareLanguage", 0, &FirmwareLanguage, 1, true}, + {"FirmwareBirthdayMonth", 0, &FirmwareBirthdayMonth, 1, true}, + {"FirmwareBirthdayDay", 0, &FirmwareBirthdayDay, 1, true}, + {"FirmwareFavouriteColour", 0, &FirmwareFavouriteColour, 0, true}, + {"FirmwareMessage", 2, &FirmwareMessage, (std::string)"", true}, + {"FirmwareMAC", 2, &FirmwareMAC, (std::string)"", true}, + + {"MPAudioMode", 0, &MPAudioMode, 1, false}, + {"MPRecvTimeout", 0, &MPRecvTimeout, 25, false}, + + {"LANDevice", 2, &LANDevice, (std::string)"", false}, + {"DirectLAN", 1, &DirectLAN, false, false}, + + {"SavStaRelocSRAM", 1, &SavestateRelocSRAM, false, false}, + + {"AudioInterp", 0, &AudioInterp, 0, false}, + {"AudioBitrate", 0, &AudioBitrate, 0, false}, + {"AudioVolume", 0, &AudioVolume, 256, true}, + {"MicInputType", 0, &MicInputType, 1, false}, + {"MicWavPath", 2, &MicWavPath, (std::string)"", false}, + + {"LastROMFolder", 2, &LastROMFolder, (std::string)"", true}, + + {"RecentROM_0", 2, &RecentROMList[0], (std::string)"", true}, + {"RecentROM_1", 2, &RecentROMList[1], (std::string)"", true}, + {"RecentROM_2", 2, &RecentROMList[2], (std::string)"", true}, + {"RecentROM_3", 2, &RecentROMList[3], (std::string)"", true}, + {"RecentROM_4", 2, &RecentROMList[4], (std::string)"", true}, + {"RecentROM_5", 2, &RecentROMList[5], (std::string)"", true}, + {"RecentROM_6", 2, &RecentROMList[6], (std::string)"", true}, + {"RecentROM_7", 2, &RecentROMList[7], (std::string)"", true}, + {"RecentROM_8", 2, &RecentROMList[8], (std::string)"", true}, + {"RecentROM_9", 2, &RecentROMList[9], (std::string)"", true}, + + {"SaveFilePath", 2, &SaveFilePath, (std::string)"", true}, + {"SavestatePath", 2, &SavestatePath, (std::string)"", true}, + {"CheatFilePath", 2, &CheatFilePath, (std::string)"", true}, + + {"EnableCheats", 1, &EnableCheats, false, true}, + + {"MouseHide", 1, &MouseHide, false, false}, + {"MouseHideSeconds", 0, &MouseHideSeconds, 5, false}, + {"PauseLostFocus", 1, &PauseLostFocus, false, false}, + + {"DSBatteryLevelOkay", 1, &DSBatteryLevelOkay, true, true}, + {"DSiBatteryLevel", 0, &DSiBatteryLevel, 0xF, true}, + {"DSiBatteryCharging", 1, &DSiBatteryCharging, true, true}, + + {"", -1, nullptr, 0, false} }; -void Load() +void LoadFile(int inst) { - ConfigEntry* entry = &ConfigFile[0]; - for (;;) + FILE* f; + if (inst > 0) { - if (!entry->Value) break; - - switch (entry->Type) - { - case 0: *(int*)entry->Value = std::get(entry->Default); break; - case 1: *(bool*)entry->Value = std::get(entry->Default); break; - case 2: *(std::string*)entry->Value = std::get(entry->Default); break; - } - - entry++; + char name[100] = {0}; + snprintf(name, 99, kUniqueConfigFile, inst+1); + f = Platform::OpenLocalFile(name, "r"); } + else + f = Platform::OpenLocalFile(kConfigFile, "r"); - FILE* f = Platform::OpenLocalFile(kConfigFile, "r"); if (!f) return; char linebuf[1024]; @@ -349,13 +346,13 @@ void Load() entryname[31] = '\0'; if (ret < 2) continue; - ConfigEntry* entry = &ConfigFile[0]; - for (;;) + for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++) { - if (!entry->Value) break; - if (!strncmp(entry->Name, entryname, 32)) { + if ((inst > 0) && (!entry->InstanceUnique)) + break; + switch (entry->Type) { case 0: *(int*)entry->Value = strtol(entryval, NULL, 10); break; @@ -365,23 +362,52 @@ void Load() break; } - - entry++; } } fclose(f); } +void Load() +{ + + for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++) + { + switch (entry->Type) + { + case 0: *(int*)entry->Value = std::get(entry->Default); break; + case 1: *(bool*)entry->Value = std::get(entry->Default); break; + case 2: *(std::string*)entry->Value = std::get(entry->Default); break; + } + } + + LoadFile(0); + + int inst = Platform::InstanceID(); + if (inst > 0) + LoadFile(inst); +} + void Save() { - FILE* f = Platform::OpenLocalFile(kConfigFile, "w"); + int inst = Platform::InstanceID(); + + FILE* f; + if (inst > 0) + { + char name[100] = {0}; + snprintf(name, 99, kUniqueConfigFile, inst+1); + f = Platform::OpenLocalFile(name, "w"); + } + else + f = Platform::OpenLocalFile(kConfigFile, "w"); + if (!f) return; - ConfigEntry* entry = &ConfigFile[0]; - for (;;) + for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++) { - if (!entry->Value) break; + if ((inst > 0) && (!entry->InstanceUnique)) + continue; switch (entry->Type) { @@ -389,8 +415,6 @@ void Save() case 1: fprintf(f, "%s=%d\r\n", entry->Name, *(bool*)entry->Value ? 1:0); break; case 2: fprintf(f, "%s=%s\r\n", entry->Name, (*(std::string*)entry->Value).c_str()); break; } - - entry++; } fclose(f); diff --git a/src/frontend/qt_sdl/Config.h b/src/frontend/qt_sdl/Config.h index f2f8ddcd04..cc6792c0c0 100644 --- a/src/frontend/qt_sdl/Config.h +++ b/src/frontend/qt_sdl/Config.h @@ -58,6 +58,7 @@ struct ConfigEntry int Type; // 0=int 1=bool 2=string void* Value; // pointer to the value variable std::variant Default; + bool InstanceUnique; // whether the setting can exist individually for each instance in multiplayer }; @@ -141,9 +142,10 @@ extern int FirmwareBirthdayDay; extern int FirmwareFavouriteColour; extern std::string FirmwareMessage; extern std::string FirmwareMAC; -extern bool RandomizeMAC; -extern bool SocketBindAnyAddr; +extern int MPAudioMode; +extern int MPRecvTimeout; + extern std::string LANDevice; extern bool DirectLAN; diff --git a/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp b/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp index 754cc8a523..ffca5676d3 100644 --- a/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp +++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp @@ -18,6 +18,7 @@ #include +#include "Platform.h" #include "Config.h" #include "FirmwareSettingsDialog.h" @@ -64,10 +65,14 @@ FirmwareSettingsDialog::FirmwareSettingsDialog(QWidget* parent) : QDialog(parent ui->overrideFirmwareBox->setChecked(Config::FirmwareOverrideSettings); ui->txtMAC->setText(QString::fromStdString(Config::FirmwareMAC)); - ui->cbRandomizeMAC->setChecked(Config::RandomizeMAC); on_overrideFirmwareBox_toggled(); - on_cbRandomizeMAC_toggled(); + + int inst = Platform::InstanceID(); + if (inst > 0) + ui->lblInstanceNum->setText(QString("Configuring settings for instance %1").arg(inst+1)); + else + ui->lblInstanceNum->hide(); } FirmwareSettingsDialog::~FirmwareSettingsDialog() @@ -135,7 +140,6 @@ void FirmwareSettingsDialog::done(int r) std::string newMessage = ui->messageEdit->text().toStdString(); std::string newMAC = ui->txtMAC->text().toStdString(); - bool newRandomizeMAC = ui->cbRandomizeMAC->isChecked(); if ( newOverride != Config::FirmwareOverrideSettings || newName != Config::FirmwareUsername @@ -144,8 +148,7 @@ void FirmwareSettingsDialog::done(int r) || newBirthdayDay != Config::FirmwareBirthdayDay || newBirthdayMonth != Config::FirmwareBirthdayMonth || newMessage != Config::FirmwareMessage - || newMAC != Config::FirmwareMAC - || newRandomizeMAC != Config::RandomizeMAC) + || newMAC != Config::FirmwareMAC) { if (RunningSomething && QMessageBox::warning(this, "Reset necessary to apply changes", @@ -163,7 +166,6 @@ void FirmwareSettingsDialog::done(int r) Config::FirmwareMessage = newMessage; Config::FirmwareMAC = newMAC; - Config::RandomizeMAC = newRandomizeMAC; Config::Save(); @@ -210,9 +212,3 @@ void FirmwareSettingsDialog::on_overrideFirmwareBox_toggled() ui->grpUserSettings->setDisabled(disable); ui->grpWifiSettings->setDisabled(disable); } - -void FirmwareSettingsDialog::on_cbRandomizeMAC_toggled() -{ - bool disable = ui->cbRandomizeMAC->isChecked(); - ui->txtMAC->setDisabled(disable); -} diff --git a/src/frontend/qt_sdl/FirmwareSettingsDialog.h b/src/frontend/qt_sdl/FirmwareSettingsDialog.h index 97bf5c07fa..b3695e2fd5 100644 --- a/src/frontend/qt_sdl/FirmwareSettingsDialog.h +++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.h @@ -124,7 +124,6 @@ private slots: void on_cbxBirthdayMonth_currentIndexChanged(int idx); void on_overrideFirmwareBox_toggled(); - void on_cbRandomizeMAC_toggled(); private: bool verifyMAC(); diff --git a/src/frontend/qt_sdl/FirmwareSettingsDialog.ui b/src/frontend/qt_sdl/FirmwareSettingsDialog.ui index a97689cfb1..37146296ab 100644 --- a/src/frontend/qt_sdl/FirmwareSettingsDialog.ui +++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.ui @@ -7,7 +7,7 @@ 0 0 511 - 342 + 357 @@ -23,6 +23,13 @@ QLayout::SetFixedSize + + + + Configuring settings for instance X + + + @@ -144,9 +151,9 @@ - + - Randomize + (leave empty to use default MAC) diff --git a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp index 697e983a20..92a0186751 100644 --- a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp +++ b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp @@ -24,6 +24,7 @@ #include #include "types.h" +#include "Platform.h" #include "Config.h" #include "MapButton.h" @@ -123,6 +124,12 @@ InputConfigDialog::InputConfigDialog(QWidget* parent) : QDialog(parent), ui(new } setupKeypadPage(); + + int inst = Platform::InstanceID(); + if (inst > 0) + ui->lblInstanceNum->setText(QString("Configuring mappings for instance %1").arg(inst+1)); + else + ui->lblInstanceNum->hide(); } InputConfigDialog::~InputConfigDialog() diff --git a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui index 15cb683d44..0db61b15fe 100644 --- a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui +++ b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui @@ -7,7 +7,7 @@ 0 0 770 - 719 + 678 @@ -20,7 +20,7 @@ QLayout::SetFixedSize - + Qt::Horizontal @@ -30,49 +30,7 @@ - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Joystick: - - - - - - - - 0 - 0 - - - - <html><head/><body><p>Selects which joystick will be used for joystick input, if any is present.</p></body></html> - - - - - - + 0 @@ -167,7 +125,7 @@ - 76 + 70 0 @@ -258,7 +216,7 @@ - 76 + 70 0 @@ -384,7 +342,7 @@ - 76 + 70 0 @@ -464,7 +422,7 @@ - 76 + 70 0 @@ -523,7 +481,7 @@ - 76 + 70 0 @@ -615,7 +573,7 @@ - 76 + 70 0 @@ -698,7 +656,7 @@ - 76 + 70 0 @@ -757,7 +715,7 @@ - 76 + 70 0 @@ -882,7 +840,7 @@ - 76 + 70 0 @@ -962,7 +920,7 @@ - 76 + 70 0 @@ -1021,7 +979,7 @@ - 76 + 70 0 @@ -1113,7 +1071,7 @@ - 76 + 70 0 @@ -1289,7 +1247,7 @@ - 76 + 70 0 @@ -1441,7 +1399,7 @@ - 76 + 70 0 @@ -1521,7 +1479,7 @@ - 76 + 70 0 @@ -1580,7 +1538,7 @@ - 76 + 70 0 @@ -1672,7 +1630,7 @@ - 76 + 70 0 @@ -1814,7 +1772,7 @@ - 76 + 70 0 @@ -1894,7 +1852,7 @@ - 76 + 70 0 @@ -1953,7 +1911,7 @@ - 76 + 70 0 @@ -2045,7 +2003,7 @@ - 76 + 70 0 @@ -2128,7 +2086,7 @@ - 76 + 70 0 @@ -2187,7 +2145,7 @@ - 76 + 70 0 @@ -2251,7 +2209,7 @@ - 76 + 70 0 @@ -2321,6 +2279,55 @@ + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Joystick: + + + + + + + + 0 + 0 + + + + <html><head/><body><p>Selects which joystick will be used for joystick input, if any is present.</p></body></html> + + + + + + + + + Configuring mappings for instance X + + + diff --git a/src/frontend/qt_sdl/LAN_PCap.cpp b/src/frontend/qt_sdl/LAN_PCap.cpp index 57223eb865..86c218f1c9 100644 --- a/src/frontend/qt_sdl/LAN_PCap.cpp +++ b/src/frontend/qt_sdl/LAN_PCap.cpp @@ -114,6 +114,12 @@ bool TryLoadPCap(void* lib) bool Init(bool open_adapter) { + PCapAdapter = NULL; + PacketLen = 0; + RXNum = 0; + + NumAdapters = 0; + // TODO: how to deal with cases where an adapter is unplugged or changes config?? if (!PCapLib) { @@ -142,12 +148,6 @@ bool Init(bool open_adapter) } } - PCapAdapter = NULL; - PacketLen = 0; - RXNum = 0; - - NumAdapters = 0; - char errbuf[PCAP_ERRBUF_SIZE]; int ret; diff --git a/src/frontend/qt_sdl/LocalMP.cpp b/src/frontend/qt_sdl/LocalMP.cpp new file mode 100644 index 0000000000..fb7ef7ad87 --- /dev/null +++ b/src/frontend/qt_sdl/LocalMP.cpp @@ -0,0 +1,634 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS 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 3 of the License, or (at your option) + any later version. + + melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include +#include +#include + +#ifdef __WIN32__ + #include +#else + #include + #include + #include + #ifdef __APPLE__ + #include "sem_timedwait.h" + #endif +#endif + +#include +#include + +#include "Config.h" +#include "LocalMP.h" + + +namespace LocalMP +{ + +u32 MPUniqueID; +u8 PacketBuffer[2048]; + +struct MPQueueHeader +{ + u16 NumInstances; + u16 InstanceBitmask; // bitmask of all instances present + u16 ConnectedBitmask; // bitmask of which instances are ready to send/receive packets + u32 PacketWriteOffset; + u32 ReplyWriteOffset; + u16 MPHostInstanceID; // instance ID from which the last CMD frame was sent + u16 MPReplyBitmask; // bitmask of which clients replied in time +}; + +struct MPPacketHeader +{ + u32 Magic; + u32 SenderID; + u32 Type; // 0=regular 1=CMD 2=reply 3=ack + u32 Length; + u64 Timestamp; +}; + +struct MPSync +{ + u32 Magic; + u32 SenderID; + u16 ClientMask; + u16 Type; + u64 Timestamp; +}; + +QSharedMemory* MPQueue; +int InstanceID; +u32 PacketReadOffset; +u32 ReplyReadOffset; + +const u32 kQueueSize = 0x20000; +const u32 kMaxFrameSize = 0x800; +const u32 kPacketStart = sizeof(MPQueueHeader); +const u32 kReplyStart = kQueueSize / 2; +const u32 kPacketEnd = kReplyStart; +const u32 kReplyEnd = kQueueSize; + +int RecvTimeout; + +int LastHostID; + + +// we need to come up with our own abstraction layer for named semaphores +// because QSystemSemaphore doesn't support waiting with a timeout +// and, as such, is unsuitable to our needs + +#ifdef __WIN32__ + +bool SemInited[32]; +HANDLE SemPool[32]; + +void SemPoolInit() +{ + for (int i = 0; i < 32; i++) + { + SemPool[i] = INVALID_HANDLE_VALUE; + SemInited[i] = false; + } +} + +void SemDeinit(int num); + +void SemPoolDeinit() +{ + for (int i = 0; i < 32; i++) + SemDeinit(i); +} + +bool SemInit(int num) +{ + if (SemInited[num]) + return true; + + char semname[64]; + sprintf(semname, "Local\\melonNIFI_Sem%02d", num); + + HANDLE sem = CreateSemaphore(nullptr, 0, 64, semname); + SemPool[num] = sem; + SemInited[num] = true; + return sem != INVALID_HANDLE_VALUE; +} + +void SemDeinit(int num) +{ + if (SemPool[num] != INVALID_HANDLE_VALUE) + { + CloseHandle(SemPool[num]); + SemPool[num] = INVALID_HANDLE_VALUE; + } + + SemInited[num] = false; +} + +bool SemPost(int num) +{ + SemInit(num); + return ReleaseSemaphore(SemPool[num], 1, nullptr) != 0; +} + +bool SemWait(int num, int timeout) +{ + return WaitForSingleObject(SemPool[num], timeout) == WAIT_OBJECT_0; +} + +void SemReset(int num) +{ + while (WaitForSingleObject(SemPool[num], 0) == WAIT_OBJECT_0); +} + +#else + +bool SemInited[32]; +sem_t* SemPool[32]; + +void SemPoolInit() +{ + for (int i = 0; i < 32; i++) + { + SemPool[i] = SEM_FAILED; + SemInited[i] = false; + } +} + +void SemDeinit(int num); + +void SemPoolDeinit() +{ + for (int i = 0; i < 32; i++) + SemDeinit(i); +} + +bool SemInit(int num) +{ + if (SemInited[num]) + return true; + + char semname[64]; + sprintf(semname, "/melonNIFI_Sem%02d", num); + + sem_t* sem = sem_open(semname, O_CREAT, 0644, 0); + SemPool[num] = sem; + SemInited[num] = true; + return sem != SEM_FAILED; +} + +void SemDeinit(int num) +{ + if (SemPool[num] != SEM_FAILED) + { + sem_close(SemPool[num]); + SemPool[num] = SEM_FAILED; + } + + SemInited[num] = false; +} + +bool SemPost(int num) +{ + SemInit(num); + return sem_post(SemPool[num]) == 0; +} + +bool SemWait(int num, int timeout) +{ + if (!timeout) + return sem_trywait(SemPool[num]) == 0; + + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_nsec += timeout * 1000000; + long sec = ts.tv_nsec / 1000000000; + ts.tv_nsec -= sec * 1000000000; + ts.tv_sec += sec; + + return sem_timedwait(SemPool[num], &ts) == 0; +} + +void SemReset(int num) +{ + while (sem_trywait(SemPool[num]) == 0); +} + +#endif + + +bool Init() +{ + MPQueue = new QSharedMemory("melonNIFI"); + + if (!MPQueue->attach()) + { + printf("MP sharedmem doesn't exist. creating\n"); + if (!MPQueue->create(kQueueSize)) + { + printf("MP sharedmem create failed :(\n"); + return false; + } + + MPQueue->lock(); + memset(MPQueue->data(), 0, MPQueue->size()); + MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); + header->PacketWriteOffset = kPacketStart; + header->ReplyWriteOffset = kReplyStart; + MPQueue->unlock(); + } + + MPQueue->lock(); + MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); + + u16 mask = header->InstanceBitmask; + for (int i = 0; i < 16; i++) + { + if (!(mask & (1<InstanceBitmask |= (1<ConnectedBitmask |= (1 << i); + break; + } + } + header->NumInstances++; + + PacketReadOffset = header->PacketWriteOffset; + ReplyReadOffset = header->ReplyWriteOffset; + + MPQueue->unlock(); + + // prepare semaphores + // semaphores 0-15: regular frames; semaphore I is posted when instance I needs to process a new frame + // semaphores 16-31: MP replies; semaphore I is posted when instance I needs to process a new MP reply + + SemPoolInit(); + SemInit(InstanceID); + SemInit(16+InstanceID); + + LastHostID = -1; + + printf("MP comm init OK, instance ID %d\n", InstanceID); + + RecvTimeout = 25; + + return true; +} + +void DeInit() +{ + MPQueue->lock(); + MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); + header->ConnectedBitmask &= ~(1 << InstanceID); + header->InstanceBitmask &= ~(1 << InstanceID); + header->NumInstances--; + MPQueue->unlock(); + + SemPoolDeinit(); + + MPQueue->detach(); + delete MPQueue; +} + +void SetRecvTimeout(int timeout) +{ + RecvTimeout = timeout; +} + +void Begin() +{ + MPQueue->lock(); + MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); + PacketReadOffset = header->PacketWriteOffset; + ReplyReadOffset = header->ReplyWriteOffset; + SemReset(InstanceID); + SemReset(16+InstanceID); + header->ConnectedBitmask |= (1 << InstanceID); + MPQueue->unlock(); +} + +void End() +{ + MPQueue->lock(); + MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); + //SemReset(InstanceID); + //SemReset(16+InstanceID); + header->ConnectedBitmask &= ~(1 << InstanceID); + MPQueue->unlock(); +} + +void FIFORead(int fifo, void* buf, int len) +{ + u8* data = (u8*)MPQueue->data(); + + u32 offset, start, end; + if (fifo == 0) + { + offset = PacketReadOffset; + start = kPacketStart; + end = kPacketEnd; + } + else + { + offset = ReplyReadOffset; + start = kReplyStart; + end = kReplyEnd; + } + + if ((offset + len) >= end) + { + u32 part1 = end - offset; + memcpy(buf, &data[offset], part1); + memcpy(&((u8*)buf)[part1], &data[start], len - part1); + offset = start + len - part1; + } + else + { + memcpy(buf, &data[offset], len); + offset += len; + } + + if (fifo == 0) PacketReadOffset = offset; + else ReplyReadOffset = offset; +} + +void FIFOWrite(int fifo, void* buf, int len) +{ + u8* data = (u8*)MPQueue->data(); + MPQueueHeader* header = (MPQueueHeader*)&data[0]; + + u32 offset, start, end; + if (fifo == 0) + { + offset = header->PacketWriteOffset; + start = kPacketStart; + end = kPacketEnd; + } + else + { + offset = header->ReplyWriteOffset; + start = kReplyStart; + end = kReplyEnd; + } + + if ((offset + len) >= end) + { + u32 part1 = end - offset; + memcpy(&data[offset], buf, part1); + memcpy(&data[start], &((u8*)buf)[part1], len - part1); + offset = start + len - part1; + } + else + { + memcpy(&data[offset], buf, len); + offset += len; + } + + if (fifo == 0) header->PacketWriteOffset = offset; + else header->ReplyWriteOffset = offset; +} + +int SendPacketGeneric(u32 type, u8* packet, int len, u64 timestamp) +{ + MPQueue->lock(); + u8* data = (u8*)MPQueue->data(); + MPQueueHeader* header = (MPQueueHeader*)&data[0]; + + u16 mask = header->ConnectedBitmask; + + // TODO: check if the FIFO is full! + + MPPacketHeader pktheader; + pktheader.Magic = 0x4946494E; + pktheader.SenderID = InstanceID; + pktheader.Type = type; + pktheader.Length = len; + pktheader.Timestamp = timestamp; + + type &= 0xFFFF; + int nfifo = (type == 2) ? 1 : 0; + FIFOWrite(nfifo, &pktheader, sizeof(pktheader)); + if (len) + FIFOWrite(nfifo, packet, len); + + if (type == 1) + { + // NOTE: this is not guarded against, say, multiple multiplay games happening on the same machine + // we would need to pass the packet's SenderID through the wifi module for that + header->MPHostInstanceID = InstanceID; + header->MPReplyBitmask = 0; + ReplyReadOffset = header->ReplyWriteOffset; + SemReset(16 + InstanceID); + } + else if (type == 2) + { + header->MPReplyBitmask |= (1 << InstanceID); + } + + MPQueue->unlock(); + + if (type == 2) + { + SemPost(16 + header->MPHostInstanceID); + } + else + { + for (int i = 0; i < 16; i++) + { + if (mask & (1<lock(); + u8* data = (u8*)MPQueue->data(); + MPQueueHeader* header = (MPQueueHeader*)&data[0]; + + MPPacketHeader pktheader; + FIFORead(0, &pktheader, sizeof(pktheader)); + + if (pktheader.Magic != 0x4946494E) + { + printf("PACKET FIFO OVERFLOW\n"); + PacketReadOffset = header->PacketWriteOffset; + SemReset(InstanceID); + MPQueue->unlock(); + return 0; + } + + if (pktheader.SenderID == InstanceID) + { + // skip this packet + PacketReadOffset += pktheader.Length; + if (PacketReadOffset >= kPacketEnd) + PacketReadOffset += kPacketStart - kPacketEnd; + + MPQueue->unlock(); + continue; + } + + if (pktheader.Length) + { + FIFORead(0, packet, pktheader.Length); + + if (pktheader.Type == 1) + LastHostID = pktheader.SenderID; + } + + if (timestamp) *timestamp = pktheader.Timestamp; + MPQueue->unlock(); + return pktheader.Length; + } +} + +int SendPacket(u8* packet, int len, u64 timestamp) +{ + return SendPacketGeneric(0, packet, len, timestamp); +} + +int RecvPacket(u8* packet, u64* timestamp) +{ + return RecvPacketGeneric(packet, false, timestamp); +} + + +int SendCmd(u8* packet, int len, u64 timestamp) +{ + return SendPacketGeneric(1, packet, len, timestamp); +} + +int SendReply(u8* packet, int len, u64 timestamp, u16 aid) +{ + return SendPacketGeneric(2 | (aid<<16), packet, len, timestamp); +} + +int SendAck(u8* packet, int len, u64 timestamp) +{ + return SendPacketGeneric(3, packet, len, timestamp); +} + +int RecvHostPacket(u8* packet, u64* timestamp) +{ + if (LastHostID != -1) + { + // check if the host is still connected + + MPQueue->lock(); + u8* data = (u8*)MPQueue->data(); + MPQueueHeader* header = (MPQueueHeader*)&data[0]; + u16 curinstmask = header->ConnectedBitmask; + MPQueue->unlock(); + + if (!(curinstmask & (1 << LastHostID))) + return -1; + } + + return RecvPacketGeneric(packet, true, timestamp); +} + +u16 RecvReplies(u8* packets, u64 timestamp, u16 aidmask) +{ + u16 ret = 0; + u16 myinstmask = (1 << InstanceID); + u16 curinstmask; + + { + MPQueue->lock(); + u8* data = (u8*)MPQueue->data(); + MPQueueHeader* header = (MPQueueHeader*)&data[0]; + curinstmask = header->ConnectedBitmask; + MPQueue->unlock(); + } + + // if all clients have left: return early + if ((myinstmask & curinstmask) == curinstmask) + return 0; + + for (;;) + { + if (!SemWait(16+InstanceID, RecvTimeout)) + { + // no more replies available + return ret; + } + + MPQueue->lock(); + u8* data = (u8*)MPQueue->data(); + MPQueueHeader* header = (MPQueueHeader*)&data[0]; + + MPPacketHeader pktheader; + FIFORead(1, &pktheader, sizeof(pktheader)); + + if (pktheader.Magic != 0x4946494E) + { + printf("REPLY FIFO OVERFLOW\n"); + ReplyReadOffset = header->ReplyWriteOffset; + SemReset(16+InstanceID); + MPQueue->unlock(); + return 0; + } + + if ((pktheader.SenderID == InstanceID) || // packet we sent out (shouldn't happen, but hey) + (pktheader.Timestamp < (timestamp - 32))) // stale packet + { + // skip this packet + ReplyReadOffset += pktheader.Length; + if (ReplyReadOffset >= kReplyEnd) + ReplyReadOffset += kReplyStart - kReplyEnd; + + MPQueue->unlock(); + continue; + } + + if (pktheader.Length) + { + u32 aid = (pktheader.Type >> 16); + FIFORead(1, &packets[(aid-1)*1024], pktheader.Length); + ret |= (1 << aid); + } + + myinstmask |= (1 << pktheader.SenderID); + if (((myinstmask & curinstmask) == curinstmask) || + ((ret & aidmask) == aidmask)) + { + // all the clients have sent their reply + + MPQueue->unlock(); + return ret; + } + + MPQueue->unlock(); + } +} + +} + diff --git a/src/frontend/qt_sdl/LocalMP.h b/src/frontend/qt_sdl/LocalMP.h new file mode 100644 index 0000000000..51dfcb936e --- /dev/null +++ b/src/frontend/qt_sdl/LocalMP.h @@ -0,0 +1,45 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS 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 3 of the License, or (at your option) + any later version. + + melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef LOCALMP_H +#define LOCALMP_H + +#include "types.h" + +namespace LocalMP +{ + +bool Init(); +void DeInit(); + +void SetRecvTimeout(int timeout); + +void Begin(); +void End(); + +int SendPacket(u8* data, int len, u64 timestamp); +int RecvPacket(u8* data, u64* timestamp); +int SendCmd(u8* data, int len, u64 timestamp); +int SendReply(u8* data, int len, u64 timestamp, u16 aid); +int SendAck(u8* data, int len, u64 timestamp); +int RecvHostPacket(u8* data, u64* timestamp); +u16 RecvReplies(u8* data, u64 timestamp, u16 aidmask); + +} + +#endif // LOCALMP_H diff --git a/src/frontend/qt_sdl/MPSettingsDialog.cpp b/src/frontend/qt_sdl/MPSettingsDialog.cpp new file mode 100644 index 0000000000..e31142200e --- /dev/null +++ b/src/frontend/qt_sdl/MPSettingsDialog.cpp @@ -0,0 +1,73 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS 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 3 of the License, or (at your option) + any later version. + + melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include +#include + +#include "types.h" +#include "Platform.h" +#include "Config.h" + +#include "LAN_Socket.h" +#include "LAN_PCap.h" +#include "Wifi.h" + +#include "MPSettingsDialog.h" +#include "ui_MPSettingsDialog.h" + + +MPSettingsDialog* MPSettingsDialog::currentDlg = nullptr; + +extern bool RunningSomething; + + +MPSettingsDialog::MPSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MPSettingsDialog) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + + grpAudioMode = new QButtonGroup(this); + grpAudioMode->addButton(ui->rbAudioAll, 0); + grpAudioMode->addButton(ui->rbAudioOneOnly, 1); + grpAudioMode->addButton(ui->rbAudioActiveOnly, 2); + grpAudioMode->button(Config::MPAudioMode)->setChecked(true); + + ui->sbReceiveTimeout->setValue(Config::MPRecvTimeout); +} + +MPSettingsDialog::~MPSettingsDialog() +{ + delete ui; +} + +void MPSettingsDialog::done(int r) +{ + if (r == QDialog::Accepted) + { + Config::MPAudioMode = grpAudioMode->checkedId(); + Config::MPRecvTimeout = ui->sbReceiveTimeout->value(); + + Config::Save(); + } + + QDialog::done(r); + + closeDlg(); +} + +// diff --git a/src/frontend/qt_sdl/MPSettingsDialog.h b/src/frontend/qt_sdl/MPSettingsDialog.h new file mode 100644 index 0000000000..fe917e89b2 --- /dev/null +++ b/src/frontend/qt_sdl/MPSettingsDialog.h @@ -0,0 +1,65 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS 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 3 of the License, or (at your option) + any later version. + + melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef MPSETTINGSDIALOG_H +#define MPSETTINGSDIALOG_H + +#include +#include + +namespace Ui { class MPSettingsDialog; } +class MPSettingsDialog; + +class MPSettingsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit MPSettingsDialog(QWidget* parent); + ~MPSettingsDialog(); + + static MPSettingsDialog* currentDlg; + static MPSettingsDialog* openDlg(QWidget* parent) + { + if (currentDlg) + { + currentDlg->activateWindow(); + return currentDlg; + } + + currentDlg = new MPSettingsDialog(parent); + currentDlg->open(); + return currentDlg; + } + static void closeDlg() + { + currentDlg = nullptr; + } + +private slots: + void done(int r); + + // + +private: + Ui::MPSettingsDialog* ui; + + QButtonGroup* grpAudioMode; +}; + +#endif // MPSETTINGSDIALOG_H diff --git a/src/frontend/qt_sdl/MPSettingsDialog.ui b/src/frontend/qt_sdl/MPSettingsDialog.ui new file mode 100644 index 0000000000..bce0fc94f9 --- /dev/null +++ b/src/frontend/qt_sdl/MPSettingsDialog.ui @@ -0,0 +1,142 @@ + + + MPSettingsDialog + + + + 0 + 0 + 466 + 202 + + + + Multiplayer settings - melonDS + + + + + + Audio output + + + + + + Instance 1 only + + + + + + + All instances + + + + + + + Active instance only + + + + + + + + + + Network + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + 1000 + + + + + + + Data reception timeout: + + + + + + + + 1 + 0 + + + + milliseconds + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + MPSettingsDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + MPSettingsDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/frontend/qt_sdl/PathSettingsDialog.cpp b/src/frontend/qt_sdl/PathSettingsDialog.cpp index 7fa517d379..286032e9b6 100644 --- a/src/frontend/qt_sdl/PathSettingsDialog.cpp +++ b/src/frontend/qt_sdl/PathSettingsDialog.cpp @@ -22,6 +22,7 @@ #include "types.h" #include "Config.h" +#include "Platform.h" #include "PathSettingsDialog.h" #include "ui_PathSettingsDialog.h" @@ -43,6 +44,12 @@ PathSettingsDialog::PathSettingsDialog(QWidget* parent) : QDialog(parent), ui(ne ui->txtSaveFilePath->setText(QString::fromStdString(Config::SaveFilePath)); ui->txtSavestatePath->setText(QString::fromStdString(Config::SavestatePath)); ui->txtCheatFilePath->setText(QString::fromStdString(Config::CheatFilePath)); + + int inst = Platform::InstanceID(); + if (inst > 0) + ui->lblInstanceNum->setText(QString("Configuring paths for instance %1").arg(inst+1)); + else + ui->lblInstanceNum->hide(); } PathSettingsDialog::~PathSettingsDialog() diff --git a/src/frontend/qt_sdl/PathSettingsDialog.ui b/src/frontend/qt_sdl/PathSettingsDialog.ui index 95f5acc91a..295b1c4493 100644 --- a/src/frontend/qt_sdl/PathSettingsDialog.ui +++ b/src/frontend/qt_sdl/PathSettingsDialog.ui @@ -7,49 +7,63 @@ 0 0 439 - 166 + 185 Path settings - melonDS - - - - true + + + + Cheat files path: - + Browse... - + Savestates path: + + + + + + + - + true - - + + - Leave a path blank to use the current ROM's path. + Browse... - + + + + true + + + + Qt::Horizontal @@ -59,45 +73,38 @@ - - + + - Cheat files path: + Browse... - - + + - Browse... + Save files path: - - + + - Browse... + Leave a path blank to use the current ROM's path. - + true - - - - Save files path: - - - - - + + - + Configuring paths for instance X diff --git a/src/frontend/qt_sdl/Platform.cpp b/src/frontend/qt_sdl/Platform.cpp index 4306c9887a..68bdd3eaf6 100644 --- a/src/frontend/qt_sdl/Platform.cpp +++ b/src/frontend/qt_sdl/Platform.cpp @@ -20,28 +20,7 @@ #include #include -#ifdef __WIN32__ - #define NTDDI_VERSION 0x06000000 // GROSS FUCKING HACK - #include - #include - //#include // FUCK THAT SHIT - #include - #include - #include - #define dup _dup - #define socket_t SOCKET - #define sockaddr_t SOCKADDR -#else - #include - #include - #include - #include - - #define socket_t int - #define sockaddr_t struct sockaddr - #define closesocket close -#endif - +#include #include #include #include @@ -49,32 +28,80 @@ #include #include #include +#include #include "Platform.h" #include "Config.h" #include "ROMManager.h" #include "LAN_Socket.h" #include "LAN_PCap.h" -#include - -#ifndef INVALID_SOCKET - #define INVALID_SOCKET (socket_t)-1 -#endif +#include "LocalMP.h" std::string EmuDirectory; void emuStop(); - namespace Platform { -socket_t MPSocket; -sockaddr_t MPSendAddr; -u8 PacketBuffer[2048]; +QSharedMemory* IPCBuffer = nullptr; +int IPCInstanceID; + +void IPCInit() +{ + IPCInstanceID = 0; + + IPCBuffer = new QSharedMemory("melonIPC"); + + if (!IPCBuffer->attach()) + { + printf("IPC sharedmem doesn't exist. creating\n"); + if (!IPCBuffer->create(1024)) + { + printf("IPC sharedmem create failed :(\n"); + delete IPCBuffer; + IPCBuffer = nullptr; + return; + } + + IPCBuffer->lock(); + memset(IPCBuffer->data(), 0, IPCBuffer->size()); + IPCBuffer->unlock(); + } + + IPCBuffer->lock(); + u8* data = (u8*)IPCBuffer->data(); + u16 mask = *(u16*)&data[0]; + for (int i = 0; i < 16; i++) + { + if (!(mask & (1<unlock(); + + printf("IPC: instance ID %d\n", IPCInstanceID); +} -#define NIFI_VER 1 +void IPCDeInit() +{ + if (IPCBuffer) + { + IPCBuffer->lock(); + u8* data = (u8*)IPCBuffer->data(); + *(u16*)&data[0] &= ~(1<unlock(); + + IPCBuffer->detach(); + delete IPCBuffer; + } + + IPCBuffer = nullptr; +} void Init(int argc, char** argv) @@ -110,10 +137,13 @@ void Init(int argc, char** argv) confdir = config.absolutePath() + "/melonDS/"; EmuDirectory = confdir.toStdString(); #endif + + IPCInit(); } void DeInit() { + IPCDeInit(); } @@ -123,6 +153,22 @@ void StopEmu() } +int InstanceID() +{ + return IPCInstanceID; +} + +std::string InstanceFileSuffix() +{ + int inst = IPCInstanceID; + if (inst == 0) return ""; + + char suffix[16] = {0}; + snprintf(suffix, 15, ".%d", inst+1); + return suffix; +} + + int GetConfigInt(ConfigEntry entry) { const int imgsizes[] = {0, 256, 512, 1024, 2048, 4096}; @@ -169,7 +215,6 @@ bool GetConfigBool(ConfigEntry entry) case DSiSD_ReadOnly: return Config::DSiSDReadOnly != 0; case DSiSD_FolderSync: return Config::DSiSDFolderSync != 0; - case Firm_RandomizeMAC: return Config::RandomizeMAC != 0; case Firm_OverrideSettings: return Config::FirmwareOverrideSettings != 0; } @@ -372,6 +417,11 @@ bool Mutex_TryLock(Mutex* mutex) return ((QMutex*) mutex)->try_lock(); } +void Sleep(u64 usecs) +{ + QThread::usleep(usecs); +} + void WriteNDSSave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen) { @@ -386,146 +436,60 @@ void WriteGBASave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen } + bool MP_Init() { - int opt_true = 1; - int res; - -#ifdef __WIN32__ - WSADATA wsadata; - if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0) - { - return false; - } -#endif // __WIN32__ - - MPSocket = socket(AF_INET, SOCK_DGRAM, 0); - if (MPSocket < 0) - { - return false; - } - - res = setsockopt(MPSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt_true, sizeof(int)); - if (res < 0) - { - closesocket(MPSocket); - MPSocket = INVALID_SOCKET; - return false; - } - -#if defined(BSD) || defined(__APPLE__) - res = setsockopt(MPSocket, SOL_SOCKET, SO_REUSEPORT, (const char*)&opt_true, sizeof(int)); - if (res < 0) - { - closesocket(MPSocket); - MPSocket = INVALID_SOCKET; - return false; - } -#endif - - sockaddr_t saddr; - saddr.sa_family = AF_INET; - *(u32*)&saddr.sa_data[2] = htonl(Config::SocketBindAnyAddr ? INADDR_ANY : INADDR_LOOPBACK); - *(u16*)&saddr.sa_data[0] = htons(7064); - res = bind(MPSocket, &saddr, sizeof(sockaddr_t)); - if (res < 0) - { - closesocket(MPSocket); - MPSocket = INVALID_SOCKET; - return false; - } - - res = setsockopt(MPSocket, SOL_SOCKET, SO_BROADCAST, (const char*)&opt_true, sizeof(int)); - if (res < 0) - { - closesocket(MPSocket); - MPSocket = INVALID_SOCKET; - return false; - } - - MPSendAddr.sa_family = AF_INET; - *(u32*)&MPSendAddr.sa_data[2] = htonl(INADDR_BROADCAST); - *(u16*)&MPSendAddr.sa_data[0] = htons(7064); - - return true; + return LocalMP::Init(); } void MP_DeInit() { - if (MPSocket >= 0) - closesocket(MPSocket); - -#ifdef __WIN32__ - WSACleanup(); -#endif // __WIN32__ + return LocalMP::DeInit(); } -int MP_SendPacket(u8* data, int len) +void MP_Begin() { - if (MPSocket < 0) - return 0; - - if (len > 2048-8) - { - printf("MP_SendPacket: error: packet too long (%d)\n", len); - return 0; - } - - *(u32*)&PacketBuffer[0] = htonl(0x4946494E); // NIFI - PacketBuffer[4] = NIFI_VER; - PacketBuffer[5] = 0; - *(u16*)&PacketBuffer[6] = htons(len); - memcpy(&PacketBuffer[8], data, len); - - int slen = sendto(MPSocket, (const char*)PacketBuffer, len+8, 0, &MPSendAddr, sizeof(sockaddr_t)); - if (slen < 8) return 0; - return slen - 8; + return LocalMP::Begin(); } -int MP_RecvPacket(u8* data, bool block) +void MP_End() { - if (MPSocket < 0) - return 0; - - fd_set fd; - struct timeval tv; + return LocalMP::End(); +} - FD_ZERO(&fd); - FD_SET(MPSocket, &fd); - tv.tv_sec = 0; - tv.tv_usec = block ? 5000 : 0; +int MP_SendPacket(u8* data, int len, u64 timestamp) +{ + return LocalMP::SendPacket(data, len, timestamp); +} - if (!select(MPSocket+1, &fd, 0, 0, &tv)) - { - return 0; - } +int MP_RecvPacket(u8* data, u64* timestamp) +{ + return LocalMP::RecvPacket(data, timestamp); +} - sockaddr_t fromAddr; - socklen_t fromLen = sizeof(sockaddr_t); - int rlen = recvfrom(MPSocket, (char*)PacketBuffer, 2048, 0, &fromAddr, &fromLen); - if (rlen < 8+24) - { - return 0; - } - rlen -= 8; +int MP_SendCmd(u8* data, int len, u64 timestamp) +{ + return LocalMP::SendCmd(data, len, timestamp); +} - if (ntohl(*(u32*)&PacketBuffer[0]) != 0x4946494E) - { - return 0; - } +int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid) +{ + return LocalMP::SendReply(data, len, timestamp, aid); +} - if (PacketBuffer[4] != NIFI_VER) - { - return 0; - } +int MP_SendAck(u8* data, int len, u64 timestamp) +{ + return LocalMP::SendAck(data, len, timestamp); +} - if (ntohs(*(u16*)&PacketBuffer[6]) != rlen) - { - return 0; - } +int MP_RecvHostPacket(u8* data, u64* timestamp) +{ + return LocalMP::RecvHostPacket(data, timestamp); +} - memcpy(data, &PacketBuffer[8], rlen); - return rlen; +u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask) +{ + return LocalMP::RecvReplies(data, timestamp, aidmask); } @@ -573,9 +537,4 @@ int LAN_RecvPacket(u8* data) return LAN_Socket::RecvPacket(data); } -void Sleep(u64 usecs) -{ - QThread::usleep(usecs); -} - } diff --git a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp index 499c176ad8..89f74e5f27 100644 --- a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp +++ b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp @@ -23,6 +23,7 @@ #include "DSi_I2C.h" #include "NDS.h" #include "Config.h" +#include "Platform.h" #include "types.h" @@ -65,6 +66,12 @@ PowerManagementDialog::PowerManagementDialog(QWidget* parent) : QDialog(parent), } ui->sliderDSiBatteryLevel->setValue(dsiBatterySliderPos); + int inst = Platform::InstanceID(); + if (inst > 0) + ui->lblInstanceNum->setText(QString("Setting battery levels for instance %1").arg(inst+1)); + else + ui->lblInstanceNum->hide(); + inited = true; } diff --git a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.ui b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.ui index e0e7c6e863..77af2254fb 100644 --- a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.ui +++ b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.ui @@ -7,7 +7,7 @@ 0 0 562 - 279 + 288 @@ -23,37 +23,7 @@ QLayout::SetFixedSize - - - - DS Battery - - - - - - Low - - - - - - - Battery Level - - - - - - - Okay - - - - - - - + Qt::Horizontal @@ -63,7 +33,7 @@ - + DSi Battery @@ -219,6 +189,49 @@ + + + + DS Battery + + + + + + Low + + + + + + + Battery Level + + + + + + + Okay + + + + + + + + + + + 0 + 0 + + + + Configuring settings for instance X + + + diff --git a/src/frontend/qt_sdl/ROMManager.cpp b/src/frontend/qt_sdl/ROMManager.cpp index 304862eb7b..716a4543ba 100644 --- a/src/frontend/qt_sdl/ROMManager.cpp +++ b/src/frontend/qt_sdl/ROMManager.cpp @@ -326,6 +326,7 @@ bool LoadState(std::string filename) std::string savefile = filename.substr(LastSep(filename)+1); savefile = GetAssetPath(false, Config::SaveFilePath, ".sav", savefile); + savefile += Platform::InstanceFileSuffix(); NDSSave->SetPath(savefile, true); } @@ -350,6 +351,7 @@ bool SaveState(std::string filename) { std::string savefile = filename.substr(LastSep(filename)+1); savefile = GetAssetPath(false, Config::SaveFilePath, ".sav", savefile); + savefile += Platform::InstanceFileSuffix(); NDSSave->SetPath(savefile, false); } @@ -432,6 +434,7 @@ void Reset() { std::string oldsave = NDSSave->GetPath(); std::string newsave = GetAssetPath(false, Config::SaveFilePath, ".sav"); + newsave += Platform::InstanceFileSuffix(); if (oldsave != newsave) NDSSave->SetPath(newsave, false); } @@ -440,6 +443,7 @@ void Reset() { std::string oldsave = GBASave->GetPath(); std::string newsave = GetAssetPath(true, Config::SaveFilePath, ".sav"); + newsave += Platform::InstanceFileSuffix(); if (oldsave != newsave) GBASave->SetPath(newsave, false); } @@ -562,7 +566,11 @@ bool LoadROM(QStringList filepath, bool reset) u8* savedata = nullptr; std::string savname = GetAssetPath(false, Config::SaveFilePath, ".sav"); + std::string origsav = savname; + savname += Platform::InstanceFileSuffix(); + FILE* sav = Platform::OpenFile(savname, "rb", true); + if (!sav) sav = Platform::OpenFile(origsav, "rb", true); if (sav) { fseek(sav, 0, SEEK_END); @@ -711,7 +719,11 @@ bool LoadGBAROM(QStringList filepath) u8* savedata = nullptr; std::string savname = GetAssetPath(true, Config::SaveFilePath, ".sav"); + std::string origsav = savname; + savname += Platform::InstanceFileSuffix(); + FILE* sav = Platform::OpenFile(savname, "rb", true); + if (!sav) sav = Platform::OpenFile(origsav, "rb", true); if (sav) { fseek(sav, 0, SEEK_END); diff --git a/src/frontend/qt_sdl/WifiSettingsDialog.cpp b/src/frontend/qt_sdl/WifiSettingsDialog.cpp index 19cece6f8e..9bf265e98b 100644 --- a/src/frontend/qt_sdl/WifiSettingsDialog.cpp +++ b/src/frontend/qt_sdl/WifiSettingsDialog.cpp @@ -50,12 +50,12 @@ WifiSettingsDialog::WifiSettingsDialog(QWidget* parent) : QDialog(parent), ui(ne ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - LAN_Socket::Init(); haspcap = LAN_PCap::Init(false); ui->rbDirectMode->setText("Direct mode (requires " PCAP_NAME " and ethernet connection)"); - ui->cbBindAnyAddr->setChecked(Config::SocketBindAnyAddr); + ui->lblAdapterMAC->setText("(none)"); + ui->lblAdapterIP->setText("(none)"); int sel = 0; for (int i = 0; i < LAN_PCap::NumAdapters; i++) @@ -88,7 +88,6 @@ void WifiSettingsDialog::done(int r) if (r == QDialog::Accepted) { - Config::SocketBindAnyAddr = ui->cbBindAnyAddr->isChecked(); Config::DirectLAN = ui->rbDirectMode->isChecked(); int sel = ui->cbxDirectAdapter->currentIndex(); diff --git a/src/frontend/qt_sdl/WifiSettingsDialog.ui b/src/frontend/qt_sdl/WifiSettingsDialog.ui index 0897059569..444e1d5fff 100644 --- a/src/frontend/qt_sdl/WifiSettingsDialog.ui +++ b/src/frontend/qt_sdl/WifiSettingsDialog.ui @@ -7,7 +7,7 @@ 0 0 572 - 273 + 217 @@ -26,16 +26,26 @@ - Local + Network mode - + + + <html><head/><body><p>Indirect mode uses libslirp. It requires no extra setup and is easy to use.</p></body></html> + + + Indirect mode (uses libslirp, recommended) + + + + + - <html><head/><body><p>Enabling this allows (theoretically) playing local multiplayer games over a local network. It may or may not help make for a better connection in general.</p></body></html> + <html><head/><body><p>Direct mode directly routes network traffic to the host network. It is the most reliable, but requires an ethernet connection.</p><p><br/></p><p>Non-direct mode uses a layer of emulation to get around this, but is more prone to problems.</p></body></html> - Bind socket to any address + Direct mode [TEXT PLACEHOLDER] @@ -43,91 +53,62 @@ - + - Online + Direct mode settings - - - - - Direct mode settings + + + + + Network adapter: - - - - - Network adapter: - - - - - - - - 0 - 0 - - - - - 300 - 0 - - - - <html><head/><body><p>Selects the network adapter through which to route network traffic under direct mode.</p></body></html> - - - - - - - MAC address: - - - - - - - [PLACEHOLDER] - - - - - - - IP address: - - - - - - - [PLACEHOLDER] - - - - - - + + + + + 0 + 0 + + + + + 300 + 0 + + - <html><head/><body><p>Indirect mode uses libslirp. It requires no extra setup and is easy to use.</p></body></html> + <html><head/><body><p>Selects the network adapter through which to route network traffic under direct mode.</p></body></html> + + + + - Indirect mode (uses libslirp, recommended) + MAC address: + + + + + + + [PLACEHOLDER] - - - <html><head/><body><p>Direct mode directly routes network traffic to the host network. It is the most reliable, but requires an ethernet connection.</p><p><br/></p><p>Non-direct mode uses a layer of emulation to get around this, but is more prone to problems.</p></body></html> + + + IP address: + + + + - Direct mode [TEXT PLACEHOLDER] + [PLACEHOLDER] diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp index 2401a53afc..34cd03aa7e 100644 --- a/src/frontend/qt_sdl/main.cpp +++ b/src/frontend/qt_sdl/main.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -57,6 +58,7 @@ #include "AudioSettingsDialog.h" #include "FirmwareSettingsDialog.h" #include "PathSettingsDialog.h" +#include "MPSettingsDialog.h" #include "WifiSettingsDialog.h" #include "InterfaceSettingsDialog.h" #include "ROMInfoDialog.h" @@ -77,6 +79,7 @@ #include "SPU.h" #include "Wifi.h" #include "Platform.h" +#include "LocalMP.h" #include "Config.h" #include "Savestate.h" @@ -101,6 +104,7 @@ bool videoSettingsDirty; SDL_AudioDeviceID audioDevice; int audioFreq; +bool audioMuted; SDL_cond* audioSync; SDL_mutex* audioSyncLock; @@ -138,7 +142,7 @@ void audioCallback(void* data, Uint8* stream, int len) SDL_CondSignal(audioSync); SDL_UnlockMutex(audioSyncLock); - if (num_in < 1) + if ((num_in < 1) || audioMuted) { memset(stream, 0, len*sizeof(s16)*2); return; @@ -158,6 +162,23 @@ void audioCallback(void* data, Uint8* stream, int len) Frontend::AudioOut_Resample(buf_in, num_in, (s16*)stream, len, Config::AudioVolume); } +void audioMute() +{ + int inst = Platform::InstanceID(); + audioMuted = false; + + switch (Config::MPAudioMode) + { + case 1: // only instance 1 + if (inst > 0) audioMuted = true; + break; + + case 2: // only currently focused instance + if (!mainWindow->isActiveWindow()) audioMuted = true; + break; + } +} + void micOpen() { @@ -646,7 +667,11 @@ void EmuThread::run() if (winUpdateFreq < 1) winUpdateFreq = 1; - sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget); + int inst = Platform::InstanceID(); + if (inst == 0) + sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget); + else + sprintf(melontitle, "[%d/%.0f] melonDS (%d)", fps, fpstarget, inst+1); changeWindowTitle(melontitle); } } @@ -661,7 +686,11 @@ void EmuThread::run() EmuStatus = EmuRunning; - sprintf(melontitle, "melonDS " MELONDS_VERSION); + int inst = Platform::InstanceID(); + if (inst == 0) + sprintf(melontitle, "melonDS " MELONDS_VERSION); + else + sprintf(melontitle, "melonDS (%d)", inst+1); changeWindowTitle(melontitle); SDL_Delay(75); @@ -1330,6 +1359,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) setWindowTitle("melonDS " MELONDS_VERSION); setAttribute(Qt::WA_DeleteOnClose); setAcceptDrops(true); + setFocusPolicy(Qt::ClickFocus); + + int inst = Platform::InstanceID(); QMenuBar* menubar = new QMenuBar(); { @@ -1462,19 +1494,30 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) actEnableCheats->setCheckable(true); connect(actEnableCheats, &QAction::triggered, this, &MainWindow::onEnableCheats); - actSetupCheats = menu->addAction("Setup cheat codes"); - actSetupCheats->setMenuRole(QAction::NoRole); - connect(actSetupCheats, &QAction::triggered, this, &MainWindow::onSetupCheats); + //if (inst == 0) + { + actSetupCheats = menu->addAction("Setup cheat codes"); + actSetupCheats->setMenuRole(QAction::NoRole); + connect(actSetupCheats, &QAction::triggered, this, &MainWindow::onSetupCheats); - menu->addSeparator(); - actROMInfo = menu->addAction("ROM info"); - connect(actROMInfo, &QAction::triggered, this, &MainWindow::onROMInfo); + menu->addSeparator(); + actROMInfo = menu->addAction("ROM info"); + connect(actROMInfo, &QAction::triggered, this, &MainWindow::onROMInfo); - actRAMInfo = menu->addAction("RAM search"); - connect(actRAMInfo, &QAction::triggered, this, &MainWindow::onRAMInfo); + actRAMInfo = menu->addAction("RAM search"); + connect(actRAMInfo, &QAction::triggered, this, &MainWindow::onRAMInfo); + + actTitleManager = menu->addAction("Manage DSi titles"); + connect(actTitleManager, &QAction::triggered, this, &MainWindow::onOpenTitleManager); + } - actTitleManager = menu->addAction("Manage DSi titles"); - connect(actTitleManager, &QAction::triggered, this, &MainWindow::onOpenTitleManager); + { + menu->addSeparator(); + QMenu* submenu = menu->addMenu("Multiplayer"); + + actMPNewInstance = submenu->addAction("Launch new instance"); + connect(actMPNewInstance, &QAction::triggered, this, &MainWindow::onMPNewInstance); + } } { QMenu* menu = menubar->addMenu("Config"); @@ -1483,7 +1526,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) connect(actEmuSettings, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); #ifdef __APPLE__ - QAction* actPreferences = menu->addAction("Preferences..."); + actPreferences = menu->addAction("Preferences..."); connect(actPreferences, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); actPreferences->setMenuRole(QAction::PreferencesRole); #endif @@ -1497,15 +1540,18 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) actAudioSettings = menu->addAction("Audio settings"); connect(actAudioSettings, &QAction::triggered, this, &MainWindow::onOpenAudioSettings); + actMPSettings = menu->addAction("Multiplayer settings"); + connect(actMPSettings, &QAction::triggered, this, &MainWindow::onOpenMPSettings); + actWifiSettings = menu->addAction("Wifi settings"); connect(actWifiSettings, &QAction::triggered, this, &MainWindow::onOpenWifiSettings); - actInterfaceSettings = menu->addAction("Interface settings"); - connect(actInterfaceSettings, &QAction::triggered, this, &MainWindow::onOpenInterfaceSettings); - actFirmwareSettings = menu->addAction("Firmware settings"); connect(actFirmwareSettings, &QAction::triggered, this, &MainWindow::onOpenFirmwareSettings); + actInterfaceSettings = menu->addAction("Interface settings"); + connect(actInterfaceSettings, &QAction::triggered, this, &MainWindow::onOpenInterfaceSettings); + actPathSettings = menu->addAction("Path settings"); connect(actPathSettings, &QAction::triggered, this, &MainWindow::onOpenPathSettings); @@ -1661,6 +1707,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) resize(Config::WindowWidth, Config::WindowHeight); + if (Config::FirmwareUsername == "Arisotura") + actMPNewInstance->setText("Fart"); + #ifdef Q_OS_MAC QPoint screenCenter = screen()->availableGeometry().center(); QRect frameGeo = frameGeometry(); @@ -1740,6 +1789,19 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) actLimitFramerate->setChecked(Config::LimitFPS); actAudioSync->setChecked(Config::AudioSync); + + if (inst > 0) + { + actEmuSettings->setEnabled(false); + actVideoSettings->setEnabled(false); + actMPSettings->setEnabled(false); + actWifiSettings->setEnabled(false); + actInterfaceSettings->setEnabled(false); + +#ifdef __APPLE__ + actPreferences->setEnabled(false); +#endif // __APPLE__ + } } MainWindow::~MainWindow() @@ -1828,7 +1890,7 @@ void MainWindow::keyPressEvent(QKeyEvent* event) if (event->isAutoRepeat()) return; // TODO!! REMOVE ME IN RELEASE BUILDS!! - //if (event->key() == Qt::Key_F11) NDS::debug(0); + if (event->key() == Qt::Key_F11) NDS::debug(0); Input::KeyPress(event); } @@ -1930,6 +1992,16 @@ void MainWindow::dropEvent(QDropEvent* event) } } +void MainWindow::focusInEvent(QFocusEvent* event) +{ + audioMute(); +} + +void MainWindow::focusOutEvent(QFocusEvent* event) +{ + audioMute(); +} + void MainWindow::onAppStateChanged(Qt::ApplicationState state) { if (state == Qt::ApplicationInactive) @@ -2602,6 +2674,23 @@ void MainWindow::onOpenTitleManager() TitleManagerDialog* dlg = TitleManagerDialog::openDlg(this); } +void MainWindow::onMPNewInstance() +{ + //QProcess::startDetached(QApplication::applicationFilePath()); + QProcess newinst; + newinst.setProgram(QApplication::applicationFilePath()); + newinst.setArguments(QApplication::arguments().mid(1, QApplication::arguments().length()-1)); + +#ifdef __WIN32__ + newinst.setCreateProcessArgumentsModifier([] (QProcess::CreateProcessArguments *args) + { + args->flags |= CREATE_NEW_CONSOLE; + }); +#endif + + newinst.startDetached(); +} + void MainWindow::onOpenEmuSettings() { emuThread->emuPause(); @@ -2736,6 +2825,22 @@ void MainWindow::onAudioSettingsFinished(int res) micOpen(); } +void MainWindow::onOpenMPSettings() +{ + emuThread->emuPause(); + + MPSettingsDialog* dlg = MPSettingsDialog::openDlg(this); + connect(dlg, &MPSettingsDialog::finished, this, &MainWindow::onMPSettingsFinished); +} + +void MainWindow::onMPSettingsFinished(int res) +{ + audioMute(); + LocalMP::SetRecvTimeout(Config::MPRecvTimeout); + + emuThread->emuUnpause(); +} + void MainWindow::onOpenWifiSettings() { emuThread->emuPause(); @@ -2746,12 +2851,6 @@ void MainWindow::onOpenWifiSettings() void MainWindow::onWifiSettingsFinished(int res) { - if (Wifi::MPInited) - { - Platform::MP_DeInit(); - Platform::MP_Init(); - } - Platform::LAN_DeInit(); Platform::LAN_Init(); @@ -3070,6 +3169,7 @@ int main(int argc, char** argv) format.setSwapInterval(0); QSurfaceFormat::setDefaultFormat(format); + audioMuted = false; audioSync = SDL_CreateCond(); audioSyncLock = SDL_CreateMutex(); @@ -3123,6 +3223,8 @@ int main(int argc, char** argv) emuThread->start(); emuThread->emuPause(); + audioMute(); + QObject::connect(&melon, &QApplication::applicationStateChanged, mainWindow, &MainWindow::onAppStateChanged); if (argc > 1) @@ -3180,12 +3282,13 @@ int CALLBACK WinMain(HINSTANCE hinst, HINSTANCE hprev, LPSTR cmdline, int cmdsho if (argv_w) LocalFree(argv_w); - /*if (AttachConsole(ATTACH_PARENT_PROCESS)) + //if (AttachConsole(ATTACH_PARENT_PROCESS)) + if (AllocConsole()) { freopen("CONOUT$", "w", stdout); freopen("CONOUT$", "w", stderr); printf("\n"); - }*/ + } int ret = main(argc, argv); diff --git a/src/frontend/qt_sdl/main.h b/src/frontend/qt_sdl/main.h index 45d1da0137..5d03e54aa9 100644 --- a/src/frontend/qt_sdl/main.h +++ b/src/frontend/qt_sdl/main.h @@ -226,6 +226,9 @@ class MainWindow : public QMainWindow void dragEnterEvent(QDragEnterEvent* event) override; void dropEvent(QDropEvent* event) override; + void focusInEvent(QFocusEvent* event) override; + void focusOutEvent(QFocusEvent* event) override; + signals: void screenLayoutChange(); @@ -255,6 +258,7 @@ private slots: void onROMInfo(); void onRAMInfo(); void onOpenTitleManager(); + void onMPNewInstance(); void onOpenEmuSettings(); void onEmuSettingsDialogFinished(int res); @@ -267,6 +271,8 @@ private slots: void onOpenPathSettings(); void onUpdateAudioSettings(); void onAudioSettingsFinished(int res); + void onOpenMPSettings(); + void onMPSettingsFinished(int res); void onOpenWifiSettings(); void onWifiSettingsFinished(int res); void onFirmwareSettingsFinished(int res); @@ -344,12 +350,17 @@ private slots: QAction* actROMInfo; QAction* actRAMInfo; QAction* actTitleManager; + QAction* actMPNewInstance; QAction* actEmuSettings; +#ifdef __APPLE__ + QAction* actPreferences; +#endif QAction* actPowerManagement; QAction* actInputConfig; QAction* actVideoSettings; QAction* actAudioSettings; + QAction* actMPSettings; QAction* actWifiSettings; QAction* actFirmwareSettings; QAction* actPathSettings; diff --git a/src/frontend/qt_sdl/sem_timedwait.cpp b/src/frontend/qt_sdl/sem_timedwait.cpp new file mode 100644 index 0000000000..38b3c16f6a --- /dev/null +++ b/src/frontend/qt_sdl/sem_timedwait.cpp @@ -0,0 +1,488 @@ +/* + * s e m _ t i m e d w a i t + * + * Function: + * Implements a version of sem_timedwait(). + * + * Description: + * Not all systems implement sem_timedwait(), which is a version of + * sem_wait() with a timeout. Mac OS X is one example, at least up to + * and including version 10.6 (Leopard). If such a function is needed, + * this code provides a reasonable implementation, which I think is + * compatible with the standard version, although possibly less + * efficient. It works by creating a thread that interrupts a normal + * sem_wait() call after the specified timeout. + * + * Call: + * + * The Linux man pages say: + * + * #include + * + * int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); + * + * sem_timedwait() is the same as sem_wait(), except that abs_timeout + * specifies a limit on the amount of time that the call should block if + * the decrement cannot be immediately performed. The abs_timeout argument + * points to a structure that specifies an absolute timeout in seconds and + * nanoseconds since the Epoch (00:00:00, 1 January 1970). This structure + * is defined as follows: + * + * struct timespec { + * time_t tv_sec; Seconds + * long tv_nsec; Nanoseconds [0 .. 999999999] + * }; + * + * If the timeout has already expired by the time of the call, and the + * semaphore could not be locked immediately, then sem_timedwait() fails + * with a timeout error (errno set to ETIMEDOUT). + * If the operation can be performed immediately, then sem_timedwait() + * never fails with a timeout error, regardless of the value of abs_timeout. + * Furthermore, the validity of abs_timeout is not checked in this case. + * + * Limitations: + * + * The mechanism used involves sending a SIGUSR2 signal to the thread + * calling sem_timedwait(). The handler for this signal is set to a null + * routine which does nothing, and with any flags for the signal + * (eg SA_RESTART) cleared. Note that this effective disabling of the + * SIGUSR2 signal is a side-effect of using this routine, and means it + * may not be a completely transparent plug-in replacement for a + * 'normal' sig_timedwait() call. Since OS X does not declare the + * sem_timedwait() call in its standard include files, the relevant + * declaration (shown above in the man pages extract) will probably have + * to be added to any code that uses this. + * + * Compiling: + * This compiles and runs cleanly on OS X (10.6) with gcc with the + * -Wall -ansi -pedantic flags. On Linux, using -ansi causes a sweep of + * compiler complaints about the timespec structure, but it compiles + * and works fine with just -Wall -pedantic. (Since Linux provides + * sem_timedwait() anyway, this really isn't needed on Linux.) However, + * since Linux provides sem_timedwait anyway, the sem_timedwait() + * code in this file is only compiled on OS X, and is a null on other + * systems. + * + * Testing: + * This file contains a test program that exercises the sem_timedwait + * code. It is compiled if the pre-processor variable TEST is defined. + * For more details, see the comments for the test routine at the end + * of the file. + * + * Author: Keith Shortridge, AAO. + * + * History: + * 8th Sep 2009. Original version. KS. + * 24th Sep 2009. Added test that the calling thread still exists before + * trying to set the timed-out flag. KS. + * 2nd Oct 2009. No longer restores the original SIGUSR2 signal handler. + * See comments in the body of the code for more details. + * Prototypes for now discontinued internal routines removed. + * 12th Aug 2010. Added the cleanup handler, so that this code no longer + * leaks resources if the calling thread is cancelled. KS. + * 21st Sep 2011. Added copyright notice below. Modified header comments + * to describe the use of SIGUSR2 more accurately in the + * light of the 2/10/09 change above. Now undefs DEBUG + * before defining it, to avoid any possible clash. KS. + * 14th Feb 2012. Tidied out a number of TABs that had got into the + * code. KS. + * 6th May 2013. Copyright notice modified to one based on the MIT licence, + * which is more permissive than the previous notice. KS. + * + * Copyright (c) Australian Astronomical Observatory (AAO), (2013). + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifdef __APPLE__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sem_timedwait.h" + +/* Some useful definitions - TRUE, FALSE, and DEBUG */ + +#undef TRUE +#define TRUE 1 +#undef FALSE +#define FALSE 0 +#undef DEBUG +#define DEBUG printf + +/* A structure of type timeoutDetails is passed to the thread used to + * implement the timeout. + */ + +typedef struct { + struct timespec delay; /* Specifies the delay, relative to now */ + pthread_t callingThread; /* The thread doing the sem_wait call */ + volatile short *timedOutShort; /* Address of a flag set to indicate that + * the timeout was triggered. */ +} timeoutDetails; + +/* A structure of type cleanupDetails is passed to the thread cleanup + * routine which is called at the end of the routine or if the thread calling + * it is cancelled. + */ + +typedef struct { + pthread_t *threadIdAddr; /* Address of the variable that holds + * the Id of the timeout thread. */ + struct sigaction *sigHandlerAddr; /* Address of the old signal action + * handler. */ + volatile short *timedOutShort; /* Address of a flag set to indicate that + * the timeout was triggered. */ +} cleanupDetails; + +/* Forward declarations of internal routines */ + +static void* timeoutThreadMain (void* passedPtr); +static int triggerSignal (int Signal, pthread_t Thread); +static void ignoreSignal (int Signal); +static void timeoutThreadCleanup (void* passedPtr); + +/* -------------------------------------------------------------------------- */ +/* + * s e m _ t i m e d w a i t + * + * This is the main code for the sem_timedwait() implementation. + */ + +int sem_timedwait ( + sem_t *sem, + const struct timespec *abs_timeout) +{ + int result = 0; /* Code returned by this routine 0 or -1 */ + + /* "Under no circumstances shall the function fail if the semaphore + * can be locked immediately". So we try to get it quickly to see if we + * can avoid all the timeout overheads. + */ + + if (sem_trywait(sem) == 0) { + + /* Yes, got it immediately. */ + + result = 0; + + } else { + + /* No, we've got to do it with a sem_wait() call and a thread to run + * the timeout. First, work out the time from now to the specified + * timeout, which we will pass to the timeout thread in a way that can + * be used to pass to nanosleep(). So we need this in seconds and + * nanoseconds. Along the way, we check for an invalid passed time, + * and for one that's already expired. + */ + + if ((abs_timeout->tv_nsec < 0) || (abs_timeout->tv_nsec > 1000000000)) { + + /* Passed time is invalid */ + + result = -1; + errno = EINVAL; + + } else { + + struct timeval currentTime; /* Time now */ + long secsToWait,nsecsToWait; /* Seconds and nsec to delay */ + gettimeofday (¤tTime,NULL); + secsToWait = abs_timeout->tv_sec - currentTime.tv_sec; + nsecsToWait = (abs_timeout->tv_nsec - (currentTime.tv_usec * 1000)); + while (nsecsToWait < 0) { + nsecsToWait += 1000000000; + secsToWait--; + } + if ((secsToWait < 0) || ((secsToWait == 0) && (nsecsToWait < 0))) { + + /* Time has passed. Report an immediate timeout. */ + + result = -1; + errno = ETIMEDOUT; + + } else { + + /* We're going to have to do a sem_wait() with a timeout thread. + * The thread will wait the specified time, then will issue a + * SIGUSR2 signal that will interrupt the sem_wait() call. + * We pass the thread the id of the current thread, the delay, + * and the address of a flag to set on a timeout, so we can + * distinguish an interrupt caused by the timeout thread from + * one caused by some other signal. + */ + + volatile short timedOut; /* Flag to set on timeout */ + timeoutDetails details; /* All the stuff the thread must know */ + struct sigaction oldSignalAction; /* Current signal setting */ + pthread_t timeoutThread; /* Id of timeout thread */ + cleanupDetails cleaningDetails; /* What the cleanup routine needs */ + int oldCancelState; /* Previous cancellation state */ + int ignoreCancelState; /* Used in call, but ignored */ + int createStatus; /* Status of pthread_create() call */ + + /* If the current thread is cancelled (and CML does do this) + * we don't want to leave our timer thread running - if we've + * started the thread we want to make sure we join it in order + * to release its resources. So we set a cleanup handler to + * do this. We pass it the address of the structure that will + * hold all it needs to know. While we set all this up, + * we prevent ourselves being cancelled, so all this data is + * coherent. + */ + + pthread_setcancelstate (PTHREAD_CANCEL_DISABLE,&oldCancelState); + timeoutThread = (pthread_t) 0; + cleaningDetails.timedOutShort = &timedOut; + cleaningDetails.threadIdAddr = &timeoutThread; + cleaningDetails.sigHandlerAddr = &oldSignalAction; + pthread_cleanup_push (timeoutThreadCleanup,&cleaningDetails); + + /* Set up the details for the thread. Clear the timeout flag, + * record the current SIGUSR2 action settings so we can restore + * them later. + */ + + details.delay.tv_sec = secsToWait; + details.delay.tv_nsec = nsecsToWait; + details.callingThread = pthread_self(); + details.timedOutShort = &timedOut; + timedOut = FALSE; + sigaction (SIGUSR2,NULL,&oldSignalAction); + + /* Start up the timeout thread. Once we've done that, we can + * restore the previous cancellation state. + */ + + createStatus = pthread_create(&timeoutThread,NULL, + timeoutThreadMain, (void*)&details); + pthread_setcancelstate (oldCancelState,&ignoreCancelState); + + if (createStatus < 0) { + + /* Failed to create thread. errno will already be set properly */ + + result = -1; + + } else { + + /* Thread created OK. This is where we wait for the semaphore. + */ + + if (sem_wait(sem) == 0) { + + /* Got the semaphore OK. We return zero, and all's well. */ + + result = 0; + + } else { + + /* If we got a -1 error from sem_wait(), it may be because + * it was interrupted by a timeout, or failed for some + * other reason. We check for the expected timeout + * condition, which is an 'interrupted' status and the + * timeout flag set by the timeout thread. We report that as + * a timeout error. Anything else is some other error and + * errno is already set properly. + */ + + result = -1; + if (errno == EINTR) { + if (timedOut) errno = ETIMEDOUT; + } + } + + } + + /* The cleanup routine - timeoutThreadCleanup() - packages up + * any tidying up that is needed, including joining with the + * timer thread. This will be called if the current thread is + * cancelled, but we need it to happen anyway, so we set the + * execute flag true here as we remove it from the list of + * cleanup routines to be called. So normally, this line amounts + * to calling timeoutThreadCleanup(). + */ + + pthread_cleanup_pop (TRUE); + } + } + } + return (result); +} + +/* -------------------------------------------------------------------------- */ +/* + * t i m e o u t T h r e a d C l e a n u p + * + * This internal routine tidies up at the end of a sem_timedwait() call. + * It is set as a cleanup routine for the current thread (not the timer + * thread) so it is executed even if the thread is cancelled. This is + * important, as we need to tidy up the timeout thread. If we took the + * semaphore (in other words, if we didn't timeout) then the timer thread + * will still be running, sitting in its nanosleep() call, and we need + * to cancel it. If the timer thread did signal a timeout then it will + * now be closing down. In either case, we need to join it (using a call + * to pthread_join()) or its resources will never be released. + * The single argument is a pointer to a cleanupDetails structure that has + * all the routine needs to know. + */ + +static void timeoutThreadCleanup (void* passedPtr) +{ + /* Get what we need from the structure we've been passed. */ + + cleanupDetails *detailsPtr = (cleanupDetails*) passedPtr; + short timedOut = *(detailsPtr->timedOutShort); + pthread_t timeoutThread = *(detailsPtr->threadIdAddr); + + /* If we created the thread, stop it - doesn't matter if it's no longer + * running, pthread_cancel can handle that. We make sure we wait for it + * to complete, because it is this pthread_join() call that releases any + * memory the thread may have allocated. Note that cancelling a thread is + * generally not a good idea, because of the difficulty of cleaning up + * after it, but this is a very simple thread that does nothing but call + * nanosleep(), and that we can cancel quite happily. + */ + + if (!timedOut) pthread_cancel(timeoutThread); + pthread_join(timeoutThread,NULL); + + /* The code originally restored the old action handler, which generally + * was the default handler that caused the task to exit. Just occasionally, + * there seem to be cases where the signal is still queued and ready to + * trigger even though the thread that presumably sent it off just before + * it was cancelled has finished. I had thought that once we'd joined + * that thread, we could be sure of not seeing the signal, but that seems + * not to be the case, and so restoring a handler that will allow the task + * to crash is not a good idea, and so the line below has been commented + * out. + * + * sigaction (SIGUSR2,detailsPtr->sigHandlerAddr,NULL); + */ +} + +/* -------------------------------------------------------------------------- */ +/* + * t i m e o u t T h r e a d M a i n + * + * This internal routine is the main code for the timeout thread. + * The single argument is a pointer to a timeoutDetails structure that has + * all the thread needs to know - thread to signal, delay time, and the + * address of a flag to set if it triggers a timeout. + */ + +static void* timeoutThreadMain (void* passedPtr) +{ + void* Return = (void*) 0; + + /* We grab all the data held in the calling thread right now. In some + * cases, we find that the calling thread has vanished and released + * its memory, including the details structure, by the time the timeout + * expires, and then we get an access violation when we try to set the + * 'timed out' flag. + */ + + timeoutDetails details = *((timeoutDetails*) passedPtr); + struct timespec requestedDelay = details.delay; + + /* We do a nanosleep() for the specified delay, and then trigger a + * timeout. Note that we allow for the case where the nanosleep() is + * interrupted, and restart it for the remaining time. If the + * thread that is doing the sem_wait() call gets the semaphore, it + * will cancel this thread, which is fine as we aren't doing anything + * other than a sleep and a signal. + */ + + for (;;) { + struct timespec remainingDelay; + if (nanosleep (&requestedDelay,&remainingDelay) == 0) { + break; + } else if (errno == EINTR) { + requestedDelay = remainingDelay; + } else { + Return = (void*) errno; + break; + } + } + + /* We've completed the delay without being cancelled, so we now trigger + * the timeout by sending a signal to the calling thread. And that's it, + * although we set the timeout flag first to indicate that it was us + * that interrupted the sem_wait() call. One precaution: before we + * try to set the timed-out flag, make sure the calling thread still + * exists - this may not be the case if things are closing down a bit + * messily. We check this quickly using a zero test signal. + */ + + if (pthread_kill(details.callingThread,0) == 0) { + *(details.timedOutShort) = TRUE; + if (triggerSignal (SIGUSR2,details.callingThread) < 0) { + Return = (void*) errno; + } + } + + return Return; +} + +/* -------------------------------------------------------------------------- */ +/* + * t r i g g e r S i g n a l + * + * This is a general purpose routine that sends a specified signal to + * a specified thread, setting up a signal handler that does nothing, + * and then giving the signal. The only effect will be to interrupt any + * operation that is currently blocking - in this case, we expect this to + * be a sem_wait() call. + */ + +static int triggerSignal (int Signal, pthread_t Thread) +{ + int Result = 0; + struct sigaction SignalDetails; + SignalDetails.sa_handler = ignoreSignal; + SignalDetails.sa_flags = 0; + (void) sigemptyset(&SignalDetails.sa_mask); + if ((Result = sigaction(Signal,&SignalDetails,NULL)) == 0) { + Result = pthread_kill(Thread,Signal); + } + return Result; +} + +/* -------------------------------------------------------------------------- */ +/* + * i g n o r e S i g n a l + * + * And this is the signal handler that does nothing. (It clears its argument, + * but this has no effect and prevents a compiler warning about an unused + * argument.) + */ + +static void ignoreSignal (int Signal) { + Signal = 0; +} + +#endif diff --git a/src/frontend/qt_sdl/sem_timedwait.h b/src/frontend/qt_sdl/sem_timedwait.h new file mode 100644 index 0000000000..42ae201e55 --- /dev/null +++ b/src/frontend/qt_sdl/sem_timedwait.h @@ -0,0 +1,8 @@ +#ifndef __SEM_TIMEDWAIT_H +#define __SEM_TIMEDWAIT_H + +#ifdef __APPLE__ +int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); +#endif + +#endif