Merge branch 'master' into sd-card

This commit is contained in:
wheremyfoodat 2023-09-03 14:55:44 +03:00
commit 42bfcaf980
68 changed files with 3543 additions and 310 deletions

View file

View file

@ -0,0 +1,21 @@
#include "applets/applet_manager.hpp"
using namespace Applets;
AppletManager::AppletManager(Memory& mem) : miiSelector(mem), swkbd(mem) {}
void AppletManager::reset() {
miiSelector.reset();
swkbd.reset();
}
AppletBase* AppletManager::getApplet(u32 id) {
switch (id) {
case AppletIDs::MiiSelector:
case AppletIDs::MiiSelector2: return &miiSelector;
case AppletIDs::SoftwareKeyboard:
case AppletIDs::SoftwareKeyboard2: return &swkbd;
default: return nullptr;
}
}

View file

@ -0,0 +1,11 @@
#include "applets/mii_selector.hpp"
using namespace Applets;
void MiiSelectorApplet::reset() {}
Result::HorizonResult MiiSelectorApplet::start() { return Result::Success; }
Result::HorizonResult MiiSelectorApplet::receiveParameter() {
Helpers::warn("Mii Selector: Unimplemented ReceiveParameter");
return Result::Success;
}

View file

@ -0,0 +1,11 @@
#include "applets/software_keyboard.hpp"
using namespace Applets;
void SoftwareKeyboardApplet::reset() {}
Result::HorizonResult SoftwareKeyboardApplet::start() { return Result::Success; }
Result::HorizonResult SoftwareKeyboardApplet::receiveParameter() {
Helpers::warn("Software keyboard: Unimplemented ReceiveParameter");
return Result::Success;
}

View file

@ -131,20 +131,20 @@ std::optional<u32> NCCHArchive::readFile(FileSession* file, u64 offset, u32 size
}
auto cxi = mem.getCXI();
IOFile& ioFile = mem.CXIFile;
auto hb3dsx = mem.get3DSX();
NCCH::FSInfo fsInfo;
// If isCXI is true then we've loaded a CXI/3DS file, else it's 3DSX
bool isCXI = cxi != nullptr;
// Seek to file offset depending on if we're reading from RomFS, ExeFS, etc
switch (type) {
case PathType::RomFS: {
const u64 romFSSize = cxi->romFS.size;
const u64 romFSOffset = cxi->romFS.offset;
const u64 romFSSize = isCXI ? cxi->romFS.size : hb3dsx->romFSSize;
const u64 romFSOffset = isCXI ? cxi->romFS.offset : hb3dsx->romFSOffset;
if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) {
Helpers::panic("Tried to read from NCCH with too big of an offset");
}
fsInfo = cxi->romFS;
offset += 0x1000;
break;
}
@ -154,7 +154,8 @@ std::optional<u32> NCCHArchive::readFile(FileSession* file, u64 offset, u32 size
}
std::unique_ptr<u8[]> data(new u8[size]);
auto [success, bytesRead] = cxi->readFromFile(ioFile, fsInfo, &data[0], offset, size);
auto [success, bytesRead] =
isCXI ? cxi->readFromFile(mem.CXIFile, cxi->romFS, &data[0], offset, size) : hb3dsx->readRomFSBytes(&data[0], offset, size);
if (!success) {
Helpers::panic("Failed to read from NCCH archive");

View file

@ -69,57 +69,79 @@ std::optional<u32> SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32
return std::nullopt;
}
auto cxi = mem.getCXI();
IOFile& ioFile = mem.CXIFile;
bool success = false;
std::size_t bytesRead = 0;
std::vector<u8> data;
NCCH::FSInfo fsInfo;
if (auto cxi = mem.getCXI(); cxi != nullptr) {
IOFile& ioFile = mem.CXIFile;
// Seek to file offset depending on if we're reading from RomFS, ExeFS, etc
switch (type) {
case PathType::RomFS: {
const u64 romFSSize = cxi->romFS.size;
const u64 romFSOffset = cxi->romFS.offset;
if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) {
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
NCCH::FSInfo fsInfo;
// Seek to file offset depending on if we're reading from RomFS, ExeFS, etc
switch (type) {
case PathType::RomFS: {
const u64 romFSSize = cxi->romFS.size;
const u64 romFSOffset = cxi->romFS.offset;
if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) {
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
}
fsInfo = cxi->romFS;
offset += 0x1000;
break;
}
fsInfo = cxi->romFS;
offset += 0x1000;
break;
}
case PathType::ExeFS: {
const u64 exeFSSize = cxi->exeFS.size;
const u64 exeFSOffset = cxi->exeFS.offset;
if ((offset >> 32) || (offset >= exeFSSize) || (offset + size >= exeFSSize)) {
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
}
case PathType::ExeFS: {
const u64 exeFSSize = cxi->exeFS.size;
const u64 exeFSOffset = cxi->exeFS.offset;
if ((offset >> 32) || (offset >= exeFSSize) || (offset + size >= exeFSSize)) {
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
fsInfo = cxi->exeFS;
break;
}
fsInfo = cxi->exeFS;
break;
}
// Normally, the update RomFS should overlay the cartridge RomFS when reading from this and an update is installed.
// So to support updates, we need to perform this overlaying. For now, read from the cartridge RomFS.
case PathType::UpdateRomFS: {
Helpers::warn("Reading from update RomFS but updates are currently not supported! Reading from regular RomFS instead\n");
// Normally, the update RomFS should overlay the cartridge RomFS when reading from this and an update is installed.
// So to support updates, we need to perform this overlaying. For now, read from the cartridge RomFS.
case PathType::UpdateRomFS: {
Helpers::warn("Reading from update RomFS but updates are currently not supported! Reading from regular RomFS instead\n");
const u64 romFSSize = cxi->romFS.size;
const u64 romFSOffset = cxi->romFS.offset;
if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) {
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
}
const u64 romFSSize = cxi->romFS.size;
const u64 romFSOffset = cxi->romFS.offset;
if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) {
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
fsInfo = cxi->romFS;
offset += 0x1000;
break;
}
fsInfo = cxi->romFS;
offset += 0x1000;
break;
default: Helpers::panic("Unimplemented file path type for SelfNCCH archive");
}
default: Helpers::panic("Unimplemented file path type for SelfNCCH archive");
data.resize(size);
std::tie(success, bytesRead) = cxi->readFromFile(ioFile, fsInfo, &data[0], offset, size);
}
std::unique_ptr<u8[]> data(new u8[size]);
auto [success, bytesRead] = cxi->readFromFile(ioFile, fsInfo, &data[0], offset, size);
else if (auto hb3dsx = mem.get3DSX(); hb3dsx != nullptr) {
switch (type) {
case PathType::RomFS: {
const u64 romFSSize = hb3dsx->romFSSize;
if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) {
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
}
break;
}
default: Helpers::panic("Unimplemented file path type for 3DSX SelfNCCH archive");
}
data.resize(size);
std::tie(success, bytesRead) = hb3dsx->readRomFSBytes(&data[0], offset, size);
}
if (!success) {
Helpers::panic("Failed to read from SelfNCCH archive");

View file

@ -0,0 +1,198 @@
#include <algorithm>
#include <memory>
#include "fs/archive_user_save_data.hpp"
namespace fs = std::filesystem;
HorizonResult UserSaveDataArchive::createFile(const FSPath& path, u64 size) {
if (path.type == PathType::UTF16) {
if (!isPathSafe<PathType::UTF16>(path)) Helpers::panic("Unsafe path in UserSaveData::CreateFile");
fs::path p = IOFile::getAppData() / "SaveData";
p += fs::path(path.utf16_string).make_preferred();
if (fs::exists(p)) return Result::FS::AlreadyExists;
IOFile file(p.string().c_str(), "wb");
// If the size is 0, leave the file empty and return success
if (size == 0) {
file.close();
return Result::Success;
}
// If it is not empty, seek to size - 1 and write a 0 to create a file of size "size"
else if (file.seek(size - 1, SEEK_SET) && file.writeBytes("", 1).second == 1) {
file.close();
return Result::Success;
}
file.close();
return Result::FS::FileTooLarge;
}
Helpers::panic("UserSaveDataArchive::OpenFile: Failed");
return Result::Success;
}
HorizonResult UserSaveDataArchive::createDirectory(const FSPath& path) {
if (path.type == PathType::UTF16) {
if (!isPathSafe<PathType::UTF16>(path)) Helpers::panic("Unsafe path in UserSaveData::OpenFile");
fs::path p = IOFile::getAppData() / "SaveData";
p += fs::path(path.utf16_string).make_preferred();
if (fs::is_directory(p)) return Result::FS::AlreadyExists;
if (fs::is_regular_file(p)) {
Helpers::panic("File path passed to UserSaveData::CreateDirectory");
}
bool success = fs::create_directory(p);
return success ? Result::Success : Result::FS::UnexpectedFileOrDir;
} else {
Helpers::panic("Unimplemented UserSaveData::CreateDirectory");
}
}
HorizonResult UserSaveDataArchive::deleteFile(const FSPath& path) {
if (path.type == PathType::UTF16) {
if (!isPathSafe<PathType::UTF16>(path)) Helpers::panic("Unsafe path in UserSaveData::DeleteFile");
fs::path p = IOFile::getAppData() / "SaveData";
p += fs::path(path.utf16_string).make_preferred();
if (fs::is_directory(p)) {
Helpers::panic("UserSaveData::DeleteFile: Tried to delete directory");
}
if (!fs::is_regular_file(p)) {
return Result::FS::FileNotFoundAlt;
}
std::error_code ec;
bool success = fs::remove(p, ec);
// It might still be possible for fs::remove to fail, if there's eg an open handle to a file being deleted
// In this case, print a warning, but still return success for now
if (!success) {
Helpers::warn("UserSaveData::DeleteFile: fs::remove failed\n");
}
return Result::Success;
}
Helpers::panic("UserSaveDataArchive::DeleteFile: Unknown path type");
return Result::Success;
}
FileDescriptor UserSaveDataArchive::openFile(const FSPath& path, const FilePerms& perms) {
if (path.type == PathType::UTF16) {
if (!isPathSafe<PathType::UTF16>(path)) Helpers::panic("Unsafe path in UserSaveData::OpenFile");
if (perms.raw == 0 || (perms.create() && !perms.write())) Helpers::panic("[UserSaveData] Unsupported flags for OpenFile");
fs::path p = IOFile::getAppData() / "SaveData";
p += fs::path(path.utf16_string).make_preferred();
const char* permString = perms.write() ? "r+b" : "rb";
if (fs::exists(p)) { // Return file descriptor if the file exists
IOFile file(p.string().c_str(), permString);
return file.isOpen() ? file.getHandle() : FileError;
} else {
// If the file is not found, create it if the create flag is on
if (perms.create()) {
IOFile file(p.string().c_str(), "wb"); // Create file
file.close(); // Close it
file.open(p.string().c_str(), permString); // Reopen with proper perms
return file.isOpen() ? file.getHandle() : FileError;
} else {
return FileError;
}
}
}
Helpers::panic("UserSaveDataArchive::OpenFile: Failed");
return FileError;
}
Rust::Result<DirectorySession, HorizonResult> UserSaveDataArchive::openDirectory(const FSPath& path) {
if (path.type == PathType::UTF16) {
if (!isPathSafe<PathType::UTF16>(path)) Helpers::panic("Unsafe path in UserSaveData::OpenDirectory");
fs::path p = IOFile::getAppData() / "SaveData";
p += fs::path(path.utf16_string).make_preferred();
if (fs::is_regular_file(p)) {
printf("SaveData: OpenDirectory used with a file path");
return Err(Result::FS::UnexpectedFileOrDir);
}
if (fs::is_directory(p)) {
return Ok(DirectorySession(this, p));
} else {
return Err(Result::FS::FileNotFoundAlt);
}
}
Helpers::panic("UserSaveDataArchive::OpenDirectory: Unimplemented path type");
return Err(Result::Success);
}
Rust::Result<ArchiveBase::FormatInfo, HorizonResult> UserSaveDataArchive::getFormatInfo(const FSPath& path) {
const fs::path formatInfoPath = getFormatInfoPath();
IOFile file(formatInfoPath, "rb");
// If the file failed to open somehow, we return that the archive is not formatted
if (!file.isOpen()) {
return Err(Result::FS::NotFormatted);
}
FormatInfo ret;
auto [success, bytesRead] = file.readBytes(&ret, sizeof(FormatInfo));
file.close();
if (!success || bytesRead != sizeof(FormatInfo)) {
Helpers::warn("UserSaveData::GetFormatInfo: Format file exists but was not properly read into the FormatInfo struct");
return Err(Result::FS::NotFormatted);
}
return Ok(ret);
}
void UserSaveDataArchive::format(const FSPath& path, const ArchiveBase::FormatInfo& info) {
const fs::path saveDataPath = IOFile::getAppData() / "SaveData";
const fs::path formatInfoPath = getFormatInfoPath();
// Delete all contents by deleting the directory then recreating it
fs::remove_all(saveDataPath);
fs::create_directories(saveDataPath);
// Write format info on disk
IOFile file(formatInfoPath, "wb");
file.writeBytes(&info, sizeof(info));
file.flush();
file.close();
}
Rust::Result<ArchiveBase*, HorizonResult> UserSaveDataArchive::openArchive(const FSPath& path) {
if (path.type != PathType::Binary) {
Helpers::panic("Unimplemented path type for UserSaveData archive: %d\n", path.type);
return Err(Result::FS::NotFoundInvalid);
}
const fs::path formatInfoPath = getFormatInfoPath();
// Format info not found so the archive is not formatted
if (!fs::is_regular_file(formatInfoPath)) {
return Err(Result::FS::NotFormatted);
}
return Ok((ArchiveBase*)this);
}
std::optional<u32> UserSaveDataArchive::readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) {
Helpers::panic("Unimplemented UserSaveData::ReadFile");
return 0;
}

View file

@ -124,6 +124,7 @@ void Kernel::deleteObjectData(KernelObject& object) {
case KernelObjectType::Session: delete object.getData<Session>(); return;
case KernelObjectType::Mutex: delete object.getData<Mutex>(); return;
case KernelObjectType::Semaphore: delete object.getData<Semaphore>(); return;
case KernelObjectType::Timer: delete object.getData<Timer>(); return;
case KernelObjectType::Thread: return;
case KernelObjectType::Dummy: return;
default: [[unlikely]] Helpers::warn("unknown object type"); return;

View file

@ -252,6 +252,14 @@ void Kernel::acquireSyncObject(KernelObject* object, const Thread& thread) {
case KernelObjectType::Thread:
break;
case KernelObjectType::Timer: {
Timer* timer = object->getData<Timer>();
if (timer->resetType == ResetType::OneShot) { // One-shot timers automatically get cleared after waking up a thread
timer->fired = false;
}
break;
}
default: Helpers::panic("Acquiring unimplemented sync object %s", object->getTypeName());
}
}
@ -652,6 +660,9 @@ bool Kernel::shouldWaitOnObject(KernelObject* object) {
case KernelObjectType::Thread: // Waiting on a thread waits until it's dead. If it's dead then no need to wait
return object->getData<Thread>()->status != ThreadStatus::Dead;
case KernelObjectType::Timer: // We should wait on a timer only if it has not been signalled
return !object->getData<Timer>()->fired;
case KernelObjectType::Semaphore: // Wait if the semaphore count <= 0
return object->getData<Semaphore>()->availableCount <= 0;

View file

@ -1,6 +1,127 @@
#include "kernel.hpp"
#include "cpu.hpp"
void Kernel::svcCreateTimer() { Helpers::panic("Kernel::CreateTimer"); }
void Kernel::svcSetTimer() { Helpers::panic("Kernel::SetTimer"); }
void Kernel::svcClearTimer() { Helpers::panic("Kernel::ClearTimer"); }
void Kernel::svcCancelTimer() { Helpers::panic("Kernel::CancelTimer"); }
Handle Kernel::makeTimer(ResetType type) {
Handle ret = makeObject(KernelObjectType::Timer);
objects[ret].data = new Timer(type);
if (type == ResetType::Pulse) {
Helpers::panic("Created pulse timer");
}
// timerHandles.push_back(ret);
return ret;
}
void Kernel::updateTimer(Handle handle, Timer* timer) {
if (timer->running) {
const u64 currentTicks = cpu.getTicks();
u64 elapsedTicks = currentTicks - timer->startTick;
constexpr double ticksPerSec = double(CPU::ticksPerSec);
constexpr double nsPerTick = ticksPerSec / 1000000000.0;
const s64 elapsedNs = s64(double(elapsedTicks) * nsPerTick);
// Timer has fired
if (elapsedNs >= timer->currentDelay) {
timer->startTick = currentTicks;
timer->currentDelay = timer->interval;
signalTimer(handle, timer);
}
}
}
void Kernel::cancelTimer(Timer* timer) {
timer->running = false;
// TODO: When we have a scheduler this should properly cancel timer events in the scheduler
}
void Kernel::signalTimer(Handle timerHandle, Timer* timer) {
timer->fired = true;
requireReschedule();
// Check if there's any thread waiting on this event
if (timer->waitlist != 0) {
wakeupAllThreads(timer->waitlist, timerHandle);
timer->waitlist = 0; // No threads waiting;
switch (timer->resetType) {
case ResetType::OneShot: timer->fired = false; break;
case ResetType::Sticky: break;
case ResetType::Pulse: Helpers::panic("Signalled pulsing timer"); break;
}
}
}
void Kernel::svcCreateTimer() {
const u32 resetType = regs[1];
if (resetType > 2) {
Helpers::panic("Invalid reset type for event %d", resetType);
}
// Have a warning here until our timers don't suck
Helpers::warn("Called Kernel::CreateTimer. Timers are currently not updated nor triggered properly!");
logSVC("CreateTimer (resetType = %s)\n", resetTypeToString(resetType));
regs[0] = Result::Success;
regs[1] = makeTimer(static_cast<ResetType>(resetType));
}
void Kernel::svcSetTimer() {
Handle handle = regs[0];
// TODO: Is this actually s64 or u64? 3DBrew says s64, but u64 makes more sense
const s64 initial = s64(u64(regs[1]) | (u64(regs[2]) << 32));
const s64 interval = s64(u64(regs[3]) | (u64(regs[4]) << 32));
logSVC("SetTimer (handle = %X, initial delay = %llX, interval delay = %llX)\n", handle, initial, interval);
KernelObject* object = getObject(handle, KernelObjectType::Timer);
if (object == nullptr) {
Helpers::panic("Tried to set non-existent timer %X\n", handle);
regs[0] = Result::Kernel::InvalidHandle;
}
Timer* timer = object->getData<Timer>();
cancelTimer(timer);
timer->currentDelay = initial;
timer->interval = interval;
timer->running = true;
timer->startTick = cpu.getTicks();
// If the initial delay is 0 then instantly signal the timer
if (initial == 0) {
signalTimer(handle, timer);
} else {
// This should schedule an event in the scheduler when we have one
}
regs[0] = Result::Success;
}
void Kernel::svcClearTimer() {
Handle handle = regs[0];
logSVC("ClearTimer (handle = %X)\n", handle);
KernelObject* object = getObject(handle, KernelObjectType::Timer);
if (object == nullptr) {
Helpers::panic("Tried to clear non-existent timer %X\n", handle);
regs[0] = Result::Kernel::InvalidHandle;
} else {
object->getData<Timer>()->fired = false;
regs[0] = Result::Success;
}
}
void Kernel::svcCancelTimer() {
Handle handle = regs[0];
logSVC("CancelTimer (handle = %X)\n", handle);
KernelObject* object = getObject(handle, KernelObjectType::Timer);
if (object == nullptr) {
Helpers::panic("Tried to cancel non-existent timer %X\n", handle);
regs[0] = Result::Kernel::InvalidHandle;
} else {
cancelTimer(object->getData<Timer>());
regs[0] = Result::Success;
}
}

302
src/core/loader/3dsx.cpp Normal file
View file

@ -0,0 +1,302 @@
#include "loader/3dsx.hpp"
#include <cstring>
#include <optional>
#include <span>
#include "memory.hpp"
namespace {
struct LoadInfo {
u32 codeSegSizeAligned;
u32 rodataSegSizeAligned;
u32 dataSegSizeAligned;
};
static inline u32 translateAddr(const u32 off, const u32* addrs, const u32* offsets) {
if (off < offsets[1]) {
return addrs[0] + off;
}
if (off < offsets[2]) {
return addrs[1] + off - offsets[1];
}
return addrs[2] + off - offsets[2];
}
} // namespace
bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) {
const LoadInfo hbInfo = {
.codeSegSizeAligned = (header.codeSegSize + 0xFFF) & ~0xFFF,
.rodataSegSizeAligned = (header.rodataSegSize + 0xFFF) & ~0xFFF,
.dataSegSizeAligned = (header.dataSegSize + 0xFFF) & ~0xFFF,
};
const u32 textSegAddr = HB3DSX::entrypoint;
const u32 rodataSegAddr = textSegAddr + hbInfo.codeSegSizeAligned;
const u32 dataSegAddr = rodataSegAddr + hbInfo.rodataSegSizeAligned;
const u32 extraPageAddr = dataSegAddr + hbInfo.dataSegSizeAligned;
printf("Text address = %08X, size = %08X\n", textSegAddr, hbInfo.codeSegSizeAligned);
printf("Rodata address = %08X, size = %08X\n", rodataSegAddr, hbInfo.rodataSegSizeAligned);
printf("Data address = %08X, size = %08X\n", dataSegAddr, hbInfo.dataSegSizeAligned);
// Allocate stack, 3dsx/libctru don't require anymore than this
if (!allocateMainThreadStack(4_KB)) {
// Should be unreachable
printf("Failed to allocate stack for 3DSX.\n");
return false;
}
// Map code file to memory
// Total memory to allocate for loading
// suum of aligned values is always aligned, have an extra RW page for libctru
const u32 totalSize = hbInfo.codeSegSizeAligned + hbInfo.rodataSegSizeAligned + hbInfo.dataSegSizeAligned + 4_KB;
const auto opt = findPaddr(totalSize);
if (!opt.has_value()) {
Helpers::panic("Failed to find paddr to map 3DSX file's code to");
return false;
}
// Map the ROM on the kernel side
const u32 textOffset = 0;
const u32 rodataOffset = textOffset + hbInfo.codeSegSizeAligned;
const u32 dataOffset = rodataOffset + hbInfo.rodataSegSizeAligned;
const u32 extraPageOffset = dataOffset + hbInfo.dataSegSizeAligned;
std::array<HB3DSX::RelocHeader, 3> relocHeaders;
auto [success, count] = hb3dsx.file.read(&relocHeaders[0], relocHeaders.size(), sizeof(HB3DSX::RelocHeader));
if (!success || count != relocHeaders.size()) {
Helpers::panic("Failed to read 3DSX relocation headers");
return false;
}
const u32 dataLoadsize = header.dataSegSize - header.bssSize; // 3DSX data size in header includes bss
std::vector<u8> code(totalSize, 0);
std::tie(success, count) = hb3dsx.file.readBytes(&code[textOffset], header.codeSegSize);
if (!success || count != header.codeSegSize) {
Helpers::panic("Failed to read 3DSX text segment");
return false;
}
std::tie(success, count) = hb3dsx.file.readBytes(&code[rodataOffset], header.rodataSegSize);
if (!success || count != header.rodataSegSize) {
Helpers::panic("Failed to read 3DSX rodata segment");
return false;
}
std::tie(success, count) = hb3dsx.file.readBytes(&code[dataOffset], dataLoadsize);
if (!success || count != dataLoadsize) {
Helpers::panic("Failed to read 3DSX data segment");
return false;
}
std::vector<HB3DSX::Reloc> currentRelocs;
const u32 segAddrs[] = {
textSegAddr,
rodataSegAddr,
dataSegAddr,
extraPageAddr,
};
const u32 segOffs[] = {
textOffset,
rodataOffset,
dataOffset,
extraPageOffset,
};
const u32 segSizes[] = {
header.codeSegSize,
header.rodataSegSize,
dataLoadsize,
0x1000,
};
for (const auto& relocHeader : relocHeaders) {
currentRelocs.resize(relocHeader.absoluteCount + relocHeader.relativeCount);
std::tie(success, count) = hb3dsx.file.read(&currentRelocs[0], currentRelocs.size(), sizeof(HB3DSX::Reloc));
if (!success || count != currentRelocs.size()) {
Helpers::panic("Failed to read 3DSX relocations");
return false;
}
const auto allRelocs = std::span(currentRelocs);
const auto absoluteRelocs = allRelocs.subspan(0, relocHeader.absoluteCount);
const auto relativeRelocs = allRelocs.subspan(relocHeader.absoluteCount, relocHeader.relativeCount);
const auto currentSeg = &relocHeader - &relocHeaders[0];
const auto sectionDataStartAs = std::span(code).subspan(segOffs[currentSeg], segSizes[currentSeg]);
auto sectionData = sectionDataStartAs;
const auto RelocationAction = [&](const HB3DSX::Reloc& reloc, const HB3DSX::RelocType relocType) -> bool {
if (reloc.skip) {
sectionData = sectionData.subspan(reloc.skip * sizeof(u32)); // advance by `skip` words (32-bit values)
}
for (u32 m = 0; m < reloc.patch && !sectionData.empty(); ++m) {
const u32 inAddr = textSegAddr + (sectionData.data() - code.data()); // byte offset -> word count
u32 origData = 0;
std::memcpy(&origData, &sectionData[0], sizeof(u32));
const u32 subType = origData >> (32 - 4);
const u32 addr = translateAddr(origData & ~0xF0000000, segAddrs, segOffs);
switch (relocType) {
case HB3DSX::RelocType::Absolute: {
if (subType != 0) {
Helpers::panic("Unsupported absolute reloc subtype");
return false;
}
std::memcpy(&sectionData[0], &addr, sizeof(u32));
break;
}
case HB3DSX::RelocType::Relative: {
u32 data = addr - inAddr;
switch (subType) {
case 1: // 31-bit signed offset
data &= ~(1u << 31);
case 0: // 32-bit signed offset
std::memcpy(&sectionData[0], &data, sizeof(u32));
break;
default: Helpers::panic("Unsupported relative reloc subtype"); return false;
}
break;
}
}
sectionData = sectionData.subspan(sizeof(u32));
}
return true;
};
for (const auto& reloc : absoluteRelocs) {
if (!RelocationAction(reloc, HB3DSX::RelocType::Absolute)) {
return false;
}
}
sectionData = sectionDataStartAs; // restart from the beginning for the next part
for (const auto& reloc : relativeRelocs) {
if (!RelocationAction(reloc, HB3DSX::RelocType::Relative)) {
return false;
}
}
}
// Detect and fill _prm structure
HB3DSX::PrmStruct pst;
std::memcpy(&pst, &code[4], sizeof(pst));
if (pst.magic[0] == '_' && pst.magic[1] == 'p' && pst.magic[2] == 'r' && pst.magic[3] == 'm') {
// if there was any argv to put, it would go there
// first u32: argc
// remaining: continuous argv string (NUL-char separated, ofc)
// std::memcpy(&code[extraPageOffset], argvBuffer, ...);
// setting to NULL (default) = run from system. load romfs from process.
// non-NULL = homebrew launcher. load romfs from 3dsx @ argv[0]
// pst.pSrvOverride = extraPageAddr + 0xFFC;
pst.pArgList = extraPageAddr;
// RUNFLAG_APTREINIT: Reinitialize APT.
// From libctru. Because there's no previously running software here
pst.runFlags |= 1 << 1;
/* s64 dummy;
bool isN3DS = svcGetSystemInfo(&dummy, 0x10001, 0) == 0;
if (isN3DS)
{
pst->heapSize = u32(48_MB);
pst->linearHeapSize = u32(64_MB);
} else */ {
pst.heapSize = u32(24_MB);
pst.linearHeapSize = u32(32_MB);
}
std::memcpy(&code[4], &pst, sizeof(pst));
}
const auto paddr = opt.value();
std::memcpy(&fcram[paddr], &code[0], totalSize); // Copy the 3 segments + BSS to FCRAM
allocateMemory(textSegAddr, paddr + textOffset, hbInfo.codeSegSizeAligned, true, true, false, true); // Text is R-X
allocateMemory(rodataSegAddr, paddr + rodataOffset, hbInfo.rodataSegSizeAligned, true, true, false, false); // Rodata is R--
allocateMemory(dataSegAddr, paddr + dataOffset, hbInfo.dataSegSizeAligned + 0x1000, true, true, true, false); // Data+BSS+Extra is RW-
return true;
}
std::optional<u32> Memory::load3DSX(const std::filesystem::path& path) {
HB3DSX hb3dsx;
if (!hb3dsx.file.open(path, "rb")) {
return std::nullopt;
}
u8 magic[4]; // Must be "3DSX"
auto [success, bytes] = hb3dsx.file.readBytes(magic, 4);
if (!success || bytes != 4) {
printf("Failed to read 3DSX magic\n");
return std::nullopt;
}
if (magic[0] != '3' || magic[1] != 'D' || magic[2] != 'S' || magic[3] != 'X') {
printf("3DSX with wrong magic value\n");
return std::nullopt;
}
HB3DSX::Header hbHeader;
std::tie(success, bytes) = hb3dsx.file.readBytes(&hbHeader, sizeof(hbHeader));
if (!success || bytes != sizeof(hbHeader)) {
printf("Failed to read 3DSX header\n");
return std::nullopt;
}
if (hbHeader.headerSize == 0x20 || hbHeader.headerSize == 0x2C) {
if (hbHeader.headerSize == 0x2C) {
hb3dsx.file.seek(8, SEEK_CUR); // skip SMDH info
std::tie(success, bytes) = hb3dsx.file.readBytes(&hb3dsx.romFSOffset, 4);
if (!success || bytes != 4) {
printf("Failed to read 3DSX romFS offset\n");
return std::nullopt;
}
const auto fileSize = hb3dsx.file.size();
if (!fileSize) {
printf("Failed to get 3DSX size\n");
return std::nullopt;
}
hb3dsx.romFSSize = *fileSize - hb3dsx.romFSOffset;
}
} else {
printf("Invalid 3DSX header size\n");
return std::nullopt;
}
if (!map3DSX(hb3dsx, hbHeader)) {
printf("Failed to map 3DSX\n");
return std::nullopt;
}
loaded3DSX = std::move(hb3dsx);
return HB3DSX::entrypoint;
}
bool HB3DSX::hasRomFs() const { return romFSSize != 0 && romFSOffset != 0; }
std::pair<bool, std::size_t> HB3DSX::readRomFSBytes(void* dst, std::size_t offset, std::size_t size) {
if (!hasRomFs()) {
return {false, 0};
}
if (!file.seek(romFSOffset + offset)) {
return {false, 0};
}
return file.readBytes(dst, size);
}

View file

@ -150,6 +150,7 @@ u32 Memory::read32(u32 vaddr) {
return *(u32*)(pointer + offset);
} else {
switch (vaddr) {
case 0x1FF80000: return u32(kernelVersion) << 16;
case ConfigMem::Datetime0: return u32(timeSince3DSEpoch()); // ms elapsed since Jan 1 1900, bottom 32 bits
case ConfigMem::Datetime0 + 4:
return u32(timeSince3DSEpoch() >> 32); // top 32 bits

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,3 @@
#include "renderer_vk/vk_api.hpp"
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE;

View file

@ -0,0 +1,119 @@
#include "renderer_vk/vk_descriptor_heap.hpp"
#include <algorithm>
#include <optional>
#include <unordered_map>
namespace Vulkan {
DescriptorHeap::DescriptorHeap(vk::Device device) : device(device) {}
std::optional<vk::DescriptorSet> DescriptorHeap::allocateDescriptorSet() {
// Find a free slot
const auto freeSlot = std::find(allocationMap.begin(), allocationMap.end(), false);
// If there is no free slot, return
if (freeSlot == allocationMap.end()) {
return std::nullopt;
}
// Mark the slot as allocated
*freeSlot = true;
const u16 index = static_cast<u16>(std::distance(allocationMap.begin(), freeSlot));
vk::UniqueDescriptorSet& newDescriptorSet = descriptorSets[index];
if (!newDescriptorSet) {
// Descriptor set doesn't exist yet. Allocate a new one
vk::DescriptorSetAllocateInfo allocateInfo = {};
allocateInfo.descriptorPool = descriptorPool.get();
allocateInfo.pSetLayouts = &descriptorSetLayout.get();
allocateInfo.descriptorSetCount = 1;
if (auto AllocateResult = device.allocateDescriptorSetsUnique(allocateInfo); AllocateResult.result == vk::Result::eSuccess) {
newDescriptorSet = std::move(AllocateResult.value[0]);
} else {
// Error allocating descriptor set
return std::nullopt;
}
}
return newDescriptorSet.get();
}
bool DescriptorHeap::freeDescriptorSet(vk::DescriptorSet Set) {
// Find the descriptor set
const auto found =
std::find_if(descriptorSets.begin(), descriptorSets.end(), [&Set](const auto& CurSet) -> bool { return CurSet.get() == Set; });
// If the descriptor set is not found, return
if (found == descriptorSets.end()) {
return false;
}
// Mark the slot as free
const u16 index = static_cast<u16>(std::distance(descriptorSets.begin(), found));
allocationMap[index] = false;
return true;
}
std::optional<DescriptorHeap> DescriptorHeap::create(
vk::Device device, std::span<const vk::DescriptorSetLayoutBinding> bindings, u16 descriptorHeapCount
) {
DescriptorHeap newDescriptorHeap(device);
// Create a histogram of each of the descriptor types and how many of each
// the pool should have
// Todo: maybe keep this around as a hash table to do more dynamic
// allocations of descriptor sets rather than allocating them all up-front
std::vector<vk::DescriptorPoolSize> poolSizes;
{
std::unordered_map<vk::DescriptorType, u16> descriptorTypeCounts;
for (const auto& binding : bindings) {
descriptorTypeCounts[binding.descriptorType] += binding.descriptorCount;
}
for (const auto& descriptorTypeCount : descriptorTypeCounts) {
poolSizes.push_back(vk::DescriptorPoolSize(descriptorTypeCount.first, descriptorTypeCount.second * descriptorHeapCount));
}
}
// Create descriptor pool
{
vk::DescriptorPoolCreateInfo poolInfo;
poolInfo.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet;
poolInfo.maxSets = descriptorHeapCount;
poolInfo.pPoolSizes = poolSizes.data();
poolInfo.poolSizeCount = poolSizes.size();
if (auto createResult = device.createDescriptorPoolUnique(poolInfo); createResult.result == vk::Result::eSuccess) {
newDescriptorHeap.descriptorPool = std::move(createResult.value);
} else {
return std::nullopt;
}
}
// Create descriptor set layout
{
vk::DescriptorSetLayoutCreateInfo layoutInfo;
layoutInfo.pBindings = bindings.data();
layoutInfo.bindingCount = bindings.size();
if (auto createResult = device.createDescriptorSetLayoutUnique(layoutInfo); createResult.result == vk::Result::eSuccess) {
newDescriptorHeap.descriptorSetLayout = std::move(createResult.value);
} else {
return std::nullopt;
}
}
newDescriptorHeap.descriptorSets.resize(descriptorHeapCount);
newDescriptorHeap.allocationMap.resize(descriptorHeapCount);
newDescriptorHeap.bindings.assign(bindings.begin(), bindings.end());
return {std::move(newDescriptorHeap)};
}
} // namespace Vulkan

View file

@ -0,0 +1,98 @@
#include "renderer_vk/vk_descriptor_update_batch.hpp"
#include <memory>
#include <span>
namespace Vulkan {
void DescriptorUpdateBatch::flush() {
device.updateDescriptorSets({std::span(descriptorWrites.get(), descriptorWriteEnd)}, {std::span(descriptorCopies.get(), descriptorCopyEnd)});
descriptorWriteEnd = 0;
descriptorCopyEnd = 0;
}
void DescriptorUpdateBatch::addImage(vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::ImageView imageView, vk::ImageLayout imageLayout) {
if (descriptorWriteEnd >= descriptorWriteMax) {
flush();
}
const auto& imageInfo = descriptorInfos[descriptorWriteEnd].emplace<vk::DescriptorImageInfo>(vk::Sampler(), imageView, imageLayout);
descriptorWrites[descriptorWriteEnd] =
vk::WriteDescriptorSet(targetDescriptor, targetBinding, 0, 1, vk::DescriptorType::eSampledImage, &imageInfo, nullptr, nullptr);
++descriptorWriteEnd;
}
void DescriptorUpdateBatch::addSampler(vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::Sampler sampler) {
if (descriptorWriteEnd >= descriptorWriteMax) {
flush();
}
const auto& imageInfo = descriptorInfos[descriptorWriteEnd].emplace<vk::DescriptorImageInfo>(sampler, vk::ImageView(), vk::ImageLayout());
descriptorWrites[descriptorWriteEnd] =
vk::WriteDescriptorSet(targetDescriptor, targetBinding, 0, 1, vk::DescriptorType::eSampler, &imageInfo, nullptr, nullptr);
++descriptorWriteEnd;
}
void DescriptorUpdateBatch::addImageSampler(
vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::ImageView imageView, vk::Sampler sampler, vk::ImageLayout imageLayout
) {
if (descriptorWriteEnd >= descriptorWriteMax) {
flush();
}
const auto& imageInfo = descriptorInfos[descriptorWriteEnd].emplace<vk::DescriptorImageInfo>(sampler, imageView, imageLayout);
descriptorWrites[descriptorWriteEnd] =
vk::WriteDescriptorSet(targetDescriptor, targetBinding, 0, 1, vk::DescriptorType::eCombinedImageSampler, &imageInfo, nullptr, nullptr);
++descriptorWriteEnd;
}
void DescriptorUpdateBatch::addBuffer(
vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::Buffer buffer, vk::DeviceSize offset, vk::DeviceSize size
) {
if (descriptorWriteEnd >= descriptorWriteMax) {
flush();
}
const auto& bufferInfo = descriptorInfos[descriptorWriteEnd].emplace<vk::DescriptorBufferInfo>(buffer, offset, size);
descriptorWrites[descriptorWriteEnd] =
vk::WriteDescriptorSet(targetDescriptor, targetBinding, 0, 1, vk::DescriptorType::eStorageImage, nullptr, &bufferInfo, nullptr);
++descriptorWriteEnd;
}
void DescriptorUpdateBatch::copyBinding(
vk::DescriptorSet sourceDescriptor, vk::DescriptorSet targetDescriptor, u8 sourceBinding, u8 targetBinding, u8 sourceArrayElement,
u8 targetArrayElement, u8 descriptorCount
) {
if (descriptorCopyEnd >= descriptorCopyMax) {
flush();
}
descriptorCopies[descriptorCopyEnd] = vk::CopyDescriptorSet(
sourceDescriptor, sourceBinding, sourceArrayElement, targetDescriptor, targetBinding, targetArrayElement, descriptorCount
);
++descriptorCopyEnd;
}
std::optional<DescriptorUpdateBatch> DescriptorUpdateBatch::create(vk::Device device, usize descriptorWriteMax, usize descriptorCopyMax)
{
DescriptorUpdateBatch newDescriptorUpdateBatch(device, descriptorWriteMax, descriptorCopyMax);
newDescriptorUpdateBatch.descriptorInfos = std::make_unique<DescriptorInfoUnion[]>(descriptorWriteMax);
newDescriptorUpdateBatch.descriptorWrites = std::make_unique<vk::WriteDescriptorSet[]>(descriptorWriteMax);
newDescriptorUpdateBatch.descriptorCopies = std::make_unique<vk::CopyDescriptorSet[]>(descriptorCopyMax);
return {std::move(newDescriptorUpdateBatch)};
}
} // namespace Vulkan

View file

@ -0,0 +1,174 @@
#include "renderer_vk/vk_memory.hpp"
namespace Vulkan {
static constexpr vk::DeviceSize alignUp(vk::DeviceSize value, std::size_t size) {
const vk::DeviceSize mod = static_cast<vk::DeviceSize>(value % size);
value -= mod;
return static_cast<vk::DeviceSize>(mod == vk::DeviceSize{0} ? value : value + size);
}
// Given a speculative heap-allocation, defined by its current size and
// memory-type bits, appends a memory requirements structure to it, updating
// both the size and the required memory-type-bits. Returns the offset within
// the heap for the current MemoryRequirements Todo: Sun Apr 23 13:28:25 PDT
// 2023 Rather than using a running-size of the heap, look at all of the memory
// requests and optimally create a packing for all of the offset and alignment
// requirements. Such as by satisfying all of the largest alignments first, and
// then the smallest, to reduce padding
static vk::DeviceSize commitMemoryRequestToHeap(
const vk::MemoryRequirements& curMemoryRequirements, vk::DeviceSize& curHeapEnd, u32& curMemoryTypeBits, vk::DeviceSize sizeAlignment
) {
// Accumulate a mask of all the memory types that satisfies each of the
// handles
curMemoryTypeBits &= curMemoryRequirements.memoryTypeBits;
// Pad up the memory sizes so they are not considered aliasing
const vk::DeviceSize curMemoryOffset = alignUp(curHeapEnd, curMemoryRequirements.alignment);
// Pad the size by the required size-alignment.
// Intended for BufferImageGranularity
const vk::DeviceSize curMemorySize = alignUp(curMemoryRequirements.size, sizeAlignment);
curHeapEnd = (curMemoryOffset + curMemorySize);
return curMemoryOffset;
}
s32 findMemoryTypeIndex(
vk::PhysicalDevice physicalDevice, u32 memoryTypeMask, vk::MemoryPropertyFlags memoryProperties,
vk::MemoryPropertyFlags memoryExcludeProperties
) {
const vk::PhysicalDeviceMemoryProperties deviceMemoryProperties = physicalDevice.getMemoryProperties();
// Iterate the physical device's memory types until we find a match
for (std::size_t i = 0; i < deviceMemoryProperties.memoryTypeCount; i++) {
if(
// Is within memory type mask
(((memoryTypeMask >> i) & 0b1) == 0b1) &&
// Has property flags
(deviceMemoryProperties.memoryTypes[i].propertyFlags
& memoryProperties)
== memoryProperties
&&
// None of the excluded properties are enabled
!(deviceMemoryProperties.memoryTypes[i].propertyFlags
& memoryExcludeProperties) )
{
return static_cast<u32>(i);
}
}
return -1;
}
std::tuple<vk::Result, vk::UniqueDeviceMemory> commitImageHeap(
vk::Device device, vk::PhysicalDevice physicalDevice, const std::span<const vk::Image> images, vk::MemoryPropertyFlags memoryProperties,
vk::MemoryPropertyFlags memoryExcludeProperties
) {
vk::MemoryAllocateInfo imageHeapAllocInfo = {};
u32 imageHeapMemoryTypeBits = 0xFFFFFFFF;
std::vector<vk::BindImageMemoryInfo> imageHeapBinds;
const vk::DeviceSize bufferImageGranularity = physicalDevice.getProperties().limits.bufferImageGranularity;
for (const vk::Image& curImage : images) {
const vk::DeviceSize curBindOffset = commitMemoryRequestToHeap(
device.getImageMemoryRequirements(curImage), imageHeapAllocInfo.allocationSize, imageHeapMemoryTypeBits, bufferImageGranularity
);
if (imageHeapMemoryTypeBits == 0) {
// No possible memory heap for all of the images to share
return std::make_tuple(vk::Result::eErrorOutOfDeviceMemory, vk::UniqueDeviceMemory());
}
// Put nullptr for the device memory for now
imageHeapBinds.emplace_back(vk::BindImageMemoryInfo{curImage, nullptr, curBindOffset});
}
const s32 memoryTypeIndex = findMemoryTypeIndex(physicalDevice, imageHeapMemoryTypeBits, memoryProperties, memoryExcludeProperties);
if (memoryTypeIndex < 0) {
// Unable to find a memory heap that satisfies all the images
return std::make_tuple(vk::Result::eErrorOutOfDeviceMemory, vk::UniqueDeviceMemory());
}
imageHeapAllocInfo.memoryTypeIndex = memoryTypeIndex;
vk::UniqueDeviceMemory imageHeapMemory = {};
if (auto allocResult = device.allocateMemoryUnique(imageHeapAllocInfo); allocResult.result == vk::Result::eSuccess) {
imageHeapMemory = std::move(allocResult.value);
} else {
return std::make_tuple(allocResult.result, vk::UniqueDeviceMemory());
}
// Assign the device memory to the bindings
for (vk::BindImageMemoryInfo& curBind : imageHeapBinds) {
curBind.memory = imageHeapMemory.get();
}
// Now bind them all in one call
if (const vk::Result bindResult = device.bindImageMemory2(imageHeapBinds); bindResult == vk::Result::eSuccess) {
// Binding memory succeeded
} else {
return std::make_tuple(bindResult, vk::UniqueDeviceMemory());
}
return std::make_tuple(vk::Result::eSuccess, std::move(imageHeapMemory));
}
std::tuple<vk::Result, vk::UniqueDeviceMemory> commitBufferHeap(
vk::Device device, vk::PhysicalDevice physicalDevice, const std::span<const vk::Buffer> buffers, vk::MemoryPropertyFlags memoryProperties,
vk::MemoryPropertyFlags memoryExcludeProperties
) {
vk::MemoryAllocateInfo bufferHeapAllocInfo = {};
u32 bufferHeapMemoryTypeBits = 0xFFFFFFFF;
std::vector<vk::BindBufferMemoryInfo> bufferHeapBinds;
const vk::DeviceSize bufferImageGranularity = physicalDevice.getProperties().limits.bufferImageGranularity;
for (const vk::Buffer& curBuffer : buffers) {
const vk::DeviceSize curBindOffset = commitMemoryRequestToHeap(
device.getBufferMemoryRequirements(curBuffer), bufferHeapAllocInfo.allocationSize, bufferHeapMemoryTypeBits, bufferImageGranularity
);
if (bufferHeapMemoryTypeBits == 0) {
// No possible memory heap for all of the buffers to share
return std::make_tuple(vk::Result::eErrorOutOfDeviceMemory, vk::UniqueDeviceMemory());
}
// Put nullptr for the device memory for now
bufferHeapBinds.emplace_back(vk::BindBufferMemoryInfo{curBuffer, nullptr, curBindOffset});
}
const s32 memoryTypeIndex = findMemoryTypeIndex(physicalDevice, bufferHeapMemoryTypeBits, memoryProperties, memoryExcludeProperties);
if (memoryTypeIndex < 0) {
// Unable to find a memory heap that satisfies all the buffers
return std::make_tuple(vk::Result::eErrorOutOfDeviceMemory, vk::UniqueDeviceMemory());
}
bufferHeapAllocInfo.memoryTypeIndex = memoryTypeIndex;
vk::UniqueDeviceMemory bufferHeapMemory = {};
if (auto allocResult = device.allocateMemoryUnique(bufferHeapAllocInfo); allocResult.result == vk::Result::eSuccess) {
bufferHeapMemory = std::move(allocResult.value);
} else {
return std::make_tuple(allocResult.result, vk::UniqueDeviceMemory());
}
// Assign the device memory to the bindings
for (vk::BindBufferMemoryInfo& curBind : bufferHeapBinds) {
curBind.memory = bufferHeapMemory.get();
}
// Now bind them all in one call
if (const vk::Result bindResult = device.bindBufferMemory2(bufferHeapBinds); bindResult == vk::Result::eSuccess) {
// Binding memory succeeded
} else {
return std::make_tuple(bindResult, vk::UniqueDeviceMemory());
}
return std::make_tuple(vk::Result::eSuccess, std::move(bufferHeapMemory));
}
} // namespace Vulkan

View file

@ -0,0 +1,39 @@
#include "renderer_vk/vk_pica.hpp"
namespace Vulkan {
vk::Format colorFormatToVulkan(PICA::ColorFmt colorFormat) {
switch (colorFormat) {
case PICA::ColorFmt::RGBA8: return vk::Format::eR8G8B8A8Unorm;
// VK_FORMAT_R8G8B8A8_UNORM is mandated by the vulkan specification
// VK_FORMAT_R8G8B8_UNORM may not be supported
// TODO: Detect this!
// case PICA::ColorFmt::RGB8: return vk::Format::eR8G8B8Unorm;
case PICA::ColorFmt::RGB8: return vk::Format::eR8G8B8A8Unorm;
case PICA::ColorFmt::RGBA5551: return vk::Format::eR5G5B5A1UnormPack16;
case PICA::ColorFmt::RGB565: return vk::Format::eR5G6B5UnormPack16;
case PICA::ColorFmt::RGBA4: return vk::Format::eR4G4B4A4UnormPack16;
}
return vk::Format::eUndefined;
}
vk::Format depthFormatToVulkan(PICA::DepthFmt depthFormat) {
switch (depthFormat) {
// VK_FORMAT_D16_UNORM is mandated by the vulkan specification
case PICA::DepthFmt::Depth16: return vk::Format::eD16Unorm;
case PICA::DepthFmt::Unknown1: return vk::Format::eUndefined;
// The GPU may _not_ support these formats natively
// Only one of:
// VK_FORMAT_X8_D24_UNORM_PACK32 and VK_FORMAT_D32_SFLOAT
// and one of:
// VK_FORMAT_D24_UNORM_S8_UINT and VK_FORMAT_D32_SFLOAT_S8_UINT
// will be supported
// TODO: Detect this!
// case PICA::DepthFmt::Depth24: return vk::Format::eX8D24UnormPack32;
// case PICA::DepthFmt::Depth24Stencil8: return vk::Format::eD24UnormS8Uint;
case PICA::DepthFmt::Depth24: return vk::Format::eD32Sfloat;
case PICA::DepthFmt::Depth24Stencil8: return vk::Format::eD32SfloatS8Uint;
}
return vk::Format::eUndefined;
}
} // namespace Vulkan

View file

@ -0,0 +1,31 @@
#include "renderer_vk/vk_sampler_cache.hpp"
#include <vulkan/vulkan_hash.hpp>
#include "helpers.hpp"
namespace Vulkan {
SamplerCache::SamplerCache(vk::Device device) : device(device) {}
const vk::Sampler& SamplerCache::getSampler(const vk::SamplerCreateInfo& samplerInfo) {
const std::size_t samplerHash = std::hash<vk::SamplerCreateInfo>()(samplerInfo);
// Cache hit
if (samplerMap.contains(samplerHash)) {
return samplerMap.at(samplerHash).get();
}
if (auto createResult = device.createSamplerUnique(samplerInfo); createResult.result == vk::Result::eSuccess) {
return (samplerMap[samplerHash] = std::move(createResult.value)).get();
} else {
Helpers::panic("Error creating sampler: %s\n", vk::to_string(createResult.result).c_str());
}
}
std::optional<SamplerCache> SamplerCache::create(vk::Device device) {
SamplerCache newSamplerCache(device);
return {std::move(newSamplerCache)};
}
} // namespace Vulkan

View file

@ -1,3 +0,0 @@
#include "renderer_vk/vulkan_api.hpp"
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE;

View file

@ -8,11 +8,15 @@ namespace ACCommands {
CloseAsync = 0x00080004,
GetLastErrorCode = 0x000A0000,
RegisterDisconnectEvent = 0x00300004,
IsConnected = 0x003E0042,
SetClientVersion = 0x00400042,
};
}
void ACService::reset() {}
void ACService::reset() {
connected = false;
disconnectEvent = std::nullopt;
}
void ACService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
@ -21,6 +25,7 @@ void ACService::handleSyncRequest(u32 messagePointer) {
case ACCommands::CloseAsync: closeAsync(messagePointer); break;
case ACCommands::CreateDefaultConfig: createDefaultConfig(messagePointer); break;
case ACCommands::GetLastErrorCode: getLastErrorCode(messagePointer); break;
case ACCommands::IsConnected: isConnected(messagePointer); break;
case ACCommands::RegisterDisconnectEvent: registerDisconnectEvent(messagePointer); break;
case ACCommands::SetClientVersion: setClientVersion(messagePointer); break;
default: Helpers::panic("AC service requested. Command: %08X\n", command);
@ -37,6 +42,11 @@ void ACService::cancelConnectAsync(u32 messagePointer) {
void ACService::closeAsync(u32 messagePointer) {
log("AC::CloseAsync (stubbed)\n");
connected = false;
if (disconnectEvent.has_value()) {
Helpers::warn("AC::DisconnectEvent should be signalled but isn't implemented yet");
}
// TODO: Verify if this response header is correct on hardware
mem.write32(messagePointer, IPC::responseHeader(0x8, 1, 0));
@ -59,6 +69,15 @@ void ACService::getLastErrorCode(u32 messagePointer) {
mem.write32(messagePointer + 8, 0); // Hopefully this means no error?
}
void ACService::isConnected(u32 messagePointer) {
log("AC::IsConnected\n");
// This has parameters according to the command word but it's unknown what they are
mem.write32(messagePointer, IPC::responseHeader(0x3E, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write8(messagePointer + 8, connected ? 1 : 0);
}
void ACService::setClientVersion(u32 messagePointer) {
u32 version = mem.read32(messagePointer + 4);
log("AC::SetClientVersion (version = %d)\n", version);
@ -71,9 +90,11 @@ void ACService::registerDisconnectEvent(u32 messagePointer) {
log("AC::RegisterDisconnectEvent (stubbed)\n");
const u32 pidHeader = mem.read32(messagePointer + 4);
const u32 copyHandleHeader = mem.read32(messagePointer + 12);
// Event signaled when disconnecting from AC
// Event signaled when disconnecting from AC. TODO: Properly implement it.
const Handle eventHandle = mem.read32(messagePointer + 16);
disconnectEvent = eventHandle;
mem.write32(messagePointer, IPC::responseHeader(0x30, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}

View file

@ -17,7 +17,10 @@ void ACTService::handleSyncRequest(u32 messagePointer) {
case ACTCommands::GenerateUUID: generateUUID(messagePointer); break;
case ACTCommands::GetAccountDataBlock: getAccountDataBlock(messagePointer); break;
case ACTCommands::Initialize: initialize(messagePointer); break;
default: Helpers::panic("ACT service requested. Command: %08X\n", command);
default:
Helpers::warn("Undocumented ACT service requested. Command: %08X", command);
mem.write32(messagePointer + 4, Result::Success);
break;
}
}

View file

@ -9,10 +9,15 @@ namespace APTCommands {
GetLockHandle = 0x00010040,
Initialize = 0x00020080,
Enable = 0x00030040,
GetAppletInfo = 0x00060040,
IsRegistered = 0x00090040,
InquireNotification = 0x000B0040,
SendParameter = 0x000C0104,
ReceiveParameter = 0x000D0080,
GlanceParameter = 0x000E0080,
PreloadLibraryApplet = 0x00160040,
PrepareToStartLibraryApplet = 0x00180040,
StartLibraryApplet = 0x001E0084,
ReplySleepQuery = 0x003E0080,
NotifyToWait = 0x00430040,
GetSharedFont = 0x00440000,
@ -60,6 +65,8 @@ void APTService::reset() {
lockHandle = std::nullopt;
notificationEvent = std::nullopt;
resumeEvent = std::nullopt;
appletManager.reset();
}
void APTService::handleSyncRequest(u32 messagePointer) {
@ -69,18 +76,22 @@ void APTService::handleSyncRequest(u32 messagePointer) {
case APTCommands::CheckNew3DS: checkNew3DS(messagePointer); break;
case APTCommands::CheckNew3DSApp: checkNew3DSApp(messagePointer); break;
case APTCommands::Enable: enable(messagePointer); break;
case APTCommands::GetAppletInfo: getAppletInfo(messagePointer); break;
case APTCommands::GetSharedFont: getSharedFont(messagePointer); break;
case APTCommands::Initialize: initialize(messagePointer); break;
case APTCommands::InquireNotification: [[likely]] inquireNotification(messagePointer); break;
case APTCommands::IsRegistered: isRegistered(messagePointer); break;
case APTCommands::GetApplicationCpuTimeLimit: getApplicationCpuTimeLimit(messagePointer); break;
case APTCommands::GetLockHandle: getLockHandle(messagePointer); break;
case APTCommands::GetWirelessRebootInfo: getWirelessRebootInfo(messagePointer); break;
case APTCommands::GlanceParameter: glanceParameter(messagePointer); break;
case APTCommands::NotifyToWait: notifyToWait(messagePointer); break;
case APTCommands::PreloadLibraryApplet: preloadLibraryApplet(messagePointer); break;
case APTCommands::PrepareToStartLibraryApplet: prepareToStartLibraryApplet(messagePointer); break;
case APTCommands::ReceiveParameter: [[likely]] receiveParameter(messagePointer); break;
case APTCommands::ReplySleepQuery: replySleepQuery(messagePointer); break;
case APTCommands::SetApplicationCpuTimeLimit: setApplicationCpuTimeLimit(messagePointer); break;
case APTCommands::SendParameter: sendParameter(messagePointer); break;
case APTCommands::SetScreencapPostPermission: setScreencapPostPermission(messagePointer); break;
case APTCommands::TheSmashBrosFunction: theSmashBrosFunction(messagePointer); break;
default:
@ -116,9 +127,38 @@ void APTService::appletUtility(u32 messagePointer) {
}
}
void APTService::getAppletInfo(u32 messagePointer) {
const u32 appID = mem.read32(messagePointer + 4);
Helpers::warn("APT::GetAppletInfo (appID = %X)\n", appID);
mem.write32(messagePointer, IPC::responseHeader(0x06, 7, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write8(messagePointer + 20, 1); // 1 = registered
mem.write8(messagePointer + 24, 1); // 1 = loaded
// TODO: The rest of this
}
void APTService::isRegistered(u32 messagePointer) {
const u32 appID = mem.read32(messagePointer + 4);
Helpers::warn("APT::IsRegistered (appID = %X)", appID);
mem.write32(messagePointer, IPC::responseHeader(0x09, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write8(messagePointer + 8, 1); // Return that the app is always registered. This might break with home menu?
}
void APTService::preloadLibraryApplet(u32 messagePointer) {
const u32 appID = mem.read32(messagePointer + 4);
log("APT::PreloadLibraryApplet (app ID = %d) (stubbed)\n", appID);
log("APT::PreloadLibraryApplet (app ID = %X) (stubbed)\n", appID);
mem.write32(messagePointer, IPC::responseHeader(0x16, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void APTService::prepareToStartLibraryApplet(u32 messagePointer) {
const u32 appID = mem.read32(messagePointer + 4);
log("APT::PrepareToStartLibraryApplet (app ID = %X) (stubbed)\n", appID);
mem.write32(messagePointer, IPC::responseHeader(0x16, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
@ -193,6 +233,35 @@ void APTService::notifyToWait(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
}
void APTService::sendParameter(u32 messagePointer) {
const u32 sourceAppID = mem.read32(messagePointer + 4);
const u32 destAppID = mem.read32(messagePointer + 8);
const u32 cmd = mem.read32(messagePointer + 12);
const u32 paramSize = mem.read32(messagePointer + 16);
const u32 parameterHandle = mem.read32(messagePointer + 24); // What dis?
const u32 parameterPointer = mem.read32(messagePointer + 32);
log("APT::SendParameter (source app = %X, dest app = %X, cmd = %X, size = %X) (Stubbed)", sourceAppID, destAppID, cmd, paramSize);
mem.write32(messagePointer, IPC::responseHeader(0x0C, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
if (sourceAppID != Applets::AppletIDs::Application) {
Helpers::warn("APT::SendParameter: Unimplemented source applet ID");
}
Applets::AppletBase* destApplet = appletManager.getApplet(destAppID);
if (destApplet == nullptr) {
Helpers::warn("APT::SendParameter: Unimplemented dest applet ID");
} else {
auto result = destApplet->receiveParameter();
}
if (resumeEvent.has_value()) {
kernel.signalEvent(resumeEvent.value());
}
}
void APTService::receiveParameter(u32 messagePointer) {
const u32 app = mem.read32(messagePointer + 4);
const u32 size = mem.read32(messagePointer + 8);

View file

@ -18,6 +18,9 @@ namespace FRDCommands {
GetMyScreenName = 0x00090000,
GetMyMii = 0x000A0000,
GetFriendKeyList = 0x00110080,
GetFriendPresence = 0x00120042,
GetFriendProfile = 0x00150042,
GetFriendAttributeFlags = 0x00170042,
UpdateGameModeDescription = 0x001D0002,
};
}
@ -28,7 +31,10 @@ void FRDService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case FRDCommands::AttachToEventNotification: attachToEventNotification(messagePointer); break;
case FRDCommands::GetFriendAttributeFlags: getFriendAttributeFlags(messagePointer); break;
case FRDCommands::GetFriendKeyList: getFriendKeyList(messagePointer); break;
case FRDCommands::GetFriendPresence: getFriendPresence(messagePointer); break;
case FRDCommands::GetFriendProfile: getFriendProfile(messagePointer); break;
case FRDCommands::GetMyFriendKey: getMyFriendKey(messagePointer); break;
case FRDCommands::GetMyMii: getMyMii(messagePointer); break;
case FRDCommands::GetMyPresence: getMyPresence(messagePointer); break;
@ -83,6 +89,41 @@ void FRDService::getFriendKeyList(u32 messagePointer) {
}
}
void FRDService::getFriendProfile(u32 messagePointer) {
log("FRD::GetFriendProfile\n");
const u32 count = mem.read32(messagePointer + 4);
const u32 friendKeyList = mem.read32(messagePointer + 12); // Pointer to list of friend keys
const u32 profile = mem.read32(messagePointer + 0x104); // Pointer to friend profile where we'll write info to
mem.write32(messagePointer, IPC::responseHeader(0x15, 1, 2));
mem.write32(messagePointer + 4, Result::Success);
// Clear all profiles
for (u32 i = 0; i < count; i++) {
const u32 pointer = profile + (i * sizeof(Profile));
for (u32 j = 0; j < sizeof(Profile); j++) {
mem.write8(pointer + j, 0);
}
}
}
void FRDService::getFriendAttributeFlags(u32 messagePointer) {
log("FRD::GetFriendAttributeFlags\n");
const u32 count = mem.read32(messagePointer + 4);
const u32 friendKeyList = mem.read32(messagePointer + 12); // Pointer to list of friend keys
const u32 profile = mem.read32(messagePointer + 0x104); // Pointer to friend profile where we'll write info to
mem.write32(messagePointer, IPC::responseHeader(0x17, 1, 2));
mem.write32(messagePointer + 4, Result::Success);
// Clear flags
for (u32 i = 0; i < count; i++) {
mem.write8(profile + i, 0);
}
}
void FRDService::getMyPresence(u32 messagePointer) {
static constexpr u32 presenceSize = 0x12C; // A presence seems to be 12C bytes of data, not sure what it contains
log("FRD::GetMyPresence\n");
@ -96,6 +137,14 @@ void FRDService::getMyPresence(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
}
void FRDService::getFriendPresence(u32 messagePointer) {
Helpers::warn("FRD::GetFriendPresence (stubbed)");
// TODO: Implement and document this,
mem.write32(messagePointer, IPC::responseHeader(0x12, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void FRDService::getMyProfile(u32 messagePointer) {
mem.write32(messagePointer, IPC::responseHeader(0x7, 3, 0)); // Not sure if the header here has the correct # of responses?
mem.write32(messagePointer + 4, Result::Success);

View file

@ -26,13 +26,19 @@ namespace FSCommands {
GetFreeBytes = 0x08120080,
IsSdmcDetected = 0x08170000,
IsSdmcWritable = 0x08180000,
AbnegateAccessRight = 0x08400040,
GetFormatInfo = 0x084500C2,
GetArchiveResource = 0x08490040,
FormatSaveData = 0x084C0242,
CreateExtSaveData = 0x08510242,
DeleteExtSaveData = 0x08520100,
SetArchivePriority = 0x085A00C0,
InitializeWithSdkVersion = 0x08610042,
SetPriority = 0x08620040,
GetPriority = 0x08630000
GetPriority = 0x08630000,
SetThisSaveDataSecureValue = 0x086E00C0,
GetThisSaveDataSecureValue = 0x086F0040,
TheGameboyVCFunction = 0x08750180,
};
}
@ -71,6 +77,8 @@ ArchiveBase* FSService::getArchiveFromID(u32 id, const FSPath& archivePath) {
switch (id) {
case ArchiveID::SelfNCCH: return &selfNcch;
case ArchiveID::SaveData: return &saveData;
case ArchiveID::UserSaveData2: return &userSaveData2;
case ArchiveID::ExtSaveData:
return &extSaveData_sdmc;
@ -155,9 +163,11 @@ void FSService::handleSyncRequest(u32 messagePointer) {
case FSCommands::DeleteFile: deleteFile(messagePointer); break;
case FSCommands::FormatSaveData: formatSaveData(messagePointer); break;
case FSCommands::FormatThisUserSaveData: formatThisUserSaveData(messagePointer); break;
case FSCommands::GetArchiveResource: getArchiveResource(messagePointer); break;
case FSCommands::GetFreeBytes: getFreeBytes(messagePointer); break;
case FSCommands::GetFormatInfo: getFormatInfo(messagePointer); break;
case FSCommands::GetPriority: getPriority(messagePointer); break;
case FSCommands::GetThisSaveDataSecureValue: getThisSaveDataSecureValue(messagePointer); break;
case FSCommands::Initialize: initialize(messagePointer); break;
case FSCommands::InitializeWithSdkVersion: initializeWithSdkVersion(messagePointer); break;
case FSCommands::IsSdmcDetected: isSdmcDetected(messagePointer); break;
@ -166,7 +176,11 @@ void FSService::handleSyncRequest(u32 messagePointer) {
case FSCommands::OpenDirectory: openDirectory(messagePointer); break;
case FSCommands::OpenFile: [[likely]] openFile(messagePointer); break;
case FSCommands::OpenFileDirectly: [[likely]] openFileDirectly(messagePointer); break;
case FSCommands::SetArchivePriority: setArchivePriority(messagePointer); break;
case FSCommands::SetPriority: setPriority(messagePointer); break;
case FSCommands::SetThisSaveDataSecureValue: setThisSaveDataSecureValue(messagePointer); break;
case FSCommands::AbnegateAccessRight: abnegateAccessRight(messagePointer); break;
case FSCommands::TheGameboyVCFunction: theGameboyVCFunction(messagePointer); break;
default: Helpers::panic("FS service requested. Command: %08X\n", command);
}
}
@ -575,6 +589,36 @@ void FSService::getPriority(u32 messagePointer) {
mem.write32(messagePointer + 8, priority);
}
void FSService::getArchiveResource(u32 messagePointer) {
const u32 mediaType = mem.read32(messagePointer + 4);
log("FS::GetArchiveResource (media type = %d) (stubbed)\n");
// For the time being, return the same stubbed archive resource for every media type
static constexpr ArchiveResource resource = {
.sectorSize = 512,
.clusterSize = 16_KB,
.partitionCapacityInClusters = 0x80000, // 0x80000 * 16 KB = 8GB
.freeSpaceInClusters = 0x80000, // Same here
};
mem.write32(messagePointer, IPC::responseHeader(0x849, 5, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, resource.sectorSize);
mem.write32(messagePointer + 12, resource.clusterSize);
mem.write32(messagePointer + 16, resource.partitionCapacityInClusters);
mem.write32(messagePointer + 20, resource.freeSpaceInClusters);
}
void FSService::setArchivePriority(u32 messagePointer) {
Handle archive = mem.read64(messagePointer + 4);
const u32 value = mem.read32(messagePointer + 12);
log("FS::SetArchivePriority (priority = %d, archive handle = %X)\n", value, handle);
mem.write32(messagePointer, IPC::responseHeader(0x85A, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void FSService::setPriority(u32 messagePointer) {
const u32 value = mem.read32(messagePointer + 4);
log("FS::SetPriority (priority = %d)\n", value);
@ -584,6 +628,45 @@ void FSService::setPriority(u32 messagePointer) {
priority = value;
}
void FSService::abnegateAccessRight(u32 messagePointer) {
const u32 right = mem.read32(messagePointer + 4);
log("FS::AbnegateAccessRight (right = %d)\n", right);
if (right >= 0x38) {
Helpers::warn("FS::AbnegateAccessRight: Invalid access right");
}
mem.write32(messagePointer, IPC::responseHeader(0x840, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void FSService::getThisSaveDataSecureValue(u32 messagePointer) {
Helpers::warn("Unimplemented FS::GetThisSaveDataSecureValue");
mem.write32(messagePointer, IPC::responseHeader(0x86F, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void FSService::setThisSaveDataSecureValue(u32 messagePointer) {
const u64 value = mem.read32(messagePointer + 4);
const u32 slot = mem.read32(messagePointer + 12);
const u32 id = mem.read32(messagePointer + 16);
const u8 variation = mem.read8(messagePointer + 20);
// TODO: Actually do something with this.
Helpers::warn("Unimplemented FS::SetThisSaveDataSecureValue");
mem.write32(messagePointer, IPC::responseHeader(0x86E, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void FSService::theGameboyVCFunction(u32 messagePointer) {
Helpers::warn("Unimplemented FS: function: 0x08750180");
mem.write32(messagePointer, IPC::responseHeader(0x875, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void FSService::isSdmcDetected(u32 messagePointer) {
log("FS::IsSdmcDetected\n");

View file

@ -15,6 +15,8 @@ namespace ServiceCommands {
FlushDataCache = 0x00080082,
SetLCDForceBlack = 0x000B0040,
TriggerCmdReqQueue = 0x000C0000,
ImportDisplayCaptureInfo = 0x00180000,
SaveVramSysArea = 0x00190000,
SetInternalPriorities = 0x001E0080,
StoreDataCache = 0x001F0082
};
@ -42,15 +44,17 @@ void GPUService::reset() {
void GPUService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case ServiceCommands::TriggerCmdReqQueue: [[likely]] triggerCmdReqQueue(messagePointer); break;
case ServiceCommands::AcquireRight: acquireRight(messagePointer); break;
case ServiceCommands::FlushDataCache: flushDataCache(messagePointer); break;
case ServiceCommands::ImportDisplayCaptureInfo: importDisplayCaptureInfo(messagePointer); break;
case ServiceCommands::RegisterInterruptRelayQueue: registerInterruptRelayQueue(messagePointer); break;
case ServiceCommands::SaveVramSysArea: saveVramSysArea(messagePointer); break;
case ServiceCommands::SetAxiConfigQoSMode: setAxiConfigQoSMode(messagePointer); break;
case ServiceCommands::SetBufferSwap: setBufferSwap(messagePointer); break;
case ServiceCommands::SetInternalPriorities: setInternalPriorities(messagePointer); break;
case ServiceCommands::SetLCDForceBlack: setLCDForceBlack(messagePointer); break;
case ServiceCommands::StoreDataCache: storeDataCache(messagePointer); break;
case ServiceCommands::TriggerCmdReqQueue: [[likely]] triggerCmdReqQueue(messagePointer); break;
case ServiceCommands::WriteHwRegs: writeHwRegs(messagePointer); break;
case ServiceCommands::WriteHwRegsWithMask: writeHwRegsWithMask(messagePointer); break;
default: Helpers::panic("GPU service requested. Command: %08X\n", command);
@ -456,3 +460,20 @@ void GPUService::triggerTextureCopy(u32* cmd) {
// NSMB2 relies on this
requestInterrupt(GPUInterrupt::PPF);
}
// Used when transitioning from the app to an OS applet, such as software keyboard, mii maker, mii selector, etc
// Stubbed until we decide to support LLE applets
void GPUService::saveVramSysArea(u32 messagePointer) {
Helpers::warn("GSP::GPU::SaveVramSysArea (stubbed)");
mem.write32(messagePointer, IPC::responseHeader(0x19, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
// Used in similar fashion to the SaveVramSysArea function
void GPUService::importDisplayCaptureInfo(u32 messagePointer) {
Helpers::warn("GSP::GPU::ImportDisplayCaptureInfo (stubbed)");
mem.write32(messagePointer, IPC::responseHeader(0x18, 9, 0));
mem.write32(messagePointer + 4, Result::Success);
}

View file

@ -1,14 +1,19 @@
#include "services/mic.hpp"
#include "ipc.hpp"
#include "kernel/kernel.hpp"
namespace MICCommands {
enum : u32 {
MapSharedMem = 0x00010042,
UnmapSharedMem = 0x00020000,
StartSampling = 0x00030140,
StopSampling = 0x00050000,
IsSampling = 0x00060000,
GetEventHandle = 0x00070000,
SetGain = 0x00080040,
GetGain = 0x00090000,
SetPower = 0x000A0040,
GetPower = 0x000B0000,
SetIirFilter = 0x000C0042,
SetClamp = 0x000D0040,
CaptainToadFunction = 0x00100040,
@ -18,14 +23,19 @@ namespace MICCommands {
void MICService::reset() {
micEnabled = false;
shouldClamp = false;
isSampling = false;
currentlySampling = false;
gain = 0;
eventHandle = std::nullopt;
}
void MICService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case MICCommands::GetEventHandle: getEventHandle(messagePointer); break;
case MICCommands::GetGain: getGain(messagePointer); break;
case MICCommands::GetPower: getPower(messagePointer); break;
case MICCommands::IsSampling: isSampling(messagePointer); break;
case MICCommands::MapSharedMem: mapSharedMem(messagePointer); break;
case MICCommands::SetClamp: setClamp(messagePointer); break;
case MICCommands::SetGain: setGain(messagePointer); break;
@ -33,6 +43,7 @@ void MICService::handleSyncRequest(u32 messagePointer) {
case MICCommands::SetPower: setPower(messagePointer); break;
case MICCommands::StartSampling: startSampling(messagePointer); break;
case MICCommands::StopSampling: stopSampling(messagePointer); break;
case MICCommands::UnmapSharedMem: unmapSharedMem(messagePointer); break;
case MICCommands::CaptainToadFunction: theCaptainToadFunction(messagePointer); break;
default: Helpers::panic("MIC service requested. Command: %08X\n", command);
}
@ -47,6 +58,27 @@ void MICService::mapSharedMem(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
}
void MICService::unmapSharedMem(u32 messagePointer) {
log("MIC::UnmapSharedMem (stubbed)\n");
mem.write32(messagePointer, IPC::responseHeader(0x2, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void MICService::getEventHandle(u32 messagePointer) {
log("MIC::GetEventHandle\n");
Helpers::warn("Acquire MIC event handle");
if (!eventHandle.has_value()) {
eventHandle = kernel.makeEvent(ResetType::OneShot);
}
mem.write32(messagePointer, IPC::responseHeader(0x7, 1, 2));
mem.write32(messagePointer + 4, Result::Success);
// TODO: Translation descriptor
mem.write32(messagePointer + 12, eventHandle.value());
}
void MICService::getGain(u32 messagePointer) {
log("MIC::GetGain\n");
mem.write32(messagePointer, IPC::responseHeader(0x9, 2, 0));
@ -71,6 +103,14 @@ void MICService::setPower(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
}
void MICService::getPower(u32 messagePointer) {
log("MIC::GetPower\n");
mem.write32(messagePointer, IPC::responseHeader(0xB, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write8(messagePointer + 8, micEnabled ? 1 : 0);
}
void MICService::setClamp(u32 messagePointer) {
u8 val = mem.read8(messagePointer + 4);
log("MIC::SetClamp (value = %d)\n", val);
@ -91,19 +131,27 @@ void MICService::startSampling(u32 messagePointer) {
encoding, sampleRate, offset, dataSize, loop ? "yes" : "no"
);
isSampling = true;
currentlySampling = true;
mem.write32(messagePointer, IPC::responseHeader(0x3, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void MICService::stopSampling(u32 messagePointer) {
log("MIC::StopSampling\n");
isSampling = false;
currentlySampling = false;
mem.write32(messagePointer, IPC::responseHeader(0x5, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void MICService::isSampling(u32 messagePointer) {
log("MIC::IsSampling");
mem.write32(messagePointer, IPC::responseHeader(0x6, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write8(messagePointer + 8, currentlySampling ? 1 : 0);
}
void MICService::setIirFilter(u32 messagePointer) {
const u32 size = mem.read32(messagePointer + 4);
const u32 pointer = mem.read32(messagePointer + 12);

View file

@ -5,8 +5,10 @@
namespace NFCCommands {
enum : u32 {
Initialize = 0x00010040,
Shutdown = 0x00020040,
StartCommunication = 0x00030000,
StopCommunication = 0x00040000,
StartTagScanning = 0x00050040,
GetTagInRangeEvent = 0x000B0000,
GetTagOutOfRangeEvent = 0x000C0000,
GetTagState = 0x000D0000,
@ -32,7 +34,9 @@ void NFCService::handleSyncRequest(u32 messagePointer) {
case NFCCommands::GetTagInRangeEvent: getTagInRangeEvent(messagePointer); break;
case NFCCommands::GetTagOutOfRangeEvent: getTagOutOfRangeEvent(messagePointer); break;
case NFCCommands::GetTagState: getTagState(messagePointer); break;
case NFCCommands::Shutdown: shutdown(messagePointer); break;
case NFCCommands::StartCommunication: startCommunication(messagePointer); break;
case NFCCommands::StartTagScanning: startTagScanning(messagePointer); break;
case NFCCommands::StopCommunication: stopCommunication(messagePointer); break;
default: Helpers::panic("NFC service requested. Command: %08X\n", command);
}
@ -50,6 +54,16 @@ void NFCService::initialize(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
}
void NFCService::shutdown(u32 messagePointer) {
log("MFC::Shutdown");
const u8 mode = mem.read8(messagePointer + 4);
Helpers::warn("NFC::Shutdown: Unimplemented mode: %d", mode);
mem.write32(messagePointer, IPC::responseHeader(0x2, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
/*
The NFC service provides userland with 2 events. One that is signaled when an NFC tag gets in range,
And one that is signaled when it gets out of range. Userland can have a thread sleep on this so it will be alerted
@ -114,6 +128,14 @@ void NFCService::startCommunication(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
}
void NFCService::startTagScanning(u32 messagePointer) {
log("NFC::StartTagScanning\n");
tagStatus = TagStatus::Scanning;
mem.write32(messagePointer, IPC::responseHeader(0x5, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void NFCService::stopCommunication(u32 messagePointer) {
log("NFC::StopCommunication\n");
adapterStatus = Old3DSAdapterStatus::InitializationComplete;

View file

@ -8,7 +8,7 @@
ServiceManager::ServiceManager(std::span<u32, 16> regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel, const EmulatorConfig& config)
: regs(regs), mem(mem), kernel(kernel), ac(mem), am(mem), boss(mem), act(mem), apt(mem, kernel), cam(mem, kernel), cecd(mem, kernel), cfg(mem),
dlp_srvr(mem), dsp(mem, kernel), hid(mem, kernel), http(mem), ir_user(mem, kernel), frd(mem), fs(mem, kernel, config),
gsp_gpu(mem, gpu, kernel, currentPID), gsp_lcd(mem), ldr(mem), mcu_hwc(mem, config), mic(mem), nfc(mem, kernel), nim(mem), ndm(mem),
gsp_gpu(mem, gpu, kernel, currentPID), gsp_lcd(mem), ldr(mem), mcu_hwc(mem, config), mic(mem, kernel), nfc(mem, kernel), nim(mem), ndm(mem),
news_u(mem), ptm(mem, config), soc(mem), ssl(mem), y2r(mem, kernel) {}
static constexpr int MAX_NOTIFICATION_COUNT = 16;

View file

@ -1,4 +1,5 @@
#include "services/y2r.hpp"
#include "ipc.hpp"
#include "kernel.hpp"
@ -6,8 +7,10 @@ namespace Y2RCommands {
enum : u32 {
SetInputFormat = 0x00010040,
SetOutputFormat = 0x00030040,
GetOutputFormat = 0x00040000,
SetRotation = 0x00050040,
SetBlockAlignment = 0x00070040,
GetBlockAlignment = 0x00080000,
SetSpacialDithering = 0x00090040,
SetTemporalDithering = 0x000B0040,
SetTransferEndInterrupt = 0x000D0040,
@ -17,7 +20,9 @@ namespace Y2RCommands {
SetSendingV = 0x00120102,
SetReceiving = 0x00180102,
SetInputLineWidth = 0x001A0040,
GetInputLineWidth = 0x001B0000,
SetInputLines = 0x001C0040,
GetInputLines = 0x001D0000,
SetStandardCoeff = 0x00200040,
SetAlpha = 0x00220040,
StartConversion = 0x00260000,
@ -26,7 +31,7 @@ namespace Y2RCommands {
SetPackageParameter = 0x002901C0,
PingProcess = 0x002A0000,
DriverInitialize = 0x002B0000,
DriverFinalize = 0x002C0000
DriverFinalize = 0x002C0000,
};
}
@ -52,6 +57,10 @@ void Y2RService::handleSyncRequest(u32 messagePointer) {
switch (command) {
case Y2RCommands::DriverInitialize: driverInitialize(messagePointer); break;
case Y2RCommands::DriverFinalize: driverFinalize(messagePointer); break;
case Y2RCommands::GetBlockAlignment: getBlockAlignment(messagePointer); break;
case Y2RCommands::GetInputLines: getInputLines(messagePointer); break;
case Y2RCommands::GetInputLineWidth: getInputLineWidth(messagePointer); break;
case Y2RCommands::GetOutputFormat: getOutputFormat(messagePointer); break;
case Y2RCommands::GetTransferEndEvent: getTransferEndEvent(messagePointer); break;
case Y2RCommands::IsBusyConversion: isBusyConversion(messagePointer); break;
case Y2RCommands::PingProcess: pingProcess(messagePointer); break;
@ -81,7 +90,7 @@ void Y2RService::pingProcess(u32 messagePointer) {
log("Y2R::PingProcess\n");
mem.write32(messagePointer, IPC::responseHeader(0x2A, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 0); // Connected number
mem.write32(messagePointer + 8, 0); // Connected number
}
void Y2RService::driverInitialize(u32 messagePointer) {
@ -98,8 +107,9 @@ void Y2RService::driverFinalize(u32 messagePointer) {
void Y2RService::getTransferEndEvent(u32 messagePointer) {
log("Y2R::GetTransferEndEvent\n");
if (!transferEndEvent.has_value())
if (!transferEndEvent.has_value()) {
transferEndEvent = kernel.makeEvent(ResetType::OneShot);
}
mem.write32(messagePointer, IPC::responseHeader(0xF, 1, 2));
mem.write32(messagePointer + 4, Result::Success);
@ -150,6 +160,14 @@ void Y2RService::setBlockAlignment(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
}
void Y2RService::getBlockAlignment(u32 messagePointer) {
log("Y2R::GetBlockAlignment\n");
mem.write32(messagePointer, IPC::responseHeader(0x8, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, static_cast<u32>(alignment));
}
void Y2RService::setInputFormat(u32 messagePointer) {
const u32 format = mem.read32(messagePointer + 4);
log("Y2R::SetInputFormat (format = %d)\n", format);
@ -178,6 +196,14 @@ void Y2RService::setOutputFormat(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
}
void Y2RService::getOutputFormat(u32 messagePointer) {
log("Y2R::GetOutputFormat\n");
mem.write32(messagePointer, IPC::responseHeader(0x4, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, static_cast<u32>(outputFmt));
}
void Y2RService::setPackageParameter(u32 messagePointer) {
// Package parameter is 3 words
const u32 word1 = mem.read32(messagePointer + 4);
@ -243,6 +269,13 @@ void Y2RService::setInputLineWidth(u32 messagePointer) {
}
}
void Y2RService::getInputLineWidth(u32 messagePointer) {
log("Y2R::GetInputLineWidth\n");
mem.write32(messagePointer, IPC::responseHeader(0x1B, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, inputLineWidth);
}
void Y2RService::setInputLines(u32 messagePointer) {
const u16 lines = mem.read16(messagePointer + 4);
log("Y2R::SetInputLines (lines = %d)\n", lines);
@ -253,19 +286,30 @@ void Y2RService::setInputLines(u32 messagePointer) {
Helpers::panic("Y2R: Invalid input line count");
} else {
// According to Citra, the Y2R module seems to accidentally skip setting the line # if it's 1024
if (lines != 1024)
if (lines != 1024) {
inputLines = lines;
}
mem.write32(messagePointer + 4, Result::Success);
}
}
void Y2RService::getInputLines(u32 messagePointer) {
log("Y2R::GetInputLines\n");
mem.write32(messagePointer, IPC::responseHeader(0x1D, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, inputLines);
}
void Y2RService::setStandardCoeff(u32 messagePointer) {
const u32 coeff = mem.read32(messagePointer + 4);
log("Y2R::SetStandardCoeff (coefficient = %d)\n", coeff);
mem.write32(messagePointer, IPC::responseHeader(0x20, 1, 0));
if (coeff > 3)
Helpers::panic("Y2R: Invalid standard coefficient");
if (coeff > 3) {
Helpers::panic("Y2R: Invalid standard coefficient (coefficient = %d)\n", coeff);
}
else {
Helpers::warn("Unimplemented: Y2R standard coefficient");
mem.write32(messagePointer + 4, Result::Success);
@ -316,4 +360,4 @@ void Y2RService::startConversion(u32 messagePointer) {
if (transferEndEvent.has_value()) {
kernel.signalEvent(transferEndEvent.value());
}
}
}