fix up LocalMP

This commit is contained in:
Arisotura 2024-06-05 00:10:12 +02:00
parent 153f01ec54
commit 15bb45d069
5 changed files with 148 additions and 333 deletions

View File

@ -38,6 +38,7 @@
#include "EmuInstance.h"
#include "Config.h"
#include "Platform.h"
#include "LocalMP.h"
#include "NDS.h"
#include "DSi.h"
@ -99,6 +100,8 @@ EmuInstance::~EmuInstance()
{
// TODO window cleanup and shit?
LocalMP::End(instanceID);
emuThread->emuExit();
emuThread->wait();
delete emuThread;

View File

@ -33,6 +33,8 @@
#include <string>
#include <QSharedMemory>
#include <QMutex>
#include <QSemaphore>
#include "Config.h"
#include "LocalMP.h"
@ -47,17 +49,12 @@ using Platform::LogLevel;
namespace LocalMP
{
u32 MPUniqueID;
u8 PacketBuffer[2048];
struct MPQueueHeader
struct MPStatusData
{
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 MPHostinst; // instance ID from which the last CMD frame was sent
u16 MPReplyBitmask; // bitmask of which clients replied in time
};
@ -70,23 +67,15 @@ struct MPPacketHeader
u64 Timestamp;
};
struct MPSync
{
u32 Magic;
u32 SenderID;
u16 ClientMask;
u16 Type;
u64 Timestamp;
};
QSharedMemory* MPQueue;
int InstanceID;
u32 PacketReadOffset;
u32 ReplyReadOffset;
QMutex MPQueueLock;
MPStatusData MPStatus;
u8* MPQueue = nullptr;
u32 PacketReadOffset[16];
u32 ReplyReadOffset[16];
const u32 kQueueSize = 0x20000;
const u32 kMaxFrameSize = 0x800;
const u32 kPacketStart = sizeof(MPQueueHeader);
const u32 kPacketStart = 0;
const u32 kReplyStart = kQueueSize / 2;
const u32 kPacketEnd = kReplyStart;
const u32 kReplyEnd = kQueueSize;
@ -96,204 +85,57 @@ 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];
QSemaphore SemPool[32];
void SemPoolInit()
{
for (int i = 0; i < 32; i++)
{
SemPool[i] = INVALID_HANDLE_VALUE;
SemInited[i] = false;
SemPool[i].acquire(SemPool[i].available());
}
}
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 = CreateSemaphoreA(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;
SemPool[num].release(1);
return true;
}
bool SemWait(int num, int timeout)
{
if (!timeout)
return sem_trywait(SemPool[num]) == 0;
return SemPool[num].tryAcquire(1);
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;
return SemPool[num].tryAcquire(1, timeout);
}
void SemReset(int num)
{
while (sem_trywait(SemPool[num]) == 0);
SemPool[num].acquire(SemPool[num].available());
}
#endif
bool Init()
{
MPQueue = new QSharedMemory("melonNIFI");
MPQueueLock.lock();
if (!MPQueue->attach())
{
Log(LogLevel::Info, "MP sharedmem doesn't exist. creating\n");
if (!MPQueue->create(kQueueSize))
{
Log(LogLevel::Error, "MP sharedmem create failed :( (%d)\n", MPQueue->error());
delete MPQueue;
MPQueue = nullptr;
return false;
}
MPQueue = new u8[kQueueSize];
memset(MPQueue, 0, kQueueSize);
memset(&MPStatus, 0, sizeof(MPStatus));
memset(PacketReadOffset, 0, sizeof(PacketReadOffset));
memset(ReplyReadOffset, 0, sizeof(ReplyReadOffset));
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<<i)))
{
InstanceID = i;
header->InstanceBitmask |= (1<<i);
//header->ConnectedBitmask |= (1 << i);
break;
}
}
header->NumInstances++;
PacketReadOffset = header->PacketWriteOffset;
ReplyReadOffset = header->ReplyWriteOffset;
MPQueue->unlock();
MPQueueLock.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;
Log(LogLevel::Info, "MP comm init OK, instance ID %d\n", InstanceID);
Log(LogLevel::Info, "MP comm init OK\n");
RecvTimeout = 25;
@ -302,23 +144,6 @@ bool Init()
void DeInit()
{
if (MPQueue)
{
MPQueue->lock();
if (MPQueue->data() != nullptr)
{
MPQueueHeader *header = (MPQueueHeader *) MPQueue->data();
header->ConnectedBitmask &= ~(1 << InstanceID);
header->InstanceBitmask &= ~(1 << InstanceID);
header->NumInstances--;
}
MPQueue->unlock();
SemPoolDeinit();
MPQueue->detach();
}
delete MPQueue;
MPQueue = nullptr;
}
@ -328,44 +153,42 @@ void SetRecvTimeout(int timeout)
RecvTimeout = timeout;
}
void Begin()
void Begin(int inst)
{
if (!MPQueue) return;
MPQueue->lock();
MPQueueHeader* header = (MPQueueHeader*)MPQueue->data();
PacketReadOffset = header->PacketWriteOffset;
ReplyReadOffset = header->ReplyWriteOffset;
SemReset(InstanceID);
SemReset(16+InstanceID);
header->ConnectedBitmask |= (1 << InstanceID);
MPQueue->unlock();
MPQueueLock.lock();
PacketReadOffset[inst] = MPStatus.PacketWriteOffset;
ReplyReadOffset[inst] = MPStatus.ReplyWriteOffset;
SemReset(inst);
SemReset(16+inst);
MPStatus.ConnectedBitmask |= (1 << inst);
MPQueueLock.unlock();
}
void End()
void End(int inst)
{
if (!MPQueue) return;
MPQueue->lock();
MPQueueHeader* header = (MPQueueHeader*)MPQueue->data();
//SemReset(InstanceID);
//SemReset(16+InstanceID);
header->ConnectedBitmask &= ~(1 << InstanceID);
MPQueue->unlock();
MPQueueLock.lock();
MPStatus.ConnectedBitmask &= ~(1 << inst);
MPQueueLock.unlock();
}
void FIFORead(int fifo, void* buf, int len)
void FIFORead(int inst, int fifo, void* buf, int len)
{
u8* data = (u8*)MPQueue->data();
u8* data = MPQueue;
u32 offset, start, end;
if (fifo == 0)
{
offset = PacketReadOffset;
offset = PacketReadOffset[inst];
start = kPacketStart;
end = kPacketEnd;
}
else
{
offset = ReplyReadOffset;
offset = ReplyReadOffset[inst];
start = kReplyStart;
end = kReplyEnd;
}
@ -383,25 +206,24 @@ void FIFORead(int fifo, void* buf, int len)
offset += len;
}
if (fifo == 0) PacketReadOffset = offset;
else ReplyReadOffset = offset;
if (fifo == 0) PacketReadOffset[inst] = offset;
else ReplyReadOffset[inst] = offset;
}
void FIFOWrite(int fifo, void* buf, int len)
void FIFOWrite(int inst, int fifo, void* buf, int len)
{
u8* data = (u8*)MPQueue->data();
MPQueueHeader* header = (MPQueueHeader*)&data[0];
u8* data = MPQueue;
u32 offset, start, end;
if (fifo == 0)
{
offset = header->PacketWriteOffset;
offset = MPStatus.PacketWriteOffset;
start = kPacketStart;
end = kPacketEnd;
}
else
{
offset = header->ReplyWriteOffset;
offset = MPStatus.ReplyWriteOffset;
start = kReplyStart;
end = kReplyEnd;
}
@ -419,53 +241,51 @@ void FIFOWrite(int fifo, void* buf, int len)
offset += len;
}
if (fifo == 0) header->PacketWriteOffset = offset;
else header->ReplyWriteOffset = offset;
if (fifo == 0) MPStatus.PacketWriteOffset = offset;
else MPStatus.ReplyWriteOffset = offset;
}
int SendPacketGeneric(u32 type, u8* packet, int len, u64 timestamp)
int SendPacketGeneric(int inst, u32 type, u8* packet, int len, u64 timestamp)
{
if (!MPQueue) return 0;
MPQueue->lock();
u8* data = (u8*)MPQueue->data();
MPQueueHeader* header = (MPQueueHeader*)&data[0];
MPQueueLock.lock();
u16 mask = header->ConnectedBitmask;
u16 mask = MPStatus.ConnectedBitmask;
// TODO: check if the FIFO is full!
MPPacketHeader pktheader;
pktheader.Magic = 0x4946494E;
pktheader.SenderID = InstanceID;
pktheader.SenderID = inst;
pktheader.Type = type;
pktheader.Length = len;
pktheader.Timestamp = timestamp;
type &= 0xFFFF;
int nfifo = (type == 2) ? 1 : 0;
FIFOWrite(nfifo, &pktheader, sizeof(pktheader));
FIFOWrite(inst, nfifo, &pktheader, sizeof(pktheader));
if (len)
FIFOWrite(nfifo, packet, len);
FIFOWrite(inst, 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);
MPStatus.MPHostinst = inst;
MPStatus.MPReplyBitmask = 0;
ReplyReadOffset[inst] = MPStatus.ReplyWriteOffset;
SemReset(16 + inst);
}
else if (type == 2)
{
header->MPReplyBitmask |= (1 << InstanceID);
MPStatus.MPReplyBitmask |= (1 << inst);
}
MPQueue->unlock();
MPQueueLock.unlock();
if (type == 2)
{
SemPost(16 + header->MPHostInstanceID);
SemPost(16 + MPStatus.MPHostinst);
}
else
{
@ -479,84 +299,82 @@ int SendPacketGeneric(u32 type, u8* packet, int len, u64 timestamp)
return len;
}
int RecvPacketGeneric(u8* packet, bool block, u64* timestamp)
int RecvPacketGeneric(int inst, u8* packet, bool block, u64* timestamp)
{
if (!MPQueue) return 0;
for (;;)
{
if (!SemWait(InstanceID, block ? RecvTimeout : 0))
if (!SemWait(inst, block ? RecvTimeout : 0))
{
return 0;
}
MPQueue->lock();
u8* data = (u8*)MPQueue->data();
MPQueueHeader* header = (MPQueueHeader*)&data[0];
MPQueueLock.lock();
MPPacketHeader pktheader;
FIFORead(0, &pktheader, sizeof(pktheader));
FIFORead(inst, 0, &pktheader, sizeof(pktheader));
if (pktheader.Magic != 0x4946494E)
{
Log(LogLevel::Warn, "PACKET FIFO OVERFLOW\n");
PacketReadOffset = header->PacketWriteOffset;
SemReset(InstanceID);
MPQueue->unlock();
PacketReadOffset[inst] = MPStatus.PacketWriteOffset;
SemReset(inst);
MPQueueLock.unlock();
return 0;
}
if (pktheader.SenderID == InstanceID)
if (pktheader.SenderID == inst)
{
// skip this packet
PacketReadOffset += pktheader.Length;
if (PacketReadOffset >= kPacketEnd)
PacketReadOffset += kPacketStart - kPacketEnd;
PacketReadOffset[inst] += pktheader.Length;
if (PacketReadOffset[inst] >= kPacketEnd)
PacketReadOffset[inst] += kPacketStart - kPacketEnd;
MPQueue->unlock();
MPQueueLock.unlock();
continue;
}
if (pktheader.Length)
{
FIFORead(0, packet, pktheader.Length);
FIFORead(inst, 0, packet, pktheader.Length);
if (pktheader.Type == 1)
LastHostID = pktheader.SenderID;
}
if (timestamp) *timestamp = pktheader.Timestamp;
MPQueue->unlock();
MPQueueLock.unlock();
return pktheader.Length;
}
}
int SendPacket(u8* packet, int len, u64 timestamp)
int SendPacket(int inst, u8* packet, int len, u64 timestamp)
{
return SendPacketGeneric(0, packet, len, timestamp);
return SendPacketGeneric(inst, 0, packet, len, timestamp);
}
int RecvPacket(u8* packet, u64* timestamp)
int RecvPacket(int inst, u8* packet, u64* timestamp)
{
return RecvPacketGeneric(packet, false, timestamp);
return RecvPacketGeneric(inst, packet, false, timestamp);
}
int SendCmd(u8* packet, int len, u64 timestamp)
int SendCmd(int inst, u8* packet, int len, u64 timestamp)
{
return SendPacketGeneric(1, packet, len, timestamp);
return SendPacketGeneric(inst, 1, packet, len, timestamp);
}
int SendReply(u8* packet, int len, u64 timestamp, u16 aid)
int SendReply(int inst, u8* packet, int len, u64 timestamp, u16 aid)
{
return SendPacketGeneric(2 | (aid<<16), packet, len, timestamp);
return SendPacketGeneric(inst, 2 | (aid<<16), packet, len, timestamp);
}
int SendAck(u8* packet, int len, u64 timestamp)
int SendAck(int inst, u8* packet, int len, u64 timestamp)
{
return SendPacketGeneric(3, packet, len, timestamp);
return SendPacketGeneric(inst, 3, packet, len, timestamp);
}
int RecvHostPacket(u8* packet, u64* timestamp)
int RecvHostPacket(int inst, u8* packet, u64* timestamp)
{
if (!MPQueue) return -1;
@ -564,33 +382,30 @@ int RecvHostPacket(u8* packet, u64* timestamp)
{
// 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();
MPQueueLock.lock();
u8* data = MPQueue;
u16 curinstmask = MPStatus.ConnectedBitmask;
MPQueueLock.unlock();
if (!(curinstmask & (1 << LastHostID)))
return -1;
}
return RecvPacketGeneric(packet, true, timestamp);
return RecvPacketGeneric(inst, packet, true, timestamp);
}
u16 RecvReplies(u8* packets, u64 timestamp, u16 aidmask)
u16 RecvReplies(int inst, u8* packets, u64 timestamp, u16 aidmask)
{
if (!MPQueue) return 0;
u16 ret = 0;
u16 myinstmask = (1 << InstanceID);
u16 myinstmask = (1 << inst);
u16 curinstmask;
{
MPQueue->lock();
u8* data = (u8*)MPQueue->data();
MPQueueHeader* header = (MPQueueHeader*)&data[0];
curinstmask = header->ConnectedBitmask;
MPQueue->unlock();
//MPQueueLock.lock();
curinstmask = MPStatus.ConnectedBitmask;
//MPQueueLock.unlock();
}
// if all clients have left: return early
@ -599,44 +414,42 @@ u16 RecvReplies(u8* packets, u64 timestamp, u16 aidmask)
for (;;)
{
if (!SemWait(16+InstanceID, RecvTimeout))
if (!SemWait(16+inst, RecvTimeout))
{
// no more replies available
return ret;
}
MPQueue->lock();
u8* data = (u8*)MPQueue->data();
MPQueueHeader* header = (MPQueueHeader*)&data[0];
MPQueueLock.lock();
MPPacketHeader pktheader;
FIFORead(1, &pktheader, sizeof(pktheader));
FIFORead(inst, 1, &pktheader, sizeof(pktheader));
if (pktheader.Magic != 0x4946494E)
{
Log(LogLevel::Warn, "REPLY FIFO OVERFLOW\n");
ReplyReadOffset = header->ReplyWriteOffset;
SemReset(16+InstanceID);
MPQueue->unlock();
ReplyReadOffset[inst] = MPStatus.ReplyWriteOffset;
SemReset(16+inst);
MPQueueLock.unlock();
return 0;
}
if ((pktheader.SenderID == InstanceID) || // packet we sent out (shouldn't happen, but hey)
if ((pktheader.SenderID == inst) || // 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;
ReplyReadOffset[inst] += pktheader.Length;
if (ReplyReadOffset[inst] >= kReplyEnd)
ReplyReadOffset[inst] += kReplyStart - kReplyEnd;
MPQueue->unlock();
MPQueueLock.unlock();
continue;
}
if (pktheader.Length)
{
u32 aid = (pktheader.Type >> 16);
FIFORead(1, &packets[(aid-1)*1024], pktheader.Length);
FIFORead(inst, 1, &packets[(aid-1)*1024], pktheader.Length);
ret |= (1 << aid);
}
@ -646,11 +459,11 @@ u16 RecvReplies(u8* packets, u64 timestamp, u16 aidmask)
{
// all the clients have sent their reply
MPQueue->unlock();
MPQueueLock.unlock();
return ret;
}
MPQueue->unlock();
MPQueueLock.unlock();
}
}

View File

@ -30,16 +30,16 @@ void DeInit();
void SetRecvTimeout(int timeout);
void Begin();
void End();
void Begin(int inst);
void End(int inst);
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);
int SendPacket(int inst, u8* data, int len, u64 timestamp);
int RecvPacket(int inst, u8* data, u64* timestamp);
int SendCmd(int inst, u8* data, int len, u64 timestamp);
int SendReply(int inst, u8* data, int len, u64 timestamp, u16 aid);
int SendAck(int inst, u8* data, int len, u64 timestamp);
int RecvHostPacket(int inst, u8* data, u64* timestamp);
u16 RecvReplies(int inst, u8* data, u64 timestamp, u16 aidmask);
}

View File

@ -50,12 +50,6 @@
extern CameraManager* camManager[2];
void emuStop();
// TEMP
//#include "main.h"
//extern MainWindow* mainWindow;
namespace melonDS::Platform
{
@ -454,65 +448,65 @@ void WriteDateTime(int year, int month, int day, int hour, int minute, int secon
bool MP_Init(void* userdata)
{
//return LocalMP::Init();
return true;
}
void MP_DeInit(void* userdata)
{
//return LocalMP::DeInit();
}
void MP_Begin(void* userdata)
{
//return LocalMP::Begin();
int inst = ((EmuInstance*)userdata)->getInstanceID();
LocalMP::Begin(inst);
}
void MP_End(void* userdata)
{
//return LocalMP::End();
int inst = ((EmuInstance*)userdata)->getInstanceID();
LocalMP::End(inst);
}
int MP_SendPacket(u8* data, int len, u64 timestamp, void* userdata)
{
//return LocalMP::SendPacket(data, len, timestamp);
return 0;
int inst = ((EmuInstance*)userdata)->getInstanceID();
return LocalMP::SendPacket(inst, data, len, timestamp);
}
int MP_RecvPacket(u8* data, u64* timestamp, void* userdata)
{
//return LocalMP::RecvPacket(data, timestamp);
return 0;
int inst = ((EmuInstance*)userdata)->getInstanceID();
return LocalMP::RecvPacket(inst, data, timestamp);
}
int MP_SendCmd(u8* data, int len, u64 timestamp, void* userdata)
{
//return LocalMP::SendCmd(data, len, timestamp);
return 0;
int inst = ((EmuInstance*)userdata)->getInstanceID();
return LocalMP::SendCmd(inst, data, len, timestamp);
}
int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid, void* userdata)
{
//return LocalMP::SendReply(data, len, timestamp, aid);
return 0;
int inst = ((EmuInstance*)userdata)->getInstanceID();
return LocalMP::SendReply(inst, data, len, timestamp, aid);
}
int MP_SendAck(u8* data, int len, u64 timestamp, void* userdata)
{
//return LocalMP::SendAck(data, len, timestamp);
return 0;
int inst = ((EmuInstance*)userdata)->getInstanceID();
return LocalMP::SendAck(inst, data, len, timestamp);
}
int MP_RecvHostPacket(u8* data, u64* timestamp, void* userdata)
{
//return LocalMP::RecvHostPacket(data, timestamp);
return 0;
int inst = ((EmuInstance*)userdata)->getInstanceID();
return LocalMP::RecvHostPacket(inst, data, timestamp);
}
u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask, void* userdata)
{
//return LocalMP::RecvReplies(data, timestamp, aidmask);
return 0;
int inst = ((EmuInstance*)userdata)->getInstanceID();
return LocalMP::RecvReplies(inst, data, timestamp, aidmask);
}
bool LAN_Init(void* userdata)

View File

@ -74,6 +74,7 @@
#include "EmuInstance.h"
#include "ArchiveUtil.h"
#include "CameraManager.h"
#include "LocalMP.h"
#include "CLI.h"
@ -266,6 +267,8 @@ int main(int argc, char** argv)
}
}
LocalMP::Init();
/* mainWindow = new MainWindow();
if (options->fullscreen)
ToggleFullscreen(mainWindow);
@ -318,6 +321,8 @@ int main(int argc, char** argv)
// but with this we make extra sure they are all deleted
deleteAllEmuInstances();
LocalMP::DeInit();
//AudioInOut::DeInit();
delete camManager[0];
delete camManager[1];