Merge branch 'master' into timerz

This commit is contained in:
wheremyfoodat 2023-08-21 18:57:18 +03:00
commit 1d601e41ba
63 changed files with 871 additions and 262282 deletions

View file

@ -1,5 +1,6 @@
#include "config.hpp"
#include <cmath>
#include <fstream>
#include <string>
@ -57,6 +58,29 @@ void EmulatorConfig::load(const std::filesystem::path& path) {
shaderJitEnabled = toml::find_or<toml::boolean>(gpu, "EnableShaderJIT", true);
}
}
if (data.contains("Battery")) {
auto batteryResult = toml::expect<toml::value>(data.at("Battery"));
if (batteryResult.is_ok()) {
auto battery = batteryResult.unwrap();
chargerPlugged = toml::find_or<toml::boolean>(battery, "ChargerPlugged", true);
batteryPercentage = toml::find_or<toml::integer>(battery, "BatteryPercentage", 3);
// Clamp battery % to [0, 100] to make sure it's a valid value
batteryPercentage = std::clamp(batteryPercentage, 0, 100);
}
}
if (data.contains("SD")) {
auto sdResult = toml::expect<toml::value>(data.at("SD"));
if (sdResult.is_ok()) {
auto sd = sdResult.unwrap();
sdCardInserted = toml::find_or<toml::boolean>(sd, "UseVirtualSD", true);
sdWriteProtected = toml::find_or<toml::boolean>(sd, "WriteProtectVirtualSD", false);
}
}
}
void EmulatorConfig::save(const std::filesystem::path& path) {
@ -81,6 +105,12 @@ void EmulatorConfig::save(const std::filesystem::path& path) {
data["GPU"]["EnableShaderJIT"] = shaderJitEnabled;
data["GPU"]["Renderer"] = std::string(Renderer::typeToString(rendererType));
data["Battery"]["ChargerPlugged"] = chargerPlugged;
data["Battery"]["BatteryPercentage"] = batteryPercentage;
data["SD"]["UseVirtualSD"] = sdCardInserted;
data["SD"]["WriteProtectVirtualSD"] = sdWriteProtected;
std::ofstream file(path, std::ios::out);
file << data;
file.close();

View file

@ -20,9 +20,11 @@ HorizonResult ExtSaveDataArchive::createFile(const FSPath& path, u64 size) {
// Create a file of size "size" by creating an empty one, seeking to size - 1 and just writing a 0 there
IOFile file(p.string().c_str(), "wb");
if (file.seek(size - 1, SEEK_SET) && file.writeBytes("", 1).second == 1) {
file.close();
return Result::Success;
}
file.close();
return Result::FS::FileTooLarge;
}

View file

@ -19,14 +19,17 @@ HorizonResult SaveDataArchive::createFile(const FSPath& path, u64 size) {
// If the size is 0, leave the file empty and return success
if (size == 0) {
file.close();
return Result::Success;
}
// If it is not empty, seek to size - 1 and write a 0 to create a file of size "size"
else if (file.seek(size - 1, SEEK_SET) && file.writeBytes("", 1).second == 1) {
file.close();
return Result::Success;
}
file.close();
return Result::FS::FileTooLarge;
}

View file

@ -5,7 +5,8 @@
namespace PathType {
enum : u32 {
RomFS = 0,
ExeFS = 2
ExeFS = 2,
UpdateRomFS = 5,
};
};
@ -33,8 +34,8 @@ FileDescriptor SelfNCCHArchive::openFile(const FSPath& path, const FilePerms& pe
// Where to read the file from. (https://www.3dbrew.org/wiki/Filesystem_services#SelfNCCH_File_Path_Data_Format)
// We currently only know how to read from an NCCH's RomFS, ie type = 0
const u32 type = *(u32*)&path.binary[0]; // TODO: Get rid of UB here
if (type != PathType::RomFS && type != PathType::ExeFS) {
Helpers::panic("Read from NCCH's non-RomFS & non-exeFS section!");
if (type != PathType::RomFS && type != PathType::ExeFS && type != PathType::UpdateRomFS) {
Helpers::panic("Read from NCCH's non-RomFS & non-exeFS section! Path type: %d", type);
}
return NoFile; // No file descriptor needed for RomFS
@ -50,8 +51,8 @@ Rust::Result<ArchiveBase*, HorizonResult> SelfNCCHArchive::openArchive(const FSP
}
std::optional<u32> SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) {
const FSPath& path = file->path; // Path of the file
const u32 type = *(u32*)&path.binary[0]; // Type of the path
const FSPath& path = file->path; // Path of the file
const u32 type = *(u32*)&path.binary[0]; // Type of the path
if (type == PathType::RomFS && !hasRomFS()) {
Helpers::panic("Tried to read file from non-existent RomFS");
@ -98,8 +99,23 @@ std::optional<u32> SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32
break;
}
default:
Helpers::panic("Unimplemented file path type for SelfNCCH archive");
// 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");
}
fsInfo = cxi->romFS;
offset += 0x1000;
break;
}
default: Helpers::panic("Unimplemented file path type for SelfNCCH archive");
}
std::unique_ptr<u8[]> data(new u8[size]);

View file

@ -3,8 +3,8 @@
#include "kernel_types.hpp"
#include "cpu.hpp"
Kernel::Kernel(CPU& cpu, Memory& mem, GPU& gpu)
: cpu(cpu), regs(cpu.regs()), mem(mem), handleCounter(0), serviceManager(regs, mem, gpu, currentProcess, *this) {
Kernel::Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config)
: cpu(cpu), regs(cpu.regs()), mem(mem), handleCounter(0), serviceManager(regs, mem, gpu, currentProcess, *this, config) {
objects.reserve(512); // Make room for a few objects to avoid further memory allocs later
mutexHandles.reserve(8);
timerHandles.reserve(8);
@ -36,6 +36,8 @@ void Kernel::serviceSVC(u32 svc) {
case 0x0A: svcSleepThread(); break;
case 0x0B: getThreadPriority(); break;
case 0x0C: setThreadPriority(); break;
case 0x0F: getThreadIdealProcessor(); break;
case 0x11: getCurrentProcessorNumber(); break;
case 0x13: svcCreateMutex(); break;
case 0x14: svcReleaseMutex(); break;
case 0x15: svcCreateSemaphore(); break;
@ -161,7 +163,7 @@ void Kernel::reset() {
// Make main thread object. We do not have to set the entrypoint and SP for it as the ROM loader does.
// Main thread seems to have a priority of 0x30. TODO: This creates a dummy context for thread 0,
// which is thankfully not used. Maybe we should prevent this
mainThread = makeThread(0, VirtualAddrs::StackTop, 0x30, -2, 0, ThreadStatus::Running);
mainThread = makeThread(0, VirtualAddrs::StackTop, 0x30, ProcessorID::Default, 0, ThreadStatus::Running);
currentThreadIndex = 0;
setupIdleThread();

View file

@ -1,5 +1,4 @@
#include "kernel.hpp"
#include "services/shared_font.hpp"
namespace Operation {
enum : u32 {
@ -137,7 +136,7 @@ void Kernel::mapMemoryBlock() {
break;
case KernelHandles::FontSharedMemHandle:
std::memcpy(ptr, _shared_font_bin, _shared_font_len);
mem.copySharedFont(ptr);
break;
default: Helpers::panic("Mapping unknown shared memory block: %X", block);

View file

@ -109,7 +109,7 @@ void Kernel::sendSyncRequest() {
// If we're actually communicating with a port
const auto session = getObject(handle, KernelObjectType::Session);
if (session == nullptr) [[unlikely]] {
Helpers::panic("SendSyncRequest: Invalid handle");
Helpers::warn("SendSyncRequest: Invalid handle");
regs[0] = Result::Kernel::InvalidHandle;
return;
}
@ -127,4 +127,4 @@ void Kernel::sendSyncRequest() {
const auto portData = objects[portHandle].getData<Port>();
Helpers::panic("SendSyncRequest targetting port %s\n", portData->name);
}
}
}

View file

@ -89,6 +89,7 @@ s32 Kernel::getCurrentResourceValue(const KernelObject* limit, u32 resourceName)
u32 Kernel::getMaxForResource(const KernelObject* limit, u32 resourceName) {
switch (resourceName) {
case ResourceType::Commit: return appResourceLimits.maxCommit;
case ResourceType::Thread: return appResourceLimits.maxThreads;
default: Helpers::panic("Attempted to get the max of unknown kernel resource: %d\n", resourceName);
}
}
}

View file

@ -106,7 +106,7 @@ void Kernel::rescheduleThreads() {
}
// Internal OS function to spawn a thread
Handle Kernel::makeThread(u32 entrypoint, u32 initialSP, u32 priority, s32 id, u32 arg, ThreadStatus status) {
Handle Kernel::makeThread(u32 entrypoint, u32 initialSP, u32 priority, ProcessorID id, u32 arg, ThreadStatus status) {
int index; // Index of the created thread in the threads array
if (threadCount < appResourceLimits.maxThreads) [[likely]] { // If we have not yet created over too many threads
@ -397,8 +397,12 @@ void Kernel::createThread() {
return;
}
if (id < -2 || id > 3) {
Helpers::panic("Invalid processor ID in CreateThread");
}
regs[0] = Result::Success;
regs[1] = makeThread(entrypoint, initialSP, priority, id, arg, ThreadStatus::Ready);
regs[1] = makeThread(entrypoint, initialSP, priority, static_cast<ProcessorID>(id), arg, ThreadStatus::Ready);
requireReschedule();
}
@ -449,6 +453,15 @@ void Kernel::getThreadPriority() {
}
}
void Kernel::getThreadIdealProcessor() {
const Handle handle = regs[1]; // Thread handle
logSVC("GetThreadIdealProcessor (handle = %X)\n", handle);
// TODO: Not documented what this is or what it does. Citra doesn't implement it at all. Return AppCore as the ideal processor for now
regs[0] = Result::Success;
regs[1] = static_cast<u32>(ProcessorID::AppCore);
}
void Kernel::setThreadPriority() {
const Handle handle = regs[0];
const u32 priority = regs[1];
@ -476,6 +489,33 @@ void Kernel::setThreadPriority() {
requireReschedule();
}
void Kernel::getCurrentProcessorNumber() {
logSVC("GetCurrentProcessorNumber()\n");
const ProcessorID id = threads[currentThreadIndex].processorID;
s32 ret;
// Until we properly implement per-core schedulers, return whatever processor ID passed to svcCreateThread
switch (id) {
// TODO: This is picked from exheader
case ProcessorID::Default:
ret = static_cast<s32>(ProcessorID::AppCore);
break;
case ProcessorID::AllCPUs:
ret = static_cast<s32>(ProcessorID::AppCore);
Helpers::warn("GetCurrentProcessorNumber on thread created to run on all CPUs...?\n");
break;
default: ret = static_cast<s32>(id); break;
}
if (ret != static_cast<s32>(ProcessorID::AppCore)) {
Helpers::warn("GetCurrentProcessorNumber: Thread not running on appcore\n");
}
regs[0] = static_cast<u32>(ret);
}
void Kernel::exitThread() {
logSVC("ExitThread\n");
@ -630,4 +670,4 @@ bool Kernel::shouldWaitOnObject(KernelObject* object) {
Helpers::panic("Not sure whether to wait on object (type: %s)", object->getTypeName());
return true;
}
}
}

View file

@ -131,6 +131,13 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn
return false;
}
if (!aesEngine.haveGenerator()) {
Helpers::panic(
"Loading an encrypted ROM but your AES keys don't seem to provide the \"generator\" constant which Panda3DS requires for decryption\n"
"Please add it to your aes_keys.txt in a line like \"generator=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\" where the Xs are replaced with the actual generator constant value"
);
}
if (!gotCryptoKeys) {
Helpers::panic("ROM is encrypted but it seems we couldn't get either the primary or the secondary key");
return false;
@ -279,6 +286,7 @@ bool NCCH::parseSMDH(const std::vector<u8>& smdh) {
} else if (taiwan) {
region = Regions::Taiwan;
}
return true;
}
std::pair<bool, Crypto::AESKey> NCCH::getPrimaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY) {

View file

@ -2,14 +2,18 @@
#include <cassert>
#include <chrono> // For time since epoch
#include <cmrc/cmrc.hpp>
#include <ctime>
#include "config_mem.hpp"
#include "resource_limits.hpp"
#include "services/ptm.hpp"
CMRC_DECLARE(ConsoleFonts);
using namespace KernelMemoryTypes;
Memory::Memory(u64& cpuTicks) : cpuTicks(cpuTicks) {
Memory::Memory(u64& cpuTicks, const EmulatorConfig& config) : cpuTicks(cpuTicks), config(config) {
fcram = new uint8_t[FCRAM_SIZE]();
dspRam = new uint8_t[DSP_RAM_SIZE]();
@ -45,6 +49,12 @@ void Memory::reset() {
// Initialize shared memory blocks and reserve memory for them
for (auto& e : sharedMemBlocks) {
if (e.handle == KernelHandles::FontSharedMemHandle) {
// Read font size from the cmrc filesystem the font is stored in
auto fonts = cmrc::ConsoleFonts::get_filesystem();
e.size = fonts.open("CitraSharedFontUSRelocated.bin").size();
}
e.mapped = false;
e.paddr = allocateSysMemory(e.size);
}
@ -85,7 +95,18 @@ u8 Memory::read8(u32 vaddr) {
return *(u8*)(pointer + offset);
} else {
switch (vaddr) {
case ConfigMem::BatteryState: return getBatteryState(true, true, BatteryLevel::FourBars);
case ConfigMem::BatteryState: {
// Set by the PTM module
// Charger plugged: Shows whether the charger is plugged
// Charging: Shows whether the charger is plugged and the console is actually charging, ie the battery is not full
// BatteryLevel: A battery level calculated via PTM::GetBatteryLevel
// These are all assembled into a bitfield and returned via config memory
const bool chargerPlugged = config.chargerPlugged;
const bool charging = config.chargerPlugged && (config.batteryPercentage < 100);
const auto batteryLevel = static_cast<BatteryLevel>(PTMService::batteryPercentToLevel(config.batteryPercentage));
return getBatteryState(chargerPlugged, charging, batteryLevel);
}
case ConfigMem::EnvInfo: return envInfo;
case ConfigMem::HardwareType: return ConfigMem::HardwareCodes::Product;
case ConfigMem::KernelVersionMinor: return u8(kernelVersion & 0xff);
@ -152,8 +173,14 @@ u32 Memory::read32(u32 vaddr) {
default:
if (vaddr >= VirtualAddrs::VramStart && vaddr < VirtualAddrs::VramStart + VirtualAddrs::VramSize) {
Helpers::warn("VRAM read!\n");
return 0;
static int shutUpCounter = 0;
if (shutUpCounter < 5) { // Stop spamming about VRAM reads after the first 5
shutUpCounter++;
Helpers::warn("VRAM read!\n");
}
// TODO: Properly handle framebuffer readbacks and the like
return *(u32*)&vram[vaddr - VirtualAddrs::VramStart];
}
Helpers::panic("Unimplemented 32-bit read, addr: %08X", vaddr);
@ -481,3 +508,9 @@ Regions Memory::getConsoleRegion() {
// For now we pick one based on the ROM header
return region;
}
void Memory::copySharedFont(u8* pointer) {
auto fonts = cmrc::ConsoleFonts::get_filesystem();
auto font = fonts.open("CitraSharedFontUSRelocated.bin");
std::memcpy(pointer, font.begin(), font.size());
}

View file

@ -118,6 +118,7 @@ void RendererGL::initGraphicsContext(SDL_Window* window) {
dummyVBO.create();
dummyVAO.create();
gl.disableScissor();
// Create texture and framebuffer for the 3DS screen
const u32 screenTextureWidth = 400; // Top screen is 400 pixels wide, bottom is 320
@ -126,6 +127,24 @@ void RendererGL::initGraphicsContext(SDL_Window* window) {
glGenTextures(1, &lightLUTTextureArray);
auto prevTexture = OpenGL::getTex2D();
// Create a plain black texture for when a game reads an invalid texture. It is common for games to configure the PICA to read texture info from NULL.
// Some games that do this are Pokemon X, Cars 2, Tomodachi Life, and more. We bind the texture to an FBO, clear it, and free the FBO
blankTexture.create(8, 8, GL_RGBA8);
blankTexture.bind();
blankTexture.setMinFilter(OpenGL::Linear);
blankTexture.setMagFilter(OpenGL::Linear);
OpenGL::Framebuffer dummyFBO;
dummyFBO.createWithDrawTexture(blankTexture); // Create FBO and bind our texture to it
dummyFBO.bind(OpenGL::DrawFramebuffer);
// Clear the texture and then delete FBO
OpenGL::setViewport(8, 8);
gl.setClearColour(0.0, 0.0, 0.0, 1.0);
OpenGL::clearColor();
dummyFBO.free();
screenTexture.create(screenTextureWidth, screenTextureHeight, GL_RGBA8);
screenTexture.bind();
screenTexture.setMinFilter(OpenGL::Linear);
@ -143,7 +162,6 @@ void RendererGL::initGraphicsContext(SDL_Window* window) {
GLint oldViewport[4];
glGetIntegerv(GL_VIEWPORT, oldViewport);
OpenGL::setViewport(screenTextureWidth, screenTextureHeight);
gl.setClearColour(0.0, 0.0, 0.0, 1.0);
OpenGL::clearColor();
OpenGL::setViewport(oldViewport[0], oldViewport[1], oldViewport[2], oldViewport[3]);
@ -321,9 +339,17 @@ void RendererGL::bindTexturesToSlots() {
u32 format = regs[ioBase + (i == 0 ? 13 : 5)] & 0xF;
glActiveTexture(GL_TEXTURE0 + i);
Texture targetTex(addr, static_cast<PICA::TextureFmt>(format), width, height, config);
OpenGL::Texture tex = getTexture(targetTex);
tex.bind();
if (addr != 0) [[likely]] {
Texture targetTex(addr, static_cast<PICA::TextureFmt>(format), width, height, config);
OpenGL::Texture tex = getTexture(targetTex);
tex.bind();
} else {
// Mapping a texture from NULL. PICA seems to read the last sampled colour, but for now we will display a black texture instead since it is far easier.
// Games that do this don't really care what it does, they just expect the PICA to not crash, since it doesn't have a PU/MMU and can do all sorts of
// Weird invalid memory accesses without crashing
blankTexture.bind();
}
}
glActiveTexture(GL_TEXTURE0 + 3);

View file

@ -3,7 +3,11 @@
namespace ACCommands {
enum : u32 {
CreateDefaultConfig = 0x00010000,
CancelConnectAsync = 0x00070002,
CloseAsync = 0x00080004,
GetLastErrorCode = 0x000A0000,
RegisterDisconnectEvent = 0x00300004,
SetClientVersion = 0x00400042,
};
}
@ -13,12 +17,40 @@ void ACService::reset() {}
void ACService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case ACCommands::CancelConnectAsync: cancelConnectAsync(messagePointer); break;
case ACCommands::CloseAsync: closeAsync(messagePointer); break;
case ACCommands::CreateDefaultConfig: createDefaultConfig(messagePointer); break;
case ACCommands::GetLastErrorCode: getLastErrorCode(messagePointer); break;
case ACCommands::RegisterDisconnectEvent: registerDisconnectEvent(messagePointer); break;
case ACCommands::SetClientVersion: setClientVersion(messagePointer); break;
default: Helpers::panic("AC service requested. Command: %08X\n", command);
}
}
void ACService::cancelConnectAsync(u32 messagePointer) {
log("AC::CancelCommandAsync (stubbed)\n");
// TODO: Verify if this response header is correct on hardware
mem.write32(messagePointer, IPC::responseHeader(0x7, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void ACService::closeAsync(u32 messagePointer) {
log("AC::CloseAsync (stubbed)\n");
// TODO: Verify if this response header is correct on hardware
mem.write32(messagePointer, IPC::responseHeader(0x8, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void ACService::createDefaultConfig(u32 messagePointer) {
log("AC::CreateDefaultConfig (stubbed)\n");
mem.write32(messagePointer, IPC::responseHeader(0x1, 1, 2));
mem.write32(messagePointer + 4, Result::Success);
// TODO: Verify response buffer on hardware
}
void ACService::getLastErrorCode(u32 messagePointer) {
log("AC::GetLastErrorCode (stubbed)\n");
@ -33,4 +65,15 @@ void ACService::setClientVersion(u32 messagePointer) {
mem.write32(messagePointer, IPC::responseHeader(0x40, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void ACService::registerDisconnectEvent(u32 messagePointer) {
log("AC::RegisterDisconnectEvent (stubbed)\n");
const u32 pidHeader = mem.read32(messagePointer + 4);
const u32 copyHandleHeader = mem.read32(messagePointer + 12);
// Event signaled when disconnecting from AC
const Handle eventHandle = mem.read32(messagePointer + 16);
mem.write32(messagePointer, IPC::responseHeader(0x30, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}

View file

@ -4,7 +4,8 @@
namespace AMCommands {
enum : u32 {
GetDLCTitleInfo = 0x10050084,
ListTitleInfo = 0x10070102
ListTitleInfo = 0x10070102,
GetPatchTitleInfo = 0x100D0084,
};
}
@ -13,6 +14,7 @@ void AMService::reset() {}
void AMService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case AMCommands::GetPatchTitleInfo: getPatchTitleInfo(messagePointer); break;
case AMCommands::GetDLCTitleInfo: getDLCTitleInfo(messagePointer); break;
case AMCommands::ListTitleInfo: listTitleInfo(messagePointer); break;
default: Helpers::panic("AM service requested. Command: %08X\n", command);
@ -42,7 +44,16 @@ void AMService::listTitleInfo(u32 messagePointer) {
void AMService::getDLCTitleInfo(u32 messagePointer) {
log("AM::GetDLCTitleInfo (stubbed to fail)\n");
Helpers::warn("Unimplemented AM::GetDLCTitleInfo. Will need to be implemented to support DLC\n");
mem.write32(messagePointer, IPC::responseHeader(0x1005, 1, 4));
mem.write32(messagePointer + 4, -1);
mem.write32(messagePointer + 4, Result::FailurePlaceholder);
}
void AMService::getPatchTitleInfo(u32 messagePointer) {
log("AM::GetPatchTitleInfo (stubbed to fail)\n");
Helpers::warn("Unimplemented AM::GetDLCTitleInfo. Will need to be implemented to support updates\n");
mem.write32(messagePointer, IPC::responseHeader(0x100D, 1, 4));
mem.write32(messagePointer + 4, Result::FailurePlaceholder);
}

View file

@ -6,14 +6,22 @@ namespace BOSSCommands {
InitializeSession = 0x00010082,
UnregisterStorage = 0x00030000,
GetTaskStorageInfo = 0x00040000,
RegisterNewArrivalEvent = 0x00080002,
GetOptoutFlag = 0x000A0000,
RegisterTask = 0x000B00C2,
UnregisterTask = 0x000C0082,
GetTaskIdList = 0x000E0000,
GetNsDataIdList = 0x00100102,
GetNsDataIdList1 = 0x00110102,
SendProperty = 0x00140082,
ReceiveProperty = 0x00160082,
GetTaskServiceStatus = 0x001B0042,
StartTask = 0x001C0042,
CancelTask = 0x001E0042,
GetTaskState = 0x00200082,
GetTaskStatus = 0x002300C2,
GetTaskInfo = 0x00250082,
GetErrorCode = 0x002E0040,
RegisterStorageEntry = 0x002F0140,
GetStorageEntryInfo = 0x00300000,
};
@ -27,16 +35,25 @@ void BOSSService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case BOSSCommands::CancelTask: cancelTask(messagePointer); break;
case BOSSCommands::GetNsDataIdList: getNsDataIdList(messagePointer); break;
case BOSSCommands::GetErrorCode: getErrorCode(messagePointer); break;
case BOSSCommands::GetNsDataIdList:
case BOSSCommands::GetNsDataIdList1:
getNsDataIdList(messagePointer, command); break;
case BOSSCommands::GetOptoutFlag: getOptoutFlag(messagePointer); break;
case BOSSCommands::GetStorageEntryInfo: getStorageEntryInfo(messagePointer); break;
case BOSSCommands::GetTaskIdList: getTaskIdList(messagePointer); break;
case BOSSCommands::GetTaskInfo: getTaskInfo(messagePointer); break;
case BOSSCommands::GetTaskServiceStatus: getTaskServiceStatus(messagePointer); break;
case BOSSCommands::GetTaskState: getTaskState(messagePointer); break;
case BOSSCommands::GetTaskStatus: getTaskStatus(messagePointer); break;
case BOSSCommands::GetTaskStorageInfo: getTaskStorageInfo(messagePointer); break;
case BOSSCommands::InitializeSession: initializeSession(messagePointer); break;
case BOSSCommands::ReceiveProperty: receiveProperty(messagePointer); break;
case BOSSCommands::RegisterNewArrivalEvent: registerNewArrivalEvent(messagePointer); break;
case BOSSCommands::RegisterStorageEntry: registerStorageEntry(messagePointer); break;
case BOSSCommands::RegisterTask: registerTask(messagePointer); break;
case BOSSCommands::SendProperty: sendProperty(messagePointer); break;
case BOSSCommands::StartTask: startTask(messagePointer); break;
case BOSSCommands::UnregisterStorage: unregisterStorage(messagePointer); break;
case BOSSCommands::UnregisterTask: unregisterTask(messagePointer); break;
default: Helpers::panic("BOSS service requested. Command: %08X\n", command);
@ -68,6 +85,28 @@ void BOSSService::getTaskState(u32 messagePointer) {
mem.write8(messagePointer + 16, 0); // TODO: Figure out what this should be
}
void BOSSService::getTaskStatus(u32 messagePointer) {
// TODO: 3DBrew does not mention what the parameters are, or what the return values are.
log("BOSS::GetTaskStatus (Stubbed)\n");
// Response values stubbed based on Citra
mem.write32(messagePointer, IPC::responseHeader(0x23, 2, 2));
mem.write32(messagePointer + 4, Result::Success);
mem.write8(messagePointer + 8, 0);
// TODO: Citra pushes a buffer here?
}
void BOSSService::getTaskServiceStatus(u32 messagePointer) {
// TODO: 3DBrew does not mention what the parameters are, or what the return values are... again
log("BOSS::GetTaskServiceStatus (Stubbed)\n");
// Response values stubbed based on Citra
mem.write32(messagePointer, IPC::responseHeader(0x1B, 2, 2));
mem.write32(messagePointer + 4, Result::Success);
mem.write8(messagePointer + 8, 0);
// TODO: Citra pushes a buffer here too?
}
void BOSSService::getTaskStorageInfo(u32 messagePointer) {
log("BOSS::GetTaskStorageInfo (stubbed)\n");
mem.write32(messagePointer, IPC::responseHeader(0x4, 2, 0));
@ -90,6 +129,13 @@ void BOSSService::getTaskInfo(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
}
void BOSSService::getErrorCode(u32 messagePointer) {
log("BOSS::GetErrorCode (stubbed)\n");
mem.write32(messagePointer, IPC::responseHeader(0x2E, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, Result::Success); // No error code
}
void BOSSService::getStorageEntryInfo(u32 messagePointer) {
log("BOSS::GetStorageEntryInfo (undocumented)\n");
mem.write32(messagePointer, IPC::responseHeader(0x30, 3, 0));
@ -98,33 +144,76 @@ void BOSSService::getStorageEntryInfo(u32 messagePointer) {
mem.write16(messagePointer + 12, 0); // s16, unknown meaning
}
void BOSSService::sendProperty(u32 messagePointer) {
const u32 id = mem.read32(messagePointer + 4);
const u32 size = mem.read32(messagePointer + 8);
const u32 ptr = mem.read32(messagePointer + 16);
log("BOSS::SendProperty (id = %d, size = %08X, ptr = %08X) (stubbed)\n", id, size, ptr);
mem.write32(messagePointer, IPC::responseHeader(0x14, 1, 2));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 0); // Read size
// TODO: Should this do anything else?
}
void BOSSService::receiveProperty(u32 messagePointer) {
const u32 id = mem.read32(messagePointer + 4);
const u32 size = mem.read32(messagePointer + 8);
const u32 ptr = mem.read32(messagePointer + 16);
log("BOSS::ReceiveProperty(stubbed) (id = %d, size = %08X, ptr = %08X)\n", id, size, ptr);
log("BOSS::ReceiveProperty (id = %d, size = %08X, ptr = %08X) (stubbed)\n", id, size, ptr);
mem.write32(messagePointer, IPC::responseHeader(0x16, 2, 2));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 0); // Read size
}
// This seems to accept a KEvent as a parameter and register it for something Spotpass related
// I need to update the 3DBrew page when it's known what it does properly
void BOSSService::registerNewArrivalEvent(u32 messagePointer) {
const Handle eventHandle = mem.read32(messagePointer + 4); // Kernel event handle to register
log("BOSS::RegisterNewArrivalEvent (handle = %X)\n", eventHandle);
mem.write32(messagePointer, IPC::responseHeader(0x8, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void BOSSService::startTask(u32 messagePointer) {
log("BOSS::StartTask (stubbed)\n");
const u32 bufferSize = mem.read32(messagePointer + 4);
const u32 descriptor = mem.read32(messagePointer + 8);
const u32 bufferData = mem.read32(messagePointer + 12);
mem.write32(messagePointer, IPC::responseHeader(0x1C, 1, 2));
mem.write32(messagePointer + 4, Result::Success);
}
void BOSSService::cancelTask(u32 messagePointer) {
log("BOSS::CancelTask (stubbed)\n");
mem.write32(messagePointer, IPC::responseHeader(0x1E, 1, 2));
mem.write32(messagePointer + 4, Result::Success);
}
void BOSSService::registerTask(u32 messagePointer) {
log("BOSS::RegisterTask (stubbed)\n");
const u32 bufferSize = mem.read32(messagePointer + 4);
const u32 dataPointr = mem.read32(messagePointer + 20);
mem.write32(messagePointer, IPC::responseHeader(0x0B, 1, 2));
mem.write32(messagePointer + 4, Result::Success);
}
void BOSSService::unregisterTask(u32 messagePointer) {
log("BOSS::UnregisterTask (stubbed)\n");
mem.write32(messagePointer, IPC::responseHeader(0x0C, 1, 2));
mem.write32(messagePointer + 4, Result::Success);
}
void BOSSService::getNsDataIdList(u32 messagePointer) {
// There's multiple aliases for this command. commandWord is the first word in the IPC buffer with the command word, needed for the response header
void BOSSService::getNsDataIdList(u32 messagePointer, u32 commandWord) {
log("BOSS::GetNsDataIdList (stubbed)\n");
mem.write32(messagePointer, IPC::responseHeader(0x10, 3, 2));
mem.write32(messagePointer, IPC::responseHeader(commandWord >> 16, 3, 2));
mem.write32(messagePointer + 4, Result::Success);
mem.write16(messagePointer + 8, 0); // u16: Actual number of output entries.
mem.write16(messagePointer + 12, 0); // u16: Last word-index copied to output in the internal NsDataId list.

View file

@ -1,19 +1,22 @@
#include "services/cam.hpp"
#include "ipc.hpp"
#include "kernel.hpp"
namespace CAMCommands {
enum : u32 {
GetBufferErrorInterruptEvent = 0x00060040,
DriverInitialize = 0x00390000,
GetMaxLines = 0x000A0080
GetMaxLines = 0x000A0080,
};
}
void CAMService::reset() {}
void CAMService::reset() { bufferErrorInterruptEvents.fill(std::nullopt); }
void CAMService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
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);
}
@ -55,4 +58,24 @@ void CAMService::getMaxLines(u32 messagePointer) {
mem.write32(messagePointer + 4, result);
mem.write16(messagePointer + 8, lines);
}
}
void CAMService::getBufferErrorInterruptEvent(u32 messagePointer) {
const u32 port = mem.read32(messagePointer + 4);
log("CAM::GetBufferErrorInterruptEvent (port = %d)\n", port);
mem.write32(messagePointer, IPC::responseHeader(0x6, 1, 2));
if (port >= portCount) {
Helpers::panic("CAM::GetBufferErrorInterruptEvent: Invalid port");
} else {
auto& event = bufferErrorInterruptEvents[port];
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());
}
}

View file

@ -1,21 +1,22 @@
#include "services/cecd.hpp"
#include "ipc.hpp"
#include "kernel.hpp"
namespace CECDCommands {
enum : u32 {
GetInfoEventHandle = 0x000F0000
GetInfoEventHandle = 0x000F0000,
OpenAndRead = 0x00120104,
};
}
void CECDService::reset() {
infoEvent = std::nullopt;
}
void CECDService::reset() { infoEvent = std::nullopt; }
void CECDService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case CECDCommands::GetInfoEventHandle: getInfoEventHandle(messagePointer); break;
case CECDCommands::OpenAndRead: openAndRead(messagePointer); break;
default:
Helpers::panicDev("CECD service requested. Command: %08X\n", command);
mem.write32(messagePointer + 4, Result::Success);
@ -34,4 +35,17 @@ void CECDService::getInfoEventHandle(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
// TODO: Translation descriptor here?
mem.write32(messagePointer + 12, infoEvent.value());
}
void CECDService::openAndRead(u32 messagePointer) {
const u32 bufferSize = mem.read32(messagePointer + 4);
const u32 programID = mem.read32(messagePointer + 8);
const u32 pathType = mem.read32(messagePointer + 12);
const u32 bufferAddress = mem.read32(messagePointer + 32);
log("CECD::OpenAndRead (size = %08X, address = %08X, path type = %d)\n", bufferSize, bufferAddress, pathType);
// TODO: We should implement this properly the time comes
mem.write32(messagePointer, IPC::responseHeader(0x12, 2, 2));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 0); // Bytes read
}

Binary file not shown.

View file

@ -1,23 +1,28 @@
#include <string>
#include "services/frd.hpp"
#include "services/region_codes.hpp"
#include <string>
#include "ipc.hpp"
#include "services/region_codes.hpp"
namespace FRDCommands {
enum : u32 {
HasLoggedIn = 0x00010000,
AttachToEventNotification = 0x00200002,
SetNotificationMask = 0x00210040,
SetClientSdkVersion = 0x00320042,
Logout = 0x00040000,
GetMyFriendKey = 0x00050000,
GetMyProfile = 0x00070000,
GetMyPresence = 0x00080000,
GetMyScreenName = 0x00090000,
GetMyMii = 0x000A0000,
GetFriendKeyList = 0x00110080
GetFriendKeyList = 0x00110080,
UpdateGameModeDescription = 0x001D0002,
};
}
void FRDService::reset() {}
void FRDService::reset() { loggedIn = false; }
void FRDService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
@ -29,8 +34,11 @@ void FRDService::handleSyncRequest(u32 messagePointer) {
case FRDCommands::GetMyPresence: getMyPresence(messagePointer); break;
case FRDCommands::GetMyProfile: getMyProfile(messagePointer); break;
case FRDCommands::GetMyScreenName: getMyScreenName(messagePointer); break;
case FRDCommands::HasLoggedIn: hasLoggedIn(messagePointer); break;
case FRDCommands::Logout: logout(messagePointer); break;
case FRDCommands::SetClientSdkVersion: setClientSDKVersion(messagePointer); break;
case FRDCommands::SetNotificationMask: setNotificationMask(messagePointer); break;
case FRDCommands::UpdateGameModeDescription: updateGameModeDescription(messagePointer); break;
default: Helpers::panic("FRD service requested. Command: %08X\n", command);
}
}
@ -40,6 +48,14 @@ void FRDService::attachToEventNotification(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
}
// This is supposed to post stuff on your user profile so uhh can't really emulate it
void FRDService::updateGameModeDescription(u32 messagePointer) {
log("FRD::UpdateGameModeDescription\n");
mem.write32(messagePointer, IPC::responseHeader(0x1D, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void FRDService::getMyFriendKey(u32 messagePointer) {
log("FRD::GetMyFriendKey\n");
@ -134,4 +150,20 @@ void FRDService::getMyMii(u32 messagePointer) {
// TODO: How is the mii data even returned?
mem.write32(messagePointer, IPC::responseHeader(0xA, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void FRDService::hasLoggedIn(u32 messagePointer) {
log("FRD::HasLoggedIn\n");
mem.write32(messagePointer, IPC::responseHeader(0x1, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write8(messagePointer + 8, loggedIn ? 1 : 0);
}
void FRDService::logout(u32 messagePointer) {
log("FRD::Logout\n");
loggedIn = false;
mem.write32(messagePointer, IPC::responseHeader(0x4, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}

View file

@ -27,6 +27,8 @@ namespace FSCommands {
IsSdmcWritable = 0x08180000,
GetFormatInfo = 0x084500C2,
FormatSaveData = 0x084C0242,
CreateExtSaveData = 0x08510242,
DeleteExtSaveData = 0x08520100,
InitializeWithSdkVersion = 0x08610042,
SetPriority = 0x08620040,
GetPriority = 0x08630000
@ -144,9 +146,11 @@ void FSService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case FSCommands::CreateDirectory: createDirectory(messagePointer); break;
case FSCommands::CreateExtSaveData: createExtSaveData(messagePointer); break;
case FSCommands::CreateFile: createFile(messagePointer); break;
case FSCommands::ControlArchive: controlArchive(messagePointer); break;
case FSCommands::CloseArchive: closeArchive(messagePointer); break;
case FSCommands::DeleteExtSaveData: deleteExtSaveData(messagePointer); break;
case FSCommands::DeleteFile: deleteFile(messagePointer); break;
case FSCommands::FormatSaveData: formatSaveData(messagePointer); break;
case FSCommands::FormatThisUserSaveData: formatThisUserSaveData(messagePointer); break;
@ -455,6 +459,40 @@ void FSService::formatSaveData(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
}
void FSService::deleteExtSaveData(u32 messagePointer) {
Helpers::warn("Stubbed call to FS::DeleteExtSaveData!");
// First 4 words of parameters are the ExtSaveData info
// https://www.3dbrew.org/wiki/Filesystem_services#ExtSaveDataInfo
const u8 mediaType = mem.read8(messagePointer + 4);
const u64 saveID = mem.read64(messagePointer + 8);
log("FS::DeleteExtSaveData (media type = %d, saveID = %llx) (stubbed)\n", mediaType, saveID);
mem.write32(messagePointer, IPC::responseHeader(0x0852, 1, 0));
// TODO: We can't properly implement this yet until we properly support title/save IDs. We will stub this and insert a warning for now. Required for Planet Robobot
// When we properly implement it, it will just be a recursive directory deletion
mem.write32(messagePointer + 4, Result::Success);
}
void FSService::createExtSaveData(u32 messagePointer) {
Helpers::warn("Stubbed call to FS::CreateExtSaveData!");
// First 4 words of parameters are the ExtSaveData info
// https://www.3dbrew.org/wiki/Filesystem_services#ExtSaveDataInfo
// This creates the ExtSaveData with the specified saveid in the specified media type. It stores the SMDH as "icon" in the root of the created directory.
const u8 mediaType = mem.read8(messagePointer + 4);
const u64 saveID = mem.read64(messagePointer + 8);
const u32 numOfDirectories = mem.read32(messagePointer + 20);
const u32 numOfFiles = mem.read32(messagePointer + 24);
const u64 sizeLimit = mem.read64(messagePointer + 28);
const u32 smdhSize = mem.read32(messagePointer + 36);
const u32 smdhPointer = mem.read32(messagePointer + 44);
log("FS::CreateExtSaveData (stubbed)\n");
mem.write32(messagePointer, IPC::responseHeader(0x0851, 1, 0));
// TODO: Similar to DeleteExtSaveData, we need to refactor how our ExtSaveData stuff works before properly implementing this
mem.write32(messagePointer + 4, Result::Success);
}
void FSService::formatThisUserSaveData(u32 messagePointer) {
log("FS::FormatThisUserSaveData\n");

View file

@ -11,7 +11,8 @@ namespace HIDCommands {
EnableGyroscopeLow = 0x00130000,
DisableGyroscopeLow = 0x00140000,
GetGyroscopeLowRawToDpsCoefficient = 0x00150000,
GetGyroscopeLowCalibrateParam = 0x00160000
GetGyroscopeLowCalibrateParam = 0x00160000,
GetSoundVolume = 0x00170000,
};
}
@ -46,6 +47,7 @@ void HIDService::handleSyncRequest(u32 messagePointer) {
case HIDCommands::GetGyroscopeLowCalibrateParam: getGyroscopeLowCalibrateParam(messagePointer); break;
case HIDCommands::GetGyroscopeLowRawToDpsCoefficient: getGyroscopeCoefficient(messagePointer); break;
case HIDCommands::GetIPCHandles: getIPCHandles(messagePointer); break;
case HIDCommands::GetSoundVolume: getSoundVolume(messagePointer); break;
default: Helpers::panic("HID service requested. Command: %08X\n", command);
}
}
@ -107,6 +109,18 @@ void HIDService::getGyroscopeCoefficient(u32 messagePointer) {
mem.write32(messagePointer + 8, Helpers::bit_cast<u32, float>(gyroscopeCoeff));
}
// The volume here is in the range [0, 0x3F]
// It is read directly from I2C Device 3 register 0x09
// Since we currently do not have audio, set the volume a bit below max (0x30)
void HIDService::getSoundVolume(u32 messagePointer) {
log("HID::GetSoundVolume\n");
constexpr u8 volume = 0x30;
mem.write32(messagePointer, IPC::responseHeader(0x17, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write8(messagePointer + 8, volume);
}
void HIDService::getIPCHandles(u32 messagePointer) {
log("HID::GetIPCHandles\n");

View file

@ -0,0 +1,27 @@
#include "ipc.hpp"
#include "result/result.hpp"
#include "services/mcu/mcu_hwc.hpp"
namespace MCU::HWCCommands {
enum : u32 {
GetBatteryLevel = 0x00050000,
};
}
void MCU::HWCService::reset() {}
void MCU::HWCService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case HWCCommands::GetBatteryLevel: getBatteryLevel(messagePointer); break;
default: Helpers::panic("MCU::HWC service requested. Command: %08X\n", command);
}
}
void MCU::HWCService::getBatteryLevel(u32 messagePointer) {
log("MCU::HWC::GetBatteryLevel\n");
mem.write32(messagePointer, IPC::responseHeader(0x5, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write8(messagePointer + 8, config.batteryPercentage);
}

View file

@ -5,6 +5,7 @@ namespace MICCommands {
enum : u32 {
MapSharedMem = 0x00010042,
StartSampling = 0x00030140,
StopSampling = 0x00050000,
SetGain = 0x00080040,
GetGain = 0x00090000,
SetPower = 0x000A0040,
@ -17,6 +18,7 @@ namespace MICCommands {
void MICService::reset() {
micEnabled = false;
shouldClamp = false;
isSampling = false;
gain = 0;
}
@ -30,6 +32,7 @@ void MICService::handleSyncRequest(u32 messagePointer) {
case MICCommands::SetIirFilter: setIirFilter(messagePointer); break;
case MICCommands::SetPower: setPower(messagePointer); break;
case MICCommands::StartSampling: startSampling(messagePointer); break;
case MICCommands::StopSampling: stopSampling(messagePointer); break;
case MICCommands::CaptainToadFunction: theCaptainToadFunction(messagePointer); break;
default: Helpers::panic("MIC service requested. Command: %08X\n", command);
}
@ -88,10 +91,19 @@ void MICService::startSampling(u32 messagePointer) {
encoding, sampleRate, offset, dataSize, loop ? "yes" : "no"
);
isSampling = true;
mem.write32(messagePointer, IPC::responseHeader(0x3, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void MICService::stopSampling(u32 messagePointer) {
log("MIC::StopSampling\n");
isSampling = false;
mem.write32(messagePointer, IPC::responseHeader(0x5, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void MICService::setIirFilter(u32 messagePointer) {
const u32 size = mem.read32(messagePointer + 4);
const u32 pointer = mem.read32(messagePointer + 12);

View file

@ -7,7 +7,8 @@ namespace NDMCommands {
SuspendDaemons = 0x00060040,
ResumeDaemons = 0x00070040,
SuspendScheduler = 0x00080040,
ResumeScheduler = 0x00090000
ResumeScheduler = 0x00090000,
ClearHalfAwakeMacFilter = 0x00170000,
};
}
@ -16,6 +17,7 @@ void NDMService::reset() {}
void NDMService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case NDMCommands::ClearHalfAwakeMacFilter: clearHalfAwakeMacFilter(messagePointer); break;
case NDMCommands::OverrideDefaultDaemons: overrideDefaultDaemons(messagePointer); break;
case NDMCommands::ResumeDaemons: resumeDaemons(messagePointer); break;
case NDMCommands::ResumeScheduler: resumeScheduler(messagePointer); break;
@ -26,31 +28,37 @@ void NDMService::handleSyncRequest(u32 messagePointer) {
}
void NDMService::overrideDefaultDaemons(u32 messagePointer) {
log("NDM::OverrideDefaultDaemons(stubbed)\n");
log("NDM::OverrideDefaultDaemons (stubbed)\n");
mem.write32(messagePointer, IPC::responseHeader(0x14, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void NDMService::resumeDaemons(u32 messagePointer) {
log("NDM::resumeDaemons(stubbed)\n");
log("NDM::resumeDaemons (stubbed)\n");
mem.write32(messagePointer, IPC::responseHeader(0x7, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void NDMService::suspendDaemons(u32 messagePointer) {
log("NDM::SuspendDaemons(stubbed)\n");
log("NDM::SuspendDaemons (stubbed)\n");
mem.write32(messagePointer, IPC::responseHeader(0x6, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void NDMService::resumeScheduler(u32 messagePointer) {
log("NDM::ResumeScheduler(stubbed)\n");
log("NDM::ResumeScheduler (stubbed)\n");
mem.write32(messagePointer, IPC::responseHeader(0x9, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void NDMService::suspendScheduler(u32 messagePointer) {
log("NDM::SuspendScheduler(stubbed)\n");
log("NDM::SuspendScheduler (stubbed)\n");
mem.write32(messagePointer, IPC::responseHeader(0x8, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void NDMService::clearHalfAwakeMacFilter(u32 messagePointer) {
log("NDM::ClearHalfAwakeMacFilter (stubbed)\n");
mem.write32(messagePointer, IPC::responseHeader(0x17, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}

View file

@ -0,0 +1,15 @@
#include "ipc.hpp"
#include "services/news_u.hpp"
namespace NewsCommands {
enum : u32 {};
}
void NewsUService::reset() {}
void NewsUService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
default: Helpers::panic("news:u service requested. Command: %08X\n", command);
}
}

View file

@ -5,22 +5,35 @@
namespace NFCCommands {
enum : u32 {
Initialize = 0x00010040,
StartCommunication = 0x00030000,
StopCommunication = 0x00040000,
GetTagInRangeEvent = 0x000B0000,
GetTagOutOfRangeEvent = 0x000C0000
GetTagOutOfRangeEvent = 0x000C0000,
GetTagState = 0x000D0000,
CommunicationGetStatus = 0x000F0000,
CommunicationGetResult = 0x00120000,
};
}
void NFCService::reset() {
tagInRangeEvent = std::nullopt;
tagOutOfRangeEvent = std::nullopt;
adapterStatus = Old3DSAdapterStatus::Idle;
tagStatus = TagStatus::NotInitialized;
initialized = false;
}
void NFCService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case NFCCommands::CommunicationGetStatus: communicationGetStatus(messagePointer); break;
case NFCCommands::Initialize: initialize(messagePointer); break;
case NFCCommands::GetTagInRangeEvent: getTagInRangeEvent(messagePointer); break;
case NFCCommands::GetTagOutOfRangeEvent: getTagOutOfRangeEvent(messagePointer); break;
case NFCCommands::GetTagState: getTagState(messagePointer); break;
case NFCCommands::StartCommunication: startCommunication(messagePointer); break;
case NFCCommands::StopCommunication: stopCommunication(messagePointer); break;
default: Helpers::panic("NFC service requested. Command: %08X\n", command);
}
}
@ -29,6 +42,9 @@ void NFCService::initialize(u32 messagePointer) {
const u8 type = mem.read8(messagePointer + 4);
log("NFC::Initialize (type = %d)\n", type);
adapterStatus = Old3DSAdapterStatus::InitializationComplete;
tagStatus = TagStatus::Initialized;
initialized = true;
// TODO: This should error if already initialized. Also sanitize type.
mem.write32(messagePointer, IPC::responseHeader(0x1, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
@ -67,4 +83,42 @@ void NFCService::getTagOutOfRangeEvent(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
// TODO: Translation descriptor here
mem.write32(messagePointer + 12, tagOutOfRangeEvent.value());
}
void NFCService::getTagState(u32 messagePointer) {
log("NFC::GetTagState");
mem.write32(messagePointer, IPC::responseHeader(0xD, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write8(messagePointer + 8, static_cast<u8>(tagStatus));
}
void NFCService::communicationGetStatus(u32 messagePointer) {
log("NFC::CommunicationGetStatus");
if (!initialized) {
Helpers::warn("NFC::CommunicationGetStatus: Old 3DS NFC Adapter not initialized\n");
}
mem.write32(messagePointer, IPC::responseHeader(0xF, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write8(messagePointer + 8, static_cast<u32>(adapterStatus));
}
void NFCService::startCommunication(u32 messagePointer) {
log("NFC::StartCommunication\n");
// adapterStatus = Old3DSAdapterStatus::Active;
// TODO: Actually start communication when we emulate amiibo
mem.write32(messagePointer, IPC::responseHeader(0x3, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void NFCService::stopCommunication(u32 messagePointer) {
log("NFC::StopCommunication\n");
adapterStatus = Old3DSAdapterStatus::InitializationComplete;
// TODO: Actually stop communication when we emulate amiibo
mem.write32(messagePointer, IPC::responseHeader(0x4, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}

View file

@ -27,20 +27,18 @@ void PTMService::handleSyncRequest(u32 messagePointer) {
void PTMService::getAdapterState(u32 messagePointer) {
log("PTM::GetAdapterState\n");
constexpr bool adapterConnected = true; // Pretend the 3DS is charging cause why not
mem.write32(messagePointer, IPC::responseHeader(0x5, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write8(messagePointer + 8, adapterConnected ? 1 : 0);
mem.write8(messagePointer + 8, config.chargerPlugged ? 1 : 0);
}
void PTMService::getBatteryLevel(u32 messagePointer) {
log("PTM::GetBatteryLevel");
constexpr u8 batteryPercent = 3; // 3% battery so users can suffer
mem.write32(messagePointer, IPC::responseHeader(0x7, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write8(messagePointer + 8, batteryPercentToLevel(batteryPercent));
mem.write8(messagePointer + 8, batteryPercentToLevel(config.batteryPercentage));
}
void PTMService::getStepHistory(u32 messagePointer) {

View file

@ -5,11 +5,11 @@
#include "ipc.hpp"
#include "kernel.hpp"
ServiceManager::ServiceManager(std::span<u32, 16> regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel)
: regs(regs), mem(mem), kernel(kernel), ac(mem), am(mem), boss(mem), act(mem), apt(mem, kernel), cam(mem), cecd(mem, kernel), cfg(mem),
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),
gsp_gpu(mem, gpu, kernel, currentPID), gsp_lcd(mem), ldr(mem), mic(mem), nfc(mem, kernel), nim(mem), ndm(mem), ptm(mem), soc(mem),
ssl(mem), y2r(mem, kernel) {}
gsp_gpu(mem, gpu, kernel, currentPID), gsp_lcd(mem), ldr(mem), mcu_hwc(mem, config), mic(mem), nfc(mem, kernel), nim(mem), ndm(mem),
news_u(mem), ptm(mem, config), soc(mem), ssl(mem), y2r(mem, kernel) {}
static constexpr int MAX_NOTIFICATION_COUNT = 16;
@ -32,10 +32,13 @@ void ServiceManager::reset() {
fs.reset();
gsp_gpu.reset();
gsp_lcd.reset();
ldr.reset();
ldr.reset();
mcu_hwc.reset();
mic.reset();
nim.reset();
ndm.reset();
news_u.reset();
nfc.reset();
nim.reset();
ptm.reset();
soc.reset();
ssl.reset();
@ -110,8 +113,10 @@ static std::map<std::string, Handle> serviceMap = {
{ "gsp::Gpu", KernelHandles::GPU },
{ "gsp::Lcd", KernelHandles::LCD },
{ "ldr:ro", KernelHandles::LDR_RO },
{ "mcu::HWC", KernelHandles::MCU_HWC },
{ "mic:u", KernelHandles::MIC },
{ "ndm:u", KernelHandles::NDM },
{ "news:u", KernelHandles::NEWS_U },
{ "nfc:u", KernelHandles::NFC },
{ "nim:aoc", KernelHandles::NIM },
{ "ptm:u", KernelHandles::PTM }, // TODO: ptm:u and ptm:sysm have very different command sets
@ -181,10 +186,10 @@ void ServiceManager::sendCommandToService(u32 messagePointer, Handle handle) {
case KernelHandles::APT: [[likely]] apt.handleSyncRequest(messagePointer); break;
case KernelHandles::DSP: [[likely]] dsp.handleSyncRequest(messagePointer); break;
case KernelHandles::AC: ac.handleSyncRequest(messagePointer); break;
case KernelHandles::AC: ac.handleSyncRequest(messagePointer); break;
case KernelHandles::ACT: act.handleSyncRequest(messagePointer); break;
case KernelHandles::AM: am.handleSyncRequest(messagePointer); break;
case KernelHandles::BOSS: boss.handleSyncRequest(messagePointer); break;
case KernelHandles::AM: am.handleSyncRequest(messagePointer); break;
case KernelHandles::BOSS: boss.handleSyncRequest(messagePointer); break;
case KernelHandles::CAM: cam.handleSyncRequest(messagePointer); break;
case KernelHandles::CECD: cecd.handleSyncRequest(messagePointer); break;
case KernelHandles::CFG_U: cfg.handleSyncRequest(messagePointer); break;
@ -192,13 +197,15 @@ void ServiceManager::sendCommandToService(u32 messagePointer, Handle handle) {
case KernelHandles::HID: hid.handleSyncRequest(messagePointer); break;
case KernelHandles::HTTP: http.handleSyncRequest(messagePointer); break;
case KernelHandles::IR_USER: ir_user.handleSyncRequest(messagePointer); break;
case KernelHandles::FRD: frd.handleSyncRequest(messagePointer); break;
case KernelHandles::FRD: frd.handleSyncRequest(messagePointer); break;
case KernelHandles::LCD: gsp_lcd.handleSyncRequest(messagePointer); break;
case KernelHandles::LDR_RO: ldr.handleSyncRequest(messagePointer); break;
case KernelHandles::LDR_RO: ldr.handleSyncRequest(messagePointer); break;
case KernelHandles::MCU_HWC: mcu_hwc.handleSyncRequest(messagePointer); break;
case KernelHandles::MIC: mic.handleSyncRequest(messagePointer); break;
case KernelHandles::NFC: nfc.handleSyncRequest(messagePointer); break;
case KernelHandles::NIM: nim.handleSyncRequest(messagePointer); break;
case KernelHandles::NIM: nim.handleSyncRequest(messagePointer); break;
case KernelHandles::NDM: ndm.handleSyncRequest(messagePointer); break;
case KernelHandles::NEWS_U: news_u.handleSyncRequest(messagePointer); break;
case KernelHandles::PTM: ptm.handleSyncRequest(messagePointer); break;
case KernelHandles::SOC: soc.handleSyncRequest(messagePointer); break;
case KernelHandles::SSL: ssl.handleSyncRequest(messagePointer); break;

File diff suppressed because it is too large Load diff

View file

@ -23,6 +23,7 @@ namespace Y2RCommands {
StartConversion = 0x00260000,
StopConversion = 0x00270000,
IsBusyConversion = 0x00280000,
SetPackageParameter = 0x002901C0,
PingProcess = 0x002A0000,
DriverInitialize = 0x002B0000,
DriverFinalize = 0x002C0000
@ -60,6 +61,7 @@ void Y2RService::handleSyncRequest(u32 messagePointer) {
case Y2RCommands::SetInputLineWidth: setInputLineWidth(messagePointer); break;
case Y2RCommands::SetInputLines: setInputLines(messagePointer); break;
case Y2RCommands::SetOutputFormat: setOutputFormat(messagePointer); break;
case Y2RCommands::SetPackageParameter: setPackageParameter(messagePointer); break;
case Y2RCommands::SetReceiving: setReceiving(messagePointer); break;
case Y2RCommands::SetRotation: setRotation(messagePointer); break;
case Y2RCommands::SetSendingY: setSendingY(messagePointer); break;
@ -176,6 +178,17 @@ void Y2RService::setOutputFormat(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
}
void Y2RService::setPackageParameter(u32 messagePointer) {
// Package parameter is 3 words
const u32 word1 = mem.read32(messagePointer + 4);
const u32 word2 = mem.read32(messagePointer + 8);
const u32 word3 = mem.read32(messagePointer + 12);
Helpers::warn("Y2R::SetPackageParameter\n");
mem.write32(messagePointer, IPC::responseHeader(0x29, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void Y2RService::setRotation(u32 messagePointer) {
const u32 rot = mem.read32(messagePointer + 4);
log("Y2R::SetRotation (format = %d)\n", rot);

View file

@ -12,8 +12,8 @@ __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1;
#endif
Emulator::Emulator()
: config(std::filesystem::current_path() / "config.toml"), kernel(cpu, memory, gpu), cpu(memory, kernel), gpu(memory, config),
memory(cpu.getTicksRef()), cheats(memory, kernel.getServiceManager().getHID()), running(false), programRunning(false)
: config(std::filesystem::current_path() / "config.toml"), kernel(cpu, memory, gpu, config), cpu(memory, kernel), gpu(memory, config),
memory(cpu.getTicksRef(), config), cheats(memory, kernel.getServiceManager().getHID()), running(false), programRunning(false)
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
, httpServer(this)
#endif
@ -316,19 +316,36 @@ void Emulator::run() {
// Detect mouse motion events for gyroscope emulation
case SDL_MOUSEMOTION: {
if (romType == ROMType::None) break;
// Handle "dragging" across the touchscreen
if (hid.isTouchScreenPressed()) {
const s32 x = event.motion.x;
const s32 y = event.motion.y;
// Check if touch falls in the touch screen area and register the new touch screen position
if (y >= 240 && y <= 480 && x >= 40 && x < 40 + 320) {
// Convert to 3DS coordinates
u16 x_converted = static_cast<u16>(x) - 40;
u16 y_converted = static_cast<u16>(y) - 240;
hid.setTouchScreenPress(x_converted, y_converted);
}
}
// We use right click to indicate we want to rotate the console. If right click is not held, then this is not a gyroscope rotation
if (romType == ROMType::None || !holdingRightClick) break;
if (holdingRightClick) {
// Relative motion since last mouse motion event
const s32 motionX = event.motion.xrel;
const s32 motionY = event.motion.yrel;
// Relative motion since last mouse motion event
const s32 motionX = event.motion.xrel;
const s32 motionY = event.motion.yrel;
// The gyroscope involves lots of weird math I don't want to bother with atm
// So up until then, we will set the gyroscope euler angles to fixed values based on the direction of the relative motion
const s32 roll = motionX > 0 ? 0x7f : -0x7f;
const s32 pitch = motionY > 0 ? 0x7f : -0x7f;
hid.setRoll(roll);
hid.setPitch(pitch);
// The gyroscope involves lots of weird math I don't want to bother with atm
// So up until then, we will set the gyroscope euler angles to fixed values based on the direction of the relative motion
const s32 roll = motionX > 0 ? 0x7f : -0x7f;
const s32 pitch = motionY > 0 ? 0x7f : -0x7f;
hid.setRoll(roll);
hid.setPitch(pitch);
}
break;
}

View file

@ -232,8 +232,8 @@ void calcLighting(out vec4 primary_color, out vec4 secondary_color) {
// Positional Light
if (bitfieldExtract(GPUREG_LIGHTi_CONFIG, 0, 1) == 0) {
error_unimpl = true;
// half_vector = normalize(normalize(light_vector + v_view) + view);
// error_unimpl = true;
half_vector = normalize(normalize(light_vector + v_view) + view);
}
// Directional light
@ -328,7 +328,7 @@ void calcLighting(out vec4 primary_color, out vec4 secondary_color) {
if (fresnel_output2 == 1u) secondary_color.a = d[FR_LUT];
if (error_unimpl) {
secondary_color = primary_color = vec4(1.0, 0., 1.0, 1.0);
// secondary_color = primary_color = vec4(1.0, 0., 1.0, 1.0);
}
}