mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-07 14:45:41 +12:00
Merge pull request #1 from wheremyfoodat/Sync-Objects
Make the emulator actually boot games
This commit is contained in:
commit
b01cbc588a
121 changed files with 295783 additions and 1016 deletions
2
.github/workflows/Linux_Build.yml
vendored
2
.github/workflows/Linux_Build.yml
vendored
|
@ -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
|
||||
|
|
6
.github/workflows/MacOS_Build.yml
vendored
6
.github/workflows/MacOS_Build.yml
vendored
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
17
include/colour.hpp
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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) {}
|
||||
};
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
22
include/fs/archive_ext_save_data.hpp
Normal file
22
include/fs/archive_ext_save_data.hpp
Normal 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.
|
||||
};
|
|
@ -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());
|
||||
}
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
29
include/fs/archive_self_ncch.hpp
Normal file
29
include/fs/archive_self_ncch.hpp
Normal 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());
|
||||
}
|
||||
};
|
132
include/fs/bad_word_list.hpp
Normal file
132
include/fs/bad_word_list.hpp
Normal 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
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
6140
include/fs/mii_data.hpp
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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;
|
||||
|
|
|
@ -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
9
include/ipc.hpp
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(); }
|
||||
};
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -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> \
|
||||
|
|
|
@ -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; }
|
||||
};
|
|
@ -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>
|
||||
|
|
99
include/renderer_gl/renderer_gl.hpp
Normal file
99
include/renderer_gl/renderer_gl.hpp
Normal 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;
|
||||
};
|
66
include/renderer_gl/surface_cache.hpp
Normal file
66
include/renderer_gl/surface_cache.hpp
Normal 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];
|
||||
}
|
||||
};
|
184
include/renderer_gl/surfaces.hpp
Normal file
184
include/renderer_gl/surfaces.hpp
Normal 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();
|
||||
}
|
||||
};
|
85
include/renderer_gl/textures.hpp
Normal file
85
include/renderer_gl/textures.hpp
Normal 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
19
include/services/ac.hpp
Normal 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
19
include/services/act.hpp
Normal 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
20
include/services/am.hpp
Normal 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);
|
||||
};
|
|
@ -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
27
include/services/boss.hpp
Normal 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
20
include/services/cam.hpp
Normal 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);
|
||||
};
|
|
@ -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);
|
||||
};
|
|
@ -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:
|
||||
|
|
|
@ -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
36
include/services/frd.hpp
Normal 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);
|
||||
};
|
|
@ -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();
|
||||
};
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
20
include/services/ldr_ro.hpp
Normal file
20
include/services/ldr_ro.hpp
Normal 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);
|
||||
};
|
|
@ -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) {}
|
||||
|
|
|
@ -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
28
include/services/nfc.hpp
Normal 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
19
include/services/nim.hpp
Normal 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);
|
||||
};
|
|
@ -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) {}
|
||||
|
|
|
@ -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,
|
||||
};
|
|
@ -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(); }
|
||||
};
|
5
include/services/shared_font.hpp
Normal file
5
include/services/shared_font.hpp
Normal 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
94
include/services/y2r.hpp
Normal 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
23
include/system_models.hpp
Normal 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
|
||||
};
|
||||
}
|
|
@ -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
|
509
src/core/CPU/dynarmic_cycles.cpp
Normal file
509
src/core/CPU/dynarmic_cycles.cpp
Normal 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;
|
||||
}
|
|
@ -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], ¤tAttributes[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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
|
|
108
src/core/fs/archive_ext_save_data.cpp
Normal file
108
src/core/fs/archive_ext_save_data.cpp
Normal 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;
|
||||
}
|
|
@ -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++) {
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
118
src/core/fs/archive_self_ncch.cpp
Normal file
118
src/core/fs/archive_self_ncch.cpp
Normal 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;
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
46
src/core/kernel/directory_operations.cpp
Normal file
46
src/core/kernel/directory_operations.cpp
Normal 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);
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
70
src/core/kernel/idle_thread.cpp
Normal file
70
src/core/kernel/idle_thread.cpp
Normal 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();
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
80
src/core/loader/lz77.cpp
Normal 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;
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
119
src/core/renderer_gl/etc1.cpp
Normal file
119
src/core/renderer_gl/etc1.cpp
Normal 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);
|
||||
}
|
451
src/core/renderer_gl/renderer_gl.cpp
Normal file
451
src/core/renderer_gl/renderer_gl.cpp
Normal 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;
|
||||
}
|
292
src/core/renderer_gl/textures.cpp
Normal file
292
src/core/renderer_gl/textures.cpp
Normal 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
32
src/core/services/ac.cpp
Normal 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
31
src/core/services/act.cpp
Normal 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
54
src/core/services/am.cpp
Normal 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);
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
96
src/core/services/boss.cpp
Normal file
96
src/core/services/boss.cpp
Normal 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
64
src/core/services/cam.cpp
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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
143
src/core/services/frd.cpp
Normal 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);
|
||||
}
|
|
@ -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.
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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
Loading…
Add table
Reference in a new issue