Merge branch 'master' into specialized-shaderz

This commit is contained in:
wheremyfoodat 2024-02-02 22:22:23 +02:00
commit 9dc12e0681
77 changed files with 2403 additions and 305 deletions

View file

@ -1,32 +1,59 @@
#ifdef CPU_DYNARMIC
#include "cpu_dynarmic.hpp"
#include "arm_defs.hpp"
#include "emulator.hpp"
CPU::CPU(Memory& mem, Kernel& kernel) : mem(mem), env(mem, kernel) {
cp15 = std::make_shared<CP15>();
CPU::CPU(Memory& mem, Kernel& kernel, Emulator& emu) : mem(mem), emu(emu), scheduler(emu.getScheduler()), env(mem, kernel, emu.getScheduler()) {
cp15 = std::make_shared<CP15>();
Dynarmic::A32::UserConfig config;
config.arch_version = Dynarmic::A32::ArchVersion::v6K;
config.callbacks = &env;
config.coprocessors[15] = cp15;
config.define_unpredictable_behaviour = true;
config.global_monitor = &exclusiveMonitor;
config.processor_id = 0;
jit = std::make_unique<Dynarmic::A32::Jit>(config);
Dynarmic::A32::UserConfig config;
config.arch_version = Dynarmic::A32::ArchVersion::v6K;
config.callbacks = &env;
config.coprocessors[15] = cp15;
config.define_unpredictable_behaviour = true;
config.global_monitor = &exclusiveMonitor;
config.processor_id = 0;
jit = std::make_unique<Dynarmic::A32::Jit>(config);
}
void CPU::reset() {
setCPSR(CPSR::UserMode);
setFPSCR(FPSCR::MainThreadDefault);
env.totalTicks = 0;
setCPSR(CPSR::UserMode);
setFPSCR(FPSCR::MainThreadDefault);
env.totalTicks = 0;
cp15->reset();
cp15->setTLSBase(VirtualAddrs::TLSBase); // Set cp15 TLS pointer to the main thread's thread-local storage
jit->Reset();
jit->ClearCache();
jit->Regs().fill(0);
jit->ExtRegs().fill(0);
cp15->reset();
cp15->setTLSBase(VirtualAddrs::TLSBase); // Set cp15 TLS pointer to the main thread's thread-local storage
jit->Reset();
jit->ClearCache();
jit->Regs().fill(0);
jit->ExtRegs().fill(0);
}
#endif // CPU_DYNARMIC
void CPU::runFrame() {
emu.frameDone = false;
while (!emu.frameDone) {
// Run CPU until the next scheduler event
env.ticksLeft = scheduler.nextTimestamp - scheduler.currentTimestamp;
execute:
const auto exitReason = jit->Run();
// Handle any scheduler events that need handling.
emu.pollScheduler();
if (static_cast<u32>(exitReason) != 0) [[unlikely]] {
// Cache invalidation needs to exit the JIT so it returns a CacheInvalidation HaltReason. In our case, we just go back to executing
// The goto might be terrible but it does guarantee that this does not recursively call run and crash, instead getting optimized to a jump
if (Dynarmic::Has(exitReason, Dynarmic::HaltReason::CacheInvalidation)) {
goto execute;
} else {
Helpers::panic("Exit reason: %d\nPC: %08X", static_cast<u32>(exitReason), getReg(15));
}
}
}
}
#endif // CPU_DYNARMIC

View file

@ -139,6 +139,65 @@ void ActionReplay::executeDType(const Cheat& cheat, u32 instruction) {
switch (instruction) {
case 0xD3000000: offset1 = cheat[pc++]; break;
case 0xD3000001: offset2 = cheat[pc++]; break;
case 0xD6000000:
write32(*activeOffset + cheat[pc++], u32(*activeData));
*activeOffset += 4;
break;
case 0xD6000001:
write32(*activeOffset + cheat[pc++], u32(data1));
*activeOffset += 4;
break;
case 0xD6000002:
write32(*activeOffset + cheat[pc++], u32(data2));
*activeOffset += 4;
break;
case 0xD7000000:
write16(*activeOffset + cheat[pc++], u16(*activeData));
*activeOffset += 2;
break;
case 0xD7000001:
write16(*activeOffset + cheat[pc++], u16(data1));
*activeOffset += 2;
break;
case 0xD7000002:
write16(*activeOffset + cheat[pc++], u16(data2));
*activeOffset += 2;
break;
case 0xD8000000:
write8(*activeOffset + cheat[pc++], u8(*activeData));
*activeOffset += 1;
break;
case 0xD8000001:
write8(*activeOffset + cheat[pc++], u8(data1));
*activeOffset += 1;
break;
case 0xD8000002:
write8(*activeOffset + cheat[pc++], u8(data2));
*activeOffset += 1;
break;
case 0xD9000000: *activeData = read32(cheat[pc++] + *activeOffset); break;
case 0xD9000001: data1 = read32(cheat[pc++] + *activeOffset); break;
case 0xD9000002: data2 = read32(cheat[pc++] + *activeOffset); break;
case 0xDA000000: *activeData = read16(cheat[pc++] + *activeOffset); break;
case 0xDA000001: data1 = read16(cheat[pc++] + *activeOffset); break;
case 0xDA000002: data2 = read16(cheat[pc++] + *activeOffset); break;
case 0xDB000000: *activeData = read8(cheat[pc++] + *activeOffset); break;
case 0xDB000001: data1 = read8(cheat[pc++] + *activeOffset); break;
case 0xDB000002: data2 = read8(cheat[pc++] + *activeOffset); break;
case 0xDC000000: *activeOffset += cheat[pc++]; break;
// DD000000 XXXXXXXX - if KEYPAD has value XXXXXXXX execute next block

View file

@ -4,13 +4,14 @@
using namespace Applets;
AppletManager::AppletManager(Memory& mem) : miiSelector(mem, nextParameter), swkbd(mem, nextParameter) {}
AppletManager::AppletManager(Memory& mem) : miiSelector(mem, nextParameter), swkbd(mem, nextParameter), error(mem, nextParameter) {}
void AppletManager::reset() {
nextParameter = std::nullopt;
miiSelector.reset();
swkbd.reset();
error.reset();
}
AppletBase* AppletManager::getApplet(u32 id) {
@ -21,6 +22,9 @@ AppletBase* AppletManager::getApplet(u32 id) {
case AppletIDs::SoftwareKeyboard:
case AppletIDs::SoftwareKeyboard2: return &swkbd;
case AppletIDs::ErrDisp:
case AppletIDs::ErrDisp2: return &error;
default: return nullptr;
}
}

View file

@ -0,0 +1,32 @@
#include "applets/error_applet.hpp"
#include "kernel/handles.hpp"
using namespace Applets;
void ErrorApplet::reset() {}
Result::HorizonResult ErrorApplet::start(const MemoryBlock* sharedMem, const std::vector<u8>& parameters, u32 appID) {
Applets::Parameter param = Applets::Parameter{
.senderID = appID,
.destID = AppletIDs::Application,
.signal = static_cast<u32>(APTSignal::WakeupByExit),
.object = 0,
.data = parameters, // TODO: Figure out how the data format for this applet
};
nextParameter = param;
return Result::Success;
}
Result::HorizonResult ErrorApplet::receiveParameter(const Applets::Parameter& parameter) {
Applets::Parameter param = Applets::Parameter{
.senderID = parameter.destID,
.destID = AppletIDs::Application,
.signal = static_cast<u32>(APTSignal::Response),
.object = KernelHandles::APTCaptureSharedMemHandle,
.data = {},
};
nextParameter = param;
return Result::Success;
}

View file

@ -1,11 +1,86 @@
#include "applets/mii_selector.hpp"
#include <boost/crc.hpp>
#include <limits>
#include "kernel/handles.hpp"
using namespace Applets;
void MiiSelectorApplet::reset() {}
Result::HorizonResult MiiSelectorApplet::start() { return Result::Success; }
Result::HorizonResult MiiSelectorApplet::start(const MemoryBlock* sharedMem, const std::vector<u8>& parameters, u32 appID) {
// Get mii configuration from the application
std::memcpy(&config, &parameters[0], sizeof(config));
Applets::Parameter param = Applets::Parameter{
.senderID = appID,
.destID = AppletIDs::Application,
.signal = static_cast<u32>(APTSignal::WakeupByExit),
.object = 0,
};
// Thanks to Citra devs as always for the default mii data and other applet help
output = getDefaultMii();
output.returnCode = 0; // Success
output.selectedGuestMiiIndex = std::numeric_limits<u32>::max();
output.miiChecksum = boost::crc<16, 0x1021, 0, 0, false, false>(&output.selectedMiiData, sizeof(MiiData) + sizeof(output.unknown1));
// Copy output into the response parameter
param.data.resize(sizeof(output));
std::memcpy(&param.data[0], &output, sizeof(output));
nextParameter = param;
return Result::Success;
}
Result::HorizonResult MiiSelectorApplet::receiveParameter(const Applets::Parameter& parameter) {
Helpers::warn("Mii Selector: Unimplemented ReceiveParameter");
Applets::Parameter param = Applets::Parameter{
.senderID = parameter.destID,
.destID = AppletIDs::Application,
.signal = static_cast<u32>(APTSignal::Response),
.object = KernelHandles::APTCaptureSharedMemHandle,
.data = {},
};
nextParameter = param;
return Result::Success;
}
}
MiiResult MiiSelectorApplet::getDefaultMii() {
// This data was obtained from Citra
MiiData miiData;
miiData.version = 0x03;
miiData.miiOptions = 0x00;
miiData.miiPos = 0x10;
miiData.consoleID = 0x30;
miiData.systemID = 0xD285B6B300C8850A;
miiData.miiID = 0x98391EE4;
miiData.creatorMAC = {0x40, 0xF4, 0x07, 0xB7, 0x37, 0x10};
miiData.padding = 0x0000;
miiData.miiDetails = 0xA600;
miiData.miiName = {'P', 'a', 'n', 'd', 'a', '3', 'D', 'S', 0x0, 0x0};
miiData.height = 0x40;
miiData.width = 0x40;
miiData.faceStyle = 0x00;
miiData.faceDetails = 0x00;
miiData.hairStyle = 0x21;
miiData.hairDetails = 0x01;
miiData.eyeDetails = 0x02684418;
miiData.eyebrowDetails = 0x26344614;
miiData.noseDetails = 0x8112;
miiData.mouthDetails = 0x1768;
miiData.moustacheDetails = 0x0D00;
miiData.beardDetails = 0x0029;
miiData.glassesDetails = 0x0052;
miiData.moleDetails = 0x4850;
miiData.authorName = {u'B', u'O', u'N', u'K', u'E', u'R'};
MiiResult result;
result.returnCode = 0x0;
result.isGuestMiiSelected = 0x0;
result.selectedGuestMiiIndex = std::numeric_limits<u32>::max();
result.selectedMiiData = miiData;
result.guestMiiName.fill(0x0);
return result;
}

View file

@ -1,20 +1,93 @@
#include "applets/software_keyboard.hpp"
#include <cstring>
#include <string>
#include "kernel/handles.hpp"
using namespace Applets;
void SoftwareKeyboardApplet::reset() {}
Result::HorizonResult SoftwareKeyboardApplet::start() { return Result::Success; }
Result::HorizonResult SoftwareKeyboardApplet::receiveParameter(const Applets::Parameter& parameter) {
Helpers::warn("Software keyboard: Unimplemented ReceiveParameter");
switch (parameter.signal) {
// Signal == request -> Applet is asking swkbd for a shared memory handle for backing up the framebuffer before opening the applet
case u32(APTSignal::Request): {
Applets::Parameter param = Applets::Parameter{
.senderID = parameter.destID,
.destID = AppletIDs::Application,
.signal = static_cast<u32>(APTSignal::Response),
.object = KernelHandles::APTCaptureSharedMemHandle,
.data = {},
};
nextParameter = param;
break;
}
default: Helpers::panic("Unimplemented swkbd signal %d\n", parameter.signal);
}
return Result::Success;
}
Result::HorizonResult SoftwareKeyboardApplet::start(const MemoryBlock* sharedMem, const std::vector<u8>& parameters, u32 appID) {
if (parameters.size() < sizeof(SoftwareKeyboardConfig)) {
Helpers::warn("SoftwareKeyboard::Start: Invalid size for keyboard configuration");
return Result::Success;
}
if (sharedMem == nullptr) {
Helpers::warn("SoftwareKeyboard: Missing shared memory");
return Result::Success;
}
// Get keyboard configuration from the application
std::memcpy(&config, &parameters[0], sizeof(config));
const std::u16string text = u"Pand";
u32 textAddress = sharedMem->addr;
// Copy text to shared memory the app gave us
for (u32 i = 0; i < text.size(); i++) {
mem.write16(textAddress, u16(text[i]));
textAddress += sizeof(u16);
}
mem.write16(textAddress, 0); // Write UTF-16 null terminator
// Temporarily hardcode the pressed button to be the firs tone
switch (config.numButtonsM1) {
case SoftwareKeyboardButtonConfig::SingleButton: config.returnCode = SoftwareKeyboardResult::D0Click; break;
case SoftwareKeyboardButtonConfig::DualButton: config.returnCode = SoftwareKeyboardResult::D1Click1; break;
case SoftwareKeyboardButtonConfig::TripleButton: config.returnCode = SoftwareKeyboardResult::D2Click2; break;
case SoftwareKeyboardButtonConfig::NoButton: config.returnCode = SoftwareKeyboardResult::None; break;
default: Helpers::warn("Software keyboard: Invalid button mode specification"); break;
}
config.textOffset = 0;
config.textLength = static_cast<u16>(text.size());
static_assert(offsetof(SoftwareKeyboardConfig, textOffset) == 324);
static_assert(offsetof(SoftwareKeyboardConfig, textLength) == 328);
if (config.filterFlags & SoftwareKeyboardFilter::Callback) {
Helpers::warn("Unimplemented software keyboard profanity callback");
}
closeKeyboard(appID);
return Result::Success;
}
void SoftwareKeyboardApplet::closeKeyboard(u32 appID) {
Applets::Parameter param = Applets::Parameter{
.senderID = parameter.destID,
.senderID = appID,
.destID = AppletIDs::Application,
.signal = static_cast<u32>(APTSignal::Response),
.data = {},
.signal = static_cast<u32>(APTSignal::WakeupByExit),
.object = 0,
};
// Copy software keyboard configuration into the response parameter
param.data.resize(sizeof(config));
std::memcpy(&param.data[0], &config, sizeof(config));
nextParameter = param;
return Result::Success;
}

View file

@ -1,4 +1,5 @@
#include "cheats.hpp"
#include "swap.hpp"
Cheats::Cheats(Memory& mem, HIDService& hid) : ar(mem, hid) { reset(); }
@ -23,6 +24,27 @@ u32 Cheats::addCheat(const Cheat& cheat) {
return cheats.size() - 1;
}
u32 Cheats::addCheat(const u8* data, size_t size) {
if ((size % 8) != 0) {
return badCheatHandle;
}
Cheats::Cheat cheat;
cheat.enabled = true;
cheat.type = Cheats::CheatType::ActionReplay;
for (size_t i = 0; i < size; i += 8) {
auto read32 = [](const u8* ptr) { return (u32(ptr[3]) << 24) | (u32(ptr[2]) << 16) | (u32(ptr[1]) << 8) | u32(ptr[0]); };
// Data is passed to us in big endian so we bswap
u32 firstWord = Common::swap32(read32(data + i));
u32 secondWord = Common::swap32(read32(data + i + 4));
cheat.instructions.insert(cheat.instructions.end(), {firstWord, secondWord});
}
return addCheat(cheat);
}
void Cheats::removeCheat(u32 id) {
if (id >= cheats.size()) {
return;

View file

@ -45,8 +45,16 @@ HorizonResult SDMCArchive::deleteFile(const FSPath& path) {
FileDescriptor SDMCArchive::openFile(const FSPath& path, const FilePerms& perms) {
FilePerms realPerms = perms;
// SD card always has read permission
realPerms.raw |= (1 << 0);
if (isWriteOnly) {
if (perms.read()) {
Helpers::warn("SDMC: Read flag is not allowed in SDMC Write-Only archive");
return FileError;
}
} else {
// Regular SDMC archive always has read permission
realPerms.raw |= (1 << 0);
}
if ((realPerms.create() && !realPerms.write())) {
Helpers::panic("[SDMC] Unsupported flags for OpenFile");
@ -130,6 +138,11 @@ HorizonResult SDMCArchive::createDirectory(const FSPath& path) {
}
Rust::Result<DirectorySession, HorizonResult> SDMCArchive::openDirectory(const FSPath& path) {
if (isWriteOnly) {
Helpers::warn("SDMC: OpenDirectory is not allowed in SDMC Write-Only archive");
return Err(Result::FS::UnexpectedFileOrDir);
}
if (path.type == PathType::UTF16) {
if (!isPathSafe<PathType::UTF16>(path)) {
Helpers::panic("Unsafe path in SaveData::OpenDirectory");

View file

@ -126,8 +126,7 @@ void Kernel::waitSynchronization1() {
auto& t = threads[currentThreadIndex];
t.waitList.resize(1);
t.status = ThreadStatus::WaitSync1;
t.sleepTick = cpu.getTicks();
t.waitingNanoseconds = ns;
t.wakeupTick = getWakeupTick(ns);
t.waitList[0] = handle;
// Add the current thread to the object's wait list
@ -220,8 +219,7 @@ void Kernel::waitSynchronizationN() {
t.waitList.resize(handleCount);
t.status = ThreadStatus::WaitSyncAny;
t.outPointer = outPointer;
t.waitingNanoseconds = ns;
t.sleepTick = cpu.getTicks();
t.wakeupTick = getWakeupTick(ns);
for (s32 i = 0; i < handleCount; i++) {
t.waitList[i] = waitObjects[i].first; // Add object to this thread's waitlist

View file

@ -8,13 +8,6 @@
The code for our idle thread looks like this
idle_thread_main:
mov r0, #4096 @ Loop counter
.loop:
nop; nop; nop; nop @ NOP 4 times to waste some cycles
subs r0, #1 @ Decrement counter by 1, go back to looping if loop counter != 0
bne .loop
// Sleep for 0 seconds with the SleepThread SVC, which just yields execution
mov r0, #0
mov r1, #0
@ -24,14 +17,10 @@ idle_thread_main:
*/
static constexpr u8 idleThreadCode[] = {
0x01, 0x0A, 0xA0, 0xE3, // mov r0, #4096
0x00, 0xF0, 0x20, 0xE3, 0x00, 0xF0, 0x20, 0xE3, 0x00, 0xF0, 0x20, 0xE3, 0x00, 0xF0, 0x20, 0xE3, // nop (4 times)
0x01, 0x00, 0x50, 0xE2, // subs r0, #1
0xF9, 0xFF, 0xFF, 0x1A, // bne loop
0x00, 0x00, 0xA0, 0xE3, // mov r0, #0
0x00, 0x10, 0xA0, 0xE3, // mov r1, #0
0x0A, 0x00, 0x00, 0xEF, // svc SleepThread
0xF4, 0xFF, 0xFF, 0xEA // b idle_thread_main
0xFB, 0xFF, 0xFF, 0xEA // b idle_thread_main
};
// Set up an idle thread to run when no thread is able to run

View file

@ -50,6 +50,7 @@ void Kernel::serviceSVC(u32 svc) {
case 0x1D: svcClearTimer(); break;
case 0x1E: createMemoryBlock(); break;
case 0x1F: mapMemoryBlock(); break;
case 0x20: unmapMemoryBlock(); break;
case 0x21: createAddressArbiter(); break;
case 0x22: arbitrateAddress(); break;
case 0x23: svcCloseHandle(); break;

View file

@ -144,6 +144,7 @@ void Kernel::mapMemoryBlock() {
printf("Mapping CSND memory block\n");
break;
case KernelHandles::APTCaptureSharedMemHandle: break;
default: Helpers::panic("Mapping unknown shared memory block: %X", block);
}
} else {
@ -206,3 +207,12 @@ void Kernel::createMemoryBlock() {
regs[0] = Result::Success;
regs[1] = makeMemoryBlock(addr, size, myPermission, otherPermission);
}
void Kernel::unmapMemoryBlock() {
Handle block = regs[0];
u32 addr = regs[1];
logSVC("Unmap memory block (block handle = %X, addr = %08X)\n", block, addr);
Helpers::warn("Stubbed svcUnmapMemoryBlock!");
regs[0] = Result::Success;
}

View file

@ -52,14 +52,8 @@ bool Kernel::canThreadRun(const Thread& t) {
return true;
} else if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1
|| t.status == ThreadStatus::WaitSyncAny || t.status == ThreadStatus::WaitSyncAll) {
const u64 elapsedTicks = cpu.getTicks() - t.sleepTick;
constexpr double ticksPerSec = double(CPU::ticksPerSec);
constexpr double nsPerTick = ticksPerSec / 1000000000.0;
// TODO: Set r0 to the correct error code on timeout for WaitSync{1/Any/All}
const s64 elapsedNs = s64(double(elapsedTicks) * nsPerTick);
return elapsedNs >= t.waitingNanoseconds;
return cpu.getTicks() >= t.wakeupTick;
}
// Handle timeouts and stuff here
@ -82,6 +76,15 @@ std::optional<int> Kernel::getNextThread() {
return std::nullopt;
}
u64 Kernel::getWakeupTick(s64 ns) {
// Timeout == -1 means that the thread doesn't plan on waking up automatically
if (ns == -1) {
return std::numeric_limits<u64>::max();
}
return cpu.getTicks() + Scheduler::nsToCycles(ns);
}
// See if there is a higher priority, ready thread and switch to that
void Kernel::rescheduleThreads() {
Thread& current = threads[currentThreadIndex]; // Current running thread
@ -368,13 +371,30 @@ void Kernel::sleepThread(s64 ns) {
if (index != idleThreadIndex) {
switchThread(index);
}
} else {
if (currentThreadIndex == idleThreadIndex) {
const Scheduler& scheduler = cpu.getScheduler();
u64 timestamp = scheduler.nextTimestamp;
for (auto i : threadIndices) {
const Thread& t = threads[i];
if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1 || t.status == ThreadStatus::WaitSyncAny ||
t.status == ThreadStatus::WaitSyncAll) {
timestamp = std::min<u64>(timestamp, t.wakeupTick);
}
}
if (timestamp > scheduler.currentTimestamp) {
u64 idleCycles = timestamp - scheduler.currentTimestamp;
cpu.addTicks(idleCycles);
}
}
}
} else { // If we're sleeping for >= 0 ns
Thread& t = threads[currentThreadIndex];
t.status = ThreadStatus::WaitSleep;
t.waitingNanoseconds = ns;
t.sleepTick = cpu.getTicks();
t.wakeupTick = getWakeupTick(ns);
requireReschedule();
}

View file

@ -1,5 +1,8 @@
#include "kernel.hpp"
#include <limits>
#include "cpu.hpp"
#include "kernel.hpp"
#include "scheduler.hpp"
Handle Kernel::makeTimer(ResetType type) {
Handle ret = makeObject(KernelObjectType::Timer);
@ -13,27 +16,44 @@ Handle Kernel::makeTimer(ResetType type) {
return ret;
}
void Kernel::updateTimer(Handle handle, Timer* timer) {
if (timer->running) {
const u64 currentTicks = cpu.getTicks();
u64 elapsedTicks = currentTicks - timer->startTick;
void Kernel::pollTimers() {
u64 currentTick = cpu.getTicks();
constexpr double ticksPerSec = double(CPU::ticksPerSec);
constexpr double nsPerTick = ticksPerSec / 1000000000.0;
const s64 elapsedNs = s64(double(elapsedTicks) * nsPerTick);
// Find the next timestamp we'll poll KTimers on. To do this, we find the minimum tick one of our timers will fire
u64 nextTimestamp = std::numeric_limits<u64>::max();
// Do we have any active timers anymore? If not, then we won't need to schedule a new timer poll event
bool haveActiveTimers = false;
// Timer has fired
if (elapsedNs >= timer->currentDelay) {
timer->startTick = currentTicks;
timer->currentDelay = timer->interval;
signalTimer(handle, timer);
for (auto handle : timerHandles) {
KernelObject* object = getObject(handle, KernelObjectType::Timer);
if (object != nullptr) {
Timer* timer = object->getData<Timer>();
if (timer->running) {
// If timer has fired, signal it and set the tick it will next time
if (currentTick >= timer->fireTick) {
signalTimer(handle, timer);
}
// Update our next timer fire timestamp and mark that we should schedule a new event to poll timers
// We recheck timer->running because signalling a timer stops it if interval == 0
if (timer->running) {
nextTimestamp = std::min<u64>(nextTimestamp, timer->fireTick);
haveActiveTimers = true;
}
}
}
}
// If we still have active timers, schedule next poll event
if (haveActiveTimers) {
Scheduler& scheduler = cpu.getScheduler();
scheduler.addEvent(Scheduler::EventType::UpdateTimers, nextTimestamp);
}
}
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) {
@ -54,6 +74,8 @@ void Kernel::signalTimer(Handle timerHandle, Timer* timer) {
if (timer->interval == 0) {
cancelTimer(timer);
} else {
timer->fireTick = cpu.getTicks() + Scheduler::nsToCycles(timer->interval);
}
}
@ -87,18 +109,20 @@ void Kernel::svcSetTimer() {
Timer* timer = object->getData<Timer>();
cancelTimer(timer);
timer->currentDelay = initial;
timer->interval = interval;
timer->running = true;
timer->startTick = cpu.getTicks();
timer->fireTick = cpu.getTicks() + Scheduler::nsToCycles(initial);
Scheduler& scheduler = cpu.getScheduler();
// Signal an event to poll timers as soon as possible
scheduler.removeEvent(Scheduler::EventType::UpdateTimers);
scheduler.addEvent(Scheduler::EventType::UpdateTimers, cpu.getTicks() + 1);
// 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;
}

View file

@ -64,6 +64,7 @@ void APTService::handleSyncRequest(u32 messagePointer) {
case APTCommands::NotifyToWait: notifyToWait(messagePointer); break;
case APTCommands::PreloadLibraryApplet: preloadLibraryApplet(messagePointer); break;
case APTCommands::PrepareToStartLibraryApplet: prepareToStartLibraryApplet(messagePointer); break;
case APTCommands::StartLibraryApplet: startLibraryApplet(messagePointer); break;
case APTCommands::ReceiveParameter: [[likely]] receiveParameter(messagePointer); break;
case APTCommands::ReplySleepQuery: replySleepQuery(messagePointer); break;
case APTCommands::SetApplicationCpuTimeLimit: setApplicationCpuTimeLimit(messagePointer); break;
@ -140,6 +141,39 @@ void APTService::prepareToStartLibraryApplet(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
}
void APTService::startLibraryApplet(u32 messagePointer) {
const u32 appID = mem.read32(messagePointer + 4);
const u32 bufferSize = mem.read32(messagePointer + 8);
const Handle parameters = mem.read32(messagePointer + 16);
const u32 buffer = mem.read32(messagePointer + 24);
log("APT::StartLibraryApplet (app ID = %X)\n", appID);
Applets::AppletBase* destApplet = appletManager.getApplet(appID);
if (destApplet == nullptr) {
Helpers::warn("APT::StartLibraryApplet: Unimplemented dest applet ID");
mem.write32(messagePointer, IPC::responseHeader(0x1E, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
} else {
KernelObject* sharedMemObject = kernel.getObject(parameters);
const MemoryBlock* sharedMem = sharedMemObject ? sharedMemObject->getData<MemoryBlock>() : nullptr;
std::vector<u8> data;
data.reserve(bufferSize);
for (u32 i = 0; i < bufferSize; i++) {
data.push_back(mem.read8(buffer + i));
}
Result::HorizonResult result = destApplet->start(sharedMem, data, appID);
if (resumeEvent.has_value()) {
kernel.signalEvent(resumeEvent.value());
}
mem.write32(messagePointer, IPC::responseHeader(0x1E, 1, 0));
mem.write32(messagePointer + 4, result);
}
}
void APTService::checkNew3DS(u32 messagePointer) {
log("APT::CheckNew3DS\n");
mem.write32(messagePointer, IPC::responseHeader(0x102, 2, 0));
@ -222,7 +256,7 @@ void APTService::sendParameter(u32 messagePointer) {
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);
log("APT::SendParameter (source app = %X, dest app = %X, cmd = %X, size = %X)", sourceAppID, destAppID, cmd, paramSize);
mem.write32(messagePointer, IPC::responseHeader(0x0C, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
@ -260,7 +294,9 @@ void APTService::sendParameter(u32 messagePointer) {
void APTService::receiveParameter(u32 messagePointer) {
const u32 app = mem.read32(messagePointer + 4);
const u32 size = mem.read32(messagePointer + 8);
log("APT::ReceiveParameter(app ID = %X, size = %04X) (STUBBED)\n", app, size);
// Parameter data pointer is in the thread static buffer, which starts 0x100 bytes after the command buffer
const u32 buffer = mem.read32(messagePointer + 0x100 + 4);
log("APT::ReceiveParameter(app ID = %X, size = %04X)\n", app, size);
if (size > 0x1000) Helpers::panic("APT::ReceiveParameter with size > 0x1000");
auto parameter = appletManager.receiveParameter();
@ -274,14 +310,21 @@ void APTService::receiveParameter(u32 messagePointer) {
// Size of parameter data
mem.write32(messagePointer + 16, parameter.data.size());
mem.write32(messagePointer + 20, 0x10);
mem.write32(messagePointer + 24, 0);
mem.write32(messagePointer + 24, parameter.object);
mem.write32(messagePointer + 28, 0);
const u32 transferSize = std::min<u32>(size, parameter.data.size());
for (u32 i = 0; i < transferSize; i++) {
mem.write8(buffer + i, parameter.data[i]);
}
}
void APTService::glanceParameter(u32 messagePointer) {
const u32 app = mem.read32(messagePointer + 4);
const u32 size = mem.read32(messagePointer + 8);
log("APT::GlanceParameter(app ID = %X, size = %04X) (STUBBED)\n", app, size);
// Parameter data pointer is in the thread static buffer, which starts 0x100 bytes after the command buffer
const u32 buffer = mem.read32(messagePointer + 0x100 + 4);
log("APT::GlanceParameter(app ID = %X, size = %04X)\n", app, size);
if (size > 0x1000) Helpers::panic("APT::GlanceParameter with size > 0x1000");
auto parameter = appletManager.glanceParameter();
@ -296,8 +339,13 @@ void APTService::glanceParameter(u32 messagePointer) {
// Size of parameter data
mem.write32(messagePointer + 16, parameter.data.size());
mem.write32(messagePointer + 20, 0);
mem.write32(messagePointer + 24, 0);
mem.write32(messagePointer + 24, parameter.object);
mem.write32(messagePointer + 28, 0);
const u32 transferSize = std::min<u32>(size, parameter.data.size());
for (u32 i = 0; i < transferSize; i++) {
mem.write8(buffer + i, parameter.data[i]);
}
}
void APTService::replySleepQuery(u32 messagePointer) {

View file

@ -6,6 +6,7 @@ namespace BOSSCommands {
InitializeSession = 0x00010082,
UnregisterStorage = 0x00030000,
GetTaskStorageInfo = 0x00040000,
GetNewArrivalFlag = 0x00070000,
RegisterNewArrivalEvent = 0x00080002,
SetOptoutFlag = 0x00090040,
GetOptoutFlag = 0x000A0000,
@ -37,6 +38,7 @@ void BOSSService::handleSyncRequest(u32 messagePointer) {
switch (command) {
case BOSSCommands::CancelTask: cancelTask(messagePointer); break;
case BOSSCommands::GetErrorCode: getErrorCode(messagePointer); break;
case BOSSCommands::GetNewArrivalFlag: getNewArrivalFlag(messagePointer); break;
case BOSSCommands::GetNsDataIdList:
case BOSSCommands::GetNsDataIdList1:
getNsDataIdList(messagePointer, command); break;
@ -240,4 +242,11 @@ void BOSSService::unregisterStorage(u32 messagePointer) {
log("BOSS::UnregisterStorage (stubbed)\n");
mem.write32(messagePointer, IPC::responseHeader(0x3, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void BOSSService::getNewArrivalFlag(u32 messagePointer) {
log("BOSS::GetNewArrivalFlag (stubbed)\n");
mem.write32(messagePointer, IPC::responseHeader(0x7, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write8(messagePointer + 8, 0); // Flag
}

View file

@ -1,31 +1,96 @@
#include "services/cam.hpp"
#include <vector>
#include "ipc.hpp"
#include "kernel.hpp"
namespace CAMCommands {
enum : u32 {
StartCapture = 0x00010040,
GetBufferErrorInterruptEvent = 0x00060040,
SetReceiving = 0x00070102,
DriverInitialize = 0x00390000,
DriverFinalize = 0x003A0000,
SetTransferLines = 0x00090100,
GetMaxLines = 0x000A0080,
SetTransferBytes = 0x000B0100,
GetTransferBytes = 0x000C0040,
GetMaxBytes = 0x000D0080,
SetTrimming = 0x000E0080,
SetTrimmingParamsCenter = 0x00120140,
SetSize = 0x001F00C0, // Set size has different headers between cam:u and New3DS QTM module
SetFrameRate = 0x00200080,
SetContrast = 0x00230080,
GetSuitableY2rStandardCoefficient = 0x00360000,
};
}
void CAMService::reset() { bufferErrorInterruptEvents.fill(std::nullopt); }
// Helper struct for working with camera ports
class PortSelect {
u32 value;
public:
PortSelect(u32 val) : value(val) {}
bool isValid() const { return value < 4; }
bool isSinglePort() const {
// 1 corresponds to the first camera port and 2 corresponds to the second port
return value == 1 || value == 2;
}
bool isBothPorts() const {
// 3 corresponds to both ports
return value == 3;
}
// Returns the index of the camera port, assuming that it's only a single port
int getSingleIndex() const {
if (!isSinglePort()) [[unlikely]] {
Helpers::panic("Camera: getSingleIndex called for port with invalid value");
}
return value - 1;
}
std::vector<int> getPortIndices() const {
switch (value) {
case 1: return {0}; // Only port 1
case 2: return {1}; // Only port 2
case 3: return {0, 1}; // Both port 1 and port 2
default: return {}; // No ports or invalid ports
}
}
};
void CAMService::reset() {
for (auto& port : ports) {
port.reset();
}
}
void CAMService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case CAMCommands::DriverInitialize: driverInitialize(messagePointer); break;
case CAMCommands::DriverFinalize: driverFinalize(messagePointer); break;
case CAMCommands::GetBufferErrorInterruptEvent: getBufferErrorInterruptEvent(messagePointer); break;
case CAMCommands::GetMaxBytes: getMaxBytes(messagePointer); break;
case CAMCommands::GetMaxLines: getMaxLines(messagePointer); break;
case CAMCommands::GetSuitableY2rStandardCoefficient: getSuitableY2RCoefficients(messagePointer); break;
case CAMCommands::GetTransferBytes: getTransferBytes(messagePointer); break;
case CAMCommands::SetContrast: setContrast(messagePointer); break;
case CAMCommands::SetFrameRate: setFrameRate(messagePointer); break;
case CAMCommands::SetReceiving: setReceiving(messagePointer); break;
case CAMCommands::SetSize: setSize(messagePointer); break;
case CAMCommands::SetTransferLines: setTransferLines(messagePointer); break;
case CAMCommands::SetTrimming: setTrimming(messagePointer); break;
case CAMCommands::SetTrimmingParamsCenter: setTrimmingParamsCenter(messagePointer); break;
case CAMCommands::StartCapture: startCapture(messagePointer); break;
default:
Helpers::panic("Unimplemented CAM service requested. Command: %08X\n", command);
Helpers::warn("Unimplemented CAM service requested. Command: %08X\n", command);
mem.write32(messagePointer + 4, Result::Success);
break;
}
}
@ -36,6 +101,12 @@ void CAMService::driverInitialize(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
}
void CAMService::driverFinalize(u32 messagePointer) {
log("CAM::DriverFinalize\n");
mem.write32(messagePointer, IPC::responseHeader(0x3A, 1, 0));
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);
@ -46,13 +117,46 @@ void CAMService::setContrast(u32 messagePointer) {
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);
void CAMService::setTransferBytes(u32 messagePointer) {
const u32 portIndex = mem.read8(messagePointer + 4);
const u32 bytes = mem.read16(messagePointer + 8);
// ...why do these parameters even exist?
const u16 width = mem.read16(messagePointer + 12);
const u16 height = mem.read16(messagePointer + 16);
const PortSelect port(portIndex);
log("CAM::SetTransferLines (port = %d, lines = %d, width = %d, height = %d)\n", port, lines, width, height);
if (port.isValid()) {
for (int i : port.getPortIndices()) {
ports[i].transferBytes = bytes;
}
} else {
Helpers::warn("CAM::SetTransferBytes: Invalid port\n");
}
log("CAM::SetTransferBytes (port = %d, bytes = %d, width = %d, height = %d)\n", portIndex, bytes, width, height);
mem.write32(messagePointer, IPC::responseHeader(0x9, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void CAMService::setTransferLines(u32 messagePointer) {
const u32 portIndex = mem.read8(messagePointer + 4);
const u16 lines = mem.read16(messagePointer + 8);
const u16 width = mem.read16(messagePointer + 12);
const u16 height = mem.read16(messagePointer + 16);
const PortSelect port(portIndex);
if (port.isValid()) {
const u32 transferBytes = lines * width * 2;
for (int i : port.getPortIndices()) {
ports[i].transferBytes = transferBytes;
}
} else {
Helpers::warn("CAM::SetTransferLines: Invalid port\n");
}
log("CAM::SetTransferLines (port = %d, lines = %d, width = %d, height = %d)\n", portIndex, lines, width, height);
mem.write32(messagePointer, IPC::responseHeader(0x9, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
@ -68,6 +172,41 @@ void CAMService::setFrameRate(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
}
void CAMService::setSize(u32 messagePointer) {
const u32 cameraSelect = mem.read32(messagePointer + 4);
const u32 size = mem.read32(messagePointer + 8);
const u32 context = mem.read32(messagePointer + 12);
log("CAM::SetSize (camera select = %d, size = %d, context = %d)\n", cameraSelect, size, context);
mem.write32(messagePointer, IPC::responseHeader(0x1F, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void CAMService::setTrimming(u32 messagePointer) {
const u32 port = mem.read8(messagePointer + 4);
const bool trim = mem.read8(messagePointer + 8) != 0;
log("CAM::SetTrimming (port = %d, trimming = %s)\n", port, trim ? "enabled" : "disabled");
mem.write32(messagePointer, IPC::responseHeader(0x0E, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void CAMService::setTrimmingParamsCenter(u32 messagePointer) {
const u32 port = mem.read8(messagePointer + 4);
const s16 trimWidth = s16(mem.read16(messagePointer + 8));
const s16 trimHeight = s16(mem.read16(messagePointer + 12));
const s16 cameraWidth = s16(mem.read16(messagePointer + 16));
const s16 cameraHeight = s16(mem.read16(messagePointer + 20));
log("CAM::SetTrimmingParamsCenter (port = %d), trim size = (%d, %d), camera size = (%d, %d)\n", port, trimWidth, trimHeight, cameraWidth,
cameraHeight);
mem.write32(messagePointer, IPC::responseHeader(0x12, 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) {
@ -100,16 +239,62 @@ void CAMService::getMaxLines(u32 messagePointer) {
}
}
void CAMService::getMaxBytes(u32 messagePointer) {
const u16 width = mem.read16(messagePointer + 4);
const u16 height = mem.read16(messagePointer + 8);
log("CAM::GetMaxBytes (width = %d, height = %d)\n", width, height);
constexpr u32 MIN_TRANSFER_UNIT = 256;
constexpr u32 MAX_BUFFER_SIZE = 2560;
if (width * height * 2 % MIN_TRANSFER_UNIT != 0) {
Helpers::panic("CAM::GetMaxLines out of range");
} else {
u32 bytes = MAX_BUFFER_SIZE;
while (width * height * 2 % bytes != 0) {
bytes -= MIN_TRANSFER_UNIT;
}
mem.write32(messagePointer, IPC::responseHeader(0xA, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, bytes);
}
}
void CAMService::getSuitableY2RCoefficients(u32 messagePointer) {
log("CAM::GetSuitableY2RCoefficients\n");
mem.write32(messagePointer, IPC::responseHeader(0x36, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
// Y2R standard coefficient value
mem.write32(messagePointer + 8, 0);
}
void CAMService::getTransferBytes(u32 messagePointer) {
const u32 portIndex = mem.read8(messagePointer + 4);
const PortSelect port(portIndex);
log("CAM::GetTransferBytes (port = %d)\n", portIndex);
mem.write32(messagePointer, IPC::responseHeader(0x0C, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
if (port.isSinglePort()) {
mem.write32(messagePointer + 8, ports[port.getSingleIndex()].transferBytes);
} else {
// TODO: This should return the proper error code
Helpers::warn("CAM::GetTransferBytes: Invalid port index");
mem.write32(messagePointer + 8, 0);
}
}
void CAMService::getBufferErrorInterruptEvent(u32 messagePointer) {
const u32 port = mem.read32(messagePointer + 4);
log("CAM::GetBufferErrorInterruptEvent (port = %d)\n", port);
const u32 portIndex = mem.read8(messagePointer + 4);
const PortSelect port(portIndex);
log("CAM::GetBufferErrorInterruptEvent (port = %d)\n", portIndex);
mem.write32(messagePointer, IPC::responseHeader(0x6, 1, 2));
if (port >= portCount) {
Helpers::panic("CAM::GetBufferErrorInterruptEvent: Invalid port");
} else {
auto& event = bufferErrorInterruptEvents[port];
if (port.isSinglePort()) {
auto& event = ports[port.getSingleIndex()].bufferErrorInterruptEvent;
if (!event.has_value()) {
event = kernel.makeEvent(ResetType::OneShot);
}
@ -117,5 +302,55 @@ void CAMService::getBufferErrorInterruptEvent(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 0);
mem.write32(messagePointer + 12, event.value());
} else {
Helpers::panic("CAM::GetBufferErrorInterruptEvent: Invalid port");
}
}
}
void CAMService::setReceiving(u32 messagePointer) {
const u32 destination = mem.read32(messagePointer + 4);
const u32 portIndex = mem.read8(messagePointer + 8);
const u32 size = mem.read32(messagePointer + 12);
const u16 transferUnit = mem.read16(messagePointer + 16);
const Handle process = mem.read32(messagePointer + 24);
const PortSelect port(portIndex);
log("CAM::SetReceiving (port = %d)\n", portIndex);
mem.write32(messagePointer, IPC::responseHeader(0x7, 1, 2));
if (port.isSinglePort()) {
auto& event = ports[port.getSingleIndex()].receiveEvent;
if (!event.has_value()) {
event = kernel.makeEvent(ResetType::OneShot);
}
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 0);
mem.write32(messagePointer + 12, event.value());
} else {
Helpers::panic("CAM::SetReceiving: Invalid port");
}
}
void CAMService::startCapture(u32 messagePointer) {
const u32 portIndex = mem.read8(messagePointer + 4);
const PortSelect port(portIndex);
log("CAM::StartCapture (port = %d)\n", portIndex);
mem.write32(messagePointer, IPC::responseHeader(0x01, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
if (port.isValid()) {
for (int i : port.getPortIndices()) {
auto& event = ports[port.getSingleIndex()].receiveEvent;
// Until we properly implement cameras, immediately signal the receive event
if (event.has_value()) {
kernel.signalEvent(event.value());
}
}
} else {
Helpers::warn("CAM::StartCapture: Invalid port index");
}
}

View file

@ -27,6 +27,7 @@ namespace FSCommands {
CloseArchive = 0x080E0080,
FormatThisUserSaveData = 0x080F0180,
GetFreeBytes = 0x08120080,
GetSdmcArchiveResource = 0x08140000,
IsSdmcDetected = 0x08170000,
IsSdmcWritable = 0x08180000,
CardSlotIsInserted = 0x08210000,
@ -96,6 +97,7 @@ ArchiveBase* FSService::getArchiveFromID(u32 id, const FSPath& archivePath) {
case ArchiveID::SystemSaveData: return &systemSaveData;
case ArchiveID::SDMC: return &sdmc;
case ArchiveID::SDMCWriteOnly: return &sdmcWriteOnly;
case ArchiveID::SavedataAndNcch: return &ncch; // This can only access NCCH outside of FSPXI
default:
Helpers::panic("Unknown archive. ID: %d\n", id);
@ -179,6 +181,7 @@ void FSService::handleSyncRequest(u32 messagePointer) {
case FSCommands::GetFreeBytes: getFreeBytes(messagePointer); break;
case FSCommands::GetFormatInfo: getFormatInfo(messagePointer); break;
case FSCommands::GetPriority: getPriority(messagePointer); break;
case FSCommands::GetSdmcArchiveResource: getSdmcArchiveResource(messagePointer); break;
case FSCommands::GetThisSaveDataSecureValue: getThisSaveDataSecureValue(messagePointer); break;
case FSCommands::Initialize: initialize(messagePointer); break;
case FSCommands::InitializeWithSdkVersion: initializeWithSdkVersion(messagePointer); break;
@ -764,3 +767,22 @@ void FSService::renameFile(u32 messagePointer) {
const HorizonResult res = sourceArchive->archive->renameFile(sourcePath, destPath);
mem.write32(messagePointer + 4, static_cast<u32>(res));
}
void FSService::getSdmcArchiveResource(u32 messagePointer) {
log("FS::GetSdmcArchiveResource"); // 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(0x814, 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);
}

View file

@ -18,6 +18,7 @@ namespace ServiceCommands {
ReleaseRight = 0x00170000,
ImportDisplayCaptureInfo = 0x00180000,
SaveVramSysArea = 0x00190000,
RestoreVramSysArea = 0x001A0000,
SetInternalPriorities = 0x001E0080,
StoreDataCache = 0x001F0082
};
@ -51,6 +52,7 @@ void GPUService::handleSyncRequest(u32 messagePointer) {
case ServiceCommands::ImportDisplayCaptureInfo: importDisplayCaptureInfo(messagePointer); break;
case ServiceCommands::RegisterInterruptRelayQueue: registerInterruptRelayQueue(messagePointer); break;
case ServiceCommands::ReleaseRight: releaseRight(messagePointer); break;
case ServiceCommands::RestoreVramSysArea: restoreVramSysArea(messagePointer); break;
case ServiceCommands::SaveVramSysArea: saveVramSysArea(messagePointer); break;
case ServiceCommands::SetAxiConfigQoSMode: setAxiConfigQoSMode(messagePointer); break;
case ServiceCommands::SetBufferSwap: setBufferSwap(messagePointer); break;
@ -143,8 +145,7 @@ void GPUService::requestInterrupt(GPUInterrupt type) {
// Not emulating this causes Yoshi's Wooly World, Captain Toad, Metroid 2 et al to hang
if (type == GPUInterrupt::VBlank0 || type == GPUInterrupt::VBlank1) {
int screen = static_cast<u32>(type) - static_cast<u32>(GPUInterrupt::VBlank0); // 0 for top screen, 1 for bottom
// TODO: Offset depends on GSP thread being triggered
FramebufferUpdate* update = reinterpret_cast<FramebufferUpdate*>(&sharedMem[0x200 + screen * sizeof(FramebufferUpdate)]);
FramebufferUpdate* update = getFramebufferInfo(screen);
if (update->dirtyFlag & 1) {
setBufferSwapImpl(screen, update->framebufferInfo[update->index]);
@ -482,10 +483,50 @@ void GPUService::saveVramSysArea(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
}
void GPUService::restoreVramSysArea(u32 messagePointer) {
Helpers::warn("GSP::GPU::RestoreVramSysArea (stubbed)");
mem.write32(messagePointer, IPC::responseHeader(0x1A, 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);
if (sharedMem == nullptr) {
Helpers::warn("GSP::GPU::ImportDisplayCaptureInfo called without GSP module being properly initialized!");
return;
}
FramebufferUpdate* topScreen = getTopFramebufferInfo();
FramebufferUpdate* bottomScreen = getBottomFramebufferInfo();
// Capture the relevant data for both screens and return them to the caller
CaptureInfo topScreenCapture = {
.leftFramebuffer = topScreen->framebufferInfo[topScreen->index].leftFramebufferVaddr,
.rightFramebuffer = topScreen->framebufferInfo[topScreen->index].rightFramebufferVaddr,
.format = topScreen->framebufferInfo[topScreen->index].format,
.stride = topScreen->framebufferInfo[topScreen->index].stride,
};
CaptureInfo bottomScreenCapture = {
.leftFramebuffer = bottomScreen->framebufferInfo[bottomScreen->index].leftFramebufferVaddr,
.rightFramebuffer = bottomScreen->framebufferInfo[bottomScreen->index].rightFramebufferVaddr,
.format = bottomScreen->framebufferInfo[bottomScreen->index].format,
.stride = bottomScreen->framebufferInfo[bottomScreen->index].stride,
};
mem.write32(messagePointer + 8, topScreenCapture.leftFramebuffer);
mem.write32(messagePointer + 12, topScreenCapture.rightFramebuffer);
mem.write32(messagePointer + 16, topScreenCapture.format);
mem.write32(messagePointer + 20, topScreenCapture.stride);
mem.write32(messagePointer + 24, bottomScreenCapture.leftFramebuffer);
mem.write32(messagePointer + 28, bottomScreenCapture.rightFramebuffer);
mem.write32(messagePointer + 32, bottomScreenCapture.format);
mem.write32(messagePointer + 36, bottomScreenCapture.stride);
}

View file

@ -6,6 +6,7 @@ namespace PTMCommands {
GetAdapterState = 0x00050000,
GetBatteryLevel = 0x00070000,
GetBatteryChargeState = 0x00080000,
GetPedometerState = 0x00090000,
GetStepHistory = 0x000B00C2,
GetTotalStepCount = 0x000C0000,
GetStepHistoryAll = 0x000F0084,
@ -30,6 +31,7 @@ void PTMService::handleSyncRequest(u32 messagePointer, PTMService::Type type) {
case PTMCommands::GetAdapterState: getAdapterState(messagePointer); break;
case PTMCommands::GetBatteryChargeState: getBatteryChargeState(messagePointer); break;
case PTMCommands::GetBatteryLevel: getBatteryLevel(messagePointer); break;
case PTMCommands::GetPedometerState: getPedometerState(messagePointer); break;
case PTMCommands::GetStepHistory: getStepHistory(messagePointer); break;
case PTMCommands::GetStepHistoryAll: getStepHistoryAll(messagePointer); break;
case PTMCommands::GetTotalStepCount: getTotalStepCount(messagePointer); break;
@ -67,11 +69,20 @@ void PTMService::getBatteryChargeState(u32 messagePointer) {
// 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, IPC::responseHeader(0x8, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write8(messagePointer + 8, charging ? 1 : 0);
}
void PTMService::getPedometerState(u32 messagePointer) {
log("PTM::GetPedometerState");
constexpr bool countingSteps = true;
mem.write32(messagePointer, IPC::responseHeader(0x9, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write8(messagePointer + 8, countingSteps ? 1 : 0);
}
void PTMService::getBatteryLevel(u32 messagePointer) {
log("PTM::GetBatteryLevel");

View file

@ -18,6 +18,7 @@ namespace Y2RCommands {
SetSendingY = 0x00100102,
SetSendingU = 0x00110102,
SetSendingV = 0x00120102,
SetSendingYUV = 0x00130102,
SetReceiving = 0x00180102,
SetInputLineWidth = 0x001A0040,
GetInputLineWidth = 0x001B0000,
@ -82,6 +83,7 @@ void Y2RService::handleSyncRequest(u32 messagePointer) {
case Y2RCommands::SetSendingY: setSendingY(messagePointer); break;
case Y2RCommands::SetSendingU: setSendingU(messagePointer); break;
case Y2RCommands::SetSendingV: setSendingV(messagePointer); break;
case Y2RCommands::SetSendingYUV: setSendingYUV(messagePointer); break;
case Y2RCommands::SetSpacialDithering: setSpacialDithering(messagePointer); break;
case Y2RCommands::SetStandardCoeff: setStandardCoeff(messagePointer); break;
case Y2RCommands::SetTemporalDithering: setTemporalDithering(messagePointer); break;
@ -399,6 +401,14 @@ void Y2RService::setSendingV(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
}
void Y2RService::setSendingYUV(u32 messagePointer) {
log("Y2R::SetSendingYUV\n");
Helpers::warn("Unimplemented Y2R::SetSendingYUV");
mem.write32(messagePointer, IPC::responseHeader(0x13, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void Y2RService::setReceiving(u32 messagePointer) {
log("Y2R::SetReceiving\n");
Helpers::warn("Unimplemented Y2R::setReceiving");