mirror of
https://github.com/jpd002/Play-.git
synced 2025-04-28 13:47:57 +03:00

Some checks failed
Build macOS / build_macos (push) Has been cancelled
Build Android / build_android (apk) (push) Has been cancelled
Build Android / build_android (libretro) (push) Has been cancelled
Build Linux ARM32 / build_linux_arm32 (push) Has been cancelled
Build Linux ARM64 / build_linux_arm64 (push) Has been cancelled
Build Windows Psf / build_windows_psf (off, x86_64, Visual Studio 16 2019, installer64.nsi, x64) (push) Has been cancelled
Build Windows Psf / build_windows_psf (on, x86_64, Visual Studio 16 2019, installer64.nsi, x64) (push) Has been cancelled
Build Windows / build_windows (x86_32, Visual Studio 16 2019, installer32.nsi, win32_msvc2019, Win32) (push) Has been cancelled
Build Windows / build_windows (x86_64, Visual Studio 16 2019, installer64.nsi, win64_msvc2019_64, x64) (push) Has been cancelled
Check Format / run_clangformat (push) Has been cancelled
Build iOS / build_ios (push) Has been cancelled
Build JavaScript / build_js (push) Has been cancelled
Build Linux / build_linux (push) Has been cancelled
1345 lines
36 KiB
C++
1345 lines
36 KiB
C++
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#include <states/XmlStateFile.h>
|
|
#include <xml/Utils.h>
|
|
#include "string_format.h"
|
|
#include "AppConfig.h"
|
|
#include "../PS2VM_Preferences.h"
|
|
#include "Log.h"
|
|
#include "Iop_McServ.h"
|
|
#include "Iop_PathUtils.h"
|
|
#include "Iop_Sysmem.h"
|
|
#include "Iop_SifCmd.h"
|
|
#include "Iop_SifManPs2.h"
|
|
#include "IopBios.h"
|
|
#include "StdStreamUtils.h"
|
|
#include "StringUtils.h"
|
|
#include "MIPSAssembler.h"
|
|
#include "FilesystemUtils.h"
|
|
|
|
using namespace Iop;
|
|
|
|
#define CLUSTER_SIZE 0x400
|
|
|
|
#define LOG_NAME ("iop_mcserv")
|
|
|
|
#define MODULE_NAME "mcserv"
|
|
#define MODULE_VERSION 0x101
|
|
|
|
#define CUSTOM_STARTREADFAST 0x666
|
|
#define CUSTOM_PROCEEDREADFAST 0x667
|
|
#define CUSTOM_FINISHREADFAST 0x668
|
|
|
|
#define SEPARATOR_CHAR '/'
|
|
|
|
#define CMD_DELAY_DEFAULT 100000
|
|
|
|
#define STATE_MEMCARDS_FILE ("iop_mcserv/memcards.xml")
|
|
#define STATE_MEMCARDS_NODE "Memorycards"
|
|
#define STATE_MEMCARDS_CARDNODE "Memorycard"
|
|
|
|
#define STATE_MEMCARDS_CARDNODE_PORTATTRIBUTE ("Port")
|
|
#define STATE_MEMCARDS_CARDNODE_KNOWNATTRIBUTE ("Known")
|
|
|
|
#define MC_FILE_ATTR_FOLDER (MC_FILE_0400 | MC_FILE_ATTR_EXISTS | MC_FILE_ATTR_SUBDIR | MC_FILE_ATTR_READABLE | MC_FILE_ATTR_WRITEABLE | MC_FILE_ATTR_EXECUTABLE)
|
|
|
|
// clang-format off
|
|
const char* CMcServ::m_mcPathPreference[MAX_PORTS] =
|
|
{
|
|
PREF_PS2_MC0_DIRECTORY,
|
|
PREF_PS2_MC1_DIRECTORY,
|
|
};
|
|
// clang-format on
|
|
|
|
CMcServ::CMcServ(CIopBios& bios, CSifMan& sifMan, CSifCmd& sifCmd, CSysmem& sysMem, uint8* ram)
|
|
: m_bios(bios)
|
|
, m_sifMan(sifMan)
|
|
, m_sifCmd(sifCmd)
|
|
, m_sysMem(sysMem)
|
|
, m_ram(ram)
|
|
{
|
|
m_moduleDataAddr = m_sysMem.AllocateMemory(sizeof(MODULEDATA), 0, 0);
|
|
sifMan.RegisterModule(MODULE_ID, this);
|
|
BuildCustomCode();
|
|
SetModuleVersion(1000);
|
|
}
|
|
|
|
void CMcServ::SetModuleVersion(unsigned int)
|
|
{
|
|
//We don't really care about the version here.
|
|
|
|
//SetModuleVersion is called when IOP is reset, make sure we also reset the state of this module.
|
|
//Calling Init doesn't reset the state of known memory cards.
|
|
//It's probably only reset when the module is reloaded.
|
|
|
|
for(bool& knownMemoryCard : m_knownMemoryCards)
|
|
{
|
|
knownMemoryCard = false;
|
|
}
|
|
}
|
|
|
|
const char* CMcServ::GetMcPathPreference(unsigned int port)
|
|
{
|
|
return m_mcPathPreference[port];
|
|
}
|
|
|
|
std::string CMcServ::EncodeMcName(const std::string& inputName)
|
|
{
|
|
std::string result;
|
|
for(size_t i = 0; i < inputName.size(); i++)
|
|
{
|
|
auto inputChar = inputName[i];
|
|
if(inputChar == 0) break;
|
|
//Encode problematic characters (for Windows)
|
|
if((inputChar == ':') || (inputChar == '\\'))
|
|
{
|
|
result += string_format("%%%02X", inputChar);
|
|
}
|
|
else
|
|
{
|
|
result += inputChar;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string CMcServ::DecodeMcName(const std::string& inputName)
|
|
{
|
|
std::string result;
|
|
for(size_t i = 0; i < inputName.size(); i++)
|
|
{
|
|
auto inputChar = inputName[i];
|
|
if(inputChar == '%')
|
|
{
|
|
int decodedChar = 0;
|
|
FRAMEWORK_MAYBE_UNUSED int scanCount = sscanf(inputName.c_str() + i, "%%%02X", &decodedChar);
|
|
assert(scanCount == 1);
|
|
result += decodedChar;
|
|
i += 2;
|
|
}
|
|
else
|
|
{
|
|
result += inputChar;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string CMcServ::MakeAbsolutePath(const std::string& inputPath)
|
|
{
|
|
auto frags = StringUtils::Split(inputPath, '/', true);
|
|
std::vector<std::string> newFrags;
|
|
for(const auto& frag : frags)
|
|
{
|
|
if(frag.empty()) continue;
|
|
if(frag == ".")
|
|
{
|
|
continue;
|
|
}
|
|
if(frag == "..")
|
|
{
|
|
if(newFrags.empty())
|
|
{
|
|
//Going up too much, don't bother
|
|
continue;
|
|
}
|
|
newFrags.pop_back();
|
|
}
|
|
else
|
|
{
|
|
newFrags.push_back(frag);
|
|
}
|
|
}
|
|
auto outputPath = std::string();
|
|
for(const auto& frag : newFrags)
|
|
{
|
|
if(frag.empty()) continue;
|
|
outputPath += "/";
|
|
outputPath += frag;
|
|
}
|
|
return outputPath;
|
|
}
|
|
|
|
std::string CMcServ::GetId() const
|
|
{
|
|
return MODULE_NAME;
|
|
}
|
|
|
|
std::string CMcServ::GetFunctionName(unsigned int) const
|
|
{
|
|
return "unknown";
|
|
}
|
|
|
|
void CMcServ::CountTicks(uint32 ticks, CSifMan* sifMan)
|
|
{
|
|
auto moduleData = reinterpret_cast<MODULEDATA*>(m_ram + m_moduleDataAddr);
|
|
if(moduleData->pendingCommand == CMD_ID_NONE) return;
|
|
|
|
moduleData->pendingCommandDelay -= std::min<uint32>(moduleData->pendingCommandDelay, ticks);
|
|
if(moduleData->pendingCommandDelay == 0)
|
|
{
|
|
sifMan->SendCallReply(MODULE_ID, nullptr);
|
|
moduleData->pendingCommand = CMD_ID_NONE;
|
|
}
|
|
}
|
|
|
|
void CMcServ::Invoke(CMIPS& context, unsigned int functionId)
|
|
{
|
|
switch(functionId)
|
|
{
|
|
case CUSTOM_STARTREADFAST:
|
|
StartReadFast(context);
|
|
break;
|
|
case CUSTOM_PROCEEDREADFAST:
|
|
ProceedReadFast(context);
|
|
break;
|
|
case CUSTOM_FINISHREADFAST:
|
|
FinishReadFast(context);
|
|
break;
|
|
default:
|
|
CLog::GetInstance().Warn(LOG_NAME, "Unknown module method invoked (%d).\r\n", functionId);
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool CMcServ::Invoke(uint32 method, uint32* args, uint32 argsSize, uint32* ret, uint32 retSize, uint8* ram)
|
|
{
|
|
bool isDirectCall = (method & CMD_FLAG_DIRECT) != 0;
|
|
method &= ~CMD_FLAG_MASK;
|
|
|
|
switch(method)
|
|
{
|
|
case CMD_ID_GETINFO:
|
|
case 0x78:
|
|
// Many games seem to be sensitive to the delay response of this function:
|
|
//- Nights Into Dreams (issues 2 Syncs very close to each other, infinite loop if GetInfo is instantenous)
|
|
//- Melty Blood Actress Again
|
|
//- Baroque
|
|
//- Naruto Shippuden: Ultimate Ninja 5 (if GetInfo doesn't return quickly enough, MC thread is killed and game will hang)
|
|
GetInfo(args, argsSize, ret, retSize, ram);
|
|
break;
|
|
case CMD_ID_OPEN:
|
|
case 0x71:
|
|
// Operation Winback 2 expects a delay here, otherwise it hangs trying to read or write a save file
|
|
Open(args, argsSize, ret, retSize, ram);
|
|
break;
|
|
case CMD_ID_CLOSE:
|
|
case 0x72:
|
|
Close(args, argsSize, ret, retSize, ram);
|
|
break;
|
|
case CMD_ID_SEEK:
|
|
Seek(args, argsSize, ret, retSize, ram);
|
|
break;
|
|
case CMD_ID_READ:
|
|
case 0x73:
|
|
// Operation Winback 2 expects a delay here, otherwise it hangs trying to read a save file
|
|
Read(args, argsSize, ret, retSize, ram);
|
|
break;
|
|
case CMD_ID_WRITE:
|
|
case 0x74:
|
|
// Operation Winback 2 expects a delay here, otherwise it hangs trying to write a save file
|
|
Write(args, argsSize, ret, retSize, ram);
|
|
break;
|
|
case 0x0A:
|
|
case 0x7A:
|
|
Flush(args, argsSize, ret, retSize, ram);
|
|
break;
|
|
case CMD_ID_CHDIR:
|
|
ChDir(args, argsSize, ret, retSize, ram);
|
|
break;
|
|
case CMD_ID_GETDIR:
|
|
case 0x76: //Used by homebrew (ex.: ps2infones)
|
|
GetDir(args, argsSize, ret, retSize, ram);
|
|
break;
|
|
case CMD_ID_SETFILEINFO:
|
|
case 0x7C:
|
|
SetFileInfo(args, argsSize, ret, retSize, ram);
|
|
break;
|
|
case CMD_ID_DELETE:
|
|
case 0x79:
|
|
Delete(args, argsSize, ret, retSize, ram);
|
|
break;
|
|
case CMD_ID_GETENTSPACE:
|
|
GetEntSpace(args, argsSize, ret, retSize, ram);
|
|
break;
|
|
case CMD_ID_SETTHREADPRIORITY:
|
|
SetThreadPriority(args, argsSize, ret, retSize, ram);
|
|
break;
|
|
case 0x15:
|
|
GetSlotMax(args, argsSize, ret, retSize, ram);
|
|
break;
|
|
case 0x16:
|
|
return ReadFast(args, argsSize, ret, retSize, ram);
|
|
break;
|
|
case 0x1B:
|
|
WriteFast(args, argsSize, ret, retSize, ram);
|
|
break;
|
|
case 0xFE:
|
|
case 0x70:
|
|
Init(args, argsSize, ret, retSize, ram);
|
|
break;
|
|
default:
|
|
CLog::GetInstance().Warn(LOG_NAME, "Unknown RPC method invoked (0x%08X).\r\n", method);
|
|
return true;
|
|
}
|
|
|
|
if(!isDirectCall)
|
|
{
|
|
// Delay all commands a bit (except ReadFast, which has a different mecanism)
|
|
// Direct calls also don't require SIF RPC replies
|
|
// Fixes games which receive the rpc response before they are ready to receive them
|
|
auto moduleData = reinterpret_cast<MODULEDATA*>(m_ram + m_moduleDataAddr);
|
|
assert(moduleData->pendingCommand == CMD_ID_NONE);
|
|
moduleData->pendingCommand = method;
|
|
moduleData->pendingCommandDelay = CMD_DELAY_DEFAULT;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CMcServ::BuildCustomCode()
|
|
{
|
|
auto moduleData = reinterpret_cast<MODULEDATA*>(m_ram + m_moduleDataAddr);
|
|
|
|
auto exportTable = reinterpret_cast<uint32*>(moduleData->trampoline);
|
|
*(exportTable++) = 0x41E00000;
|
|
*(exportTable++) = 0;
|
|
*(exportTable++) = MODULE_VERSION;
|
|
strcpy(reinterpret_cast<char*>(exportTable), MODULE_NAME);
|
|
exportTable += (strlen(MODULE_NAME) + 3) / 4;
|
|
|
|
{
|
|
CMIPSAssembler assembler(exportTable);
|
|
uint32 codeBase = (reinterpret_cast<uint8*>(exportTable) - m_ram);
|
|
|
|
m_startReadFastAddr = codeBase + (assembler.GetProgramSize() * 4);
|
|
assembler.JR(CMIPS::RA);
|
|
assembler.ADDIU(CMIPS::R0, CMIPS::R0, CUSTOM_STARTREADFAST);
|
|
|
|
m_proceedReadFastAddr = codeBase + (assembler.GetProgramSize() * 4);
|
|
assembler.JR(CMIPS::RA);
|
|
assembler.ADDIU(CMIPS::R0, CMIPS::R0, CUSTOM_PROCEEDREADFAST);
|
|
|
|
m_finishReadFastAddr = codeBase + (assembler.GetProgramSize() * 4);
|
|
assembler.JR(CMIPS::RA);
|
|
assembler.ADDIU(CMIPS::R0, CMIPS::R0, CUSTOM_FINISHREADFAST);
|
|
|
|
m_readFastAddr = codeBase + AssembleReadFast(assembler);
|
|
|
|
exportTable += assembler.GetProgramSize();
|
|
}
|
|
|
|
assert((reinterpret_cast<uint8*>(exportTable) - moduleData->trampoline) <= MODULEDATA::TRAMPOLINE_SIZE);
|
|
}
|
|
|
|
uint32 CMcServ::AssembleReadFast(CMIPSAssembler& assembler)
|
|
{
|
|
//Extra stack alloc for SifCallRpc
|
|
static const int16 stackAlloc = 0x100;
|
|
|
|
uint32 result = assembler.GetProgramSize() * 4;
|
|
auto readNextLabel = assembler.CreateLabel();
|
|
|
|
assembler.ADDIU(CMIPS::SP, CMIPS::SP, -stackAlloc);
|
|
assembler.SW(CMIPS::RA, 0xFC, CMIPS::SP);
|
|
assembler.SW(CMIPS::S0, 0xF8, CMIPS::SP);
|
|
|
|
assembler.LI(CMIPS::S0, m_moduleDataAddr);
|
|
|
|
assembler.JAL(m_startReadFastAddr);
|
|
assembler.NOP();
|
|
|
|
assembler.MarkLabel(readNextLabel);
|
|
|
|
assembler.JAL(m_proceedReadFastAddr);
|
|
assembler.NOP();
|
|
|
|
assembler.LW(CMIPS::A0, offsetof(MODULEDATA, readFastSize), CMIPS::S0);
|
|
assembler.BNE(CMIPS::A0, CMIPS::R0, readNextLabel);
|
|
assembler.NOP();
|
|
|
|
assembler.JAL(m_finishReadFastAddr);
|
|
assembler.NOP();
|
|
|
|
assembler.LW(CMIPS::S0, 0xF8, CMIPS::SP);
|
|
assembler.LW(CMIPS::RA, 0xFC, CMIPS::SP);
|
|
assembler.JR(CMIPS::RA);
|
|
assembler.ADDIU(CMIPS::SP, CMIPS::SP, stackAlloc);
|
|
|
|
return result;
|
|
}
|
|
|
|
void CMcServ::GetInfo(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize, uint8* ram)
|
|
{
|
|
assert(argsSize >= 0x1C);
|
|
|
|
//The layout of this can actually vary according to the version of the
|
|
//MCSERV module currently loaded
|
|
uint32 port = args[1];
|
|
uint32 slot = args[2];
|
|
bool wantFormatted = args[3] != 0;
|
|
bool wantFreeSpace = args[4] != 0;
|
|
bool wantType = args[5] != 0;
|
|
uint32* retBuffer = reinterpret_cast<uint32*>(&ram[args[7]]);
|
|
|
|
CLog::GetInstance().Print(LOG_NAME, "GetInfo(port = %i, slot = %i, wantType = %i, wantFreeSpace = %i, wantFormatted = %i, retBuffer = 0x%08X);\r\n",
|
|
port, slot, wantType, wantFreeSpace, wantFormatted, args[7]);
|
|
|
|
if(HandleInvalidPortOrSlot(port, slot, ret))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(wantType)
|
|
{
|
|
retBuffer[0x00] = 2; //2 -> PS2 memory card
|
|
}
|
|
if(wantFreeSpace)
|
|
{
|
|
retBuffer[0x01] = 0x2000; //Number of clusters, cluster size = 1024 bytes
|
|
}
|
|
if(wantFormatted)
|
|
{
|
|
retBuffer[0x24] = 1;
|
|
}
|
|
|
|
if(port >= MAX_PORTS)
|
|
{
|
|
assert(0);
|
|
ret[0] = -2;
|
|
return;
|
|
}
|
|
|
|
bool isKnownCard = m_knownMemoryCards[port];
|
|
m_knownMemoryCards[port] = true;
|
|
|
|
//Return values
|
|
// 0 if same card as previous call
|
|
// -1 if new formatted card
|
|
// -2 if new unformatted card
|
|
//> -2 on error
|
|
ret[0] = isKnownCard ? 0 : -1;
|
|
}
|
|
|
|
void CMcServ::Open(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize, uint8* ram)
|
|
{
|
|
assert(argsSize >= 0x414);
|
|
|
|
CMD* cmd = reinterpret_cast<CMD*>(args);
|
|
|
|
CLog::GetInstance().Print(LOG_NAME, "Open(port = %i, slot = %i, flags = %i, name = '%s');\r\n",
|
|
cmd->port, cmd->slot, cmd->flags, cmd->name);
|
|
|
|
if(HandleInvalidPortOrSlot(cmd->port, cmd->slot, ret))
|
|
{
|
|
return;
|
|
}
|
|
|
|
auto name = EncodeMcName(cmd->name);
|
|
|
|
fs::path filePath;
|
|
|
|
try
|
|
{
|
|
filePath = GetHostFilePath(cmd->port, cmd->slot, name.c_str());
|
|
}
|
|
catch(const std::exception& exception)
|
|
{
|
|
CLog::GetInstance().Warn(LOG_NAME, "Error while executing Open: %s.\r\n", exception.what());
|
|
ret[0] = -1;
|
|
return;
|
|
}
|
|
|
|
if(cmd->flags == 0x40)
|
|
{
|
|
//Directory only?
|
|
uint32 result = -1;
|
|
try
|
|
{
|
|
if(fs::exists(filePath))
|
|
{
|
|
result = RET_NO_ENTRY;
|
|
}
|
|
else
|
|
{
|
|
fs::create_directory(filePath);
|
|
result = 0;
|
|
}
|
|
}
|
|
catch(...)
|
|
{
|
|
}
|
|
ret[0] = result;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if(cmd->flags & OPEN_FLAG_CREAT)
|
|
{
|
|
if(!fs::exists(filePath))
|
|
{
|
|
//Create file if it doesn't exist
|
|
try
|
|
{
|
|
Framework::CreateOutputStdStream(filePath.native());
|
|
}
|
|
catch(...)
|
|
{
|
|
//Might fail in some conditions (ex.: if file is to be created in a directory that doesn't exist).
|
|
ret[0] = RET_NO_ENTRY;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(cmd->flags & OPEN_FLAG_TRUNC)
|
|
{
|
|
if(fs::exists(filePath))
|
|
{
|
|
//Create file (discard contents) if it exists
|
|
Framework::CreateOutputStdStream(filePath.native());
|
|
}
|
|
}
|
|
|
|
//At this point, we assume that the file has been created or truncated
|
|
try
|
|
{
|
|
auto file = Framework::CreateUpdateExistingStdStream(filePath.native());
|
|
uint32 handle = GenerateHandle();
|
|
if(handle == -1)
|
|
{
|
|
//Exhausted all file handles
|
|
throw std::exception();
|
|
}
|
|
m_files[handle] = std::move(file);
|
|
ret[0] = handle;
|
|
}
|
|
catch(...)
|
|
{
|
|
//Not existing file?
|
|
ret[0] = RET_NO_ENTRY;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CMcServ::Close(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize, uint8* ram)
|
|
{
|
|
auto cmd = reinterpret_cast<FILECMD*>(args);
|
|
|
|
CLog::GetInstance().Print(LOG_NAME, "Close(handle = %i);\r\n", cmd->handle);
|
|
|
|
auto file = GetFileFromHandle(cmd->handle);
|
|
if(file == nullptr)
|
|
{
|
|
ret[0] = -1;
|
|
assert(0);
|
|
return;
|
|
}
|
|
|
|
file->Clear();
|
|
|
|
ret[0] = 0;
|
|
}
|
|
|
|
void CMcServ::Seek(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize, uint8* ram)
|
|
{
|
|
FILECMD* cmd = reinterpret_cast<FILECMD*>(args);
|
|
|
|
CLog::GetInstance().Print(LOG_NAME, "Seek(handle = %i, offset = 0x%08X, origin = 0x%08X);\r\n",
|
|
cmd->handle, cmd->offset, cmd->origin);
|
|
|
|
auto file = GetFileFromHandle(cmd->handle);
|
|
if(file == nullptr)
|
|
{
|
|
ret[0] = -1;
|
|
assert(0);
|
|
return;
|
|
}
|
|
|
|
Framework::STREAM_SEEK_DIRECTION origin = Framework::STREAM_SEEK_SET;
|
|
switch(cmd->origin)
|
|
{
|
|
case 0:
|
|
origin = Framework::STREAM_SEEK_SET;
|
|
break;
|
|
case 1:
|
|
origin = Framework::STREAM_SEEK_CUR;
|
|
break;
|
|
case 2:
|
|
origin = Framework::STREAM_SEEK_END;
|
|
break;
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
|
|
file->Seek(static_cast<int32>(cmd->offset), origin);
|
|
ret[0] = static_cast<uint32>(file->Tell());
|
|
}
|
|
|
|
void CMcServ::Read(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize, uint8* ram)
|
|
{
|
|
FILECMD* cmd = reinterpret_cast<FILECMD*>(args);
|
|
|
|
CLog::GetInstance().Print(LOG_NAME, "Read(handle = %i, size = 0x%08X, bufferAddress = 0x%08X, paramAddress = 0x%08X);\r\n",
|
|
cmd->handle, cmd->size, cmd->bufferAddress, cmd->paramAddress);
|
|
|
|
if(cmd->paramAddress != 0)
|
|
{
|
|
//This param buffer is used in the callback after calling this method.
|
|
//It seems to be used to ferry some data to reduce the number of transfers between IOP and EE.
|
|
reinterpret_cast<uint32*>(&ram[cmd->paramAddress])[0] = 0;
|
|
reinterpret_cast<uint32*>(&ram[cmd->paramAddress])[1] = 0;
|
|
}
|
|
|
|
auto file = GetFileFromHandle(cmd->handle);
|
|
if(!file)
|
|
{
|
|
CLog::GetInstance().Warn(LOG_NAME, "Warning. Attempted to read from an invalid fd (%d).\r\n", cmd->handle);
|
|
ret[0] = RET_PERMISSION_DENIED;
|
|
return;
|
|
}
|
|
|
|
assert(cmd->bufferAddress != 0);
|
|
void* dst = &ram[cmd->bufferAddress];
|
|
|
|
if(file->IsEOF())
|
|
{
|
|
ret[0] = 0;
|
|
}
|
|
else
|
|
{
|
|
ret[0] = static_cast<uint32>(file->Read(dst, cmd->size));
|
|
|
|
//Sync pointer for games that write after read without seeking or flushing
|
|
file->Seek(file->Tell(), Framework::STREAM_SEEK_SET);
|
|
}
|
|
}
|
|
|
|
void CMcServ::Write(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize, uint8* ram)
|
|
{
|
|
FILECMD* cmd = reinterpret_cast<FILECMD*>(args);
|
|
|
|
CLog::GetInstance().Print(LOG_NAME, "Write(handle = %i, nSize = 0x%08X, bufferAddress = 0x%08X, origin = 0x%08X);\r\n",
|
|
cmd->handle, cmd->size, cmd->bufferAddress, cmd->origin);
|
|
|
|
auto file = GetFileFromHandle(cmd->handle);
|
|
if(file == nullptr)
|
|
{
|
|
ret[0] = RET_PERMISSION_DENIED;
|
|
assert(0);
|
|
return;
|
|
}
|
|
|
|
const void* dst = &ram[cmd->bufferAddress];
|
|
uint32 result = 0;
|
|
|
|
//Write "origin" bytes from "data" field first
|
|
if(cmd->origin != 0)
|
|
{
|
|
file->Write(cmd->data, cmd->origin);
|
|
result += cmd->origin;
|
|
}
|
|
|
|
result += static_cast<uint32>(file->Write(dst, cmd->size));
|
|
ret[0] = result;
|
|
|
|
//Force flushing for games that read after write without seeking or flushing
|
|
file->Flush();
|
|
}
|
|
|
|
void CMcServ::Flush(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize, uint8* ram)
|
|
{
|
|
FILECMD* cmd = reinterpret_cast<FILECMD*>(args);
|
|
|
|
CLog::GetInstance().Print(LOG_NAME, "Flush(handle = %d);\r\n", cmd->handle);
|
|
|
|
auto file = GetFileFromHandle(cmd->handle);
|
|
if(file == nullptr)
|
|
{
|
|
ret[0] = -1;
|
|
assert(0);
|
|
return;
|
|
}
|
|
|
|
file->Flush();
|
|
|
|
ret[0] = 0;
|
|
}
|
|
|
|
void CMcServ::ChDir(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize, uint8* ram)
|
|
{
|
|
assert(argsSize >= 0x414);
|
|
CMD* cmd = reinterpret_cast<CMD*>(args);
|
|
|
|
CLog::GetInstance().Print(LOG_NAME, "ChDir(port = %i, slot = %i, tableAddress = 0x%08X, name = '%s');\r\n",
|
|
cmd->port, cmd->slot, cmd->tableAddress, cmd->name);
|
|
|
|
if(HandleInvalidPortOrSlot(cmd->port, cmd->slot, ret))
|
|
{
|
|
return;
|
|
}
|
|
|
|
uint32 result = -1;
|
|
auto& currentDirectory = m_currentDirectory[cmd->port];
|
|
|
|
//Write out current directory
|
|
if(cmd->tableAddress != 0)
|
|
{
|
|
//Make sure we return '/' even if the current directory is empty, needed by Silent Hill 3
|
|
auto curDir = currentDirectory.empty() ? std::string(1, SEPARATOR_CHAR) : currentDirectory;
|
|
|
|
const size_t maxCurDirSize = 256;
|
|
char* currentDirOut = reinterpret_cast<char*>(ram + cmd->tableAddress);
|
|
strncpy(currentDirOut, curDir.c_str(), maxCurDirSize - 1);
|
|
}
|
|
|
|
try
|
|
{
|
|
std::string newCurrentDirectory;
|
|
std::string requestedDirectory = EncodeMcName(cmd->name);
|
|
|
|
if(!requestedDirectory.empty() && (requestedDirectory[0] == SEPARATOR_CHAR))
|
|
{
|
|
if(requestedDirectory.length() != 1)
|
|
{
|
|
newCurrentDirectory = requestedDirectory;
|
|
}
|
|
else
|
|
{
|
|
//Clear if only separator char
|
|
newCurrentDirectory.clear();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
newCurrentDirectory = currentDirectory + SEPARATOR_CHAR + requestedDirectory;
|
|
}
|
|
|
|
//Some games (EA games) will try to ChDir('..') from the MC's root
|
|
//Kim Possible: What's the Switch will also attempt this and rely on the result
|
|
//to consider other MC operations to be successes.
|
|
|
|
newCurrentDirectory = MakeAbsolutePath(newCurrentDirectory);
|
|
|
|
auto mcPath = CAppConfig::GetInstance().GetPreferencePath(m_mcPathPreference[cmd->port]);
|
|
auto hostPath = Iop::PathUtils::MakeHostPath(mcPath, newCurrentDirectory.c_str());
|
|
|
|
if(!Iop::PathUtils::IsInsideBasePath(mcPath, hostPath))
|
|
{
|
|
//This shouldn't happen, but fail just in case.
|
|
assert(false);
|
|
result = RET_NO_ENTRY;
|
|
}
|
|
else if(fs::exists(hostPath) && fs::is_directory(hostPath))
|
|
{
|
|
currentDirectory = newCurrentDirectory;
|
|
result = 0;
|
|
}
|
|
else
|
|
{
|
|
//Not found (I guess)
|
|
result = RET_NO_ENTRY;
|
|
}
|
|
}
|
|
catch(const std::exception& exception)
|
|
{
|
|
CLog::GetInstance().Warn(LOG_NAME, "Error while executing ChDir: %s.\r\n", exception.what());
|
|
}
|
|
|
|
ret[0] = result;
|
|
}
|
|
|
|
void CMcServ::GetDir(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize, uint8* ram)
|
|
{
|
|
uint32 result = 0;
|
|
|
|
assert(argsSize >= 0x414);
|
|
|
|
auto cmd = reinterpret_cast<const CMD*>(args);
|
|
|
|
CLog::GetInstance().Print(LOG_NAME, "GetDir(port = %i, slot = %i, flags = %i, maxEntries = %i, tableAddress = 0x%08X, name = '%s');\r\n",
|
|
cmd->port, cmd->slot, cmd->flags, cmd->maxEntries, cmd->tableAddress, cmd->name);
|
|
|
|
if(HandleInvalidPortOrSlot(cmd->port, cmd->slot, ret))
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
if(cmd->flags == 0)
|
|
{
|
|
m_pathFinder.Reset();
|
|
|
|
auto mcPath = CAppConfig::GetInstance().GetPreferencePath(m_mcPathPreference[cmd->port]);
|
|
if(cmd->name[0] != SEPARATOR_CHAR)
|
|
{
|
|
mcPath = Iop::PathUtils::MakeHostPath(mcPath, m_currentDirectory[cmd->port].c_str());
|
|
}
|
|
mcPath = fs::absolute(mcPath);
|
|
|
|
if(!fs::exists(mcPath))
|
|
{
|
|
//Directory doesn't exist
|
|
ret[0] = RET_NO_ENTRY;
|
|
return;
|
|
}
|
|
|
|
auto searchPath = Iop::PathUtils::MakeHostPath(mcPath, cmd->name);
|
|
searchPath.remove_filename();
|
|
if(!fs::exists(searchPath))
|
|
{
|
|
//Specified directory doesn't exist, this is an error
|
|
ret[0] = RET_NO_ENTRY;
|
|
return;
|
|
}
|
|
|
|
assert(*mcPath.string().rbegin() != '/');
|
|
m_pathFinder.Search(mcPath, cmd->name);
|
|
}
|
|
|
|
auto entries = (cmd->maxEntries > 0) ? reinterpret_cast<ENTRY*>(&ram[cmd->tableAddress]) : nullptr;
|
|
result = m_pathFinder.Read(entries, cmd->maxEntries);
|
|
}
|
|
catch(const std::exception& exception)
|
|
{
|
|
CLog::GetInstance().Warn(LOG_NAME, "Error while executing GetDir: %s.\r\n", exception.what());
|
|
}
|
|
|
|
ret[0] = result;
|
|
}
|
|
|
|
void CMcServ::SetFileInfo(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize, uint8* ram)
|
|
{
|
|
auto cmd = reinterpret_cast<const CMD*>(args);
|
|
CLog::GetInstance().Print(LOG_NAME, "SetFileInfo(port = %i, slot = %i, flags = %i, name = '%s');\r\n", cmd->port, cmd->slot, cmd->flags, cmd->name);
|
|
|
|
if(HandleInvalidPortOrSlot(cmd->port, cmd->slot, ret))
|
|
{
|
|
return;
|
|
}
|
|
|
|
auto entry = reinterpret_cast<ENTRY*>(ram + cmd->tableAddress);
|
|
|
|
auto flags = cmd->flags;
|
|
|
|
if(flags & MC_FILE_ATTR_FILE)
|
|
{
|
|
auto filePath1 = GetHostFilePath(cmd->port, cmd->slot, cmd->name);
|
|
auto filePath2 = GetHostFilePath(cmd->port, cmd->slot, cmd->name);
|
|
filePath2.replace_filename(reinterpret_cast<const char*>(entry->name));
|
|
|
|
if(filePath1 != filePath2)
|
|
{
|
|
try
|
|
{
|
|
if(!fs::exists(filePath1))
|
|
{
|
|
ret[0] = RET_NO_ENTRY;
|
|
return;
|
|
}
|
|
|
|
fs::rename(filePath1, filePath2);
|
|
}
|
|
catch(...)
|
|
{
|
|
ret[0] = -1;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
flags &= ~MC_FILE_ATTR_FILE;
|
|
|
|
if(flags != 0)
|
|
{
|
|
// TODO: We only support file renaming for the moment
|
|
CLog::GetInstance().Warn(LOG_NAME, "Setting unknown file attribute flag %i\r\n", cmd->flags);
|
|
}
|
|
|
|
ret[0] = 0;
|
|
}
|
|
|
|
void CMcServ::Delete(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize, uint8* ram)
|
|
{
|
|
auto cmd = reinterpret_cast<const CMD*>(args);
|
|
|
|
CLog::GetInstance().Print(LOG_NAME, "Delete(port = %d, slot = %d, name = '%s');\r\n", cmd->port, cmd->slot, cmd->name);
|
|
|
|
if(HandleInvalidPortOrSlot(cmd->port, cmd->slot, ret))
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
auto filePath = GetHostFilePath(cmd->port, cmd->slot, cmd->name);
|
|
if(fs::exists(filePath))
|
|
{
|
|
fs::remove(filePath);
|
|
ret[0] = 0;
|
|
}
|
|
else
|
|
{
|
|
ret[0] = RET_NO_ENTRY;
|
|
}
|
|
}
|
|
catch(const fs::filesystem_error& exception)
|
|
{
|
|
CLog::GetInstance().Warn(LOG_NAME, "Error while executing Delete: %s.\r\n", exception.what());
|
|
auto errorCode = exception.code();
|
|
if(errorCode == std::errc::directory_not_empty)
|
|
{
|
|
//Musashi Samurai Legends will try to delete a directory when overwriting a save
|
|
ret[0] = RET_NOT_EMPTY;
|
|
}
|
|
else
|
|
{
|
|
ret[0] = -1;
|
|
}
|
|
}
|
|
catch(const std::exception& exception)
|
|
{
|
|
CLog::GetInstance().Warn(LOG_NAME, "Error while executing Delete: %s.\r\n", exception.what());
|
|
ret[0] = -1;
|
|
}
|
|
}
|
|
|
|
void CMcServ::GetEntSpace(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize, uint8* ram)
|
|
{
|
|
auto cmd = reinterpret_cast<CMD*>(args);
|
|
|
|
CLog::GetInstance().Print(LOG_NAME, "GetEntSpace(port = %i, slot = %i, flags = %i, name = '%s');\r\n",
|
|
cmd->port, cmd->slot, cmd->flags, cmd->name);
|
|
|
|
if(HandleInvalidPortOrSlot(cmd->port, cmd->slot, ret))
|
|
{
|
|
return;
|
|
}
|
|
|
|
auto mcPath = CAppConfig::GetInstance().GetPreferencePath(m_mcPathPreference[cmd->port]);
|
|
auto savePath = Iop::PathUtils::MakeHostPath(mcPath, cmd->name);
|
|
|
|
try
|
|
{
|
|
if(fs::exists(savePath) && fs::is_directory(savePath))
|
|
{
|
|
// Arbitrarity number, allows Drakengard to detect MC
|
|
ret[0] = 0xFE;
|
|
}
|
|
else
|
|
{
|
|
ret[0] = RET_NO_ENTRY;
|
|
}
|
|
}
|
|
catch(const std::exception& exception)
|
|
{
|
|
CLog::GetInstance().Warn(LOG_NAME, "Error while executing GetEntSpace: %s.\r\n", exception.what());
|
|
ret[0] = -1;
|
|
}
|
|
}
|
|
|
|
void CMcServ::SetThreadPriority(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize, uint8* ram)
|
|
{
|
|
auto cmd = reinterpret_cast<CMD*>(args);
|
|
uint32 priority = *reinterpret_cast<uint32*>(cmd->name);
|
|
|
|
CLog::GetInstance().Print(LOG_NAME, "SetThreadPriority(priority = %d);\r\n", priority);
|
|
|
|
//We don't really care about thread priority here, just report a success
|
|
ret[0] = 0;
|
|
}
|
|
|
|
void CMcServ::GetSlotMax(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize, uint8* ram)
|
|
{
|
|
int port = args[1];
|
|
CLog::GetInstance().Print(LOG_NAME, "GetSlotMax(port = %i);\r\n", port);
|
|
ret[0] = MAX_SLOTS;
|
|
}
|
|
|
|
bool CMcServ::ReadFast(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize, uint8* ram)
|
|
{
|
|
//Based on mcemu code: https://github.com/ifcaro/Open-PS2-Loader/blob/master/modules/mcemu/mcemu_rpc.c
|
|
|
|
auto cmd = reinterpret_cast<FILECMD*>(args);
|
|
CLog::GetInstance().Print(LOG_NAME, "ReadFast(handle = %d, size = 0x%08X, bufferAddress = 0x%08X, paramAddress = 0x%08X);\r\n",
|
|
cmd->handle, cmd->size, cmd->bufferAddress, cmd->paramAddress);
|
|
|
|
auto file = GetFileFromHandle(cmd->handle);
|
|
if(file == nullptr)
|
|
{
|
|
ret[0] = -1;
|
|
return true;
|
|
}
|
|
|
|
//Returns the amount of bytes read
|
|
assert(cmd->size >= file->GetRemainingLength());
|
|
ret[0] = cmd->size;
|
|
|
|
auto moduleData = reinterpret_cast<MODULEDATA*>(m_ram + m_moduleDataAddr);
|
|
moduleData->readFastHandle = cmd->handle;
|
|
moduleData->readFastSize = cmd->size;
|
|
moduleData->readFastBufferAddress = cmd->bufferAddress;
|
|
|
|
m_bios.TriggerCallback(m_readFastAddr);
|
|
return false;
|
|
}
|
|
|
|
void CMcServ::WriteFast(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize, uint8* ram)
|
|
{
|
|
FILECMD* cmd = reinterpret_cast<FILECMD*>(args);
|
|
|
|
CLog::GetInstance().Print(LOG_NAME, "WriteFast(handle = %d, size = 0x%08X, bufferAddress = 0x%08X, paramAddress = 0x%08X);\r\n",
|
|
cmd->handle, cmd->size, cmd->bufferAddress, cmd->paramAddress);
|
|
|
|
auto file = GetFileFromHandle(cmd->handle);
|
|
if(file == nullptr)
|
|
{
|
|
ret[0] = RET_PERMISSION_DENIED;
|
|
return;
|
|
}
|
|
|
|
const void* dst = &ram[cmd->bufferAddress];
|
|
uint32 result = 0;
|
|
|
|
result += static_cast<uint32>(file->Write(dst, cmd->size));
|
|
ret[0] = result;
|
|
}
|
|
|
|
void CMcServ::Init(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize, uint8* ram)
|
|
{
|
|
assert(argsSize == 0x30);
|
|
assert(retSize == 0x0C);
|
|
|
|
ret[0] = 0x00000000;
|
|
ret[1] = 0x0000020A; //mcserv version
|
|
ret[2] = 0x0000020E; //mcman version
|
|
|
|
CLog::GetInstance().Print(LOG_NAME, "Init();\r\n");
|
|
}
|
|
|
|
bool CMcServ::HandleInvalidPortOrSlot(uint32 port, uint32 slot, uint32* ret)
|
|
{
|
|
if(port >= MAX_PORTS)
|
|
{
|
|
CLog::GetInstance().Warn(LOG_NAME, "Called mc function with invalid port %d\r\n", port);
|
|
ret[0] = -1;
|
|
return true;
|
|
}
|
|
|
|
if(slot >= MAX_SLOTS)
|
|
{
|
|
//Just warn if an invalid slot is specified. Should not be a big deal since we never use that parameter
|
|
//in our functions. It shouldn't also matter since we don't support multitap.
|
|
//Note that some games specify 'slot = 1' while never checking GetSlotMax:
|
|
//- Dragon Quest 8
|
|
CLog::GetInstance().Warn(LOG_NAME, "Called mc function with invalid slot %d\r\n", slot);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CMcServ::StartReadFast(CMIPS& context)
|
|
{
|
|
auto moduleData = reinterpret_cast<MODULEDATA*>(m_ram + m_moduleDataAddr);
|
|
if(!moduleData->initialized)
|
|
{
|
|
context.m_State.nGPR[CMIPS::A0].nV0 = m_moduleDataAddr + offsetof(MODULEDATA, rpcClientData);
|
|
context.m_State.nGPR[CMIPS::A1].nV0 = MODULE_ID;
|
|
context.m_State.nGPR[CMIPS::A2].nV0 = 0; //Wait mode
|
|
m_sifCmd.SifBindRpc(context);
|
|
|
|
moduleData->initialized = true;
|
|
}
|
|
}
|
|
|
|
void CMcServ::ProceedReadFast(CMIPS& context)
|
|
{
|
|
auto moduleData = reinterpret_cast<MODULEDATA*>(m_ram + m_moduleDataAddr);
|
|
|
|
auto file = GetFileFromHandle(moduleData->readFastHandle);
|
|
assert(file);
|
|
|
|
uint32 readSize = std::min<uint32>(moduleData->readFastSize, CLUSTER_SIZE);
|
|
|
|
uint8 cluster[CLUSTER_SIZE];
|
|
FRAMEWORK_MAYBE_UNUSED uint32 amountRead = file->Read(cluster, readSize);
|
|
assert(amountRead == readSize);
|
|
moduleData->readFastSize -= readSize;
|
|
|
|
if(auto sifManPs2 = dynamic_cast<CSifManPs2*>(&m_sifMan))
|
|
{
|
|
auto eeRam = sifManPs2->GetEeRam();
|
|
memcpy(eeRam + moduleData->readFastBufferAddress, cluster, readSize);
|
|
}
|
|
|
|
reinterpret_cast<uint32*>(moduleData->rpcBuffer)[3] = readSize;
|
|
|
|
context.m_State.nGPR[CMIPS::A0].nV0 = m_moduleDataAddr + offsetof(MODULEDATA, rpcClientData);
|
|
context.m_State.nGPR[CMIPS::A1].nV0 = 2;
|
|
context.m_State.nGPR[CMIPS::A2].nV0 = 0;
|
|
context.m_State.nGPR[CMIPS::A3].nV0 = m_moduleDataAddr + offsetof(MODULEDATA, rpcBuffer);
|
|
context.m_pMemoryMap->SetWord(context.m_State.nGPR[CMIPS::SP].nV0 + 0x10, MODULEDATA::RPC_BUFFER_SIZE);
|
|
context.m_pMemoryMap->SetWord(context.m_State.nGPR[CMIPS::SP].nV0 + 0x14, m_moduleDataAddr + offsetof(MODULEDATA, rpcBuffer));
|
|
context.m_pMemoryMap->SetWord(context.m_State.nGPR[CMIPS::SP].nV0 + 0x18, MODULEDATA::RPC_BUFFER_SIZE);
|
|
context.m_pMemoryMap->SetWord(context.m_State.nGPR[CMIPS::SP].nV0 + 0x1C, 0);
|
|
context.m_pMemoryMap->SetWord(context.m_State.nGPR[CMIPS::SP].nV0 + 0x20, 0);
|
|
|
|
m_sifCmd.SifCallRpc(context);
|
|
}
|
|
|
|
void CMcServ::FinishReadFast(CMIPS& context)
|
|
{
|
|
m_sifMan.SendCallReply(MODULE_ID, nullptr);
|
|
}
|
|
|
|
uint32 CMcServ::GenerateHandle()
|
|
{
|
|
for(unsigned int i = 0; i < MAX_FILES; i++)
|
|
{
|
|
if(m_files[i].IsEmpty()) return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
Framework::CStdStream* CMcServ::GetFileFromHandle(uint32 handle)
|
|
{
|
|
assert(handle < MAX_FILES);
|
|
if(handle >= MAX_FILES)
|
|
{
|
|
return nullptr;
|
|
}
|
|
auto& file = m_files[handle];
|
|
if(file.IsEmpty())
|
|
{
|
|
return nullptr;
|
|
}
|
|
return &file;
|
|
}
|
|
|
|
fs::path CMcServ::GetHostFilePath(unsigned int port, unsigned int slot, const char* path) const
|
|
{
|
|
auto mcPath = CAppConfig::GetInstance().GetPreferencePath(m_mcPathPreference[port]);
|
|
|
|
auto nameLength = strlen(path);
|
|
if(nameLength == 0) return mcPath;
|
|
|
|
std::string guestPath;
|
|
if(path[0] == SEPARATOR_CHAR)
|
|
{
|
|
guestPath = path;
|
|
}
|
|
else
|
|
{
|
|
const auto& currentDirectory = m_currentDirectory[port];
|
|
guestPath = currentDirectory + SEPARATOR_CHAR + std::string(path);
|
|
}
|
|
|
|
guestPath = MakeAbsolutePath(guestPath);
|
|
|
|
return Iop::PathUtils::MakeHostPath(mcPath, guestPath.c_str());
|
|
}
|
|
|
|
void CMcServ::LoadState(Framework::CZipArchiveReader& archive)
|
|
{
|
|
auto stateFile = CXmlStateFile(*archive.BeginReadFile(STATE_MEMCARDS_FILE));
|
|
auto stateNode = stateFile.GetRoot();
|
|
|
|
auto cardNodes = stateNode->SelectNodes(STATE_MEMCARDS_NODE "/" STATE_MEMCARDS_CARDNODE);
|
|
|
|
int i = 0;
|
|
for(auto fileNode : cardNodes)
|
|
{
|
|
Framework::Xml::GetAttributeIntValue(fileNode, STATE_MEMCARDS_CARDNODE_PORTATTRIBUTE, &i);
|
|
Framework::Xml::GetAttributeBoolValue(fileNode, STATE_MEMCARDS_CARDNODE_KNOWNATTRIBUTE, &m_knownMemoryCards[i]);
|
|
}
|
|
}
|
|
|
|
void CMcServ::SaveState(Framework::CZipArchiveWriter& archive) const
|
|
{
|
|
auto stateFile = std::make_unique<CXmlStateFile>(STATE_MEMCARDS_FILE, STATE_MEMCARDS_NODE);
|
|
auto stateNode = stateFile->GetRoot();
|
|
|
|
for(unsigned int i = 0; i < MAX_PORTS; i++)
|
|
{
|
|
auto cardNode = std::make_unique<Framework::Xml::CNode>(STATE_MEMCARDS_CARDNODE, true);
|
|
cardNode->InsertAttribute(Framework::Xml::CreateAttributeIntValue(STATE_MEMCARDS_CARDNODE_PORTATTRIBUTE, i));
|
|
cardNode->InsertAttribute(Framework::Xml::CreateAttributeBoolValue(STATE_MEMCARDS_CARDNODE_KNOWNATTRIBUTE, m_knownMemoryCards[i]));
|
|
stateNode->InsertNode(std::move(cardNode));
|
|
}
|
|
|
|
archive.InsertFile(std::move(stateFile));
|
|
}
|
|
|
|
/////////////////////////////////////////////
|
|
//CPathFinder Implementation
|
|
/////////////////////////////////////////////
|
|
|
|
CMcServ::CPathFinder::CPathFinder()
|
|
: m_index(0)
|
|
{
|
|
}
|
|
|
|
CMcServ::CPathFinder::~CPathFinder()
|
|
{
|
|
}
|
|
|
|
void CMcServ::CPathFinder::Reset()
|
|
{
|
|
m_entries.clear();
|
|
m_index = 0;
|
|
}
|
|
|
|
void CMcServ::CPathFinder::Search(const fs::path& basePath, const char* filter)
|
|
{
|
|
m_basePath = basePath;
|
|
|
|
std::string filterPathString = filter;
|
|
|
|
//Resolve relative paths (only when filter starts with one)
|
|
if(filterPathString.find("./") == 0)
|
|
{
|
|
filterPathString = filterPathString.substr(1);
|
|
}
|
|
|
|
if(filterPathString[0] != '/')
|
|
{
|
|
filterPathString = "/" + filterPathString;
|
|
}
|
|
|
|
//Remove slash at end
|
|
if((filterPathString.size() > 1) && (*filterPathString.rbegin() == '/'))
|
|
{
|
|
filterPathString.erase(filterPathString.end() - 1);
|
|
}
|
|
|
|
{
|
|
std::string filterExpString = filterPathString;
|
|
filterExpString = StringUtils::ReplaceAll(filterExpString, "\\", "\\\\");
|
|
filterExpString = StringUtils::ReplaceAll(filterExpString, ".", "\\.");
|
|
filterExpString = StringUtils::ReplaceAll(filterExpString, "?", ".?");
|
|
filterExpString = StringUtils::ReplaceAll(filterExpString, "*", ".*");
|
|
m_filterExp = std::regex(filterExpString);
|
|
}
|
|
|
|
auto filterPath = fs::path(filterPathString);
|
|
filterPath.remove_filename();
|
|
|
|
auto currentDirPath = filterPath / ".";
|
|
auto parentDirPath = filterPath / "..";
|
|
auto currentDirPathString = currentDirPath.generic_string();
|
|
auto parentDirPathString = parentDirPath.generic_string();
|
|
|
|
if(std::regex_match(currentDirPathString, m_filterExp))
|
|
{
|
|
ENTRY entry;
|
|
memset(&entry, 0, sizeof(entry));
|
|
strcpy(reinterpret_cast<char*>(entry.name), ".");
|
|
entry.size = 0;
|
|
entry.attributes = MC_FILE_ATTR_FOLDER;
|
|
m_entries.push_back(entry);
|
|
}
|
|
|
|
if(std::regex_match(parentDirPathString, m_filterExp))
|
|
{
|
|
ENTRY entry;
|
|
memset(&entry, 0, sizeof(entry));
|
|
strcpy(reinterpret_cast<char*>(entry.name), "..");
|
|
entry.size = 0;
|
|
entry.attributes = MC_FILE_ATTR_FOLDER;
|
|
m_entries.push_back(entry);
|
|
}
|
|
|
|
SearchRecurse(m_basePath);
|
|
}
|
|
|
|
unsigned int CMcServ::CPathFinder::Read(ENTRY* entry, unsigned int size)
|
|
{
|
|
assert(m_index <= m_entries.size());
|
|
unsigned int remaining = m_entries.size() - m_index;
|
|
unsigned int readCount = std::min<unsigned int>(remaining, size);
|
|
if(entry != nullptr)
|
|
{
|
|
for(unsigned int i = 0; i < readCount; i++)
|
|
{
|
|
entry[i] = m_entries[i + m_index];
|
|
}
|
|
}
|
|
m_index += readCount;
|
|
return readCount;
|
|
}
|
|
|
|
void CMcServ::CPathFinder::SearchRecurse(const fs::path& path)
|
|
{
|
|
bool found = false;
|
|
for(const auto& element : fs::directory_iterator(path))
|
|
{
|
|
const auto& relativePath(element.path());
|
|
std::string relativePathString(relativePath.generic_string());
|
|
|
|
//"Extract" a more appropriate relative path from the memory card point of view
|
|
relativePathString.erase(0, m_basePath.generic_string().size());
|
|
|
|
//Attempt to match this against the filter
|
|
if(std::regex_match(relativePathString, m_filterExp))
|
|
{
|
|
//Fill in the information
|
|
ENTRY entry;
|
|
memset(&entry, 0, sizeof(entry));
|
|
|
|
auto entryName = DecodeMcName(relativePath.filename().string());
|
|
strncpy(reinterpret_cast<char*>(entry.name), entryName.c_str(), 0x1F);
|
|
entry.name[0x1F] = 0;
|
|
|
|
if(element.is_directory())
|
|
{
|
|
entry.size = CountEntries(element);
|
|
entry.attributes = MC_FILE_ATTR_FOLDER;
|
|
}
|
|
else
|
|
{
|
|
entry.size = static_cast<uint32>(element.file_size());
|
|
entry.attributes = MC_FILE_0400 | MC_FILE_ATTR_EXISTS | MC_FILE_ATTR_CLOSED | MC_FILE_ATTR_FILE | MC_FILE_ATTR_READABLE | MC_FILE_ATTR_WRITEABLE | MC_FILE_ATTR_EXECUTABLE;
|
|
}
|
|
|
|
//Fill in modification date info
|
|
{
|
|
auto changeSystemTime = Framework::ConvertFsTimeToSystemTime(element.last_write_time());
|
|
auto localChangeDate = std::localtime(&changeSystemTime);
|
|
|
|
entry.modificationTime.second = localChangeDate->tm_sec;
|
|
entry.modificationTime.minute = localChangeDate->tm_min;
|
|
entry.modificationTime.hour = localChangeDate->tm_hour;
|
|
entry.modificationTime.day = localChangeDate->tm_mday;
|
|
entry.modificationTime.month = localChangeDate->tm_mon;
|
|
entry.modificationTime.year = localChangeDate->tm_year + 1900;
|
|
}
|
|
|
|
//std::filesystem doesn't provide a way to get creation time, so just make it the same as modification date
|
|
entry.creationTime = entry.modificationTime;
|
|
|
|
m_entries.push_back(entry);
|
|
found = true;
|
|
}
|
|
|
|
if(element.is_directory() && !found)
|
|
{
|
|
SearchRecurse(element);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32 CMcServ::CPathFinder::CountEntries(const fs::path& path)
|
|
{
|
|
uint32 entryCount = 0;
|
|
assert(fs::is_directory(path));
|
|
for(FRAMEWORK_MAYBE_UNUSED const auto& entry : fs::directory_iterator(path))
|
|
{
|
|
entryCount++;
|
|
}
|
|
return entryCount;
|
|
}
|