Files
melonDS/src/frontend/qt_sdl/SaveManager.cpp
Jesse Talavera-Greenberg 5bfe51e670 Refactor the core's handling of firmware and BIOS images to rely less on the file system (#1826)
* Introduce firmware-related structs

* Fix some indents

* Move the generated firmware identifier to a constant

* Document the WifiAccessPoint constructors

* Add some constants

* Remove a stray comment

* Implement Firmware::UserData

* Add Firmware::Mask

* Document Firmware::Buffer

* Add a Firmware constructor that uses a FileHandle

* Set the default username in UserData

* Update the UserData checksum

* Forgot to include Platform.h

* Remove some redundant assignments in the default Firmware constructor

* const-ify CRC16

* Replace the plain Firmware buffer with a Firmware object

- Remove some functions that were reimplemented in the Firmware constructors

* Fix some crashes due to undefined behavior

* Fix the user data initialization

- Initialize both user data objects to default
- Set both user data objects to the same touch screen calibration

* Follow the DS logic in determining which user data section is current

* Remove an unneeded include

* Remove another unneeded include

* Initialize FirmwareMask in Firmware::Firmware

* Use the DEFAULT_SSID constant

* Add SPI_Firmware::InstallFirmware and SPI_Firmware::RemoveFirmware

* Move a logging call to after the file is written

* Add a SaveManager for the firmware

* Touch up the SPI_Firmware::Firmware declaration

* Move all firmware loading and customization to the frontend

* Call Platform::WriteFirmware when it's time to write the firmware back to disk

* Fix some missing stuff

* Remove the Get* functions from SPI_Firmware in favor of GetFirmware()

* Implement SPI_Firmware::DeInit in terms of RemoveFirmware

* Add Firmware::UpdateChecksums

* Fix an incorrect length

* Update all checksums in the firmware after setting touch screen calibration data

* Use the Firmware object's Position methods

* Remove register fields from the Firmware object

* Install the firmware before seeing if direct boot is necessary

* Install the firmware before calling NDS::Reset in LoadROM

* Slight cleanup in ROMManager

* Fix the default access point name

* Shorten the various getters in Firmware

* Add qualifiers for certain uses of firmware types

- GCC can get picky if -fpermissive isn't defined

* Add an InstallFirmware overload that takes a unique_ptr

* Log when firmware is added or removed

* Don't clear the firmware in SPI_Firmware::Init

- The original code didn't, it just set the pointer to nullptr

* Fix a typo

* Write back the full firmware if it's not generated

* Move the FreeBIOS to an external file

* Load wfcsettings.bin into the correct part of the generated firmware blob

* Load BIOS files in the frontend, not in the core

* Fix logging the firmware ID

* Add some utility functions

* Mark Firmware's constructors as explicit

* Remove obsolete ConfigEntry values

* Include <locale> explicitly in ROMManager

* Fix up some includes

* Add Firmware::IsBootable()

* Add a newline to a log entry

- Whoops

* Log the number of bytes written out in SaveManager

* Mark FirmwareHeader's constructor as explicit

* Clean up GenerateDefaultFirmware and LoadFirmwareFromFile

- Now they return a pair instead of two by-ref values

* Refactor SaveManager a little bit

- Manage its buffers as unique_ptrs to mitigate leaks
- Reallocate the internal buffer if SetPath is asked to reload the file (and the new length is different)

* Remove some stray parens

* Fix some firmware-related bugs I introduced

- Firmware settings are now properly saved to disk (beforehand I misunderstood when the firmware blob was written)
- Firmware is no longer overwritten by contents of wfcsettings.bin

* Slight cleanup
2023-09-18 21:09:11 +02:00

198 lines
4.8 KiB
C++

/*
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 <stdio.h>
#include <string.h>
#include "SaveManager.h"
#include "Platform.h"
using namespace Platform;
SaveManager::SaveManager(const std::string& path) : QThread()
{
SecondaryBuffer = nullptr;
SecondaryBufferLength = 0;
SecondaryBufferLock = new QMutex();
Running = false;
Path = path;
Buffer = nullptr;
Length = 0;
FlushRequested = false;
FlushVersion = 0;
PreviousFlushVersion = 0;
TimeAtLastFlushRequest = 0;
if (!path.empty())
{
Running = true;
start();
}
}
SaveManager::~SaveManager()
{
if (Running)
{
Running = false;
wait();
FlushSecondaryBuffer();
}
SecondaryBuffer = nullptr;
delete SecondaryBufferLock;
Buffer = nullptr;
}
std::string SaveManager::GetPath()
{
return Path;
}
void SaveManager::SetPath(const std::string& path, bool reload)
{
Path = path;
if (reload)
{ // If we should load whatever file is at the new path...
if (FileHandle* f = Platform::OpenFile(Path, FileMode::Read))
{
if (u32 length = Platform::FileLength(f); length != Length)
{ // If the new file is a different size, we need to re-allocate the buffer.
Length = length;
Buffer = std::make_unique<u8[]>(Length);
}
FileRead(Buffer.get(), 1, Length, f);
CloseFile(f);
}
}
else
FlushRequested = true;
}
void SaveManager::RequestFlush(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen)
{
if (Length != savelen)
{
Length = savelen;
Buffer = std::make_unique<u8[]>(Length);
memcpy(Buffer.get(), savedata, Length);
}
else
{
if ((writeoffset+writelen) > savelen)
{
u32 len = savelen - writeoffset;
memcpy(&Buffer[writeoffset], &savedata[writeoffset], len);
len = writelen - len;
if (len > savelen) len = savelen;
memcpy(&Buffer[0], &savedata[0], len);
}
else
{
memcpy(&Buffer[writeoffset], &savedata[writeoffset], writelen);
}
}
FlushRequested = true;
}
void SaveManager::CheckFlush()
{
if (!FlushRequested) return;
SecondaryBufferLock->lock();
Log(LogLevel::Info, "SaveManager: Flush requested\n");
if (SecondaryBufferLength != Length)
{
SecondaryBufferLength = Length;
SecondaryBuffer = std::make_unique<u8[]>(SecondaryBufferLength);
}
memcpy(SecondaryBuffer.get(), Buffer.get(), Length);
FlushRequested = false;
FlushVersion++;
TimeAtLastFlushRequest = time(nullptr);
SecondaryBufferLock->unlock();
}
void SaveManager::run()
{
for (;;)
{
QThread::msleep(100);
if (!Running) return;
// We debounce for two seconds after last flush request to ensure that writing has finished.
if (TimeAtLastFlushRequest == 0 || difftime(time(nullptr), TimeAtLastFlushRequest) < 2)
{
continue;
}
FlushSecondaryBuffer();
}
}
void SaveManager::FlushSecondaryBuffer(u8* dst, u32 dstLength)
{
if (!SecondaryBuffer) return;
// 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;
SecondaryBufferLock->lock();
if (dst)
{
memcpy(dst, SecondaryBuffer.get(), SecondaryBufferLength);
}
else
{
FileHandle* f = Platform::OpenFile(Path, FileMode::Write);
if (f)
{
FileWrite(SecondaryBuffer.get(), SecondaryBufferLength, 1, f);
Log(LogLevel::Info, "SaveManager: Wrote %u bytes to %s\n", SecondaryBufferLength, Path.c_str());
CloseFile(f);
}
}
PreviousFlushVersion = FlushVersion;
TimeAtLastFlushRequest = 0;
SecondaryBufferLock->unlock();
}
bool SaveManager::NeedsFlush()
{
return FlushVersion != PreviousFlushVersion;
}