mirror of
https://github.com/melonDS-emu/melonDS.git
synced 2024-11-14 21:37:42 -07:00
185 lines
4.6 KiB
C++
185 lines
4.6 KiB
C++
/*
|
|
Copyright 2016-2021 Arisotura
|
|
|
|
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 <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <time.h>
|
|
#include <atomic>
|
|
#include "NDSCart_SRAMManager.h"
|
|
#include "Platform.h"
|
|
|
|
namespace NDSCart_SRAMManager
|
|
{
|
|
|
|
Platform::Thread* FlushThread;
|
|
std::atomic_bool FlushThreadRunning;
|
|
Platform::Mutex* SecondaryBufferLock;
|
|
|
|
char Path[1024];
|
|
|
|
u8* Buffer;
|
|
u32 Length;
|
|
|
|
u8* SecondaryBuffer;
|
|
u32 SecondaryBufferLength;
|
|
|
|
time_t TimeAtLastFlushRequest;
|
|
|
|
// We keep versions in case the user closes the application before
|
|
// a flush cycle is finished.
|
|
u32 PreviousFlushVersion;
|
|
u32 FlushVersion;
|
|
|
|
void FlushThreadFunc();
|
|
|
|
bool Init()
|
|
{
|
|
SecondaryBufferLock = Platform::Mutex_Create();
|
|
|
|
return true;
|
|
}
|
|
|
|
void DeInit()
|
|
{
|
|
if (FlushThreadRunning)
|
|
{
|
|
FlushThreadRunning = false;
|
|
Platform::Thread_Wait(FlushThread);
|
|
Platform::Thread_Free(FlushThread);
|
|
FlushSecondaryBuffer();
|
|
}
|
|
|
|
if (SecondaryBuffer) delete[] SecondaryBuffer;
|
|
SecondaryBuffer = NULL;
|
|
|
|
Platform::Mutex_Free(SecondaryBufferLock);
|
|
}
|
|
|
|
void Setup(const char* path, u8* buffer, u32 length)
|
|
{
|
|
// Flush SRAM in case there is unflushed data from previous state.
|
|
FlushSecondaryBuffer();
|
|
|
|
Platform::Mutex_Lock(SecondaryBufferLock);
|
|
|
|
strncpy(Path, path, 1023);
|
|
Path[1023] = '\0';
|
|
|
|
Buffer = buffer;
|
|
Length = length;
|
|
|
|
if(SecondaryBuffer) delete[] SecondaryBuffer; // Delete secondary buffer, there might be previous state.
|
|
|
|
SecondaryBuffer = new u8[length];
|
|
SecondaryBufferLength = length;
|
|
|
|
FlushVersion = 0;
|
|
PreviousFlushVersion = 0;
|
|
TimeAtLastFlushRequest = 0;
|
|
|
|
Platform::Mutex_Unlock(SecondaryBufferLock);
|
|
|
|
if (path[0] != '\0' && !FlushThreadRunning)
|
|
{
|
|
FlushThread = Platform::Thread_Create(FlushThreadFunc);
|
|
FlushThreadRunning = true;
|
|
}
|
|
else if (path[0] == '\0' && FlushThreadRunning)
|
|
{
|
|
FlushThreadRunning = false;
|
|
Platform::Thread_Wait(FlushThread);
|
|
Platform::Thread_Free(FlushThread);
|
|
}
|
|
}
|
|
|
|
void RequestFlush()
|
|
{
|
|
Platform::Mutex_Lock(SecondaryBufferLock);
|
|
printf("NDS SRAM: Flush requested\n");
|
|
memcpy(SecondaryBuffer, Buffer, Length);
|
|
FlushVersion++;
|
|
TimeAtLastFlushRequest = time(NULL);
|
|
Platform::Mutex_Unlock(SecondaryBufferLock);
|
|
}
|
|
|
|
void FlushThreadFunc()
|
|
{
|
|
for (;;)
|
|
{
|
|
Platform::Sleep(100 * 1000); // 100ms
|
|
|
|
if (!FlushThreadRunning) return;
|
|
|
|
// We debounce for two seconds after last flush request to ensure that writing has finished.
|
|
if (TimeAtLastFlushRequest == 0 || difftime(time(NULL), TimeAtLastFlushRequest) < 2)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FlushSecondaryBuffer();
|
|
}
|
|
}
|
|
|
|
void FlushSecondaryBuffer(u8* dst, s32 dstLength)
|
|
{
|
|
// When flushing to a file, there's no point in re-writing the exact same data.
|
|
if (!dst && !NeedsFlush()) return;
|
|
// When flushing to memory, we don't know if dst already has any data so we only check that we CAN flush.
|
|
if (dst && dstLength < SecondaryBufferLength) return;
|
|
|
|
Platform::Mutex_Lock(SecondaryBufferLock);
|
|
if (dst)
|
|
{
|
|
memcpy(dst, SecondaryBuffer, SecondaryBufferLength);
|
|
}
|
|
else
|
|
{
|
|
FILE* f = Platform::OpenFile(Path, "wb");
|
|
if (f)
|
|
{
|
|
printf("NDS SRAM: Written\n");
|
|
fwrite(SecondaryBuffer, SecondaryBufferLength, 1, f);
|
|
fclose(f);
|
|
}
|
|
}
|
|
PreviousFlushVersion = FlushVersion;
|
|
TimeAtLastFlushRequest = 0;
|
|
Platform::Mutex_Unlock(SecondaryBufferLock);
|
|
}
|
|
|
|
bool NeedsFlush()
|
|
{
|
|
return FlushVersion != PreviousFlushVersion;
|
|
}
|
|
|
|
void UpdateBuffer(u8* src, s32 srcLength)
|
|
{
|
|
if (!src || srcLength != Length) return;
|
|
|
|
// should we create a lock for the primary buffer? this method is not intended to be called from a secondary thread in the way Flush is
|
|
memcpy(Buffer, src, srcLength);
|
|
Platform::Mutex_Lock(SecondaryBufferLock);
|
|
memcpy(SecondaryBuffer, src, srcLength);
|
|
Platform::Mutex_Unlock(SecondaryBufferLock);
|
|
|
|
PreviousFlushVersion = FlushVersion;
|
|
}
|
|
|
|
}
|