Merge pull request #1 from wheremyfoodat/Sync-Objects

Make the emulator actually boot games
This commit is contained in:
wheremyfoodat 2023-05-16 00:03:53 +03:00 committed by GitHub
commit b01cbc588a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
121 changed files with 295783 additions and 1016 deletions

View file

@ -26,7 +26,7 @@ jobs:
- name: Configure CMake
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
- name: Build
# Build your program with the given configuration

View file

@ -19,13 +19,17 @@ jobs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Fetch submodules
run: git submodule update --init --recursive
- name: Install LLVM # MacOS comes with "AppleClang" instead of regular Clang, and it can't build the project because no proper C++20
run: brew install llvm
- name: Configure CMake
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=/usr/local/opt/llvm/bin/clang -DCMAKE_CXX_COMPILER=/usr/local/opt/llvm/bin/clang++
- name: Build
# Build your program with the given configuration

View file

@ -1,23 +1,28 @@
cmake_minimum_required(VERSION 3.10)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fbracket-depth=4096")
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 12)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fbracket-depth=4096")
endif()
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
project(Alber)
set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
include_directories(${PROJECT_SOURCE_DIR}/include/)
include_directories(${PROJECT_SOURCE_DIR}/include/kernel)
include_directories (${FMT_INCLUDE_DIR})
include_directories(third_party/boost/)
include_directories(third_party/elfio/)
include_directories(third_party/gl3w/)
include_directories(third_party/imgui/)
include_directories(third_party/dynarmic/src)
include_directories(third_party/cryptopp/)
include_directories(third_party/result/include/)
add_compile_definitions(NOMINMAX)
add_compile_definitions(SDL_MAIN_HANDLED)
@ -33,36 +38,61 @@ set(Boost_NO_SYSTEM_PATHS ON)
add_library(boost INTERFACE)
target_include_directories(boost SYSTEM INTERFACE ${Boost_INCLUDE_DIR})
set(CRYPTOPP_BUILD_TESTING OFF)
add_subdirectory(third_party/cryptopp)
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "x86-64")
# Check for x64
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "x86-64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
set(HOST_X64 TRUE)
else()
set(HOST_X64 FALSE)
endif()
# Check for arm64
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64")
set(HOST_ARM64 TRUE)
else()
set(HOST_ARM64 FALSE)
endif()
if(HOST_X64 OR HOST_ARM64)
set(DYNARMIC_TESTS OFF)
#set(DYNARMIC_NO_BUNDLED_FMT ON)
set(DYNARMIC_FRONTENDS "A32" CACHE STRING "")
add_subdirectory(third_party/dynarmic)
add_compile_definitions(CPU_DYNARMIC)
else()
add_compile_definitions(CPU_KVM)
message(FATAL_ERROR "THIS IS NOT x64 WAIT FOR THE KVM IMPLEMENTATION")
message(FATAL_ERROR "Currently unsupported CPU architecture")
endif()
set(SOURCE_FILES src/main.cpp src/emulator.cpp src/core/CPU/cpu_dynarmic.cpp src/core/memory.cpp)
set(SOURCE_FILES src/main.cpp src/emulator.cpp src/core/CPU/cpu_dynarmic.cpp src/core/CPU/dynarmic_cycles.cpp
src/core/memory.cpp
)
set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limits.cpp
src/core/kernel/memory_management.cpp src/core/kernel/ports.cpp
src/core/kernel/events.cpp src/core/kernel/threads.cpp
src/core/kernel/address_arbiter.cpp src/core/kernel/error.cpp
src/core/kernel/file_operations.cpp
src/core/kernel/file_operations.cpp src/core/kernel/directory_operations.cpp
src/core/kernel/idle_thread.cpp
)
set(SERVICE_SOURCE_FILES src/core/services/service_manager.cpp src/core/services/apt.cpp src/core/services/hid.cpp
src/core/services/fs.cpp src/core/services/gsp_gpu.cpp src/core/services/gsp_lcd.cpp
src/core/services/ndm.cpp src/core/services/dsp.cpp src/core/services/cfg.cpp
src/core/services/ptm.cpp src/core/services/mic.cpp src/core/services/cecd.cpp
src/core/services/ac.cpp src/core/services/am.cpp src/core/services/boss.cpp
src/core/services/frd.cpp src/core/services/nim.cpp src/core/services/shared_font.cpp
src/core/services/y2r.cpp src/core/services/cam.cpp src/core/services/ldr_ro.cpp
src/core/services/act.cpp src/core/services/nfc.cpp
)
set(PICA_SOURCE_FILES src/core/PICA/gpu.cpp src/core/PICA/regs.cpp src/core/PICA/shader_unit.cpp
src/core/PICA/shader_interpreter.cpp src/core/PICA/renderer_opengl.cpp
src/core/PICA/shader_interpreter.cpp
)
set(RENDERER_GL_SOURCE_FILES src/core/renderer_gl/renderer_gl.cpp src/core/renderer_gl/textures.cpp src/core/renderer_gl/etc1.cpp)
set(LOADER_SOURCE_FILES src/core/loader/elf.cpp src/core/loader/ncsd.cpp src/core/loader/ncch.cpp src/core/loader/lz77.cpp)
set(FS_SOURCE_FILES src/core/fs/archive_self_ncch.cpp src/core/fs/archive_save_data.cpp src/core/fs/archive_sdmc.cpp
src/core/fs/archive_ext_save_data.cpp src/core/fs/archive_ncch.cpp
)
set(LOADER_SOURCE_FILES src/core/loader/elf.cpp src/core/loader/ncsd.cpp src/core/loader/ncch.cpp)
set(FS_SOURCE_FILES src/core/fs/archive_ncch.cpp src/core/fs/archive_save_data.cpp src/core/fs/archive_sdmc.cpp)
set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/opengl.hpp include/termcolor.hpp
include/cpu.hpp include/cpu_dynarmic.hpp include/memory.hpp include/kernel/kernel.hpp
@ -73,10 +103,16 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/opengl.hpp inc
include/PICA/gpu.hpp include/PICA/regs.hpp include/services/ndm.hpp
include/PICA/shader.hpp include/PICA/shader_unit.hpp include/PICA/float_types.hpp
include/logger.hpp include/loader/ncch.hpp include/loader/ncsd.hpp include/io_file.hpp
include/loader/lz77.hpp include/fs/archive_base.hpp include/fs/archive_ncch.hpp
include/loader/lz77.hpp include/fs/archive_base.hpp include/fs/archive_self_ncch.hpp
include/services/dsp.hpp include/services/cfg.hpp include/services/region_codes.hpp
include/fs/archive_save_data.hpp include/fs/archive_sdmc.hpp include/services/ptm.hpp
include/services/mic.hpp include/services/cecd.hpp
include/services/mic.hpp include/services/cecd.hpp include/renderer_gl/renderer_gl.hpp
include/renderer_gl/surfaces.hpp include/renderer_gl/surface_cache.hpp include/services/ac.hpp
include/services/am.hpp include/services/boss.hpp include/services/frd.hpp include/services/nim.hpp
include/fs/archive_ext_save_data.hpp include/services/shared_font.hpp include/fs/archive_ncch.hpp
include/renderer_gl/textures.hpp include/colour.hpp include/services/y2r.hpp include/services/cam.hpp
include/services/ldr_ro.hpp include/ipc.hpp include/services/act.hpp include/services/nfc.hpp
include/system_models.hpp
)
set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp
@ -87,7 +123,6 @@ set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp
third_party/gl3w/gl3w.cpp
)
#add_library(Alber ${HEADER_FILES})
source_group("Header Files\\Core" FILES ${HEADER_FILES})
source_group("Source Files\\Core" FILES ${SOURCE_FILES})
source_group("Source Files\\Core\\Filesystem" FILES ${FS_SOURCE_FILES})
@ -95,8 +130,9 @@ source_group("Source Files\\Core\\Kernel" FILES ${KERNEL_SOURCE_FILES})
source_group("Source Files\\Core\\Loader" FILES ${LOADER_SOURCE_FILES})
source_group("Source Files\\Core\\Services" FILES ${SERVICE_SOURCE_FILES})
source_group("Source Files\\Core\\PICA" FILES ${PICA_SOURCE_FILES})
source_group("Source Files\\Core\\OpenGL Renderer" FILES ${RENDERER_GL_SOURCE_FILES})
source_group("Source Files\\Third Party" FILES ${THIRD_PARTY_SOURCE_FILES})
add_executable(Alber ${SOURCE_FILES} ${FS_SOURCE_FILES} ${KERNEL_SOURCE_FILES} ${LOADER_SOURCE_FILES} ${SERVICE_SOURCE_FILES}
${PICA_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES} ${HEADER_FILES})
${PICA_SOURCE_FILES} ${RENDERER_GL_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES} ${HEADER_FILES})
target_link_libraries(Alber PRIVATE dynarmic SDL2-static)

View file

@ -3,12 +3,15 @@
#include "helpers.hpp"
#include "logger.hpp"
#include "memory.hpp"
#include "opengl.hpp"
#include "PICA/float_types.hpp"
#include "PICA/regs.hpp"
#include "PICA/shader_unit.hpp"
#include "renderer_gl/renderer_gl.hpp"
class GPU {
static constexpr u32 regNum = 0x300;
using vec4f = OpenGL::Vector<Floats::f24, 4>;
using Registers = std::array<u32, regNum>;
Memory& mem;
ShaderUnit shaderUnit;
@ -16,42 +19,14 @@ class GPU {
MAKE_LOG_FUNCTION(log, gpuLogger)
static constexpr u32 maxAttribCount = 12; // Up to 12 vertex attributes
static constexpr u32 regNum = 0x300;
static constexpr u32 vramSize = 6_MB;
std::array<u32, regNum> regs; // GPU internal registers
Registers regs; // GPU internal registers
std::array<vec4f, 16> currentAttributes; // Vertex attributes before being passed to the shader
struct Vertex {
OpenGL::vec4 position;
OpenGL::vec4 colour;
};
// Read a value of type T from physical address paddr
// This is necessary because vertex attribute fetching uses physical addresses
template<typename T>
T readPhysical(u32 paddr) {
if (paddr >= PhysicalAddrs::FCRAM && paddr <= PhysicalAddrs::FCRAMEnd) {
u8* fcram = mem.getFCRAM();
u32 index = paddr - PhysicalAddrs::FCRAM;
return *(T*)&fcram[index];
} else {
Helpers::panic("[PICA] Read unimplemented paddr %08X", paddr);
}
}
// Get a pointer of type T* to the data starting from physical address paddr
template<typename T>
T* getPointerPhys(u32 paddr) {
if (paddr >= PhysicalAddrs::FCRAM && paddr <= PhysicalAddrs::FCRAMEnd) {
u8* fcram = mem.getFCRAM();
u32 index = paddr - PhysicalAddrs::FCRAM;
return (T*)&fcram[index];
}
else {
Helpers::panic("[PICA] Pointer to unimplemented paddr %08X", paddr);
}
}
std::array<vec4f, 16> immediateModeAttributes; // Vertex attributes uploaded via immediate mode submission
std::array<Vertex, 3> immediateModeVertices;
uint immediateModeVertIndex;
uint immediateModeAttrIndex; // Index of the immediate mode attribute we're uploading
template <bool indexed>
void drawArrays();
@ -64,12 +39,17 @@ class GPU {
int size = 0; // Bytes per vertex
u32 config1 = 0;
u32 config2 = 0;
u32 componentCount = 0; // Number of components for the attribute
u64 getConfigFull() {
return u64(config1) | (u64(config2) << 32);
}
};
u64 getVertexShaderInputConfig() {
return u64(regs[PICAInternalRegs::VertexShaderInputCfgLow]) | (u64(regs[PICAInternalRegs::VertexShaderInputCfgHigh]) << 32);
}
std::array<AttribInfo, maxAttribCount> attributeInfo; // Info for each of the 12 attributes
u32 totalAttribCount = 0; // Number of vertex attributes to send to VS
u32 fixedAttribMask = 0; // Which attributes are fixed?
@ -78,33 +58,25 @@ class GPU {
u32 fixedAttribCount = 0; // How many attribute components have we written? When we get to 4 the attr will actually get submitted
std::array<u32, 3> fixedAttrBuff; // Buffer to hold fixed attributes in until they get submitted
// OpenGL renderer state
OpenGL::Framebuffer fbo;
OpenGL::Texture fboTexture;
OpenGL::Program triangleProgram;
OpenGL::Program displayProgram;
OpenGL::VertexArray vao;
OpenGL::VertexBuffer vbo;
GLint alphaControlLoc = -1;
u32 oldAlphaControl = 0;
// Dummy VAO/VBO for blitting the final output
OpenGL::VertexArray dummyVAO;
OpenGL::VertexBuffer dummyVBO;
static constexpr u32 vertexBufferSize = 0x1000;
void drawVertices(OpenGL::Primitives primType, Vertex* vertices, u32 count);
// Command processor pointers for GPU command lists
u32* cmdBuffStart = nullptr;
u32* cmdBuffEnd = nullptr;
u32* cmdBuffCurr = nullptr;
Renderer renderer;
Vertex getImmediateModeVertex();
public:
GPU(Memory& mem);
void initGraphicsContext(); // Initialize graphics context
void getGraphicsContext(); // Set up the graphics context for rendering
void display(); // Display the screen contents onto our window
void initGraphicsContext() { renderer.initGraphicsContext(); }
void getGraphicsContext() { renderer.getGraphicsContext(); }
void display() { renderer.display(); }
void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control);
void fireDMA(u32 dest, u32 source, u32 size);
void reset();
Registers& getRegisters() { return regs; }
void startCommandList(u32 addr, u32 size);
// Used by the GSP GPU service for readHwRegs/writeHwRegs/writeHwRegsMasked
u32 readReg(u32 address);
void writeReg(u32 address, u32 value);
@ -112,4 +84,46 @@ public:
// Used when processing GPU command lists
u32 readInternalReg(u32 index);
void writeInternalReg(u32 index, u32 value, u32 mask);
// TODO: Emulate the transfer engine & its registers
// Then this can be emulated by just writing the appropriate values there
void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) {
renderer.clearBuffer(startAddress, endAddress, value, control);
}
// TODO: Emulate the transfer engine & its registers
// Then this can be emulated by just writing the appropriate values there
void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) {
renderer.displayTransfer(inputAddr, outputAddr, inputSize, outputSize, flags);
}
// Read a value of type T from physical address paddr
// This is necessary because vertex attribute fetching uses physical addresses
template <typename T>
T readPhysical(u32 paddr) {
if (paddr >= PhysicalAddrs::FCRAM && paddr <= PhysicalAddrs::FCRAMEnd) {
u8* fcram = mem.getFCRAM();
u32 index = paddr - PhysicalAddrs::FCRAM;
return *(T*)&fcram[index];
} else {
Helpers::panic("[PICA] Read unimplemented paddr %08X", paddr);
}
}
// Get a pointer of type T* to the data starting from physical address paddr
template <typename T>
T* getPointerPhys(u32 paddr) {
if (paddr >= PhysicalAddrs::FCRAM && paddr <= PhysicalAddrs::FCRAMEnd) {
u8* fcram = mem.getFCRAM();
u32 index = paddr - PhysicalAddrs::FCRAM;
return (T*)&fcram[index];
} else if (paddr >= PhysicalAddrs::VRAM && paddr <= PhysicalAddrs::VRAMEnd) {
u32 index = paddr - PhysicalAddrs::VRAM;
return (T*)&vram[index];
} else [[unlikely]] {
Helpers::panic("[GPU] Tried to access unknown physical address: %08X", paddr);
}
}
};

View file

@ -10,10 +10,22 @@ namespace PICAInternalRegs {
DepthScale = 0x4D,
DepthOffset = 0x4E,
ShaderOutputCount = 0x4F,
DepthmapEnable = 0x6D,
TexUnitCfg = 0x80,
// Framebuffer registers
ColourOperation = 0x100,
BlendFunc = 0x101,
BlendColour = 0x103,
AlphaTestConfig = 0x104,
DepthAndColorMask = 0x107,
DepthBufferFormat = 0x116,
ColourBufferFormat = 0x117,
DepthBufferLoc = 0x11C,
ColourBufferLoc = 0x11D,
FramebufferSize = 0x11E,
// Geometry pipeline registers
VertexAttribLoc = 0x200,
@ -59,10 +71,20 @@ namespace PICAInternalRegs {
FixedAttribData0 = 0x233,
FixedAttribData1 = 0x234,
FixedAttribData2 = 0x235,
// Command processor registers
CmdBufSize0 = 0x238,
CmdBufSize1 = 0x239,
CmdBufAddr0 = 0x23A,
CmdBufAddr1 = 0x23B,
CmdBufTrigger0 = 0x23C,
CmdBufTrigger1 = 0x23D,
PrimitiveConfig = 0x25E,
PrimitiveRestart = 0x25F,
// Vertex shader registers
VertexShaderAttrNum = 0x242,
VertexBoolUniform = 0x2B0,
VertexIntUniform0 = 0x2B1,
VertexIntUniform1 = 0x2B2,
@ -81,6 +103,10 @@ namespace PICAInternalRegs {
VertexFloatUniformData6 = 0x2C7,
VertexFloatUniformData7 = 0x2C8,
VertexShaderInputBufferCfg = 0x2B9,
VertexShaderInputCfgLow = 0x2BB,
VertexShaderInputCfgHigh = 0x2BC,
VertexShaderTransferIndex = 0x2CB,
VertexShaderData0 = 0x2CC,
VertexShaderData1 = 0x2CD,

View file

@ -16,17 +16,26 @@ namespace ShaderOpcodes {
DP3 = 0x01,
DP4 = 0x02,
MUL = 0x08,
SLT = 0x0A,
FLR = 0x0B,
MAX = 0x0C,
MIN = 0x0D,
RCP = 0x0E,
RSQ = 0x0F,
MOVA = 0x12,
MOV = 0x13,
SGEI = 0x1A,
SLTI = 0x1B,
NOP = 0x21,
END = 0x22,
CALL = 0x24,
CALLC = 0x25,
CALLU = 0x26,
IFU = 0x27,
IFC = 0x28,
LOOP = 0x29,
JMPC = 0x2C,
JMPU = 0x2D,
CMP1 = 0x2E, // Both of these instructions are CMP
CMP2 = 0x2F,
MAD = 0x38 // Everything between 0x38-0x3F is a MAD but fuck it
@ -85,19 +94,29 @@ class PICAShader {
// Shader opcodes
void add(u32 instruction);
void call(u32 instruction);
void callc(u32 instruction);
void callu(u32 instruction);
void cmp(u32 instruction);
void dp3(u32 instruction);
void dp4(u32 instruction);
void flr(u32 instruction);
void ifc(u32 instruction);
void ifu(u32 instruction);
void jmpc(u32 instruction);
void jmpu(u32 instruction);
void loop(u32 instruction);
void mad(u32 instruction);
void madi(u32 instruction);
void max(u32 instruction);
void min(u32 instruction);
void mov(u32 instruction);
void mova(u32 instruction);
void mul(u32 instruction);
void rcp(u32 instruction);
void rsq(u32 instruction);
void sgei(u32 instruction);
void slt(u32 instruction);
void slti(u32 instruction);
// src1, src2 and src3 have different negation & component swizzle bits in the operand descriptor
// https://problemkaputt.github.io/gbatek.htm#3dsgpushaderinstructionsetopcodesummary in the
@ -159,7 +178,7 @@ public:
std::array<vec4f, 96> floatUniforms;
std::array<vec4f, 16> fixedAttributes; // Fixed vertex attributes
std::array<vec4f, 16> attributes; // Attributes passed to the shader
std::array<vec4f, 16> inputs; // Attributes passed to the shader
std::array<vec4f, 16> outputs;
PICAShader(ShaderType type) : type(type) {}
@ -170,8 +189,7 @@ public:
}
void setBufferIndex(u32 index) {
if (index != 0) Helpers::panic("How many bits is the shader buffer index reg meant to be?");
bufferIndex = (index >> 2) & 0xfff;
bufferIndex = index & 0xfff;
}
void setOpDescriptorIndex(u32 index) {
@ -200,7 +218,7 @@ public:
if (floatUniformIndex >= 96)
Helpers::panic("[PICA] Tried to write float uniform %d", floatUniformIndex);
if ((f32UniformTransfer && floatUniformWordCount == 4) || (!f32UniformTransfer && floatUniformWordCount == 3)) {
if ((f32UniformTransfer && floatUniformWordCount >= 4) || (!f32UniformTransfer && floatUniformWordCount >= 3)) {
vec4f& uniform = floatUniforms[floatUniformIndex++];
floatUniformWordCount = 0;

View file

@ -1,8 +1,9 @@
#pragma once
#include <cstdint>
// Status register definitions
namespace CPSR {
enum : u32 {
enum : std::uint32_t {
// Privilege modes
UserMode = 16,
FIQMode = 17,
@ -26,7 +27,7 @@ namespace CPSR {
namespace FPSCR {
// FPSCR Flags
enum : u32 {
enum : std::uint32_t {
Sign = 1U << 31U, // Negative condition flag
Zero = 1 << 30, // Zero condition flag
Carry = 1 << 29, // Carry condition flag
@ -61,6 +62,7 @@ namespace FPSCR {
RoundToZero = 3 << 22,
// Default FPSCR value for threads
ThreadDefault = DefaultNan | FlushToZero | RoundToZero | IXC
ThreadDefault = DefaultNan | FlushToZero | RoundToZero,
MainThreadDefault = ThreadDefault | IXC
};
}

17
include/colour.hpp Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#include "helpers.hpp"
// Helpers functions for converting colour channels between bit depths
namespace Colour {
inline static constexpr u8 convert4To8Bit(u8 c) {
return (c << 4) | c;
}
inline static constexpr u8 convert5To8Bit(u8 c) {
return (c << 3) | (c >> 2);
}
inline static constexpr u8 convert6To8Bit(u8 c) {
return (c << 2) | (c >> 4);
}
}

View file

@ -17,6 +17,8 @@ public:
Memory& mem;
Kernel& kernel;
u64 getCyclesForInstruction(bool isThumb, u32 instruction);
u8 MemoryRead8(u32 vaddr) override {
return mem.read8(vaddr);
}
@ -77,7 +79,13 @@ public:
}
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override {
Helpers::panic("Fired exception oops");
switch (exception) {
case Dynarmic::A32::Exception::UnpredictableInstruction:
Helpers::panic("Unpredictable instruction at pc = %08X", pc);
break;
default: Helpers::panic("Fired exception oops");
}
}
void AddTicks(u64 ticks) override {
@ -94,6 +102,10 @@ public:
return ticksLeft;
}
u64 GetTicksForCode(bool isThumb, u32 vaddr, u32 instruction) override {
return getCyclesForInstruction(isThumb, instruction);
}
MyEnvironment(Memory& mem, Kernel& kernel, CPU& cpu) : mem(mem), kernel(kernel) {}
};

View file

@ -5,6 +5,7 @@
#include <SDL.h>
#include "cpu.hpp"
#include "io_file.hpp"
#include "memory.hpp"
#include "opengl.hpp"
#include "PICA/gpu.hpp"
@ -38,11 +39,14 @@ public:
Helpers::panic("Failed to initialize SDL2");
}
// Request OpenGL 4.1 (Max available on MacOS)
// Request OpenGL 4.1 Core (Max available on MacOS)
// MacOS gets mad if we don't explicitly demand a core profile
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
window = SDL_CreateWindow("Alber", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_OPENGL);
glContext = SDL_GL_CreateContext(window);
reset();
}

View file

@ -1,7 +1,15 @@
#pragma once
#include <cassert>
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <optional>
#include <string>
#include <type_traits>
#include <vector>
#include "helpers.hpp"
#include "memory.hpp"
#include "result.hpp"
namespace PathType {
enum : u32 {
@ -21,10 +29,12 @@ namespace ArchiveID {
SharedExtSaveData = 7,
SystemSaveData = 8,
SDMC = 9,
SDMCWriteOnly = 0xA
SDMCWriteOnly = 0xA,
SavedataAndNcch = 0x2345678A
};
static const char* toString(u32 id) {
static std::string toString(u32 id) {
switch (id) {
case SelfNCCH: return "SelfNCCH";
case SaveData: return "SaveData";
@ -33,27 +43,66 @@ namespace ArchiveID {
case SystemSaveData: return "SystemSaveData";
case SDMC: return "SDMC";
case SDMCWriteOnly: return "SDMC (Write-only)";
case SavedataAndNcch: return "Savedata & NCCH (archive 0x2345678A)";
default: return "Unknown archive";
}
}
}
struct FSPath {
u32 type;
u32 size;
u32 pointer; // Pointer to the actual path data
u32 type = PathType::Invalid;
std::vector<u8> binary; // Path data for binary paths
std::string string; // Path data for ASCII paths
std::u16string utf16_string;
FSPath() {}
FSPath(u32 type, const std::vector<u8>& vec) : type(type) {
switch (type) {
case PathType::Binary:
binary = std::move(vec);
break;
case PathType::ASCII:
string.resize(vec.size() - 1); // -1 because of the null terminator
std::memcpy(string.data(), vec.data(), vec.size() - 1); // Copy string data
break;
case PathType::UTF16: {
const size_t size = vec.size() / sizeof(u16) - 1; // Character count. -1 because null terminator here too
utf16_string.resize(size);
std::memcpy(utf16_string.data(), vec.data(), size * sizeof(u16));
break;
}
; }
}
};
struct FilePerms {
u32 raw;
FilePerms(u32 val) : raw(val) {}
bool read() const { return (raw & 1) != 0; }
bool write() const { return (raw & 2) != 0; }
bool create() const { return (raw & 4) != 0; }
};
class ArchiveBase;
struct FileSession {
ArchiveBase* archive = nullptr;
FILE* fd = nullptr; // File descriptor for file sessions that require them.
FSPath path;
FSPath archivePath;
u32 priority = 0; // TODO: What does this even do
bool isOpen;
FileSession(ArchiveBase* archive, const FSPath& filePath, bool isOpen = true) : archive(archive), isOpen(isOpen) {
path = filePath;
}
FileSession(ArchiveBase* archive, const FSPath& filePath, const FSPath& archivePath, FILE* fd, bool isOpen = true) :
archive(archive), path(filePath), archivePath(archivePath), fd(fd), isOpen(isOpen), priority(0) {}
// For cloning a file session
FileSession(const FileSession& other) : archive(other.archive), path(other.path),
archivePath(other.archivePath), fd(other.fd), isOpen(other.isOpen), priority(other.priority) {}
};
struct ArchiveSession {
@ -61,22 +110,119 @@ struct ArchiveSession {
FSPath path;
bool isOpen;
ArchiveSession(ArchiveBase* archive, const FSPath& filePath, bool isOpen = true) : archive(archive), isOpen(isOpen) {
path = filePath;
}
ArchiveSession(ArchiveBase* archive, const FSPath& filePath, bool isOpen = true) : archive(archive), path(filePath), isOpen(isOpen) {}
};
struct DirectorySession {
ArchiveBase* archive = nullptr;
// For directories which are mirrored to a specific path on the disk, this contains that path
// Otherwise this is a nullopt
std::optional<std::filesystem::path> pathOnDisk;
bool isOpen;
DirectorySession(ArchiveBase* archive, std::filesystem::path path, bool isOpen = true) : archive(archive), pathOnDisk(path),
isOpen(isOpen) {}
};
// Represents a file descriptor obtained from OpenFile. If the optional is nullopt, opening the file failed.
// Otherwise the fd of the opened file is returned (or nullptr if the opened file doesn't require one)
using FileDescriptor = std::optional<FILE*>;
enum class FSResult : u32 {
Success = 0,
AlreadyExists = 0x82044BE,
FileTooLarge = 0x86044D2,
FileNotFound = 0xC8804470,
NotFoundInvalid = 0xC8A04478, // Also a not found error code used here and there in the FS module.
NotFormatted = 0xC8A04554, // Trying to access an archive that needs formatting and has not been formatted
UnexpectedFileOrDir = 0xE0C04702
};
class ArchiveBase {
public:
struct FormatInfo {
u32 size; // Archive size
u32 numOfDirectories; // Number of directories
u32 numOfFiles; // Number of files
bool duplicateData; // Whether to duplicate data or not
};
protected:
using Handle = u32;
static constexpr FileDescriptor NoFile = nullptr;
static constexpr FileDescriptor FileError = std::nullopt;
Memory& mem;
public:
virtual const char* name() = 0;
virtual u64 getFreeBytes() = 0;
virtual bool openFile(const FSPath& path) = 0;
// Returns if a specified 3DS path in UTF16 or ASCII format is safe or not
// A 3DS path is considered safe if its first character is '/' which means we're not trying to access anything outside the root of the fs
// And if it doesn't contain enough instances of ".." (Indicating "climb up a folder" in filesystems) to let the software climb up the directory tree
// And access files outside of the emulator's app data folder
template <u32 format>
bool isPathSafe(const FSPath& path) {
static_assert(format == PathType::ASCII || format == PathType::UTF16);
using String = typename std::conditional<format == PathType::UTF16, std::u16string, std::string>::type; // String type for the path
using Char = typename String::value_type; // Char type for the path
virtual ArchiveBase* openArchive(const FSPath& path) = 0;
String pathString, dots;
if constexpr (std::is_same<String, std::u16string>()) {
pathString = path.utf16_string;
dots = u"..";
} else {
pathString = path.string;
dots = "..";
}
// If the path string doesn't begin with / then that means it's accessing outside the FS root, which is invalid & unsafe
if (pathString[0] != Char('/')) return false;
// Counts how many folders sans the root our file is nested under.
// If it's < 0 at any point of parsing, then the path is unsafe and tries to crawl outside our file sandbox.
// If it's 0 then this is the FS root.
// If it's > 0 then we're in a subdirectory of the root.
int level = 0;
// Split the string on / characters and see how many of the substrings are ".."
size_t pos = 0;
while ((pos = pathString.find(Char('/'))) != String::npos) {
String token = pathString.substr(0, pos);
pathString.erase(0, pos + 1);
if (token == dots) {
level--;
if (level < 0) return false;
} else {
level++;
}
}
return true;
}
public:
virtual std::string name() = 0;
virtual u64 getFreeBytes() = 0;
virtual FSResult createFile(const FSPath& path, u64 size) = 0;
virtual FSResult deleteFile(const FSPath& path) = 0;
virtual FormatInfo getFormatInfo(const FSPath& path) {
Helpers::panic("Unimplemented GetFormatInfo for %s archive", name().c_str());
// Return a dummy struct just to avoid the UB of not returning anything, even if we panic
return FormatInfo{ .size = 0, .numOfDirectories = 0, .numOfFiles = 0, .duplicateData = false };
}
virtual FSResult createDirectory(const FSPath& path) {
Helpers::panic("Unimplemented CreateDirectory for %s archive", name().c_str());
return FSResult::AlreadyExists;
}
// Returns nullopt if opening the file failed, otherwise returns a file descriptor to it (nullptr if none is needed)
virtual FileDescriptor openFile(const FSPath& path, const FilePerms& perms) = 0;
virtual Rust::Result<ArchiveBase*, FSResult> openArchive(const FSPath& path) = 0;
virtual Rust::Result<DirectorySession, FSResult> openDirectory(const FSPath& path) {
Helpers::panic("Unimplemented OpenDirectory for %s archive", name().c_str());
return Err(FSResult::FileNotFound);
}
// Read size bytes from a file starting at offset "offset" into a certain buffer in memory
// Returns the number of bytes read, or nullopt if the read failed

View file

@ -0,0 +1,22 @@
#pragma once
#include "archive_base.hpp"
class ExtSaveDataArchive : public ArchiveBase {
public:
ExtSaveDataArchive(Memory& mem, const std::string& folder, bool isShared = false) : ArchiveBase(mem),
isShared(isShared), backingFolder(folder) {}
u64 getFreeBytes() override { Helpers::panic("ExtSaveData::GetFreeBytes unimplemented"); return 0; }
std::string name() override { return "ExtSaveData::" + backingFolder; }
FSResult createFile(const FSPath& path, u64 size) override;
FSResult deleteFile(const FSPath& path) override;
Rust::Result<ArchiveBase*, FSResult> openArchive(const FSPath& path) override;
Rust::Result<DirectorySession, FSResult> openDirectory(const FSPath& path) override;
FileDescriptor openFile(const FSPath& path, const FilePerms& perms) override;
std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override;
bool isShared = false;
std::string backingFolder; // Backing folder for the archive. Can be NAND, Gamecard or SD depending on the archive path.
};

View file

@ -1,16 +1,18 @@
#pragma once
#include "archive_base.hpp"
class SelfNCCHArchive : public ArchiveBase {
class NCCHArchive : public ArchiveBase {
public:
SelfNCCHArchive(Memory& mem) : ArchiveBase(mem) {}
NCCHArchive(Memory& mem) : ArchiveBase(mem) {}
u64 getFreeBytes() override { return 0; }
const char* name() override { return "SelfNCCH"; }
u64 getFreeBytes() override { Helpers::panic("NCCH::GetFreeBytes unimplemented"); return 0; }
std::string name() override { return "NCCH"; }
bool openFile(const FSPath& path) override;
ArchiveBase* openArchive(const FSPath& path) override;
FSResult createFile(const FSPath& path, u64 size) override;
FSResult deleteFile(const FSPath& path) override;
Rust::Result<ArchiveBase*, FSResult> openArchive(const FSPath& path) override;
FileDescriptor openFile(const FSPath& path, const FilePerms& perms) override;
std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override;
// Returns whether the cart has a RomFS
@ -18,4 +20,10 @@ public:
auto cxi = mem.getCXI();
return (cxi != nullptr && cxi->hasRomFS());
}
// Returns whether the cart has an ExeFS (All executable carts should have an ExeFS. This is just here to be safe)
bool hasExeFS() {
auto cxi = mem.getCXI();
return (cxi != nullptr && cxi->hasExeFS());
}
};

View file

@ -2,15 +2,20 @@
#include "archive_base.hpp"
class SaveDataArchive : public ArchiveBase {
public:
SaveDataArchive(Memory& mem) : ArchiveBase(mem) {}
u64 getFreeBytes() override { return 0; }
const char* name() override { return "SaveData"; }
u64 getFreeBytes() override { Helpers::panic("SaveData::GetFreeBytes unimplemented"); return 0; }
std::string name() override { return "SaveData"; }
bool openFile(const FSPath& path) override;
ArchiveBase* openArchive(const FSPath& path) override;
FSResult createDirectory(const FSPath& path) override;
FSResult createFile(const FSPath& path, u64 size) override;
FSResult deleteFile(const FSPath& path) override;
FormatInfo getFormatInfo(const FSPath& path) override;
Rust::Result<ArchiveBase*, FSResult> openArchive(const FSPath& path) override;
Rust::Result<DirectorySession, FSResult> openDirectory(const FSPath& path) override;
FileDescriptor openFile(const FSPath& path, const FilePerms& perms) override;
std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override;
// Returns whether the cart has save data or not

View file

@ -2,14 +2,16 @@
#include "archive_base.hpp"
class SDMCArchive : public ArchiveBase {
public:
SDMCArchive(Memory& mem) : ArchiveBase(mem) {}
u64 getFreeBytes() override { return 0; }
const char* name() override { return "SDMC"; }
u64 getFreeBytes() override { Helpers::panic("SDMC::GetFreeBytes unimplemented"); return 0; }
std::string name() override { return "SDMC"; }
bool openFile(const FSPath& path) override;
ArchiveBase* openArchive(const FSPath& path) override;
FSResult createFile(const FSPath& path, u64 size) override;
FSResult deleteFile(const FSPath& path) override;
Rust::Result<ArchiveBase*, FSResult> openArchive(const FSPath& path) override;
FileDescriptor openFile(const FSPath& path, const FilePerms& perms) override;
std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override;
};

View file

@ -0,0 +1,29 @@
#pragma once
#include "archive_base.hpp"
class SelfNCCHArchive : public ArchiveBase {
public:
SelfNCCHArchive(Memory& mem) : ArchiveBase(mem) {}
u64 getFreeBytes() override { return 0; }
std::string name() override { return "SelfNCCH"; }
FSResult createFile(const FSPath& path, u64 size) override;
FSResult deleteFile(const FSPath& path) override;
Rust::Result<ArchiveBase*, FSResult> openArchive(const FSPath& path) override;
FileDescriptor openFile(const FSPath& path, const FilePerms& perms) override;
std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override;
// Returns whether the cart has a RomFS
bool hasRomFS() {
auto cxi = mem.getCXI();
return (cxi != nullptr && cxi->hasRomFS());
}
// Returns whether the cart has an ExeFS (All executable carts should have an ExeFS. This is just here to be safe)
bool hasExeFS() {
auto cxi = mem.getCXI();
return (cxi != nullptr && cxi->hasExeFS());
}
};

View file

@ -0,0 +1,132 @@
// Generated with https://github.com/B3n30/citra_system_archives
#pragma once
const unsigned char BAD_WORD_LIST_DATA[] = {
0x28, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
0x34, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
0x4c, 0x00, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00, 0x24, 0x03, 0x00, 0x00,
0xc0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xec, 0x02, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00,
0xc0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x02, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0x8c, 0x01, 0x00, 0x00, 0x84, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0xe4, 0x01, 0x00, 0x00, 0x94, 0x02, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0x3c, 0x02, 0x00, 0x00, 0x34, 0x01, 0x00, 0x00,
0x2c, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0xb8, 0x01, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0x0a, 0x00, 0x00, 0x00, 0x30, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x78, 0x00,
0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00,
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x0a, 0x00, 0x00, 0x00,
0x31, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x78, 0x00, 0x74, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x84, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x31, 0x00, 0x30, 0x00,
0x2e, 0x00, 0x74, 0x00, 0x78, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb0, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0x0c, 0x00, 0x00, 0x00, 0x31, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x74, 0x00,
0x78, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdc, 0x00, 0x00, 0x00,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00,
0x31, 0x00, 0x32, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x78, 0x00, 0x74, 0x00,
0x00, 0x00, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x31, 0x00, 0x33, 0x00,
0x2e, 0x00, 0x74, 0x00, 0x78, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00,
0x34, 0x01, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0x0c, 0x00, 0x00, 0x00, 0x31, 0x00, 0x34, 0x00, 0x2e, 0x00, 0x74, 0x00,
0x78, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00,
0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00,
0x31, 0x00, 0x35, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x78, 0x00, 0x74, 0x00,
0x00, 0x00, 0x00, 0x00, 0x8c, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x31, 0x00, 0x36, 0x00,
0x2e, 0x00, 0x74, 0x00, 0x78, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00,
0xb8, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0x0a, 0x00, 0x00, 0x00, 0x32, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x78, 0x00,
0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe4, 0x01, 0x00, 0x00,
0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x0a, 0x00, 0x00, 0x00,
0x33, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x78, 0x00, 0x74, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0x0a, 0x00, 0x00, 0x00, 0x34, 0x00, 0x2e, 0x00,
0x74, 0x00, 0x78, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3c, 0x02, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0x0a, 0x00, 0x00, 0x00, 0x35, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x78, 0x00,
0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x02, 0x00, 0x00,
0xa0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
0x36, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x78, 0x00, 0x74, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x94, 0x02, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xdc, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x37, 0x00, 0x2e, 0x00,
0x74, 0x00, 0x78, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xc0, 0x02, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0x0a, 0x00, 0x00, 0x00, 0x38, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x78, 0x00,
0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xec, 0x02, 0x00, 0x00,
0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
0x39, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x78, 0x00, 0x74, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x20, 0x02, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0x16, 0x00, 0x00, 0x00, 0x76, 0x00, 0x65, 0x00,
0x72, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x2e, 0x00,
0x64, 0x00, 0x61, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xfe, 0x5e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x77, 0x00,
0x6f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00, 0x0a, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x5e, 0x00,
0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00,
0x64, 0x00, 0x24, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x5e, 0x00, 0x62, 0x00, 0x61, 0x00,
0x64, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00,
0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xfe, 0x5e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x77, 0x00,
0x6f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00, 0x0a, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x5e, 0x00,
0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00,
0x64, 0x00, 0x24, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x5e, 0x00, 0x62, 0x00, 0x61, 0x00,
0x64, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00,
0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xfe, 0x5e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x77, 0x00,
0x6f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00, 0x0a, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x5e, 0x00,
0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00,
0x64, 0x00, 0x24, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x5e, 0x00, 0x62, 0x00, 0x61, 0x00,
0x64, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00,
0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xfe, 0x5e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x77, 0x00,
0x6f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00, 0x0a, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x5e, 0x00,
0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00,
0x64, 0x00, 0x24, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x5e, 0x00, 0x62, 0x00, 0x61, 0x00,
0x64, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00,
0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xfe, 0x5e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x77, 0x00,
0x6f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00, 0x0a, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x5e, 0x00,
0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00,
0x64, 0x00, 0x24, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x5e, 0x00, 0x62, 0x00, 0x61, 0x00,
0x64, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00,
0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xfe, 0x5e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x77, 0x00,
0x6f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00, 0x0a, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x5e, 0x00,
0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00,
0x64, 0x00, 0x24, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00
};
const unsigned int BAD_WORD_LIST_DATA_len = 1508;

18260
include/fs/country_list.hpp Normal file

File diff suppressed because it is too large Load diff

6140
include/fs/mii_data.hpp Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,9 @@
#pragma once
#include <iostream>
#include <vector>
#include <cstdarg>
#include <fstream>
#include <iostream>
#include <iterator>
#include <vector>
#include "termcolor.hpp"
using u8 = std::uint8_t;
@ -23,21 +24,18 @@ namespace Helpers {
va_start(args, fmt);
std::cout << termcolor::on_red << "[FATAL] ";
std::vprintf (fmt, args);
std::cout << termcolor::reset;
std::cout << std::endl;
std::cout << termcolor::reset << "\n";
va_end(args);
exit(1);
}
static void warn(const char* fmt, ...) {
return;
std::va_list args;
va_start(args, fmt);
std::cout << termcolor::on_red << "[Warning] ";
std::vprintf (fmt, args);
std::cout << termcolor::reset;
std::cout << "\n";
std::cout << termcolor::reset << "\n";
va_end(args);
}
@ -122,10 +120,6 @@ namespace Helpers {
static_for_impl<T, Begin>( std::forward<Func>(f), std::make_integer_sequence<T, End - Begin>{ } );
}
static constexpr inline u8 get8BitColor (u8 colorRGB555) {
return (colorRGB555 << 3) | (colorRGB555 >> 2);
}
// For values < 0x99
static constexpr inline u8 incBCDByte(u8 value) {
return ((value & 0xf) == 0x9) ? value + 7 : value + 1;

View file

@ -2,6 +2,7 @@
#include <cstdint>
#include <cstdio>
#include <filesystem>
#include <optional>
#include <utility>
#ifdef _MSC_VER
@ -17,10 +18,23 @@
#define _CRT_SECURE_NO_WARNINGS
#endif
#ifdef WIN32
#include <io.h> // For _chsize_s
#else
#include <unistd.h> // For ftruncate
#endif
class IOFile {
FILE* handle = nullptr;
static inline std::filesystem::path appData = ""; // Directory for holding app data. AppData on Windows
public:
IOFile() {}
IOFile(FILE* handle) : handle(handle) {}
IOFile(const std::filesystem::path& path, const char* permissions = "rb") {
open(path, permissions);
}
bool isOpen() {
return handle != nullptr;
}
@ -55,17 +69,30 @@ public:
return read(data, count, sizeof(std::uint8_t));
}
std::uint64_t size() {
if (!isOpen()) return 0;
std::pair<bool, std::size_t> write(const void* data, std::size_t length, std::size_t dataSize) {
if (!isOpen()) {
return { false, std::numeric_limits<std::size_t>::max() };
}
if (length == 0) return { true, 0 };
return { true, std::fwrite(data, dataSize, length, handle) };
}
auto writeBytes(const void* data, std::size_t count) {
return write(data, count, sizeof(std::uint8_t));
}
std::optional<std::uint64_t> size() {
if (!isOpen()) return {};
std::uint64_t pos = ftello(handle);
if (fseeko(handle, 0, SEEK_END) != 0) {
return 0;
return {};
}
std::uint64_t size = ftello(handle);
if ((size != pos) && (fseeko(handle, pos, SEEK_SET) != 0)) {
return 0;
return {};
}
return size;
@ -81,4 +108,29 @@ public:
bool rewind() {
return seek(0, SEEK_SET);
}
FILE* getHandle() {
return handle;
}
static void setAppDataDir(const std::filesystem::path& dir) {
if (dir == "") Helpers::panic("Failed to set app data directory");
appData = dir;
}
// Sets the size of the file to "size" and returns whether it succeeded or not
bool setSize(std::uint64_t size) {
if (!isOpen()) return false;
bool success;
#ifdef WIN32
success = _chsize_s(_fileno(handle), size) == 0;
#else
success = ftruncate(fileno(handle), size) == 0;
#endif
fflush(handle);
return success;
}
static std::filesystem::path getAppData() { return IOFile::appData; }
};

9
include/ipc.hpp Normal file
View file

@ -0,0 +1,9 @@
#pragma once
#include <cstdint>
namespace IPC {
constexpr std::uint32_t responseHeader(std::uint32_t commandID, std::uint32_t normalResponses, std::uint32_t translateResponses) {
// TODO: Maybe validate the response count stuff fits in 6 bits
return (commandID << 16) | (normalResponses << 6) | translateResponses;
}
}

View file

@ -6,9 +6,25 @@ namespace ConfigMem {
enum : u32 {
KernelVersionMinor = 0x1FF80002,
KernelVersionMajor = 0x1FF80003,
SyscoreVer = 0x1FF80010,
EnvInfo = 0x1FF80014,
AppMemAlloc = 0x1FF80040,
HardwareType = 0x1FF81004,
Datetime0 = 0x1FF81020,
LedState3D = 0x1FF81084
NetworkState = 0x1FF81067,
LedState3D = 0x1FF81084,
BatteryState = 0x1FF81085,
Unknown1086 = 0x1FF81086,
HeadphonesConnectedMaybe = 0x1FF810C0 // TODO: What is actually stored here?
};
// Shows what type of hardware we're running on
namespace HardwareCodes {
enum : u8 {
Product = 1,
Devboard = 2,
Debugger = 3,
Capture = 4
};
}
}

View file

@ -10,32 +10,37 @@ namespace KernelHandles {
// Hardcoded handles
CurrentThread = 0xFFFF8000, // Used by the original kernel
CurrentProcess = 0xFFFF8001, // Used by the original kernel
AC, // Something network related
ACT, // Handles NNID accounts
AM, // Application manager
APT, // App Title something service?
CECD, // Streetpass stuff?
BOSS, // Streetpass stuff?
CAM, // Camera service
CECD, // More Streetpass stuff?
CFG, // CFG service (Console & region info)
HID, // HID service (Handles everything input-related including gyro)
FRD, // Friend service (Miiverse friend service)
FS, // Filesystem service
GPU, // GPU service
DSP, // DSP service (Used for audio decoding and output)
LCD, // LCD service (Used for configuring the displays)
LDR_RO, // Loader service. Used for loading CROs.
MIC, // MIC service (Controls the microphone)
NFC, // NFC (Duh), used for Amiibo
NIM, // Updates, DLC, etc
NDM, // ?????
PTM, // PTM service (Used for accessing various console info, such as battery, shell and pedometer state)
Y2R, // Also does camera stuff
MinServiceHandle = APT,
MaxServiceHandle = PTM,
MinServiceHandle = AC,
MaxServiceHandle = Y2R,
GSPSharedMemHandle = MaxServiceHandle + 1, // Handle for the GSP shared memory
FontSharedMemHandle,
HIDSharedMemHandle,
MinSharedMemHandle = GSPSharedMemHandle,
MaxSharedMemHandle = HIDSharedMemHandle,
HIDEvent0,
HIDEvent1,
HIDEvent2,
HIDEvent3,
HIDEvent4
};
// Returns whether "handle" belongs to one of the OS services
@ -51,17 +56,27 @@ namespace KernelHandles {
// Returns the name of a handle as a string based on the given handle
static const char* getServiceName(Handle handle) {
switch (handle) {
case AC: return "AC";
case ACT: return "ACT";
case AM: return "AM";
case APT: return "APT";
case BOSS: return "BOSS";
case CAM: return "CAM";
case CECD: return "CECD";
case CFG: return "CFG";
case HID: return "HID";
case FRD: return "FRD";
case FS: return "FS";
case DSP: return "DSP";
case GPU: return "GPU";
case LCD: return "LCD";
case GPU: return "GSP::GPU";
case LCD: return "GSP::LCD";
case LDR_RO: return "LDR:RO";
case MIC: return "MIC";
case NDM: return "NDM";
case NFC: return "NFC";
case NIM: return "NIM";
case PTM: return "PTM";
case Y2R: return "Y2R";
default: return "Unknown";
}
}

View file

@ -1,5 +1,6 @@
#pragma once
#include <array>
#include <cassert>
#include <limits>
#include <string>
#include <vector>
@ -19,7 +20,16 @@ class Kernel {
// The handle number for the next kernel object to be created
u32 handleCounter;
std::array<Thread, appResourceLimits.maxThreads> threads;
// A list of our OS threads, the max number of which depends on the resource limit (hardcoded 32 per process on retail it seems).
// We have an extra thread for when no thread is capable of running. This thread is called the "idle thread" in our code
// This thread is set up in setupIdleThread and just yields in a loop to see if any other thread has woken up
std::array<Thread, appResourceLimits.maxThreads + 1> threads;
static constexpr int idleThreadIndex = appResourceLimits.maxThreads;
// Our waitlist system uses a bitfield of 64 bits to show which threads are waiting on an object.
// That means we can have a maximum of 63 threads + 1 idle thread. This assert should never trigger because the max thread # is 32
// But we have it here for safety purposes
static_assert(appResourceLimits.maxThreads <= 63, "The waitlist system is built on the premise that <= 63 threads max can be active");
std::vector<KernelObject> objects;
std::vector<Handle> portHandles;
@ -33,19 +43,28 @@ class Kernel {
Handle errorPortHandle; // Handle for the err:f port used for displaying errors
u32 arbiterCount;
u32 threadCount;
u32 threadCount; // How many threads in our thread pool have been used as of now (Up to 32)
u32 aliveThreadCount; // How many of these threads are actually alive?
ServiceManager serviceManager;
// Top 8 bits are the major version, bottom 8 are the minor version
u16 kernelVersion = 0;
Handle makeArbiter();
Handle makeEvent(ResetType resetType);
Handle makeProcess(u32 id);
Handle makePort(const char* name);
Handle makeSession(Handle port);
Handle makeThread(u32 entrypoint, u32 initialSP, u32 priority, s32 id, u32 arg,ThreadStatus status = ThreadStatus::Dormant);
Handle makeMemoryBlock(u32 addr, u32 size, u32 myPermission, u32 otherPermission);
public:
Handle makeEvent(ResetType resetType); // Needs to be public to be accessible to the APT/HID services
Handle makeMutex(bool locked = false); // Needs to be public to be accessible to the APT/DSP services
Handle makeSemaphore(u32 initialCount, u32 maximumCount); // Needs to be public to be accessible to the service manager port
// Signals an event, returns true on success or false if the event does not exist
bool signalEvent(Handle e);
private:
void signalArbiter(u32 waitingAddress, s32 threadCount);
void sleepThread(s64 ns);
void sleepThreadOnArbiter(u32 waitingAddress);
@ -55,6 +74,14 @@ class Kernel {
void switchToNextThread();
void rescheduleThreads();
bool canThreadRun(const Thread& t);
bool shouldWaitOnObject(KernelObject* object);
void releaseMutex(Mutex* moo);
// Wake up the thread with the highest priority out of all threads in the waitlist
// Returns the index of the woken up thread
// Do not call this function with an empty waitlist!!!
int wakeupOneThread(u64 waitlist, Handle handle);
void wakeupAllThreads(u64 waitlist, Handle handle);
std::optional<Handle> getPortHandle(const char* name);
void deleteObjectData(KernelObject& object);
@ -63,6 +90,10 @@ class Kernel {
s32 getCurrentResourceValue(const KernelObject* limit, u32 resourceName);
u32 getMaxForResource(const KernelObject* limit, u32 resourceName);
u32 getTLSPointer();
void setupIdleThread();
void acquireSyncObject(KernelObject* object, const Thread& thread);
bool isWaitable(const KernelObject* object);
// Functions for the err:f port
void handleErrorSyncRequest(u32 messagePointer);
@ -73,19 +104,19 @@ class Kernel {
MAKE_LOG_FUNCTION(log, kernelLogger)
MAKE_LOG_FUNCTION(logSVC, svcLogger)
MAKE_LOG_FUNCTION(logThread, threadLogger)
MAKE_LOG_FUNCTION(logDebugString, debugStringLogger)
MAKE_LOG_FUNCTION(logError, errorLogger)
MAKE_LOG_FUNCTION(logFileIO, fileIOLogger)
// SVC implementations
void arbitrateAddress();
void clearEvent();
void createAddressArbiter();
void createEvent();
void createMutex();
void createMemoryBlock();
void createThread();
void controlMemory();
void duplicateHandle();
void exitThread();
void mapMemoryBlock();
void queryMemory();
void getProcessID();
@ -96,11 +127,16 @@ class Kernel {
void getSystemTick();
void getThreadID();
void getThreadPriority();
void releaseMutex();
void sendSyncRequest();
void setThreadPriority();
void signalEvent();
void svcClearEvent();
void svcCloseHandle();
void svcCreateEvent();
void svcCreateMutex();
void svcCreateSemaphore();
void svcReleaseMutex();
void svcReleaseSemaphore();
void svcSignalEvent();
void svcSleepThread();
void connectToPort();
void outputDebugString();
@ -111,9 +147,20 @@ class Kernel {
void handleFileOperation(u32 messagePointer, Handle file);
void closeFile(u32 messagePointer, Handle file);
void readFile(u32 messagePointer, Handle file);
void writeFile(u32 messagePointer, Handle file);
void getFileSize(u32 messagePointer, Handle file);
void openLinkFile(u32 messagePointer, Handle file);
void setFileSize(u32 messagePointer, Handle file);
void setFilePriority(u32 messagePointer, Handle file);
// Directory operations
void handleDirectoryOperation(u32 messagePointer, Handle directory);
void closeDirectory(u32 messagePointer, Handle directory);
void readDirectory(u32 messagePointer, Handle directory);
public:
Kernel(CPU& cpu, Memory& mem, GPU& gpu);
void initializeFS() { return serviceManager.initializeFS(); }
void setVersion(u8 major, u8 minor);
void serviceSVC(u32 svc);
void reset();
@ -152,4 +199,6 @@ public:
}
void sendGPUInterrupt(GPUInterrupt type) { serviceManager.requestGPUInterrupt(type); }
void signalDSPEvents() { serviceManager.signalDSPEvents(); }
void updateInputs() { serviceManager.updateInputs(); }
};

View file

@ -17,15 +17,25 @@ namespace SVCResult {
BadHandle = 0xD8E007F7,
BadHandleAlt = 0xD9001BF7,
InvalidCombination = 0xE0E01BEE, // Used for invalid memory permission combinations
UnalignedAddr = 0xE0E01BF1,
UnalignedSize = 0xE0E01BF2,
BadThreadPriority = 0xE0E01BFD,
PortNameTooLong = 0xE0E0181E,
// Returned when a thread stops waiting due to timing out
Timeout = 0x9401BFE,
// Returned when a thread releases a mutex it does not own
InvalidMutexRelease = 0xD8E0041F
};
}
enum class KernelObjectType : u8 {
AddressArbiter, Archive, File, Port, Process, ResourceLimit, Session, Dummy,
AddressArbiter, Archive, Directory, File, MemoryBlock, Process, ResourceLimit, Session, Dummy,
// Bundle waitable objects together in the enum to let the compiler optimize certain checks better
Event, Mutex, Semaphore, Thread
Event, Mutex, Port, Semaphore, Timer, Thread
};
enum class ResourceLimitCategory : int {
@ -68,10 +78,11 @@ struct Process {
};
struct Event {
u64 waitlist; // A bitfield where each bit symbolizes if the thread with thread with the corresponding index is waiting on the event
ResetType resetType = ResetType::OneShot;
bool fired = false;
Event(ResetType resetType) : resetType(resetType) {}
Event(ResetType resetType) : resetType(resetType), waitlist(0) {}
};
struct Port {
@ -97,7 +108,8 @@ enum class ThreadStatus {
Ready, // Ready to run
WaitArbiter, // Waiting on an address arbiter
WaitSleep, // Waiting due to a SleepThread SVC
WaitSync1, // Waiting for AT LEAST one sync object in its wait list to be ready
WaitSync1, // Waiting for the single object in the wait list to be ready
WaitSyncAny, // Wait for one object of the many that might be in the wait list to be ready
WaitSyncAll, // Waiting for ALL sync objects in its wait list to be ready
WaitIPC, // Waiting for the reply from an IPC request
Dormant, // Created but not yet made ready
@ -118,9 +130,15 @@ struct Thread {
u32 waitingAddress;
// The nanoseconds until a thread wakes up from being asleep or from timing out while waiting on an arbiter
s64 waitingNanoseconds;
u64 waitingNanoseconds;
// The tick this thread went to sleep on
u64 sleepTick;
// For WaitSynchronization(N): A vector of objects this thread is waiting for
std::vector<Handle> waitList;
// For WaitSynchronizationN: Shows whether the object should wait for all objects in the wait list or just one
bool waitAll;
// For WaitSynchronizationN: The "out" pointer
u32 outPointer;
// Thread context used for switching between threads
std::array<u32, 16> gprs;
@ -128,14 +146,19 @@ struct Thread {
u32 cpsr;
u32 fpscr;
u32 tlsBase; // Base pointer for thread-local storage
// A list of threads waiting for this thread to terminate. Yes, threads are sync objects too.
u64 threadsWaitingForTermination;
};
static const char* kernelObjectTypeToString(KernelObjectType t) {
switch (t) {
case KernelObjectType::AddressArbiter: return "address arbiter";
case KernelObjectType::Archive: return "archive";
case KernelObjectType::Directory: return "directory";
case KernelObjectType::Event: return "event";
case KernelObjectType::File: return "file";
case KernelObjectType::MemoryBlock: return "memory block";
case KernelObjectType::Port: return "port";
case KernelObjectType::Process: return "process";
case KernelObjectType::ResourceLimit: return "resource limit";
@ -148,6 +171,35 @@ static const char* kernelObjectTypeToString(KernelObjectType t) {
}
}
struct Mutex {
u64 waitlist; // Refer to the getWaitlist function below for documentation
Handle ownerThread = 0; // Index of the thread that holds the mutex if it's locked
Handle handle; // Handle of the mutex itself
u32 lockCount; // Number of times this mutex has been locked by its daddy. 0 = not locked
bool locked;
Mutex(bool lock, Handle handle) : locked(lock), waitlist(0), lockCount(lock ? 1 : 0), handle(handle) {}
};
struct Semaphore {
u64 waitlist; // Refer to the getWaitlist function below for documentation
s32 availableCount;
s32 maximumCount;
Semaphore(s32 initialCount, s32 maximumCount) : availableCount(initialCount), maximumCount(maximumCount), waitlist(0) {}
};
struct MemoryBlock {
u32 addr = 0;
u32 size = 0;
u32 myPermission = 0;
u32 otherPermission = 0;
bool mapped = false;
MemoryBlock(u32 addr, u32 size, u32 myPerm, u32 otherPerm) : addr(addr), size(size), myPermission(myPerm), otherPermission(otherPerm),
mapped(false) {}
};
// Generic kernel object class
struct KernelObject {
Handle handle = 0; // A u32 the OS will use to identify objects
@ -164,4 +216,27 @@ struct KernelObject {
T* getData() {
return static_cast<T*>(data);
}
const char* getTypeName() const {
return kernelObjectTypeToString(type);
}
// Retrieves a reference to the waitlist for a specified object
// We return a reference because this function is only called in the kernel threading internals
// We want the kernel to be able to easily manage waitlists, by reading/parsing them or setting/clearing bits.
// As we mention in the definition of the "Event" struct, the format for wailists is very simple and made to be efficient.
// Each bit corresponds to a thread index and denotes whether the corresponding thread is waiting on this object
// For example if bit 0 of the wait list is set, then the thread with index 0 is waiting on our object
u64& getWaitlist() {
// This code is actually kinda trash but eh good enough
switch (type) {
case KernelObjectType::Event: return getData<Event>()->waitlist;
case KernelObjectType::Mutex: return getData<Mutex>()->waitlist;
case KernelObjectType::Semaphore: return getData<Mutex>()->waitlist;
case KernelObjectType::Thread: return getData<Thread>()->threadsWaitingForTermination;
// This should be unreachable once we fully implement sync objects
default: [[unlikely]]
Helpers::panic("Called GetWaitList on kernel object without a waitlist (Type: %s)", getTypeName());
}
}
};

View file

@ -1,88 +1,17 @@
#pragma once
#include <algorithm>
#include <vector>
#include "helpers.hpp"
// For parsing the LZ77 format used for compressing the .code file in the ExeFS
namespace CartLZ77 {
// The difference in size between the compressed and decompressed file is stored
// As a footer in the compressed file. To get the decompressed size, we extract the diff
// And add it to the compressed size
static u32 decompressedSize(const u8* buffer, u32 compressedSize) {
u32 sizeDiff;
std::memcpy(&sizeDiff, buffer + compressedSize - 4, sizeof(u32));
return sizeDiff + compressedSize;
}
// Retrieves the uncompressed size of the compressed LZ77 data stored in buffer with a specific compressed size
u32 decompressedSize(const u8* buffer, u32 compressedSize);
template <typename T>
static u32 decompressedSize(const std::vector<T>& buffer) {
return decompressedSize((u8*)buffer.data(), u32(buffer.size() * sizeof(T)));
}
static bool decompress(std::vector<u8>& output, const std::vector<u8>& input) {
u32 sizeCompressed = input.size() * sizeof(u8);
u32 sizeDecompressed = decompressedSize(input);
output.resize(sizeDecompressed);
const u8* compressed = (u8*)input.data();
const u8* footer = compressed + sizeCompressed - 8;
u32 bufferTopAndBottom;
std::memcpy(&bufferTopAndBottom, footer, sizeof(u32));
u32 out = sizeDecompressed; // TODO: Is this meant to be u32 or s32?
u32 index = sizeCompressed - ((bufferTopAndBottom >> 24) & 0xff);
u32 stopIndex = sizeCompressed - (bufferTopAndBottom & 0xffffff);
// Set all of the decompressed buffer to 0 and copy the compressed buffer to the start of it
std::fill(output.begin(), output.end(), 0);
std::copy(input.begin(), input.end(), output.begin());
while (index > stopIndex) {
u8 control = compressed[--index];
for (uint i = 0; i < 8; i++) {
if (index <= stopIndex)
break;
if (index <= 0)
break;
if (out <= 0)
break;
if (control & 0x80) {
// Check if compression is out of bounds
if (index < 2)
return false;
index -= 2;
u32 segmentOffset = compressed[index] | (compressed[index + 1] << 8);
u32 segment_size = ((segmentOffset >> 12) & 15) + 3;
segmentOffset &= 0x0FFF;
segmentOffset += 2;
// Check if compression is out of bounds
if (out < segment_size)
return false;
for (uint j = 0; j < segment_size; j++) {
// Check if compression is out of bounds
if (out + segmentOffset >= sizeDecompressed)
return false;
u8 data = output[out + segmentOffset];
output[--out] = data;
}
}
else {
// Check if compression is out of bounds
if (out < 1)
return false;
output[--out] = compressed[--index];
}
control <<= 1;
}
}
return true;
}
}
// Decompresses an LZ77-compressed buffer stored in input to output
bool decompress(std::vector<u8>& output, const std::vector<u8>& input);
} // End namespace CartLZ77

View file

@ -17,27 +17,41 @@ namespace Log {
}
};
#define false 0
// Our loggers here. Enable/disable by toggling the template param
static Logger<true> kernelLogger;
static Logger<false> kernelLogger;
static Logger<true> debugStringLogger; // Enables output for the outputDebugString SVC
static Logger<true> errorLogger;
static Logger<true> fileIOLogger;
static Logger<true> svcLogger;
static Logger<true> gpuLogger;
static Logger<false> errorLogger;
static Logger<false> fileIOLogger;
static Logger<false> svcLogger;
static Logger<false> threadLogger;
static Logger<false> gpuLogger;
static Logger<false> rendererLogger;
// Service loggers
static Logger<true> aptLogger;
static Logger<true> cecdLogger;
static Logger<true> cfgLogger;
static Logger<true> dspServiceLogger;
static Logger<true> fsLogger;
static Logger<true> hidLogger;
static Logger<true> gspGPULogger;
static Logger<true> gspLCDLogger;
static Logger<true> micLogger;
static Logger<true> ndmLogger;
static Logger<true> ptmLogger;
static Logger<true> srvLogger;
static Logger<false> acLogger;
static Logger<false> actLogger;
static Logger<false> amLogger;
static Logger<false> aptLogger;
static Logger<false> bossLogger;
static Logger<false> camLogger;
static Logger<false> cecdLogger;
static Logger<false> cfgLogger;
static Logger<false> dspServiceLogger;
static Logger<false> frdLogger;
static Logger<false> fsLogger;
static Logger<false> hidLogger;
static Logger<false> gspGPULogger;
static Logger<false> gspLCDLogger;
static Logger<false> ldrLogger;
static Logger<false> micLogger;
static Logger<false> nfcLogger;
static Logger<false> nimLogger;
static Logger<false> ndmLogger;
static Logger<false> ptmLogger;
static Logger<false> y2rLogger;
static Logger<false> srvLogger;
#undef false
#define MAKE_LOG_FUNCTION(functionName, logger) \
template <typename... Args> \

View file

@ -8,9 +8,12 @@
#include "helpers.hpp"
#include "handles.hpp"
#include "loader/ncsd.hpp"
#include "services/shared_font.hpp"
namespace PhysicalAddrs {
enum : u32 {
VRAM = 0x18000000,
VRAMEnd = VRAM + 0x005FFFFF,
FCRAM = 0x20000000,
FCRAMEnd = FCRAM + 0x07FFFFFF
};
@ -38,6 +41,7 @@ namespace VirtualAddrs {
VramStart = 0x1F000000,
VramSize = 0x00600000,
FcramTotalSize = 128_MB,
DSPMemStart = 0x1FF00000
};
}
@ -101,11 +105,13 @@ class Memory {
// This tracks our OS' memory allocations
std::vector<KernelMemoryTypes::MemoryInfo> memoryInfo;
std::array<SharedMemoryBlock, 2> sharedMemBlocks = {
std::array<SharedMemoryBlock, 3> sharedMemBlocks = {
SharedMemoryBlock(0, _shared_font_len, KernelHandles::FontSharedMemHandle), // Shared memory for the system font
SharedMemoryBlock(0, 0x1000, KernelHandles::GSPSharedMemHandle), // GSP shared memory
SharedMemoryBlock(0, 0x1000, KernelHandles::HIDSharedMemHandle) // HID shared memory
};
};
public:
static constexpr u32 pageShift = 12;
static constexpr u32 pageSize = 1 << pageShift;
static constexpr u32 pageMask = pageSize - 1;
@ -120,6 +126,7 @@ class Memory {
static constexpr u32 DSP_CODE_MEMORY_OFFSET = 0_KB;
static constexpr u32 DSP_DATA_MEMORY_OFFSET = 256_KB;
private:
std::bitset<FCRAM_PAGE_COUNT> usedFCRAMPages;
std::optional<u32> findPaddr(u32 size);
u64 timeSince3DSEpoch();
@ -130,7 +137,8 @@ class Memory {
public:
u16 kernelVersion = 0;
u32 usedUserMemory = 0;
u32 usedUserMemory = 0_MB; // How much of the APPLICATION FCRAM range is used (allocated to the appcore)
u32 usedSystemMemory = 0_MB; // Similar for the SYSTEM range (reserved for the syscore)
Memory(u64& cpuTicks);
void reset();
@ -153,6 +161,33 @@ public:
u32 getLinearHeapVaddr();
u8* getFCRAM() { return fcram; }
// Total amount of OS-only FCRAM available (Can vary depending on how much FCRAM the app requests via the cart exheader)
u32 totalSysFCRAM() {
return FCRAM_SIZE - FCRAM_APPLICATION_SIZE;
}
// Amount of OS-only FCRAM currently available
u32 remainingSysFCRAM() {
return totalSysFCRAM() - usedSystemMemory;
}
// Physical FCRAM index to the start of OS FCRAM
// We allocate the first part of physical FCRAM for the application, and the rest to the OS. So the index for the OS = application ram size
u32 sysFCRAMIndex() {
return FCRAM_APPLICATION_SIZE;
}
enum class BatteryLevel {
Empty = 0, AlmostEmpty, OneBar, TwoBars, ThreeBars, FourBars
};
u8 getBatteryState(bool adapterConnected, bool charging, BatteryLevel batteryLevel) {
u8 value = static_cast<u8>(batteryLevel) << 2; // Bits 2:4 are the battery level from 0 to 5
if (adapterConnected) value |= 1 << 0; // Bit 0 shows if the charger is connected
if (charging) value |= 1 << 1; // Bit 1 shows if we're charging
return value;
}
NCCH* getCXI() {
if (loadedCXI.has_value()) {
return &loadedCXI.value();
@ -169,18 +204,20 @@ public:
// Allocate "size" bytes of RAM starting from FCRAM index "paddr" (We pick it ourself if paddr == 0)
// And map them to virtual address "vaddr" (We also pick it ourself if vaddr == 0).
// If the "linear" flag is on, the paddr pages must be adjacent in FCRAM
// This function is for interacting with the *user* portion of FCRAM mainly. For OS RAM, we use other internal functions below
// r, w, x: Permissions for the allocated memory
// adjustAddrs: If it's true paddr == 0 or vaddr == 0 tell the allocator to pick its own addresses. Used for eg svc ControlMemory
// isMap: Shows whether this is a reserve operation, that allocates memory and maps it to the addr space, or if it's a map operation,
// which just maps memory from paddr to vaddr without hassle. The latter is useful for shared memory mapping, the "map" ControlMemory, op, etc
// Returns the vaddr the FCRAM was mapped to or nullopt if allocation failed
std::optional<u32> allocateMemory(u32 vaddr, u32 paddr, u32 size, bool linear, bool r = true, bool w = true, bool x = true,
bool adjustsAddrs = false);
bool adjustsAddrs = false, bool isMap = false);
KernelMemoryTypes::MemoryInfo queryMemory(u32 vaddr);
// For internal use:
// Reserve FCRAM linearly starting from physical address "paddr" (paddr == 0 is NOT special) with a size of "size"
// Without actually mapping the memory to a vaddr
// Returns true if the reservation succeeded and false if not
bool reserveMemory(u32 paddr, u32 size);
// For internal use
// Allocates a "size"-sized chunk of system FCRAM and returns the index of physical FCRAM used for the allocation
// Used for allocating things like shared memory and the like
u32 allocateSysMemory(u32 size);
// Map a shared memory block to virtual address vaddr with permissions "myPerms"
// The kernel has a second permission parameter in MapMemoryBlock but not sure what's used for
@ -188,6 +225,10 @@ public:
// Returns a pointer to the FCRAM block used for the memory if allocation succeeded
u8* mapSharedMemory(Handle handle, u32 vaddr, u32 myPerms, u32 otherPerms);
// Mirrors the page mapping for "size" bytes starting from sourceAddress, to "size" bytes in destAddress
// All of the above must be page-aligned.
void mirrorMapping(u32 destAddress, u32 sourceAddress, u32 size);
// Backup of the game's CXI partition info, if any
std::optional<NCCH> loadedCXI = std::nullopt;
// File handle for reading the loaded ncch
@ -196,4 +237,6 @@ public:
u8* getDSPMem() { return dspRam; }
u8* getDSPDataMem() { return &dspRam[DSP_DATA_MEMORY_OFFSET]; }
u8* getDSPCodeMem() { return &dspRam[DSP_CODE_MEMORY_OFFSET]; }
u32 getUsedUserMem() { return usedUserMemory; }
};

View file

@ -30,6 +30,9 @@
#include "gl3w.h"
// Uncomment the following define if you want GL objects to automatically free themselves when their lifetime ends
// #define OPENGL_DESTRUCTORS
namespace OpenGL {
// Workaround for using static_assert inside constexpr if
@ -51,7 +54,9 @@ namespace OpenGL {
}
}
~VertexArray() { glDeleteVertexArrays(1, &m_handle); }
#ifdef OPENGL_DESTRUCTORS
~VertexArray() { free(); }
#endif
GLuint handle() { return m_handle; }
bool exists() { return m_handle != 0; }
void bind() { glBindVertexArray(m_handle); }
@ -121,6 +126,10 @@ namespace OpenGL {
void enableAttribute(GLuint index) { glEnableVertexAttribArray(index); }
void disableAttribute(GLuint index) { glDisableVertexAttribArray(index); }
void free() {
glDeleteVertexArrays(1, &m_handle);
}
};
enum FramebufferTypes {
@ -129,6 +138,25 @@ namespace OpenGL {
DrawAndReadFramebuffer = GL_FRAMEBUFFER
};
// Texture filters
enum Filters {
Nearest = GL_NEAREST,
Linear = GL_LINEAR,
NearestMipmapNearest = GL_NEAREST_MIPMAP_NEAREST,
NearestMipmapLinear = GL_NEAREST_MIPMAP_LINEAR,
LinearMipmapNearest = GL_LINEAR_MIPMAP_NEAREST,
LinearMipmapLinear = GL_LINEAR_MIPMAP_LINEAR
};
// Wrapping mode for texture UVs
enum WrappingMode {
ClampToEdge = GL_CLAMP_TO_EDGE,
ClampToBorder = GL_CLAMP_TO_BORDER,
RepeatMirrored = GL_MIRRORED_REPEAT,
Repeat = GL_REPEAT,
MirrorClampToEdge = GL_MIRROR_CLAMP_TO_EDGE
};
struct Texture {
GLuint m_handle = 0;
int m_width, m_height;
@ -165,12 +193,48 @@ namespace OpenGL {
create(width, height, internalFormat, GL_TEXTURE_2D_MULTISAMPLE, samples);
}
~Texture() { glDeleteTextures(1, &m_handle); }
// Creates a depth, stencil or depth-stencil texture
void createDSTexture(int width, int height, GLenum internalFormat, GLenum format, const void* data = nullptr,
GLenum type = GL_FLOAT, GLenum binding = GL_TEXTURE_2D) {
m_width = width;
m_height = height;
m_binding = binding;
glGenTextures(1, &m_handle);
bind();
glTexImage2D(binding, 0, internalFormat, width, height, 0, format, type, data);
}
void setWrapS(WrappingMode mode) {
glTexParameteri(m_binding, GL_TEXTURE_WRAP_S, static_cast<GLint>(mode));
}
void setWrapT(WrappingMode mode) {
glTexParameteri(m_binding, GL_TEXTURE_WRAP_T, static_cast<GLint>(mode));
}
void setWrapR(WrappingMode mode) {
glTexParameteri(m_binding, GL_TEXTURE_WRAP_R, static_cast<GLint>(mode));
}
void setMinFilter(Filters filter) {
glTexParameteri(m_binding, GL_TEXTURE_MIN_FILTER, static_cast<GLint>(filter));
}
void setMagFilter(Filters filter) {
glTexParameteri(m_binding, GL_TEXTURE_MAG_FILTER, static_cast<GLint>(filter));
}
#ifdef OPENGL_DESTRUCTORS
~Texture() { free(); }
#endif
GLuint handle() { return m_handle; }
bool exists() { return m_handle != 0; }
void bind() { glBindTexture(m_binding, m_handle); }
int width() { return m_width; }
int height() { return m_height; }
void free() { glDeleteTextures(1, &m_handle); }
};
struct Framebuffer {
@ -189,11 +253,14 @@ namespace OpenGL {
}
}
~Framebuffer() { glDeleteFramebuffers(1, &m_handle); }
#ifdef OPENGL_DESTRUCTORS
~Framebuffer() { free(); }
#endif
GLuint handle() { return m_handle; }
bool exists() { return m_handle != 0; }
void bind(GLenum target) { glBindFramebuffer(target, m_handle); }
void bind(FramebufferTypes target) { bind(static_cast<GLenum>(target)); }
void free() { glDeleteFramebuffers(1, &m_handle); }
void createWithTexture(Texture& tex, GLenum mode = GL_FRAMEBUFFER, GLenum textureType = GL_TEXTURE_2D) {
m_textureType = textureType;
@ -313,10 +380,13 @@ namespace OpenGL {
}
}
~VertexBuffer() { glDeleteBuffers(1, &m_handle); }
#ifdef OPENGL_DESTRUCTORS
~VertexBuffer() { free(); }
#endif
GLuint handle() { return m_handle; }
bool exists() { return m_handle != 0; }
void bind() { glBindBuffer(GL_ARRAY_BUFFER, m_handle); }
void free() { glDeleteBuffers(1, &m_handle); }
// Reallocates the buffer on every call. Prefer the sub version if possible.
template <typename VertType>

View file

@ -0,0 +1,99 @@
#pragma once
#include <array>
#include "helpers.hpp"
#include "logger.hpp"
#include "opengl.hpp"
#include "surface_cache.hpp"
#include "textures.hpp"
// More circular dependencies!
class GPU;
struct Vertex {
OpenGL::vec4 position;
OpenGL::vec4 colour;
OpenGL::vec2 UVs;
};
class Renderer {
GPU& gpu;
OpenGL::Program triangleProgram;
OpenGL::Program displayProgram;
OpenGL::VertexArray vao;
OpenGL::VertexBuffer vbo;
GLint alphaControlLoc = -1;
GLint texUnitConfigLoc = -1;
// Depth configuration uniform locations
GLint depthOffsetLoc = -1;
GLint depthScaleLoc = -1;
GLint depthmapEnableLoc = -1;
u32 oldAlphaControl = 0;
u32 oldTexUnitConfig = 0;
float oldDepthScale = -1.0;
float oldDepthOffset = 0.0;
bool oldDepthmapEnable = false;
SurfaceCache<DepthBuffer, 10> depthBufferCache;
SurfaceCache<ColourBuffer, 10> colourBufferCache;
SurfaceCache<Texture, 256> textureCache;
OpenGL::uvec2 fbSize; // The size of the framebuffer (ie both the colour and depth buffer)'
u32 colourBufferLoc; // Location in 3DS VRAM for the colour buffer
ColourBuffer::Formats colourBufferFormat; // Format of the colours stored in the colour buffer
// Same for the depth/stencil buffer
u32 depthBufferLoc;
DepthBuffer::Formats depthBufferFormat;
// Dummy VAO/VBO for blitting the final output
OpenGL::VertexArray dummyVAO;
OpenGL::VertexBuffer dummyVBO;
static constexpr u32 regNum = 0x300; // Number of internal PICA registers
const std::array<u32, regNum>& regs;
OpenGL::Framebuffer getColourFBO();
OpenGL::Texture getTexture(Texture& tex);
MAKE_LOG_FUNCTION(log, rendererLogger)
void setupBlending();
void bindDepthBuffer();
public:
Renderer(GPU& gpu, const std::array<u32, regNum>& internalRegs) : gpu(gpu), regs(internalRegs) {}
void reset();
void display(); // Display the 3DS screen contents to the window
void initGraphicsContext(); // Initialize graphics context
void getGraphicsContext(); // Set up graphics context for rendering
void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control); // Clear a GPU buffer in VRAM
void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags); // Perform display transfer
void drawVertices(OpenGL::Primitives primType, Vertex* vertices, u32 count); // Draw the given vertices
void setFBSize(u32 width, u32 height) {
fbSize.x() = width;
fbSize.y() = height;
}
void setColourFormat(ColourBuffer::Formats format) { colourBufferFormat = format; }
void setColourFormat(u32 format) { colourBufferFormat = static_cast<ColourBuffer::Formats>(format); }
void setDepthFormat(DepthBuffer::Formats format) { depthBufferFormat = format; }
void setDepthFormat(u32 format) {
if (format == 1) {
Helpers::panic("[PICA] Undocumented depth-stencil mode!");
}
depthBufferFormat = static_cast<DepthBuffer::Formats>(format);
}
void setColourBufferLoc(u32 loc) { colourBufferLoc = loc; }
void setDepthBufferLoc(u32 loc) { depthBufferLoc = loc; }
static constexpr u32 vertexBufferSize = 0x10000;
};

View file

@ -0,0 +1,66 @@
#pragma once
#include <functional>
#include <optional>
#include "surfaces.hpp"
#include "textures.hpp"
// Surface cache class that can fit "capacity" instances of the "SurfaceType" class of surfaces
// SurfaceType *must* have all of the following
// - An "allocate" function that allocates GL resources for the surfaces
// - A "free" function that frees up all resources the surface is taking up
// - A "matches" function that, when provided with a SurfaceType object reference
// Will tell us if the 2 surfaces match (Only as far as location in VRAM, format, dimensions, etc)
// Are concerned. We could overload the == operator, but that implies full equality
// Including equality of the allocated OpenGL resources, which we don't want
// - A "valid" member that tells us whether the function is still valid or not
template <typename SurfaceType, size_t capacity>
class SurfaceCache {
// Vanilla std::optional can't hold actual references
using OptionalRef = std::optional<std::reference_wrapper<SurfaceType>>;
static_assert(std::is_same<SurfaceType, ColourBuffer>() || std::is_same<SurfaceType, DepthBuffer>() ||
std::is_same<SurfaceType, Texture>(), "Invalid surface type");
size_t size;
std::array<SurfaceType, capacity> buffer;
public:
void reset() {
size = 0;
for (auto& e : buffer) { // Free the VRAM of all surfaces
e.free();
}
}
OptionalRef find(SurfaceType& other) {
for (auto& e : buffer) {
if (e.matches(other) && e.valid)
return e;
}
return std::nullopt;
}
// Adds a surface object to the cache and returns it
SurfaceType& add(const SurfaceType& surface) {
if (size >= capacity) {
Helpers::panic("Surface cache full! Add emptying!");
}
size++;
// Find an invalid entry in the cache and overwrite it with the new surface
for (auto& e : buffer) {
if (!e.valid) {
e = surface;
e.allocate();
return e;
}
}
// This should be unreachable but helps to panic anyways
Helpers::panic("Couldn't add surface to cache\n");
}
SurfaceType& operator[](size_t i) {
return buffer[i];
}
};

View file

@ -0,0 +1,184 @@
#pragma once
#include "boost/icl/interval.hpp"
#include "helpers.hpp"
#include "opengl.hpp"
template <typename T>
using Interval = boost::icl::right_open_interval<T>;
struct ColourBuffer {
enum class Formats : u32 {
RGBA8 = 0,
BGR8 = 1,
RGB5A1 = 2,
RGB565 = 3,
RGBA4 = 4,
Trash1 = 5, Trash2 = 6, Trash3 = 7 // Technically selectable, but their function is unknown
};
u32 location;
Formats format;
OpenGL::uvec2 size;
bool valid;
// Range of VRAM taken up by buffer
Interval<u32> range;
// OpenGL resources allocated to buffer
OpenGL::Texture texture;
OpenGL::Framebuffer fbo;
ColourBuffer() : valid(false) {}
ColourBuffer(u32 loc, Formats format, u32 x, u32 y, bool valid = true)
: location(loc), format(format), size({x, y}), valid(valid) {
u64 endLoc = (u64)loc + sizeInBytes();
// Check if start and end are valid here
range = Interval<u32>(loc, (u32)endLoc);
}
void allocate() {
// Create texture for the FBO, setting up filters and the like
// Reading back the current texture is slow, but allocate calls should be few and far between.
// If this becomes a bottleneck, we can fix it semi-easily
auto prevTexture = OpenGL::getTex2D();
texture.create(size.x(), size.y(), GL_RGBA8);
texture.bind();
texture.setMinFilter(OpenGL::Linear);
texture.setMagFilter(OpenGL::Linear);
glBindTexture(GL_TEXTURE_2D, prevTexture);
//Helpers::panic("Creating FBO: %d, %d\n", size.x(), size.y());
fbo.createWithDrawTexture(texture);
fbo.bind(OpenGL::DrawAndReadFramebuffer);
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(size.x(), size.y());
OpenGL::setClearColor(0.0, 0.0, 0.0, 1.0);
OpenGL::clearColor();
OpenGL::setViewport(oldViewport[0], oldViewport[1], oldViewport[2], oldViewport[3]);
}
void free() {
valid = false;
if (texture.exists() || fbo.exists())
Helpers::panic("Make this buffer free itself");
}
bool matches(ColourBuffer& other) {
return location == other.location && format == other.format &&
size.x() == other.size.x() && size.y() == other.size.y();
}
// Size occupied by each pixel in bytes
// All formats are 16BPP except for RGBA8 (32BPP) and BGR8 (24BPP)
size_t sizePerPixel() {
switch (format) {
case Formats::BGR8: return 3;
case Formats::RGBA8: return 4;
default: return 2;
}
}
size_t sizeInBytes() {
return (size_t)size.x() * (size_t)size.y() * sizePerPixel();
}
};
struct DepthBuffer {
enum class Formats : u32 {
Depth16 = 0,
Garbage = 1,
Depth24 = 2,
Depth24Stencil8 = 3
};
u32 location;
Formats format;
OpenGL::uvec2 size; // Implicitly set to the size of the framebuffer
bool valid;
// Range of VRAM taken up by buffer
Interval<u32> range;
// OpenGL texture used for storing depth/stencil
OpenGL::Texture texture;
DepthBuffer() : valid(false) {}
DepthBuffer(u32 loc, Formats format, u32 x, u32 y, bool valid = true) :
location(loc), format(format), size({x, y}), valid(valid) {
u64 endLoc = (u64)loc + sizeInBytes();
// Check if start and end are valid here
range = Interval<u32>(loc, (u32)endLoc);
}
bool hasStencil() {
return format == Formats::Depth24Stencil8;
}
void allocate() {
// Create texture for the FBO, setting up filters and the like
// Reading back the current texture is slow, but allocate calls should be few and far between.
// If this becomes a bottleneck, we can fix it semi-easily
auto prevTexture = OpenGL::getTex2D();
// Internal formats for the texture based on format
static constexpr std::array<GLenum, 4> internalFormats = {
GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT24, GL_DEPTH24_STENCIL8
};
// Format of the texture
static constexpr std::array<GLenum, 4> formats = {
GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT, GL_DEPTH_STENCIL
};
static constexpr std::array<GLenum, 4> types = {
GL_UNSIGNED_SHORT, GL_UNSIGNED_INT, GL_UNSIGNED_INT, GL_UNSIGNED_INT_24_8
};
auto internalFormat = internalFormats[(int)format];
auto fmt = formats[(int)format];
auto type = types[(int)format];
texture.createDSTexture(size.x(), size.y(), internalFormat, fmt, nullptr, type, GL_TEXTURE_2D);
texture.bind();
texture.setMinFilter(OpenGL::Nearest);
texture.setMagFilter(OpenGL::Nearest);
glBindTexture(GL_TEXTURE_2D, prevTexture);
}
void free() {
valid = false;
printf("Make this depth buffer free itself\n");
}
bool matches(DepthBuffer& other) {
return location == other.location && format == other.format &&
size.x() == other.size.x() && size.y() == other.size.y();
}
// Size occupied by each pixel in bytes
size_t sizePerPixel() {
switch (format) {
case Formats::Depth16: return 2;
case Formats::Depth24: return 3;
case Formats::Depth24Stencil8: return 4;
default: return 1; // Invalid format
}
}
size_t sizeInBytes() {
return (size_t)size.x() * (size_t)size.y() * sizePerPixel();
}
};

View file

@ -0,0 +1,85 @@
#pragma once
#include <array>
#include <string>
#include "boost/icl/interval.hpp"
#include "helpers.hpp"
#include "opengl.hpp"
template <typename T>
using Interval = boost::icl::right_open_interval<T>;
struct Texture {
enum class Formats : u32 {
RGBA8 = 0,
RGB8 = 1,
RGBA5551 = 2,
RGB565 = 3,
RGBA4 = 4,
IA8 = 5,
RG8 = 6,
I8 = 7,
A8 = 8,
IA4 = 9,
I4 = 10,
A4 = 11,
ETC1 = 12,
ETC1A4 = 13,
Trash1 = 14, Trash2 = 15 // TODO: What are these?
};
u32 location;
u32 config; // Magnification/minification filter, wrapping configs, etc
Formats format;
OpenGL::uvec2 size;
bool valid;
// Range of VRAM taken up by buffer
Interval<u32> range;
// OpenGL resources allocated to buffer
OpenGL::Texture texture;
Texture() : valid(false) {}
Texture(u32 loc, Formats format, u32 x, u32 y, u32 config, bool valid = true)
: location(loc), format(format), size({x, y}), config(config), valid(valid) {
u64 endLoc = (u64)loc + sizeInBytes();
// Check if start and end are valid here
range = Interval<u32>(loc, (u32)endLoc);
}
// For 2 textures to "match" we only care about their locations, formats, and dimensions to match
// For other things, such as filtering mode, etc, we can just switch the attributes of the cached texture
bool matches(Texture& other) {
return location == other.location && format == other.format &&
size.x() == other.size.x() && size.y() == other.size.y();
}
void allocate();
void setNewConfig(u32 newConfig);
void decodeTexture(const void* data);
void free();
u64 sizeInBytes();
u32 decodeTexel(u32 u, u32 v, Formats fmt, const void* data);
// Get the morton interleave offset of a texel based on its U and V values
static u32 mortonInterleave(u32 u, u32 v);
// Get the byte offset of texel (u, v) in the texture
static u32 getSwizzledOffset(u32 u, u32 v, u32 width, u32 bytesPerPixel);
static u32 getSwizzledOffset_4bpp(u32 u, u32 v, u32 width);
// Returns the string representation of a texture format
static std::string textureFormatToString(Formats fmt);
// Returns the format of this texture as a string
std::string formatToString() {
return textureFormatToString(format);
}
// Returns the texel at coordinates (u, v) of an ETC1(A4) texture
// TODO: Make hasAlpha a template parameter
u32 getTexelETC(bool hasAlpha, u32 u, u32 v, u32 width, const void* data);
u32 decodeETC(u32 alpha, u32 u, u32 v, u64 colourData);
};

19
include/services/ac.hpp Normal file
View file

@ -0,0 +1,19 @@
#pragma once
#include "helpers.hpp"
#include "kernel_types.hpp"
#include "logger.hpp"
#include "memory.hpp"
class ACService {
Handle handle = KernelHandles::AC;
Memory& mem;
MAKE_LOG_FUNCTION(log, acLogger)
// Service commands
void setClientVersion(u32 messagePointer);
public:
ACService(Memory& mem) : mem(mem) {}
void reset();
void handleSyncRequest(u32 messagePointer);
};

19
include/services/act.hpp Normal file
View file

@ -0,0 +1,19 @@
#pragma once
#include "helpers.hpp"
#include "kernel_types.hpp"
#include "logger.hpp"
#include "memory.hpp"
class ACTService {
Handle handle = KernelHandles::ACT;
Memory& mem;
MAKE_LOG_FUNCTION(log, actLogger)
// Service commands
void initialize(u32 messagePointer);
public:
ACTService(Memory& mem) : mem(mem) {}
void reset();
void handleSyncRequest(u32 messagePointer);
};

20
include/services/am.hpp Normal file
View file

@ -0,0 +1,20 @@
#pragma once
#include "helpers.hpp"
#include "kernel_types.hpp"
#include "logger.hpp"
#include "memory.hpp"
class AMService {
Handle handle = KernelHandles::AM;
Memory& mem;
MAKE_LOG_FUNCTION(log, amLogger)
// Service commands
void getDLCTitleInfo(u32 messagePointer);
void listTitleInfo(u32 messagePointer);
public:
AMService(Memory& mem) : mem(mem) {}
void reset();
void handleSyncRequest(u32 messagePointer);
};

View file

@ -1,12 +1,28 @@
#pragma once
#include <optional>
#include "helpers.hpp"
#include "kernel_types.hpp"
#include "logger.hpp"
#include "memory.hpp"
// Yay, more circular dependencies
class Kernel;
enum class ConsoleModel : u32 {
Old3DS, New3DS
};
class APTService {
Handle handle = KernelHandles::APT;
Memory& mem;
Kernel& kernel;
std::optional<Handle> lockHandle = std::nullopt;
std::optional<Handle> notificationEvent = std::nullopt;
std::optional<Handle> resumeEvent = std::nullopt;
ConsoleModel model = ConsoleModel::Old3DS;
MAKE_LOG_FUNCTION(log, aptLogger)
// Service commands
@ -16,16 +32,40 @@ class APTService {
void checkNew3DS(u32 messagePointer);
void checkNew3DSApp(u32 messagePointer);
void enable(u32 messagePointer);
void getSharedFont(u32 messagePointer);
void getWirelessRebootInfo(u32 messagePointer);
void glanceParameter(u32 messagePointer);
void initialize(u32 messagePointer);
void inquireNotification(u32 messagePointer);
void notifyToWait(u32 messagePointer);
void receiveParameter(u32 messagePointer);
void replySleepQuery(u32 messagePointer);
void setApplicationCpuTimeLimit(u32 messagePointer);
void setScreencapPostPermission(u32 messagePointer);
void theSmashBrosFunction(u32 messagePointer);
// Percentage of the syscore available to the application, between 5% and 89%
u32 cpuTimeLimit;
enum class NotificationType : u32 {
None = 0,
HomeButton1 = 1,
HomeButton2 = 2,
SleepQuery = 3,
SleepCanceledByOpen = 4,
SleepAccepted = 5,
SleepAwake = 6,
Shutdown = 7,
PowerButtonClick = 8,
PowerButtonClear = 9,
TrySleep = 10,
OrderToClose = 11
};
u32 screencapPostPermission;
public:
APTService(Memory& mem) : mem(mem) {}
APTService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
void reset();
void handleSyncRequest(u32 messagePointer);
};

27
include/services/boss.hpp Normal file
View file

@ -0,0 +1,27 @@
#pragma once
#include "helpers.hpp"
#include "kernel_types.hpp"
#include "logger.hpp"
#include "memory.hpp"
class BOSSService {
Handle handle = KernelHandles::BOSS;
Memory& mem;
MAKE_LOG_FUNCTION(log, bossLogger)
// Service commands
void initializeSession(u32 messagePointer);
void getOptoutFlag(u32 messagePointer);
void getStorageEntryInfo(u32 messagePointer); // Unknown what this is, name taken from Citra
void getTaskIdList(u32 messagePointer);
void receiveProperty(u32 messagePointer);
void registerStorageEntry(u32 messagePointer);
void unregisterStorage(u32 messagePointer);
void unregisterTask(u32 messagePointer);
s8 optoutFlag;
public:
BOSSService(Memory& mem) : mem(mem) {}
void reset();
void handleSyncRequest(u32 messagePointer);
};

20
include/services/cam.hpp Normal file
View file

@ -0,0 +1,20 @@
#pragma once
#include "helpers.hpp"
#include "kernel_types.hpp"
#include "logger.hpp"
#include "memory.hpp"
class CAMService {
Handle handle = KernelHandles::CAM;
Memory& mem;
MAKE_LOG_FUNCTION(log, camLogger)
// Service commands
void driverInitialize(u32 messagePointer);
void getMaxLines(u32 messagePointer);
public:
CAMService(Memory& mem) : mem(mem) {}
void reset();
void handleSyncRequest(u32 messagePointer);
};

View file

@ -1,18 +1,25 @@
#pragma once
#include <optional>
#include "helpers.hpp"
#include "kernel_types.hpp"
#include "logger.hpp"
#include "memory.hpp"
class Kernel;
class CECDService {
Handle handle = KernelHandles::CECD;
Memory& mem;
Kernel& kernel;
MAKE_LOG_FUNCTION(log, cecdLogger)
// Service commands
std::optional<Handle> infoEvent;
// Service commands
void getInfoEventHandle(u32 messagePointer);
public:
CECDService(Memory& mem) : mem(mem) {}
CECDService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
void reset();
void handleSyncRequest(u32 messagePointer);
};

View file

@ -1,15 +1,23 @@
#pragma once
#include <cstring>
#include "helpers.hpp"
#include "logger.hpp"
#include "memory.hpp"
#include "region_codes.hpp"
class CFGService {
Handle handle = KernelHandles::CFG;
Memory& mem;
CountryCodes country = CountryCodes::US; // Default to USA
MAKE_LOG_FUNCTION(log, cfgLogger)
void writeStringU16(u32 pointer, const std::u16string& string);
// Service functions
void getConfigInfoBlk2(u32 messagePointer);
void getRegionCanadaUSA(u32 messagePointer);
void getSystemModel(u32 messagePointer);
void genUniqueConsoleHash(u32 messagePointer);
void secureInfoGetRegion(u32 messagePointer);
public:

View file

@ -1,4 +1,6 @@
#pragma once
#include <array>
#include <optional>
#include "helpers.hpp"
#include "logger.hpp"
#include "memory.hpp"
@ -41,17 +43,39 @@ public:
}
};
// Circular dependencies!
class Kernel;
class DSPService {
Handle handle = KernelHandles::DSP;
Memory& mem;
Kernel& kernel;
MAKE_LOG_FUNCTION(log, dspServiceLogger)
// Number of DSP pipes
static constexpr size_t pipeCount = 8;
DSPPipe audioPipe;
// DSP service event handles
using DSPEvent = std::optional<Handle>;
DSPEvent semaphoreEvent;
DSPEvent interrupt0;
DSPEvent interrupt1;
std::array<DSPEvent, pipeCount> pipeEvents;
DSPEvent& getEventRef(u32 type, u32 pipe);
static constexpr size_t maxEventCount = 6;
// Total number of DSP service events registered with registerInterruptEvents
size_t totalEventCount;
// Service functions
void convertProcessAddressFromDspDram(u32 messagePointer); // Nice function name
void flushDataCache(u32 messagePointer);
void getHeadphoneStatus(u32 messagePointer);
void getSemaphoreHandle(u32 messagePointer);
void getSemaphoreEventHandle(u32 messagePointer);
void invalidateDCache(u32 messagePointer);
void loadComponent(u32 messagePointer);
void readPipeIfPossible(u32 messagePointer);
void registerInterruptEvents(u32 messagePointer);
@ -60,7 +84,7 @@ class DSPService {
void writeProcessPipe(u32 messagePointer);
public:
DSPService(Memory& mem) : mem(mem) {}
DSPService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
void reset();
void handleSyncRequest(u32 messagePointer);
@ -69,4 +93,6 @@ public:
Stereo = 1,
Surround = 2
};
void signalEvents();
};

36
include/services/frd.hpp Normal file
View file

@ -0,0 +1,36 @@
#pragma once
#include <cassert>
#include "helpers.hpp"
#include "kernel_types.hpp"
#include "logger.hpp"
#include "memory.hpp"
// It's important to keep this struct to 16 bytes as we use its sizeof in the service functions in frd.cpp
struct FriendKey {
u32 friendID;
u32 dummy;
u64 friendCode;
};
static_assert(sizeof(FriendKey) == 16);
class FRDService {
Handle handle = KernelHandles::FRD;
Memory& mem;
MAKE_LOG_FUNCTION(log, frdLogger)
// Service commands
void attachToEventNotification(u32 messagePointer);
void getFriendKeyList(u32 messagePointer);
void getMyFriendKey(u32 messagePointer);
void getMyMii(u32 messagePointer);
void getMyPresence(u32 messagePointer);
void getMyProfile(u32 messagePointer);
void getMyScreenName(u32 messsagePointer);
void setClientSDKVersion(u32 messagePointer);
void setNotificationMask(u32 messagePointer);
public:
FRDService(Memory& mem) : mem(mem) {}
void reset();
void handleSyncRequest(u32 messagePointer);
};

View file

@ -1,7 +1,9 @@
#pragma once
#include "fs/archive_ext_save_data.hpp"
#include "fs/archive_ncch.hpp"
#include "fs/archive_save_data.hpp"
#include "fs/archive_sdmc.hpp"
#include "fs/archive_self_ncch.hpp"
#include "helpers.hpp"
#include "kernel_types.hpp"
#include "logger.hpp"
@ -17,21 +19,38 @@ class FSService {
MAKE_LOG_FUNCTION(log, fsLogger)
// The different filesystem archives (Save data, RomFS+ExeFS, etc)
// The different filesystem archives (Save data, SelfNCCH, SDMC, NCCH, ExtData, etc)
SelfNCCHArchive selfNcch;
SaveDataArchive saveData;
SDMCArchive sdmc;
NCCHArchive ncch;
ArchiveBase* getArchiveFromID(u32 id);
ExtSaveDataArchive extSaveData_nand;
ExtSaveDataArchive extSaveData_cart;
ExtSaveDataArchive sharedExtSaveData_nand;
ExtSaveDataArchive sharedExtSaveData_cart;
ArchiveBase* getArchiveFromID(u32 id, const FSPath& archivePath);
std::optional<Handle> openArchiveHandle(u32 archiveID, const FSPath& path);
std::optional<Handle> openFileHandle(ArchiveBase* archive, const FSPath& path);
Rust::Result<Handle, FSResult> openDirectoryHandle(ArchiveBase* archive, const FSPath& path);
std::optional<Handle> openFileHandle(ArchiveBase* archive, const FSPath& path, const FSPath& archivePath, const FilePerms& perms);
FSPath readPath(u32 type, u32 pointer, u32 size);
// Service commands
void createDirectory(u32 messagePointer);
void createFile(u32 messagePointer);
void closeArchive(u32 messagePointer);
void controlArchive(u32 messagePointer);
void deleteFile(u32 messagePointer);
void formatSaveData(u32 messagePointer);
void getFreeBytes(u32 messagePointer);
void getFormatInfo(u32 messagePointer);
void getPriority(u32 messagePointer);
void initialize(u32 messagePointer);
void initializeWithSdkVersion(u32 messagePointer);
void isSdmcDetected(u32 messagePointer);
void openArchive(u32 messagePointer);
void openDirectory(u32 messagePointer);
void openFile(u32 messagePointer);
void openFileDirectly(u32 messagePointer);
void setPriority(u32 messagePointer);
@ -40,7 +59,13 @@ class FSService {
u32 priority;
public:
FSService(Memory& mem, Kernel& kernel) : mem(mem), saveData(mem), sdmc(mem), selfNcch(mem), kernel(kernel) {}
FSService(Memory& mem, Kernel& kernel) : mem(mem), saveData(mem), extSaveData_nand(mem, "NAND"),
sharedExtSaveData_nand(mem, "NAND", true), extSaveData_cart(mem, "CartSave"), sharedExtSaveData_cart(mem, "CartSave", true),
sdmc(mem), selfNcch(mem), ncch(mem), kernel(kernel)
{}
void reset();
void handleSyncRequest(u32 messagePointer);
// Creates directories for NAND, ExtSaveData, etc if they don't already exist. Should be executed after loading a new ROM.
void initializeFilesystem();
};

View file

@ -1,5 +1,6 @@
#pragma once
#include <cstring>
#include <optional>
#include "PICA/gpu.hpp"
#include "helpers.hpp"
#include "kernel_types.hpp"
@ -16,16 +17,21 @@ enum class GPUInterrupt : u8 {
DMA = 6
};
// More circular dependencies
class Kernel;
class GPUService {
Handle handle = KernelHandles::GPU;
Memory& mem;
GPU& gpu;
Kernel& kernel;
u32& currentPID; // Process ID of the current process
u8* sharedMem; // Pointer to GSP shared memory
// At any point in time only 1 process has privileges to use rendering functions
// This is the PID of that process
u32 privilegedProcess;
std::optional<Handle> interruptEvent;
MAKE_LOG_FUNCTION(log, gspGPULogger)
void processCommandBuffer();
@ -37,6 +43,7 @@ class GPUService {
void setAxiConfigQoSMode(u32 messagePointer);
void setInternalPriorities(u32 messagePointer);
void setLCDForceBlack(u32 messagePointer);
void storeDataCache(u32 messagePointer);
void triggerCmdReqQueue(u32 messagePointer);
void writeHwRegs(u32 messagePointer);
void writeHwRegsWithMask(u32 messagePointer);
@ -46,10 +53,12 @@ class GPUService {
void memoryFill(u32* cmd);
void triggerDisplayTransfer(u32* cmd);
void triggerDMARequest(u32* cmd);
void triggerTextureCopy(u32* cmd);
void flushCacheRegions(u32* cmd);
public:
GPUService(Memory& mem, GPU& gpu, u32& currentPID) : mem(mem), gpu(gpu), currentPID(currentPID) {}
GPUService(Memory& mem, GPU& gpu, Kernel& kernel, u32& currentPID) : mem(mem), gpu(gpu),
kernel(kernel), currentPID(currentPID) {}
void reset();
void handleSyncRequest(u32 messagePointer);
void requestInterrupt(GPUInterrupt type);

View file

@ -1,17 +1,26 @@
#pragma once
#include <array>
#include <optional>
#include "helpers.hpp"
#include "kernel_types.hpp"
#include "logger.hpp"
#include "memory.hpp"
// Circular dependency because we need HID to spawn events
class Kernel;
class HIDService {
Handle handle = KernelHandles::HID;
Memory& mem;
Kernel& kernel;
u8* sharedMem = nullptr; // Pointer to HID shared memory
bool accelerometerEnabled;
bool eventsInitialized;
bool gyroEnabled;
std::array<std::optional<Handle>, 5> events;
MAKE_LOG_FUNCTION(log, hidLogger)
// Service commands
@ -22,9 +31,10 @@ class HIDService {
void getIPCHandles(u32 messagePointer);
public:
HIDService(Memory& mem) : mem(mem) {}
HIDService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
void reset();
void handleSyncRequest(u32 messagePointer);
void updateInputs();
void setSharedMem(u8* ptr) {
sharedMem = ptr;

View file

@ -0,0 +1,20 @@
#pragma once
#include "helpers.hpp"
#include "kernel_types.hpp"
#include "logger.hpp"
#include "memory.hpp"
class LDRService {
Handle handle = KernelHandles::LDR_RO;
Memory& mem;
MAKE_LOG_FUNCTION(log, ldrLogger)
// Service commands
void initialize(u32 messagePointer);
void loadCRR(u32 messagePointer);
public:
LDRService(Memory& mem) : mem(mem) {}
void reset();
void handleSyncRequest(u32 messagePointer);
};

View file

@ -10,6 +10,17 @@ class MICService {
MAKE_LOG_FUNCTION(log, micLogger)
// Service commands
void getGain(u32 messagePointer);
void mapSharedMem(u32 messagePointer);
void setClamp(u32 messagePointer);
void setGain(u32 messagePointer);
void setPower(u32 messagePointer);
void startSampling(u32 messagePointer);
void theCaptainToadFunction(u32 messagePointer);
u8 gain = 0; // How loud our microphone input signal is
bool micEnabled = false;
bool shouldClamp = false;
public:
MICService(Memory& mem) : mem(mem) {}

View file

@ -11,6 +11,8 @@ class NDMService {
// Service commands
void overrideDefaultDaemons(u32 messagePointer);
void resumeDaemons(u32 messagePointer);
void resumeScheduler(u32 messagePointer);
void suspendDaemons(u32 messagePointer);
void suspendScheduler(u32 messagePointer);

28
include/services/nfc.hpp Normal file
View file

@ -0,0 +1,28 @@
#pragma once
#include "helpers.hpp"
#include "kernel_types.hpp"
#include "logger.hpp"
#include "memory.hpp"
// You know the drill
class Kernel;
class NFCService {
Handle handle = KernelHandles::NFC;
Memory& mem;
Kernel& kernel;
MAKE_LOG_FUNCTION(log, nfcLogger)
// Kernel events signaled when an NFC tag goes in and out of range respectively
std::optional<Handle> tagInRangeEvent, tagOutOfRangeEvent;
// Service commands
void initialize(u32 messagePointer);
void getTagInRangeEvent(u32 messagePointer);
void getTagOutOfRangeEvent(u32 messagePointer);
public:
NFCService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
void reset();
void handleSyncRequest(u32 messagePointer);
};

19
include/services/nim.hpp Normal file
View file

@ -0,0 +1,19 @@
#pragma once
#include "helpers.hpp"
#include "kernel_types.hpp"
#include "logger.hpp"
#include "memory.hpp"
class NIMService {
Handle handle = KernelHandles::NIM;
Memory& mem;
MAKE_LOG_FUNCTION(log, nimLogger)
// Service commands
void initialize(u32 messagePointer);
public:
NIMService(Memory& mem) : mem(mem) {}
void reset();
void handleSyncRequest(u32 messagePointer);
};

View file

@ -10,6 +10,9 @@ class PTMService {
MAKE_LOG_FUNCTION(log, ptmLogger)
// Service commands
void configureNew3DSCPU(u32 messagePointer);
void getStepHistory(u32 messagePointer);
void getTotalStepCount(u32 messagePointer);
public:
PTMService(Memory& mem) : mem(mem) {}

View file

@ -39,4 +39,140 @@ enum class LanguageCodes : u32 {
Portuguese = PT,
Russian = RU,
Taiwanese = TW
};
enum class CountryCodes : u32 {
JP = 1,
AI = 8,
AG = 9,
AR = 10,
AW = 11,
BS = 12,
BB = 13,
BZ = 14,
BO = 15,
BR = 16,
VG = 17,
CA = 18,
KY = 19,
CL = 20,
CO = 21,
CR = 22,
DM = 23,
DO = 24,
EC = 25,
SV = 26,
GF = 27,
GD = 28,
GP = 29,
GT = 30,
GY = 31,
HT = 32,
HN = 33,
JM = 34,
MQ = 35,
MX = 36,
MS = 37,
AN = 38,
NI = 39,
PA = 40,
PY = 41,
PE = 42,
KN = 43,
LC = 44,
VC = 45,
SR = 46,
TT = 47,
TC = 48,
US = 49,
UY = 50,
VI = 51,
VE = 52,
AL = 64,
AU = 65,
AT = 66,
BE = 67,
BA = 68,
BW = 69,
BG = 70,
HR = 71,
CY = 72,
CZ = 73,
DK = 74,
EE = 75,
FI = 76,
FR = 77,
DE = 78,
GR = 79,
HU = 80,
IS = 81,
IE = 82,
IT = 83,
LV = 84,
LS = 85,
LI = 86,
LT = 87,
LU = 88,
MK = 89,
MT = 90,
ME = 91,
MZ = 92,
NA = 93,
NL = 94,
NZ = 95,
NO = 96,
PL = 97,
PT = 98,
RO = 99,
RU = 100,
RS = 101,
SK = 102,
SI = 103,
ZA = 104,
ES = 105,
SZ = 106,
SE = 107,
CH = 108,
TR = 109,
GB = 110,
ZM = 111,
ZW = 112,
AZ = 113,
MR = 114,
ML = 115,
NE = 116,
TD = 117,
SD = 118,
ER = 119,
DJ = 120,
SO = 121,
AD = 122,
GI = 123,
GG = 124,
IM = 125,
JE = 126,
MC = 127,
TW = 128,
KR = 136,
HK = 144,
MO = 145,
ID = 152,
SG = 153,
TH = 154,
PH = 155,
MY = 156,
CN = 160,
AE = 168,
IND = 169, // We can't use the 2-letter country code for India because the Windows SDK does #define IN...
EG = 170,
OM = 171,
QA = 172,
KW = 173,
SA = 174,
SY = 175,
BH = 176,
JO = 177,
SM = 184,
VA = 185,
BM = 186,
};

View file

@ -1,48 +1,76 @@
#pragma once
#include <array>
#include "helpers.hpp"
#include <optional>
#include "kernel_types.hpp"
#include "logger.hpp"
#include "memory.hpp"
#include "services/ac.hpp"
#include "services/act.hpp"
#include "services/am.hpp"
#include "services/apt.hpp"
#include "services/boss.hpp"
#include "services/cam.hpp"
#include "services/cecd.hpp"
#include "services/cfg.hpp"
#include "services/dsp.hpp"
#include "services/hid.hpp"
#include "services/frd.hpp"
#include "services/fs.hpp"
#include "services/gsp_gpu.hpp"
#include "services/gsp_lcd.hpp"
#include "services/ldr_ro.hpp"
#include "services/mic.hpp"
#include "services/nfc.hpp"
#include "services/nim.hpp"
#include "services/ndm.hpp"
#include "services/ptm.hpp"
#include "services/y2r.hpp"
// More circular dependencies!!
class Kernel;
class ServiceManager {
std::array<u32, 16>& regs;
Memory& mem;
Kernel& kernel;
std::optional<Handle> notificationSemaphore;
MAKE_LOG_FUNCTION(log, srvLogger)
ACService ac;
ACTService act;
AMService am;
APTService apt;
BOSSService boss;
CAMService cam;
CECDService cecd;
CFGService cfg;
DSPService dsp;
HIDService hid;
FRDService frd;
FSService fs;
GPUService gsp_gpu;
LCDService gsp_lcd;
LDRService ldr;
MICService mic;
NFCService nfc;
NIMService nim;
NDMService ndm;
PTMService ptm;
Y2RService y2r;
// "srv:" commands
void enableNotification(u32 messagePointer);
void getServiceHandle(u32 messagePointer);
void receiveNotification(u32 messagePointer);
void registerClient(u32 messagePointer);
void subscribe(u32 messagePointer);
public:
ServiceManager(std::array<u32, 16>& regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel);
void reset();
void initializeFS() { fs.initializeFilesystem(); }
void handleSyncRequest(u32 messagePointer);
// Forward a SendSyncRequest IPC message to the service with the respective handle
@ -52,4 +80,7 @@ public:
void requestGPUInterrupt(GPUInterrupt type) { gsp_gpu.requestInterrupt(type); }
void setGSPSharedMem(u8* ptr) { gsp_gpu.setSharedMem(ptr); }
void setHIDSharedMem(u8* ptr) { hid.setSharedMem(ptr); }
void signalDSPEvents() { dsp.signalEvents(); }
void updateInputs() { hid.updateInputs(); }
};

View file

@ -0,0 +1,5 @@
#pragma once
#include <cstddef>
extern unsigned char _shared_font_bin[];
extern size_t _shared_font_len;

94
include/services/y2r.hpp Normal file
View file

@ -0,0 +1,94 @@
#pragma once
#include <optional>
#include "helpers.hpp"
#include "kernel_types.hpp"
#include "logger.hpp"
#include "memory.hpp"
// Circular dependencies go br
class Kernel;
class Y2RService {
Handle handle = KernelHandles::Y2R;
Memory& mem;
Kernel& kernel;
MAKE_LOG_FUNCTION(log, y2rLogger)
std::optional<Handle> transferEndEvent;
bool transferEndInterruptEnabled;
enum class BusyStatus : u32 {
NotBusy = 0,
Busy = 1
};
enum class InputFormat : u32 {
YUV422_Individual8 = 0,
YUV420_Individual8 = 1,
YUV422_Individual16 = 2,
YUV420_Individual16 = 3,
YUV422_Batch = 4,
};
enum class OutputFormat : u32 {
RGB32 = 0,
RGB24 = 1,
RGB15 = 2,
RGB565 = 3
};
// Clockwise rotation
enum class Rotation : u32 {
None = 0,
Rotate90 = 1,
Rotate180 = 2,
Rotate270 = 3
};
enum class BlockAlignment : u32 {
Line = 0, // Output buffer's pixels are arranged linearly. Used when outputting to the framebuffer.
Block8x8 = 1, // Output buffer's pixels are morton swizzled. Used when outputting to a GPU texture.
};
InputFormat inputFmt;
OutputFormat outputFmt;
Rotation rotation;
BlockAlignment alignment;
bool spacialDithering;
bool temporalDithering;
u16 alpha;
u16 inputLineWidth;
u16 inputLines;
// Service commands
void driverInitialize(u32 messagePointer);
void driverFinalize(u32 messagePointer);
void isBusyConversion(u32 messagePointer);
void pingProcess(u32 messagePointer);
void setTransferEndInterrupt(u32 messagePointer);
void getTransferEndEvent(u32 messagePointer);
void setAlpha(u32 messagePointer);
void setBlockAlignment(u32 messagePointer);
void setInputFormat(u32 messagePointer);
void setInputLineWidth(u32 messagePointer);
void setInputLines(u32 messagePointer);
void setOutputFormat(u32 messagePointer);
void setReceiving(u32 messagePointer);
void setRotation(u32 messagePointer);
void setSendingY(u32 messagePointer);
void setSendingU(u32 messagePointer);
void setSendingV(u32 messagePointer);
void setSpacialDithering(u32 messagePointer);
void setStandardCoeff(u32 messagePointer);
void setTemporalDithering(u32 messagePointer);
void startConversion(u32 messagePointer);
void stopConversion(u32 messagePointer);
public:
Y2RService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
void reset();
void handleSyncRequest(u32 messagePointer);
};

23
include/system_models.hpp Normal file
View file

@ -0,0 +1,23 @@
#pragma once
#include <cstdint>
// Codes for every 3DS system model
// The 3-letter codes are codenames used by the ACT service, and can also be found on the hardware itself
// This info can be retrieved in a variety of ways, usually through CFG::GetSystemModel
namespace SystemModel {
enum : std::uint32_t {
Nintendo3DS = 0,
Nintendo3DS_XL = 1,
NewNintendo3DS = 2,
Nintendo2DS = 3,
NewNintendo3DS_XL = 4,
NewNintendo2DS_XL = 5,
CTR = Nintendo3DS,
SPR = Nintendo3DS_XL,
KTR = NewNintendo3DS,
FTR = Nintendo2DS,
RED = NewNintendo3DS_XL,
JAN = NewNintendo2DS_XL
};
}

View file

@ -9,7 +9,7 @@ CPU::CPU(Memory& mem, Kernel& kernel) : mem(mem), env(mem, kernel, *this) {
config.arch_version = Dynarmic::A32::ArchVersion::v6K;
config.callbacks = &env;
config.coprocessors[15] = cp15;
// config.define_unpredictable_behaviour = true;
config.define_unpredictable_behaviour = true;
config.global_monitor = &exclusiveMonitor;
config.processor_id = 0;
@ -18,7 +18,7 @@ CPU::CPU(Memory& mem, Kernel& kernel) : mem(mem), env(mem, kernel, *this) {
void CPU::reset() {
setCPSR(CPSR::UserMode);
setFPSCR(FPSCR::ThreadDefault);
setFPSCR(FPSCR::MainThreadDefault);
env.totalTicks = 0;
cp15->reset();
@ -26,6 +26,7 @@ void CPU::reset() {
jit->Reset();
jit->ClearCache();
jit->Regs().fill(0);
jit->ExtRegs().fill(0);
}
#endif // CPU_DYNARMIC

View file

@ -0,0 +1,509 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// This file is slightly adjusted to my liking from the original
#include <bit>
#include <functional>
#include "cpu_dynarmic.hpp"
#include "helpers.hpp"
namespace {
template <size_t N>
struct StringLiteral {
constexpr StringLiteral(const char(&str)[N]) {
std::copy_n(str, N, value);
}
static constexpr std::size_t strlen = N - 1;
static constexpr std::size_t size = N;
char value[N];
};
template <StringLiteral haystack, StringLiteral needle>
constexpr u32 GetMatchingBitsFromStringLiteral() {
u32 result = 0;
for (size_t i = 0; i < haystack.strlen; i++) {
for (size_t a = 0; a < needle.strlen; a++) {
if (haystack.value[i] == needle.value[a]) {
result |= 1 << (haystack.strlen - 1 - i);
}
}
}
return result;
}
template <u32 mask_>
constexpr u32 DepositBits(u32 val) {
u32 mask = mask_;
u32 res = 0;
for (u32 bb = 1; mask; bb += bb) {
u32 neg_mask = 0 - mask;
if (val & bb)
res |= mask & neg_mask;
mask &= mask - 1;
}
return res;
}
template <StringLiteral haystack>
struct MatcherArg {
template <StringLiteral needle>
u32 Get() {
return DepositBits<GetMatchingBitsFromStringLiteral<haystack, needle>()>(instruction);
}
u32 instruction;
};
struct Matcher {
u32 mask;
u32 expect;
std::function<u64(u32)> fn;
};
u64 DataProcessing_imm(auto i) {
if (i.template Get<"d">() == 15) {
return 7;
}
return 1;
}
u64 DataProcessing_reg(auto i) {
if (i.template Get<"d">() == 15) {
return 7;
}
return 1;
}
u64 DataProcessing_rsr(auto i) {
if (i.template Get<"d">() == 15) {
return 8;
}
return 2;
}
u64 LoadStoreSingle_imm(auto) {
return 2;
}
u64 LoadStoreSingle_reg(auto i) {
// TODO: Load PC
if (i.template Get<"u">() == 1 && i.template Get<"r">() == 0 &&
(i.template Get<"v">() == 0 || i.template Get<"v">() == 2)) {
return 2;
}
return 4;
}
u64 LoadStoreMultiple(auto i) {
// TODO: Load PC
return 1 + std::popcount(i.template Get<"x">()) / 2;
}
u64 SupervisorCall(auto i) {
// Consume extra cycles for the GetSystemTick SVC since some games wait with it in a loop rather than
// Properly sleeping until a VBlank interrupt
if (i.template Get<"v">() == 0x28) {
return 152;
}
return 8;
}
#define INST(NAME, BS, CYCLES) \
Matcher{GetMatchingBitsFromStringLiteral<BS, "01">(), \
GetMatchingBitsFromStringLiteral<BS, "1">(), \
std::function<u64(u32)>{[](u32 instruction) -> u64 { \
[[maybe_unused]] MatcherArg<BS> i{instruction}; \
return (CYCLES); \
}}},
const std::array arm_matchers{
// clang-format off
// Branch instructions
INST("BLX (imm)", "1111101hvvvvvvvvvvvvvvvvvvvvvvvv", 5) // v5
INST("BLX (reg)", "cccc000100101111111111110011mmmm", 6) // v5
INST("B", "cccc1010vvvvvvvvvvvvvvvvvvvvvvvv", 4) // v1
INST("BL", "cccc1011vvvvvvvvvvvvvvvvvvvvvvvv", 4) // v1
INST("BX", "cccc000100101111111111110001mmmm", 5) // v4T
INST("BXJ", "cccc000100101111111111110010mmmm", 1) // v5J
// Coprocessor instructions
INST("CDP", "cccc1110ooooNNNNDDDDppppooo0MMMM", 1) // v2 (CDP2: v5)
INST("LDC", "cccc110pudw1nnnnDDDDppppvvvvvvvv", 1) // v2 (LDC2: v5)
INST("MCR", "cccc1110ooo0NNNNttttppppooo1MMMM", 2) // v2 (MCR2: v5)
INST("MCRR", "cccc11000100uuuuttttppppooooMMMM", 2) // v5E (MCRR2: v6)
INST("MRC", "cccc1110ooo1NNNNttttppppooo1MMMM", 2) // v2 (MRC2: v5)
INST("MRRC", "cccc11000101uuuuttttppppooooMMMM", 2) // v5E (MRRC2: v6)
INST("STC", "cccc110pudw0nnnnDDDDppppvvvvvvvv", 1) // v2 (STC2: v5)
// Data Processing instructions
INST("ADC (imm)", "cccc0010101Snnnnddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
INST("ADC (reg)", "cccc0000101Snnnnddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
INST("ADC (rsr)", "cccc0000101Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
INST("ADD (imm)", "cccc0010100Snnnnddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
INST("ADD (reg)", "cccc0000100Snnnnddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
INST("ADD (rsr)", "cccc0000100Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
INST("AND (imm)", "cccc0010000Snnnnddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
INST("AND (reg)", "cccc0000000Snnnnddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
INST("AND (rsr)", "cccc0000000Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
INST("BIC (imm)", "cccc0011110Snnnnddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
INST("BIC (reg)", "cccc0001110Snnnnddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
INST("BIC (rsr)", "cccc0001110Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
INST("CMN (imm)", "cccc00110111nnnn0000rrrrvvvvvvvv", DataProcessing_imm(i)) // v1
INST("CMN (reg)", "cccc00010111nnnn0000vvvvvrr0mmmm", DataProcessing_reg(i)) // v1
INST("CMN (rsr)", "cccc00010111nnnn0000ssss0rr1mmmm", DataProcessing_rsr(i)) // v1
INST("CMP (imm)", "cccc00110101nnnn0000rrrrvvvvvvvv", DataProcessing_imm(i)) // v1
INST("CMP (reg)", "cccc00010101nnnn0000vvvvvrr0mmmm", DataProcessing_reg(i)) // v1
INST("CMP (rsr)", "cccc00010101nnnn0000ssss0rr1mmmm", DataProcessing_rsr(i)) // v1
INST("EOR (imm)", "cccc0010001Snnnnddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
INST("EOR (reg)", "cccc0000001Snnnnddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
INST("EOR (rsr)", "cccc0000001Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
INST("MOV (imm)", "cccc0011101S0000ddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
INST("MOV (reg)", "cccc0001101S0000ddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
INST("MOV (rsr)", "cccc0001101S0000ddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
INST("MVN (imm)", "cccc0011111S0000ddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
INST("MVN (reg)", "cccc0001111S0000ddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
INST("MVN (rsr)", "cccc0001111S0000ddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
INST("ORR (imm)", "cccc0011100Snnnnddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
INST("ORR (reg)", "cccc0001100Snnnnddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
INST("ORR (rsr)", "cccc0001100Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
INST("RSB (imm)", "cccc0010011Snnnnddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
INST("RSB (reg)", "cccc0000011Snnnnddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
INST("RSB (rsr)", "cccc0000011Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
INST("RSC (imm)", "cccc0010111Snnnnddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
INST("RSC (reg)", "cccc0000111Snnnnddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
INST("RSC (rsr)", "cccc0000111Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
INST("SBC (imm)", "cccc0010110Snnnnddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
INST("SBC (reg)", "cccc0000110Snnnnddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
INST("SBC (rsr)", "cccc0000110Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
INST("SUB (imm)", "cccc0010010Snnnnddddrrrrvvvvvvvv", DataProcessing_imm(i)) // v1
INST("SUB (reg)", "cccc0000010Snnnnddddvvvvvrr0mmmm", DataProcessing_reg(i)) // v1
INST("SUB (rsr)", "cccc0000010Snnnnddddssss0rr1mmmm", DataProcessing_rsr(i)) // v1
INST("TEQ (imm)", "cccc00110011nnnn0000rrrrvvvvvvvv", DataProcessing_imm(i)) // v1
INST("TEQ (reg)", "cccc00010011nnnn0000vvvvvrr0mmmm", DataProcessing_reg(i)) // v1
INST("TEQ (rsr)", "cccc00010011nnnn0000ssss0rr1mmmm", DataProcessing_rsr(i)) // v1
INST("TST (imm)", "cccc00110001nnnn0000rrrrvvvvvvvv", DataProcessing_imm(i)) // v1
INST("TST (reg)", "cccc00010001nnnn0000vvvvvrr0mmmm", DataProcessing_reg(i)) // v1
INST("TST (rsr)", "cccc00010001nnnn0000ssss0rr1mmmm", DataProcessing_rsr(i)) // v1
// Exception Generating instructions
INST("BKPT", "cccc00010010vvvvvvvvvvvv0111vvvv", 8) // v5
INST("SVC", "cccc1111vvvvvvvvvvvvvvvvvvvvvvvv", SupervisorCall(i)) // v1
INST("UDF", "111001111111------------1111----", 8)
// Extension instructions
INST("SXTB", "cccc011010101111ddddrr000111mmmm", 1) // v6
INST("SXTB16", "cccc011010001111ddddrr000111mmmm", 1) // v6
INST("SXTH", "cccc011010111111ddddrr000111mmmm", 1) // v6
INST("SXTAB", "cccc01101010nnnnddddrr000111mmmm", 1) // v6
INST("SXTAB16", "cccc01101000nnnnddddrr000111mmmm", 1) // v6
INST("SXTAH", "cccc01101011nnnnddddrr000111mmmm", 1) // v6
INST("UXTB", "cccc011011101111ddddrr000111mmmm", 1) // v6
INST("UXTB16", "cccc011011001111ddddrr000111mmmm", 1) // v6
INST("UXTH", "cccc011011111111ddddrr000111mmmm", 1) // v6
INST("UXTAB", "cccc01101110nnnnddddrr000111mmmm", 1) // v6
INST("UXTAB16", "cccc01101100nnnnddddrr000111mmmm", 1) // v6
INST("UXTAH", "cccc01101111nnnnddddrr000111mmmm", 1) // v6
// Hint instructions
INST("PLD (imm)", "11110101uz01nnnn1111iiiiiiiiiiii", 1) // v5E for PLD; v7 for PLDW
INST("PLD (reg)", "11110111uz01nnnn1111iiiiitt0mmmm", 1) // v5E for PLD; v7 for PLDW
INST("SEV", "----0011001000001111000000000100", 1) // v6K
INST("WFE", "----0011001000001111000000000010", 1) // v6K
INST("WFI", "----0011001000001111000000000011", 1) // v6K
INST("YIELD", "----0011001000001111000000000001", 1) // v6K
// Synchronization Primitive instructions
INST("CLREX", "11110101011111111111000000011111", 1) // v6K
INST("SWP", "cccc00010000nnnntttt00001001uuuu", 4) // v2S (v6: Deprecated)
INST("SWPB", "cccc00010100nnnntttt00001001uuuu", 4) // v2S (v6: Deprecated)
INST("STREX", "cccc00011000nnnndddd11111001mmmm", 2) // v6
INST("LDREX", "cccc00011001nnnndddd111110011111", 2) // v6
INST("STREXD", "cccc00011010nnnndddd11111001mmmm", 2) // v6K
INST("LDREXD", "cccc00011011nnnndddd111110011111", 2) // v6K
INST("STREXB", "cccc00011100nnnndddd11111001mmmm", 2) // v6K
INST("LDREXB", "cccc00011101nnnndddd111110011111", 2) // v6K
INST("STREXH", "cccc00011110nnnndddd11111001mmmm", 2) // v6K
INST("LDREXH", "cccc00011111nnnndddd111110011111", 2) // v6K
// Load/Store instructions
INST("LDRBT (A1)", "----0100-111--------------------", 1) // v1
INST("LDRBT (A2)", "----0110-111---------------0----", 1) // v1
INST("LDRT (A1)", "----0100-011--------------------", 1) // v1
INST("LDRT (A2)", "----0110-011---------------0----", 1) // v1
INST("STRBT (A1)", "----0100-110--------------------", 1) // v1
INST("STRBT (A2)", "----0110-110---------------0----", 1) // v1
INST("STRT (A1)", "----0100-010--------------------", 1) // v1
INST("STRT (A2)", "----0110-010---------------0----", 1) // v1
INST("LDR (lit)", "cccc0101u0011111ttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1
INST("LDR (imm)", "cccc010pu0w1nnnnttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1
INST("LDR (reg)", "cccc011pu0w1nnnnttttvvvvvrr0mmmm", LoadStoreSingle_reg(i)) // v1
INST("LDRB (lit)", "cccc0101u1011111ttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1
INST("LDRB (imm)", "cccc010pu1w1nnnnttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1
INST("LDRB (reg)", "cccc011pu1w1nnnnttttvvvvvrr0mmmm", LoadStoreSingle_reg(i)) // v1
INST("LDRD (lit)", "cccc0001u1001111ttttvvvv1101vvvv", LoadStoreSingle_imm(i)) // v5E
INST("LDRD (imm)", "cccc000pu1w0nnnnttttvvvv1101vvvv", LoadStoreSingle_imm(i)) // v5E
INST("LDRD (reg)", "cccc000pu0w0nnnntttt00001101mmmm", LoadStoreSingle_reg(i)) // v5E
INST("LDRH (lit)", "cccc000pu1w11111ttttvvvv1011vvvv", LoadStoreSingle_imm(i)) // v4
INST("LDRH (imm)", "cccc000pu1w1nnnnttttvvvv1011vvvv", LoadStoreSingle_imm(i)) // v4
INST("LDRH (reg)", "cccc000pu0w1nnnntttt00001011mmmm", LoadStoreSingle_reg(i)) // v4
INST("LDRSB (lit)", "cccc0001u1011111ttttvvvv1101vvvv", LoadStoreSingle_imm(i)) // v4
INST("LDRSB (imm)", "cccc000pu1w1nnnnttttvvvv1101vvvv", LoadStoreSingle_imm(i)) // v4
INST("LDRSB (reg)", "cccc000pu0w1nnnntttt00001101mmmm", LoadStoreSingle_reg(i)) // v4
INST("LDRSH (lit)", "cccc0001u1011111ttttvvvv1111vvvv", LoadStoreSingle_imm(i)) // v4
INST("LDRSH (imm)", "cccc000pu1w1nnnnttttvvvv1111vvvv", LoadStoreSingle_imm(i)) // v4
INST("LDRSH (reg)", "cccc000pu0w1nnnntttt00001111mmmm", LoadStoreSingle_reg(i)) // v4
INST("STR (imm)", "cccc010pu0w0nnnnttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1
INST("STR (reg)", "cccc011pu0w0nnnnttttvvvvvrr0mmmm", LoadStoreSingle_reg(i)) // v1
INST("STRB (imm)", "cccc010pu1w0nnnnttttvvvvvvvvvvvv", LoadStoreSingle_imm(i)) // v1
INST("STRB (reg)", "cccc011pu1w0nnnnttttvvvvvrr0mmmm", LoadStoreSingle_reg(i)) // v1
INST("STRD (imm)", "cccc000pu1w0nnnnttttvvvv1111vvvv", LoadStoreSingle_imm(i)) // v5E
INST("STRD (reg)", "cccc000pu0w0nnnntttt00001111mmmm", LoadStoreSingle_reg(i)) // v5E
INST("STRH (imm)", "cccc000pu1w0nnnnttttvvvv1011vvvv", LoadStoreSingle_imm(i)) // v4
INST("STRH (reg)", "cccc000pu0w0nnnntttt00001011mmmm", LoadStoreSingle_reg(i)) // v4
// Load/Store Multiple instructions
INST("LDM", "cccc100010w1nnnnxxxxxxxxxxxxxxxx", LoadStoreMultiple(i)) // v1
INST("LDMDA", "cccc100000w1nnnnxxxxxxxxxxxxxxxx", LoadStoreMultiple(i)) // v1
INST("LDMDB", "cccc100100w1nnnnxxxxxxxxxxxxxxxx", LoadStoreMultiple(i)) // v1
INST("LDMIB", "cccc100110w1nnnnxxxxxxxxxxxxxxxx", LoadStoreMultiple(i)) // v1
INST("LDM (usr reg)", "----100--101--------------------", 1) // v1
INST("LDM (exce ret)", "----100--1-1----1---------------", 1) // v1
INST("STM", "cccc100010w0nnnnxxxxxxxxxxxxxxxx", LoadStoreMultiple(i)) // v1
INST("STMDA", "cccc100000w0nnnnxxxxxxxxxxxxxxxx", LoadStoreMultiple(i)) // v1
INST("STMDB", "cccc100100w0nnnnxxxxxxxxxxxxxxxx", LoadStoreMultiple(i)) // v1
INST("STMIB", "cccc100110w0nnnnxxxxxxxxxxxxxxxx", LoadStoreMultiple(i)) // v1
INST("STM (usr reg)", "----100--100--------------------", 1) // v1
// Miscellaneous instructions
INST("CLZ", "cccc000101101111dddd11110001mmmm", 1) // v5
INST("NOP", "----0011001000001111000000000000", 1) // v6K
INST("SEL", "cccc01101000nnnndddd11111011mmmm", 1) // v6
// Unsigned Sum of Absolute Differences instructions
INST("USAD8", "cccc01111000dddd1111mmmm0001nnnn", 1) // v6
INST("USADA8", "cccc01111000ddddaaaammmm0001nnnn", 1) // v6
// Packing instructions
INST("PKHBT", "cccc01101000nnnnddddvvvvv001mmmm", 1) // v6K
INST("PKHTB", "cccc01101000nnnnddddvvvvv101mmmm", 1) // v6K
// Reversal instructions
INST("REV", "cccc011010111111dddd11110011mmmm", 1) // v6
INST("REV16", "cccc011010111111dddd11111011mmmm", 1) // v6
INST("REVSH", "cccc011011111111dddd11111011mmmm", 1) // v6
// Saturation instructions
INST("SSAT", "cccc0110101vvvvvddddvvvvvr01nnnn", 1) // v6
INST("SSAT16", "cccc01101010vvvvdddd11110011nnnn", 1) // v6
INST("USAT", "cccc0110111vvvvvddddvvvvvr01nnnn", 1) // v6
INST("USAT16", "cccc01101110vvvvdddd11110011nnnn", 1) // v6
// Multiply (Normal) instructions
INST("MLA", "cccc0000001Sddddaaaammmm1001nnnn", (i.template Get<"S">() ? 5 : 2)) // v2
INST("MUL", "cccc0000000Sdddd0000mmmm1001nnnn", (i.template Get<"S">() ? 5 : 2)) // v2
// Multiply (Long) instructions
INST("SMLAL", "cccc0000111Sddddaaaammmm1001nnnn", (i.template Get<"S">() ? 6 : 3)) // v3M
INST("SMULL", "cccc0000110Sddddaaaammmm1001nnnn", (i.template Get<"S">() ? 6 : 3)) // v3M
INST("UMAAL", "cccc00000100ddddaaaammmm1001nnnn", 3) // v6
INST("UMLAL", "cccc0000101Sddddaaaammmm1001nnnn", (i.template Get<"S">() ? 6 : 3)) // v3M
INST("UMULL", "cccc0000100Sddddaaaammmm1001nnnn", (i.template Get<"S">() ? 6 : 3)) // v3M
// Multiply (Halfword) instructions
INST("SMLALXY", "cccc00010100ddddaaaammmm1xy0nnnn", 2) // v5xP
INST("SMLAXY", "cccc00010000ddddaaaammmm1xy0nnnn", 1) // v5xP
INST("SMULXY", "cccc00010110dddd0000mmmm1xy0nnnn", 1) // v5xP
// Multiply (Word by Halfword) instructions
INST("SMLAWY", "cccc00010010ddddaaaammmm1y00nnnn", 1) // v5xP
INST("SMULWY", "cccc00010010dddd0000mmmm1y10nnnn", 1) // v5xP
// Multiply (Most Significant Word) instructions
INST("SMMUL", "cccc01110101dddd1111mmmm00R1nnnn", 2) // v6
INST("SMMLA", "cccc01110101ddddaaaammmm00R1nnnn", 2) // v6
INST("SMMLS", "cccc01110101ddddaaaammmm11R1nnnn", 2) // v6
// Multiply (Dual) instructions
INST("SMLAD", "cccc01110000ddddaaaammmm00M1nnnn", 2) // v6
INST("SMLALD", "cccc01110100ddddaaaammmm00M1nnnn", 2) // v6
INST("SMLSD", "cccc01110000ddddaaaammmm01M1nnnn", 2) // v6
INST("SMLSLD", "cccc01110100ddddaaaammmm01M1nnnn", 2) // v6
INST("SMUAD", "cccc01110000dddd1111mmmm00M1nnnn", 2) // v6
INST("SMUSD", "cccc01110000dddd1111mmmm01M1nnnn", 2) // v6
// Parallel Add/Subtract (Modulo) instructions
INST("SADD8", "cccc01100001nnnndddd11111001mmmm", 1) // v6
INST("SADD16", "cccc01100001nnnndddd11110001mmmm", 1) // v6
INST("SASX", "cccc01100001nnnndddd11110011mmmm", 1) // v6
INST("SSAX", "cccc01100001nnnndddd11110101mmmm", 1) // v6
INST("SSUB8", "cccc01100001nnnndddd11111111mmmm", 1) // v6
INST("SSUB16", "cccc01100001nnnndddd11110111mmmm", 1) // v6
INST("UADD8", "cccc01100101nnnndddd11111001mmmm", 1) // v6
INST("UADD16", "cccc01100101nnnndddd11110001mmmm", 1) // v6
INST("UASX", "cccc01100101nnnndddd11110011mmmm", 1) // v6
INST("USAX", "cccc01100101nnnndddd11110101mmmm", 1) // v6
INST("USUB8", "cccc01100101nnnndddd11111111mmmm", 1) // v6
INST("USUB16", "cccc01100101nnnndddd11110111mmmm", 1) // v6
// Parallel Add/Subtract (Saturating) instructions
INST("QADD8", "cccc01100010nnnndddd11111001mmmm", 1) // v6
INST("QADD16", "cccc01100010nnnndddd11110001mmmm", 1) // v6
INST("QASX", "cccc01100010nnnndddd11110011mmmm", 1) // v6
INST("QSAX", "cccc01100010nnnndddd11110101mmmm", 1) // v6
INST("QSUB8", "cccc01100010nnnndddd11111111mmmm", 1) // v6
INST("QSUB16", "cccc01100010nnnndddd11110111mmmm", 1) // v6
INST("UQADD8", "cccc01100110nnnndddd11111001mmmm", 1) // v6
INST("UQADD16", "cccc01100110nnnndddd11110001mmmm", 1) // v6
INST("UQASX", "cccc01100110nnnndddd11110011mmmm", 1) // v6
INST("UQSAX", "cccc01100110nnnndddd11110101mmmm", 1) // v6
INST("UQSUB8", "cccc01100110nnnndddd11111111mmmm", 1) // v6
INST("UQSUB16", "cccc01100110nnnndddd11110111mmmm", 1) // v6
// Parallel Add/Subtract (Halving) instructions
INST("SHADD8", "cccc01100011nnnndddd11111001mmmm", 1) // v6
INST("SHADD16", "cccc01100011nnnndddd11110001mmmm", 1) // v6
INST("SHASX", "cccc01100011nnnndddd11110011mmmm", 1) // v6
INST("SHSAX", "cccc01100011nnnndddd11110101mmmm", 1) // v6
INST("SHSUB8", "cccc01100011nnnndddd11111111mmmm", 1) // v6
INST("SHSUB16", "cccc01100011nnnndddd11110111mmmm", 1) // v6
INST("UHADD8", "cccc01100111nnnndddd11111001mmmm", 1) // v6
INST("UHADD16", "cccc01100111nnnndddd11110001mmmm", 1) // v6
INST("UHASX", "cccc01100111nnnndddd11110011mmmm", 1) // v6
INST("UHSAX", "cccc01100111nnnndddd11110101mmmm", 1) // v6
INST("UHSUB8", "cccc01100111nnnndddd11111111mmmm", 1) // v6
INST("UHSUB16", "cccc01100111nnnndddd11110111mmmm", 1) // v6
// Saturated Add/Subtract instructions
INST("QADD", "cccc00010000nnnndddd00000101mmmm", 1) // v5xP
INST("QSUB", "cccc00010010nnnndddd00000101mmmm", 1) // v5xP
INST("QDADD", "cccc00010100nnnndddd00000101mmmm", 1) // v5xP
INST("QDSUB", "cccc00010110nnnndddd00000101mmmm", 1) // v5xP
// Status Register Access instructions
INST("CPS", "111100010000---00000000---0-----", 1) // v6
INST("SETEND", "1111000100000001000000e000000000", 1) // v6
INST("MRS", "cccc000100001111dddd000000000000", 1) // v3
INST("MSR (imm)", "cccc00110010mmmm1111rrrrvvvvvvvv", (i.template Get<"m">() == 0b1000 ? 1 : 4)) // v3
INST("MSR (reg)", "cccc00010010mmmm111100000000nnnn", (i.template Get<"m">() == 0b1000 ? 1 : 4)) // v3
INST("RFE", "1111100--0-1----0000101000000000", 9) // v6
INST("SRS", "1111100--1-0110100000101000-----", 1) // v6
// clang-format on
};
const std::array thumb_matchers{
// clang-format off
// Shift (immediate) add, subtract, move and compare instructions
INST("LSL (imm)", "00000vvvvvmmmddd", 1)
INST("LSR (imm)", "00001vvvvvmmmddd", 1)
INST("ASR (imm)", "00010vvvvvmmmddd", 1)
INST("ADD (reg, T1)", "0001100mmmnnnddd", 1)
INST("SUB (reg)", "0001101mmmnnnddd", 1)
INST("ADD (imm, T1)", "0001110vvvnnnddd", 1)
INST("SUB (imm, T1)", "0001111vvvnnnddd", 1)
INST("MOV (imm)", "00100dddvvvvvvvv", 1)
INST("CMP (imm)", "00101nnnvvvvvvvv", 1)
INST("ADD (imm, T2)", "00110dddvvvvvvvv", 1)
INST("SUB (imm, T2)", "00111dddvvvvvvvv", 1)
// Data-processing instructions
INST("AND (reg)", "0100000000mmmddd", 1)
INST("EOR (reg)", "0100000001mmmddd", 1)
INST("LSL (reg)", "0100000010mmmddd", 1)
INST("LSR (reg)", "0100000011mmmddd", 1)
INST("ASR (reg)", "0100000100mmmddd", 1)
INST("ADC (reg)", "0100000101mmmddd", 1)
INST("SBC (reg)", "0100000110mmmddd", 1)
INST("ROR (reg)", "0100000111sssddd", 1)
INST("TST (reg)", "0100001000mmmnnn", 1)
INST("RSB (imm)", "0100001001nnnddd", 1)
INST("CMP (reg, T1)", "0100001010mmmnnn", 1)
INST("CMN (reg)", "0100001011mmmnnn", 1)
INST("ORR (reg)", "0100001100mmmddd", 1)
INST("MUL (reg)", "0100001101nnnddd", 1)
INST("BIC (reg)", "0100001110mmmddd", 1)
INST("MVN (reg)", "0100001111mmmddd", 1)
// Special data instructions
INST("ADD (reg, T2)", "01000100Dmmmmddd", 1) // v4T, Low regs: v6T2
INST("CMP (reg, T2)", "01000101Nmmmmnnn", 1) // v4T
INST("MOV (reg)", "01000110Dmmmmddd", 1) // v4T, Low regs: v6
// Store/Load single data item instructions
INST("LDR (literal)", "01001tttvvvvvvvv", 2)
INST("STR (reg)", "0101000mmmnnnttt", 2)
INST("STRH (reg)", "0101001mmmnnnttt", 2)
INST("STRB (reg)", "0101010mmmnnnttt", 2)
INST("LDRSB (reg)", "0101011mmmnnnttt", 2)
INST("LDR (reg)", "0101100mmmnnnttt", 2)
INST("LDRH (reg)", "0101101mmmnnnttt", 2)
INST("LDRB (reg)", "0101110mmmnnnttt", 2)
INST("LDRSH (reg)", "0101111mmmnnnttt", 2)
INST("STR (imm, T1)", "01100vvvvvnnnttt", 2)
INST("LDR (imm, T1)", "01101vvvvvnnnttt", 2)
INST("STRB (imm)", "01110vvvvvnnnttt", 2)
INST("LDRB (imm)", "01111vvvvvnnnttt", 2)
INST("STRH (imm)", "10000vvvvvnnnttt", 2)
INST("LDRH (imm)", "10001vvvvvnnnttt", 2)
INST("STR (imm, T2)", "10010tttvvvvvvvv", 2)
INST("LDR (imm, T2)", "10011tttvvvvvvvv", 2)
// Generate relative address instructions
INST("ADR", "10100dddvvvvvvvv", 1)
INST("ADD (SP plus imm, T1)", "10101dddvvvvvvvv", 1)
INST("ADD (SP plus imm, T2)", "101100000vvvvvvv", 1) // v4T
INST("SUB (SP minus imm)", "101100001vvvvvvv", 1) // v4T
// Hint instructions
INST("NOP", "10111111--------", (1)) // IT on v7
// Miscellaneous 16-bit instructions
INST("SXTH", "1011001000mmmddd", 1) // v6
INST("SXTB", "1011001001mmmddd", 1) // v6
INST("UXTH", "1011001010mmmddd", 1) // v6
INST("UXTB", "1011001011mmmddd", 1) // v6
INST("PUSH", "1011010xxxxxxxxx", LoadStoreMultiple(i)) // v4T
INST("POP", "1011110xxxxxxxxx", LoadStoreMultiple(i)) // v4T
INST("SETEND", "101101100101x000", 1) // v6
INST("CPS", "10110110011m0aif", 1) // v6
INST("REV", "1011101000mmmddd", 1) // v6
INST("REV16", "1011101001mmmddd", 1) // v6
INST("REVSH", "1011101011mmmddd", 1) // v6
INST("BKPT", "10111110xxxxxxxx", 8) // v5
// Store/Load multiple registers
INST("STMIA", "11000nnnxxxxxxxx", LoadStoreMultiple(i))
INST("LDMIA", "11001nnnxxxxxxxx", LoadStoreMultiple(i))
// Branch instructions
INST("BX", "010001110mmmm000", 5) // v4T
INST("BLX (reg)", "010001111mmmm000", 6) // v5T
INST("UDF", "11011110--------", 8)
INST("SVC", "11011111vvvvvvvv", SupervisorCall(i))
INST("B (T1)", "1101ccccvvvvvvvv", 4)
INST("B (T2)", "11100vvvvvvvvvvv", 4)
INST("BL (imm)", "11110Svvvvvvvvvv11j1jvvvvvvvvvvv", 4) // v4T
INST("BLX (imm)", "11110Svvvvvvvvvv11j0jvvvvvvvvvvv", 5) // v5T
// clang-format on
};
} // namespace
u64 MyEnvironment::getCyclesForInstruction(bool is_thumb, u32 instruction) {
if (is_thumb) {
return 1;
}
const auto matches_instruction = [instruction](const auto& matcher) {
return (instruction & matcher.mask) == matcher.expect;
};
auto iter = std::find_if(arm_matchers.begin(), arm_matchers.end(), matches_instruction);
if (iter != arm_matchers.end()) {
return iter->fn(instruction);
}
return 1;
}

View file

@ -5,7 +5,7 @@
using namespace Floats;
GPU::GPU(Memory& mem) : mem(mem) {
GPU::GPU(Memory& mem) : mem(mem), renderer(*this, regs) {
vram = new u8[vramSize];
}
@ -18,6 +18,9 @@ void GPU::reset() {
fixedAttribMask = 0;
fixedAttribIndex = 0;
fixedAttribCount = 0;
immediateModeAttrIndex = 0;
immediateModeVertIndex = 0;
fixedAttrBuff.fill(0);
for (auto& e : attributeInfo) {
@ -27,7 +30,7 @@ void GPU::reset() {
e.config2 = 0;
}
// TODO: Reset blending, texturing, etc here
renderer.reset();
}
void GPU::drawArrays(bool indexed) {
@ -37,6 +40,8 @@ void GPU::drawArrays(bool indexed) {
drawArrays<false>();
}
Vertex* vertices = new Vertex[Renderer::vertexBufferSize];
template <bool indexed>
void GPU::drawArrays() {
// Base address for vertex attributes
@ -47,15 +52,13 @@ void GPU::drawArrays() {
// Configures the type of primitive and the number of vertex shader outputs
const u32 primConfig = regs[PICAInternalRegs::PrimitiveConfig];
const u32 primType = (primConfig >> 8) & 3;
if (primType != 0 && primType != 1) Helpers::panic("[PICA] Tried to draw unimplemented shape %d\n", primType);
if (vertexCount > vertexBufferSize) Helpers::panic("[PICA] vertexCount > vertexBufferSize");
if (primType != 0 && primType != 1 && primType != 3) Helpers::panic("[PICA] Tried to draw unimplemented shape %d\n", primType);
if (vertexCount > Renderer::vertexBufferSize) Helpers::panic("[PICA] vertexCount > vertexBufferSize");
if ((primType == 0 && vertexCount % 3) || (primType == 1 && vertexCount < 3)) {
Helpers::panic("Invalid vertex count for primitive. Type: %d, vert count: %d\n", primType, vertexCount);
}
Vertex vertices[vertexBufferSize];
// Get the configuration for the index buffer, used only for indexed drawing
u32 indexBufferConfig = regs[PICAInternalRegs::IndexBufferConfig];
u32 indexBufferPointer = vertexBase + (indexBufferConfig & 0xfffffff);
@ -72,6 +75,10 @@ void GPU::drawArrays() {
log("PICA::DrawElements(vertex count = %d, index buffer config = %08X)\n", vertexCount, indexBufferConfig);
}
// Total number of input attributes to shader. Differs between GS and VS. Currently stubbed to the VS one, as we don't have geometry shaders.
const u32 inputAttrCount = (regs[PICAInternalRegs::VertexShaderInputBufferCfg] & 0xf) + 1;
const u64 inputAttrCfg = getVertexShaderInputConfig();
for (u32 i = 0; i < vertexCount; i++) {
u32 vertexIndex; // Index of the vertex in the VBO
@ -89,73 +96,170 @@ void GPU::drawArrays() {
}
}
int attrCount = 0; // Number of attributes we've passed to the shader
for (int attrCount = 0; attrCount < totalAttribCount; attrCount++) {
int attrCount = 0;
int buffer = 0; // Vertex buffer index for non-fixed attributes
while (attrCount < totalAttribCount) {
// Check if attribute is fixed or not
if (fixedAttribMask & (1 << attrCount)) { // Fixed attribute
vec4f& fixedAttr = shaderUnit.vs.fixedAttributes[attrCount]; // TODO: Is this how it works?
vec4f& inputAttr = shaderUnit.vs.attributes[attrCount];
vec4f& inputAttr = currentAttributes[attrCount];
std::memcpy(&inputAttr, &fixedAttr, sizeof(vec4f)); // Copy fixed attr to input attr
attrCount++;
} else { // Non-fixed attribute
auto& attr = attributeInfo[attrCount]; // Get information for this attribute
auto& attr = attributeInfo[buffer]; // Get information for this attribute
u64 attrCfg = attr.getConfigFull(); // Get config1 | (config2 << 32)
uint index = (attrCfg >> (attrCount * 4)) & 0xf; // Get index of attribute in vertexCfg
if (index >= 12) Helpers::panic("[PICA] Vertex attribute used as padding");
u32 attribInfo = (vertexCfg >> (index * 4)) & 0xf;
u32 attribType = attribInfo & 0x3; // Type of attribute(sbyte/ubyte/short/float)
u32 componentCount = (attribInfo >> 2) + 1; // Total number of components
// Address to fetch the attribute from
u32 attrAddress = vertexBase + attr.offset + (vertexIndex * attr.size);
vec4f& attribute = shaderUnit.vs.attributes[attrCount];
uint component; // Current component
switch (attribType) {
case 2: { // Short
s16* ptr = getPointerPhys<s16>(attrAddress);
for (component = 0; component < componentCount; component++) {
float val = static_cast<float>(*ptr++);
attribute[component] = f24::fromFloat32(val);
for (int j = 0; j < attr.componentCount; j++) {
uint index = (attrCfg >> (j * 4)) & 0xf; // Get index of attribute in vertexCfg
if (index >= 12) Helpers::panic("[PICA] Vertex attribute used as padding");
u32 attribInfo = (vertexCfg >> (index * 4)) & 0xf;
u32 attribType = attribInfo & 0x3; // Type of attribute(sbyte/ubyte/short/float)
u32 size = (attribInfo >> 2) + 1; // Total number of components
//printf("vertex_attribute_strides[%d] = %d\n", attrCount, attr.size);
vec4f& attribute = currentAttributes[attrCount];
uint component; // Current component
switch (attribType) {
case 0: { // Signed byte
s8* ptr = getPointerPhys<s8>(attrAddress);
for (component = 0; component < size; component++) {
float val = static_cast<float>(*ptr++);
attribute[component] = f24::fromFloat32(val);
}
attrAddress += size * sizeof(s8);
break;
}
break;
case 1: { // Unsigned byte
u8* ptr = getPointerPhys<u8>(attrAddress);
for (component = 0; component < size; component++) {
float val = static_cast<float>(*ptr++);
attribute[component] = f24::fromFloat32(val);
}
attrAddress += size * sizeof(u8);
break;
}
case 2: { // Short
s16* ptr = getPointerPhys<s16>(attrAddress);
for (component = 0; component < size; component++) {
float val = static_cast<float>(*ptr++);
attribute[component] = f24::fromFloat32(val);
}
attrAddress += size * sizeof(s16);
break;
}
case 3: { // Float
float* ptr = getPointerPhys<float>(attrAddress);
for (component = 0; component < size; component++) {
float val = *ptr++;
attribute[component] = f24::fromFloat32(val);
}
attrAddress += size * sizeof(float);
break;
}
default: Helpers::panic("[PICA] Unimplemented attribute type %d", attribType);
}
case 3: { // Float
float* ptr = getPointerPhys<float>(attrAddress);
for (component = 0; component < componentCount; component++) {
float val = *ptr++;
attribute[component] = f24::fromFloat32(val);
}
break;
// Fill the remaining attribute lanes with default parameters (1.0 for alpha/w, 0.0) for everything else
// Corgi does this although I'm not sure if it's actually needed for anything.
// TODO: Find out
while (component < 4) {
attribute[component] = (component == 3) ? f24::fromFloat32(1.0) : f24::fromFloat32(0.0);
component++;
}
default: Helpers::panic("[PICA] Unimplemented attribute type %d", attribType);
}
// Fill the remaining attribute lanes with default parameters (1.0 for alpha/w, 0.0) for everything else
// Corgi does this although I'm not sure if it's actually needed for anything.
// TODO: Find out
while (component < 4) {
attribute[component] = (component == 3) ? f24::fromFloat32(1.0) : f24::fromFloat32(0.0);
component++;
attrCount++;
}
buffer++;
}
}
// Before running the shader, the PICA maps the fetched attributes from the attribute registers to the shader input registers
// Based on the SH_ATTRIBUTES_PERMUTATION registers.
// Ie it might attribute #0 to v2, #1 to v7, etc
for (int j = 0; j < totalAttribCount; j++) {
const u32 mapping = (inputAttrCfg >> (j * 4)) & 0xf;
std::memcpy(&shaderUnit.vs.inputs[mapping], &currentAttributes[j], sizeof(vec4f));
}
shaderUnit.vs.run();
std::memcpy(&vertices[i].position, &shaderUnit.vs.outputs[0], sizeof(vec4f));
std::memcpy(&vertices[i].colour, &shaderUnit.vs.outputs[1], sizeof(vec4f));
std::memcpy(&vertices[i].UVs, &shaderUnit.vs.outputs[2], 2 * sizeof(f24));
//printf("(x, y, z, w) = (%f, %f, %f, %f)\n", (double)vertices[i].position.x(), (double)vertices[i].position.y(), (double)vertices[i].position.z(), (double)vertices[i].position.w());
//printf("(r, g, b, a) = (%f, %f, %f, %f)\n", (double)vertices[i].colour.r(), (double)vertices[i].colour.g(), (double)vertices[i].colour.b(), (double)vertices[i].colour.a());
//printf("(u, v ) = (%f, %f)\n", vertices[i].UVs.u(), vertices[i].UVs.v());
}
// The fourth type is meant to be "Geometry primitive". TODO: Find out what that is
static constexpr std::array<OpenGL::Primitives, 4> primTypes = {
OpenGL::Triangle, OpenGL::TriangleStrip, OpenGL::TriangleFan, OpenGL::LineStrip
OpenGL::Triangle, OpenGL::TriangleStrip, OpenGL::TriangleFan, OpenGL::Triangle
};
const auto shape = primTypes[primType];
drawVertices(shape, vertices, vertexCount);
}
renderer.drawVertices(shape, vertices, vertexCount);
}
Vertex GPU::getImmediateModeVertex() {
Vertex v;
const int totalAttrCount = (regs[PICAInternalRegs::VertexShaderAttrNum] & 0xf) + 1;
// Copy immediate mode attributes to vertex shader unit
for (int i = 0; i < totalAttrCount; i++) {
shaderUnit.vs.inputs[i] = immediateModeAttributes[i];
}
// Run VS and return vertex data. TODO: Don't hardcode offsets for each attribute
shaderUnit.vs.run();
std::memcpy(&v.position, &shaderUnit.vs.outputs[0], sizeof(vec4f));
std::memcpy(&v.colour, &shaderUnit.vs.outputs[1], sizeof(vec4f));
std::memcpy(&v.UVs, &shaderUnit.vs.outputs[2], 2 * sizeof(f24));
printf("(x, y, z, w) = (%f, %f, %f, %f)\n", (double)v.position.x(), (double)v.position.y(), (double)v.position.z(), (double)v.position.w());
printf("(r, g, b, a) = (%f, %f, %f, %f)\n", (double)v.colour.r(), (double)v.colour.g(), (double)v.colour.b(), (double)v.colour.a());
printf("(u, v ) = (%f, %f)\n", v.UVs.u(), v.UVs.v());
return v;
}
void GPU::fireDMA(u32 dest, u32 source, u32 size) {
log("[GPU] DMA of %08X bytes from %08X to %08X\n", size, source, dest);
constexpr u32 vramStart = VirtualAddrs::VramStart;
constexpr u32 vramSize = VirtualAddrs::VramSize;
const u32 fcramStart = mem.getLinearHeapVaddr();
constexpr u32 fcramSize = VirtualAddrs::FcramTotalSize;
// Shows whether this transfer is an FCRAM->VRAM transfer that's trivially optimizable
bool cpuToVRAM = true;
if (dest - vramStart >= vramSize || size > (vramSize - (dest - vramStart))) [[unlikely]] {
cpuToVRAM = false;
Helpers::panic("GPU DMA does not target VRAM");
}
if (source - fcramStart >= fcramSize || size > (fcramSize - (dest - fcramStart))) [[unlikely]] {
cpuToVRAM = false;
// Helpers::panic("GPU DMA does not have FCRAM as its source");
}
if (cpuToVRAM) [[likely]] {
// Valid, optimized FCRAM->VRAM DMA. TODO: Is VRAM->VRAM DMA allowed?
u8* fcram = mem.getFCRAM();
std::memcpy(&vram[dest - vramStart], &fcram[source - fcramStart], size);
} else {
printf("Non-trivially optimizable GPU DMA. Falling back to byte-by-byte transfer");
std::memcpy(&vram[dest - vramStart], mem.getReadPointer(source), size);
for (u32 i = 0; i < size; i++) {
mem.write8(dest + i, mem.read8(source + i));
}
}
}

View file

@ -4,8 +4,13 @@
using namespace Floats;
u32 GPU::readReg(u32 address) {
log("Ignoring read from GPU register %08X\n", address);
return 0;
if (address >= 0x1EF01000 && address < 0x1EF01C00) { // Internal registers
const u32 index = (address - 0x1EF01000) / sizeof(u32);
return readInternalReg(index);
} else {
log("Ignoring read to external GPU register %08X.\n", address);
return 0;
}
}
void GPU::writeReg(u32 address, u32 value) {
@ -54,6 +59,37 @@ void GPU::writeInternalReg(u32 index, u32 value, u32 mask) {
fixedAttribMask = (value >> 16) & 0xfff; // Determines which vertex attributes are fixed for all vertices
break;
case ColourBufferLoc: {
u32 loc = (value & 0x0fffffff) << 3;
renderer.setColourBufferLoc(loc);
break;
};
case ColourBufferFormat: {
u32 format = (value >> 16) & 7;
renderer.setColourFormat(format);
break;
}
case DepthBufferLoc: {
u32 loc = (value & 0x0fffffff) << 3;
renderer.setDepthBufferLoc(loc);
break;
}
case DepthBufferFormat: {
u32 fmt = value & 0x3;
renderer.setDepthFormat(fmt);
break;
}
case FramebufferSize: {
const u32 width = value & 0x7ff;
const u32 height = ((value >> 12) & 0x3ff) + 1;
renderer.setFBSize(width, height);
break;
}
case VertexFloatUniformIndex:
shaderUnit.vs.setFloatUniformIndex(value);
break;
@ -68,24 +104,85 @@ void GPU::writeInternalReg(u32 index, u32 value, u32 mask) {
fixedAttribCount = 0;
fixedAttribIndex = value & 0xf;
if (fixedAttribIndex == 0xf) Helpers::panic("[PICA] Immediate mode vertex submission");
if (fixedAttribIndex == 0xf) {
log("[PICA] Immediate mode vertex submission enabled");
immediateModeAttrIndex = 0;
immediateModeVertIndex = 0;
}
break;
// Restart immediate mode primitive drawing
case PrimitiveRestart:
if (value & 1) {
immediateModeAttrIndex = 0;
immediateModeVertIndex = 0;
}
break;
case FixedAttribData0: case FixedAttribData1: case FixedAttribData2:
if (fixedAttribIndex >= 12) Helpers::panic("[PICA] Tried to write to fixed attribute %d", fixedAttribIndex);
fixedAttrBuff[fixedAttribCount++] = value;
if (fixedAttribCount == 3) {
fixedAttribCount = 0;
vec4f& attr = shaderUnit.vs.fixedAttributes[fixedAttribIndex];
vec4f attr;
// These are stored in the reverse order anyone would expect them to be in
attr.x() = f24::fromRaw(fixedAttrBuff[2] & 0xffffff);
attr.y() = f24::fromRaw(((fixedAttrBuff[1] & 0xffff) << 8) | (fixedAttrBuff[2] >> 24));
attr.z() = f24::fromRaw(((fixedAttrBuff[0] & 0xff) << 16) | (fixedAttrBuff[1] >> 16));
attr.w() = f24::fromRaw(fixedAttrBuff[0] >> 8);
fixedAttribIndex++;
// If the fixed attribute index is < 12, we're just writing to one of the fixed attributes
if (fixedAttribIndex < 12) [[likely]] {
shaderUnit.vs.fixedAttributes[fixedAttribIndex++] = attr;
} else if (fixedAttribIndex == 15) { // Otherwise if it's 15, we're submitting an immediate mode vertex
const uint totalAttrCount = (regs[PICAInternalRegs::VertexShaderAttrNum] & 0xf) + 1;
if (totalAttrCount <= immediateModeAttrIndex) {
printf("Broken state in the immediate mode vertex submission pipeline. Failing silently\n");
immediateModeAttrIndex = 0;
immediateModeVertIndex = 0;
}
immediateModeAttributes[immediateModeAttrIndex++] = attr;
if (immediateModeAttrIndex == totalAttrCount) {
Vertex v = getImmediateModeVertex();
immediateModeAttrIndex = 0;
immediateModeVertices[immediateModeVertIndex++] = v;
// Get primitive type
const u32 primConfig = regs[PICAInternalRegs::PrimitiveConfig];
const u32 primType = (primConfig >> 8) & 3;
// If we've reached 3 verts, issue a draw call
// Handle rendering depending on the primitive type
if (immediateModeVertIndex == 3) {
renderer.drawVertices(OpenGL::Triangle, &immediateModeVertices[0], 3);
switch (primType) {
// Triangle or geometry primitive. Draw a triangle and discard all vertices
case 0: case 3:
immediateModeVertIndex = 0;
break;
// Triangle strip. Draw triangle, discard first vertex and keep the last 2
case 1:
immediateModeVertIndex = 2;
immediateModeVertices[0] = immediateModeVertices[1];
immediateModeVertices[1] = immediateModeVertices[2];
break;
// Triangle fan. Draw triangle, keep first vertex and last vertex, discard second vertex
case 2:
immediateModeVertIndex = 2;
immediateModeVertices[1] = immediateModeVertices[2];
break;
}
}
}
} else { // Writing to fixed attributes 13 and 14 probably does nothing, but we'll see
log("Wrote to invalid fixed vertex attribute %d\n", fixedAttribIndex);
}
}
break;
@ -125,6 +222,23 @@ void GPU::writeInternalReg(u32 index, u32 value, u32 mask) {
shaderUnit.vs.setBufferIndex(value);
break;
// Command lists can write to the command processor registers and change the command list stream
// Several games are known to do this, including New Super Mario Bros 2 and Super Mario 3D Land
case CmdBufTrigger0:
case CmdBufTrigger1: {
if (value != 0) { // A non-zero value triggers command list processing
int bufferIndex = index - CmdBufTrigger0; // Index of the command buffer to execute (0 or 1)
u32 addr = (regs[CmdBufAddr0 + bufferIndex] & 0xfffffff) << 3;
u32 size = (regs[CmdBufSize0 + bufferIndex] & 0xfffff) << 3;
// Set command buffer state to execute the new buffer
cmdBuffStart = getPointerPhys<u32>(addr);
cmdBuffCurr = cmdBuffStart;
cmdBuffEnd = cmdBuffStart + (size / sizeof(u32));
}
break;
}
default:
// Vertex attribute registers
if (index >= AttribInfoStart && index <= AttribInfoEnd) {
@ -140,6 +254,7 @@ void GPU::writeInternalReg(u32 index, u32 value, u32 mask) {
case 2:
attr.config2 = value;
attr.size = (value >> 16) & 0xff;
attr.componentCount = value >> 28;
break;
}
} else {
@ -147,4 +262,55 @@ void GPU::writeInternalReg(u32 index, u32 value, u32 mask) {
}
break;
}
}
void GPU::startCommandList(u32 addr, u32 size) {
cmdBuffStart = static_cast<u32*>(mem.getReadPointer(addr));
if (!cmdBuffStart) Helpers::panic("Couldn't get buffer for command list");
// TODO: This is very memory unsafe. We get a pointer to FCRAM and just keep writing without checking if we're gonna go OoB
cmdBuffCurr = cmdBuffStart;
cmdBuffEnd = cmdBuffStart + (size / sizeof(u32));
// LUT for converting the parameter mask to an actual 32-bit mask
// The parameter mask is 4 bits long, each bit corresponding to one byte of the mask
// If the bit is 0 then the corresponding mask byte is 0, otherwise the mask byte is 0xff
// So for example if the parameter mask is 0b1001, the full mask is 0xff'00'00'ff
static constexpr std::array<u32, 16> maskLUT = {
0x00000000, 0x000000ff, 0x0000ff00, 0x0000ffff, 0x00ff0000, 0x00ff00ff, 0x00ffff00, 0x00ffffff,
0xff000000, 0xff0000ff, 0xff00ff00, 0xff00ffff, 0xffff0000, 0xffff00ff, 0xffffff00, 0xffffffff,
};
while (cmdBuffCurr < cmdBuffEnd) {
// If the buffer is not aligned to an 8 byte boundary, force align it by moving the pointer up a word
// The curr pointer starts out doubleword-aligned and is increased by 4 bytes each time
// So to check if it is aligned, we get the number of words it's been incremented by
// If that number is an odd value then the buffer is not aligned, otherwise it is
if ((cmdBuffCurr - cmdBuffStart) % 2 != 0) {
cmdBuffCurr++;
}
// The first word of a command is the command parameter and the second one is the header
u32 param1 = *cmdBuffCurr++;
u32 header = *cmdBuffCurr++;
u32 id = header & 0xffff;
u32 paramMaskIndex = (header >> 16) & 0xf;
u32 paramCount = (header >> 20) & 0xff; // Number of additional parameters
// Bit 31 tells us whether this command is going to write to multiple sequential registers (if the bit is 1)
// Or if all written values will go to the same register (If the bit is 0). It's essentially the value that
// gets added to the "id" field after each register write
bool consecutiveWritingMode = (header >> 31) != 0;
u32 mask = maskLUT[paramMaskIndex]; // Actual parameter mask
// Increment the ID by 1 after each write if we're in consecutive mode, or 0 otherwise
u32 idIncrement = (consecutiveWritingMode) ? 1 : 0;
writeInternalReg(id, param1, mask);
for (u32 i = 0; i < paramCount; i++) {
id += idIncrement;
u32 param = *cmdBuffCurr++;
writeInternalReg(id, param, mask);
}
}
}

View file

@ -1,256 +0,0 @@
#include "PICA/float_types.hpp"
#include "PICA/gpu.hpp"
#include "PICA/regs.hpp"
#include "opengl.hpp"
using namespace Floats;
// This is all hacked up to display our first triangle
const char* vertexShader = R"(
#version 420 core
layout (location = 0) in vec4 coords;
layout (location = 1) in vec4 vertexColour;
out vec4 colour;
void main() {
gl_Position = coords * vec4(1.0, 1.0, -1.0, 1.0);
colour = vertexColour;
}
)";
const char* fragmentShader = R"(
#version 420 core
in vec4 colour;
out vec4 fragColour;
uniform uint u_alphaControl;
void main() {
fragColour = colour;
if ((u_alphaControl & 1u) != 0u) { // Check if alpha test is on
uint func = (u_alphaControl >> 4u) & 7u;
float reference = float((u_alphaControl >> 8u) & 0xffu) / 255.0;
float alpha = fragColour.a;
switch (func) {
case 0: discard; break; // Never pass alpha test
case 1: break; // Always pass alpha test
case 2: // Pass if equal
if (alpha != reference)
discard;
break;
case 3: // Pass if not equal
if (alpha == reference)
discard;
break;
case 4: // Pass if less than
if (alpha >= reference)
discard;
break;
case 5: // Pass if less than or equal
if (alpha > reference)
discard;
break;
case 6: // Pass if greater than
if (alpha <= reference)
discard;
break;
case 7: // Pass if greater than or equal
if (alpha < reference)
discard;
break;
}
}
}
)";
const char* displayVertexShader = R"(
#version 420 core
out vec2 UV;
void main() {
const vec4 positions[4] = vec4[](
vec4(-1.0, 1.0, 1.0, 1.0), // Top-left
vec4(1.0, 1.0, 1.0, 1.0), // Top-right
vec4(-1.0, -1.0, 1.0, 1.0), // Bottom-left
vec4(1.0, -1.0, 1.0, 1.0) // Bottom-right
);
// The 3DS displays both screens' framebuffer rotated 90 deg counter clockwise
// So we adjust our texcoords accordingly
const vec2 texcoords[4] = vec2[](
vec2(1.0, 1.0), // Top-right
vec2(1.0, 0.0), // Bottom-right
vec2(0.0, 1.0), // Top-left
vec2(0.0, 0.0) // Bottom-left
);
gl_Position = positions[gl_VertexID];
UV = texcoords[gl_VertexID];
}
)";
const char* displayFragmentShader = R"(
#version 420 core
in vec2 UV;
out vec4 FragColor;
uniform sampler2D u_texture; // TODO: Properly init this to 0 when I'm not lazy
void main() {
FragColor = texture(u_texture, UV);
}
)";
void GPU::initGraphicsContext() {
// Set up texture for top screen
fboTexture.create(400, 240, GL_RGBA8);
fboTexture.bind();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
fbo.createWithDrawTexture(fboTexture);
fbo.bind(OpenGL::DrawAndReadFramebuffer);
GLuint rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 400, 240); // use a single renderbuffer object for both a depth AND stencil buffer.
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo); // now actually attach it
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
Helpers::panic("Incomplete framebuffer");
//glBindRenderbuffer(GL_RENDERBUFFER, 0);
OpenGL::setViewport(400, 240);
OpenGL::setClearColor(0.0, 0.0, 0.0, 1.0);
OpenGL::clearColor();
OpenGL::Shader vert(vertexShader, OpenGL::Vertex);
OpenGL::Shader frag(fragmentShader, OpenGL::Fragment);
triangleProgram.create({ vert, frag });
triangleProgram.use();
alphaControlLoc = OpenGL::uniformLocation(triangleProgram, "u_alphaControl");
glUniform1ui(alphaControlLoc, 0); // Default alpha control to 0
OpenGL::Shader vertDisplay(displayVertexShader, OpenGL::Vertex);
OpenGL::Shader fragDisplay(displayFragmentShader, OpenGL::Fragment);
displayProgram.create({ vertDisplay, fragDisplay });
vbo.createFixedSize(sizeof(Vertex) * vertexBufferSize, GL_STREAM_DRAW);
vbo.bind();
vao.create();
vao.bind();
// Position (x, y, z, w) attributes
vao.setAttributeFloat<float>(0, 4, sizeof(Vertex), offsetof(Vertex, position));
vao.enableAttribute(0);
// Colour attribute
vao.setAttributeFloat<float>(1, 4, sizeof(Vertex), offsetof(Vertex, colour));
vao.enableAttribute(1);
dummyVBO.create();
dummyVAO.create();
}
void GPU::getGraphicsContext() {
OpenGL::disableScissor();
OpenGL::setViewport(400, 240);
fbo.bind(OpenGL::DrawAndReadFramebuffer);
vbo.bind();
vao.bind();
triangleProgram.use();
}
void GPU::drawVertices(OpenGL::Primitives primType, Vertex* vertices, u32 count) {
// Adjust alpha test if necessary
const u32 alphaControl = regs[PICAInternalRegs::AlphaTestConfig];
if (alphaControl != oldAlphaControl) {
oldAlphaControl = alphaControl;
glUniform1ui(alphaControlLoc, alphaControl);
}
const u32 depthControl = regs[PICAInternalRegs::DepthAndColorMask];
bool depthEnable = depthControl & 1;
bool depthWriteEnable = (depthControl >> 12) & 1;
int depthFunc = (depthControl >> 4) & 7;
int colourMask = (depthControl >> 8) & 0xf;
static constexpr std::array<GLenum, 8> depthModes = {
GL_NEVER, GL_ALWAYS, GL_EQUAL, GL_NOTEQUAL, GL_LESS, GL_LEQUAL, GL_GREATER, GL_GEQUAL
};
f24 depthScale = f24::fromRaw(regs[PICAInternalRegs::DepthScale] & 0xffffff);
f24 depthOffset = f24::fromRaw(regs[PICAInternalRegs::DepthOffset] & 0xffffff);
printf("Depth enable: %d, func: %d, writeEnable: %d\n", depthEnable, depthFunc, depthWriteEnable);
if (depthScale.toFloat32() != -1.0 || depthOffset.toFloat32() != 0.0)
Helpers::panic("TODO: Implement depth scale/offset. Remove the depth *= -1.0 from vertex shader");
// TODO: Actually use this
float viewportWidth = f24::fromRaw(regs[PICAInternalRegs::ViewportWidth] & 0xffffff).toFloat32() * 2.0;
float viewportHeight = f24::fromRaw(regs[PICAInternalRegs::ViewportHeight] & 0xffffff).toFloat32() * 2.0;
if (depthEnable) {
OpenGL::enableDepth();
glDepthFunc(depthModes[depthFunc]);
glDepthMask(depthWriteEnable ? GL_TRUE : GL_FALSE);
} else {
if (depthWriteEnable) {
OpenGL::enableDepth();
glDepthFunc(GL_ALWAYS);
} else {
OpenGL::disableDepth();
}
}
if (colourMask != 0xf) Helpers::panic("[PICA] Colour mask = %X != 0xf", colourMask);
vbo.bufferVertsSub(vertices, count);
OpenGL::draw(primType, count);
}
constexpr u32 topScreenBuffer = 0x1f000000;
constexpr u32 bottomScreenBuffer = 0x1f05dc00;
// Quick hack to display top screen for now
void GPU::display() {
OpenGL::disableDepth();
OpenGL::disableScissor();
OpenGL::bindScreenFramebuffer();
fboTexture.bind();
displayProgram.use();
dummyVAO.bind();
OpenGL::setClearColor(0.0, 0.0, 0.0, 1.0); // Clear screen colour
OpenGL::clearColor();
OpenGL::setViewport(0, 240, 400, 240); // Actually draw our 3DS screen
OpenGL::draw(OpenGL::TriangleStrip, 4);
}
void GPU::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) {
log("GPU: Clear buffer\nStart: %08X End: %08X\nValue: %08X Control: %08X\n", startAddress, endAddress, value, control);
const float r = float((value >> 24) & 0xff) / 255.0;
const float g = float((value >> 16) & 0xff) / 255.0;
const float b = float((value >> 8) & 0xff) / 255.0;
const float a = float(value & 0xff) / 255.0;
if (startAddress == topScreenBuffer) {
log("GPU: Cleared top screen\n");
} else if (startAddress == bottomScreenBuffer) {
log("GPU: Tried to clear bottom screen\n");
return;
} else {
log("GPU: Clearing some unknown buffer\n");
}
OpenGL::setClearColor(r, g, b, a);
OpenGL::clearColor();
}

View file

@ -14,6 +14,7 @@ void PICAShader::run() {
switch (opcode) {
case ShaderOpcodes::ADD: add(instruction); break;
case ShaderOpcodes::CALL: call(instruction); break;
case ShaderOpcodes::CALLC: callc(instruction); break;
case ShaderOpcodes::CALLU: callu(instruction); break;
case ShaderOpcodes::CMP1: case ShaderOpcodes::CMP2:
cmp(instruction);
@ -21,15 +22,27 @@ void PICAShader::run() {
case ShaderOpcodes::DP3: dp3(instruction); break;
case ShaderOpcodes::DP4: dp4(instruction); break;
case ShaderOpcodes::END: return; // Stop running shader
case ShaderOpcodes::FLR: flr(instruction); break;
case ShaderOpcodes::IFC: ifc(instruction); break;
case ShaderOpcodes::IFU: ifu(instruction); break;
case ShaderOpcodes::JMPC: jmpc(instruction); break;
case ShaderOpcodes::JMPU: jmpu(instruction); break;
case ShaderOpcodes::LOOP: loop(instruction); break;
case ShaderOpcodes::MAX: max(instruction); break;
case ShaderOpcodes::MIN: min(instruction); break;
case ShaderOpcodes::MOV: mov(instruction); break;
case ShaderOpcodes::MOVA: mova(instruction); break;
case ShaderOpcodes::MUL: mul(instruction); break;
case ShaderOpcodes::NOP: break; // Do nothing
case ShaderOpcodes::RCP: rcp(instruction); break;
case ShaderOpcodes::RSQ: rsq(instruction); break;
case ShaderOpcodes::SGEI: sgei(instruction); break;
case ShaderOpcodes::SLT: slt(instruction); break;
case ShaderOpcodes::SLTI: slti(instruction); break;
case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37:
madi(instruction);
break;
case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F:
mad(instruction);
@ -92,13 +105,15 @@ u8 PICAShader::getIndexedSource(u32 source, u32 index) {
PICAShader::vec4f PICAShader::getSource(u32 source) {
if (source < 0x10)
return attributes[source];
return inputs[source];
else if (source < 0x20)
return tempRegisters[source - 0x10];
else if (source <= 0x7f)
return floatUniforms[source - 0x20];
Helpers::panic("[PICA] Unimplemented source value: %X", source);
else {
Helpers::warn("[PICA] Unimplemented source value: %X\n", source);
return vec4f({ f24::zero(), f24::zero(), f24::zero(), f24::zero() });
}
}
PICAShader::vec4f& PICAShader::getDest(u32 dest) {
@ -129,12 +144,12 @@ bool PICAShader::isCondTrue(u32 instruction) {
void PICAShader::add(u32 instruction) {
const u32 operandDescriptor = operandDescriptors[instruction & 0x7f];
const u32 src1 = (instruction >> 12) & 0x7f;
u32 src1 = (instruction >> 12) & 0x7f;
const u32 src2 = (instruction >> 7) & 0x1f; // src2 coming first because PICA moment
const u32 idx = (instruction >> 19) & 3;
const u32 dest = (instruction >> 21) & 0x1f;
if (idx) Helpers::panic("[PICA] ADD: idx != 0");
src1 = getIndexedSource(src1, idx);
vec4f srcVec1 = getSourceSwizzled<1>(src1, operandDescriptor);
vec4f srcVec2 = getSourceSwizzled<2>(src2, operandDescriptor);
@ -150,12 +165,12 @@ void PICAShader::add(u32 instruction) {
void PICAShader::mul(u32 instruction) {
const u32 operandDescriptor = operandDescriptors[instruction & 0x7f];
const u32 src1 = (instruction >> 12) & 0x7f;
u32 src1 = (instruction >> 12) & 0x7f;
const u32 src2 = (instruction >> 7) & 0x1f; // src2 coming first because PICA moment
const u32 idx = (instruction >> 19) & 3;
const u32 dest = (instruction >> 21) & 0x1f;
if (idx) Helpers::panic("[PICA] MUL: idx != 0");
src1 = getIndexedSource(src1, idx);
vec4f srcVec1 = getSourceSwizzled<1>(src1, operandDescriptor);
vec4f srcVec2 = getSourceSwizzled<2>(src2, operandDescriptor);
@ -169,6 +184,46 @@ void PICAShader::mul(u32 instruction) {
}
}
void PICAShader::flr(u32 instruction) {
const u32 operandDescriptor = operandDescriptors[instruction & 0x7f];
u32 src = (instruction >> 12) & 0x7f;
const u32 idx = (instruction >> 19) & 3;
const u32 dest = (instruction >> 21) & 0x1f;
src = getIndexedSource(src, idx);
vec4f srcVector = getSourceSwizzled<1>(src, operandDescriptor);
vec4f& destVector = getDest(dest);
u32 componentMask = operandDescriptor & 0xf;
for (int i = 0; i < 4; i++) {
if (componentMask & (1 << i)) {
destVector[3 - i] = f24::fromFloat32(std::floor(srcVector[3 - 1].toFloat32()));
}
}
}
void PICAShader::max(u32 instruction) {
const u32 operandDescriptor = operandDescriptors[instruction & 0x7f];
const u32 src1 = (instruction >> 12) & 0x7f;
const u32 src2 = (instruction >> 7) & 0x1f; // src2 coming first because PICA moment
const u32 idx = (instruction >> 19) & 3;
const u32 dest = (instruction >> 21) & 0x1f;
if (idx) Helpers::panic("[PICA] MAX: idx != 0");
vec4f srcVec1 = getSourceSwizzled<1>(src1, operandDescriptor);
vec4f srcVec2 = getSourceSwizzled<2>(src2, operandDescriptor);
vec4f& destVector = getDest(dest);
u32 componentMask = operandDescriptor & 0xf;
for (int i = 0; i < 4; i++) {
if (componentMask & (1 << i)) {
const auto maximum = srcVec1[3 - i] > srcVec2[3 - i] ? srcVec1[3 - i] : srcVec2[3 - i];
destVector[3 - i] = maximum;
}
}
}
void PICAShader::min(u32 instruction) {
const u32 operandDescriptor = operandDescriptors[instruction & 0x7f];
const u32 src1 = (instruction >> 12) & 0x7f;
@ -213,7 +268,6 @@ void PICAShader::mova(u32 instruction) {
const u32 operandDescriptor = operandDescriptors[instruction & 0x7f];
const u32 src = (instruction >> 12) & 0x7f;
const u32 idx = (instruction >> 19) & 3;
const u32 dest = (instruction >> 21) & 0x1f;
if (idx) Helpers::panic("[PICA] MOVA: idx != 0");
vec4f srcVector = getSourceSwizzled<1>(src, operandDescriptor);
@ -227,12 +281,12 @@ void PICAShader::mova(u32 instruction) {
void PICAShader::dp3(u32 instruction) {
const u32 operandDescriptor = operandDescriptors[instruction & 0x7f];
const u32 src1 = (instruction >> 12) & 0x7f;
u32 src1 = (instruction >> 12) & 0x7f;
const u32 src2 = (instruction >> 7) & 0x1f; // src2 coming first because PICA moment
const u32 idx = (instruction >> 19) & 3;
const u32 dest = (instruction >> 21) & 0x1f;
if (idx) Helpers::panic("[PICA] DP3: idx != 0");
src1 = getIndexedSource(src1, idx);
vec4f srcVec1 = getSourceSwizzled<1>(src1, operandDescriptor);
vec4f srcVec2 = getSourceSwizzled<2>(src2, operandDescriptor);
@ -249,12 +303,12 @@ void PICAShader::dp3(u32 instruction) {
void PICAShader::dp4(u32 instruction) {
const u32 operandDescriptor = operandDescriptors[instruction & 0x7f];
const u32 src1 = (instruction >> 12) & 0x7f;
u32 src1 = (instruction >> 12) & 0x7f;
const u32 src2 = (instruction >> 7) & 0x1f; // src2 coming first because PICA moment
const u32 idx = (instruction >> 19) & 3;
const u32 dest = (instruction >> 21) & 0x1f;
if (idx) Helpers::panic("[PICA] DP4: idx != 0");
src1 = getIndexedSource(src1, idx);
vec4f srcVec1 = getSourceSwizzled<1>(src1, operandDescriptor);
vec4f srcVec2 = getSourceSwizzled<2>(src2, operandDescriptor);
@ -269,6 +323,26 @@ void PICAShader::dp4(u32 instruction) {
}
}
void PICAShader::rcp(u32 instruction) {
const u32 operandDescriptor = operandDescriptors[instruction & 0x7f];
const u32 src1 = (instruction >> 12) & 0x7f;
const u32 idx = (instruction >> 19) & 3;
const u32 dest = (instruction >> 21) & 0x1f;
if (idx) Helpers::panic("[PICA] RCP: idx != 0");
vec4f srcVec1 = getSourceSwizzled<1>(src1, operandDescriptor);
vec4f& destVector = getDest(dest);
f24 res = f24::fromFloat32(1.0f) / srcVec1[0];
u32 componentMask = operandDescriptor & 0xf;
for (int i = 0; i < 4; i++) {
if (componentMask & (1 << i)) {
destVector[3 - i] = res;
}
}
}
void PICAShader::rsq(u32 instruction) {
const u32 operandDescriptor = operandDescriptors[instruction & 0x7f];
const u32 src1 = (instruction >> 12) & 0x7f;
@ -279,7 +353,7 @@ void PICAShader::rsq(u32 instruction) {
vec4f srcVec1 = getSourceSwizzled<1>(src1, operandDescriptor);
vec4f& destVector = getDest(dest);
f24 res = f24::fromFloat32(1.0f / std::sqrtf(srcVec1[0].toFloat32()));
f24 res = f24::fromFloat32(1.0f / std::sqrt(srcVec1[0].toFloat32()));
u32 componentMask = operandDescriptor & 0xf;
for (int i = 0; i < 4; i++) {
@ -312,6 +386,91 @@ void PICAShader::mad(u32 instruction) {
}
}
void PICAShader::madi(u32 instruction) {
const u32 operandDescriptor = operandDescriptors[instruction & 0x1f];
const u32 src1 = (instruction >> 17) & 0x1f;
const u32 src2 = (instruction >> 12) & 0x1f;
u32 src3 = (instruction >> 5) & 0x7f;
const u32 idx = (instruction >> 22) & 3;
const u32 dest = (instruction >> 24) & 0x1f;
src3 = getIndexedSource(src3, idx);
auto srcVec1 = getSourceSwizzled<1>(src1, operandDescriptor);
auto srcVec2 = getSourceSwizzled<2>(src2, operandDescriptor);
auto srcVec3 = getSourceSwizzled<3>(src3, 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] + srcVec3[3 - i];
}
}
}
void PICAShader::slt(u32 instruction) {
const u32 operandDescriptor = operandDescriptors[instruction & 0x7f];
u32 src1 = (instruction >> 12) & 0x7f;
const u32 src2 = (instruction >> 7) & 0x1f; // src2 coming first because PICA moment
const u32 idx = (instruction >> 19) & 3;
const u32 dest = (instruction >> 21) & 0x1f;
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 = (instruction >> 14) & 0x1f;
u32 src2 = (instruction >> 7) & 0x7f;
const u32 idx = (instruction >> 19) & 3;
const u32 dest = (instruction >> 21) & 0x1f;
src2 = getIndexedSource(src2, idx);
auto srcVec1 = getSourceSwizzled<1>(src1, operandDescriptor);
auto 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::slti(u32 instruction) {
const u32 operandDescriptor = operandDescriptors[instruction & 0x7f];
const u32 src1 = (instruction >> 14) & 0x1f;
u32 src2 = (instruction >> 7) & 0x7f;
const u32 idx = (instruction >> 19) & 3;
const u32 dest = (instruction >> 21) & 0x1f;
src2 = getIndexedSource(src2, idx);
auto srcVec1 = getSourceSwizzled<1>(src1, operandDescriptor);
auto 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::cmp(u32 instruction) {
const u32 operandDescriptor = operandDescriptors[instruction & 0x7f];
const u32 src1 = (instruction >> 12) & 0x7f;
@ -408,6 +567,12 @@ void PICAShader::call(u32 instruction) {
pc = dest;
}
void PICAShader::callc(u32 instruction) {
if (isCondTrue(instruction)) {
call(instruction); // Pls inline
}
}
void PICAShader::callu(u32 instruction) {
const u32 bit = (instruction >> 22) & 0xf; // Bit of the bool uniform to check
@ -439,4 +604,18 @@ void PICAShader::loop(u32 instruction) {
loop.endingPC = dest + 1; // Loop is inclusive so we need + 1 here
loop.iterations = uniform.x() + 1;
loop.increment = uniform.z();
}
void PICAShader::jmpc(u32 instruction) {
if (isCondTrue(instruction))
pc = (instruction >> 10) & 0xfff;
}
void PICAShader::jmpu(u32 instruction) {
const u32 test = (instruction & 1) ^ 1; // If the LSB is 0 we want to compare to true, otherwise compare to false
const u32 dest = (instruction >> 10) & 0xfff;
const u32 bit = (instruction >> 22) & 0xf; // Bit of the bool uniform to check
if (((boolUniform >> bit) & 1) == test) // Jump if the bool uniform is the value we want
pc = dest;
}

View file

@ -18,7 +18,7 @@ void PICAShader::reset() {
f32UniformTransfer = false;
const vec4f zero = vec4f({ f24::zero(), f24::zero(), f24::zero(), f24::zero() });
attributes.fill(zero);
inputs.fill(zero);
floatUniforms.fill(zero);
outputs.fill(zero);
tempRegisters.fill(zero);

View file

@ -0,0 +1,108 @@
#include "fs/archive_ext_save_data.hpp"
#include <memory>
namespace fs = std::filesystem;
FSResult ExtSaveDataArchive::createFile(const FSPath& path, u64 size) {
if (size == 0)
Helpers::panic("ExtSaveData file does not support size == 0");
if (path.type == PathType::UTF16) {
if (!isPathSafe<PathType::UTF16>(path))
Helpers::panic("Unsafe path in ExtSaveData::CreateFile");
fs::path p = IOFile::getAppData() / backingFolder;
p += fs::path(path.utf16_string).make_preferred();
if (fs::exists(p))
return FSResult::AlreadyExists;
// 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) {
return FSResult::Success;
}
return FSResult::FileTooLarge;
}
Helpers::panic("ExtSaveDataArchive::OpenFile: Failed");
return FSResult::Success;
}
FSResult ExtSaveDataArchive::deleteFile(const FSPath& path) {
if (path.type == PathType::UTF16) {
if (!isPathSafe<PathType::UTF16>(path))
Helpers::panic("Unsafe path in ExtSaveData::DeleteFile");
fs::path p = IOFile::getAppData() / backingFolder;
p += fs::path(path.utf16_string).make_preferred();
std::error_code ec;
bool success = fs::remove(p, ec);
return success ? FSResult::Success : FSResult::FileNotFound;
}
Helpers::panic("ExtSaveDataArchive::DeleteFile: Failed");
return FSResult::Success;
}
FileDescriptor ExtSaveDataArchive::openFile(const FSPath& path, const FilePerms& perms) {
if (path.type == PathType::UTF16) {
if (!isPathSafe<PathType::UTF16>(path))
Helpers::panic("Unsafe path in ExtSaveData::OpenFile");
if (perms.create())
Helpers::panic("[ExtSaveData] Can't open file with create flag");
fs::path p = IOFile::getAppData() / backingFolder;
p += fs::path(path.utf16_string).make_preferred();
if (fs::exists(p)) { // Return file descriptor if the file exists
IOFile file(p.string().c_str(), "r+b"); // According to Citra, this ignores the OpenFlags field and always opens as r+b? TODO: Check
return file.isOpen() ? file.getHandle() : FileError;
} else {
return FileError;
}
}
Helpers::panic("ExtSaveDataArchive::OpenFile: Failed");
return FileError;
}
Rust::Result<ArchiveBase*, FSResult> ExtSaveDataArchive::openArchive(const FSPath& path) {
if (path.type != PathType::Binary || path.binary.size() != 12) {
Helpers::panic("ExtSaveData accessed with an invalid path in OpenArchive");
}
return Ok((ArchiveBase*)this);
}
Rust::Result<DirectorySession, FSResult> ExtSaveDataArchive::openDirectory(const FSPath& path) {
if (path.type == PathType::UTF16) {
if (!isPathSafe<PathType::UTF16>(path))
Helpers::panic("Unsafe path in ExtSaveData::OpenDirectory");
fs::path p = IOFile::getAppData() / backingFolder;
p += fs::path(path.utf16_string).make_preferred();
if (fs::is_regular_file(p)) {
printf("ExtSaveData: OpenArchive used with a file path");
return Err(FSResult::UnexpectedFileOrDir);
}
if (fs::is_directory(p)) {
return Ok(DirectorySession(this, p));
} else {
return Err(FSResult::FileNotFound);
}
}
Helpers::panic("ExtSaveDataArchive::OpenDirectory: Unimplemented path type");
return Err(FSResult::Success);
}
std::optional<u32> ExtSaveDataArchive::readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) {
Helpers::panic("ExtSaveDataArchive::ReadFile: Failed");
return std::nullopt;
}

View file

@ -1,62 +1,162 @@
#include "fs/archive_ncch.hpp"
#include "fs/bad_word_list.hpp"
#include "fs/country_list.hpp"
#include "fs/mii_data.hpp"
#include <algorithm>
#include <memory>
bool SelfNCCHArchive::openFile(const FSPath& path) {
if (!hasRomFS()) {
printf("Tried to open a SelfNCCH file without a RomFS\n");
return false;
}
namespace PathType {
enum : u32 {
RomFS = 0,
Code = 1,
ExeFS = 2,
};
};
if (path.type != PathType::Binary) {
printf("Invalid SelfNCCH path type\n");
return false;
}
namespace MediaType {
enum : u8 {
NAND = 0,
SD = 1,
Gamecard = 2
};
};
// We currently only know how to read from an NCCH's RomFS
if (mem.read32(path.pointer) != 0) {
Helpers::panic("Read from NCCH's non-RomFS section!");
}
return true;
FSResult NCCHArchive::createFile(const FSPath& path, u64 size) {
Helpers::panic("[NCCH] CreateFile not yet supported");
return FSResult::Success;
}
ArchiveBase* SelfNCCHArchive::openArchive(const FSPath& path) {
if (path.type != PathType::Empty) {
printf("Invalid path type for SelfNCCH archive: %d\n", path.type);
return nullptr;
}
return this;
FSResult NCCHArchive::deleteFile(const FSPath& path) {
Helpers::panic("[NCCH] Unimplemented DeleteFile");
return FSResult::Success;
}
std::optional<u32> SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) {
if (!hasRomFS()) {
FileDescriptor NCCHArchive::openFile(const FSPath& path, const FilePerms& perms) {
if (path.type != PathType::Binary || path.binary.size() != 20) {
Helpers::panic("NCCHArchive::OpenFile: Invalid path");
}
const u32 media = *(u32*)&path.binary[0]; // 0 for NCCH, 1 for SaveData
if (media != 0)
Helpers::panic("NCCHArchive::OpenFile: Tried to read non-NCCH file");
// Third word of the binary path indicates what we're reading from.
const u32 type = *(u32*)&path.binary[8];
if (media == 0 && type > 2)
Helpers::panic("NCCHArchive::OpenFile: Invalid file path type");
return NoFile;
}
Rust::Result<ArchiveBase*, FSResult> NCCHArchive::openArchive(const FSPath& path) {
if (path.type != PathType::Binary || path.binary.size() != 16) {
Helpers::panic("NCCHArchive::OpenArchive: Invalid path");
}
const u32 mediaType = path.binary[8];
if (mediaType != 0)
Helpers::panic("NCCH archive. Tried to access a mediatype other than the NAND. Type: %d", mediaType);
return Ok((ArchiveBase*)this);
}
std::optional<u32> NCCHArchive::readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) {
const auto& path = file->path.binary; // Path of the file
const auto& archivePath = file->archivePath.binary; // Path of the archive
const auto mediaType = archivePath[8];
const auto media = *(u32*)&path[0]; // 0 for NCCH, 1 for savedata
const auto partition = *(u32*)&path[4];
const auto type = *(u32*)&path[8]; // Type of the path
if (mediaType == MediaType::NAND) {
const u32 lowProgramID = *(u32*)&archivePath[0];
const u32 highProgramID = *(u32*)&archivePath[4];
// High Title ID of the archive (from Citra). https://3dbrew.org/wiki/Title_list.
constexpr u32 sharedDataArchive = 0x0004009B;
constexpr u32 systemDataArchive = 0x000400DB;
// Low ID (taken from Citra)
constexpr u32 miiData = 0x00010202;
constexpr u32 regionManifest = 0x00010402;
constexpr u32 badWordList = 0x00010302;
constexpr u32 sharedFont = 0x00014002;
std::vector<u8> fileData;
if (highProgramID == sharedDataArchive) {
if (lowProgramID == miiData) fileData = std::vector<u8>(std::begin(MII_DATA), std::end(MII_DATA));
else if (lowProgramID == regionManifest) fileData = std::vector<u8>(std::begin(COUNTRY_LIST_DATA), std::end(COUNTRY_LIST_DATA));
else Helpers::panic("[NCCH archive] Read unimplemented NAND file. ID: %08X", lowProgramID);
} else if (highProgramID == systemDataArchive && lowProgramID == badWordList) {
fileData = std::vector<u8>(std::begin(BAD_WORD_LIST_DATA), std::end(BAD_WORD_LIST_DATA));
} else {
Helpers::panic("[NCCH archive] Read from unimplemented NCCH archive file. High program ID: %08X, low ID: %08X",
highProgramID, lowProgramID);
}
if (offset >= fileData.size()) {
Helpers::panic("[NCCH archive] Out of bounds read from NAND file");
}
u32 availableBytes = u32(fileData.size() - offset); // How many bytes we can read from the file
u32 bytesRead = std::min<u32>(size, availableBytes); // Cap the amount of bytes to read if we're going to go out of bounds
for (u32 i = 0; i < bytesRead; i++) {
mem.write8(dataPointer + i, fileData[offset + i]);
}
return bytesRead;
} else {
Helpers::panic("NCCH archive tried to read non-NAND file");
}
// Code below is for mediaType == 2 (gamecard). Currently unused
if (partition != 0)
Helpers::panic("[NCCH] Tried to read from non-zero partition");
if (type == PathType::RomFS && !hasRomFS()) {
Helpers::panic("Tried to read file from non-existent RomFS");
return std::nullopt;
}
if (type == PathType::ExeFS && !hasExeFS()) {
Helpers::panic("Tried to read file from non-existent RomFS");
return std::nullopt;
}
if (!file->isOpen) {
printf("Tried to read from closed SelfNCCH file session");
printf("Tried to read from closed NCCH file session");
return std::nullopt;
}
auto cxi = mem.getCXI();
const u32 romFSSize = cxi->romFS.size;
const u32 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");
}
IOFile& ioFile = mem.CXIFile;
if (!ioFile.seek(cxi->fileOffset + romFSOffset + offset + 0x1000)) {
Helpers::panic("Failed to seek while reading from RomFS");
// Seek to file offset depending on if we're reading from RomFS, ExeFS, etc
switch (type) {
case PathType::RomFS: {
const u32 romFSSize = cxi->romFS.size;
const u32 romFSOffset = cxi->romFS.offset;
if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) {
Helpers::panic("Tried to read from NCCH with too big of an offset");
}
if (!ioFile.seek(cxi->fileOffset + romFSOffset + offset + 0x1000)) {
Helpers::panic("Failed to seek while reading from RomFS");
}
break;
}
default:
Helpers::panic("Unimplemented file path type for NCCH archive");
}
std::unique_ptr<u8[]> data(new u8[size]);
auto [success, bytesRead] = ioFile.readBytes(&data[0], size);
if (!success) {
Helpers::panic("Failed to read from RomFS");
Helpers::panic("Failed to read from NCCH archive");
}
for (u64 i = 0; i < bytesRead; i++) {

View file

@ -2,58 +2,112 @@
#include <algorithm>
#include <memory>
bool SaveDataArchive::openFile(const FSPath& path) {
if (!cartHasSaveData()) {
printf("Tried to read SaveData FS without save data\n");
return false;
}
namespace fs = std::filesystem;
if (path.type == PathType::UTF16 && mem.readString(path.pointer, path.size) == "/") {
printf("Reading root save data dir\n");
return true;
}
if (path.type != PathType::Binary) {
printf("Unimplemented SaveData path type: %d\n", path.type);
return false;
}
return true;
FSResult SaveDataArchive::createFile(const FSPath& path, u64 size) {
Helpers::panic("[SaveData] CreateFile not yet supported");
return FSResult::Success;
}
ArchiveBase* SaveDataArchive::openArchive(const FSPath& path) {
if (path.type != PathType::Empty) {
Helpers::panic("Unimplemented path type for SaveData archive: %d\n", path.type);
return nullptr;
FSResult SaveDataArchive::createDirectory(const FSPath& path) {
if (path.type == PathType::UTF16) {
if (!isPathSafe<PathType::UTF16>(path))
Helpers::panic("Unsafe path in SaveData::OpenFile");
fs::path p = IOFile::getAppData() / "SaveData";
p += fs::path(path.utf16_string).make_preferred();
if (fs::is_directory(p))
return FSResult::AlreadyExists;
if (fs::is_regular_file(p)) {
Helpers::panic("File path passed to SaveData::CreateDirectory");
}
bool success = fs::create_directory(p);
return success ? FSResult::Success : FSResult::UnexpectedFileOrDir;
} else {
Helpers::panic("Unimplemented SaveData::CreateDirectory");
}
}
FSResult SaveDataArchive::deleteFile(const FSPath& path) {
Helpers::panic("[SaveData] Unimplemented DeleteFile");
return FSResult::Success;
}
FileDescriptor SaveDataArchive::openFile(const FSPath& path, const FilePerms& perms) {
if (path.type == PathType::UTF16) {
if (!isPathSafe<PathType::UTF16>(path))
Helpers::panic("Unsafe path in SaveData::OpenFile");
if (perms.raw == 0 || (perms.create() && !perms.write()))
Helpers::panic("[SaveData] Unsupported flags for OpenFile");
fs::path p = IOFile::getAppData() / "SaveData";
p += fs::path(path.utf16_string).make_preferred();
const char* permString = perms.write() ? "r+b" : "rb";
if (fs::exists(p)) { // Return file descriptor if the file exists
IOFile file(p.string().c_str(), permString);
return file.isOpen() ? file.getHandle() : FileError;
} else {
// If the file is not found, create it if the create flag is on
if (perms.create()) {
IOFile file(p.string().c_str(), "wb"); // Create file
file.close(); // Close it
file.open(p.string().c_str(), permString); // Reopen with proper perms
return file.isOpen() ? file.getHandle() : FileError;
} else {
return FileError;
}
}
}
return this;
Helpers::panic("SaveDataArchive::OpenFile: Failed");
return FileError;
}
Rust::Result<DirectorySession, FSResult> SaveDataArchive::openDirectory(const FSPath& path) {
if (path.type == PathType::UTF16) {
if (!isPathSafe<PathType::UTF16>(path))
Helpers::panic("Unsafe path in SaveData::OpenDirectory");
fs::path p = IOFile::getAppData() / "SaveData";
p += fs::path(path.utf16_string).make_preferred();
if (fs::is_regular_file(p)) {
printf("SaveData: OpenDirectory used with a file path");
return Err(FSResult::UnexpectedFileOrDir);
}
if (fs::is_directory(p)) {
return Ok(DirectorySession(this, p));
} else {
return Err(FSResult::FileNotFound);
}
}
Helpers::panic("SaveDataArchive::OpenDirectory: Unimplemented path type");
return Err(FSResult::Success);
}
ArchiveBase::FormatInfo SaveDataArchive::getFormatInfo(const FSPath& path) {
//Helpers::panic("Unimplemented SaveData::GetFormatInfo");
return FormatInfo{ .size = 0, .numOfDirectories = 255, .numOfFiles = 255, .duplicateData = false };
}
Rust::Result<ArchiveBase*, FSResult> SaveDataArchive::openArchive(const FSPath& path) {
if (path.type != PathType::Empty) {
Helpers::panic("Unimplemented path type for SaveData archive: %d\n", path.type);
return Err(FSResult::NotFoundInvalid);
}
return Ok((ArchiveBase*)this);
}
std::optional<u32> SaveDataArchive::readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) {
if (!cartHasSaveData()) {
printf("Tried to read SaveData FS without save data\n");
return std::nullopt;
}
auto cxi = mem.getCXI();
const u64 saveSize = cxi->saveData.size();
if (offset >= saveSize) {
printf("Tried to read from past the end of save data\n");
return std::nullopt;
}
const u64 endOffset = std::min<u64>(saveSize, offset + size); // Don't go past the end of the save file
const u32 bytesRead = endOffset - offset;
if (bytesRead != 0x22) Helpers::panic("Might want to actually implement SaveData");
static constexpr std::array<u8, 0x22> saveDataStub = { 0x00, 0x23, 0x3C, 0x77, 0x67, 0x28, 0x30, 0x33, 0x58, 0x61, 0x39, 0x61, 0x48, 0x59, 0x36, 0x55, 0x43, 0x76, 0x58, 0x61, 0x6F, 0x65, 0x48, 0x6D, 0x2B, 0x5E, 0x6F, 0x62, 0x3E, 0x6F, 0x34, 0x00, 0x77, 0x09};
for (u32 i = 0; i < bytesRead; i++) {
mem.write8(dataPointer + i, saveDataStub[i]);
}
return bytesRead;
Helpers::panic("Unimplemented SaveData::ReadFile");
return 0;
}

View file

@ -1,17 +1,27 @@
#include "fs/archive_sdmc.hpp"
#include <memory>
bool SDMCArchive::openFile(const FSPath& path) {
printf("SDMCArchive::OpenFile: Failed");
return false;
FSResult SDMCArchive::createFile(const FSPath& path, u64 size) {
Helpers::panic("[SDMC] CreateFile not yet supported");
return FSResult::Success;
}
ArchiveBase* SDMCArchive::openArchive(const FSPath& path) {
printf("SDMCArchive::OpenArchive: Failed");
return nullptr;
FSResult SDMCArchive::deleteFile(const FSPath& path) {
Helpers::panic("[SDMC] Unimplemented DeleteFile");
return FSResult::Success;
}
FileDescriptor SDMCArchive::openFile(const FSPath& path, const FilePerms& perms) {
printf("SDMCArchive::OpenFile: Failed");
return FileError;
}
Rust::Result<ArchiveBase*, FSResult> SDMCArchive::openArchive(const FSPath& path) {
printf("SDMCArchive::OpenArchive: Failed\n");
return Ok((ArchiveBase*)nullptr);
}
std::optional<u32> SDMCArchive::readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) {
printf("SDMCArchive::ReadFile: Failed");
printf("SDMCArchive::ReadFile: Failed\n");
return std::nullopt;
}

View file

@ -0,0 +1,118 @@
#include "fs/archive_self_ncch.hpp"
#include <memory>
// The part of the NCCH archive we're trying to access. Depends on the first 4 bytes of the binary file path
namespace PathType {
enum : u32 {
RomFS = 0,
ExeFS = 2
};
};
FSResult SelfNCCHArchive::createFile(const FSPath& path, u64 size) {
Helpers::panic("[SelfNCCH] CreateFile not yet supported");
return FSResult::Success;
}
FSResult SelfNCCHArchive::deleteFile(const FSPath& path) {
Helpers::panic("[SelfNCCH] Unimplemented DeleteFile");
return FSResult::Success;
}
FileDescriptor SelfNCCHArchive::openFile(const FSPath& path, const FilePerms& perms) {
if (!hasRomFS()) {
printf("Tried to open a SelfNCCH file without a RomFS\n");
return FileError;
}
if (path.type != PathType::Binary || path.binary.size() != 12) {
printf("Invalid SelfNCCH path type\n");
return FileError;
}
// 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!");
}
return NoFile; // No file descriptor needed for RomFS
}
Rust::Result<ArchiveBase*, FSResult> SelfNCCHArchive::openArchive(const FSPath& path) {
if (path.type != PathType::Empty) {
Helpers::panic("Invalid path type for SelfNCCH archive: %d\n", path.type);
return Err(FSResult::NotFoundInvalid);
}
return Ok((ArchiveBase*)this);
}
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
if (type == PathType::RomFS && !hasRomFS()) {
Helpers::panic("Tried to read file from non-existent RomFS");
return std::nullopt;
}
if (type == PathType::ExeFS && !hasExeFS()) {
Helpers::panic("Tried to read file from non-existent RomFS");
return std::nullopt;
}
if (!file->isOpen) {
printf("Tried to read from closed SelfNCCH file session");
return std::nullopt;
}
auto cxi = mem.getCXI();
IOFile& ioFile = mem.CXIFile;
// Seek to file offset depending on if we're reading from RomFS, ExeFS, etc
switch (type) {
case PathType::RomFS: {
const u32 romFSSize = cxi->romFS.size;
const u32 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");
}
if (!ioFile.seek(cxi->fileOffset + romFSOffset + offset + 0x1000)) {
Helpers::panic("Failed to seek while reading from RomFS");
}
break;
}
case PathType::ExeFS: {
const u32 exeFSSize = cxi->exeFS.size;
const u32 exeFSOffset = cxi->exeFS.offset;
if ((offset >> 32) || (offset >= exeFSSize) || (offset + size >= exeFSSize)) {
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
}
if (!ioFile.seek(cxi->fileOffset + exeFSOffset + offset)) { // TODO: Not sure if this needs the + 0x1000
Helpers::panic("Failed to seek while reading from ExeFS");
}
break;
}
default:
Helpers::panic("Unimplemented file path type for SelfNCCH archive");
}
std::unique_ptr<u8[]> data(new u8[size]);
auto [success, bytesRead] = ioFile.readBytes(&data[0], size);
if (!success) {
Helpers::panic("Failed to read from SelfNCCH archive");
}
for (u64 i = 0; i < bytesRead; i++) {
mem.write8(dataPointer + i, data[i]);
}
return bytesRead;
}

View file

@ -25,6 +25,7 @@ Handle Kernel::makeArbiter() {
// Result CreateAddressArbiter(Handle* arbiter)
void Kernel::createAddressArbiter() {
logSVC("CreateAddressArbiter\n");
regs[0] = SVCResult::Success;
regs[1] = makeArbiter();
}
@ -47,7 +48,7 @@ void Kernel::arbitrateAddress() {
}
if (address & 3) [[unlikely]] {
Helpers::panic("ArbitrateAddres:: Unaligned address");
Helpers::panic("ArbitrateAddress: Unaligned address");
}
if (type > 4) [[unlikely]] {
@ -85,6 +86,8 @@ void Kernel::arbitrateAddress() {
default:
Helpers::panic("ArbitrateAddress: Unimplemented type %s", arbitrationTypeToString(type));
}
rescheduleThreads();
}
// Signal up to "threadCount" threads waiting on the arbiter indicated by "waitingAddress"
@ -103,8 +106,4 @@ void Kernel::signalArbiter(u32 waitingAddress, s32 threadCount) {
if (count == threadCount && threadCount > 0) break;
}
}
if (count != 0) {
rescheduleThreads();
}
}

View file

@ -0,0 +1,46 @@
#include "kernel.hpp"
namespace DirectoryOps {
enum : u32 {
Read = 0x08010042,
Close = 0x08020000
};
}
namespace Result {
enum : u32 {
Success = 0
};
}
void Kernel::handleDirectoryOperation(u32 messagePointer, Handle directory) {
const u32 cmd = mem.read32(messagePointer);
switch (cmd) {
case DirectoryOps::Close: closeDirectory(messagePointer, directory); break;
case DirectoryOps::Read: readDirectory(messagePointer, directory); break;
default: Helpers::panic("Unknown directory operation: %08X", cmd);
}
}
void Kernel::closeDirectory(u32 messagePointer, Handle directory) {
logFileIO("Closed directory %X\n", directory);
const auto p = getObject(directory, KernelObjectType::Directory);
if (p == nullptr) [[unlikely]] {
Helpers::panic("Called CloseFile on non-existent file");
}
p->getData<DirectorySession>()->isOpen = false;
mem.write32(messagePointer + 4, Result::Success);
}
void Kernel::readDirectory(u32 messagePointer, Handle directory) {
const u32 entryCount = mem.read32(messagePointer + 4);
const u32 outPointer = mem.read32(messagePointer + 12);
logFileIO("Directory::Read (handle = %X, entry count = %d, out pointer = %08X)\n", directory, entryCount, outPointer);
Helpers::panic("Unimplemented FsDir::Read");
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 0);
}

View file

@ -21,7 +21,9 @@ namespace FatalErrorType {
void Kernel::handleErrorSyncRequest(u32 messagePointer) {
u32 cmd = mem.read32(messagePointer);
switch (cmd) {
case Commands::Throw: throwError(messagePointer); break;
case Commands::Throw:
throwError(messagePointer);
break;
default:
Helpers::panic("Unimplemented err:f command %08X\n", cmd);

View file

@ -1,4 +1,7 @@
#include "kernel.hpp"
#include "cpu.hpp"
#include <bit>
#include <utility>
const char* Kernel::resetTypeToString(u32 type) {
switch (type) {
@ -15,8 +18,44 @@ Handle Kernel::makeEvent(ResetType resetType) {
return ret;
}
bool Kernel::signalEvent(Handle handle) {
KernelObject* object = getObject(handle, KernelObjectType::Event);
if (object == nullptr) [[unlikely]] {
Helpers::panic("Tried to signal non-existent event");
return false;
}
Event* event = object->getData<Event>();
event->fired = true;
// One shot events go back to being not fired once they are signaled
if (event->resetType == ResetType::Pulse) {
event->fired = false;
}
// Check if there's any thread waiting on this event
if (event->waitlist != 0) {
// One-shot events get cleared once they are acquired by some thread and only wake up 1 thread at a time
if (event->resetType == ResetType::OneShot) {
int index = wakeupOneThread(event->waitlist, handle); // Wake up one thread with the highest priority
event->waitlist ^= (1ull << index); // Remove thread from waitlist
event->fired = false;
} else {
wakeupAllThreads(event->waitlist, handle);
event->waitlist = 0; // No threads waiting;
}
// We must reschedule our threads if we signalled one. Some games such as FE: Awakening rely on this
// If this does not happen, we can have phenomena such as a thread waiting up a higher priority thread,
// and the higher priority thread just never running
rescheduleThreads();
}
return true;
}
// Result CreateEvent(Handle* event, ResetType resetType)
void Kernel::createEvent() {
void Kernel::svcCreateEvent() {
const u32 outPointer = regs[0];
const u32 resetType = regs[1];
@ -30,12 +69,13 @@ void Kernel::createEvent() {
}
// Result ClearEvent(Handle event)
void Kernel::clearEvent() {
void Kernel::svcClearEvent() {
const Handle handle = regs[0];
const auto event = getObject(handle, KernelObjectType::Event);
logSVC("ClearEvent(event handle = %X)\n", handle);
if (event == nullptr) [[unlikely]] {
Helpers::panic("Tried to clear non-existent event (handle = %X)", handle);
regs[0] = SVCResult::BadHandle;
return;
}
@ -45,63 +85,152 @@ void Kernel::clearEvent() {
}
// Result SignalEvent(Handle event)
void Kernel::signalEvent() {
void Kernel::svcSignalEvent() {
const Handle handle = regs[0];
const auto event = getObject(handle, KernelObjectType::Event);
logSVC("SignalEvent(event handle = %X)\n", handle);
printf("Stubbed SignalEvent!!\n");
KernelObject* object = getObject(handle, KernelObjectType::Event);
/*
if (event == nullptr) [[unlikely]] {
regs[0] = SVCResult::BadHandle;
return;
}
event->getData<Event>()->fired = true;
*/
regs[0] = SVCResult::Success;
if (object == nullptr) {
Helpers::panic("Signalled non-existent event: %X\n", handle);
regs[0] = SVCResult::BadHandle;
} else {
// We must signalEvent after setting r0, otherwise the r0 of the new thread will ne corrupted
regs[0] = SVCResult::Success;
signalEvent(handle);
}
}
// Result WaitSynchronization1(Handle handle, s64 timeout_nanoseconds)
void Kernel::waitSynchronization1() {
const Handle handle = regs[0];
const s64 ns = s64(u64(regs[1]) | (u64(regs[2]) << 32));
const auto event = getObject(handle, KernelObjectType::Event);
logSVC("WaitSynchronization1(handle = %X, ns = %lld)\n", handle, ns);
if (event == nullptr) [[unlikely]] {
Helpers::panic("WaitSynchronization1: Bad event handle");
const auto object = getObject(handle);
if (object == nullptr) [[unlikely]] {
Helpers::panic("WaitSynchronization1: Bad event handle %X\n", handle);
regs[0] = SVCResult::BadHandle;
return;
}
logSVC("WaitSynchronization1(handle = %X, ns = %lld) (STUBBED)\n", handle, ns);
regs[0] = SVCResult::Success;
if (!isWaitable(object)) [[unlikely]] {
Helpers::panic("Tried to wait on a non waitable object. Type: %s, handle: %X\n", object->getTypeName(), handle);
}
if (!shouldWaitOnObject(object)) {
acquireSyncObject(object, threads[currentThreadIndex]); // Acquire the object since it's ready
regs[0] = SVCResult::Success;
rescheduleThreads();
} else {
// Timeout is 0, don't bother waiting, instantly timeout
if (ns == 0) {
regs[0] = SVCResult::Timeout;
return;
}
regs[0] = SVCResult::Timeout; // This will be overwritten with success if we don't timeout
auto& t = threads[currentThreadIndex];
t.waitList.resize(1);
t.status = ThreadStatus::WaitSync1;
t.sleepTick = cpu.getTicks();
t.waitingNanoseconds = ns;
t.waitList[0] = handle;
// Add the current thread to the object's wait list
object->getWaitlist() |= (1ull << currentThreadIndex);
switchToNextThread();
}
}
// Result WaitSynchronizationN(s32* out, Handle* handles, s32 handlecount, bool waitAll, s64 timeout_nanoseconds)
void Kernel::waitSynchronizationN() {
// TODO: Are these arguments even correct?
u32 ns1 = regs[0];
s32 ns1 = regs[0];
u32 handles = regs[1];
u32 handleCount = regs[2];
s32 handleCount = regs[2];
bool waitAll = regs[3] != 0;
u32 ns2 = regs[4];
u32 out = regs[5];
s32 outPointer = regs[5]; // "out" pointer - shows which object got bonked if we're waiting on multiple objects
s64 ns = s64(ns1) | (s64(ns2) << 32);
logSVC("WaitSynchronizationN (STUBBED)\n");
regs[0] = SVCResult::Success;
logSVC("WaitSynchronizationN (handle pointer: %08X, count: %d, timeout = %lld)\n", handles, handleCount, ns);
printf("Hacky WaitSync stuff for OoT triggered!!!\n");
threads[currentThreadIndex].status = ThreadStatus::Ready;
if (handleCount <= 0)
Helpers::panic("WaitSyncN: Invalid handle count");
while (1) {
auto index = rand() % threadCount;
auto& thread = threads[index];
using WaitObject = std::pair<Handle, KernelObject*>;
std::vector<WaitObject> waitObjects(handleCount);
if (canThreadRun(thread)) {
switchThread(rand() % threadCount);
break;
// We don't actually need to wait if waitAll == true unless one of the objects is not ready
bool allReady = true; // Default initialize to true, set to fault if one of the objects is not ready
// Tracks whether at least one object is ready, + the index of the first ready object
// This is used when waitAll == false, because if one object is already available then we can skip the sleeping
bool oneObjectReady = false;
s32 firstReadyObjectIndex = 0;
for (s32 i = 0; i < handleCount; i++) {
Handle handle = mem.read32(handles);
handles += sizeof(Handle);
auto object = getObject(handle);
// Panic if one of the objects is not even an object
if (object == nullptr) [[unlikely]] {
Helpers::panic("WaitSynchronizationN: Bad object handle %X\n", handle);
regs[0] = SVCResult::BadHandle;
return;
}
// Panic if one of the objects is not a valid sync object
if (!isWaitable(object)) [[unlikely]] {
Helpers::panic("Tried to wait on a non waitable object in WaitSyncN. Type: %s, handle: %X\n",
object->getTypeName(), handle);
}
if (shouldWaitOnObject(object)) {
allReady = false; // Derp, not all objects are ready :(
} else { /// At least one object is ready to be acquired ahead of time. If it's the first one, write it down
if (!oneObjectReady) {
oneObjectReady = true;
firstReadyObjectIndex = i;
}
}
waitObjects[i] = {handle, object};
}
auto& t = threads[currentThreadIndex];
// We only need to wait on one object. Easy...?!
if (!waitAll) {
// If there's ready objects, acquire the first one and return
if (oneObjectReady) {
regs[0] = SVCResult::Success;
regs[1] = firstReadyObjectIndex; // Return index of the acquired object
acquireSyncObject(waitObjects[firstReadyObjectIndex].second, t); // Acquire object
rescheduleThreads();
return;
}
regs[0] = SVCResult::Timeout; // This will be overwritten with success if we don't timeout
// If the thread wakes up without timeout, this will be adjusted to the index of the handle that woke us up
regs[1] = 0xFFFFFFFF;
t.waitList.resize(handleCount);
t.status = ThreadStatus::WaitSyncAny;
t.outPointer = outPointer;
t.waitingNanoseconds = ns;
t.sleepTick = cpu.getTicks();
for (s32 i = 0; i < handleCount; i++) {
t.waitList[i] = waitObjects[i].first; // Add object to this thread's waitlist
waitObjects[i].second->getWaitlist() |= (1ull << currentThreadIndex); // And add the thread to the object's waitlist
}
switchToNextThread();
} else {
Helpers::panic("WaitSynchronizatioN with waitAll");
}
}

View file

@ -3,7 +3,12 @@
namespace FileOps {
enum : u32 {
Read = 0x080200C2,
Close = 0x08080000
Write = 0x08030102,
GetSize = 0x08040000,
SetSize = 0x08050080,
Close = 0x08080000,
SetPriority = 0x080A0040,
OpenLinkFile = 0x080C0000
};
}
@ -18,7 +23,12 @@ void Kernel::handleFileOperation(u32 messagePointer, Handle file) {
const u32 cmd = mem.read32(messagePointer);
switch (cmd) {
case FileOps::Close: closeFile(messagePointer, file); break;
case FileOps::GetSize: getFileSize(messagePointer, file); break;
case FileOps::OpenLinkFile: openLinkFile(messagePointer, file); break;
case FileOps::Read: readFile(messagePointer, file); break;
case FileOps::SetSize: setFileSize(messagePointer, file); break;
case FileOps::SetPriority: setFilePriority(messagePointer, file); break;
case FileOps::Write: writeFile(messagePointer, file); break;
default: Helpers::panic("Unknown file operation: %08X", cmd);
}
}
@ -52,9 +62,31 @@ void Kernel::readFile(u32 messagePointer, Handle fileHandle) {
if (!file->isOpen) {
Helpers::panic("Tried to read closed file");
}
// Handle files with their own file descriptors by just fread'ing the data
if (file->fd) {
std::unique_ptr<u8[]> data(new u8[size]);
IOFile f(file->fd);
auto [success, bytesRead] = f.readBytes(data.get(), size);
if (!success) {
Helpers::panic("Kernel::ReadFile with file descriptor failed");
}
else {
for (size_t i = 0; i < bytesRead; i++) {
mem.write8(dataPointer + i, data[i]);
}
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, bytesRead);
}
return;
}
// Handle files without their own FD, such as SelfNCCH files
auto archive = file->archive;
std::optional<u32> bytesRead = archive->readFile(file, offset, size, dataPointer);
if (!bytesRead.has_value()) {
Helpers::panic("Kernel::ReadFile failed");
@ -62,4 +94,142 @@ void Kernel::readFile(u32 messagePointer, Handle fileHandle) {
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, bytesRead.value());
}
}
void Kernel::writeFile(u32 messagePointer, Handle fileHandle) {
u64 offset = mem.read64(messagePointer + 4);
u32 size = mem.read32(messagePointer + 12);
u32 writeOption = mem.read32(messagePointer + 16);
u32 dataPointer = mem.read32(messagePointer + 24);
logFileIO("Trying to write %X bytes to file %X, starting from file offset %llX and memory address %08X\n",
size, fileHandle, offset, dataPointer);
const auto p = getObject(fileHandle, KernelObjectType::File);
if (p == nullptr) [[unlikely]] {
Helpers::panic("Called ReadFile on non-existent file");
}
FileSession* file = p->getData<FileSession>();
if (!file->isOpen) {
Helpers::panic("Tried to write closed file");
}
if (!file->fd)
Helpers::panic("[Kernel::File::WriteFile] Tried to write to file without a valid file descriptor");
std::unique_ptr<u8[]> data(new u8[size]);
for (size_t i = 0; i < size; i++) {
data[i] = mem.read8(dataPointer + i);
}
IOFile f(file->fd);
auto [success, bytesWritten] = f.writeBytes(data.get(), size);
if (!success) {
Helpers::panic("Kernel::WriteFile failed");
} else {
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, bytesWritten);
}
}
void Kernel::setFileSize(u32 messagePointer, Handle fileHandle) {
logFileIO("Setting size of file %X\n", fileHandle);
const auto p = getObject(fileHandle, KernelObjectType::File);
if (p == nullptr) [[unlikely]] {
Helpers::panic("Called SetFileSize on non-existent file");
}
FileSession* file = p->getData<FileSession>();
if (!file->isOpen) {
Helpers::panic("Tried to get size of closed file");
}
if (file->fd) {
const u64 newSize = mem.read64(messagePointer + 4);
IOFile f(file->fd);
bool success = f.setSize(newSize);
if (success) {
mem.write32(messagePointer + 4, Result::Success);
} else {
Helpers::panic("FileOp::SetFileSize failed");
}
} else {
Helpers::panic("Tried to set file size of file without file descriptor");
}
}
void Kernel::getFileSize(u32 messagePointer, Handle fileHandle) {
logFileIO("Getting size of file %X\n", fileHandle);
const auto p = getObject(fileHandle, KernelObjectType::File);
if (p == nullptr) [[unlikely]] {
Helpers::panic("Called GetFileSize on non-existent file");
}
FileSession* file = p->getData<FileSession>();
if (!file->isOpen) {
Helpers::panic("Tried to get size of closed file");
}
if (file->fd) {
IOFile f(file->fd);
std::optional<u64> size = f.size();
if (size.has_value()) {
mem.write32(messagePointer + 4, Result::Success);
mem.write64(messagePointer + 8, size.value());
} else {
Helpers::panic("FileOp::GetFileSize failed");
}
} else {
Helpers::panic("Tried to get file size of file without file descriptor");
}
}
void Kernel::openLinkFile(u32 messagePointer, Handle fileHandle) {
logFileIO("Open link file (clone) of file %X\n", fileHandle);
const auto p = getObject(fileHandle, KernelObjectType::File);
if (p == nullptr) [[unlikely]] {
Helpers::panic("Called GetFileSize on non-existent file");
}
FileSession* file = p->getData<FileSession>();
if (!file->isOpen) {
Helpers::panic("Tried to clone closed file");
}
// Make clone object
auto handle = makeObject(KernelObjectType::File);
auto& cloneFile = getObjects()[handle];
// Make a clone of the file by copying the archive/archive path/file path/file descriptor/etc of the original file
// TODO: Maybe we should duplicate the file handle instead of copying. This way their offsets will be separate
// However we do seek properly on every file access so this shouldn't matter
cloneFile.data = new FileSession(*file);
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 12, handle);
}
void Kernel::setFilePriority(u32 messagePointer, Handle fileHandle) {
const u32 priority = mem.read32(messagePointer + 4);
logFileIO("Setting priority of file %X to %d\n", fileHandle, priority);
const auto p = getObject(fileHandle, KernelObjectType::File);
if (p == nullptr) [[unlikely]] {
Helpers::panic("Called GetFileSize on non-existent file");
}
FileSession* file = p->getData<FileSession>();
if (!file->isOpen) {
Helpers::panic("Tried to clone closed file");
}
file->priority = priority;
mem.write32(messagePointer + 4, Result::Success);
}

View file

@ -0,0 +1,70 @@
#include <cstring>
#include "arm_defs.hpp"
#include "kernel.hpp"
/*
This file sets up an idle thread that's meant to run when no other OS thread can run.
It simply idles and constantly yields to check if there's any other thread that can run
The code for our idle thread looks like this
idle_thread_main:
mov r0, #4096 @ Loop counter
.loop:
nop; nop; nop; nop @ NOP 4 times to waste some cycles
subs r0, #1 @ Decrement counter by 1, go back to looping if loop counter != 0
bne .loop
// Sleep for 0 seconds with the SleepThread SVC, which just yields execution
mov r0, #0
mov r1, #0
svc SleepThread
b idle_thread_main
*/
static constexpr u8 idleThreadCode[] = {
0x01, 0x0A, 0xA0, 0xE3, // mov r0, #4096
0x00, 0xF0, 0x20, 0xE3, 0x00, 0xF0, 0x20, 0xE3, 0x00, 0xF0, 0x20, 0xE3, 0x00, 0xF0, 0x20, 0xE3, // nop (4 times)
0x01, 0x00, 0x50, 0xE2, // subs r0, #1
0xF9, 0xFF, 0xFF, 0x1A, // bne loop
0x00, 0x00, 0xA0, 0xE3, // mov r0, #0
0x00, 0x10, 0xA0, 0xE3, // mov r1, #0
0x0A, 0x00, 0x00, 0xEF, // svc SleepThread
0xF4, 0xFF, 0xFF, 0xEA // b idle_thread_main
};
// Set up an idle thread to run when no thread is able to run
void Kernel::setupIdleThread() {
Thread& t = threads[idleThreadIndex];
constexpr u32 codeAddress = 0xBFC00000;
// Reserve some memory for the idle thread's code. We map this memory to vaddr BFC00000 which is not userland-accessible
// We only allocate 4KB (1 page) because our idle code is pretty small
const u32 fcramIndex = mem.allocateSysMemory(Memory::pageSize);
auto vaddr = mem.allocateMemory(codeAddress, fcramIndex, Memory::pageSize, true, true, false, true, false, true);
if (!vaddr.has_value() || vaddr.value() != codeAddress) {
Helpers::panic("Failed to setup idle thread");
}
// Copy idle thread code to the allocated FCRAM
std::memcpy(&mem.getFCRAM()[fcramIndex], idleThreadCode, sizeof(idleThreadCode));
t.entrypoint = codeAddress;
t.tlsBase = 0;
t.gprs[13] = 0; // Set SP & LR to 0 just in case. The idle thread should never access memory, but let's be safe
t.gprs[14] = 0;
t.gprs[15] = codeAddress;
t.cpsr = CPSR::UserMode;
t.fpscr = FPSCR::ThreadDefault;
// Our idle thread should have as low of a priority as possible, because, well, it's an idle thread.
// We handle this by giving it a priority of 0xff, which is lower than is actually allowed for user threads
// (High priority value = low priority)
t.priority = 0xff;
t.status = ThreadStatus::Ready;
// Add idle thread to the list of thread indices
threadIndices.push_back(idleThreadIndex);
sortThreads();
}

View file

@ -10,9 +10,16 @@ Kernel::Kernel(CPU& cpu, Memory& mem, GPU& gpu)
threadIndices.reserve(appResourceLimits.maxThreads);
for (int i = 0; i < threads.size(); i++) {
threads[i].index = i;
threads[i].tlsBase = VirtualAddrs::TLSBase + i * VirtualAddrs::TLSSize;
threads[i].status = ThreadStatus::Dead;
Thread& t = threads[i];
t.index = i;
t.tlsBase = VirtualAddrs::TLSBase + i * VirtualAddrs::TLSSize;
t.status = ThreadStatus::Dead;
t.waitList.clear();
t.waitList.reserve(10); // Reserve some space for the wait list to avoid further memory allocs later
// The state below isn't necessary to initialize but we do it anyways out of caution
t.outPointer = 0;
t.waitAll = false;
}
setVersion(1, 69);
@ -23,14 +30,18 @@ void Kernel::serviceSVC(u32 svc) {
case 0x01: controlMemory(); break;
case 0x02: queryMemory(); break;
case 0x08: createThread(); break;
case 0x09: exitThread(); break;
case 0x0A: svcSleepThread(); break;
case 0x0B: getThreadPriority(); break;
case 0x0C: setThreadPriority(); break;
case 0x13: createMutex(); break;
case 0x14: releaseMutex(); break;
case 0x17: createEvent(); break;
case 0x18: signalEvent(); break;
case 0x19: clearEvent(); break;
case 0x13: svcCreateMutex(); break;
case 0x14: svcReleaseMutex(); break;
case 0x15: svcCreateSemaphore(); break;
case 0x16: svcReleaseSemaphore(); break;
case 0x17: svcCreateEvent(); break;
case 0x18: svcSignalEvent(); break;
case 0x19: svcClearEvent(); break;
case 0x1E: createMemoryBlock(); break;
case 0x1F: mapMemoryBlock(); break;
case 0x21: createAddressArbiter(); break;
case 0x22: arbitrateAddress(); break;
@ -98,9 +109,12 @@ void Kernel::reset() {
handleCounter = 0;
arbiterCount = 0;
threadCount = 0;
aliveThreadCount = 0;
for (auto& t : threads) {
t.status = ThreadStatus::Dead;
t.waitList.clear();
t.threadsWaitingForTermination = 0; // No threads are waiting for this thread to terminate cause it's dead
}
for (auto& object : objects) {
@ -120,6 +134,7 @@ void Kernel::reset() {
// which is thankfully not used. Maybe we should prevent this
mainThread = makeThread(0, VirtualAddrs::StackTop, 0x30, -2, 0, ThreadStatus::Running);
currentThreadIndex = 0;
setupIdleThread();
// Create some of the OS ports
srvHandle = makePort("srv:"); // Service manager port
@ -184,6 +199,13 @@ void Kernel::getProcessInfo() {
}
switch (type) {
// According to 3DBrew: Amount of private (code, data, heap) memory used by the process + total supervisor-mode
// stack size + page-rounded size of the external handle table
case 2:
regs[1] = mem.getUsedUserMem();
regs[2] = 0;
break;
case 20: // Returns 0x20000000 - <linear memory base vaddr for process>
regs[1] = PhysicalAddrs::FCRAM - mem.getLinearHeapVaddr();
regs[2] = 0;
@ -196,7 +218,7 @@ void Kernel::getProcessInfo() {
regs[0] = SVCResult::Success;
}
// Result GetThreadId(u32* threadId, Handle thread)
// Result DuplicateHandle(Handle* out, Handle original)
void Kernel::duplicateHandle() {
Handle original = regs[1];
logSVC("DuplicateHandle(handle = %X)\n", original);

View file

@ -1,4 +1,5 @@
#include "kernel.hpp"
#include "services/shared_font.hpp"
namespace Operation {
enum : u32 {
@ -15,6 +16,21 @@ namespace Operation {
};
}
namespace MemoryPermissions {
enum : u32 {
None = 0, // ---
Read = 1, // R--
Write = 2, // -W-
ReadWrite = 3, // RW-
Execute = 4, // --X
ReadExecute = 5, // R-X
WriteExecute = 6, // -WX
ReadWriteExecute = 7, // RWX
DontCare = 0x10000000
};
}
// Returns whether "value" is aligned to a page boundary (Ie a boundary of 4096 bytes)
static constexpr bool isAligned(u32 value) {
return (value & 0xFFF) == 0;
@ -31,8 +47,8 @@ void Kernel::controlMemory() {
u32 size = regs[3];
u32 perms = regs[4];
if (perms == 0x10000000) {
perms = 3; // We make "don't care" equivalent to read-write
if (perms == MemoryPermissions::DontCare) {
perms = MemoryPermissions::ReadWrite; // We make "don't care" equivalent to read-write
Helpers::panic("Unimplemented allocation permission: DONTCARE");
}
@ -63,6 +79,14 @@ void Kernel::controlMemory() {
break;
}
case Operation::Map:
mem.mirrorMapping(addr0, addr1, size);
break;
case Operation::Protect:
Helpers::warn("Ignoring mprotect! Hope nothing goes wrong but if the game accesses invalid memory or crashes then we prolly need to implement this\n");
break;
default: Helpers::panic("ControlMemory: unknown operation %X\n", operation);
}
@ -89,7 +113,7 @@ void Kernel::queryMemory() {
// Result MapMemoryBlock(Handle memblock, u32 addr, MemoryPermission myPermissions, MemoryPermission otherPermission)
void Kernel::mapMemoryBlock() {
const Handle block = regs[0];
const u32 addr = regs[1];
u32 addr = regs[1];
const u32 myPerms = regs[2];
const u32 otherPerms = regs[3];
logSVC("MapMemoryBlock(block = %X, addr = %08X, myPerms = %X, otherPerms = %X\n", block, addr, myPerms, otherPerms);
@ -99,19 +123,82 @@ void Kernel::mapMemoryBlock() {
}
if (KernelHandles::isSharedMemHandle(block)) {
if (block == KernelHandles::FontSharedMemHandle && addr == 0) addr = 0x18000000;
u8* ptr = mem.mapSharedMemory(block, addr, myPerms, otherPerms); // Map shared memory block
// Pass pointer to shared memory to the appropriate service
if (block == KernelHandles::HIDSharedMemHandle) {
serviceManager.setHIDSharedMem(ptr);
} else if (block == KernelHandles::GSPSharedMemHandle) {
serviceManager.setGSPSharedMem(ptr);
} else {
Helpers::panic("Mapping unknown shared memory block: %X", block);
switch (block) {
case KernelHandles::HIDSharedMemHandle:
serviceManager.setHIDSharedMem(ptr);
break;
case KernelHandles::GSPSharedMemHandle:
serviceManager.setGSPSharedMem(ptr);
break;
case KernelHandles::FontSharedMemHandle:
std::memcpy(ptr, _shared_font_bin, _shared_font_len);
break;
default: Helpers::panic("Mapping unknown shared memory block: %X", block);
}
} else {
Helpers::panic("MapMemoryBlock where the handle does not refer to GSP memory");
Helpers::panic("MapMemoryBlock where the handle does not refer to a known piece of kernel shared mem");
}
regs[0] = SVCResult::Success;
}
Handle Kernel::makeMemoryBlock(u32 addr, u32 size, u32 myPermission, u32 otherPermission) {
Handle ret = makeObject(KernelObjectType::MemoryBlock);
objects[ret].data = new MemoryBlock(addr, size, myPermission, otherPermission);
return ret;
}
void Kernel::createMemoryBlock() {
const u32 addr = regs[1];
const u32 size = regs[2];
u32 myPermission = regs[3];
u32 otherPermission = mem.read32(regs[13] + 4); // This is placed on the stack rather than r4
logSVC("CreateMemoryBlock (addr = %08X, size = %08X, myPermission = %d, otherPermission = %d)\n", addr, size, myPermission, otherPermission);
// Returns whether a permission is valid
auto isPermValid = [](u32 permission) {
switch (permission) {
case MemoryPermissions::None:
case MemoryPermissions::Read:
case MemoryPermissions::Write:
case MemoryPermissions::ReadWrite:
case MemoryPermissions::DontCare:
return true;
default: // Permissions with the executable flag enabled or invalid permissions are not allowed
return false;
}
};
// Throw error if the size of the shared memory block is not aligned to page boundary
if (!isAligned(size)) {
regs[0] = SVCResult::UnalignedSize;
return;
}
// Throw error if one of the permissions is not valid
if (!isPermValid(myPermission) || !isPermValid(otherPermission)) {
regs[0] = SVCResult::InvalidCombination;
return;
}
// TODO: The address needs to be in a specific range otherwise it throws an invalid address error
if (addr == 0)
Helpers::panic("CreateMemoryBlock: Tried to use addr = 0");
// Implement "Don't care" permission as RW
if (myPermission == MemoryPermissions::DontCare) myPermission = MemoryPermissions::ReadWrite;
if (otherPermission == MemoryPermissions::DontCare) otherPermission = MemoryPermissions::ReadWrite;
regs[0] = SVCResult::Success;
regs[1] = makeMemoryBlock(addr, size, myPermission, otherPermission);
}

View file

@ -78,16 +78,26 @@ void Kernel::sendSyncRequest() {
// The sync request is being sent at a service rather than whatever port, so have the service manager intercept it
if (KernelHandles::isServiceHandle(handle)) {
serviceManager.sendCommandToService(messagePointer, handle);
// The service call might cause a reschedule and change threads. Hence, set r0 before executing the service call
// Because if the service call goes first, we might corrupt the new thread's r0!!
regs[0] = SVCResult::Success;
serviceManager.sendCommandToService(messagePointer, handle);
return;
}
// Check if our sync request is targetting a file instead of a service
bool isFileOperation = getObject(handle, KernelObjectType::File) != nullptr;
if (isFileOperation) {
regs[0] = SVCResult::Success; // r0 goes first here too
handleFileOperation(messagePointer, handle);
regs[0] = SVCResult::Success;
return;
}
// Check if our sync request is targetting a directory instead of a service
bool isDirectoryOperation = getObject(handle, KernelObjectType::Directory) != nullptr;
if (isDirectoryOperation) {
regs[0] = SVCResult::Success; // r0 goes first here too
handleDirectoryOperation(messagePointer, handle);
return;
}
@ -103,13 +113,13 @@ void Kernel::sendSyncRequest() {
const Handle portHandle = sessionData->portHandle;
if (portHandle == srvHandle) { // Special-case SendSyncRequest targetting the "srv: port"
regs[0] = SVCResult::Success;
serviceManager.handleSyncRequest(messagePointer);
} else if (portHandle == errorPortHandle) { // Special-case "err:f" for juicy logs too
regs[0] = SVCResult::Success;
handleErrorSyncRequest(messagePointer);
} else {
const auto portData = objects[portHandle].getData<Port>();
Helpers::panic("SendSyncRequest targetting port %s\n", portData->name);
}
regs[0] = SVCResult::Success;
}

View file

@ -1,3 +1,4 @@
#include <cassert>
#include <cstring>
#include "kernel.hpp"
#include "arm_defs.hpp"
@ -11,7 +12,7 @@ void Kernel::switchThread(int newThreadIndex) {
auto& oldThread = threads[currentThreadIndex];
auto& newThread = threads[newThreadIndex];
newThread.status = ThreadStatus::Running;
printf("Switching from thread %d to %d\n", currentThreadIndex, newThreadIndex);
logThread("Switching from thread %d to %d\n", currentThreadIndex, newThreadIndex);
// Bail early if the new thread is actually the old thread
if (currentThreadIndex == newThreadIndex) [[unlikely]] {
@ -46,12 +47,14 @@ void Kernel::sortThreads() {
bool Kernel::canThreadRun(const Thread& t) {
if (t.status == ThreadStatus::Ready) {
return true;
} else if (t.status == ThreadStatus::WaitSleep) {
} else if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1
|| t.status == ThreadStatus::WaitSyncAny || t.status == ThreadStatus::WaitSyncAll) {
const u64 elapsedTicks = cpu.getTicks() - t.sleepTick;
constexpr double ticksPerSec = double(CPU::ticksPerSec);
constexpr double nsPerTick = ticksPerSec / 1000000000.0;
// TODO: Set r0 to the correct error code on timeout for WaitSync{1/Any/All}
const s64 elapsedNs = s64(double(elapsedTicks) * nsPerTick);
return elapsedNs >= t.waitingNanoseconds;
}
@ -70,8 +73,6 @@ std::optional<int> Kernel::getNextThread() {
if (canThreadRun(t)) {
return index;
}
// TODO: Check timeouts here
}
// No thread was found
@ -82,7 +83,16 @@ void Kernel::switchToNextThread() {
std::optional<int> newThreadIndex = getNextThread();
if (!newThreadIndex.has_value()) {
Helpers::panic("Kernel tried to switch to the next thread but none found");
log("Kernel tried to switch to the next thread but none found. Switching to random thread\n");
assert(aliveThreadCount != 0);
Helpers::panic("rpog");
int index;
do {
index = rand() % threadCount;
} while (threads[index].status == ThreadStatus::Dead); // TODO: Pray this doesn't hang
switchThread(index);
} else {
switchThread(newThreadIndex.value());
}
@ -92,7 +102,7 @@ void Kernel::switchToNextThread() {
void Kernel::rescheduleThreads() {
std::optional<int> newThreadIndex = getNextThread();
if (newThreadIndex.has_value()) {
if (newThreadIndex.has_value() && newThreadIndex.value() != currentThreadIndex) {
threads[currentThreadIndex].status = ThreadStatus::Ready;
switchThread(newThreadIndex.value());
}
@ -100,12 +110,25 @@ 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) {
if (threadCount >= appResourceLimits.maxThreads) {
Helpers::panic("Overflowed the number of threads");
}
threadIndices.push_back(threadCount);
int index; // Index of the created thread in the threads array
Thread& t = threads[threadCount++]; // Reference to thread data
if (threadCount < appResourceLimits.maxThreads) [[likely]] { // If we have not yet created over too many threads
index = threadCount++;
} else if (aliveThreadCount < appResourceLimits.maxThreads) { // If we have created many threads but at least one is dead & reusable
for (int i = 0; i < threads.size(); i++) {
if (threads[i].status == ThreadStatus::Dead) {
index = i;
break;
}
}
} else { // There is no thread we can use, we're screwed
Helpers::panic("Overflowed thread count!!");
}
aliveThreadCount++;
threadIndices.push_back(index);
Thread& t = threads[index]; // Reference to thread data
Handle ret = makeObject(KernelObjectType::Thread);
objects[ret].data = &t;
@ -127,6 +150,7 @@ Handle Kernel::makeThread(u32 entrypoint, u32 initialSP, u32 priority, s32 id, u
t.status = status;
t.handle = ret;
t.waitingAddress = 0;
t.threadsWaitingForTermination = 0; // Thread just spawned, no other threads waiting for it to terminate
t.cpsr = CPSR::UserMode | (isThumb ? CPSR::Thumb : 0);
t.fpscr = FPSCR::ThreadDefault;
@ -137,6 +161,47 @@ Handle Kernel::makeThread(u32 entrypoint, u32 initialSP, u32 priority, s32 id, u
return ret;
}
Handle Kernel::makeMutex(bool locked) {
Handle ret = makeObject(KernelObjectType::Mutex);
objects[ret].data = new Mutex(locked, ret);
// If the mutex is initially locked, store the index of the thread that owns it and set lock count to 1
if (locked) {
Mutex* moo = objects[ret].getData<Mutex>();
moo->ownerThread = currentThreadIndex;
}
return ret;
}
void Kernel::releaseMutex(Mutex* moo) {
// TODO: Assert lockCount > 0 before release, maybe. The SVC should be safe at least.
moo->lockCount--; // Decrement lock count
// If the lock count reached 0 then the thread no longer owns the mootex and it can be given to a new one
if (moo->lockCount == 0) {
moo->locked = false;
if (moo->waitlist != 0) {
int index = wakeupOneThread(moo->waitlist, moo->handle); // Wake up one thread and get its index
moo->waitlist ^= (1ull << index); // Remove thread from waitlist
// Have new thread acquire mutex
moo->locked = true;
moo->lockCount = 1;
moo->ownerThread = index;
}
rescheduleThreads();
}
}
Handle Kernel::makeSemaphore(u32 initialCount, u32 maximumCount) {
Handle ret = makeObject(KernelObjectType::Semaphore);
objects[ret].data = new Semaphore(initialCount, maximumCount);
return ret;
}
void Kernel::sleepThreadOnArbiter(u32 waitingAddress) {
Thread& t = threads[currentThreadIndex];
t.status = ThreadStatus::WaitArbiter;
@ -145,14 +210,138 @@ void Kernel::sleepThreadOnArbiter(u32 waitingAddress) {
switchToNextThread();
}
// Acquires an object that is **ready to be acquired** without waiting on it
void Kernel::acquireSyncObject(KernelObject* object, const Thread& thread) {
switch (object->type) {
case KernelObjectType::Event: {
Event* e = object->getData<Event>();
if (e->resetType == ResetType::OneShot) { // One-shot events automatically get cleared after waking up a thread
e->fired = false;
}
break;
}
case KernelObjectType::Mutex: {
Mutex* moo = object->getData<Mutex>();
moo->locked = true; // Set locked to true, whether it's false or not because who cares
// Increment lock count by 1. If a thread acquires a mootex multiple times, it needs to release it until count == 0
// For the mootex to be free.
moo->lockCount++;
moo->ownerThread = thread.index;
break;
}
case KernelObjectType::Semaphore: {
Semaphore* s = object->getData<Semaphore>();
if (s->availableCount <= 0) [[unlikely]] // This should be unreachable but let's check anyways
Helpers::panic("Tried to acquire unacquirable semaphore");
s->availableCount -= 1;
break;
}
case KernelObjectType::Thread:
break;
default: Helpers::panic("Acquiring unimplemented sync object %s", object->getTypeName());
}
}
// Wake up one of the threads in the waitlist (the one with highest prio) and return its index
// Must not be called with an empty waitlist
int Kernel::wakeupOneThread(u64 waitlist, Handle handle) {
if (waitlist == 0) [[unlikely]]
Helpers::panic("[Internal error] It shouldn't be possible to call wakeupOneThread when there's 0 threads waiting!");
// Find the waiting thread with the highest priority.
// We do this by first picking the first thread in the waitlist, then checking each other thread and comparing priority
int threadIndex = std::countr_zero(waitlist); // Index of first thread
int maxPriority = threads[threadIndex].priority; // Set initial max prio to the prio of the first thread
waitlist ^= (1ull << threadIndex); // Remove thread from the waitlist
while (waitlist != 0) {
int newThread = std::countr_zero(waitlist); // Get new thread and evaluate whether it has a higher priority
if (threads[newThread].priority < maxPriority) { // Low priority value means high priority
threadIndex = newThread;
maxPriority = threads[newThread].priority;
}
waitlist ^= (1ull << threadIndex); // Remove thread from waitlist
}
Thread& t = threads[threadIndex];
switch (t.status) {
case ThreadStatus::WaitSync1:
t.status = ThreadStatus::Ready;
t.gprs[0] = SVCResult::Success; // The thread did not timeout, so write success to r0
break;
case ThreadStatus::WaitSyncAny:
t.status = ThreadStatus::Ready;
t.gprs[0] = SVCResult::Success; // The thread did not timeout, so write success to r0
// Get the index of the event in the object's waitlist, write it to r1
for (size_t i = 0; i < t.waitList.size(); i++) {
if (t.waitList[i] == handle) {
t.gprs[1] = i;
break;
}
}
break;
case ThreadStatus::WaitSyncAll:
Helpers::panic("WakeupOneThread: Thread on WaitSyncAll");
break;
}
return threadIndex;
}
// Wake up every single thread in the waitlist using a bit scanning algorithm
void Kernel::wakeupAllThreads(u64 waitlist, Handle handle) {
while (waitlist != 0) {
const uint index = std::countr_zero(waitlist); // Get one of the set bits to see which thread is waiting
waitlist ^= (1ull << index); // Remove thread from waitlist by toggling its bit
// Get the thread we'll be signalling
Thread& t = threads[index];
switch (t.status) {
case ThreadStatus::WaitSync1:
t.status = ThreadStatus::Ready;
t.gprs[0] = SVCResult::Success; // The thread did not timeout, so write success to r0
break;
case ThreadStatus::WaitSyncAny:
t.status = ThreadStatus::Ready;
t.gprs[0] = SVCResult::Success; // The thread did not timeout, so write success to r0
// Get the index of the event in the object's waitlist, write it to r1
for (size_t i = 0; i < t.waitList.size(); i++) {
if (t.waitList[i] == handle) {
t.gprs[1] = i;
break;
}
}
break;
case ThreadStatus::WaitSyncAll:
Helpers::panic("WakeupAllThreads: Thread on WaitSyncAll");
break;
}
}
}
// Make a thread sleep for a certain amount of nanoseconds at minimum
void Kernel::sleepThread(s64 ns) {
if (ns < 0) {
Helpers::panic("Sleeping a thread for a negative amount of ns");
} else if (ns == 0) { // Used when we want to force a thread switch
int curr = currentThreadIndex;
switchToNextThread(); // Mark thread as ready after switching, to avoid switching to the same thread
threads[curr].status = ThreadStatus::Ready;
std::optional<int> newThreadIndex = getNextThread();
// If there's no other thread waiting, don't bother yielding
if (newThreadIndex.has_value()) {
threads[currentThreadIndex].status = ThreadStatus::Ready;
switchThread(newThreadIndex.value());
}
} else { // If we're sleeping for > 0 ns
Thread& t = threads[currentThreadIndex];
t.status = ThreadStatus::WaitSleep;
@ -182,15 +371,16 @@ void Kernel::createThread() {
regs[0] = SVCResult::Success;
regs[1] = makeThread(entrypoint, initialSP, priority, id, arg, ThreadStatus::Ready);
rescheduleThreads();
}
// void SleepThread(s64 nanoseconds)
void Kernel::svcSleepThread() {
const s64 ns = s64(u64(regs[0]) | (u64(regs[1]) << 32));
logSVC("SleepThread(ns = %lld)\n", ns);
//logSVC("SleepThread(ns = %lld)\n", ns);
sleepThread(ns);
regs[0] = SVCResult::Success;
sleepThread(ns);
}
void Kernel::getThreadID() {
@ -215,7 +405,7 @@ void Kernel::getThreadID() {
void Kernel::getThreadPriority() {
const Handle handle = regs[1];
log("GetThreadPriority (handle = %X)\n", handle);
logSVC("GetThreadPriority (handle = %X)\n", handle);
if (handle == KernelHandles::CurrentThread) {
regs[0] = SVCResult::Success;
@ -234,7 +424,7 @@ void Kernel::getThreadPriority() {
void Kernel::setThreadPriority() {
const Handle handle = regs[0];
const u32 priority = regs[1];
log("SetThreadPriority (handle = %X, priority = %X)\n", handle, priority);
logSVC("SetThreadPriority (handle = %X, priority = %X)\n", handle, priority);
if (priority > 0x3F) {
regs[0] = SVCResult::BadThreadPriority;
@ -254,20 +444,142 @@ void Kernel::setThreadPriority() {
object->getData<Thread>()->priority = priority;
}
}
sortThreads();
rescheduleThreads();
}
void Kernel::createMutex() {
bool locked = regs[1] != 0;
Helpers::panic("CreateMutex (initially locked: %s)\n", locked ? "yes" : "no");
void Kernel::exitThread() {
logSVC("ExitThread\n");
regs[0] = SVCResult::Success;
// Remove the index of this thread from the thread indices vector
for (int i = 0; i < threadIndices.size(); i++) {
if (threadIndices[i] == currentThreadIndex)
threadIndices.erase(threadIndices.begin() + i);
}
Thread& t = threads[currentThreadIndex];
t.status = ThreadStatus::Dead;
aliveThreadCount--;
// Check if any threads are sleeping, waiting for this thread to terminate, and wake them up
// This is how thread joining is implemented in the kernel - you wait on a thread, like any other wait object.
if (t.threadsWaitingForTermination != 0) {
// TODO: Handle cloned handles? Not sure how those interact with wait object signalling
wakeupAllThreads(t.threadsWaitingForTermination, t.handle);
t.threadsWaitingForTermination = 0; // No other threads waiting
}
switchToNextThread();
}
void Kernel::releaseMutex() {
const Handle handle = regs[0];
void Kernel::svcCreateMutex() {
bool locked = regs[1] != 0;
logSVC("CreateMutex (locked = %s)\n", locked ? "yes" : "no");
logSVC("ReleaseMutex (handle = %x) (STUBBED)\n", handle);
regs[0] = SVCResult::Success;
regs[1] = makeMutex(locked);
}
void Kernel::svcReleaseMutex() {
const Handle handle = regs[0];
logSVC("ReleaseMutex (handle = %x)\n", handle);
const auto object = getObject(handle, KernelObjectType::Mutex);
if (object == nullptr) [[unlikely]] {
Helpers::panic("Tried to release non-existent mutex");
regs[0] = SVCResult::BadHandle;
return;
}
Mutex* moo = object->getData<Mutex>();
// A thread can't release a mutex it does not own
if (!moo->locked || moo->ownerThread != currentThreadIndex) {
regs[0] = SVCResult::InvalidMutexRelease;
return;
}
regs[0] = SVCResult::Success;
releaseMutex(moo);
}
void Kernel::svcCreateSemaphore() {
s32 initialCount = static_cast<s32>(regs[1]);
s32 maxCount = static_cast<s32>(regs[2]);
logSVC("CreateSemaphore (initial count = %d, max count = %d)\n", initialCount, maxCount);
if (initialCount > maxCount)
Helpers::panic("CreateSemaphore: Initial count higher than max count");
if (initialCount < 0 || maxCount < 0)
Helpers::panic("CreateSemaphore: Negative count value");
regs[0] = SVCResult::Success;
regs[1] = makeSemaphore(initialCount, maxCount);
}
void Kernel::svcReleaseSemaphore() {
const Handle handle = regs[1];
const s32 releaseCount = static_cast<s32>(regs[2]);
logSVC("ReleaseSemaphore (handle = %X, release count = %d)\n", handle, releaseCount);
const auto object = getObject(handle, KernelObjectType::Semaphore);
if (object == nullptr) [[unlikely]] {
Helpers::panic("Tried to release non-existent semaphore");
regs[0] = SVCResult::BadHandle;
return;
}
if (releaseCount < 0)
Helpers::panic("ReleaseSemaphore: Negative count");
Semaphore* s = object->getData<Semaphore>();
if (s->maximumCount - s->availableCount < releaseCount)
Helpers::panic("ReleaseSemaphore: Release count too high");
// Write success and old available count to r0 and r1 respectively
regs[0] = SVCResult::Success;
regs[1] = s->availableCount;
// Bump available count
s->availableCount += releaseCount;
// Wake up threads one by one until the available count hits 0 or we run out of threads to wake up
while (s->availableCount > 0 && s->waitlist != 0) {
int index = wakeupOneThread(s->waitlist, handle); // Wake up highest priority thread
s->waitlist ^= (1ull << index); // Remove thread from waitlist
s->availableCount--; // Decrement available count
}
}
// Returns whether an object is waitable or not
// The KernelObject type enum is arranged in a specific order in kernel_types.hpp so this
// can simply compile to a fast sub+cmp+set despite looking slow
bool Kernel::isWaitable(const KernelObject* object) {
auto type = object->type;
using enum KernelObjectType;
return type == Event || type == Mutex || type == Port || type == Semaphore || type == Timer || type == Thread;
}
// Returns whether we should wait on a sync object or not
bool Kernel::shouldWaitOnObject(KernelObject* object) {
switch (object->type) {
case KernelObjectType::Event: // We should wait on an event only if it has not been signalled
return !object->getData<Event>()->fired;
case KernelObjectType::Mutex: {
Mutex* moo = object->getData<Mutex>(); // mooooooooooo
return moo->locked && moo->ownerThread != currentThreadIndex; // If the current thread owns the moo then no reason to wait
}
case KernelObjectType::Thread: // Waiting on a thread waits until it's dead. If it's dead then no need to wait
return object->getData<Thread>()->status != ThreadStatus::Dead;
case KernelObjectType::Semaphore: // Wait if the semaphore count <= 0
return object->getData<Semaphore>()->availableCount <= 0;
default:
Helpers::panic("Not sure whether to wait on object (type: %s)", object->getTypeName());
return true;
}
}

80
src/core/loader/lz77.cpp Normal file
View file

@ -0,0 +1,80 @@
#pragma once
#include <algorithm>
#include <cstring>
#include "loader/lz77.hpp"
// The difference in size between the compressed and decompressed file is stored
// As a footer in the compressed file. To get the decompressed size, we extract the diff
// And add it to the compressed size
u32 CartLZ77::decompressedSize(const u8* buffer, u32 compressedSize) {
u32 sizeDiff;
std::memcpy(&sizeDiff, buffer + compressedSize - 4, sizeof(u32));
return sizeDiff + compressedSize;
}
bool CartLZ77::decompress(std::vector<u8>& output, const std::vector<u8>& input) {
u32 sizeCompressed = input.size() * sizeof(u8);
u32 sizeDecompressed = decompressedSize(input);
output.resize(sizeDecompressed);
const u8* compressed = (u8*)input.data();
const u8* footer = compressed + sizeCompressed - 8;
u32 bufferTopAndBottom;
std::memcpy(&bufferTopAndBottom, footer, sizeof(u32));
u32 out = sizeDecompressed; // TODO: Is this meant to be u32 or s32?
u32 index = sizeCompressed - ((bufferTopAndBottom >> 24) & 0xff);
u32 stopIndex = sizeCompressed - (bufferTopAndBottom & 0xffffff);
// Set all of the decompressed buffer to 0 and copy the compressed buffer to the start of it
std::fill(output.begin(), output.end(), 0);
std::copy(input.begin(), input.end(), output.begin());
while (index > stopIndex) {
u8 control = compressed[--index];
for (uint i = 0; i < 8; i++) {
if (index <= stopIndex)
break;
if (index <= 0)
break;
if (out <= 0)
break;
if (control & 0x80) {
// Check if compression is out of bounds
if (index < 2)
return false;
index -= 2;
u32 segmentOffset = compressed[index] | (compressed[index + 1] << 8);
u32 segment_size = ((segmentOffset >> 12) & 15) + 3;
segmentOffset &= 0x0FFF;
segmentOffset += 2;
// Check if compression is out of bounds
if (out < segment_size)
return false;
for (uint j = 0; j < segment_size; j++) {
// Check if compression is out of bounds
if (out + segmentOffset >= sizeDecompressed)
return false;
u8 data = output[out + segmentOffset];
output[--out] = data;
}
}
else {
// Check if compression is out of bounds
if (out < 1)
return false;
output[--out] = compressed[--index];
}
control <<= 1;
}
}
return true;
}

View file

@ -1,3 +1,4 @@
#include <cstring>
#include <vector>
#include "loader/lz77.hpp"
#include "loader/ncch.hpp"
@ -126,7 +127,7 @@ bool NCCH::loadFromHeader(u8* header, IOFile& file) {
}
if (stackSize != 0 && stackSize != VirtualAddrs::DefaultStackSize) {
Helpers::panic("Stack size != 0x4000");
Helpers::warn("Requested stack size is %08X bytes. Temporarily emulated as 0x4000 until adjustable sizes are added\n", stackSize);
}
if (encrypted) {

View file

@ -1,3 +1,4 @@
#include <cstring>
#include <optional>
#include "loader/ncsd.hpp"
#include "memory.hpp"
@ -74,9 +75,9 @@ std::optional<NCSD> Memory::loadNCSD(const std::filesystem::path& path) {
return std::nullopt;
}
printf("Text address = %08X, page count = %08X\n", cxi.text.address, cxi.text.size);
printf("Rodata address = %08X, page count = %08X\n", cxi.rodata.address, cxi.rodata.size);
printf("Data address = %08X, page count = %08X\n", cxi.data.address, cxi.data.size);
printf("Text address = %08X, size = %08X\n", cxi.text.address, cxi.text.size);
printf("Rodata address = %08X, size = %08X\n", cxi.rodata.address, cxi.rodata.size);
printf("Data address = %08X, size = %08X\n", cxi.data.address, cxi.data.size);
// Map code file to memory
auto& code = cxi.codeFile;

View file

@ -20,6 +20,7 @@ void Memory::reset() {
memoryInfo.clear();
usedFCRAMPages.reset();
usedUserMemory = 0_MB;
usedSystemMemory = 0_MB;
for (u32 i = 0; i < totalPageCount; i++) {
readTable[i] = 0;
@ -42,14 +43,7 @@ void Memory::reset() {
// Initialize shared memory blocks and reserve memory for them
for (auto& e : sharedMemBlocks) {
e.mapped = false;
std::optional<u32> possiblePaddr = findPaddr(e.size);
if (!possiblePaddr.has_value()) Helpers::panic("Failed to find paddr for shared memory block");
e.paddr = possiblePaddr.value();
if (!reserveMemory(e.paddr, e.size)) {
Helpers::panic("Failed to reserve memory for shared memory block");
}
e.paddr = allocateSysMemory(e.size);
}
// Map DSP RAM as R/W at [0x1FF00000, 0x1FF7FFFF]
@ -74,10 +68,15 @@ u8 Memory::read8(u32 vaddr) {
}
else {
switch (vaddr) {
case ConfigMem::BatteryState: return getBatteryState(true, true, BatteryLevel::FourBars);
case ConfigMem::EnvInfo: return envInfo;
case ConfigMem::HardwareType: return ConfigMem::HardwareCodes::Product;
case ConfigMem::KernelVersionMinor: return u8(kernelVersion & 0xff);
case ConfigMem::KernelVersionMajor: return u8(kernelVersion >> 8);
case ConfigMem::LedState3D: return 1; // Report the 3D LED as always off (non-zero) for now
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
default: Helpers::panic("Unimplemented 8-bit read, addr: %08X", vaddr);
}
}
@ -115,8 +114,14 @@ u32 Memory::read32(u32 vaddr) {
return 0; // Set to 0 by PTM
case ConfigMem::AppMemAlloc: return appResourceLimits.maxCommit;
case ConfigMem::SyscoreVer: return 2;
case 0x1FF81000: return 0; // TODO: Figure out what this config mem address does
default:
if (vaddr >= VirtualAddrs::VramStart && vaddr < VirtualAddrs::VramStart + VirtualAddrs::VramSize) {
Helpers::warn("VRAM read!\n");
return 0;
}
Helpers::panic("Unimplemented 32-bit read, addr: %08X", vaddr);
break;
}
@ -212,19 +217,20 @@ u32 Memory::getLinearHeapVaddr() {
}
std::optional<u32> Memory::allocateMemory(u32 vaddr, u32 paddr, u32 size, bool linear, bool r, bool w, bool x,
bool adjustAddrs) {
bool adjustAddrs, bool isMap) {
// Kernel-allocated memory & size must always be aligned to a page boundary
// Additionally assert we don't OoM and that we don't try to allocate physical FCRAM past what's available to userland
// If we're mapping there's no fear of OoM, because we're not really allocating memory, just binding vaddrs to specific paddrs
assert(isAligned(vaddr) && isAligned(paddr) && isAligned(size));
assert(size <= FCRAM_APPLICATION_SIZE);
assert(usedUserMemory + size <= FCRAM_APPLICATION_SIZE);
assert(paddr + size <= FCRAM_APPLICATION_SIZE);
assert(size <= FCRAM_APPLICATION_SIZE || isMap);
assert(usedUserMemory + size <= FCRAM_APPLICATION_SIZE || isMap);
assert(paddr + size <= FCRAM_APPLICATION_SIZE || isMap);
// Amount of available user FCRAM pages and FCRAM pages to allocate respectively
const u32 availablePageCount = (FCRAM_APPLICATION_SIZE - usedUserMemory) / pageSize;
const u32 neededPageCount = size / pageSize;
assert(availablePageCount >= neededPageCount);
assert(availablePageCount >= neededPageCount || isMap);
// If the paddr is 0, that means we need to select our own
// TODO: Fix. This method always tries to allocate blocks linearly.
@ -236,7 +242,7 @@ std::optional<u32> Memory::allocateMemory(u32 vaddr, u32 paddr, u32 size, bool l
Helpers::panic("Failed to find paddr");
paddr = newPaddr.value();
assert(paddr + size <= FCRAM_APPLICATION_SIZE);
assert(paddr + size <= FCRAM_APPLICATION_SIZE || isMap);
}
// If the vaddr is 0 that means we need to select our own
@ -254,7 +260,8 @@ std::optional<u32> Memory::allocateMemory(u32 vaddr, u32 paddr, u32 size, bool l
}
}
usedUserMemory += size;
if (!isMap)
usedUserMemory += size;
// Do linear mapping
u32 virtualPage = vaddr >> pageShift;
@ -310,23 +317,31 @@ std::optional<u32> Memory::findPaddr(u32 size) {
return std::nullopt;
}
bool Memory::reserveMemory(u32 paddr, u32 size) {
if (!isAligned(paddr) || !isAligned(size)) {
Helpers::panic("Memory::reserveMemory: Physical address or size is not page aligned. Paddr: %08X, size: %08X", paddr, size);
; }
u32 Memory::allocateSysMemory(u32 size) {
// Should never be triggered, only here as a sanity check
if (!isAligned(size)) {
Helpers::panic("Memory::allocateSysMemory: Size is not page aligned (val = %08X)", size);
}
const u32 pageCount = size / pageSize; // Number of pages we need to reserve
const u32 startingPage = paddr / pageSize; // The first page of FCRAM we'll start allocating from
// We use a pretty dumb allocator for OS memory since this is not really accessible to the app and is only used internally
// It works by just allocating memory linearly, starting from index 0 of OS memory and going up
// This should also be unreachable in practice and exists as a sanity check
if (size > remainingSysFCRAM()) {
Helpers::panic("Memory::allocateSysMemory: Overflowed OS FCRAM");
}
const u32 pageCount = size / pageSize; // Number of pages that will be used up
const u32 startIndex = sysFCRAMIndex() + usedSystemMemory; // Starting FCRAM index
const u32 startingPage = startIndex / pageSize;
// Assert that all of the pages are not yet reserved. TODO: Smarter memory allocator
for (u32 i = 0; i < pageCount; i++) {
if (usedFCRAMPages[startingPage + i])
if (usedFCRAMPages[startingPage + i]) // Also a theoretically unreachable panic for safety
Helpers::panic("Memory::reserveMemory: Trying to reserve already reserved memory");
usedFCRAMPages[startingPage + i] = true;
}
usedUserMemory += size;
return true;
usedSystemMemory += size;
return startIndex;
}
// The way I understand how the kernel's QueryMemory is supposed to work is that you give it a vaddr
@ -363,12 +378,7 @@ u8* Memory::mapSharedMemory(Handle handle, u32 vaddr, u32 myPerms, u32 otherPerm
bool w = myPerms & 0b010;
bool x = myPerms & 0b100;
// This memory was not actually used, we just didn't want QueryMemory, getResourceLimitCurrentValues and such
// To report memory sizes wrongly. We subtract the size from the usedUserMemory size so
// allocateMemory won't break
usedUserMemory -= size;
const auto result = allocateMemory(vaddr, paddr, size, true, r, w, x);
const auto result = allocateMemory(vaddr, paddr, size, true, r, w, x, false, true);
e.mapped = true;
if (!result.has_value()) {
Helpers::panic("Memory::mapSharedMemory: Failed to map shared memory block");
@ -384,6 +394,24 @@ u8* Memory::mapSharedMemory(Handle handle, u32 vaddr, u32 myPerms, u32 otherPerm
return nullptr;
}
void Memory::mirrorMapping(u32 destAddress, u32 sourceAddress, u32 size) {
// Should theoretically be unreachable, only here for safety purposes
assert(isAligned(destAddress) && isAligned(sourceAddress) && isAligned(size));
const u32 pageCount = size / pageSize; // How many pages we need to mirror
for (u32 i = 0; i < pageCount; i++) {
// Redo the shift here to "properly" handle wrapping around the address space instead of reading OoB
const u32 sourcePage = sourceAddress / pageSize;
const u32 destPage = destAddress / pageSize;
readTable[destPage] = readTable[sourcePage];
writeTable[destPage] = writeTable[sourcePage];
sourceAddress += pageSize;
destAddress += pageSize;
}
}
// Get the number of ms since Jan 1 1900
u64 Memory::timeSince3DSEpoch() {
using namespace std::chrono;
@ -391,6 +419,6 @@ u64 Memory::timeSince3DSEpoch() {
// ms since Jan 1 1970
milliseconds ms = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
// ms between Jan 1 1900 and Jan 1 1970 (2208988800 seconds elapsed between the two)
const u64 offset = 2208988800ull * 1000;
constexpr u64 offset = 2208988800ull * 1000;
return ms.count() + offset;
}

View file

@ -0,0 +1,119 @@
#include <algorithm>
#include "colour.hpp"
#include "renderer_gl/renderer_gl.hpp"
#include "renderer_gl/textures.hpp"
static constexpr u32 signExtend3To32(u32 val) {
return (u32)(s32(val) << 29 >> 29);
}
u32 Texture::getTexelETC(bool hasAlpha, u32 u, u32 v, u32 width, const void* data) {
// Pixel offset of the 8x8 tile based on u, v and the width of the texture
u32 offs = ((u & ~7) * 8) + ((v & ~7) * width);
if (!hasAlpha)
offs >>= 1;
// In-tile offsets for u/v
u &= 7;
v &= 7;
// ETC1(A4) also subdivide the 8x8 tile to 4 4x4 tiles
// Each tile is 8 bytes for ETC1, but since ETC1A4 has 4 alpha bits per pixel, that becomes 16 bytes
const u32 subTileSize = hasAlpha ? 16 : 8;
const u32 subTileIndex = (u / 4) + 2 * (v / 4); // Which of the 4 subtiles is this texel in?
// In-subtile offsets for u/v
u &= 3;
v &= 3;
offs += subTileSize * subTileIndex;
u32 alpha;
const u8* tmp = static_cast<const u8*>(data) + offs; // Pointer to colour and alpha data as u8*
const u64* ptr = reinterpret_cast<const u64*>(tmp); // Cast to u64*
if (hasAlpha) {
// First 64 bits of the 4x4 subtile are alpha data
const u64 alphaData = *ptr++;
alpha = Colour::convert4To8Bit((alphaData >> (4 * (u * 4 + v))) & 0xf);
}
else {
alpha = 0xff; // ETC1 without alpha uses ff for every pixel
}
// Next 64 bits of the subtile are colour data
u64 colourData = *ptr;
return decodeETC(alpha, u, v, colourData);
}
u32 Texture::decodeETC(u32 alpha, u32 u, u32 v, u64 colourData) {
static constexpr u32 modifiers[8][2] = {
{ 2, 8 },
{ 5, 17 },
{ 9, 29 },
{ 13, 42 },
{ 18, 60 },
{ 24, 80 },
{ 33, 106 },
{ 47, 183 },
};
// Parse colour data for 4x4 block
const u32 subindices = colourData & 0xffff;
const u32 negationFlags = (colourData >> 16) & 0xffff;
const bool flip = (colourData >> 32) & 1;
const bool diffMode = (colourData >> 33) & 1;
// Note: index1 is indeed stored on the higher bits, with index2 in the lower bits
const u32 tableIndex1 = (colourData >> 37) & 7;
const u32 tableIndex2 = (colourData >> 34) & 7;
const u32 texelIndex = u * 4 + v; // Index of the texel in the block
if (flip)
std::swap(u, v);
s32 r, g, b;
if (diffMode) {
r = (colourData >> 59) & 0x1f;
g = (colourData >> 51) & 0x1f;
b = (colourData >> 43) & 0x1f;
if (u >= 2) {
r += signExtend3To32((colourData >> 56) & 0x7);
g += signExtend3To32((colourData >> 48) & 0x7);
b += signExtend3To32((colourData >> 40) & 0x7);
}
// Expand from 5 to 8 bits per channel
r = Colour::convert5To8Bit(r);
g = Colour::convert5To8Bit(g);
b = Colour::convert5To8Bit(b);
} else {
if (u < 2) {
r = (colourData >> 60) & 0xf;
g = (colourData >> 52) & 0xf;
b = (colourData >> 44) & 0xf;
} else {
r = (colourData >> 56) & 0xf;
g = (colourData >> 48) & 0xf;
b = (colourData >> 40) & 0xf;
}
// Expand from 4 to 8 bits per channel
r = Colour::convert4To8Bit(r);
g = Colour::convert4To8Bit(g);
b = Colour::convert4To8Bit(b);
}
const u32 index = (u < 2) ? tableIndex1 : tableIndex2;
s32 modifier = modifiers[index][(subindices >> texelIndex) & 1];
if (((negationFlags >> texelIndex) & 1) != 0) {
modifier = -modifier;
}
r = std::clamp(r + modifier, 0, 255);
g = std::clamp(g + modifier, 0, 255);
b = std::clamp(b + modifier, 0, 255);
return (alpha << 24) | (u32(b) << 16) | (u32(g) << 8) | u32(r);
}

View file

@ -0,0 +1,451 @@
#include "renderer_gl/renderer_gl.hpp"
#include "PICA/float_types.hpp"
#include "PICA/gpu.hpp"
#include "PICA/regs.hpp"
using namespace Floats;
// This is all hacked up to display our first triangle
const char* vertexShader = R"(
#version 410 core
layout (location = 0) in vec4 coords;
layout (location = 1) in vec4 vertexColour;
layout (location = 2) in vec2 inUVs_texture0;
out vec4 colour;
out vec2 tex0_UVs;
void main() {
gl_Position = coords;
colour = vertexColour;
// Flip y axis of UVs because OpenGL uses an inverted y for texture sampling compared to the PICA
tex0_UVs = vec2(inUVs_texture0.x, 1.0 - inUVs_texture0.y);
}
)";
const char* fragmentShader = R"(
#version 420 core
in vec4 colour;
in vec2 tex0_UVs;
out vec4 fragColour;
uniform uint u_alphaControl;
uniform uint u_textureConfig;
// Depth control uniforms
uniform float u_depthScale;
uniform float u_depthOffset;
uniform bool u_depthmapEnable;
uniform sampler2D u_tex0;
void main() {
if ((u_textureConfig & 1u) != 0) { // Render texture 0 if enabled
fragColour = texture(u_tex0, tex0_UVs);
} else {
fragColour = colour;
}
// Get original depth value by converting from [near, far] = [0, 1] to [-1, 1]
// We do this by converting to [0, 2] first and subtracting 1 to go to [-1, 1]
float z_over_w = gl_FragCoord.z * 2.0f - 1.0f;
float depth = z_over_w * u_depthScale + u_depthOffset;
if (!u_depthmapEnable) // Divide z by w if depthmap enable == 0 (ie using W-buffering)
depth /= gl_FragCoord.w;
// Write final fragment depth
gl_FragDepth = depth;
if ((u_alphaControl & 1u) != 0u) { // Check if alpha test is on
uint func = (u_alphaControl >> 4u) & 7u;
float reference = float((u_alphaControl >> 8u) & 0xffu) / 255.0;
float alpha = fragColour.a;
switch (func) {
case 0: discard; // Never pass alpha test
case 1: break; // Always pass alpha test
case 2: // Pass if equal
if (alpha != reference)
discard;
break;
case 3: // Pass if not equal
if (alpha == reference)
discard;
break;
case 4: // Pass if less than
if (alpha >= reference)
discard;
break;
case 5: // Pass if less than or equal
if (alpha > reference)
discard;
break;
case 6: // Pass if greater than
if (alpha <= reference)
discard;
break;
case 7: // Pass if greater than or equal
if (alpha < reference)
discard;
break;
}
}
}
)";
const char* displayVertexShader = R"(
#version 420 core
out vec2 UV;
void main() {
const vec4 positions[4] = vec4[](
vec4(-1.0, 1.0, 1.0, 1.0), // Top-left
vec4(1.0, 1.0, 1.0, 1.0), // Top-right
vec4(-1.0, -1.0, 1.0, 1.0), // Bottom-left
vec4(1.0, -1.0, 1.0, 1.0) // Bottom-right
);
// The 3DS displays both screens' framebuffer rotated 90 deg counter clockwise
// So we adjust our texcoords accordingly
const vec2 texcoords[4] = vec2[](
vec2(1.0, 1.0), // Top-right
vec2(1.0, 0.0), // Bottom-right
vec2(0.0, 1.0), // Top-left
vec2(0.0, 0.0) // Bottom-left
);
gl_Position = positions[gl_VertexID];
UV = texcoords[gl_VertexID];
}
)";
const char* displayFragmentShader = R"(
#version 420 core
in vec2 UV;
out vec4 FragColor;
uniform sampler2D u_texture;
void main() {
FragColor = texture(u_texture, UV);
}
)";
void Renderer::reset() {
depthBufferCache.reset();
colourBufferCache.reset();
textureCache.reset();
// Init the colour/depth buffer settings to some random defaults on reset
colourBufferLoc = 0;
colourBufferFormat = ColourBuffer::Formats::RGBA8;
depthBufferLoc = 0;
depthBufferFormat = DepthBuffer::Formats::Depth16;
if (triangleProgram.exists()) {
const auto oldProgram = OpenGL::getProgram();
triangleProgram.use();
oldAlphaControl = 0; // Default alpha control to 0
oldTexUnitConfig = 0; // Default tex unit config to 0
oldDepthScale = -1.0; // Default depth scale to -1.0, which is what games typically use
oldDepthOffset = 0.0; // Default depth offset to 0
oldDepthmapEnable = false; // Enable w buffering
glUniform1ui(alphaControlLoc, oldAlphaControl);
glUniform1ui(texUnitConfigLoc, oldTexUnitConfig);
glUniform1f(depthScaleLoc, oldDepthScale);
glUniform1f(depthOffsetLoc, oldDepthOffset);
glUniform1i(depthmapEnableLoc, oldDepthmapEnable);
glUseProgram(oldProgram); // Switch to old GL program
}
}
void Renderer::initGraphicsContext() {
OpenGL::Shader vert(vertexShader, OpenGL::Vertex);
OpenGL::Shader frag(fragmentShader, OpenGL::Fragment);
triangleProgram.create({ vert, frag });
triangleProgram.use();
alphaControlLoc = OpenGL::uniformLocation(triangleProgram, "u_alphaControl");
texUnitConfigLoc = OpenGL::uniformLocation(triangleProgram, "u_textureConfig");
depthScaleLoc = OpenGL::uniformLocation(triangleProgram, "u_depthScale");
depthOffsetLoc = OpenGL::uniformLocation(triangleProgram, "u_depthOffset");
depthmapEnableLoc = OpenGL::uniformLocation(triangleProgram, "u_depthmapEnable");
glUniform1i(OpenGL::uniformLocation(triangleProgram, "u_tex0"), 0); // Init sampler object
OpenGL::Shader vertDisplay(displayVertexShader, OpenGL::Vertex);
OpenGL::Shader fragDisplay(displayFragmentShader, OpenGL::Fragment);
displayProgram.create({ vertDisplay, fragDisplay });
displayProgram.use();
glUniform1i(OpenGL::uniformLocation(displayProgram, "u_texture"), 0); // Init sampler object
vbo.createFixedSize(sizeof(Vertex) * vertexBufferSize, GL_STREAM_DRAW);
vbo.bind();
vao.create();
vao.bind();
// Position (x, y, z, w) attributes
vao.setAttributeFloat<float>(0, 4, sizeof(Vertex), offsetof(Vertex, position));
vao.enableAttribute(0);
// Colour attribute
vao.setAttributeFloat<float>(1, 4, sizeof(Vertex), offsetof(Vertex, colour));
vao.enableAttribute(1);
// UV attribute
vao.setAttributeFloat<float>(2, 2, sizeof(Vertex), offsetof(Vertex, UVs));
vao.enableAttribute(2);
dummyVBO.create();
dummyVAO.create();
reset();
}
void Renderer::getGraphicsContext() {
OpenGL::disableScissor();
OpenGL::setViewport(400, 240);
vbo.bind();
vao.bind();
triangleProgram.use();
}
// Set up the OpenGL blending context to match the emulated PICA
void Renderer::setupBlending() {
const bool blendingEnabled = (regs[PICAInternalRegs::ColourOperation] & (1 << 8)) != 0;
// Map of PICA blending equations to OpenGL blending equations. The unused blending equations are equivalent to equation 0 (add)
static constexpr std::array<GLenum, 8> blendingEquations = {
GL_FUNC_ADD, GL_FUNC_SUBTRACT, GL_FUNC_REVERSE_SUBTRACT, GL_MIN, GL_MAX, GL_FUNC_ADD, GL_FUNC_ADD, GL_FUNC_ADD
};
// Map of PICA blending funcs to OpenGL blending funcs. Func = 15 is undocumented and stubbed to GL_ONE for now
static constexpr std::array<GLenum, 16> blendingFuncs = {
GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_CONSTANT_COLOR, GL_ONE_MINUS_CONSTANT_COLOR, GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA,
GL_SRC_ALPHA_SATURATE, GL_ONE
};
if (!blendingEnabled) {
OpenGL::disableBlend();
} else {
OpenGL::enableBlend();
// Get blending equations
const u32 blendControl = regs[PICAInternalRegs::BlendFunc];
const u32 rgbEquation = blendControl & 0x7;
const u32 alphaEquation = (blendControl >> 8) & 0x7;
// Get blending functions
const u32 rgbSourceFunc = (blendControl >> 16) & 0xf;
const u32 rgbDestFunc = (blendControl >> 20) & 0xf;
const u32 alphaSourceFunc = (blendControl >> 24) & 0xf;
const u32 alphaDestFunc = (blendControl >> 28) & 0xf;
const u32 constantColor = regs[PICAInternalRegs::BlendColour];
const u32 r = constantColor & 0xff;
const u32 g = (constantColor >> 8) & 0xff;
const u32 b = (constantColor >> 16) & 0xff;
const u32 a = (constantColor >> 24) & 0xff;
OpenGL::setBlendColor(float(r) / 255.f, float(g) / 255.f, float(b) / 255.f, float(a) / 255.f);
// Translate equations and funcs to their GL equivalents and set them
glBlendEquationSeparate(blendingEquations[rgbEquation], blendingEquations[alphaEquation]);
glBlendFuncSeparate(blendingFuncs[rgbSourceFunc], blendingFuncs[rgbDestFunc], blendingFuncs[alphaSourceFunc], blendingFuncs[alphaDestFunc]);
}
}
void Renderer::drawVertices(OpenGL::Primitives primType, Vertex* vertices, u32 count) {
// Adjust alpha test if necessary
const u32 alphaControl = regs[PICAInternalRegs::AlphaTestConfig];
if (alphaControl != oldAlphaControl) {
oldAlphaControl = alphaControl;
glUniform1ui(alphaControlLoc, alphaControl);
}
setupBlending();
OpenGL::Framebuffer poop = getColourFBO();
poop.bind(OpenGL::DrawAndReadFramebuffer);
const u32 depthControl = regs[PICAInternalRegs::DepthAndColorMask];
const bool depthEnable = depthControl & 1;
const bool depthWriteEnable = (depthControl >> 12) & 1;
const int depthFunc = (depthControl >> 4) & 7;
const int colourMask = (depthControl >> 8) & 0xf;
glColorMask(colourMask & 1, colourMask & 2, colourMask & 4, colourMask & 8);
static constexpr std::array<GLenum, 8> depthModes = {
GL_NEVER, GL_ALWAYS, GL_EQUAL, GL_NOTEQUAL, GL_LESS, GL_LEQUAL, GL_GREATER, GL_GEQUAL
};
const float depthScale = f24::fromRaw(regs[PICAInternalRegs::DepthScale] & 0xffffff).toFloat32();
const float depthOffset = f24::fromRaw(regs[PICAInternalRegs::DepthOffset] & 0xffffff).toFloat32();
const bool depthMapEnable = regs[PICAInternalRegs::DepthmapEnable] & 1;
// Update depth uniforms
if (oldDepthScale != depthScale) {
oldDepthScale = depthScale;
glUniform1f(depthScaleLoc, depthScale);
}
if (oldDepthOffset != depthOffset) {
oldDepthOffset = depthOffset;
glUniform1f(depthOffsetLoc, depthOffset);
}
if (oldDepthmapEnable != depthMapEnable) {
oldDepthmapEnable = depthMapEnable;
glUniform1i(depthmapEnableLoc, depthMapEnable);
}
// Hack for rendering texture 1
if (regs[0x80] & 1) {
const u32 dim = regs[0x82];
const u32 config = regs[0x83];
const u32 height = dim & 0x7ff;
const u32 width = (dim >> 16) & 0x7ff;
const u32 addr = (regs[0x85] & 0x0FFFFFFF) << 3;
const u32 format = regs[0x8E] & 0xF;
Texture targetTex(addr, static_cast<Texture::Formats>(format), width, height, config);
OpenGL::Texture tex = getTexture(targetTex);
tex.bind();
}
// Update the texture unit configuration uniform if it changed
const u32 texUnitConfig = regs[PICAInternalRegs::TexUnitCfg];
if (oldTexUnitConfig != texUnitConfig) {
oldTexUnitConfig = texUnitConfig;
glUniform1ui(texUnitConfigLoc, texUnitConfig);
}
// TODO: Actually use this
float viewportWidth = f24::fromRaw(regs[PICAInternalRegs::ViewportWidth] & 0xffffff).toFloat32() * 2.0;
float viewportHeight = f24::fromRaw(regs[PICAInternalRegs::ViewportHeight] & 0xffffff).toFloat32() * 2.0;
OpenGL::setViewport(viewportWidth, viewportHeight);
// Note: The code below must execute after we've bound the colour buffer & its framebuffer
// Because it attaches a depth texture to the aforementioned colour buffer
if (depthEnable) {
OpenGL::enableDepth();
glDepthFunc(depthModes[depthFunc]);
glDepthMask(depthWriteEnable ? GL_TRUE : GL_FALSE);
bindDepthBuffer();
} else {
if (depthWriteEnable) {
OpenGL::enableDepth();
glDepthFunc(GL_ALWAYS);
glDepthMask(GL_TRUE);
bindDepthBuffer();
} else {
OpenGL::disableDepth();
}
}
vbo.bufferVertsSub(vertices, count);
OpenGL::draw(primType, count);
}
constexpr u32 topScreenBuffer = 0x1f000000;
constexpr u32 bottomScreenBuffer = 0x1f05dc00;
// Quick hack to display top screen for now
void Renderer::display() {
OpenGL::disableDepth();
OpenGL::disableScissor();
OpenGL::bindScreenFramebuffer();
colourBufferCache[0].texture.bind();
displayProgram.use();
dummyVAO.bind();
OpenGL::setClearColor(0.0, 0.0, 1.0, 1.0); // Clear screen colour
OpenGL::clearColor();
OpenGL::setViewport(0, 240, 400, 240); // Actually draw our 3DS screen
OpenGL::draw(OpenGL::TriangleStrip, 4);
}
void Renderer::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) {
return;
log("GPU: Clear buffer\nStart: %08X End: %08X\nValue: %08X Control: %08X\n", startAddress, endAddress, value, control);
const float r = float((value >> 24) & 0xff) / 255.0;
const float g = float((value >> 16) & 0xff) / 255.0;
const float b = float((value >> 8) & 0xff) / 255.0;
const float a = float(value & 0xff) / 255.0;
if (startAddress == topScreenBuffer) {
log("GPU: Cleared top screen\n");
} else if (startAddress == bottomScreenBuffer) {
log("GPU: Tried to clear bottom screen\n");
return;
} else {
log("GPU: Clearing some unknown buffer\n");
}
OpenGL::setClearColor(r, g, b, a);
OpenGL::clearColor();
}
OpenGL::Framebuffer Renderer::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
ColourBuffer sampleBuffer(colourBufferLoc, colourBufferFormat, fbSize.x(), fbSize.y());
auto buffer = colourBufferCache.find(sampleBuffer);
if (buffer.has_value()) {
return buffer.value().get().fbo;
} else {
return colourBufferCache.add(sampleBuffer).fbo;
}
}
void Renderer::bindDepthBuffer() {
// Similar logic as the getColourFBO function
DepthBuffer sampleBuffer(depthBufferLoc, depthBufferFormat, fbSize.x(), fbSize.y());
auto buffer = depthBufferCache.find(sampleBuffer);
GLuint tex;
if (buffer.has_value()) {
tex = buffer.value().get().texture.m_handle;
} else {
tex = depthBufferCache.add(sampleBuffer).texture.m_handle;
}
if (DepthBuffer::Formats::Depth24Stencil8 != depthBufferFormat) Helpers::panic("TODO: Should we remove stencil attachment?");
auto attachment = depthBufferFormat == DepthBuffer::Formats::Depth24Stencil8 ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT;
glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, tex, 0);
}
OpenGL::Texture Renderer::getTexture(Texture& tex) {
// Similar logic as the getColourFBO/bindDepthBuffer functions
auto buffer = textureCache.find(tex);
if (buffer.has_value()) {
return buffer.value().get().texture;
} else {
const void* textureData = gpu.getPointerPhys<void*>(tex.location); // Get pointer to the texture data in 3DS memory
Texture& newTex = textureCache.add(tex);
newTex.decodeTexture(textureData);
return newTex.texture;
}
}
void Renderer::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) {
const u32 inputWidth = inputSize & 0xffff;
const u32 inputGap = inputSize >> 16;
const u32 outputWidth = outputSize & 0xffff;
const u32 outputGap = outputSize >> 16;
}

View file

@ -0,0 +1,292 @@
#include "renderer_gl/textures.hpp"
#include "colour.hpp"
#include <array>
void Texture::allocate() {
glGenTextures(1, &texture.m_handle);
texture.create(size.u(), size.v(), GL_RGBA8);
texture.bind();
setNewConfig(config);
}
// Set the texture's configuration, which includes min/mag filters, wrapping S/T modes, and so on
void Texture::setNewConfig(u32 cfg) {
config = cfg;
// The wrapping mode field is 3 bits instead of 2 bits. The bottom 4 undocumented wrapping modes are taken from Citra.
static constexpr std::array<OpenGL::WrappingMode, 8> wrappingModes = {
OpenGL::ClampToEdge, OpenGL::ClampToBorder, OpenGL::Repeat, OpenGL::RepeatMirrored,
OpenGL::ClampToEdge, OpenGL::ClampToBorder, OpenGL::Repeat, OpenGL::Repeat
};
const auto magFilter = (cfg & 0x2) != 0 ? OpenGL::Linear : OpenGL::Nearest;
const auto minFilter = (cfg & 0x4) != 0 ? OpenGL::Linear : OpenGL::Nearest;
const auto wrapT = wrappingModes[(cfg >> 8) & 0x7];
const auto wrapS = wrappingModes[(cfg >> 12) & 0x7];
texture.setMinFilter(minFilter);
texture.setMagFilter(magFilter);
texture.setWrapS(wrapS);
texture.setWrapT(wrapT);
}
void Texture::free() {
valid = false;
if (texture.exists())
Helpers::panic("Make this texture free itself");
}
u64 Texture::sizeInBytes() {
u64 pixelCount = u64(size.x()) * u64(size.y());
switch (format) {
case Formats::RGBA8: // 4 bytes per pixel
return pixelCount * 4;
case Formats::RGB8: // 3 bytes per pixel
return pixelCount * 3;
case Formats::RGBA5551: // 2 bytes per pixel
case Formats::RGB565:
case Formats::RGBA4:
case Formats::RG8:
case Formats::IA8:
return pixelCount * 2;
case Formats::A8: // 1 byte per pixel
case Formats::I8:
case Formats::IA4:
return pixelCount;
case Formats::I4: // 4 bits per pixel
case Formats::A4:
return pixelCount / 2;
case Formats::ETC1: // Compressed formats
case Formats::ETC1A4: {
// Number of 4x4 tiles
const u64 tileCount = pixelCount / 16;
// Tiles are 8 bytes each on ETC1 and 16 bytes each on ETC1A4
const u64 tileSize = format == Formats::ETC1 ? 8 : 16;
return tileCount * tileSize;
}
default:
Helpers::panic("[PICA] Attempted to get size of invalid texture type");
}
}
// u and v are the UVs of the relevant texel
// Texture data is stored interleaved in Morton order, ie in a Z - order curve as shown here
// https://en.wikipedia.org/wiki/Z-order_curve
// Textures are split into 8x8 tiles.This function returns the in - tile offset depending on the u & v of the texel
// The in - tile offset is the sum of 2 offsets, one depending on the value of u % 8 and the other on the value of y % 8
// As documented in this picture https ://en.wikipedia.org/wiki/File:Moser%E2%80%93de_Bruijn_addition.svg
u32 Texture::mortonInterleave(u32 u, u32 v) {
static constexpr u32 xOffsets[] = { 0, 1, 4, 5, 16, 17, 20, 21 };
static constexpr u32 yOffsets[] = { 0, 2, 8, 10, 32, 34, 40, 42 };
return xOffsets[u & 7] + yOffsets[v & 7];
}
// Get the byte offset of texel (u, v) in the texture
u32 Texture::getSwizzledOffset(u32 u, u32 v, u32 width, u32 bytesPerPixel) {
u32 offset = ((u & ~7) * 8) + ((v & ~7) * width); // Offset of the 8x8 tile the texel belongs to
offset += mortonInterleave(u, v); // Add the in-tile offset of the texel
return offset * bytesPerPixel;
}
// Same as the above code except we need to divide by 2 because 4 bits is smaller than a byte
u32 Texture::getSwizzledOffset_4bpp(u32 u, u32 v, u32 width) {
u32 offset = ((u & ~7) * 8) + ((v & ~7) * width); // Offset of the 8x8 tile the texel belongs to
offset += mortonInterleave(u, v); // Add the in-tile offset of the texel
return offset / 2;
}
// Get the texel at position (u, v)
// fmt: format of the texture
// data: texture data of the texture
u32 Texture::decodeTexel(u32 u, u32 v, Texture::Formats fmt, const void* data) {
switch (fmt) {
case Formats::RGBA4: {
u32 offset = getSwizzledOffset(u, v, size.u(), 2);
auto ptr = static_cast<const u8*>(data);
u16 texel = u16(ptr[offset]) | (u16(ptr[offset + 1]) << 8);
u8 alpha = Colour::convert4To8Bit(texel & 0xf);
u8 b = Colour::convert4To8Bit((texel >> 4) & 0xf);
u8 g = Colour::convert4To8Bit((texel >> 8) & 0xf);
u8 r = Colour::convert4To8Bit((texel >> 12) & 0xf);
return (alpha << 24) | (b << 16) | (g << 8) | r;
}
case Formats::RGBA5551: {
u32 offset = getSwizzledOffset(u, v, size.u(), 2);
auto ptr = static_cast<const u8*>(data);
u16 texel = u16(ptr[offset]) | (u16(ptr[offset + 1]) << 8);
u8 alpha = (texel & 1) ? 0xff : 0;
u8 b = Colour::convert5To8Bit((texel >> 1) & 0x1f);
u8 g = Colour::convert5To8Bit((texel >> 6) & 0x1f);
u8 r = Colour::convert5To8Bit((texel >> 11) & 0x1f);
return (alpha << 24) | (b << 16) | (g << 8) | r;
}
case Formats::RGB565: {
u32 offset = getSwizzledOffset(u, v, size.u(), 2);
auto ptr = static_cast<const u8*>(data);
u16 texel = u16(ptr[offset]) | (u16(ptr[offset + 1]) << 8);
u8 b = Colour::convert5To8Bit(texel & 0x1f);
u8 g = Colour::convert6To8Bit((texel >> 5) & 0x3f);
u8 r = Colour::convert5To8Bit((texel >> 11) & 0x1f);
return (0xff << 24) | (b << 16) | (g << 8) | r;
}
case Formats::RG8: {
u32 offset = getSwizzledOffset(u, v, size.u(), 2);
auto ptr = static_cast<const u8*>(data);
constexpr u8 b = 0;
u8 g = ptr[offset];
u8 r = ptr[offset + 1];
return (0xff << 24) | (b << 16) | (g << 8) | r;
}
case Formats::RGB8: {
u32 offset = getSwizzledOffset(u, v, size.u(), 3);
auto ptr = static_cast<const u8*>(data);
u8 b = ptr[offset];
u8 g = ptr[offset + 1];
u8 r = ptr[offset + 2];
return (0xff << 24) | (b << 16) | (g << 8) | r;
}
case Formats::RGBA8: {
u32 offset = getSwizzledOffset(u, v, size.u(), 4);
auto ptr = static_cast<const u8*>(data);
u8 alpha = ptr[offset];
u8 b = ptr[offset + 1];
u8 g = ptr[offset + 2];
u8 r = ptr[offset + 3];
return (alpha << 24) | (b << 16) | (g << 8) | r;
}
case Formats::IA4: {
u32 offset = getSwizzledOffset(u, v, size.u(), 1);
auto ptr = static_cast<const u8*>(data);
const u8 texel = ptr[offset];
const u8 alpha = Colour::convert4To8Bit(texel & 0xf);
const u8 intensity = Colour::convert4To8Bit(texel >> 4);
// Intensity formats just copy the intensity value to every colour channel
return (alpha << 24) | (intensity << 16) | (intensity << 8) | intensity;
}
case Formats::A4: {
u32 offset = getSwizzledOffset_4bpp(u, v, size.u());
auto ptr = static_cast<const u8*>(data);
// For odd U coordinates, grab the top 4 bits, and the low 4 bits for even coordinates
u8 alpha = ptr[offset] >> ((u % 2) ? 4 : 0);
alpha = Colour::convert4To8Bit(alpha & 0xf);
// A8 sets RGB to 0
return (alpha << 24) | (0 << 16) | (0 << 8) | 0;
}
case Formats::A8: {
u32 offset = getSwizzledOffset(u, v, size.u(), 1);
auto ptr = static_cast<const u8*>(data);
const u8 alpha = ptr[offset];
// A8 sets RGB to 0
return (alpha << 24) | (0 << 16) | (0 << 8) | 0;
}
case Formats::I4: {
u32 offset = getSwizzledOffset_4bpp(u, v, size.u());
auto ptr = static_cast<const u8*>(data);
// For odd U coordinates, grab the top 4 bits, and the low 4 bits for even coordinates
u8 intensity = ptr[offset] >> ((u % 2) ? 4 : 0);
intensity = Colour::convert4To8Bit(intensity & 0xf);
// Intensity formats just copy the intensity value to every colour channel
return (0xff << 24) | (intensity << 16) | (intensity << 8) | intensity;
}
case Formats::I8: {
u32 offset = getSwizzledOffset(u, v, size.u(), 1);
auto ptr = static_cast<const u8*>(data);
const u8 intensity = ptr[offset];
// Intensity formats just copy the intensity value to every colour channel
return (0xff << 24) | (intensity << 16) | (intensity << 8) | intensity;
}
case Formats::IA8: {
u32 offset = getSwizzledOffset(u, v, size.u(), 2);
auto ptr = static_cast<const u8*>(data);
// Same as I8 except each pixel gets its own alpha value too
const u8 alpha = ptr[offset];
const u8 intensity = ptr[offset + 1];
return (alpha << 24) | (intensity << 16) | (intensity << 8) | intensity;
}
case Formats::ETC1: return getTexelETC(false, u, v, size.u(), data);
case Formats::ETC1A4: return getTexelETC(true, u, v, size.u(), data);
default:
Helpers::panic("[Texture::DecodeTexel] Unimplemented format = %d", static_cast<int>(fmt));
}
}
void Texture::decodeTexture(const void* data) {
std::vector<u32> decoded;
decoded.reserve(u64(size.u()) * u64(size.v()));
// Decode texels line by line
for (u32 v = 0; v < size.v(); v++) {
for (u32 u = 0; u < size.u(); u++) {
u32 colour = decodeTexel(u, v, format, data);
decoded.push_back(colour);
}
}
texture.bind();
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, size.u(), size.v(), GL_RGBA, GL_UNSIGNED_BYTE, decoded.data());
}
std::string Texture::textureFormatToString(Texture::Formats fmt) {
switch (fmt) {
case Formats::A4: return "A4";
case Formats::A8: return "A8";
case Formats::ETC1: return "ETC1";
case Formats::ETC1A4: return "ETC1A4";
case Formats::I4: return "I4";
case Formats::I8: return "I8";
case Formats::IA4: return "IA4";
case Formats::IA8: return "IA8";
case Formats::RG8: return "RG8";
case Formats::RGB565: return "RGB565";
case Formats::RGB8: return "RGB8";
case Formats::RGBA4: return "RGBA4";
case Formats::RGBA5551: return "RGBA5551";
case Formats::RGBA8: return "RGBA8";
default: return "Unknown";
}
}

32
src/core/services/ac.cpp Normal file
View file

@ -0,0 +1,32 @@
#include "services/ac.hpp"
#include "ipc.hpp"
namespace ACCommands {
enum : u32 {
SetClientVersion = 0x00400042
};
}
namespace Result {
enum : u32 {
Success = 0,
};
}
void ACService::reset() {}
void ACService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case ACCommands::SetClientVersion: setClientVersion(messagePointer); break;
default: Helpers::panic("AC service requested. Command: %08X\n", command);
}
}
void ACService::setClientVersion(u32 messagePointer) {
u32 version = mem.read32(messagePointer + 4);
log("AC::SetClientVersion (version = %d)\n", version);
mem.write32(messagePointer, IPC::responseHeader(0x40, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}

31
src/core/services/act.cpp Normal file
View file

@ -0,0 +1,31 @@
#include "services/act.hpp"
#include "ipc.hpp"
namespace ACTCommands {
enum : u32 {
Initialize = 0x00010084
};
}
namespace Result {
enum : u32 {
Success = 0,
};
}
void ACTService::reset() {}
void ACTService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case ACTCommands::Initialize: initialize(messagePointer); break;
default: Helpers::panic("ACT service requested. Command: %08X\n", command);
}
}
void ACTService::initialize(u32 messagePointer) {
log("ACT::Initialize");
mem.write32(messagePointer, IPC::responseHeader(0x1, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}

54
src/core/services/am.cpp Normal file
View file

@ -0,0 +1,54 @@
#include "services/am.hpp"
#include "ipc.hpp"
namespace AMCommands {
enum : u32 {
GetDLCTitleInfo = 0x10050084,
ListTitleInfo = 0x10070102
};
}
namespace Result {
enum : u32 {
Success = 0,
};
}
void AMService::reset() {}
void AMService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case AMCommands::GetDLCTitleInfo: getDLCTitleInfo(messagePointer); break;
case AMCommands::ListTitleInfo: listTitleInfo(messagePointer); break;
default: Helpers::panic("AM service requested. Command: %08X\n", command);
}
}
void AMService::listTitleInfo(u32 messagePointer) {
log("AM::ListDLCOrLicenseTicketInfos\n"); // Yes this is the actual name
u32 ticketCount = mem.read32(messagePointer + 4);
u64 titleID = mem.read64(messagePointer + 8);
u32 pointer = mem.read32(messagePointer + 24);
for (u32 i = 0; i < ticketCount; i++) {
mem.write64(pointer, titleID); // Title ID
mem.write64(pointer + 8, 0); // Ticket ID
mem.write16(pointer + 16, 0); // Version
mem.write16(pointer + 18, 0); // Padding
mem.write32(pointer + 20, 0); // Size
pointer += 24; // = sizeof(TicketInfo)
}
mem.write32(messagePointer, IPC::responseHeader(0x1007, 2, 2));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, ticketCount);
}
void AMService::getDLCTitleInfo(u32 messagePointer) {
log("AM::GetDLCTitleInfo (stubbed to fail)\n");
mem.write32(messagePointer, IPC::responseHeader(0x1005, 1, 4));
mem.write32(messagePointer + 4, -1);
}

View file

@ -1,37 +1,68 @@
#include "services/apt.hpp"
#include "ipc.hpp"
#include "kernel.hpp"
namespace APTCommands {
enum : u32 {
GetLockHandle = 0x00010040,
Initialize = 0x00020080,
Enable = 0x00030040,
InquireNotification = 0x000B0040,
ReceiveParameter = 0x000D0080,
GlanceParameter = 0x000E0080,
ReplySleepQuery = 0x003E0080,
NotifyToWait = 0x00430040,
GetSharedFont = 0x00440000,
GetWirelessRebootInfo = 0x00450040,
AppletUtility = 0x004B00C2,
SetApplicationCpuTimeLimit = 0x004F0080,
GetApplicationCpuTimeLimit = 0x00500040,
SetScreencapPostPermission = 0x00550040,
CheckNew3DSApp = 0x01010000,
CheckNew3DS = 0x01020000
};
}
namespace Model {
enum : u8 {
Old3DS = 0,
New3DS = 1
CheckNew3DS = 0x01020000,
TheSmashBrosFunction = 0x01030000
};
}
namespace Result {
enum : u32 {
Success = 0,
Failure = 0xFFFFFFFF
};
}
// https://www.3dbrew.org/wiki/NS_and_APT_Services#Command
namespace APTTransitions {
enum : u32 {
None = 0,
Wakeup = 1,
Request = 2,
Response = 3,
Exit = 4,
Message = 5,
HomeButtonSingle = 6,
HomeButtonDouble = 7,
DSPSleep = 8,
DSPWakeup = 9,
WakeupByExit = 10,
WakuepByPause = 11,
WakeupByCancel = 12,
WakeupByCancelAll = 13,
WakeupByPowerButton = 14,
WakeupToJumpHome = 15,
RequestForApplet = 16,
WakeupToLaunchApp = 17,
ProcessDed = 0x41
};
}
void APTService::reset() {
// Set the default CPU time limit to 30%. Seems safe, as this is what Metroid 2 uses by default
cpuTimeLimit = 30;
// Reset the handles for the various service objects
lockHandle = std::nullopt;
notificationEvent = std::nullopt;
resumeEvent = std::nullopt;
}
void APTService::handleSyncRequest(u32 messagePointer) {
@ -41,12 +72,19 @@ void APTService::handleSyncRequest(u32 messagePointer) {
case APTCommands::CheckNew3DS: checkNew3DS(messagePointer); break;
case APTCommands::CheckNew3DSApp: checkNew3DSApp(messagePointer); break;
case APTCommands::Enable: enable(messagePointer); break;
case APTCommands::GetSharedFont: getSharedFont(messagePointer); break;
case APTCommands::Initialize: initialize(messagePointer); break;
case APTCommands::InquireNotification: [[likely]] inquireNotification(messagePointer); break;
case APTCommands::GetApplicationCpuTimeLimit: getApplicationCpuTimeLimit(messagePointer); break;
case APTCommands::GetLockHandle: getLockHandle(messagePointer); break;
case APTCommands::GetWirelessRebootInfo: getWirelessRebootInfo(messagePointer); break;
case APTCommands::GlanceParameter: glanceParameter(messagePointer); break;
case APTCommands::NotifyToWait: notifyToWait(messagePointer); break;
case APTCommands::ReceiveParameter: receiveParameter(messagePointer); break;
case APTCommands::ReceiveParameter: [[likely]] receiveParameter(messagePointer); break;
case APTCommands::ReplySleepQuery: replySleepQuery(messagePointer); break;
case APTCommands::SetApplicationCpuTimeLimit: setApplicationCpuTimeLimit(messagePointer); break;
case APTCommands::SetScreencapPostPermission: setScreencapPostPermission(messagePointer); break;
case APTCommands::TheSmashBrosFunction: theSmashBrosFunction(messagePointer); break;
default: Helpers::panic("APT service requested. Command: %08X\n", command);
}
}
@ -59,35 +97,76 @@ void APTService::appletUtility(u32 messagePointer) {
log("APT::AppletUtility(utility = %d, input size = %x, output size = %x, inputPointer = %08X)\n", utility, inputSize,
outputSize, inputPointer);
mem.write32(messagePointer, IPC::responseHeader(0x4B, 2, 2));
mem.write32(messagePointer + 4, Result::Success);
}
void APTService::checkNew3DS(u32 messagePointer) {
log("APT::CheckNew3DS\n");
mem.write32(messagePointer, IPC::responseHeader(0x102, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write8(messagePointer + 8, Model::Old3DS); // u8, Status (0 = Old 3DS, 1 = New 3DS)
mem.write8(messagePointer + 8, (model == ConsoleModel::New3DS) ? 1 : 0); // u8, Status (0 = Old 3DS, 1 = New 3DS)
}
// TODO: Figure out the slight way this differs from APT::CheckNew3DS
void APTService::checkNew3DSApp(u32 messagePointer) {
log("APT::CheckNew3DSApp\n");
checkNew3DS(messagePointer);
mem.write32(messagePointer, IPC::responseHeader(0x101, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write8(messagePointer + 8, (model == ConsoleModel::New3DS) ? 1 : 0); // u8, Status (0 = Old 3DS, 1 = New 3DS)
}
void APTService::enable(u32 messagePointer) {
log("APT::Enable\n");
mem.write32(messagePointer, IPC::responseHeader(0x3, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void APTService::initialize(u32 messagePointer) {
log("APT::Initialize\n");
if (!notificationEvent.has_value() || !resumeEvent.has_value()) {
notificationEvent = kernel.makeEvent(ResetType::OneShot);
resumeEvent = kernel.makeEvent(ResetType::OneShot);
kernel.signalEvent(resumeEvent.value()); // Seems to be signalled on startup
}
mem.write32(messagePointer, IPC::responseHeader(0x2, 1, 3));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 0x04000000); // Translation descriptor
mem.write32(messagePointer + 12, notificationEvent.value()); // Notification Event Handle
mem.write32(messagePointer + 16, resumeEvent.value()); // Resume Event Handle
}
void APTService::inquireNotification(u32 messagePointer) {
log("APT::InquireNotification (STUBBED TO RETURN NONE)\n");
mem.write32(messagePointer, IPC::responseHeader(0xB, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, static_cast<u32>(NotificationType::None));
}
void APTService::getLockHandle(u32 messagePointer) {
log("APT::GetLockHandle (Failure)\n");
mem.write32(messagePointer + 4, Result::Failure); // Result code
log("APT::GetLockHandle\n");
// Create a lock handle if none exists
if (!lockHandle.has_value() || kernel.getObject(lockHandle.value(), KernelObjectType::Mutex) == nullptr) {
lockHandle = kernel.makeMutex();
}
mem.write32(messagePointer, IPC::responseHeader(0x1, 3, 2));
mem.write32(messagePointer + 4, Result::Success); // Result code
mem.write32(messagePointer + 8, 0); // AppletAttr
mem.write32(messagePointer + 12, 0); // APT State (bit0 = Power Button State, bit1 = Order To Close State)
mem.write32(messagePointer + 16, 0); // Translation descriptor
mem.write32(messagePointer + 20, lockHandle.value()); // Lock handle
}
// This apparently does nothing on the original kernel either?
void APTService::notifyToWait(u32 messagePointer) {
log("APT::NotifyToWait\n");
mem.write32(messagePointer, IPC::responseHeader(0x43, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
@ -98,11 +177,30 @@ void APTService::receiveParameter(u32 messagePointer) {
if (size > 0x1000) Helpers::panic("APT::ReceiveParameter with size > 0x1000");
// TODO: Properly implement this. We currently stub it in the same way as 3dmoo
// TODO: Properly implement this. We currently stub somewhat like 3dmoo
mem.write32(messagePointer, IPC::responseHeader(0xD, 4, 4));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 0); // Sender App ID
mem.write32(messagePointer + 12, 1); // Signal type (1 = app just started, 0xB = returning to app, 0xC = exiting app)
mem.write32(messagePointer + 16, 0x10);
mem.write32(messagePointer + 12, APTTransitions::Wakeup); // Command
mem.write32(messagePointer + 16, 0);
mem.write32(messagePointer + 20, 0x10);
mem.write32(messagePointer + 24, 0);
mem.write32(messagePointer + 28, 0);
}
void APTService::glanceParameter(u32 messagePointer) {
const u32 app = mem.read32(messagePointer + 4);
const u32 size = mem.read32(messagePointer + 8);
log("APT::GlanceParameter(app ID = %X, size = %04X) (STUBBED)\n", app, size);
if (size > 0x1000) Helpers::panic("APT::GlanceParameter with size > 0x1000");
// TODO: Properly implement this. We currently stub it similar
mem.write32(messagePointer, IPC::responseHeader(0xE, 4, 4));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 0); // Sender App ID
mem.write32(messagePointer + 12, APTTransitions::Wakeup); // Command
mem.write32(messagePointer + 16, 0);
mem.write32(messagePointer + 20, 0);
mem.write32(messagePointer + 24, 0);
mem.write32(messagePointer + 28, 0);
@ -110,6 +208,7 @@ void APTService::receiveParameter(u32 messagePointer) {
void APTService::replySleepQuery(u32 messagePointer) {
log("APT::ReplySleepQuery (Stubbed)\n");
mem.write32(messagePointer, IPC::responseHeader(0x3E, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
@ -121,6 +220,7 @@ void APTService::setApplicationCpuTimeLimit(u32 messagePointer) {
if (percentage < 5 || percentage > 89 || fixed != 1) {
Helpers::panic("Invalid parameters passed to APT::SetApplicationCpuTimeLimit");
} else {
mem.write32(messagePointer, IPC::responseHeader(0x4F, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
cpuTimeLimit = percentage;
}
@ -128,6 +228,51 @@ void APTService::setApplicationCpuTimeLimit(u32 messagePointer) {
void APTService::getApplicationCpuTimeLimit(u32 messagePointer) {
log("APT::GetApplicationCpuTimeLimit\n");
mem.write32(messagePointer, IPC::responseHeader(0x50, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, cpuTimeLimit);
}
void APTService::setScreencapPostPermission(u32 messagePointer) {
u32 perm = mem.read32(messagePointer + 4);
log("APT::SetScreencapPostPermission (perm = %d)\n");
mem.write32(messagePointer, IPC::responseHeader(0x55, 1, 0));
// Apparently only 1-3 are valid values, but I see 0 used in some games like Pokemon Rumble
mem.write32(messagePointer + 4, Result::Success);
screencapPostPermission = perm;
}
void APTService::getSharedFont(u32 messagePointer) {
log("APT::GetSharedFont\n");
constexpr u32 fontVaddr = 0x18000000;
mem.write32(messagePointer, IPC::responseHeader(0x44, 2, 2));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, fontVaddr);
mem.write32(messagePointer + 16, KernelHandles::FontSharedMemHandle);
}
// This function is entirely undocumented. We know Smash Bros uses it and that it normally writes 2 to cmdreply[2] on New 3DS
// And that writing 1 stops it from accessing the ir:USER service for New 3DS HID use
void APTService::theSmashBrosFunction(u32 messagePointer) {
log("APT: Called the elusive Smash Bros function\n");
mem.write32(messagePointer, IPC::responseHeader(0x103, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, (model == ConsoleModel::New3DS) ? 2 : 1);
}
void APTService::getWirelessRebootInfo(u32 messagePointer) {
const u32 size = mem.read32(messagePointer + 4); // Size of data to read
log("APT::GetWirelessRebootInfo (size = %X)\n", size);
if (size > 0x10)
Helpers::panic("APT::GetWirelessInfo with size > 0x10 bytes");
mem.write32(messagePointer, IPC::responseHeader(0x45, 1, 2));
mem.write32(messagePointer + 4, Result::Success);
for (u32 i = 0; i < size; i++) {
mem.write8(messagePointer + 0x104 + i, 0); // Temporarily stub this until we add SetWirelessRebootInfo
}
}

View file

@ -0,0 +1,96 @@
#include "services/boss.hpp"
#include "ipc.hpp"
namespace BOSSCommands {
enum : u32 {
InitializeSession = 0x00010082,
UnregisterStorage = 0x00030000,
GetOptoutFlag = 0x000A0000,
UnregisterTask = 0x000C0082,
GetTaskIdList = 0x000E0000,
ReceiveProperty = 0x00160082,
RegisterStorageEntry = 0x002F0140,
GetStorageEntryInfo = 0x00300000
};
}
namespace Result {
enum : u32 {
Success = 0,
};
}
void BOSSService::reset() {
optoutFlag = 0;
}
void BOSSService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case BOSSCommands::GetOptoutFlag: getOptoutFlag(messagePointer); break;
case BOSSCommands::GetStorageEntryInfo: getStorageEntryInfo(messagePointer); break;
case BOSSCommands::GetTaskIdList: getTaskIdList(messagePointer); break;
case BOSSCommands::InitializeSession: initializeSession(messagePointer); break;
case BOSSCommands::ReceiveProperty: receiveProperty(messagePointer); break;
case BOSSCommands::RegisterStorageEntry: registerStorageEntry(messagePointer); break;
case BOSSCommands::UnregisterStorage: unregisterStorage(messagePointer); break;
case BOSSCommands::UnregisterTask: unregisterTask(messagePointer); break;
default: Helpers::panic("BOSS service requested. Command: %08X\n", command);
}
}
void BOSSService::initializeSession(u32 messagePointer) {
log("BOSS::InitializeSession (stubbed)\n");
mem.write32(messagePointer, IPC::responseHeader(0x1, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void BOSSService::getOptoutFlag(u32 messagePointer) {
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::getTaskIdList(u32 messagePointer) {
log("BOSS::GetTaskIdList (stubbed)\n");
mem.write32(messagePointer, IPC::responseHeader(0xE, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void BOSSService::getStorageEntryInfo(u32 messagePointer) {
log("BOSS::GetStorageEntryInfo (undocumented)\n");
mem.write32(messagePointer, IPC::responseHeader(0x30, 3, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 0); // u32, unknown meaning
mem.write16(messagePointer + 12, 0); // s16, unknown meaning
}
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);
mem.write32(messagePointer, IPC::responseHeader(0x16, 2, 2));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 0); // Read size
}
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::registerStorageEntry(u32 messagePointer) {
log("BOSS::RegisterStorageEntry (stubbed)\n");
mem.write32(messagePointer, IPC::responseHeader(0x2F, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void BOSSService::unregisterStorage(u32 messagePointer) {
log("BOSS::UnregisterStorage (stubbed)\n");
mem.write32(messagePointer, IPC::responseHeader(0x3, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}

64
src/core/services/cam.cpp Normal file
View file

@ -0,0 +1,64 @@
#include "services/cam.hpp"
#include "ipc.hpp"
namespace CAMCommands {
enum : u32 {
DriverInitialize = 0x00390000,
GetMaxLines = 0x000A0080
};
}
namespace Result {
enum : u32 {
Success = 0,
};
}
void CAMService::reset() {}
void CAMService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case CAMCommands::DriverInitialize: driverInitialize(messagePointer); break;
case CAMCommands::GetMaxLines: getMaxLines(messagePointer); break;
default: Helpers::panic("CAM service requested. Command: %08X\n", command);
}
}
void CAMService::driverInitialize(u32 messagePointer) {
log("CAM::DriverInitialize\n");
mem.write32(messagePointer, IPC::responseHeader(0x39, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
// Algorithm taken from Citra
// https://github.com/citra-emu/citra/blob/master/src/core/hle/service/cam/cam.cpp#L465
void CAMService::getMaxLines(u32 messagePointer) {
const u16 width = mem.read16(messagePointer + 4);
const u16 height = mem.read16(messagePointer + 8);
log("CAM::GetMaxLines (width = %d, height = %d)\n", width, height);
constexpr u32 MIN_TRANSFER_UNIT = 256;
constexpr u32 MAX_BUFFER_SIZE = 2560;
if (width * height * 2 % MIN_TRANSFER_UNIT != 0) {
Helpers::panic("CAM::GetMaxLines out of range");
} else {
u32 lines = MAX_BUFFER_SIZE / width;
if (lines > height) {
lines = height;
}
u32 result = Result::Success;
while (height % lines != 0 || (lines * width * 2 % MIN_TRANSFER_UNIT != 0)) {
--lines;
if (lines == 0) {
Helpers::panic("CAM::GetMaxLines out of range");
break;
}
}
mem.write32(messagePointer, IPC::responseHeader(0xA, 2, 0));
mem.write32(messagePointer + 4, result);
mem.write16(messagePointer + 8, lines);
}
}

View file

@ -1,7 +1,10 @@
#include "services/cecd.hpp"
#include "ipc.hpp"
#include "kernel.hpp"
namespace CECDCommands {
enum : u32 {
GetInfoEventHandle = 0x000F0000
};
}
@ -11,11 +14,27 @@ namespace Result {
};
}
void CECDService::reset() {}
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;
default: Helpers::panic("CECD service requested. Command: %08X\n", command);
}
}
void CECDService::getInfoEventHandle(u32 messagePointer) {
log("CECD::GetInfoEventHandle (stubbed)\n");
if (!infoEvent.has_value()) {
infoEvent = kernel.makeEvent(ResetType::OneShot);
}
mem.write32(messagePointer, IPC::responseHeader(0xF, 1, 2));
mem.write32(messagePointer + 4, Result::Success);
// TODO: Translation descriptor here?
mem.write32(messagePointer + 12, infoEvent.value());
}

View file

@ -1,11 +1,18 @@
#include "services/cfg.hpp"
#include "services/dsp.hpp"
#include "services/region_codes.hpp"
#include "system_models.hpp"
#include "ipc.hpp"
#include <array>
#include <bit>
namespace CFGCommands {
enum : u32 {
GetConfigInfoBlk2 = 0x00010082,
SecureInfoGetRegion = 0x00020000
SecureInfoGetRegion = 0x00020000,
GenHashConsoleUnique = 0x00030040,
GetRegionCanadaUSA = 0x00040000,
GetSystemModel = 0x00050000
};
}
@ -20,12 +27,33 @@ void CFGService::reset() {}
void CFGService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case CFGCommands::GetConfigInfoBlk2: getConfigInfoBlk2(messagePointer); break;
case CFGCommands::GetConfigInfoBlk2: [[likely]] getConfigInfoBlk2(messagePointer); break;
case CFGCommands::GetRegionCanadaUSA: getRegionCanadaUSA(messagePointer); break;
case CFGCommands::GetSystemModel: getSystemModel(messagePointer); break;
case CFGCommands::GenHashConsoleUnique: genUniqueConsoleHash(messagePointer); break;
case CFGCommands::SecureInfoGetRegion: secureInfoGetRegion(messagePointer); break;
default: Helpers::panic("CFG service requested. Command: %08X\n", command);
}
}
void CFGService::getSystemModel(u32 messagePointer) {
log("CFG::GetSystemModel\n");
mem.write32(messagePointer, IPC::responseHeader(0x05, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write8(messagePointer + 8, SystemModel::Nintendo3DS); // TODO: Make this adjustable via GUI
}
// Write a UTF16 string to 3DS memory starting at "pointer". Appends a null terminator.
void CFGService::writeStringU16(u32 pointer, const std::u16string& string) {
for (auto c : string) {
mem.write16(pointer, static_cast<u16>(c));
pointer += 2;
}
mem.write16(pointer, static_cast<u16>(u'\0')); // Null terminator
}
void CFGService::getConfigInfoBlk2(u32 messagePointer) {
u32 size = mem.read32(messagePointer + 4);
u32 blockID = mem.read32(messagePointer + 8);
@ -37,18 +65,97 @@ void CFGService::getConfigInfoBlk2(u32 messagePointer) {
mem.write8(output, static_cast<u8>(DSPService::SoundOutputMode::Stereo));
} else if (size == 1 && blockID == 0xA0002){ // System language
mem.write8(output, static_cast<u8>(LanguageCodes::English));
} else if (size == 4 && blockID == 0xB0000) { // Country info
mem.write8(output, 0); // Unknown
mem.write8(output + 1, 0); // Unknown
mem.write8(output + 2, 2); // Province (Temporarily stubbed to Washington DC like Citra)
mem.write8(output + 3, static_cast<u8>(country)); // Country code
} else if (size == 0x20 && blockID == 0x50005) {
printf("[Unimplemented] Read stereo display settings from NAND\n");
// "Stereo Camera settings"
// Implementing this properly fixes NaN uniforms in some games. Values taken from 3dmoo & Citra
static constexpr std::array<float, 8> STEREO_CAMERA_SETTINGS = {
62.0f, 289.0f, 76.80000305175781f, 46.08000183105469f,
10.0f, 5.0f, 55.58000183105469f, 21.56999969482422f
};
for (int i = 0; i < 8; i++) {
mem.write32(output + i * 4, std::bit_cast<u32, float>(STEREO_CAMERA_SETTINGS[i]));
}
} else if (size == 0x1C && blockID == 0xA0000) { // Username
writeStringU16(output, u"Pander");
} else if (size == 0xC0 && blockID == 0xC0000) { // Parental restrictions info
for (int i = 0; i < 0xC0; i++)
mem.write8(output + i, 0);
} else if (size == 4 && blockID == 0xD0000) { // Agreed EULA version (first 2 bytes) and latest EULA version (next 2 bytes)
log("Read EULA info\n");
mem.write16(output, 0x0202); // Agreed EULA version = 2.2 (Random number. TODO: Check)
mem.write16(output + 2, 0x0202); // Latest EULA version = 2.2
} else if (size == 0x800 && blockID == 0xB0001) { // UTF-16 name for our country in every language at 0x80 byte intervals
constexpr size_t languageCount = 16;
constexpr size_t nameSize = 0x80; // Max size of each name in bytes
std::u16string name = u"PandaLand (Home of PandaSemi LLC) (aka Pandistan)"; // Note: This + the null terminator needs to fit in 0x80 bytes
for (int i = 0; i < languageCount; i++) {
u32 pointer = output + i * nameSize;
writeStringU16(pointer, name);
}
} else if (size == 0x800 && blockID == 0xB0002) { // UTF-16 name for our state in every language at 0x80 byte intervals
constexpr size_t languageCount = 16;
constexpr size_t nameSize = 0x80; // Max size of each name in bytes
std::u16string name = u"Pandington"; // Note: This + the null terminator needs to fit in 0x80 bytes
for (int i = 0; i < languageCount; i++) {
u32 pointer = output + i * nameSize;
writeStringU16(pointer, name);
}
} else if (size == 4 && blockID == 0xB0003) { // Coordinates (latidude and longtitude) as s16
mem.write16(output, 0); // Latitude
mem.write16(output + 2, 0); // Longtitude
} else if (size == 2 && blockID == 0xA0001) { // Birthday
mem.write8(output, 5); // Month (May)
mem.write8(output + 1, 5); // Day (Fifth)
} else if (size == 8 && blockID == 0x30001) { // User time offset
printf("Read from user time offset field in NAND. TODO: What is this\n");
mem.write64(output, 0);
} else if (size == 20 && blockID == 0xC0001) { // COPPACS restriction data, used by games when they detect a USA/Canada region for market restriction stuff
for (u32 i = 0; i < size; i += 4) {
mem.write32(output + i, 0);
}
} else {
Helpers::panic("Unhandled GetConfigInfoBlk2 configuration");
Helpers::panic("Unhandled GetConfigInfoBlk2 configuration. Size = %d, block = %X", size, blockID);
}
mem.write32(messagePointer, IPC::responseHeader(0x1, 1, 2));
mem.write32(messagePointer + 4, Result::Success);
}
void CFGService::secureInfoGetRegion(u32 messagePointer) {
log("CFG::SecureInfoGetRegion\n");
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
}
void CFGService::genUniqueConsoleHash(u32 messagePointer) {
log("CFG::GenUniqueConsoleHash (semi-stubbed)\n");
const u32 salt = mem.read32(messagePointer + 4) & 0x000FFFFF;
mem.write32(messagePointer, IPC::responseHeader(0x3, 3, 0));
mem.write32(messagePointer + 4, Result::Success);
// We need to implement hash generation & the SHA-256 digest properly later on. We have cryptopp so the hashing isn't too hard to do
// Let's stub it for now
mem.write32(messagePointer + 8, 0x33646D6F ^ salt); // Lower word of hash
mem.write32(messagePointer + 12, 0xA3534841 ^ salt); // Upper word of hash
}
// Returns 1 if the console region is either Canada or USA, otherwise returns 0
// 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;
mem.write32(messagePointer, IPC::responseHeader(0x4, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write8(messagePointer + 8, ret);
}

View file

@ -1,4 +1,6 @@
#include "services/dsp.hpp"
#include "ipc.hpp"
#include "kernel.hpp"
namespace DSPCommands {
enum : u32 {
@ -7,8 +9,10 @@ namespace DSPCommands {
WriteProcessPipe = 0x000D0082,
ReadPipeIfPossible = 0x001000C0,
LoadComponent = 0x001100C2,
FlushDataCache = 0x00130082,
InvalidateDataCache = 0x00140082,
RegisterInterruptEvents = 0x00150082,
GetSemaphoreHandle = 0x00160000,
GetSemaphoreEventHandle = 0x00160000,
SetSemaphoreMask = 0x00170040,
GetHeadphoneStatus = 0x001F0000
};
@ -24,14 +28,25 @@ namespace Result {
void DSPService::reset() {
audioPipe.reset();
totalEventCount = 0;
semaphoreEvent = std::nullopt;
interrupt0 = std::nullopt;
interrupt1 = std::nullopt;
for (DSPEvent& e : pipeEvents) {
e = std::nullopt;
}
}
void DSPService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case DSPCommands::ConvertProcessAddressFromDspDram: convertProcessAddressFromDspDram(messagePointer); break;
case DSPCommands::FlushDataCache: flushDataCache(messagePointer); break;
case DSPCommands::InvalidateDataCache: invalidateDCache(messagePointer); break;
case DSPCommands::GetHeadphoneStatus: getHeadphoneStatus(messagePointer); break;
case DSPCommands::GetSemaphoreHandle: getSemaphoreHandle(messagePointer); break;
case DSPCommands::GetSemaphoreEventHandle: getSemaphoreEventHandle(messagePointer); break;
case DSPCommands::LoadComponent: loadComponent(messagePointer); break;
case DSPCommands::ReadPipeIfPossible: readPipeIfPossible(messagePointer); break;
case DSPCommands::RegisterInterruptEvents: registerInterruptEvents(messagePointer); break;
@ -45,8 +60,9 @@ void DSPService::handleSyncRequest(u32 messagePointer) {
void DSPService::convertProcessAddressFromDspDram(u32 messagePointer) {
const u32 address = mem.read32(messagePointer + 4);
log("DSP::ConvertProcessAddressFromDspDram (address = %08X)\n", address);
const u32 converted = (address << 1) + 0x1FF40000;
mem.write32(messagePointer, IPC::responseHeader(0xC, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, converted); // Converted address
}
@ -57,6 +73,7 @@ void DSPService::loadComponent(u32 messagePointer) {
u32 dataMask = mem.read32(messagePointer + 12);
log("DSP::LoadComponent (size = %08X, program mask = %X, data mask = %X\n", size, programMask, dataMask);
mem.write32(messagePointer, IPC::responseHeader(0x11, 2, 2));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 1); // Component loaded
mem.write32(messagePointer + 12, (size << 4) | 0xA);
@ -85,37 +102,81 @@ void DSPService::readPipeIfPossible(u32 messagePointer) {
mem.write16(buffer + i, pipe.readUnchecked());
}
mem.write32(messagePointer, IPC::responseHeader(0x10, 2, 2));
mem.write32(messagePointer + 4, Result::Success);
mem.write16(messagePointer + 8, i); // Number of bytes read
}
void DSPService::registerInterruptEvents(u32 messagePointer) {
u32 interrupt = mem.read32(messagePointer + 4);
u32 channel = mem.read32(messagePointer + 8);
u32 event = mem.read32(messagePointer + 16);
DSPService::DSPEvent& DSPService::getEventRef(u32 type, u32 pipe) {
switch (type) {
case 0: return interrupt0;
case 1: return interrupt1;
log("DSP::RegisterInterruptEvents (interrupt = %d, channel = %d, event = %d)\n", interrupt, channel, event);
mem.write32(messagePointer + 4, Result::Success);
case 2:
if (pipe >= pipeCount)
Helpers::panic("Tried to access the event of an invalid pipe");
return pipeEvents[pipe];
default:
Helpers::panic("Unknown type for DSP::getEventRef");
}
}
void DSPService::registerInterruptEvents(u32 messagePointer) {
const u32 interrupt = mem.read32(messagePointer + 4);
const u32 channel = mem.read32(messagePointer + 8);
const u32 eventHandle = mem.read32(messagePointer + 16);
log("DSP::RegisterInterruptEvents (interrupt = %d, channel = %d, event = %d)\n", interrupt, channel, eventHandle);
// The event handle being 0 means we're removing an event
if (eventHandle == 0) {
Helpers::panic("DSP::DSP::RegisterinterruptEvents Trying to remove a registered interrupt");
} else {
const KernelObject* object = kernel.getObject(eventHandle, KernelObjectType::Event);
if (!object) {
Helpers::panic("DSP::DSP::RegisterInterruptEvents with invalid event handle");
}
if (totalEventCount >= maxEventCount)
Helpers::panic("DSP::RegisterInterruptEvents overflowed total number of allowed events");
else {
getEventRef(interrupt, channel) = eventHandle;
mem.write32(messagePointer, IPC::responseHeader(0x15, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
totalEventCount++;
kernel.signalEvent(eventHandle);
}
}
}
void DSPService::getHeadphoneStatus(u32 messagePointer) {
log("DSP::GetHeadphoneStatus\n");
mem.write32(messagePointer, IPC::responseHeader(0x1F, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, Result::HeadphonesInserted); // This should be toggleable for shits and giggles
}
void DSPService::getSemaphoreHandle(u32 messagePointer) {
log("DSP::GetSemaphoreHandle\n");
void DSPService::getSemaphoreEventHandle(u32 messagePointer) {
log("DSP::GetSemaphoreEventHandle\n");
if (!semaphoreEvent.has_value()) {
semaphoreEvent = kernel.makeEvent(ResetType::OneShot);
}
mem.write32(messagePointer, IPC::responseHeader(0x16, 1, 2));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 12, 0xF9991234); // Semaphore handle (stubbed with random, obvious number)
// TODO: Translation descriptor here?
mem.write32(messagePointer + 12, semaphoreEvent.value()); // Semaphore event handle
kernel.signalEvent(semaphoreEvent.value());
}
void DSPService::setSemaphore(u32 messagePointer) {
const u16 value = mem.read16(messagePointer + 4);
log("DSP::SetSemaphore(value = %04X)\n", value);
mem.write32(messagePointer, IPC::responseHeader(0x7, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
@ -123,14 +184,46 @@ void DSPService::setSemaphoreMask(u32 messagePointer) {
const u16 mask = mem.read16(messagePointer + 4);
log("DSP::SetSemaphoreMask(mask = %04X)\n", mask);
mem.write32(messagePointer, IPC::responseHeader(0x17, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void DSPService::writeProcessPipe(u32 messagePointer) {
u32 channel = mem.read32(messagePointer + 4);
u32 size = mem.read32(messagePointer + 8);
u32 buffer = mem.read32(messagePointer + 16);
const u32 channel = mem.read32(messagePointer + 4);
const u32 size = mem.read32(messagePointer + 8);
const u32 buffer = mem.read32(messagePointer + 16);
log("DSP::writeProcessPipe (channel = %d, size = %X, buffer = %08X)\n", channel, size, buffer);
mem.write32(messagePointer, IPC::responseHeader(0xD, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void DSPService::flushDataCache(u32 messagePointer) {
const u32 address = mem.read32(messagePointer + 4);
const u32 size = mem.read32(messagePointer + 8);
const Handle process = mem.read32(messagePointer + 16);
log("DSP::FlushDataCache (addr = %08X, size = %08X, process = %X)\n", address, size, process);
mem.write32(messagePointer, IPC::responseHeader(0x13, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void DSPService::invalidateDCache(u32 messagePointer) {
const u32 address = mem.read32(messagePointer + 4);
const u32 size = mem.read32(messagePointer + 8);
const Handle process = mem.read32(messagePointer + 16);
log("DSP::InvalidateDataCache (addr = %08X, size = %08X, process = %X)\n", address, size, process);
mem.write32(messagePointer, IPC::responseHeader(0x14, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void DSPService::signalEvents() {
for (const DSPEvent& e : pipeEvents) {
if (e.has_value()) { kernel.signalEvent(e.value()); }
}
if (semaphoreEvent.has_value()) { kernel.signalEvent(semaphoreEvent.value()); }
if (interrupt0.has_value()) { kernel.signalEvent(interrupt0.value()); }
if (interrupt1.has_value()) { kernel.signalEvent(interrupt1.value()); }
}

143
src/core/services/frd.cpp Normal file
View file

@ -0,0 +1,143 @@
#include <string>
#include "services/frd.hpp"
#include "services/region_codes.hpp"
#include "ipc.hpp"
namespace FRDCommands {
enum : u32 {
AttachToEventNotification = 0x00200002,
SetNotificationMask = 0x00210040,
SetClientSdkVersion = 0x00320042,
GetMyFriendKey = 0x00050000,
GetMyProfile = 0x00070000,
GetMyPresence = 0x00080000,
GetMyScreenName = 0x00090000,
GetMyMii = 0x000A0000,
GetFriendKeyList = 0x00110080
};
}
namespace Result {
enum : u32 {
Success = 0,
};
}
void FRDService::reset() {}
void FRDService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case FRDCommands::AttachToEventNotification: attachToEventNotification(messagePointer); break;
case FRDCommands::GetFriendKeyList: getFriendKeyList(messagePointer); break;
case FRDCommands::GetMyFriendKey: getMyFriendKey(messagePointer); break;
case FRDCommands::GetMyMii: getMyMii(messagePointer); break;
case FRDCommands::GetMyPresence: getMyPresence(messagePointer); break;
case FRDCommands::GetMyProfile: getMyProfile(messagePointer); break;
case FRDCommands::GetMyScreenName: getMyScreenName(messagePointer); break;
case FRDCommands::SetClientSdkVersion: setClientSDKVersion(messagePointer); break;
case FRDCommands::SetNotificationMask: setNotificationMask(messagePointer); break;
default: Helpers::panic("FRD service requested. Command: %08X\n", command);
}
}
void FRDService::attachToEventNotification(u32 messagePointer) {
log("FRD::AttachToEventNotification (Undocumented)\n");
mem.write32(messagePointer + 4, Result::Success);
}
void FRDService::getMyFriendKey(u32 messagePointer) {
log("FRD::GetMyFriendKey\n");
mem.write32(messagePointer, IPC::responseHeader(0x5, 5, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 0); // Principal ID
mem.write32(messagePointer + 12, 0); // Padding (?)
mem.write32(messagePointer + 16, 0); // Local friend code
mem.write32(messagePointer + 20, 0);
}
void FRDService::getFriendKeyList(u32 messagePointer) {
log("FRD::GetFriendKeyList\n");
const u32 count = mem.read32(messagePointer + 8); // From what I understand this is a cap on the number of keys to receive?
constexpr u32 friendCount = 0; // And this should be the number of friends whose keys were actually received?
mem.write32(messagePointer, IPC::responseHeader(0x11, 2, 2));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, friendCount);
// Zero out friend keys
for (u32 i = 0; i < count * sizeof(FriendKey); i += 4) {
mem.write32(messagePointer + 12 + i, 0);
}
}
void FRDService::getMyPresence(u32 messagePointer) {
static constexpr u32 presenceSize = 0x12C; // A presence seems to be 12C bytes of data, not sure what it contains
log("FRD::GetMyPresence\n");
u32 buffer = mem.read32(messagePointer + 0x104); // Buffer to write presence info to.
for (u32 i = 0; i < presenceSize; i += 4) { // Clear presence info with 0s for now
mem.write32(buffer + i, 0);
}
mem.write32(messagePointer, IPC::responseHeader(0x8, 1, 2));
mem.write32(messagePointer + 4, Result::Success);
}
void FRDService::getMyProfile(u32 messagePointer) {
mem.write32(messagePointer, IPC::responseHeader(0x7, 3, 0)); // Not sure if the header here has the correct # of responses?
mem.write32(messagePointer + 4, Result::Success);
// TODO: Should maybe make these user-configurable. Not super important though
mem.write8(messagePointer + 8, static_cast<u8>(Regions::USA)); // Region
mem.write8(messagePointer + 9, static_cast<u8>(CountryCodes::US)); // Country
mem.write8(messagePointer + 10, 2); // Area (this should be Washington)
mem.write8(messagePointer + 11, static_cast<u8>(LanguageCodes::English)); // Language
mem.write8(messagePointer + 12, 2); // Platform (always 2 for CTR)
// Padding
mem.write8(messagePointer + 13, 0);
mem.write8(messagePointer + 14, 0);
mem.write8(messagePointer + 15, 0);
}
void FRDService::getMyScreenName(u32 messagePointer) {
log("FRD::GetMyScreenName\n");
static const std::u16string name = u"Pander";
mem.write32(messagePointer + 4, Result::Success);
// TODO: Assert the name fits in the response buffer
u32 pointer = messagePointer + 8;
for (auto c : name) {
mem.write16(pointer, static_cast<u16>(c));
pointer += sizeof(u16);
}
// Add null terminator
mem.write16(pointer, static_cast<u16>(u'\0'));
}
void FRDService::setClientSDKVersion(u32 messagePointer) {
u32 version = mem.read32(messagePointer + 4);
log("FRD::SetClientSdkVersion (version = %d)\n", version);
mem.write32(messagePointer, IPC::responseHeader(0x32, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void FRDService::setNotificationMask(u32 messagePointer) {
log("FRD::SetNotificationMask (Not documented)\n");
mem.write32(messagePointer, IPC::responseHeader(0x21, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void FRDService::getMyMii(u32 messagePointer) {
log("FRD::GetMyMii (stubbed)\n");
// TODO: How is the mii data even returned?
mem.write32(messagePointer, IPC::responseHeader(0xA, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
}

View file

@ -1,23 +1,41 @@
#include "services/fs.hpp"
#include "kernel/kernel.hpp"
#include "io_file.hpp"
#include "ipc.hpp"
#ifdef CreateFile // windows.h defines CreateFile & DeleteFile because of course it does.
#undef CreateDirectory
#undef CreateFile
#undef DeleteFile
#endif
namespace FSCommands {
enum : u32 {
Initialize = 0x08010002,
OpenFile = 0x080201C2,
OpenFileDirectly = 0x08030204,
DeleteFile = 0x08040142,
CreateFile = 0x08080202,
CreateDirectory = 0x08090182,
OpenDirectory = 0x080B0102,
OpenArchive = 0x080C00C2,
ControlArchive = 0x080D0144,
CloseArchive = 0x080E0080,
GetFreeBytes = 0x08120080,
IsSdmcDetected = 0x08170000,
GetFormatInfo = 0x084500C2,
FormatSaveData = 0x084C0242,
InitializeWithSdkVersion = 0x08610042,
SetPriority = 0x08620040,
GetPriority = 0x08630000
};
}
namespace Result {
namespace ResultCode {
enum : u32 {
Success = 0,
Failure = 0xFFFFFFFF
FileNotFound = 0xC8804464, // TODO: Verify this
Failure = 0xFFFFFFFF,
};
}
@ -25,23 +43,64 @@ void FSService::reset() {
priority = 0;
}
ArchiveBase* FSService::getArchiveFromID(u32 id) {
// Creates directories for NAND, ExtSaveData, etc if they don't already exist. Should be executed after loading a new ROM.
void FSService::initializeFilesystem() {
const auto nandPath = IOFile::getAppData() / "NAND"; // Create NAND
const auto cartPath = IOFile::getAppData() / "CartSave"; // Create cartridge save folder for use with ExtSaveData
const auto savePath = IOFile::getAppData() / "SaveData"; // Create SaveData
namespace fs = std::filesystem;
// TODO: SDMC, etc
if (!fs::is_directory(nandPath)) {
fs::create_directories(nandPath);
}
if (!fs::is_directory(cartPath)) {
fs::create_directories(cartPath);
}
if (!fs::is_directory(savePath)) {
fs::create_directories(savePath);
}
}
ArchiveBase* FSService::getArchiveFromID(u32 id, const FSPath& archivePath) {
switch (id) {
case ArchiveID::SelfNCCH: return &selfNcch;
case ArchiveID::SaveData: return &saveData;
case ArchiveID::ExtSaveData:
if (archivePath.type == PathType::Binary) {
switch (archivePath.binary[0]) {
case 0: return &extSaveData_nand;
case 1: return &extSaveData_cart;
}
}
return nullptr;
case ArchiveID::SharedExtSaveData:
if (archivePath.type == PathType::Binary) {
switch (archivePath.binary[0]) {
case 0: return &sharedExtSaveData_nand;
case 1: return &sharedExtSaveData_cart;
}
}
return nullptr;
case ArchiveID::SDMC: return &sdmc;
case ArchiveID::SavedataAndNcch: return &ncch; // This can only access NCCH outside of FSPXI
default:
Helpers::panic("Unknown archive. ID: %d\n", id);
return nullptr;
}
}
std::optional<Handle> FSService::openFileHandle(ArchiveBase* archive, const FSPath& path) {
bool opened = archive->openFile(path);
if (opened) {
std::optional<Handle> FSService::openFileHandle(ArchiveBase* archive, const FSPath& path, const FSPath& archivePath, const FilePerms& perms) {
FileDescriptor opened = archive->openFile(path, perms);
if (opened.has_value()) { // If opened doesn't have a value, we failed to open the file
auto handle = kernel.makeObject(KernelObjectType::File);
auto& file = kernel.getObjects()[handle];
file.data = new FileSession(archive, path);
file.data = new FileSession(archive, path, archivePath, opened.value());
return handle;
} else {
@ -49,19 +108,32 @@ std::optional<Handle> FSService::openFileHandle(ArchiveBase* archive, const FSPa
}
}
Rust::Result<Handle, FSResult> FSService::openDirectoryHandle(ArchiveBase* archive, const FSPath& path) {
Rust::Result<DirectorySession, FSResult> opened = archive->openDirectory(path);
if (opened.isOk()) { // If opened doesn't have a value, we failed to open the directory
auto handle = kernel.makeObject(KernelObjectType::Directory);
auto& object = kernel.getObjects()[handle];
object.data = new DirectorySession(opened.unwrap());
return Ok(handle);
} else {
return Err(opened.unwrapErr());
}
}
std::optional<Handle> FSService::openArchiveHandle(u32 archiveID, const FSPath& path) {
ArchiveBase* archive = getArchiveFromID(archiveID);
ArchiveBase* archive = getArchiveFromID(archiveID, path);
if (archive == nullptr) [[unlikely]] {
Helpers::panic("OpenArchive: Tried to open unknown archive %d.", archiveID);
return std::nullopt;
}
bool opened = archive->openArchive(path);
if (opened) {
Rust::Result<ArchiveBase*, FSResult> res = archive->openArchive(path);
if (res.isOk()) {
auto handle = kernel.makeObject(KernelObjectType::Archive);
auto& archiveObject = kernel.getObjects()[handle];
archiveObject.data = new ArchiveSession(archive, path);
archiveObject.data = new ArchiveSession(res.unwrap(), path);
return handle;
}
@ -70,16 +142,35 @@ std::optional<Handle> FSService::openArchiveHandle(u32 archiveID, const FSPath&
}
}
FSPath FSService::readPath(u32 type, u32 pointer, u32 size) {
std::vector<u8> data;
data.resize(size);
for (u32 i = 0; i < size; i++)
data[i] = mem.read8(pointer + i);
return FSPath(type, data);
}
void FSService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case FSCommands::CreateDirectory: createDirectory(messagePointer); break;
case FSCommands::CreateFile: createFile(messagePointer); break;
case FSCommands::ControlArchive: controlArchive(messagePointer); break;
case FSCommands::CloseArchive: closeArchive(messagePointer); break;
case FSCommands::DeleteFile: deleteFile(messagePointer); break;
case FSCommands::FormatSaveData: formatSaveData(messagePointer); break;
case FSCommands::GetFreeBytes: getFreeBytes(messagePointer); break;
case FSCommands::GetFormatInfo: getFormatInfo(messagePointer); break;
case FSCommands::GetPriority: getPriority(messagePointer); break;
case FSCommands::Initialize: initialize(messagePointer); break;
case FSCommands::InitializeWithSdkVersion: initializeWithSdkVersion(messagePointer); break;
case FSCommands::IsSdmcDetected: isSdmcDetected(messagePointer); break;
case FSCommands::OpenArchive: openArchive(messagePointer); break;
case FSCommands::OpenFile: openFile(messagePointer); break;
case FSCommands::OpenFileDirectly: openFileDirectly(messagePointer); break;
case FSCommands::OpenDirectory: openDirectory(messagePointer); break;
case FSCommands::OpenFile: [[likely]] openFile(messagePointer); break;
case FSCommands::OpenFileDirectly: [[likely]] openFileDirectly(messagePointer); break;
case FSCommands::SetPriority: setPriority(messagePointer); break;
default: Helpers::panic("FS service requested. Command: %08X\n", command);
}
@ -87,7 +178,8 @@ void FSService::handleSyncRequest(u32 messagePointer) {
void FSService::initialize(u32 messagePointer) {
log("FS::Initialize\n");
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer, IPC::responseHeader(0x801, 1, 0));
mem.write32(messagePointer + 4, ResultCode::Success);
}
// TODO: Figure out how this is different from Initialize
@ -95,7 +187,8 @@ void FSService::initializeWithSdkVersion(u32 messagePointer) {
const auto version = mem.read32(messagePointer + 4);
log("FS::InitializeWithSDKVersion(version = %d)\n", version);
initialize(messagePointer);
mem.write32(messagePointer, IPC::responseHeader(0x861, 1, 0));
mem.write32(messagePointer + 4, ResultCode::Success);
}
void FSService::closeArchive(u32 messagePointer) {
@ -103,12 +196,14 @@ void FSService::closeArchive(u32 messagePointer) {
const auto object = kernel.getObject(handle, KernelObjectType::Archive);
log("FSService::CloseArchive(handle = %X)\n", handle);
mem.write32(messagePointer, IPC::responseHeader(0x80E, 1, 0));
if (object == nullptr) {
log("FSService::CloseArchive: Tried to close invalid archive %X\n", handle);
mem.write32(messagePointer + 4, Result::Failure);
mem.write32(messagePointer + 4, ResultCode::Failure);
} else {
object->getData<ArchiveSession>()->isOpen = false;
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 4, ResultCode::Success);
}
}
@ -117,22 +212,23 @@ void FSService::openArchive(u32 messagePointer) {
const u32 archivePathType = mem.read32(messagePointer + 8);
const u32 archivePathSize = mem.read32(messagePointer + 12);
const u32 archivePathPointer = mem.read32(messagePointer + 20);
FSPath archivePath{ .type = archivePathType, .size = archivePathSize, .pointer = archivePathPointer };
auto archivePath = readPath(archivePathType, archivePathPointer, archivePathSize);
log("FS::OpenArchive(archive ID = %d, archive path type = %d)\n", archiveID, archivePathType);
std::optional<Handle> handle = openArchiveHandle(archiveID, archivePath);
mem.write32(messagePointer, IPC::responseHeader(0x80C, 3, 0));
if (handle.has_value()) {
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 4, ResultCode::Success);
mem.write64(messagePointer + 8, handle.value());
} else {
log("FS::OpenArchive: Failed to open archive with id = %d\n", archiveID);
mem.write32(messagePointer + 4, Result::Failure);
mem.write32(messagePointer + 4, ResultCode::Failure);
}
}
void FSService::openFile(u32 messagePointer) {
const u32 archiveHandle = mem.read64(messagePointer + 8);
const Handle archiveHandle = mem.read64(messagePointer + 8);
const u32 filePathType = mem.read32(messagePointer + 16);
const u32 filePathSize = mem.read32(messagePointer + 20);
const u32 openFlags = mem.read32(messagePointer + 24);
@ -144,24 +240,79 @@ void FSService::openFile(u32 messagePointer) {
auto archiveObject = kernel.getObject(archiveHandle, KernelObjectType::Archive);
if (archiveObject == nullptr) [[unlikely]] {
log("FS::OpenFile: Invalid archive handle %d\n", archiveHandle);
mem.write32(messagePointer + 4, Result::Failure);
mem.write32(messagePointer + 4, ResultCode::Failure);
return;
}
ArchiveBase* archive = archiveObject->getData<ArchiveSession>()->archive;
FSPath filePath{ .type = filePathType, .size = filePathSize, .pointer = filePathPointer };
const FSPath& archivePath = archiveObject->getData<ArchiveSession>()->path;
std::optional<Handle> handle = openFileHandle(archive, filePath);
auto filePath = readPath(filePathType, filePathPointer, filePathSize);
const FilePerms perms(openFlags);
std::optional<Handle> handle = openFileHandle(archive, filePath, archivePath, perms);
mem.write32(messagePointer, IPC::responseHeader(0x802, 1, 2));
if (!handle.has_value()) {
Helpers::panic("OpenFile: Failed to open file with given path");
}
else {
mem.write32(messagePointer + 4, Result::Success);
printf("OpenFile failed\n");
mem.write32(messagePointer + 4, ResultCode::FileNotFound);
} else {
mem.write32(messagePointer + 4, ResultCode::Success);
mem.write32(messagePointer + 8, 0x10); // "Move handle descriptor"
mem.write32(messagePointer + 12, handle.value());
}
}
void FSService::createDirectory(u32 messagePointer) {
log("FS::CreateDirectory\n");
const Handle archiveHandle = (Handle)mem.read64(messagePointer + 8);
const u32 pathType = mem.read32(messagePointer + 16);
const u32 pathSize = mem.read32(messagePointer + 20);
const u32 pathPointer = mem.read32(messagePointer + 32);
KernelObject* archiveObject = kernel.getObject(archiveHandle, KernelObjectType::Archive);
if (archiveObject == nullptr) [[unlikely]] {
log("FS::CreateDirectory: Invalid archive handle %d\n", archiveHandle);
mem.write32(messagePointer + 4, ResultCode::Failure);
return;
}
ArchiveBase* archive = archiveObject->getData<ArchiveSession>()->archive;
const auto dirPath = readPath(pathType, pathPointer, pathSize);
const FSResult res = archive->createDirectory(dirPath);
mem.write32(messagePointer, IPC::responseHeader(0x809, 1, 0));
mem.write32(messagePointer + 4, static_cast<u32>(res));
}
void FSService::openDirectory(u32 messagePointer) {
log("FS::OpenDirectory\n");
const Handle archiveHandle = (Handle)mem.read64(messagePointer + 4);
const u32 pathType = mem.read32(messagePointer + 12);
const u32 pathSize = mem.read32(messagePointer + 16);
const u32 pathPointer = mem.read32(messagePointer + 24);
KernelObject* archiveObject = kernel.getObject(archiveHandle, KernelObjectType::Archive);
if (archiveObject == nullptr) [[unlikely]] {
log("FS::OpenDirectory: Invalid archive handle %d\n", archiveHandle);
mem.write32(messagePointer + 4, ResultCode::Failure);
return;
}
ArchiveBase* archive = archiveObject->getData<ArchiveSession>()->archive;
const auto dirPath = readPath(pathType, pathPointer, pathSize);
auto dir = openDirectoryHandle(archive, dirPath);
mem.write32(messagePointer, IPC::responseHeader(0x80B, 1, 2));
if (dir.isOk()) {
mem.write32(messagePointer + 4, ResultCode::Success);
mem.write32(messagePointer + 12, dir.unwrap());
} else {
printf("FS::OpenDirectory failed\n");
mem.write32(messagePointer + 4, static_cast<u32>(dir.unwrapErr()));
}
}
void FSService::openFileDirectly(u32 messagePointer) {
const u32 archiveID = mem.read32(messagePointer + 8);
const u32 archivePathType = mem.read32(messagePointer + 12);
@ -172,42 +323,194 @@ void FSService::openFileDirectly(u32 messagePointer) {
const u32 attributes = mem.read32(messagePointer + 32);
const u32 archivePathPointer = mem.read32(messagePointer + 40);
const u32 filePathPointer = mem.read32(messagePointer + 48);
log("FS::OpenFileDirectly\n");
ArchiveBase* archive = getArchiveFromID(archiveID);
auto archivePath = readPath(archivePathType, archivePathPointer, archivePathSize);
ArchiveBase* archive = getArchiveFromID(archiveID, archivePath);
if (archive == nullptr) [[unlikely]] {
Helpers::panic("OpenFileDirectly: Tried to open unknown archive %d.", archiveID);
}
auto filePath = readPath(filePathType, filePathPointer, filePathSize);
const FilePerms perms(openFlags);
FSPath archivePath { .type = archivePathType, .size = archivePathSize, .pointer = archivePathPointer };
FSPath filePath { .type = filePathType, .size = filePathSize, .pointer = filePathPointer };
archive = archive->openArchive(archivePath);
if (archive == nullptr) [[unlikely]] {
Rust::Result<ArchiveBase*, FSResult> res = archive->openArchive(archivePath);
if (res.isErr()) [[unlikely]] {
Helpers::panic("OpenFileDirectly: Failed to open archive with given path");
}
archive = res.unwrap();
std::optional<Handle> handle = openFileHandle(archive, filePath);
std::optional<Handle> handle = openFileHandle(archive, filePath, archivePath, perms);
mem.write32(messagePointer, IPC::responseHeader(0x803, 1, 2));
if (!handle.has_value()) {
Helpers::panic("OpenFileDirectly: Failed to open file with given path");
} else {
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 4, ResultCode::Success);
mem.write32(messagePointer + 12, handle.value());
}
}
void FSService::createFile(u32 messagePointer) {
const Handle archiveHandle = mem.read64(messagePointer + 8);
const u32 filePathType = mem.read32(messagePointer + 16);
const u32 filePathSize = mem.read32(messagePointer + 20);
const u32 attributes = mem.read32(messagePointer + 24);
const u64 size = mem.read64(messagePointer + 28);
const u32 filePathPointer = mem.read32(messagePointer + 40);
log("FS::CreateFile\n");
auto archiveObject = kernel.getObject(archiveHandle, KernelObjectType::Archive);
if (archiveObject == nullptr) [[unlikely]] {
log("FS::OpenFile: Invalid archive handle %d\n", archiveHandle);
mem.write32(messagePointer + 4, ResultCode::Failure);
return;
}
ArchiveBase* archive = archiveObject->getData<ArchiveSession>()->archive;
auto filePath = readPath(filePathType, filePathPointer, filePathSize);
FSResult res = archive->createFile(filePath, size);
mem.write32(messagePointer, IPC::responseHeader(0x808, 1, 0));
mem.write32(messagePointer + 4, static_cast<u32>(res));
}
void FSService::deleteFile(u32 messagePointer) {
const Handle archiveHandle = mem.read64(messagePointer + 8);
const u32 filePathType = mem.read32(messagePointer + 16);
const u32 filePathSize = mem.read32(messagePointer + 20);
const u32 filePathPointer = mem.read32(messagePointer + 28);
log("FS::DeleteFile\n");
auto archiveObject = kernel.getObject(archiveHandle, KernelObjectType::Archive);
if (archiveObject == nullptr) [[unlikely]] {
log("FS::DeleteFile: Invalid archive handle %d\n", archiveHandle);
mem.write32(messagePointer + 4, ResultCode::Failure);
return;
}
ArchiveBase* archive = archiveObject->getData<ArchiveSession>()->archive;
auto filePath = readPath(filePathType, filePathPointer, filePathSize);
FSResult res = archive->deleteFile(filePath);
mem.write32(messagePointer, IPC::responseHeader(0x804, 1, 0));
mem.write32(messagePointer + 4, static_cast<u32>(res));
}
void FSService::getFormatInfo(u32 messagePointer) {
const u32 archiveID = mem.read32(messagePointer + 4);
const u32 pathType = mem.read32(messagePointer + 8);
const u32 pathSize = mem.read32(messagePointer + 12);
const u32 pathPointer = mem.read32(messagePointer + 20);
const auto path = readPath(pathType, pathPointer, pathSize);
log("FS::GetFormatInfo(archive ID = %d, archive path type = %d)\n", archiveID, pathType);
ArchiveBase* archive = getArchiveFromID(archiveID, path);
if (archive == nullptr) [[unlikely]] {
Helpers::panic("OpenArchive: Tried to open unknown archive %d.", archiveID);
}
ArchiveBase::FormatInfo info = archive->getFormatInfo(path);
mem.write32(messagePointer, IPC::responseHeader(0x845, 5, 0));
mem.write32(messagePointer + 4, ResultCode::Success);
mem.write32(messagePointer + 8, info.size);
mem.write32(messagePointer + 12, info.numOfDirectories);
mem.write32(messagePointer + 16, info.numOfFiles);
mem.write8(messagePointer + 20, info.duplicateData ? 1 : 0);
}
void FSService::formatSaveData(u32 messagePointer) {
const u32 archiveID = mem.read32(messagePointer + 4);
if (archiveID != ArchiveID::SaveData)
Helpers::panic("FS::FormatSaveData: Archive is not SaveData");
// Read path and path info
const u32 pathType = mem.read32(messagePointer + 8);
const u32 pathSize = mem.read32(messagePointer + 12);
const u32 pathPointer = mem.read32(messagePointer + 44);
auto path = readPath(pathType, pathPointer, pathSize);
// Size of a block. Seems to always be 0x200
const u32 blockSize = mem.read32(messagePointer + 16);
if (blockSize != 0x200 && blockSize != 0x1000)
Helpers::panic("FS::FormatSaveData: Invalid SaveData block size");
const u32 directoryNum = mem.read32(messagePointer + 20); // Max number of directories
const u32 fileNum = mem.read32(messagePointer + 24); // Max number of files
const u32 directoryBucketNum = mem.read32(messagePointer + 28); // Not sure what a directory bucket is...?
const u32 fileBucketNum = mem.read32(messagePointer + 32); // Same here
const bool duplicateData = mem.read8(messagePointer + 36) != 0;
printf("Stubbed FS::FormatSaveData. File num: %d, directory num: %d\n", fileNum, directoryNum);
mem.write32(messagePointer, IPC::responseHeader(0x84C, 1, 0));
mem.write32(messagePointer + 4, ResultCode::Success);
}
void FSService::controlArchive(u32 messagePointer) {
const Handle archiveHandle = mem.read64(messagePointer + 4);
const u32 action = mem.read32(messagePointer + 12);
const u32 inputSize = mem.read32(messagePointer + 16);
const u32 outputSize = mem.read32(messagePointer + 20);
const u32 input = mem.read32(messagePointer + 28);
const u32 output = mem.read32(messagePointer + 36);
log("FS::ControlArchive (action = %X, handle = %X)\n", action, archiveHandle);
auto archiveObject = kernel.getObject(archiveHandle, KernelObjectType::Archive);
mem.write32(messagePointer, IPC::responseHeader(0x80D, 1, 0));
if (archiveObject == nullptr) [[unlikely]] {
log("FS::ControlArchive: Invalid archive handle %d\n", archiveHandle);
mem.write32(messagePointer + 4, ResultCode::Failure);
return;
}
switch (action) {
case 0: // Commit save data changes. Shouldn't need us to do anything
mem.write32(messagePointer + 4, ResultCode::Success);
break;
default:
Helpers::panic("Unimplemented action for ControlArchive (action = %X)\n", action);
break;
}
}
void FSService::getFreeBytes(u32 messagePointer) {
log("FS::GetFreeBytes\n");
const Handle archiveHandle = (Handle)mem.read64(messagePointer + 4);
auto session = kernel.getObject(archiveHandle, KernelObjectType::Archive);
mem.write32(messagePointer, IPC::responseHeader(0x812, 3, 0));
if (session == nullptr) [[unlikely]] {
log("FS::GetFreeBytes: Invalid archive handle %d\n", archiveHandle);
mem.write32(messagePointer + 4, ResultCode::Failure);
return;
}
const u64 bytes = session->getData<ArchiveSession>()->archive->getFreeBytes();
mem.write64(messagePointer + 8, bytes);
}
void FSService::getPriority(u32 messagePointer) {
log("FS::GetPriority\n");
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer, IPC::responseHeader(0x863, 2, 0));
mem.write32(messagePointer + 4, ResultCode::Success);
mem.write32(messagePointer + 8, priority);
}
void FSService::setPriority(u32 messagePointer) {
const u32 value = mem.read32(messagePointer + 4);
log("FS::SetPriority (priority = %d)\n", value);
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer, IPC::responseHeader(0x862, 1, 0));
mem.write32(messagePointer + 4, ResultCode::Success);
priority = value;
}
void FSService::isSdmcDetected(u32 messagePointer) {
log("FS::IsSdmcDetected\n");
mem.write32(messagePointer, IPC::responseHeader(0x817, 2, 0));
mem.write32(messagePointer + 4, ResultCode::Success);
mem.write32(messagePointer + 8, 0); // Whether SD is detected. For now we emulate a 3DS without an SD.
}

View file

@ -1,4 +1,6 @@
#include "services/gsp_gpu.hpp"
#include "ipc.hpp"
#include "kernel.hpp"
// Commands used with SendSyncRequest targetted to the GSP::GPU service
namespace ServiceCommands {
@ -11,7 +13,8 @@ namespace ServiceCommands {
FlushDataCache = 0x00080082,
SetLCDForceBlack = 0x000B0040,
TriggerCmdReqQueue = 0x000C0000,
SetInternalPriorities = 0x001E0080
SetInternalPriorities = 0x001E0080,
StoreDataCache = 0x001F0082
};
}
@ -22,6 +25,7 @@ namespace GXCommands {
ProcessCommandList = 1,
MemoryFill = 2,
TriggerDisplayTransfer = 3,
TriggerTextureCopy = 4,
FlushCacheRegions = 5
};
}
@ -35,6 +39,7 @@ namespace Result {
void GPUService::reset() {
privilegedProcess = 0xFFFFFFFF; // Set the privileged process to an invalid handle
interruptEvent = std::nullopt;
sharedMem = nullptr;
}
@ -47,6 +52,7 @@ void GPUService::handleSyncRequest(u32 messagePointer) {
case ServiceCommands::SetAxiConfigQoSMode: setAxiConfigQoSMode(messagePointer); break;
case ServiceCommands::SetInternalPriorities: setInternalPriorities(messagePointer); break;
case ServiceCommands::SetLCDForceBlack: setLCDForceBlack(messagePointer); break;
case ServiceCommands::StoreDataCache: storeDataCache(messagePointer); break;
case ServiceCommands::TriggerCmdReqQueue: [[likely]] triggerCmdReqQueue(messagePointer); break;
case ServiceCommands::WriteHwRegs: writeHwRegs(messagePointer); break;
case ServiceCommands::WriteHwRegsWithMask: writeHwRegsWithMask(messagePointer); break;
@ -69,6 +75,7 @@ void GPUService::acquireRight(u32 messagePointer) {
privilegedProcess = pid;
}
mem.write32(messagePointer, IPC::responseHeader(0x16, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
@ -85,23 +92,59 @@ void GPUService::registerInterruptRelayQueue(u32 messagePointer) {
const u32 eventHandle = mem.read32(messagePointer + 12);
log("GSP::GPU::RegisterInterruptRelayQueue (flags = %X, event handle = %X)\n", flags, eventHandle);
mem.write32(messagePointer + 4, Result::SuccessRegisterIRQ);
const auto event = kernel.getObject(eventHandle, KernelObjectType::Event);
if (event == nullptr) { // Check if interrupt event is invalid
Helpers::panic("Invalid event passed to GSP::GPU::RegisterInterruptRelayQueue");
} else {
interruptEvent = eventHandle;
}
mem.write32(messagePointer, IPC::responseHeader(0x13, 2, 2));
mem.write32(messagePointer + 4, Result::SuccessRegisterIRQ); // First init returns a unique result
mem.write32(messagePointer + 8, 0); // TODO: GSP module thread index
mem.write32(messagePointer + 12, 0); // Translation descriptor
mem.write32(messagePointer + 16, KernelHandles::GSPSharedMemHandle);
}
void GPUService::requestInterrupt(GPUInterrupt type) {
// HACK: Signal DSP events on GPU interrupt for now until we have the DSP since games need DSP events
// Maybe there's a better alternative?
kernel.signalDSPEvents();
if (sharedMem == nullptr) [[unlikely]] { // Shared memory hasn't been set up yet
return;
}
// TODO: Add support for multiple GSP threads
u8 index = sharedMem[0]; // The interrupt block is normally located at sharedMem + processGSPIndex*0x40
u8& interruptCount = sharedMem[1];
u8 flagIndex = (index + interruptCount) % 0x34;
interruptCount++;
sharedMem[0xC + flagIndex] = static_cast<u8>(type);
sharedMem[2] = 0; // Set error code to 0
sharedMem[0xC + flagIndex] = static_cast<u8>(type); // Write interrupt type to queue
// Update framebuffer info in shared memory
// Most new games check to make sure that the "flag" byte of the framebuffer info header is set to 0
// 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];
if (dirtyFlag & 1) {
// TODO: Submit buffer info here
dirtyFlag &= ~1;
}
}
// Signal interrupt event
if (interruptEvent.has_value()) {
kernel.signalEvent(interruptEvent.value());
}
}
void GPUService::writeHwRegs(u32 messagePointer) {
@ -130,6 +173,8 @@ void GPUService::writeHwRegs(u32 messagePointer) {
dataPointer += 4;
ioAddr += 4;
}
mem.write32(messagePointer, IPC::responseHeader(0x1, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
@ -172,6 +217,7 @@ void GPUService::writeHwRegsWithMask(u32 messagePointer) {
ioAddr += 4;
}
mem.write32(messagePointer, IPC::responseHeader(0x2, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
@ -181,6 +227,17 @@ void GPUService::flushDataCache(u32 messagePointer) {
u32 processHandle = handle = mem.read32(messagePointer + 16);
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);
}
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);
mem.write32(messagePointer, IPC::responseHeader(0x1F, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
@ -192,23 +249,27 @@ void GPUService::setLCDForceBlack(u32 messagePointer) {
printf("Filled both LCDs with black\n");
}
mem.write32(messagePointer, IPC::responseHeader(0xB, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void GPUService::triggerCmdReqQueue(u32 messagePointer) {
processCommandBuffer();
mem.write32(messagePointer, IPC::responseHeader(0xC, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
// Seems to be completely undocumented, probably not very important or useful
void GPUService::setAxiConfigQoSMode(u32 messagePointer) {
log("GSP::GPU::SetAxiConfigQoSMode\n");
mem.write32(messagePointer, IPC::responseHeader(0x10, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
// Seems to also be completely undocumented
void GPUService::setInternalPriorities(u32 messagePointer) {
log("GSP::GPU::SetInternalPriorities\n");
mem.write32(messagePointer, IPC::responseHeader(0x1E, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
@ -233,6 +294,7 @@ void GPUService::processCommandBuffer() {
case GXCommands::MemoryFill: memoryFill(cmd); break;
case GXCommands::TriggerDisplayTransfer: triggerDisplayTransfer(cmd); break;
case GXCommands::TriggerDMARequest: triggerDMARequest(cmd); break;
case GXCommands::TriggerTextureCopy: triggerTextureCopy(cmd); break;
case GXCommands::FlushCacheRegions: flushCacheRegions(cmd); break;
default: Helpers::panic("GSP::GPU::ProcessCommands: Unknown cmd ID %d", cmdID);
}
@ -260,25 +322,35 @@ void GPUService::memoryFill(u32* cmd) {
if (start0 != 0) {
gpu.clearBuffer(start0, end0, value0, control0);
requestInterrupt(GPUInterrupt::PSC0);
}
if (start1 != 0) {
gpu.clearBuffer(start1, end1, value1, control1);
requestInterrupt(GPUInterrupt::PSC1);
}
}
void GPUService::triggerDisplayTransfer(u32* cmd) {
const u32 inputAddr = cmd[1];
const u32 outputAddr = cmd[2];
const u32 inputSize = cmd[3];
const u32 outputSize = cmd[4];
const u32 flags = cmd[5];
log("GSP::GPU::TriggerDisplayTransfer (Stubbed)\n");
gpu.displayTransfer(inputAddr, outputAddr, inputSize, outputSize, flags);
requestInterrupt(GPUInterrupt::PPF); // Send "Display transfer finished" interrupt
}
void GPUService::triggerDMARequest(u32* cmd) {
u32 source = cmd[1];
u32 dest = cmd[2];
u32 size = cmd[3];
bool flush = cmd[7] == 1;
const u32 source = cmd[1];
const u32 dest = cmd[2];
const u32 size = cmd[3];
const bool flush = cmd[7] == 1;
log("GSP::GPU::TriggerDMARequest (source = %08X, dest = %08X, size = %08X) (Unimplemented)\n", source, dest, size);
log("GSP::GPU::TriggerDMARequest (source = %08X, dest = %08X, size = %08X)\n", source, dest, size);
gpu.fireDMA(dest, source, size);
requestInterrupt(GPUInterrupt::DMA);
}
@ -288,60 +360,21 @@ void GPUService::flushCacheRegions(u32* cmd) {
// Actually send command list (aka display list) to GPU
void GPUService::processCommandList(u32* cmd) {
u32 address = cmd[1] & ~7; // Buffer address
u32 size = cmd[2] & ~3; // Buffer size in bytes
bool updateGas = cmd[3] == 1; // Update gas additive blend results (0 = don't update, 1 = update)
bool flushBuffer = cmd[7] == 1; // Flush buffer (0 = don't flush, 1 = flush)
u32* bufferStart = static_cast<u32*>(mem.getReadPointer(address));
if (!bufferStart) Helpers::panic("Couldn't get buffer for command list");
// TODO: This is very memory unsafe. We get a pointer to FCRAM and just keep writing without checking if we're gonna go OoB
u32* curr = bufferStart;
u32* bufferEnd = bufferStart + (size / sizeof(u32));
// LUT for converting the parameter mask to an actual 32-bit mask
// The parameter mask is 4 bits long, each bit corresponding to one byte of the mask
// If the bit is 0 then the corresponding mask byte is 0, otherwise the mask byte is 0xff
// So for example if the parameter mask is 0b1001, the full mask is 0xff'00'00'ff
static constexpr std::array<u32, 16> maskLUT = {
0x00000000, 0x000000ff, 0x0000ff00, 0x0000ffff, 0x00ff0000, 0x00ff00ff, 0x00ffff00, 0x00ffffff,
0xff000000, 0xff0000ff, 0xff00ff00, 0xff00ffff, 0xffff0000, 0xffff00ff, 0xffffff00, 0xffffffff,
};
while (curr < bufferEnd) {
// If the buffer is not aligned to an 8 byte boundary, force align it by moving the pointer up a word
// The curr pointer starts out doubleword-aligned and is increased by 4 bytes each time
// So to check if it is aligned, we get the number of words it's been incremented by
// If that number is an odd value then the buffer is not aligned, otherwise it is
if ((curr - bufferStart) % 2 != 0) {
curr++;
}
// The first word of a command is the command parameter and the second one is the header
u32 param1 = *curr++;
u32 header = *curr++;
u32 id = header & 0xffff;
u32 paramMaskIndex = (header >> 16) & 0xf;
u32 paramCount = (header >> 20) & 0xff; // Number of additional parameters
// Bit 31 tells us whether this command is going to write to multiple sequential registers (if the bit is 1)
// Or if all written values will go to the same register (If the bit is 0). It's essentially the value that
// gets added to the "id" field after each register write
bool consecutiveWritingMode = (header >> 31) != 0;
u32 mask = maskLUT[paramMaskIndex]; // Actual parameter mask
// Increment the ID by 1 after each write if we're in consecutive mode, or 0 otherwise
u32 idIncrement = (consecutiveWritingMode) ? 1 : 0;
gpu.writeInternalReg(id, param1, mask);
for (u32 i = 0; i < paramCount; i++) {
id += idIncrement;
u32 param = *curr++;
gpu.writeInternalReg(id, param, mask);
}
}
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)
log("GPU::GSP::processCommandList. Address: %08X, size in bytes: %08X\n", address, size);
gpu.startCommandList(address, size);
requestInterrupt(GPUInterrupt::P3D); // Send an IRQ when command list processing is over
}
// 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");
// This uses the transfer engine and thus needs to fire a PPF interrupt.
// NSMB2 relies on this
requestInterrupt(GPUInterrupt::PPF);
}

View file

@ -1,4 +1,5 @@
#include "services/gsp_lcd.hpp"
#include "ipc.hpp"
namespace LCDCommands {
enum : u32 {

Some files were not shown because too many files have changed in this diff Show more