Play-/Source/iop/Iop_McServ.cpp

1346 lines
36 KiB
C++
Raw Permalink Normal View History

#include <assert.h>
#include <stdio.h>
#include <algorithm>
2022-07-29 17:06:47 -04:00
#include <cstring>
2021-05-09 14:17:51 +02:00
#include <states/XmlStateFile.h>
#include <xml/Utils.h>
2022-02-07 12:05:50 -05:00
#include "string_format.h"
2025-03-11 12:48:26 -04:00
#include "AppConfig.h"
#include "../PS2VM_Preferences.h"
2025-03-11 12:48:26 -04:00
#include "Log.h"
#include "Iop_McServ.h"
#include "Iop_PathUtils.h"
2018-12-19 19:54:26 -05:00
#include "Iop_Sysmem.h"
#include "Iop_SifCmd.h"
#include "Iop_SifManPs2.h"
#include "IopBios.h"
#include "StdStreamUtils.h"
#include "StringUtils.h"
2018-12-19 19:54:26 -05:00
#include "MIPSAssembler.h"
2019-10-17 13:46:38 -04:00
#include "FilesystemUtils.h"
using namespace Iop;
2018-12-19 19:54:26 -05:00
#define CLUSTER_SIZE 0x400
#define LOG_NAME ("iop_mcserv")
2018-12-19 19:54:26 -05:00
#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
2021-05-09 14:17:51 +02:00
#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")
2021-05-16 18:30:00 +02:00
#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)
2018-12-19 19:25:29 -05:00
// clang-format off
const char* CMcServ::m_mcPathPreference[MAX_PORTS] =
2018-12-19 19:25:29 -05:00
{
PREF_PS2_MC0_DIRECTORY,
PREF_PS2_MC1_DIRECTORY,
};
2018-12-19 19:25:29 -05:00
// clang-format on
2018-12-19 19:54:26 -05:00
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)
{
2018-12-19 19:54:26 -05:00
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;
}
}
2014-11-15 23:22:29 -05:00
const char* CMcServ::GetMcPathPreference(unsigned int port)
{
return m_mcPathPreference[port];
}
2022-02-07 12:05:50 -05:00
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 == '\\'))
2022-02-07 12:05:50 -05:00
{
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;
2023-05-02 08:56:25 -04:00
FRAMEWORK_MAYBE_UNUSED int scanCount = sscanf(inputName.c_str() + i, "%%%02X", &decodedChar);
2022-02-07 12:05:50 -05:00
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;
2024-01-29 17:23:01 -05:00
if(frag == ".")
{
continue;
}
if(frag == "..")
{
if(newFrags.empty())
{
//Going up too much, don't bother
continue;
}
newFrags.pop_back();
}
else
{
newFrags.push_back(frag);
}
}
2022-05-20 13:38:08 -04:00
auto outputPath = std::string();
for(const auto& frag : newFrags)
{
if(frag.empty()) continue;
outputPath += "/";
2022-05-20 13:38:08 -04:00
outputPath += frag;
}
return outputPath;
}
std::string CMcServ::GetId() const
{
2018-12-19 19:54:26 -05:00
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)
{
2018-12-19 19:54:26 -05:00
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:
2018-05-28 13:20:08 -04:00
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;
2020-03-25 08:02:59 -04:00
case CMD_ID_OPEN:
2018-05-30 13:08:55 -04:00
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;
2020-03-25 08:02:59 -04:00
case CMD_ID_CLOSE:
2018-05-30 13:08:55 -04:00
case 0x72:
Close(args, argsSize, ret, retSize, ram);
break;
2020-03-25 08:02:59 -04:00
case CMD_ID_SEEK:
Seek(args, argsSize, ret, retSize, ram);
break;
2020-03-25 08:02:59 -04:00
case CMD_ID_READ:
2018-05-30 13:08:55 -04:00
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;
2020-03-27 20:38:13 -04:00
case CMD_ID_WRITE:
2018-05-30 13:08:55 -04:00
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:
2018-05-30 13:08:55 -04:00
case 0x7A:
Flush(args, argsSize, ret, retSize, ram);
break;
2020-04-01 08:02:47 -04:00
case CMD_ID_CHDIR:
ChDir(args, argsSize, ret, retSize, ram);
break;
2020-03-30 08:49:06 -04:00
case CMD_ID_GETDIR:
2018-05-10 14:00:19 -04:00
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;
2021-03-07 18:03:32 -05:00
case CMD_ID_DELETE:
2018-05-30 13:08:55 -04:00
case 0x79:
2014-11-10 22:57:24 -05:00
Delete(args, argsSize, ret, retSize, ram);
break;
2021-12-29 15:59:16 -05:00
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;
2018-12-19 19:54:26 -05:00
case 0x16:
return ReadFast(args, argsSize, ret, retSize, ram);
2018-12-19 19:54:26 -05:00
break;
case 0x1B:
WriteFast(args, argsSize, ret, retSize, ram);
break;
case 0xFE:
2018-05-28 13:20:08 -04:00
case 0x70:
Init(args, argsSize, ret, retSize, ram);
break;
default:
2018-12-19 19:53:12 -05:00
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;
}
2018-12-19 19:54:26 -05:00
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
2018-04-30 21:01:23 +01:00
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",
2018-04-30 21:01:23 +01:00
port, slot, wantType, wantFreeSpace, wantFormatted, args[7]);
if(HandleInvalidPortOrSlot(port, slot, ret))
{
return;
}
2014-08-07 23:04:45 -04:00
if(wantType)
{
2018-04-30 21:01:23 +01:00
retBuffer[0x00] = 2; //2 -> PS2 memory card
}
2014-08-07 23:04:45 -04:00
if(wantFreeSpace)
{
2018-04-30 21:01:23 +01:00
retBuffer[0x01] = 0x2000; //Number of clusters, cluster size = 1024 bytes
}
2014-08-07 23:04:45 -04:00
if(wantFormatted)
{
2014-08-07 23:04:45 -04:00
retBuffer[0x24] = 1;
}
2021-05-09 13:08:29 +02:00
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);
2014-08-07 23:04:45 -04:00
CMD* cmd = reinterpret_cast<CMD*>(args);
2020-05-04 11:36:51 -04:00
CLog::GetInstance().Print(LOG_NAME, "Open(port = %i, slot = %i, flags = %i, name = '%s');\r\n",
2018-04-30 21:01:23 +01:00
cmd->port, cmd->slot, cmd->flags, cmd->name);
if(HandleInvalidPortOrSlot(cmd->port, cmd->slot, ret))
{
return;
}
2022-02-07 12:05:50 -05:00
auto name = EncodeMcName(cmd->name);
2019-10-16 20:51:11 -04:00
fs::path filePath;
try
{
filePath = GetHostFilePath(cmd->port, cmd->slot, name.c_str());
}
catch(const std::exception& exception)
{
2018-05-30 13:11:43 -04:00
CLog::GetInstance().Warn(LOG_NAME, "Error while executing Open: %s.\r\n", exception.what());
ret[0] = -1;
return;
}
2014-08-07 23:04:45 -04:00
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)
{
2019-10-16 20:51:11 -04:00
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)
{
2019-10-16 20:51:11 -04:00
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(...)
{
2015-05-23 00:34:44 -04:00
//Not existing file?
ret[0] = RET_NO_ENTRY;
return;
}
}
}
void CMcServ::Close(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize, uint8* ram)
{
2018-12-19 19:53:12 -05:00
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)
{
2014-08-07 23:04:45 -04:00
FILECMD* cmd = reinterpret_cast<FILECMD*>(args);
CLog::GetInstance().Print(LOG_NAME, "Seek(handle = %i, offset = 0x%08X, origin = 0x%08X);\r\n",
2018-04-30 21:01:23 +01:00
cmd->handle, cmd->offset, cmd->origin);
auto file = GetFileFromHandle(cmd->handle);
2014-08-07 23:04:45 -04:00
if(file == nullptr)
{
ret[0] = -1;
assert(0);
return;
}
Framework::STREAM_SEEK_DIRECTION origin = Framework::STREAM_SEEK_SET;
2014-08-07 23:04:45 -04:00
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;
}
2022-03-02 13:22:51 -05:00
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)
{
2014-08-07 23:04:45 -04:00
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",
2018-04-30 21:01:23 +01:00
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;
}
2014-08-07 23:04:45 -04:00
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)
{
2014-08-07 23:04:45 -04:00
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",
2018-04-30 21:01:23 +01:00
cmd->handle, cmd->size, cmd->bufferAddress, cmd->origin);
auto file = GetFileFromHandle(cmd->handle);
2014-08-07 23:04:45 -04:00
if(file == nullptr)
{
ret[0] = RET_PERMISSION_DENIED;
assert(0);
return;
}
2014-08-07 23:04:45 -04:00
const void* dst = &ram[cmd->bufferAddress];
uint32 result = 0;
//Write "origin" bytes from "data" field first
2014-08-07 23:04:45 -04:00
if(cmd->origin != 0)
{
file->Write(cmd->data, cmd->origin);
2014-08-07 23:04:45 -04:00
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);
2014-08-07 23:04:45 -04:00
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);
2014-08-07 23:04:45 -04:00
CMD* cmd = reinterpret_cast<CMD*>(args);
2020-05-04 11:36:51 -04:00
CLog::GetInstance().Print(LOG_NAME, "ChDir(port = %i, slot = %i, tableAddress = 0x%08X, name = '%s');\r\n",
2018-04-30 21:01:23 +01:00
cmd->port, cmd->slot, cmd->tableAddress, cmd->name);
if(HandleInvalidPortOrSlot(cmd->port, cmd->slot, ret))
{
return;
}
2014-08-07 23:04:45 -04:00
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;
2022-02-07 12:05:50 -05:00
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;
}
2023-12-15 08:52:06 -05:00
//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;
2014-08-07 23:04:45 -04:00
result = 0;
}
else
{
//Not found (I guess)
2015-05-23 00:34:44 -04:00
result = RET_NO_ENTRY;
}
}
2014-08-07 23:04:45 -04:00
catch(const std::exception& exception)
{
2018-05-30 13:11:43 -04:00
CLog::GetInstance().Warn(LOG_NAME, "Error while executing ChDir: %s.\r\n", exception.what());
}
2014-08-07 23:04:45 -04:00
ret[0] = result;
}
void CMcServ::GetDir(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize, uint8* ram)
{
2014-08-07 23:04:45 -04:00
uint32 result = 0;
assert(argsSize >= 0x414);
2014-11-16 00:54:10 -05:00
auto cmd = reinterpret_cast<const CMD*>(args);
2020-05-04 11:36:51 -04:00
CLog::GetInstance().Print(LOG_NAME, "GetDir(port = %i, slot = %i, flags = %i, maxEntries = %i, tableAddress = 0x%08X, name = '%s');\r\n",
2018-04-30 21:01:23 +01:00
cmd->port, cmd->slot, cmd->flags, cmd->maxEntries, cmd->tableAddress, cmd->name);
if(HandleInvalidPortOrSlot(cmd->port, cmd->slot, ret))
{
return;
}
try
{
2014-08-07 23:04:45 -04:00
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());
}
2019-10-16 20:51:11 -04:00
mcPath = fs::absolute(mcPath);
2019-10-16 20:51:11 -04:00
if(!fs::exists(mcPath))
{
//Directory doesn't exist
2015-05-23 00:34:44 -04:00
ret[0] = RET_NO_ENTRY;
return;
}
auto searchPath = Iop::PathUtils::MakeHostPath(mcPath, cmd->name);
searchPath.remove_filename();
2019-10-16 20:51:11 -04:00
if(!fs::exists(searchPath))
{
//Specified directory doesn't exist, this is an error
2015-05-23 00:34:44 -04:00
ret[0] = RET_NO_ENTRY;
return;
}
2019-10-24 12:48:32 -04:00
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);
}
2014-08-07 23:04:45 -04:00
catch(const std::exception& exception)
{
2018-05-30 13:11:43 -04:00
CLog::GetInstance().Warn(LOG_NAME, "Error while executing GetDir: %s.\r\n", exception.what());
}
2014-08-07 23:04:45 -04:00
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;
}
2014-11-10 22:57:24 -05:00
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;
}
2014-11-10 22:57:24 -05:00
try
{
auto filePath = GetHostFilePath(cmd->port, cmd->slot, cmd->name);
2019-10-16 20:51:11 -04:00
if(fs::exists(filePath))
{
2019-10-16 20:51:11 -04:00
fs::remove(filePath);
ret[0] = 0;
}
else
{
ret[0] = RET_NO_ENTRY;
}
2014-11-10 22:57:24 -05:00
}
2021-01-22 13:07:02 -05:00
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;
}
}
2014-11-10 22:57:24 -05:00
catch(const std::exception& exception)
{
2018-05-30 13:11:43 -04:00
CLog::GetInstance().Warn(LOG_NAME, "Error while executing Delete: %s.\r\n", exception.what());
2014-11-10 22:57:24 -05:00
ret[0] = -1;
}
}
void CMcServ::GetEntSpace(uint32* args, uint32 argsSize, uint32* ret, uint32 retSize, uint8* ram)
{
auto cmd = reinterpret_cast<CMD*>(args);
2020-09-04 08:55:35 -04:00
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);
2022-01-11 07:58:31 -05:00
try
{
2022-01-11 07:58:31 -05:00
if(fs::exists(savePath) && fs::is_directory(savePath))
{
// Arbitrarity number, allows Drakengard to detect MC
ret[0] = 0xFE;
}
else
{
ret[0] = RET_NO_ENTRY;
}
}
2022-01-11 07:58:31 -05:00
catch(const std::exception& exception)
{
2022-01-11 07:58:31 -05:00
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;
}
2018-12-19 19:54:26 -05:00
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;
}
2022-02-17 09:55:27 -05:00
//Returns the amount of bytes read
assert(cmd->size >= file->GetRemainingLength());
ret[0] = cmd->size;
2018-12-19 19:54:26 -05:00
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;
2018-04-30 21:01:23 +01:00
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;
}
2018-12-19 19:54:26 -05:00
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];
2023-05-02 08:56:25 -04:00
FRAMEWORK_MAYBE_UNUSED uint32 amountRead = file->Read(cluster, readSize);
2018-12-19 19:54:26 -05:00
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
2014-11-10 22:57:24 -05:00
{
auto mcPath = CAppConfig::GetInstance().GetPreferencePath(m_mcPathPreference[port]);
2014-11-10 22:57:24 -05:00
auto nameLength = strlen(path);
if(nameLength == 0) return mcPath;
std::string guestPath;
if(path[0] == SEPARATOR_CHAR)
2014-11-10 22:57:24 -05:00
{
guestPath = path;
2014-11-10 22:57:24 -05:00
}
else
{
const auto& currentDirectory = m_currentDirectory[port];
guestPath = currentDirectory + SEPARATOR_CHAR + std::string(path);
2014-11-10 22:57:24 -05:00
}
guestPath = MakeAbsolutePath(guestPath);
return Iop::PathUtils::MakeHostPath(mcPath, guestPath.c_str());
2014-11-10 22:57:24 -05:00
}
2021-05-09 14:17:51 +02:00
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
{
2023-07-23 17:22:18 -04:00
auto stateFile = std::make_unique<CXmlStateFile>(STATE_MEMCARDS_FILE, STATE_MEMCARDS_NODE);
2021-05-09 14:17:51 +02:00
auto stateNode = stateFile->GetRoot();
for(unsigned int i = 0; i < MAX_PORTS; i++)
{
2023-11-14 17:42:31 -05:00
auto cardNode = std::make_unique<Framework::Xml::CNode>(STATE_MEMCARDS_CARDNODE, true);
2021-05-09 14:17:51 +02:00
cardNode->InsertAttribute(Framework::Xml::CreateAttributeIntValue(STATE_MEMCARDS_CARDNODE_PORTATTRIBUTE, i));
cardNode->InsertAttribute(Framework::Xml::CreateAttributeBoolValue(STATE_MEMCARDS_CARDNODE_KNOWNATTRIBUTE, m_knownMemoryCards[i]));
2023-11-14 17:42:31 -05:00
stateNode->InsertNode(std::move(cardNode));
2021-05-09 14:17:51 +02:00
}
2023-07-23 17:22:18 -04:00
archive.InsertFile(std::move(stateFile));
2021-05-09 14:17:51 +02:00
}
/////////////////////////////////////////////
//CPathFinder Implementation
/////////////////////////////////////////////
CMcServ::CPathFinder::CPathFinder()
2018-04-30 21:01:23 +01:00
: m_index(0)
{
}
CMcServ::CPathFinder::~CPathFinder()
{
}
void CMcServ::CPathFinder::Reset()
{
m_entries.clear();
2014-08-07 23:04:45 -04:00
m_index = 0;
}
2019-10-16 20:51:11 -04:00
void CMcServ::CPathFinder::Search(const fs::path& basePath, const char* filter)
{
2014-11-16 00:54:10 -05:00
m_basePath = basePath;
2014-11-16 00:54:10 -05:00
std::string filterPathString = filter;
//Resolve relative paths (only when filter starts with one)
if(filterPathString.find("./") == 0)
{
filterPathString = filterPathString.substr(1);
}
2014-11-16 00:54:10 -05:00
if(filterPathString[0] != '/')
{
2014-11-16 00:54:10 -05:00
filterPathString = "/" + filterPathString;
}
2014-11-16 00:54:10 -05:00
//Remove slash at end
if((filterPathString.size() > 1) && (*filterPathString.rbegin() == '/'))
{
filterPathString.erase(filterPathString.end() - 1);
}
{
2014-11-16 00:54:10 -05:00
std::string filterExpString = filterPathString;
filterExpString = StringUtils::ReplaceAll(filterExpString, "\\", "\\\\");
filterExpString = StringUtils::ReplaceAll(filterExpString, ".", "\\.");
filterExpString = StringUtils::ReplaceAll(filterExpString, "?", ".?");
filterExpString = StringUtils::ReplaceAll(filterExpString, "*", ".*");
2014-11-16 00:54:10 -05:00
m_filterExp = std::regex(filterExpString);
}
2019-10-16 20:51:11 -04:00
auto filterPath = fs::path(filterPathString);
filterPath.remove_filename();
auto currentDirPath = filterPath / ".";
auto parentDirPath = filterPath / "..";
auto currentDirPathString = currentDirPath.generic_string();
auto parentDirPathString = parentDirPath.generic_string();
2014-11-16 00:54:10 -05:00
if(std::regex_match(currentDirPathString, m_filterExp))
{
ENTRY entry;
memset(&entry, 0, sizeof(entry));
strcpy(reinterpret_cast<char*>(entry.name), ".");
entry.size = 0;
2021-05-16 18:30:00 +02:00
entry.attributes = MC_FILE_ATTR_FOLDER;
m_entries.push_back(entry);
}
2014-11-16 00:54:10 -05:00
if(std::regex_match(parentDirPathString, m_filterExp))
{
ENTRY entry;
memset(&entry, 0, sizeof(entry));
strcpy(reinterpret_cast<char*>(entry.name), "..");
entry.size = 0;
2021-05-16 18:30:00 +02:00
entry.attributes = MC_FILE_ATTR_FOLDER;
m_entries.push_back(entry);
}
2014-08-07 23:04:45 -04:00
SearchRecurse(m_basePath);
}
2014-08-07 23:04:45 -04:00
unsigned int CMcServ::CPathFinder::Read(ENTRY* entry, unsigned int size)
{
2014-08-07 23:04:45 -04:00
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];
}
}
2014-08-07 23:04:45 -04:00
m_index += readCount;
return readCount;
}
2019-10-16 20:51:11 -04:00
void CMcServ::CPathFinder::SearchRecurse(const fs::path& path)
{
bool found = false;
2024-09-12 21:07:03 -04:00
for(const auto& element : fs::directory_iterator(path))
{
2024-09-12 21:07:03 -04:00
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
2014-11-16 00:54:10 -05:00
if(std::regex_match(relativePathString, m_filterExp))
{
//Fill in the information
ENTRY entry;
memset(&entry, 0, sizeof(entry));
2022-02-07 12:05:50 -05:00
auto entryName = DecodeMcName(relativePath.filename().string());
strncpy(reinterpret_cast<char*>(entry.name), entryName.c_str(), 0x1F);
2014-08-07 23:04:45 -04:00
entry.name[0x1F] = 0;
2024-09-12 21:07:03 -04:00
if(element.is_directory())
{
2024-09-12 21:07:03 -04:00
entry.size = CountEntries(element);
2021-05-16 18:30:00 +02:00
entry.attributes = MC_FILE_ATTR_FOLDER;
}
else
{
2024-09-12 21:07:03 -04:00
entry.size = static_cast<uint32>(element.file_size());
2021-05-16 18:30:00 +02:00
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
{
2024-09-12 21:07:03 -04:00
auto changeSystemTime = Framework::ConvertFsTimeToSystemTime(element.last_write_time());
2019-10-16 20:51:11 -04:00
auto localChangeDate = std::localtime(&changeSystemTime);
2019-10-18 12:55:49 -04:00
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;
}
2019-10-16 20:51:11 -04:00
//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;
}
2024-09-12 21:07:03 -04:00
if(element.is_directory() && !found)
{
2024-09-12 21:07:03 -04:00
SearchRecurse(element);
}
}
}
uint32 CMcServ::CPathFinder::CountEntries(const fs::path& path)
{
uint32 entryCount = 0;
assert(fs::is_directory(path));
2024-09-12 21:07:03 -04:00
for(FRAMEWORK_MAYBE_UNUSED const auto& entry : fs::directory_iterator(path))
{
entryCount++;
}
return entryCount;
}