mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-07-10 08:51:19 +12:00
Add all the files
This commit is contained in:
parent
e3db07a16a
commit
d60742f52b
1445 changed files with 430238 additions and 0 deletions
376
src/Cafe/IOSU/PDM/iosu_pdm.cpp
Normal file
376
src/Cafe/IOSU/PDM/iosu_pdm.cpp
Normal 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();
|
||||
}
|
||||
|
||||
};
|
||||
};
|
38
src/Cafe/IOSU/PDM/iosu_pdm.h
Normal file
38
src/Cafe/IOSU/PDM/iosu_pdm.h
Normal 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);
|
||||
};
|
||||
};
|
83
src/Cafe/IOSU/fsa/fsa_types.h
Normal file
83
src/Cafe/IOSU/fsa/fsa_types.h
Normal 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
|
824
src/Cafe/IOSU/fsa/iosu_fsa.cpp
Normal file
824
src/Cafe/IOSU/fsa/iosu_fsa.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
162
src/Cafe/IOSU/fsa/iosu_fsa.h
Normal file
162
src/Cafe/IOSU/fsa/iosu_fsa.h
Normal 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();
|
||||
}
|
||||
}
|
73
src/Cafe/IOSU/iosu_ipc_common.h
Normal file
73
src/Cafe/IOSU/iosu_ipc_common.h
Normal 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*
|
||||
|
||||
|
||||
*/
|
||||
};
|
22
src/Cafe/IOSU/iosu_types_common.h
Normal file
22
src/Cafe/IOSU/iosu_types_common.h
Normal 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;
|
||||
}
|
556
src/Cafe/IOSU/kernel/iosu_kernel.cpp
Normal file
556
src/Cafe/IOSU/kernel/iosu_kernel.cpp
Normal 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 ⁢
|
||||
}
|
||||
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 ⁢
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
23
src/Cafe/IOSU/kernel/iosu_kernel.h
Normal file
23
src/Cafe/IOSU/kernel/iosu_kernel.h
Normal 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();
|
||||
}
|
||||
}
|
612
src/Cafe/IOSU/legacy/iosu_acp.cpp
Normal file
612
src/Cafe/IOSU/legacy/iosu_acp.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
195
src/Cafe/IOSU/legacy/iosu_acp.h
Normal file
195
src/Cafe/IOSU/legacy/iosu_acp.h
Normal 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();
|
||||
}
|
742
src/Cafe/IOSU/legacy/iosu_act.cpp
Normal file
742
src/Cafe/IOSU/legacy/iosu_act.cpp
Normal 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;
|
||||
}
|
118
src/Cafe/IOSU/legacy/iosu_act.h
Normal file
118
src/Cafe/IOSU/legacy/iosu_act.h
Normal 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();
|
1029
src/Cafe/IOSU/legacy/iosu_boss.cpp
Normal file
1029
src/Cafe/IOSU/legacy/iosu_boss.cpp
Normal file
File diff suppressed because it is too large
Load diff
54
src/Cafe/IOSU/legacy/iosu_boss.h
Normal file
54
src/Cafe/IOSU/legacy/iosu_boss.h
Normal 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();
|
||||
}
|
699
src/Cafe/IOSU/legacy/iosu_crypto.cpp
Normal file
699
src/Cafe/IOSU/legacy/iosu_crypto.cpp
Normal 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;
|
||||
}
|
81
src/Cafe/IOSU/legacy/iosu_crypto.h
Normal file
81
src/Cafe/IOSU/legacy/iosu_crypto.h
Normal 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();
|
1050
src/Cafe/IOSU/legacy/iosu_fpd.cpp
Normal file
1050
src/Cafe/IOSU/legacy/iosu_fpd.cpp
Normal file
File diff suppressed because it is too large
Load diff
351
src/Cafe/IOSU/legacy/iosu_fpd.h
Normal file
351
src/Cafe/IOSU/legacy/iosu_fpd.h
Normal 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();
|
||||
}
|
||||
}
|
68
src/Cafe/IOSU/legacy/iosu_ioctl.cpp
Normal file
68
src/Cafe/IOSU/legacy/iosu_ioctl.cpp
Normal 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();
|
||||
}
|
||||
}
|
64
src/Cafe/IOSU/legacy/iosu_ioctl.h
Normal file
64
src/Cafe/IOSU/legacy/iosu_ioctl.h
Normal 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);
|
268
src/Cafe/IOSU/legacy/iosu_mcp.cpp
Normal file
268
src/Cafe/IOSU/legacy/iosu_mcp.cpp
Normal 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();
|
||||
}
|
||||
};
|
||||
}
|
81
src/Cafe/IOSU/legacy/iosu_mcp.h
Normal file
81
src/Cafe/IOSU/legacy/iosu_mcp.h
Normal 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();
|
||||
};
|
||||
}
|
333
src/Cafe/IOSU/legacy/iosu_nim.cpp
Normal file
333
src/Cafe/IOSU/legacy/iosu_nim.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
75
src/Cafe/IOSU/legacy/iosu_nim.h
Normal file
75
src/Cafe/IOSU/legacy/iosu_nim.h
Normal 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();
|
||||
}
|
||||
}
|
86
src/Cafe/IOSU/nn/iosu_nn_service.cpp
Normal file
86
src/Cafe/IOSU/nn/iosu_nn_service.cpp
Normal 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;
|
||||
}
|
||||
};
|
||||
};
|
64
src/Cafe/IOSU/nn/iosu_nn_service.h
Normal file
64
src/Cafe/IOSU/nn/iosu_nn_service.h
Normal 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;
|
||||
};
|
||||
};
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue