mirror of
https://github.com/cemu-project/Cemu.git
synced 2024-11-25 18:46:55 +01:00
nn_acp: Implement ACPGetOlvAccesskey + code clean up
Added ACPGetOlvAccesskey() which is used by Super Mario Maker iosu acp, nn_acp and nn_save all cross talk with each other and are mostly legacy code. Modernized it a tiny bit and moved functions to where they should be. A larger refactor should be done in the future but for now this works ok
This commit is contained in:
parent
33a74c2035
commit
12eda10387
@ -530,6 +530,7 @@ namespace CafeSystem
|
|||||||
{
|
{
|
||||||
// entries in this list are ordered by initialization order. Shutdown in reverse order
|
// entries in this list are ordered by initialization order. Shutdown in reverse order
|
||||||
iosu::kernel::GetModule(),
|
iosu::kernel::GetModule(),
|
||||||
|
iosu::acp::GetModule(),
|
||||||
iosu::fpd::GetModule(),
|
iosu::fpd::GetModule(),
|
||||||
iosu::pdm::GetModule(),
|
iosu::pdm::GetModule(),
|
||||||
};
|
};
|
||||||
|
@ -8,10 +8,19 @@
|
|||||||
#include "Cafe/OS/libs/nn_acp/nn_acp.h"
|
#include "Cafe/OS/libs/nn_acp/nn_acp.h"
|
||||||
#include "Cafe/OS/libs/coreinit/coreinit_FS.h"
|
#include "Cafe/OS/libs/coreinit/coreinit_FS.h"
|
||||||
#include "Cafe/Filesystem/fsc.h"
|
#include "Cafe/Filesystem/fsc.h"
|
||||||
#include "Cafe/HW/Espresso/PPCState.h"
|
//#include "Cafe/HW/Espresso/PPCState.h"
|
||||||
|
|
||||||
|
#include "Cafe/IOSU/iosu_types_common.h"
|
||||||
|
#include "Cafe/IOSU/nn/iosu_nn_service.h"
|
||||||
|
|
||||||
|
#include "Cafe/IOSU/legacy/iosu_act.h"
|
||||||
|
#include "Cafe/CafeSystem.h"
|
||||||
|
#include "config/ActiveSettings.h"
|
||||||
|
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
using ACPDeviceType = iosu::acp::ACPDeviceType;
|
||||||
|
|
||||||
static_assert(sizeof(acpMetaXml_t) == 0x3440);
|
static_assert(sizeof(acpMetaXml_t) == 0x3440);
|
||||||
static_assert(offsetof(acpMetaXml_t, title_id) == 0x0000);
|
static_assert(offsetof(acpMetaXml_t, title_id) == 0x0000);
|
||||||
static_assert(offsetof(acpMetaXml_t, boss_id) == 0x0008);
|
static_assert(offsetof(acpMetaXml_t, boss_id) == 0x0008);
|
||||||
@ -506,48 +515,6 @@ namespace iosu
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
sint32 ACPCreateSaveDirEx(uint8 accountSlot, uint64 titleId)
|
|
||||||
{
|
|
||||||
uint32 persistentId = 0;
|
|
||||||
nn::save::GetPersistentIdEx(accountSlot, &persistentId);
|
|
||||||
|
|
||||||
uint32 high = GetTitleIdHigh(titleId) & (~0xC);
|
|
||||||
uint32 low = GetTitleIdLow(titleId);
|
|
||||||
|
|
||||||
sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND;
|
|
||||||
char path[256];
|
|
||||||
|
|
||||||
sprintf(path, "%susr/boss/", "/vol/storage_mlc01/");
|
|
||||||
fsc_createDir(path, &fscStatus);
|
|
||||||
sprintf(path, "%susr/boss/%08x/", "/vol/storage_mlc01/", high);
|
|
||||||
fsc_createDir(path, &fscStatus);
|
|
||||||
sprintf(path, "%susr/boss/%08x/%08x/", "/vol/storage_mlc01/", high, low);
|
|
||||||
fsc_createDir(path, &fscStatus);
|
|
||||||
sprintf(path, "%susr/boss/%08x/%08x/user/", "/vol/storage_mlc01/", high, low);
|
|
||||||
fsc_createDir(path, &fscStatus);
|
|
||||||
sprintf(path, "%susr/boss/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low);
|
|
||||||
fsc_createDir(path, &fscStatus);
|
|
||||||
sprintf(path, "%susr/boss/%08x/%08x/user/%08x/", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId);
|
|
||||||
fsc_createDir(path, &fscStatus);
|
|
||||||
|
|
||||||
sprintf(path, "%susr/save/%08x/", "/vol/storage_mlc01/", high);
|
|
||||||
fsc_createDir(path, &fscStatus);
|
|
||||||
sprintf(path, "%susr/save/%08x/%08x/", "/vol/storage_mlc01/", high, low);
|
|
||||||
fsc_createDir(path, &fscStatus);
|
|
||||||
sprintf(path, "%susr/save/%08x/%08x/meta/", "/vol/storage_mlc01/", high, low);
|
|
||||||
fsc_createDir(path, &fscStatus);
|
|
||||||
sprintf(path, "%susr/save/%08x/%08x/user/", "/vol/storage_mlc01/", high, low);
|
|
||||||
fsc_createDir(path, &fscStatus);
|
|
||||||
sprintf(path, "%susr/save/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low);
|
|
||||||
fsc_createDir(path, &fscStatus);
|
|
||||||
sprintf(path, "%susr/save/%08x/%08x/user/%08x", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId);
|
|
||||||
fsc_createDir(path, &fscStatus);
|
|
||||||
|
|
||||||
// copy xml meta files
|
|
||||||
nn::acp::CreateSaveMetaFiles(persistentId, titleId);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int iosuAcp_thread()
|
int iosuAcp_thread()
|
||||||
{
|
{
|
||||||
SetThreadName("iosuAcp_thread");
|
SetThreadName("iosuAcp_thread");
|
||||||
@ -584,7 +551,7 @@ namespace iosu
|
|||||||
}
|
}
|
||||||
else if (acpCemuRequest->requestCode == IOSU_ACP_CREATE_SAVE_DIR_EX)
|
else if (acpCemuRequest->requestCode == IOSU_ACP_CREATE_SAVE_DIR_EX)
|
||||||
{
|
{
|
||||||
acpCemuRequest->returnCode = ACPCreateSaveDirEx(acpCemuRequest->accountSlot, acpCemuRequest->titleId);
|
acpCemuRequest->returnCode = acp::ACPCreateSaveDirEx(acpCemuRequest->accountSlot, acpCemuRequest->titleId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
cemu_assert_unimplemented();
|
cemu_assert_unimplemented();
|
||||||
@ -610,5 +577,237 @@ namespace iosu
|
|||||||
return iosuAcp.isInitialized;
|
return iosuAcp.isInitialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Above is the legacy implementation. Below is the new style implementation which also matches the official IPC protocol and works with the real nn_acp.rpl */
|
||||||
|
|
||||||
}
|
namespace acp
|
||||||
|
{
|
||||||
|
|
||||||
|
uint64 _ACPGetTimestamp()
|
||||||
|
{
|
||||||
|
return coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
nnResult ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType)
|
||||||
|
{
|
||||||
|
if (deviceType == ACPDeviceType::UnknownType)
|
||||||
|
{
|
||||||
|
return (nnResult)0xA030FB80;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create or modify the saveinfo
|
||||||
|
const auto saveinfoPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/saveinfo.xml", GetTitleIdHigh(titleId), GetTitleIdLow(titleId));
|
||||||
|
auto saveinfoData = FileStream::LoadIntoMemory(saveinfoPath);
|
||||||
|
if (saveinfoData && !saveinfoData->empty())
|
||||||
|
{
|
||||||
|
namespace xml = tinyxml2;
|
||||||
|
xml::XMLDocument doc;
|
||||||
|
tinyxml2::XMLError xmlError = doc.Parse((const char*)saveinfoData->data(), saveinfoData->size());
|
||||||
|
if (xmlError == xml::XML_SUCCESS || xmlError == xml::XML_ERROR_EMPTY_DOCUMENT)
|
||||||
|
{
|
||||||
|
xml::XMLNode* child = doc.FirstChild();
|
||||||
|
// check for declaration -> <?xml version="1.0" encoding="utf-8"?>
|
||||||
|
if (!child || !child->ToDeclaration())
|
||||||
|
{
|
||||||
|
xml::XMLDeclaration* decl = doc.NewDeclaration();
|
||||||
|
doc.InsertFirstChild(decl);
|
||||||
|
}
|
||||||
|
|
||||||
|
xml::XMLElement* info = doc.FirstChildElement("info");
|
||||||
|
if (!info)
|
||||||
|
{
|
||||||
|
info = doc.NewElement("info");
|
||||||
|
doc.InsertEndChild(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
// find node with persistentId
|
||||||
|
char tmp[64];
|
||||||
|
sprintf(tmp, "%08x", persistentId);
|
||||||
|
bool foundNode = false;
|
||||||
|
for (xml::XMLElement* account = info->FirstChildElement("account"); account; account = account->NextSiblingElement("account"))
|
||||||
|
{
|
||||||
|
if (account->Attribute("persistentId", tmp))
|
||||||
|
{
|
||||||
|
// found the entry! -> update timestamp
|
||||||
|
xml::XMLElement* timestamp = account->FirstChildElement("timestamp");
|
||||||
|
sprintf(tmp, "%" PRIx64, _ACPGetTimestamp());
|
||||||
|
if (timestamp)
|
||||||
|
timestamp->SetText(tmp);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
timestamp = doc.NewElement("timestamp");
|
||||||
|
account->InsertFirstChild(timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
foundNode = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundNode)
|
||||||
|
{
|
||||||
|
tinyxml2::XMLElement* account = doc.NewElement("account");
|
||||||
|
{
|
||||||
|
sprintf(tmp, "%08x", persistentId);
|
||||||
|
account->SetAttribute("persistentId", tmp);
|
||||||
|
|
||||||
|
tinyxml2::XMLElement* timestamp = doc.NewElement("timestamp");
|
||||||
|
{
|
||||||
|
sprintf(tmp, "%" PRIx64, _ACPGetTimestamp());
|
||||||
|
timestamp->SetText(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
account->InsertFirstChild(timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
info->InsertFirstChild(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update file
|
||||||
|
tinyxml2::XMLPrinter printer;
|
||||||
|
doc.Print(&printer);
|
||||||
|
FileStream* fs = FileStream::createFile2(saveinfoPath);
|
||||||
|
if (fs)
|
||||||
|
{
|
||||||
|
fs->writeString(printer.CStr());
|
||||||
|
delete fs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NN_RESULT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateSaveMetaFiles(uint32 persistentId, uint64 titleId)
|
||||||
|
{
|
||||||
|
std::string titlePath = CafeSystem::GetMlcStoragePath(CafeSystem::GetForegroundTitleId());
|
||||||
|
|
||||||
|
sint32 fscStatus;
|
||||||
|
FSCVirtualFile* fscFile = fsc_open((titlePath + "/meta/meta.xml").c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus);
|
||||||
|
if (fscFile)
|
||||||
|
{
|
||||||
|
sint32 fileSize = fsc_getFileSize(fscFile);
|
||||||
|
|
||||||
|
std::unique_ptr<uint8[]> fileContent = std::make_unique<uint8[]>(fileSize);
|
||||||
|
fsc_readFile(fscFile, fileContent.get(), fileSize);
|
||||||
|
fsc_close(fscFile);
|
||||||
|
|
||||||
|
const auto outPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/meta.xml", GetTitleIdHigh(titleId), GetTitleIdLow(titleId));
|
||||||
|
|
||||||
|
std::ofstream myFile(outPath, std::ios::out | std::ios::binary);
|
||||||
|
myFile.write((char*)fileContent.get(), fileSize);
|
||||||
|
myFile.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
fscFile = fsc_open((titlePath + "/meta/iconTex.tga").c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus);
|
||||||
|
if (fscFile)
|
||||||
|
{
|
||||||
|
sint32 fileSize = fsc_getFileSize(fscFile);
|
||||||
|
|
||||||
|
std::unique_ptr<uint8[]> fileContent = std::make_unique<uint8[]>(fileSize);
|
||||||
|
fsc_readFile(fscFile, fileContent.get(), fileSize);
|
||||||
|
fsc_close(fscFile);
|
||||||
|
|
||||||
|
const auto outPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/iconTex.tga", GetTitleIdHigh(titleId), GetTitleIdLow(titleId));
|
||||||
|
|
||||||
|
std::ofstream myFile(outPath, std::ios::out | std::ios::binary);
|
||||||
|
myFile.write((char*)fileContent.get(), fileSize);
|
||||||
|
myFile.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
ACPUpdateSaveTimeStamp(persistentId, titleId, iosu::acp::ACPDeviceType::InternalDeviceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sint32 _ACPCreateSaveDir(uint32 persistentId, uint64 titleId, ACPDeviceType type)
|
||||||
|
{
|
||||||
|
uint32 high = GetTitleIdHigh(titleId) & (~0xC);
|
||||||
|
uint32 low = GetTitleIdLow(titleId);
|
||||||
|
|
||||||
|
sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND;
|
||||||
|
char path[256];
|
||||||
|
|
||||||
|
sprintf(path, "%susr/boss/", "/vol/storage_mlc01/");
|
||||||
|
fsc_createDir(path, &fscStatus);
|
||||||
|
sprintf(path, "%susr/boss/%08x/", "/vol/storage_mlc01/", high);
|
||||||
|
fsc_createDir(path, &fscStatus);
|
||||||
|
sprintf(path, "%susr/boss/%08x/%08x/", "/vol/storage_mlc01/", high, low);
|
||||||
|
fsc_createDir(path, &fscStatus);
|
||||||
|
sprintf(path, "%susr/boss/%08x/%08x/user/", "/vol/storage_mlc01/", high, low);
|
||||||
|
fsc_createDir(path, &fscStatus);
|
||||||
|
sprintf(path, "%susr/boss/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low);
|
||||||
|
fsc_createDir(path, &fscStatus);
|
||||||
|
sprintf(path, "%susr/boss/%08x/%08x/user/%08x/", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId);
|
||||||
|
fsc_createDir(path, &fscStatus);
|
||||||
|
|
||||||
|
sprintf(path, "%susr/save/%08x/", "/vol/storage_mlc01/", high);
|
||||||
|
fsc_createDir(path, &fscStatus);
|
||||||
|
sprintf(path, "%susr/save/%08x/%08x/", "/vol/storage_mlc01/", high, low);
|
||||||
|
fsc_createDir(path, &fscStatus);
|
||||||
|
sprintf(path, "%susr/save/%08x/%08x/meta/", "/vol/storage_mlc01/", high, low);
|
||||||
|
fsc_createDir(path, &fscStatus);
|
||||||
|
sprintf(path, "%susr/save/%08x/%08x/user/", "/vol/storage_mlc01/", high, low);
|
||||||
|
fsc_createDir(path, &fscStatus);
|
||||||
|
sprintf(path, "%susr/save/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low);
|
||||||
|
fsc_createDir(path, &fscStatus);
|
||||||
|
sprintf(path, "%susr/save/%08x/%08x/user/%08x", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId);
|
||||||
|
fsc_createDir(path, &fscStatus);
|
||||||
|
|
||||||
|
// copy xml meta files
|
||||||
|
CreateSaveMetaFiles(persistentId, titleId);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
nnResult ACPCreateSaveDir(uint32 persistentId, ACPDeviceType type)
|
||||||
|
{
|
||||||
|
uint64 titleId = CafeSystem::GetForegroundTitleId();
|
||||||
|
return _ACPCreateSaveDir(persistentId, titleId, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
sint32 ACPCreateSaveDirEx(uint8 accountSlot, uint64 titleId)
|
||||||
|
{
|
||||||
|
uint32 persistentId = 0;
|
||||||
|
cemu_assert_debug(accountSlot >= 1 && accountSlot <= 13); // outside valid slot range?
|
||||||
|
bool r = iosu::act::GetPersistentId(accountSlot, &persistentId);
|
||||||
|
cemu_assert_debug(r);
|
||||||
|
return _ACPCreateSaveDir(persistentId, titleId, ACPDeviceType::InternalDeviceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
nnResult ACPGetOlvAccesskey(uint32be* accessKey)
|
||||||
|
{
|
||||||
|
*accessKey = CafeSystem::GetForegroundTitleOlvAccesskey();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AcpMainService : public iosu::nn::IPCService
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AcpMainService() : iosu::nn::IPCService("/dev/acp_main") {}
|
||||||
|
|
||||||
|
nnResult ServiceCall(uint32 serviceId, void* request, void* response) override
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "Unsupported service call to /dev/acp_main");
|
||||||
|
cemu_assert_unimplemented();
|
||||||
|
return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACP, 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AcpMainService gACPMainService;
|
||||||
|
|
||||||
|
class : public ::IOSUModule
|
||||||
|
{
|
||||||
|
void TitleStart() override
|
||||||
|
{
|
||||||
|
gACPMainService.Start();
|
||||||
|
// gACPMainService.SetTimerUpdate(1000); // call TimerUpdate() once a second
|
||||||
|
}
|
||||||
|
void TitleStop() override
|
||||||
|
{
|
||||||
|
gACPMainService.Stop();
|
||||||
|
}
|
||||||
|
}sIOSUModuleNNACP;
|
||||||
|
|
||||||
|
IOSUModule* GetModule()
|
||||||
|
{
|
||||||
|
return static_cast<IOSUModule*>(&sIOSUModuleNNACP);
|
||||||
|
}
|
||||||
|
} // namespace acp
|
||||||
|
} // namespace iosu
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "Cafe/IOSU/iosu_types_common.h"
|
||||||
|
#include "Cafe/OS/libs/nn_common.h" // for nnResult
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
/* +0x0000 */ uint64 title_id; // parsed via GetHex64
|
/* +0x0000 */ uint64 title_id; // parsed via GetHex64
|
||||||
@ -192,4 +195,24 @@ typedef struct
|
|||||||
namespace iosu
|
namespace iosu
|
||||||
{
|
{
|
||||||
void iosuAcp_init();
|
void iosuAcp_init();
|
||||||
|
|
||||||
|
namespace acp
|
||||||
|
{
|
||||||
|
enum ACPDeviceType
|
||||||
|
{
|
||||||
|
UnknownType = 0,
|
||||||
|
InternalDeviceType = 1,
|
||||||
|
USBDeviceType = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
class IOSUModule* GetModule();
|
||||||
|
|
||||||
|
void CreateSaveMetaFiles(uint32 persistentId, uint64 titleId);
|
||||||
|
nnResult ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType);
|
||||||
|
|
||||||
|
nnResult ACPCreateSaveDir(uint32 persistentId, ACPDeviceType type);
|
||||||
|
sint32 ACPCreateSaveDirEx(uint8 accountSlot, uint64 titleId);
|
||||||
|
nnResult ACPGetOlvAccesskey(uint32be* accessKey);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -240,6 +240,18 @@ namespace iosu
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GetPersistentId(uint8 slot, uint32* persistentId)
|
||||||
|
{
|
||||||
|
sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot);
|
||||||
|
if(!_actAccountData[accountIndex].isValid)
|
||||||
|
{
|
||||||
|
*persistentId = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*persistentId = _actAccountData[accountIndex].persistentId;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
class ActService : public iosu::nn::IPCService
|
class ActService : public iosu::nn::IPCService
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -49,6 +49,7 @@ namespace iosu
|
|||||||
bool getMii(uint8 slot, FFLData_t* fflData);
|
bool getMii(uint8 slot, FFLData_t* fflData);
|
||||||
bool getScreenname(uint8 slot, uint16 screenname[ACT_NICKNAME_LENGTH]);
|
bool getScreenname(uint8 slot, uint16 screenname[ACT_NICKNAME_LENGTH]);
|
||||||
bool getCountryIndex(uint8 slot, uint32* countryIndex);
|
bool getCountryIndex(uint8 slot, uint32* countryIndex);
|
||||||
|
bool GetPersistentId(uint8 slot, uint32* persistentId);
|
||||||
|
|
||||||
std::string getAccountId2(uint8 slot);
|
std::string getAccountId2(uint8 slot);
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ namespace iosu
|
|||||||
{
|
{
|
||||||
namespace nn
|
namespace nn
|
||||||
{
|
{
|
||||||
// a simple service interface which wraps handle management and Ioctlv/IoctlvAsync
|
// a simple service interface which wraps handle management and Ioctlv/IoctlvAsync (used by /dev/fpd and others are still to be determined)
|
||||||
class IPCSimpleService
|
class IPCSimpleService
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -88,7 +88,7 @@ namespace iosu
|
|||||||
uint32be nnResultCode;
|
uint32be nnResultCode;
|
||||||
};
|
};
|
||||||
|
|
||||||
// a complex service interface which wraps Ioctlv and adds an additional service channel, used by /dev/act, ?
|
// a complex service interface which wraps Ioctlv and adds an additional service channel, used by /dev/act, /dev/acp_main, ?
|
||||||
class IPCService
|
class IPCService
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
#include "Common/FileStream.h"
|
#include "Common/FileStream.h"
|
||||||
#include "Cafe/CafeSystem.h"
|
#include "Cafe/CafeSystem.h"
|
||||||
|
|
||||||
|
using ACPDeviceType = iosu::acp::ACPDeviceType;
|
||||||
|
|
||||||
#define acpPrepareRequest() \
|
#define acpPrepareRequest() \
|
||||||
StackAllocator<iosuAcpCemuRequest_t> _buf_acpRequest; \
|
StackAllocator<iosuAcpCemuRequest_t> _buf_acpRequest; \
|
||||||
StackAllocator<ioBufferVector_t> _buf_bufferVector; \
|
StackAllocator<ioBufferVector_t> _buf_bufferVector; \
|
||||||
@ -30,12 +32,14 @@ namespace nn
|
|||||||
{
|
{
|
||||||
namespace acp
|
namespace acp
|
||||||
{
|
{
|
||||||
ACPStatus _ACPConvertResultToACPStatus(uint32* nnResult, const char* functionName, uint32 someConstant)
|
ACPStatus ACPConvertResultToACPStatus(uint32* nnResult, const char* functionName, uint32 lineNumber)
|
||||||
{
|
{
|
||||||
// todo
|
// todo
|
||||||
return ACPStatus::SUCCESS;
|
return ACPStatus::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define _ACPConvertResultToACPStatus(nnResult) ACPConvertResultToACPStatus(nnResult, __func__, __LINE__)
|
||||||
|
|
||||||
ACPStatus ACPGetApplicationBox(uint32be* applicationBox, uint64 titleId)
|
ACPStatus ACPGetApplicationBox(uint32be* applicationBox, uint64 titleId)
|
||||||
{
|
{
|
||||||
// todo
|
// todo
|
||||||
@ -43,6 +47,12 @@ namespace acp
|
|||||||
return ACPStatus::SUCCESS;
|
return ACPStatus::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ACPStatus ACPGetOlvAccesskey(uint32be* accessKey)
|
||||||
|
{
|
||||||
|
nnResult r = iosu::acp::ACPGetOlvAccesskey(accessKey);
|
||||||
|
return _ACPConvertResultToACPStatus(&r);
|
||||||
|
}
|
||||||
|
|
||||||
bool sSaveDirMounted{false};
|
bool sSaveDirMounted{false};
|
||||||
|
|
||||||
ACPStatus ACPMountSaveDir()
|
ACPStatus ACPMountSaveDir()
|
||||||
@ -56,7 +66,7 @@ namespace acp
|
|||||||
const auto mlc = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/", high, low);
|
const auto mlc = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/", high, low);
|
||||||
FSCDeviceHostFS_Mount("/vol/save/", _pathToUtf8(mlc), FSC_PRIORITY_BASE);
|
FSCDeviceHostFS_Mount("/vol/save/", _pathToUtf8(mlc), FSC_PRIORITY_BASE);
|
||||||
nnResult mountResult = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACP, 0);
|
nnResult mountResult = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACP, 0);
|
||||||
return _ACPConvertResultToACPStatus(&mountResult, "ACPMountSaveDir", 0x60);
|
return _ACPConvertResultToACPStatus(&mountResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
ACPStatus ACPUnmountSaveDir()
|
ACPStatus ACPUnmountSaveDir()
|
||||||
@ -66,201 +76,24 @@ namespace acp
|
|||||||
return ACPStatus::SUCCESS;
|
return ACPStatus::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64 _acpGetTimestamp()
|
|
||||||
{
|
|
||||||
return coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK;
|
|
||||||
}
|
|
||||||
|
|
||||||
nnResult __ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType)
|
|
||||||
{
|
|
||||||
if (deviceType == UnknownType)
|
|
||||||
{
|
|
||||||
return (nnResult)0xA030FB80;
|
|
||||||
}
|
|
||||||
|
|
||||||
// create or modify the saveinfo
|
|
||||||
const auto saveinfoPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/saveinfo.xml", GetTitleIdHigh(titleId), GetTitleIdLow(titleId));
|
|
||||||
auto saveinfoData = FileStream::LoadIntoMemory(saveinfoPath);
|
|
||||||
if (saveinfoData && !saveinfoData->empty())
|
|
||||||
{
|
|
||||||
namespace xml = tinyxml2;
|
|
||||||
xml::XMLDocument doc;
|
|
||||||
tinyxml2::XMLError xmlError = doc.Parse((const char*)saveinfoData->data(), saveinfoData->size());
|
|
||||||
if (xmlError == xml::XML_SUCCESS || xmlError == xml::XML_ERROR_EMPTY_DOCUMENT)
|
|
||||||
{
|
|
||||||
xml::XMLNode* child = doc.FirstChild();
|
|
||||||
// check for declaration -> <?xml version="1.0" encoding="utf-8"?>
|
|
||||||
if (!child || !child->ToDeclaration())
|
|
||||||
{
|
|
||||||
xml::XMLDeclaration* decl = doc.NewDeclaration();
|
|
||||||
doc.InsertFirstChild(decl);
|
|
||||||
}
|
|
||||||
|
|
||||||
xml::XMLElement* info = doc.FirstChildElement("info");
|
|
||||||
if (!info)
|
|
||||||
{
|
|
||||||
info = doc.NewElement("info");
|
|
||||||
doc.InsertEndChild(info);
|
|
||||||
}
|
|
||||||
|
|
||||||
// find node with persistentId
|
|
||||||
char tmp[64];
|
|
||||||
sprintf(tmp, "%08x", persistentId);
|
|
||||||
bool foundNode = false;
|
|
||||||
for (xml::XMLElement* account = info->FirstChildElement("account"); account; account = account->NextSiblingElement("account"))
|
|
||||||
{
|
|
||||||
if (account->Attribute("persistentId", tmp))
|
|
||||||
{
|
|
||||||
// found the entry! -> update timestamp
|
|
||||||
xml::XMLElement* timestamp = account->FirstChildElement("timestamp");
|
|
||||||
sprintf(tmp, "%" PRIx64, _acpGetTimestamp());
|
|
||||||
if (timestamp)
|
|
||||||
timestamp->SetText(tmp);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
timestamp = doc.NewElement("timestamp");
|
|
||||||
account->InsertFirstChild(timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
foundNode = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!foundNode)
|
|
||||||
{
|
|
||||||
tinyxml2::XMLElement* account = doc.NewElement("account");
|
|
||||||
{
|
|
||||||
sprintf(tmp, "%08x", persistentId);
|
|
||||||
account->SetAttribute("persistentId", tmp);
|
|
||||||
|
|
||||||
tinyxml2::XMLElement* timestamp = doc.NewElement("timestamp");
|
|
||||||
{
|
|
||||||
sprintf(tmp, "%" PRIx64, _acpGetTimestamp());
|
|
||||||
timestamp->SetText(tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
account->InsertFirstChild(timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
info->InsertFirstChild(account);
|
|
||||||
}
|
|
||||||
|
|
||||||
// update file
|
|
||||||
tinyxml2::XMLPrinter printer;
|
|
||||||
doc.Print(&printer);
|
|
||||||
FileStream* fs = FileStream::createFile2(saveinfoPath);
|
|
||||||
if (fs)
|
|
||||||
{
|
|
||||||
fs->writeString(printer.CStr());
|
|
||||||
delete fs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NN_RESULT_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
ACPStatus ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType)
|
ACPStatus ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType)
|
||||||
{
|
{
|
||||||
nnResult r = __ACPUpdateSaveTimeStamp(persistentId, titleId, deviceType);
|
nnResult r = iosu::acp::ACPUpdateSaveTimeStamp(persistentId, titleId, deviceType);
|
||||||
return ACPStatus::SUCCESS;
|
return ACPStatus::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreateSaveMetaFiles(uint32 persistentId, uint64 titleId)
|
|
||||||
{
|
|
||||||
std::string titlePath = CafeSystem::GetMlcStoragePath(CafeSystem::GetForegroundTitleId());
|
|
||||||
|
|
||||||
sint32 fscStatus;
|
|
||||||
FSCVirtualFile* fscFile = fsc_open((titlePath + "/meta/meta.xml").c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus);
|
|
||||||
if (fscFile)
|
|
||||||
{
|
|
||||||
sint32 fileSize = fsc_getFileSize(fscFile);
|
|
||||||
|
|
||||||
std::unique_ptr<uint8[]> fileContent = std::make_unique<uint8[]>(fileSize);
|
|
||||||
fsc_readFile(fscFile, fileContent.get(), fileSize);
|
|
||||||
fsc_close(fscFile);
|
|
||||||
|
|
||||||
const auto outPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/meta.xml", GetTitleIdHigh(titleId), GetTitleIdLow(titleId));
|
|
||||||
|
|
||||||
std::ofstream myFile(outPath, std::ios::out | std::ios::binary);
|
|
||||||
myFile.write((char*)fileContent.get(), fileSize);
|
|
||||||
myFile.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
fscFile = fsc_open((titlePath + "/meta/iconTex.tga").c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus);
|
|
||||||
if (fscFile)
|
|
||||||
{
|
|
||||||
sint32 fileSize = fsc_getFileSize(fscFile);
|
|
||||||
|
|
||||||
std::unique_ptr<uint8[]> fileContent = std::make_unique<uint8[]>(fileSize);
|
|
||||||
fsc_readFile(fscFile, fileContent.get(), fileSize);
|
|
||||||
fsc_close(fscFile);
|
|
||||||
|
|
||||||
const auto outPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/iconTex.tga", GetTitleIdHigh(titleId), GetTitleIdLow(titleId));
|
|
||||||
|
|
||||||
std::ofstream myFile(outPath, std::ios::out | std::ios::binary);
|
|
||||||
myFile.write((char*)fileContent.get(), fileSize);
|
|
||||||
myFile.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
ACPUpdateSaveTimeStamp(persistentId, titleId, InternalDeviceType);
|
|
||||||
}
|
|
||||||
|
|
||||||
nnResult CreateSaveDir(uint32 persistentId, ACPDeviceType type)
|
|
||||||
{
|
|
||||||
uint64 titleId = CafeSystem::GetForegroundTitleId();
|
|
||||||
uint32 high = GetTitleIdHigh(titleId) & (~0xC);
|
|
||||||
uint32 low = GetTitleIdLow(titleId);
|
|
||||||
|
|
||||||
sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND;
|
|
||||||
char path[256];
|
|
||||||
|
|
||||||
sprintf(path, "%susr/save/%08x/", "/vol/storage_mlc01/", high);
|
|
||||||
fsc_createDir(path, &fscStatus);
|
|
||||||
sprintf(path, "%susr/save/%08x/%08x/", "/vol/storage_mlc01/", high, low);
|
|
||||||
fsc_createDir(path, &fscStatus);
|
|
||||||
sprintf(path, "%susr/save/%08x/%08x/meta/", "/vol/storage_mlc01/", high, low);
|
|
||||||
fsc_createDir(path, &fscStatus);
|
|
||||||
sprintf(path, "%susr/save/%08x/%08x/user/", "/vol/storage_mlc01/", high, low);
|
|
||||||
fsc_createDir(path, &fscStatus);
|
|
||||||
sprintf(path, "%susr/save/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low);
|
|
||||||
fsc_createDir(path, &fscStatus);
|
|
||||||
sprintf(path, "%susr/save/%08x/%08x/user/%08x", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId);
|
|
||||||
fsc_createDir(path, &fscStatus);
|
|
||||||
|
|
||||||
// not sure about this
|
|
||||||
sprintf(path, "%susr/boss/", "/vol/storage_mlc01/");
|
|
||||||
fsc_createDir(path, &fscStatus);
|
|
||||||
sprintf(path, "%susr/boss/%08x/", "/vol/storage_mlc01/", high);
|
|
||||||
fsc_createDir(path, &fscStatus);
|
|
||||||
sprintf(path, "%susr/boss/%08x/%08x/", "/vol/storage_mlc01/", high, low);
|
|
||||||
fsc_createDir(path, &fscStatus);
|
|
||||||
sprintf(path, "%susr/boss/%08x/%08x/user/", "/vol/storage_mlc01/", high, low);
|
|
||||||
fsc_createDir(path, &fscStatus);
|
|
||||||
sprintf(path, "%susr/boss/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low);
|
|
||||||
fsc_createDir(path, &fscStatus);
|
|
||||||
sprintf(path, "%susr/boss/%08x/%08x/user/%08x/", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId);
|
|
||||||
fsc_createDir(path, &fscStatus);
|
|
||||||
|
|
||||||
// copy xml meta files
|
|
||||||
CreateSaveMetaFiles(persistentId, titleId);
|
|
||||||
|
|
||||||
nnResult result = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACP, 0);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
ACPStatus ACPCreateSaveDir(uint32 persistentId, ACPDeviceType type)
|
|
||||||
{
|
|
||||||
nnResult result = CreateSaveDir(persistentId, type);
|
|
||||||
return _ACPConvertResultToACPStatus(&result, "ACPCreateSaveDir", 0x2FA);
|
|
||||||
}
|
|
||||||
|
|
||||||
ACPStatus ACPCheckApplicationDeviceEmulation(uint32be* isEmulated)
|
ACPStatus ACPCheckApplicationDeviceEmulation(uint32be* isEmulated)
|
||||||
{
|
{
|
||||||
*isEmulated = 0;
|
*isEmulated = 0;
|
||||||
return ACPStatus::SUCCESS;
|
return ACPStatus::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ACPStatus ACPCreateSaveDir(uint32 persistentId, ACPDeviceType type)
|
||||||
|
{
|
||||||
|
nnResult result = iosu::acp::ACPCreateSaveDir(persistentId, type);
|
||||||
|
return _ACPConvertResultToACPStatus(&result);
|
||||||
|
}
|
||||||
|
|
||||||
nnResult ACPCreateSaveDirEx(uint8 accountSlot, uint64 titleId)
|
nnResult ACPCreateSaveDirEx(uint8 accountSlot, uint64 titleId)
|
||||||
{
|
{
|
||||||
acpPrepareRequest();
|
acpPrepareRequest();
|
||||||
@ -279,7 +112,7 @@ namespace acp
|
|||||||
ppcDefineParamU8(accountSlot, 0);
|
ppcDefineParamU8(accountSlot, 0);
|
||||||
ppcDefineParamU64(titleId, 2); // index 2 because of alignment -> guessed parameters
|
ppcDefineParamU64(titleId, 2); // index 2 because of alignment -> guessed parameters
|
||||||
nnResult result = ACPCreateSaveDirEx(accountSlot, titleId);
|
nnResult result = ACPCreateSaveDirEx(accountSlot, titleId);
|
||||||
osLib_returnFromFunction(hCPU, _ACPConvertResultToACPStatus(&result, "ACPCreateSaveDirEx", 0x300));
|
osLib_returnFromFunction(hCPU, _ACPConvertResultToACPStatus(&result));
|
||||||
}
|
}
|
||||||
|
|
||||||
void export_ACPGetSaveDataTitleIdList(PPCInterpreter_t* hCPU)
|
void export_ACPGetSaveDataTitleIdList(PPCInterpreter_t* hCPU)
|
||||||
@ -511,6 +344,8 @@ namespace acp
|
|||||||
|
|
||||||
cafeExportRegister("nn_acp", ACPGetApplicationBox, LogType::Placeholder);
|
cafeExportRegister("nn_acp", ACPGetApplicationBox, LogType::Placeholder);
|
||||||
|
|
||||||
|
cafeExportRegister("nn_acp", ACPGetOlvAccesskey, LogType::Placeholder);
|
||||||
|
|
||||||
osLib_addFunction("nn_acp", "ACPIsOverAgeEx", export_ACPIsOverAgeEx);
|
osLib_addFunction("nn_acp", "ACPIsOverAgeEx", export_ACPIsOverAgeEx);
|
||||||
|
|
||||||
osLib_addFunction("nn_acp", "ACPGetNetworkTime", export_ACPGetNetworkTime);
|
osLib_addFunction("nn_acp", "ACPGetNetworkTime", export_ACPGetNetworkTime);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "Cafe/IOSU/legacy/iosu_acp.h"
|
||||||
|
|
||||||
namespace nn
|
namespace nn
|
||||||
{
|
{
|
||||||
@ -9,20 +10,13 @@ namespace acp
|
|||||||
SUCCESS = 0,
|
SUCCESS = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ACPDeviceType
|
using ACPDeviceType = iosu::acp::ACPDeviceType;
|
||||||
{
|
|
||||||
UnknownType = 0,
|
|
||||||
InternalDeviceType = 1,
|
|
||||||
USBDeviceType = 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
void CreateSaveMetaFiles(uint32 persistentId, uint64 titleId);
|
|
||||||
|
|
||||||
ACPStatus ACPGetApplicationBox(uint32be* applicationBox, uint64 titleId);
|
ACPStatus ACPGetApplicationBox(uint32be* applicationBox, uint64 titleId);
|
||||||
ACPStatus ACPMountSaveDir();
|
ACPStatus ACPMountSaveDir();
|
||||||
ACPStatus ACPUnmountSaveDir();
|
ACPStatus ACPUnmountSaveDir();
|
||||||
ACPStatus ACPCreateSaveDir(uint32 persistentId, ACPDeviceType type);
|
ACPStatus ACPCreateSaveDir(uint32 persistentId, iosu::acp::ACPDeviceType type);
|
||||||
ACPStatus ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType);;
|
ACPStatus ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, iosu::acp::ACPDeviceType deviceType);
|
||||||
|
|
||||||
void load();
|
void load();
|
||||||
}
|
}
|
||||||
|
@ -72,11 +72,11 @@ namespace save
|
|||||||
return result != 0;
|
return result != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GetCurrentTitleApplicationBox(acp::ACPDeviceType* deviceType)
|
bool GetCurrentTitleApplicationBox(nn::acp::ACPDeviceType* deviceType)
|
||||||
{
|
{
|
||||||
if (deviceType)
|
if (deviceType)
|
||||||
{
|
{
|
||||||
*deviceType = acp::InternalDeviceType;
|
*deviceType = nn::acp::ACPDeviceType::InternalDeviceType;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -84,7 +84,7 @@ namespace save
|
|||||||
|
|
||||||
void UpdateSaveTimeStamp(uint32 persistentId)
|
void UpdateSaveTimeStamp(uint32 persistentId)
|
||||||
{
|
{
|
||||||
acp::ACPDeviceType deviceType;
|
nn::acp::ACPDeviceType deviceType;
|
||||||
if (GetCurrentTitleApplicationBox(&deviceType))
|
if (GetCurrentTitleApplicationBox(&deviceType))
|
||||||
ACPUpdateSaveTimeStamp(persistentId, CafeSystem::GetForegroundTitleId(), deviceType);
|
ACPUpdateSaveTimeStamp(persistentId, CafeSystem::GetForegroundTitleId(), deviceType);
|
||||||
}
|
}
|
||||||
@ -314,7 +314,7 @@ namespace save
|
|||||||
sprintf(path, "%susr/save/%08x/%08x/meta/", "/vol/storage_mlc01/", high, low);
|
sprintf(path, "%susr/save/%08x/%08x/meta/", "/vol/storage_mlc01/", high, low);
|
||||||
fsc_createDir(path, &fscStatus);
|
fsc_createDir(path, &fscStatus);
|
||||||
|
|
||||||
acp::CreateSaveMetaFiles(ActiveSettings::GetPersistentId(), titleId);
|
iosu::acp::CreateSaveMetaFiles(ActiveSettings::GetPersistentId(), titleId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return SAVE_STATUS_OK;
|
return SAVE_STATUS_OK;
|
||||||
@ -669,7 +669,7 @@ namespace save
|
|||||||
uint32 persistentId;
|
uint32 persistentId;
|
||||||
if (GetPersistentIdEx(accountSlot, &persistentId))
|
if (GetPersistentIdEx(accountSlot, &persistentId))
|
||||||
{
|
{
|
||||||
acp::ACPStatus status = ACPCreateSaveDir(persistentId, acp::InternalDeviceType);
|
acp::ACPStatus status = nn::acp::ACPCreateSaveDir(persistentId, iosu::acp::ACPDeviceType::InternalDeviceType);
|
||||||
result = ConvertACPToSaveStatus(status);
|
result = ConvertACPToSaveStatus(status);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
Loading…
Reference in New Issue
Block a user