mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-20 04:29:13 +12:00
Merge branch 'master' into mii_selector
This commit is contained in:
commit
3b22f15e67
103 changed files with 2118 additions and 262550 deletions
|
@ -1,5 +1,6 @@
|
|||
#include "config.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
|
@ -54,7 +55,30 @@ void EmulatorConfig::load(const std::filesystem::path& path) {
|
|||
rendererType = RendererType::OpenGL;
|
||||
}
|
||||
|
||||
shaderJitEnabled = toml::find_or<toml::boolean>(gpu, "EnableShaderJIT", false);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
|
|
@ -143,7 +143,9 @@ void ShaderEmitter::compileInstruction(const PICAShader& shaderUnit) {
|
|||
break;
|
||||
case ShaderOpcodes::DP3: recDP3(shaderUnit, instruction); break;
|
||||
case ShaderOpcodes::DP4: recDP4(shaderUnit, instruction); break;
|
||||
case ShaderOpcodes::DPH: recDPH(shaderUnit, instruction); break;
|
||||
case ShaderOpcodes::DPH:
|
||||
case ShaderOpcodes::DPHI:
|
||||
recDPH(shaderUnit, instruction); break;
|
||||
case ShaderOpcodes::END: recEND(shaderUnit, instruction); break;
|
||||
case ShaderOpcodes::EX2: recEX2(shaderUnit, instruction); break;
|
||||
case ShaderOpcodes::FLR: recFLR(shaderUnit, instruction); break;
|
||||
|
@ -531,15 +533,17 @@ void ShaderEmitter::recDP4(const PICAShader& shader, u32 instruction) {
|
|||
}
|
||||
|
||||
void ShaderEmitter::recDPH(const PICAShader& shader, u32 instruction) {
|
||||
const bool isDPHI = (instruction >> 26) == ShaderOpcodes::DPHI;
|
||||
|
||||
const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f];
|
||||
const u32 src1 = getBits<12, 7>(instruction);
|
||||
const u32 src2 = getBits<7, 5>(instruction); // src2 coming first because PICA moment
|
||||
const u32 src1 = isDPHI ? getBits<14, 5>(instruction) : getBits<12, 7>(instruction);
|
||||
const u32 src2 = isDPHI ? getBits<7, 7>(instruction) : getBits<7, 5>(instruction);
|
||||
const u32 idx = getBits<19, 2>(instruction);
|
||||
const u32 dest = getBits<21, 5>(instruction);
|
||||
|
||||
// TODO: Safe multiplication equivalent (Multiplication is not IEEE compliant on the PICA)
|
||||
loadRegister<1>(src1_xmm, shader, src1, idx, operandDescriptor);
|
||||
loadRegister<2>(src2_xmm, shader, src2, 0, operandDescriptor);
|
||||
loadRegister<1>(src1_xmm, shader, src1, isDPHI ? 0 : idx, operandDescriptor);
|
||||
loadRegister<2>(src2_xmm, shader, src2, isDPHI ? idx : 0, operandDescriptor);
|
||||
|
||||
// Attach 1.0 to the w component of src1
|
||||
if (haveSSE4_1) {
|
||||
|
|
|
@ -16,6 +16,12 @@
|
|||
#include "renderer_vk/renderer_vk.hpp"
|
||||
#endif
|
||||
|
||||
constexpr u32 topScreenWidth = 240;
|
||||
constexpr u32 topScreenHeight = 400;
|
||||
|
||||
constexpr u32 bottomScreenWidth = 240;
|
||||
constexpr u32 bottomScreenHeight = 300;
|
||||
|
||||
using namespace Floats;
|
||||
|
||||
// Note: For when we have multiple backends, the GL state manager can stay here and have the constructor for the Vulkan-or-whatever renderer ignore it
|
||||
|
@ -26,24 +32,24 @@ GPU::GPU(Memory& mem, EmulatorConfig& config) : mem(mem), config(config) {
|
|||
|
||||
switch (config.rendererType) {
|
||||
case RendererType::Null: {
|
||||
renderer.reset(new RendererNull(*this, regs));
|
||||
renderer.reset(new RendererNull(*this, regs, externalRegs));
|
||||
break;
|
||||
}
|
||||
|
||||
case RendererType::Software: {
|
||||
renderer.reset(new RendererSw(*this, regs));
|
||||
renderer.reset(new RendererSw(*this, regs, externalRegs));
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef PANDA3DS_ENABLE_OPENGL
|
||||
case RendererType::OpenGL: {
|
||||
renderer.reset(new RendererGL(*this, regs));
|
||||
renderer.reset(new RendererGL(*this, regs, externalRegs));
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef PANDA3DS_ENABLE_VULKAN
|
||||
case RendererType::Vulkan: {
|
||||
renderer.reset(new RendererVK(*this, regs));
|
||||
renderer.reset(new RendererVK(*this, regs, externalRegs));
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
@ -78,6 +84,27 @@ void GPU::reset() {
|
|||
e.config2 = 0;
|
||||
}
|
||||
|
||||
// Initialize the framebuffer registers. Values taken from Citra.
|
||||
|
||||
using namespace PICA::ExternalRegs;
|
||||
// Top screen addresses and dimentions.
|
||||
externalRegs[Framebuffer0AFirstAddr] = 0x181E6000;
|
||||
externalRegs[Framebuffer0ASecondAddr] = 0x1822C800;
|
||||
externalRegs[Framebuffer0BFirstAddr] = 0x18273000;
|
||||
externalRegs[Framebuffer0BSecondAddr] = 0x182B9800;
|
||||
externalRegs[Framebuffer0Size] = (topScreenHeight << 16) | topScreenWidth;
|
||||
externalRegs[Framebuffer0Stride] = 720;
|
||||
externalRegs[Framebuffer0Config] = static_cast<u32>(PICA::ColorFmt::RGB8);
|
||||
externalRegs[Framebuffer0Select] = 0;
|
||||
|
||||
// Bottom screen addresses and dimentions.
|
||||
externalRegs[Framebuffer1AFirstAddr] = 0x1848F000;
|
||||
externalRegs[Framebuffer1ASecondAddr] = 0x184C7800;
|
||||
externalRegs[Framebuffer1Size] = (bottomScreenHeight << 16) | bottomScreenWidth;
|
||||
externalRegs[Framebuffer1Stride] = 720;
|
||||
externalRegs[Framebuffer1Config] = static_cast<u32>(PICA::ColorFmt::RGB8);
|
||||
externalRegs[Framebuffer1Select] = 0;
|
||||
|
||||
renderer->reset();
|
||||
}
|
||||
|
||||
|
@ -321,15 +348,17 @@ PICA::Vertex GPU::getImmediateModeVertex() {
|
|||
|
||||
// Run VS and return vertex data. TODO: Don't hardcode offsets for each attribute
|
||||
shaderUnit.vs.run();
|
||||
std::memcpy(&v.s.positions, &shaderUnit.vs.outputs[0], sizeof(vec4f));
|
||||
std::memcpy(&v.s.colour, &shaderUnit.vs.outputs[1], sizeof(vec4f));
|
||||
std::memcpy(&v.s.texcoord0, &shaderUnit.vs.outputs[2], 2 * sizeof(f24));
|
||||
|
||||
// Map shader outputs to fixed function properties
|
||||
const u32 totalShaderOutputs = regs[PICA::InternalRegs::ShaderOutputCount] & 7;
|
||||
for (int i = 0; i < totalShaderOutputs; i++) {
|
||||
const u32 config = regs[PICA::InternalRegs::ShaderOutmap0 + i];
|
||||
|
||||
printf(
|
||||
"(x, y, z, w) = (%f, %f, %f, %f)\n", (double)v.s.positions[0], (double)v.s.positions[1], (double)v.s.positions[2], (double)v.s.positions[3]
|
||||
);
|
||||
printf("(r, g, b, a) = (%f, %f, %f, %f)\n", (double)v.s.colour[0], (double)v.s.colour[1], (double)v.s.colour[2], (double)v.s.colour[3]);
|
||||
printf("(u, v ) = (%f, %f)\n", (double)v.s.texcoord0[0], (double)v.s.texcoord0[1]);
|
||||
for (int j = 0; j < 4; j++) { // pls unroll
|
||||
const u32 mapping = (config >> (j * 8)) & 0x1F;
|
||||
v.raw[mapping] = shaderUnit.vs.outputs[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
|
|
@ -19,11 +19,36 @@ void GPU::writeReg(u32 address, u32 value) {
|
|||
if (address >= 0x1EF01000 && address < 0x1EF01C00) { // Internal registers
|
||||
const u32 index = (address - 0x1EF01000) / sizeof(u32);
|
||||
writeInternalReg(index, value, 0xffffffff);
|
||||
} else if (address >= 0x1EF00004 && address < 0x1EF01000) {
|
||||
const u32 index = (address - 0x1EF00004) / sizeof(u32);
|
||||
writeExternalReg(index, value);
|
||||
} else {
|
||||
log("Ignoring write to external GPU register %08X. Value: %08X\n", address, value);
|
||||
log("Ignoring write to unknown GPU register %08X. Value: %08X\n", address, value);
|
||||
}
|
||||
}
|
||||
|
||||
u32 GPU::readExternalReg(u32 index) {
|
||||
using namespace PICA::ExternalRegs;
|
||||
|
||||
if (index > 0x1000) [[unlikely]] {
|
||||
Helpers::panic("Tried to read invalid external GPU register. Index: %X\n", index);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return externalRegs[index];
|
||||
}
|
||||
|
||||
void GPU::writeExternalReg(u32 index, u32 value) {
|
||||
using namespace PICA::ExternalRegs;
|
||||
|
||||
if (index > 0x1000) [[unlikely]] {
|
||||
Helpers::panic("Tried to write to invalid external GPU register. Index: %X, value: %08X\n", index, value);
|
||||
return;
|
||||
}
|
||||
|
||||
externalRegs[index] = value;
|
||||
}
|
||||
|
||||
u32 GPU::readInternalReg(u32 index) {
|
||||
using namespace PICA::InternalRegs;
|
||||
|
||||
|
@ -384,4 +409,4 @@ void GPU::startCommandList(u32 addr, u32 size) {
|
|||
writeInternalReg(id, param, mask);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ void PICAShader::run() {
|
|||
case ShaderOpcodes::NOP: break; // Do nothing
|
||||
case ShaderOpcodes::RCP: rcp(instruction); break;
|
||||
case ShaderOpcodes::RSQ: rsq(instruction); break;
|
||||
case ShaderOpcodes::SGE: sge(instruction); break;
|
||||
case ShaderOpcodes::SGEI: sgei(instruction); break;
|
||||
case ShaderOpcodes::SLT: slt(instruction); break;
|
||||
case ShaderOpcodes::SLTI: slti(instruction); break;
|
||||
|
@ -517,6 +518,26 @@ void PICAShader::slt(u32 instruction) {
|
|||
}
|
||||
}
|
||||
|
||||
void PICAShader::sge(u32 instruction) {
|
||||
const u32 operandDescriptor = operandDescriptors[instruction & 0x7f];
|
||||
u32 src1 = getBits<12, 7>(instruction);
|
||||
const u32 src2 = getBits<7, 5>(instruction); // src2 coming first because PICA moment
|
||||
const u32 idx = getBits<19, 2>(instruction);
|
||||
const u32 dest = getBits<21, 5>(instruction);
|
||||
|
||||
src1 = getIndexedSource(src1, idx);
|
||||
vec4f srcVec1 = getSourceSwizzled<1>(src1, operandDescriptor);
|
||||
vec4f srcVec2 = getSourceSwizzled<2>(src2, operandDescriptor);
|
||||
auto& destVector = getDest(dest);
|
||||
|
||||
u32 componentMask = operandDescriptor & 0xf;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (componentMask & (1 << i)) {
|
||||
destVector[3 - i] = srcVec1[3 - i] >= srcVec2[3 - i] ? f24::fromFloat32(1.0) : f24::zero();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PICAShader::sgei(u32 instruction) {
|
||||
const u32 operandDescriptor = operandDescriptors[instruction & 0x7f];
|
||||
const u32 src1 = getBits<14, 5>(instruction);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -58,7 +58,7 @@ namespace IVFC {
|
|||
|
||||
// According to 3DBrew, this is usually the case but not guaranteed
|
||||
if (ivfcActualSize != ivfcDescriptorSize) {
|
||||
printf("IVFC descriptor size mismatch: %lx != %lx\n", ivfcActualSize, ivfcDescriptorSize);
|
||||
printf("IVFC descriptor size mismatch: %llx != %llx\n", ivfcActualSize, ivfcDescriptorSize);
|
||||
}
|
||||
|
||||
if (magicIdentifier == 0x10000 && ivfcActualSize != 0x5C) {
|
||||
|
@ -73,4 +73,4 @@ namespace IVFC {
|
|||
|
||||
return ivfcActualSize;
|
||||
}
|
||||
} // namespace IVFC
|
||||
} // namespace IVFC
|
||||
|
|
|
@ -3,9 +3,10 @@
|
|||
#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);
|
||||
portHandles.reserve(32);
|
||||
threadIndices.reserve(appResourceLimits.maxThreads);
|
||||
|
||||
|
@ -34,6 +35,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;
|
||||
|
@ -41,6 +44,10 @@ void Kernel::serviceSVC(u32 svc) {
|
|||
case 0x17: svcCreateEvent(); break;
|
||||
case 0x18: svcSignalEvent(); break;
|
||||
case 0x19: svcClearEvent(); break;
|
||||
case 0x1A: svcCreateTimer(); break;
|
||||
case 0x1B: svcSetTimer(); break;
|
||||
case 0x1C: svcCancelTimer(); break;
|
||||
case 0x1D: svcClearTimer(); break;
|
||||
case 0x1E: createMemoryBlock(); break;
|
||||
case 0x1F: mapMemoryBlock(); break;
|
||||
case 0x21: createAddressArbiter(); break;
|
||||
|
@ -139,6 +146,7 @@ void Kernel::reset() {
|
|||
deleteObjectData(object);
|
||||
}
|
||||
objects.clear();
|
||||
mutexHandles.clear();
|
||||
portHandles.clear();
|
||||
threadIndices.clear();
|
||||
serviceManager.reset();
|
||||
|
@ -152,7 +160,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();
|
||||
|
||||
|
@ -256,6 +264,7 @@ void Kernel::duplicateHandle() {
|
|||
|
||||
namespace SystemInfoType {
|
||||
enum : u32 {
|
||||
MemoryInformation = 0,
|
||||
// Gets information related to Citra (We don't implement this, we just report this emulator is not Citra)
|
||||
CitraInformation = 0x20000,
|
||||
// Gets information related to this emulator
|
||||
|
@ -292,6 +301,22 @@ void Kernel::getSystemInfo() {
|
|||
|
||||
regs[0] = Result::Success;
|
||||
switch (infoType) {
|
||||
case SystemInfoType::MemoryInformation: {
|
||||
switch (subtype) {
|
||||
// Total used memory size in the APPLICATION memory region
|
||||
case 1:
|
||||
regs[1] = mem.getUsedUserMem();
|
||||
regs[2] = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
Helpers::panic("GetSystemInfo: Unknown MemoryInformation subtype %x\n", subtype);
|
||||
regs[0] = Result::FailurePlaceholder;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SystemInfoType::CitraInformation: {
|
||||
switch (subtype) {
|
||||
case CitraInfoType::IsCitra:
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -168,6 +168,11 @@ Handle Kernel::makeMutex(bool locked) {
|
|||
moo->ownerThread = currentThreadIndex;
|
||||
}
|
||||
|
||||
// Push the new mutex to our list of mutex handles
|
||||
// We need a list of mutex handles so that when a thread is killed, we can look which mutexes from this list the thread owns and free them
|
||||
// Alternatively this could be a per-thread list, but I don't want to push_back and remove on every mutex lock and release
|
||||
// Since some mutexes like the APT service mutex are locked and unlocked constantly, while ExitThread is a relatively "rare" SVC
|
||||
mutexHandles.push_back(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -384,8 +389,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();
|
||||
}
|
||||
|
||||
|
@ -436,6 +445,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];
|
||||
|
@ -463,9 +481,53 @@ 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");
|
||||
|
||||
// Find which mutexes this thread owns, release them
|
||||
for (auto handle : mutexHandles) {
|
||||
KernelObject* object = getObject(handle, KernelObjectType::Mutex);
|
||||
|
||||
// Make sure that the handle actually matches to a mutex, and if our exiting thread owns the mutex, release it
|
||||
if (object != nullptr) {
|
||||
Mutex* moo = object->getData<Mutex>();
|
||||
|
||||
if (moo->locked && moo->ownerThread == currentThreadIndex) {
|
||||
// Release the mutex by setting lock count to 1 and releasing it once. We set lock count to 1 since it's a recursive mutex
|
||||
// Therefore if its lock count was > 1, simply calling releaseMutex would not fully release it
|
||||
moo->lockCount = 1;
|
||||
releaseMutex(moo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the index of this thread from the thread indices vector
|
||||
for (int i = 0; i < threadIndices.size(); i++) {
|
||||
if (threadIndices[i] == currentThreadIndex)
|
||||
|
@ -597,4 +659,4 @@ bool Kernel::shouldWaitOnObject(KernelObject* object) {
|
|||
Helpers::panic("Not sure whether to wait on object (type: %s)", object->getTypeName());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
6
src/core/kernel/timers.cpp
Normal file
6
src/core/kernel/timers.cpp
Normal file
|
@ -0,0 +1,6 @@
|
|||
#include "kernel.hpp"
|
||||
|
||||
void Kernel::svcCreateTimer() { Helpers::panic("Kernel::CreateTimer"); }
|
||||
void Kernel::svcSetTimer() { Helpers::panic("Kernel::SetTimer"); }
|
||||
void Kernel::svcClearTimer() { Helpers::panic("Kernel::ClearTimer"); }
|
||||
void Kernel::svcCancelTimer() { Helpers::panic("Kernel::CancelTimer"); }
|
|
@ -63,5 +63,7 @@ std::optional<u32> Memory::loadELF(std::ifstream& file) {
|
|||
allocateMemory(vaddr, fcramAddr, memorySize, true, r, w, x);
|
||||
}
|
||||
|
||||
// ELF can't specify a region, make it default to USA
|
||||
region = Regions::USA;
|
||||
return static_cast<u32>(reader.get_entry());
|
||||
}
|
|
@ -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;
|
||||
|
@ -209,10 +216,25 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn
|
|||
codeFile.resize(fileSize);
|
||||
readFromFile(file, exeFS, codeFile.data(), fileOffset + exeFSHeaderSize, fileSize);
|
||||
}
|
||||
} else if (std::strcmp(name, "icon") == 0) {
|
||||
// Parse icon file to extract region info and more in the future (logo, etc)
|
||||
std::vector<u8> tmp;
|
||||
tmp.resize(fileSize);
|
||||
readFromFile(file, exeFS, tmp.data(), fileOffset + exeFSHeaderSize, fileSize);
|
||||
|
||||
if (!parseSMDH(tmp)) {
|
||||
printf("Failed to parse SMDH!\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no region has been detected for CXI, set the region to USA by default
|
||||
if (!region.has_value() && partitionIndex == 0) {
|
||||
printf("No region detected for CXI, defaulting to USA\n");
|
||||
region = Regions::USA;
|
||||
}
|
||||
|
||||
if (hasRomFS()) {
|
||||
printf("RomFS offset: %08llX, size: %08llX\n", romFS.offset, romFS.size);
|
||||
}
|
||||
|
@ -221,6 +243,52 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn
|
|||
return true;
|
||||
}
|
||||
|
||||
bool NCCH::parseSMDH(const std::vector<u8>& smdh) {
|
||||
if (smdh.size() < 0x36C0) {
|
||||
printf("The cartridge .icon file is too small, considered invalid. Must be 0x36C0 bytes minimum\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (char(smdh[0]) != 'S' || char(smdh[1]) != 'M' || char(smdh[2]) != 'D' || char(smdh[3]) != 'H') {
|
||||
printf("Invalid SMDH magic!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bitmask showing which regions are allowed.
|
||||
// https://www.3dbrew.org/wiki/SMDH#Region_Lockout
|
||||
const u32 regionMasks = *(u32*)&smdh[0x2018];
|
||||
// Detect when games are region free (ie all regions are allowed) for future use
|
||||
[[maybe_unused]] const bool isRegionFree = (regionMasks & 0x7f) == 0x7f;
|
||||
|
||||
// See which countries are allowed
|
||||
const bool japan = (regionMasks & 0x1) != 0;
|
||||
const bool northAmerica = (regionMasks & 0x2) != 0;
|
||||
const bool europe = (regionMasks & 0x4) != 0;
|
||||
const bool australia = (regionMasks & 0x8) != 0;
|
||||
const bool china = (regionMasks & 0x10) != 0;
|
||||
const bool korea = (regionMasks & 0x20) != 0;
|
||||
const bool taiwan = (regionMasks & 0x40) != 0;
|
||||
|
||||
// Based on the allowed regions, set the autodetected 3DS region. We currently prefer English-speaking regions for practical purposes.
|
||||
// But this should be configurable later.
|
||||
if (northAmerica) {
|
||||
region = Regions::USA;
|
||||
} else if (europe) {
|
||||
region = Regions::Europe;
|
||||
} else if (australia) {
|
||||
region = Regions::Australia;
|
||||
} else if (japan) {
|
||||
region = Regions::Japan;
|
||||
} else if (korea) {
|
||||
region = Regions::Korea;
|
||||
} else if (china) {
|
||||
region = Regions::China;
|
||||
} else if (taiwan) {
|
||||
region = Regions::Taiwan;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::pair<bool, Crypto::AESKey> NCCH::getPrimaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY) {
|
||||
Crypto::AESKey result;
|
||||
|
||||
|
|
|
@ -11,6 +11,12 @@ bool Memory::mapCXI(NCSD& ncsd, NCCH& cxi) {
|
|||
printf("Data address = %08X, size = %08X\n", cxi.data.address, cxi.data.size);
|
||||
printf("Stack size: %08X\n", cxi.stackSize);
|
||||
|
||||
static constexpr std::array<const char*, 7> regionNames = {"Japan", "North America", "Europe", "Australia", "China", "Korea", "Taiwan" };
|
||||
|
||||
// Set autodetected 3DS region to one of the values allowed by the CXI's SMDH
|
||||
region = cxi.region.value();
|
||||
printf("Console region autodetected to: %s\n", regionNames[static_cast<size_t>(region)]);
|
||||
|
||||
if (!isAligned(cxi.stackSize)) {
|
||||
Helpers::warn("CXI has a suspicious stack size of %08X which is not a multiple of 4KB", cxi.stackSize);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
@ -59,6 +69,9 @@ void Memory::reset() {
|
|||
readTable[i + initialPage] = pointer;
|
||||
writeTable[i + initialPage] = pointer;
|
||||
}
|
||||
|
||||
// Later adjusted based on ROM header when possible
|
||||
region = Regions::USA;
|
||||
}
|
||||
|
||||
bool Memory::allocateMainThreadStack(u32 size) {
|
||||
|
@ -82,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);
|
||||
|
@ -91,6 +115,12 @@ u8 Memory::read8(u32 vaddr) {
|
|||
case ConfigMem::NetworkState: return 2; // Report that we've got an internet connection
|
||||
case ConfigMem::HeadphonesConnectedMaybe: return 0;
|
||||
case ConfigMem::Unknown1086: return 1; // It's unknown what this is but some games want it to be 1
|
||||
|
||||
case ConfigMem::FirmUnknown: return firm.unk;
|
||||
case ConfigMem::FirmRevision: return firm.revision;
|
||||
case ConfigMem::FirmVersionMinor: return firm.minor;
|
||||
case ConfigMem::FirmVersionMajor: return firm.major;
|
||||
|
||||
default: Helpers::panic("Unimplemented 8-bit read, addr: %08X", vaddr);
|
||||
}
|
||||
}
|
||||
|
@ -138,11 +168,19 @@ u32 Memory::read32(u32 vaddr) {
|
|||
|
||||
// 3D slider. Float in range 0.0 = off, 1.0 = max.
|
||||
case ConfigMem::SliderState3D: return Helpers::bit_cast<u32, float>(0.0f);
|
||||
case ConfigMem::FirmUnknown:
|
||||
return u32(read8(vaddr)) | (u32(read8(vaddr + 1)) << 8) | (u32(read8(vaddr + 2)) << 16) | (u32(read8(vaddr + 3)) << 24);
|
||||
|
||||
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);
|
||||
|
@ -464,3 +502,15 @@ u64 Memory::timeSince3DSEpoch() {
|
|||
milliseconds ms = duration_cast<milliseconds>(seconds(rawTime + timezoneDifference + offset));
|
||||
return ms.count();
|
||||
}
|
||||
|
||||
Regions Memory::getConsoleRegion() {
|
||||
// TODO: Let the user force the console region as they want
|
||||
// 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());
|
||||
}
|
|
@ -10,6 +10,15 @@ void GLStateManager::resetBlend() {
|
|||
OpenGL::setLogicOp(GL_COPY);
|
||||
}
|
||||
|
||||
void GLStateManager::resetClearing() {
|
||||
clearRed = 0.f;
|
||||
clearBlue = 0.f;
|
||||
clearGreen = 0.f;
|
||||
clearAlpha = 1.f;
|
||||
|
||||
OpenGL::setClearColor(clearRed, clearBlue, clearGreen, clearAlpha);
|
||||
}
|
||||
|
||||
void GLStateManager::resetClipping() {
|
||||
// Disable all (supported) clip planes
|
||||
enabledClipPlanes = 0;
|
||||
|
@ -64,6 +73,7 @@ void GLStateManager::resetProgram() {
|
|||
|
||||
void GLStateManager::reset() {
|
||||
resetBlend();
|
||||
resetClearing();
|
||||
resetClipping();
|
||||
resetColourMask();
|
||||
resetDepth();
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "PICA/float_types.hpp"
|
||||
#include "PICA/gpu.hpp"
|
||||
#include "PICA/regs.hpp"
|
||||
#include "math_util.hpp"
|
||||
|
||||
CMRC_DECLARE(RendererGL);
|
||||
|
||||
|
@ -117,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
|
||||
|
@ -125,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);
|
||||
|
@ -134,13 +154,14 @@ void RendererGL::initGraphicsContext(SDL_Window* window) {
|
|||
screenFramebuffer.createWithDrawTexture(screenTexture);
|
||||
screenFramebuffer.bind(OpenGL::DrawAndReadFramebuffer);
|
||||
|
||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) Helpers::panic("Incomplete framebuffer");
|
||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||
Helpers::panic("Incomplete framebuffer");
|
||||
}
|
||||
|
||||
// TODO: This should not clear the framebuffer contents. It should load them from VRAM.
|
||||
GLint oldViewport[4];
|
||||
glGetIntegerv(GL_VIEWPORT, oldViewport);
|
||||
OpenGL::setViewport(screenTextureWidth, screenTextureHeight);
|
||||
OpenGL::setClearColor(0.0, 0.0, 0.0, 1.0);
|
||||
OpenGL::clearColor();
|
||||
OpenGL::setViewport(oldViewport[0], oldViewport[1], oldViewport[2], oldViewport[3]);
|
||||
|
||||
|
@ -318,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);
|
||||
|
@ -368,8 +397,8 @@ void RendererGL::drawVertices(PICA::PrimType primType, std::span<const Vertex> v
|
|||
}
|
||||
|
||||
setupBlending();
|
||||
OpenGL::Framebuffer poop = getColourFBO();
|
||||
poop.bind(OpenGL::DrawAndReadFramebuffer);
|
||||
auto poop = getColourBuffer(colourBufferLoc, colourBufferFormat, fbSize[0], fbSize[1]);
|
||||
poop->fbo.bind(OpenGL::DrawAndReadFramebuffer);
|
||||
|
||||
const u32 depthControl = regs[PICA::InternalRegs::DepthAndColorMask];
|
||||
const bool depthWrite = regs[PICA::InternalRegs::DepthBufferWrite];
|
||||
|
@ -412,10 +441,12 @@ void RendererGL::drawVertices(PICA::PrimType primType, std::span<const Vertex> v
|
|||
updateLightingLUT();
|
||||
}
|
||||
|
||||
// TODO: Actually use this
|
||||
GLsizei viewportWidth = GLsizei(f24::fromRaw(regs[PICA::InternalRegs::ViewportWidth] & 0xffffff).toFloat32() * 2.0f);
|
||||
GLsizei viewportHeight = GLsizei(f24::fromRaw(regs[PICA::InternalRegs::ViewportHeight] & 0xffffff).toFloat32() * 2.0f);
|
||||
OpenGL::setViewport(viewportWidth, viewportHeight);
|
||||
const GLsizei viewportX = regs[PICA::InternalRegs::ViewportXY] & 0x3ff;
|
||||
const GLsizei viewportY = (regs[PICA::InternalRegs::ViewportXY] >> 16) & 0x3ff;
|
||||
const GLsizei viewportWidth = GLsizei(f24::fromRaw(regs[PICA::InternalRegs::ViewportWidth] & 0xffffff).toFloat32() * 2.0f);
|
||||
const GLsizei viewportHeight = GLsizei(f24::fromRaw(regs[PICA::InternalRegs::ViewportHeight] & 0xffffff).toFloat32() * 2.0f);
|
||||
const auto rect = poop->getSubRect(colourBufferLoc, fbSize[0], fbSize[1]);
|
||||
OpenGL::setViewport(rect.left + viewportX, rect.bottom + viewportY, viewportWidth, viewportHeight);
|
||||
|
||||
const u32 stencilConfig = regs[PICA::InternalRegs::StencilTest];
|
||||
const bool stencilEnable = getBit<0>(stencilConfig);
|
||||
|
@ -450,6 +481,42 @@ void RendererGL::drawVertices(PICA::PrimType primType, std::span<const Vertex> v
|
|||
|
||||
void RendererGL::display() {
|
||||
gl.disableScissor();
|
||||
gl.disableBlend();
|
||||
gl.disableDepth();
|
||||
gl.disableScissor();
|
||||
// This will work fine whether or not logic ops are enabled. We set logic op to copy instead of disabling to avoid state changes
|
||||
gl.setLogicOp(GL_COPY);
|
||||
gl.setColourMask(true, true, true, true);
|
||||
gl.useProgram(displayProgram);
|
||||
gl.bindVAO(dummyVAO);
|
||||
|
||||
gl.disableClipPlane(0);
|
||||
gl.disableClipPlane(1);
|
||||
|
||||
screenFramebuffer.bind(OpenGL::DrawFramebuffer);
|
||||
gl.setClearColour(0.f, 0.f, 0.f, 1.f);
|
||||
OpenGL::clearColor();
|
||||
|
||||
using namespace PICA::ExternalRegs;
|
||||
const u32 topActiveFb = externalRegs[Framebuffer0Select] & 1;
|
||||
const u32 topScreenAddr = externalRegs[topActiveFb == 0 ? Framebuffer0AFirstAddr : Framebuffer0ASecondAddr];
|
||||
auto topScreen = colourBufferCache.findFromAddress(topScreenAddr);
|
||||
|
||||
if (topScreen) {
|
||||
topScreen->get().texture.bind();
|
||||
OpenGL::setViewport(0, 240, 400, 240); // Top screen viewport
|
||||
OpenGL::draw(OpenGL::TriangleStrip, 4); // Actually draw our 3DS screen
|
||||
}
|
||||
|
||||
const u32 bottomActiveFb = externalRegs[Framebuffer1Select] & 1;
|
||||
const u32 bottomScreenAddr = externalRegs[bottomActiveFb == 0 ? Framebuffer1AFirstAddr : Framebuffer1ASecondAddr];
|
||||
auto bottomScreen = colourBufferCache.findFromAddress(bottomScreenAddr);
|
||||
|
||||
if (bottomScreen) {
|
||||
bottomScreen->get().texture.bind();
|
||||
OpenGL::setViewport(40, 0, 320, 240);
|
||||
OpenGL::draw(OpenGL::TriangleStrip, 4);
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
screenFramebuffer.bind(OpenGL::ReadFramebuffer);
|
||||
|
@ -467,8 +534,9 @@ void RendererGL::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 co
|
|||
const float b = getBits<8, 8>(value) / 255.0f;
|
||||
const float a = (value & 0xff) / 255.0f;
|
||||
color->get().fbo.bind(OpenGL::DrawFramebuffer);
|
||||
|
||||
gl.setColourMask(true, true, true, true);
|
||||
OpenGL::setClearColor(r, g, b, a);
|
||||
gl.setClearColour(r, g, b, a);
|
||||
OpenGL::clearColor();
|
||||
return;
|
||||
}
|
||||
|
@ -505,7 +573,7 @@ void RendererGL::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 co
|
|||
|
||||
OpenGL::Framebuffer RendererGL::getColourFBO() {
|
||||
// We construct a colour buffer object and see if our cache has any matching colour buffers in it
|
||||
// If not, we allocate a texture & FBO for our framebuffer and store it in the cache
|
||||
// If not, we allocate a texture & FBO for our framebuffer and store it in the cache
|
||||
ColourBuffer sampleBuffer(colourBufferLoc, colourBufferFormat, fbSize[0], fbSize[1]);
|
||||
auto buffer = colourBufferCache.find(sampleBuffer);
|
||||
|
||||
|
@ -550,42 +618,152 @@ OpenGL::Texture RendererGL::getTexture(Texture& tex) {
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE: The GPU format has RGB5551 and RGB655 swapped compared to internal regs format
|
||||
PICA::ColorFmt ToColorFmt(u32 format) {
|
||||
switch (format) {
|
||||
case 2: return PICA::ColorFmt::RGB565;
|
||||
case 3: return PICA::ColorFmt::RGBA5551;
|
||||
default: return static_cast<PICA::ColorFmt>(format);
|
||||
}
|
||||
}
|
||||
|
||||
void RendererGL::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) {
|
||||
const u32 inputWidth = inputSize & 0xffff;
|
||||
const u32 inputGap = inputSize >> 16;
|
||||
const u32 inputHeight = inputSize >> 16;
|
||||
const auto inputFormat = ToColorFmt(Helpers::getBits<8, 3>(flags));
|
||||
const auto outputFormat = ToColorFmt(Helpers::getBits<12, 3>(flags));
|
||||
const bool verticalFlip = flags & 1;
|
||||
const PICA::Scaling scaling = static_cast<PICA::Scaling>(Helpers::getBits<24, 2>(flags));
|
||||
|
||||
const u32 outputWidth = outputSize & 0xffff;
|
||||
const u32 outputGap = outputSize >> 16;
|
||||
u32 outputWidth = outputSize & 0xffff;
|
||||
u32 outputHeight = outputSize >> 16;
|
||||
|
||||
auto framebuffer = colourBufferCache.findFromAddress(inputAddr);
|
||||
// If there's a framebuffer at this address, use it. Otherwise go back to our old hack and display framebuffer 0
|
||||
// Displays are hard I really don't want to try implementing them because getting a fast solution is terrible
|
||||
OpenGL::Texture& tex = framebuffer.has_value() ? framebuffer.value().get().texture : colourBufferCache[0].texture;
|
||||
OpenGL::DebugScope scope("DisplayTransfer inputAddr 0x%08X outputAddr 0x%08X inputWidth %d outputWidth %d inputHeight %d outputHeight %d",
|
||||
inputAddr, outputAddr, inputWidth, outputWidth, inputHeight, outputHeight);
|
||||
|
||||
tex.bind();
|
||||
screenFramebuffer.bind(OpenGL::DrawFramebuffer);
|
||||
auto srcFramebuffer = getColourBuffer(inputAddr, inputFormat, inputWidth, outputHeight);
|
||||
Math::Rect<u32> srcRect = srcFramebuffer->getSubRect(inputAddr, outputWidth, outputHeight);
|
||||
|
||||
gl.disableBlend();
|
||||
gl.disableLogicOp();
|
||||
gl.disableDepth();
|
||||
gl.disableScissor();
|
||||
gl.disableStencil();
|
||||
gl.setColourMask(true, true, true, true);
|
||||
gl.useProgram(displayProgram);
|
||||
gl.bindVAO(dummyVAO);
|
||||
|
||||
gl.disableClipPlane(0);
|
||||
gl.disableClipPlane(1);
|
||||
|
||||
// Hack: Detect whether we are writing to the top or bottom screen by checking output gap and drawing to the proper part of the output texture
|
||||
// We consider output gap == 320 to mean bottom, and anything else to mean top
|
||||
if (outputGap == 320) {
|
||||
OpenGL::setViewport(40, 0, 320, 240); // Bottom screen viewport
|
||||
} else {
|
||||
OpenGL::setViewport(0, 240, 400, 240); // Top screen viewport
|
||||
if (verticalFlip) {
|
||||
std::swap(srcRect.bottom, srcRect.top);
|
||||
}
|
||||
|
||||
OpenGL::draw(OpenGL::TriangleStrip, 4); // Actually draw our 3DS screen
|
||||
// Apply scaling for the destination rectangle.
|
||||
if (scaling == PICA::Scaling::X || scaling == PICA::Scaling::XY) {
|
||||
outputWidth >>= 1;
|
||||
}
|
||||
|
||||
if (scaling == PICA::Scaling::XY) {
|
||||
outputHeight >>= 1;
|
||||
}
|
||||
|
||||
auto destFramebuffer = getColourBuffer(outputAddr, outputFormat, outputWidth, outputHeight);
|
||||
Math::Rect<u32> destRect = destFramebuffer->getSubRect(outputAddr, outputWidth, outputHeight);
|
||||
|
||||
if (inputWidth != outputWidth) {
|
||||
// Helpers::warn("Strided display transfer is not handled correctly!\n");
|
||||
}
|
||||
|
||||
// Blit the framebuffers
|
||||
srcFramebuffer->fbo.bind(OpenGL::ReadFramebuffer);
|
||||
destFramebuffer->fbo.bind(OpenGL::DrawFramebuffer);
|
||||
gl.disableScissor();
|
||||
|
||||
glBlitFramebuffer(
|
||||
srcRect.left, srcRect.bottom, srcRect.right, srcRect.top, destRect.left, destRect.bottom, destRect.right, destRect.top, GL_COLOR_BUFFER_BIT,
|
||||
GL_LINEAR
|
||||
);
|
||||
}
|
||||
|
||||
void RendererGL::textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) {
|
||||
// Texture copy size is aligned to 16 byte units
|
||||
const u32 copySize = totalBytes & ~0xf;
|
||||
if (copySize == 0) {
|
||||
printf("TextureCopy total bytes less than 16!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// The width and gap are provided in 16-byte units.
|
||||
const u32 inputWidth = (inputSize & 0xffff) << 4;
|
||||
const u32 inputGap = (inputSize >> 16) << 4;
|
||||
const u32 outputWidth = (outputSize & 0xffff) << 4;
|
||||
const u32 outputGap = (outputSize >> 16) << 4;
|
||||
|
||||
OpenGL::DebugScope scope("TextureCopy inputAddr 0x%08X outputAddr 0x%08X totalBytes %d inputWidth %d inputGap %d outputWidth %d outputGap %d",
|
||||
inputAddr, outputAddr, totalBytes, inputWidth, inputGap, outputWidth, outputGap);
|
||||
|
||||
if (inputGap != 0 || outputGap != 0) {
|
||||
// Helpers::warn("Strided texture copy\n");
|
||||
}
|
||||
if (inputWidth != outputWidth) {
|
||||
Helpers::warn("Input width does not match output width, cannot accelerate texture copy!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Texture copy is a raw data copy in PICA, which means no format or tiling information is provided to the engine.
|
||||
// Depending if the target surface is linear or tiled, games set inputWidth to either the width of the texture or
|
||||
// the width multiplied by eight (because tiles are stored linearly in memory).
|
||||
// To properly accelerate this we must examine each surface individually. For now we assume the most common case
|
||||
// of tiled surface with RGBA8 format. If our assumption does not hold true, we abort the texture copy as inserting
|
||||
// that surface is not correct.
|
||||
|
||||
// We assume the source surface is tiled and RGBA8. inputWidth is in bytes so divide it
|
||||
// by eight * sizePerPixel(RGBA8) to convert it to a useable width.
|
||||
const u32 bpp = sizePerPixel(PICA::ColorFmt::RGBA8);
|
||||
const u32 copyStride = (inputWidth + inputGap) / (8 * bpp);
|
||||
const u32 copyWidth = inputWidth / (8 * bpp);
|
||||
|
||||
// inputHeight/outputHeight are typically set to zero so they cannot be used to get the height of the copy region
|
||||
// in contrast to display transfer. Compute height manually by dividing the copy size with the copy width. The result
|
||||
// is the number of vertical tiles so multiply that by eight to get the actual copy height.
|
||||
const u32 copyHeight = (copySize / inputWidth) * 8;
|
||||
|
||||
// Find the source surface.
|
||||
auto srcFramebuffer = getColourBuffer(inputAddr, PICA::ColorFmt::RGBA8, copyStride, copyHeight, false);
|
||||
if (!srcFramebuffer) {
|
||||
static int shutUpCounter = 0; // Don't want to spam the console too much, so shut up after 5 times
|
||||
|
||||
if (shutUpCounter < 5) {
|
||||
shutUpCounter++;
|
||||
printf("RendererGL::TextureCopy failed to locate src framebuffer!\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Math::Rect<u32> srcRect = srcFramebuffer->getSubRect(inputAddr, copyWidth, copyHeight);
|
||||
|
||||
// Assume the destination surface has the same format. Unless the surfaces have the same block width,
|
||||
// texture copy does not make sense.
|
||||
auto destFramebuffer = getColourBuffer(outputAddr, srcFramebuffer->format, copyWidth, copyHeight);
|
||||
Math::Rect<u32> destRect = destFramebuffer->getSubRect(outputAddr, copyWidth, copyHeight);
|
||||
|
||||
// Blit the framebuffers
|
||||
srcFramebuffer->fbo.bind(OpenGL::ReadFramebuffer);
|
||||
destFramebuffer->fbo.bind(OpenGL::DrawFramebuffer);
|
||||
gl.disableScissor();
|
||||
|
||||
glBlitFramebuffer(
|
||||
srcRect.left, srcRect.bottom, srcRect.right, srcRect.top, destRect.left, destRect.bottom, destRect.right, destRect.top, GL_COLOR_BUFFER_BIT,
|
||||
GL_LINEAR
|
||||
);
|
||||
}
|
||||
|
||||
std::optional<ColourBuffer> RendererGL::getColourBuffer(u32 addr, PICA::ColorFmt format, u32 width, u32 height, bool createIfnotFound) {
|
||||
// Try to find an already existing buffer that contains the provided address
|
||||
// This is a more relaxed check compared to getColourFBO as display transfer/texcopy may refer to
|
||||
// subrect of a surface and in case of texcopy we don't know the format of the surface.
|
||||
auto buffer = colourBufferCache.findFromAddress(addr);
|
||||
if (buffer.has_value()) {
|
||||
return buffer.value().get();
|
||||
}
|
||||
|
||||
if (!createIfnotFound) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Otherwise create and cache a new buffer.
|
||||
ColourBuffer sampleBuffer(addr, format, width, height);
|
||||
return colourBufferCache.add(sampleBuffer);
|
||||
}
|
||||
|
||||
void RendererGL::screenshot(const std::string& name) {
|
||||
|
|
|
@ -9,6 +9,11 @@ void Texture::allocate() {
|
|||
texture.create(size.u(), size.v(), GL_RGBA8);
|
||||
texture.bind();
|
||||
|
||||
#ifdef GPU_DEBUG_INFO
|
||||
const auto name = Helpers::format("Surface %dx%d %s from 0x%08X", size.x(), size.y(), PICA::textureFormatToString(format), location);
|
||||
OpenGL::setObjectLabel(GL_TEXTURE, texture.handle(), name.c_str());
|
||||
#endif
|
||||
|
||||
setNewConfig(config);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "renderer_null/renderer_null.hpp"
|
||||
|
||||
RendererNull::RendererNull(GPU& gpu, const std::array<u32, regNum>& internalRegs) : Renderer(gpu, internalRegs) {}
|
||||
RendererNull::RendererNull(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs)
|
||||
: Renderer(gpu, internalRegs, externalRegs) {}
|
||||
RendererNull::~RendererNull() {}
|
||||
|
||||
void RendererNull::reset() {}
|
||||
|
@ -8,5 +9,6 @@ void RendererNull::display() {}
|
|||
void RendererNull::initGraphicsContext(SDL_Window* window) {}
|
||||
void RendererNull::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) {}
|
||||
void RendererNull::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) {}
|
||||
void RendererNull::textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) {}
|
||||
void RendererNull::drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) {}
|
||||
void RendererNull::screenshot(const std::string& name) {}
|
||||
void RendererNull::screenshot(const std::string& name) {}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "renderer_sw/renderer_sw.hpp"
|
||||
|
||||
RendererSw::RendererSw(GPU& gpu, const std::array<u32, regNum>& internalRegs) : Renderer(gpu, internalRegs) {}
|
||||
RendererSw::RendererSw(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs)
|
||||
: Renderer(gpu, internalRegs, externalRegs) {}
|
||||
RendererSw::~RendererSw() {}
|
||||
|
||||
void RendererSw::reset() { printf("RendererSW: Unimplemented reset call\n"); }
|
||||
|
@ -13,8 +14,12 @@ void RendererSw::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u
|
|||
printf("RendererSW: Unimplemented displayTransfer call\n");
|
||||
}
|
||||
|
||||
void RendererSw::textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) {
|
||||
printf("RendererSW: Unimplemented textureCopy call\n");
|
||||
}
|
||||
|
||||
void RendererSw::drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) {
|
||||
printf("RendererSW: Unimplemented drawVertices call\n");
|
||||
}
|
||||
|
||||
void RendererSw::screenshot(const std::string& name) { printf("RendererSW: Unimplemented screenshot call\n"); }
|
||||
void RendererSw::screenshot(const std::string& name) { printf("RendererSW: Unimplemented screenshot call\n"); }
|
||||
|
|
|
@ -200,7 +200,8 @@ vk::Result RendererVK::recreateSwapchain(vk::SurfaceKHR surface, vk::Extent2D sw
|
|||
return vk::Result::eSuccess;
|
||||
}
|
||||
|
||||
RendererVK::RendererVK(GPU& gpu, const std::array<u32, regNum>& internalRegs) : Renderer(gpu, internalRegs) {}
|
||||
RendererVK::RendererVK(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs)
|
||||
: Renderer(gpu, internalRegs, externalRegs) {}
|
||||
|
||||
RendererVK::~RendererVK() {}
|
||||
|
||||
|
@ -541,6 +542,8 @@ void RendererVK::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 co
|
|||
|
||||
void RendererVK::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) {}
|
||||
|
||||
void RendererVK::textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) {}
|
||||
|
||||
void RendererVK::drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) {}
|
||||
|
||||
void RendererVK::screenshot(const std::string& name) {}
|
||||
void RendererVK::screenshot(const std::string& name) {}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -2,6 +2,8 @@
|
|||
#include "ipc.hpp"
|
||||
#include "kernel.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace APTCommands {
|
||||
enum : u32 {
|
||||
GetLockHandle = 0x00010040,
|
||||
|
@ -114,10 +116,24 @@ void APTService::appletUtility(u32 messagePointer) {
|
|||
u32 outputSize = mem.read32(messagePointer + 12);
|
||||
u32 inputPointer = mem.read32(messagePointer + 20);
|
||||
|
||||
log("APT::AppletUtility(utility = %d, input size = %x, output size = %x, inputPointer = %08X) (Stubbed)\n", utility, inputSize,
|
||||
outputSize, inputPointer);
|
||||
log("APT::AppletUtility(utility = %d, input size = %x, output size = %x, inputPointer = %08X) (Stubbed)\n", utility, inputSize, outputSize,
|
||||
inputPointer);
|
||||
|
||||
std::vector<u8> out(outputSize);
|
||||
const u32 outputBuffer = mem.read32(messagePointer + 0x104);
|
||||
|
||||
if (outputSize >= 1 && utility == 6) {
|
||||
// TryLockTransition expects a bool indicating success in the output buffer. Set it to true to avoid games panicking (Thanks to Citra)
|
||||
out[0] = true;
|
||||
}
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x4B, 2, 2));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write32(messagePointer + 8, Result::Success);
|
||||
|
||||
for (u32 i = 0; i < outputSize; i++) {
|
||||
mem.write8(outputBuffer + i, out[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void APTService::getAppletInfo(u32 messagePointer) {
|
||||
|
|
|
@ -6,15 +6,24 @@ 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
|
||||
GetStorageEntryInfo = 0x00300000,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -26,15 +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);
|
||||
|
@ -48,17 +67,51 @@ void BOSSService::initializeSession(u32 messagePointer) {
|
|||
}
|
||||
|
||||
void BOSSService::getOptoutFlag(u32 messagePointer) {
|
||||
log("BOSS::getOptoutFlag\n");
|
||||
log("BOSS::GetOptoutFlag\n");
|
||||
mem.write32(messagePointer, IPC::responseHeader(0xA, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write8(messagePointer + 8, optoutFlag);
|
||||
}
|
||||
|
||||
void BOSSService::getTaskState(u32 messagePointer) {
|
||||
const u32 taskIDBufferSize = mem.read32(messagePointer + 4);
|
||||
const u32 taskIDDataPointer = mem.read32(messagePointer + 16);
|
||||
log("BOSS::GetTaskStatus (task buffer size: %08X, task data pointer: %08X) (stubbed)\n", taskIDBufferSize, taskIDDataPointer);
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x20, 2, 2));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write8(messagePointer + 8, 0); // TaskStatus: Report the task finished successfully
|
||||
mem.write32(messagePointer + 12, 0); // Current state value for task PropertyID 0x4
|
||||
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));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write32(messagePointer + 8, 0); // Seems to be unknown what this is?
|
||||
mem.write32(messagePointer + 8, 0);
|
||||
}
|
||||
|
||||
void BOSSService::getTaskIdList(u32 messagePointer) {
|
||||
|
@ -76,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));
|
||||
|
@ -84,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.
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -134,7 +134,7 @@ void CFGService::secureInfoGetRegion(u32 messagePointer) {
|
|||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x2, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write32(messagePointer + 8, static_cast<u32>(Regions::USA)); // TODO: Detect the game region and report it
|
||||
mem.write32(messagePointer + 8, static_cast<u32>(mem.getConsoleRegion()));
|
||||
}
|
||||
|
||||
void CFGService::genUniqueConsoleHash(u32 messagePointer) {
|
||||
|
@ -153,7 +153,16 @@ void CFGService::genUniqueConsoleHash(u32 messagePointer) {
|
|||
// Used for market restriction-related stuff
|
||||
void CFGService::getRegionCanadaUSA(u32 messagePointer) {
|
||||
log("CFG::GetRegionCanadaUSA\n");
|
||||
const u8 ret = (country == CountryCodes::US || country == CountryCodes::CA) ? 1 : 0;
|
||||
bool regionUSA = mem.getConsoleRegion() == Regions::USA;
|
||||
u8 ret;
|
||||
|
||||
// First, this function checks that the console region is 1 (USA). If not then it instantly returns 0
|
||||
// Then it checks whether the country is US or Canda. If yes it returns 1, else it returns 0.
|
||||
if (!regionUSA) {
|
||||
ret = 0;
|
||||
} else {
|
||||
ret = (country == CountryCodes::US || country == CountryCodes::CA) ? 1 : 0;
|
||||
}
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x4, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
|
|
BIN
src/core/services/fonts/CitraSharedFontUSRelocated.bin
Normal file
BIN
src/core/services/fonts/CitraSharedFontUSRelocated.bin
Normal file
Binary file not shown.
|
@ -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);
|
||||
}
|
|
@ -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");
|
||||
|
||||
|
@ -499,6 +537,12 @@ void FSService::controlArchive(u32 messagePointer) {
|
|||
case 0: // Commit save data changes. Shouldn't need us to do anything
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
break;
|
||||
|
||||
case 1: // Retrieves a file's last-modified timestamp. Seen in DDLC, stubbed for the moment
|
||||
Helpers::warn("FS::ControlArchive: Tried to retrieve a file's last-modified timestamp");
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
break;
|
||||
|
||||
default:
|
||||
Helpers::panic("Unimplemented action for ControlArchive (action = %X)\n", action);
|
||||
break;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "services/gsp_gpu.hpp"
|
||||
#include "PICA/regs.hpp"
|
||||
#include "ipc.hpp"
|
||||
#include "kernel.hpp"
|
||||
|
||||
|
@ -10,6 +11,7 @@ namespace ServiceCommands {
|
|||
RegisterInterruptRelayQueue = 0x00130042,
|
||||
WriteHwRegs = 0x00010082,
|
||||
WriteHwRegsWithMask = 0x00020084,
|
||||
SetBufferSwap = 0x00050200,
|
||||
FlushDataCache = 0x00080082,
|
||||
SetLCDForceBlack = 0x000B0040,
|
||||
TriggerCmdReqQueue = 0x000C0000,
|
||||
|
@ -49,12 +51,13 @@ void GPUService::handleSyncRequest(u32 messagePointer) {
|
|||
case ServiceCommands::RegisterInterruptRelayQueue: registerInterruptRelayQueue(messagePointer); break;
|
||||
case ServiceCommands::SaveVramSysArea: saveVramSysArea(messagePointer); break;
|
||||
case ServiceCommands::SetAxiConfigQoSMode: setAxiConfigQoSMode(messagePointer); break;
|
||||
case ServiceCommands::SetBufferSwap: setBufferSwap(messagePointer); break;
|
||||
case ServiceCommands::SetInternalPriorities: setInternalPriorities(messagePointer); break;
|
||||
case ServiceCommands::SetLCDForceBlack: setLCDForceBlack(messagePointer); break;
|
||||
case ServiceCommands::StoreDataCache: storeDataCache(messagePointer); break;
|
||||
case ServiceCommands::WriteHwRegs: writeHwRegs(messagePointer); break;
|
||||
case ServiceCommands::WriteHwRegsWithMask: writeHwRegsWithMask(messagePointer); break;
|
||||
; default: Helpers::panic("GPU service requested. Command: %08X\n", command);
|
||||
default: Helpers::panic("GPU service requested. Command: %08X\n", command);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,15 +131,12 @@ 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
|
||||
|
||||
constexpr u32 FBInfoSize = 0x40;
|
||||
// TODO: Offset depends on GSP thread being triggered
|
||||
u8* info = &sharedMem[0x200 + screen * FBInfoSize];
|
||||
u8& dirtyFlag = info[1];
|
||||
FramebufferUpdate* update = reinterpret_cast<FramebufferUpdate*>(&sharedMem[0x200 + screen * sizeof(FramebufferUpdate)]);
|
||||
|
||||
if (dirtyFlag & 1) {
|
||||
// TODO: Submit buffer info here
|
||||
dirtyFlag &= ~1;
|
||||
if (update->dirtyFlag & 1) {
|
||||
setBufferSwapImpl(screen, update->framebufferInfo[update->index]);
|
||||
update->dirtyFlag &= ~1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -224,7 +224,7 @@ void GPUService::flushDataCache(u32 messagePointer) {
|
|||
u32 address = mem.read32(messagePointer + 4);
|
||||
u32 size = mem.read32(messagePointer + 8);
|
||||
u32 processHandle = handle = mem.read32(messagePointer + 16);
|
||||
log("GSP::GPU::FlushDataCache(address = %08X, size = %X, process = %X\n", address, size, processHandle);
|
||||
log("GSP::GPU::FlushDataCache(address = %08X, size = %X, process = %X)\n", address, size, processHandle);
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x8, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
|
@ -234,7 +234,7 @@ void GPUService::storeDataCache(u32 messagePointer) {
|
|||
u32 address = mem.read32(messagePointer + 4);
|
||||
u32 size = mem.read32(messagePointer + 8);
|
||||
u32 processHandle = handle = mem.read32(messagePointer + 16);
|
||||
log("GSP::GPU::StoreDataCache(address = %08X, size = %X, process = %X\n", address, size, processHandle);
|
||||
log("GSP::GPU::StoreDataCache(address = %08X, size = %X, process = %X)\n", address, size, processHandle);
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x1F, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
|
@ -265,6 +265,24 @@ void GPUService::setAxiConfigQoSMode(u32 messagePointer) {
|
|||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void GPUService::setBufferSwap(u32 messagePointer) {
|
||||
FramebufferInfo info{};
|
||||
const u32 screenId = mem.read32(messagePointer + 4); // Selects either PDC0 or PDC1
|
||||
info.activeFb = mem.read32(messagePointer + 8);
|
||||
info.leftFramebufferVaddr = mem.read32(messagePointer + 12);
|
||||
info.rightFramebufferVaddr = mem.read32(messagePointer + 16);
|
||||
info.stride = mem.read32(messagePointer + 20);
|
||||
info.format = mem.read32(messagePointer + 24);
|
||||
info.displayFb = mem.read32(messagePointer + 28); // Selects either framebuffer A or B
|
||||
|
||||
log("GSP::GPU::SetBufferSwap\n");
|
||||
Helpers::panic("Untested GSP::GPU::SetBufferSwap call");
|
||||
|
||||
setBufferSwapImpl(screenId, info);
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x05, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
// Seems to also be completely undocumented
|
||||
void GPUService::setInternalPriorities(u32 messagePointer) {
|
||||
log("GSP::GPU::SetInternalPriorities\n");
|
||||
|
@ -287,7 +305,7 @@ void GPUService::processCommandBuffer() {
|
|||
log("Processing %d GPU commands\n", commandsLeft);
|
||||
|
||||
while (commandsLeft != 0) {
|
||||
u32 cmdID = cmd[0] & 0xff;
|
||||
const u32 cmdID = cmd[0] & 0xff;
|
||||
switch (cmdID) {
|
||||
case GXCommands::ProcessCommandList: processCommandList(cmd); break;
|
||||
case GXCommands::MemoryFill: memoryFill(cmd); break;
|
||||
|
@ -379,12 +397,47 @@ void GPUService::flushCacheRegions(u32* cmd) {
|
|||
log("GSP::GPU::FlushCacheRegions (Stubbed)\n");
|
||||
}
|
||||
|
||||
void GPUService::setBufferSwapImpl(u32 screenId, const FramebufferInfo& info) {
|
||||
using namespace PICA::ExternalRegs;
|
||||
|
||||
static constexpr std::array<u32, 8> fbAddresses = {
|
||||
Framebuffer0AFirstAddr,
|
||||
Framebuffer0BFirstAddr,
|
||||
Framebuffer1AFirstAddr,
|
||||
Framebuffer1BFirstAddr,
|
||||
Framebuffer0ASecondAddr,
|
||||
Framebuffer0BSecondAddr,
|
||||
Framebuffer1ASecondAddr,
|
||||
Framebuffer1BSecondAddr,
|
||||
};
|
||||
|
||||
auto& regs = gpu.getExtRegisters();
|
||||
|
||||
const u32 fbIndex = info.activeFb * 4 + screenId * 2;
|
||||
regs[fbAddresses[fbIndex]] = VaddrToPaddr(info.leftFramebufferVaddr);
|
||||
regs[fbAddresses[fbIndex + 1]] = VaddrToPaddr(info.rightFramebufferVaddr);
|
||||
|
||||
static constexpr std::array<u32, 6> configAddresses = {
|
||||
Framebuffer0Config,
|
||||
Framebuffer0Select,
|
||||
Framebuffer0Stride,
|
||||
Framebuffer1Config,
|
||||
Framebuffer1Select,
|
||||
Framebuffer1Stride,
|
||||
};
|
||||
|
||||
const u32 configIndex = screenId * 3;
|
||||
regs[configAddresses[configIndex]] = info.format;
|
||||
regs[configAddresses[configIndex + 1]] = info.displayFb;
|
||||
regs[configAddresses[configIndex + 2]] = info.stride;
|
||||
}
|
||||
|
||||
// Actually send command list (aka display list) to GPU
|
||||
void GPUService::processCommandList(u32* cmd) {
|
||||
const u32 address = cmd[1] & ~7; // Buffer address
|
||||
const u32 size = cmd[2] & ~3; // Buffer size in bytes
|
||||
const bool updateGas = cmd[3] == 1; // Update gas additive blend results (0 = don't update, 1 = update)
|
||||
const bool flushBuffer = cmd[7] == 1; // Flush buffer (0 = don't flush, 1 = flush)
|
||||
[[maybe_unused]] const bool updateGas = cmd[3] == 1; // Update gas additive blend results (0 = don't update, 1 = update)
|
||||
[[maybe_unused]] const bool flushBuffer = cmd[7] == 1; // Flush buffer (0 = don't flush, 1 = flush)
|
||||
|
||||
log("GPU::GSP::processCommandList. Address: %08X, size in bytes: %08X\n", address, size);
|
||||
gpu.startCommandList(address, size);
|
||||
|
@ -394,7 +447,15 @@ void GPUService::processCommandList(u32* cmd) {
|
|||
// TODO: Emulate the transfer engine & its registers
|
||||
// Then this can be emulated by just writing the appropriate values there
|
||||
void GPUService::triggerTextureCopy(u32* cmd) {
|
||||
Helpers::warn("GSP::GPU::TriggerTextureCopy (unimplemented)\n");
|
||||
const u32 inputAddr = VaddrToPaddr(cmd[1]);
|
||||
const u32 outputAddr = VaddrToPaddr(cmd[2]);
|
||||
const u32 totalBytes = cmd[3];
|
||||
const u32 inputSize = cmd[4];
|
||||
const u32 outputSize = cmd[5];
|
||||
const u32 flags = cmd[6];
|
||||
|
||||
log("GSP::GPU::TriggerTextureCopy (Stubbed)\n");
|
||||
gpu.textureCopy(inputAddr, outputAddr, totalBytes, inputSize, outputSize, flags);
|
||||
// This uses the transfer engine and thus needs to fire a PPF interrupt.
|
||||
// NSMB2 relies on this
|
||||
requestInterrupt(GPUInterrupt::PPF);
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
27
src/core/services/mcu/mcu_hwc.cpp
Normal file
27
src/core/services/mcu/mcu_hwc.cpp
Normal 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);
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
15
src/core/services/news_u.cpp
Normal file
15
src/core/services/news_u.cpp
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -3,9 +3,11 @@
|
|||
|
||||
namespace PTMCommands {
|
||||
enum : u32 {
|
||||
GetAdapterState = 0x00050000,
|
||||
GetBatteryLevel = 0x00070000,
|
||||
GetStepHistory = 0x000B00C2,
|
||||
GetTotalStepCount = 0x000C0000,
|
||||
ConfigureNew3DSCPU = 0x08180040
|
||||
ConfigureNew3DSCPU = 0x08180040,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -15,12 +17,30 @@ void PTMService::handleSyncRequest(u32 messagePointer) {
|
|||
const u32 command = mem.read32(messagePointer);
|
||||
switch (command) {
|
||||
case PTMCommands::ConfigureNew3DSCPU: configureNew3DSCPU(messagePointer); break;
|
||||
case PTMCommands::GetAdapterState: getAdapterState(messagePointer); break;
|
||||
case PTMCommands::GetBatteryLevel: getBatteryLevel(messagePointer); break;
|
||||
case PTMCommands::GetStepHistory: getStepHistory(messagePointer); break;
|
||||
case PTMCommands::GetTotalStepCount: getTotalStepCount(messagePointer); break;
|
||||
default: Helpers::panic("PTM service requested. Command: %08X\n", command);
|
||||
}
|
||||
}
|
||||
|
||||
void PTMService::getAdapterState(u32 messagePointer) {
|
||||
log("PTM::GetAdapterState\n");
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x5, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write8(messagePointer + 8, config.chargerPlugged ? 1 : 0);
|
||||
}
|
||||
|
||||
void PTMService::getBatteryLevel(u32 messagePointer) {
|
||||
log("PTM::GetBatteryLevel");
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x7, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write8(messagePointer + 8, batteryPercentToLevel(config.batteryPercentage));
|
||||
}
|
||||
|
||||
void PTMService::getStepHistory(u32 messagePointer) {
|
||||
log("PTM::GetStepHistory [stubbed]\n");
|
||||
mem.write32(messagePointer, IPC::responseHeader(0xB, 1, 2));
|
||||
|
|
|
@ -5,10 +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), 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;
|
||||
|
||||
|
@ -31,11 +32,16 @@ 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();
|
||||
y2r.reset();
|
||||
|
||||
notificationSemaphore = std::nullopt;
|
||||
|
@ -95,7 +101,8 @@ static std::map<std::string, Handle> serviceMap = {
|
|||
{ "boss:U", KernelHandles::BOSS },
|
||||
{ "cam:u", KernelHandles::CAM },
|
||||
{ "cecd:u", KernelHandles::CECD },
|
||||
{ "cfg:u", KernelHandles::CFG },
|
||||
{ "cfg:u", KernelHandles::CFG_U },
|
||||
{ "cfg:i", KernelHandles::CFG_I },
|
||||
{ "dlp:SRVR", KernelHandles::DLP_SRVR },
|
||||
{ "dsp::DSP", KernelHandles::DSP },
|
||||
{ "hid:USER", KernelHandles::HID },
|
||||
|
@ -106,12 +113,16 @@ 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
|
||||
{ "ptm:sysm", KernelHandles::PTM },
|
||||
{ "soc:U", KernelHandles::SOC },
|
||||
{ "ssl:C", KernelHandles::SSL },
|
||||
{ "y2r:u", KernelHandles::Y2R }
|
||||
};
|
||||
// clang-format on
|
||||
|
@ -175,25 +186,29 @@ 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: cfg.handleSyncRequest(messagePointer); break;
|
||||
case KernelHandles::CFG_U: cfg.handleSyncRequest(messagePointer); break;
|
||||
case KernelHandles::DLP_SRVR: dlp_srvr.handleSyncRequest(messagePointer); break;
|
||||
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;
|
||||
case KernelHandles::Y2R: y2r.handleSyncRequest(messagePointer); break;
|
||||
default: Helpers::panic("Sent IPC message to unknown service %08X\n Command: %08X", handle, mem.read32(messagePointer));
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
33
src/core/services/soc.cpp
Normal file
33
src/core/services/soc.cpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
#include "services/soc.hpp"
|
||||
|
||||
#include "ipc.hpp"
|
||||
#include "result/result.hpp"
|
||||
|
||||
namespace SOCCommands {
|
||||
enum : u32 {
|
||||
InitializeSockets = 0x00010044,
|
||||
};
|
||||
}
|
||||
|
||||
void SOCService::reset() { initialized = false; }
|
||||
|
||||
void SOCService::handleSyncRequest(u32 messagePointer) {
|
||||
const u32 command = mem.read32(messagePointer);
|
||||
switch (command) {
|
||||
case SOCCommands::InitializeSockets: initializeSockets(messagePointer); break;
|
||||
default: Helpers::panic("SOC service requested. Command: %08X\n", command);
|
||||
}
|
||||
}
|
||||
|
||||
void SOCService::initializeSockets(u32 messagePointer) {
|
||||
const u32 memoryBlockSize = mem.read32(messagePointer + 4);
|
||||
const Handle sharedMemHandle = mem.read32(messagePointer + 20);
|
||||
log("SOC::InitializeSockets (memory block size = %08X, shared mem handle = %08X)\n", memoryBlockSize, sharedMemHandle);
|
||||
|
||||
// TODO: Does double initialization return an error code?
|
||||
// TODO: Implement the rest of this stuff when it's time to do online. Also implement error checking for the size, shared mem handle, and so on
|
||||
initialized = true;
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x01, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
63
src/core/services/ssl.cpp
Normal file
63
src/core/services/ssl.cpp
Normal file
|
@ -0,0 +1,63 @@
|
|||
#include "ipc.hpp"
|
||||
#include "result/result.hpp"
|
||||
#include "services/ssl.hpp"
|
||||
|
||||
namespace SSLCommands {
|
||||
enum : u32 {
|
||||
Initialize = 0x00010002,
|
||||
GenerateRandomData = 0x00110042,
|
||||
};
|
||||
}
|
||||
|
||||
void SSLService::reset() {
|
||||
initialized = false;
|
||||
|
||||
// Use the default seed on reset to avoid funny bugs
|
||||
rng.seed();
|
||||
}
|
||||
|
||||
void SSLService::handleSyncRequest(u32 messagePointer) {
|
||||
const u32 command = mem.read32(messagePointer);
|
||||
switch (command) {
|
||||
case SSLCommands::Initialize: initialize(messagePointer); break;
|
||||
case SSLCommands::GenerateRandomData: generateRandomData(messagePointer); break;
|
||||
default: Helpers::panic("SSL service requested. Command: %08X\n", command);
|
||||
}
|
||||
}
|
||||
|
||||
void SSLService::initialize(u32 messagePointer) {
|
||||
log("SSL::Initialize\n");
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x01, 1, 0));
|
||||
|
||||
if (initialized) {
|
||||
Helpers::warn("SSL service initialized twice");
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
rng.seed(std::random_device()()); // Seed rng via std::random_device
|
||||
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void SSLService::generateRandomData(u32 messagePointer) {
|
||||
const u32 size = mem.read32(messagePointer + 4);
|
||||
const u32 output = mem.read32(messagePointer + 12);
|
||||
log("SSL::GenerateRandomData (out = %08X, size = %08X)\n", output, size);
|
||||
|
||||
// TODO: This might be a biiit slow, might want to make it write in word quantities
|
||||
u32 data;
|
||||
|
||||
for (u32 i = 0; i < size; i++) {
|
||||
// We don't have an available random value since we're on a multiple of 4 bytes and our Twister is 32-bit, generate a new one from the Mersenne Twister
|
||||
if ((i & 3) == 0) {
|
||||
data = rng();
|
||||
}
|
||||
|
||||
mem.write8(output + i, u8(data));
|
||||
// Shift data by 8 to get the next byte
|
||||
data >>= 8;
|
||||
}
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x11, 1, 2));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
|
||||
Renderer::Renderer(GPU& gpu, const std::array<u32, regNum>& internalRegs) : gpu(gpu), regs(internalRegs) {}
|
||||
Renderer::Renderer(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs)
|
||||
: gpu(gpu), regs(internalRegs), externalRegs(externalRegs) {}
|
||||
Renderer::~Renderer() {}
|
||||
|
||||
std::optional<RendererType> Renderer::typeFromString(std::string inString) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue