mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-08 07:05:40 +12:00
Merge branch 'master' into vfp-timing
This commit is contained in:
commit
639ef614eb
157 changed files with 6820 additions and 263696 deletions
43
.github/mac-bundle.sh
vendored
Executable file
43
.github/mac-bundle.sh
vendored
Executable file
|
@ -0,0 +1,43 @@
|
||||||
|
# Taken from pcsx-redux create-app-bundle.sh
|
||||||
|
# For Plist buddy
|
||||||
|
PATH="$PATH:/usr/libexec"
|
||||||
|
|
||||||
|
|
||||||
|
# Construct the app iconset.
|
||||||
|
mkdir alber.iconset
|
||||||
|
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 72 -resize 16x16 alber.iconset/icon_16x16.png
|
||||||
|
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 144 -resize 32x32 alber.iconset/icon_16x16@2x.png
|
||||||
|
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 72 -resize 32x32 alber.iconset/icon_32x32.png
|
||||||
|
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 144 -resize 64x64 alber.iconset/icon_32x32@2x.png
|
||||||
|
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 72 -resize 128x128 alber.iconset/icon_128x128.png
|
||||||
|
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 144 -resize 256x256 alber.iconset/icon_128x128@2x.png
|
||||||
|
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 72 -resize 256x256 alber.iconset/icon_256x256.png
|
||||||
|
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 144 -resize 512x512 alber.iconset/icon_256x256@2x.png
|
||||||
|
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 72 -resize 512x512 alber.iconset/icon_512x512.png
|
||||||
|
convert docs/img/alber-icon.ico -alpha on -background none -units PixelsPerInch -density 144 -resize 1024x1024 alber.iconset/icon_512x512@2x.png
|
||||||
|
iconutil --convert icns alber.iconset
|
||||||
|
|
||||||
|
# Set up the .app directory
|
||||||
|
mkdir -p Alber.app/Contents/MacOS/Libraries
|
||||||
|
mkdir Alber.app/Contents/Resources
|
||||||
|
|
||||||
|
|
||||||
|
# Copy binary into App
|
||||||
|
cp ./build/Alber Alber.app/Contents/MacOS/Alber
|
||||||
|
chmod a+x Alber.app/Contents/Macos/Alber
|
||||||
|
|
||||||
|
# Copy icons into App
|
||||||
|
cp alber.icns Alber.app/Contents/Resources/AppIcon.icns
|
||||||
|
|
||||||
|
# Fix up Plist stuff
|
||||||
|
PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleDisplayName string Alber"
|
||||||
|
PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleIconName string AppIcon"
|
||||||
|
PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleIconFile string AppIcon"
|
||||||
|
PlistBuddy Alber.app/Contents/Info.plist -c "add NSHighResolutionCapable bool true"
|
||||||
|
PlistBuddy Alber.app/Contents/version.plist -c "add ProjectName string Alber"
|
||||||
|
|
||||||
|
# Bundle dylibs
|
||||||
|
dylibbundler -od -b -x Alber.app/Contents/MacOS/Alber -d Alber.app/Contents/Frameworks/ -p @rpath -s /Users/runner/work/Panda3DS/Panda3DS/VULKAN_SDK/lib
|
||||||
|
|
||||||
|
# relative rpath
|
||||||
|
install_name_tool -add_rpath @loader_path/../Frameworks Alber.app/Contents/MacOS/Alber
|
40
.github/workflows/HTTP_Build.yml
vendored
Normal file
40
.github/workflows/HTTP_Build.yml
vendored
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
name: HTTP Server Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||||
|
BUILD_TYPE: Release
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
# The CMake configure and build commands are platform agnostic and should work equally
|
||||||
|
# well on Windows or Mac. You can convert this to a matrix build if you need
|
||||||
|
# cross-platform coverage.
|
||||||
|
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Fetch submodules
|
||||||
|
run: git submodule update --init --recursive
|
||||||
|
|
||||||
|
- name: Setup Vulkan SDK
|
||||||
|
uses: humbletim/setup-vulkan-sdk@v1.2.0
|
||||||
|
with:
|
||||||
|
vulkan-query-version: latest
|
||||||
|
vulkan-use-cache: true
|
||||||
|
vulkan-components: Vulkan-Headers, Vulkan-Loader, Glslang
|
||||||
|
|
||||||
|
- 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}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_USER_BUILD=ON -DENABLE_HTTP_SERVER=ON
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
# Build your program with the given configuration
|
||||||
|
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
10
.github/workflows/Linux_Build.yml
vendored
10
.github/workflows/Linux_Build.yml
vendored
|
@ -23,6 +23,16 @@ jobs:
|
||||||
- name: Fetch submodules
|
- name: Fetch submodules
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
|
|
||||||
|
- name: Install misc packages
|
||||||
|
run: sudo apt-get update && sudo apt install libx11-dev libgl1-mesa-glx mesa-common-dev
|
||||||
|
|
||||||
|
- name: Setup Vulkan SDK
|
||||||
|
uses: humbletim/setup-vulkan-sdk@v1.2.0
|
||||||
|
with:
|
||||||
|
vulkan-query-version: latest
|
||||||
|
vulkan-use-cache: true
|
||||||
|
vulkan-components: Vulkan-Headers, Vulkan-Loader, Glslang
|
||||||
|
|
||||||
- name: Configure CMake
|
- 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.
|
# 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
|
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
|
||||||
|
|
25
.github/workflows/MacOS_Build.yml
vendored
25
.github/workflows/MacOS_Build.yml
vendored
|
@ -23,6 +23,13 @@ jobs:
|
||||||
- name: Fetch submodules
|
- name: Fetch submodules
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
|
|
||||||
|
- name: Setup Vulkan SDK
|
||||||
|
uses: humbletim/setup-vulkan-sdk@v1.2.0
|
||||||
|
with:
|
||||||
|
vulkan-query-version: latest
|
||||||
|
vulkan-use-cache: true
|
||||||
|
vulkan-components: Vulkan-Headers, Vulkan-Loader, Glslang
|
||||||
|
|
||||||
- name: Configure CMake
|
- 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.
|
# 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
|
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
|
||||||
|
@ -32,8 +39,20 @@ jobs:
|
||||||
# Build your program with the given configuration
|
# Build your program with the given configuration
|
||||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||||
|
|
||||||
- name: Upload executable
|
- name: Install bundle dependencies
|
||||||
|
run: brew install dylibbundler imagemagick
|
||||||
|
|
||||||
|
- name: Run bundle script
|
||||||
|
run: ./.github/mac-bundle.sh
|
||||||
|
|
||||||
|
- name: Sign the App
|
||||||
|
run: codesign --force -s - -vvvv Alber.app
|
||||||
|
|
||||||
|
- name: Zip it up
|
||||||
|
run: zip -r Alber Alber.app
|
||||||
|
|
||||||
|
- name: Upload MacOS App
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: MacOS executable
|
name: MacOS Alber App Bundle
|
||||||
path: './build/Alber'
|
path: 'Alber.zip'
|
||||||
|
|
7
.github/workflows/Windows_Build.yml
vendored
7
.github/workflows/Windows_Build.yml
vendored
|
@ -23,6 +23,13 @@ jobs:
|
||||||
- name: Fetch submodules
|
- name: Fetch submodules
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
|
|
||||||
|
- name: Setup Vulkan SDK
|
||||||
|
uses: humbletim/setup-vulkan-sdk@v1.2.0
|
||||||
|
with:
|
||||||
|
vulkan-query-version: latest
|
||||||
|
vulkan-use-cache: true
|
||||||
|
vulkan-components: Vulkan-Headers, Vulkan-Loader, Glslang
|
||||||
|
|
||||||
- name: Configure CMake
|
- 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.
|
# 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
|
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
|
||||||
|
|
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -55,6 +55,12 @@ fb.bat
|
||||||
*.idb
|
*.idb
|
||||||
|
|
||||||
# 3DS files
|
# 3DS files
|
||||||
|
*.3ds
|
||||||
*.3dsx
|
*.3dsx
|
||||||
|
*.app
|
||||||
|
*.cci
|
||||||
|
*.cxi
|
||||||
*.elf
|
*.elf
|
||||||
*.smdh
|
*.smdh
|
||||||
|
|
||||||
|
config.toml
|
9
.gitmodules
vendored
9
.gitmodules
vendored
|
@ -25,3 +25,12 @@
|
||||||
[submodule "stb"]
|
[submodule "stb"]
|
||||||
path = third_party/stb
|
path = third_party/stb
|
||||||
url = https://github.com/nothings/stb
|
url = https://github.com/nothings/stb
|
||||||
|
[submodule "third_party/cmrc"]
|
||||||
|
path = third_party/cmrc
|
||||||
|
url = https://github.com/vector-of-bool/cmrc
|
||||||
|
[submodule "third_party/glm"]
|
||||||
|
path = third_party/glm
|
||||||
|
url = https://github.com/g-truc/glm
|
||||||
|
[submodule "third_party/discord-rpc"]
|
||||||
|
path = third_party/discord-rpc
|
||||||
|
url = https://github.com/Panda3DS-emu/discord-rpc
|
||||||
|
|
131
CMakeLists.txt
131
CMakeLists.txt
|
@ -1,4 +1,11 @@
|
||||||
cmake_minimum_required(VERSION 3.10)
|
# We need to be able to use enable_language(OBJC) on Mac, so we need CMake 3.16 vs the 3.10 we use otherwise. Blame Apple.
|
||||||
|
if (APPLE)
|
||||||
|
set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0" CACHE STRING "Minimum OS X deployment version")
|
||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
else()
|
||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
endif()
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||||
|
|
||||||
|
@ -7,12 +14,16 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT CMAKE_BUILD_TYPE)
|
if(NOT CMAKE_BUILD_TYPE)
|
||||||
set(CMAKE_BUILD_TYPE Release)
|
set(CMAKE_BUILD_TYPE Release)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
project(Alber)
|
project(Alber)
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
if(APPLE)
|
||||||
|
enable_language(OBJC)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-format-nonliteral -Wno-format-security")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-format-nonliteral -Wno-format-security")
|
||||||
endif()
|
endif()
|
||||||
|
@ -20,9 +31,11 @@ endif()
|
||||||
option(DISABLE_PANIC_DEV "Make a build with fewer and less intrusive asserts" OFF)
|
option(DISABLE_PANIC_DEV "Make a build with fewer and less intrusive asserts" OFF)
|
||||||
option(GPU_DEBUG_INFO "Enable additional GPU debugging info" OFF)
|
option(GPU_DEBUG_INFO "Enable additional GPU debugging info" OFF)
|
||||||
option(ENABLE_OPENGL "Enable OpenGL rendering backend" ON)
|
option(ENABLE_OPENGL "Enable OpenGL rendering backend" ON)
|
||||||
|
option(ENABLE_VULKAN "Enable Vulkan rendering backend" ON)
|
||||||
option(ENABLE_LTO "Enable link-time optimization" OFF)
|
option(ENABLE_LTO "Enable link-time optimization" OFF)
|
||||||
option(ENABLE_USER_BUILD "Make a user-facing build. These builds have various assertions disabled, LTO, and more" OFF)
|
option(ENABLE_USER_BUILD "Make a user-facing build. These builds have various assertions disabled, LTO, and more" OFF)
|
||||||
option(ENABLE_HTTP_SERVER "Enable HTTP server. Used for Discord bot support" OFF)
|
option(ENABLE_HTTP_SERVER "Enable HTTP server. Used for Discord bot support" OFF)
|
||||||
|
option(ENABLE_DISCORD_RPC "Compile with Discord RPC support (disabled by default)" ON)
|
||||||
|
|
||||||
include_directories(${PROJECT_SOURCE_DIR}/include/)
|
include_directories(${PROJECT_SOURCE_DIR}/include/)
|
||||||
include_directories(${PROJECT_SOURCE_DIR}/include/kernel)
|
include_directories(${PROJECT_SOURCE_DIR}/include/kernel)
|
||||||
|
@ -37,11 +50,17 @@ include_directories(third_party/result/include)
|
||||||
include_directories(third_party/xxhash/include)
|
include_directories(third_party/xxhash/include)
|
||||||
include_directories(third_party/httplib)
|
include_directories(third_party/httplib)
|
||||||
include_directories(third_party/stb)
|
include_directories(third_party/stb)
|
||||||
|
include_directories(third_party/opengl)
|
||||||
|
|
||||||
add_compile_definitions(NOMINMAX) # Make windows.h not define min/max macros because third-party deps don't like it
|
add_compile_definitions(NOMINMAX) # Make windows.h not define min/max macros because third-party deps don't like it
|
||||||
add_compile_definitions(WIN32_LEAN_AND_MEAN) # Make windows.h not include literally everything
|
add_compile_definitions(WIN32_LEAN_AND_MEAN) # Make windows.h not include literally everything
|
||||||
add_compile_definitions(SDL_MAIN_HANDLED)
|
add_compile_definitions(SDL_MAIN_HANDLED)
|
||||||
|
|
||||||
|
if(ENABLE_DISCORD_RPC AND NOT ANDROID)
|
||||||
|
add_subdirectory(third_party/discord-rpc)
|
||||||
|
include_directories(third_party/discord-rpc/include)
|
||||||
|
endif()
|
||||||
|
|
||||||
set(SDL_STATIC ON CACHE BOOL "" FORCE)
|
set(SDL_STATIC ON CACHE BOOL "" FORCE)
|
||||||
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
||||||
set(SDL_TEST OFF CACHE BOOL "" FORCE)
|
set(SDL_TEST OFF CACHE BOOL "" FORCE)
|
||||||
|
@ -50,6 +69,9 @@ add_subdirectory(third_party/SDL2)
|
||||||
add_subdirectory(third_party/toml11)
|
add_subdirectory(third_party/toml11)
|
||||||
include_directories(${SDL2_INCLUDE_DIR})
|
include_directories(${SDL2_INCLUDE_DIR})
|
||||||
include_directories(third_party/toml11)
|
include_directories(third_party/toml11)
|
||||||
|
include_directories(third_party/glm)
|
||||||
|
|
||||||
|
add_subdirectory(third_party/cmrc)
|
||||||
|
|
||||||
set(BOOST_ROOT "${CMAKE_SOURCE_DIR}/third_party/boost")
|
set(BOOST_ROOT "${CMAKE_SOURCE_DIR}/third_party/boost")
|
||||||
set(Boost_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/third_party/boost")
|
set(Boost_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/third_party/boost")
|
||||||
|
@ -61,6 +83,7 @@ target_include_directories(boost SYSTEM INTERFACE ${Boost_INCLUDE_DIR})
|
||||||
|
|
||||||
set(CRYPTOPP_BUILD_TESTING OFF)
|
set(CRYPTOPP_BUILD_TESTING OFF)
|
||||||
add_subdirectory(third_party/cryptopp)
|
add_subdirectory(third_party/cryptopp)
|
||||||
|
add_subdirectory(third_party/glad)
|
||||||
|
|
||||||
# Check for x64
|
# Check for x64
|
||||||
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "x86-64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
|
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "x86-64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
|
||||||
|
@ -93,7 +116,9 @@ endif()
|
||||||
|
|
||||||
set(SOURCE_FILES src/main.cpp src/emulator.cpp src/io_file.cpp src/config.cpp
|
set(SOURCE_FILES src/main.cpp src/emulator.cpp src/io_file.cpp src/config.cpp
|
||||||
src/core/CPU/cpu_dynarmic.cpp src/core/CPU/dynarmic_cycles.cpp
|
src/core/CPU/cpu_dynarmic.cpp src/core/CPU/dynarmic_cycles.cpp
|
||||||
src/core/memory.cpp src/renderer.cpp src/httpserver.cpp src/stb_image_write.c
|
src/core/memory.cpp src/renderer.cpp src/core/renderer_null/renderer_null.cpp
|
||||||
|
src/http_server.cpp src/stb_image_write.c src/core/cheats.cpp src/core/action_replay.cpp
|
||||||
|
src/discord_rpc.cpp
|
||||||
)
|
)
|
||||||
set(CRYPTO_SOURCE_FILES src/core/crypto/aes_engine.cpp)
|
set(CRYPTO_SOURCE_FILES src/core/crypto/aes_engine.cpp)
|
||||||
set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limits.cpp
|
set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limits.cpp
|
||||||
|
@ -101,17 +126,18 @@ set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limi
|
||||||
src/core/kernel/events.cpp src/core/kernel/threads.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/address_arbiter.cpp src/core/kernel/error.cpp
|
||||||
src/core/kernel/file_operations.cpp src/core/kernel/directory_operations.cpp
|
src/core/kernel/file_operations.cpp src/core/kernel/directory_operations.cpp
|
||||||
src/core/kernel/idle_thread.cpp
|
src/core/kernel/idle_thread.cpp src/core/kernel/timers.cpp
|
||||||
)
|
)
|
||||||
set(SERVICE_SOURCE_FILES src/core/services/service_manager.cpp src/core/services/apt.cpp src/core/services/hid.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/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/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/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/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/frd.cpp src/core/services/nim.cpp src/core/services/mcu/mcu_hwc.cpp
|
||||||
src/core/services/y2r.cpp src/core/services/cam.cpp src/core/services/ldr_ro.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 src/core/services/dlp_srvr.cpp
|
src/core/services/act.cpp src/core/services/nfc.cpp src/core/services/dlp_srvr.cpp
|
||||||
src/core/services/ir_user.cpp
|
src/core/services/ir_user.cpp src/core/services/http.cpp src/core/services/soc.cpp
|
||||||
|
src/core/services/ssl.cpp src/core/services/news_u.cpp
|
||||||
)
|
)
|
||||||
set(PICA_SOURCE_FILES src/core/PICA/gpu.cpp src/core/PICA/regs.cpp src/core/PICA/shader_unit.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/dynapica/shader_rec.cpp
|
src/core/PICA/shader_interpreter.cpp src/core/PICA/dynapica/shader_rec.cpp
|
||||||
|
@ -120,15 +146,18 @@ set(PICA_SOURCE_FILES src/core/PICA/gpu.cpp src/core/PICA/regs.cpp src/core/PICA
|
||||||
|
|
||||||
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(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
|
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
|
src/core/fs/archive_ext_save_data.cpp src/core/fs/archive_ncch.cpp src/core/fs/romfs.cpp
|
||||||
|
src/core/fs/ivfc.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(RENDERER_SW_SOURCE_FILES src/core/renderer_sw/renderer_sw.cpp)
|
||||||
|
|
||||||
set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp
|
set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp
|
||||||
include/cpu.hpp include/cpu_dynarmic.hpp include/memory.hpp include/renderer.hpp include/kernel/kernel.hpp
|
include/cpu.hpp include/cpu_dynarmic.hpp include/memory.hpp include/renderer.hpp include/kernel/kernel.hpp
|
||||||
include/dynarmic_cp15.hpp include/kernel/resource_limits.hpp include/kernel/kernel_types.hpp
|
include/dynarmic_cp15.hpp include/kernel/resource_limits.hpp include/kernel/kernel_types.hpp
|
||||||
include/kernel/config_mem.hpp include/services/service_manager.hpp include/services/apt.hpp
|
include/kernel/config_mem.hpp include/services/service_manager.hpp include/services/apt.hpp
|
||||||
include/kernel/handles.hpp include/services/hid.hpp include/services/fs.hpp
|
include/kernel/handles.hpp include/services/hid.hpp include/services/fs.hpp
|
||||||
include/services/gsp_gpu.hpp include/services/gsp_lcd.hpp include/arm_defs.hpp
|
include/services/gsp_gpu.hpp include/services/gsp_lcd.hpp include/arm_defs.hpp include/renderer_null/renderer_null.hpp
|
||||||
include/PICA/gpu.hpp include/PICA/regs.hpp include/services/ndm.hpp
|
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/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/logger.hpp include/loader/ncch.hpp include/loader/ncsd.hpp include/io_file.hpp
|
||||||
|
@ -137,8 +166,8 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp
|
||||||
include/fs/archive_save_data.hpp include/fs/archive_sdmc.hpp include/services/ptm.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/ac.hpp
|
include/services/mic.hpp include/services/cecd.hpp include/services/ac.hpp
|
||||||
include/services/am.hpp include/services/boss.hpp include/services/frd.hpp include/services/nim.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/fs/archive_ext_save_data.hpp include/fs/archive_ncch.hpp include/services/mcu/mcu_hwc.hpp
|
||||||
include/colour.hpp include/services/y2r.hpp include/services/cam.hpp
|
include/colour.hpp include/services/y2r.hpp include/services/cam.hpp include/services/ssl.hpp
|
||||||
include/services/ldr_ro.hpp include/ipc.hpp include/services/act.hpp include/services/nfc.hpp
|
include/services/ldr_ro.hpp include/ipc.hpp include/services/act.hpp include/services/nfc.hpp
|
||||||
include/system_models.hpp include/services/dlp_srvr.hpp include/PICA/dynapica/pica_recs.hpp
|
include/system_models.hpp include/services/dlp_srvr.hpp include/PICA/dynapica/pica_recs.hpp
|
||||||
include/PICA/dynapica/x64_regs.hpp include/PICA/dynapica/vertex_loader_rec.hpp include/PICA/dynapica/shader_rec.hpp
|
include/PICA/dynapica/x64_regs.hpp include/PICA/dynapica/vertex_loader_rec.hpp include/PICA/dynapica/shader_rec.hpp
|
||||||
|
@ -146,7 +175,17 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp
|
||||||
include/result/result_common.hpp include/result/result_fs.hpp include/result/result_fnd.hpp
|
include/result/result_common.hpp include/result/result_fs.hpp include/result/result_fnd.hpp
|
||||||
include/result/result_gsp.hpp include/result/result_kernel.hpp include/result/result_os.hpp
|
include/result/result_gsp.hpp include/result/result_kernel.hpp include/result/result_os.hpp
|
||||||
include/crypto/aes_engine.hpp include/metaprogramming.hpp include/PICA/pica_vertex.hpp
|
include/crypto/aes_engine.hpp include/metaprogramming.hpp include/PICA/pica_vertex.hpp
|
||||||
include/config.hpp include/services/ir_user.hpp include/httpserver.hpp
|
include/config.hpp include/services/ir_user.hpp include/http_server.hpp include/cheats.hpp
|
||||||
|
include/action_replay.hpp include/renderer_sw/renderer_sw.hpp include/compiler_builtins.hpp
|
||||||
|
include/fs/romfs.hpp include/fs/ivfc.hpp include/discord_rpc.hpp include/services/http.hpp include/result/result_cfg.hpp
|
||||||
|
include/math_util.hpp include/services/soc.hpp include/services/news_u.hpp
|
||||||
|
)
|
||||||
|
|
||||||
|
cmrc_add_resource_library(
|
||||||
|
resources_console_fonts
|
||||||
|
NAMESPACE ConsoleFonts
|
||||||
|
WHENCE "src/core/services/fonts/"
|
||||||
|
"src/core/services/fonts/CitraSharedFontUSRelocated.bin"
|
||||||
)
|
)
|
||||||
|
|
||||||
set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp
|
set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp
|
||||||
|
@ -165,48 +204,100 @@ 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\\Loader" FILES ${LOADER_SOURCE_FILES})
|
||||||
source_group("Source Files\\Core\\Services" FILES ${SERVICE_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\\PICA" FILES ${PICA_SOURCE_FILES})
|
||||||
|
source_group("Source Files\\Core\\Software Renderer" FILES ${RENDERER_SW_SOURCE_FILES})
|
||||||
source_group("Source Files\\Third Party" FILES ${THIRD_PARTY_SOURCE_FILES})
|
source_group("Source Files\\Third Party" FILES ${THIRD_PARTY_SOURCE_FILES})
|
||||||
|
|
||||||
set(RENDERER_GL_SOURCE_FILES "") # Empty by default unless we are compiling with the GL renderer
|
set(RENDERER_GL_SOURCE_FILES "") # Empty by default unless we are compiling with the GL renderer
|
||||||
|
set(RENDERER_VK_SOURCE_FILES "") # Empty by default unless we are compiling with the VK renderer
|
||||||
|
|
||||||
if(ENABLE_OPENGL)
|
if(ENABLE_OPENGL)
|
||||||
add_subdirectory(third_party/glad)
|
# This may look weird but opengl.hpp is our header even if it's in the third_party folder
|
||||||
|
set(RENDERER_GL_INCLUDE_FILES third_party/opengl/opengl.hpp
|
||||||
set(RENDERER_GL_INCLUDE_FILES include/renderer_gl/opengl.hpp
|
|
||||||
include/renderer_gl/renderer_gl.hpp include/renderer_gl/textures.hpp
|
include/renderer_gl/renderer_gl.hpp include/renderer_gl/textures.hpp
|
||||||
include/renderer_gl/surfaces.hpp include/renderer_gl/surface_cache.hpp
|
include/renderer_gl/surfaces.hpp include/renderer_gl/surface_cache.hpp
|
||||||
include/renderer_gl/gl_state.hpp
|
include/renderer_gl/gl_state.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(RENDERER_GL_SOURCE_FILES src/core/renderer_gl/renderer_gl.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
|
src/core/renderer_gl/textures.cpp src/core/renderer_gl/etc1.cpp
|
||||||
src/core/renderer_gl/gl_state.cpp
|
src/core/renderer_gl/gl_state.cpp src/host_shaders/opengl_display.frag
|
||||||
|
src/host_shaders/opengl_display.vert src/host_shaders/opengl_vertex_shader.vert
|
||||||
|
src/host_shaders/opengl_fragment_shader.frag
|
||||||
)
|
)
|
||||||
|
|
||||||
set(HEADER_FILES ${HEADER_FILES} ${RENDERER_GL_INCLUDE_FILES})
|
set(HEADER_FILES ${HEADER_FILES} ${RENDERER_GL_INCLUDE_FILES})
|
||||||
source_group("Source Files\\Core\\OpenGL Renderer" FILES ${RENDERER_GL_SOURCE_FILES})
|
source_group("Source Files\\Core\\OpenGL Renderer" FILES ${RENDERER_GL_SOURCE_FILES})
|
||||||
|
|
||||||
|
cmrc_add_resource_library(
|
||||||
|
resources_renderer_gl
|
||||||
|
NAMESPACE RendererGL
|
||||||
|
WHENCE "src/host_shaders/"
|
||||||
|
"src/host_shaders/opengl_display.frag"
|
||||||
|
"src/host_shaders/opengl_display.vert"
|
||||||
|
"src/host_shaders/opengl_vertex_shader.vert"
|
||||||
|
"src/host_shaders/opengl_fragment_shader.frag"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(ENABLE_VULKAN)
|
||||||
|
find_package(
|
||||||
|
Vulkan 1.3.206 REQUIRED
|
||||||
|
COMPONENTS glslangValidator
|
||||||
|
)
|
||||||
|
|
||||||
|
set(RENDERER_VK_INCLUDE_FILES include/renderer_vk/renderer_vk.hpp
|
||||||
|
include/renderer_vk/vulkan_api.hpp include/renderer_vk/vk_debug.hpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(RENDERER_VK_SOURCE_FILES src/core/renderer_vk/renderer_vk.cpp
|
||||||
|
src/core/renderer_vk/vulkan_api.cpp src/core/renderer_vk/vk_debug.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(HEADER_FILES ${HEADER_FILES} ${RENDERER_VK_INCLUDE_FILES})
|
||||||
|
source_group("Source Files\\Core\\Vulkan Renderer" FILES ${RENDERER_VK_SOURCE_FILES})
|
||||||
|
|
||||||
|
cmrc_add_resource_library(
|
||||||
|
resources_renderer_vk
|
||||||
|
NAMESPACE RendererVK
|
||||||
|
WHENCE "src/host_shaders/"
|
||||||
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
source_group("Header Files\\Core" FILES ${HEADER_FILES})
|
source_group("Header Files\\Core" FILES ${HEADER_FILES})
|
||||||
set(ALL_SOURCES ${SOURCE_FILES} ${FS_SOURCE_FILES} ${CRYPTO_SOURCE_FILES} ${KERNEL_SOURCE_FILES} ${LOADER_SOURCE_FILES} ${SERVICE_SOURCE_FILES}
|
set(ALL_SOURCES ${SOURCE_FILES} ${FS_SOURCE_FILES} ${CRYPTO_SOURCE_FILES} ${KERNEL_SOURCE_FILES} ${LOADER_SOURCE_FILES} ${SERVICE_SOURCE_FILES}
|
||||||
${PICA_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES} ${HEADER_FILES})
|
${RENDERER_SW_SOURCE_FILES} ${PICA_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES} ${HEADER_FILES})
|
||||||
|
|
||||||
if(ENABLE_OPENGL)
|
if(ENABLE_OPENGL)
|
||||||
# Add the OpenGL source files to ALL_SOURCES
|
# Add the OpenGL source files to ALL_SOURCES
|
||||||
set(ALL_SOURCES ${ALL_SOURCES} ${RENDERER_GL_SOURCE_FILES})
|
set(ALL_SOURCES ${ALL_SOURCES} ${RENDERER_GL_SOURCE_FILES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(ENABLE_VULKAN)
|
||||||
|
# Add the Vulkan source files to ALL_SOURCES
|
||||||
|
set(ALL_SOURCES ${ALL_SOURCES} ${RENDERER_VK_SOURCE_FILES})
|
||||||
|
endif()
|
||||||
|
|
||||||
add_executable(Alber ${ALL_SOURCES})
|
add_executable(Alber ${ALL_SOURCES})
|
||||||
|
|
||||||
if(ENABLE_LTO OR ENABLE_USER_BUILD)
|
if(ENABLE_LTO OR ENABLE_USER_BUILD)
|
||||||
set_target_properties(Alber PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE)
|
set_target_properties(Alber PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_link_libraries(Alber PRIVATE dynarmic SDL2-static cryptopp)
|
target_link_libraries(Alber PRIVATE dynarmic SDL2-static cryptopp glad resources_console_fonts)
|
||||||
|
|
||||||
|
if(ENABLE_DISCORD_RPC AND NOT ANDROID)
|
||||||
|
target_compile_definitions(Alber PUBLIC "PANDA3DS_ENABLE_DISCORD_RPC=1")
|
||||||
|
target_link_libraries(Alber PRIVATE discord-rpc)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(ENABLE_OPENGL)
|
if(ENABLE_OPENGL)
|
||||||
target_compile_definitions(Alber PUBLIC "PANDA3DS_ENABLE_OPENGL=1")
|
target_compile_definitions(Alber PUBLIC "PANDA3DS_ENABLE_OPENGL=1")
|
||||||
target_link_libraries(Alber PRIVATE glad)
|
target_link_libraries(Alber PRIVATE resources_renderer_gl)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(ENABLE_VULKAN)
|
||||||
|
target_compile_definitions(Alber PUBLIC "PANDA3DS_ENABLE_VULKAN=1")
|
||||||
|
target_link_libraries(Alber PRIVATE Vulkan::Vulkan resources_renderer_vk)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(GPU_DEBUG_INFO)
|
if(GPU_DEBUG_INFO)
|
||||||
|
|
BIN
docs/img/KirbyRobobot.png
Normal file
BIN
docs/img/KirbyRobobot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 71 KiB |
BIN
docs/img/MK7.png
BIN
docs/img/MK7.png
Binary file not shown.
Before Width: | Height: | Size: 360 KiB After Width: | Height: | Size: 389 KiB |
BIN
docs/img/alber-icon.ico
Normal file
BIN
docs/img/alber-icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 2 MiB |
|
@ -91,6 +91,7 @@ class ShaderEmitter : public Xbyak::CodeGenerator {
|
||||||
void recCMP(const PICAShader& shader, u32 instruction);
|
void recCMP(const PICAShader& shader, u32 instruction);
|
||||||
void recDP3(const PICAShader& shader, u32 instruction);
|
void recDP3(const PICAShader& shader, u32 instruction);
|
||||||
void recDP4(const PICAShader& shader, u32 instruction);
|
void recDP4(const PICAShader& shader, u32 instruction);
|
||||||
|
void recDPH(const PICAShader& shader, u32 instruction);
|
||||||
void recEMIT(const PICAShader& shader, u32 instruction);
|
void recEMIT(const PICAShader& shader, u32 instruction);
|
||||||
void recEND(const PICAShader& shader, u32 instruction);
|
void recEND(const PICAShader& shader, u32 instruction);
|
||||||
void recEX2(const PICAShader& shader, u32 instruction);
|
void recEX2(const PICAShader& shader, u32 instruction);
|
||||||
|
@ -111,7 +112,6 @@ class ShaderEmitter : public Xbyak::CodeGenerator {
|
||||||
void recRSQ(const PICAShader& shader, u32 instruction);
|
void recRSQ(const PICAShader& shader, u32 instruction);
|
||||||
void recSETEMIT(const PICAShader& shader, u32 instruction);
|
void recSETEMIT(const PICAShader& shader, u32 instruction);
|
||||||
void recSGE(const PICAShader& shader, u32 instruction);
|
void recSGE(const PICAShader& shader, u32 instruction);
|
||||||
void recSGEI(const PICAShader& shader, u32 instruction);
|
|
||||||
void recSLT(const PICAShader& shader, u32 instruction);
|
void recSLT(const PICAShader& shader, u32 instruction);
|
||||||
|
|
||||||
MAKE_LOG_FUNCTION(log, shaderJITLogger)
|
MAKE_LOG_FUNCTION(log, shaderJITLogger)
|
||||||
|
|
|
@ -14,8 +14,11 @@
|
||||||
|
|
||||||
class GPU {
|
class GPU {
|
||||||
static constexpr u32 regNum = 0x300;
|
static constexpr u32 regNum = 0x300;
|
||||||
|
static constexpr u32 extRegNum = 0x1000;
|
||||||
|
|
||||||
using vec4f = std::array<Floats::f24, 4>;
|
using vec4f = std::array<Floats::f24, 4>;
|
||||||
using Registers = std::array<u32, regNum>;
|
using Registers = std::array<u32, regNum>; // Internal registers (named registers in short since they're the main ones)
|
||||||
|
using ExternalRegisters = std::array<u32, extRegNum>;
|
||||||
|
|
||||||
Memory& mem;
|
Memory& mem;
|
||||||
EmulatorConfig& config;
|
EmulatorConfig& config;
|
||||||
|
@ -83,7 +86,7 @@ class GPU {
|
||||||
bool lightingLUTDirty = false;
|
bool lightingLUTDirty = false;
|
||||||
|
|
||||||
GPU(Memory& mem, EmulatorConfig& config);
|
GPU(Memory& mem, EmulatorConfig& config);
|
||||||
void initGraphicsContext() { renderer->initGraphicsContext(); }
|
void initGraphicsContext(SDL_Window* window) { renderer->initGraphicsContext(window); }
|
||||||
void display() { renderer->display(); }
|
void display() { renderer->display(); }
|
||||||
void screenshot(const std::string& name) { renderer->screenshot(name); }
|
void screenshot(const std::string& name) { renderer->screenshot(name); }
|
||||||
|
|
||||||
|
@ -91,21 +94,23 @@ class GPU {
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
Registers& getRegisters() { return regs; }
|
Registers& getRegisters() { return regs; }
|
||||||
|
ExternalRegisters& getExtRegisters() { return externalRegs; }
|
||||||
void startCommandList(u32 addr, u32 size);
|
void startCommandList(u32 addr, u32 size);
|
||||||
|
|
||||||
// Used by the GSP GPU service for readHwRegs/writeHwRegs/writeHwRegsMasked
|
// Used by the GSP GPU service for readHwRegs/writeHwRegs/writeHwRegsMasked
|
||||||
u32 readReg(u32 address);
|
u32 readReg(u32 address);
|
||||||
void writeReg(u32 address, u32 value);
|
void writeReg(u32 address, u32 value);
|
||||||
|
|
||||||
|
u32 readExternalReg(u32 index);
|
||||||
|
void writeExternalReg(u32 index, u32 value);
|
||||||
|
|
||||||
// Used when processing GPU command lists
|
// Used when processing GPU command lists
|
||||||
u32 readInternalReg(u32 index);
|
u32 readInternalReg(u32 index);
|
||||||
void writeInternalReg(u32 index, u32 value, u32 mask);
|
void writeInternalReg(u32 index, u32 value, u32 mask);
|
||||||
|
|
||||||
// TODO: Emulate the transfer engine & its registers
|
// TODO: Emulate the transfer engine & its registers
|
||||||
// Then this can be emulated by just writing the appropriate values there
|
// Then this can be emulated by just writing the appropriate values there
|
||||||
void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) {
|
void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) { renderer->clearBuffer(startAddress, endAddress, value, control); }
|
||||||
renderer->clearBuffer(startAddress, endAddress, value, control);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Emulate the transfer engine & its registers
|
// TODO: Emulate the transfer engine & its registers
|
||||||
// Then this can be emulated by just writing the appropriate values there
|
// Then this can be emulated by just writing the appropriate values there
|
||||||
|
@ -113,6 +118,10 @@ class GPU {
|
||||||
renderer->displayTransfer(inputAddr, outputAddr, inputSize, outputSize, flags);
|
renderer->displayTransfer(inputAddr, outputAddr, inputSize, outputSize, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) {
|
||||||
|
renderer->textureCopy(inputAddr, outputAddr, totalBytes, inputSize, outputSize, flags);
|
||||||
|
}
|
||||||
|
|
||||||
// Read a value of type T from physical address paddr
|
// Read a value of type T from physical address paddr
|
||||||
// This is necessary because vertex attribute fetching uses physical addresses
|
// This is necessary because vertex attribute fetching uses physical addresses
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
@ -129,17 +138,23 @@ class GPU {
|
||||||
|
|
||||||
// Get a pointer of type T* to the data starting from physical address paddr
|
// Get a pointer of type T* to the data starting from physical address paddr
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T* getPointerPhys(u32 paddr) {
|
T* getPointerPhys(u32 paddr, u32 size = 0) {
|
||||||
if (paddr >= PhysicalAddrs::FCRAM && paddr <= PhysicalAddrs::FCRAMEnd) {
|
if (paddr >= PhysicalAddrs::FCRAM && paddr + size <= PhysicalAddrs::FCRAMEnd) {
|
||||||
u8* fcram = mem.getFCRAM();
|
u8* fcram = mem.getFCRAM();
|
||||||
u32 index = paddr - PhysicalAddrs::FCRAM;
|
u32 index = paddr - PhysicalAddrs::FCRAM;
|
||||||
|
|
||||||
return (T*)&fcram[index];
|
return (T*)&fcram[index];
|
||||||
} else if (paddr >= PhysicalAddrs::VRAM && paddr <= PhysicalAddrs::VRAMEnd) {
|
} else if (paddr >= PhysicalAddrs::VRAM && paddr + size <= PhysicalAddrs::VRAMEnd) {
|
||||||
u32 index = paddr - PhysicalAddrs::VRAM;
|
u32 index = paddr - PhysicalAddrs::VRAM;
|
||||||
return (T*)&vram[index];
|
return (T*)&vram[index];
|
||||||
} else [[unlikely]] {
|
} else [[unlikely]] {
|
||||||
Helpers::panic("[GPU] Tried to access unknown physical address: %08X", paddr);
|
Helpers::panic("[GPU] Tried to access unknown physical address: %08X", paddr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
private:
|
||||||
|
// GPU external registers
|
||||||
|
// We have them in the end of the struct for cache locality reasons. Tl;dr we want the more commonly used things to be packed in the start
|
||||||
|
// Of the struct, instead of externalRegs being in the middle
|
||||||
|
ExternalRegisters externalRegs;
|
||||||
|
};
|
||||||
|
|
|
@ -22,6 +22,7 @@ namespace PICA {
|
||||||
ShaderOutputCount = 0x4F,
|
ShaderOutputCount = 0x4F,
|
||||||
ShaderOutmap0 = 0x50,
|
ShaderOutmap0 = 0x50,
|
||||||
|
|
||||||
|
ViewportXY = 0x68,
|
||||||
DepthmapEnable = 0x6D,
|
DepthmapEnable = 0x6D,
|
||||||
|
|
||||||
// Texture registers
|
// Texture registers
|
||||||
|
@ -53,9 +54,13 @@ namespace PICA {
|
||||||
// Framebuffer registers
|
// Framebuffer registers
|
||||||
ColourOperation = 0x100,
|
ColourOperation = 0x100,
|
||||||
BlendFunc = 0x101,
|
BlendFunc = 0x101,
|
||||||
|
LogicOp = 0x102,
|
||||||
BlendColour = 0x103,
|
BlendColour = 0x103,
|
||||||
AlphaTestConfig = 0x104,
|
AlphaTestConfig = 0x104,
|
||||||
|
StencilTest = 0x105,
|
||||||
|
StencilOp = 0x106,
|
||||||
DepthAndColorMask = 0x107,
|
DepthAndColorMask = 0x107,
|
||||||
|
DepthBufferWrite = 0x115,
|
||||||
DepthBufferFormat = 0x116,
|
DepthBufferFormat = 0x116,
|
||||||
ColourBufferFormat = 0x117,
|
ColourBufferFormat = 0x117,
|
||||||
DepthBufferLoc = 0x11C,
|
DepthBufferLoc = 0x11C,
|
||||||
|
@ -174,6 +179,53 @@ namespace PICA {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace ExternalRegs {
|
||||||
|
enum : u32 {
|
||||||
|
MemFill1BufferStartPaddr = 0x3,
|
||||||
|
MemFill1BufferEndPAddr = 0x4,
|
||||||
|
MemFill1Value = 0x5,
|
||||||
|
MemFill1Control = 0x6,
|
||||||
|
MemFill2BufferStartPaddr = 0x7,
|
||||||
|
MemFill2BufferEndPAddr = 0x8,
|
||||||
|
MemFill2Value = 0x9,
|
||||||
|
MemFill2Control = 0xA,
|
||||||
|
VramBankControl = 0xB,
|
||||||
|
GPUBusy = 0xC,
|
||||||
|
BacklightControl = 0xBC,
|
||||||
|
Framebuffer0Size = 0x118,
|
||||||
|
Framebuffer0AFirstAddr = 0x119,
|
||||||
|
Framebuffer0ASecondAddr = 0x11A,
|
||||||
|
Framebuffer0Config = 0x11B,
|
||||||
|
Framebuffer0Select = 0x11D,
|
||||||
|
Framebuffer0Stride = 0x123,
|
||||||
|
Framebuffer0BFirstAddr = 0x124,
|
||||||
|
Framebuffer0BSecondAddr = 0x125,
|
||||||
|
Framebuffer1Size = 0x156,
|
||||||
|
Framebuffer1AFirstAddr = 0x159,
|
||||||
|
Framebuffer1ASecondAddr = 0x15A,
|
||||||
|
Framebuffer1Config = 0x15B,
|
||||||
|
Framebuffer1Select = 0x15D,
|
||||||
|
Framebuffer1Stride = 0x163,
|
||||||
|
Framebuffer1BFirstAddr = 0x164,
|
||||||
|
Framebuffer1BSecondAddr = 0x165,
|
||||||
|
TransferInputPAddr = 0x2FF,
|
||||||
|
TransferOutputPAddr = 0x300,
|
||||||
|
DisplayTransferOutputDim = 0x301,
|
||||||
|
DisplayTransferInputDim = 0x302,
|
||||||
|
TransferFlags = 0x303,
|
||||||
|
TransferTrigger = 0x305,
|
||||||
|
TextureCopyTotalBytes = 0x307,
|
||||||
|
TextureCopyInputLineGap = 0x308,
|
||||||
|
TextureCopyOutputLineGap = 0x309,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Scaling : u32 {
|
||||||
|
None = 0,
|
||||||
|
X = 1,
|
||||||
|
XY = 2,
|
||||||
|
};
|
||||||
|
|
||||||
namespace Lights {
|
namespace Lights {
|
||||||
enum : u32 {
|
enum : u32 {
|
||||||
LUT_D0 = 0,
|
LUT_D0 = 0,
|
||||||
|
@ -292,4 +344,4 @@ namespace PICA {
|
||||||
GeometryPrimitive = 3,
|
GeometryPrimitive = 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace PICA
|
} // namespace PICA
|
||||||
|
|
|
@ -23,6 +23,7 @@ namespace ShaderOpcodes {
|
||||||
LG2 = 0x06,
|
LG2 = 0x06,
|
||||||
LIT = 0x07,
|
LIT = 0x07,
|
||||||
MUL = 0x08,
|
MUL = 0x08,
|
||||||
|
SGE = 0x09,
|
||||||
SLT = 0x0A,
|
SLT = 0x0A,
|
||||||
FLR = 0x0B,
|
FLR = 0x0B,
|
||||||
MAX = 0x0C,
|
MAX = 0x0C,
|
||||||
|
@ -158,6 +159,7 @@ class PICAShader {
|
||||||
void mul(u32 instruction);
|
void mul(u32 instruction);
|
||||||
void rcp(u32 instruction);
|
void rcp(u32 instruction);
|
||||||
void rsq(u32 instruction);
|
void rsq(u32 instruction);
|
||||||
|
void sge(u32 instruction);
|
||||||
void sgei(u32 instruction);
|
void sgei(u32 instruction);
|
||||||
void slt(u32 instruction);
|
void slt(u32 instruction);
|
||||||
void slti(u32 instruction);
|
void slti(u32 instruction);
|
||||||
|
|
52
include/action_replay.hpp
Normal file
52
include/action_replay.hpp
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
#pragma once
|
||||||
|
#include <array>
|
||||||
|
#include <bitset>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include "memory.hpp"
|
||||||
|
#include "services/hid.hpp"
|
||||||
|
|
||||||
|
class ActionReplay {
|
||||||
|
using Cheat = std::vector<u32>; // A cheat is really just a bunch of 64-bit opcodes neatly encoded into 32-bit chunks
|
||||||
|
static constexpr size_t ifStackSize = 32; // TODO: How big is this, really?
|
||||||
|
|
||||||
|
u32 offset1, offset2; // Memory offset registers. Non-persistent.
|
||||||
|
u32 data1, data2; // Data offset registers. Non-persistent.
|
||||||
|
u32 storage1, storage2; // Storage registers. Persistent.
|
||||||
|
|
||||||
|
// When an instruction does not specify which offset or data register to use, we use the "active" one
|
||||||
|
// Which is by default #1 and may be changed by certain AR operations
|
||||||
|
u32 *activeOffset, *activeData, *activeStorage;
|
||||||
|
u32 ifStackIndex; // Our index in the if stack. Shows how many entries we have at the moment.
|
||||||
|
u32 loopStackIndex; // Same but for loops
|
||||||
|
std::bitset<32> ifStack;
|
||||||
|
|
||||||
|
// Program counter
|
||||||
|
u32 pc = 0;
|
||||||
|
Memory& mem;
|
||||||
|
HIDService& hid;
|
||||||
|
|
||||||
|
// Has the cheat ended?
|
||||||
|
bool running = false;
|
||||||
|
// Run 1 AR instruction
|
||||||
|
void runInstruction(const Cheat& cheat, u32 instruction);
|
||||||
|
|
||||||
|
// Action Replay has a billion D-type opcodes so this handles all of them
|
||||||
|
void executeDType(const Cheat& cheat, u32 instruction);
|
||||||
|
|
||||||
|
u8 read8(u32 addr);
|
||||||
|
u16 read16(u32 addr);
|
||||||
|
u32 read32(u32 addr);
|
||||||
|
|
||||||
|
void write8(u32 addr, u8 value);
|
||||||
|
void write16(u32 addr, u16 value);
|
||||||
|
void write32(u32 addr, u32 value);
|
||||||
|
|
||||||
|
void pushConditionBlock(bool condition);
|
||||||
|
|
||||||
|
public:
|
||||||
|
ActionReplay(Memory& mem, HIDService& hid);
|
||||||
|
void runCheat(const Cheat& cheat);
|
||||||
|
void reset();
|
||||||
|
};
|
36
include/cheats.hpp
Normal file
36
include/cheats.hpp
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
#pragma once
|
||||||
|
#include <array>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "action_replay.hpp"
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include "services/hid.hpp"
|
||||||
|
|
||||||
|
// Forward-declare this since it's just passed and we don't want to include memory.hpp and increase compile time
|
||||||
|
class Memory;
|
||||||
|
|
||||||
|
class Cheats {
|
||||||
|
public:
|
||||||
|
enum class CheatType {
|
||||||
|
ActionReplay, // CTRPF cheats
|
||||||
|
// TODO: Other cheat devices and standards?
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Cheat {
|
||||||
|
CheatType type;
|
||||||
|
std::vector<u32> instructions;
|
||||||
|
};
|
||||||
|
|
||||||
|
Cheats(Memory& mem, HIDService& hid);
|
||||||
|
void addCheat(const Cheat& cheat);
|
||||||
|
void reset();
|
||||||
|
void run();
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
bool haveCheats() const { return cheatsLoaded; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
ActionReplay ar; // An ActionReplay cheat machine for executing CTRPF codes
|
||||||
|
std::vector<Cheat> cheats;
|
||||||
|
bool cheatsLoaded = false;
|
||||||
|
};
|
7
include/compiler_builtins.hpp
Normal file
7
include/compiler_builtins.hpp
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#define ALWAYS_INLINE __forceinline
|
||||||
|
#else
|
||||||
|
#define ALWAYS_INLINE __attribute__((always_inline))
|
||||||
|
#endif
|
|
@ -1,10 +1,22 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include "renderer.hpp"
|
||||||
|
|
||||||
// Remember to initialize every field here to its default value otherwise bad things will happen
|
// Remember to initialize every field here to its default value otherwise bad things will happen
|
||||||
struct EmulatorConfig {
|
struct EmulatorConfig {
|
||||||
bool shaderJitEnabled = false;
|
bool shaderJitEnabled = true;
|
||||||
|
bool discordRpcEnabled = false;
|
||||||
|
RendererType rendererType = RendererType::OpenGL;
|
||||||
|
|
||||||
|
bool sdCardInserted = true;
|
||||||
|
bool sdWriteProtected = false;
|
||||||
|
|
||||||
|
bool chargerPlugged = true;
|
||||||
|
// Default to 3% battery to make users suffer
|
||||||
|
int batteryPercentage = 3;
|
||||||
|
|
||||||
|
EmulatorConfig(const std::filesystem::path& path);
|
||||||
void load(const std::filesystem::path& path);
|
void load(const std::filesystem::path& path);
|
||||||
void save(const std::filesystem::path& path);
|
void save(const std::filesystem::path& path);
|
||||||
};
|
};
|
|
@ -108,7 +108,7 @@ public:
|
||||||
return getCyclesForInstruction(isThumb, instruction);
|
return getCyclesForInstruction(isThumb, instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
MyEnvironment(Memory& mem, Kernel& kernel, CPU& cpu) : mem(mem), kernel(kernel) {}
|
MyEnvironment(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class CPU {
|
class CPU {
|
||||||
|
|
|
@ -105,6 +105,7 @@ namespace Crypto {
|
||||||
AESEngine() {}
|
AESEngine() {}
|
||||||
void loadKeys(const std::filesystem::path& path);
|
void loadKeys(const std::filesystem::path& path);
|
||||||
bool haveKeys() { return keysLoaded; }
|
bool haveKeys() { return keysLoaded; }
|
||||||
|
bool haveGenerator() { return m_generator.has_value(); }
|
||||||
|
|
||||||
constexpr bool hasKeyX(std::size_t slotId) {
|
constexpr bool hasKeyX(std::size_t slotId) {
|
||||||
if (slotId >= AesKeySlotCount) {
|
if (slotId >= AesKeySlotCount) {
|
||||||
|
|
23
include/discord_rpc.hpp
Normal file
23
include/discord_rpc.hpp
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef PANDA3DS_ENABLE_DISCORD_RPC
|
||||||
|
#include <discord_rpc.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Discord {
|
||||||
|
enum class RPCStatus { Idling, Playing };
|
||||||
|
|
||||||
|
class RPC {
|
||||||
|
std::uint64_t startTimestamp;
|
||||||
|
bool enabled = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void init();
|
||||||
|
void update(RPCStatus status, const std::string& title);
|
||||||
|
void stop();
|
||||||
|
};
|
||||||
|
} // namespace Discord
|
||||||
|
|
||||||
|
#endif
|
|
@ -7,14 +7,16 @@
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
#include "PICA/gpu.hpp"
|
#include "PICA/gpu.hpp"
|
||||||
|
#include "cheats.hpp"
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
#include "cpu.hpp"
|
#include "cpu.hpp"
|
||||||
#include "crypto/aes_engine.hpp"
|
#include "crypto/aes_engine.hpp"
|
||||||
|
#include "discord_rpc.hpp"
|
||||||
#include "io_file.hpp"
|
#include "io_file.hpp"
|
||||||
#include "memory.hpp"
|
#include "memory.hpp"
|
||||||
|
|
||||||
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
||||||
#include "httpserver.hpp"
|
#include "http_server.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
enum class ROMType {
|
enum class ROMType {
|
||||||
|
@ -25,13 +27,14 @@ enum class ROMType {
|
||||||
};
|
};
|
||||||
|
|
||||||
class Emulator {
|
class Emulator {
|
||||||
|
EmulatorConfig config;
|
||||||
CPU cpu;
|
CPU cpu;
|
||||||
GPU gpu;
|
GPU gpu;
|
||||||
Memory memory;
|
Memory memory;
|
||||||
Kernel kernel;
|
Kernel kernel;
|
||||||
Crypto::AESEngine aesEngine;
|
Crypto::AESEngine aesEngine;
|
||||||
|
Cheats cheats;
|
||||||
|
|
||||||
EmulatorConfig config;
|
|
||||||
SDL_Window* window;
|
SDL_Window* window;
|
||||||
|
|
||||||
#ifdef PANDA3DS_ENABLE_OPENGL
|
#ifdef PANDA3DS_ENABLE_OPENGL
|
||||||
|
@ -54,12 +57,19 @@ class Emulator {
|
||||||
static constexpr u32 width = 400;
|
static constexpr u32 width = 400;
|
||||||
static constexpr u32 height = 240 * 2; // * 2 because 2 screens
|
static constexpr u32 height = 240 * 2; // * 2 because 2 screens
|
||||||
ROMType romType = ROMType::None;
|
ROMType romType = ROMType::None;
|
||||||
bool running = true;
|
bool running = false; // Is the emulator running a game?
|
||||||
|
bool programRunning = false; // Is the emulator program itself running?
|
||||||
|
|
||||||
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
||||||
HttpServer httpServer;
|
HttpServer httpServer;
|
||||||
|
friend struct HttpServer;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef PANDA3DS_ENABLE_DISCORD_RPC
|
||||||
|
Discord::RPC discordRpc;
|
||||||
|
#endif
|
||||||
|
void updateDiscord();
|
||||||
|
|
||||||
// Keep the handle for the ROM here to reload when necessary and to prevent deleting it
|
// Keep the handle for the ROM here to reload when necessary and to prevent deleting it
|
||||||
// This is currently only used for ELFs, NCSDs use the IOFile API instead
|
// This is currently only used for ELFs, NCSDs use the IOFile API instead
|
||||||
std::ifstream loadedELF;
|
std::ifstream loadedELF;
|
||||||
|
@ -70,8 +80,8 @@ class Emulator {
|
||||||
public:
|
public:
|
||||||
// Decides whether to reload or not reload the ROM when resetting. We use enum class over a plain bool for clarity.
|
// Decides whether to reload or not reload the ROM when resetting. We use enum class over a plain bool for clarity.
|
||||||
// If NoReload is selected, the emulator will not reload its selected ROM. This is useful for things like booting up the emulator, or resetting to
|
// If NoReload is selected, the emulator will not reload its selected ROM. This is useful for things like booting up the emulator, or resetting to
|
||||||
// change ROMs. If Reload is selected, the emulator will reload its selected ROM. This is useful for eg a "reset" button that keeps the current ROM
|
// change ROMs. If Reload is selected, the emulator will reload its selected ROM. This is useful for eg a "reset" button that keeps the current
|
||||||
// and just resets the emu
|
// ROM and just resets the emu
|
||||||
enum class ReloadOption { NoReload, Reload };
|
enum class ReloadOption { NoReload, Reload };
|
||||||
|
|
||||||
Emulator();
|
Emulator();
|
||||||
|
@ -83,13 +93,13 @@ class Emulator {
|
||||||
void run();
|
void run();
|
||||||
void runFrame();
|
void runFrame();
|
||||||
|
|
||||||
|
void resume(); // Resume the emulator
|
||||||
|
void pause(); // Pause the emulator
|
||||||
|
void togglePause();
|
||||||
|
|
||||||
bool loadROM(const std::filesystem::path& path);
|
bool loadROM(const std::filesystem::path& path);
|
||||||
bool loadNCSD(const std::filesystem::path& path, ROMType type);
|
bool loadNCSD(const std::filesystem::path& path, ROMType type);
|
||||||
bool loadELF(const std::filesystem::path& path);
|
bool loadELF(const std::filesystem::path& path);
|
||||||
bool loadELF(std::ifstream& file);
|
bool loadELF(std::ifstream& file);
|
||||||
void initGraphicsContext();
|
void initGraphicsContext();
|
||||||
|
|
||||||
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
|
||||||
void pollHttpServer();
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -116,15 +116,34 @@ struct ArchiveSession {
|
||||||
ArchiveSession(ArchiveBase* archive, const FSPath& filePath, bool isOpen = true) : archive(archive), path(filePath), isOpen(isOpen) {}
|
ArchiveSession(ArchiveBase* archive, const FSPath& filePath, bool isOpen = true) : archive(archive), path(filePath), isOpen(isOpen) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DirectorySession {
|
struct DirectoryEntry {
|
||||||
ArchiveBase* archive = nullptr;
|
std::filesystem::path path;
|
||||||
// For directories which are mirrored to a specific path on the disk, this contains that path
|
bool isDirectory;
|
||||||
// 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),
|
struct DirectorySession {
|
||||||
isOpen(isOpen) {}
|
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;
|
||||||
|
|
||||||
|
// The list of directory entries + the index of the entry we're currently inspecting
|
||||||
|
std::vector<DirectoryEntry> entries;
|
||||||
|
size_t currentEntry;
|
||||||
|
|
||||||
|
bool isOpen;
|
||||||
|
|
||||||
|
DirectorySession(ArchiveBase* archive, std::filesystem::path path, bool isOpen = true) : archive(archive), pathOnDisk(path), isOpen(isOpen) {
|
||||||
|
currentEntry = 0; // Start from entry 0
|
||||||
|
|
||||||
|
// Read all directory entries, cache them
|
||||||
|
for (auto& e : std::filesystem::directory_iterator(path)) {
|
||||||
|
DirectoryEntry entry;
|
||||||
|
entry.path = e.path();
|
||||||
|
entry.isDirectory = std::filesystem::is_directory(e);
|
||||||
|
entries.push_back(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Represents a file descriptor obtained from OpenFile. If the optional is nullopt, opening the file failed.
|
// Represents a file descriptor obtained from OpenFile. If the optional is nullopt, opening the file failed.
|
||||||
|
|
|
@ -5,7 +5,7 @@ class SaveDataArchive : public ArchiveBase {
|
||||||
public:
|
public:
|
||||||
SaveDataArchive(Memory& mem) : ArchiveBase(mem) {}
|
SaveDataArchive(Memory& mem) : ArchiveBase(mem) {}
|
||||||
|
|
||||||
u64 getFreeBytes() override { Helpers::panic("SaveData::GetFreeBytes unimplemented"); return 0; }
|
u64 getFreeBytes() override { return 32_MB; }
|
||||||
std::string name() override { return "SaveData"; }
|
std::string name() override { return "SaveData"; }
|
||||||
|
|
||||||
HorizonResult createDirectory(const FSPath& path) override;
|
HorizonResult createDirectory(const FSPath& path) override;
|
||||||
|
|
20
include/fs/ivfc.hpp
Normal file
20
include/fs/ivfc.hpp
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "helpers.hpp"
|
||||||
|
|
||||||
|
namespace IVFC {
|
||||||
|
struct IVFCLevel {
|
||||||
|
u64 logicalOffset;
|
||||||
|
u64 size;
|
||||||
|
u64 blockSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IVFC {
|
||||||
|
u64 masterHashSize;
|
||||||
|
std::vector<IVFCLevel> levels;
|
||||||
|
};
|
||||||
|
|
||||||
|
size_t parseIVFC(uintptr_t ivfcStart, IVFC& ivfc);
|
||||||
|
} // namespace IVFC
|
22
include/fs/romfs.hpp
Normal file
22
include/fs/romfs.hpp
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
#pragma once
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "helpers.hpp"
|
||||||
|
|
||||||
|
namespace RomFS {
|
||||||
|
struct RomFSNode {
|
||||||
|
std::u16string name;
|
||||||
|
// The file/directory offset relative to the start of the RomFS
|
||||||
|
u64 metadataOffset = 0;
|
||||||
|
u64 dataOffset = 0;
|
||||||
|
u64 dataSize = 0;
|
||||||
|
bool isDirectory = false;
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<RomFSNode>> directories;
|
||||||
|
std::vector<std::unique_ptr<RomFSNode>> files;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<RomFSNode> parseRomFSTree(uintptr_t romFS, u64 romFSSize);
|
||||||
|
} // namespace RomFS
|
|
@ -7,6 +7,7 @@
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "termcolor.hpp"
|
#include "termcolor.hpp"
|
||||||
|
|
||||||
|
@ -30,6 +31,17 @@ using s32 = std::int32_t;
|
||||||
using s64 = std::int64_t;
|
using s64 = std::int64_t;
|
||||||
|
|
||||||
namespace Helpers {
|
namespace Helpers {
|
||||||
|
template <class... Args>
|
||||||
|
std::string format(const std::string& fmt, Args&&... args) {
|
||||||
|
const int size = std::snprintf(nullptr, 0, fmt.c_str(), args...) + 1;
|
||||||
|
if (size <= 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto buf = std::make_unique<char[]>(size);
|
||||||
|
std::snprintf(buf.get(), size, fmt.c_str(), args ...);
|
||||||
|
return std::string(buf.get(), buf.get() + size - 1);
|
||||||
|
}
|
||||||
|
|
||||||
// Unconditional panic, unlike panicDev which does not panic on user builds
|
// Unconditional panic, unlike panicDev which does not panic on user builds
|
||||||
template <class... Args>
|
template <class... Args>
|
||||||
[[noreturn]] static void panic(const char* fmt, Args&&... args) {
|
[[noreturn]] static void panic(const char* fmt, Args&&... args) {
|
||||||
|
@ -125,7 +137,7 @@ namespace Helpers {
|
||||||
return getBits<offset, bits, ValueT, ValueT>(value);
|
return getBits<offset, bits, ValueT, ValueT>(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HELPERS_APPLE_CLANG
|
#if defined(HELPERS_APPLE_CLANG) || defined(__ANDROID__)
|
||||||
template <class To, class From>
|
template <class To, class From>
|
||||||
constexpr To bit_cast(const From& from) noexcept {
|
constexpr To bit_cast(const From& from) noexcept {
|
||||||
return *reinterpret_cast<const To*>(&from);
|
return *reinterpret_cast<const To*>(&from);
|
||||||
|
@ -155,4 +167,3 @@ namespace Helpers {
|
||||||
constexpr size_t operator""_KB(unsigned long long int x) { return 1024ULL * x; }
|
constexpr size_t operator""_KB(unsigned long long int x) { return 1024ULL * x; }
|
||||||
constexpr size_t operator""_MB(unsigned long long int x) { return 1024_KB * x; }
|
constexpr size_t operator""_MB(unsigned long long int x) { return 1024_KB * x; }
|
||||||
constexpr size_t operator""_GB(unsigned long long int x) { return 1024_MB * x; }
|
constexpr size_t operator""_GB(unsigned long long int x) { return 1024_MB * x; }
|
||||||
|
|
||||||
|
|
81
include/http_server.hpp
Normal file
81
include/http_server.hpp
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <atomic>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "helpers.hpp"
|
||||||
|
|
||||||
|
enum class HttpActionType { None, Screenshot, Key, TogglePause, Reset, LoadRom, Step };
|
||||||
|
|
||||||
|
class Emulator;
|
||||||
|
namespace httplib {
|
||||||
|
class Server;
|
||||||
|
struct Response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapper for httplib::Response that allows the HTTP server to wait for the response to be ready
|
||||||
|
struct DeferredResponseWrapper {
|
||||||
|
DeferredResponseWrapper(httplib::Response& response) : inner_response(response) {}
|
||||||
|
|
||||||
|
httplib::Response& inner_response;
|
||||||
|
std::mutex mutex;
|
||||||
|
std::condition_variable cv;
|
||||||
|
bool ready = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Actions derive from this class and are used to communicate with the HTTP server
|
||||||
|
class HttpAction {
|
||||||
|
HttpActionType type;
|
||||||
|
|
||||||
|
public:
|
||||||
|
HttpAction(HttpActionType type) : type(type) {}
|
||||||
|
virtual ~HttpAction() = default;
|
||||||
|
|
||||||
|
HttpActionType getType() const { return type; }
|
||||||
|
|
||||||
|
static std::unique_ptr<HttpAction> createScreenshotAction(DeferredResponseWrapper& response);
|
||||||
|
static std::unique_ptr<HttpAction> createKeyAction(u32 key, bool state);
|
||||||
|
static std::unique_ptr<HttpAction> createLoadRomAction(DeferredResponseWrapper& response, const std::filesystem::path& path, bool paused);
|
||||||
|
static std::unique_ptr<HttpAction> createTogglePauseAction();
|
||||||
|
static std::unique_ptr<HttpAction> createResetAction();
|
||||||
|
static std::unique_ptr<HttpAction> createStepAction(DeferredResponseWrapper& response, int frames);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HttpServer {
|
||||||
|
HttpServer(Emulator* emulator);
|
||||||
|
~HttpServer();
|
||||||
|
void processActions();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr const char* httpServerScreenshotPath = "screenshot.png";
|
||||||
|
|
||||||
|
Emulator* emulator;
|
||||||
|
std::unique_ptr<httplib::Server> server;
|
||||||
|
|
||||||
|
std::thread httpServerThread;
|
||||||
|
std::queue<std::unique_ptr<HttpAction>> actionQueue;
|
||||||
|
std::mutex actionQueueMutex;
|
||||||
|
std::unique_ptr<HttpAction> currentStepAction {};
|
||||||
|
|
||||||
|
std::map<std::string, u32> keyMap;
|
||||||
|
bool paused = false;
|
||||||
|
int framesToRun = 0;
|
||||||
|
|
||||||
|
void startHttpServer();
|
||||||
|
void pushAction(std::unique_ptr<HttpAction> action);
|
||||||
|
std::string status();
|
||||||
|
u32 stringToKey(const std::string& key_name);
|
||||||
|
|
||||||
|
HttpServer(const HttpServer&) = delete;
|
||||||
|
HttpServer& operator=(const HttpServer&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PANDA3DS_ENABLE_HTTP_SERVER
|
|
@ -1,36 +0,0 @@
|
||||||
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <atomic>
|
|
||||||
#include <map>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
#include "helpers.hpp"
|
|
||||||
|
|
||||||
enum class HttpAction { None, Screenshot, PressKey, ReleaseKey };
|
|
||||||
|
|
||||||
struct HttpServer {
|
|
||||||
static constexpr const char* httpServerScreenshotPath = "screenshot.png";
|
|
||||||
|
|
||||||
std::atomic_bool pendingAction = false;
|
|
||||||
HttpAction action = HttpAction::None;
|
|
||||||
std::mutex actionMutex = {};
|
|
||||||
u32 pendingKey = 0;
|
|
||||||
|
|
||||||
HttpServer();
|
|
||||||
|
|
||||||
void startHttpServer();
|
|
||||||
std::string status();
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::map<std::string, std::pair<u32, bool>> keyMap;
|
|
||||||
std::array<bool, 12> pressedKeys = {};
|
|
||||||
bool paused = false;
|
|
||||||
|
|
||||||
u32 stringToKey(const std::string& key_name);
|
|
||||||
bool getKeyState(const std::string& key_name);
|
|
||||||
void setKeyState(const std::string& key_name, bool state);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // PANDA3DS_ENABLE_HTTP_SERVER
|
|
|
@ -27,6 +27,7 @@ class IOFile {
|
||||||
|
|
||||||
bool seek(std::int64_t offset, int origin = SEEK_SET);
|
bool seek(std::int64_t offset, int origin = SEEK_SET);
|
||||||
bool rewind();
|
bool rewind();
|
||||||
|
bool flush();
|
||||||
FILE* getHandle();
|
FILE* getHandle();
|
||||||
static void setAppDataDir(const std::filesystem::path& dir);
|
static void setAppDataDir(const std::filesystem::path& dir);
|
||||||
static std::filesystem::path getAppData() { return appData; }
|
static std::filesystem::path getAppData() { return appData; }
|
||||||
|
|
|
@ -9,10 +9,18 @@ namespace ConfigMem {
|
||||||
SyscoreVer = 0x1FF80010,
|
SyscoreVer = 0x1FF80010,
|
||||||
EnvInfo = 0x1FF80014,
|
EnvInfo = 0x1FF80014,
|
||||||
AppMemAlloc = 0x1FF80040,
|
AppMemAlloc = 0x1FF80040,
|
||||||
|
FirmUnknown = 0x1FF80060,
|
||||||
|
FirmRevision = 0x1FF80061,
|
||||||
|
FirmVersionMinor = 0x1FF80062,
|
||||||
|
FirmVersionMajor = 0x1FF80063,
|
||||||
|
FirmSyscoreVer = 0x1FF80064,
|
||||||
|
FirmSdkVer = 0x1FF80068,
|
||||||
|
|
||||||
HardwareType = 0x1FF81004,
|
HardwareType = 0x1FF81004,
|
||||||
Datetime0 = 0x1FF81020,
|
Datetime0 = 0x1FF81020,
|
||||||
WifiMac = 0x1FF81060,
|
WifiMac = 0x1FF81060,
|
||||||
NetworkState = 0x1FF81067,
|
NetworkState = 0x1FF81067,
|
||||||
|
SliderState3D = 0x1FF81080,
|
||||||
LedState3D = 0x1FF81084,
|
LedState3D = 0x1FF81084,
|
||||||
BatteryState = 0x1FF81085,
|
BatteryState = 0x1FF81085,
|
||||||
Unknown1086 = 0x1FF81086,
|
Unknown1086 = 0x1FF81086,
|
||||||
|
|
|
@ -17,22 +17,28 @@ namespace KernelHandles {
|
||||||
BOSS, // Streetpass stuff?
|
BOSS, // Streetpass stuff?
|
||||||
CAM, // Camera service
|
CAM, // Camera service
|
||||||
CECD, // More Streetpass stuff?
|
CECD, // More Streetpass stuff?
|
||||||
CFG, // CFG service (Console & region info)
|
CFG_U, // CFG service (Console & region info)
|
||||||
DLP_SRVR, // Download Play: Server. Used for network play.
|
CFG_I,
|
||||||
DSP, // DSP service (Used for audio decoding and output)
|
DLP_SRVR, // Download Play: Server. Used for network play.
|
||||||
HID, // HID service (Handles everything input-related including gyro)
|
DSP, // DSP service (Used for audio decoding and output)
|
||||||
IR_USER, // One of 3 infrared communication services
|
HID, // HID service (Handles input-related things including gyro. Does NOT handle New3DS controls or CirclePadPro)
|
||||||
FRD, // Friend service (Miiverse friend service)
|
HTTP, // HTTP service (Handles HTTP requests)
|
||||||
FS, // Filesystem service
|
IR_USER, // One of 3 infrared communication services
|
||||||
GPU, // GPU service
|
FRD, // Friend service (Miiverse friend service)
|
||||||
LCD, // LCD service (Used for configuring the displays)
|
FS, // Filesystem service
|
||||||
LDR_RO, // Loader service. Used for loading CROs.
|
GPU, // GPU service
|
||||||
MIC, // MIC service (Controls the microphone)
|
LCD, // LCD service (Used for configuring the displays)
|
||||||
NFC, // NFC (Duh), used for Amiibo
|
LDR_RO, // Loader service. Used for loading CROs.
|
||||||
NIM, // Updates, DLC, etc
|
MCU_HWC, // Used for various MCU hardware-related things like battery control
|
||||||
NDM, // ?????
|
MIC, // MIC service (Controls the microphone)
|
||||||
PTM, // PTM service (Used for accessing various console info, such as battery, shell and pedometer state)
|
NFC, // NFC (Duh), used for Amiibo
|
||||||
Y2R, // Also does camera stuff
|
NIM, // Updates, DLC, etc
|
||||||
|
NDM, // ?????
|
||||||
|
NEWS_U, // This service literally has 1 command (AddNotification) and I don't even understand what it does
|
||||||
|
PTM, // PTM service (Used for accessing various console info, such as battery, shell and pedometer state)
|
||||||
|
SOC, // Socket service
|
||||||
|
SSL, // SSL service (Totally didn't expect that)
|
||||||
|
Y2R, // Also does camera stuff
|
||||||
|
|
||||||
MinServiceHandle = AC,
|
MinServiceHandle = AC,
|
||||||
MaxServiceHandle = Y2R,
|
MaxServiceHandle = Y2R,
|
||||||
|
@ -65,21 +71,27 @@ namespace KernelHandles {
|
||||||
case BOSS: return "BOSS";
|
case BOSS: return "BOSS";
|
||||||
case CAM: return "CAM";
|
case CAM: return "CAM";
|
||||||
case CECD: return "CECD";
|
case CECD: return "CECD";
|
||||||
case CFG: return "CFG";
|
case CFG_U: return "CFG:U";
|
||||||
|
case CFG_I: return "CFG:I";
|
||||||
case DSP: return "DSP";
|
case DSP: return "DSP";
|
||||||
case DLP_SRVR: return "DLP::SRVR";
|
case DLP_SRVR: return "DLP::SRVR";
|
||||||
case HID: return "HID";
|
case HID: return "HID";
|
||||||
|
case HTTP: return "HTTP";
|
||||||
case IR_USER: return "IR:USER";
|
case IR_USER: return "IR:USER";
|
||||||
case FRD: return "FRD";
|
case FRD: return "FRD";
|
||||||
case FS: return "FS";
|
case FS: return "FS";
|
||||||
case GPU: return "GSP::GPU";
|
case GPU: return "GSP::GPU";
|
||||||
case LCD: return "GSP::LCD";
|
case LCD: return "GSP::LCD";
|
||||||
case LDR_RO: return "LDR:RO";
|
case LDR_RO: return "LDR:RO";
|
||||||
|
case MCU_HWC: return "MCU::HWC";
|
||||||
case MIC: return "MIC";
|
case MIC: return "MIC";
|
||||||
case NDM: return "NDM";
|
case NDM: return "NDM";
|
||||||
|
case NEWS_U: return "NEWS_U";
|
||||||
case NFC: return "NFC";
|
case NFC: return "NFC";
|
||||||
case NIM: return "NIM";
|
case NIM: return "NIM";
|
||||||
case PTM: return "PTM";
|
case PTM: return "PTM";
|
||||||
|
case SOC: return "SOC";
|
||||||
|
case SSL: return "SSL";
|
||||||
case Y2R: return "Y2R";
|
case Y2R: return "Y2R";
|
||||||
default: return "Unknown";
|
default: return "Unknown";
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "config.hpp"
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "kernel_types.hpp"
|
#include "kernel_types.hpp"
|
||||||
#include "logger.hpp"
|
#include "logger.hpp"
|
||||||
|
@ -34,6 +35,7 @@ class Kernel {
|
||||||
|
|
||||||
std::vector<KernelObject> objects;
|
std::vector<KernelObject> objects;
|
||||||
std::vector<Handle> portHandles;
|
std::vector<Handle> portHandles;
|
||||||
|
std::vector<Handle> mutexHandles;
|
||||||
|
|
||||||
// Thread indices, sorted by priority
|
// Thread indices, sorted by priority
|
||||||
std::vector<int> threadIndices;
|
std::vector<int> threadIndices;
|
||||||
|
@ -52,17 +54,21 @@ class Kernel {
|
||||||
// Top 8 bits are the major version, bottom 8 are the minor version
|
// Top 8 bits are the major version, bottom 8 are the minor version
|
||||||
u16 kernelVersion = 0;
|
u16 kernelVersion = 0;
|
||||||
|
|
||||||
|
// Shows whether a reschedule will be need
|
||||||
|
bool needReschedule = false;
|
||||||
|
|
||||||
Handle makeArbiter();
|
Handle makeArbiter();
|
||||||
Handle makeProcess(u32 id);
|
Handle makeProcess(u32 id);
|
||||||
Handle makePort(const char* name);
|
Handle makePort(const char* name);
|
||||||
Handle makeSession(Handle port);
|
Handle makeSession(Handle port);
|
||||||
Handle makeThread(u32 entrypoint, u32 initialSP, u32 priority, s32 id, u32 arg,ThreadStatus status = ThreadStatus::Dormant);
|
Handle makeThread(u32 entrypoint, u32 initialSP, u32 priority, ProcessorID id, u32 arg,ThreadStatus status = ThreadStatus::Dormant);
|
||||||
Handle makeMemoryBlock(u32 addr, u32 size, u32 myPermission, u32 otherPermission);
|
Handle makeMemoryBlock(u32 addr, u32 size, u32 myPermission, u32 otherPermission);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Handle makeEvent(ResetType resetType); // Needs to be public to be accessible to the APT/HID services
|
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 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
|
Handle makeSemaphore(u32 initialCount, u32 maximumCount); // Needs to be public to be accessible to the service manager port
|
||||||
|
Handle makeTimer(ResetType resetType);
|
||||||
|
|
||||||
// Signals an event, returns true on success or false if the event does not exist
|
// Signals an event, returns true on success or false if the event does not exist
|
||||||
bool signalEvent(Handle e);
|
bool signalEvent(Handle e);
|
||||||
|
@ -73,7 +79,6 @@ private:
|
||||||
void switchThread(int newThreadIndex);
|
void switchThread(int newThreadIndex);
|
||||||
void sortThreads();
|
void sortThreads();
|
||||||
std::optional<int> getNextThread();
|
std::optional<int> getNextThread();
|
||||||
void switchToNextThread();
|
|
||||||
void rescheduleThreads();
|
void rescheduleThreads();
|
||||||
bool canThreadRun(const Thread& t);
|
bool canThreadRun(const Thread& t);
|
||||||
bool shouldWaitOnObject(KernelObject* object);
|
bool shouldWaitOnObject(KernelObject* object);
|
||||||
|
@ -107,9 +112,9 @@ private:
|
||||||
MAKE_LOG_FUNCTION(log, kernelLogger)
|
MAKE_LOG_FUNCTION(log, kernelLogger)
|
||||||
MAKE_LOG_FUNCTION(logSVC, svcLogger)
|
MAKE_LOG_FUNCTION(logSVC, svcLogger)
|
||||||
MAKE_LOG_FUNCTION(logThread, threadLogger)
|
MAKE_LOG_FUNCTION(logThread, threadLogger)
|
||||||
MAKE_LOG_FUNCTION(logDebugString, debugStringLogger)
|
|
||||||
MAKE_LOG_FUNCTION(logError, errorLogger)
|
MAKE_LOG_FUNCTION(logError, errorLogger)
|
||||||
MAKE_LOG_FUNCTION(logFileIO, fileIOLogger)
|
MAKE_LOG_FUNCTION(logFileIO, fileIOLogger)
|
||||||
|
MAKE_LOG_FUNCTION_USER(logDebugString, debugStringLogger)
|
||||||
|
|
||||||
// SVC implementations
|
// SVC implementations
|
||||||
void arbitrateAddress();
|
void arbitrateAddress();
|
||||||
|
@ -121,24 +126,31 @@ private:
|
||||||
void exitThread();
|
void exitThread();
|
||||||
void mapMemoryBlock();
|
void mapMemoryBlock();
|
||||||
void queryMemory();
|
void queryMemory();
|
||||||
|
void getCurrentProcessorNumber();
|
||||||
void getProcessID();
|
void getProcessID();
|
||||||
void getProcessInfo();
|
void getProcessInfo();
|
||||||
void getResourceLimit();
|
void getResourceLimit();
|
||||||
void getResourceLimitLimitValues();
|
void getResourceLimitLimitValues();
|
||||||
void getResourceLimitCurrentValues();
|
void getResourceLimitCurrentValues();
|
||||||
|
void getSystemInfo();
|
||||||
void getSystemTick();
|
void getSystemTick();
|
||||||
void getThreadID();
|
void getThreadID();
|
||||||
|
void getThreadIdealProcessor();
|
||||||
void getThreadPriority();
|
void getThreadPriority();
|
||||||
void sendSyncRequest();
|
void sendSyncRequest();
|
||||||
void setThreadPriority();
|
void setThreadPriority();
|
||||||
|
void svcCancelTimer();
|
||||||
void svcClearEvent();
|
void svcClearEvent();
|
||||||
|
void svcClearTimer();
|
||||||
void svcCloseHandle();
|
void svcCloseHandle();
|
||||||
void svcCreateEvent();
|
void svcCreateEvent();
|
||||||
void svcCreateMutex();
|
void svcCreateMutex();
|
||||||
void svcCreateSemaphore();
|
void svcCreateSemaphore();
|
||||||
|
void svcCreateTimer();
|
||||||
void svcReleaseMutex();
|
void svcReleaseMutex();
|
||||||
void svcReleaseSemaphore();
|
void svcReleaseSemaphore();
|
||||||
void svcSignalEvent();
|
void svcSignalEvent();
|
||||||
|
void svcSetTimer();
|
||||||
void svcSleepThread();
|
void svcSleepThread();
|
||||||
void connectToPort();
|
void connectToPort();
|
||||||
void outputDebugString();
|
void outputDebugString();
|
||||||
|
@ -162,12 +174,21 @@ private:
|
||||||
void readDirectory(u32 messagePointer, Handle directory);
|
void readDirectory(u32 messagePointer, Handle directory);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Kernel(CPU& cpu, Memory& mem, GPU& gpu);
|
Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config);
|
||||||
void initializeFS() { return serviceManager.initializeFS(); }
|
void initializeFS() { return serviceManager.initializeFS(); }
|
||||||
void setVersion(u8 major, u8 minor);
|
void setVersion(u8 major, u8 minor);
|
||||||
void serviceSVC(u32 svc);
|
void serviceSVC(u32 svc);
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
|
void requireReschedule() { needReschedule = true; }
|
||||||
|
|
||||||
|
void evalReschedule() {
|
||||||
|
if (needReschedule) {
|
||||||
|
needReschedule = false;
|
||||||
|
rescheduleThreads();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Handle makeObject(KernelObjectType type) {
|
Handle makeObject(KernelObjectType type) {
|
||||||
if (handleCounter > KernelHandles::Max) [[unlikely]] {
|
if (handleCounter > KernelHandles::Max) [[unlikely]] {
|
||||||
Helpers::panic("Hlep we somehow created enough kernel objects to overflow this thing");
|
Helpers::panic("Hlep we somehow created enough kernel objects to overflow this thing");
|
||||||
|
|
|
@ -34,6 +34,16 @@ enum class ArbitrationType {
|
||||||
DecrementAndWaitIfLessTimeout = 4
|
DecrementAndWaitIfLessTimeout = 4
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class ProcessorID : s32 {
|
||||||
|
AllCPUs = -1,
|
||||||
|
Default = -2,
|
||||||
|
|
||||||
|
AppCore = 0,
|
||||||
|
Syscore = 1,
|
||||||
|
New3DSExtra1 = 2,
|
||||||
|
New3DSExtra2 = 3
|
||||||
|
};
|
||||||
|
|
||||||
struct AddressArbiter {};
|
struct AddressArbiter {};
|
||||||
|
|
||||||
struct ResourceLimits {
|
struct ResourceLimits {
|
||||||
|
@ -95,7 +105,7 @@ struct Thread {
|
||||||
u32 entrypoint; // Initial r15 value
|
u32 entrypoint; // Initial r15 value
|
||||||
u32 priority;
|
u32 priority;
|
||||||
u32 arg;
|
u32 arg;
|
||||||
s32 processorID;
|
ProcessorID processorID;
|
||||||
ThreadStatus status;
|
ThreadStatus status;
|
||||||
Handle handle; // OS handle for this thread
|
Handle handle; // OS handle for this thread
|
||||||
int index; // Index of the thread. 0 for the first thread, 1 for the second, and so on
|
int index; // Index of the thread. 0 for the first thread, 1 for the second, and so on
|
||||||
|
|
|
@ -2,77 +2,84 @@
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "io_file.hpp"
|
|
||||||
#include "helpers.hpp"
|
|
||||||
#include "crypto/aes_engine.hpp"
|
#include "crypto/aes_engine.hpp"
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include "io_file.hpp"
|
||||||
|
#include "services/region_codes.hpp"
|
||||||
|
|
||||||
struct NCCH {
|
struct NCCH {
|
||||||
struct EncryptionInfo {
|
struct EncryptionInfo {
|
||||||
Crypto::AESKey normalKey;
|
Crypto::AESKey normalKey;
|
||||||
Crypto::AESKey initialCounter;
|
Crypto::AESKey initialCounter;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FSInfo { // Info on the ExeFS/RomFS
|
struct FSInfo { // Info on the ExeFS/RomFS
|
||||||
u64 offset = 0;
|
u64 offset = 0;
|
||||||
u64 size = 0;
|
u64 size = 0;
|
||||||
u64 hashRegionSize = 0;
|
u64 hashRegionSize = 0;
|
||||||
std::optional<EncryptionInfo> encryptionInfo;
|
std::optional<EncryptionInfo> encryptionInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Descriptions for .text, .data and .rodata sections
|
// Descriptions for .text, .data and .rodata sections
|
||||||
struct CodeSetInfo {
|
struct CodeSetInfo {
|
||||||
u32 address = 0;
|
u32 address = 0;
|
||||||
u32 pageCount = 0;
|
u32 pageCount = 0;
|
||||||
u32 size = 0;
|
u32 size = 0;
|
||||||
|
|
||||||
// Extract the code set info from the relevant header data
|
// Extract the code set info from the relevant header data
|
||||||
void extract(const u8* headerEntry) {
|
void extract(const u8 *headerEntry) {
|
||||||
address = *(u32*)&headerEntry[0];
|
address = *(u32 *)&headerEntry[0];
|
||||||
pageCount = *(u32*)&headerEntry[4];
|
pageCount = *(u32 *)&headerEntry[4];
|
||||||
size = *(u32*)&headerEntry[8];
|
size = *(u32 *)&headerEntry[8];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
u64 partitionIndex = 0;
|
u64 partitionIndex = 0;
|
||||||
u64 fileOffset = 0;
|
u64 fileOffset = 0;
|
||||||
|
|
||||||
bool isNew3DS = false;
|
bool isNew3DS = false;
|
||||||
bool initialized = false;
|
bool initialized = false;
|
||||||
bool compressCode = false; // Shows whether the .code file in the ExeFS is compressed
|
bool compressCode = false; // Shows whether the .code file in the ExeFS is compressed
|
||||||
bool mountRomFS = false;
|
bool mountRomFS = false;
|
||||||
bool encrypted = false;
|
bool encrypted = false;
|
||||||
bool fixedCryptoKey = false;
|
bool fixedCryptoKey = false;
|
||||||
bool seedCrypto = false;
|
bool seedCrypto = false;
|
||||||
u8 secondaryKeySlot = 0;
|
u8 secondaryKeySlot = 0;
|
||||||
|
|
||||||
static constexpr u64 mediaUnit = 0x200;
|
static constexpr u64 mediaUnit = 0x200;
|
||||||
u64 size = 0; // Size of NCCH converted to bytes
|
u64 size = 0; // Size of NCCH converted to bytes
|
||||||
u32 stackSize = 0;
|
u32 stackSize = 0;
|
||||||
u32 bssSize = 0;
|
u32 bssSize = 0;
|
||||||
u32 exheaderSize = 0;
|
u32 exheaderSize = 0;
|
||||||
|
|
||||||
FSInfo exheaderInfo;
|
FSInfo exheaderInfo;
|
||||||
FSInfo exeFS;
|
FSInfo exeFS;
|
||||||
FSInfo romFS;
|
FSInfo romFS;
|
||||||
CodeSetInfo text, data, rodata;
|
CodeSetInfo text, data, rodata;
|
||||||
|
|
||||||
// Contents of the .code file in the ExeFS
|
// Contents of the .code file in the ExeFS
|
||||||
std::vector<u8> codeFile;
|
std::vector<u8> codeFile;
|
||||||
// Contains of the cart's save data
|
// Contains of the cart's save data
|
||||||
std::vector<u8> saveData;
|
std::vector<u8> saveData;
|
||||||
|
// The cart region. Only the CXI's region matters to us. Necessary to get past region locking
|
||||||
|
std::optional<Regions> region = std::nullopt;
|
||||||
|
|
||||||
// Returns true on success, false on failure
|
// Returns true on success, false on failure
|
||||||
// Partition index/offset/size must have been set before this
|
// Partition index/offset/size must have been set before this
|
||||||
bool loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSInfo &info);
|
bool loadFromHeader(Crypto::AESEngine &aesEngine, IOFile &file, const FSInfo &info);
|
||||||
|
|
||||||
bool hasExtendedHeader() { return exheaderSize != 0; }
|
bool hasExtendedHeader() { return exheaderSize != 0; }
|
||||||
bool hasExeFS() { return exeFS.size != 0; }
|
bool hasExeFS() { return exeFS.size != 0; }
|
||||||
bool hasRomFS() { return romFS.size != 0; }
|
bool hasRomFS() { return romFS.size != 0; }
|
||||||
bool hasCode() { return codeFile.size() != 0; }
|
bool hasCode() { return codeFile.size() != 0; }
|
||||||
bool hasSaveData() { return saveData.size() != 0; }
|
bool hasSaveData() { return saveData.size() != 0; }
|
||||||
|
|
||||||
std::pair<bool, Crypto::AESKey> getPrimaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY);
|
// Parse SMDH for region info and such. Returns false on failure, true on success
|
||||||
std::pair<bool, Crypto::AESKey> getSecondaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY);
|
bool parseSMDH(const std::vector<u8> &smdh);
|
||||||
|
|
||||||
std::pair<bool, std::size_t> readFromFile(IOFile& file, const FSInfo &info, u8 *dst, std::size_t offset, std::size_t size);
|
std::pair<bool, Crypto::AESKey> getPrimaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY);
|
||||||
|
std::pair<bool, Crypto::AESKey> getSecondaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY);
|
||||||
|
|
||||||
|
std::pair<bool, std::size_t> readFromFile(IOFile &file, const FSInfo &info, u8 *dst, std::size_t offset, std::size_t size);
|
||||||
};
|
};
|
|
@ -3,60 +3,80 @@
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
namespace Log {
|
namespace Log {
|
||||||
// Our logger class
|
// Our logger class
|
||||||
template <bool enabled>
|
template <bool enabled>
|
||||||
class Logger {
|
class Logger {
|
||||||
public:
|
public:
|
||||||
void log(const char* fmt, ...) {
|
void log(const char* fmt, ...) {
|
||||||
if constexpr (!enabled) return;
|
if constexpr (!enabled) return;
|
||||||
|
|
||||||
std::va_list args;
|
|
||||||
va_start(args, fmt);
|
|
||||||
std::vprintf(fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Our loggers here. Enable/disable by toggling the template param
|
std::va_list args;
|
||||||
static Logger<false> kernelLogger;
|
va_start(args, fmt);
|
||||||
static Logger<true> debugStringLogger; // Enables output for the outputDebugString SVC
|
std::vprintf(fmt, args);
|
||||||
static Logger<false> errorLogger;
|
va_end(args);
|
||||||
static Logger<false> fileIOLogger;
|
}
|
||||||
static Logger<false> svcLogger;
|
};
|
||||||
static Logger<false> threadLogger;
|
|
||||||
static Logger<false> gpuLogger;
|
|
||||||
static Logger<false> rendererLogger;
|
|
||||||
static Logger<false> shaderJITLogger;
|
|
||||||
|
|
||||||
// Service loggers
|
// Our loggers here. Enable/disable by toggling the template param
|
||||||
static Logger<false> acLogger;
|
static Logger<false> kernelLogger;
|
||||||
static Logger<false> actLogger;
|
// Enables output for the outputDebugString SVC
|
||||||
static Logger<false> amLogger;
|
static Logger<true> debugStringLogger;
|
||||||
static Logger<false> aptLogger;
|
static Logger<false> errorLogger;
|
||||||
static Logger<false> bossLogger;
|
static Logger<false> fileIOLogger;
|
||||||
static Logger<false> camLogger;
|
static Logger<false> svcLogger;
|
||||||
static Logger<false> cecdLogger;
|
static Logger<false> threadLogger;
|
||||||
static Logger<false> cfgLogger;
|
static Logger<false> gpuLogger;
|
||||||
static Logger<false> dspServiceLogger;
|
static Logger<false> rendererLogger;
|
||||||
static Logger<false> dlpSrvrLogger;
|
static Logger<false> shaderJITLogger;
|
||||||
static Logger<false> frdLogger;
|
|
||||||
static Logger<false> fsLogger;
|
// Service loggers
|
||||||
static Logger<false> hidLogger;
|
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> dlpSrvrLogger;
|
||||||
|
static Logger<false> frdLogger;
|
||||||
|
static Logger<false> fsLogger;
|
||||||
|
static Logger<false> hidLogger;
|
||||||
|
static Logger<false> httpLogger;
|
||||||
static Logger<false> irUserLogger;
|
static Logger<false> irUserLogger;
|
||||||
static Logger<false> gspGPULogger;
|
static Logger<false> gspGPULogger;
|
||||||
static Logger<false> gspLCDLogger;
|
static Logger<false> gspLCDLogger;
|
||||||
static Logger<false> ldrLogger;
|
static Logger<false> ldrLogger;
|
||||||
static Logger<false> micLogger;
|
static Logger<false> mcuLogger;
|
||||||
static Logger<false> nfcLogger;
|
static Logger<false> micLogger;
|
||||||
static Logger<false> nimLogger;
|
static Logger<false> newsLogger;
|
||||||
static Logger<false> ndmLogger;
|
static Logger<false> nfcLogger;
|
||||||
static Logger<false> ptmLogger;
|
static Logger<false> nimLogger;
|
||||||
static Logger<false> y2rLogger;
|
static Logger<false> ndmLogger;
|
||||||
static Logger<false> srvLogger;
|
static Logger<false> ptmLogger;
|
||||||
|
static Logger<false> socLogger;
|
||||||
|
static Logger<false> sslLogger;
|
||||||
|
static Logger<false> y2rLogger;
|
||||||
|
static Logger<false> srvLogger;
|
||||||
|
|
||||||
#define MAKE_LOG_FUNCTION(functionName, logger) \
|
// We have 2 ways to create a log function
|
||||||
template <typename... Args> \
|
// MAKE_LOG_FUNCTION: Creates a log function which is toggleable but always killed for user-facing builds
|
||||||
void functionName(const char* fmt, Args... args) { \
|
// MAKE_LOG_FUNCTION_USER: Creates a log function which is toggleable, may be on for user builds as well
|
||||||
Log::logger.log(fmt, args...); \
|
// We need this because sadly due to the loggers taking variadic arguments, compilers will not properly
|
||||||
}
|
// Kill them fully even when they're disabled. The only way they will is if the function with varargs is totally empty
|
||||||
|
|
||||||
|
#define MAKE_LOG_FUNCTION_USER(functionName, logger) \
|
||||||
|
template <typename... Args> \
|
||||||
|
void functionName(const char* fmt, Args&&... args) { \
|
||||||
|
Log::logger.log(fmt, args...); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef PANDA3DS_USER_BUILD
|
||||||
|
#define MAKE_LOG_FUNCTION(functionName, logger) \
|
||||||
|
template <typename... Args> \
|
||||||
|
void functionName(const char* fmt, Args&&... args) {}
|
||||||
|
#else
|
||||||
|
#define MAKE_LOG_FUNCTION(functionName, logger) MAKE_LOG_FUNCTION_USER(functionName, logger)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
73
include/math_util.hpp
Normal file
73
include/math_util.hpp
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project / 2023 Panda3DS Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace Math {
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct Rectangle {
|
||||||
|
T left{};
|
||||||
|
T top{};
|
||||||
|
T right{};
|
||||||
|
T bottom{};
|
||||||
|
|
||||||
|
constexpr Rectangle() = default;
|
||||||
|
|
||||||
|
constexpr Rectangle(T left, T top, T right, T bottom)
|
||||||
|
: left(left), top(top), right(right), bottom(bottom) {}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr bool operator==(const Rectangle<T>& rhs) const {
|
||||||
|
return (left == rhs.left) && (top == rhs.top) && (right == rhs.right) &&
|
||||||
|
(bottom == rhs.bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr bool operator!=(const Rectangle<T>& rhs) const {
|
||||||
|
return !operator==(rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Rectangle<T> operator*(const T value) const {
|
||||||
|
return Rectangle{left * value, top * value, right * value, bottom * value};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Rectangle<T> operator/(const T value) const {
|
||||||
|
return Rectangle{left / value, top / value, right / value, bottom / value};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] T getWidth() const {
|
||||||
|
return std::abs(static_cast<std::make_signed_t<T>>(right - left));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] T getHeight() const {
|
||||||
|
return std::abs(static_cast<std::make_signed_t<T>>(bottom - top));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] T getArea() const {
|
||||||
|
return getWidth() * getHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Rectangle<T> translateX(const T x) const {
|
||||||
|
return Rectangle{left + x, top, right + x, bottom};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Rectangle<T> translateY(const T y) const {
|
||||||
|
return Rectangle{left, top + y, right, bottom + y};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Rectangle<T> scale(const float s) const {
|
||||||
|
return Rectangle{left, top, static_cast<T>(left + getWidth() * s),
|
||||||
|
static_cast<T>(top + getHeight() * s)};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
Rectangle(T, T, T, T) -> Rectangle<T>;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using Rect = Rectangle<T>;
|
||||||
|
|
||||||
|
} // end namespace Math
|
|
@ -5,11 +5,13 @@
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "config.hpp"
|
||||||
#include "crypto/aes_engine.hpp"
|
#include "crypto/aes_engine.hpp"
|
||||||
#include "helpers.hpp"
|
|
||||||
#include "handles.hpp"
|
#include "handles.hpp"
|
||||||
|
#include "helpers.hpp"
|
||||||
#include "loader/ncsd.hpp"
|
#include "loader/ncsd.hpp"
|
||||||
#include "services/shared_font.hpp"
|
#include "services/region_codes.hpp"
|
||||||
|
|
||||||
namespace PhysicalAddrs {
|
namespace PhysicalAddrs {
|
||||||
enum : u32 {
|
enum : u32 {
|
||||||
|
@ -110,7 +112,7 @@ class Memory {
|
||||||
std::vector<KernelMemoryTypes::MemoryInfo> memoryInfo;
|
std::vector<KernelMemoryTypes::MemoryInfo> memoryInfo;
|
||||||
|
|
||||||
std::array<SharedMemoryBlock, 3> sharedMemBlocks = {
|
std::array<SharedMemoryBlock, 3> sharedMemBlocks = {
|
||||||
SharedMemoryBlock(0, u32(_shared_font_len), KernelHandles::FontSharedMemHandle), // Shared memory for the system font
|
SharedMemoryBlock(0, 0, KernelHandles::FontSharedMemHandle), // Shared memory for the system font (size is 0 because we read the size from the cmrc filesystem
|
||||||
SharedMemoryBlock(0, 0x1000, KernelHandles::GSPSharedMemHandle), // GSP shared memory
|
SharedMemoryBlock(0, 0x1000, KernelHandles::GSPSharedMemHandle), // GSP shared memory
|
||||||
SharedMemoryBlock(0, 0x1000, KernelHandles::HIDSharedMemHandle) // HID shared memory
|
SharedMemoryBlock(0, 0x1000, KernelHandles::HIDSharedMemHandle) // HID shared memory
|
||||||
};
|
};
|
||||||
|
@ -139,12 +141,28 @@ private:
|
||||||
// Report a retail unit without JTAG
|
// Report a retail unit without JTAG
|
||||||
static constexpr u32 envInfo = 1;
|
static constexpr u32 envInfo = 1;
|
||||||
|
|
||||||
|
// Stored in Configuration Memory starting @ 0x1FF80060
|
||||||
|
struct FirmwareInfo {
|
||||||
|
u8 unk; // Usually 0 according to 3DBrew
|
||||||
|
u8 revision;
|
||||||
|
u8 minor;
|
||||||
|
u8 major;
|
||||||
|
u32 syscoreVer;
|
||||||
|
u32 sdkVer;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Values taken from 3DBrew and Citra
|
||||||
|
static constexpr FirmwareInfo firm{.unk = 0, .revision = 0, .minor = 0x34, .major = 2, .syscoreVer = 2, .sdkVer = 0x0000F297};
|
||||||
|
// Adjusted upon loading a ROM based on the ROM header. Used by CFG::SecureInfoGetArea to get past region locks
|
||||||
|
Regions region = Regions::USA;
|
||||||
|
const EmulatorConfig& config;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
u16 kernelVersion = 0;
|
u16 kernelVersion = 0;
|
||||||
u32 usedUserMemory = u32(0_MB); // How much of the APPLICATION FCRAM range is used (allocated to the appcore)
|
u32 usedUserMemory = u32(0_MB); // How much of the APPLICATION FCRAM range is used (allocated to the appcore)
|
||||||
u32 usedSystemMemory = u32(0_MB); // Similar for the SYSTEM range (reserved for the syscore)
|
u32 usedSystemMemory = u32(0_MB); // Similar for the SYSTEM range (reserved for the syscore)
|
||||||
|
|
||||||
Memory(u64& cpuTicks);
|
Memory(u64& cpuTicks, const EmulatorConfig& config);
|
||||||
void reset();
|
void reset();
|
||||||
void* getReadPointer(u32 address);
|
void* getReadPointer(u32 address);
|
||||||
void* getWritePointer(u32 address);
|
void* getWritePointer(u32 address);
|
||||||
|
@ -248,4 +266,6 @@ public:
|
||||||
|
|
||||||
void setVRAM(u8* pointer) { vram = pointer; }
|
void setVRAM(u8* pointer) { vram = pointer; }
|
||||||
bool allocateMainThreadStack(u32 size);
|
bool allocateMainThreadStack(u32 size);
|
||||||
};
|
Regions getConsoleRegion();
|
||||||
|
void copySharedFont(u8* ptr);
|
||||||
|
};
|
||||||
|
|
|
@ -1,18 +1,31 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <span>
|
#include <span>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include "PICA/pica_vertex.hpp"
|
#include "PICA/pica_vertex.hpp"
|
||||||
#include "PICA/regs.hpp"
|
#include "PICA/regs.hpp"
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
|
|
||||||
|
enum class RendererType : s8 {
|
||||||
|
// Todo: Auto = -1,
|
||||||
|
Null = 0,
|
||||||
|
OpenGL = 1,
|
||||||
|
Vulkan = 2,
|
||||||
|
Software = 3,
|
||||||
|
};
|
||||||
|
|
||||||
class GPU;
|
class GPU;
|
||||||
|
struct SDL_Window;
|
||||||
|
|
||||||
class Renderer {
|
class Renderer {
|
||||||
protected:
|
protected:
|
||||||
GPU& gpu;
|
GPU& gpu;
|
||||||
static constexpr u32 regNum = 0x300; // Number of internal PICA registers
|
static constexpr u32 regNum = 0x300; // Number of internal PICA registers
|
||||||
|
static constexpr u32 extRegNum = 0x1000; // Number of external PICA registers
|
||||||
|
|
||||||
const std::array<u32, regNum>& regs;
|
const std::array<u32, regNum>& regs;
|
||||||
|
const std::array<u32, extRegNum>& externalRegs;
|
||||||
|
|
||||||
std::array<u32, 2> fbSize; // The size of the framebuffer (ie both the colour and depth buffer)'
|
std::array<u32, 2> fbSize; // The size of the framebuffer (ie both the colour and depth buffer)'
|
||||||
|
|
||||||
|
@ -24,16 +37,19 @@ class Renderer {
|
||||||
PICA::DepthFmt depthBufferFormat;
|
PICA::DepthFmt depthBufferFormat;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Renderer(GPU& gpu, const std::array<u32, regNum>& internalRegs);
|
Renderer(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs);
|
||||||
virtual ~Renderer();
|
virtual ~Renderer();
|
||||||
|
|
||||||
static constexpr u32 vertexBufferSize = 0x10000;
|
static constexpr u32 vertexBufferSize = 0x10000;
|
||||||
|
static std::optional<RendererType> typeFromString(std::string inString);
|
||||||
|
static const char* typeToString(RendererType rendererType);
|
||||||
|
|
||||||
virtual void reset() = 0;
|
virtual void reset() = 0;
|
||||||
virtual void display() = 0; // Display the 3DS screen contents to the window
|
virtual void display() = 0; // Display the 3DS screen contents to the window
|
||||||
virtual void initGraphicsContext() = 0; // Initialize graphics context
|
virtual void initGraphicsContext(SDL_Window* window) = 0; // Initialize graphics context
|
||||||
virtual void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) = 0; // Clear a GPU buffer in VRAM
|
virtual void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) = 0; // Clear a GPU buffer in VRAM
|
||||||
virtual void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) = 0; // Perform display transfer
|
virtual void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) = 0; // Perform display transfer
|
||||||
|
virtual void textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) = 0;
|
||||||
virtual void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) = 0; // Draw the given vertices
|
virtual void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) = 0; // Draw the given vertices
|
||||||
|
|
||||||
virtual void screenshot(const std::string& name) = 0;
|
virtual void screenshot(const std::string& name) = 0;
|
||||||
|
@ -53,4 +69,4 @@ class Renderer {
|
||||||
|
|
||||||
void setColourBufferLoc(u32 loc) { colourBufferLoc = loc; }
|
void setColourBufferLoc(u32 loc) { colourBufferLoc = loc; }
|
||||||
void setDepthBufferLoc(u32 loc) { depthBufferLoc = loc; }
|
void setDepthBufferLoc(u32 loc) { depthBufferLoc = loc; }
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include "helpers.hpp"
|
||||||
#include "opengl.hpp"
|
#include "opengl.hpp"
|
||||||
|
|
||||||
// GL state manager object for use in the OpenGL GPU renderer and potentially other things in the future (such as a potential ImGui GUI)
|
// GL state manager object for use in the OpenGL GPU renderer and potentially other things in the future (such as a potential ImGui GUI)
|
||||||
|
@ -18,28 +19,42 @@
|
||||||
// backend-agnostic as possible
|
// backend-agnostic as possible
|
||||||
|
|
||||||
struct GLStateManager {
|
struct GLStateManager {
|
||||||
|
// We only support 6 clipping planes in our state manager because that's the minimum for GL_MAX_CLIP_PLANES
|
||||||
|
// And nobody needs more than 6 clip planes anyways
|
||||||
|
static constexpr GLint clipPlaneCount = 6;
|
||||||
|
|
||||||
bool blendEnabled;
|
bool blendEnabled;
|
||||||
|
bool logicOpEnabled;
|
||||||
bool depthEnabled;
|
bool depthEnabled;
|
||||||
bool scissorEnabled;
|
bool scissorEnabled;
|
||||||
|
bool stencilEnabled;
|
||||||
|
u32 enabledClipPlanes; // Bitfield of enabled clip planes
|
||||||
|
|
||||||
// Colour/depth masks
|
// Colour/depth masks
|
||||||
bool redMask, greenMask, blueMask, alphaMask;
|
bool redMask, greenMask, blueMask, alphaMask;
|
||||||
bool depthMask;
|
bool depthMask;
|
||||||
|
|
||||||
|
float clearRed, clearBlue, clearGreen, clearAlpha;
|
||||||
|
|
||||||
|
GLuint stencilMask;
|
||||||
GLuint boundVAO;
|
GLuint boundVAO;
|
||||||
GLuint boundVBO;
|
GLuint boundVBO;
|
||||||
GLuint currentProgram;
|
GLuint currentProgram;
|
||||||
|
|
||||||
GLenum depthFunc;
|
GLenum depthFunc;
|
||||||
|
GLenum logicOp;
|
||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
void resetBlend();
|
void resetBlend();
|
||||||
|
void resetClearing();
|
||||||
|
void resetClipping();
|
||||||
void resetColourMask();
|
void resetColourMask();
|
||||||
void resetDepth();
|
void resetDepth();
|
||||||
void resetVAO();
|
void resetVAO();
|
||||||
void resetVBO();
|
void resetVBO();
|
||||||
void resetProgram();
|
void resetProgram();
|
||||||
void resetScissor();
|
void resetScissor();
|
||||||
|
void resetStencil();
|
||||||
|
|
||||||
void enableDepth() {
|
void enableDepth() {
|
||||||
if (!depthEnabled) {
|
if (!depthEnabled) {
|
||||||
|
@ -83,6 +98,70 @@ struct GLStateManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void enableStencil() {
|
||||||
|
if (!stencilEnabled) {
|
||||||
|
stencilEnabled = true;
|
||||||
|
OpenGL::enableStencil();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void disableStencil() {
|
||||||
|
if (stencilEnabled) {
|
||||||
|
stencilEnabled = false;
|
||||||
|
OpenGL::disableStencil();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void enableLogicOp() {
|
||||||
|
if (!logicOpEnabled) {
|
||||||
|
logicOpEnabled = true;
|
||||||
|
OpenGL::enableLogicOp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void disableLogicOp() {
|
||||||
|
if (logicOpEnabled) {
|
||||||
|
logicOpEnabled = false;
|
||||||
|
OpenGL::disableLogicOp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLogicOp(GLenum op) {
|
||||||
|
if (logicOp != op) {
|
||||||
|
logicOp = op;
|
||||||
|
OpenGL::setLogicOp(op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void enableClipPlane(GLuint index) {
|
||||||
|
if (index >= clipPlaneCount) [[unlikely]] {
|
||||||
|
Helpers::panic("Enabled invalid clipping plane %d\n", index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((enabledClipPlanes & (1 << index)) == 0) {
|
||||||
|
enabledClipPlanes |= 1 << index; // Enable relevant bit in clipping plane bitfield
|
||||||
|
OpenGL::enableClipPlane(index); // Enable plane
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void disableClipPlane(GLuint index) {
|
||||||
|
if (index >= clipPlaneCount) [[unlikely]] {
|
||||||
|
Helpers::panic("Disabled invalid clipping plane %d\n", index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((enabledClipPlanes & (1 << index)) != 0) {
|
||||||
|
enabledClipPlanes ^= 1 << index; // Disable relevant bit in bitfield by flipping it
|
||||||
|
OpenGL::disableClipPlane(index); // Disable plane
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setStencilMask(GLuint mask) {
|
||||||
|
if (stencilMask != mask) {
|
||||||
|
stencilMask = mask;
|
||||||
|
OpenGL::setStencilMask(mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void bindVAO(GLuint handle) {
|
void bindVAO(GLuint handle) {
|
||||||
if (boundVAO != handle) {
|
if (boundVAO != handle) {
|
||||||
boundVAO = handle;
|
boundVAO = handle;
|
||||||
|
@ -133,6 +212,17 @@ struct GLStateManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setClearColour(float r, float g, float b, float a) {
|
||||||
|
if (clearRed != r || clearGreen != g || clearBlue != b || clearAlpha != a) {
|
||||||
|
clearRed = r;
|
||||||
|
clearGreen = g;
|
||||||
|
clearBlue = b;
|
||||||
|
clearAlpha = a;
|
||||||
|
|
||||||
|
OpenGL::setClearColor(r, g, b, a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void setDepthFunc(OpenGL::DepthFunc func) { setDepthFunc(static_cast<GLenum>(func)); }
|
void setDepthFunc(OpenGL::DepthFunc func) { setDepthFunc(static_cast<GLenum>(func)); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -44,8 +44,8 @@ class RendererGL final : public Renderer {
|
||||||
float oldDepthOffset = 0.0;
|
float oldDepthOffset = 0.0;
|
||||||
bool oldDepthmapEnable = false;
|
bool oldDepthmapEnable = false;
|
||||||
|
|
||||||
SurfaceCache<DepthBuffer, 10, true> depthBufferCache;
|
SurfaceCache<DepthBuffer, 16, true> depthBufferCache;
|
||||||
SurfaceCache<ColourBuffer, 10, true> colourBufferCache;
|
SurfaceCache<ColourBuffer, 16, true> colourBufferCache;
|
||||||
SurfaceCache<Texture, 256, true> textureCache;
|
SurfaceCache<Texture, 256, true> textureCache;
|
||||||
|
|
||||||
// Dummy VAO/VBO for blitting the final output
|
// Dummy VAO/VBO for blitting the final output
|
||||||
|
@ -55,27 +55,34 @@ class RendererGL final : public Renderer {
|
||||||
OpenGL::Texture screenTexture;
|
OpenGL::Texture screenTexture;
|
||||||
GLuint lightLUTTextureArray;
|
GLuint lightLUTTextureArray;
|
||||||
OpenGL::Framebuffer screenFramebuffer;
|
OpenGL::Framebuffer screenFramebuffer;
|
||||||
|
OpenGL::Texture blankTexture;
|
||||||
|
|
||||||
OpenGL::Framebuffer getColourFBO();
|
OpenGL::Framebuffer getColourFBO();
|
||||||
OpenGL::Texture getTexture(Texture& tex);
|
OpenGL::Texture getTexture(Texture& tex);
|
||||||
|
|
||||||
MAKE_LOG_FUNCTION(log, rendererLogger)
|
MAKE_LOG_FUNCTION(log, rendererLogger)
|
||||||
void setupBlending();
|
void setupBlending();
|
||||||
|
void setupStencilTest(bool stencilEnable);
|
||||||
void bindDepthBuffer();
|
void bindDepthBuffer();
|
||||||
void setupTextureEnvState();
|
void setupTextureEnvState();
|
||||||
void bindTexturesToSlots();
|
void bindTexturesToSlots();
|
||||||
void updateLightingLUT();
|
void updateLightingLUT();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RendererGL(GPU& gpu, const std::array<u32, regNum>& internalRegs) : Renderer(gpu, internalRegs) {}
|
RendererGL(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs)
|
||||||
|
: Renderer(gpu, internalRegs, externalRegs) {}
|
||||||
|
~RendererGL() override;
|
||||||
|
|
||||||
void reset() override;
|
void reset() override;
|
||||||
void display() override; // Display the 3DS screen contents to the window
|
void display() override; // Display the 3DS screen contents to the window
|
||||||
void initGraphicsContext() override; // Initialize graphics context
|
void initGraphicsContext(SDL_Window* window) override; // Initialize graphics context
|
||||||
void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) override; // Clear a GPU buffer in VRAM
|
void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) override; // Clear a GPU buffer in VRAM
|
||||||
void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) override; // Perform display transfer
|
void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) override; // Perform display transfer
|
||||||
|
void textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override;
|
||||||
void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) override; // Draw the given vertices
|
void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) override; // Draw the given vertices
|
||||||
|
|
||||||
|
std::optional<ColourBuffer> getColourBuffer(u32 addr, PICA::ColorFmt format, u32 width, u32 height, bool createIfnotFound = true);
|
||||||
|
|
||||||
// Take a screenshot of the screen and store it in a file
|
// Take a screenshot of the screen and store it in a file
|
||||||
void screenshot(const std::string& name) override;
|
void screenshot(const std::string& name) override;
|
||||||
};
|
};
|
||||||
|
|
|
@ -76,6 +76,16 @@ public:
|
||||||
|
|
||||||
size++;
|
size++;
|
||||||
|
|
||||||
|
// Find an existing surface we completely invalidate and overwrite it with the new surface
|
||||||
|
for (auto& e : buffer) {
|
||||||
|
if (e.valid && e.range.lower() >= surface.range.lower() && e.range.upper() <= surface.range.upper()) {
|
||||||
|
e.free();
|
||||||
|
e = surface;
|
||||||
|
e.allocate();
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Find an invalid entry in the cache and overwrite it with the new surface
|
// Find an invalid entry in the cache and overwrite it with the new surface
|
||||||
for (auto& e : buffer) {
|
for (auto& e : buffer) {
|
||||||
if (!e.valid) {
|
if (!e.valid) {
|
||||||
|
|
|
@ -2,62 +2,70 @@
|
||||||
#include "PICA/regs.hpp"
|
#include "PICA/regs.hpp"
|
||||||
#include "boost/icl/interval.hpp"
|
#include "boost/icl/interval.hpp"
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
|
#include "math_util.hpp"
|
||||||
#include "opengl.hpp"
|
#include "opengl.hpp"
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
using Interval = boost::icl::right_open_interval<T>;
|
using Interval = boost::icl::right_open_interval<T>;
|
||||||
|
|
||||||
struct ColourBuffer {
|
struct ColourBuffer {
|
||||||
u32 location;
|
u32 location;
|
||||||
PICA::ColorFmt format;
|
PICA::ColorFmt format;
|
||||||
OpenGL::uvec2 size;
|
OpenGL::uvec2 size;
|
||||||
bool valid;
|
bool valid;
|
||||||
|
|
||||||
// Range of VRAM taken up by buffer
|
// Range of VRAM taken up by buffer
|
||||||
Interval<u32> range;
|
Interval<u32> range;
|
||||||
// OpenGL resources allocated to buffer
|
// OpenGL resources allocated to buffer
|
||||||
OpenGL::Texture texture;
|
OpenGL::Texture texture;
|
||||||
OpenGL::Framebuffer fbo;
|
OpenGL::Framebuffer fbo;
|
||||||
|
|
||||||
ColourBuffer() : valid(false) {}
|
ColourBuffer() : valid(false) {}
|
||||||
|
|
||||||
ColourBuffer(u32 loc, PICA::ColorFmt format, u32 x, u32 y, bool valid = true)
|
ColourBuffer(u32 loc, PICA::ColorFmt format, u32 x, u32 y, bool valid = true) : location(loc), format(format), size({x, y}), valid(valid) {
|
||||||
: 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);
|
||||||
|
}
|
||||||
|
|
||||||
u64 endLoc = (u64)loc + sizeInBytes();
|
void allocate() {
|
||||||
// Check if start and end are valid here
|
// Create texture for the FBO, setting up filters and the like
|
||||||
range = Interval<u32>(loc, (u32)endLoc);
|
// 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);
|
||||||
|
|
||||||
void allocate() {
|
#ifdef GPU_DEBUG_INFO
|
||||||
// Create texture for the FBO, setting up filters and the like
|
const auto name = Helpers::format("Surface %dx%d %s from 0x%08X", size.x(), size.y(), PICA::textureFormatToString(format), location);
|
||||||
// Reading back the current texture is slow, but allocate calls should be few and far between.
|
OpenGL::setObjectLabel(GL_TEXTURE, texture.handle(), name.c_str());
|
||||||
// If this becomes a bottleneck, we can fix it semi-easily
|
#endif
|
||||||
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);
|
||||||
|
|
||||||
fbo.createWithDrawTexture(texture);
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||||
fbo.bind(OpenGL::DrawAndReadFramebuffer);
|
Helpers::panic("Incomplete framebuffer");
|
||||||
|
}
|
||||||
|
|
||||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
// TODO: This should not clear the framebuffer contents. It should load them from VRAM.
|
||||||
Helpers::panic("Incomplete framebuffer");
|
GLint oldViewport[4];
|
||||||
|
GLfloat oldClearColour[4];
|
||||||
|
|
||||||
// TODO: This should not clear the framebuffer contents. It should load them from VRAM.
|
glGetIntegerv(GL_VIEWPORT, oldViewport);
|
||||||
GLint oldViewport[4];
|
glGetFloatv(GL_COLOR_CLEAR_VALUE, oldClearColour);
|
||||||
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() {
|
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]);
|
||||||
|
OpenGL::setClearColor(oldClearColour[0], oldClearColour[1], oldClearColour[2], oldClearColour[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void free() {
|
||||||
valid = false;
|
valid = false;
|
||||||
|
|
||||||
if (texture.exists() || fbo.exists()) {
|
if (texture.exists() || fbo.exists()) {
|
||||||
|
@ -66,82 +74,102 @@ struct ColourBuffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool matches(ColourBuffer& other) {
|
Math::Rect<u32> getSubRect(u32 inputAddress, u32 width, u32 height) {
|
||||||
return location == other.location && format == other.format &&
|
// PICA textures have top-left origin while OpenGL has bottom-left origin.
|
||||||
size.x() == other.size.x() && size.y() == other.size.y();
|
// Flip the rectangle on the x axis to account for this.
|
||||||
}
|
const u32 startOffset = (inputAddress - location) / sizePerPixel(format);
|
||||||
|
const u32 x0 = (startOffset % (size.x() * 8)) / 8;
|
||||||
|
const u32 y0 = (startOffset / (size.x() * 8)) * 8;
|
||||||
|
return Math::Rect<u32>{x0, size.y() - y0, x0 + width, size.y() - height - y0};
|
||||||
|
}
|
||||||
|
|
||||||
size_t sizeInBytes() {
|
bool matches(ColourBuffer& other) {
|
||||||
return (size_t)size.x() * (size_t)size.y() * PICA::sizePerPixel(format);
|
return location == other.location && format == other.format && size.x() == other.size.x() && size.y() == other.size.y();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t sizeInBytes() {
|
||||||
|
return (size_t)size.x() * (size_t)size.y() * PICA::sizePerPixel(format);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DepthBuffer {
|
struct DepthBuffer {
|
||||||
u32 location;
|
u32 location;
|
||||||
PICA::DepthFmt format;
|
PICA::DepthFmt format;
|
||||||
OpenGL::uvec2 size; // Implicitly set to the size of the framebuffer
|
OpenGL::uvec2 size; // Implicitly set to the size of the framebuffer
|
||||||
bool valid;
|
bool valid;
|
||||||
|
|
||||||
// Range of VRAM taken up by buffer
|
// Range of VRAM taken up by buffer
|
||||||
Interval<u32> range;
|
Interval<u32> range;
|
||||||
// OpenGL texture used for storing depth/stencil
|
// OpenGL texture used for storing depth/stencil
|
||||||
OpenGL::Texture texture;
|
OpenGL::Texture texture;
|
||||||
|
OpenGL::Framebuffer fbo;
|
||||||
|
|
||||||
DepthBuffer() : valid(false) {}
|
DepthBuffer() : valid(false) {}
|
||||||
|
|
||||||
DepthBuffer(u32 loc, PICA::DepthFmt format, u32 x, u32 y, bool valid = true) :
|
DepthBuffer(u32 loc, PICA::DepthFmt format, u32 x, u32 y, bool valid = true) : location(loc), format(format), size({x, y}), valid(valid) {
|
||||||
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);
|
||||||
|
}
|
||||||
|
|
||||||
u64 endLoc = (u64)loc + sizeInBytes();
|
void allocate() {
|
||||||
// Check if start and end are valid here
|
// Create texture for the FBO, setting up filters and the like
|
||||||
range = Interval<u32>(loc, (u32)endLoc);
|
// 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();
|
||||||
|
|
||||||
void allocate() {
|
// Internal formats for the texture based on format
|
||||||
// Create texture for the FBO, setting up filters and the like
|
static constexpr std::array<GLenum, 4> internalFormats = {
|
||||||
// Reading back the current texture is slow, but allocate calls should be few and far between.
|
GL_DEPTH_COMPONENT16,
|
||||||
// If this becomes a bottleneck, we can fix it semi-easily
|
GL_DEPTH_COMPONENT24,
|
||||||
auto prevTexture = OpenGL::getTex2D();
|
GL_DEPTH_COMPONENT24,
|
||||||
|
GL_DEPTH24_STENCIL8,
|
||||||
|
};
|
||||||
|
|
||||||
// Internal formats for the texture based on format
|
// Format of the texture
|
||||||
static constexpr std::array<GLenum, 4> internalFormats = {
|
static constexpr std::array<GLenum, 4> formats = {
|
||||||
GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT24, GL_DEPTH24_STENCIL8
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
// Format of the texture
|
auto internalFormat = internalFormats[(int)format];
|
||||||
static constexpr std::array<GLenum, 4> formats = {
|
auto fmt = formats[(int)format];
|
||||||
GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT, GL_DEPTH_STENCIL
|
auto type = types[(int)format];
|
||||||
};
|
|
||||||
|
|
||||||
static constexpr std::array<GLenum, 4> types = {
|
texture.createDSTexture(size.x(), size.y(), internalFormat, fmt, nullptr, type, GL_TEXTURE_2D);
|
||||||
GL_UNSIGNED_SHORT, GL_UNSIGNED_INT, GL_UNSIGNED_INT, GL_UNSIGNED_INT_24_8
|
texture.bind();
|
||||||
};
|
texture.setMinFilter(OpenGL::Nearest);
|
||||||
|
texture.setMagFilter(OpenGL::Nearest);
|
||||||
|
|
||||||
auto internalFormat = internalFormats[(int)format];
|
glBindTexture(GL_TEXTURE_2D, prevTexture);
|
||||||
auto fmt = formats[(int)format];
|
fbo.createWithDrawTexture(texture, fmt == GL_DEPTH_STENCIL ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT);
|
||||||
auto type = types[(int)format];
|
|
||||||
|
|
||||||
texture.createDSTexture(size.x(), size.y(), internalFormat, fmt, nullptr, type, GL_TEXTURE_2D);
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||||
texture.bind();
|
Helpers::panic("Incomplete framebuffer");
|
||||||
texture.setMinFilter(OpenGL::Nearest);
|
}
|
||||||
texture.setMagFilter(OpenGL::Nearest);
|
}
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, prevTexture);
|
|
||||||
}
|
|
||||||
|
|
||||||
void free() {
|
void free() {
|
||||||
valid = false;
|
valid = false;
|
||||||
if (texture.exists()) {
|
if (texture.exists()) {
|
||||||
texture.free();
|
texture.free();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool matches(DepthBuffer& other) {
|
bool matches(DepthBuffer& other) {
|
||||||
return location == other.location && format == other.format &&
|
return location == other.location && format == other.format && size.x() == other.size.x() && size.y() == other.size.y();
|
||||||
size.x() == other.size.x() && size.y() == other.size.y();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
size_t sizeInBytes() {
|
size_t sizeInBytes() {
|
||||||
return (size_t)size.x() * (size_t)size.y() * PICA::sizePerPixel(format);
|
return (size_t)size.x() * (size_t)size.y() * PICA::sizePerPixel(format);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include "PICA/regs.hpp"
|
#include "PICA/regs.hpp"
|
||||||
#include "boost/icl/interval.hpp"
|
#include "boost/icl/interval.hpp"
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
|
#include "math_util.hpp"
|
||||||
#include "opengl.hpp"
|
#include "opengl.hpp"
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
@ -40,11 +41,11 @@ struct Texture {
|
||||||
|
|
||||||
void allocate();
|
void allocate();
|
||||||
void setNewConfig(u32 newConfig);
|
void setNewConfig(u32 newConfig);
|
||||||
void decodeTexture(const void* data);
|
void decodeTexture(std::span<const u8> data);
|
||||||
void free();
|
void free();
|
||||||
u64 sizeInBytes();
|
u64 sizeInBytes();
|
||||||
|
|
||||||
u32 decodeTexel(u32 u, u32 v, PICA::TextureFmt fmt, const void* data);
|
u32 decodeTexel(u32 u, u32 v, PICA::TextureFmt fmt, std::span<const u8> data);
|
||||||
|
|
||||||
// Get the morton interleave offset of a texel based on its U and V values
|
// Get the morton interleave offset of a texel based on its U and V values
|
||||||
static u32 mortonInterleave(u32 u, u32 v);
|
static u32 mortonInterleave(u32 u, u32 v);
|
||||||
|
@ -53,12 +54,12 @@ struct Texture {
|
||||||
static u32 getSwizzledOffset_4bpp(u32 u, u32 v, u32 width);
|
static u32 getSwizzledOffset_4bpp(u32 u, u32 v, u32 width);
|
||||||
|
|
||||||
// Returns the format of this texture as a string
|
// Returns the format of this texture as a string
|
||||||
std::string formatToString() {
|
std::string_view formatToString() {
|
||||||
return PICA::textureFormatToString(format);
|
return PICA::textureFormatToString(format);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the texel at coordinates (u, v) of an ETC1(A4) texture
|
// Returns the texel at coordinates (u, v) of an ETC1(A4) texture
|
||||||
// TODO: Make hasAlpha a template parameter
|
// TODO: Make hasAlpha a template parameter
|
||||||
u32 getTexelETC(bool hasAlpha, u32 u, u32 v, u32 width, const void* data);
|
u32 getTexelETC(bool hasAlpha, u32 u, u32 v, u32 width, std::span<const u8> data);
|
||||||
u32 decodeETC(u32 alpha, u32 u, u32 v, u64 colourData);
|
u32 decodeETC(u32 alpha, u32 u, u32 v, u64 colourData);
|
||||||
};
|
};
|
||||||
|
|
18
include/renderer_null/renderer_null.hpp
Normal file
18
include/renderer_null/renderer_null.hpp
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#include "renderer.hpp"
|
||||||
|
|
||||||
|
class GPU;
|
||||||
|
|
||||||
|
class RendererNull final : public Renderer {
|
||||||
|
public:
|
||||||
|
RendererNull(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs);
|
||||||
|
~RendererNull() override;
|
||||||
|
|
||||||
|
void reset() override;
|
||||||
|
void display() override;
|
||||||
|
void initGraphicsContext(SDL_Window* window) override;
|
||||||
|
void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) override;
|
||||||
|
void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) override;
|
||||||
|
void textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override;
|
||||||
|
void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) override;
|
||||||
|
void screenshot(const std::string& name) override;
|
||||||
|
};
|
18
include/renderer_sw/renderer_sw.hpp
Normal file
18
include/renderer_sw/renderer_sw.hpp
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#include "renderer.hpp"
|
||||||
|
|
||||||
|
class GPU;
|
||||||
|
|
||||||
|
class RendererSw final : public Renderer {
|
||||||
|
public:
|
||||||
|
RendererSw(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs);
|
||||||
|
~RendererSw() override;
|
||||||
|
|
||||||
|
void reset() override;
|
||||||
|
void display() override;
|
||||||
|
void initGraphicsContext(SDL_Window* window) override;
|
||||||
|
void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) override;
|
||||||
|
void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) override;
|
||||||
|
void textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override;
|
||||||
|
void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) override;
|
||||||
|
void screenshot(const std::string& name) override;
|
||||||
|
};
|
58
include/renderer_vk/renderer_vk.hpp
Normal file
58
include/renderer_vk/renderer_vk.hpp
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
#include "renderer.hpp"
|
||||||
|
#include "vulkan_api.hpp"
|
||||||
|
|
||||||
|
class GPU;
|
||||||
|
|
||||||
|
class RendererVK final : public Renderer {
|
||||||
|
SDL_Window* targetWindow;
|
||||||
|
|
||||||
|
// The order of these `Unique*` members is important, they will be destroyed in RAII order
|
||||||
|
vk::UniqueInstance instance = {};
|
||||||
|
vk::UniqueDebugUtilsMessengerEXT debugMessenger = {};
|
||||||
|
|
||||||
|
vk::UniqueSurfaceKHR surface = {};
|
||||||
|
|
||||||
|
vk::PhysicalDevice physicalDevice = {};
|
||||||
|
|
||||||
|
vk::UniqueDevice device = {};
|
||||||
|
|
||||||
|
vk::Queue presentQueue = {};
|
||||||
|
u32 presentQueueFamily = ~0u;
|
||||||
|
vk::Queue graphicsQueue = {};
|
||||||
|
u32 graphicsQueueFamily = ~0u;
|
||||||
|
vk::Queue computeQueue = {};
|
||||||
|
u32 computeQueueFamily = ~0u;
|
||||||
|
vk::Queue transferQueue = {};
|
||||||
|
u32 transferQueueFamily = ~0u;
|
||||||
|
|
||||||
|
vk::UniqueCommandPool commandPool = {};
|
||||||
|
|
||||||
|
vk::UniqueSwapchainKHR swapchain = {};
|
||||||
|
u32 swapchainImageCount = ~0u;
|
||||||
|
std::vector<vk::Image> swapchainImages = {};
|
||||||
|
std::vector<vk::UniqueImageView> swapchainImageViews = {};
|
||||||
|
|
||||||
|
// Per-swapchain-image data
|
||||||
|
// Each vector is `swapchainImageCount` in size
|
||||||
|
std::vector<vk::UniqueCommandBuffer> presentCommandBuffers = {};
|
||||||
|
std::vector<vk::UniqueSemaphore> swapImageFreeSemaphore = {};
|
||||||
|
std::vector<vk::UniqueSemaphore> renderFinishedSemaphore = {};
|
||||||
|
std::vector<vk::UniqueFence> frameFinishedFences = {};
|
||||||
|
|
||||||
|
// Recreate the swapchain, possibly re-using the old one in the case of a resize
|
||||||
|
vk::Result recreateSwapchain(vk::SurfaceKHR surface, vk::Extent2D swapchainExtent);
|
||||||
|
|
||||||
|
u64 currentFrame = 0;
|
||||||
|
public:
|
||||||
|
RendererVK(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs);
|
||||||
|
~RendererVK() override;
|
||||||
|
|
||||||
|
void reset() override;
|
||||||
|
void display() override;
|
||||||
|
void initGraphicsContext(SDL_Window* window) override;
|
||||||
|
void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) override;
|
||||||
|
void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) override;
|
||||||
|
void textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override;
|
||||||
|
void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) override;
|
||||||
|
void screenshot(const std::string& name) override;
|
||||||
|
};
|
48
include/renderer_vk/vk_debug.hpp
Normal file
48
include/renderer_vk/vk_debug.hpp
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "vulkan_api.hpp"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
VKAPI_ATTR VkBool32 VKAPI_CALL debugMessageCallback(
|
||||||
|
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType,
|
||||||
|
const VkDebugUtilsMessengerCallbackDataEXT* callbackData, void* userData
|
||||||
|
);
|
||||||
|
|
||||||
|
void setObjectName(vk::Device device, vk::ObjectType objectType, const void* objectHandle, const char* format, ...);
|
||||||
|
|
||||||
|
template <typename T, typename = std::enable_if_t<vk::isVulkanHandleType<T>::value == true>, typename... ArgsT>
|
||||||
|
inline void setObjectName(vk::Device device, const T objectHandle, const char* format, ArgsT&&... args) {
|
||||||
|
setObjectName(device, T::objectType, objectHandle, format, std::forward<ArgsT>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
void beginDebugLabel(vk::CommandBuffer commandBuffer, std::span<const float, 4> color, const char* format, ...);
|
||||||
|
|
||||||
|
void insertDebugLabel(vk::CommandBuffer commandBuffer, std::span<const float, 4> color, const char* format, ...);
|
||||||
|
|
||||||
|
void endDebugLabel(vk::CommandBuffer commandBuffer);
|
||||||
|
|
||||||
|
class DebugLabelScope {
|
||||||
|
private:
|
||||||
|
const vk::CommandBuffer commandBuffer;
|
||||||
|
|
||||||
|
public:
|
||||||
|
template <typename... ArgsT>
|
||||||
|
DebugLabelScope(vk::CommandBuffer targetCommandBuffer, std::span<const float, 4> color, const char* format, ArgsT&&... args)
|
||||||
|
: commandBuffer(targetCommandBuffer) {
|
||||||
|
beginDebugLabel(commandBuffer, color, format, std::forward<ArgsT>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... ArgsT>
|
||||||
|
void operator()(std::span<const float, 4> color, const char* format, ArgsT&&... args) const {
|
||||||
|
insertDebugLabel(commandBuffer, color, format, std::forward<ArgsT>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
~DebugLabelScope() { endDebugLabel(commandBuffer); }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
12
include/renderer_vk/vulkan_api.hpp
Normal file
12
include/renderer_vk/vulkan_api.hpp
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define VK_NO_PROTOTYPES
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
|
||||||
|
#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1
|
||||||
|
#define VULKAN_HPP_NO_EXCEPTIONS
|
||||||
|
// Disable asserts on result-codes
|
||||||
|
#define VULKAN_HPP_ASSERT_ON_RESULT
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
#include <vulkan/vulkan_format_traits.hpp>
|
||||||
|
#include <vulkan/vulkan_hash.hpp>
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "result_cfg.hpp"
|
||||||
#include "result_common.hpp"
|
#include "result_common.hpp"
|
||||||
#include "result_kernel.hpp"
|
#include "result_kernel.hpp"
|
||||||
#include "result_os.hpp"
|
#include "result_os.hpp"
|
||||||
|
|
8
include/result/result_cfg.hpp
Normal file
8
include/result/result_cfg.hpp
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#pragma once
|
||||||
|
#include "result_common.hpp"
|
||||||
|
|
||||||
|
DEFINE_HORIZON_RESULT_MODULE(Result::CFG, Config);
|
||||||
|
|
||||||
|
namespace Result::CFG {
|
||||||
|
DEFINE_HORIZON_RESULT(NotFound, 1018, WrongArgument, Permanent);
|
||||||
|
};
|
|
@ -11,6 +11,11 @@ class ACService {
|
||||||
MAKE_LOG_FUNCTION(log, acLogger)
|
MAKE_LOG_FUNCTION(log, acLogger)
|
||||||
|
|
||||||
// Service commands
|
// Service commands
|
||||||
|
void cancelConnectAsync(u32 messagePointer);
|
||||||
|
void closeAsync(u32 messagePointer);
|
||||||
|
void createDefaultConfig(u32 messagePointer);
|
||||||
|
void getLastErrorCode(u32 messagePointer);
|
||||||
|
void registerDisconnectEvent(u32 messagePointer);
|
||||||
void setClientVersion(u32 messagePointer);
|
void setClientVersion(u32 messagePointer);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -13,6 +13,7 @@ class ACTService {
|
||||||
// Service commands
|
// Service commands
|
||||||
void initialize(u32 messagePointer);
|
void initialize(u32 messagePointer);
|
||||||
void generateUUID(u32 messagePointer);
|
void generateUUID(u32 messagePointer);
|
||||||
|
void getAccountDataBlock(u32 messagePointer);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ACTService(Memory& mem) : mem(mem) {}
|
ACTService(Memory& mem) : mem(mem) {}
|
||||||
|
|
|
@ -12,6 +12,7 @@ class AMService {
|
||||||
|
|
||||||
// Service commands
|
// Service commands
|
||||||
void getDLCTitleInfo(u32 messagePointer);
|
void getDLCTitleInfo(u32 messagePointer);
|
||||||
|
void getPatchTitleInfo(u32 messagePointer);
|
||||||
void listTitleInfo(u32 messagePointer);
|
void listTitleInfo(u32 messagePointer);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -11,15 +11,24 @@ class BOSSService {
|
||||||
MAKE_LOG_FUNCTION(log, bossLogger)
|
MAKE_LOG_FUNCTION(log, bossLogger)
|
||||||
|
|
||||||
// Service commands
|
// Service commands
|
||||||
|
void cancelTask(u32 messagePointer);
|
||||||
void initializeSession(u32 messagePointer);
|
void initializeSession(u32 messagePointer);
|
||||||
void getNsDataIdList(u32 messagePointer);
|
void getErrorCode(u32 messagePointer);
|
||||||
|
void getNsDataIdList(u32 messagePointer, u32 commandWord);
|
||||||
void getOptoutFlag(u32 messagePointer);
|
void getOptoutFlag(u32 messagePointer);
|
||||||
void getStorageEntryInfo(u32 messagePointer); // Unknown what this is, name taken from Citra
|
void getStorageEntryInfo(u32 messagePointer); // Unknown what this is, name taken from Citra
|
||||||
void getTaskIdList(u32 messagePointer);
|
void getTaskIdList(u32 messagePointer);
|
||||||
void getTaskInfo(u32 messagePOinter);
|
void getTaskInfo(u32 messagePointer);
|
||||||
|
void getTaskServiceStatus(u32 messagePointer);
|
||||||
|
void getTaskState(u32 messagePointer);
|
||||||
|
void getTaskStatus(u32 messagePointer);
|
||||||
void getTaskStorageInfo(u32 messagePointer);
|
void getTaskStorageInfo(u32 messagePointer);
|
||||||
void receiveProperty(u32 messagePointer);
|
void receiveProperty(u32 messagePointer);
|
||||||
|
void registerNewArrivalEvent(u32 messagePointer);
|
||||||
void registerStorageEntry(u32 messagePointer);
|
void registerStorageEntry(u32 messagePointer);
|
||||||
|
void registerTask(u32 messagePointer);
|
||||||
|
void sendProperty(u32 messagePointer);
|
||||||
|
void startTask(u32 messagePointer);
|
||||||
void unregisterStorage(u32 messagePointer);
|
void unregisterStorage(u32 messagePointer);
|
||||||
void unregisterTask(u32 messagePointer);
|
void unregisterTask(u32 messagePointer);
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,33 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include <array>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "kernel_types.hpp"
|
#include "kernel_types.hpp"
|
||||||
#include "logger.hpp"
|
#include "logger.hpp"
|
||||||
#include "memory.hpp"
|
#include "memory.hpp"
|
||||||
#include "result/result.hpp"
|
#include "result/result.hpp"
|
||||||
|
|
||||||
|
// Yay, circular dependencies!
|
||||||
|
class Kernel;
|
||||||
|
|
||||||
class CAMService {
|
class CAMService {
|
||||||
Handle handle = KernelHandles::CAM;
|
Handle handle = KernelHandles::CAM;
|
||||||
Memory& mem;
|
Memory& mem;
|
||||||
|
Kernel& kernel;
|
||||||
MAKE_LOG_FUNCTION(log, camLogger)
|
MAKE_LOG_FUNCTION(log, camLogger)
|
||||||
|
|
||||||
|
using Event = std::optional<Handle>;
|
||||||
|
static constexpr size_t portCount = 4; // PORT_NONE, PORT_CAM1, PORT_CAM2, PORT_BOTH
|
||||||
|
std::array<Event, portCount> bufferErrorInterruptEvents;
|
||||||
|
|
||||||
// Service commands
|
// Service commands
|
||||||
void driverInitialize(u32 messagePointer);
|
void driverInitialize(u32 messagePointer);
|
||||||
void getMaxLines(u32 messagePointer);
|
void getMaxLines(u32 messagePointer);
|
||||||
|
void getBufferErrorInterruptEvent(u32 messagePointer);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CAMService(Memory& mem) : mem(mem) {}
|
CAMService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
|
||||||
void reset();
|
void reset();
|
||||||
void handleSyncRequest(u32 messagePointer);
|
void handleSyncRequest(u32 messagePointer);
|
||||||
};
|
};
|
|
@ -18,6 +18,7 @@ class CECDService {
|
||||||
|
|
||||||
// Service commands
|
// Service commands
|
||||||
void getInfoEventHandle(u32 messagePointer);
|
void getInfoEventHandle(u32 messagePointer);
|
||||||
|
void openAndRead(u32 messagePointer);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CECDService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
|
CECDService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
#include "result/result.hpp"
|
#include "result/result.hpp"
|
||||||
|
|
||||||
class CFGService {
|
class CFGService {
|
||||||
Handle handle = KernelHandles::CFG;
|
|
||||||
Memory& mem;
|
Memory& mem;
|
||||||
CountryCodes country = CountryCodes::US; // Default to USA
|
CountryCodes country = CountryCodes::US; // Default to USA
|
||||||
MAKE_LOG_FUNCTION(log, cfgLogger)
|
MAKE_LOG_FUNCTION(log, cfgLogger)
|
||||||
|
@ -16,6 +15,7 @@ class CFGService {
|
||||||
|
|
||||||
// Service functions
|
// Service functions
|
||||||
void getConfigInfoBlk2(u32 messagePointer);
|
void getConfigInfoBlk2(u32 messagePointer);
|
||||||
|
void getCountryCodeID(u32 messagePointer);
|
||||||
void getRegionCanadaUSA(u32 messagePointer);
|
void getRegionCanadaUSA(u32 messagePointer);
|
||||||
void getSystemModel(u32 messagePointer);
|
void getSystemModel(u32 messagePointer);
|
||||||
void genUniqueConsoleHash(u32 messagePointer);
|
void genUniqueConsoleHash(u32 messagePointer);
|
||||||
|
|
|
@ -19,6 +19,8 @@ class FRDService {
|
||||||
Memory& mem;
|
Memory& mem;
|
||||||
MAKE_LOG_FUNCTION(log, frdLogger)
|
MAKE_LOG_FUNCTION(log, frdLogger)
|
||||||
|
|
||||||
|
bool loggedIn = false;
|
||||||
|
|
||||||
// Service commands
|
// Service commands
|
||||||
void attachToEventNotification(u32 messagePointer);
|
void attachToEventNotification(u32 messagePointer);
|
||||||
void getFriendKeyList(u32 messagePointer);
|
void getFriendKeyList(u32 messagePointer);
|
||||||
|
@ -27,8 +29,11 @@ class FRDService {
|
||||||
void getMyPresence(u32 messagePointer);
|
void getMyPresence(u32 messagePointer);
|
||||||
void getMyProfile(u32 messagePointer);
|
void getMyProfile(u32 messagePointer);
|
||||||
void getMyScreenName(u32 messsagePointer);
|
void getMyScreenName(u32 messsagePointer);
|
||||||
|
void hasLoggedIn(u32 messagePointer);
|
||||||
|
void logout(u32 messagePointer);
|
||||||
void setClientSDKVersion(u32 messagePointer);
|
void setClientSDKVersion(u32 messagePointer);
|
||||||
void setNotificationMask(u32 messagePointer);
|
void setNotificationMask(u32 messagePointer);
|
||||||
|
void updateGameModeDescription(u32 messagePointer);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FRDService(Memory& mem) : mem(mem) {}
|
FRDService(Memory& mem) : mem(mem) {}
|
||||||
|
|
|
@ -37,9 +37,11 @@ class FSService {
|
||||||
|
|
||||||
// Service commands
|
// Service commands
|
||||||
void createDirectory(u32 messagePointer);
|
void createDirectory(u32 messagePointer);
|
||||||
|
void createExtSaveData(u32 messagePointer);
|
||||||
void createFile(u32 messagePointer);
|
void createFile(u32 messagePointer);
|
||||||
void closeArchive(u32 messagePointer);
|
void closeArchive(u32 messagePointer);
|
||||||
void controlArchive(u32 messagePointer);
|
void controlArchive(u32 messagePointer);
|
||||||
|
void deleteExtSaveData(u32 messagePointer);
|
||||||
void deleteFile(u32 messagePointer);
|
void deleteFile(u32 messagePointer);
|
||||||
void formatSaveData(u32 messagePointer);
|
void formatSaveData(u32 messagePointer);
|
||||||
void formatThisUserSaveData(u32 messagePointer);
|
void formatThisUserSaveData(u32 messagePointer);
|
||||||
|
|
|
@ -40,11 +40,32 @@ class GPUService {
|
||||||
MAKE_LOG_FUNCTION(log, gspGPULogger)
|
MAKE_LOG_FUNCTION(log, gspGPULogger)
|
||||||
void processCommandBuffer();
|
void processCommandBuffer();
|
||||||
|
|
||||||
|
struct FramebufferInfo {
|
||||||
|
u32 activeFb;
|
||||||
|
u32 leftFramebufferVaddr;
|
||||||
|
u32 rightFramebufferVaddr;
|
||||||
|
u32 stride;
|
||||||
|
u32 format;
|
||||||
|
u32 displayFb;
|
||||||
|
u32 attribute;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(FramebufferInfo) == 28, "GSP::GPU::FramebufferInfo has the wrong size");
|
||||||
|
|
||||||
|
struct FramebufferUpdate {
|
||||||
|
u8 index;
|
||||||
|
u8 dirtyFlag;
|
||||||
|
u16 pad0;
|
||||||
|
std::array<FramebufferInfo, 2> framebufferInfo;
|
||||||
|
u32 pad1;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(FramebufferUpdate) == 64, "GSP::GPU::FramebufferUpdate has the wrong size");
|
||||||
|
|
||||||
// Service commands
|
// Service commands
|
||||||
void acquireRight(u32 messagePointer);
|
void acquireRight(u32 messagePointer);
|
||||||
void flushDataCache(u32 messagePointer);
|
void flushDataCache(u32 messagePointer);
|
||||||
void registerInterruptRelayQueue(u32 messagePointer);
|
void registerInterruptRelayQueue(u32 messagePointer);
|
||||||
void setAxiConfigQoSMode(u32 messagePointer);
|
void setAxiConfigQoSMode(u32 messagePointer);
|
||||||
|
void setBufferSwap(u32 messagePointer);
|
||||||
void setInternalPriorities(u32 messagePointer);
|
void setInternalPriorities(u32 messagePointer);
|
||||||
void setLCDForceBlack(u32 messagePointer);
|
void setLCDForceBlack(u32 messagePointer);
|
||||||
void storeDataCache(u32 messagePointer);
|
void storeDataCache(u32 messagePointer);
|
||||||
|
@ -60,6 +81,8 @@ class GPUService {
|
||||||
void triggerTextureCopy(u32* cmd);
|
void triggerTextureCopy(u32* cmd);
|
||||||
void flushCacheRegions(u32* cmd);
|
void flushCacheRegions(u32* cmd);
|
||||||
|
|
||||||
|
void setBufferSwapImpl(u32 screen_id, const FramebufferInfo& info);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GPUService(Memory& mem, GPU& gpu, Kernel& kernel, u32& currentPID) : mem(mem), gpu(gpu),
|
GPUService(Memory& mem, GPU& gpu, Kernel& kernel, u32& currentPID) : mem(mem), gpu(gpu),
|
||||||
kernel(kernel), currentPID(currentPID) {}
|
kernel(kernel), currentPID(currentPID) {}
|
||||||
|
@ -72,4 +95,4 @@ public:
|
||||||
std::memset(ptr, 0, 0x1000);
|
std::memset(ptr, 0, 0x1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -71,6 +71,7 @@ class HIDService {
|
||||||
void getGyroscopeLowCalibrateParam(u32 messagePointer);
|
void getGyroscopeLowCalibrateParam(u32 messagePointer);
|
||||||
void getGyroscopeCoefficient(u32 messagePointer);
|
void getGyroscopeCoefficient(u32 messagePointer);
|
||||||
void getIPCHandles(u32 messagePointer);
|
void getIPCHandles(u32 messagePointer);
|
||||||
|
void getSoundVolume(u32 messagePointer);
|
||||||
|
|
||||||
// Don't call these prior to initializing shared mem pls
|
// Don't call these prior to initializing shared mem pls
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
@ -91,8 +92,9 @@ class HIDService {
|
||||||
void pressKey(u32 mask) { newButtons |= mask; }
|
void pressKey(u32 mask) { newButtons |= mask; }
|
||||||
void releaseKey(u32 mask) { newButtons &= ~mask; }
|
void releaseKey(u32 mask) { newButtons &= ~mask; }
|
||||||
|
|
||||||
s16 getCirclepadX() { return circlePadX; }
|
u32 getOldButtons() const { return oldButtons; }
|
||||||
s16 getCirclepadY() { return circlePadY; }
|
s16 getCirclepadX() const { return circlePadX; }
|
||||||
|
s16 getCirclepadY() const { return circlePadY; }
|
||||||
|
|
||||||
void setCirclepadX(s16 x) {
|
void setCirclepadX(s16 x) {
|
||||||
circlePadX = x;
|
circlePadX = x;
|
||||||
|
@ -140,4 +142,6 @@ class HIDService {
|
||||||
void releaseTouchScreen() {
|
void releaseTouchScreen() {
|
||||||
touchScreenPressed = false;
|
touchScreenPressed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isTouchScreenPressed() { return touchScreenPressed; }
|
||||||
};
|
};
|
||||||
|
|
21
include/services/http.hpp
Normal file
21
include/services/http.hpp
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include "kernel_types.hpp"
|
||||||
|
#include "logger.hpp"
|
||||||
|
#include "memory.hpp"
|
||||||
|
|
||||||
|
class HTTPService {
|
||||||
|
Handle handle = KernelHandles::HTTP;
|
||||||
|
Memory& mem;
|
||||||
|
MAKE_LOG_FUNCTION(log, httpLogger)
|
||||||
|
|
||||||
|
bool initialized = false;
|
||||||
|
|
||||||
|
// Service commands
|
||||||
|
void initialize(u32 messagePointer);
|
||||||
|
|
||||||
|
public:
|
||||||
|
HTTPService(Memory& mem) : mem(mem) {}
|
||||||
|
void reset();
|
||||||
|
void handleSyncRequest(u32 messagePointer);
|
||||||
|
};
|
|
@ -11,6 +11,10 @@
|
||||||
class Kernel;
|
class Kernel;
|
||||||
|
|
||||||
class IRUserService {
|
class IRUserService {
|
||||||
|
enum class DeviceID : u8 {
|
||||||
|
CirclePadPro = 1,
|
||||||
|
};
|
||||||
|
|
||||||
Handle handle = KernelHandles::IR_USER;
|
Handle handle = KernelHandles::IR_USER;
|
||||||
Memory& mem;
|
Memory& mem;
|
||||||
Kernel& kernel;
|
Kernel& kernel;
|
||||||
|
@ -20,10 +24,35 @@ class IRUserService {
|
||||||
void disconnect(u32 messagePointer);
|
void disconnect(u32 messagePointer);
|
||||||
void finalizeIrnop(u32 messagePointer);
|
void finalizeIrnop(u32 messagePointer);
|
||||||
void getConnectionStatusEvent(u32 messagePointer);
|
void getConnectionStatusEvent(u32 messagePointer);
|
||||||
|
void getReceiveEvent(u32 messagePointer);
|
||||||
void initializeIrnopShared(u32 messagePointer);
|
void initializeIrnopShared(u32 messagePointer);
|
||||||
void requireConnection(u32 messagePointer);
|
void requireConnection(u32 messagePointer);
|
||||||
|
void sendIrnop(u32 messagePointer);
|
||||||
|
|
||||||
std::optional<Handle> connectionStatusEvent = std::nullopt;
|
using IREvent = std::optional<Handle>;
|
||||||
|
|
||||||
|
IREvent connectionStatusEvent = std::nullopt;
|
||||||
|
IREvent receiveEvent = std::nullopt;
|
||||||
|
|
||||||
|
std::optional<MemoryBlock> sharedMemory = std::nullopt;
|
||||||
|
bool connectedDevice = false;
|
||||||
|
|
||||||
|
// Header of the IR shared memory containing various bits of info
|
||||||
|
// https://www.3dbrew.org/wiki/IRUSER_Shared_Memory
|
||||||
|
struct SharedMemoryStatus {
|
||||||
|
u32 latestReceiveError;
|
||||||
|
u32 latestSharedError;
|
||||||
|
|
||||||
|
u8 connectionStatus;
|
||||||
|
u8 connectionAttemptStatus;
|
||||||
|
u8 connectionRole;
|
||||||
|
u8 machineID;
|
||||||
|
u8 isConnected;
|
||||||
|
u8 networkID;
|
||||||
|
u8 isInitialized; // https://github.com/citra-emu/citra/blob/c10ffda91feb3476a861c47fb38641c1007b9d33/src/core/hle/service/ir/ir_user.cpp#L41
|
||||||
|
u8 unk1;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(SharedMemoryStatus) == 16);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
IRUserService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
|
IRUserService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
|
||||||
|
|
24
include/services/mcu/mcu_hwc.hpp
Normal file
24
include/services/mcu/mcu_hwc.hpp
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#pragma once
|
||||||
|
#include "config.hpp"
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include "kernel_types.hpp"
|
||||||
|
#include "logger.hpp"
|
||||||
|
#include "memory.hpp"
|
||||||
|
|
||||||
|
namespace MCU {
|
||||||
|
class HWCService {
|
||||||
|
Handle handle = KernelHandles::MCU_HWC;
|
||||||
|
Memory& mem;
|
||||||
|
MAKE_LOG_FUNCTION(log, mcuLogger)
|
||||||
|
|
||||||
|
const EmulatorConfig& config;
|
||||||
|
|
||||||
|
// Service commands
|
||||||
|
void getBatteryLevel(u32 messagePointer);
|
||||||
|
|
||||||
|
public:
|
||||||
|
HWCService(Memory& mem, const EmulatorConfig& config) : mem(mem), config(config) {}
|
||||||
|
void reset();
|
||||||
|
void handleSyncRequest(u32 messagePointer);
|
||||||
|
};
|
||||||
|
} // namespace MCU
|
|
@ -15,13 +15,16 @@ class MICService {
|
||||||
void mapSharedMem(u32 messagePointer);
|
void mapSharedMem(u32 messagePointer);
|
||||||
void setClamp(u32 messagePointer);
|
void setClamp(u32 messagePointer);
|
||||||
void setGain(u32 messagePointer);
|
void setGain(u32 messagePointer);
|
||||||
|
void setIirFilter(u32 messagePointer);
|
||||||
void setPower(u32 messagePointer);
|
void setPower(u32 messagePointer);
|
||||||
void startSampling(u32 messagePointer);
|
void startSampling(u32 messagePointer);
|
||||||
|
void stopSampling(u32 messagePointer);
|
||||||
void theCaptainToadFunction(u32 messagePointer);
|
void theCaptainToadFunction(u32 messagePointer);
|
||||||
|
|
||||||
u8 gain = 0; // How loud our microphone input signal is
|
u8 gain = 0; // How loud our microphone input signal is
|
||||||
bool micEnabled = false;
|
bool micEnabled = false;
|
||||||
bool shouldClamp = false;
|
bool shouldClamp = false;
|
||||||
|
bool isSampling = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MICService(Memory& mem) : mem(mem) {}
|
MICService(Memory& mem) : mem(mem) {}
|
||||||
|
|
|
@ -11,6 +11,7 @@ class NDMService {
|
||||||
MAKE_LOG_FUNCTION(log, ndmLogger)
|
MAKE_LOG_FUNCTION(log, ndmLogger)
|
||||||
|
|
||||||
// Service commands
|
// Service commands
|
||||||
|
void clearHalfAwakeMacFilter(u32 messagePointer);
|
||||||
void overrideDefaultDaemons(u32 messagePointer);
|
void overrideDefaultDaemons(u32 messagePointer);
|
||||||
void resumeDaemons(u32 messagePointer);
|
void resumeDaemons(u32 messagePointer);
|
||||||
void resumeScheduler(u32 messagePointer);
|
void resumeScheduler(u32 messagePointer);
|
||||||
|
|
18
include/services/news_u.hpp
Normal file
18
include/services/news_u.hpp
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#pragma once
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include "kernel_types.hpp"
|
||||||
|
#include "logger.hpp"
|
||||||
|
#include "memory.hpp"
|
||||||
|
|
||||||
|
class NewsUService {
|
||||||
|
Handle handle = KernelHandles::NEWS_U;
|
||||||
|
Memory& mem;
|
||||||
|
MAKE_LOG_FUNCTION(log, newsLogger)
|
||||||
|
|
||||||
|
// Service commands
|
||||||
|
|
||||||
|
public:
|
||||||
|
NewsUService(Memory& mem) : mem(mem) {}
|
||||||
|
void reset();
|
||||||
|
void handleSyncRequest(u32 messagePointer);
|
||||||
|
};
|
|
@ -14,13 +14,38 @@ class NFCService {
|
||||||
Kernel& kernel;
|
Kernel& kernel;
|
||||||
MAKE_LOG_FUNCTION(log, nfcLogger)
|
MAKE_LOG_FUNCTION(log, nfcLogger)
|
||||||
|
|
||||||
|
enum class Old3DSAdapterStatus : u32 {
|
||||||
|
Idle = 0,
|
||||||
|
AttemptingToInitialize = 1,
|
||||||
|
InitializationComplete = 2,
|
||||||
|
Active = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class TagStatus : u8 {
|
||||||
|
NotInitialized = 0,
|
||||||
|
Initialized = 1,
|
||||||
|
Scanning = 2,
|
||||||
|
InRange = 3,
|
||||||
|
OutOfRange = 4,
|
||||||
|
Loaded = 5,
|
||||||
|
};
|
||||||
|
|
||||||
// Kernel events signaled when an NFC tag goes in and out of range respectively
|
// Kernel events signaled when an NFC tag goes in and out of range respectively
|
||||||
std::optional<Handle> tagInRangeEvent, tagOutOfRangeEvent;
|
std::optional<Handle> tagInRangeEvent, tagOutOfRangeEvent;
|
||||||
|
|
||||||
|
Old3DSAdapterStatus adapterStatus;
|
||||||
|
TagStatus tagStatus;
|
||||||
|
bool initialized = false;
|
||||||
|
|
||||||
// Service commands
|
// Service commands
|
||||||
|
void communicationGetResult(u32 messagePointer);
|
||||||
|
void communicationGetStatus(u32 messagePointer);
|
||||||
void initialize(u32 messagePointer);
|
void initialize(u32 messagePointer);
|
||||||
void getTagInRangeEvent(u32 messagePointer);
|
void getTagInRangeEvent(u32 messagePointer);
|
||||||
void getTagOutOfRangeEvent(u32 messagePointer);
|
void getTagOutOfRangeEvent(u32 messagePointer);
|
||||||
|
void getTagState(u32 messagePointer);
|
||||||
|
void startCommunication(u32 messagePointer);
|
||||||
|
void stopCommunication(u32 messagePointer);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
NFCService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
|
NFCService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "config.hpp"
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "kernel_types.hpp"
|
#include "kernel_types.hpp"
|
||||||
#include "logger.hpp"
|
#include "logger.hpp"
|
||||||
|
@ -10,13 +11,39 @@ class PTMService {
|
||||||
Memory& mem;
|
Memory& mem;
|
||||||
MAKE_LOG_FUNCTION(log, ptmLogger)
|
MAKE_LOG_FUNCTION(log, ptmLogger)
|
||||||
|
|
||||||
|
const EmulatorConfig& config;
|
||||||
|
|
||||||
// Service commands
|
// Service commands
|
||||||
void configureNew3DSCPU(u32 messagePointer);
|
void configureNew3DSCPU(u32 messagePointer);
|
||||||
|
void getAdapterState(u32 messagePointer);
|
||||||
|
void getBatteryLevel(u32 messagePointer);
|
||||||
void getStepHistory(u32 messagePointer);
|
void getStepHistory(u32 messagePointer);
|
||||||
void getTotalStepCount(u32 messagePointer);
|
void getTotalStepCount(u32 messagePointer);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PTMService(Memory& mem) : mem(mem) {}
|
PTMService(Memory& mem, const EmulatorConfig& config) : mem(mem), config(config) {}
|
||||||
void reset();
|
void reset();
|
||||||
void handleSyncRequest(u32 messagePointer);
|
void handleSyncRequest(u32 messagePointer);
|
||||||
|
|
||||||
|
// 0% -> 0 (shutting down)
|
||||||
|
// 1-5% -> 1
|
||||||
|
// 6-10% -> 2
|
||||||
|
// 11-30% -> 3
|
||||||
|
// 31-60% -> 4
|
||||||
|
// 61-100% -> 5
|
||||||
|
static constexpr u8 batteryPercentToLevel(u8 percent) {
|
||||||
|
if (percent == 0) {
|
||||||
|
return 0;
|
||||||
|
} else if (percent >= 1 && percent <= 5) {
|
||||||
|
return 1;
|
||||||
|
} else if (percent >= 6 && percent <= 10) {
|
||||||
|
return 2;
|
||||||
|
} else if (percent >= 11 && percent <= 30) {
|
||||||
|
return 3;
|
||||||
|
} else if (percent >= 31 && percent <= 60) {
|
||||||
|
return 4;
|
||||||
|
} else {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
|
@ -21,15 +21,21 @@
|
||||||
#include "services/gsp_gpu.hpp"
|
#include "services/gsp_gpu.hpp"
|
||||||
#include "services/gsp_lcd.hpp"
|
#include "services/gsp_lcd.hpp"
|
||||||
#include "services/hid.hpp"
|
#include "services/hid.hpp"
|
||||||
|
#include "services/http.hpp"
|
||||||
#include "services/ir_user.hpp"
|
#include "services/ir_user.hpp"
|
||||||
#include "services/ldr_ro.hpp"
|
#include "services/ldr_ro.hpp"
|
||||||
|
#include "services/mcu/mcu_hwc.hpp"
|
||||||
#include "services/mic.hpp"
|
#include "services/mic.hpp"
|
||||||
#include "services/ndm.hpp"
|
#include "services/ndm.hpp"
|
||||||
|
#include "services/news_u.hpp"
|
||||||
#include "services/nfc.hpp"
|
#include "services/nfc.hpp"
|
||||||
#include "services/nim.hpp"
|
#include "services/nim.hpp"
|
||||||
#include "services/ptm.hpp"
|
#include "services/ptm.hpp"
|
||||||
|
#include "services/soc.hpp"
|
||||||
|
#include "services/ssl.hpp"
|
||||||
#include "services/y2r.hpp"
|
#include "services/y2r.hpp"
|
||||||
|
|
||||||
|
struct EmulatorConfig;
|
||||||
// More circular dependencies!!
|
// More circular dependencies!!
|
||||||
class Kernel;
|
class Kernel;
|
||||||
|
|
||||||
|
@ -53,6 +59,7 @@ class ServiceManager {
|
||||||
DlpSrvrService dlp_srvr;
|
DlpSrvrService dlp_srvr;
|
||||||
DSPService dsp;
|
DSPService dsp;
|
||||||
HIDService hid;
|
HIDService hid;
|
||||||
|
HTTPService http;
|
||||||
IRUserService ir_user;
|
IRUserService ir_user;
|
||||||
FRDService frd;
|
FRDService frd;
|
||||||
FSService fs;
|
FSService fs;
|
||||||
|
@ -60,12 +67,17 @@ class ServiceManager {
|
||||||
LCDService gsp_lcd;
|
LCDService gsp_lcd;
|
||||||
LDRService ldr;
|
LDRService ldr;
|
||||||
MICService mic;
|
MICService mic;
|
||||||
|
NDMService ndm;
|
||||||
|
NewsUService news_u;
|
||||||
NFCService nfc;
|
NFCService nfc;
|
||||||
NIMService nim;
|
NIMService nim;
|
||||||
NDMService ndm;
|
|
||||||
PTMService ptm;
|
PTMService ptm;
|
||||||
|
SOCService soc;
|
||||||
|
SSLService ssl;
|
||||||
Y2RService y2r;
|
Y2RService y2r;
|
||||||
|
|
||||||
|
MCU::HWCService mcu_hwc;
|
||||||
|
|
||||||
// "srv:" commands
|
// "srv:" commands
|
||||||
void enableNotification(u32 messagePointer);
|
void enableNotification(u32 messagePointer);
|
||||||
void getServiceHandle(u32 messagePointer);
|
void getServiceHandle(u32 messagePointer);
|
||||||
|
@ -74,7 +86,7 @@ class ServiceManager {
|
||||||
void subscribe(u32 messagePointer);
|
void subscribe(u32 messagePointer);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ServiceManager(std::span<u32, 16> regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel);
|
ServiceManager(std::span<u32, 16> regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel, const EmulatorConfig& config);
|
||||||
void reset();
|
void reset();
|
||||||
void initializeFS() { fs.initializeFilesystem(); }
|
void initializeFS() { fs.initializeFilesystem(); }
|
||||||
void handleSyncRequest(u32 messagePointer);
|
void handleSyncRequest(u32 messagePointer);
|
||||||
|
@ -90,17 +102,5 @@ class ServiceManager {
|
||||||
void signalDSPEvents() { dsp.signalEvents(); }
|
void signalDSPEvents() { dsp.signalEvents(); }
|
||||||
|
|
||||||
// Input function wrappers
|
// Input function wrappers
|
||||||
void pressKey(u32 key) { hid.pressKey(key); }
|
HIDService& getHID() { return hid; }
|
||||||
void releaseKey(u32 key) { hid.releaseKey(key); }
|
|
||||||
s16 getCirclepadX() { return hid.getCirclepadX(); }
|
|
||||||
s16 getCirclepadY() { return hid.getCirclepadY(); }
|
|
||||||
void setCirclepadX(s16 x) { hid.setCirclepadX(x); }
|
|
||||||
void setCirclepadY(s16 y) { hid.setCirclepadY(y); }
|
|
||||||
void updateInputs(u64 currentTimestamp) { hid.updateInputs(currentTimestamp); }
|
|
||||||
void setTouchScreenPress(u16 x, u16 y) { hid.setTouchScreenPress(x, y); }
|
|
||||||
void releaseTouchScreen() { hid.releaseTouchScreen(); }
|
|
||||||
|
|
||||||
void setRoll(s16 roll) { hid.setRoll(roll); }
|
|
||||||
void setPitch(s16 pitch) { hid.setPitch(pitch); }
|
|
||||||
void setYaw(s16 yaw) { hid.setYaw(yaw); }
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include <cstddef>
|
|
||||||
|
|
||||||
extern unsigned char _shared_font_bin[];
|
|
||||||
extern size_t _shared_font_len;
|
|
21
include/services/soc.hpp
Normal file
21
include/services/soc.hpp
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include "kernel_types.hpp"
|
||||||
|
#include "logger.hpp"
|
||||||
|
#include "memory.hpp"
|
||||||
|
|
||||||
|
class SOCService {
|
||||||
|
Handle handle = KernelHandles::SOC;
|
||||||
|
Memory& mem;
|
||||||
|
MAKE_LOG_FUNCTION(log, socLogger)
|
||||||
|
|
||||||
|
bool initialized = false;
|
||||||
|
|
||||||
|
// Service commands
|
||||||
|
void initializeSockets(u32 messagePointer);
|
||||||
|
|
||||||
|
public:
|
||||||
|
SOCService(Memory& mem) : mem(mem) {}
|
||||||
|
void reset();
|
||||||
|
void handleSyncRequest(u32 messagePointer);
|
||||||
|
};
|
25
include/services/ssl.hpp
Normal file
25
include/services/ssl.hpp
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
#pragma once
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include "kernel_types.hpp"
|
||||||
|
#include "logger.hpp"
|
||||||
|
#include "memory.hpp"
|
||||||
|
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
class SSLService {
|
||||||
|
Handle handle = KernelHandles::SSL;
|
||||||
|
Memory& mem;
|
||||||
|
MAKE_LOG_FUNCTION(log, sslLogger)
|
||||||
|
|
||||||
|
std::mt19937 rng; // Use a Mersenne Twister for RNG since this service is supposed to have better rng than just rand()
|
||||||
|
bool initialized;
|
||||||
|
|
||||||
|
// Service commands
|
||||||
|
void initialize(u32 messagePointer);
|
||||||
|
void generateRandomData(u32 messagePointer);
|
||||||
|
|
||||||
|
public:
|
||||||
|
SSLService(Memory& mem) : mem(mem) {}
|
||||||
|
void reset();
|
||||||
|
void handleSyncRequest(u32 messagePointer);
|
||||||
|
};
|
|
@ -75,6 +75,7 @@ class Y2RService {
|
||||||
void setInputLineWidth(u32 messagePointer);
|
void setInputLineWidth(u32 messagePointer);
|
||||||
void setInputLines(u32 messagePointer);
|
void setInputLines(u32 messagePointer);
|
||||||
void setOutputFormat(u32 messagePointer);
|
void setOutputFormat(u32 messagePointer);
|
||||||
|
void setPackageParameter(u32 messagePointer);
|
||||||
void setReceiving(u32 messagePointer);
|
void setReceiving(u32 messagePointer);
|
||||||
void setRotation(u32 messagePointer);
|
void setRotation(u32 messagePointer);
|
||||||
void setSendingY(u32 messagePointer);
|
void setSendingY(u32 messagePointer);
|
||||||
|
|
25
readme.md
25
readme.md
|
@ -8,17 +8,27 @@ Join our Discord server by pressing on the banner below!
|
||||||
|
|
||||||
[](https://discord.gg/ZYbugsEmsw)
|
[](https://discord.gg/ZYbugsEmsw)
|
||||||
|
|
||||||
  
|
  
|
||||||
|
|
||||||
|
# Download
|
||||||
|
You can download stable builds from the Releases tab, or you can download the latest build from the table below
|
||||||
|
|
||||||
|
|Platform|Status|Download|
|
||||||
|
|--------|------------|--------|
|
||||||
|
|Windows build|[](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Windows_Build.yml)|[Windows Executable](https://nightly.link/wheremyfoodat/Panda3DS/workflows/Windows_Build/master/Windows%20executable.zip)|
|
||||||
|
|MacOS build|[](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/MacOS_Build.yml)|[MacOS App Bundle](https://nightly.link/wheremyfoodat/Panda3DS/workflows/MacOS_Build/master/MacOS%20Alber%20App%20Bundle.zip)|
|
||||||
|
|Linux build|[](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Linux_Build.yml)|[Linux Executable](https://nightly.link/wheremyfoodat/Panda3DS/workflows/Linux_Build/master/Linux%20executable.zip)|
|
||||||
|
|
||||||
# Compatibility
|
# Compatibility
|
||||||
Panda3DS is still in the early stages of development. Many games boot, many don't. Most games have at least some hilariously broken graphics, audio is not supported, performance leaves a bit to be desired mainly thanks to lack of shader acceleration, and most QoL features (including a GUI) are missing.
|
Panda3DS is still in the early stages of development. Many games boot, many don't. Most games have at least some hilariously broken graphics, audio is not supported, and some QoL features (including a GUI) are missing.
|
||||||
|
|
||||||
In addition, some games don't quiiite work with the upstream code. A lot of them might need some panics in the source code to be commented out before they work, etc. However, just the fact things can work as well as they do now is promising in itself.
|
In addition, some games don't quiiite work with the upstream code. A lot of them might need some panics in the source code to be commented out before they work, etc. However, just the fact things can work as well as they do now is promising in itself.
|
||||||
|
|
||||||
|
Check out [this Google spreadsheet](https://docs.google.com/spreadsheets/d/1nWZTzfaMPkZdyhqHEawMRBaP0qSMmQdxrVfAbgapYrM/edit?usp=sharing) for an unofficial compatibility list.
|
||||||
# Why?
|
# Why?
|
||||||
The 3DS emulation scene is already pretty mature, with offerings such as [Citra](https://github.com/citra-emu/citra) which can offer a great playing experience for most games in the library, [Corgi3DS](https://github.com/PSI-Rockin/Corgi3DS), an innovative LLE emulator, or [Mikage](https://mikage.app/). However, there's always room for more emulators! While Panda3DS was initially a mere curiosity, there's many different concepts I would like to explore with it in the future, such as:
|
The 3DS emulation scene is already pretty mature, with offerings such as [Citra](https://github.com/citra-emu/citra) which can offer a great playing experience for most games in the library, [Corgi3DS](https://github.com/PSI-Rockin/Corgi3DS), an innovative LLE emulator, or [Mikage](https://mikage.app/). However, there's always room for more emulators! While Panda3DS was initially a mere curiosity, there's many different concepts I would like to explore with it in the future, such as:
|
||||||
|
|
||||||
- Virtualization. What motivated the creation of this emulator was actually a discussion on whether it is possible to get fast 3DS emulation on low-end hardware such as the Raspberry Pi 4, using the KVM API. At the moment, Panda3DS is powered by dynarmic rather than using virtualization, but this is definitely a concept I want to explore in the future.
|
- Virtualization. What motivated the creation of this emulator was actually a discussion on whether it is possible to get fast 3DS emulation on low-end hardware such as the Raspberry Pi 4, using the KVM API. At the moment, Panda3DS is powered by Dynarmic rather than using virtualization, but this is definitely a concept I want to explore in the future.
|
||||||
|
|
||||||
- Debugging, reverse engineering and modding tools. While contributing to [PCSX-Redux](https://github.com/grumpycoders/pcsx-redux) and collaborating with the other developers, I had the chance to find out how useful tools like these can be. They can serve as indispensable tools for the homebrew devs, modders, reverse engineers, as well as emulator developers themselves. Some tools can even become fun toys the casual user can mess around with. As such, I think they can really improve the experience in a project like this. Of course, I'd like to thank @nicolasnoble and the entire Redux team for helping me learn the value of these tools, as well as making me improve as a programmer.
|
- Debugging, reverse engineering and modding tools. While contributing to [PCSX-Redux](https://github.com/grumpycoders/pcsx-redux) and collaborating with the other developers, I had the chance to find out how useful tools like these can be. They can serve as indispensable tools for the homebrew devs, modders, reverse engineers, as well as emulator developers themselves. Some tools can even become fun toys the casual user can mess around with. As such, I think they can really improve the experience in a project like this. Of course, I'd like to thank @nicolasnoble and the entire Redux team for helping me learn the value of these tools, as well as making me improve as a programmer.
|
||||||
|
|
||||||
|
@ -29,7 +39,7 @@ The 3DS emulation scene is already pretty mature, with offerings such as [Citra]
|
||||||
Keep in mind, these are all long-term plans. Until then, the main focus is just improving compatibility
|
Keep in mind, these are all long-term plans. Until then, the main focus is just improving compatibility
|
||||||
|
|
||||||
# How to build
|
# How to build
|
||||||
Panda3DS compiles on Windows, Linux and MacOS, without needing to download any system dependencies.
|
Panda3DS compiles on Windows, Linux and MacOS, with only 1 system dependency, the Vulkan SDK. However, if you don't want to install the Vulkan SDK you can always build the emulator with only OpenGL support, by adding `-DENABLE_VULKAN=OFF` to the `cmake` command
|
||||||
|
|
||||||
All you need is CMake and a generator of your choice (Make, Visual Studio, Ninja, etc). Simply clone the repo recursively and build it like your average CMake project.
|
All you need is CMake and a generator of your choice (Make, Visual Studio, Ninja, etc). Simply clone the repo recursively and build it like your average CMake project.
|
||||||
|
|
||||||
|
@ -70,6 +80,9 @@ Keyboard & Mouse
|
||||||
- Select button Backspace
|
- Select button Backspace
|
||||||
- Touch Screen Left click
|
- Touch Screen Left click
|
||||||
- Gyroscope Hold right click and swipe your mouse left and right (support is kind of shaky atm, but games that require gyro here and there like Kirby should work)
|
- Gyroscope Hold right click and swipe your mouse left and right (support is kind of shaky atm, but games that require gyro here and there like Kirby should work)
|
||||||
|
- Pause/Resume F4
|
||||||
|
- Reload F5
|
||||||
|
|
||||||
|
|
||||||
Panda3DS also supports controller input using the SDL2 GameController API.
|
Panda3DS also supports controller input using the SDL2 GameController API.
|
||||||
|
|
||||||
|
@ -91,6 +104,8 @@ Panda3DS also supports controller input using the SDL2 GameController API.
|
||||||
- [MelonDS](https://github.com/melonDS-emu/melonDS): "DS emulator, sorta" - Arisotura
|
- [MelonDS](https://github.com/melonDS-emu/melonDS): "DS emulator, sorta" - Arisotura
|
||||||
- [Kaizen](https://github.com/SimoneN64/Kaizen): Experimental work-in-progress low-level N64 emulator
|
- [Kaizen](https://github.com/SimoneN64/Kaizen): Experimental work-in-progress low-level N64 emulator
|
||||||
- [ChonkyStation](https://github.com/liuk7071/ChonkyStation): Work-in-progress PlayStation emulator
|
- [ChonkyStation](https://github.com/liuk7071/ChonkyStation): Work-in-progress PlayStation emulator
|
||||||
|
- [shadPS4](https://github.com/georgemoralis/shadPS4): Work-in-progress PS4 emulator by the founder of PCSX, PCSX2 and more
|
||||||
|
- [Hydra](https://github.com/hydra-emu/hydra): Cross-platform GameBoy, NES, N64 and Chip-8 emulator
|
||||||
|
|
||||||
# Support
|
# Support
|
||||||
If you find this project exciting and want to support the founder, check out [his Patreon page](https://www.patreon.com/wheremyfoodat)
|
If you find this project exciting and want to support the founder, check out [his Patreon page](https://www.patreon.com/wheremyfoodat)
|
||||||
|
@ -99,5 +114,5 @@ Keep in mind, funding is only aimed to cover various life costs and support deve
|
||||||
|
|
||||||
Nintendo 3DS is a registered trademark of Nintendo Co., Ltd.
|
Nintendo 3DS is a registered trademark of Nintendo Co., Ltd.
|
||||||
|
|
||||||

|

|
||||||
Here's a panda it go blep
|
Here's a panda it go blep
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "toml.hpp"
|
#include "toml.hpp"
|
||||||
|
@ -9,6 +11,8 @@
|
||||||
// We are legally allowed, as per the author's wish, to use the above code without any licensing restrictions
|
// We are legally allowed, as per the author's wish, to use the above code without any licensing restrictions
|
||||||
// However we still want to follow the license as closely as possible and offer the proper attributions.
|
// However we still want to follow the license as closely as possible and offer the proper attributions.
|
||||||
|
|
||||||
|
EmulatorConfig::EmulatorConfig(const std::filesystem::path& path) { load(path); }
|
||||||
|
|
||||||
void EmulatorConfig::load(const std::filesystem::path& path) {
|
void EmulatorConfig::load(const std::filesystem::path& path) {
|
||||||
// If the configuration file does not exist, create it and return
|
// If the configuration file does not exist, create it and return
|
||||||
std::error_code error;
|
std::error_code error;
|
||||||
|
@ -26,12 +30,55 @@ void EmulatorConfig::load(const std::filesystem::path& path) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.contains("General")) {
|
||||||
|
auto generalResult = toml::expect<toml::value>(data.at("General"));
|
||||||
|
if (generalResult.is_ok()) {
|
||||||
|
auto general = generalResult.unwrap();
|
||||||
|
|
||||||
|
discordRpcEnabled = toml::find_or<toml::boolean>(general, "EnableDiscordRPC", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (data.contains("GPU")) {
|
if (data.contains("GPU")) {
|
||||||
auto gpuResult = toml::expect<toml::value>(data.at("GPU"));
|
auto gpuResult = toml::expect<toml::value>(data.at("GPU"));
|
||||||
if (gpuResult.is_ok()) {
|
if (gpuResult.is_ok()) {
|
||||||
auto gpu = gpuResult.unwrap();
|
auto gpu = gpuResult.unwrap();
|
||||||
|
|
||||||
shaderJitEnabled = toml::find_or<toml::boolean>(gpu, "EnableShaderJIT", false);
|
// Get renderer
|
||||||
|
auto rendererName = toml::find_or<std::string>(gpu, "Renderer", "OpenGL");
|
||||||
|
auto configRendererType = Renderer::typeFromString(rendererName);
|
||||||
|
|
||||||
|
if (configRendererType.has_value()) {
|
||||||
|
rendererType = configRendererType.value();
|
||||||
|
} else {
|
||||||
|
Helpers::warn("Invalid renderer specified: %s\n", rendererName.c_str());
|
||||||
|
rendererType = RendererType::OpenGL;
|
||||||
|
}
|
||||||
|
|
||||||
|
shaderJitEnabled = toml::find_or<toml::boolean>(gpu, "EnableShaderJIT", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.contains("Battery")) {
|
||||||
|
auto batteryResult = toml::expect<toml::value>(data.at("Battery"));
|
||||||
|
if (batteryResult.is_ok()) {
|
||||||
|
auto battery = batteryResult.unwrap();
|
||||||
|
|
||||||
|
chargerPlugged = toml::find_or<toml::boolean>(battery, "ChargerPlugged", true);
|
||||||
|
batteryPercentage = toml::find_or<toml::integer>(battery, "BatteryPercentage", 3);
|
||||||
|
|
||||||
|
// Clamp battery % to [0, 100] to make sure it's a valid value
|
||||||
|
batteryPercentage = std::clamp(batteryPercentage, 0, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.contains("SD")) {
|
||||||
|
auto sdResult = toml::expect<toml::value>(data.at("SD"));
|
||||||
|
if (sdResult.is_ok()) {
|
||||||
|
auto sd = sdResult.unwrap();
|
||||||
|
|
||||||
|
sdCardInserted = toml::find_or<toml::boolean>(sd, "UseVirtualSD", true);
|
||||||
|
sdWriteProtected = toml::find_or<toml::boolean>(sd, "WriteProtectVirtualSD", false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +90,7 @@ void EmulatorConfig::save(const std::filesystem::path& path) {
|
||||||
if (std::filesystem::exists(path, error)) {
|
if (std::filesystem::exists(path, error)) {
|
||||||
try {
|
try {
|
||||||
data = toml::parse<toml::preserve_comments>(path);
|
data = toml::parse<toml::preserve_comments>(path);
|
||||||
} catch (std::exception& ex) {
|
} catch (const std::exception& ex) {
|
||||||
Helpers::warn("Exception trying to parse config file. Exception: %s\n", ex.what());
|
Helpers::warn("Exception trying to parse config file. Exception: %s\n", ex.what());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -54,7 +101,15 @@ void EmulatorConfig::save(const std::filesystem::path& path) {
|
||||||
printf("Saving new configuration file %s\n", path.string().c_str());
|
printf("Saving new configuration file %s\n", path.string().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data["General"]["EnableDiscordRPC"] = discordRpcEnabled;
|
||||||
data["GPU"]["EnableShaderJIT"] = shaderJitEnabled;
|
data["GPU"]["EnableShaderJIT"] = shaderJitEnabled;
|
||||||
|
data["GPU"]["Renderer"] = std::string(Renderer::typeToString(rendererType));
|
||||||
|
|
||||||
|
data["Battery"]["ChargerPlugged"] = chargerPlugged;
|
||||||
|
data["Battery"]["BatteryPercentage"] = batteryPercentage;
|
||||||
|
|
||||||
|
data["SD"]["UseVirtualSD"] = sdCardInserted;
|
||||||
|
data["SD"]["WriteProtectVirtualSD"] = sdWriteProtected;
|
||||||
|
|
||||||
std::ofstream file(path, std::ios::out);
|
std::ofstream file(path, std::ios::out);
|
||||||
file << data;
|
file << data;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#include "cpu_dynarmic.hpp"
|
#include "cpu_dynarmic.hpp"
|
||||||
#include "arm_defs.hpp"
|
#include "arm_defs.hpp"
|
||||||
|
|
||||||
CPU::CPU(Memory& mem, Kernel& kernel) : mem(mem), env(mem, kernel, *this) {
|
CPU::CPU(Memory& mem, Kernel& kernel) : mem(mem), env(mem, kernel) {
|
||||||
cp15 = std::make_shared<CP15>();
|
cp15 = std::make_shared<CP15>();
|
||||||
|
|
||||||
Dynarmic::A32::UserConfig config;
|
Dynarmic::A32::UserConfig config;
|
||||||
|
|
|
@ -143,6 +143,9 @@ void ShaderEmitter::compileInstruction(const PICAShader& shaderUnit) {
|
||||||
break;
|
break;
|
||||||
case ShaderOpcodes::DP3: recDP3(shaderUnit, instruction); break;
|
case ShaderOpcodes::DP3: recDP3(shaderUnit, instruction); break;
|
||||||
case ShaderOpcodes::DP4: recDP4(shaderUnit, instruction); break;
|
case ShaderOpcodes::DP4: recDP4(shaderUnit, instruction); break;
|
||||||
|
case ShaderOpcodes::DPH:
|
||||||
|
case ShaderOpcodes::DPHI:
|
||||||
|
recDPH(shaderUnit, instruction); break;
|
||||||
case ShaderOpcodes::END: recEND(shaderUnit, instruction); break;
|
case ShaderOpcodes::END: recEND(shaderUnit, instruction); break;
|
||||||
case ShaderOpcodes::EX2: recEX2(shaderUnit, instruction); break;
|
case ShaderOpcodes::EX2: recEX2(shaderUnit, instruction); break;
|
||||||
case ShaderOpcodes::FLR: recFLR(shaderUnit, instruction); break;
|
case ShaderOpcodes::FLR: recFLR(shaderUnit, instruction); break;
|
||||||
|
@ -179,6 +182,10 @@ void ShaderEmitter::compileInstruction(const PICAShader& shaderUnit) {
|
||||||
case ShaderOpcodes::SLTI:
|
case ShaderOpcodes::SLTI:
|
||||||
recSLT(shaderUnit, instruction); break;
|
recSLT(shaderUnit, instruction); break;
|
||||||
|
|
||||||
|
case ShaderOpcodes::SGE:
|
||||||
|
case ShaderOpcodes::SGEI:
|
||||||
|
recSGE(shaderUnit, instruction); break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Helpers::panic("Shader JIT: Unimplemented PICA opcode %X", opcode);
|
Helpers::panic("Shader JIT: Unimplemented PICA opcode %X", opcode);
|
||||||
}
|
}
|
||||||
|
@ -525,6 +532,32 @@ void ShaderEmitter::recDP4(const PICAShader& shader, u32 instruction) {
|
||||||
storeRegister(src1_xmm, shader, dest, operandDescriptor);
|
storeRegister(src1_xmm, shader, dest, operandDescriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ShaderEmitter::recDPH(const PICAShader& shader, u32 instruction) {
|
||||||
|
const bool isDPHI = (instruction >> 26) == ShaderOpcodes::DPHI;
|
||||||
|
|
||||||
|
const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f];
|
||||||
|
const u32 src1 = isDPHI ? getBits<14, 5>(instruction) : getBits<12, 7>(instruction);
|
||||||
|
const u32 src2 = isDPHI ? getBits<7, 7>(instruction) : getBits<7, 5>(instruction);
|
||||||
|
const u32 idx = getBits<19, 2>(instruction);
|
||||||
|
const u32 dest = getBits<21, 5>(instruction);
|
||||||
|
|
||||||
|
// TODO: Safe multiplication equivalent (Multiplication is not IEEE compliant on the PICA)
|
||||||
|
loadRegister<1>(src1_xmm, shader, src1, isDPHI ? 0 : idx, operandDescriptor);
|
||||||
|
loadRegister<2>(src2_xmm, shader, src2, isDPHI ? idx : 0, operandDescriptor);
|
||||||
|
|
||||||
|
// Attach 1.0 to the w component of src1
|
||||||
|
if (haveSSE4_1) {
|
||||||
|
blendps(src1_xmm, xword[rip + onesVector], 0b1000);
|
||||||
|
} else {
|
||||||
|
movaps(scratch1, src1_xmm);
|
||||||
|
unpckhps(scratch1, xword[rip + onesVector]);
|
||||||
|
unpcklpd(src1_xmm, scratch1);
|
||||||
|
}
|
||||||
|
|
||||||
|
dpps(src1_xmm, src2_xmm, 0b11111111); // 4-lane dot product between the 2 registers, store the result in all lanes of scratch1 similarly to PICA
|
||||||
|
storeRegister(src1_xmm, shader, dest, operandDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
void ShaderEmitter::recMAX(const PICAShader& shader, u32 instruction) {
|
void ShaderEmitter::recMAX(const PICAShader& shader, u32 instruction) {
|
||||||
const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f];
|
const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f];
|
||||||
const u32 src1 = getBits<12, 7>(instruction);
|
const u32 src1 = getBits<12, 7>(instruction);
|
||||||
|
@ -656,6 +689,24 @@ void ShaderEmitter::recSLT(const PICAShader& shader, u32 instruction) {
|
||||||
storeRegister(src1_xmm, shader, dest, operandDescriptor);
|
storeRegister(src1_xmm, shader, dest, operandDescriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ShaderEmitter::recSGE(const PICAShader& shader, u32 instruction) {
|
||||||
|
const bool isSGEI = (instruction >> 26) == ShaderOpcodes::SGEI;
|
||||||
|
const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f];
|
||||||
|
|
||||||
|
const u32 src1 = isSGEI ? getBits<14, 5>(instruction) : getBits<12, 7>(instruction);
|
||||||
|
const u32 src2 = isSGEI ? getBits<7, 7>(instruction) : getBits<7, 5>(instruction);
|
||||||
|
const u32 idx = getBits<19, 2>(instruction);
|
||||||
|
const u32 dest = getBits<21, 5>(instruction);
|
||||||
|
|
||||||
|
loadRegister<1>(src1_xmm, shader, src1, isSGEI ? 0 : idx, operandDescriptor);
|
||||||
|
loadRegister<2>(src2_xmm, shader, src2, isSGEI ? idx : 0, operandDescriptor);
|
||||||
|
|
||||||
|
// SSE does not have a cmpgeps instruction so we turn src1 >= src2 to src2 <= src1, result in src2
|
||||||
|
cmpleps(src2_xmm, src1_xmm);
|
||||||
|
andps(src2_xmm, xword[rip + onesVector]);
|
||||||
|
storeRegister(src2_xmm, shader, dest, operandDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
void ShaderEmitter::recCMP(const PICAShader& shader, u32 instruction) {
|
void ShaderEmitter::recCMP(const PICAShader& shader, u32 instruction) {
|
||||||
const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f];
|
const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f];
|
||||||
const u32 src1 = getBits<12, 7>(instruction);
|
const u32 src1 = getBits<12, 7>(instruction);
|
||||||
|
|
|
@ -7,10 +7,20 @@
|
||||||
|
|
||||||
#include "PICA/float_types.hpp"
|
#include "PICA/float_types.hpp"
|
||||||
#include "PICA/regs.hpp"
|
#include "PICA/regs.hpp"
|
||||||
|
#include "renderer_null/renderer_null.hpp"
|
||||||
|
#include "renderer_sw/renderer_sw.hpp"
|
||||||
#ifdef PANDA3DS_ENABLE_OPENGL
|
#ifdef PANDA3DS_ENABLE_OPENGL
|
||||||
#include "renderer_gl/renderer_gl.hpp"
|
#include "renderer_gl/renderer_gl.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef PANDA3DS_ENABLE_VULKAN
|
||||||
|
#include "renderer_vk/renderer_vk.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
constexpr u32 topScreenWidth = 240;
|
||||||
|
constexpr u32 topScreenHeight = 400;
|
||||||
|
|
||||||
|
constexpr u32 bottomScreenWidth = 240;
|
||||||
|
constexpr u32 bottomScreenHeight = 300;
|
||||||
|
|
||||||
using namespace Floats;
|
using namespace Floats;
|
||||||
|
|
||||||
|
@ -20,10 +30,34 @@ GPU::GPU(Memory& mem, EmulatorConfig& config) : mem(mem), config(config) {
|
||||||
vram = new u8[vramSize];
|
vram = new u8[vramSize];
|
||||||
mem.setVRAM(vram); // Give the bus a pointer to our VRAM
|
mem.setVRAM(vram); // Give the bus a pointer to our VRAM
|
||||||
|
|
||||||
// TODO: Configurable backend
|
switch (config.rendererType) {
|
||||||
|
case RendererType::Null: {
|
||||||
|
renderer.reset(new RendererNull(*this, regs, externalRegs));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case RendererType::Software: {
|
||||||
|
renderer.reset(new RendererSw(*this, regs, externalRegs));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef PANDA3DS_ENABLE_OPENGL
|
#ifdef PANDA3DS_ENABLE_OPENGL
|
||||||
renderer.reset(new RendererGL(*this, regs));
|
case RendererType::OpenGL: {
|
||||||
|
renderer.reset(new RendererGL(*this, regs, externalRegs));
|
||||||
|
break;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef PANDA3DS_ENABLE_VULKAN
|
||||||
|
case RendererType::Vulkan: {
|
||||||
|
renderer.reset(new RendererVK(*this, regs, externalRegs));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
default: {
|
||||||
|
Helpers::panic("Rendering backend not supported: %s", Renderer::typeToString(config.rendererType));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPU::reset() {
|
void GPU::reset() {
|
||||||
|
@ -50,6 +84,27 @@ void GPU::reset() {
|
||||||
e.config2 = 0;
|
e.config2 = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize the framebuffer registers. Values taken from Citra.
|
||||||
|
|
||||||
|
using namespace PICA::ExternalRegs;
|
||||||
|
// Top screen addresses and dimentions.
|
||||||
|
externalRegs[Framebuffer0AFirstAddr] = 0x181E6000;
|
||||||
|
externalRegs[Framebuffer0ASecondAddr] = 0x1822C800;
|
||||||
|
externalRegs[Framebuffer0BFirstAddr] = 0x18273000;
|
||||||
|
externalRegs[Framebuffer0BSecondAddr] = 0x182B9800;
|
||||||
|
externalRegs[Framebuffer0Size] = (topScreenHeight << 16) | topScreenWidth;
|
||||||
|
externalRegs[Framebuffer0Stride] = 720;
|
||||||
|
externalRegs[Framebuffer0Config] = static_cast<u32>(PICA::ColorFmt::RGB8);
|
||||||
|
externalRegs[Framebuffer0Select] = 0;
|
||||||
|
|
||||||
|
// Bottom screen addresses and dimentions.
|
||||||
|
externalRegs[Framebuffer1AFirstAddr] = 0x1848F000;
|
||||||
|
externalRegs[Framebuffer1ASecondAddr] = 0x184C7800;
|
||||||
|
externalRegs[Framebuffer1Size] = (bottomScreenHeight << 16) | bottomScreenWidth;
|
||||||
|
externalRegs[Framebuffer1Stride] = 720;
|
||||||
|
externalRegs[Framebuffer1Config] = static_cast<u32>(PICA::ColorFmt::RGB8);
|
||||||
|
externalRegs[Framebuffer1Select] = 0;
|
||||||
|
|
||||||
renderer->reset();
|
renderer->reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,15 +348,17 @@ PICA::Vertex GPU::getImmediateModeVertex() {
|
||||||
|
|
||||||
// Run VS and return vertex data. TODO: Don't hardcode offsets for each attribute
|
// Run VS and return vertex data. TODO: Don't hardcode offsets for each attribute
|
||||||
shaderUnit.vs.run();
|
shaderUnit.vs.run();
|
||||||
std::memcpy(&v.s.positions, &shaderUnit.vs.outputs[0], sizeof(vec4f));
|
|
||||||
std::memcpy(&v.s.colour, &shaderUnit.vs.outputs[1], sizeof(vec4f));
|
// Map shader outputs to fixed function properties
|
||||||
std::memcpy(&v.s.texcoord0, &shaderUnit.vs.outputs[2], 2 * sizeof(f24));
|
const u32 totalShaderOutputs = regs[PICA::InternalRegs::ShaderOutputCount] & 7;
|
||||||
|
for (int i = 0; i < totalShaderOutputs; i++) {
|
||||||
|
const u32 config = regs[PICA::InternalRegs::ShaderOutmap0 + i];
|
||||||
|
|
||||||
printf(
|
for (int j = 0; j < 4; j++) { // pls unroll
|
||||||
"(x, y, z, w) = (%f, %f, %f, %f)\n", (double)v.s.positions[0], (double)v.s.positions[1], (double)v.s.positions[2], (double)v.s.positions[3]
|
const u32 mapping = (config >> (j * 8)) & 0x1F;
|
||||||
);
|
v.raw[mapping] = shaderUnit.vs.outputs[i][j];
|
||||||
printf("(r, g, b, a) = (%f, %f, %f, %f)\n", (double)v.s.colour[0], (double)v.s.colour[1], (double)v.s.colour[2], (double)v.s.colour[3]);
|
}
|
||||||
printf("(u, v ) = (%f, %f)\n", (double)v.s.texcoord0[0], (double)v.s.texcoord0[1]);
|
}
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,11 +19,36 @@ void GPU::writeReg(u32 address, u32 value) {
|
||||||
if (address >= 0x1EF01000 && address < 0x1EF01C00) { // Internal registers
|
if (address >= 0x1EF01000 && address < 0x1EF01C00) { // Internal registers
|
||||||
const u32 index = (address - 0x1EF01000) / sizeof(u32);
|
const u32 index = (address - 0x1EF01000) / sizeof(u32);
|
||||||
writeInternalReg(index, value, 0xffffffff);
|
writeInternalReg(index, value, 0xffffffff);
|
||||||
|
} else if (address >= 0x1EF00004 && address < 0x1EF01000) {
|
||||||
|
const u32 index = (address - 0x1EF00004) / sizeof(u32);
|
||||||
|
writeExternalReg(index, value);
|
||||||
} else {
|
} else {
|
||||||
log("Ignoring write to external GPU register %08X. Value: %08X\n", address, value);
|
log("Ignoring write to unknown GPU register %08X. Value: %08X\n", address, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u32 GPU::readExternalReg(u32 index) {
|
||||||
|
using namespace PICA::ExternalRegs;
|
||||||
|
|
||||||
|
if (index > 0x1000) [[unlikely]] {
|
||||||
|
Helpers::panic("Tried to read invalid external GPU register. Index: %X\n", index);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return externalRegs[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPU::writeExternalReg(u32 index, u32 value) {
|
||||||
|
using namespace PICA::ExternalRegs;
|
||||||
|
|
||||||
|
if (index > 0x1000) [[unlikely]] {
|
||||||
|
Helpers::panic("Tried to write to invalid external GPU register. Index: %X, value: %08X\n", index, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
externalRegs[index] = value;
|
||||||
|
}
|
||||||
|
|
||||||
u32 GPU::readInternalReg(u32 index) {
|
u32 GPU::readInternalReg(u32 index) {
|
||||||
using namespace PICA::InternalRegs;
|
using namespace PICA::InternalRegs;
|
||||||
|
|
||||||
|
@ -384,4 +409,4 @@ void GPU::startCommandList(u32 addr, u32 size) {
|
||||||
writeInternalReg(id, param, mask);
|
writeInternalReg(id, param, mask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ void PICAShader::run() {
|
||||||
case ShaderOpcodes::NOP: break; // Do nothing
|
case ShaderOpcodes::NOP: break; // Do nothing
|
||||||
case ShaderOpcodes::RCP: rcp(instruction); break;
|
case ShaderOpcodes::RCP: rcp(instruction); break;
|
||||||
case ShaderOpcodes::RSQ: rsq(instruction); break;
|
case ShaderOpcodes::RSQ: rsq(instruction); break;
|
||||||
|
case ShaderOpcodes::SGE: sge(instruction); break;
|
||||||
case ShaderOpcodes::SGEI: sgei(instruction); break;
|
case ShaderOpcodes::SGEI: sgei(instruction); break;
|
||||||
case ShaderOpcodes::SLT: slt(instruction); break;
|
case ShaderOpcodes::SLT: slt(instruction); break;
|
||||||
case ShaderOpcodes::SLTI: slti(instruction); break;
|
case ShaderOpcodes::SLTI: slti(instruction); break;
|
||||||
|
@ -517,6 +518,26 @@ void PICAShader::slt(u32 instruction) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PICAShader::sge(u32 instruction) {
|
||||||
|
const u32 operandDescriptor = operandDescriptors[instruction & 0x7f];
|
||||||
|
u32 src1 = getBits<12, 7>(instruction);
|
||||||
|
const u32 src2 = getBits<7, 5>(instruction); // src2 coming first because PICA moment
|
||||||
|
const u32 idx = getBits<19, 2>(instruction);
|
||||||
|
const u32 dest = getBits<21, 5>(instruction);
|
||||||
|
|
||||||
|
src1 = getIndexedSource(src1, idx);
|
||||||
|
vec4f srcVec1 = getSourceSwizzled<1>(src1, operandDescriptor);
|
||||||
|
vec4f srcVec2 = getSourceSwizzled<2>(src2, operandDescriptor);
|
||||||
|
auto& destVector = getDest(dest);
|
||||||
|
|
||||||
|
u32 componentMask = operandDescriptor & 0xf;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
if (componentMask & (1 << i)) {
|
||||||
|
destVector[3 - i] = srcVec1[3 - i] >= srcVec2[3 - i] ? f24::fromFloat32(1.0) : f24::zero();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PICAShader::sgei(u32 instruction) {
|
void PICAShader::sgei(u32 instruction) {
|
||||||
const u32 operandDescriptor = operandDescriptors[instruction & 0x7f];
|
const u32 operandDescriptor = operandDescriptors[instruction & 0x7f];
|
||||||
const u32 src1 = getBits<14, 5>(instruction);
|
const u32 src1 = getBits<14, 5>(instruction);
|
||||||
|
|
235
src/core/action_replay.cpp
Normal file
235
src/core/action_replay.cpp
Normal file
|
@ -0,0 +1,235 @@
|
||||||
|
#include "action_replay.hpp"
|
||||||
|
|
||||||
|
ActionReplay::ActionReplay(Memory& mem, HIDService& hid) : mem(mem), hid(hid) { reset(); }
|
||||||
|
|
||||||
|
void ActionReplay::reset() {
|
||||||
|
// Default value of storage regs is 0
|
||||||
|
storage1 = 0;
|
||||||
|
storage2 = 0;
|
||||||
|
|
||||||
|
// TODO: Is the active storage persistent or not?
|
||||||
|
activeStorage = &storage1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActionReplay::runCheat(const Cheat& cheat) {
|
||||||
|
// Set offset and data registers to 0 at the start of a cheat
|
||||||
|
data1 = data2 = offset1 = offset2 = 0;
|
||||||
|
pc = 0;
|
||||||
|
ifStackIndex = 0;
|
||||||
|
loopStackIndex = 0;
|
||||||
|
running = true;
|
||||||
|
|
||||||
|
activeOffset = &offset1;
|
||||||
|
activeData = &data1;
|
||||||
|
|
||||||
|
while (running) {
|
||||||
|
// See if we can fetch 1 64-bit opcode, otherwise we're out of bounds. Cheats seem to end when going out of bounds?
|
||||||
|
if (pc + 1 >= cheat.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Fetch instruction
|
||||||
|
const u32 instruction = cheat[pc++];
|
||||||
|
|
||||||
|
// Instructions D0000000 00000000 and D2000000 00000000 are unconditional
|
||||||
|
bool isUnconditional = cheat[pc] == 0 && (instruction == 0xD0000000 || instruction == 0xD2000000);
|
||||||
|
if (ifStackIndex > 0 && !isUnconditional && !ifStack[ifStackIndex - 1]) {
|
||||||
|
pc++; // Eat up dummy word
|
||||||
|
continue; // Skip conditional instructions where the condition is false
|
||||||
|
}
|
||||||
|
|
||||||
|
runInstruction(cheat, instruction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 ActionReplay::read8(u32 addr) { return mem.read8(addr); }
|
||||||
|
u16 ActionReplay::read16(u32 addr) { return mem.read16(addr); }
|
||||||
|
u32 ActionReplay::read32(u32 addr) { return mem.read32(addr); }
|
||||||
|
|
||||||
|
// Some AR cheats seem to want to write to unmapped memory or memory that straight up does not exist
|
||||||
|
|
||||||
|
#define MAKE_WRITE_HANDLER(size) \
|
||||||
|
void ActionReplay::write##size(u32 addr, u##size value) { \
|
||||||
|
auto pointerWrite = mem.getWritePointer(addr); \
|
||||||
|
if (pointerWrite) { \
|
||||||
|
*(u##size*)pointerWrite = value; \
|
||||||
|
} else { \
|
||||||
|
auto pointerRead = mem.getReadPointer(addr); \
|
||||||
|
if (pointerRead) { \
|
||||||
|
*(u##size*)pointerRead = value; \
|
||||||
|
} else { \
|
||||||
|
Helpers::warn("AR code tried to write to invalid address: %08X\n", addr); \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
MAKE_WRITE_HANDLER(8)
|
||||||
|
MAKE_WRITE_HANDLER(16)
|
||||||
|
MAKE_WRITE_HANDLER(32)
|
||||||
|
#undef MAKE_WRITE_HANDLER
|
||||||
|
|
||||||
|
void ActionReplay::runInstruction(const Cheat& cheat, u32 instruction) {
|
||||||
|
// Top nibble determines the instruction type
|
||||||
|
const u32 type = instruction >> 28;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
// 32-bit write to [XXXXXXX + offset]
|
||||||
|
case 0x0: {
|
||||||
|
const u32 baseAddr = Helpers::getBits<0, 28>(instruction);
|
||||||
|
const u32 value = cheat[pc++];
|
||||||
|
write32(baseAddr + *activeOffset, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 16-bit write to [XXXXXXX + offset]
|
||||||
|
case 0x1: {
|
||||||
|
const u32 baseAddr = Helpers::getBits<0, 28>(instruction);
|
||||||
|
const u16 value = u16(cheat[pc++]);
|
||||||
|
write16(baseAddr + *activeOffset, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8-bit write to [XXXXXXX + offset]
|
||||||
|
case 0x2: {
|
||||||
|
const u32 baseAddr = Helpers::getBits<0, 28>(instruction);
|
||||||
|
const u8 value = u8(cheat[pc++]);
|
||||||
|
write8(baseAddr + *activeOffset, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
#define MAKE_IF_INSTRUCTION(opcode, comparator) \
|
||||||
|
case opcode: { \
|
||||||
|
const u32 baseAddr = Helpers::getBits<0, 28>(instruction); \
|
||||||
|
const u32 imm = cheat[pc++]; \
|
||||||
|
const u32 value = read32(baseAddr + *activeOffset); \
|
||||||
|
\
|
||||||
|
pushConditionBlock(imm comparator value); \
|
||||||
|
break; \
|
||||||
|
}
|
||||||
|
|
||||||
|
// Greater Than (YYYYYYYY > [XXXXXXX + offset]) (Unsigned)
|
||||||
|
MAKE_IF_INSTRUCTION(3, >)
|
||||||
|
|
||||||
|
// Less Than (YYYYYYYY < [XXXXXXX + offset]) (Unsigned)
|
||||||
|
MAKE_IF_INSTRUCTION(4, <)
|
||||||
|
|
||||||
|
// Equal to (YYYYYYYY == [XXXXXXX + offset])
|
||||||
|
MAKE_IF_INSTRUCTION(5, ==)
|
||||||
|
|
||||||
|
// Not Equal (YYYYYYYY != [XXXXXXX + offset])
|
||||||
|
MAKE_IF_INSTRUCTION(6, !=)
|
||||||
|
#undef MAKE_IF_INSTRUCTION
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
// BXXXXXXX 00000000 - offset = *(XXXXXXX + offset)
|
||||||
|
case 0xB: {
|
||||||
|
const u32 baseAddr = Helpers::getBits<0, 28>(instruction);
|
||||||
|
*activeOffset = read32(baseAddr + *activeOffset);
|
||||||
|
|
||||||
|
pc++; // Eat up dummy word
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 0xD: executeDType(cheat, instruction); break;
|
||||||
|
default: Helpers::panic("Unimplemented ActionReplay instruction type %X", type); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActionReplay::executeDType(const Cheat& cheat, u32 instruction) {
|
||||||
|
switch (instruction) {
|
||||||
|
case 0xD3000000: offset1 = cheat[pc++]; break;
|
||||||
|
case 0xD3000001: offset2 = cheat[pc++]; break;
|
||||||
|
case 0xDC000000: *activeOffset += cheat[pc++]; break;
|
||||||
|
|
||||||
|
// DD000000 XXXXXXXX - if KEYPAD has value XXXXXXXX execute next block
|
||||||
|
case 0xDD000000: {
|
||||||
|
const u32 mask = cheat[pc++];
|
||||||
|
const u32 buttons = hid.getOldButtons();
|
||||||
|
|
||||||
|
pushConditionBlock((buttons & mask) == mask);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset register ops
|
||||||
|
case 0xDF000000: {
|
||||||
|
const u32 subopcode = cheat[pc++];
|
||||||
|
switch (subopcode) {
|
||||||
|
case 0x00000000: activeOffset = &offset1; break;
|
||||||
|
case 0x00000001: activeOffset = &offset2; break;
|
||||||
|
case 0x00010000: offset2 = offset1; break;
|
||||||
|
case 0x00010001: offset1 = offset2; break;
|
||||||
|
case 0x00020000: data1 = offset1; break;
|
||||||
|
case 0x00020001: data2 = offset2; break;
|
||||||
|
default:
|
||||||
|
Helpers::warn("Unknown ActionReplay offset operation");
|
||||||
|
running = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data register operations
|
||||||
|
case 0xDF000001: {
|
||||||
|
const u32 subopcode = cheat[pc++];
|
||||||
|
switch (subopcode) {
|
||||||
|
case 0x00000000: activeData = &data1; break;
|
||||||
|
case 0x00000001: activeData = &data2; break;
|
||||||
|
|
||||||
|
case 0x00010000: data2 = data1; break;
|
||||||
|
case 0x00010001: data1 = data2; break;
|
||||||
|
case 0x00020000: offset1 = data1; break;
|
||||||
|
case 0x00020001: offset2 = data2; break;
|
||||||
|
default:
|
||||||
|
Helpers::warn("Unknown ActionReplay data operation");
|
||||||
|
running = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storage register operations
|
||||||
|
case 0xDF000002: {
|
||||||
|
const u32 subopcode = cheat[pc++];
|
||||||
|
switch (subopcode) {
|
||||||
|
case 0x00000000: activeStorage = &storage1; break;
|
||||||
|
case 0x00000001: activeStorage = &storage2; break;
|
||||||
|
|
||||||
|
case 0x00010000: data1 = storage1; break;
|
||||||
|
case 0x00010001: data2 = storage2; break;
|
||||||
|
case 0x00020000: storage1 = data1; break;
|
||||||
|
case 0x00020001: storage2 = data2; break;
|
||||||
|
default:
|
||||||
|
Helpers::warn("Unknown ActionReplay data operation: %08X", subopcode);
|
||||||
|
running = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Control flow block operations
|
||||||
|
case 0xD2000000: {
|
||||||
|
const u32 subopcode = cheat[pc++];
|
||||||
|
switch (subopcode) {
|
||||||
|
// Ends all loop/execute blocks
|
||||||
|
case 0:
|
||||||
|
loopStackIndex = 0;
|
||||||
|
ifStackIndex = 0;
|
||||||
|
break;
|
||||||
|
default: Helpers::panic("Unknown ActionReplay control flow operation: %08X", subopcode); break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: Helpers::panic("ActionReplay: Unimplemented d-type opcode: %08X", instruction); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActionReplay::pushConditionBlock(bool condition) {
|
||||||
|
if (ifStackIndex >= 32) {
|
||||||
|
Helpers::warn("ActionReplay if stack overflowed");
|
||||||
|
running = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ifStack[ifStackIndex++] = condition;
|
||||||
|
}
|
31
src/core/cheats.cpp
Normal file
31
src/core/cheats.cpp
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#include "cheats.hpp"
|
||||||
|
|
||||||
|
Cheats::Cheats(Memory& mem, HIDService& hid) : ar(mem, hid) { reset(); }
|
||||||
|
|
||||||
|
void Cheats::reset() {
|
||||||
|
clear(); // Clear loaded cheats
|
||||||
|
ar.reset(); // Reset ActionReplay
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cheats::addCheat(const Cheat& cheat) {
|
||||||
|
cheats.push_back(cheat);
|
||||||
|
cheatsLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cheats::clear() {
|
||||||
|
cheats.clear();
|
||||||
|
cheatsLoaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cheats::run() {
|
||||||
|
for (const Cheat& cheat : cheats) {
|
||||||
|
switch (cheat.type) {
|
||||||
|
case CheatType::ActionReplay: {
|
||||||
|
ar.runCheat(cheat.instructions);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: Helpers::panic("Unknown cheat device!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,9 +20,11 @@ HorizonResult ExtSaveDataArchive::createFile(const FSPath& path, u64 size) {
|
||||||
// Create a file of size "size" by creating an empty one, seeking to size - 1 and just writing a 0 there
|
// 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");
|
IOFile file(p.string().c_str(), "wb");
|
||||||
if (file.seek(size - 1, SEEK_SET) && file.writeBytes("", 1).second == 1) {
|
if (file.seek(size - 1, SEEK_SET) && file.writeBytes("", 1).second == 1) {
|
||||||
|
file.close();
|
||||||
return Result::Success;
|
return Result::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
file.close();
|
||||||
return Result::FS::FileTooLarge;
|
return Result::FS::FileTooLarge;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,14 +19,17 @@ HorizonResult SaveDataArchive::createFile(const FSPath& path, u64 size) {
|
||||||
|
|
||||||
// If the size is 0, leave the file empty and return success
|
// If the size is 0, leave the file empty and return success
|
||||||
if (size == 0) {
|
if (size == 0) {
|
||||||
|
file.close();
|
||||||
return Result::Success;
|
return Result::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it is not empty, seek to size - 1 and write a 0 to create a file of size "size"
|
// If it is not empty, seek to size - 1 and write a 0 to create a file of size "size"
|
||||||
else if (file.seek(size - 1, SEEK_SET) && file.writeBytes("", 1).second == 1) {
|
else if (file.seek(size - 1, SEEK_SET) && file.writeBytes("", 1).second == 1) {
|
||||||
|
file.close();
|
||||||
return Result::Success;
|
return Result::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
file.close();
|
||||||
return Result::FS::FileTooLarge;
|
return Result::FS::FileTooLarge;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,6 +159,8 @@ Rust::Result<ArchiveBase::FormatInfo, HorizonResult> SaveDataArchive::getFormatI
|
||||||
|
|
||||||
FormatInfo ret;
|
FormatInfo ret;
|
||||||
auto [success, bytesRead] = file.readBytes(&ret, sizeof(FormatInfo));
|
auto [success, bytesRead] = file.readBytes(&ret, sizeof(FormatInfo));
|
||||||
|
file.close();
|
||||||
|
|
||||||
if (!success || bytesRead != sizeof(FormatInfo)) {
|
if (!success || bytesRead != sizeof(FormatInfo)) {
|
||||||
Helpers::warn("SaveData::GetFormatInfo: Format file exists but was not properly read into the FormatInfo struct");
|
Helpers::warn("SaveData::GetFormatInfo: Format file exists but was not properly read into the FormatInfo struct");
|
||||||
return Err(Result::FS::NotFormatted);
|
return Err(Result::FS::NotFormatted);
|
||||||
|
@ -175,6 +180,8 @@ void SaveDataArchive::format(const FSPath& path, const ArchiveBase::FormatInfo&
|
||||||
// Write format info on disk
|
// Write format info on disk
|
||||||
IOFile file(formatInfoPath, "wb");
|
IOFile file(formatInfoPath, "wb");
|
||||||
file.writeBytes(&info, sizeof(info));
|
file.writeBytes(&info, sizeof(info));
|
||||||
|
file.flush();
|
||||||
|
file.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
Rust::Result<ArchiveBase*, HorizonResult> SaveDataArchive::openArchive(const FSPath& path) {
|
Rust::Result<ArchiveBase*, HorizonResult> SaveDataArchive::openArchive(const FSPath& path) {
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
namespace PathType {
|
namespace PathType {
|
||||||
enum : u32 {
|
enum : u32 {
|
||||||
RomFS = 0,
|
RomFS = 0,
|
||||||
ExeFS = 2
|
ExeFS = 2,
|
||||||
|
UpdateRomFS = 5,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -33,8 +34,8 @@ FileDescriptor SelfNCCHArchive::openFile(const FSPath& path, const FilePerms& pe
|
||||||
// Where to read the file from. (https://www.3dbrew.org/wiki/Filesystem_services#SelfNCCH_File_Path_Data_Format)
|
// 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
|
// 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
|
const u32 type = *(u32*)&path.binary[0]; // TODO: Get rid of UB here
|
||||||
if (type != PathType::RomFS && type != PathType::ExeFS) {
|
if (type != PathType::RomFS && type != PathType::ExeFS && type != PathType::UpdateRomFS) {
|
||||||
Helpers::panic("Read from NCCH's non-RomFS & non-exeFS section!");
|
Helpers::panic("Read from NCCH's non-RomFS & non-exeFS section! Path type: %d", type);
|
||||||
}
|
}
|
||||||
|
|
||||||
return NoFile; // No file descriptor needed for RomFS
|
return NoFile; // No file descriptor needed for RomFS
|
||||||
|
@ -50,8 +51,8 @@ Rust::Result<ArchiveBase*, HorizonResult> SelfNCCHArchive::openArchive(const FSP
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<u32> SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) {
|
std::optional<u32> SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) {
|
||||||
const FSPath& path = file->path; // Path of the file
|
const FSPath& path = file->path; // Path of the file
|
||||||
const u32 type = *(u32*)&path.binary[0]; // Type of the path
|
const u32 type = *(u32*)&path.binary[0]; // Type of the path
|
||||||
|
|
||||||
if (type == PathType::RomFS && !hasRomFS()) {
|
if (type == PathType::RomFS && !hasRomFS()) {
|
||||||
Helpers::panic("Tried to read file from non-existent RomFS");
|
Helpers::panic("Tried to read file from non-existent RomFS");
|
||||||
|
@ -98,8 +99,23 @@ std::optional<u32> SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
// Normally, the update RomFS should overlay the cartridge RomFS when reading from this and an update is installed.
|
||||||
Helpers::panic("Unimplemented file path type for SelfNCCH archive");
|
// So to support updates, we need to perform this overlaying. For now, read from the cartridge RomFS.
|
||||||
|
case PathType::UpdateRomFS: {
|
||||||
|
Helpers::warn("Reading from update RomFS but updates are currently not supported! Reading from regular RomFS instead\n");
|
||||||
|
|
||||||
|
const u64 romFSSize = cxi->romFS.size;
|
||||||
|
const u64 romFSOffset = cxi->romFS.offset;
|
||||||
|
if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) {
|
||||||
|
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
|
||||||
|
}
|
||||||
|
|
||||||
|
fsInfo = cxi->romFS;
|
||||||
|
offset += 0x1000;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: Helpers::panic("Unimplemented file path type for SelfNCCH archive");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<u8[]> data(new u8[size]);
|
std::unique_ptr<u8[]> data(new u8[size]);
|
||||||
|
|
76
src/core/fs/ivfc.cpp
Normal file
76
src/core/fs/ivfc.cpp
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
#include "fs/ivfc.hpp"
|
||||||
|
|
||||||
|
namespace IVFC {
|
||||||
|
size_t parseIVFC(uintptr_t ivfcStart, IVFC& ivfc) {
|
||||||
|
uintptr_t ivfcPointer = ivfcStart;
|
||||||
|
|
||||||
|
char* ivfcCharPtr = (char*)ivfcPointer;
|
||||||
|
if (ivfcCharPtr[0] != 'I' || ivfcCharPtr[1] != 'V' || ivfcCharPtr[2] != 'F' || ivfcCharPtr[3] != 'C') {
|
||||||
|
printf("Invalid header on IVFC\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
ivfcPointer += 4;
|
||||||
|
|
||||||
|
u32 magicIdentifier = *(u32*)ivfcPointer;
|
||||||
|
ivfcPointer += 4;
|
||||||
|
|
||||||
|
// RomFS IVFC uses 0x10000, DISA/DIFF IVFC uses 0x20000 here
|
||||||
|
if (magicIdentifier != 0x10000 && magicIdentifier != 0x20000) {
|
||||||
|
printf("Invalid IVFC magic identifier: %08X\n", magicIdentifier);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (magicIdentifier == 0x10000) {
|
||||||
|
ivfc.masterHashSize = *(u32*)ivfcPointer;
|
||||||
|
ivfcPointer += 4;
|
||||||
|
// RomFS IVFC uses 3 levels
|
||||||
|
ivfc.levels.resize(3);
|
||||||
|
} else {
|
||||||
|
ivfc.masterHashSize = *(u64*)ivfcPointer;
|
||||||
|
ivfcPointer += 8;
|
||||||
|
// DISA/DIFF IVFC uses 4 levels
|
||||||
|
ivfc.levels.resize(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ivfc.levels.size(); i++) {
|
||||||
|
IVFCLevel level;
|
||||||
|
|
||||||
|
level.logicalOffset = *(u64*)ivfcPointer;
|
||||||
|
ivfcPointer += 8;
|
||||||
|
|
||||||
|
level.size = *(u64*)ivfcPointer;
|
||||||
|
ivfcPointer += 8;
|
||||||
|
|
||||||
|
// This field is in log2
|
||||||
|
level.blockSize = 1 << *(u32*)ivfcPointer;
|
||||||
|
ivfcPointer += 4;
|
||||||
|
|
||||||
|
// Skip 4 reserved bytes
|
||||||
|
ivfcPointer += 4;
|
||||||
|
|
||||||
|
ivfc.levels[i] = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 ivfcDescriptorSize = *(u64*)ivfcPointer;
|
||||||
|
ivfcPointer += 8;
|
||||||
|
|
||||||
|
uintptr_t ivfcActualSize = ivfcPointer - ivfcStart;
|
||||||
|
|
||||||
|
// According to 3DBrew, this is usually the case but not guaranteed
|
||||||
|
if (ivfcActualSize != ivfcDescriptorSize) {
|
||||||
|
printf("IVFC descriptor size mismatch: %llx != %llx\n", ivfcActualSize, ivfcDescriptorSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (magicIdentifier == 0x10000 && ivfcActualSize != 0x5C) {
|
||||||
|
// This is always 0x5C bytes long
|
||||||
|
printf("Invalid IVFC size: %08x\n", (u32)ivfcActualSize);
|
||||||
|
return 0;
|
||||||
|
} else if (magicIdentifier == 0x20000 && ivfcActualSize != 0x78) {
|
||||||
|
// This is always 0x78 bytes long
|
||||||
|
printf("Invalid IVFC size: %08x\n", (u32)ivfcActualSize);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ivfcActualSize;
|
||||||
|
}
|
||||||
|
} // namespace IVFC
|
194
src/core/fs/romfs.cpp
Normal file
194
src/core/fs/romfs.cpp
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
#include "fs/romfs.hpp"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <queue>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "fs/ivfc.hpp"
|
||||||
|
#include "helpers.hpp"
|
||||||
|
|
||||||
|
namespace RomFS {
|
||||||
|
constexpr u32 metadataInvalidEntry = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
struct Level3Header {
|
||||||
|
u32 headerSize;
|
||||||
|
u32 directoryHashTableOffset;
|
||||||
|
u32 directoryHashTableSize;
|
||||||
|
u32 directoryMetadataOffset;
|
||||||
|
u32 directoryMetadataSize;
|
||||||
|
u32 fileHashTableOffset;
|
||||||
|
u32 fileHashTableSize;
|
||||||
|
u32 fileMetadataOffset;
|
||||||
|
u32 fileMetadataSize;
|
||||||
|
u32 fileDataOffset;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline constexpr uintptr_t alignUp(uintptr_t value, uintptr_t alignment) {
|
||||||
|
if (value % alignment == 0) return value;
|
||||||
|
|
||||||
|
return value + (alignment - (value % alignment));
|
||||||
|
}
|
||||||
|
|
||||||
|
void printNode(const RomFSNode& node, int indentation) {
|
||||||
|
for (int i = 0; i < indentation; i++) {
|
||||||
|
printf(" ");
|
||||||
|
}
|
||||||
|
printf("%s/\n", std::string(node.name.begin(), node.name.end()).c_str());
|
||||||
|
|
||||||
|
for (auto& file : node.files) {
|
||||||
|
for (int i = 0; i <= indentation; i++) {
|
||||||
|
printf(" ");
|
||||||
|
}
|
||||||
|
printf("%s\n", std::string(file->name.begin(), file->name.end()).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
indentation++;
|
||||||
|
for (auto& directory : node.directories) {
|
||||||
|
printNode(*directory, indentation);
|
||||||
|
}
|
||||||
|
indentation--;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<RomFSNode>> getFiles(uintptr_t fileMetadataBase, u32 currentFileOffset) {
|
||||||
|
std::vector<std::unique_ptr<RomFSNode>> files;
|
||||||
|
|
||||||
|
while (currentFileOffset != metadataInvalidEntry) {
|
||||||
|
u32* metadataPtr = (u32*)(fileMetadataBase + currentFileOffset);
|
||||||
|
metadataPtr++; // Skip the containing directory
|
||||||
|
u32 nextFileOffset = *metadataPtr++;
|
||||||
|
u64 fileDataOffset = *(u64*)metadataPtr;
|
||||||
|
metadataPtr += 2;
|
||||||
|
u64 fileSize = *(u64*)metadataPtr;
|
||||||
|
metadataPtr += 2;
|
||||||
|
metadataPtr++; // Skip the offset of the next file in the same hash table bucket
|
||||||
|
u32 nameLength = *metadataPtr++ / 2;
|
||||||
|
|
||||||
|
// Arbitrary limit
|
||||||
|
if (nameLength > 128) {
|
||||||
|
printf("Invalid file name length: %08X\n", nameLength);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
char16_t* namePtr = (char16_t*)metadataPtr;
|
||||||
|
std::u16string name(namePtr, nameLength);
|
||||||
|
|
||||||
|
std::unique_ptr file = std::make_unique<RomFSNode>();
|
||||||
|
file->isDirectory = false;
|
||||||
|
file->name = name;
|
||||||
|
file->metadataOffset = currentFileOffset;
|
||||||
|
file->dataOffset = fileDataOffset;
|
||||||
|
file->dataSize = fileSize;
|
||||||
|
|
||||||
|
files.push_back(std::move(file));
|
||||||
|
|
||||||
|
currentFileOffset = nextFileOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<RomFSNode> parseRootDirectory(uintptr_t directoryMetadataBase, uintptr_t fileMetadataBase) {
|
||||||
|
std::unique_ptr<RomFSNode> rootDirectory = std::make_unique<RomFSNode>();
|
||||||
|
rootDirectory->isDirectory = true;
|
||||||
|
rootDirectory->name = u"romfs:";
|
||||||
|
rootDirectory->metadataOffset = 0;
|
||||||
|
|
||||||
|
u32 rootFilesOffset = *((u32*)(directoryMetadataBase) + 3);
|
||||||
|
if (rootFilesOffset != metadataInvalidEntry) {
|
||||||
|
rootDirectory->files = getFiles(fileMetadataBase, rootFilesOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::queue<RomFSNode*> directoryOffsets;
|
||||||
|
directoryOffsets.push(rootDirectory.get());
|
||||||
|
|
||||||
|
while (!directoryOffsets.empty()) {
|
||||||
|
RomFSNode* currentNode = directoryOffsets.front();
|
||||||
|
directoryOffsets.pop();
|
||||||
|
|
||||||
|
u32* metadataPtr = (u32*)(directoryMetadataBase + currentNode->metadataOffset);
|
||||||
|
metadataPtr += 2;
|
||||||
|
|
||||||
|
// Offset of first child directory
|
||||||
|
u32 currentDirectoryOffset = *metadataPtr;
|
||||||
|
|
||||||
|
// Loop over all the sibling directories of the first child to get all the children directories
|
||||||
|
// of the current directory
|
||||||
|
while (currentDirectoryOffset != metadataInvalidEntry) {
|
||||||
|
metadataPtr = (u32*)(directoryMetadataBase + currentDirectoryOffset);
|
||||||
|
metadataPtr++; // Skip the parent offset
|
||||||
|
u32 siblingDirectoryOffset = *metadataPtr++;
|
||||||
|
metadataPtr++; // Skip offset of first child directory
|
||||||
|
u32 currentFileOffset = *metadataPtr++;
|
||||||
|
metadataPtr++; // Skip offset of next directory in the same hash table bucket
|
||||||
|
u32 nameLength = *metadataPtr++ / 2;
|
||||||
|
|
||||||
|
// Arbitrary limit
|
||||||
|
if (nameLength > 128) {
|
||||||
|
printf("Invalid directory name length: %08X\n", nameLength);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
char16_t* namePtr = (char16_t*)metadataPtr;
|
||||||
|
std::u16string name(namePtr, nameLength);
|
||||||
|
|
||||||
|
std::unique_ptr directory = std::make_unique<RomFSNode>();
|
||||||
|
directory->isDirectory = true;
|
||||||
|
directory->name = name;
|
||||||
|
directory->metadataOffset = currentDirectoryOffset;
|
||||||
|
directory->files = getFiles(fileMetadataBase, currentFileOffset);
|
||||||
|
|
||||||
|
currentNode->directories.push_back(std::move(directory));
|
||||||
|
currentDirectoryOffset = siblingDirectoryOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& directory : currentNode->directories) {
|
||||||
|
directoryOffsets.push(directory.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<RomFSNode> parseRomFSTree(uintptr_t romFS, u64 romFSSize) {
|
||||||
|
IVFC::IVFC ivfc;
|
||||||
|
size_t ivfcSize = IVFC::parseIVFC((uintptr_t)romFS, ivfc);
|
||||||
|
|
||||||
|
if (ivfcSize == 0) {
|
||||||
|
printf("Failed to parse IVFC\n");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
uintptr_t masterHashOffset = RomFS::alignUp(ivfcSize, 0x10);
|
||||||
|
// From GBATEK:
|
||||||
|
// The "Logical Offsets" are completely unrelated to the physical offsets in the RomFS partition.
|
||||||
|
// Instead, the "Logical Offsets" might be something about where to map the Level 1-3 sections in
|
||||||
|
// virtual memory (with the physical Level 3,1,2 ordering being re-ordered to Level 1,2,3)?
|
||||||
|
uintptr_t level3Offset = RomFS::alignUp(masterHashOffset + ivfc.masterHashSize, ivfc.levels[2].blockSize);
|
||||||
|
uintptr_t level3Base = (uintptr_t)romFS + level3Offset;
|
||||||
|
u32* level3Ptr = (u32*)level3Base;
|
||||||
|
|
||||||
|
Level3Header header;
|
||||||
|
header.headerSize = *level3Ptr++;
|
||||||
|
header.directoryHashTableOffset = *level3Ptr++;
|
||||||
|
header.directoryHashTableSize = *level3Ptr++;
|
||||||
|
header.directoryMetadataOffset = *level3Ptr++;
|
||||||
|
header.directoryMetadataSize = *level3Ptr++;
|
||||||
|
header.fileHashTableOffset = *level3Ptr++;
|
||||||
|
header.fileHashTableSize = *level3Ptr++;
|
||||||
|
header.fileMetadataOffset = *level3Ptr++;
|
||||||
|
header.fileMetadataSize = *level3Ptr++;
|
||||||
|
header.fileDataOffset = *level3Ptr;
|
||||||
|
|
||||||
|
if (header.headerSize != 0x28) {
|
||||||
|
printf("Invalid level 3 header size: %08X\n", header.headerSize);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<RomFSNode> root = parseRootDirectory(level3Base + header.directoryMetadataOffset, level3Base + header.fileMetadataOffset);
|
||||||
|
|
||||||
|
// If you want to print the tree, uncomment this
|
||||||
|
// printNode(*root, 0);
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
} // namespace RomFS
|
|
@ -87,7 +87,7 @@ void Kernel::arbitrateAddress() {
|
||||||
Helpers::panic("ArbitrateAddress: Unimplemented type %s", arbitrationTypeToString(type));
|
Helpers::panic("ArbitrateAddress: Unimplemented type %s", arbitrationTypeToString(type));
|
||||||
}
|
}
|
||||||
|
|
||||||
rescheduleThreads();
|
requireReschedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signal up to "threadCount" threads waiting on the arbiter indicated by "waitingAddress"
|
// Signal up to "threadCount" threads waiting on the arbiter indicated by "waitingAddress"
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
#include <array>
|
||||||
|
#include <cctype>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "ipc.hpp"
|
||||||
#include "kernel.hpp"
|
#include "kernel.hpp"
|
||||||
|
|
||||||
namespace DirectoryOps {
|
namespace DirectoryOps {
|
||||||
|
@ -7,6 +14,79 @@ namespace DirectoryOps {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper to convert std::string to an 8.3 filename to mimic how Directory::Read works
|
||||||
|
using ShortFilename = std::array<char, 9>;
|
||||||
|
using ShortExtension = std::array<char, 4>;
|
||||||
|
using Filename83 = std::pair<ShortFilename, ShortExtension>;
|
||||||
|
|
||||||
|
// The input string should be the stem and extension together, not separately
|
||||||
|
// Eg something like "boop.png", "panda.txt", etc
|
||||||
|
Filename83 convertTo83(const std::string& path) {
|
||||||
|
ShortFilename filename;
|
||||||
|
ShortExtension extension;
|
||||||
|
|
||||||
|
// Convert a character to add it to the 8.3 name
|
||||||
|
// "Characters such as + are changed to the underscore _, and letters are put in uppercase"
|
||||||
|
// For now we put letters in uppercase until we find out what is supposed to be converted to _ and so on
|
||||||
|
auto convertCharacter = [](char c) { return (char) std::toupper(c); };
|
||||||
|
|
||||||
|
// List of forbidden character for 8.3 filenames, from Citra
|
||||||
|
// TODO: Use constexpr when C++20 support is solid
|
||||||
|
const std::string forbiddenChars = ".\"/\\[]:;=, ";
|
||||||
|
|
||||||
|
// By default space-initialize the whole name, append null terminator in the end for both the filename and extension
|
||||||
|
filename.fill(' ');
|
||||||
|
extension.fill(' ');
|
||||||
|
filename[filename.size() - 1] = '\0';
|
||||||
|
extension[extension.size() - 1] = '\0';
|
||||||
|
|
||||||
|
// Find the position of the dot in the string
|
||||||
|
auto dotPos = path.rfind('.');
|
||||||
|
// Wikipedia: If a file name has no extension, a trailing . has no effect
|
||||||
|
// Thus check if the last character is a dot and ignore it, prefering the previous dot if it exists
|
||||||
|
if (dotPos == path.size() - 1) {
|
||||||
|
dotPos = path.rfind('.', dotPos); // Get previous dot
|
||||||
|
}
|
||||||
|
|
||||||
|
// If pointPos is not npos we have a valid dot character, and as such an extension
|
||||||
|
bool haveExtension = dotPos != std::string::npos;
|
||||||
|
int validCharacterCount = 0;
|
||||||
|
bool filenameTooBig = false;
|
||||||
|
|
||||||
|
// Parse characters until we're done OR until we reach 9 characters, in which case according to Wikipedia we must truncate to 6 letters
|
||||||
|
// And append ~1 in the end
|
||||||
|
for (auto c : path.substr(0, dotPos)) {
|
||||||
|
// Character is forbidden, we must ignore it
|
||||||
|
if (forbiddenChars.find(c) != std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We already have capped the amount of characters, thus our filename is too big
|
||||||
|
if (validCharacterCount == 8) {
|
||||||
|
filenameTooBig = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
filename[validCharacterCount++] = convertCharacter(c); // Append character to filename
|
||||||
|
}
|
||||||
|
|
||||||
|
// Truncate name to 6 characters and denote that it is too big
|
||||||
|
// TODO: Wikipedia says we should also do this if the filename contains an invalid character, including spaces. Must test
|
||||||
|
if (filenameTooBig) {
|
||||||
|
filename[6] = '~';
|
||||||
|
filename[7] = '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (haveExtension) {
|
||||||
|
int extensionLen = 0;
|
||||||
|
// Copy up to 3 characters from the dot onwards to the extension
|
||||||
|
for (auto c : path.substr(dotPos + 1, 3)) {
|
||||||
|
extension[extensionLen++] = convertCharacter(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {filename, extension};
|
||||||
|
}
|
||||||
|
|
||||||
void Kernel::handleDirectoryOperation(u32 messagePointer, Handle directory) {
|
void Kernel::handleDirectoryOperation(u32 messagePointer, Handle directory) {
|
||||||
const u32 cmd = mem.read32(messagePointer);
|
const u32 cmd = mem.read32(messagePointer);
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
|
@ -25,16 +105,77 @@ void Kernel::closeDirectory(u32 messagePointer, Handle directory) {
|
||||||
}
|
}
|
||||||
|
|
||||||
p->getData<DirectorySession>()->isOpen = false;
|
p->getData<DirectorySession>()->isOpen = false;
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x802, 1, 0));
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Kernel::readDirectory(u32 messagePointer, Handle directory) {
|
void Kernel::readDirectory(u32 messagePointer, Handle directory) {
|
||||||
const u32 entryCount = mem.read32(messagePointer + 4);
|
const u32 entryCount = mem.read32(messagePointer + 4);
|
||||||
const u32 outPointer = mem.read32(messagePointer + 12);
|
const u32 outPointer = mem.read32(messagePointer + 12);
|
||||||
logFileIO("Directory::Read (handle = %X, entry count = %d, out pointer = %08X)\n", directory, entryCount, outPointer);
|
logFileIO("Directory::Read (handle = %X, entry count = %d, out pointer = %08X)\n", directory, entryCount, outPointer);
|
||||||
Helpers::panicDev("Unimplemented FsDir::Read");
|
|
||||||
|
const auto p = getObject(directory, KernelObjectType::Directory);
|
||||||
|
if (p == nullptr) [[unlikely]] {
|
||||||
|
Helpers::panic("Called ReadDirectory on non-existent directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectorySession* session = p->getData<DirectorySession>();
|
||||||
|
if (!session->pathOnDisk.has_value()) [[unlikely]] {
|
||||||
|
Helpers::panic("Called ReadDirectory on directory that doesn't have a path on disk");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path dirPath = session->pathOnDisk.value();
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
while (count < entryCount && session->currentEntry < session->entries.size()) {
|
||||||
|
const auto& entry = session->entries[session->currentEntry];
|
||||||
|
std::filesystem::path path = entry.path;
|
||||||
|
std::filesystem::path filename = path.filename();
|
||||||
|
|
||||||
|
std::filesystem::path relative = path.lexically_relative(dirPath);
|
||||||
|
bool isDirectory = std::filesystem::is_directory(relative);
|
||||||
|
|
||||||
|
std::u16string nameU16 = relative.u16string();
|
||||||
|
bool isHidden = nameU16[0] == u'.'; // If the first character is a dot then this is a hidden file/folder
|
||||||
|
|
||||||
|
const u32 entryPointer = outPointer + (count * 0x228); // 0x228 is the size of a single entry
|
||||||
|
u32 utfPointer = entryPointer;
|
||||||
|
u32 namePointer = entryPointer + 0x20C;
|
||||||
|
u32 extensionPointer = entryPointer + 0x216;
|
||||||
|
u32 attributePointer = entryPointer + 0x21C;
|
||||||
|
u32 sizePointer = entryPointer + 0x220;
|
||||||
|
|
||||||
|
std::string filenameString = filename.string();
|
||||||
|
auto [shortFilename, shortExtension] = convertTo83(filenameString);
|
||||||
|
|
||||||
|
for (auto c : nameU16) {
|
||||||
|
mem.write16(utfPointer, u16(c));
|
||||||
|
utfPointer += sizeof(u16);
|
||||||
|
}
|
||||||
|
mem.write16(utfPointer, 0); // Null terminate the UTF16 name
|
||||||
|
|
||||||
|
// Write 8.3 filename-extension
|
||||||
|
for (auto c : shortFilename) {
|
||||||
|
mem.write8(namePointer, u8(c));
|
||||||
|
namePointer += sizeof(u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto c : shortExtension) {
|
||||||
|
mem.write8(extensionPointer, u8(c));
|
||||||
|
extensionPointer += sizeof(u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
mem.write8(outPointer + 0x21A, 1); // Always 1 according to 3DBrew
|
||||||
|
mem.write8(attributePointer, entry.isDirectory ? 1 : 0); // "Is directory" attribute
|
||||||
|
mem.write8(attributePointer + 1, isHidden ? 1 : 0); // "Is hidden" attribute
|
||||||
|
mem.write8(attributePointer + 2, entry.isDirectory ? 0 : 1); // "Is archive" attribute
|
||||||
|
mem.write8(attributePointer + 3, 0); // "Is read-only" attribute
|
||||||
|
|
||||||
|
count++; // Increment number of read directories
|
||||||
|
session->currentEntry++; // Increment index of the entry currently being read
|
||||||
|
}
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x801, 2, 2));
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
mem.write32(messagePointer + 8, 0);
|
mem.write32(messagePointer + 8, count);
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,22 +35,15 @@ bool Kernel::signalEvent(Handle handle) {
|
||||||
|
|
||||||
// Check if there's any thread waiting on this event
|
// Check if there's any thread waiting on this event
|
||||||
if (event->waitlist != 0) {
|
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
|
wakeupAllThreads(event->waitlist, handle);
|
||||||
|
event->waitlist = 0; // No threads waiting;
|
||||||
|
|
||||||
if (event->resetType == ResetType::OneShot) {
|
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;
|
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rescheduleThreads();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,7 +114,6 @@ void Kernel::waitSynchronization1() {
|
||||||
if (!shouldWaitOnObject(object)) {
|
if (!shouldWaitOnObject(object)) {
|
||||||
acquireSyncObject(object, threads[currentThreadIndex]); // Acquire the object since it's ready
|
acquireSyncObject(object, threads[currentThreadIndex]); // Acquire the object since it's ready
|
||||||
regs[0] = Result::Success;
|
regs[0] = Result::Success;
|
||||||
rescheduleThreads();
|
|
||||||
} else {
|
} else {
|
||||||
// Timeout is 0, don't bother waiting, instantly timeout
|
// Timeout is 0, don't bother waiting, instantly timeout
|
||||||
if (ns == 0) {
|
if (ns == 0) {
|
||||||
|
@ -141,7 +133,7 @@ void Kernel::waitSynchronization1() {
|
||||||
// Add the current thread to the object's wait list
|
// Add the current thread to the object's wait list
|
||||||
object->getWaitlist() |= (1ull << currentThreadIndex);
|
object->getWaitlist() |= (1ull << currentThreadIndex);
|
||||||
|
|
||||||
switchToNextThread();
|
requireReschedule();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,14 +196,13 @@ void Kernel::waitSynchronizationN() {
|
||||||
|
|
||||||
auto& t = threads[currentThreadIndex];
|
auto& t = threads[currentThreadIndex];
|
||||||
|
|
||||||
// We only need to wait on one object. Easy...?!
|
// We only need to wait on one object. Easy.
|
||||||
if (!waitAll) {
|
if (!waitAll) {
|
||||||
// If there's ready objects, acquire the first one and return
|
// If there's ready objects, acquire the first one and return
|
||||||
if (oneObjectReady) {
|
if (oneObjectReady) {
|
||||||
regs[0] = Result::Success;
|
regs[0] = Result::Success;
|
||||||
regs[1] = firstReadyObjectIndex; // Return index of the acquired object
|
regs[1] = firstReadyObjectIndex; // Return index of the acquired object
|
||||||
acquireSyncObject(waitObjects[firstReadyObjectIndex].second, t); // Acquire object
|
acquireSyncObject(waitObjects[firstReadyObjectIndex].second, t); // Acquire object
|
||||||
rescheduleThreads();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,8 +220,8 @@ void Kernel::waitSynchronizationN() {
|
||||||
waitObjects[i].second->getWaitlist() |= (1ull << currentThreadIndex); // And add the thread to the object's waitlist
|
waitObjects[i].second->getWaitlist() |= (1ull << currentThreadIndex); // And add the thread to the object's waitlist
|
||||||
}
|
}
|
||||||
|
|
||||||
switchToNextThread();
|
requireReschedule();
|
||||||
} else {
|
} else {
|
||||||
Helpers::panic("WaitSynchronizatioN with waitAll");
|
Helpers::panic("WaitSynchronizationN with waitAll");
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,9 +3,10 @@
|
||||||
#include "kernel_types.hpp"
|
#include "kernel_types.hpp"
|
||||||
#include "cpu.hpp"
|
#include "cpu.hpp"
|
||||||
|
|
||||||
Kernel::Kernel(CPU& cpu, Memory& mem, GPU& gpu)
|
Kernel::Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config)
|
||||||
: cpu(cpu), regs(cpu.regs()), mem(mem), handleCounter(0), serviceManager(regs, mem, gpu, currentProcess, *this) {
|
: cpu(cpu), regs(cpu.regs()), mem(mem), handleCounter(0), serviceManager(regs, mem, gpu, currentProcess, *this, config) {
|
||||||
objects.reserve(512); // Make room for a few objects to avoid further memory allocs later
|
objects.reserve(512); // Make room for a few objects to avoid further memory allocs later
|
||||||
|
mutexHandles.reserve(8);
|
||||||
portHandles.reserve(32);
|
portHandles.reserve(32);
|
||||||
threadIndices.reserve(appResourceLimits.maxThreads);
|
threadIndices.reserve(appResourceLimits.maxThreads);
|
||||||
|
|
||||||
|
@ -34,6 +35,8 @@ void Kernel::serviceSVC(u32 svc) {
|
||||||
case 0x0A: svcSleepThread(); break;
|
case 0x0A: svcSleepThread(); break;
|
||||||
case 0x0B: getThreadPriority(); break;
|
case 0x0B: getThreadPriority(); break;
|
||||||
case 0x0C: setThreadPriority(); break;
|
case 0x0C: setThreadPriority(); break;
|
||||||
|
case 0x0F: getThreadIdealProcessor(); break;
|
||||||
|
case 0x11: getCurrentProcessorNumber(); break;
|
||||||
case 0x13: svcCreateMutex(); break;
|
case 0x13: svcCreateMutex(); break;
|
||||||
case 0x14: svcReleaseMutex(); break;
|
case 0x14: svcReleaseMutex(); break;
|
||||||
case 0x15: svcCreateSemaphore(); break;
|
case 0x15: svcCreateSemaphore(); break;
|
||||||
|
@ -41,6 +44,10 @@ void Kernel::serviceSVC(u32 svc) {
|
||||||
case 0x17: svcCreateEvent(); break;
|
case 0x17: svcCreateEvent(); break;
|
||||||
case 0x18: svcSignalEvent(); break;
|
case 0x18: svcSignalEvent(); break;
|
||||||
case 0x19: svcClearEvent(); break;
|
case 0x19: svcClearEvent(); break;
|
||||||
|
case 0x1A: svcCreateTimer(); break;
|
||||||
|
case 0x1B: svcSetTimer(); break;
|
||||||
|
case 0x1C: svcCancelTimer(); break;
|
||||||
|
case 0x1D: svcClearTimer(); break;
|
||||||
case 0x1E: createMemoryBlock(); break;
|
case 0x1E: createMemoryBlock(); break;
|
||||||
case 0x1F: mapMemoryBlock(); break;
|
case 0x1F: mapMemoryBlock(); break;
|
||||||
case 0x21: createAddressArbiter(); break;
|
case 0x21: createAddressArbiter(); break;
|
||||||
|
@ -50,6 +57,7 @@ void Kernel::serviceSVC(u32 svc) {
|
||||||
case 0x25: waitSynchronizationN(); break;
|
case 0x25: waitSynchronizationN(); break;
|
||||||
case 0x27: duplicateHandle(); break;
|
case 0x27: duplicateHandle(); break;
|
||||||
case 0x28: getSystemTick(); break;
|
case 0x28: getSystemTick(); break;
|
||||||
|
case 0x2A: getSystemInfo(); break;
|
||||||
case 0x2B: getProcessInfo(); break;
|
case 0x2B: getProcessInfo(); break;
|
||||||
case 0x2D: connectToPort(); break;
|
case 0x2D: connectToPort(); break;
|
||||||
case 0x32: sendSyncRequest(); break;
|
case 0x32: sendSyncRequest(); break;
|
||||||
|
@ -61,6 +69,8 @@ void Kernel::serviceSVC(u32 svc) {
|
||||||
case 0x3D: outputDebugString(); break;
|
case 0x3D: outputDebugString(); break;
|
||||||
default: Helpers::panic("Unimplemented svc: %X @ %08X", svc, regs[15]); break;
|
default: Helpers::panic("Unimplemented svc: %X @ %08X", svc, regs[15]); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
evalReschedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Kernel::setVersion(u8 major, u8 minor) {
|
void Kernel::setVersion(u8 major, u8 minor) {
|
||||||
|
@ -136,10 +146,13 @@ void Kernel::reset() {
|
||||||
deleteObjectData(object);
|
deleteObjectData(object);
|
||||||
}
|
}
|
||||||
objects.clear();
|
objects.clear();
|
||||||
|
mutexHandles.clear();
|
||||||
portHandles.clear();
|
portHandles.clear();
|
||||||
threadIndices.clear();
|
threadIndices.clear();
|
||||||
serviceManager.reset();
|
serviceManager.reset();
|
||||||
|
|
||||||
|
needReschedule = false;
|
||||||
|
|
||||||
// Allocate handle #0 to a dummy object and make a main process object
|
// Allocate handle #0 to a dummy object and make a main process object
|
||||||
makeObject(KernelObjectType::Dummy);
|
makeObject(KernelObjectType::Dummy);
|
||||||
currentProcess = makeProcess(1); // Use ID = 1 for main process
|
currentProcess = makeProcess(1); // Use ID = 1 for main process
|
||||||
|
@ -147,7 +160,7 @@ void Kernel::reset() {
|
||||||
// Make main thread object. We do not have to set the entrypoint and SP for it as the ROM loader does.
|
// Make main thread object. We do not have to set the entrypoint and SP for it as the ROM loader does.
|
||||||
// Main thread seems to have a priority of 0x30. TODO: This creates a dummy context for thread 0,
|
// Main thread seems to have a priority of 0x30. TODO: This creates a dummy context for thread 0,
|
||||||
// which is thankfully not used. Maybe we should prevent this
|
// which is thankfully not used. Maybe we should prevent this
|
||||||
mainThread = makeThread(0, VirtualAddrs::StackTop, 0x30, -2, 0, ThreadStatus::Running);
|
mainThread = makeThread(0, VirtualAddrs::StackTop, 0x30, ProcessorID::Default, 0, ThreadStatus::Running);
|
||||||
currentThreadIndex = 0;
|
currentThreadIndex = 0;
|
||||||
setupIdleThread();
|
setupIdleThread();
|
||||||
|
|
||||||
|
@ -249,6 +262,99 @@ void Kernel::duplicateHandle() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace SystemInfoType {
|
||||||
|
enum : u32 {
|
||||||
|
MemoryInformation = 0,
|
||||||
|
// Gets information related to Citra (We don't implement this, we just report this emulator is not Citra)
|
||||||
|
CitraInformation = 0x20000,
|
||||||
|
// Gets information related to this emulator
|
||||||
|
PandaInformation = 0x20001,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace CitraInfoType {
|
||||||
|
enum : u32 {
|
||||||
|
IsCitra = 0,
|
||||||
|
BuildName = 10, // (ie: Nightly, Canary).
|
||||||
|
BuildVersion = 11, // Build version.
|
||||||
|
BuildDate1 = 20, // Build date first 7 characters.
|
||||||
|
BuildDate2 = 21, // Build date next 7 characters.
|
||||||
|
BuildDate3 = 22, // Build date next 7 characters.
|
||||||
|
BuildDate4 = 23, // Build date last 7 characters.
|
||||||
|
BuildBranch1 = 30, // Git branch first 7 characters.
|
||||||
|
BuildBranch2 = 31, // Git branch last 7 characters.
|
||||||
|
BuildDesc1 = 40, // Git description (commit) first 7 characters.
|
||||||
|
BuildDesc2 = 41, // Git description (commit) last 7 characters.
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace PandaInfoType {
|
||||||
|
enum : u32 {
|
||||||
|
IsPanda = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kernel::getSystemInfo() {
|
||||||
|
const u32 infoType = regs[1];
|
||||||
|
const u32 subtype = regs[2];
|
||||||
|
log("GetSystemInfo (type = %X, subtype = %X)\n", infoType, subtype);
|
||||||
|
|
||||||
|
regs[0] = Result::Success;
|
||||||
|
switch (infoType) {
|
||||||
|
case SystemInfoType::MemoryInformation: {
|
||||||
|
switch (subtype) {
|
||||||
|
// Total used memory size in the APPLICATION memory region
|
||||||
|
case 1:
|
||||||
|
regs[1] = mem.getUsedUserMem();
|
||||||
|
regs[2] = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Helpers::panic("GetSystemInfo: Unknown MemoryInformation subtype %x\n", subtype);
|
||||||
|
regs[0] = Result::FailurePlaceholder;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SystemInfoType::CitraInformation: {
|
||||||
|
switch (subtype) {
|
||||||
|
case CitraInfoType::IsCitra:
|
||||||
|
// Report that we're not Citra
|
||||||
|
regs[1] = 0;
|
||||||
|
regs[2] = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Helpers::warn("GetSystemInfo: Unknown CitraInformation subtype %x\n", subtype);
|
||||||
|
regs[0] = Result::FailurePlaceholder;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SystemInfoType::PandaInformation: {
|
||||||
|
switch (subtype) {
|
||||||
|
case PandaInfoType::IsPanda:
|
||||||
|
// This is indeed us, set output to 1
|
||||||
|
regs[1] = 1;
|
||||||
|
regs[2] = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Helpers::warn("GetSystemInfo: Unknown PandaInformation subtype %x\n", subtype);
|
||||||
|
regs[0] = Result::FailurePlaceholder;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: Helpers::panic("GetSystemInfo: Unknown system info type: %x (subtype: %x)\n", infoType, subtype); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::string Kernel::getProcessName(u32 pid) {
|
std::string Kernel::getProcessName(u32 pid) {
|
||||||
if (pid == KernelHandles::CurrentProcess) {
|
if (pid == KernelHandles::CurrentProcess) {
|
||||||
return "current";
|
return "current";
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#include "kernel.hpp"
|
#include "kernel.hpp"
|
||||||
#include "services/shared_font.hpp"
|
|
||||||
|
|
||||||
namespace Operation {
|
namespace Operation {
|
||||||
enum : u32 {
|
enum : u32 {
|
||||||
|
@ -137,7 +136,7 @@ void Kernel::mapMemoryBlock() {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KernelHandles::FontSharedMemHandle:
|
case KernelHandles::FontSharedMemHandle:
|
||||||
std::memcpy(ptr, _shared_font_bin, _shared_font_len);
|
mem.copySharedFont(ptr);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: Helpers::panic("Mapping unknown shared memory block: %X", block);
|
default: Helpers::panic("Mapping unknown shared memory block: %X", block);
|
||||||
|
|
|
@ -76,6 +76,11 @@ void Kernel::sendSyncRequest() {
|
||||||
u32 messagePointer = getTLSPointer() + 0x80; // The message is stored starting at TLS+0x80
|
u32 messagePointer = getTLSPointer() + 0x80; // The message is stored starting at TLS+0x80
|
||||||
logSVC("SendSyncRequest(session handle = %X)\n", handle);
|
logSVC("SendSyncRequest(session handle = %X)\n", handle);
|
||||||
|
|
||||||
|
// Service calls via SendSyncRequest and file access needs to put the caller to sleep for a given amount of time
|
||||||
|
// To make sure that the other threads don't get starved. Various games rely on this (including Sonic Boom: Shattering Crystal it seems)
|
||||||
|
constexpr u64 syncRequestDelayNs = 39000;
|
||||||
|
sleepThread(syncRequestDelayNs);
|
||||||
|
|
||||||
// The sync request is being sent at a service rather than whatever port, so have the service manager intercept it
|
// The sync request is being sent at a service rather than whatever port, so have the service manager intercept it
|
||||||
if (KernelHandles::isServiceHandle(handle)) {
|
if (KernelHandles::isServiceHandle(handle)) {
|
||||||
// The service call might cause a reschedule and change threads. Hence, set r0 before executing the service call
|
// The service call might cause a reschedule and change threads. Hence, set r0 before executing the service call
|
||||||
|
@ -104,7 +109,7 @@ void Kernel::sendSyncRequest() {
|
||||||
// If we're actually communicating with a port
|
// If we're actually communicating with a port
|
||||||
const auto session = getObject(handle, KernelObjectType::Session);
|
const auto session = getObject(handle, KernelObjectType::Session);
|
||||||
if (session == nullptr) [[unlikely]] {
|
if (session == nullptr) [[unlikely]] {
|
||||||
Helpers::panic("SendSyncRequest: Invalid handle");
|
Helpers::warn("SendSyncRequest: Invalid handle");
|
||||||
regs[0] = Result::Kernel::InvalidHandle;
|
regs[0] = Result::Kernel::InvalidHandle;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -122,4 +127,4 @@ void Kernel::sendSyncRequest() {
|
||||||
const auto portData = objects[portHandle].getData<Port>();
|
const auto portData = objects[portHandle].getData<Port>();
|
||||||
Helpers::panic("SendSyncRequest targetting port %s\n", portData->name);
|
Helpers::panic("SendSyncRequest targetting port %s\n", portData->name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,7 @@ s32 Kernel::getCurrentResourceValue(const KernelObject* limit, u32 resourceName)
|
||||||
u32 Kernel::getMaxForResource(const KernelObject* limit, u32 resourceName) {
|
u32 Kernel::getMaxForResource(const KernelObject* limit, u32 resourceName) {
|
||||||
switch (resourceName) {
|
switch (resourceName) {
|
||||||
case ResourceType::Commit: return appResourceLimits.maxCommit;
|
case ResourceType::Commit: return appResourceLimits.maxCommit;
|
||||||
|
case ResourceType::Thread: return appResourceLimits.maxThreads;
|
||||||
default: Helpers::panic("Attempted to get the max of unknown kernel resource: %d\n", resourceName);
|
default: Helpers::panic("Attempted to get the max of unknown kernel resource: %d\n", resourceName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,37 +82,31 @@ std::optional<int> Kernel::getNextThread() {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Kernel::switchToNextThread() {
|
// See if there is a higher priority, ready thread and switch to that
|
||||||
std::optional<int> newThreadIndex = getNextThread();
|
|
||||||
|
|
||||||
if (!newThreadIndex.has_value()) {
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// See if there;s a higher priority, ready thread and switch to that
|
|
||||||
void Kernel::rescheduleThreads() {
|
void Kernel::rescheduleThreads() {
|
||||||
|
Thread& current = threads[currentThreadIndex]; // Current running thread
|
||||||
|
|
||||||
|
// If the current thread is running and hasn't gone to sleep or whatever, set it to Ready instead of Running
|
||||||
|
// So that getNextThread will evaluate it properly
|
||||||
|
if (current.status == ThreadStatus::Running) {
|
||||||
|
current.status = ThreadStatus::Ready;
|
||||||
|
}
|
||||||
|
ThreadStatus currentStatus = current.status;
|
||||||
std::optional<int> newThreadIndex = getNextThread();
|
std::optional<int> newThreadIndex = getNextThread();
|
||||||
|
|
||||||
if (newThreadIndex.has_value() && newThreadIndex.value() != currentThreadIndex) {
|
// Case 1: A thread can run
|
||||||
threads[currentThreadIndex].status = ThreadStatus::Ready;
|
if (newThreadIndex.has_value()) {
|
||||||
switchThread(newThreadIndex.value());
|
switchThread(newThreadIndex.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 2: No other thread can run, straight to the idle thread
|
||||||
|
else {
|
||||||
|
switchThread(idleThreadIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal OS function to spawn a thread
|
// Internal OS function to spawn a thread
|
||||||
Handle Kernel::makeThread(u32 entrypoint, u32 initialSP, u32 priority, s32 id, u32 arg, ThreadStatus status) {
|
Handle Kernel::makeThread(u32 entrypoint, u32 initialSP, u32 priority, ProcessorID id, u32 arg, ThreadStatus status) {
|
||||||
int index; // Index of the created thread in the threads array
|
int index; // Index of the created thread in the threads array
|
||||||
|
|
||||||
if (threadCount < appResourceLimits.maxThreads) [[likely]] { // If we have not yet created over too many threads
|
if (threadCount < appResourceLimits.maxThreads) [[likely]] { // If we have not yet created over too many threads
|
||||||
|
@ -174,6 +168,11 @@ Handle Kernel::makeMutex(bool locked) {
|
||||||
moo->ownerThread = currentThreadIndex;
|
moo->ownerThread = currentThreadIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Push the new mutex to our list of mutex handles
|
||||||
|
// We need a list of mutex handles so that when a thread is killed, we can look which mutexes from this list the thread owns and free them
|
||||||
|
// Alternatively this could be a per-thread list, but I don't want to push_back and remove on every mutex lock and release
|
||||||
|
// Since some mutexes like the APT service mutex are locked and unlocked constantly, while ExitThread is a relatively "rare" SVC
|
||||||
|
mutexHandles.push_back(ret);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,6 +183,7 @@ void Kernel::releaseMutex(Mutex* moo) {
|
||||||
// If the lock count reached 0 then the thread no longer owns the mootex and it can be given to a new one
|
// 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) {
|
if (moo->lockCount == 0) {
|
||||||
moo->locked = false;
|
moo->locked = false;
|
||||||
|
|
||||||
if (moo->waitlist != 0) {
|
if (moo->waitlist != 0) {
|
||||||
int index = wakeupOneThread(moo->waitlist, moo->handle); // Wake up one thread and get its index
|
int index = wakeupOneThread(moo->waitlist, moo->handle); // Wake up one thread and get its index
|
||||||
moo->waitlist ^= (1ull << index); // Remove thread from waitlist
|
moo->waitlist ^= (1ull << index); // Remove thread from waitlist
|
||||||
|
@ -194,7 +194,7 @@ void Kernel::releaseMutex(Mutex* moo) {
|
||||||
moo->ownerThread = index;
|
moo->ownerThread = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
rescheduleThreads();
|
requireReschedule();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,7 +210,7 @@ void Kernel::sleepThreadOnArbiter(u32 waitingAddress) {
|
||||||
t.status = ThreadStatus::WaitArbiter;
|
t.status = ThreadStatus::WaitArbiter;
|
||||||
t.waitingAddress = waitingAddress;
|
t.waitingAddress = waitingAddress;
|
||||||
|
|
||||||
switchToNextThread();
|
requireReschedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Acquires an object that is **ready to be acquired** without waiting on it
|
// Acquires an object that is **ready to be acquired** without waiting on it
|
||||||
|
@ -226,7 +226,13 @@ void Kernel::acquireSyncObject(KernelObject* object, const Thread& thread) {
|
||||||
|
|
||||||
case KernelObjectType::Mutex: {
|
case KernelObjectType::Mutex: {
|
||||||
Mutex* moo = object->getData<Mutex>();
|
Mutex* moo = object->getData<Mutex>();
|
||||||
moo->locked = true; // Set locked to true, whether it's false or not because who cares
|
|
||||||
|
// Only reschedule if we're acquiring the mutex for the first time
|
||||||
|
if (!moo->locked) {
|
||||||
|
moo->locked = true;
|
||||||
|
requireReschedule();
|
||||||
|
}
|
||||||
|
|
||||||
// Increment lock count by 1. If a thread acquires a mootex multiple times, it needs to release it until count == 0
|
// 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.
|
// For the mootex to be free.
|
||||||
moo->lockCount++;
|
moo->lockCount++;
|
||||||
|
@ -338,20 +344,31 @@ void Kernel::wakeupAllThreads(u64 waitlist, Handle handle) {
|
||||||
void Kernel::sleepThread(s64 ns) {
|
void Kernel::sleepThread(s64 ns) {
|
||||||
if (ns < 0) {
|
if (ns < 0) {
|
||||||
Helpers::panic("Sleeping a thread for a negative amount of ns");
|
Helpers::panic("Sleeping a thread for a negative amount of ns");
|
||||||
} else if (ns == 0) { // Used when we want to force a thread switch
|
} else if (ns == 0) {
|
||||||
std::optional<int> newThreadIndex = getNextThread();
|
// TODO: This is garbage, but it works so eh we can keep it for now
|
||||||
// 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];
|
Thread& t = threads[currentThreadIndex];
|
||||||
|
|
||||||
|
// See if a thread other than this and the idle thread is waiting to run by temp marking the current function as dead and searching
|
||||||
|
// If there is another thread to run, then run it. Otherwise, go back to this thread, not to the idle thread
|
||||||
|
t.status = ThreadStatus::Dead;
|
||||||
|
auto nextThreadIndex = getNextThread();
|
||||||
|
t.status = ThreadStatus::Ready;
|
||||||
|
|
||||||
|
if (nextThreadIndex.has_value()) {
|
||||||
|
const auto index = nextThreadIndex.value();
|
||||||
|
|
||||||
|
if (index != idleThreadIndex) {
|
||||||
|
switchThread(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // If we're sleeping for >= 0 ns
|
||||||
|
Thread& t = threads[currentThreadIndex];
|
||||||
|
|
||||||
t.status = ThreadStatus::WaitSleep;
|
t.status = ThreadStatus::WaitSleep;
|
||||||
t.waitingNanoseconds = ns;
|
t.waitingNanoseconds = ns;
|
||||||
t.sleepTick = cpu.getTicks();
|
t.sleepTick = cpu.getTicks();
|
||||||
|
|
||||||
switchToNextThread();
|
requireReschedule();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -372,9 +389,13 @@ void Kernel::createThread() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (id < -2 || id > 3) {
|
||||||
|
Helpers::panic("Invalid processor ID in CreateThread");
|
||||||
|
}
|
||||||
|
|
||||||
regs[0] = Result::Success;
|
regs[0] = Result::Success;
|
||||||
regs[1] = makeThread(entrypoint, initialSP, priority, id, arg, ThreadStatus::Ready);
|
regs[1] = makeThread(entrypoint, initialSP, priority, static_cast<ProcessorID>(id), arg, ThreadStatus::Ready);
|
||||||
rescheduleThreads();
|
requireReschedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
// void SleepThread(s64 nanoseconds)
|
// void SleepThread(s64 nanoseconds)
|
||||||
|
@ -424,6 +445,15 @@ void Kernel::getThreadPriority() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Kernel::getThreadIdealProcessor() {
|
||||||
|
const Handle handle = regs[1]; // Thread handle
|
||||||
|
logSVC("GetThreadIdealProcessor (handle = %X)\n", handle);
|
||||||
|
|
||||||
|
// TODO: Not documented what this is or what it does. Citra doesn't implement it at all. Return AppCore as the ideal processor for now
|
||||||
|
regs[0] = Result::Success;
|
||||||
|
regs[1] = static_cast<u32>(ProcessorID::AppCore);
|
||||||
|
}
|
||||||
|
|
||||||
void Kernel::setThreadPriority() {
|
void Kernel::setThreadPriority() {
|
||||||
const Handle handle = regs[0];
|
const Handle handle = regs[0];
|
||||||
const u32 priority = regs[1];
|
const u32 priority = regs[1];
|
||||||
|
@ -448,12 +478,56 @@ void Kernel::setThreadPriority() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sortThreads();
|
sortThreads();
|
||||||
rescheduleThreads();
|
requireReschedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kernel::getCurrentProcessorNumber() {
|
||||||
|
logSVC("GetCurrentProcessorNumber()\n");
|
||||||
|
const ProcessorID id = threads[currentThreadIndex].processorID;
|
||||||
|
s32 ret;
|
||||||
|
|
||||||
|
// Until we properly implement per-core schedulers, return whatever processor ID passed to svcCreateThread
|
||||||
|
switch (id) {
|
||||||
|
// TODO: This is picked from exheader
|
||||||
|
case ProcessorID::Default:
|
||||||
|
ret = static_cast<s32>(ProcessorID::AppCore);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ProcessorID::AllCPUs:
|
||||||
|
ret = static_cast<s32>(ProcessorID::AppCore);
|
||||||
|
Helpers::warn("GetCurrentProcessorNumber on thread created to run on all CPUs...?\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: ret = static_cast<s32>(id); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret != static_cast<s32>(ProcessorID::AppCore)) {
|
||||||
|
Helpers::warn("GetCurrentProcessorNumber: Thread not running on appcore\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
regs[0] = static_cast<u32>(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Kernel::exitThread() {
|
void Kernel::exitThread() {
|
||||||
logSVC("ExitThread\n");
|
logSVC("ExitThread\n");
|
||||||
|
|
||||||
|
// Find which mutexes this thread owns, release them
|
||||||
|
for (auto handle : mutexHandles) {
|
||||||
|
KernelObject* object = getObject(handle, KernelObjectType::Mutex);
|
||||||
|
|
||||||
|
// Make sure that the handle actually matches to a mutex, and if our exiting thread owns the mutex, release it
|
||||||
|
if (object != nullptr) {
|
||||||
|
Mutex* moo = object->getData<Mutex>();
|
||||||
|
|
||||||
|
if (moo->locked && moo->ownerThread == currentThreadIndex) {
|
||||||
|
// Release the mutex by setting lock count to 1 and releasing it once. We set lock count to 1 since it's a recursive mutex
|
||||||
|
// Therefore if its lock count was > 1, simply calling releaseMutex would not fully release it
|
||||||
|
moo->lockCount = 1;
|
||||||
|
releaseMutex(moo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remove the index of this thread from the thread indices vector
|
// Remove the index of this thread from the thread indices vector
|
||||||
for (int i = 0; i < threadIndices.size(); i++) {
|
for (int i = 0; i < threadIndices.size(); i++) {
|
||||||
if (threadIndices[i] == currentThreadIndex)
|
if (threadIndices[i] == currentThreadIndex)
|
||||||
|
@ -472,7 +546,7 @@ void Kernel::exitThread() {
|
||||||
t.threadsWaitingForTermination = 0; // No other threads waiting
|
t.threadsWaitingForTermination = 0; // No other threads waiting
|
||||||
}
|
}
|
||||||
|
|
||||||
switchToNextThread();
|
requireReschedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Kernel::svcCreateMutex() {
|
void Kernel::svcCreateMutex() {
|
||||||
|
@ -585,4 +659,4 @@ bool Kernel::shouldWaitOnObject(KernelObject* object) {
|
||||||
Helpers::panic("Not sure whether to wait on object (type: %s)", object->getTypeName());
|
Helpers::panic("Not sure whether to wait on object (type: %s)", object->getTypeName());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
6
src/core/kernel/timers.cpp
Normal file
6
src/core/kernel/timers.cpp
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#include "kernel.hpp"
|
||||||
|
|
||||||
|
void Kernel::svcCreateTimer() { Helpers::panic("Kernel::CreateTimer"); }
|
||||||
|
void Kernel::svcSetTimer() { Helpers::panic("Kernel::SetTimer"); }
|
||||||
|
void Kernel::svcClearTimer() { Helpers::panic("Kernel::ClearTimer"); }
|
||||||
|
void Kernel::svcCancelTimer() { Helpers::panic("Kernel::CancelTimer"); }
|
|
@ -63,5 +63,7 @@ std::optional<u32> Memory::loadELF(std::ifstream& file) {
|
||||||
allocateMemory(vaddr, fcramAddr, memorySize, true, r, w, x);
|
allocateMemory(vaddr, fcramAddr, memorySize, true, r, w, x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ELF can't specify a region, make it default to USA
|
||||||
|
region = Regions::USA;
|
||||||
return static_cast<u32>(reader.get_entry());
|
return static_cast<u32>(reader.get_entry());
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue