mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-20 04:29:13 +12:00
Merge remote-tracking branch 'upstream/master' into CRO
This commit is contained in:
commit
d50c94cbdc
48 changed files with 4278 additions and 3203 deletions
|
@ -87,6 +87,27 @@ FileDescriptor ExtSaveDataArchive::openFile(const FSPath& path, const FilePerms&
|
|||
return FileError;
|
||||
}
|
||||
|
||||
HorizonResult ExtSaveDataArchive::createDirectory(const FSPath& path) {
|
||||
if (path.type == PathType::UTF16) {
|
||||
if (!isPathSafe<PathType::UTF16>(path)) {
|
||||
Helpers::panic("Unsafe path in ExtSaveData::OpenFile");
|
||||
}
|
||||
|
||||
fs::path p = IOFile::getAppData() / backingFolder;
|
||||
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 ExtSaveData::CreateDirectory");
|
||||
}
|
||||
|
||||
bool success = fs::create_directory(p);
|
||||
return success ? Result::Success : Result::FS::UnexpectedFileOrDir;
|
||||
} else {
|
||||
Helpers::panic("Unimplemented ExtSaveData::CreateDirectory");
|
||||
}
|
||||
}
|
||||
|
||||
std::string ExtSaveDataArchive::getExtSaveDataPathFromBinary(const FSPath& path) {
|
||||
// TODO: Remove punning here
|
||||
const u32 mediaType = *(u32*)&path.binary[0];
|
||||
|
|
|
@ -131,9 +131,6 @@ std::optional<u32> NCCHArchive::readFile(FileSession* file, u64 offset, u32 size
|
|||
}
|
||||
|
||||
auto cxi = mem.getCXI();
|
||||
IOFile& ioFile = mem.CXIFile;
|
||||
|
||||
NCCH::FSInfo fsInfo;
|
||||
|
||||
// Seek to file offset depending on if we're reading from RomFS, ExeFS, etc
|
||||
switch (type) {
|
||||
|
@ -144,7 +141,6 @@ std::optional<u32> NCCHArchive::readFile(FileSession* file, u64 offset, u32 size
|
|||
Helpers::panic("Tried to read from NCCH with too big of an offset");
|
||||
}
|
||||
|
||||
fsInfo = cxi->romFS;
|
||||
offset += 0x1000;
|
||||
break;
|
||||
}
|
||||
|
@ -154,7 +150,7 @@ 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] = cxi->readFromFile(mem.CXIFile, cxi->romFS, &data[0], offset, size);
|
||||
|
||||
if (!success) {
|
||||
Helpers::panic("Failed to read from NCCH archive");
|
||||
|
|
|
@ -39,14 +39,17 @@ HorizonResult SaveDataArchive::createFile(const FSPath& path, u64 size) {
|
|||
|
||||
HorizonResult SaveDataArchive::createDirectory(const FSPath& path) {
|
||||
if (path.type == PathType::UTF16) {
|
||||
if (!isPathSafe<PathType::UTF16>(path))
|
||||
if (!isPathSafe<PathType::UTF16>(path)) {
|
||||
Helpers::panic("Unsafe path in SaveData::OpenFile");
|
||||
}
|
||||
|
||||
fs::path p = IOFile::getAppData() / "SaveData";
|
||||
p += fs::path(path.utf16_string).make_preferred();
|
||||
|
||||
if (fs::is_directory(p))
|
||||
if (fs::is_directory(p)) {
|
||||
return Result::FS::AlreadyExists;
|
||||
}
|
||||
|
||||
if (fs::is_regular_file(p)) {
|
||||
Helpers::panic("File path passed to SaveData::CreateDirectory");
|
||||
}
|
||||
|
@ -92,11 +95,13 @@ HorizonResult SaveDataArchive::deleteFile(const FSPath& path) {
|
|||
|
||||
FileDescriptor SaveDataArchive::openFile(const FSPath& path, const FilePerms& perms) {
|
||||
if (path.type == PathType::UTF16) {
|
||||
if (!isPathSafe<PathType::UTF16>(path))
|
||||
if (!isPathSafe<PathType::UTF16>(path)) {
|
||||
Helpers::panic("Unsafe path in SaveData::OpenFile");
|
||||
}
|
||||
|
||||
if (perms.raw == 0 || (perms.create() && !perms.write()))
|
||||
if (perms.raw == 0 || (perms.create() && !perms.write())) {
|
||||
Helpers::panic("[SaveData] Unsupported flags for OpenFile");
|
||||
}
|
||||
|
||||
fs::path p = IOFile::getAppData() / "SaveData";
|
||||
p += fs::path(path.utf16_string).make_preferred();
|
||||
|
@ -126,8 +131,9 @@ FileDescriptor SaveDataArchive::openFile(const FSPath& path, const FilePerms& pe
|
|||
|
||||
Rust::Result<DirectorySession, HorizonResult> SaveDataArchive::openDirectory(const FSPath& path) {
|
||||
if (path.type == PathType::UTF16) {
|
||||
if (!isPathSafe<PathType::UTF16>(path))
|
||||
if (!isPathSafe<PathType::UTF16>(path)) {
|
||||
Helpers::panic("Unsafe path in SaveData::OpenDirectory");
|
||||
}
|
||||
|
||||
fs::path p = IOFile::getAppData() / "SaveData";
|
||||
p += fs::path(path.utf16_string).make_preferred();
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#include "fs/archive_sdmc.hpp"
|
||||
#include <memory>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
HorizonResult SDMCArchive::createFile(const FSPath& path, u64 size) {
|
||||
Helpers::panic("[SDMC] CreateFile not yet supported");
|
||||
return Result::Success;
|
||||
|
@ -12,13 +14,123 @@ HorizonResult SDMCArchive::deleteFile(const FSPath& path) {
|
|||
}
|
||||
|
||||
FileDescriptor SDMCArchive::openFile(const FSPath& path, const FilePerms& perms) {
|
||||
printf("SDMCArchive::OpenFile: Failed");
|
||||
return FileError;
|
||||
FilePerms realPerms = perms;
|
||||
// SD card always has read permission
|
||||
realPerms.raw |= (1 << 0);
|
||||
|
||||
if ((realPerms.create() && !realPerms.write())) {
|
||||
Helpers::panic("[SDMC] Unsupported flags for OpenFile");
|
||||
}
|
||||
|
||||
std::filesystem::path p = IOFile::getAppData() / "SDMC";
|
||||
|
||||
switch (path.type) {
|
||||
case PathType::ASCII:
|
||||
if (!isPathSafe<PathType::ASCII>(path)) {
|
||||
Helpers::panic("Unsafe path in SDMCArchive::OpenFile");
|
||||
}
|
||||
|
||||
p += fs::path(path.string).make_preferred();
|
||||
break;
|
||||
|
||||
case PathType::UTF16:
|
||||
if (!isPathSafe<PathType::UTF16>(path)) {
|
||||
Helpers::panic("Unsafe path in SDMCArchive::OpenFile");
|
||||
}
|
||||
|
||||
p += fs::path(path.utf16_string).make_preferred();
|
||||
break;
|
||||
|
||||
default: Helpers::panic("SDMCArchive::OpenFile: Failed. Path type: %d", path.type); return FileError;
|
||||
}
|
||||
|
||||
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 (realPerms.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HorizonResult SDMCArchive::createDirectory(const FSPath& path) {
|
||||
std::filesystem::path p = IOFile::getAppData() / "SDMC";
|
||||
|
||||
switch (path.type) {
|
||||
case PathType::ASCII:
|
||||
if (!isPathSafe<PathType::ASCII>(path)) {
|
||||
Helpers::panic("Unsafe path in SDMCArchive::OpenFile");
|
||||
}
|
||||
|
||||
p += fs::path(path.string).make_preferred();
|
||||
break;
|
||||
|
||||
case PathType::UTF16:
|
||||
if (!isPathSafe<PathType::UTF16>(path)) {
|
||||
Helpers::panic("Unsafe path in SDMCArchive::OpenFile");
|
||||
}
|
||||
|
||||
p += fs::path(path.utf16_string).make_preferred();
|
||||
break;
|
||||
|
||||
default: Helpers::panic("SDMCArchive::CreateDirectory: Failed. Path type: %d", path.type); return Result::FailurePlaceholder;
|
||||
}
|
||||
|
||||
if (fs::is_directory(p)) {
|
||||
return Result::FS::AlreadyExists;
|
||||
}
|
||||
|
||||
if (fs::is_regular_file(p)) {
|
||||
Helpers::panic("File path passed to SDMCArchive::CreateDirectory");
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
bool success = fs::create_directory(p, ec);
|
||||
return success ? Result::Success : Result::FS::UnexpectedFileOrDir;
|
||||
}
|
||||
|
||||
Rust::Result<DirectorySession, HorizonResult> SDMCArchive::openDirectory(const FSPath& path) {
|
||||
if (path.type == PathType::UTF16) {
|
||||
if (!isPathSafe<PathType::UTF16>(path)) {
|
||||
Helpers::panic("Unsafe path in SaveData::OpenDirectory");
|
||||
}
|
||||
|
||||
fs::path p = IOFile::getAppData() / "SDMC";
|
||||
p += fs::path(path.utf16_string).make_preferred();
|
||||
|
||||
if (fs::is_regular_file(p)) {
|
||||
printf("SDMC: 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("SDMCArchive::OpenDirectory: Unimplemented path type");
|
||||
return Err(Result::Success);
|
||||
}
|
||||
|
||||
Rust::Result<ArchiveBase*, HorizonResult> SDMCArchive::openArchive(const FSPath& path) {
|
||||
printf("SDMCArchive::OpenArchive: Failed\n");
|
||||
return Err(Result::FS::NotFormatted);
|
||||
// TODO: Fail here if the SD is disabled in the connfig.
|
||||
if (path.type != PathType::Empty) {
|
||||
Helpers::panic("Unimplemented path type for SDMC::OpenArchive");
|
||||
}
|
||||
|
||||
return Ok((ArchiveBase*)this);
|
||||
}
|
||||
|
||||
std::optional<u32> SDMCArchive::readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) {
|
||||
|
|
|
@ -69,57 +69,77 @@ 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::unique_ptr<u8[]> data(new u8[size]);
|
||||
|
||||
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");
|
||||
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");
|
||||
}
|
||||
|
||||
std::tie(success, bytesRead) = hb3dsx->readRomFSBytes(&data[0], offset, size);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
Helpers::panic("Failed to read from SelfNCCH archive");
|
||||
|
|
|
@ -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;
|
||||
|
@ -262,6 +263,8 @@ void Kernel::duplicateHandle() {
|
|||
}
|
||||
}
|
||||
|
||||
void Kernel::clearInstructionCache() { cpu.clearCache(); }
|
||||
|
||||
namespace SystemInfoType {
|
||||
enum : u32 {
|
||||
MemoryInformation = 0,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
302
src/core/loader/3dsx.cpp
Normal 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(¤tRelocs[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, §ionData[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(§ionData[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(§ionData[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);
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -695,6 +695,7 @@ void RendererGL::textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32
|
|||
if (inputGap != 0 || outputGap != 0) {
|
||||
// Helpers::warn("Strided texture copy\n");
|
||||
}
|
||||
|
||||
if (inputWidth != outputWidth) {
|
||||
Helpers::warn("Input width does not match output width, cannot accelerate texture copy!");
|
||||
return;
|
||||
|
@ -716,7 +717,12 @@ void RendererGL::textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32
|
|||
// inputHeight/outputHeight are typically set to zero so they cannot be used to get the height of the copy region
|
||||
// in contrast to display transfer. Compute height manually by dividing the copy size with the copy width. The result
|
||||
// is the number of vertical tiles so multiply that by eight to get the actual copy height.
|
||||
const u32 copyHeight = (copySize / inputWidth) * 8;
|
||||
u32 copyHeight;
|
||||
if (inputWidth != 0) [[likely]] {
|
||||
copyHeight = (copySize / inputWidth) * 8;
|
||||
} else {
|
||||
copyHeight = 0;
|
||||
}
|
||||
|
||||
// Find the source surface.
|
||||
auto srcFramebuffer = getColourBuffer(inputAddr, PICA::ColorFmt::RGBA8, copyStride, copyHeight, false);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,10 @@ namespace CAMCommands {
|
|||
enum : u32 {
|
||||
GetBufferErrorInterruptEvent = 0x00060040,
|
||||
DriverInitialize = 0x00390000,
|
||||
SetTransferLines = 0x00090100,
|
||||
GetMaxLines = 0x000A0080,
|
||||
SetFrameRate = 0x00200080,
|
||||
SetContrast = 0x00230080,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -18,7 +21,12 @@ void CAMService::handleSyncRequest(u32 messagePointer) {
|
|||
case CAMCommands::DriverInitialize: driverInitialize(messagePointer); break;
|
||||
case CAMCommands::GetBufferErrorInterruptEvent: getBufferErrorInterruptEvent(messagePointer); break;
|
||||
case CAMCommands::GetMaxLines: getMaxLines(messagePointer); break;
|
||||
default: Helpers::panic("CAM service requested. Command: %08X\n", command);
|
||||
case CAMCommands::SetContrast: setContrast(messagePointer); break;
|
||||
case CAMCommands::SetFrameRate: setFrameRate(messagePointer); break;
|
||||
case CAMCommands::SetTransferLines: setTransferLines(messagePointer); break;
|
||||
default:
|
||||
Helpers::panic("Unimplemented CAM service requested. Command: %08X\n", command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,6 +36,38 @@ void CAMService::driverInitialize(u32 messagePointer) {
|
|||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void CAMService::setContrast(u32 messagePointer) {
|
||||
const u32 cameraSelect = mem.read32(messagePointer + 4);
|
||||
const u32 contrast = mem.read32(messagePointer + 8);
|
||||
|
||||
log("CAM::SetPhotoMode (camera select = %d, contrast = %d)\n", cameraSelect, contrast);
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x23, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void CAMService::setTransferLines(u32 messagePointer) {
|
||||
const u32 port = mem.read32(messagePointer + 4);
|
||||
const s16 lines = mem.read16(messagePointer + 8);
|
||||
const s16 width = mem.read16(messagePointer + 12);
|
||||
const s16 height = mem.read16(messagePointer + 16);
|
||||
|
||||
log("CAM::SetTransferLines (port = %d, lines = %d, width = %d, height = %d)\n", port, lines, width, height);
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x9, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void CAMService::setFrameRate(u32 messagePointer) {
|
||||
const u32 cameraSelect = mem.read32(messagePointer + 4);
|
||||
const u32 framerate = mem.read32(messagePointer + 8);
|
||||
|
||||
log("CAM::SetPhotoMode (camera select = %d, framerate = %d)\n", cameraSelect, framerate);
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x20, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
// Algorithm taken from Citra
|
||||
// https://github.com/citra-emu/citra/blob/master/src/core/hle/service/cam/cam.cpp#L465
|
||||
void CAMService::getMaxLines(u32 messagePointer) {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "kernel/kernel.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "ipc.hpp"
|
||||
#include "result/result.hpp"
|
||||
|
||||
#ifdef CreateFile // windows.h defines CreateFile & DeleteFile because of course it does.
|
||||
#undef CreateDirectory
|
||||
|
@ -25,13 +26,17 @@ 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,
|
||||
SetThisSaveDataSecureValue = 0x086E00C0,
|
||||
GetThisSaveDataSecureValue = 0x086F0040,
|
||||
TheGameboyVCFunction = 0x08750180,
|
||||
};
|
||||
|
@ -158,6 +163,7 @@ 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;
|
||||
|
@ -170,7 +176,10 @@ 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);
|
||||
}
|
||||
|
@ -344,7 +353,8 @@ void FSService::openFileDirectly(u32 messagePointer) {
|
|||
std::optional<Handle> handle = openFileHandle(archive, filePath, archivePath, perms);
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x803, 1, 2));
|
||||
if (!handle.has_value()) {
|
||||
Helpers::panic("OpenFileDirectly: Failed to open file with given path");
|
||||
printf("OpenFileDirectly failed\n");
|
||||
mem.write32(messagePointer + 4, Result::FS::FileNotFound);
|
||||
} else {
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write32(messagePointer + 12, handle.value());
|
||||
|
@ -579,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);
|
||||
|
@ -588,6 +628,18 @@ 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");
|
||||
|
||||
|
@ -595,6 +647,19 @@ void FSService::getThisSaveDataSecureValue(u32 messagePointer) {
|
|||
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");
|
||||
|
||||
|
@ -602,21 +667,21 @@ void FSService::theGameboyVCFunction(u32 messagePointer) {
|
|||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
// Shows whether an SD card is inserted. At the moment stubbed to no
|
||||
constexpr bool sdInserted = false;
|
||||
|
||||
void FSService::isSdmcDetected(u32 messagePointer) {
|
||||
log("FS::IsSdmcDetected\n");
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x817, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write8(messagePointer + 8, sdInserted ? 1 : 0);
|
||||
mem.write8(messagePointer + 8, config.sdCardInserted ? 1 : 0);
|
||||
}
|
||||
|
||||
// We consider our SD card to always be writable if oen is inserted for now
|
||||
// So isSdmcWritable returns 1 if an SD card is inserted (because it's always writable) and 0 if not.
|
||||
// We consider our SD card to always be writable if one is inserted for now
|
||||
// However we do make sure to respect the configs and properly return the correct value here
|
||||
void FSService::isSdmcWritable(u32 messagePointer) {
|
||||
log("FS::isSdmcWritable\n");
|
||||
const bool writeProtected = (!config.sdCardInserted) || (config.sdCardInserted && config.sdWriteProtected);
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x818, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write8(messagePointer + 8, sdInserted ? 1 : 0);
|
||||
mem.write8(messagePointer + 8, writeProtected ? 0 : 1);
|
||||
}
|
|
@ -6,6 +6,8 @@
|
|||
namespace HTTPCommands {
|
||||
enum : u32 {
|
||||
Initialize = 0x00010044,
|
||||
CreateRootCertChain = 0x002D0000,
|
||||
RootCertChainAddDefaultCert = 0x00300080,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -14,7 +16,9 @@ void HTTPService::reset() { initialized = false; }
|
|||
void HTTPService::handleSyncRequest(u32 messagePointer) {
|
||||
const u32 command = mem.read32(messagePointer);
|
||||
switch (command) {
|
||||
case HTTPCommands::CreateRootCertChain: createRootCertChain(messagePointer); break;
|
||||
case HTTPCommands::Initialize: initialize(messagePointer); break;
|
||||
case HTTPCommands::RootCertChainAddDefaultCert: rootCertChainAddDefaultCert(messagePointer); break;
|
||||
default: Helpers::panic("HTTP service requested. Command: %08X\n", command);
|
||||
}
|
||||
}
|
||||
|
@ -39,4 +43,28 @@ void HTTPService::initialize(u32 messagePointer) {
|
|||
initialized = true;
|
||||
// We currently don't emulate HTTP properly. TODO: Prepare POST buffer here
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void HTTPService::createRootCertChain(u32 messagePointer) {
|
||||
log("HTTP::CreateRootCertChain (Unimplemented)\n");
|
||||
|
||||
// TODO: Verify response header
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x2D, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
|
||||
// RootCertChain context handle. No need to emulate this yet
|
||||
mem.write32(messagePointer + 8, 0x66666666);
|
||||
}
|
||||
|
||||
void HTTPService::rootCertChainAddDefaultCert(u32 messagePointer) {
|
||||
log("HTTP::RootCertChainAddDefaultCert (Unimplemented)\n");
|
||||
const u32 contextHandle = mem.read32(messagePointer + 4);
|
||||
const u32 certID = mem.read32(messagePointer + 8);
|
||||
|
||||
// TODO: Verify response header
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x30, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
|
||||
// Cert context handle. No need to emulate this yet
|
||||
mem.write32(messagePointer + 8, 0x66666666);
|
||||
}
|
|
@ -13,6 +13,7 @@ namespace MICCommands {
|
|||
SetGain = 0x00080040,
|
||||
GetGain = 0x00090000,
|
||||
SetPower = 0x000A0040,
|
||||
GetPower = 0x000B0000,
|
||||
SetIirFilter = 0x000C0042,
|
||||
SetClamp = 0x000D0040,
|
||||
CaptainToadFunction = 0x00100040,
|
||||
|
@ -33,6 +34,7 @@ void MICService::handleSyncRequest(u32 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;
|
||||
|
@ -101,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);
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace PTMCommands {
|
|||
enum : u32 {
|
||||
GetAdapterState = 0x00050000,
|
||||
GetBatteryLevel = 0x00070000,
|
||||
GetBatteryChargeState = 0x00080000,
|
||||
GetStepHistory = 0x000B00C2,
|
||||
GetTotalStepCount = 0x000C0000,
|
||||
ConfigureNew3DSCPU = 0x08180040,
|
||||
|
@ -18,6 +19,7 @@ void PTMService::handleSyncRequest(u32 messagePointer) {
|
|||
switch (command) {
|
||||
case PTMCommands::ConfigureNew3DSCPU: configureNew3DSCPU(messagePointer); break;
|
||||
case PTMCommands::GetAdapterState: getAdapterState(messagePointer); break;
|
||||
case PTMCommands::GetBatteryChargeState: getBatteryChargeState(messagePointer); break;
|
||||
case PTMCommands::GetBatteryLevel: getBatteryLevel(messagePointer); break;
|
||||
case PTMCommands::GetStepHistory: getStepHistory(messagePointer); break;
|
||||
case PTMCommands::GetTotalStepCount: getTotalStepCount(messagePointer); break;
|
||||
|
@ -33,6 +35,16 @@ void PTMService::getAdapterState(u32 messagePointer) {
|
|||
mem.write8(messagePointer + 8, config.chargerPlugged ? 1 : 0);
|
||||
}
|
||||
|
||||
void PTMService::getBatteryChargeState(u32 messagePointer) {
|
||||
log("PTM::GetBatteryChargeState");
|
||||
// We're only charging if the battery is not already full
|
||||
const bool charging = config.chargerPlugged && (config.batteryPercentage < 100);
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x7, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write8(messagePointer + 8, charging ? 1 : 0);
|
||||
}
|
||||
|
||||
void PTMService::getBatteryLevel(u32 messagePointer) {
|
||||
log("PTM::GetBatteryLevel");
|
||||
|
||||
|
|
|
@ -7,7 +7,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),
|
||||
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, kernel), nfc(mem, kernel), nim(mem), ndm(mem),
|
||||
news_u(mem), ptm(mem, config), soc(mem), ssl(mem), y2r(mem, kernel) {}
|
||||
|
||||
|
@ -79,6 +79,7 @@ void ServiceManager::handleSyncRequest(u32 messagePointer) {
|
|||
case Commands::RegisterClient: registerClient(messagePointer); break;
|
||||
case Commands::GetServiceHandle: getServiceHandle(messagePointer); break;
|
||||
case Commands::Subscribe: subscribe(messagePointer); break;
|
||||
case Commands::Unsubscribe: unsubscribe(messagePointer); break;
|
||||
default: Helpers::panic("Unknown \"srv:\" command: %08X", header);
|
||||
}
|
||||
}
|
||||
|
@ -178,6 +179,14 @@ void ServiceManager::subscribe(u32 messagePointer) {
|
|||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void ServiceManager::unsubscribe(u32 messagePointer) {
|
||||
u32 id = mem.read32(messagePointer + 4);
|
||||
log("srv::Unsubscribe (id = %d) (stubbed)\n", id);
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0xA, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void ServiceManager::sendCommandToService(u32 messagePointer, Handle handle) {
|
||||
switch (handle) {
|
||||
// Breaking alphabetical order a bit to place the ones I think are most common at the top
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -426,6 +426,10 @@ bool Emulator::loadROM(const std::filesystem::path& path) {
|
|||
reset(ReloadOption::NoReload);
|
||||
}
|
||||
|
||||
// Reset whatever state needs to be reset before loading a new ROM
|
||||
memory.loadedCXI = std::nullopt;
|
||||
memory.loaded3DSX = std::nullopt;
|
||||
|
||||
// Get path for saving files (AppData on Windows, /home/user/.local/share/ApplcationName on Linux, etc)
|
||||
// Inside that path, we be use a game-specific folder as well. Eg if we were loading a ROM called PenguinDemo.3ds, the savedata would be in
|
||||
// %APPDATA%/Alber/PenguinDemo/SaveData on Windows, and so on. We do this because games save data in their own filesystem on the cart
|
||||
|
@ -453,6 +457,8 @@ bool Emulator::loadROM(const std::filesystem::path& path) {
|
|||
success = loadNCSD(path, ROMType::NCSD);
|
||||
else if (extension == ".cxi" || extension == ".app")
|
||||
success = loadNCSD(path, ROMType::CXI);
|
||||
else if (extension == ".3dsx")
|
||||
success = load3DSX(path);
|
||||
else {
|
||||
printf("Unknown file type\n");
|
||||
success = false;
|
||||
|
@ -492,6 +498,19 @@ bool Emulator::loadNCSD(const std::filesystem::path& path, ROMType type) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Emulator::load3DSX(const std::filesystem::path& path) {
|
||||
std::optional<u32> entrypoint = memory.load3DSX(path);
|
||||
romType = ROMType::HB_3DSX;
|
||||
|
||||
if (!entrypoint.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
cpu.setReg(15, entrypoint.value()); // Set initial PC
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Emulator::loadELF(const std::filesystem::path& path) {
|
||||
loadedELF.open(path, std::ios_base::binary); // Open ROM in binary mode
|
||||
romType = ROMType::ELF;
|
||||
|
|
|
@ -345,7 +345,7 @@ void main() {
|
|||
if ((textureConfig & 2u) != 0u) tevSources[4] = texture(u_tex1, v_texcoord1);
|
||||
if ((textureConfig & 4u) != 0u) tevSources[5] = texture(u_tex2, tex2UV);
|
||||
tevSources[13] = vec4(0.0); // Previous buffer
|
||||
tevSources[15] = vec4(0.0); // Previous combiner
|
||||
tevSources[15] = v_colour; // Previous combiner
|
||||
|
||||
tevNextPreviousBuffer = v_textureEnvBufferColor;
|
||||
uint textureEnvUpdateBuffer = readPicaReg(0xE0);
|
||||
|
|
|
@ -64,7 +64,8 @@ float decodeFP(uint hex, uint E, uint M) {
|
|||
|
||||
void main() {
|
||||
gl_Position = a_coords;
|
||||
v_colour = a_vertexColour;
|
||||
vec4 colourAbs = abs(a_vertexColour);
|
||||
v_colour = min(colourAbs, vec4(1.f));
|
||||
|
||||
// Flip y axis of UVs because OpenGL uses an inverted y for texture sampling compared to the PICA
|
||||
v_texcoord0 = vec3(a_texcoord0.x, 1.0 - a_texcoord0.y, a_texcoord0_w);
|
||||
|
@ -94,4 +95,4 @@ void main() {
|
|||
// There's also another, always-on clipping plane based on vertex z
|
||||
gl_ClipDistance[0] = -a_coords.z;
|
||||
gl_ClipDistance[1] = dot(clipData, a_coords);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue