Add all the files

This commit is contained in:
Exzap 2022-08-22 22:21:23 +02:00
parent e3db07a16a
commit d60742f52b
1445 changed files with 430238 additions and 0 deletions

View file

@ -0,0 +1,376 @@
#include "iosu_pdm.h"
#include "config/ActiveSettings.h"
#include "Common/filestream.h"
#include "util/helpers/Semaphore.h"
#if BOOST_OS_LINUX
// using chrono::year_month_date and other features require a relatively recent stdlibc++
// to avoid upping the required version we use the STL reference implementation for now
#include "Common/linux/date.h"
namespace chrono_d = date;
#else
namespace chrono_d = std::chrono;
#endif
namespace iosu
{
namespace pdm
{
std::mutex sDiaryLock;
fs::path GetPDFile(const char* filename)
{
// todo - support for per-account tracking
return ActiveSettings::GetMlcPath(fmt::format("usr/save/system/pdm/80000001/{}", filename));
}
void MakeDirectory()
{
fs::path path = GetPDFile(".");
std::error_code ec;
fs::create_directories(path, ec);
}
chrono_d::year_month_day GetDateFromDayIndex(uint16 dayIndex)
{
chrono_d::sys_days startDateDay(chrono_d::year(2000) / chrono_d::January / chrono_d::day(1));
chrono_d::sys_days lastPlayedDay = startDateDay + chrono_d::days(dayIndex);
return chrono_d::year_month_day(lastPlayedDay);
}
uint16 GetTodaysDayIndex()
{
chrono_d::sys_days startDateDay(chrono_d::year(2000) / chrono_d::January / chrono_d::day(1));
chrono_d::sys_days currentDateDay = chrono_d::floor<chrono_d::days>(std::chrono::system_clock::now());
return (uint16)(currentDateDay - startDateDay).count();
}
struct PlayStatsEntry
{
uint32be titleIdHigh;
uint32be titleIdLow;
uint32be totalMinutesPlayed;
uint16be numTimesLaunched;
uint16be firstLaunchDayIndex; // first time this title was launched
uint16be mostRecentDayIndex; // last time this title was played
uint16be ukn12; // maybe just padding?
};
static_assert(sizeof(PlayStatsEntry) == 0x14);
struct
{
FileStream* fs{};
uint32be numEntries;
PlayStatsEntry entry[NUM_PLAY_STATS_ENTRIES];
}PlayStats;
void CreatePlaystats()
{
PlayStats.fs = FileStream::createFile2(GetPDFile("PlayStats.dat"));
if (!PlayStats.fs)
{
cemuLog_log(LogType::Force, "Unable to open or create PlayStats.dat");
return;
}
uint32be entryCount = 0;
PlayStats.fs->writeData(&entryCount, sizeof(uint32be));
PlayStats.fs->writeData(PlayStats.entry, NUM_PLAY_STATS_ENTRIES * sizeof(PlayStatsEntry));
static_assert((NUM_PLAY_STATS_ENTRIES * sizeof(PlayStatsEntry)) == 0x1400);
}
void LoadPlaystats()
{
PlayStats.numEntries = 0;
for (size_t i = 0; i < NUM_PLAY_STATS_ENTRIES; i++)
{
auto& e = PlayStats.entry[i];
memset(&e, 0, sizeof(PlayStatsEntry));
}
PlayStats.fs = FileStream::openFile2(GetPDFile("PlayStats.dat"), true);
if (!PlayStats.fs)
{
CreatePlaystats();
return;
}
if (PlayStats.fs->GetSize() != (NUM_PLAY_STATS_ENTRIES * 20 + 4))
{
delete PlayStats.fs;
PlayStats.fs = nullptr;
cemuLog_log(LogType::Force, "PlayStats.dat malformed");
// dont delete the existing file in case it could still be salvaged (todo) and instead just dont track play time
return;
}
PlayStats.fs->readData(&PlayStats.numEntries, sizeof(uint32be));
if (PlayStats.numEntries > NUM_PLAY_STATS_ENTRIES)
PlayStats.numEntries = NUM_PLAY_STATS_ENTRIES;
PlayStats.fs->readData(PlayStats.entry, NUM_PLAY_STATS_ENTRIES * 20);
}
PlayStatsEntry* PlayStats_GetEntry(uint64 titleId)
{
uint32be titleIdHigh = (uint32)(titleId>>32);
uint32be titleIdLow = (uint32)(titleId & 0xFFFFFFFF);
size_t numEntries = PlayStats.numEntries;
for (size_t i = 0; i < numEntries; i++)
{
if (PlayStats.entry[i].titleIdHigh == titleIdHigh && PlayStats.entry[i].titleIdLow == titleIdLow)
return &PlayStats.entry[i];
}
return nullptr;
}
void PlayStats_WriteEntry(PlayStatsEntry* entry, bool writeEntryCount = false)
{
if (!PlayStats.fs)
return;
size_t entryIndex = entry - PlayStats.entry;
cemu_assert(entryIndex < NUM_PLAY_STATS_ENTRIES);
PlayStats.fs->SetPosition(4 + entryIndex * sizeof(PlayStatsEntry));
if (PlayStats.fs->writeData(entry, sizeof(PlayStatsEntry)) != sizeof(PlayStatsEntry))
{
cemuLog_log(LogType::Force, "Failed to write to PlayStats.dat");
return;
}
if (writeEntryCount)
{
uint32be numEntries = PlayStats.numEntries;
PlayStats.fs->SetPosition(0);
PlayStats.fs->writeData(&numEntries, sizeof(uint32be));
}
}
PlayStatsEntry* PlayStats_CreateEntry(uint64 titleId)
{
bool entryCountChanged = false;
PlayStatsEntry* newEntry;
if(PlayStats.numEntries < NUM_PLAY_STATS_ENTRIES)
{
newEntry = PlayStats.entry + PlayStats.numEntries;
PlayStats.numEntries += 1;
entryCountChanged = true;
}
else
{
// list is full - find existing entry with least amount of minutes and replace it
newEntry = PlayStats.entry + 0;
for (uint32 i = 1; i < NUM_PLAY_STATS_ENTRIES; i++)
{
if(PlayStats.entry[i].totalMinutesPlayed < newEntry->totalMinutesPlayed)
newEntry = PlayStats.entry + i;
}
}
newEntry->titleIdHigh = (uint32)(titleId >> 32);
newEntry->titleIdLow = (uint32)(titleId & 0xFFFFFFFF);
newEntry->firstLaunchDayIndex = GetTodaysDayIndex();
newEntry->mostRecentDayIndex = newEntry->firstLaunchDayIndex;
newEntry->numTimesLaunched = 1;
newEntry->totalMinutesPlayed = 0;
newEntry->ukn12 = 0;
PlayStats_WriteEntry(newEntry, entryCountChanged);
return newEntry;
}
// sets last played if entry already exists
// if it does not exist it creates a new entry with first and last played set to today
PlayStatsEntry* PlayStats_BeginNewTracking(uint64 titleId)
{
PlayStatsEntry* entry = PlayStats_GetEntry(titleId);
if (entry)
{
entry->mostRecentDayIndex = GetTodaysDayIndex();
entry->numTimesLaunched += 1;
PlayStats_WriteEntry(entry);
return entry;
}
return PlayStats_CreateEntry(titleId);
}
void PlayStats_CountAdditionalMinutes(PlayStatsEntry* entry, uint32 additionalMinutes)
{
if (additionalMinutes == 0)
return;
entry->totalMinutesPlayed += additionalMinutes;
entry->mostRecentDayIndex = GetTodaysDayIndex();
PlayStats_WriteEntry(entry);
}
struct PlayDiaryHeader
{
// the play diary is a rolling log
// initially only writeIndex increases
// after the log is filled, writeIndex wraps over and readIndex will increase as well
uint32be readIndex;
uint32be writeIndex;
};
static_assert(sizeof(PlayDiaryEntry) == 0x10);
static_assert(sizeof(PlayDiaryHeader) == 0x8);
struct
{
FileStream* fs{};
PlayDiaryHeader header;
PlayDiaryEntry entry[NUM_PLAY_DIARY_ENTRIES_MAX];
}PlayDiaryData;
void CreatePlayDiary()
{
MakeDirectory();
PlayDiaryData.fs = FileStream::createFile2(GetPDFile("PlayDiary.dat"));
if (!PlayDiaryData.fs)
{
cemuLog_log(LogType::Force, "Failed to read or write PlayDiary.dat, playtime tracking will not be possible");
}
// write header
PlayDiaryData.header.readIndex = 0;
PlayDiaryData.header.writeIndex = 0;
if (PlayDiaryData.fs)
PlayDiaryData.fs->writeData(&PlayDiaryData.header, sizeof(PlayDiaryHeader));
}
void LoadPlayDiary()
{
std::unique_lock _lock(sDiaryLock);
cemu_assert_debug(!PlayDiaryData.fs);
PlayDiaryData.fs = FileStream::openFile2(GetPDFile("PlayDiary.dat"), true);
if (!PlayDiaryData.fs)
{
CreatePlayDiary();
return;
}
// read header
if (PlayDiaryData.fs->readData(&PlayDiaryData.header, sizeof(PlayDiaryHeader)) != sizeof(PlayDiaryHeader))
{
cemuLog_log(LogType::Force, "Failed to read valid PlayDiary header");
delete PlayDiaryData.fs;
PlayDiaryData.fs = nullptr;
CreatePlayDiary();
return;
}
if (PlayDiaryData.header.readIndex > NUM_PLAY_DIARY_ENTRIES_MAX || PlayDiaryData.header.writeIndex > NUM_PLAY_DIARY_ENTRIES_MAX)
{
cemuLog_log(LogType::Force, "Bad value in play diary header (read={} write={})", (uint32)PlayDiaryData.header.readIndex, (uint32)PlayDiaryData.header.writeIndex);
PlayDiaryData.header.readIndex = PlayDiaryData.header.readIndex % NUM_PLAY_DIARY_ENTRIES_MAX;
PlayDiaryData.header.writeIndex = PlayDiaryData.header.writeIndex % NUM_PLAY_DIARY_ENTRIES_MAX;
}
// read entries and set any not-yet-written entries to zero
uint32 readBytes = PlayDiaryData.fs->readData(PlayDiaryData.entry, NUM_PLAY_DIARY_ENTRIES_MAX * sizeof(PlayDiaryEntry));
uint32 readEntries = readBytes / sizeof(PlayDiaryEntry);
while (readEntries < NUM_PLAY_DIARY_ENTRIES_MAX)
{
PlayDiaryData.entry[readEntries].titleId = 0;
PlayDiaryData.entry[readEntries].ukn08 = 0;
PlayDiaryData.entry[readEntries].dayIndex = 0;
PlayDiaryData.entry[readEntries].ukn0E = 0;
readEntries++;
}
}
uint32 GetDiaryEntries(uint8 accountSlot, PlayDiaryEntry* diaryEntries, uint32 maxEntries)
{
std::unique_lock _lock(sDiaryLock);
uint32 numReadEntries = 0;
uint32 currentEntryIndex = PlayDiaryData.header.readIndex;
while (currentEntryIndex != PlayDiaryData.header.writeIndex && numReadEntries < maxEntries)
{
*diaryEntries = PlayDiaryData.entry[currentEntryIndex];
numReadEntries++;
diaryEntries++;
currentEntryIndex = (currentEntryIndex+1) % NUM_PLAY_DIARY_ENTRIES_MAX;
}
return numReadEntries;
}
bool GetStatForGamelist(uint64 titleId, GameListStat& stat)
{
stat.last_played.year = 0;
stat.last_played.month = 0;
stat.last_played.day = 0;
stat.numMinutesPlayed = 0;
std::unique_lock _lock(sDiaryLock);
// the play stats give us last time played and the total minutes
PlayStatsEntry* playStats = PlayStats_GetEntry(titleId);
if (playStats)
{
stat.numMinutesPlayed = playStats->totalMinutesPlayed;
chrono_d::year_month_day ymd = GetDateFromDayIndex(playStats->mostRecentDayIndex);
stat.last_played.year = (int)ymd.year();
stat.last_played.month = (unsigned int)ymd.month() - 1;
stat.last_played.day = (unsigned int)ymd.day();
}
_lock.unlock();
// check legacy time tracking for game entries in settings.xml
std::unique_lock _lockGC(GetConfig().game_cache_entries_mutex);
for (auto& gameEntry : GetConfig().game_cache_entries)
{
if(gameEntry.title_id != titleId)
continue;
stat.numMinutesPlayed += (gameEntry.legacy_time_played / 60);
if (gameEntry.legacy_last_played != 0)
{
time_t td = gameEntry.legacy_last_played;
tm* date = localtime(&td);
uint32 legacyYear = (uint32)date->tm_year + 1900;
uint32 legacyMonth = (uint32)date->tm_mon;
uint32 legacyDay = (uint32)date->tm_mday;
if (stat.last_played.year == 0 || std::tie(legacyYear, legacyMonth, legacyDay) > std::tie(stat.last_played.year, stat.last_played.month, stat.last_played.day))
{
stat.last_played.year = legacyYear;
stat.last_played.month = legacyMonth;
stat.last_played.day = legacyDay;
}
}
}
return true;
}
std::thread sPDMTimeTrackingThread;
CounterSemaphore sPDMSem;
std::atomic_bool sPDMRequestExitThread{ false };
void TimeTrackingThread(uint64 titleId)
{
PlayStatsEntry* playStatsEntry = PlayStats_BeginNewTracking(titleId);
auto startTime = std::chrono::steady_clock::now();
uint32 prevMinuteCounter = 0;
while (true)
{
sPDMSem.decrementWithWaitAndTimeout(15000);
if (sPDMRequestExitThread.load(std::memory_order::relaxed))
break;
auto currentTime = std::chrono::steady_clock::now();
uint32 elapsedMinutes = std::chrono::duration_cast<std::chrono::minutes>(currentTime - startTime).count();
if (elapsedMinutes > prevMinuteCounter)
{
PlayStats_CountAdditionalMinutes(playStatsEntry, (elapsedMinutes - prevMinuteCounter));
// todo - also update PlayDiary (and other files)
prevMinuteCounter = elapsedMinutes;
}
}
}
void Initialize()
{
// todo - add support for per-account handling
LoadPlaystats();
LoadPlayDiary();
}
void StartTrackingTime(uint64 titleId)
{
sPDMRequestExitThread = false;
sPDMTimeTrackingThread = std::thread(TimeTrackingThread, titleId);
}
void Stop()
{
sPDMRequestExitThread.store(true);
sPDMSem.increment();
sPDMTimeTrackingThread.join();
}
};
};

View file

@ -0,0 +1,38 @@
#pragma once
namespace iosu
{
namespace pdm
{
void Initialize();
void StartTrackingTime(uint64 titleId);
void Stop();
inline constexpr size_t NUM_PLAY_STATS_ENTRIES = 256;
inline constexpr size_t NUM_PLAY_DIARY_ENTRIES_MAX = 18250; // 0x474A
struct PlayDiaryEntry
{
uint64be titleId;
uint32be ukn08; // probably minutes
uint16be dayIndex;
uint16be ukn0E;
};
uint32 GetDiaryEntries(uint8 accountSlot, PlayDiaryEntry* diaryEntries, uint32 maxEntries);
/* Helper for UI game list */
struct GameListStat
{
struct
{
uint32 year; // if 0 -> never played
uint32 month;
uint32 day;
}last_played;
uint32 numMinutesPlayed;
};
bool GetStatForGamelist(uint64 titleId, GameListStat& stat);
};
};

View file

@ -0,0 +1,83 @@
#pragma once
enum class FS_RESULT : sint32 // aka FSStatus
{
SUCCESS = 0,
END_ITERATION = -2, // used by FSGetMountSource / FSGetMountSourceNext to indicate when last element was reached
FATAL_ERROR = -0x400,
ALREADY_EXISTS = -5,
NOT_FOUND = -6,
NOT_FILE = -7,
NOT_DIR = -8,
PERMISSION_ERROR = -10,
INVALID_CLIENT_HANDLE = -0x30000 - 37,
ERR_PLACEHOLDER = -9999, // used when exact error code has yet to be determined
};
enum class FSA_RESULT : sint32 // aka FSError/FSAStatus
{
SUCCESS = 0,
INVALID_CLIENT_HANDLE = -0x30000 - 37,
INVALID_HANDLE_UKN38 = -0x30000 - 38,
FATAL_ERROR = -0x30000 - 0x400,
};
// todo - error handling in the IOSU part is pretty hacky right now and we use FS_RESULT in most places which we shouldn't be doing. Rework it
using FSResHandle = sint32;
using FSFileHandle2 = FSResHandle;
using FSDirHandle2 = FSResHandle;
#define FS_INVALID_HANDLE_VALUE -1
#define FSA_FILENAME_SIZE_MAX 128
#define FSA_PATH_SIZE_MAX (512 + FSA_FILENAME_SIZE_MAX)
#define FSA_CMD_PATH_MAX_LENGTH FSA_PATH_SIZE_MAX
#define FSA_MAX_CLIENTS 32
typedef sint32 FSStatus; // DEPR - replaced by FS_RESULT
typedef uint32 FS_ERROR_MASK; // replace with enum bitmask
typedef uint32 FSFileSize;
typedef uint64 FSLargeSize;
typedef uint64 FSTime;
enum class FSFlag : uint32
{
NONE = 0,
IS_DIR = 0x80000000,
};
DEFINE_ENUM_FLAG_OPERATORS(FSFlag);
#pragma pack(1)
struct FSStat_t
{
/* +0x000 */ betype<FSFlag> flag;
/* +0x004 */ uint32be permissions;
/* +0x008 */ uint32be ownerId;
/* +0x00C */ uint32be groupId;
/* +0x010 */ betype<FSFileSize> size;
/* +0x014 */ betype<FSFileSize> allocatedSize;
/* +0x018 */ betype<FSLargeSize> quotaSize;
/* +0x020 */ uint32be entryId;
/* +0x024 */ betype<FSTime> createdTime;
/* +0x02C */ betype<FSTime> modifiedTime;
/* +0x034 */ uint8 attributes[0x30];
};
static_assert(sizeof(FSStat_t) == 0x64);
struct FSDirEntry_t
{
/* +0x00 */ FSStat_t stat;
/* +0x64 */ char name[FSA_FILENAME_SIZE_MAX];
};
static_assert(sizeof(FSDirEntry_t) == 0xE4);
#pragma pack()
// query types for QueryInfo
#define FSA_QUERY_TYPE_FREESPACE 0
#define FSA_QUERY_TYPE_STAT 5

View file

@ -0,0 +1,824 @@
#include "fsa_types.h"
#include "iosu_fsa.h"
#include "Cafe/IOSU/kernel/iosu_kernel.h"
#include "Cafe/Filesystem/fsc.h"
#include "util/helpers/helpers.h"
#include "Cafe/OS/libs/coreinit/coreinit_FS.h" // get rid of this dependency, requires reworking some of the IPC stuff. See locations where we use coreinit::FSCmdBlockBody_t
#include "Cafe/HW/Latte/Core/LatteBufferCache.h" // also remove this dependency
#include "Cafe/HW/MMU/MMU.h"
using namespace iosu::kernel;
namespace iosu
{
namespace fsa
{
IOSMsgQueueId sFSAIoMsgQueue;
SysAllocator<iosu::kernel::IOSMessage, 352> _m_sFSAIoMsgQueueMsgBuffer;
std::thread sFSAIoThread;
struct FSAClient // IOSU's counterpart to the coreinit FSClient struct
{
std::string workingDirectory;
bool isAllocated{ false };
void AllocateAndInitialize()
{
isAllocated = true;
workingDirectory = std::string("/");
}
void ReleaseAndCleanup()
{
isAllocated = false;
}
};
std::array<FSAClient, 624> sFSAClientArray;
IOS_ERROR FSAAllocateClient(sint32& indexOut)
{
for (size_t i = 0; i < sFSAClientArray.size(); i++)
{
if(sFSAClientArray[i].isAllocated)
continue;
sFSAClientArray[i].AllocateAndInitialize();
indexOut = (sint32)i;
return IOS_ERROR_OK;
}
return (IOS_ERROR)0xFFFCFFEE;
}
sint32 FSA_convertFSCtoFSStatus(sint32 fscError)
{
if (fscError == FSC_STATUS_OK)
return (FSStatus)FS_RESULT::SUCCESS;
else if (fscError == FSC_STATUS_FILE_NOT_FOUND)
return (sint32)FS_RESULT::NOT_FOUND;
else if (fscError == FSC_STATUS_ALREADY_EXISTS)
return (sint32)FS_RESULT::ALREADY_EXISTS;
cemu_assert_unimplemented();
return -1;
}
std::string __FSATranslatePath(FSAClient* fsaClient, std::string_view input, bool endWithSlash = false)
{
std::string tmp;
if (input.empty())
{
tmp.assign(fsaClient->workingDirectory);
if (endWithSlash)
tmp.push_back('/');
return tmp;
}
char c = input.front();
cemu_assert_debug(c != '\\'); // how to handle backward slashes?
if (c == '/')
{
// absolute path
tmp.assign("/"); // keep the leading slash
}
else if (c == '~')
{
cemu_assert_debug(false);
input.remove_prefix(1);
tmp.assign("/");
}
else
{
// in all other cases the path is relative to the working directory
tmp.assign(fsaClient->workingDirectory);
}
// parse path
size_t idx = 0;
while (idx < input.size())
{
c = input[idx];
if (c == '/')
{
idx++;
continue; // filter leading and repeated slashes
}
if (c == '.')
{
if ((input.size() - idx) >= 3 && input[idx + 1] == '.' && input[idx + 2] == '/')
{
// "../"
cemu_assert_unimplemented();
idx += 3;
continue;
}
else if ((input.size() - idx) == 2 && input[idx + 1] == '.')
{
// ".." at the end
cemu_assert_unimplemented();
idx += 2;
continue;
}
else if ((input.size() - idx) >= 2 && input[idx + 1] == '/')
{
// "./"
idx += 2;
continue;
}
}
if (!tmp.empty() && tmp.back() != '/')
tmp.push_back('/');
while (idx < input.size())
{
c = input[idx];
if (c == '/')
break;
tmp.push_back(c);
idx++;
}
}
if (endWithSlash)
{
if (tmp.empty() || tmp.back() != '/')
tmp.push_back('/');
}
return tmp;
}
FSCVirtualFile* __FSAOpenNode(FSAClient* client, std::string_view path, FSC_ACCESS_FLAG accessFlags, sint32& fscStatus)
{
std::string translatedPath = __FSATranslatePath(client, path);
return fsc_open(translatedPath.c_str(), accessFlags, &fscStatus);
}
class _FSAHandleTable
{
struct _FSAHandleResource
{
bool isAllocated{ false };
FSCVirtualFile* fscFile;
uint16 handleCheckValue;
};
public:
FSA_RESULT AllocateHandle(FSResHandle& handleOut, FSCVirtualFile* fscFile)
{
for (size_t i = 0; i < m_handleTable.size(); i++)
{
auto& it = m_handleTable.at(i);
if(it.isAllocated)
continue;
uint16 checkValue = (uint16)m_currentCounter;
m_currentCounter++;
it.handleCheckValue = checkValue;
it.fscFile = fscFile;
it.isAllocated = true;
uint32 handleVal = ((uint32)i << 16) | (uint32)checkValue;
handleOut = (FSResHandle)handleVal;
return FSA_RESULT::SUCCESS;
}
cemuLog_log(LogType::Force, "FSA: Ran out of file handles");
return FSA_RESULT::FATAL_ERROR;
}
FSA_RESULT ReleaseHandle(FSResHandle handle)
{
uint16 index = (uint16)((uint32)handle >> 16);
uint16 checkValue = (uint16)(handle & 0xFFFF);
if (index >= m_handleTable.size())
return FSA_RESULT::INVALID_HANDLE_UKN38;
auto& it = m_handleTable.at(index);
if(!it.isAllocated)
return FSA_RESULT::INVALID_HANDLE_UKN38;
if(it.handleCheckValue != checkValue)
return FSA_RESULT::INVALID_HANDLE_UKN38;
it.fscFile = nullptr;
it.isAllocated = false;
return FSA_RESULT::SUCCESS;
}
FSCVirtualFile* GetByHandle(FSResHandle handle)
{
uint16 index = (uint16)((uint32)handle >> 16);
uint16 checkValue = (uint16)(handle & 0xFFFF);
if (index >= m_handleTable.size())
return nullptr;
auto& it = m_handleTable.at(index);
if (!it.isAllocated)
return nullptr;
if (it.handleCheckValue != checkValue)
return nullptr;
return it.fscFile;
}
private:
uint32 m_currentCounter = 1;
std::array<_FSAHandleResource, 0x3C0> m_handleTable;
};
_FSAHandleTable sFileHandleTable;
_FSAHandleTable sDirHandleTable;
FSStatus __FSAOpenFile(FSAClient* client, const char* path, const char* accessModifierStr, sint32* fileHandle)
{
*fileHandle = FS_INVALID_HANDLE_VALUE;
FSC_ACCESS_FLAG accessModifier = FSC_ACCESS_FLAG::NONE;
bool truncateFile = false; // todo: Support for this
bool isAppend = false; // todo: proper support for this (all write operations should move cursor to the end of the file?)
if (strcmp(accessModifierStr, "r") == 0 || strcmp(accessModifierStr, "rb") == 0)
accessModifier = FSC_ACCESS_FLAG::READ_PERMISSION;
else if (strcmp(accessModifierStr, "r+") == 0)
{
// r+ will create a new file if it doesn't exist
// the cursor will be set to the beginning of the file
// allows read and write access
accessModifier = FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION | FSC_ACCESS_FLAG::FILE_ALLOW_CREATE; // create if non exists, read, write
}
else if (strcmp(accessModifierStr, "w") == 0)
{
accessModifier = FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION | FSC_ACCESS_FLAG::FILE_ALWAYS_CREATE; // create new file & write
truncateFile = true;
}
else if (strcmp(accessModifierStr, "w+") == 0)
{
accessModifier = FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION | FSC_ACCESS_FLAG::FILE_ALWAYS_CREATE; // create new file & read & write
truncateFile = true;
}
else if (strcmp(accessModifierStr, "wb") == 0) // used in Super Meat Boy
{
// b flag is allowed has no effect
accessModifier = FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION | FSC_ACCESS_FLAG::FILE_ALWAYS_CREATE; // create new file & write
truncateFile = true;
}
else if (strcmp(accessModifierStr, "a+") == 0)
{
cemu_assert_debug(false); // a+ is kind of special. Writing always happens at the end but the read cursor can dynamically move
// but Cafe OS might not support this. Needs investigation.
// this also used to be FILE_ALWAYS_CREATE in 1.26.2 and before
accessModifier = FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION | FSC_ACCESS_FLAG::FILE_ALLOW_CREATE;
isAppend = true;
}
else
cemu_assert_debug(false);
accessModifier |= FSC_ACCESS_FLAG::OPEN_DIR | FSC_ACCESS_FLAG::OPEN_FILE;
sint32 fscStatus;
FSCVirtualFile* fscFile = __FSAOpenNode(client, path, accessModifier, fscStatus);
if (!fscFile)
return (sint32)FS_RESULT::NOT_FOUND;
if (fscFile->fscGetType() != FSC_TYPE_FILE)
{
delete fscFile;
return (sint32)FS_RESULT::NOT_FILE;
}
if (isAppend)
fsc_setFileSeek(fscFile, fsc_getFileSize(fscFile));
FSResHandle fsFileHandle;
FSA_RESULT r = sFileHandleTable.AllocateHandle(fsFileHandle, fscFile);
if (r != FSA_RESULT::SUCCESS)
{
cemuLog_log(LogType::Force, "Exceeded maximum number of FSA file handles");
delete fscFile;
return -0x400;
}
*fileHandle = fsFileHandle;
cemuLog_log(LogType::File, "Open file {} (access: {} result: ok handle: 0x{})", path, accessModifierStr, (uint32)*fileHandle);
return (FSStatus)FS_RESULT::SUCCESS;
}
FSStatus __FSAOpenDirectory(FSAClient* client, std::string_view path, sint32* dirHandle)
{
*dirHandle = FS_INVALID_HANDLE_VALUE;
sint32 fscStatus;
FSCVirtualFile* fscFile = __FSAOpenNode(client, path, FSC_ACCESS_FLAG::OPEN_DIR | FSC_ACCESS_FLAG::OPEN_FILE, fscStatus);
if (!fscFile)
return (FSStatus)FS_RESULT::NOT_FOUND;
if (fscFile->fscGetType() != FSC_TYPE_DIRECTORY)
{
delete fscFile;
return (FSStatus)(FS_RESULT::NOT_DIR);
}
FSResHandle fsDirHandle;
FSA_RESULT r = sDirHandleTable.AllocateHandle(fsDirHandle, fscFile);
if (r != FSA_RESULT::SUCCESS)
{
delete fscFile;
return -0x400;
}
*dirHandle = fsDirHandle;
cemuLog_log(LogType::File, "Open directory {} (result: ok handle: 0x{})", path, (uint32)*dirHandle);
return (FSStatus)FS_RESULT::SUCCESS;
}
FSStatus __FSACloseFile(uint32 fileHandle)
{
uint8 handleType = 0;
FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle);
if (!fscFile)
{
forceLogDebug_printf("__FSACloseFile(): Invalid handle (0x%08x)", fileHandle);
return (FSStatus)FS_RESULT::ERR_PLACEHOLDER;
}
// unregister file
sFileHandleTable.ReleaseHandle(fileHandle); // todo - use the error code of this
fsc_close(fscFile);
return (FSStatus)FS_RESULT::SUCCESS;
}
FSStatus FSAProcessCmd_remove(FSAClient* client, FSAIpcCommand* cmd)
{
char* path = (char*)cmd->cmdRemove.path;
sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND;
fsc_remove(path, &fscStatus);
return FSA_convertFSCtoFSStatus(fscStatus);
}
FSStatus FSAProcessCmd_makeDir(FSAClient* client, FSAIpcCommand* cmd)
{
char* path = (char*)cmd->cmdMakeDir.path;
sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND;
fsc_createDir(path, &fscStatus);
return FSA_convertFSCtoFSStatus(fscStatus);
}
FSStatus FSAProcessCmd_rename(FSAClient* client, FSAIpcCommand* cmd)
{
char* srcPath = (char*)cmd->cmdRename.srcPath;
char* dstPath = (char*)cmd->cmdRename.dstPath;
sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND;
fsc_rename(srcPath, dstPath, &fscStatus);
return FSA_convertFSCtoFSStatus(fscStatus);
}
bool __FSA_GetStatFromFSCFile(FSCVirtualFile* fscFile, FSStat_t* fsStatOut)
{
memset(fsStatOut, 0x00, sizeof(FSStat_t));
FSFlag statFlag = FSFlag::NONE;
if (fsc_isDirectory(fscFile))
{
// note: Only quota (save) directories have the size field set. For other directories it's zero.
// Hyrule Warriors relies on the size field being zero for /vol/content/data/. Otherwise it will try to read it like a file and get stuck in an endless loop.
// Art Academy reads the size for save directories
statFlag |= FSFlag::IS_DIR;
fsStatOut->size = 0;
}
else if (fsc_isFile(fscFile))
{
fsStatOut->size = fsc_getFileSize(fscFile);
}
else
{
cemu_assert_suspicious();
}
fsStatOut->permissions = 0x777; // format matches unix (fstat) permissions?
fsStatOut->flag = statFlag;
return true;
}
FSStatus __FSA_GetFileStat(FSAClient* client, const char* path, FSStat_t* fsStatOut)
{
sint32 fscStatus;
FSCVirtualFile* fscFile = __FSAOpenNode(client, path, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::OPEN_DIR, fscStatus);
if (!fscFile)
return FSA_convertFSCtoFSStatus(fscStatus);
__FSA_GetStatFromFSCFile(fscFile, fsStatOut);
delete fscFile;
return (FSStatus)FS_RESULT::SUCCESS;
}
FSStatus FSAProcessCmd_queryInfo(FSAClient* client, FSAIpcCommand* cmd)
{
coreinit::FSCmdBlockBody_t* fullCmd = (coreinit::FSCmdBlockBody_t*)cmd;
char* path = (char*)cmd->cmdQueryInfo.query;
uint32 queryType = _swapEndianU32(cmd->cmdQueryInfo.queryType);
void* queryResult = memory_getPointerFromVirtualOffset(_swapEndianU32(fullCmd->returnValueMPTR));
// handle query
sint32 fscStatus = FSC_STATUS_OK;
if (queryType == FSA_QUERY_TYPE_STAT)
{
FSStat_t* fsStat = (FSStat_t*)queryResult;
FSStatus fsStatus = __FSA_GetFileStat(client, path, fsStat);
return fsStatus;
}
else if (queryType == FSA_QUERY_TYPE_FREESPACE)
{
sint32 fscStatus;
FSCVirtualFile* fscFile = __FSAOpenNode(client, path, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::OPEN_DIR, fscStatus);
if (!fscFile)
return FSA_convertFSCtoFSStatus(fscStatus);
betype<uint64>* fsStatSize = (betype<uint64>*)queryResult;
*fsStatSize = 30ull * 1024 * 1024 * 1024; // placeholder value. How is this determined?
delete fscFile;
return (FSStatus)FS_RESULT::SUCCESS;
}
else
cemu_assert_unimplemented();
return FSA_convertFSCtoFSStatus(fscStatus);
}
FSStatus FSAProcessCmd_getStatFile(FSAClient* client, FSAIpcCommand* cmd)
{
coreinit::FSCmdBlockBody_t* fullCmd = (coreinit::FSCmdBlockBody_t*)cmd;
FSFileHandle2 fileHandle = cmd->cmdGetStatFile.fileHandle;
FSStat_t* statOut = (FSStat_t*)memory_getPointerFromVirtualOffset(_swapEndianU32(fullCmd->returnValueMPTR));
FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle);
if (!fscFile)
return (FSStatus)FS_RESULT::NOT_FOUND;
cemu_assert_debug(fsc_isFile(fscFile));
__FSA_GetStatFromFSCFile(fscFile, statOut);
return (FSStatus)FS_RESULT::SUCCESS;
}
FSStatus FSAProcessCmd_read(FSAClient* client, FSAIpcCommand* cmd)
{
coreinit::FSCmdBlockBody_t* fullCmd = (coreinit::FSCmdBlockBody_t*)cmd;
uint32 filePos = _swapEndianU32(cmd->cmdDefault.transferFilePos);
uint32 fileHandle = _swapEndianU32(cmd->cmdDefault.fileHandle);
MPTR destOffset = _swapEndianU32(cmd->cmdDefault.destBufferMPTR);
void* destPtr = memory_getPointerFromVirtualOffset(destOffset);
uint32 transferSize = _swapEndianU32(fullCmd->transferSize);
uint32 transferElementSize = _swapEndianU32(fullCmd->transferElemSize);
uint32 flags = _swapEndianU32(cmd->cmdDefault.cmdFlag);
uint32 errHandling = _swapEndianU32(fullCmd->errHandling);
FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle);
if (!fscFile)
return (FSStatus)FS_RESULT::ERR_PLACEHOLDER;
uint32 elementSize = transferElementSize;
uint32 elementCount = 0;
if (transferElementSize != 0)
{
elementCount = transferSize / transferElementSize;
cemu_assert_debug((transferSize % transferElementSize) == 0);
}
else
{
cemu_assert_debug(transferSize == 0);
}
uint32 bytesToRead = transferSize;
// update file position if flag is set
if ((flags & FSA_CMD_FLAG_SET_POS) != 0)
fsc_setFileSeek(fscFile, filePos);
// todo: File permissions
uint32 bytesSuccessfullyRead = fsc_readFile(fscFile, destPtr, bytesToRead);
if (transferElementSize == 0)
return 0;
LatteBufferCache_notifyDCFlush(memory_getVirtualOffsetFromPointer(destPtr), bytesToRead);
return bytesSuccessfullyRead / transferElementSize; // return number of elements read
}
FSStatus FSAProcessCmd_write(FSAClient* client, FSAIpcCommand* cmd)
{
coreinit::FSCmdBlockBody_t* fullCmd = (coreinit::FSCmdBlockBody_t*)cmd;
uint32 filePos = _swapEndianU32(cmd->cmdDefault.transferFilePos);
uint32 fileHandle = _swapEndianU32(cmd->cmdDefault.fileHandle);
MPTR destOffset = _swapEndianU32(cmd->cmdDefault.destBufferMPTR);
void* destPtr = memory_getPointerFromVirtualOffset(destOffset);
uint32 transferSize = _swapEndianU32(fullCmd->transferSize);
uint32 transferElementSize = _swapEndianU32(fullCmd->transferElemSize);
uint32 flags = _swapEndianU32(cmd->cmdDefault.cmdFlag);
uint32 errHandling = _swapEndianU32(fullCmd->errHandling);
FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle);
if (!fscFile)
return (FSStatus)FS_RESULT::ERR_PLACEHOLDER;
uint32 elementSize = transferElementSize;
uint32 elementCount = transferSize / transferElementSize;
cemu_assert_debug((transferSize % transferElementSize) == 0);
uint32 bytesToWrite = transferSize;
// check for write permission (should this happen before or after setting file position?)
if (!fsc_isWritable(fscFile))
{
cemu_assert_debug(false);
return (FSStatus)FS_RESULT::PERMISSION_ERROR;
}
// update file position if flag is set
if ((flags & FSA_CMD_FLAG_SET_POS) != 0)
fsc_setFileSeek(fscFile, filePos);
uint32 bytesSuccessfullyWritten = fsc_writeFile(fscFile, destPtr, bytesToWrite);
debug_printf("FSAProcessCmd_write(): Writing 0x%08x bytes (bytes actually written: 0x%08x)\n", bytesToWrite, bytesSuccessfullyWritten);
return bytesSuccessfullyWritten / transferElementSize; // return number of elements read
}
FSStatus FSAProcessCmd_setPos(FSAClient* client, FSAIpcCommand* cmd)
{
uint32 fileHandle = _swapEndianU32(cmd->cmdDefault.destBufferMPTR);
uint32 filePos = _swapEndianU32(cmd->cmdDefault.ukn0008);
FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle);
if (!fscFile)
return (FSStatus)FS_RESULT::ERR_PLACEHOLDER;
fsc_setFileSeek(fscFile, filePos);
return (FSStatus)FS_RESULT::SUCCESS;
}
FSStatus FSAProcessCmd_getPos(FSAClient* client, FSAIpcCommand* cmd)
{
coreinit::FSCmdBlockBody_t* fullCmd = (coreinit::FSCmdBlockBody_t*)cmd;
uint32 fileHandle = _swapEndianU32(cmd->cmdDefault.destBufferMPTR);
MPTR returnedFilePos = _swapEndianU32(fullCmd->returnValueMPTR);
FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle);
if (!fscFile)
return (FSStatus)FS_RESULT::ERR_PLACEHOLDER;
uint32 filePos = fsc_getFileSeek(fscFile);
memory_writeU32(returnedFilePos, filePos);
return (FSStatus)FS_RESULT::SUCCESS;
}
FSStatus FSAProcessCmd_openFile(FSAClient* client, FSAIpcCommand* cmd)
{
coreinit::FSCmdBlockBody_t* fullCmd = (coreinit::FSCmdBlockBody_t*)cmd;
sint32 fileHandle = 0;
FSStatus fsStatus = __FSAOpenFile(client, (char*)cmd->cmdOpenFile.path, (char*)cmd->cmdOpenFile.mode, &fileHandle);
memory_writeU32(_swapEndianU32(fullCmd->returnValueMPTR), fileHandle);
cmd->cmdOpenFile.fileHandleOutput = fileHandle;
return fsStatus;
}
FSStatus FSAProcessCmd_closeFile(FSAClient* client, FSAIpcCommand* cmd)
{
return __FSACloseFile(cmd->cmdCloseFile.fileHandle);
}
FSStatus FSAProcessCmd_openDir(FSAClient* client, FSAIpcCommand* cmd)
{
coreinit::FSCmdBlockBody_t* fullCmd = (coreinit::FSCmdBlockBody_t*)cmd;
sint32 dirHandle = 0;
FSStatus fsStatus = __FSAOpenDirectory(client, (const char*)cmd->cmdOpenFile.path, &dirHandle);
memory_writeU32(_swapEndianU32(fullCmd->returnValueMPTR), dirHandle);
cmd->cmdOpenDir.dirHandleOutput = dirHandle;
return fsStatus;
}
FSStatus FSAProcessCmd_readDir(FSAClient* client, FSAIpcCommand* cmd)
{
coreinit::FSCmdBlockBody_t* fullCmd = (coreinit::FSCmdBlockBody_t*)cmd;
FSCVirtualFile* fscFile = sDirHandleTable.GetByHandle((sint32)cmd->cmdReadDir.dirHandle);
if (!fscFile)
return (FSStatus)FS_RESULT::ERR_PLACEHOLDER;
FSDirEntry_t* dirEntryOut = (FSDirEntry_t*)memory_getPointerFromVirtualOffset(_swapEndianU32(fullCmd->returnValueMPTR));
FSCDirEntry fscDirEntry;
if (fsc_nextDir(fscFile, &fscDirEntry) == false)
return (FSStatus)FS_RESULT::END_ITERATION;
strcpy(dirEntryOut->name, fscDirEntry.path);
FSFlag statFlag = FSFlag::NONE;
dirEntryOut->stat.size = 0;
if (fscDirEntry.isDirectory)
{
statFlag |= FSFlag::IS_DIR;
}
else if (fscDirEntry.isFile)
{
dirEntryOut->stat.size = fscDirEntry.fileSize;
}
dirEntryOut->stat.flag = statFlag;
dirEntryOut->stat.permissions = 0x777;
return (FSStatus)FS_RESULT::SUCCESS;
}
FSStatus FSAProcessCmd_closeDir(FSAClient* client, FSAIpcCommand* cmd)
{
FSCVirtualFile* fscFile = sDirHandleTable.GetByHandle((sint32)cmd->cmdReadDir.dirHandle);
if (!fscFile)
{
forceLogDebug_printf("CloseDir: Invalid handle (0x%08x)", (sint32)cmd->cmdReadDir.dirHandle);
return (FSStatus)FS_RESULT::ERR_PLACEHOLDER;
}
sDirHandleTable.ReleaseHandle(cmd->cmdReadDir.dirHandle);
fsc_close(fscFile);
return (FSStatus)FS_RESULT::SUCCESS;
}
FSStatus FSAProcessCmd_flushQuota(FSAClient* client, FSAIpcCommand* cmd)
{
return (FSStatus)FS_RESULT::SUCCESS;
}
FSStatus FSAProcessCmd_appendFile(FSAClient* client, FSAIpcCommand* cmd)
{
uint32 fileHandle = _swapEndianU32(cmd->cmdDefault.destBufferMPTR);
FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle);
if (!fscFile)
return (FSStatus)FS_RESULT::ERR_PLACEHOLDER;
#ifndef PUBLIC_RELEASE
cemuLog_force("FSAProcessCmd_appendFile(): size 0x{:08x} count 0x{:08x} (todo)\n", _swapEndianU32(cmd->cmdAppendFile.size), _swapEndianU32(cmd->cmdAppendFile.count));
#endif
return _swapEndianU32(cmd->cmdAppendFile.size) * _swapEndianU32(cmd->cmdAppendFile.count);
}
FSStatus FSAProcessCmd_truncateFile(FSAClient* client, FSAIpcCommand* cmd)
{
FSFileHandle2 fileHandle = cmd->cmdTruncateFile.fileHandle;
FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle);
if (!fscFile)
return (FSStatus)FS_RESULT::ERR_PLACEHOLDER;
fsc_setFileLength(fscFile, fsc_getFileSeek(fscFile));
return (FSStatus)FS_RESULT::SUCCESS;
}
FSStatus FSAProcessCmd_isEof(FSAClient* client, FSAIpcCommand* cmd)
{
uint32 fileHandle = _swapEndianU32(cmd->cmdDefault.destBufferMPTR);
FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle);
if (!fscFile)
return (FSStatus)FS_RESULT::ERR_PLACEHOLDER;
uint32 filePos = fsc_getFileSeek(fscFile);
uint32 fileSize = fsc_getFileSize(fscFile);
if (filePos >= fileSize)
return (FSStatus)FS_RESULT::END_ITERATION;
return (FSStatus)FS_RESULT::SUCCESS;
}
FSStatus FSAProcessCmd_getCwd(FSAClient* client, FSAIpcCommand* cmd)
{
coreinit::FSCmdBlockBody_t* fullCmd = (coreinit::FSCmdBlockBody_t*)cmd;
char* pathOutput = (char*)memory_getPointerFromVirtualOffset(_swapEndianU32(fullCmd->returnValueMPTR));
sint32 pathOutputMaxLen = _swapEndianU32(fullCmd->transferSize);
cemu_assert(pathOutputMaxLen > 0);
sint32 fscStatus = FSC_STATUS_OK;
strncpy(pathOutput, client->workingDirectory.data(), std::min(client->workingDirectory.size() + 1, (size_t)pathOutputMaxLen));
pathOutput[pathOutputMaxLen - 1] = '\0';
return FSA_convertFSCtoFSStatus(fscStatus);
}
FSStatus FSAProcessCmd_changeDir(FSAClient* client, FSAIpcCommand* cmd)
{
const char* path = (const char*)cmd->cmdChangeDir.path;
cmd->cmdChangeDir.path[sizeof(cmd->cmdChangeDir.path) - 1] = '\0';
sint32 fscStatus = FSC_STATUS_OK;
client->workingDirectory.assign(__FSATranslatePath(client, path, true));
return FSA_convertFSCtoFSStatus(fscStatus);
}
void FSAHandleCommandIoctl(FSAClient* client, IPCCommandBody* cmd, uint32 operationId, void* ptrIn, void* ptrOut)
{
FSAIpcCommand* fsaCommand = (FSAIpcCommand*)ptrIn;
FSStatus fsStatus = (FSStatus)(FS_RESULT::FATAL_ERROR);
if (operationId == FSA_CMD_OPERATION_TYPE_REMOVE)
{
fsStatus = FSAProcessCmd_remove(client, fsaCommand);
}
else if (operationId == FSA_CMD_OPERATION_TYPE_MAKEDIR)
{
fsStatus = FSAProcessCmd_makeDir(client, fsaCommand);
}
else if (operationId == FSA_CMD_OPERATION_TYPE_RENAME)
{
fsStatus = FSAProcessCmd_rename(client, fsaCommand);
}
else if (operationId == FSA_CMD_OPERATION_TYPE_READ)
{
fsStatus = FSAProcessCmd_read(client, fsaCommand);
}
else if (operationId == FSA_CMD_OPERATION_TYPE_WRITE)
{
fsStatus = FSAProcessCmd_write(client, fsaCommand);
}
else if (operationId == FSA_CMD_OPERATION_TYPE_SETPOS)
{
fsStatus = FSAProcessCmd_setPos(client, fsaCommand);
}
else if (operationId == FSA_CMD_OPERATION_TYPE_GETPOS)
{
fsStatus = FSAProcessCmd_getPos(client, fsaCommand);
}
else if (operationId == FSA_CMD_OPERATION_TYPE_OPENFILE)
{
fsStatus = FSAProcessCmd_openFile(client, fsaCommand);
}
else if (operationId == FSA_CMD_OPERATION_TYPE_CLOSEFILE)
{
fsStatus = FSAProcessCmd_closeFile(client, fsaCommand);
}
else if (operationId == FSA_CMD_OPERATION_TYPE_APPENDFILE)
{
fsStatus = FSAProcessCmd_appendFile(client, fsaCommand);
}
else if (operationId == FSA_CMD_OPERATION_TYPE_TRUNCATEFILE)
{
fsStatus = FSAProcessCmd_truncateFile(client, fsaCommand);
}
else if (operationId == FSA_CMD_OPERATION_TYPE_ISEOF)
{
fsStatus = FSAProcessCmd_isEof(client, fsaCommand);
}
else if (operationId == FSA_CMD_OPERATION_TYPE_QUERYINFO)
{
fsStatus = FSAProcessCmd_queryInfo(client, fsaCommand);
}
else if (operationId == FSA_CMD_OPERATION_TYPE_GETSTATFILE)
{
fsStatus = FSAProcessCmd_getStatFile(client, fsaCommand);
}
else if (operationId == FSA_CMD_OPERATION_TYPE_GETCWD)
{
fsStatus = FSAProcessCmd_getCwd(client, fsaCommand);
}
else if (operationId == FSA_CMD_OPERATION_TYPE_CHANGEDIR)
{
fsStatus = FSAProcessCmd_changeDir(client, fsaCommand);
}
else if (operationId == FSA_CMD_OPERATION_TYPE_OPENDIR)
{
fsStatus = FSAProcessCmd_openDir(client, fsaCommand);
}
else if (operationId == FSA_CMD_OPERATION_TYPE_READDIR)
{
fsStatus = FSAProcessCmd_readDir(client, fsaCommand);
}
else if (operationId == FSA_CMD_OPERATION_TYPE_CLOSEDIR)
{
fsStatus = FSAProcessCmd_closeDir(client, fsaCommand);
}
else if (operationId == FSA_CMD_OPERATION_TYPE_FLUSHQUOTA)
{
fsStatus = FSAProcessCmd_flushQuota(client, fsaCommand);
}
else
{
cemu_assert_unimplemented();
}
IOS_ResourceReply(cmd, (IOS_ERROR)fsStatus);
}
void FSAIoThread()
{
SetThreadName("IOSU-FSA");
IOSMessage msg;
while (true)
{
IOS_ERROR r = IOS_ReceiveMessage(sFSAIoMsgQueue, &msg, 0);
cemu_assert(!IOS_ResultIsError(r));
if (msg == 0)
return; // shutdown signaled
IPCCommandBody* cmd = MEMPTR<IPCCommandBody>(msg).GetPtr();
uint32 clientHandle = (uint32)cmd->devHandle;
if (cmd->cmdId == IPCCommandId::IOS_OPEN)
{
sint32 clientIndex = 0;
r = FSAAllocateClient(clientIndex);
if (r != IOS_ERROR_OK)
{
IOS_ResourceReply(cmd, r);
continue;
}
IOS_ResourceReply(cmd, (IOS_ERROR)clientIndex);
continue;
}
else if (cmd->cmdId == IPCCommandId::IOS_CLOSE)
{
cemu_assert(clientHandle < sFSAClientArray.size());
sFSAClientArray[clientHandle].ReleaseAndCleanup();
IOS_ResourceReply(cmd, IOS_ERROR_OK);
continue;
}
else if (cmd->cmdId == IPCCommandId::IOS_IOCTL)
{
cemu_assert(clientHandle < sFSAClientArray.size());
cemu_assert(sFSAClientArray[clientHandle].isAllocated);
FSAHandleCommandIoctl(sFSAClientArray.data() + clientHandle, cmd, cmd->args[0], MEMPTR<void>(cmd->args[1]), MEMPTR<void>(cmd->args[3]));
}
else if (cmd->cmdId == IPCCommandId::IOS_IOCTLV)
{
cemu_assert_unimplemented();
//uint32 requestId = cmd->args[0];
//uint32 numIn = cmd->args[1];
//uint32 numOut = cmd->args[2];
//IPCIoctlVector* vec = MEMPTR<IPCIoctlVector>{ cmd->args[3] }.GetPtr();
IOS_ResourceReply(cmd, IOS_ERROR_INVALID);
continue;
}
else
{
cemuLog_log(LogType::Force, "/dev/fsa: Unsupported IPC cmdId");
cemu_assert_suspicious();
IOS_ResourceReply(cmd, IOS_ERROR_INVALID);
}
}
}
void Initialize()
{
for (auto& it : sFSAClientArray)
it.ReleaseAndCleanup();
sFSAIoMsgQueue = (IOSMsgQueueId)IOS_CreateMessageQueue(_m_sFSAIoMsgQueueMsgBuffer.GetPtr(), _m_sFSAIoMsgQueueMsgBuffer.GetCount());
IOS_ERROR r = IOS_RegisterResourceManager("/dev/fsa", sFSAIoMsgQueue);
IOS_DeviceAssociateId("/dev/fsa", 11);
cemu_assert(!IOS_ResultIsError(r));
sFSAIoThread = std::thread(FSAIoThread);
}
void Shutdown()
{
IOS_SendMessage(sFSAIoMsgQueue, 0, 0);
sFSAIoThread.join();
}
}
}

View file

@ -0,0 +1,162 @@
#pragma once
#include "fsa_types.h"
namespace iosu
{
namespace fsa
{
struct FSAIpcCommand
{
union
{
struct
{
uint32 ukn0000;
uint32 destBufferMPTR; // used as fileHandle for FSSetFilePosAsync
uint32 ukn0008; // used as filePos for FSSetFilePosAsync
uint32 ukn000C;
uint32 transferFilePos; // used as filePos for read/write operation
uint32 fileHandle;
uint32 cmdFlag;
uint32 ukn001C;
uint8 ukn0020[0x10];
uint8 ukn0030[0x10];
uint8 ukn0040[0x10];
uint8 ukn0050[0x10];
uint8 ukn0060[0x10];
uint8 ukn0070[0x10];
uint8 ukn0080[0x10];
uint8 ukn0090[0x10];
uint8 ukn00A0[0x10];
uint8 ukn00B0[0x10];
uint8 ukn00C0[0x10];
uint8 ukn00D0[0x10];
uint8 ukn00E0[0x10];
uint8 ukn00F0[0x10];
uint8 ukn0100[0x100];
uint8 ukn0200[0x100];
uint8 ukn0300[0x100];
uint8 ukn0400[0x100];
uint8 ukn0500[0x100];
uint8 ukn0600[0x100];
}cmdDefault;
struct
{
uint32 ukn0000;
uint8 path[FSA_CMD_PATH_MAX_LENGTH];
uint8 mode[12]; // +0x284 note: code seems to access this value like it has a size of 0x10 but the actual struct element is only 12 bytes? Maybe a typo (10 instead of 0x10 in the struct def)
uint32 createMode; // +0x290
uint32 openFlags; // +0x294
uint32 preallocSize; // +0x298
uint8 ukn[0x2E8]; // +0x29C
// output
uint32be fileHandleOutput; // +0x584 used to return file handle on success
}cmdOpenFile;
struct
{
uint32 ukn0000; // +0x000
uint32be fileHandle; // +0x004
}cmdCloseFile;
struct
{
uint32 ukn0000;
uint8 path[FSA_CMD_PATH_MAX_LENGTH];
uint32 ukn0284;
uint8 ukn0288[0x80 - 8];
uint8 ukn0300[0x100];
uint8 ukn0400[0x100];
uint32 ukn0500;
}cmdRemove;
struct
{
uint32 ukn0000;
uint8 path[FSA_CMD_PATH_MAX_LENGTH];
uint8 ukn0284[12]; // +0x284
uint32 ukn0290; // +0x290
uint32 ukn0294; // +0x294
uint32 ukn0298; // +0x298
uint8 ukn[0x2E8]; // +0x29C
// output
uint32be dirHandleOutput; // +0x584 used to return dir handle on success
}cmdOpenDir;
struct
{
uint32 ukn0000;
betype<uint32> dirHandle;
}cmdReadDir;
struct
{
uint32 ukn0000;
betype<uint32> dirHandle;
}cmdCloseDir;
struct
{
uint32 ukn0000;
uint8 path[FSA_CMD_PATH_MAX_LENGTH];
uint32 uknParam;
uint8 ukn0288[0x80 - 8];
uint8 ukn0300[0x100];
uint8 ukn0400[0x100];
uint32 ukn0500;
}cmdMakeDir;
struct
{
uint32 ukn0000;
uint8 path[FSA_CMD_PATH_MAX_LENGTH];
uint8 ukn0284[0x80 - 4];
uint8 ukn0300[0x100];
uint8 ukn0400[0x100];
uint32 ukn0500;
}cmdChangeDir;
struct
{
uint32 ukn0000;
uint8 query[FSA_CMD_PATH_MAX_LENGTH];
uint32 queryType;
uint8 ukn0288[0x80 - 8];
uint8 ukn0300[0x100];
uint8 ukn0400[0x100];
uint32 ukn0500;
}cmdQueryInfo;
struct
{
uint32 ukn0000;
uint8 srcPath[FSA_CMD_PATH_MAX_LENGTH];
uint8 dstPath[FSA_CMD_PATH_MAX_LENGTH];
}cmdRename;
struct
{
uint32 ukn0000;
uint32 size;
uint32 count;
uint32 fileHandle;
uint32 uknParam;
}cmdAppendFile;
struct
{
uint32 ukn0000;
betype<uint32> fileHandle;
uint32be ukn0008;
}cmdTruncateFile;
struct
{
uint32 ukn0000;
betype<uint32> fileHandle;
}cmdGetStatFile;
struct
{
uint32 ukn0000;
uint8 path[FSA_CMD_PATH_MAX_LENGTH];
}cmdFlushQuota;
};
uint8 ukn0700[0x100];
uint8 ukn0800[0x10];
uint8 ukn0810[0x10];
};
static_assert(sizeof(FSAIpcCommand) == 0x820); // exact size of this is not known
void Initialize();
void Shutdown();
}
}

View file

@ -0,0 +1,73 @@
#pragma once
using IOSDevHandle = uint32;
enum class IPCDriverState : uint32
{
CLOSED = 1,
INITIALIZED = 2,
READY = 3,
SUBMITTING = 4
};
enum class IPCCommandId : uint32
{
IOS_OPEN = 1,
IOS_CLOSE = 2,
IOS_IOCTL = 6,
IOS_IOCTLV = 7,
};
struct IPCIoctlVector
{
MEMPTR<void> baseVirt;
uint32be size;
MEMPTR<void> basePhys;
};
static_assert(sizeof(IPCIoctlVector) == 0xC);
struct IPCCommandBody
{
/* +0x00 */ betype<IPCCommandId> cmdId;
/* +0x04 */ uint32be result; // set by IOSU
/* +0x08 */ betype<IOSDevHandle> devHandle;
/* +0x0C */ uint32be ukn0C;
/* +0x10 */ uint32be ukn10;
/* +0x14 */ uint32be ukn14;
/* +0x18 */ uint32be ukn18;
/* +0x1C */ uint32be ukn1C;
/* +0x20 */ uint32be ukn20;
/* +0x24 */ uint32be args[5];
// anything below may only be present on the PPC side?
/* +0x38 */ betype<IPCCommandId> prev_cmdId;
/* +0x3C */ betype<IOSDevHandle> prev_devHandle;
/* +0x40 */ MEMPTR<void> ppcVirt0;
/* +0x44 */ MEMPTR<void> ppcVirt1;
/*
IOS_Open:
args[0] = const char* path
args[1] = pathLen + 1
args[2] = flags
IOS_Close:
Only devHandle is set
IOS_Ioctl:
args[0] = requestId
args[1] = ptrIn
args[2] = sizeIn
args[3] = ptrOut
args[4] = sizeOut
IOS_Ioctlv:
args[0] = requestId
args[1] = numIn
args[2] = numOut
args[3] = IPCIoctlVector*
*/
};

View file

@ -0,0 +1,22 @@
#pragma once
using IOSMsgQueueId = uint32;
// returned for syscalls
// maybe also shared with IPC?
enum IOS_ERROR : sint32
{
IOS_ERROR_OK = 0,
IOS_ERROR_INVALID = -4,
IOS_ERROR_MAXIMUM_REACHED = -5,
IOS_ERROR_NONE_AVAILABLE = -7, // returned by non-blocking IOS_ReceiveMessage on empty message
IOS_ERROR_WOULD_BLOCK = -8,
IOS_ERROR_INVALID_ARG = -29,
};
inline bool IOS_ResultIsError(const IOS_ERROR err)
{
return (err & 0x80000000) != 0;
}

View file

@ -0,0 +1,556 @@
#include "iosu_kernel.h"
#include "util/helpers/fspinlock.h"
#include "Cafe/OS/libs/coreinit/coreinit_IPC.h"
namespace iosu
{
namespace kernel
{
std::mutex sInternalMutex;
static void _assume_lock()
{
#ifndef PUBLIC_RELEASE
cemu_assert_debug(!sInternalMutex.try_lock());
#endif
}
/* message queue */
struct IOSMessageQueue
{
// placeholder
/* +0x00 */ uint32be ukn00;
/* +0x04 */ uint32be ukn04;
/* +0x08 */ uint32be numQueuedMessages;
/* +0x0C */ uint32be readIndex;
/* +0x10 */ uint32be msgArraySize; // 0 if queue is not allocated
/* +0x14 */ MEMPTR<betype<IOSMessage>> msgArray;
/* +0x18 */ IOSMsgQueueId queueHandle;
/* +0x1C */ uint32be ukn1C;
uint32 GetWriteIndex()
{
uint32 idx = readIndex + numQueuedMessages;
if (idx >= msgArraySize)
idx -= msgArraySize;
return idx;
}
/* HLE extension */
std::condition_variable cv_send;
std::condition_variable cv_recv;
};
std::array<IOSMessageQueue, 750> sMsgQueuePool;
IOS_ERROR _IOS_GetMessageQueue(IOSMsgQueueId queueHandle, IOSMessageQueue*& queueOut)
{
_assume_lock();
uint32 index = (queueHandle & 0xFFF);
if (index >= sMsgQueuePool.size())
return IOS_ERROR_INVALID;
IOSMessageQueue& q = sMsgQueuePool.at(index);
if(q.queueHandle != queueHandle)
return IOS_ERROR_INVALID;
queueOut = &q;
return IOS_ERROR_OK;
}
IOSMsgQueueId IOS_CreateMessageQueue(IOSMessage* messageArray, uint32 messageCount)
{
std::unique_lock _l(sInternalMutex);
cemu_assert(messageCount != 0);
auto it = std::find_if(sMsgQueuePool.begin(), sMsgQueuePool.end(), [](const IOSMessageQueue& q) { return q.msgArraySize == 0; });
if (it == sMsgQueuePool.end())
{
cemu_assert_suspicious();
return IOS_ERROR_MAXIMUM_REACHED;
}
size_t index = std::distance(sMsgQueuePool.begin(), it);
IOSMessageQueue& msgQueue = sMsgQueuePool.at(index);
// create queue handle
static uint32 sQueueHandleCounter = 0;
uint32 queueHandle = (uint32)index | ((sQueueHandleCounter<<12)&0x7FFFFFFF);
sQueueHandleCounter++;
msgQueue.queueHandle = queueHandle;
msgQueue.msgArraySize = messageCount;
msgQueue.msgArray = (betype<IOSMessage>*)messageArray;
msgQueue.numQueuedMessages = 0;
msgQueue.readIndex = 0;
return queueHandle;
}
IOS_ERROR IOS_SendMessage(IOSMsgQueueId msgQueueId, IOSMessage message, uint32 flags)
{
std::unique_lock _l(sInternalMutex);
cemu_assert_debug(flags == 0 || flags == 1);
bool dontBlock = (flags & 1) != 0;
IOSMessageQueue* msgQueue = nullptr;
IOS_ERROR r = _IOS_GetMessageQueue(msgQueueId, msgQueue);
if (r != IOS_ERROR_OK)
return r;
while (true)
{
if (msgQueue->numQueuedMessages == msgQueue->msgArraySize)
{
if (dontBlock)
return IOS_ERROR_WOULD_BLOCK;
}
else
break;
msgQueue->cv_send.wait(_l);
// after returning from wait, make sure the queue handle is unchanged
if (msgQueue->queueHandle != msgQueueId)
return IOS_ERROR_INVALID;
}
uint32 writeIndex = msgQueue->GetWriteIndex();
msgQueue->msgArray[writeIndex] = message;
msgQueue->numQueuedMessages += 1;
msgQueue->cv_recv.notify_one();
return IOS_ERROR_OK;
}
IOS_ERROR IOS_ReceiveMessage(IOSMsgQueueId msgQueueId, IOSMessage* messageOut, uint32 flags)
{
std::unique_lock _l(sInternalMutex);
cemu_assert_debug(flags == 0 || flags == 1);
bool dontBlock = (flags & 1) != 0;
IOSMessageQueue* msgQueue = nullptr;
IOS_ERROR r = _IOS_GetMessageQueue(msgQueueId, msgQueue);
if (r != IOS_ERROR_OK)
return r;
while (true)
{
if (msgQueue->numQueuedMessages == 0)
{
if (dontBlock)
return IOS_ERROR_NONE_AVAILABLE;
}
else
break;
msgQueue->cv_recv.wait(_l);
// after returning from wait, make sure the queue handle is unchanged
if (msgQueue->queueHandle != msgQueueId)
return IOS_ERROR_INVALID;
}
*messageOut = msgQueue->msgArray[(uint32)msgQueue->readIndex];
msgQueue->readIndex = msgQueue->readIndex + 1;
if (msgQueue->readIndex >= msgQueue->msgArraySize)
msgQueue->readIndex -= msgQueue->msgArraySize;
msgQueue->numQueuedMessages -= 1;
msgQueue->cv_send.notify_one();
return IOS_ERROR_OK;
}
/* devices and IPC */
struct IOSResourceManager
{
bool isSet{false};
std::string path;
IOSMsgQueueId msgQueueId;
};
std::array<IOSResourceManager, 512> sDeviceResources;
IOSResourceManager* _IOS_FindResourceManager(const char* devicePath)
{
_assume_lock();
std::string_view devicePathSV{ devicePath };
for (auto& it : sDeviceResources)
{
if (it.isSet && it.path == devicePathSV)
return &it;
}
return nullptr;
}
IOSResourceManager* _IOS_CreateNewResourceManager(const char* devicePath, IOSMsgQueueId msgQueueId)
{
_assume_lock();
std::string_view devicePathSV{ devicePath };
for (auto& it : sDeviceResources)
{
if (!it.isSet)
{
it.isSet = true;
it.path = devicePath;
it.msgQueueId = msgQueueId;
return &it;
}
}
return nullptr;
}
IOS_ERROR IOS_RegisterResourceManager(const char* devicePath, IOSMsgQueueId msgQueueId)
{
std::unique_lock _lock(sInternalMutex);
if (_IOS_FindResourceManager(devicePath))
{
cemu_assert_suspicious();
return IOS_ERROR_INVALID; // correct error code?
}
// verify if queue is valid
IOSMessageQueue* msgQueue;
IOS_ERROR r = _IOS_GetMessageQueue(msgQueueId, msgQueue);
if (r != IOS_ERROR_OK)
return r;
// create resource manager
IOSResourceManager* resourceMgr = _IOS_CreateNewResourceManager(devicePath, msgQueueId);
if (!resourceMgr)
return IOS_ERROR_MAXIMUM_REACHED;
return IOS_ERROR_OK;
}
IOS_ERROR IOS_DeviceAssociateId(const char* devicePath, uint32 id)
{
// not yet implemented
return IOS_ERROR_OK;
}
/* IPC */
struct IOSDispatchableCommand
{
// stores a copy of incoming IPC requests with some extra information required for replies
IPCCommandBody body; // our dispatchable copy
IPCIoctlVector vecCopy[8]; // our copy of the Ioctlv vector array
IPCCommandBody* originalBody; // the original command that was sent to us
uint32 ppcCoreIndex;
IOSDevHandle replyHandle; // handle for outgoing replies
bool isAllocated{false};
};
SysAllocator<IOSDispatchableCommand, 96> sIPCDispatchableCommandPool;
std::queue<IOSDispatchableCommand*> sIPCFreeDispatchableCommands;
FSpinlock sIPCDispatchableCommandPoolLock;
void _IPCInitDispatchablePool()
{
sIPCDispatchableCommandPoolLock.acquire();
while (!sIPCFreeDispatchableCommands.empty())
sIPCFreeDispatchableCommands.pop();
for (size_t i = 0; i < sIPCDispatchableCommandPool.GetCount(); i++)
sIPCFreeDispatchableCommands.push(sIPCDispatchableCommandPool.GetPtr()+i);
sIPCDispatchableCommandPoolLock.release();
}
IOSDispatchableCommand* _IPCAllocateDispatchableCommand()
{
sIPCDispatchableCommandPoolLock.acquire();
if (sIPCFreeDispatchableCommands.empty())
{
cemuLog_log(LogType::Force, "IOS: Exhausted pool of dispatchable commands");
sIPCDispatchableCommandPoolLock.release();
return nullptr;
}
IOSDispatchableCommand* cmd = sIPCFreeDispatchableCommands.front();
sIPCFreeDispatchableCommands.pop();
cemu_assert_debug(!cmd->isAllocated);
cmd->isAllocated = true;
sIPCDispatchableCommandPoolLock.release();
return cmd;
}
void _IPCReleaseDispatchableCommand(IOSDispatchableCommand* cmd)
{
sIPCDispatchableCommandPoolLock.acquire();
cemu_assert_debug(cmd->isAllocated);
cmd->isAllocated = false;
sIPCFreeDispatchableCommands.push(cmd);
sIPCDispatchableCommandPoolLock.release();
}
static constexpr size_t MAX_NUM_ACTIVE_DEV_HANDLES = 96; // per process
struct IPCActiveDeviceHandle
{
bool isSet{false};
uint32 handleCheckValue{0};
std::string path;
IOSMsgQueueId msgQueueId;
// dispatch target handle (retrieved via IOS_OPEN command to dispatch target)
bool hasDispatchTargetHandle{false};
IOSDevHandle dispatchTargetHandle;
};
IPCActiveDeviceHandle sActiveDeviceHandles[MAX_NUM_ACTIVE_DEV_HANDLES];
IOS_ERROR _IPCCreateResourceHandle(const char* devicePath, IOSDevHandle& handleOut)
{
std::unique_lock _lock(sInternalMutex);
static uint32 sHandleCreationCounter = 1;
// find resource manager for device
IOSResourceManager* resMgr = _IOS_FindResourceManager(devicePath);
if (!resMgr)
{
cemuLog_log(LogType::Force, "IOSU-Kernel: IOS_Open() could not open {}", devicePath);
return IOS_ERROR_INVALID;
}
IOSMsgQueueId msgQueueId = resMgr->msgQueueId;
_lock.unlock();
// create new handle
sint32 deviceHandleIndex = -1;
for (size_t i = 0; i < MAX_NUM_ACTIVE_DEV_HANDLES; i++)
{
if (!sActiveDeviceHandles[i].isSet)
{
deviceHandleIndex = (sint32)i;
break;
}
}
cemu_assert_debug(deviceHandleIndex >= 0);
if (deviceHandleIndex < 0)
return IOS_ERROR_MAXIMUM_REACHED;
// calc handle
uint32 devHandle = deviceHandleIndex | ((sHandleCreationCounter << 12) & 0x7FFFFFFF);
sHandleCreationCounter++;
// init handle instance
sActiveDeviceHandles[deviceHandleIndex].isSet = true;
sActiveDeviceHandles[deviceHandleIndex].handleCheckValue = devHandle;
sActiveDeviceHandles[deviceHandleIndex].path = devicePath;
sActiveDeviceHandles[deviceHandleIndex].msgQueueId = msgQueueId;
sActiveDeviceHandles[deviceHandleIndex].hasDispatchTargetHandle = false;
handleOut = devHandle;
return IOS_ERROR_OK;
}
IOS_ERROR _IPCDestroyResourceHandle(IOSDevHandle devHandle)
{
std::unique_lock _lock(sInternalMutex);
uint32 index = devHandle & 0xFFF;
cemu_assert(index < MAX_NUM_ACTIVE_DEV_HANDLES);
if (!sActiveDeviceHandles[index].isSet)
{
cemuLog_log(LogType::Force, "_IPCDispatchToResourceManager(): Resource manager destroyed before all IPC commands were processed");
return IOS_ERROR_INVALID;
}
if (devHandle != sActiveDeviceHandles[index].handleCheckValue)
{
cemuLog_log(LogType::Force, "_IPCDispatchToResourceManager(): Mismatching handle");
return IOS_ERROR_INVALID;
}
sActiveDeviceHandles[index].isSet = false;
sActiveDeviceHandles[index].handleCheckValue = 0;
sActiveDeviceHandles[index].hasDispatchTargetHandle = false;
_lock.unlock();
return IOS_ERROR_OK;
}
IOS_ERROR _IPCAssignDispatchTargetHandle(IOSDevHandle devHandle, IOSDevHandle internalHandle)
{
std::unique_lock _lock(sInternalMutex);
uint32 index = devHandle & 0xFFF;
cemu_assert(index < MAX_NUM_ACTIVE_DEV_HANDLES);
if (!sActiveDeviceHandles[index].isSet)
{
cemuLog_log(LogType::Force, "_IPCDispatchToResourceManager(): Resource manager destroyed before all IPC commands were processed");
return IOS_ERROR_INVALID;
}
if (devHandle != sActiveDeviceHandles[index].handleCheckValue)
{
cemuLog_log(LogType::Force, "_IPCDispatchToResourceManager(): Mismatching handle");
return IOS_ERROR_INVALID;
}
cemu_assert_debug(!sActiveDeviceHandles[index].hasDispatchTargetHandle);
sActiveDeviceHandles[index].hasDispatchTargetHandle = true;
sActiveDeviceHandles[index].dispatchTargetHandle = internalHandle;
_lock.unlock();
return IOS_ERROR_OK;
}
IOS_ERROR _IPCDispatchToResourceManager(IOSDevHandle devHandle, IOSDispatchableCommand* dispatchCmd)
{
std::unique_lock _lock(sInternalMutex);
uint32 index = devHandle & 0xFFF;
cemu_assert(index < MAX_NUM_ACTIVE_DEV_HANDLES);
if (!sActiveDeviceHandles[index].isSet)
{
cemuLog_log(LogType::Force, "_IPCDispatchToResourceManager(): Resource manager destroyed before all IPC commands were processed");
return IOS_ERROR_INVALID;
}
if (devHandle != sActiveDeviceHandles[index].handleCheckValue)
{
cemuLog_log(LogType::Force, "_IPCDispatchToResourceManager(): Mismatching handle");
return IOS_ERROR_INVALID;
}
IOSMsgQueueId msgQueueId = sActiveDeviceHandles[index].msgQueueId;
if (dispatchCmd->body.cmdId == IPCCommandId::IOS_OPEN)
{
cemu_assert(!sActiveDeviceHandles[index].hasDispatchTargetHandle);
dispatchCmd->body.devHandle = 0;
}
else
{
cemu_assert(sActiveDeviceHandles[index].hasDispatchTargetHandle);
dispatchCmd->body.devHandle = sActiveDeviceHandles[index].dispatchTargetHandle;
}
_lock.unlock();
MEMPTR<IOSDispatchableCommand> msgVal{ dispatchCmd };
IOS_ERROR r = IOS_SendMessage(msgQueueId, msgVal.GetMPTR(), 1);
if(r != IOS_ERROR_OK)
cemuLog_log(LogType::Force, "_IPCDispatchToResourceManager(): SendMessage returned {}", (sint32)r);
return r;
}
void _IPCReplyAndRelease(IOSDispatchableCommand* dispatchCmd, uint32 result)
{
cemu_assert(dispatchCmd >= sIPCDispatchableCommandPool.GetPtr() && dispatchCmd < sIPCDispatchableCommandPool.GetPtr() + sIPCDispatchableCommandPool.GetCount());
dispatchCmd->originalBody->result = result;
// submit to COS
IPCCommandBody* responseArray[1];
responseArray[0] = dispatchCmd->originalBody;
coreinit::IPCDriver_NotifyResponses(dispatchCmd->ppcCoreIndex, responseArray, 1);
_IPCReleaseDispatchableCommand(dispatchCmd);
}
IOS_ERROR _IPCHandlerIn_IOS_Open(IOSDispatchableCommand* dispatchCmd)
{
IPCCommandBody& cmd = dispatchCmd->body;
const char* name = MEMPTR<const char>(cmd.args[0]).GetPtr();
uint32 nameLenPlusOne = cmd.args[1];
cemu_assert(nameLenPlusOne > 0);
uint32 flags = cmd.args[2];
cemu_assert_debug(flags == 0);
std::string devicePath{ name, nameLenPlusOne - 1 };
IOSDevHandle handle;
IOS_ERROR r = _IPCCreateResourceHandle(devicePath.c_str(), handle);
if (r != IOS_ERROR_OK)
return r;
dispatchCmd->replyHandle = handle;
dispatchCmd->body.devHandle = 0;
r = _IPCDispatchToResourceManager(handle, dispatchCmd);
return r;
}
IOS_ERROR _IPCHandlerIn_IOS_Close(IOSDispatchableCommand* dispatchCmd)
{
IPCCommandBody& cmd = dispatchCmd->body;
IOS_ERROR r = _IPCDispatchToResourceManager(dispatchCmd->body.devHandle, dispatchCmd);
return r;
}
IOS_ERROR _IPCHandlerIn_IOS_Ioctl(IOSDispatchableCommand* dispatchCmd)
{
IPCCommandBody& cmd = dispatchCmd->body;
IOS_ERROR r = _IPCDispatchToResourceManager(dispatchCmd->body.devHandle, dispatchCmd);
return r;
}
IOS_ERROR _IPCHandlerIn_IOS_Ioctlv(IOSDispatchableCommand* dispatchCmd)
{
IPCCommandBody& cmd = dispatchCmd->body;
uint32 requestId = dispatchCmd->body.args[0];
uint32 numIn = dispatchCmd->body.args[1];
uint32 numOut = dispatchCmd->body.args[2];
IPCIoctlVector* vec = MEMPTR<IPCIoctlVector>(cmd.args[3]).GetPtr();
// copy the vector array
uint32 numVec = numIn + numOut;
if (numVec <= 8)
{
std::copy(vec, vec + numVec, dispatchCmd->vecCopy);
dispatchCmd->body.args[3] = MEMPTR<IPCIoctlVector>(vec).GetMPTR();
}
else
{
// reuse the original vector pointer
cemuLog_log(LogType::Force, "Info: Ioctlv command with more than 8 vectors");
}
IOS_ERROR r = _IPCDispatchToResourceManager(dispatchCmd->body.devHandle, dispatchCmd);
return r;
}
// called by COS directly
void IPCSubmitFromCOS(uint32 ppcCoreIndex, IPCCommandBody* cmd)
{
// create a copy of the cmd
IOSDispatchableCommand* dispatchCmd = _IPCAllocateDispatchableCommand();
dispatchCmd->body = *cmd;
dispatchCmd->originalBody = cmd;
dispatchCmd->ppcCoreIndex = ppcCoreIndex;
dispatchCmd->replyHandle = cmd->devHandle;
// forward command to device
IOS_ERROR r = IOS_ERROR_INVALID;
switch ((IPCCommandId)cmd->cmdId)
{
case IPCCommandId::IOS_OPEN:
dispatchCmd->replyHandle = 0;
r = _IPCHandlerIn_IOS_Open(dispatchCmd);
break;
case IPCCommandId::IOS_CLOSE:
r = _IPCHandlerIn_IOS_Close(dispatchCmd);
break;
case IPCCommandId::IOS_IOCTL:
r = _IPCHandlerIn_IOS_Ioctl(dispatchCmd);
break;
case IPCCommandId::IOS_IOCTLV:
r = _IPCHandlerIn_IOS_Ioctlv(dispatchCmd);
break;
default:
cemuLog_log(LogType::Force, "Invalid IPC command {}", (uint32)(IPCCommandId)cmd->cmdId);
break;
}
if (r < 0)
{
cemuLog_log(LogType::Force, "Error occurred while trying to dispatch IPC");
_IPCReplyAndRelease(dispatchCmd, r);
// in non-error case the device handler will send the result asynchronously via IOS_ResourceReply
}
}
IOS_ERROR IOS_ResourceReply(IPCCommandBody* cmd, IOS_ERROR result)
{
IOSDispatchableCommand* dispatchCmd = (IOSDispatchableCommand*)cmd;
cemu_assert(dispatchCmd >= sIPCDispatchableCommandPool.GetPtr() && dispatchCmd < sIPCDispatchableCommandPool.GetPtr() + sIPCDispatchableCommandPool.GetCount());
cemu_assert_debug(dispatchCmd->isAllocated);
dispatchCmd->originalBody->result = result;
if (dispatchCmd->originalBody->cmdId == IPCCommandId::IOS_OPEN)
{
IOSDevHandle devHandle = dispatchCmd->replyHandle;
if (IOS_ResultIsError(result))
{
cemuLog_log(LogType::Force, "IOS_ResourceReply(): Target device triggered an error on IOS_OPEN");
// dispatch target returned error, destroy our device handle again
IOS_ERROR r = _IPCDestroyResourceHandle(devHandle);
cemu_assert(r == IOS_ERROR_OK);
}
else
{
cemu_assert(_IPCAssignDispatchTargetHandle(devHandle, (IOSDevHandle)result) == IOS_ERROR_OK);
result = (IOS_ERROR)(uint32)devHandle;
}
}
else if (dispatchCmd->originalBody->cmdId == IPCCommandId::IOS_CLOSE)
{
if (IOS_ResultIsError(result))
{
cemuLog_log(LogType::Force, "IOS_ResourceReply(): Target device triggered an error on IOS_CLOSE");
}
// reply, then destroy handle
IOSDevHandle devHandle = dispatchCmd->replyHandle;
_IPCReplyAndRelease(dispatchCmd, result);
IOS_ERROR r = _IPCDestroyResourceHandle(devHandle);
cemu_assert_debug(r == IOS_ERROR::IOS_ERROR_OK);
return IOS_ERROR_OK;
}
_IPCReplyAndRelease(dispatchCmd, result);
return IOS_ERROR_OK;
}
void Initialize()
{
_IPCInitDispatchablePool();
}
}
}

View file

@ -0,0 +1,23 @@
#pragma once
#include "Cafe/IOSU/iosu_ipc_common.h"
#include "Cafe/IOSU/iosu_types_common.h"
namespace iosu
{
namespace kernel
{
using IOSMessage = uint32;
IOSMsgQueueId IOS_CreateMessageQueue(IOSMessage* messageArray, uint32 messageCount);
IOS_ERROR IOS_SendMessage(IOSMsgQueueId msgQueueId, IOSMessage message, uint32 flags);
IOS_ERROR IOS_ReceiveMessage(IOSMsgQueueId msgQueueId, IOSMessage* messageOut, uint32 flags);
IOS_ERROR IOS_RegisterResourceManager(const char* devicePath, IOSMsgQueueId msgQueueId);
IOS_ERROR IOS_DeviceAssociateId(const char* devicePath, uint32 id);
IOS_ERROR IOS_ResourceReply(IPCCommandBody* cmd, IOS_ERROR result);
void IPCSubmitFromCOS(uint32 ppcCoreIndex, IPCCommandBody* cmd);
void Initialize();
}
}

View file

@ -0,0 +1,612 @@
#include "iosu_ioctl.h"
#include "iosu_acp.h"
#include "Cafe/OS/libs/nn_common.h"
#include "util/tinyxml2/tinyxml2.h"
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
#include "Cafe/OS/libs/nn_save/nn_save.h"
#include "util/helpers/helpers.h"
#include "Cafe/OS/libs/nn_acp/nn_acp.h"
#include "Cafe/OS/libs/coreinit/coreinit_FS.h"
#include "Cafe/Filesystem/fsc.h"
#include "Cafe/HW/Espresso/PPCState.h"
static_assert(sizeof(acpMetaXml_t) == 0x3440);
static_assert(offsetof(acpMetaXml_t, title_id) == 0x0000);
static_assert(offsetof(acpMetaXml_t, boss_id) == 0x0008);
static_assert(offsetof(acpMetaXml_t, os_version) == 0x0010);
static_assert(offsetof(acpMetaXml_t, app_size) == 0x0018);
static_assert(offsetof(acpMetaXml_t, common_save_size) == 0x0020);
static_assert(offsetof(acpMetaXml_t, version) == 0x0048);
static_assert(offsetof(acpMetaXml_t, product_code) == 0x004C);
static_assert(offsetof(acpMetaXml_t, logo_type) == 0x00B4);
static_assert(offsetof(acpMetaXml_t, pc_cero) == 0x0100);
static_assert(offsetof(acpMetaXml_t, longname_ja) == 0x038C);
static_assert(offsetof(acpMetaXml_t, shortname_ja) == 0x1B8C);
static_assert(offsetof(acpMetaXml_t, publisher_ja) == 0x278C);
static_assert(sizeof(acpMetaData_t) == 0x1AB00);
static_assert(offsetof(acpMetaData_t, bootMovie) == 0);
static_assert(offsetof(acpMetaData_t, bootLogoTex) == 0x13B38);
namespace iosu
{
struct
{
bool isInitialized;
}iosuAcp = { 0 };
void _xml_parseU32(tinyxml2::XMLElement* xmlElement, const char* name, uint32be* v)
{
tinyxml2::XMLElement* subElement = xmlElement->FirstChildElement(name);
*v = 0;
if (subElement == nullptr)
return;
const char* text = subElement->GetText();
uint32 value;
if (sscanf(text, "%u", &value) == 0)
return;
*v = value;
}
void _xml_parseHex16(tinyxml2::XMLElement* xmlElement, const char* name, uint16be* v)
{
tinyxml2::XMLElement* subElement = xmlElement->FirstChildElement(name);
*v = 0;
if (subElement == nullptr)
return;
const char* text = subElement->GetText();
uint32 value;
if (sscanf(text, "%x", &value) == 0)
return;
*v = value;
}
void _xml_parseHex32(tinyxml2::XMLElement* xmlElement, const char* name, uint32be* v)
{
tinyxml2::XMLElement* subElement = xmlElement->FirstChildElement(name);
*v = 0;
if (subElement == nullptr)
return;
const char* text = subElement->GetText();
uint32 value;
if (sscanf(text, "%x", &value) == 0)
return;
*v = value;
}
void _xml_parseHex64(tinyxml2::XMLElement* xmlElement, const char* name, uint64* v)
{
tinyxml2::XMLElement* subElement = xmlElement->FirstChildElement(name);
*v = 0;
if (subElement == nullptr)
return;
const char* text = subElement->GetText();
uint64 value;
if (sscanf(text, "%llx", &value) == 0)
return;
*v = _swapEndianU64(value);
}
void _xml_parseString_(tinyxml2::XMLElement* xmlElement, const char* name, char* output, sint32 maxLength)
{
tinyxml2::XMLElement* subElement = xmlElement->FirstChildElement(name);
output[0] = '\0';
if (subElement == nullptr)
return;
const char* text = subElement->GetText();
if (text == nullptr)
{
output[0] = '\0';
return;
}
strncpy(output, text, maxLength - 1);
output[maxLength - 1] = '\0';
}
#define _metaXml_parseString(__xmlElement, __name, __output) _xml_parseString_(__xmlElement, __name, __output, sizeof(__output));
void parseSaveMetaXml(uint8* metaXmlData, sint32 metaXmlLength, acpMetaXml_t* metaXml)
{
memset(metaXml, 0, sizeof(acpMetaXml_t));
tinyxml2::XMLDocument appXml;
appXml.Parse((const char*)metaXmlData, metaXmlLength);
uint32 titleVersion = 0xFFFFFFFF;
tinyxml2::XMLElement* menuElement = appXml.FirstChildElement("menu");
if (menuElement)
{
_xml_parseHex64(menuElement, "title_id", &metaXml->title_id);
_xml_parseHex64(menuElement, "boss_id", &metaXml->boss_id);
_xml_parseHex64(menuElement, "os_version", &metaXml->os_version);
_xml_parseHex64(menuElement, "app_size", &metaXml->app_size);
_xml_parseHex64(menuElement, "common_save_size", &metaXml->common_save_size);
_xml_parseHex64(menuElement, "account_save_size", &metaXml->account_save_size);
_xml_parseHex64(menuElement, "common_boss_size", &metaXml->common_boss_size);
_xml_parseHex64(menuElement, "account_boss_size", &metaXml->account_boss_size);
_xml_parseHex64(menuElement, "join_game_mode_mask", &metaXml->join_game_mode_mask);
_xml_parseU32(menuElement, "version", &metaXml->version);
_metaXml_parseString(menuElement, "product_code", metaXml->product_code);
_metaXml_parseString(menuElement, "content_platform", metaXml->content_platform);
_metaXml_parseString(menuElement, "company_code", metaXml->company_code);
_metaXml_parseString(menuElement, "mastering_date", metaXml->mastering_date);
_xml_parseU32(menuElement, "logo_type", &metaXml->logo_type);
_xml_parseU32(menuElement, "app_launch_type", &metaXml->app_launch_type);
_xml_parseU32(menuElement, "invisible_flag", &metaXml->invisible_flag);
_xml_parseU32(menuElement, "no_managed_flag", &metaXml->no_managed_flag);
_xml_parseU32(menuElement, "no_event_log", &metaXml->no_event_log);
_xml_parseU32(menuElement, "no_icon_database", &metaXml->no_icon_database);
_xml_parseU32(menuElement, "launching_flag", &metaXml->launching_flag);
_xml_parseU32(menuElement, "install_flag", &metaXml->install_flag);
_xml_parseU32(menuElement, "closing_msg", &metaXml->closing_msg);
_xml_parseU32(menuElement, "title_version", &metaXml->title_version);
_xml_parseHex32(menuElement, "group_id", &metaXml->group_id);
_xml_parseU32(menuElement, "save_no_rollback", &metaXml->save_no_rollback);
_xml_parseU32(menuElement, "bg_daemon_enable", &metaXml->bg_daemon_enable);
_xml_parseHex32(menuElement, "join_game_id", &metaXml->join_game_id);
_xml_parseU32(menuElement, "olv_accesskey", &metaXml->olv_accesskey);
_xml_parseU32(menuElement, "wood_tin", &metaXml->wood_tin);
_xml_parseU32(menuElement, "e_manual", &metaXml->e_manual);
_xml_parseU32(menuElement, "e_manual_version", &metaXml->e_manual_version);
_xml_parseHex32(menuElement, "region", &metaXml->region);
_xml_parseU32(menuElement, "pc_cero", &metaXml->pc_cero);
_xml_parseU32(menuElement, "pc_esrb", &metaXml->pc_esrb);
_xml_parseU32(menuElement, "pc_bbfc", &metaXml->pc_bbfc);
_xml_parseU32(menuElement, "pc_usk", &metaXml->pc_usk);
_xml_parseU32(menuElement, "pc_pegi_gen", &metaXml->pc_pegi_gen);
_xml_parseU32(menuElement, "pc_pegi_fin", &metaXml->pc_pegi_fin);
_xml_parseU32(menuElement, "pc_pegi_prt", &metaXml->pc_pegi_prt);
_xml_parseU32(menuElement, "pc_pegi_bbfc", &metaXml->pc_pegi_bbfc);
_xml_parseU32(menuElement, "pc_cob", &metaXml->pc_cob);
_xml_parseU32(menuElement, "pc_grb", &metaXml->pc_grb);
_xml_parseU32(menuElement, "pc_cgsrr", &metaXml->pc_cgsrr);
_xml_parseU32(menuElement, "pc_oflc", &metaXml->pc_oflc);
_xml_parseU32(menuElement, "pc_reserved0", &metaXml->pc_reserved0);
_xml_parseU32(menuElement, "pc_reserved1", &metaXml->pc_reserved1);
_xml_parseU32(menuElement, "pc_reserved2", &metaXml->pc_reserved2);
_xml_parseU32(menuElement, "pc_reserved3", &metaXml->pc_reserved3);
_xml_parseU32(menuElement, "ext_dev_nunchaku", &metaXml->ext_dev_nunchaku);
_xml_parseU32(menuElement, "ext_dev_classic", &metaXml->ext_dev_classic);
_xml_parseU32(menuElement, "ext_dev_urcc", &metaXml->ext_dev_urcc);
_xml_parseU32(menuElement, "ext_dev_board", &metaXml->ext_dev_board);
_xml_parseU32(menuElement, "ext_dev_usb_keyboard", &metaXml->ext_dev_usb_keyboard);
_xml_parseU32(menuElement, "ext_dev_etc", &metaXml->ext_dev_etc);
_metaXml_parseString(menuElement, "ext_dev_etc_name", metaXml->ext_dev_etc_name);
_xml_parseU32(menuElement, "eula_version", &metaXml->eula_version);
_xml_parseU32(menuElement, "drc_use", &metaXml->drc_use);
_xml_parseU32(menuElement, "network_use", &metaXml->network_use);
_xml_parseU32(menuElement, "online_account_use", &metaXml->online_account_use);
_xml_parseU32(menuElement, "direct_boot", &metaXml->direct_boot);
_xml_parseU32(menuElement, "reserved_flag0", &(metaXml->reserved_flag[0]));
_xml_parseU32(menuElement, "reserved_flag1", &(metaXml->reserved_flag[1]));
_xml_parseU32(menuElement, "reserved_flag2", &(metaXml->reserved_flag[2]));
_xml_parseU32(menuElement, "reserved_flag3", &(metaXml->reserved_flag[3]));
_xml_parseU32(menuElement, "reserved_flag4", &(metaXml->reserved_flag[4]));
_xml_parseU32(menuElement, "reserved_flag5", &(metaXml->reserved_flag[5]));
_xml_parseU32(menuElement, "reserved_flag6", &(metaXml->reserved_flag[6]));
_xml_parseU32(menuElement, "reserved_flag7", &(metaXml->reserved_flag[7]));
_metaXml_parseString(menuElement, "longname_ja", metaXml->longname_ja);
_metaXml_parseString(menuElement, "longname_en", metaXml->longname_en);
_metaXml_parseString(menuElement, "longname_fr", metaXml->longname_fr);
_metaXml_parseString(menuElement, "longname_de", metaXml->longname_de);
_metaXml_parseString(menuElement, "longname_it", metaXml->longname_it);
_metaXml_parseString(menuElement, "longname_es", metaXml->longname_es);
_metaXml_parseString(menuElement, "longname_zhs", metaXml->longname_zhs);
_metaXml_parseString(menuElement, "longname_ko", metaXml->longname_ko);
_metaXml_parseString(menuElement, "longname_nl", metaXml->longname_nl);
_metaXml_parseString(menuElement, "longname_pt", metaXml->longname_pt);
_metaXml_parseString(menuElement, "longname_ru", metaXml->longname_ru);
_metaXml_parseString(menuElement, "longname_zht", metaXml->longname_zht);
_metaXml_parseString(menuElement, "shortname_ja", metaXml->shortname_ja);
_metaXml_parseString(menuElement, "shortname_en", metaXml->shortname_en);
_metaXml_parseString(menuElement, "shortname_fr", metaXml->shortname_fr);
_metaXml_parseString(menuElement, "shortname_de", metaXml->shortname_de);
_metaXml_parseString(menuElement, "shortname_it", metaXml->shortname_it);
_metaXml_parseString(menuElement, "shortname_es", metaXml->shortname_es);
_metaXml_parseString(menuElement, "shortname_zhs", metaXml->shortname_zhs);
_metaXml_parseString(menuElement, "shortname_ko", metaXml->shortname_ko);
_metaXml_parseString(menuElement, "shortname_nl", metaXml->shortname_nl);
_metaXml_parseString(menuElement, "shortname_pt", metaXml->shortname_pt);
_metaXml_parseString(menuElement, "shortname_ru", metaXml->shortname_ru);
_metaXml_parseString(menuElement, "shortname_zht", metaXml->shortname_zht);
_metaXml_parseString(menuElement, "publisher_ja", metaXml->publisher_ja);
_metaXml_parseString(menuElement, "publisher_en", metaXml->publisher_en);
_metaXml_parseString(menuElement, "publisher_fr", metaXml->publisher_fr);
_metaXml_parseString(menuElement, "publisher_de", metaXml->publisher_de);
_metaXml_parseString(menuElement, "publisher_it", metaXml->publisher_it);
_metaXml_parseString(menuElement, "publisher_es", metaXml->publisher_es);
_metaXml_parseString(menuElement, "publisher_zhs", metaXml->publisher_zhs);
_metaXml_parseString(menuElement, "publisher_ko", metaXml->publisher_ko);
_metaXml_parseString(menuElement, "publisher_nl", metaXml->publisher_nl);
_metaXml_parseString(menuElement, "publisher_pt", metaXml->publisher_pt);
_metaXml_parseString(menuElement, "publisher_ru", metaXml->publisher_ru);
_metaXml_parseString(menuElement, "publisher_zht", metaXml->publisher_zht);
for (sint32 i = 0; i < 32; i++)
{
char tempStr[256];
sprintf(tempStr, "add_on_unique_id%d", i);
_xml_parseU32(menuElement, tempStr, &(metaXml->add_on_unique_id[i]));
}
}
}
bool _is8DigitHex(const char* str)
{
if (strlen(str) != 8)
return false;
for (sint32 f = 0; f < 8; f++)
{
if (str[f] >= '0' && str[f] <= '9')
continue;
if (str[f] >= 'a' && str[f] <= 'f')
continue;
if (str[f] >= 'A' && str[f] <= 'F')
continue;
return false;
}
return true;
}
sint32 ACPGetSaveDataTitleIdList(uint32 storageDeviceGuessed, acpTitleId_t* titleIdList, sint32 maxCount, uint32be* countOut)
{
sint32 count = 0;
const char* devicePath = "/vol/storage_mlc01/";
if (storageDeviceGuessed != 3)
cemu_assert_unimplemented();
char searchPath[FSA_CMD_PATH_MAX_LENGTH];
sprintf(searchPath, "%susr/save/", devicePath);
sint32 fscStatus = 0;
FSCVirtualFile* fscDirIteratorTitleIdHigh = fsc_openDirIterator(searchPath, &fscStatus);
FSCDirEntry dirEntryTitleIdHigh;
FSCDirEntry dirEntryTitleIdLow;
if(fscDirIteratorTitleIdHigh)
{
while (fsc_nextDir(fscDirIteratorTitleIdHigh, &dirEntryTitleIdHigh))
{
// is 8-digit hex?
if(_is8DigitHex(dirEntryTitleIdHigh.path) == false)
continue;
uint32 titleIdHigh;
sscanf(dirEntryTitleIdHigh.path, "%x", &titleIdHigh);
sprintf(searchPath, "%susr/save/%08x/", devicePath, titleIdHigh);
FSCVirtualFile* fscDirIteratorTitleIdLow = fsc_openDirIterator(searchPath, &fscStatus);
if (fscDirIteratorTitleIdLow)
{
while (fsc_nextDir(fscDirIteratorTitleIdLow, &dirEntryTitleIdLow))
{
// is 8-digit hex?
if (_is8DigitHex(dirEntryTitleIdLow.path) == false)
continue;
uint32 titleIdLow;
sscanf(dirEntryTitleIdLow.path, "%x", &titleIdLow);
// check if /meta/meta.xml exists
char tempPath[FSA_CMD_PATH_MAX_LENGTH];
sprintf(tempPath, "%susr/save/%08x/%08x/meta/meta.xml", devicePath, titleIdHigh, titleIdLow);
if (fsc_doesFileExist(tempPath))
{
if (count < maxCount)
{
titleIdList[count].titleIdHigh = titleIdHigh;
titleIdList[count].titleIdLow = titleIdLow;
count++;
}
}
else
{
forceLogDebug_printf("ACPGetSaveDataTitleIdList(): Missing meta.xml for save %08x-%08x", titleIdHigh, titleIdLow);
}
}
fsc_close(fscDirIteratorTitleIdLow);
}
}
fsc_close(fscDirIteratorTitleIdHigh);
}
*countOut = count;
return 0;
}
sint32 ACPGetTitleSaveMetaXml(uint64 titleId, acpMetaXml_t* acpMetaXml, sint32 uknType)
{
// uknType is probably the storage device?
if (uknType != 3) // mlc01 ?
assert_dbg();
char xmlPath[FSA_CMD_PATH_MAX_LENGTH];
sprintf(xmlPath, "%susr/save/%08x/%08x/meta/meta.xml", "/vol/storage_mlc01/", (uint32)(titleId>>32), (uint32)(titleId&0xFFFFFFFF));
uint32 saveMetaXmlSize = 0;
uint8* saveMetaXmlData = fsc_extractFile(xmlPath, &saveMetaXmlSize);
if (saveMetaXmlData)
{
parseSaveMetaXml(saveMetaXmlData, saveMetaXmlSize, acpMetaXml);
free(saveMetaXmlData);
}
else
{
forceLog_printf("ACPGetTitleSaveMetaXml(): Meta file \"%s\" does not exist", xmlPath);
memset(acpMetaXml, 0, sizeof(acpMetaXml_t));
}
return 0;
}
sint32 ACPGetTitleMetaData(uint64 titleId, acpMetaData_t* acpMetaData)
{
memset(acpMetaData, 0, sizeof(acpMetaData_t));
char titlePath[1024];
if (((titleId >> 32) & 0x10) != 0)
{
sprintf(titlePath, "/vol/storage_mlc01/sys/title/%08x/%08x/", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF));
}
else
{
sprintf(titlePath, "/vol/storage_mlc01/usr/title/%08x/%08x/", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF));
}
char filePath[FSA_CMD_PATH_MAX_LENGTH];
sprintf(filePath, "%smeta/bootMovie.h264", titlePath);
// bootMovie.h264
uint32 metaBootMovieSize = 0;
uint8* metaBootMovieData = fsc_extractFile(filePath, &metaBootMovieSize);
if (metaBootMovieData)
{
memcpy(acpMetaData->bootMovie, metaBootMovieData, std::min<uint32>(metaBootMovieSize, sizeof(acpMetaData->bootMovie)));
free(metaBootMovieData);
}
else
forceLog_printf("ACPGetTitleMetaData(): Unable to load \"%s\"", filePath);
// bootLogoTex.tga
sprintf(filePath, "%smeta/bootLogoTex.tga", titlePath);
uint32 metaBootLogoSize = 0;
uint8* metaBootLogoData = fsc_extractFile(filePath, &metaBootLogoSize);
if (metaBootLogoData)
{
memcpy(acpMetaData->bootLogoTex, metaBootLogoData, std::min<uint32>(metaBootLogoSize, sizeof(acpMetaData->bootLogoTex)));
free(metaBootLogoData);
}
else
forceLog_printf("ACPGetTitleMetaData(): Unable to load \"%s\"", filePath);
return 0;
}
sint32 ACPGetTitleMetaXml(uint64 titleId, acpMetaXml_t* acpMetaXml)
{
memset(acpMetaXml, 0, sizeof(acpMetaXml_t));
char titlePath[1024];
if (((titleId >> 32) & 0x10) != 0)
{
sprintf(titlePath, "/vol/storage_mlc01/sys/title/%08x/%08x/", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF));
}
else
{
sprintf(titlePath, "/vol/storage_mlc01/usr/title/%08x/%08x/", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF));
}
char filePath[FSA_CMD_PATH_MAX_LENGTH];
sprintf(filePath, "%smeta/meta.xml", titlePath);
uint32 metaXmlSize = 0;
uint8* metaXmlData = fsc_extractFile(filePath, &metaXmlSize);
if (metaXmlData)
{
parseSaveMetaXml(metaXmlData, metaXmlSize, acpMetaXml);
free(metaXmlData);
}
else
{
forceLog_printf("ACPGetTitleMetaXml(): Meta file \"%s\" does not exist", filePath);
}
return 0;
}
sint32 ACPGetTitleSaveDirEx(uint64 titleId, uint32 storageDeviceGuessed, acpSaveDirInfo_t* saveDirInfo, sint32 maxCount, uint32be* countOut)
{
sint32 count = 0;
const char* devicePath = "/vol/storage_mlc01/";
if (storageDeviceGuessed != 3)
cemu_assert_unimplemented();
char searchPath[FSA_CMD_PATH_MAX_LENGTH];
char tempPath[FSA_CMD_PATH_MAX_LENGTH];
sint32 fscStatus = 0;
// add common dir
sprintf(searchPath, "%susr/save/%08x/%08x/user/common/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF));
if (fsc_doesDirectoryExist(searchPath))
{
acpSaveDirInfo_t* entry = saveDirInfo + count;
if (count < maxCount)
{
// get dir size
sprintf(tempPath, "%susr/save/%08x/%08x/user/common/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF));
FSCVirtualFile* fscDir = fsc_open(tempPath, FSC_ACCESS_FLAG::OPEN_DIR, &fscStatus);
uint64 dirSize = 0;
if (fscDir)
{
dirSize = fsc_getFileSize(fscDir);
fsc_close(fscDir);
}
memset(entry, 0, sizeof(acpSaveDirInfo_t));
entry->ukn00 = (uint32)(titleId>>32);
entry->ukn04 = (uint32)(titleId&0xFFFFFFFF);
entry->persistentId = 0; // 0 -> common save
entry->ukn0C = 0;
entry->sizeA = _swapEndianU64(0); // ukn
entry->sizeB = _swapEndianU64(dirSize);
entry->time = _swapEndianU64((coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK));
sprintf(entry->path, "%susr/save/%08x/%08x/meta/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF));
count++;
}
}
// add user directories
sprintf(searchPath, "%susr/save/%08x/%08x/user/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF));
FSCVirtualFile* fscDirIterator = fsc_openDirIterator(searchPath, &fscStatus);
if (fscDirIterator == nullptr)
{
forceLog_printf("ACPGetTitleSaveDirEx(): Failed to iterate directories in \"%s\"", searchPath);
*countOut = 0;
}
else
{
FSCDirEntry dirEntry;
while( fsc_nextDir(fscDirIterator, &dirEntry) )
{
if(dirEntry.isDirectory == false)
continue;
// is 8-digit hex name? (persistent id)
if(_is8DigitHex(dirEntry.path) == false )
continue;
uint32 persistentId = 0;
sscanf(dirEntry.path, "%x", &persistentId);
acpSaveDirInfo_t* entry = saveDirInfo + count;
if (count < maxCount)
{
memset(entry, 0, sizeof(acpSaveDirInfo_t));
entry->ukn00 = (uint32)(titleId >> 32); // titleId?
entry->ukn04 = (uint32)(titleId & 0xFFFFFFFF); // titleId?
entry->persistentId = persistentId; // 0 -> common save
entry->ukn0C = 0;
entry->sizeA = _swapEndianU64(0);
entry->sizeB = _swapEndianU64(0);
entry->time = _swapEndianU64((coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK));
sprintf(entry->path, "%susr/save/%08x/%08x/meta/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF));
count++;
}
}
fsc_close(fscDirIterator);
}
*countOut = count;
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()
{
SetThreadName("iosuAcp_thread");
while (true)
{
uint32 returnValue = 0; // Ioctl return value
ioQueueEntry_t* ioQueueEntry = iosuIoctl_getNextWithWait(IOS_DEVICE_ACP_MAIN);
if (ioQueueEntry->request == IOSU_ACP_REQUEST_CEMU)
{
iosuAcpCemuRequest_t* acpCemuRequest = (iosuAcpCemuRequest_t*)ioQueueEntry->bufferVectors[0].buffer.GetPtr();
if (acpCemuRequest->requestCode == IOSU_ACP_GET_SAVE_DATA_TITLE_ID_LIST)
{
uint32be count = 0;
acpCemuRequest->returnCode = ACPGetSaveDataTitleIdList(acpCemuRequest->type, (acpTitleId_t*)acpCemuRequest->ptr.GetPtr(), acpCemuRequest->maxCount, &count);
acpCemuRequest->resultU32.u32 = count;
}
else if (acpCemuRequest->requestCode == IOSU_ACP_GET_TITLE_SAVE_META_XML)
{
acpCemuRequest->returnCode = ACPGetTitleSaveMetaXml(acpCemuRequest->titleId, (acpMetaXml_t*)acpCemuRequest->ptr.GetPtr(), acpCemuRequest->type);
}
else if (acpCemuRequest->requestCode == IOSU_ACP_GET_TITLE_SAVE_DIR)
{
uint32be count = 0;
acpCemuRequest->returnCode = ACPGetTitleSaveDirEx(acpCemuRequest->titleId, acpCemuRequest->type, (acpSaveDirInfo_t*)acpCemuRequest->ptr.GetPtr(), acpCemuRequest->maxCount, &count);
acpCemuRequest->resultU32.u32 = count;
}
else if (acpCemuRequest->requestCode == IOSU_ACP_GET_TITLE_META_DATA)
{
acpCemuRequest->returnCode = ACPGetTitleMetaData(acpCemuRequest->titleId, (acpMetaData_t*)acpCemuRequest->ptr.GetPtr());
}
else if (acpCemuRequest->requestCode == IOSU_ACP_GET_TITLE_META_XML)
{
acpCemuRequest->returnCode = ACPGetTitleMetaXml(acpCemuRequest->titleId, (acpMetaXml_t*)acpCemuRequest->ptr.GetPtr());
}
else if (acpCemuRequest->requestCode == IOSU_ACP_CREATE_SAVE_DIR_EX)
{
acpCemuRequest->returnCode = ACPCreateSaveDirEx(acpCemuRequest->accountSlot, acpCemuRequest->titleId);
}
else
cemu_assert_unimplemented();
}
else
cemu_assert_unimplemented();
iosuIoctl_completeRequest(ioQueueEntry, returnValue);
}
return 0;
}
void iosuAcp_init()
{
if (iosuAcp.isInitialized)
return;
std::thread t(iosuAcp_thread);
t.detach();
iosuAcp.isInitialized = true;
}
bool iosuAcp_isInitialized()
{
return iosuAcp.isInitialized;
}
}

View file

@ -0,0 +1,195 @@
#pragma once
typedef struct
{
/* +0x0000 */ uint64 title_id; // parsed via GetHex64
/* +0x0008 */ uint64 boss_id; // parsed via GetHex64
/* +0x0010 */ uint64 os_version; // parsed via GetHex64
/* +0x0018 */ uint64 app_size; // parsed via GetHex64
/* +0x0020 */ uint64 common_save_size; // parsed via GetHex64
/* +0x0028 */ uint64 account_save_size; // parsed via GetHex64
/* +0x0030 */ uint64 common_boss_size; // parsed via GetHex64
/* +0x0038 */ uint64 account_boss_size; // parsed via GetHex64
/* +0x0040 */ uint64 join_game_mode_mask; // parsed via GetHex64
/* +0x0048 */ uint32be version;
/* +0x004C */ char product_code[0x20];
/* +0x006C */ char content_platform[0x20];
/* +0x008C */ char company_code[8];
/* +0x0094 */ char mastering_date[0x20];
/* +0x00B4 */ uint32be logo_type;
/* +0x00B8 */ uint32be app_launch_type;
/* +0x00BC */ uint32be invisible_flag;
/* +0x00C0 */ uint32be no_managed_flag;
/* +0x00C4 */ uint32be no_event_log;
/* +0x00C8 */ uint32be no_icon_database;
/* +0x00CC */ uint32be launching_flag;
/* +0x00D0 */ uint32be install_flag;
/* +0x00D4 */ uint32be closing_msg;
/* +0x00D8 */ uint32be title_version;
/* +0x00DC */ uint32be group_id; // Hex32
/* +0x00E0 */ uint32be save_no_rollback;
/* +0x00E4 */ uint32be bg_daemon_enable;
/* +0x00E8 */ uint32be join_game_id; // Hex32
/* +0x00EC */ uint32be olv_accesskey;
/* +0x00F0 */ uint32be wood_tin;
/* +0x00F4 */ uint32be e_manual;
/* +0x00F8 */ uint32be e_manual_version;
/* +0x00FC */ uint32be region; // Hex32
/* +0x0100 */ uint32be pc_cero;
/* +0x0104 */ uint32be pc_esrb;
/* +0x0108 */ uint32be pc_bbfc;
/* +0x010C */ uint32be pc_usk;
/* +0x0110 */ uint32be pc_pegi_gen;
/* +0x0114 */ uint32be pc_pegi_fin;
/* +0x0118 */ uint32be pc_pegi_prt;
/* +0x011C */ uint32be pc_pegi_bbfc;
/* +0x0120 */ uint32be pc_cob;
/* +0x0124 */ uint32be pc_grb;
/* +0x0128 */ uint32be pc_cgsrr;
/* +0x012C */ uint32be pc_oflc;
/* +0x0130 */ uint32be pc_reserved0;
/* +0x0134 */ uint32be pc_reserved1;
/* +0x0138 */ uint32be pc_reserved2;
/* +0x013C */ uint32be pc_reserved3;
/* +0x0140 */ uint32be ext_dev_nunchaku;
/* +0x0144 */ uint32be ext_dev_classic;
/* +0x0148 */ uint32be ext_dev_urcc;
/* +0x014C */ uint32be ext_dev_board;
/* +0x0150 */ uint32be ext_dev_usb_keyboard;
/* +0x0154 */ uint32be ext_dev_etc;
/* +0x0158 */ char ext_dev_etc_name[0x200];
/* +0x0358 */ uint32be eula_version;
/* +0x035C */ uint32be drc_use;
/* +0x0360 */ uint32be network_use;
/* +0x0364 */ uint32be online_account_use;
/* +0x0368 */ uint32be direct_boot;
/* +0x036C */ uint32be reserved_flag[8]; // array of U32, reserved_flag%d
/* +0x038C */ char longname_ja[0x200];
/* +0x058C */ char longname_en[0x200];
/* +0x078C */ char longname_fr[0x200];
/* +0x098C */ char longname_de[0x200];
/* +0x0B8C */ char longname_it[0x200];
/* +0x0D8C */ char longname_es[0x200];
/* +0x0F8C */ char longname_zhs[0x200];
/* +0x118C */ char longname_ko[0x200];
/* +0x138C */ char longname_nl[0x200];
/* +0x158C */ char longname_pt[0x200];
/* +0x178C */ char longname_ru[0x200];
/* +0x198C */ char longname_zht[0x200];
/* +0x1B8C */ char shortname_ja[0x100];
/* +0x1C8C */ char shortname_en[0x100];
/* +0x1D8C */ char shortname_fr[0x100];
/* +0x1E8C */ char shortname_de[0x100];
/* +0x1F8C */ char shortname_it[0x100];
/* +0x208C */ char shortname_es[0x100];
/* +0x218C */ char shortname_zhs[0x100];
/* +0x228C */ char shortname_ko[0x100];
/* +0x238C */ char shortname_nl[0x100];
/* +0x248C */ char shortname_pt[0x100];
/* +0x258C */ char shortname_ru[0x100];
/* +0x268C */ char shortname_zht[0x100];
/* +0x278C */ char publisher_ja[0x100];
/* +0x288C */ char publisher_en[0x100];
/* +0x298C */ char publisher_fr[0x100];
/* +0x2A8C */ char publisher_de[0x100];
/* +0x2B8C */ char publisher_it[0x100];
/* +0x2C8C */ char publisher_es[0x100];
/* +0x2D8C */ char publisher_zhs[0x100];
/* +0x2E8C */ char publisher_ko[0x100];
/* +0x2F8C */ char publisher_nl[0x100];
/* +0x308C */ char publisher_pt[0x100];
/* +0x318C */ char publisher_ru[0x100];
/* +0x328C */ char publisher_zht[0x100];
/* +0x338C */ uint32be add_on_unique_id[0x20]; // Hex32, add_on_unique_id%d
/* +0x340C */ uint8 padding[0x3440 - 0x340C]; // guessed
// struct size is 0x3440
}acpMetaXml_t;
typedef struct
{
/* +0x06BDC | +0x00000 */ uint8 bootMovie[0x13B38];
/* +0x1A714 | +0x13B38 */ uint8 bootLogoTex[0x6FBC];
/* +0x216D0 | +0x1AAF4 */ uint8 ukn1AAF4[4];
/* +0x216D4 | +0x1AAF8 */ uint8 ukn1AAF8[4];
/* +0x216D8 | +0x1AAFC */ uint8 ukn1AAFC[4];
}acpMetaData_t;
typedef struct
{
uint32be titleIdHigh;
uint32be titleIdLow;
}acpTitleId_t;
typedef struct
{
// for ACPGetTitleSaveDirEx
uint32be ukn00;
uint32be ukn04;
uint32be persistentId;
uint32be ukn0C;
//uint32be ukn10; // ukn10/ukn14 are part of size
//uint32be ukn14;
//uint32be ukn18; // ukn18/ukn1C are part of size
//uint32be ukn1C;
uint64 sizeA;
uint64 sizeB;
// path starts at 0x20, length unknown?
char path[0x40]; // %susr/save/%08x/%08x/meta/ // /vol/storage_mlc01/
/* +0x60 */ uint64 time;
/* +0x68 */ uint8 padding[0x80 - 0x68];
// size is 0x80, but actual content size is only 0x60 and padded to 0x80?
}acpSaveDirInfo_t;
// custom dev/acp_main protocol (Cemu only)
#define IOSU_ACP_REQUEST_CEMU (0xEE)
typedef struct
{
uint32 requestCode;
// input
uint8 accountSlot;
//uint32 unique;
//uint64 titleId;
//uint32 titleVersion;
//uint32 serverId;
uint64 titleId;
sint32 type;
MEMPTR<void> ptr;
sint32 maxCount;
// output
uint32 returnCode; // ACP return value
union
{
//struct
//{
// uint64 u64;
//}resultU64;
struct
{
uint32 u32;
}resultU32;
//struct
//{
// char strBuffer[1024];
//}resultString;
//struct
//{
// uint8 binBuffer[1024];
//}resultBinary;
};
}iosuAcpCemuRequest_t;
// ACP request Cemu subcodes
#define IOSU_ACP_GET_SAVE_DATA_TITLE_ID_LIST 0x01
#define IOSU_ACP_GET_TITLE_SAVE_META_XML 0x02
#define IOSU_ACP_GET_TITLE_SAVE_DIR 0x03
#define IOSU_ACP_GET_TITLE_META_DATA 0x04
#define IOSU_ACP_GET_TITLE_META_XML 0x05
#define IOSU_ACP_CREATE_SAVE_DIR_EX 0x06
namespace iosu
{
void iosuAcp_init();
}

View file

@ -0,0 +1,742 @@
#include "iosu_act.h"
#include "iosu_ioctl.h"
#include "Cafe/OS/libs/nn_common.h"
#include "gui/CemuApp.h"
#include <algorithm>
#include <mutex>
#include "openssl/sha.h"
#include "Cafe/Account/Account.h"
#include "config/ActiveSettings.h"
#include "util/helpers/helpers.h"
#include "Cemu/napi/napi.h"
#include "Cemu/ncrypto/ncrypto.h"
#include "Cafe/IOSU/kernel/iosu_kernel.h"
#include "Cafe/IOSU/nn/iosu_nn_service.h"
using namespace iosu::kernel;
struct
{
bool isInitialized;
}iosuAct = { };
// account manager
typedef struct
{
bool isValid;
// options
bool isNetworkAccount;
bool hasParseError; // set if any occurs while parsing account.dat
// IDs
uint8 uuid[16];
uint32 persistentId;
uint64 transferableIdBase;
uint32 simpleAddressId;
uint32 principalId;
// NNID
char accountId[64];
uint8 accountPasswordCache[32];
// country & language
uint32 countryIndex;
char country[8];
// Mii
FFLData_t miiData;
uint16be miiNickname[ACT_NICKNAME_LENGTH];
}actAccountData_t;
#define IOSU_ACT_ACCOUNT_MAX_COUNT (0xC)
actAccountData_t _actAccountData[IOSU_ACT_ACCOUNT_MAX_COUNT] = {};
bool _actAccountDataInitialized = false;
void FillAccountData(const Account& account, const bool online_enabled, int index)
{
cemu_assert_debug(index < IOSU_ACT_ACCOUNT_MAX_COUNT);
auto& data = _actAccountData[index];
data.isValid = true;
// options
data.isNetworkAccount = account.IsValidOnlineAccount();
data.hasParseError = false;
// IDs
std::copy(account.GetUuid().cbegin(), account.GetUuid().cend(), data.uuid);
data.persistentId = account.GetPersistentId();
data.transferableIdBase = account.GetTransferableIdBase();
data.simpleAddressId = account.GetSimpleAddressId();
data.principalId = account.GetPrincipalId();
// NNID
std::copy(account.GetAccountId().begin(), account.GetAccountId().end(), data.accountId);
std::copy(account.GetAccountPasswordCache().begin(), account.GetAccountPasswordCache().end(), data.accountPasswordCache);
// country & language
data.countryIndex = account.GetCountry();
strcpy(data.country, NCrypto::GetCountryAsString(data.countryIndex));
// Mii
std::copy(account.GetMiiData().begin(), account.GetMiiData().end(), (uint8*)&data.miiData);
std::copy(account.GetMiiName().begin(), account.GetMiiName().end(), data.miiNickname);
// if online mode is disabled, make all accounts offline
if(!online_enabled)
{
data.isNetworkAccount = false;
data.principalId = 0;
data.simpleAddressId = 0;
memset(data.accountId, 0x00, sizeof(data.accountId));
}
}
void iosuAct_loadAccounts()
{
if (_actAccountDataInitialized)
return;
const bool online_enabled = ActiveSettings::IsOnlineEnabled();
const auto persistent_id = ActiveSettings::GetPersistentId();
// first account is always our selected one
int counter = 0;
const auto& first_acc = Account::GetAccount(persistent_id);
FillAccountData(first_acc, online_enabled, counter);
++counter;
cemuLog_force(L"IOSU_ACT: using account {} in first slot", first_acc.GetMiiName());
_actAccountDataInitialized = true;
}
bool iosuAct_isAccountDataLoaded()
{
return _actAccountDataInitialized;
}
uint32 iosuAct_acquirePrincipalIdByAccountId(const char* nnid, uint32* pid)
{
NAPI::AuthInfo authInfo;
NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo);
NAPI::ACTConvertNnidToPrincipalIdResult result = NAPI::ACT_ACTConvertNnidToPrincipalId(authInfo, nnid);
if (result.isValid() && result.isFound)
{
*pid = result.principalId;
}
else
{
*pid = 0;
return BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_ACT, 0); // what error should we return? The friend list app expects nn_act.AcquirePrincipalIdByAccountId to never return an error
}
return 0;
}
sint32 iosuAct_getAccountIndexBySlot(uint8 slot)
{
if (slot == iosu::act::ACT_SLOT_CURRENT)
return 0;
if (slot == 0xFF)
return 0; // ?
cemu_assert_debug(slot != 0);
cemu_assert_debug(slot <= IOSU_ACT_ACCOUNT_MAX_COUNT);
return slot - 1;
}
uint32 iosuAct_getAccountIdOfCurrentAccount()
{
cemu_assert_debug(_actAccountData[0].isValid);
return _actAccountData[0].persistentId;
}
// IOSU act API interface
namespace iosu
{
namespace act
{
uint8 getCurrentAccountSlot()
{
return 1;
}
bool getPrincipalId(uint8 slot, uint32* principalId)
{
sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot);
if (_actAccountData[accountIndex].isValid == false)
{
*principalId = 0;
return false;
}
*principalId = _actAccountData[accountIndex].principalId;
return true;
}
bool getAccountId(uint8 slot, char* accountId)
{
sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot);
if (_actAccountData[accountIndex].isValid == false)
{
*accountId = '\0';
return false;
}
strcpy(accountId, _actAccountData[accountIndex].accountId);
return true;
}
bool getMii(uint8 slot, FFLData_t* fflData)
{
sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot);
if (_actAccountData[accountIndex].isValid == false)
{
return false;
}
memcpy(fflData, &_actAccountData[accountIndex].miiData, sizeof(FFLData_t));
return true;
}
// return screenname in little-endian wide characters
bool getScreenname(uint8 slot, uint16 screenname[ACT_NICKNAME_LENGTH])
{
sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot);
if (_actAccountData[accountIndex].isValid == false)
{
screenname[0] = '\0';
return false;
}
for (sint32 i = 0; i < ACT_NICKNAME_LENGTH; i++)
{
screenname[i] = (uint16)_actAccountData[accountIndex].miiNickname[i];
}
return true;
}
bool getCountryIndex(uint8 slot, uint32* countryIndex)
{
sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot);
if (_actAccountData[accountIndex].isValid == false)
{
*countryIndex = 0;
return false;
}
*countryIndex = _actAccountData[accountIndex].countryIndex;
return true;
}
class ActService : public iosu::nn::IPCService
{
public:
ActService() : iosu::nn::IPCService("/dev/act") {}
nnResult ServiceCall(uint32 serviceId, void* request, void* response) override
{
cemuLog_log(LogType::Force, "Unsupported service call to /dec/act");
cemu_assert_unimplemented();
return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACT, 0);
}
};
ActService gActService;
void Initialize()
{
gActService.Start();
}
void Stop()
{
gActService.Stop();
}
}
}
// IOSU act IO
typedef struct
{
/* +0x00 */ uint32be ukn00;
/* +0x04 */ uint32be ukn04;
/* +0x08 */ uint32be ukn08;
/* +0x0C */ uint32be subcommandCode;
/* +0x10 */ uint8 ukn10;
/* +0x11 */ uint8 ukn11;
/* +0x12 */ uint8 ukn12;
/* +0x13 */ uint8 accountSlot;
/* +0x14 */ uint32be unique; // is this command specific?
}cmdActRequest00_t;
typedef struct
{
uint32be returnCode;
uint8 transferableIdBase[8];
}cmdActGetTransferableIDResult_t;
#define ACT_SUBCMD_GET_TRANSFERABLE_ID 4
#define ACT_SUBCMD_INITIALIZE 0x14
#define _cancelIfAccountDoesNotExist() \
if (_actAccountData[accountIndex].isValid == false) \
{ \
/* account does not exist*/ \
ioctlReturnValue = 0; \
actCemuRequest->setACTReturnCode(BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_ACT, NN_ACT_RESULT_ACCOUNT_DOES_NOT_EXIST)); /* 0xA071F480 */ \
actCemuRequest->resultU64.u64 = 0; \
iosuIoctl_completeRequest(ioQueueEntry, ioctlReturnValue); \
continue; \
}
nnResult ServerActErrorCodeToNNResult(NAPI::ACT_ERROR_CODE ec)
{
switch (ec)
{
case (NAPI::ACT_ERROR_CODE)1:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2401);
case (NAPI::ACT_ERROR_CODE)2:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2402);
case (NAPI::ACT_ERROR_CODE)3:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2403);
case (NAPI::ACT_ERROR_CODE)4:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2458);
case (NAPI::ACT_ERROR_CODE)5:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2642);
case (NAPI::ACT_ERROR_CODE)6:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2641);
case (NAPI::ACT_ERROR_CODE)7:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2522);
case (NAPI::ACT_ERROR_CODE)8:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2534);
case (NAPI::ACT_ERROR_CODE)9:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2404);
case (NAPI::ACT_ERROR_CODE)10:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2451);
case (NAPI::ACT_ERROR_CODE)11:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2511);
case (NAPI::ACT_ERROR_CODE)12:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2812);
case (NAPI::ACT_ERROR_CODE)100:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2571);
case (NAPI::ACT_ERROR_CODE)101:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2572);
case (NAPI::ACT_ERROR_CODE)103:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2575);
case (NAPI::ACT_ERROR_CODE)104:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2452);
case (NAPI::ACT_ERROR_CODE)105:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2592);
case (NAPI::ACT_ERROR_CODE)106:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2611);
case (NAPI::ACT_ERROR_CODE)107:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2502);
case (NAPI::ACT_ERROR_CODE)108:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2802);
case (NAPI::ACT_ERROR_CODE)109:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2503);
case (NAPI::ACT_ERROR_CODE)110:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2501);
case (NAPI::ACT_ERROR_CODE)111:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2632);
case (NAPI::ACT_ERROR_CODE)112:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2631);
case (NAPI::ACT_ERROR_CODE)113:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2452);
case (NAPI::ACT_ERROR_CODE)114:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2593);
case (NAPI::ACT_ERROR_CODE)115:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2591);
case (NAPI::ACT_ERROR_CODE)116:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2614);
case (NAPI::ACT_ERROR_CODE)117:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2651);
case (NAPI::ACT_ERROR_CODE)118:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2484);
case (NAPI::ACT_ERROR_CODE)119:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2803);
case (NAPI::ACT_ERROR_CODE)120:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2813);
case (NAPI::ACT_ERROR_CODE)121:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2804);
case (NAPI::ACT_ERROR_CODE)122:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2814);
case (NAPI::ACT_ERROR_CODE)123:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2882);
case (NAPI::ACT_ERROR_CODE)124:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2512);
case (NAPI::ACT_ERROR_CODE)125:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2485);
case (NAPI::ACT_ERROR_CODE)126:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2805);
case (NAPI::ACT_ERROR_CODE)127:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2815);
case (NAPI::ACT_ERROR_CODE)128:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2661);
case (NAPI::ACT_ERROR_CODE)129:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2615);
case (NAPI::ACT_ERROR_CODE)130:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2531);
case (NAPI::ACT_ERROR_CODE)131:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2616);
case (NAPI::ACT_ERROR_CODE)132:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2822);
case (NAPI::ACT_ERROR_CODE)133:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2832);
case (NAPI::ACT_ERROR_CODE)134:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2823);
case (NAPI::ACT_ERROR_CODE)135:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2833);
case (NAPI::ACT_ERROR_CODE)136:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2824);
case (NAPI::ACT_ERROR_CODE)137:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2834);
case (NAPI::ACT_ERROR_CODE)138:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2825);
case (NAPI::ACT_ERROR_CODE)139:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2835);
case (NAPI::ACT_ERROR_CODE)142:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2635);
case (NAPI::ACT_ERROR_CODE)143:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2634);
case (NAPI::ACT_ERROR_CODE)1004:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2503);
case (NAPI::ACT_ERROR_CODE)1006:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2471);
case (NAPI::ACT_ERROR_CODE)1016:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2532);
case (NAPI::ACT_ERROR_CODE)1017:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2483);
case (NAPI::ACT_ERROR_CODE)1018:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2533);
case (NAPI::ACT_ERROR_CODE)1019:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2481);
case (NAPI::ACT_ERROR_CODE)1020:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2473);
case NAPI::ACT_ERROR_CODE::ACT_GAME_SERVER_NOT_FOUND:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2482);
case (NAPI::ACT_ERROR_CODE)1022:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2472);
case (NAPI::ACT_ERROR_CODE)1023:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2612);
case (NAPI::ACT_ERROR_CODE)1024:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2535);
case (NAPI::ACT_ERROR_CODE)1025:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2536);
case (NAPI::ACT_ERROR_CODE)1031:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2537);
case (NAPI::ACT_ERROR_CODE)1032:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2636);
case (NAPI::ACT_ERROR_CODE)1033:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2662);
case (NAPI::ACT_ERROR_CODE)1035:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2671);
case (NAPI::ACT_ERROR_CODE)1036:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2679);
case (NAPI::ACT_ERROR_CODE)1037:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2672);
case (NAPI::ACT_ERROR_CODE)1038:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2674);
case (NAPI::ACT_ERROR_CODE)1039:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2680);
case (NAPI::ACT_ERROR_CODE)1040:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2675);
case (NAPI::ACT_ERROR_CODE)1041:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2673);
case (NAPI::ACT_ERROR_CODE)1042:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2676);
case (NAPI::ACT_ERROR_CODE)1043:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2681);
case (NAPI::ACT_ERROR_CODE)1044:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2678);
case (NAPI::ACT_ERROR_CODE)1045:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2677);
case (NAPI::ACT_ERROR_CODE)1046:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2596);
case (NAPI::ACT_ERROR_CODE)1100:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2541);
case (NAPI::ACT_ERROR_CODE)1101:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2542);
case (NAPI::ACT_ERROR_CODE)1103:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2594);
case (NAPI::ACT_ERROR_CODE)1104:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2576);
case (NAPI::ACT_ERROR_CODE)1105:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2613);
case (NAPI::ACT_ERROR_CODE)1106:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2633);
case (NAPI::ACT_ERROR_CODE)1107:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2577);
case (NAPI::ACT_ERROR_CODE)1111:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2538);
case (NAPI::ACT_ERROR_CODE)1115:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2597);
case (NAPI::ACT_ERROR_CODE)1125:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2585);
case (NAPI::ACT_ERROR_CODE)1126:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2586);
case (NAPI::ACT_ERROR_CODE)1134:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2587);
case (NAPI::ACT_ERROR_CODE)1200:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2884);
case (NAPI::ACT_ERROR_CODE)2001:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2931);
case (NAPI::ACT_ERROR_CODE)2002:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2881);
case (NAPI::ACT_ERROR_CODE)2999:
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2883);
default:
break;
}
cemuLog_log(LogType::Force, "Received unknown ACT error code {}", (uint32)ec);
return nnResultStatus(NN_RESULT_MODULE_NN_ACT, NN_ERROR_CODE::ACT_UNKNOWN_SERVER_ERROR);
}
int iosuAct_thread()
{
SetThreadName("iosuAct_thread");
while (true)
{
uint32 ioctlReturnValue = 0;
ioQueueEntry_t* ioQueueEntry = iosuIoctl_getNextWithWait(IOS_DEVICE_ACT);
if (ioQueueEntry->request == 0)
{
if (ioQueueEntry->countIn != 1 || ioQueueEntry->countOut != 1)
{
assert_dbg();
}
ioBufferVector_t* vectorsDebug = ioQueueEntry->bufferVectors.GetPtr();
void* outputBuffer = ioQueueEntry->bufferVectors[0].buffer.GetPtr();
cmdActRequest00_t* requestCmd = (cmdActRequest00_t*)ioQueueEntry->bufferVectors[0].unknownBuffer.GetPtr();
if (requestCmd->subcommandCode == ACT_SUBCMD_INITIALIZE)
{
// do nothing for now (there is no result?)
}
else if (requestCmd->subcommandCode == ACT_SUBCMD_GET_TRANSFERABLE_ID)
{
cmdActGetTransferableIDResult_t* cmdResult = (cmdActGetTransferableIDResult_t*)outputBuffer;
cmdResult->returnCode = 0;
*(uint64*)cmdResult->transferableIdBase = _swapEndianU64(0x1122334455667788);
}
else
assert_dbg();
}
else if (ioQueueEntry->request == IOSU_ACT_REQUEST_CEMU)
{
iosuActCemuRequest_t* actCemuRequest = (iosuActCemuRequest_t*)ioQueueEntry->bufferVectors[0].buffer.GetPtr();
sint32 accountIndex;
ioctlReturnValue = 0;
if (actCemuRequest->requestCode == IOSU_ARC_ACCOUNT_ID)
{
accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot);
_cancelIfAccountDoesNotExist();
strcpy(actCemuRequest->resultString.strBuffer, _actAccountData[accountIndex].accountId);
actCemuRequest->setACTReturnCode(0);
}
else if (actCemuRequest->requestCode == IOSU_ARC_UUID)
{
accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot);
if (actCemuRequest->accountSlot == 0xFF)
{
// common uuid (placeholder algorithm)
for (uint32 i = 0; i < 16; i++)
actCemuRequest->resultBinary.binBuffer[i] = i * 0x74 + i + ~i + i * 133;
}
else
{
_cancelIfAccountDoesNotExist();
memcpy(actCemuRequest->resultBinary.binBuffer, _actAccountData[accountIndex].uuid, 16);
}
cemu_assert_debug(actCemuRequest->uuidName != -1); // todo
if (actCemuRequest->uuidName != -1 && actCemuRequest->uuidName != -2)
{
// generate name based UUID
// format:
// first 10 bytes of UUID + 6 bytes of a hash
// hash algorithm:
// sha256 of
// 4 bytes uuidName (big-endian)
// 4 bytes 0x3A275E09 (big-endian)
// 6 bytes from the end of UUID
// bytes 10-15 are used from the hash and replace the last 6 bytes of the UUID
SHA256_CTX ctx_sha256;
SHA256_Init(&ctx_sha256);
uint8 tempArray[4];
uint32 name = (uint32)actCemuRequest->uuidName;
tempArray[0] = (name >> 24) & 0xFF;
tempArray[1] = (name >> 16) & 0xFF;
tempArray[2] = (name >> 8) & 0xFF;
tempArray[3] = (name >> 0) & 0xFF;
SHA256_Update(&ctx_sha256, tempArray, 4);
tempArray[0] = 0x3A;
tempArray[1] = 0x27;
tempArray[2] = 0x5E;
tempArray[3] = 0x09;
SHA256_Update(&ctx_sha256, tempArray, 4);
SHA256_Update(&ctx_sha256, actCemuRequest->resultBinary.binBuffer+10, 6);
uint8 h[32];
SHA256_Final(h, &ctx_sha256);
memcpy(actCemuRequest->resultBinary.binBuffer + 0xA, h + 0xA, 6);
}
else if (actCemuRequest->uuidName == -2)
{
// return account uuid
}
else
{
forceLogDebug_printf("Gen UUID unknown mode %d", actCemuRequest->uuidName);
}
actCemuRequest->setACTReturnCode(0);
}
else if (actCemuRequest->requestCode == IOSU_ARC_SIMPLEADDRESS)
{
accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot);
_cancelIfAccountDoesNotExist();
actCemuRequest->resultU32.u32 = _actAccountData[accountIndex].simpleAddressId;
actCemuRequest->setACTReturnCode(0);
}
else if (actCemuRequest->requestCode == IOSU_ARC_PRINCIPALID)
{
accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot);
_cancelIfAccountDoesNotExist();
actCemuRequest->resultU32.u32 = _actAccountData[accountIndex].principalId;
actCemuRequest->setACTReturnCode(0);
}
else if (actCemuRequest->requestCode == IOSU_ARC_TRANSFERABLEID)
{
accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot);
_cancelIfAccountDoesNotExist();
actCemuRequest->resultU64.u64 = _actAccountData[accountIndex].transferableIdBase;
// todo - transferable also contains a unique id
actCemuRequest->setACTReturnCode(0);
}
else if (actCemuRequest->requestCode == IOSU_ARC_PERSISTENTID)
{
accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot);
_cancelIfAccountDoesNotExist();
actCemuRequest->resultU32.u32 = _actAccountData[accountIndex].persistentId;
actCemuRequest->setACTReturnCode(0);
}
else if (actCemuRequest->requestCode == IOSU_ARC_COUNTRY)
{
accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot);
_cancelIfAccountDoesNotExist();
strcpy(actCemuRequest->resultString.strBuffer, _actAccountData[accountIndex].country);
actCemuRequest->setACTReturnCode(0);
}
else if (actCemuRequest->requestCode == IOSU_ARC_ISNETWORKACCOUNT)
{
accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot);
_cancelIfAccountDoesNotExist();
actCemuRequest->resultU32.u32 = _actAccountData[accountIndex].isNetworkAccount;
actCemuRequest->setACTReturnCode(0);
}
else if (actCemuRequest->requestCode == IOSU_ARC_ACQUIRENEXTOKEN)
{
NAPI::AuthInfo authInfo;
NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo);
NAPI::ACTGetNexTokenResult nexTokenResult = NAPI::ACT_GetNexToken_WithCache(authInfo, actCemuRequest->titleId, actCemuRequest->titleVersion, actCemuRequest->serverId);
uint32 returnCode = 0;
if (nexTokenResult.isValid())
{
*(NAPI::ACTNexToken*)actCemuRequest->resultBinary.binBuffer = nexTokenResult.nexToken;
returnCode = NN_RESULT_SUCCESS;
}
else if (nexTokenResult.apiError == NAPI_RESULT::SERVICE_ERROR)
{
returnCode = ServerActErrorCodeToNNResult(nexTokenResult.serviceError);
cemu_assert_debug((returnCode&0x80000000) != 0);
}
else
{
returnCode = nnResultStatus(NN_RESULT_MODULE_NN_ACT, NN_ERROR_CODE::ACT_UNKNOWN_SERVER_ERROR);
}
actCemuRequest->setACTReturnCode(returnCode);
}
else if (actCemuRequest->requestCode == IOSU_ARC_ACQUIREINDEPENDENTTOKEN)
{
NAPI::AuthInfo authInfo;
NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo);
NAPI::ACTGetIndependentTokenResult tokenResult = NAPI::ACT_GetIndependentToken_WithCache(authInfo, actCemuRequest->titleId, actCemuRequest->titleVersion, actCemuRequest->clientId);
uint32 returnCode = 0;
if (tokenResult.isValid())
{
for (size_t i = 0; i < std::min(tokenResult.token.size(), (size_t)200); i++)
{
actCemuRequest->resultBinary.binBuffer[i] = tokenResult.token[i];
actCemuRequest->resultBinary.binBuffer[i + 1] = '\0';
}
returnCode = 0;
}
else
{
returnCode = 0x80000000; // todo - proper error codes
}
actCemuRequest->setACTReturnCode(returnCode);
}
else if (actCemuRequest->requestCode == IOSU_ARC_ACQUIREPIDBYNNID)
{
uint32 returnCode = iosuAct_acquirePrincipalIdByAccountId(actCemuRequest->clientId, &actCemuRequest->resultU32.u32);
actCemuRequest->setACTReturnCode(returnCode);
}
else if (actCemuRequest->requestCode == IOSU_ARC_MIIDATA)
{
accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot);
_cancelIfAccountDoesNotExist();
memcpy(actCemuRequest->resultBinary.binBuffer, &_actAccountData[accountIndex].miiData, sizeof(FFLData_t));
actCemuRequest->setACTReturnCode(0);
}
else if (actCemuRequest->requestCode == IOSU_ARC_INIT)
{
iosuAct_loadAccounts();
actCemuRequest->setACTReturnCode(0);
}
else
assert_dbg();
}
else
{
assert_dbg();
}
iosuIoctl_completeRequest(ioQueueEntry, ioctlReturnValue);
}
return 0;
}
void iosuAct_init_depr()
{
if (iosuAct.isInitialized)
return;
std::thread t(iosuAct_thread);
t.detach();
iosuAct.isInitialized = true;
}
bool iosuAct_isInitialized()
{
return iosuAct.isInitialized;
}
uint16 FFLCalculateCRC16(uint8* input, sint32 length)
{
uint16 crc = 0;
for (sint32 c = 0; c < length; c++)
{
for (sint32 f = 0; f < 8; f++)
{
if ((crc & 0x8000) != 0)
{
uint16 t = crc << 1;
crc = t ^ 0x1021;
}
else
{
crc <<= 1;
}
}
crc ^= (uint16)input[c];
}
return crc;
}

View file

@ -0,0 +1,118 @@
#pragma once
void iosuAct_init_depr();
bool iosuAct_isInitialized();
// Mii
#define MII_FFL_STORAGE_SIZE (96)
#define MII_FFL_NAME_LENGTH (10) // counted in wchar_t elements (16-bit unicode)
#define ACT_NICKNAME_LENGTH (10) // aka Mii nickname
#define ACT_NICKNAME_SIZE (11)
typedef struct
{
uint32be high;
uint32be low;
}FFLDataID_t;
typedef struct
{
/* +0x00 */ uint32 uknFlags;
/* +0x04 */ FFLDataID_t miiId; // bytes 8 and 9 are part of the CRC? (miiId is based on account transferable id?)
/* +0x0C */ uint8 ukn0C[0xA];
/* +0x16 */ uint8 ukn16[2];
/* +0x18 */ uint16 ukn18;
/* +0x1A */ uint16be miiName[MII_FFL_NAME_LENGTH];
/* +0x2E */ uint16 ukn2E;
/* +0x30 */ uint8 ukn30[MII_FFL_STORAGE_SIZE-0x30];
}FFLData_t;
static_assert(sizeof(FFLData_t) == MII_FFL_STORAGE_SIZE, "FFLData_t size invalid");
static_assert(offsetof(FFLData_t, miiId) == 0x04, "FFLData->miiId offset invalid");
static_assert(offsetof(FFLData_t, miiName) == 0x1A, "FFLData->miiName offset invalid");
static_assert(offsetof(FFLData_t, ukn2E) == 0x2E, "FFLData->ukn2E offset invalid");
static uint16 FFLCalculateCRC16(uint8* input, sint32 length);
namespace iosu
{
namespace act
{
uint8 getCurrentAccountSlot();
bool getPrincipalId(uint8 slot, uint32* principalId);
bool getAccountId(uint8 slot, char* accountId);
bool getMii(uint8 slot, FFLData_t* fflData);
bool getScreenname(uint8 slot, uint16 screenname[ACT_NICKNAME_LENGTH]);
bool getCountryIndex(uint8 slot, uint32* countryIndex);
const uint8 ACT_SLOT_CURRENT = 0xFE;
void Initialize();
void Stop();
}
}
// custom dev/act/ protocol (Cemu only)
#define IOSU_ACT_REQUEST_CEMU (0xEE)
struct iosuActCemuRequest_t
{
uint32 requestCode;
// input
uint8 accountSlot;
uint32 unique;
sint32 uuidName;
uint64 titleId;
uint32 titleVersion;
uint32 serverId;
char clientId[64];
uint32 expiresIn;
// output
uint32 returnCode;
union
{
struct
{
uint64 u64;
}resultU64;
struct
{
uint32 u32;
}resultU32;
struct
{
char strBuffer[1024];
}resultString;
struct
{
uint8 binBuffer[1024];
}resultBinary;
};
void setACTReturnCode(uint32 code)
{
returnCode = code;
}
};
// Act Request Cemu subcodes
#define IOSU_ARC_INIT 0x00
#define IOSU_ARC_ACCOUNT_ID 0x01
#define IOSU_ARC_TRANSFERABLEID 0x02
#define IOSU_ARC_PERSISTENTID 0x03
#define IOSU_ARC_UUID 0x04
#define IOSU_ARC_SIMPLEADDRESS 0x05
#define IOSU_ARC_PRINCIPALID 0x06
#define IOSU_ARC_COUNTRY 0x07
#define IOSU_ARC_ISNETWORKACCOUNT 0x08
#define IOSU_ARC_ACQUIRENEXTOKEN 0x09
#define IOSU_ARC_MIIDATA 0x0A
#define IOSU_ARC_ACQUIREINDEPENDENTTOKEN 0x0B
#define IOSU_ARC_ACQUIREPIDBYNNID 0x0C
uint32 iosuAct_getAccountIdOfCurrentAccount();
bool iosuAct_isAccountDataLoaded();

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,54 @@
#pragma once
#include "iosu_ioctl.h"
// custom dev/boss protocol (Cemu only)
#define IOSU_BOSS_REQUEST_CEMU (0xEE)
typedef struct
{
uint32 requestCode;
// input
uint32 accountId;
char* taskId;
bool bool_parameter;
uint64 titleId;
uint32 timeout;
uint32 waitState;
uint32 uk1;
void* settings;
// output
uint32 returnCode; // ACP return value
union
{
struct
{
uint32 exec_count;
uint32 result;
} u32;
struct
{
uint32 exec_count;
uint64 result;
} u64;
};
}iosuBossCemuRequest_t;
#define IOSU_NN_BOSS_TASK_RUN (0x01)
#define IOSU_NN_BOSS_TASK_GET_CONTENT_LENGTH (0x02)
#define IOSU_NN_BOSS_TASK_GET_PROCESSED_LENGTH (0x03)
#define IOSU_NN_BOSS_TASK_GET_HTTP_STATUS_CODE (0x04)
#define IOSU_NN_BOSS_TASK_GET_TURN_STATE (0x05)
#define IOSU_NN_BOSS_TASK_WAIT (0x06)
#define IOSU_NN_BOSS_TASK_REGISTER (0x07)
#define IOSU_NN_BOSS_TASK_IS_REGISTERED (0x08)
#define IOSU_NN_BOSS_TASK_REGISTER_FOR_IMMEDIATE_RUN (0x09)
#define IOSU_NN_BOSS_TASK_UNREGISTER (0x0A)
#define IOSU_NN_BOSS_TASK_START_SCHEDULING (0x0B)
#define IOSU_NN_BOSS_TASK_STOP_SCHEDULING (0x0C)
namespace iosu
{
void boss_init();
}

View file

@ -0,0 +1,699 @@
#include "iosu_crypto.h"
#include "config/ActiveSettings.h"
#include "openssl/bn.h"
#include "openssl/obj_mac.h"
#include "openssl/ec.h"
#include "openssl/x509.h"
#include "openssl/ssl.h"
#include "openssl/sha.h"
#include "openssl/ecdsa.h"
#include "util/crypto/aes128.h"
#include "Common/filestream.h"
uint8 otpMem[1024];
bool hasOtpMem = false;
uint8 seepromMem[512];
bool hasSeepromMem = false;
struct
{
bool hasCertificates;
struct
{
bool isValid;
sint32 id;
X509* cert;
std::vector<uint8> certData;
RSA* pkey;
std::vector<uint8> pkeyDERData;
}certList[256];
sint32 certListCount;
}iosuCryptoCertificates = { 0 };
CertECC_t g_wiiuDeviceCert;
void iosuCrypto_readOtpData(void* output, sint32 wordIndex, sint32 size)
{
memcpy(output, otpMem + wordIndex * 4, size);
}
void iosuCrypto_readOtpData(uint32be& output, sint32 wordIndex)
{
memcpy(&output, otpMem + wordIndex * 4, sizeof(uint32be));
}
void iosuCrypto_readSeepromData(void* output, sint32 wordIndex, sint32 size)
{
memcpy(output, seepromMem + wordIndex * 2, size);
}
bool iosuCrypto_getDeviceId(uint32* deviceId)
{
uint32be deviceIdBE;
*deviceId = 0;
if (otpMem == nullptr)
return false;
iosuCrypto_readOtpData(&deviceIdBE, 0x87, sizeof(uint32));
*deviceId = (uint32)deviceIdBE;
return true;
}
void iosuCrypto_getDeviceSerialString(char* serialString)
{
char serialStringPart0[32]; // code
char serialStringPart1[32]; // serial
if (hasSeepromMem == false)
{
strcpy(serialString, "FEH000000000");
return;
}
memset(serialStringPart0, 0, sizeof(serialStringPart0));
memset(serialStringPart1, 0, sizeof(serialStringPart1));
iosuCrypto_readSeepromData(serialStringPart0, 0xAC, 8);
iosuCrypto_readSeepromData(serialStringPart1, 0xB0, 0x10);
sprintf(serialString, "%s%s", serialStringPart0, serialStringPart1);
}
static const char* base64_charset =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
int iosuCrypto_base64Encode(unsigned char const* bytes_to_encode, unsigned int inputLen, char* output)
{
int i = 0;
int j = 0;
unsigned char charArray_3[3];
unsigned char charArray_4[4];
sint32 outputLength = 0;
while (inputLen--)
{
charArray_3[i++] = *(bytes_to_encode++);
if (i == 3)
{
charArray_4[0] = (charArray_3[0] & 0xfc) >> 2;
charArray_4[1] = ((charArray_3[0] & 0x03) << 4) + ((charArray_3[1] & 0xf0) >> 4);
charArray_4[2] = ((charArray_3[1] & 0x0f) << 2) + ((charArray_3[2] & 0xc0) >> 6);
charArray_4[3] = charArray_3[2] & 0x3f;
for (i = 0; (i < 4); i++)
{
output[outputLength] = base64_charset[charArray_4[i]];
outputLength++;
}
i = 0;
}
}
if (i)
{
for (j = i; j < 3; j++)
charArray_3[j] = '\0';
charArray_4[0] = (charArray_3[0] & 0xfc) >> 2;
charArray_4[1] = ((charArray_3[0] & 0x03) << 4) + ((charArray_3[1] & 0xf0) >> 4);
charArray_4[2] = ((charArray_3[1] & 0x0f) << 2) + ((charArray_3[2] & 0xc0) >> 6);
charArray_4[3] = charArray_3[2] & 0x3f;
for (j = 0; j < (i + 1); j++)
{
output[outputLength] = base64_charset[charArray_4[j]];
outputLength++;
}
while (i++ < 3)
{
output[outputLength] = '=';
outputLength++;
}
}
return outputLength;
}
std::string iosuCrypto_base64Encode(unsigned char const* bytes_to_encode, unsigned int inputLen)
{
int encodedLen = inputLen / 3 * 4 + 16;
std::string strB64;
strB64.resize(encodedLen);
int outputLen = iosuCrypto_base64Encode(bytes_to_encode, inputLen, strB64.data());
cemu_assert_debug(outputLen < strB64.size());
strB64.resize(outputLen);
return strB64;
}
static_assert(sizeof(CertECC_t) == sizeof(CertECC_t));
EC_KEY* ECCPubKey_getPublicKey(ECCPubKey& pubKey) // verified and works
{
BIGNUM* bn_r = BN_new();
BIGNUM* bn_s = BN_new();
BN_bin2bn(pubKey.keyData + 0, 30, bn_r);
BN_bin2bn(pubKey.keyData + 30, 30, bn_s);
EC_KEY* ec_pubKey = EC_KEY_new_by_curve_name(NID_sect233r1);
int r = EC_KEY_set_public_key_affine_coordinates(ec_pubKey, bn_r, bn_s);
BN_free(bn_r);
BN_free(bn_s);
return ec_pubKey;
}
ECDSA_SIG* ECCPubKey_getSignature(CertECC_t& cert)
{
BIGNUM* bn_r = BN_new();
BIGNUM* bn_s = BN_new();
BN_bin2bn(cert.signature + 0, 30, bn_r);
BN_bin2bn(cert.signature + 30, 30, bn_s);
//EC_KEY* ec_pubKey = EC_KEY_new_by_curve_name(NID_sect233r1);
//int r = EC_KEY_set_public_key_affine_coordinates(ec_pubKey, bn_r, bn_s);
ECDSA_SIG* ec_sig = ECDSA_SIG_new();
//ECDSA_do_sign_ex
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
ECDSA_SIG_set0(ec_sig, bn_r, bn_s);
#else
BN_copy(ec_sig->r, bn_r);
BN_copy(ec_sig->s, bn_s);
#endif
BN_free(bn_r);
BN_free(bn_s);
return ec_sig;
}
void ECCPubKey_setSignature(CertECC_t& cert, ECDSA_SIG* sig)
{
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
const BIGNUM* sig_r = nullptr, * sig_s = nullptr;
ECDSA_SIG_get0(sig, &sig_r, &sig_s);
sint32 lenR = BN_num_bytes(sig_r);
sint32 lenS = BN_num_bytes(sig_s);
cemu_assert_debug(lenR <= 30);
cemu_assert_debug(lenS <= 30);
memset(cert.signature, 0, sizeof(cert.signature));
BN_bn2bin(sig_r, cert.signature + (30 - lenR));
BN_bn2bin(sig_s, cert.signature + 60 / 2 + (30 - lenS));
#else
sint32 lenR = BN_num_bytes(sig->r);
sint32 lenS = BN_num_bytes(sig->s);
cemu_assert_debug(lenR <= 30);
cemu_assert_debug(lenS <= 30);
memset(cert.signature, 0, sizeof(cert.signature));
BN_bn2bin(sig->r, cert.signature + (30 - lenR));
BN_bn2bin(sig->s, cert.signature + 60 / 2 + (30 - lenS));
#endif
}
ECCPrivKey g_consoleCertPrivKey{};
void iosuCrypto_getDeviceCertPrivateKey(void* privKeyOut, sint32 len)
{
cemu_assert(len == 30);
memcpy(privKeyOut, g_consoleCertPrivKey.keyData, 30);
}
void iosuCrypto_getDeviceCertificate(void* certOut, sint32 len)
{
cemu_assert(len == 0x180);
memcpy(certOut, &g_wiiuDeviceCert, 0x180);
}
void iosuCrypto_generateDeviceCertificate()
{
static_assert(sizeof(g_wiiuDeviceCert) == 0x180);
memset(&g_wiiuDeviceCert, 0, sizeof(g_wiiuDeviceCert));
if (otpMem == nullptr)
return; // cant generate certificate without OPT
// set header based on otp security mode
g_wiiuDeviceCert.sigType = CertECC_t::SIGTYPE::ECC_SHA256;
g_wiiuDeviceCert.ukn0C0[0] = 0x00;
g_wiiuDeviceCert.ukn0C0[1] = 0x00;
g_wiiuDeviceCert.ukn0C0[2] = 0x00;
g_wiiuDeviceCert.ukn0C0[3] = 0x02;
iosuCrypto_readOtpData(g_wiiuDeviceCert.signature, 0xA3, 0x3C);
uint32be caValue;
iosuCrypto_readOtpData(caValue, 0xA1);
uint32be msValue;
iosuCrypto_readOtpData(msValue, 0xA0);
sprintf(g_wiiuDeviceCert.certificateSubject, "Root-CA%08x-MS%08x", (uint32)caValue, (uint32)msValue);
uint32be ngNameValue;
iosuCrypto_readOtpData(ngNameValue, 0x87);
sprintf(g_wiiuDeviceCert.ngName, "NG%08x", (uint32)ngNameValue);
iosuCrypto_readOtpData(&g_wiiuDeviceCert.ngKeyId, 0xA2, sizeof(uint32));
uint8 privateKey[0x20];
memset(privateKey, 0, sizeof(privateKey));
iosuCrypto_readOtpData(privateKey, 0x88, 0x1E);
memcpy(g_consoleCertPrivKey.keyData, privateKey, 30);
auto context = BN_CTX_new();
BN_CTX_start(context);
BIGNUM* bn_privKey = BN_CTX_get(context);
BN_bin2bn(privateKey, 0x1E, bn_privKey);
EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_sect233r1);
EC_POINT *pubkey = EC_POINT_new(group);
EC_POINT_mul(group, pubkey, bn_privKey, NULL, NULL, NULL);
BIGNUM* bn_x = BN_CTX_get(context);
BIGNUM* bn_y = BN_CTX_get(context);
EC_POINT_get_affine_coordinates_GF2m(group, pubkey, bn_x, bn_y, NULL);
uint8 publicKeyOutput[0x3C];
memset(publicKeyOutput, 0, sizeof(publicKeyOutput));
sint32 lenX = BN_num_bytes(bn_x);
sint32 lenY = BN_num_bytes(bn_y);
BN_bn2bin(bn_x, publicKeyOutput + (0x1E - lenX)); // todo - verify if the bias is correct
BN_bn2bin(bn_y, publicKeyOutput + 0x3C / 2 + (0x1E - lenY));
memcpy(g_wiiuDeviceCert.publicKey, publicKeyOutput, 0x3C);
// clean up
EC_POINT_free(pubkey);
BN_CTX_end(context); // clears all BN variables
BN_CTX_free(context);
}
void CertECC_generateHashForSignature(CertECC_t& cert, CHash256& hashOut)
{
SHA256_CTX sha256;
SHA256_Init(&sha256);
SHA256_Update(&sha256, cert.certificateSubject, 0x100);
SHA256_Final(hashOut.b, &sha256);
}
bool iosuCrypto_hasAllDataForLogin()
{
if (hasOtpMem == false)
return false;
if (hasSeepromMem == false)
return false;
// todo - check if certificates are available
return true;
}
sint32 iosuCrypto_getDeviceCertificateBase64Encoded(char* output)
{
iosuCrypto_base64Encode((uint8*)&g_wiiuDeviceCert, sizeof(g_wiiuDeviceCert), output);
sint32 len = sizeof(g_wiiuDeviceCert) / 3 * 4;
output[len] = '\0';
return len;
}
bool iosuCrypto_loadCertificate(uint32 id, std::wstring_view mlcSubpath, std::wstring_view pkeyMlcSubpath)
{
X509* cert = nullptr;
// load cert data
const auto certPath = ActiveSettings::GetMlcPath(mlcSubpath);
auto certData = FileStream::LoadIntoMemory(certPath);
if (!certData)
return false; // file missing
// get optional aes encrypted private key data
std::optional<std::vector<uint8>> pkeyData;
if (!pkeyMlcSubpath.empty())
{
const auto pkeyPath = ActiveSettings::GetMlcPath(pkeyMlcSubpath);
pkeyData = FileStream::LoadIntoMemory(pkeyPath);
if (!pkeyData || pkeyData->empty())
{
cemuLog_force("Unable to load private key file {}", pkeyPath.generic_string());
return false;
}
else if ((pkeyData->size() % 16) != 0)
{
cemuLog_force("Private key file has invalid length. Possibly corrupted? File: {}", pkeyPath.generic_string());
return false;
}
}
// load certificate
unsigned char* tempPtr = (unsigned char*)certData->data();
cert = d2i_X509(nullptr, (const unsigned char**)&tempPtr, certData->size());
if (cert == nullptr)
{
cemuLog_log(LogType::Force, "IOSU_CRYPTO: Unable to load certificate \"{}\"", boost::nowide::narrow(std::wstring(mlcSubpath)));
return false;
}
// load optional rsa key
RSA* pkeyRSA = nullptr;
if (pkeyData)
{
cemu_assert((pkeyData->size() & 15) == 0);
uint8 aesKey[16];
uint8 iv[16] = { 0 };
uint8 pkeyDecryptedData[4096];
// decrypt pkey
iosuCrypto_readOtpData(aesKey, 0x120 / 4, 16);
AES128_CBC_decrypt(pkeyDecryptedData, pkeyData->data(), pkeyData->size(), aesKey, iv);
// convert to OpenSSL RSA pkey
unsigned char* pkeyTempPtr = pkeyDecryptedData;
pkeyRSA = d2i_RSAPrivateKey(nullptr, (const unsigned char **)&pkeyTempPtr, pkeyData->size());
if (pkeyRSA == nullptr)
{
cemuLog_log(LogType::Force, "IOSU_CRYPTO: Unable to decrypt private key \"{}\"", boost::nowide::narrow(std::wstring(pkeyMlcSubpath)));
return false;
}
// encode private key as DER
EVP_PKEY *evpPkey = EVP_PKEY_new();
EVP_PKEY_assign_RSA(evpPkey, pkeyRSA);
std::vector<uint8> derPKeyData(1024 * 32);
unsigned char* derPkeyTemp = derPKeyData.data();
sint32 derPkeySize = i2d_PrivateKey(evpPkey, &derPkeyTemp);
derPKeyData.resize(derPkeySize);
derPKeyData.shrink_to_fit();
iosuCryptoCertificates.certList[iosuCryptoCertificates.certListCount].pkeyDERData = derPKeyData;
}
// register certificate and optional pkey
iosuCryptoCertificates.certList[iosuCryptoCertificates.certListCount].cert = cert;
iosuCryptoCertificates.certList[iosuCryptoCertificates.certListCount].certData = *certData;
iosuCryptoCertificates.certList[iosuCryptoCertificates.certListCount].pkey = pkeyRSA;
iosuCryptoCertificates.certList[iosuCryptoCertificates.certListCount].id = id;
iosuCryptoCertificates.certList[iosuCryptoCertificates.certListCount].isValid = true;
iosuCryptoCertificates.certListCount++;
return true;
}
bool iosuCrypto_addClientCertificate(void* sslctx, sint32 certificateId)
{
SSL_CTX* ctx = (SSL_CTX*)sslctx;
// find entry
for (sint32 i = 0; i < iosuCryptoCertificates.certListCount; i++)
{
if (iosuCryptoCertificates.certList[i].isValid && iosuCryptoCertificates.certList[i].id == certificateId)
{
if (SSL_CTX_use_certificate(ctx, iosuCryptoCertificates.certList[i].cert) != 1)
{
forceLog_printf("Unable to setup certificate %d", certificateId);
return false;
}
if (SSL_CTX_use_RSAPrivateKey(ctx, iosuCryptoCertificates.certList[i].pkey) != 1)
{
forceLog_printf("Unable to setup certificate %d RSA private key", certificateId);
return false;
}
if (SSL_CTX_check_private_key(ctx) == false)
{
forceLog_printf("Certificate private key could not be validated (verify required files for online mode or disable online mode)");
}
return true;
}
}
forceLog_printf("Certificate not found (verify required files for online mode or disable online mode)");
return false;
}
bool iosuCrypto_addCACertificate(void* sslctx, sint32 certificateId)
{
SSL_CTX* ctx = (SSL_CTX*)sslctx;
// find entry
for (sint32 i = 0; i < iosuCryptoCertificates.certListCount; i++)
{
if (iosuCryptoCertificates.certList[i].isValid && iosuCryptoCertificates.certList[i].id == certificateId)
{
X509_STORE* store = SSL_CTX_get_cert_store((SSL_CTX*)sslctx);
X509_STORE_add_cert(store, iosuCryptoCertificates.certList[i].cert);
return true;
}
}
return false;
}
bool iosuCrypto_addCustomCACertificate(void* sslctx, uint8* certData, sint32 certLength)
{
SSL_CTX* ctx = (SSL_CTX*)sslctx;
X509_STORE* store = SSL_CTX_get_cert_store((SSL_CTX*)sslctx);
unsigned char* tempPtr = (unsigned char*)certData;
X509* cert = d2i_X509(NULL, (const unsigned char**)&tempPtr, certLength);
if (cert == nullptr)
{
forceLog_printf("Invalid custom server PKI certificate");
return false;
}
X509_STORE_add_cert(store, cert);
return true;
}
uint8* iosuCrypto_getCertificateDataById(sint32 certificateId, sint32* certificateSize)
{
for (sint32 i = 0; i < iosuCryptoCertificates.certListCount; i++)
{
if (iosuCryptoCertificates.certList[i].isValid && iosuCryptoCertificates.certList[i].id == certificateId)
{
*certificateSize = iosuCryptoCertificates.certList[i].certData.size();
return iosuCryptoCertificates.certList[i].certData.data();
}
}
return nullptr;
}
uint8* iosuCrypto_getCertificatePrivateKeyById(sint32 certificateId, sint32* certificateSize)
{
for (sint32 i = 0; i < iosuCryptoCertificates.certListCount; i++)
{
if (iosuCryptoCertificates.certList[i].isValid && iosuCryptoCertificates.certList[i].id == certificateId)
{
*certificateSize = iosuCryptoCertificates.certList[i].pkeyDERData.size();
return iosuCryptoCertificates.certList[i].pkeyDERData.data();
}
}
return nullptr;
}
struct
{
const int id;
const wchar_t name[256];
const wchar_t key[256];
} const g_certificates[] = {
// NINTENDO CLIENT CERTS
{ 1, L"ccerts/WIIU_COMMON_1_CERT.der", L"ccerts/WIIU_COMMON_1_RSA_KEY.aes" },
{ 3, L"ccerts/WIIU_ACCOUNT_1_CERT.der", L"ccerts/WIIU_ACCOUNT_1_RSA_KEY.aes" },
{ 4, L"ccerts/WIIU_OLIVE_1_CERT.der", L"ccerts/WIIU_OLIVE_1_RSA_KEY.aes" },
{ 5, L"ccerts/WIIU_VINO_1_CERT.der", L"ccerts/WIIU_VINO_1_RSA_KEY.aes" },
{ 6, L"ccerts/WIIU_WOOD_1_CERT.der", L"ccerts/WIIU_WOOD_1_RSA_KEY.aes" },
{ 7, L"ccerts/WIIU_OLIVE_1_CERT.der", L"ccerts/WIIU_OLIVE_1_RSA_KEY.aes" },
{ 8, L"ccerts/WIIU_WOOD_1_CERT.der", L"ccerts/WIIU_WOOD_1_RSA_KEY.aes" },
// NINTENDO CA CERTS
{ 100, L"scerts/CACERT_NINTENDO_CA.der", L"" },
{ 101, L"scerts/CACERT_NINTENDO_CA_G2.der", L"" },
{ 102, L"scerts/CACERT_NINTENDO_CA_G3.der", L"" },
{ 103, L"scerts/CACERT_NINTENDO_CLASS2_CA.der", L"" },
{ 104, L"scerts/CACERT_NINTENDO_CLASS2_CA_G2.der", L"" },
{ 105, L"scerts/CACERT_NINTENDO_CLASS2_CA_G3.der", L"" },
// COMMERCIAL CA CERTS
{ 1001, L"scerts/BALTIMORE_CYBERTRUST_ROOT_CA.der", L"" },
{ 1002, L"scerts/CYBERTRUST_GLOBAL_ROOT_CA.der", L"" },
{ 1003, L"scerts/VERIZON_GLOBAL_ROOT_CA.der", L"" },
{ 1004, L"scerts/GLOBALSIGN_ROOT_CA.der", L"" },
{ 1005, L"scerts/GLOBALSIGN_ROOT_CA_R2.der", L"" },
{ 1006, L"scerts/GLOBALSIGN_ROOT_CA_R3.der", L"" },
{ 1007, L"scerts/VERISIGN_CLASS3_PUBLIC_PRIMARY_CA_G3.der", L"" },
{ 1008, L"scerts/VERISIGN_UNIVERSAL_ROOT_CA.der", L"" },
{ 1009, L"scerts/VERISIGN_CLASS3_PUBLIC_PRIMARY_CA_G5.der", L"" },
{ 1010, L"scerts/THAWTE_PRIMARY_ROOT_CA_G3.der", L"" },
{ 1011, L"scerts/THAWTE_PRIMARY_ROOT_CA.der", L"" },
{ 1012, L"scerts/GEOTRUST_GLOBAL_CA.der", L"" },
{ 1013, L"scerts/GEOTRUST_GLOBAL_CA2.der", L"" },
{ 1014, L"scerts/GEOTRUST_PRIMARY_CA.der", L"" },
{ 1015, L"scerts/GEOTRUST_PRIMARY_CA_G3.der", L"" },
{ 1016, L"scerts/ADDTRUST_EXT_CA_ROOT.der", L"" },
{ 1017, L"scerts/COMODO_CA.der", L"" },
{ 1018, L"scerts/UTN_DATACORP_SGC_CA.der", L"" },
{ 1019, L"scerts/UTN_USERFIRST_HARDWARE_CA.der" , L"" },
{ 1020, L"scerts/DIGICERT_HIGH_ASSURANCE_EV_ROOT_CA.der", L"" },
{ 1021, L"scerts/DIGICERT_ASSURED_ID_ROOT_CA.der", L"" },
{ 1022, L"scerts/DIGICERT_GLOBAL_ROOT_CA.der", L"" },
{ 1023, L"scerts/GTE_CYBERTRUST_GLOBAL_ROOT.der", L"" },
{ 1024, L"scerts/VERISIGN_CLASS3_PUBLIC_PRIMARY_CA.der", L"" },
{ 1025, L"scerts/THAWTE_PREMIUM_SERVER_CA.der", L"" },
{ 1026, L"scerts/EQUIFAX_SECURE_CA.der", L"" },
{ 1027, L"scerts/ENTRUST_SECURE_SERVER_CA.der", L"" },
{ 1028, L"scerts/VERISIGN_CLASS3_PUBLIC_PRIMARY_CA_G2.der", L"" },
{ 1029, L"scerts/ENTRUST_CA_2048.der", L"" },
{ 1030, L"scerts/ENTRUST_ROOT_CA.der", L"" },
{ 1031, L"scerts/ENTRUST_ROOT_CA_G2.der", L"" },
{ 1032, L"scerts/DIGICERT_ASSURED_ID_ROOT_CA_G2.der", L"" },
{ 1033, L"scerts/DIGICERT_GLOBAL_ROOT_CA_G2.der", L"" },
};
void iosuCrypto_loadSSLCertificates()
{
if (iosuCryptoCertificates.hasCertificates)
return;
if (!hasOtpMem)
return; // cant load certificates without OTP keys
// load CA certificate
bool hasAllCertificates = true;
for( const auto& c : g_certificates )
{
std::wstring certDir = L"sys/title/0005001b/10054000/content/";
std::wstring certFilePath = certDir + c.name;
std::wstring keyFilePath;
if( *c.key )
keyFilePath = certDir + c.key;
else
keyFilePath.clear();
if (iosuCrypto_loadCertificate(c.id, certFilePath, keyFilePath) == false)
{
cemuLog_log(LogType::Force, "Unable to load certificate \"{}\"", boost::nowide::narrow(certFilePath));
hasAllCertificates = false;
}
}
iosuCryptoCertificates.hasCertificates = hasAllCertificates; // true
}
void iosuCrypto_init()
{
// load OTP dump
if (std::ifstream otp_file(ActiveSettings::GetPath("otp.bin"), std::ifstream::in | std::ios::binary); otp_file.is_open())
{
otp_file.seekg(0, std::ifstream::end);
const auto length = otp_file.tellg();
otp_file.seekg(0, std::ifstream::beg);
// verify if OTP is ok
if (length != 1024) // todo - should also check some fixed values to verify integrity of otp dump
{
forceLog_printf("IOSU_CRYPTO: otp.bin has wrong size (must be 1024 bytes)");
hasOtpMem = false;
}
else
{
otp_file.read((char*)otpMem, 1024);
hasOtpMem = (bool)otp_file;
}
}
else
{
forceLog_printf("IOSU_CRYPTO: No otp.bin found. Online mode cannot be used");
hasOtpMem = false;
}
if (std::ifstream seeprom_file(ActiveSettings::GetPath("seeprom.bin"), std::ifstream::in | std::ios::binary); seeprom_file.is_open())
{
seeprom_file.seekg(0, std::ifstream::end);
const auto length = seeprom_file.tellg();
seeprom_file.seekg(0, std::ifstream::beg);
// verify if seeprom is ok
if (length != 512) // todo - maybe check some known values to verify integrity of seeprom
{
cemuLog_log(LogType::Force, "IOSU_CRYPTO: seeprom.bin has wrong size (must be 512 bytes)");
hasSeepromMem = false;
}
else
{
seeprom_file.read((char*)seepromMem, 512);
hasSeepromMem = (bool)seeprom_file;
}
}
else
{
cemuLog_log(LogType::Force, "IOSU_CRYPTO: No Seeprom.bin found. Online mode cannot be used");
hasSeepromMem = false;
}
// generate device certificate
iosuCrypto_generateDeviceCertificate();
// load SSL certificates
iosuCrypto_loadSSLCertificates();
}
bool iosuCrypto_checkRequirementMLCFile(std::string_view mlcSubpath, std::wstring& additionalErrorInfo_filePath)
{
const auto path = ActiveSettings::GetMlcPath(mlcSubpath);
additionalErrorInfo_filePath = path.generic_wstring();
sint32 fileDataSize = 0;
auto fileData = FileStream::LoadIntoMemory(path);
if (!fileData)
return false;
return true;
}
sint32 iosuCrypt_checkRequirementsForOnlineMode(std::wstring& additionalErrorInfo)
{
std::error_code ec;
// check if otp.bin is present
const auto otp_file = ActiveSettings::GetPath("otp.bin");
if(!fs::exists(otp_file, ec))
return IOS_CRYPTO_ONLINE_REQ_OTP_MISSING;
if(fs::file_size(otp_file, ec) != 1024)
return IOS_CRYPTO_ONLINE_REQ_OTP_CORRUPTED;
// check if seeprom.bin is present
const auto seeprom_file = ActiveSettings::GetPath("seeprom.bin");
if (!fs::exists(seeprom_file, ec))
return IOS_CRYPTO_ONLINE_REQ_SEEPROM_MISSING;
if (fs::file_size(seeprom_file, ec) != 512)
return IOS_CRYPTO_ONLINE_REQ_SEEPROM_CORRUPTED;
for (const auto& c : g_certificates)
{
std::string subPath = fmt::format("sys/title/0005001b/10054000/content/{}", boost::nowide::narrow(c.name));
if (iosuCrypto_checkRequirementMLCFile(subPath, additionalErrorInfo) == false)
{
cemuLog_log(LogType::Force, "Missing dumped file for online mode: {}", subPath);
return IOS_CRYPTO_ONLINE_REQ_MISSING_FILE;
}
if (*c.key)
{
std::string subPath = fmt::format("sys/title/0005001b/10054000/content/{}", boost::nowide::narrow(c.key));
if (iosuCrypto_checkRequirementMLCFile(subPath, additionalErrorInfo) == false)
{
cemuLog_log(LogType::Force, "Missing dumped file for online mode: {}", subPath);
return IOS_CRYPTO_ONLINE_REQ_MISSING_FILE;
}
}
}
return IOS_CRYPTO_ONLINE_REQ_OK;
}
std::vector<const wchar_t*> iosuCrypt_getCertificateKeys()
{
std::vector<const wchar_t*> result;
result.reserve(std::size(g_certificates));
for (const auto& c : g_certificates)
{
if (c.key[0] == '\0')
continue;
result.emplace_back(c.key);
}
return result;
}
std::vector<const wchar_t*> iosuCrypt_getCertificateNames()
{
std::vector<const wchar_t*> result;
result.reserve(std::size(g_certificates));
for (const auto& c : g_certificates)
{
result.emplace_back(c.name);
}
return result;
}

View file

@ -0,0 +1,81 @@
#pragma once
void iosuCrypto_init();
bool iosuCrypto_hasAllDataForLogin();
bool iosuCrypto_getDeviceId(uint32* deviceId);
void iosuCrypto_getDeviceSerialString(char* serialString);
// certificate API
struct ECCPrivKey
{
uint8 keyData[30];
};
struct ECCPubKey
{
uint8 keyData[60];
};
struct ECCSig
{
uint8 keyData[60]; // check size?
};
struct CHash256
{
uint8 b[32];
};
struct CertECC_t
{
enum class SIGTYPE : uint32
{
ECC_SHA256 = 0x00010005
};
/* +0x000 */ betype<SIGTYPE> sigType; // 01 00 02 00
/* +0x004 */ uint8 signature[0x3C]; // from OTP 0xA3*4
/* +0x040 */ uint8 ukn040[0x40]; // seems to be just padding
/* +0x080 */ char certificateSubject[0x40]; // "Root - CA%08x - MS%08x"
/* +0x0C0 */ char ukn0C0[0x4]; // ??? 00 00 00 02 ?
/* +0x0C4 */ char ngName[0x40]; // "NG%08X"
/* +0x104 */ uint32 ngKeyId; // big endian? (from OTP 0xA2*4)
/* +0x108 */ uint8 publicKey[0x3C];
/* +0x144 */ uint8 padding[0x180 - 0x144];
};
static_assert(sizeof(CertECC_t) == 0x180);
sint32 iosuCrypto_getDeviceCertificateBase64Encoded(char* output);
bool iosuCrypto_verifyCert(CertECC_t& cert);
std::string iosuCrypto_base64Encode(unsigned char const* bytes_to_encode, unsigned int inputLen);
// certificate store
bool iosuCrypto_addClientCertificate(void* sslctx, sint32 certificateId);
bool iosuCrypto_addCACertificate(void* sslctx, sint32 certificateId);
bool iosuCrypto_addCustomCACertificate(void* sslctx, uint8* certData, sint32 certLength);
uint8* iosuCrypto_getCertificateDataById(sint32 certificateId, sint32* certificateSize);
uint8* iosuCrypto_getCertificatePrivateKeyById(sint32 certificateId, sint32* certificateSize);
// helper for online functionality
enum
{
IOS_CRYPTO_ONLINE_REQ_OK,
IOS_CRYPTO_ONLINE_REQ_OTP_MISSING,
IOS_CRYPTO_ONLINE_REQ_OTP_CORRUPTED,
IOS_CRYPTO_ONLINE_REQ_SEEPROM_MISSING,
IOS_CRYPTO_ONLINE_REQ_SEEPROM_CORRUPTED,
IOS_CRYPTO_ONLINE_REQ_MISSING_FILE
};
sint32 iosuCrypt_checkRequirementsForOnlineMode(std::wstring& additionalErrorInfo);
void iosuCrypto_readOtpData(void* output, sint32 wordIndex, sint32 size);
std::vector<const wchar_t*> iosuCrypt_getCertificateKeys();
std::vector<const wchar_t*> iosuCrypt_getCertificateNames();

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,351 @@
#pragma once
namespace iosu
{
namespace fpd
{
typedef struct
{
/* +0x0 */ uint16be year;
/* +0x2 */ uint8 month;
/* +0x3 */ uint8 day;
/* +0x4 */ uint8 hour;
/* +0x5 */ uint8 minute;
/* +0x6 */ uint8 second;
/* +0x7 */ uint8 padding;
}fpdDate_t;
static_assert(sizeof(fpdDate_t) == 8);
typedef struct
{
/* +0x000 */ uint8 type; // type(Non-Zero -> Friend, 0 -> Friend request ? )
/* +0x001 */ uint8 _padding1[7];
/* +0x008 */ uint32be pid;
/* +0x00C */ char nnid[0x10 + 1];
/* +0x01D */ uint8 ukn01D;
/* +0x01E */ uint8 _padding1E[2];
/* +0x020 */ uint16be screenname[10 + 1];
/* +0x036 */ uint8 ukn036;
/* +0x037 */ uint8 _padding037;
/* +0x038 */ uint8 mii[0x60];
/* +0x098 */ fpdDate_t uknDate;
// sub struct (the part above seems to be shared with friend requests)
union
{
struct
{
/* +0x0A0 */ uint8 ukn0A0; // country code?
/* +0x0A1 */ uint8 ukn0A1; // country subcode?
/* +0x0A2 */ uint8 _paddingA2[2];
/* +0x0A4 */ uint32be ukn0A4;
/* +0x0A8 */ uint64 gameKeyTitleId;
/* +0x0B0 */ uint16be gameKeyUkn;
/* +0x0B2 */ uint8 _paddingB2[6];
/* +0x0B8 */ uint32 ukn0B8;
/* +0x0BC */ uint32 ukn0BC;
/* +0x0C0 */ uint32 ukn0C0;
/* +0x0C4 */ uint32 ukn0C4;
/* +0x0C8 */ uint32 ukn0C8;
/* +0x0CC */ uint32 ukn0CC;
/* +0x0D0 */ uint8 appSpecificData[0x14];
/* +0x0E4 */ uint8 ukn0E4;
/* +0x0E5 */ uint8 _paddingE5;
/* +0x0E6 */ uint16 uknStr[0x80]; // game mode description (could be larger)
/* +0x1E6 */ uint8 _padding1E6[0x1EC - 0x1E6];
/* +0x1EC */ uint8 isOnline;
/* +0x1ED */ uint8 _padding1ED[3];
// some other sub struct?
/* +0x1F0 */ uint8 ukn1F0;
/* +0x1F1 */ uint8 _padding1F1;
/* +0x1F2 */ uint16be statusMessage[16 + 1]; // pops up every few seconds in friend list (ingame character name?)
/* +0x214 */ uint8 _padding214[4];
/* +0x218 */ fpdDate_t uknDate218;
/* +0x220 */ fpdDate_t lastOnline;
}friendExtraData;
struct
{
/* +0x0A0 */ uint64 messageId; // guessed
/* +0x0A8 */ uint8 ukn0A8;
/* +0x0A9 */ uint8 ukn0A9; // comment language? (guessed)
/* +0x0AA */ uint16be comment[0x40];
/* +0x12A */ uint8 ukn12A; // ingame name language? (guessed)
/* +0x12B */ uint8 _padding12B;
/* +0x12C */ uint16be uknMessage[18]; // exact length unknown, could be shorter (ingame screen name?)
/* +0x150 */ uint64 gameKeyTitleId;
/* +0x158 */ uint16be gameKeyUkn;
/* +0x15A */ uint8 _padding[6];
/* +0x160 */ fpdDate_t uknData0;
/* +0x168 */ fpdDate_t uknData1;
}requestExtraData;
};
}friendData_t;
static_assert(sizeof(friendData_t) == 0x228, "");
static_assert(offsetof(friendData_t, nnid) == 0x00C, "");
static_assert(offsetof(friendData_t, friendExtraData.gameKeyTitleId) == 0x0A8, "");
static_assert(offsetof(friendData_t, friendExtraData.appSpecificData) == 0x0D0, "");
static_assert(offsetof(friendData_t, friendExtraData.uknStr) == 0x0E6, "");
static_assert(offsetof(friendData_t, friendExtraData.ukn1F0) == 0x1F0, "");
static_assert(offsetof(friendData_t, requestExtraData.messageId) == 0x0A0, "");
static_assert(offsetof(friendData_t, requestExtraData.comment) == 0x0AA, "");
static_assert(offsetof(friendData_t, requestExtraData.uknMessage) == 0x12C, "");
static_assert(offsetof(friendData_t, requestExtraData.gameKeyTitleId) == 0x150, "");
static_assert(offsetof(friendData_t, requestExtraData.uknData1) == 0x168, "");
typedef struct
{
/* +0x00 */ uint32be pid;
/* +0x04 */ char nnid[0x11];
/* +0x15 */ uint8 ukn15;
/* +0x16 */ uint8 uknOrPadding[2];
/* +0x18 */ uint16be screenname[11];
/* +0x2E */ uint8 ukn2E; // bool option
/* +0x2F */ uint8 ukn2F;
/* +0x30 */ uint8 miiData[0x60];
/* +0x90 */ fpdDate_t uknDate90;
}friendBasicInfo_t; // size is 0x98
static_assert(sizeof(friendBasicInfo_t) == 0x98, "");
static_assert(offsetof(friendBasicInfo_t, nnid) == 0x04, "");
static_assert(offsetof(friendBasicInfo_t, ukn15) == 0x15, "");
static_assert(offsetof(friendBasicInfo_t, screenname) == 0x18, "");
static_assert(offsetof(friendBasicInfo_t, ukn2E) == 0x2E, "");
static_assert(offsetof(friendBasicInfo_t, miiData) == 0x30, "");
typedef struct
{
/* +0x000 */ uint32be pid;
/* +0x004 */ uint8 nnid[17]; // guessed type
/* +0x015 */ uint8 _uknOrPadding15;
/* +0x016 */ uint8 _uknOrPadding16;
/* +0x017 */ uint8 _uknOrPadding17;
/* +0x018 */ uint16be screenname[11];
/* +0x02E */ uint8 ukn2E; // bool option
/* +0x02F */ uint8 ukn2F; // ukn
/* +0x030 */ uint8 miiData[0x60];
/* +0x090 */ fpdDate_t uknDate;
/* +0x098 */ uint64 ukn98;
/* +0x0A0 */ uint8 isMarkedAsReceived;
/* +0x0A1 */ uint8 uknA1;
/* +0x0A2 */ uint16be message[0x40];
/* +0x122 */ uint8 ukn122;
/* +0x123 */ uint8 _padding123;
/* +0x124 */ uint16be uknString2[18];
/* +0x148 */ uint64 gameKeyTitleId;
/* +0x150 */ uint16be gameKeyUkn;
/* +0x152 */ uint8 _padding152[6];
/* +0x158 */ fpdDate_t uknDate2;
/* +0x160 */ fpdDate_t expireDate;
}friendRequest_t;
static_assert(sizeof(friendRequest_t) == 0x168, "");
static_assert(offsetof(friendRequest_t, uknDate) == 0x090, "");
static_assert(offsetof(friendRequest_t, message) == 0x0A2, "");
static_assert(offsetof(friendRequest_t, uknString2) == 0x124, "");
static_assert(offsetof(friendRequest_t, gameKeyTitleId) == 0x148, "");
typedef struct
{
/* +0x00 */ uint32be joinFlagMask;
/* +0x04 */ uint32be matchmakeType;
/* +0x08 */ uint32be joinGameId;
/* +0x0C */ uint32be joinGameMode;
/* +0x10 */ uint32be hostPid;
/* +0x14 */ uint32be groupId;
/* +0x18 */ uint8 appSpecificData[0x14];
}gameMode_t;
static_assert(sizeof(gameMode_t) == 0x2C, "");
typedef struct
{
gameMode_t gameMode;
/* +0x2C */ uint8 region;
/* +0x2D */ uint8 regionSubcode;
/* +0x2E */ uint8 platform;
/* +0x2F */ uint8 _padding2F;
/* +0x30 */ uint8 isOnline;
/* +0x31 */ uint8 isValid;
/* +0x32 */ uint8 padding[2]; // guessed
}friendPresence_t;
static_assert(sizeof(friendPresence_t) == 0x34, "");
static_assert(offsetof(friendPresence_t, region) == 0x2C, "");
static_assert(offsetof(friendPresence_t, isOnline) == 0x30, "");
static const int RELATIONSHIP_INVALID = 0;
static const int RELATIONSHIP_FRIENDREQUEST_OUT = 1;
static const int RELATIONSHIP_FRIENDREQUEST_IN = 2;
static const int RELATIONSHIP_FRIEND = 3;
typedef struct
{
uint32 requestCode;
union
{
struct
{
MEMPTR<void> ptr;
}common;
struct
{
MPTR funcPtr;
MPTR custom;
}loginAsync;
struct
{
MEMPTR<uint32be> pidList;
uint32 startIndex;
uint32 maxCount;
}getFriendList;
struct
{
MEMPTR<friendData_t> friendData;
MEMPTR<uint32be> pidList;
uint32 count;
}getFriendListEx;
struct
{
MEMPTR<friendRequest_t> friendRequest;
MEMPTR<uint32be> pidList;
uint32 count;
}getFriendRequestListEx;
struct
{
uint32 pid;
MPTR funcPtr;
MPTR custom;
}addOrRemoveFriend;
struct
{
uint64 messageId;
MPTR funcPtr;
MPTR custom;
}cancelOrAcceptFriendRequest;
struct
{
uint32 pid;
MEMPTR<uint16be> message;
MPTR funcPtr;
MPTR custom;
}addFriendRequest;
struct
{
MEMPTR<uint64> messageIdList;
uint32 count;
MPTR funcPtr;
MPTR custom;
}markFriendRequest;
struct
{
MEMPTR<friendBasicInfo_t> basicInfo;
MEMPTR<uint32be> pidList;
sint32 count;
MPTR funcPtr;
MPTR custom;
}getBasicInfo;
struct
{
uint32 notificationMask;
MPTR funcPtr;
MPTR custom;
}setNotificationHandler;
struct
{
MEMPTR<char> accountIds;
MEMPTR<uint32be> pidList;
sint32 count;
}getFriendAccountId;
struct
{
MEMPTR<uint16be> nameList;
MEMPTR<uint32be> pidList;
sint32 count;
bool replaceNonAscii;
MEMPTR<uint8> languageList;
}getFriendScreenname;
struct
{
uint8* miiList;
MEMPTR<uint32be> pidList;
sint32 count;
}getFriendMii;
struct
{
uint8* presenceList;
MEMPTR<uint32be> pidList;
sint32 count;
}getFriendPresence;
struct
{
uint8* relationshipList;
MEMPTR<uint32be> pidList;
sint32 count;
}getFriendRelationship;
struct
{
MEMPTR<gameMode_t> gameMode;
MEMPTR<uint16be> gameModeMessage;
}updateGameMode;
};
// output
uint32 returnCode; // return value
union
{
struct
{
uint32 numReturnedCount;
}resultGetFriendList;
struct
{
uint32 u32;
}resultU32;
};
}iosuFpdCemuRequest_t;
// custom dev/fpd protocol (Cemu only)
#define IOSU_FPD_REQUEST_CEMU (0xEE)
// FPD request Cemu subcodes
enum
{
_IOSU_FPD_NONE,
IOSU_FPD_INITIALIZE,
IOSU_FPD_SET_NOTIFICATION_HANDLER,
IOSU_FPD_LOGIN_ASYNC,
IOSU_FPD_IS_ONLINE,
IOSU_FPD_IS_PREFERENCE_VALID,
IOSU_FPD_UPDATE_GAMEMODE,
IOSU_FPD_GET_MY_PRINCIPAL_ID,
IOSU_FPD_GET_MY_ACCOUNT_ID,
IOSU_FPD_GET_MY_MII,
IOSU_FPD_GET_MY_SCREENNAME,
IOSU_FPD_GET_FRIEND_ACCOUNT_ID,
IOSU_FPD_GET_FRIEND_SCREENNAME,
IOSU_FPD_GET_FRIEND_MII,
IOSU_FPD_GET_FRIEND_PRESENCE,
IOSU_FPD_GET_FRIEND_RELATIONSHIP,
IOSU_FPD_GET_FRIEND_LIST,
IOSU_FPD_GET_FRIENDREQUEST_LIST,
IOSU_FPD_GET_FRIEND_LIST_ALL,
IOSU_FPD_GET_FRIEND_LIST_EX,
IOSU_FPD_GET_FRIENDREQUEST_LIST_EX,
IOSU_FPD_ADD_FRIEND,
IOSU_FPD_ADD_FRIEND_REQUEST,
IOSU_FPD_REMOVE_FRIEND_ASYNC,
IOSU_FPD_CANCEL_FRIEND_REQUEST_ASYNC,
IOSU_FPD_ACCEPT_FRIEND_REQUEST_ASYNC,
IOSU_FPD_MARK_FRIEND_REQUEST_AS_RECEIVED_ASYNC,
IOSU_FPD_GET_BASIC_INFO_ASYNC,
};
void Initialize();
}
}

View file

@ -0,0 +1,68 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/coreinit/coreinit_Thread.h"
#include "Cafe/OS/libs/coreinit/coreinit.h"
#include "iosu_ioctl.h"
#include "util/helpers/ringbuffer.h"
#include "util/helpers/Semaphore.h"
// deprecated IOCTL handling code
RingBuffer<ioQueueEntry_t*, 256> _ioctlRingbuffer[IOS_DEVICE_COUNT];
CounterSemaphore _ioctlRingbufferSemaphore[IOS_DEVICE_COUNT];
std::mutex ioctlMutex;
sint32 iosuIoctl_pushAndWait(uint32 ioctlHandle, ioQueueEntry_t* ioQueueEntry)
{
if (ioctlHandle != IOS_DEVICE_ACT && ioctlHandle != IOS_DEVICE_ACP_MAIN && ioctlHandle != IOS_DEVICE_MCP && ioctlHandle != IOS_DEVICE_BOSS && ioctlHandle != IOS_DEVICE_NIM && ioctlHandle != IOS_DEVICE_FPD)
{
forceLogDebug_printf("Unsupported IOSU device %d", ioctlHandle);
cemu_assert_debug(false);
return 0;
}
__OSLockScheduler();
ioctlMutex.lock();
ioQueueEntry->ppcThread = coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance);
_ioctlRingbuffer[ioctlHandle].Push(ioQueueEntry);
ioctlMutex.unlock();
_ioctlRingbufferSemaphore[ioctlHandle].increment();
coreinit::__OSSuspendThreadInternal(coreinit::OSGetCurrentThread());
if (ioQueueEntry->isCompleted == false)
assert_dbg();
__OSUnlockScheduler();
return ioQueueEntry->returnValue;
}
ioQueueEntry_t* iosuIoctl_getNextWithWait(uint32 deviceIndex)
{
_ioctlRingbufferSemaphore[deviceIndex].decrementWithWait();
if (_ioctlRingbuffer[deviceIndex].HasData() == false)
assert_dbg();
return _ioctlRingbuffer[deviceIndex].Pop();
}
ioQueueEntry_t* iosuIoctl_getNextWithTimeout(uint32 deviceIndex, sint32 ms)
{
if (!_ioctlRingbufferSemaphore[deviceIndex].decrementWithWaitAndTimeout(ms))
return nullptr; // timeout or spurious wake up
if (_ioctlRingbuffer[deviceIndex].HasData() == false)
return nullptr;
return _ioctlRingbuffer[deviceIndex].Pop();
}
void iosuIoctl_completeRequest(ioQueueEntry_t* ioQueueEntry, uint32 returnValue)
{
ioQueueEntry->returnValue = returnValue;
ioQueueEntry->isCompleted = true;
coreinit::OSResumeThread(ioQueueEntry->ppcThread);
}
void iosuIoctl_init()
{
for (sint32 i = 0; i < IOS_DEVICE_COUNT; i++)
{
_ioctlRingbuffer[i].Clear();
}
}

View file

@ -0,0 +1,64 @@
#pragma once
struct OSThread_t;
typedef struct _ioBufferVector_t
{
// the meaning of these values might be arbitrary? (up to the /dev/* handler to process them)
//uint32 ukn00;
MEMPTR<uint8> buffer;
uint32be bufferSize;
uint32 ukn08;
//uint32 ukn0C;
MEMPTR<uint8> unknownBuffer;
}ioBufferVector_t;
typedef struct
{
bool isIoctlv; // false -> ioctl, true -> ioctlv
bool isAsync;
uint32 request;
uint32 countIn;
uint32 countOut;
MEMPTR<ioBufferVector_t> bufferVectors;
// info about submitter
OSThread_t* ppcThread;
//MPTR ppcThread;
// result
bool isCompleted;
uint32 returnValue;
}ioQueueEntry_t;
#define IOS_PATH_SOCKET "/dev/socket"
#define IOS_PATH_ODM "/dev/odm"
#define IOS_PATH_ACT "/dev/act"
#define IOS_PATH_FPD "/dev/fpd"
#define IOS_PATH_ACP_MAIN "/dev/acp_main"
#define IOS_PATH_MCP "/dev/mcp"
#define IOS_PATH_BOSS "/dev/boss"
#define IOS_PATH_NIM "/dev/nim"
#define IOS_PATH_IOSUHAX "/dev/iosuhax"
#define IOS_DEVICE_UKN (0x1)
#define IOS_DEVICE_ODM (0x2)
#define IOS_DEVICE_SOCKET (0x3)
#define IOS_DEVICE_ACT (0x4)
#define IOS_DEVICE_FPD (0x5)
#define IOS_DEVICE_ACP_MAIN (0x6)
#define IOS_DEVICE_MCP (0x7)
#define IOS_DEVICE_BOSS (0x8)
#define IOS_DEVICE_NIM (0x9)
#define IOS_DEVICE_COUNT 10
void iosuIoctl_init();
// for use by IOSU
ioQueueEntry_t* iosuIoctl_getNextWithWait(uint32 deviceIndex);
ioQueueEntry_t* iosuIoctl_getNextWithTimeout(uint32 deviceIndex, sint32 ms);
void iosuIoctl_completeRequest(ioQueueEntry_t* ioQueueEntry, uint32 returnValue);
// for use by PPC
sint32 iosuIoctl_pushAndWait(uint32 ioctlHandle, ioQueueEntry_t* ioQueueEntry);

View file

@ -0,0 +1,268 @@
#include "iosu_ioctl.h"
#include "iosu_mcp.h"
#include "Cafe/OS/libs/nn_common.h"
#include "util/tinyxml2/tinyxml2.h"
#include "config/CemuConfig.h"
#include "Cafe/TitleList/GameInfo.h"
#include "util/helpers/helpers.h"
#include "Cafe/TitleList/TitleList.h"
#include "Cafe/CafeSystem.h"
#include "Cafe/IOSU/iosu_ipc_common.h"
#include "Cafe/IOSU/kernel/iosu_kernel.h"
using namespace iosu::kernel;
namespace iosu
{
struct
{
bool isInitialized;
}iosuMcp = { 0 };
std::recursive_mutex sTitleInfoMutex;
bool sHasAllTitlesMounted = false;
uint32 mcpBuildTitleList(MCPTitleInfo* titleList, uint32 maxTitlesToCopy, std::function<bool(const TitleInfo&)> filterFunc)
{
CafeTitleList::WaitForMandatoryScan();
std::set<TitleId> titleIdsVisited;
auto cafeTitleList = CafeTitleList::AcquireInternalList();
uint32 numTitlesCopied = 0;
for (auto& it : cafeTitleList)
{
if (numTitlesCopied == maxTitlesToCopy)
break;
uint64 titleId = it->GetAppTitleId();
if (titleIdsVisited.find(titleId) != titleIdsVisited.end())
continue;
if (!filterFunc(*it))
continue;
titleIdsVisited.emplace(titleId);
if (titleList)
{
auto& titleOut = titleList[numTitlesCopied];
titleOut.titleIdHigh = (uint32)(titleId >> 32);
titleOut.titleIdLow = (uint32)(titleId & 0xFFFFFFFF);
titleOut.titleVersion = it->GetAppTitleVersion();
titleOut.appGroup = it->GetAppGroup();
titleOut.appType = it->GetAppType();
std::string titlePath = CafeSystem::GetMlcStoragePath(titleId);
strcpy(titleOut.appPath, titlePath.c_str());
titleOut.osVersion = 0; // todo
titleOut.sdkVersion = 0;
}
numTitlesCopied++;
}
CafeTitleList::ReleaseInternalList();
if (!sHasAllTitlesMounted)
{
CafeSystem::MlcStorageMountAllTitles();
sHasAllTitlesMounted = true;
}
return numTitlesCopied;
}
sint32 mcpGetTitleList(MCPTitleInfo* titleList, uint32 titleListBufferSize, uint32be* titleCount)
{
std::unique_lock _lock(sTitleInfoMutex);
*titleCount = mcpBuildTitleList(titleList, *titleCount, [](const TitleInfo& titleInfo) -> bool { return true; });
return 0;
}
sint32 mcpGetTitleCount()
{
std::unique_lock _lock(sTitleInfoMutex);
return mcpBuildTitleList(nullptr, 0xFFFFFFFF, [](const TitleInfo& titleInfo) -> bool { return true; });
}
sint32 mcpGetTitleListByAppType(MCPTitleInfo* titleList, uint32 titleListBufferSize, uint32be* titleCount, uint32 appType)
{
std::unique_lock _lock(sTitleInfoMutex);
uint32 maxEntryCount = (uint32)*titleCount;
*titleCount = mcpBuildTitleList(titleList, maxEntryCount, [appType](const TitleInfo& titleInfo) -> bool { return titleInfo.GetAppType() == appType; });
return 0;
}
sint32 mcpGetTitleListByTitleId(MCPTitleInfo* titleList, uint32 titleListBufferSize, uint32be* titleCount, uint64 titleId)
{
std::unique_lock _lock(sTitleInfoMutex);
uint32 maxEntryCount = (uint32)*titleCount;
*titleCount = mcpBuildTitleList(titleList, maxEntryCount, [titleId](const TitleInfo& titleInfo) -> bool { return titleInfo.GetAppTitleId() == titleId; });
return 0;
}
int iosuMcp_thread()
{
SetThreadName("iosuMcp_thread");
while (true)
{
uint32 returnValue = 0; // Ioctl return value
ioQueueEntry_t* ioQueueEntry = iosuIoctl_getNextWithWait(IOS_DEVICE_MCP);
if (ioQueueEntry->request == IOSU_MCP_REQUEST_CEMU)
{
iosuMcpCemuRequest_t* mcpCemuRequest = (iosuMcpCemuRequest_t*)ioQueueEntry->bufferVectors[0].buffer.GetPtr();
if (mcpCemuRequest->requestCode == IOSU_MCP_GET_TITLE_LIST)
{
uint32be titleCount = mcpCemuRequest->titleListRequest.titleCount;
mcpCemuRequest->returnCode = mcpGetTitleList(mcpCemuRequest->titleListRequest.titleList.GetPtr(), mcpCemuRequest->titleListRequest.titleListBufferSize, &titleCount);
mcpCemuRequest->titleListRequest.titleCount = titleCount;
}
else if (mcpCemuRequest->requestCode == IOSU_MCP_GET_TITLE_LIST_BY_APP_TYPE)
{
uint32be titleCount = mcpCemuRequest->titleListRequest.titleCount;
mcpCemuRequest->returnCode = mcpGetTitleListByAppType(mcpCemuRequest->titleListRequest.titleList.GetPtr(), mcpCemuRequest->titleListRequest.titleListBufferSize, &titleCount, mcpCemuRequest->titleListRequest.appType);
mcpCemuRequest->titleListRequest.titleCount = titleCount;
}
else if (mcpCemuRequest->requestCode == IOSU_MCP_GET_TITLE_INFO)
{
uint32be titleCount = mcpCemuRequest->titleListRequest.titleCount;
mcpCemuRequest->returnCode = mcpGetTitleListByTitleId(mcpCemuRequest->titleListRequest.titleList.GetPtr(), mcpCemuRequest->titleListRequest.titleListBufferSize, &titleCount, mcpCemuRequest->titleListRequest.titleId);
mcpCemuRequest->titleListRequest.titleCount = titleCount;
}
else if (mcpCemuRequest->requestCode == IOSU_MCP_GET_TITLE_COUNT)
{
uint32be titleCount = mcpGetTitleCount();
mcpCemuRequest->returnCode = titleCount;
mcpCemuRequest->titleListRequest.titleCount = titleCount;
}
else
assert_dbg();
}
else
assert_dbg();
iosuIoctl_completeRequest(ioQueueEntry, returnValue);
}
return 0;
}
void iosuMcp_init()
{
if (iosuMcp.isInitialized)
return;
// start the act thread
std::thread t(iosuMcp_thread);
t.detach();
iosuMcp.isInitialized = true;
}
namespace mcp
{
IOSMsgQueueId sMCPIoMsgQueue;
SysAllocator<iosu::kernel::IOSMessage, 352> _m_sMCPIoMsgQueueMsgBuffer;
std::thread sMCPIoThread;
struct MCPClient
{
std::string workingDirectory;
bool isAllocated{ false };
void AllocateAndInitialize()
{
isAllocated = true;
workingDirectory = std::string("/");
}
void ReleaseAndCleanup()
{
isAllocated = false;
}
};
std::array<MCPClient, 256> sMCPClientArray;
IOS_ERROR MCPAllocateClient(sint32& indexOut)
{
for (size_t i = 0; i < sMCPClientArray.size(); i++)
{
if (sMCPClientArray[i].isAllocated)
continue;
sMCPClientArray[i].AllocateAndInitialize();
indexOut = (sint32)i;
return IOS_ERROR_OK;
}
return IOS_ERROR::IOS_ERROR_MAXIMUM_REACHED;
}
void MCPIoThread()
{
SetThreadName("IOSU-MCP");
IOSMessage msg;
while (true)
{
IOS_ERROR r = IOS_ReceiveMessage(sMCPIoMsgQueue, &msg, 0);
cemu_assert(!IOS_ResultIsError(r));
if (msg == 0)
return; // shutdown signaled
IPCCommandBody* cmd = MEMPTR<IPCCommandBody>(msg).GetPtr();
uint32 clientHandle = (uint32)cmd->devHandle;
if (cmd->cmdId == IPCCommandId::IOS_OPEN)
{
sint32 clientIndex = 0;
r = MCPAllocateClient(clientIndex);
if (r != IOS_ERROR_OK)
{
IOS_ResourceReply(cmd, r);
continue;
}
IOS_ResourceReply(cmd, (IOS_ERROR)clientIndex);
continue;
}
else if (cmd->cmdId == IPCCommandId::IOS_CLOSE)
{
cemu_assert(clientHandle < sMCPClientArray.size());
sMCPClientArray[clientHandle].ReleaseAndCleanup();
IOS_ResourceReply(cmd, IOS_ERROR_OK);
continue;
}
else if (cmd->cmdId == IPCCommandId::IOS_IOCTL)
{
cemu_assert(clientHandle < sMCPClientArray.size());
cemu_assert(sMCPClientArray[clientHandle].isAllocated);
IOS_ResourceReply(cmd, (IOS_ERROR)0x80000000);
continue;
}
else if (cmd->cmdId == IPCCommandId::IOS_IOCTLV)
{
cemu_assert_unimplemented();
//uint32 requestId = cmd->args[0];
//uint32 numIn = cmd->args[1];
//uint32 numOut = cmd->args[2];
//IPCIoctlVector* vec = MEMPTR<IPCIoctlVector>{ cmd->args[3] }.GetPtr();
IOS_ResourceReply(cmd, IOS_ERROR_INVALID);
continue;
}
else
{
cemuLog_log(LogType::Force, "/dev/mcp: Unsupported IPC cmdId");
cemu_assert_suspicious();
IOS_ResourceReply(cmd, IOS_ERROR_INVALID);
}
}
}
void Init()
{
for (auto& it : sMCPClientArray)
it.ReleaseAndCleanup();
sMCPIoMsgQueue = (IOSMsgQueueId)IOS_CreateMessageQueue(_m_sMCPIoMsgQueueMsgBuffer.GetPtr(), _m_sMCPIoMsgQueueMsgBuffer.GetCount());
IOS_ERROR r = IOS_RegisterResourceManager("/dev/mcp", sMCPIoMsgQueue);
IOS_DeviceAssociateId("/dev/mcp", 11);
cemu_assert(!IOS_ResultIsError(r));
sMCPIoThread = std::thread(MCPIoThread);
}
void Shutdown()
{
IOS_SendMessage(sMCPIoMsgQueue, 0, 0);
sMCPIoThread.join();
}
};
}

View file

@ -0,0 +1,81 @@
#pragma pack(1)
struct MCPTitleInfo
{
/* +0x00 */ uint32be titleIdHigh; // app.xml
/* +0x04 */ uint32be titleIdLow;
/* +0x08 */ uint32be appGroup;
/* +0x0C */ char appPath[0x38]; // size guessed
/* +0x44 */ uint32be appType; // app.xml
/* +0x48 */ uint16be titleVersion; // app.xml
// everything below is uncertain
/* +0x4A */ uint64be osVersion; // app.xml
/* +0x52 */ uint32be sdkVersion; // app.xml
/* +0x56 */ uint8 ukn[0x61 - 0x56];
//move this and the stuff below
};
static_assert(sizeof(MCPTitleInfo) == 0x61);
#pragma pack()
// custom dev/mcp protocol (Cemu only)
#define IOSU_MCP_REQUEST_CEMU (0xEE)
typedef struct
{
uint32 requestCode;
struct
{
MEMPTR<MCPTitleInfo> titleList;
uint32be titleCount;
uint32be titleListBufferSize;
uint64 titleId;
// filters
uint32be appType;
}titleListRequest;
// output
uint32 returnCode; // ACP return value
union
{
//struct
//{
// uint64 u64;
//}resultU64;
struct
{
uint32 u32;
}resultU32;
//struct
//{
// char strBuffer[1024];
//}resultString;
//struct
//{
// uint8 binBuffer[1024];
//}resultBinary;
};
}iosuMcpCemuRequest_t;
// MCP request Cemu subcodes
#define IOSU_MCP_GET_TITLE_LIST 0x01
#define IOSU_MCP_GET_TITLE_LIST_BY_APP_TYPE 0x02
#define IOSU_MCP_GET_TITLE_INFO 0x03 // get title list by titleId
#define IOSU_MCP_GET_TITLE_COUNT 0x04
namespace iosu
{
sint32 mcpGetTitleCount();
sint32 mcpGetTitleList(MCPTitleInfo* titleList, uint32 titleListBufferSize, uint32be* titleCount);
void iosuMcp_init();
namespace mcp
{
void Init();
void Shutdown();
};
}

View file

@ -0,0 +1,333 @@
#include "iosu_nim.h"
#include "iosu_ioctl.h"
#include "iosu_act.h"
#include "iosu_mcp.h"
#include "util/crypto/aes128.h"
#include "curl/curl.h"
#include "openssl/bn.h"
#include "openssl/x509.h"
#include "openssl/ssl.h"
#include "util/helpers/helpers.h"
#include <thread>
#include "Cemu/napi/napi.h"
#include "Cemu/ncrypto/ncrypto.h"
namespace iosu
{
namespace nim
{
struct NIMTitleLatestVersion
{
uint64 titleId;
uint32 version;
};
#define PACKAGE_TYPE_UPDATE (1)
struct NIMPackage
{
uint64 titleId;
uint16 version;
uint8 type;
};
struct
{
bool isInitialized;
// version list
sint32 latestVersion;
char fqdn[256];
std::vector<NIMTitleLatestVersion> titlesLatestVersion;
// nim packages
// note: Seems like scope.rpx expects the number of packages to never change after the initial GetNum call?
std::vector<NIMPackage*> packages;
bool packageListReady;
bool backgroundThreadStarted;
} g_nim = {};
bool nim_getLatestVersion()
{
g_nim.latestVersion = -1;
NAPI::AuthInfo authInfo;
authInfo.country = NCrypto::GetCountryAsString(Account::GetCurrentAccount().GetCountry());
authInfo.region = NCrypto::SEEPROM_GetRegion();
auto versionListVersionResult = NAPI::TAG_GetVersionListVersion(authInfo);
if (!versionListVersionResult.isValid)
return false;
if (versionListVersionResult.fqdnURL.size() >= 256)
{
cemuLog_force("NIM: fqdn URL too long");
return false;
}
g_nim.latestVersion = (sint32)versionListVersionResult.version;
strcpy(g_nim.fqdn, versionListVersionResult.fqdnURL.c_str());
return true;
}
bool nim_getVersionList()
{
g_nim.titlesLatestVersion.clear();
NAPI::AuthInfo authInfo;
authInfo.country = NCrypto::GetCountryAsString(Account::GetCurrentAccount().GetCountry());
authInfo.region = NCrypto::SEEPROM_GetRegion();
auto versionListVersionResult = NAPI::TAG_GetVersionListVersion(authInfo);
if (!versionListVersionResult.isValid)
return false;
auto versionListResult = TAG_GetVersionList(authInfo, versionListVersionResult.fqdnURL, versionListVersionResult.version);
if (!versionListResult.isValid)
return false;
for (auto& itr : versionListResult.titleVersionList)
{
NIMTitleLatestVersion titleLatestVersion;
titleLatestVersion.titleId = itr.first;
titleLatestVersion.version = itr.second;
g_nim.titlesLatestVersion.push_back(titleLatestVersion);
}
return true;
}
NIMTitleLatestVersion* nim_findTitleLatestVersion(uint64 titleId)
{
for (auto& titleLatestVersion : g_nim.titlesLatestVersion)
{
if (titleLatestVersion.titleId == titleId)
return &titleLatestVersion;
}
return nullptr;
}
void nim_buildDownloadList()
{
sint32 titleCount = mcpGetTitleCount();
MCPTitleInfo* titleList = (MCPTitleInfo*)malloc(titleCount * sizeof(MCPTitleInfo));
memset(titleList, 0, titleCount * sizeof(MCPTitleInfo));
uint32be titleCountBE = titleCount;
if (mcpGetTitleList(titleList, titleCount * sizeof(MCPTitleInfo), &titleCountBE) != 0)
{
forceLog_printf("IOSU: nim failed to acquire title list");
free(titleList);
return;
}
titleCount = titleCountBE;
// check for game updates
for (sint32 i = 0; i < titleCount; i++)
{
if( titleList[i].titleIdHigh != 0x00050000 )
continue;
// find update title in title version list
uint64 titleId = (0x0005000EULL << 32) | ((uint64)(uint32)titleList[i].titleIdLow);
NIMTitleLatestVersion* latestVersionInfo = nim_findTitleLatestVersion(titleId);
if(latestVersionInfo == nullptr)
continue;
// compare version
if(latestVersionInfo->version <= (uint32)titleList[i].titleVersion )
continue; // already on latest version
// add to packages
NIMPackage* nimPackage = (NIMPackage*)malloc(sizeof(NIMPackage));
memset(nimPackage, 0, sizeof(NIMPackage));
nimPackage->titleId = titleId;
nimPackage->type = PACKAGE_TYPE_UPDATE;
g_nim.packages.push_back(nimPackage);
}
// check for AOC/titles to download
// todo
free(titleList);
}
void nim_getPackagesInfo(uint64* titleIdList, sint32 count, titlePackageInfo_t* packageInfoList)
{
memset(packageInfoList, 0, sizeof(titlePackageInfo_t)*count);
for (sint32 i = 0; i < count; i++)
{
uint64 titleId = _swapEndianU64(titleIdList[i]);
titlePackageInfo_t* packageInfo = packageInfoList + i;
packageInfo->titleId = _swapEndianU64(titleIdList[0]);
packageInfo->ukn0C = 1; // update
// pending
packageInfo->ukn48 = 0; // with 0 there is no progress bar, with 3 there is a progress bar
packageInfo->ukn40 = (5 << 20) | (0 << 27) | (1<<31) | (0x30000<<0);
packageInfo->ukn0F = 1;
}
}
struct idbeIconCacheEntry_t
{
void setIconData(NAPI::IDBEIconDataV0& newIconData)
{
iconData = newIconData;
hasIconData = true;
}
void setNoIcon()
{
hasIconData = false;
iconData = {};
}
uint64 titleId;
uint32 lastRequestTime;
bool hasIconData{ false };
NAPI::IDBEIconDataV0 iconData{};
};
std::vector<idbeIconCacheEntry_t> idbeIconCache;
void idbe_addIconToCache(uint64 titleId, NAPI::IDBEIconDataV0* iconData)
{
idbeIconCacheEntry_t newCacheEntry;
newCacheEntry.titleId = titleId;
newCacheEntry.lastRequestTime = GetTickCount();
if (iconData)
newCacheEntry.setIconData(*iconData);
else
newCacheEntry.setNoIcon();
idbeIconCache.push_back(newCacheEntry);
}
sint32 nim_getIconDatabaseEntry(uint64 titleId, void* idbeIconOutput)
{
// if titleId is an update, replace it with gameId instead
if ((uint32)(titleId >> 32) == 0x0005000EULL)
{
titleId &= ~0xF00000000ULL;
}
for (auto& entry : idbeIconCache)
{
if (entry.titleId == titleId)
{
if( entry.hasIconData )
memcpy(idbeIconOutput, &entry.iconData, sizeof(NAPI::IDBEIconDataV0));
else
memset(idbeIconOutput, 0, sizeof(NAPI::IDBEIconDataV0));
return 0;
}
}
auto result = NAPI::IDBE_Request(titleId);
if (!result)
{
memset(idbeIconOutput, 0, sizeof(NAPI::IDBEIconDataV0));
forceLog_printf("NIM: Unable to download IDBE icon");
return 0;
}
// add new cache entry
idbe_addIconToCache(titleId, &*result);
// return result
memcpy(idbeIconOutput, &*result, sizeof(NAPI::IDBEIconDataV0));
return 0;
}
void nim_backgroundThread()
{
while (iosuAct_isAccountDataLoaded() == false)
{
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
if (nim_getLatestVersion())
{
if (nim_getVersionList())
{
nim_buildDownloadList();
}
}
g_nim.packageListReady = true;
}
void iosuNim_waitUntilPackageListReady()
{
if (g_nim.backgroundThreadStarted == false)
{
forceLog_printf("IOSU: Starting nim background thread");
std::thread t(nim_backgroundThread);
t.detach();
g_nim.backgroundThreadStarted = true;
}
while (g_nim.packageListReady == false)
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
void iosuNim_thread()
{
SetThreadName("iosuNim_thread");
while (true)
{
uint32 returnValue = 0; // Ioctl return value
ioQueueEntry_t* ioQueueEntry = iosuIoctl_getNextWithWait(IOS_DEVICE_NIM);
if (ioQueueEntry->request == IOSU_NIM_REQUEST_CEMU)
{
iosuNimCemuRequest_t* nimCemuRequest = (iosuNimCemuRequest_t*)ioQueueEntry->bufferVectors[0].buffer.GetPtr();
if (nimCemuRequest->requestCode == IOSU_NIM_GET_ICON_DATABASE_ENTRY)
{
nimCemuRequest->returnCode = nim_getIconDatabaseEntry(nimCemuRequest->titleId, nimCemuRequest->ptr.GetPtr());
}
else if (nimCemuRequest->requestCode == IOSU_NIM_GET_PACKAGE_COUNT)
{
iosuNim_waitUntilPackageListReady();
nimCemuRequest->resultU32.u32 = (uint32)g_nim.packages.size();
nimCemuRequest->returnCode = 0;
}
else if (nimCemuRequest->requestCode == IOSU_NIM_GET_PACKAGES_TITLEID)
{
iosuNim_waitUntilPackageListReady();
uint32 maxCount = nimCemuRequest->maxCount;
uint64* titleIdList = (uint64*)nimCemuRequest->ptr.GetPtr();
uint32 count = 0;
memset(titleIdList, 0, sizeof(uint64) * maxCount);
for (auto& package : g_nim.packages)
{
titleIdList[count] = _swapEndianU64(package->titleId);
count++;
if (count >= maxCount)
break;
}
nimCemuRequest->returnCode = 0;
}
else if (nimCemuRequest->requestCode == IOSU_NIM_GET_PACKAGES_INFO)
{
iosuNim_waitUntilPackageListReady();
uint32 maxCount = nimCemuRequest->maxCount;
uint64* titleIdList = (uint64*)nimCemuRequest->ptr.GetPtr();
titlePackageInfo_t* packageInfo = (titlePackageInfo_t*)nimCemuRequest->ptr2.GetPtr();
nim_getPackagesInfo(titleIdList, maxCount, packageInfo);
nimCemuRequest->returnCode = 0;
}
else
cemu_assert_unimplemented();
}
else
cemu_assert_unimplemented();
iosuIoctl_completeRequest(ioQueueEntry, returnValue);
}
return;
}
void Initialize()
{
if (g_nim.isInitialized)
return;
std::vector<idbeIconCacheEntry_t> idbeIconCache = std::vector<idbeIconCacheEntry_t>();
g_nim.titlesLatestVersion = std::vector<NIMTitleLatestVersion>();
g_nim.packages = std::vector<NIMPackage*>();
g_nim.packageListReady = false;
g_nim.backgroundThreadStarted = false;
std::thread t2(iosuNim_thread);
t2.detach();
g_nim.isInitialized = true;
}
}
}

View file

@ -0,0 +1,75 @@
#pragma once
namespace iosu
{
namespace nim
{
struct titlePackageInfo_t
{
uint64 titleId;
uint32be ukn08;
uint8 ukn0C;
uint8 ukn0D;
uint8 ukn0E;
uint8 ukn0F;
uint8 ukn10;
uint8 ukn11;
uint8 ukn12;
uint8 ukn13;
uint8 ukn14[0xC];
uint32be ukn20;
uint32be ukn24;
// ukn sint64 value (uknDA)
//uint32be ukn28_h;
//uint32be ukn2C_l;
uint64 ukn28DLProgressRelatedMax_u64be;
// ukn sint64 value (uknDB)
//uint32be ukn30_h;
//uint32be ukn34_l;
uint64 ukn30DLProgressRelatedCur_u64be;
// ukn sint64 value (uknDC)
uint32be ukn38DLProgressRelatedMax;
uint32be ukn3CDLProgressRelatedCur;
uint32be ukn40;
uint32be ukn44;
uint8 ukn48; // 0-7 are allowed values
uint8 ukn49;
uint8 ukn4A;
uint8 ukn4B;
uint32be ukn4C;
};
static_assert(sizeof(titlePackageInfo_t) == 0x50);
struct iosuNimCemuRequest_t
{
uint32 requestCode;
// input
uint64 titleId;
MEMPTR<void> ptr;
MEMPTR<void> ptr2;
sint32 maxCount;
// output
uint32 returnCode; // NIM return value
union
{
struct
{
uint32 u32;
}resultU32;
};
};
// custom dev/nim protocol (Cemu only)
#define IOSU_NIM_REQUEST_CEMU (0xEE)
// NIM request Cemu subcodes
#define IOSU_NIM_GET_ICON_DATABASE_ENTRY 0x01
#define IOSU_NIM_GET_PACKAGE_COUNT 0x02
#define IOSU_NIM_GET_PACKAGES_INFO 0x03
#define IOSU_NIM_GET_PACKAGES_TITLEID 0x04
void Initialize();
}
}

View file

@ -0,0 +1,86 @@
#include "iosu_nn_service.h"
#include "../kernel/iosu_kernel.h"
using namespace iosu::kernel;
namespace iosu
{
namespace nn
{
void IPCService::Start()
{
if (m_isRunning.exchange(true))
return;
m_threadInitialized = false;
m_requestStop = false;
m_serviceThread = std::thread(&IPCService::ServiceThread, this);
while (!m_threadInitialized) std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
void IPCService::Stop()
{
if (!m_isRunning.exchange(false))
return;
m_requestStop = true;
IOS_SendMessage(m_msgQueueId, 0, 0); // wake up thread
m_serviceThread.join();
}
void IPCService::ServiceThread()
{
m_msgQueueId = IOS_CreateMessageQueue(_m_msgBuffer.GetPtr(), _m_msgBuffer.GetCount());
cemu_assert(!IOS_ResultIsError((IOS_ERROR)m_msgQueueId));
IOS_ERROR r = IOS_RegisterResourceManager(m_devicePath.c_str(), m_msgQueueId);
cemu_assert(!IOS_ResultIsError(r));
m_threadInitialized = true;
while (true)
{
IOSMessage msg;
IOS_ERROR r = IOS_ReceiveMessage(m_msgQueueId, &msg, 0);
cemu_assert(!IOS_ResultIsError(r));
if (msg == 0)
{
cemu_assert_debug(m_requestStop);
break;
}
IPCCommandBody* cmd = MEMPTR<IPCCommandBody>(msg).GetPtr();
if (cmd->cmdId == IPCCommandId::IOS_OPEN)
{
IOS_ResourceReply(cmd, (IOS_ERROR)CreateClientHandle());
continue;
}
else if (cmd->cmdId == IPCCommandId::IOS_CLOSE)
{
CloseClientHandle((IOSDevHandle)(uint32)cmd->devHandle);
IOS_ResourceReply(cmd, IOS_ERROR_OK);
continue;
}
else if (cmd->cmdId == IPCCommandId::IOS_IOCTLV)
{
uint32 requestId = cmd->args[0];
uint32 numIn = cmd->args[1];
uint32 numOut = cmd->args[2];
IPCIoctlVector* vec = MEMPTR<IPCIoctlVector>{ cmd->args[3] }.GetPtr();
IPCIoctlVector* vecIn = vec + numIn;
IPCIoctlVector* vecOut = vec + 0;
cemu_assert(vecIn->size >= 80 && !vecIn->basePhys.IsNull());
IPCServiceRequest* serviceRequest = MEMPTR<IPCServiceRequest>(vecIn->basePhys).GetPtr();
IPCServiceResponse* serviceResponse = MEMPTR<IPCServiceResponse>(vecOut->basePhys).GetPtr();
serviceResponse->nnResultCode = (uint32)ServiceCall(serviceRequest->serviceId, nullptr, nullptr);
IOS_ResourceReply(cmd, IOS_ERROR_OK);
continue;
}
else
{
cemuLog_log(LogType::Force, "{}: Unsupported cmdId", m_devicePath);
cemu_assert_unimplemented();
IOS_ResourceReply(cmd, IOS_ERROR_INVALID);
}
}
m_threadInitialized = false;
}
};
};

View file

@ -0,0 +1,64 @@
#pragma once
#include "Cafe/IOSU/kernel/iosu_kernel.h"
#include "Cafe/IOSU/iosu_ipc_common.h"
#include "Cafe/IOSU/iosu_types_common.h"
#include "Cafe/OS/libs/nn_common.h"
namespace iosu
{
namespace nn
{
struct IPCServiceRequest
{
uint32be ukn00;
uint32be serviceId;
uint32be ukn08;
uint32be commandId;
};
static_assert(sizeof(IPCServiceRequest) == 0x10);
struct IPCServiceResponse
{
uint32be nnResultCode;
};
class IPCService
{
public:
IPCService(std::string_view devicePath) : m_devicePath(devicePath) {};
virtual ~IPCService() {};
virtual IOSDevHandle CreateClientHandle()
{
return (IOSDevHandle)0;
}
virtual void CloseClientHandle(IOSDevHandle handle)
{
}
virtual nnResult ServiceCall(uint32 serviceId, void* request, void* response)
{
cemu_assert_unimplemented();
return 0;
}
void Start();
void Stop();
private:
void ServiceThread();
std::string m_devicePath;
std::thread m_serviceThread;
std::atomic_bool m_requestStop{false};
std::atomic_bool m_isRunning{false};
std::atomic_bool m_threadInitialized{ false };
IOSMsgQueueId m_msgQueueId;
SysAllocator<iosu::kernel::IOSMessage, 128> _m_msgBuffer;
};
};
};