/* Copyright 2016-2024 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 #include #include #include "NDS.h" #include "NDSCart.h" #include "main.h" //#include "IPC.h" #include "Netplay.h" //#include "Input.h" //#include "ROMManager.h" //#include "Config.h" #include "Savestate.h" #include "Platform.h" using namespace melonDS; namespace Netplay { bool Active; bool IsHost; bool IsMirror; ENetHost* Host; ENetHost* MirrorHost; Player Players[16]; int NumPlayers; Player MyPlayer; u32 HostAddress; bool Lag; int NumMirrorClients; struct InputFrame { u32 FrameNum; u32 KeyMask; u32 Touching; u32 TouchX, TouchY; }; std::queue InputQueue; enum { Blob_CartROM = 0, Blob_CartSRAM, Blob_InitState, Blob_MAX }; const u32 kChunkSize = 0x10000; u8 ChunkBuffer[0x10 + kChunkSize]; u8* Blobs[Blob_MAX]; u32 BlobLens[Blob_MAX]; int CurBlobType; u32 CurBlobLen; bool Init() { Active = false; IsHost = false; IsMirror = false; Host = nullptr; MirrorHost = nullptr; Lag = false; memset(Players, 0, sizeof(Players)); NumPlayers = 0; NumMirrorClients = 0; for (int i = 0; i < Blob_MAX; i++) { Blobs[i] = nullptr; BlobLens[i] = 0; } CurBlobType = -1; CurBlobLen = 0; /*if (enet_initialize() != 0) { printf("enet shat itself :(\n"); return false; } printf("enet init OK\n");*/ return true; } void DeInit() { // TODO: cleanup resources properly!! //enet_deinitialize(); } void StartHost(const char* playername, int port) { ENetAddress addr; addr.host = ENET_HOST_ANY; addr.port = port; Host = enet_host_create(&addr, 16, 1, 0, 0); if (!Host) { printf("host shat itself :(\n"); return; } Player* player = &Players[0]; memset(player, 0, sizeof(Player)); player->ID = 0; strncpy(player->Name, playername, 31); player->Status = 2; player->Address = 0x0100007F; NumPlayers = 1; memcpy(&MyPlayer, player, sizeof(Player)); HostAddress = 0x0100007F; NumMirrorClients = 0; ENetAddress mirroraddr; mirroraddr.host = ENET_HOST_ANY; mirroraddr.port = port + 1; printf("host mirror host connecting to %08X:%d\n", mirroraddr.host, mirroraddr.port); MirrorHost = enet_host_create(&mirroraddr, 16, 2, 0, 0); if (!MirrorHost) { printf("mirror host shat itself :(\n"); return; } Active = true; IsHost = true; IsMirror = false; //netplayDlg->updatePlayerList(Players, NumPlayers); } void StartClient(const char* playername, const char* host, int port) { Host = enet_host_create(nullptr, 1, 1, 0, 0); if (!Host) { printf("client shat itself :(\n"); return; } printf("client created, connecting (%s, %s:%d)\n", playername, host, port); ENetAddress addr; enet_address_set_host(&addr, host); addr.port = port; ENetPeer* peer = enet_host_connect(Host, &addr, 1, 0); if (!peer) { printf("connect shat itself :(\n"); return; } ENetEvent event; bool conn = false; if (enet_host_service(Host, &event, 5000) > 0) { if (event.type == ENET_EVENT_TYPE_CONNECT) { printf("connected!\n"); conn = true; } } if (!conn) { printf("connection failed\n"); enet_peer_reset(peer); return; } Player* player = &MyPlayer; memset(player, 0, sizeof(Player)); player->ID = 0; strncpy(player->Name, playername, 31); player->Status = 3; HostAddress = addr.host; Active = true; IsHost = false; IsMirror = false; } void StartMirror(const Player* player) { for (int i = 0; i < Blob_MAX; i++) { Blobs[i] = nullptr; BlobLens[i] = 0; } CurBlobType = -1; CurBlobLen = 0; MirrorHost = enet_host_create(nullptr, 1, 2, 0, 0); if (!MirrorHost) { printf("mirror shat itself :(\n"); return; } printf("mirror created, connecting\n"); ENetAddress addr; addr.host = player->Address; addr.port = 8064+1 + player->ID; // FIXME!!!!!!!!!! printf("mirror client connecting to %08X:%d\n", addr.host, addr.port); ENetPeer* peer = enet_host_connect(MirrorHost, &addr, 2, 0); if (!peer) { printf("connect shat itself :(\n"); return; } ENetEvent event; bool conn = false; if (enet_host_service(MirrorHost, &event, 5000) > 0) { if (event.type == ENET_EVENT_TYPE_CONNECT) { printf("connected!\n"); conn = true; } } if (!conn) { printf("connection failed\n"); enet_peer_reset(peer); return; } memcpy(&MyPlayer, player, sizeof(Player)); HostAddress = addr.host; Active = true; IsHost = false; IsMirror = true; } u32 PlayerAddress(int id) { if (id < 0 || id > 16) return 0; u32 ret = Players[id].Address; if (ret == 0x0100007F) ret = HostAddress; return ret; } bool SpawnMirrorInstance(Player player) { #if 0 u16 curmask = IPC::GetInstanceBitmask(); 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 if (!newinst.startDetached()) return false; // try to determine the ID of the new instance int newid = -1; for (int tries = 0; tries < 10; tries++) { QThread::usleep(100 * 1000); u16 newmask = IPC::GetInstanceBitmask(); if (newmask == curmask) continue; newmask &= ~curmask; for (int id = 0; id < 16; id++) { if (newmask & (1 << id)) { newid = id; break; } } } if (newid == -1) return false; // setup that instance printf("netplay: spawned mirror instance for player %d with ID %d, configuring\n", player.ID, newid); //std::string rompath = ROMManager::FullROMPath.join('|').toStdString(); //IPC::SendCommandStr(1< 0) { buf[0] = 0x02; *(u32*)&buf[12] = 0; for (u32 pos = 0; pos < len; pos += kChunkSize) { u32 chunklen = kChunkSize; if ((pos + chunklen) > len) chunklen = len - pos; *(u32*)&buf[8] = pos; memcpy(&buf[16], &data[pos], chunklen); ENetPacket* pkt = enet_packet_create(buf, 16+chunklen, ENET_PACKET_FLAG_RELIABLE); enet_host_broadcast(MirrorHost, 1, pkt); //enet_host_flush(MirrorHost); } } buf[0] = 0x03; pkt = enet_packet_create(buf, 8, ENET_PACKET_FLAG_RELIABLE); enet_host_broadcast(MirrorHost, 1, pkt); return true; } void RecvBlobFromMirrorHost(ENetPeer* peer, ENetPacket* pkt) { u8* buf = pkt->data; if (buf[0] == 0x01) { if (CurBlobType != -1) return; if (pkt->dataLength != 8) return; int type = buf[1]; if (type > Blob_MAX) return; u32 len = *(u32*)&buf[4]; if (len > 0x40000000) return; if (Blobs[type] != nullptr) return; if (BlobLens[type] != 0) return; printf("[MC] start blob type=%d len=%d\n", type, len); if (len) Blobs[type] = new u8[len]; BlobLens[type] = len; CurBlobType = type; CurBlobLen = len; ENetEvent evt; while (enet_host_service(MirrorHost, &evt, 5000) > 0) { if (evt.type == ENET_EVENT_TYPE_RECEIVE && evt.channelID == 1) { RecvBlobFromMirrorHost(evt.peer, evt.packet); if (evt.packet->dataLength >= 1 && evt.packet->data[0] == 0x03) { printf("[MC] blob done while in fast recv loop\n"); break; } } else { printf("[MC] fast recv loop aborted because evt %d ch %d\n", evt.type, evt.channelID); break; } } } else if (buf[0] == 0x02) { if (CurBlobType < 0 || CurBlobType > Blob_MAX) return; if (pkt->dataLength > (16+kChunkSize)) return; int type = buf[1]; if (type != CurBlobType) return; u32 len = *(u32*)&buf[4]; if (len != CurBlobLen) return; u32 pos = *(u32*)&buf[8]; if (pos >= len) return; if ((pos + (pkt->dataLength-16)) > len) return; u8* dst = Blobs[type]; if (!dst) return; if (BlobLens[type] != len) return; printf("[MC] recv blob data, type=%d pos=%08X len=%08X data=%08X\n", type, pos, len, pkt->dataLength-16); memcpy(&dst[pos], &buf[16], pkt->dataLength-16); } else if (buf[0] == 0x03) { if (CurBlobType < 0 || CurBlobType > Blob_MAX) return; if (pkt->dataLength != 8) return; int type = buf[1]; if (type != CurBlobType) return; u32 len = *(u32*)&buf[4]; if (len != CurBlobLen) return; printf("[MC] finish blob type=%d len=%d\n", type, len); CurBlobType = -1; CurBlobLen = 0; } else if (buf[0] == 0x04) { if (pkt->dataLength != 2) return; bool res = false; #if 0 // reset NDS::SetConsoleType(buf[1]); NDS::EjectCart(); NDS::Reset(); //SetBatteryLevels(); if (Blobs[Blob_CartROM]) { res = NDS::LoadCart(Blobs[Blob_CartROM], BlobLens[Blob_CartROM], Blobs[Blob_CartSRAM], BlobLens[Blob_CartSRAM]); if (!res) { printf("!!!! FAIL!!\n"); return; } } if (res) { ROMManager::CartType = 0; //ROMManager::NDSSave = new SaveManager(savname); //LoadCheats(); } #endif // load initial state // TODO: terrible hack!! #if 0 FILE* f = Platform::OpenFile("netplay2.mln", "wb"); fwrite(Blobs[Blob_InitState], BlobLens[Blob_InitState], 1, f); fclose(f); Savestate* state = new Savestate("netplay2.mln", false); NDS::DoSavestate(state); delete state; for (int i = 0; i < Blob_MAX; i++) { if (Blobs[i]) delete[] Blobs[i]; Blobs[i] = nullptr; BlobLens[i] = 0; } /*Savestate* zorp = new Savestate("netplay3.mln", true); NDS::DoSavestate(zorp); delete zorp;*/ printf("[MC] state loaded, PC=%08X/%08X\n", NDS::GetPC(0), NDS::GetPC(1)); ENetPacket* resp = enet_packet_create(buf, 1, ENET_PACKET_FLAG_RELIABLE); enet_peer_send(peer, 1, resp); #endif } else if (buf[0] == 0x05) { printf("[MIRROR CLIENT] start\n"); StartLocal(); } } void SyncMirrorClients() { printf("[MIRROR HOST] syncing clients\n"); #if 0 SendBlobToMirrorClients(Blob_CartSRAM, NDSCart::GetSaveMemoryLength(), NDSCart::GetSaveMemory()); // send initial state // TODO: this is a terrible hack! /*printf("[MH] state start\n"); Savestate* state = new Savestate("netplay.mln", true); NDS::DoSavestate(state); delete state; printf("[MH] state taken: PC=%08X/%08X\n", NDS::GetPC(0), NDS::GetPC(1)); FILE* f = Platform::OpenLocalFile("netplay.mln", "rb"); printf("[MH] state=%d\n", f?1:0); fseek(f, 0, SEEK_END); u32 flen = ftell(f); fseek(f, 0, SEEK_SET); u8* statebuf = new u8[flen]; fread(statebuf, flen, 1, f); fclose(f); printf("[MH] state read, len=%d\n", flen); SendBlobToMirrorClients(Blob_InitState, flen, statebuf); printf("[MH] state sent\n"); delete[] statebuf;*/ u8 data[2]; data[0] = 0x04; data[1] = (u8)Config::ConsoleType; ENetPacket* pkt = enet_packet_create(&data, 2, ENET_PACKET_FLAG_RELIABLE); enet_host_broadcast(MirrorHost, 1, pkt); //enet_host_flush(MirrorHost); // wait for all clients to have caught up int ngood = 0; ENetEvent evt; while (enet_host_service(MirrorHost, &evt, 300000) > 0) {printf("EVENT %d CH %d\n", evt.type, evt.channelID); if (evt.type == ENET_EVENT_TYPE_RECEIVE && evt.channelID == 1) { if (evt.packet->dataLength == 1 && evt.packet->data[0] == 0x04) ngood++; } else break; if (ngood >= (NumPlayers-1)) break; } if (ngood != (NumPlayers-1)) printf("!!! BAD!! %d %d\n", ngood, NumPlayers); printf("[MIRROR HOST] clients synced\n"); // start data[0] = 0x05; pkt = enet_packet_create(&data, 1, ENET_PACKET_FLAG_RELIABLE); enet_host_broadcast(MirrorHost, 1, pkt); //enet_host_flush(MirrorHost); StartLocal(); #endif } void StartGame() { if (!IsHost) { printf("?????\n"); return; } // spawn mirror instances as needed for (int i = 1; i < NumPlayers; i++) { SpawnMirrorInstance(Players[i]); } //SyncMirrorClients(); // tell remote peers to start game u8 cmd[1] = {0x04}; ENetPacket* pkt = enet_packet_create(cmd, sizeof(cmd), ENET_PACKET_FLAG_RELIABLE); enet_host_broadcast(Host, 0, pkt); // tell other mirror instances to start the game //IPC::SendCommand(0xFFFF, IPC::Cmd_Start, 0, nullptr); // TO START MIRROR CLIENT SHITO // // 1. NDS::Reset() // 2. load ROM // 3. load state // start game locally //StartLocal(); } void StartLocal() { for (int i = 0; i < 4; i++) { InputFrame frame; frame.FrameNum = i; frame.KeyMask = 0xFFF; frame.Touching = 0; frame.TouchX = 0; frame.TouchY = 0; InputQueue.push(frame); } //NDS::Start(); //emuThread->emuRun(); } void ProcessHost() { if (!Host) return; ENetEvent event; while (enet_host_service(Host, &event, 0) > 0) { switch (event.type) { case ENET_EVENT_TYPE_CONNECT: { // client connected; assign player number int id; for (id = 0; id < 16; id++) { if (id >= NumPlayers) break; if (Players[id].Status == 0) break; } if (id < 16) { u8 cmd[2]; cmd[0] = 0x01; cmd[1] = (u8)id; ENetPacket* pkt = enet_packet_create(cmd, 2, ENET_PACKET_FLAG_RELIABLE); enet_peer_send(event.peer, 0, pkt); Players[id].ID = id; Players[id].Status = 3; Players[id].Address = event.peer->address.host; event.peer->data = &Players[id]; NumPlayers++; } } break; case ENET_EVENT_TYPE_DISCONNECT: { // TODO printf("disco\n"); } break; case ENET_EVENT_TYPE_RECEIVE: { if (event.packet->dataLength < 1) break; u8* data = (u8*)event.packet->data; switch (data[0]) { case 0x02: // client sending player info { if (event.packet->dataLength != (1+sizeof(Player))) break; Player player; memcpy(&player, &data[1], sizeof(Player)); player.Name[31] = '\0'; Player* hostside = (Player*)event.peer->data; if (player.ID != hostside->ID) { printf("what??? %d =/= %d\n", player.ID, hostside->ID); // TODO: disconnect break; } player.Status = 1; player.Address = event.peer->address.host; memcpy(hostside, &player, sizeof(Player)); // broadcast updated player list u8 cmd[2+sizeof(Players)]; cmd[0] = 0x03; cmd[1] = (u8)NumPlayers; memcpy(&cmd[2], Players, sizeof(Players)); ENetPacket* pkt = enet_packet_create(cmd, 2+sizeof(Players), ENET_PACKET_FLAG_RELIABLE); enet_host_broadcast(Host, 0, pkt); //netplayDlg->updatePlayerList(Players, NumPlayers); } break; } } break; } } } void ProcessClient() { if (!Host) return; ENetEvent event; while (enet_host_service(Host, &event, 0) > 0) { switch (event.type) { case ENET_EVENT_TYPE_CONNECT: printf("schmo\n"); break; case ENET_EVENT_TYPE_DISCONNECT: { // TODO printf("shma\n"); } break; case ENET_EVENT_TYPE_RECEIVE: { if (event.packet->dataLength < 1) break; u8* data = (u8*)event.packet->data; switch (data[0]) { case 0x01: // host sending player ID { if (event.packet->dataLength != 2) break; NumMirrorClients = 0; // create mirror host ENetAddress mirroraddr; mirroraddr.host = ENET_HOST_ANY; mirroraddr.port = 8064+1 + data[1]; // FIXME!!!! printf("client mirror host connecting to %08X:%d\n", mirroraddr.host, mirroraddr.port); MirrorHost = enet_host_create(&mirroraddr, 16, 2, 0, 0); if (!MirrorHost) { printf("mirror host shat itself :(\n"); break; } // send player information MyPlayer.ID = data[1]; u8 cmd[1+sizeof(Player)]; cmd[0] = 0x02; memcpy(&cmd[1], &MyPlayer, sizeof(Player)); ENetPacket* pkt = enet_packet_create(cmd, 1+sizeof(Player), ENET_PACKET_FLAG_RELIABLE); enet_peer_send(event.peer, 0, pkt); } break; case 0x03: // host sending player list { if (event.packet->dataLength != (2+sizeof(Players))) break; if (data[1] > 16) break; NumPlayers = data[1]; memcpy(Players, &data[2], sizeof(Players)); for (int i = 0; i < 16; i++) { Players[i].Name[31] = '\0'; } //netplayDlg->updatePlayerList(Players, NumPlayers); } break; case 0x04: // start game { // spawn mirror instances as needed for (int i = 0; i < NumPlayers; i++) { if (i != MyPlayer.ID) SpawnMirrorInstance(Players[i]); } //SyncMirrorClients(); printf("bourf\n"); // tell other mirror instances to start the game //IPC::SendCommand(0xFFFF, IPC::Cmd_Start, 0, nullptr); printf("birf\n"); // start game locally //StartLocal(); } break; } } break; } } } void ProcessMirrorHost() { if (!MirrorHost) return; #if 0 bool block = false; ENetEvent event; while (enet_host_service(MirrorHost, &event, block ? 5000 : 0) > 0) { switch (event.type) { case ENET_EVENT_TYPE_CONNECT: printf("[MIRROR HOST] mirror client connected\n"); NumMirrorClients++; event.peer->data = (void*)0; if (NumMirrorClients >= NumPlayers) { printf("??????\n"); } else if (NumMirrorClients == (NumPlayers-1)) { // all mirror clients are connected, we're ready to go SyncMirrorClients(); //StartLocal(); } break; case ENET_EVENT_TYPE_DISCONNECT: { // TODO printf("[MIRROR HOST] mirror client disconnected\n"); NumMirrorClients--; } break; case ENET_EVENT_TYPE_RECEIVE: if (event.channelID == 0) { if (event.packet->dataLength != 4) break; /*u8* data = (u8*)event.packet->data; if (data[0]) { event.peer->data = (void*)1; block = true; } else { event.peer->data = (void*)0; block = false; for (int i = 0; i < MirrorHost->peerCount; i++) { ENetPeer* peer = &(MirrorHost->peers[i]); if (peer->state != ENET_PEER_STATE_CONNECTED) continue; if (peer->data != (void*)0) { block = true; break; } } }*/ s32 clientframes = *(s32*)event.packet->data; //printf("[SYNC] HOST=%d CLIENT=%d\n", NDS::NumFrames, clientframes); if (clientframes < (((s32)NDS::NumFrames) - 16)) { event.peer->data = (void*)1; block = true; } else { event.peer->data = (void*)0; block = false; for (int i = 0; i < MirrorHost->peerCount; i++) { ENetPeer* peer = &(MirrorHost->peers[i]); if (peer->state != ENET_PEER_STATE_CONNECTED) continue; if (peer->data != (void*)0) { block = true; break; } } } } break; } } #endif } void ProcessMirrorClient() { if (!MirrorHost) return; #if 0 bool block = false; if (emuThread->emuIsRunning())// && NDS::NumFrames > 4) { if (InputQueue.empty()) block = true; } ENetEvent event; while (enet_host_service(MirrorHost, &event, block ? 5000 : 0) > 0) { switch (event.type) { case ENET_EVENT_TYPE_CONNECT: printf("schmu\n"); Lag = false; break; case ENET_EVENT_TYPE_DISCONNECT: { // TODO printf("shmz\n"); } break; case ENET_EVENT_TYPE_RECEIVE://printf("RX %d %d\n", event.channelID, event.packet->dataLength); if (event.channelID == 0) { if (event.packet->dataLength != sizeof(InputFrame)) break; u8* data = (u8*)event.packet->data; InputFrame frame; memcpy(&frame, data, sizeof(InputFrame)); InputQueue.push(frame); /*bool lag = (InputQueue.size() > 4*2); if (lag != Lag) { // let the mirror host know they are running too fast for us printf("mirror client lag notify: %d\n", lag); u8 data = lag ? 1 : 0; ENetPacket* pkt = enet_packet_create(&data, 1, ENET_PACKET_FLAG_RELIABLE); enet_peer_send(event.peer, 0, pkt); Lag = lag; }*/ { ENetPacket* pkt = enet_packet_create(&NDS::NumFrames, 4, ENET_PACKET_FLAG_RELIABLE); enet_peer_send(event.peer, 0, pkt); //enet_host_flush(MirrorHost); } } else if (event.channelID == 1) { RecvBlobFromMirrorHost(event.peer, event.packet); } break; } if (block) break; } #endif } void ProcessFrame() { if (IsMirror) { ProcessMirrorClient(); } else { if (IsHost) { ProcessHost(); } else { ProcessClient(); } ProcessMirrorHost(); } } void ProcessInput() { // netplay input processing // // N = current frame # // L = amount of lag frames // // host side: // we take the current input (normally meant for frame N) // and delay it to frame N+L // // client side: // we receive input from the host // apply each input to the frame it's assigned to // before running a frame, we need to wait to have received input for it // TODO: alert host if we are running too far behind #if 0 if (!IsMirror) { u32 lag = 4; // TODO: make configurable!! InputFrame frame; frame.FrameNum = NDS::NumFrames + lag; frame.KeyMask = Input::InputMask; frame.Touching = Input::Touching ? 1:0; frame.TouchX = Input::TouchX; frame.TouchY = Input::TouchY; // TODO: other shit! (some hotkeys for example?) InputQueue.push(frame); u8 cmd[sizeof(InputFrame)]; memcpy(cmd, &frame, sizeof(InputFrame)); ENetPacket* pkt = enet_packet_create(cmd, sizeof(cmd), ENET_PACKET_FLAG_RELIABLE); enet_host_broadcast(MirrorHost, 0, pkt); //enet_host_flush(MirrorHost); } if (InputQueue.empty()) { //if (NDS::NumFrames > 4) printf("Netplay: BAD! INPUT QUEUE EMPTY\n"); return; } InputFrame& frame = InputQueue.front(); if (frame.FrameNum < NDS::NumFrames) { // TODO: this situation is a desync printf("Netplay: BAD! LAGGING BEHIND\n"); while (frame.FrameNum < NDS::NumFrames) { if (InputQueue.size() < 2) break; InputQueue.pop(); frame = InputQueue.front(); } } if (frame.FrameNum > NDS::NumFrames) { // frame in the future, ignore return; } // apply this input frame if (frame.KeyMask != 0xFFF) printf("[%08d] INPUT=%08X (%08d) (backlog=%d)\n", NDS::NumFrames, frame.KeyMask, frame.FrameNum, InputQueue.size()); NDS::SetKeyMask(frame.KeyMask); if (frame.Touching) NDS::TouchScreen(frame.TouchX, frame.TouchY); else NDS::ReleaseScreen(); InputQueue.pop(); #endif } }