Merge branch 'master' into open-bp-cpp

This commit is contained in:
sylvieee-iot 2025-07-21 03:05:09 +03:00 committed by GitHub
commit 6d067d7108
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
402 changed files with 72132 additions and 27830 deletions

View file

@ -37,7 +37,7 @@ jobs:
${{ runner.os }}-pandroid-x86_64-
- name: Setup Vulkan SDK
uses: humbletim/setup-vulkan-sdk@v1.2.0
uses: humbletim/setup-vulkan-sdk@main
with:
vulkan-query-version: latest
vulkan-use-cache: true
@ -105,7 +105,7 @@ jobs:
${{ runner.os }}-pandroid-arm64-
- name: Setup Vulkan SDK
uses: humbletim/setup-vulkan-sdk@v1.2.0
uses: humbletim/setup-vulkan-sdk@main
with:
vulkan-query-version: latest
vulkan-use-cache: true

View file

@ -30,7 +30,7 @@ jobs:
sudo ./llvm.sh 17
- name: Setup Vulkan SDK
uses: humbletim/setup-vulkan-sdk@v1.2.0
uses: humbletim/setup-vulkan-sdk@main
with:
vulkan-query-version: latest
vulkan-use-cache: true

View file

@ -20,7 +20,7 @@ jobs:
run: git submodule update --init --recursive
- name: Setup Vulkan SDK
uses: humbletim/setup-vulkan-sdk@v1.2.0
uses: humbletim/setup-vulkan-sdk@main
with:
vulkan-query-version: latest
vulkan-use-cache: true
@ -63,7 +63,7 @@ jobs:
run: git submodule update --init --recursive
- name: Setup Vulkan SDK
uses: humbletim/setup-vulkan-sdk@v1.2.0
uses: humbletim/setup-vulkan-sdk@main
with:
vulkan-query-version: latest
vulkan-use-cache: true
@ -116,7 +116,7 @@ jobs:
sudo ./llvm.sh 17
- name: Setup Vulkan SDK
uses: humbletim/setup-vulkan-sdk@v1.2.0
uses: humbletim/setup-vulkan-sdk@main
with:
vulkan-query-version: latest
vulkan-use-cache: true
@ -163,7 +163,7 @@ jobs:
sudo apt-get update && sudo apt install libx11-dev libgl1 libglx-mesa0 mesa-common-dev libfuse2 libwayland-dev
- name: Setup Vulkan SDK
uses: humbletim/setup-vulkan-sdk@v1.2.0
uses: humbletim/setup-vulkan-sdk@main
with:
vulkan-query-version: latest
vulkan-use-cache: true
@ -180,3 +180,36 @@ jobs:
with:
name: Android Hydra core
path: '${{github.workspace}}/build/libAlber.so'
ARM-Libretro:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Fetch submodules
run: git submodule update --init --recursive
- name: Install misc packages
run: |
sudo apt-get update && sudo apt install libx11-dev libxext-dev libgl1 libglx-mesa0 mesa-common-dev libfuse2 libwayland-dev
- name: Install newer Clang
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x ./llvm.sh
sudo ./llvm.sh 17
- name: Configure CMake
run: |
cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17 -DENABLE_USER_BUILD=ON -DBUILD_LIBRETRO_CORE=ON -DENABLE_VULKAN=OFF -DCRYPTOPP_OPT_DISABLE_ASM=ON
- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
- name: Upload Libretro core
uses: actions/upload-artifact@v4
with:
name: Linux arm64 Libretro core
path: |
${{github.workspace}}/build/panda3ds_libretro.so
${{github.workspace}}/docs/libretro/panda3ds_libretro.info

View file

@ -33,7 +33,7 @@ jobs:
sudo ./llvm.sh 17
- name: Setup Vulkan SDK
uses: humbletim/setup-vulkan-sdk@v1.2.0
uses: humbletim/setup-vulkan-sdk@main
with:
vulkan-query-version: latest
vulkan-use-cache: true

View file

@ -33,7 +33,7 @@ jobs:
sudo ./llvm.sh 17
- name: Setup Vulkan SDK
uses: humbletim/setup-vulkan-sdk@v1.2.0
uses: humbletim/setup-vulkan-sdk@main
with:
vulkan-query-version: latest
vulkan-use-cache: true

View file

@ -25,7 +25,7 @@ jobs:
run: git submodule update --init --recursive
- name: Setup Vulkan SDK
uses: humbletim/setup-vulkan-sdk@v1.2.0
uses: humbletim/setup-vulkan-sdk@main
with:
vulkan-query-version: latest
vulkan-use-cache: true

View file

@ -26,7 +26,7 @@ jobs:
version: 6.2.0
- name: Setup Vulkan SDK
uses: humbletim/setup-vulkan-sdk@v1.2.0
uses: humbletim/setup-vulkan-sdk@main
with:
vulkan-query-version: latest
vulkan-use-cache: true
@ -64,7 +64,7 @@ jobs:
run: git submodule update --init --recursive
- name: Setup Vulkan SDK
uses: humbletim/setup-vulkan-sdk@v1.2.0
uses: humbletim/setup-vulkan-sdk@main
with:
vulkan-query-version: latest
vulkan-use-cache: true
@ -162,7 +162,7 @@ jobs:
sudo ./llvm.sh 17
- name: Setup Vulkan SDK
uses: humbletim/setup-vulkan-sdk@v1.2.0
uses: humbletim/setup-vulkan-sdk@main
with:
vulkan-query-version: latest
vulkan-use-cache: true

View file

@ -24,7 +24,7 @@ jobs:
run: git submodule update --init --recursive
- name: Setup Vulkan SDK
uses: humbletim/setup-vulkan-sdk@v1.2.0
uses: humbletim/setup-vulkan-sdk@main
with:
vulkan-query-version: latest
vulkan-use-cache: true

39
.github/workflows/iOS_Build.yml vendored Normal file
View file

@ -0,0 +1,39 @@
name: iOS Simulator 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: macos-latest
steps:
- uses: actions/checkout@v4
- name: Fetch submodules
run: git submodule update --init --recursive
- name: Update Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest
- name: Setup Vulkan SDK
uses: humbletim/setup-vulkan-sdk@main
with:
vulkan-query-version: latest
vulkan-use-cache: true
vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang
- name: Build core and frontend
run: cd src/pandios && ./build.sh

9
.gitignore vendored
View file

@ -70,3 +70,12 @@ fb.bat
config.toml
CMakeSettings.json
# IDE files
# KDevelop files
*.kdev4
# IDEA/Clion files
.idea/
# VSC files
/.vscode/

10
.gitmodules vendored
View file

@ -1,9 +1,6 @@
[submodule "third_party/elfio"]
path = third_party/elfio
url = https://github.com/serge1/ELFIO
[submodule "third_party/SDL2"]
path = third_party/SDL2
url = https://github.com/libsdl-org/SDL
[submodule "third_party/cryptopp/cryptopp"]
path = third_party/cryptopp/cryptopp
url = https://github.com/weidai11/cryptopp
@ -82,9 +79,10 @@
[submodule "third_party/fdk-aac"]
path = third_party/fdk-aac
url = https://github.com/Panda3DS-emu/fdk-aac/
[submodule "third_party/cryptoppwin"]
path = third_party/cryptoppwin
url = https://github.com/shadps4-emu/ext-cryptoppwin
[submodule "third_party/oaknut"]
path = third_party/oaknut
url = https://github.com/panda3ds-emu/oaknut
[submodule "third_party/SDL2"]
path = third_party/SDL2
url = https://github.com/libsdl-org/SDL
branch = SDL2

View file

@ -64,6 +64,14 @@ option(BUILD_HYDRA_CORE "Build a Hydra core" OFF)
option(BUILD_LIBRETRO_CORE "Build a Libretro core" OFF)
option(ENABLE_RENDERDOC_API "Build with support for Renderdoc's capture API for graphics debugging" ON)
option(DISABLE_SSE4 "Build with SSE4 instructions disabled, may reduce performance" OFF)
option(USE_LIBRETRO_AUDIO "Enable to use the LR audio device with the LR core. Otherwise our own device is used" OFF)
option(IOS_SIMULATOR_BUILD "Compiling for IOS simulator (Set to off if compiling for a real iPhone)" ON)
# Discord RPC & LuaJIT are currently not supported on iOS
if(IOS)
set(ENABLE_DISCORD_RPC OFF)
set(ENABLE_LUAJIT OFF)
endif()
set(OPENGL_PROFILE ${DEFAULT_OPENGL_PROFILE} CACHE STRING "OpenGL profile to use if OpenGL is enabled. Valid values are 'OpenGL' and 'OpenGLES'.")
set_property(CACHE OPENGL_PROFILE PROPERTY STRINGS OpenGL OpenGLES)
@ -80,6 +88,10 @@ endif()
if(BUILD_LIBRETRO_CORE)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
add_compile_definitions(__LIBRETRO__)
if(USE_LIBRETRO_AUDIO)
add_compile_definitions(USE_LIBRETRO_AUDIO_DEVICE)
endif()
endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND ENABLE_USER_BUILD)
@ -157,7 +169,6 @@ if(ENABLE_DISCORD_RPC AND NOT ANDROID)
include_directories(third_party/discord-rpc/include)
endif()
if (NOT ANDROID)
if (USE_SYSTEM_SDL2)
find_package(SDL2 CONFIG REQUIRED)
@ -303,7 +314,7 @@ else()
message(FATAL_ERROR "Currently unsupported CPU architecture")
endif()
add_subdirectory(third_party/teakra EXCLUDE_FROM_ALL)
add_subdirectory(third_party/teakra)
add_subdirectory(third_party/fdk-aac)
set(CAPSTONE_ARCHITECTURE_DEFAULT OFF)
@ -316,8 +327,8 @@ set(SOURCE_FILES src/emulator.cpp src/io_file.cpp src/config.cpp
src/core/CPU/cpu_dynarmic.cpp src/core/CPU/dynarmic_cycles.cpp
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 src/lua.cpp src/memory_mapped_file.cpp src/miniaudio.cpp src/renderdoc.cpp
src/frontend_settings.cpp
src/discord_rpc.cpp src/lua.cpp src/memory_mapped_file.cpp src/renderdoc.cpp
src/frontend_settings.cpp src/miniaudio/miniaudio.cpp src/core/screen_layout.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
@ -335,10 +346,10 @@ set(SERVICE_SOURCE_FILES src/core/services/service_manager.cpp src/core/services
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/act.cpp src/core/services/nfc.cpp src/core/services/dlp_srvr.cpp
src/core/services/ir_user.cpp src/core/services/http.cpp src/core/services/soc.cpp
src/core/services/ir/ir_user.cpp src/core/services/http.cpp src/core/services/soc.cpp
src/core/services/ssl.cpp src/core/services/news_u.cpp src/core/services/amiibo_device.cpp
src/core/services/csnd.cpp src/core/services/nwm_uds.cpp src/core/services/fonts.cpp
src/core/services/ns.cpp
src/core/services/ns.cpp src/core/services/ir/circlepad_pro.cpp src/core/services/ir/crc8.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
@ -386,7 +397,7 @@ 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_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/config.hpp include/services/ir_user.hpp include/http_server.hpp include/cheats.hpp
include/config.hpp include/services/ir/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/applets/applet.hpp include/applets/mii_selector.hpp include/math_util.hpp include/services/soc.hpp
@ -402,10 +413,21 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp
include/align.hpp include/audio/aac_decoder.hpp include/PICA/pica_simd.hpp include/services/fonts.hpp
include/audio/audio_interpolation.hpp include/audio/hle_mixer.hpp include/audio/dsp_simd.hpp
include/services/dsp_firmware_db.hpp include/frontend_settings.hpp include/fs/archive_twl_photo.hpp
include/fs/archive_twl_sound.hpp include/fs/archive_card_spi.hpp include/services/ns.hpp
include/external_haptics_manager.hpp
include/fs/archive_twl_sound.hpp include/fs/archive_card_spi.hpp include/services/ns.hpp include/audio/audio_device.hpp
include/audio/audio_device_interface.hpp include/audio/libretro_audio_device.hpp include/services/ir/ir_types.hpp
include/services/ir/ir_device.hpp include/services/ir/circlepad_pro.hpp include/services/service_intercept.hpp
include/screen_layout.hpp include/services/service_map.hpp include/audio/dsp_binary.hpp include/external_haptics_manager.hpp
)
if(IOS)
set(SOURCE_FILES ${SOURCE_FILES} src/miniaudio/miniaudio.m)
target_compile_definitions(AlberCore PUBLIC "PANDA3DS_IOS=1")
if (IOS_SIMULATOR_BUILD)
target_compile_definitions(AlberCore PUBLIC "PANDA3DS_IOS_SIMULATOR=1")
endif()
endif()
cmrc_add_resource_library(
resources_console_fonts
NAMESPACE ConsoleFonts
@ -589,14 +611,16 @@ if(ENABLE_METAL AND APPLE)
include/renderer_mtl/mtl_common.hpp
include/renderer_mtl/pica_to_mtl.hpp
include/renderer_mtl/objc_helper.hpp
include/renderer_mtl/texture_decoder.hpp
)
set(RENDERER_MTL_SOURCE_FILES src/core/renderer_mtl/metal_cpp_impl.cpp
src/core/renderer_mtl/renderer_mtl.cpp
src/core/renderer_mtl/mtl_texture.cpp
src/core/renderer_mtl/mtl_etc1.cpp
src/core/renderer_mtl/mtl_lut_texture.cpp
src/core/renderer_mtl/pica_to_mtl.cpp
src/core/renderer_mtl/objc_helper.mm
src/core/renderer_mtl/texture_decoder.cpp
src/host_shaders/metal_shaders.metal
src/host_shaders/metal_blit.metal
#src/host_shaders/metal_copy_to_lut_texture.metal
@ -610,15 +634,26 @@ if(ENABLE_METAL AND APPLE)
set(SHADER_SOURCE "${CMAKE_SOURCE_DIR}/src/host_shaders/${SHADER}.metal")
set(SHADER_IR "${CMAKE_SOURCE_DIR}/src/host_shaders/${SHADER}.ir")
set(SHADER_METALLIB "${CMAKE_SOURCE_DIR}/src/host_shaders/${SHADER}.metallib")
# MacOS, iOS and the iOS simulator all use different compilation options for shaders
set(MetalSDK "macosx")
if(IOS)
if (IOS_SIMULATOR_BUILD)
set(MetalSDK "iphonesimulator")
else()
set(MetalSDK "iphoneos")
endif()
endif()
# TODO: only include sources in debug builds
add_custom_command(
OUTPUT ${SHADER_IR}
COMMAND xcrun -sdk macosx metal -gline-tables-only -frecord-sources -o ${SHADER_IR} -c ${SHADER_SOURCE}
COMMAND xcrun -sdk ${MetalSDK} metal -gline-tables-only -frecord-sources -o ${SHADER_IR} -c ${SHADER_SOURCE}
DEPENDS ${SHADER_SOURCE}
VERBATIM)
add_custom_command(
OUTPUT ${SHADER_METALLIB}
COMMAND xcrun -sdk macosx metallib -o ${SHADER_METALLIB} ${SHADER_IR}
COMMAND xcrun -sdk ${MetalSDK} metallib -o ${SHADER_METALLIB} ${SHADER_IR}
DEPENDS ${SHADER_IR}
VERBATIM)
set(RENDERER_MTL_HOST_SHADERS_SOURCES ${RENDERER_MTL_HOST_SHADERS_SOURCES} ${SHADER_METALLIB})
@ -647,7 +682,7 @@ if(ENABLE_METAL AND APPLE)
target_compile_definitions(AlberCore PUBLIC "PANDA3DS_ENABLE_METAL=1")
target_include_directories(AlberCore PRIVATE third_party/metal-cpp)
# TODO: check if all of them are needed
target_link_libraries(AlberCore PRIVATE "-framework Metal" "-framework Foundation" "-framework QuartzCore" resources_renderer_mtl)
target_link_libraries(AlberCore PUBLIC "-framework Metal" "-framework Foundation" "-framework QuartzCore" resources_renderer_mtl)
endif()
source_group("Header Files\\Core" FILES ${HEADER_FILES})
@ -656,8 +691,8 @@ set(ALL_SOURCES ${SOURCE_FILES} ${FS_SOURCE_FILES} ${CRYPTO_SOURCE_FILES} ${KERN
${AUDIO_SOURCE_FILES} ${HEADER_FILES} ${FRONTEND_HEADER_FILES})
target_sources(AlberCore PRIVATE ${ALL_SOURCES})
target_link_libraries(AlberCore PRIVATE dynarmic glad resources_console_fonts teakra fdk-aac)
target_link_libraries(AlberCore PUBLIC glad capstone fmt::fmt)
target_link_libraries(AlberCore PRIVATE dynarmic glad resources_console_fonts fdk-aac)
target_link_libraries(AlberCore PUBLIC glad capstone fmt::fmt teakra)
if(ENABLE_DISCORD_RPC AND NOT ANDROID)
target_compile_definitions(AlberCore PUBLIC "PANDA3DS_ENABLE_DISCORD_RPC=1")
@ -703,10 +738,13 @@ if(NOT BUILD_HYDRA_CORE AND NOT BUILD_LIBRETRO_CORE)
set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp src/panda_qt/screen.cpp src/panda_qt/main_window.cpp src/panda_qt/about_window.cpp
src/panda_qt/config_window.cpp src/panda_qt/zep.cpp src/panda_qt/text_editor.cpp src/panda_qt/cheats_window.cpp src/panda_qt/mappings.cpp
src/panda_qt/patch_window.cpp src/panda_qt/elided_label.cpp src/panda_qt/shader_editor.cpp src/panda_qt/translations.cpp
src/panda_qt/thread_debugger.cpp src/panda_qt/cpu_debugger.cpp src/panda_qt/dsp_debugger.cpp src/panda_qt/input_window.cpp
)
set(FRONTEND_HEADER_FILES include/panda_qt/screen.hpp include/panda_qt/main_window.hpp include/panda_qt/about_window.hpp
include/panda_qt/config_window.hpp include/panda_qt/text_editor.hpp include/panda_qt/cheats_window.hpp
include/panda_qt/patch_window.hpp include/panda_qt/elided_label.hpp include/panda_qt/shader_editor.hpp
include/panda_qt/thread_debugger.hpp include/panda_qt/cpu_debugger.hpp include/panda_qt/dsp_debugger.hpp
include/panda_qt/disabled_widget_overlay.hpp include/panda_qt/input_window.hpp
)
source_group("Source Files\\Qt" FILES ${FRONTEND_SOURCE_FILES})
@ -753,11 +791,15 @@ if(NOT BUILD_HYDRA_CORE AND NOT BUILD_LIBRETRO_CORE)
docs/img/rsob_icon.png docs/img/rstarstruck_icon.png docs/img/rpog_icon.png docs/img/rsyn_icon.png
docs/img/settings_icon.png docs/img/display_icon.png docs/img/speaker_icon.png
docs/img/sparkling_icon.png docs/img/battery_icon.png docs/img/sdcard_icon.png
docs/img/rnap_icon.png docs/img/rcow_icon.png docs/img/skyemu_icon.png
docs/img/rnap_icon.png docs/img/rcow_icon.png docs/img/skyemu_icon.png docs/img/runpog_icon.png
docs/img/gamepad_icon.png
)
# Translation files in Qt's .ts format. Will be converted into binary files and embedded into the executable
set(TRANSLATIONS_TS docs/translations/en.ts docs/translations/el.ts docs/translations/es.ts docs/translations/pt_br.ts docs/translations/nl.ts)
set(TRANSLATIONS_TS docs/translations/en.ts docs/translations/el.ts docs/translations/es.ts docs/translations/pt_br.ts docs/translations/nl.ts
docs/translations/sv.ts
)
set_source_files_properties(${TRANSLATIONS_TS} PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/translations")
qt_add_translation(TRANSLATIONS_QM ${TRANSLATIONS_TS})
@ -782,7 +824,13 @@ if(NOT BUILD_HYDRA_CORE AND NOT BUILD_LIBRETRO_CORE)
elseif(BUILD_HYDRA_CORE)
target_compile_definitions(AlberCore PRIVATE PANDA3DS_HYDRA_CORE=1)
include_directories(third_party/hydra_core/include)
add_library(Alber SHARED src/hydra_core.cpp)
set(SHARED_SOURCE_FILES src/hydra_core.cpp)
if(IOS)
set(SHARED_SOURCE_FILES ${SHARED_SOURCE_FILES} src/ios_driver.mm)
endif()
add_library(Alber SHARED ${SHARED_SOURCE_FILES})
target_link_libraries(Alber PUBLIC AlberCore)
elseif(BUILD_LIBRETRO_CORE)
include_directories(third_party/libretro/include)

BIN
docs/img/gamepad_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

BIN
docs/img/runpog_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

774
docs/translations/sv.ts Normal file
View file

@ -0,0 +1,774 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="sv_SE">
<extra-po-header-language>sv</extra-po-header-language>
<extra-po-header-language_team></extra-po-header-language_team>
<extra-po-header-last_translator>Daniel Nylander &lt;github@danielnylander.se&gt;</extra-po-header-last_translator>
<extra-po-header-po_revision_date></extra-po-header-po_revision_date>
<extra-po-header-pot_creation_date></extra-po-header-pot_creation_date>
<extra-po-header-project_id_version></extra-po-header-project_id_version>
<extra-po-header-x_generator>Poedit 3.5</extra-po-header-x_generator>
<extra-po-headers>Project-Id-Version,POT-Creation-Date,PO-Revision-Date,Last-Translator,Language-Team,Language,MIME-Version,Content-Type,Content-Transfer-Encoding,X-Qt-Contexts,X-Generator</extra-po-headers>
<context>
<name>AboutWindow</name>
<message>
<location filename="../../src/panda_qt/about_window.cpp" line="16"/>
<source>About Panda3DS</source>
<translation>Om Panda3DS</translation>
</message>
<message>
<location filename="../../src/panda_qt/about_window.cpp" line="35"/>
<source>Panda3DS is a free and open source Nintendo 3DS emulator, for Windows, MacOS and Linux</source>
<translation>Panda3DS är en Nintendo 3DS-emulator med fri och öppen källkod för Windows, MacOS och Linux</translation>
</message>
<message>
<location filename="../../src/panda_qt/about_window.cpp" line="36"/>
<source>Visit panda3ds.com for help with Panda3DS and links to our official support sites.</source>
<translation>Besök panda3ds.com för att hjälp med Panda3DS och länkar till våra officiella supportwebbplatser.</translation>
</message>
<message>
<location filename="../../src/panda_qt/about_window.cpp" line="38"/>
<source>Panda3DS is developed by volunteers in their spare time. Below is a list of some of these volunteers who&apos;ve agreed to be listed here, in no particular order.&lt;br&gt;If you think you should be listed here too, please inform us&lt;br&gt;&lt;br&gt;- Peach (wheremyfoodat)&lt;br&gt;- noumidev&lt;br&gt;- liuk707&lt;br&gt;- Wunk&lt;br&gt;- marysaka&lt;br&gt;- Sky&lt;br&gt;- merryhime&lt;br&gt;- TGP17&lt;br&gt;- Shadow&lt;br&gt;</source>
<translation>Panda3DS utvecklas av volontärer deras fritid. Nedan finns en lista över några av dessa volontärer som har gått med att listas här, utan någon särskild ordning.&lt;br&gt;Om du tycker att du också borde listas här, informera oss&lt;br&gt;&lt;br&gt;- Peach (wheremyfoodat)&lt;br&gt;- noumidev&lt;br&gt;- liuk707&lt;br&gt;- Wunk&lt;br&gt;- marysaka&lt;br&gt;- Sky&lt;br&gt;- merryhime&lt;br&gt;- TGP17&lt;br&gt;- Shadow&lt;br&gt;</translation>
</message>
</context>
<context>
<name>CheatEditDialog</name>
<message>
<location filename="../../src/panda_qt/cheats_window.cpp" line="72"/>
<source>Edit Cheat</source>
<translation>Redigera fusk</translation>
</message>
<message>
<location filename="../../src/panda_qt/cheats_window.cpp" line="82"/>
<source>Cheat name</source>
<translation>Fusknamn</translation>
</message>
</context>
<context>
<name>CheatEntryWidget</name>
<message>
<location filename="../../src/panda_qt/cheats_window.cpp" line="34"/>
<source>Edit</source>
<translation>Redigera</translation>
</message>
</context>
<context>
<name>CheatsWindow</name>
<message>
<location filename="../../src/panda_qt/cheats_window.cpp" line="164"/>
<source>Cheats</source>
<translation>Fusk</translation>
</message>
<message>
<location filename="../../src/panda_qt/cheats_window.cpp" line="177"/>
<source>Add</source>
<translation>Lägg till</translation>
</message>
<message>
<location filename="../../src/panda_qt/cheats_window.cpp" line="178"/>
<source>Remove</source>
<translation>Ta bort</translation>
</message>
</context>
<context>
<name>ConfigWindow</name>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="7"/>
<source>Configuration</source>
<translation>Konfiguration</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="63"/>
<source>Interface Settings</source>
<translation>Inställningar för gränssnitt</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="69"/>
<source>System</source>
<translation>System</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="70"/>
<source>Light</source>
<translation>Ljus</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="71"/>
<source>Dark</source>
<translation>Mörk</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="72"/>
<source>Greetings Cat</source>
<translation>Hälsningskatt</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="73"/>
<source>Cream</source>
<translation>Grädde</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="81"/>
<source>Color theme</source>
<translation>Färgtema</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="84"/>
<source>Happy panda</source>
<translation>Glad panda</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="85"/>
<source>Happy panda (colourful)</source>
<translation>Glad panda (färgglad)</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="86"/>
<source>Sleepy panda</source>
<translation>Sömnig panda</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="87"/>
<source>Cow panda</source>
<translation>Ko-panda</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="88"/>
<source>The penguin from SkyEmu</source>
<translation>Pingvinen från SkyEmu</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="97"/>
<source>Window icon</source>
<translation>Fönsterikon</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="100"/>
<source>Language</source>
<translation>Språk</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="102"/>
<source>Show version on window title</source>
<translation>Visa version fönstertitel</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="15"/>
<location filename="../../src/panda_qt/config_window.cpp" line="109"/>
<source>Alber v%1</source>
<translation>Alber v%1</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="109"/>
<source>Alber</source>
<translation>Alber</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="114"/>
<source>Remember window position</source>
<translation>Kom ihåg fönstrets position</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="119"/>
<source>General Settings</source>
<translation>Allmänna inställningar</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="130"/>
<source>Browse...</source>
<translation>Bläddra...</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="134"/>
<source>Select Directory</source>
<translation>Välj katalog</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="146"/>
<source>Default ROMs path</source>
<translation>Standardsökväg för ROMar</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="148"/>
<source>Enable Discord RPC</source>
<translation>Aktivera Discord RPC</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="152"/>
<source>Use portable build</source>
<translation>Använd portabelt bygge</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="156"/>
<source>Print version in console output</source>
<translation>Skriv ut versionen i konsolutmatningen</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="161"/>
<source>Graphics Settings</source>
<translation>Grafikinställningar</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="167"/>
<location filename="../../src/panda_qt/config_window.cpp" line="221"/>
<source>Null</source>
<translation>Null</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="168"/>
<source>OpenGL</source>
<translation>OpenGL</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="169"/>
<source>Vulkan</source>
<translation>Vulkan</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="175"/>
<source>GPU renderer</source>
<translation>GPU-rendering</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="177"/>
<source>Enable Renderdoc</source>
<translation>Aktivera Renderdoc</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="181"/>
<source>Enable shader JIT</source>
<translation>Aktivera shader JIT</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="185"/>
<source>Enable VSync</source>
<translation>Aktivera VSync</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="189"/>
<source>Use ubershaders (No stutter, maybe slower)</source>
<translation>Använda ubershaders (inga hackningar, kanske långsammare)</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="193"/>
<source>Accurate shader multiplication</source>
<translation>Korrekt multiplicering av shaders</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="197"/>
<source>Accelerate shaders</source>
<translation>Snabbare shaders</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="201"/>
<source>Force shadergen when rendering lights</source>
<translation>Tvinga fram shadergen vid rendering av ljus</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="212"/>
<source>Light threshold for forcing shadergen</source>
<translation>Ljuströskel för att tvinga shadergen</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="215"/>
<source>Audio Settings</source>
<translation>Ljudinställningar</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="222"/>
<source>LLE</source>
<translation>LLE</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="223"/>
<source>HLE</source>
<translation>HLE</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="229"/>
<source>DSP emulation</source>
<translation>DSP-emulering</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="231"/>
<source>Enable audio</source>
<translation>Aktivera ljud</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="235"/>
<source>Enable AAC audio</source>
<translation>Aktivera AAC-ljud</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="239"/>
<source>Print DSP firmware</source>
<translation>Skriv ut firmware för DSP</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="243"/>
<source>Mute audio device</source>
<translation>Stäng av ljudet audioenheten</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="248"/>
<source>Cubic</source>
<translation>Kubisk</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="249"/>
<source>Linear</source>
<translation>Linjär</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="255"/>
<source>Volume curve</source>
<translation>Volymkurva</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="273"/>
<source>Audio device volume</source>
<translation>Ljudenhetens volym</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="276"/>
<source>Battery Settings</source>
<translation>Batteriinställningar</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="288"/>
<source>Battery percentage</source>
<translation>Batteriprocent</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="290"/>
<source>Charger plugged</source>
<translation>Laddaren är ansluten</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="295"/>
<source>SD Card Settings</source>
<translation>Inställningar för SD-kort</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="300"/>
<source>Enable virtual SD card</source>
<translation>Aktivera virtuellt SD-kort</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="304"/>
<source>Write protect virtual SD card</source>
<translation>Skrivskydd för virtuellt SD-kort</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="309"/>
<source>Interface</source>
<translation>Gränssnitt</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="309"/>
<source>User Interface settings</source>
<translation>Inställningar för användargränssnitt</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="310"/>
<source>General</source>
<translation>Allmänt</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="310"/>
<source>General emulator settings</source>
<translation>Allmänna inställningar för emulatorn</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="311"/>
<source>Graphics</source>
<translation>Grafik</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="311"/>
<source>Graphics emulation and output settings</source>
<translation>Inställningar för grafikemulering och utdata</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="312"/>
<source>Audio</source>
<translation>Ljud</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="312"/>
<source>Audio emulation and output settings</source>
<translation>Inställningar för ljudemulering och utdata</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="313"/>
<source>Battery</source>
<translation>Batteri</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="313"/>
<source>Battery emulation settings</source>
<translation>Inställningar för batteriemulering</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="314"/>
<source>SD Card</source>
<translation>SD-kort</translation>
</message>
<message>
<location filename="../../src/panda_qt/config_window.cpp" line="314"/>
<source>SD Card emulation settings</source>
<translation>Inställningar för emulering av SD-kort</translation>
</message>
<message>
<location filename="../../src/panda_qt/translations.cpp" line="75"/>
<source>Language change successful</source>
<translation>Språkändringen lyckades</translation>
</message>
<message>
<location filename="../../src/panda_qt/translations.cpp" line="76"/>
<source>Restart Panda3DS for the new language to be used.</source>
<translation>Starta om Panda3DS för att det nya språket ska kunna användas.</translation>
</message>
<message>
<location filename="../../src/panda_qt/translations.cpp" line="82"/>
<source>Language change failed</source>
<translation>Språkändringen misslyckades</translation>
</message>
<message>
<location filename="../../src/panda_qt/translations.cpp" line="83"/>
<source>The language you selected is not included in Panda3DS. If you&apos;re seeing this, someone messed up the language UI code...</source>
<translation>Det språk du valde ingår inte i Panda3DS. Om du ser detta, har någon rört till koden för språkgränssnittet...</translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="20"/>
<source>Alber</source>
<translation>Alber</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="38"/>
<source>File</source>
<translation>Arkiv</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="39"/>
<source>Emulation</source>
<translation>Emulering</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="40"/>
<source>Tools</source>
<translation>Verktyg</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="41"/>
<source>About</source>
<translation>Om</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="44"/>
<source>Load game</source>
<translation>Läs in spel</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="45"/>
<source>Load Lua script</source>
<translation>Läs in Lua-skript</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="46"/>
<source>Open Panda3DS folder</source>
<translation>Öppna Panda3DS-mappen</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="55"/>
<source>Pause</source>
<translation>Pausa</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="56"/>
<source>Resume</source>
<translation>Återuppta</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="57"/>
<source>Reset</source>
<translation>Starta om</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="58"/>
<source>Configure</source>
<translation>Konfigurera</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="64"/>
<source>Dump RomFS</source>
<translation>Dumpa RomFS</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="65"/>
<source>Open Lua Editor</source>
<translation>Öppna Lua-redigeraren</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="66"/>
<source>Open Cheats Editor</source>
<translation>Öppna fuskredigeraren</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="67"/>
<source>Open Patch Window</source>
<translation>Öppna patchfönstret</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="68"/>
<source>Open Shader Editor</source>
<translation>Öppna shader-redigeraren</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="69"/>
<source>Dump loaded DSP firmware</source>
<translation>Dumpa inläst DSP-firmware</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="78"/>
<source>About Panda3DS</source>
<translation>Om Panda3DS</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="195"/>
<source>Select 3DS ROM to load</source>
<translation>Välj 3DS ROM att läsa in</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="196"/>
<source>Nintendo 3DS ROMs (*.3ds *.cci *.cxi *.app *.ncch *.3dsx *.elf *.axf)</source>
<translation>Nintendo 3DS ROM (*.3ds *.cci *.cxi *.app *.ncch *.3dsx *.elf *.axf)</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="209"/>
<source>Select Lua script to load</source>
<translation>Välj Lua-skript som ska läsas in</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="209"/>
<source>Lua scripts (*.lua *.txt)</source>
<translation>Lua-skript (*.lua *.txt)</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="272"/>
<source>Select folder to dump RomFS files to</source>
<translation>Välj mapp för att dumpa RomFS-filer till</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="288"/>
<source>Invalid format for RomFS dumping</source>
<translation>Ogiltigt format för RomFS-dumpning</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="289"/>
<source>The currently loaded app is not in a format that supports RomFS</source>
<translation>Den aktuella appen är inte i ett format som stöder RomFS</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="292"/>
<location filename="../../src/panda_qt/main_window.cpp" line="323"/>
<location filename="../../src/panda_qt/main_window.cpp" line="336"/>
<source>OK</source>
<translation>Ok</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="299"/>
<source>No RomFS found</source>
<translation>Ingen RomFS hittades</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="299"/>
<source>No RomFS partition was found in the loaded app</source>
<translation>Ingen RomFS-partition hittades i den inlästa appen</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="305"/>
<source>Select file</source>
<translation>Välj fil</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="305"/>
<source>DSP firmware file (*.cdc)</source>
<translation>DSP firmware-fil (*.cdc)</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="320"/>
<source>No DSP firmware loaded</source>
<translation>Ingen firmware för DSP inläst</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="320"/>
<source>The currently loaded app has not uploaded a firmware to the DSP</source>
<translation>Den aktuella appen har inte skickat upp någon firmware till DSP:n</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="331"/>
<source>Failed to open output file</source>
<translation>Misslyckades med att öppna utdatafilen</translation>
</message>
<message>
<location filename="../../src/panda_qt/main_window.cpp" line="332"/>
<source>The currently loaded DSP firmware could not be written to the selected file. Please make sure you have permission to access this file</source>
<translation>Den aktuella DSP-firmware som lästes in kunde inte skrivas till den valda filen. Kontrollera att du har behörighet att komma åt den här filen</translation>
</message>
</context>
<context>
<name>PatchWindow</name>
<message>
<location filename="../../src/panda_qt/patch_window.cpp" line="15"/>
<source>ROM patcher</source>
<translation>ROM-patchare</translation>
</message>
<message>
<location filename="../../src/panda_qt/patch_window.cpp" line="23"/>
<source>Select input file</source>
<translation>Välj inmatningsfil</translation>
</message>
<message>
<location filename="../../src/panda_qt/patch_window.cpp" line="24"/>
<location filename="../../src/panda_qt/patch_window.cpp" line="36"/>
<source>Select</source>
<translation>Välj</translation>
</message>
<message>
<location filename="../../src/panda_qt/patch_window.cpp" line="35"/>
<location filename="../../src/panda_qt/patch_window.cpp" line="63"/>
<source>Select patch file</source>
<translation>Välj patchfil</translation>
</message>
<message>
<location filename="../../src/panda_qt/patch_window.cpp" line="47"/>
<source>Apply patch</source>
<translation>Applicera patch</translation>
</message>
<message>
<location filename="../../src/panda_qt/patch_window.cpp" line="56"/>
<source>Select file to patch</source>
<translation>Välj fil som ska patchas</translation>
</message>
<message>
<location filename="../../src/panda_qt/patch_window.cpp" line="56"/>
<location filename="../../src/panda_qt/patch_window.cpp" line="80"/>
<source>All files (*.*)</source>
<translation>Alla filer (*.*)</translation>
</message>
<message>
<location filename="../../src/panda_qt/patch_window.cpp" line="63"/>
<source>Patch files (*.ips *.ups *.bps)</source>
<translation>Patch-filer (*.ips *.ups *.bps)</translation>
</message>
<message>
<location filename="../../src/panda_qt/patch_window.cpp" line="71"/>
<source>Paths not provided correctly</source>
<translation>Sökvägar anges inte korrekt</translation>
</message>
<message>
<location filename="../../src/panda_qt/patch_window.cpp" line="71"/>
<source>Please provide paths for both the input file and the patch file</source>
<translation>Ange sökvägar för både indatafilen och patchfilen</translation>
</message>
<message>
<location filename="../../src/panda_qt/patch_window.cpp" line="80"/>
<source>Select file</source>
<translation>Välj fil</translation>
</message>
<message>
<location filename="../../src/panda_qt/patch_window.cpp" line="84"/>
<source>No output path</source>
<translation>Ingen sökväg för utmatning</translation>
</message>
<message>
<location filename="../../src/panda_qt/patch_window.cpp" line="84"/>
<source>No path was provided for the output file, no patching was done</source>
<translation>Ingen sökväg angavs för utdatafilen, ingen patchning gjordes</translation>
</message>
<message>
<location filename="../../src/panda_qt/patch_window.cpp" line="99"/>
<source>Unknown patch format</source>
<translation>Okänt patchformat</translation>
</message>
<message>
<location filename="../../src/panda_qt/patch_window.cpp" line="99"/>
<source>Unknown format for patch file. Currently IPS, UPS and BPS are supported</source>
<translation>Okänt format för patchfil. För närvarande stöds IPS, UPS och BPS</translation>
</message>
<message>
<location filename="../../src/panda_qt/patch_window.cpp" line="108"/>
<source>Failed to open input files</source>
<translation>Misslyckades med att öppna indatafiler</translation>
</message>
<message>
<location filename="../../src/panda_qt/patch_window.cpp" line="108"/>
<source>Make sure they&apos;re in a directory Panda3DS has access to</source>
<translation>Se till att de finns i en katalog som Panda3DS har tillgång till</translation>
</message>
<message>
<location filename="../../src/panda_qt/patch_window.cpp" line="135"/>
<source>Patching Success</source>
<translation>Patchning lyckades</translation>
</message>
<message>
<location filename="../../src/panda_qt/patch_window.cpp" line="135"/>
<source>Your file was patched successfully.</source>
<translation>Din fil patchades.</translation>
</message>
<message>
<location filename="../../src/panda_qt/patch_window.cpp" line="141"/>
<source>Checksum mismatch</source>
<translation>Kontrollsumman stämmer inte överens</translation>
</message>
<message>
<location filename="../../src/panda_qt/patch_window.cpp" line="142"/>
<source>Patch was applied successfully but a checksum mismatch was detected. The input or output files might not be correct</source>
<translation>Patchen applicerades men en avvikelse i kontrollsumman upptäcktes. Inmatnings- eller utdatafilerna kanske inte är korrekta</translation>
</message>
<message>
<location filename="../../src/panda_qt/patch_window.cpp" line="146"/>
<source>Patching error</source>
<translation>Fel vid patchning</translation>
</message>
<message>
<location filename="../../src/panda_qt/patch_window.cpp" line="146"/>
<source>An error occured while patching</source>
<translation>Ett fel uppstod vid patchning</translation>
</message>
</context>
<context>
<name>PatchWindow::PatchWindow</name>
<message>
<location filename="../../src/panda_qt/patch_window.cpp" line="153"/>
<source>OK</source>
<translation>Ok</translation>
</message>
</context>
<context>
<name>ShaderEditorWindow</name>
<message>
<location filename="../../src/panda_qt/shader_editor.cpp" line="26"/>
<source>Reload shader</source>
<translation>Läs om shader</translation>
</message>
</context>
<context>
<name>TextEditorWindow</name>
<message>
<location filename="../../src/panda_qt/text_editor.cpp" line="12"/>
<source>Lua Editor</source>
<translation>Lua-redigerare</translation>
</message>
<message>
<location filename="../../src/panda_qt/text_editor.cpp" line="27"/>
<source>Load script</source>
<translation>Läs in skript</translation>
</message>
</context>
</TS>

View file

@ -29,11 +29,11 @@ class ShaderEmitter : public Xbyak::CodeGenerator {
std::vector<u32> returnPCs;
// Vector value of (-0.0, -0.0, -0.0, -0.0) for negating vectors via pxor
Label negateVector;
Xbyak::Label negateVector;
// Vector value of (1.0, 1.0, 1.0, 1.0) for SLT(i)/SGE(i)
Label onesVector;
Xbyak::Label onesVector;
// Vector value of (0xFF, 0xFF, 0xFF, 0) for setting the w component to 0 in DP3
Label dp3Vector;
Xbyak::Label dp3Vector;
u32 recompilerPC = 0; // PC the recompiler is currently recompiling @
u32 loopLevel = 0; // The current loop nesting level (0 = not in a loop)
@ -47,7 +47,7 @@ class ShaderEmitter : public Xbyak::CodeGenerator {
bool codeHasExp2 = false;
// Whether to compile this shader using accurate, safe, non-IEEE multiplication (slow) or faster but less accurate mul
bool useSafeMUL = false;
Xbyak::Label log2Func, exp2Func;
Xbyak::Label emitLog2Func();
Xbyak::Label emitExp2Func();
@ -72,8 +72,8 @@ class ShaderEmitter : public Xbyak::CodeGenerator {
// Load register with number "srcReg" indexed by index "idx" into the xmm register "reg"
template <int sourceIndex>
void loadRegister(Xmm dest, const PICAShader& shader, u32 src, u32 idx, u32 operandDescriptor);
void storeRegister(Xmm source, const PICAShader& shader, u32 dest, u32 operandDescriptor);
void loadRegister(Xbyak::Xmm dest, const PICAShader& shader, u32 src, u32 idx, u32 operandDescriptor);
void storeRegister(Xbyak::Xmm source, const PICAShader& shader, u32 dest, u32 operandDescriptor);
const vec4f& getSourceRef(const PICAShader& shader, u32 src);
const vec4f& getDestRef(const PICAShader& shader, u32 dest);

View file

@ -2,39 +2,37 @@
#ifdef PANDA3DS_X64_HOST
#include "xbyak/xbyak.h"
using namespace Xbyak;
using namespace Xbyak::util;
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
#define PANDA3DS_MS_ABI
constexpr Reg32 arg1 = ecx; // register where first arg is stored
constexpr Reg32 arg2 = edx; // register where second arg is stored
constexpr Reg32 arg3 = r8d; // register where third arg is stored
constexpr Reg32 arg4 = r9d; // register where fourth arg is stored
constexpr Xbyak::Reg32 arg1 = Xbyak::util::ecx; // register where first arg is stored
constexpr Xbyak::Reg32 arg2 = Xbyak::util::edx; // register where second arg is stored
constexpr Xbyak::Reg32 arg3 = Xbyak::util::r8d; // register where third arg is stored
constexpr Xbyak::Reg32 arg4 = Xbyak::util::r9d; // register where fourth arg is stored
// Similar for floating point and vector arguemnts.
constexpr Xmm arg1f = xmm0;
constexpr Xmm arg2f = xmm1;
constexpr Xmm arg3f = xmm2;
constexpr Xmm arg4f = xmm3;
constexpr Xbyak::Xmm arg1f = Xbyak::util::xmm0;
constexpr Xbyak::Xmm arg2f = Xbyak::util::xmm1;
constexpr Xbyak::Xmm arg3f = Xbyak::util::xmm2;
constexpr Xbyak::Xmm arg4f = Xbyak::util::xmm3;
constexpr bool isWindows() { return true; }
#else // System V calling convention
#define PANDA3DS_SYSV_ABI
constexpr Reg32 arg1 = edi;
constexpr Reg32 arg2 = esi;
constexpr Reg32 arg3 = edx;
constexpr Reg32 arg4 = ecx;
constexpr Xbyak::Reg32 arg1 = Xbyak::util::edi;
constexpr Xbyak::Reg32 arg2 = Xbyak::util::esi;
constexpr Xbyak::Reg32 arg3 = Xbyak::util::edx;
constexpr Xbyak::Reg32 arg4 = Xbyak::util::ecx;
constexpr Xmm arg1f = xmm0;
constexpr Xmm arg2f = xmm1;
constexpr Xmm arg3f = xmm2;
constexpr Xmm arg4f = xmm3;
constexpr Xmm arg5f = xmm4;
constexpr Xmm arg6f = xmm5;
constexpr Xmm arg7f = xmm6;
constexpr Xmm arg8f = xmm7;
constexpr Xbyak::Xmm arg1f = Xbyak::util::xmm0;
constexpr Xbyak::Xmm arg2f = Xbyak::util::xmm1;
constexpr Xbyak::Xmm arg3f = Xbyak::util::xmm2;
constexpr Xbyak::Xmm arg4f = Xbyak::util::xmm3;
constexpr Xbyak::Xmm arg5f = Xbyak::util::xmm4;
constexpr Xbyak::Xmm arg6f = Xbyak::util::xmm5;
constexpr Xbyak::Xmm arg7f = Xbyak::util::xmm6;
constexpr Xbyak::Xmm arg8f = Xbyak::util::xmm7;
constexpr bool isWindows() { return false; }
#endif

View file

@ -6,156 +6,125 @@
#include <cmath>
#include <cstring>
#include "helpers.hpp"
namespace Floats {
/**
* Template class for converting arbitrary Pica float types to IEEE 754 32-bit single-precision
* floating point.
*
* When decoding, format is as follows:
* - The first `M` bits are the mantissa
* - The next `E` bits are the exponent
* - The last bit is the sign bit
*
* @todo Verify on HW if this conversion is sufficiently accurate.
*/
template <unsigned M, unsigned E>
struct Float {
public:
static Float<M, E> fromFloat32(float val) {
Float<M, E> ret;
ret.value = val;
return ret;
}
/**
* Template class for converting arbitrary Pica float types to IEEE 754 32-bit single-precision
* floating point.
*
* When decoding, format is as follows:
* - The first `M` bits are the mantissa
* - The next `E` bits are the exponent
* - The last bit is the sign bit
*
* @todo Verify on HW if this conversion is sufficiently accurate.
*/
template <unsigned M, unsigned E>
struct Float {
public:
static Float<M, E> fromFloat32(float val) {
Float<M, E> ret;
ret.value = val;
return ret;
}
static Float<M, E> fromRaw(u32 hex) {
Float<M, E> res;
static Float<M, E> fromRaw(u32 hex) {
Float<M, E> res;
const int width = M + E + 1;
const int bias = 128 - (1 << (E - 1));
int exponent = (hex >> M) & ((1 << E) - 1);
const unsigned mantissa = hex & ((1 << M) - 1);
const unsigned sign = (hex >> (E + M)) << 31;
const int width = M + E + 1;
const int bias = 128 - (1 << (E - 1));
int exponent = (hex >> M) & ((1 << E) - 1);
const unsigned mantissa = hex & ((1 << M) - 1);
const unsigned sign = (hex >> (E + M)) << 31;
if (hex & ((1 << (width - 1)) - 1)) {
if (exponent == (1 << E) - 1)
exponent = 255;
else
exponent += bias;
hex = sign | (mantissa << (23 - M)) | (exponent << 23);
}
else {
hex = sign;
}
if (hex & ((1 << (width - 1)) - 1)) {
if (exponent == (1 << E) - 1)
exponent = 255;
else
exponent += bias;
hex = sign | (mantissa << (23 - M)) | (exponent << 23);
} else {
hex = sign;
}
std::memcpy(&res.value, &hex, sizeof(float));
std::memcpy(&res.value, &hex, sizeof(float));
return res;
}
return res;
}
static Float<M, E> zero() {
return fromFloat32(0.f);
}
static Float<M, E> zero() { return fromFloat32(0.f); }
// Not recommended for anything but logging
float toFloat32() const {
return value;
}
// Not recommended for anything but logging
float toFloat32() const { return value; }
double toFloat64() const {
return static_cast<double>(value);
}
double toFloat64() const { return static_cast<double>(value); }
operator float() {
return toFloat32();
}
operator float() { return toFloat32(); }
operator double() {
return toFloat64();
}
operator double() { return toFloat64(); }
Float<M, E> operator*(const Float<M, E>& flt) const {
float result = value * flt.toFloat32();
// PICA gives 0 instead of NaN when multiplying by inf
if (std::isnan(result))
if (!std::isnan(value) && !std::isnan(flt.toFloat32()))
result = 0.f;
return Float<M, E>::fromFloat32(result);
}
Float<M, E> operator*(const Float<M, E>& flt) const {
float result = value * flt.toFloat32();
// PICA gives 0 instead of NaN when multiplying by inf
if (std::isnan(result))
if (!std::isnan(value) && !std::isnan(flt.toFloat32())) result = 0.f;
return Float<M, E>::fromFloat32(result);
}
Float<M, E> operator/(const Float<M, E>& flt) const {
return Float<M, E>::fromFloat32(toFloat32() / flt.toFloat32());
}
Float<M, E> operator/(const Float<M, E>& flt) const { return Float<M, E>::fromFloat32(toFloat32() / flt.toFloat32()); }
Float<M, E> operator+(const Float<M, E>& flt) const {
return Float<M, E>::fromFloat32(toFloat32() + flt.toFloat32());
}
Float<M, E> operator+(const Float<M, E>& flt) const { return Float<M, E>::fromFloat32(toFloat32() + flt.toFloat32()); }
Float<M, E> operator-(const Float<M, E>& flt) const {
return Float<M, E>::fromFloat32(toFloat32() - flt.toFloat32());
}
Float<M, E> operator-(const Float<M, E>& flt) const { return Float<M, E>::fromFloat32(toFloat32() - flt.toFloat32()); }
Float<M, E>& operator*=(const Float<M, E>& flt) {
value = operator*(flt).value;
return *this;
}
Float<M, E>& operator*=(const Float<M, E>& flt) {
value = operator*(flt).value;
return *this;
}
Float<M, E>& operator/=(const Float<M, E>& flt) {
value /= flt.toFloat32();
return *this;
}
Float<M, E>& operator/=(const Float<M, E>& flt) {
value /= flt.toFloat32();
return *this;
}
Float<M, E>& operator+=(const Float<M, E>& flt) {
value += flt.toFloat32();
return *this;
}
Float<M, E>& operator+=(const Float<M, E>& flt) {
value += flt.toFloat32();
return *this;
}
Float<M, E>& operator-=(const Float<M, E>& flt) {
value -= flt.toFloat32();
return *this;
}
Float<M, E>& operator-=(const Float<M, E>& flt) {
value -= flt.toFloat32();
return *this;
}
Float<M, E> operator-() const {
return Float<M, E>::fromFloat32(-toFloat32());
}
Float<M, E> operator-() const { return Float<M, E>::fromFloat32(-toFloat32()); }
bool operator<(const Float<M, E>& flt) const {
return toFloat32() < flt.toFloat32();
}
bool operator<(const Float<M, E>& flt) const { return toFloat32() < flt.toFloat32(); }
bool operator>(const Float<M, E>& flt) const {
return toFloat32() > flt.toFloat32();
}
bool operator>(const Float<M, E>& flt) const { return toFloat32() > flt.toFloat32(); }
bool operator>=(const Float<M, E>& flt) const {
return toFloat32() >= flt.toFloat32();
}
bool operator>=(const Float<M, E>& flt) const { return toFloat32() >= flt.toFloat32(); }
bool operator<=(const Float<M, E>& flt) const {
return toFloat32() <= flt.toFloat32();
}
bool operator<=(const Float<M, E>& flt) const { return toFloat32() <= flt.toFloat32(); }
bool operator==(const Float<M, E>& flt) const {
return toFloat32() == flt.toFloat32();
}
bool operator==(const Float<M, E>& flt) const { return toFloat32() == flt.toFloat32(); }
bool operator!=(const Float<M, E>& flt) const {
return toFloat32() != flt.toFloat32();
}
bool operator!=(const Float<M, E>& flt) const { return toFloat32() != flt.toFloat32(); }
private:
static constexpr unsigned MASK = (1 << (M + E + 1)) - 1;
static constexpr unsigned MANTISSA_MASK = (1 << M) - 1;
static constexpr unsigned EXPONENT_MASK = (1 << E) - 1;
private:
static constexpr unsigned MASK = (1 << (M + E + 1)) - 1;
static constexpr unsigned MANTISSA_MASK = (1 << M) - 1;
static constexpr unsigned EXPONENT_MASK = (1 << E) - 1;
// Stored as a regular float, merely for convenience
// TODO: Perform proper arithmetic on this!
float value;
};
// Stored as a regular float, merely for convenience
// TODO: Perform proper arithmetic on this!
float value;
};
using f24 = Float<16, 7>;
using f20 = Float<12, 7>;
using f16 = Float<10, 5>;
using f24 = Float<16, 7>;
using f20 = Float<12, 7>;
using f16 = Float<10, 5>;
} // namespace Floats
} // namespace Floats

View file

@ -89,6 +89,7 @@ class GPU {
PICA::Vertex getImmediateModeVertex();
void getAcceleratedDrawInfo(PICA::DrawAcceleration& accel, bool indexed);
public:
// 256 entries per LUT with each LUT as its own row forming a 2D image 256 * LUT_COUNT
// Encoded in PICA native format
@ -134,6 +135,8 @@ class GPU {
// Used for setting the size of the window we'll be outputting graphics to
void setOutputSize(u32 width, u32 height) { renderer->setOutputSize(width, height); }
// Used for notifying the renderer the screen layout has changed
void reloadScreenLayout() { renderer->reloadScreenLayout(); }
// TODO: Emulate the transfer engine & its registers
// Then this can be emulated by just writing the appropriate values there
@ -181,6 +184,7 @@ class GPU {
}
Renderer* getRenderer() { return renderer.get(); }
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
@ -189,8 +193,8 @@ class GPU {
ALWAYS_INLINE void setVsOutputMask(u32 val) {
val &= 0xffff;
// Avoid recomputing this if not necessary
// Avoid recomputing this if not necessary
if (oldVsOutputMask != val) [[unlikely]] {
oldVsOutputMask = val;

View file

@ -107,7 +107,7 @@ class PICAShader {
alignas(16) std::array<vec4f, 16> inputs; // Attributes passed to the shader
alignas(16) std::array<vec4f, 16> outputs;
alignas(16) vec4f dummy = vec4f({f24::zero(), f24::zero(), f24::zero(), f24::zero()}); // Dummy register used by the JIT
// We use a hashmap for matching 3DS shaders to their equivalent compiled code in our shader cache in the shader JIT
// We choose our hash type to be a 64-bit integer by default, as the collision chance is very tiny and generating it is decently optimal
// Ideally we want to be able to support multiple different types of hash depending on compilation settings, but let's get this working first
@ -234,7 +234,7 @@ class PICAShader {
public:
static constexpr size_t maxInstructionCount = 4096;
std::array<u32, maxInstructionCount> loadedShader; // Currently loaded & active shader
std::array<u32, maxInstructionCount> loadedShader; // Currently loaded & active shader
PICAShader(ShaderType type) : type(type) {}

View file

@ -2,8 +2,8 @@
namespace PICA::ShaderGen {
// Graphics API this shader is targetting
enum class API { GL, GLES, Vulkan };
enum class API { GL, GLES, Vulkan, Metal };
// Shading language to use (Only GLSL for the time being)
enum class Language { GLSL };
} // namespace PICA::ShaderGen
// Shading language to use
enum class Language { GLSL, MSL };
} // namespace PICA::ShaderGen

View file

@ -0,0 +1,9 @@
#pragma once
#if defined(__LIBRETRO__) && defined(USE_LIBRETRO_AUDIO_DEVICE)
#include "audio/libretro_audio_device.hpp"
using AudioDevice = LibretroAudioDevice;
#else
#include "audio/miniaudio_device.hpp"
using AudioDevice = MiniAudioDevice;
#endif

View file

@ -0,0 +1,36 @@
#pragma once
#include <array>
#include "config.hpp"
#include "helpers.hpp"
#include "ring_buffer.hpp"
class AudioDeviceInterface {
protected:
static constexpr usize maxFrameCount = 0x2000;
using Samples = Common::RingBuffer<s16, maxFrameCount * 2>;
using RenderBatchCallback = usize (*)(const s16*, usize);
Samples* samples = nullptr;
const AudioDeviceConfig& audioSettings;
// Store the last stereo sample we output. We play this when underruning to avoid pops.
std::array<s16, 2> lastStereoSample{};
public:
AudioDeviceInterface(Samples* samples, const AudioDeviceConfig& audioSettings) : samples(samples), audioSettings(audioSettings) {}
bool running = false;
Samples* getSamples() { return samples; }
// If safe is on, we create a null audio device
virtual void init(Samples& samples, bool safe = false) = 0;
virtual void close() = 0;
virtual void start() = 0;
virtual void stop() = 0;
// Only used for audio devices that render multiple audio frames in one go, eg the libretro audio device.
virtual void renderBatch(RenderBatchCallback callback) {}
};

View file

@ -0,0 +1,29 @@
#pragma once
#include "helpers.hpp"
struct Dsp1 {
// All sizes are in bytes unless otherwise specified
u8 signature[0x100];
u8 magic[4];
u32 size;
u8 codeMemLayout;
u8 dataMemLayout;
u8 pad[3];
u8 specialType;
u8 segmentCount;
u8 flags;
u32 specialStart;
u32 specialSize;
u64 zeroBits;
struct Segment {
u32 offs; // Offset of the segment data
u32 dspAddr; // Start of the segment in 16-bit units
u32 size;
u8 pad[3];
u8 type;
u8 hash[0x20];
};
Segment segments[10];
};

View file

@ -1,8 +1,5 @@
#pragma once
#include <array>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <vector>
@ -63,6 +60,24 @@ namespace Audio {
Samples& getSamples() { return sampleBuffer; }
virtual void setAudioEnabled(bool enable) { audioEnabled = enable; }
virtual Type getType() = 0;
virtual void* getRegisters() { return nullptr; }
// Read a word from program memory. By default, just perform a regular DSP RAM read for the HLE cores
// The LLE cores translate the address, accounting for the way Teak memory is mapped
virtual u16 readProgramWord(u32 address) {
u8* dspRam = getDspMemory();
auto readByte = [&](u32 addr) {
if (addr >= 256_KB) return u8(0);
return dspRam[addr];
};
u16 lsb = u16(readByte(address));
u16 msb = u16(readByte(address + 1));
return u16(lsb | (msb << 8));
}
};
std::unique_ptr<DSPCore> makeDSPCore(EmulatorConfig& config, Memory& mem, Scheduler& scheduler, DSPService& dspService);

View file

@ -69,7 +69,9 @@ namespace Audio {
// In order to save up on CPU time.
uint enabledMixStages = 0;
u32 samplePosition; // Sample number into the current audio buffer
u32 samplePosition; // Sample number into the current audio buffer
u32 currentBufferPaddr; // Physical address of current audio buffer
float rateMultiplier;
u16 syncCount;
u16 currentBufferID;
@ -213,6 +215,7 @@ namespace Audio {
void runAudioFrame(u64 eventTimestamp) override;
u8* getDspMemory() override { return dspRam.rawMemory.data(); }
DSPCore::Type getType() override { return DSPCore::Type::HLE; }
u16 recvData(u32 regId) override;
bool recvDataIsReady(u32 regId) override { return true; } // Treat data as always ready

View file

@ -0,0 +1,61 @@
#pragma once
#include <cstring>
#include "audio/audio_device_interface.hpp"
class LibretroAudioDevice final : public AudioDeviceInterface {
bool initialized = false;
public:
LibretroAudioDevice(const AudioDeviceConfig& audioSettings) : AudioDeviceInterface(nullptr, audioSettings), initialized(false) {
running = false;
}
void init(Samples& samples, bool safe = false) override {
this->samples = &samples;
initialized = true;
running = false;
}
void close() override {
initialized = false;
running = false;
};
void start() override { running = true; }
void stop() override { running = false; };
void renderBatch(RenderBatchCallback callback) override {
if (running) {
static constexpr usize sampleRate = 32768; // 3DS samples per second
static constexpr usize frameCount = sampleRate / 60; // 3DS samples per video frame
static constexpr usize channelCount = 2;
static s16 audioBuffer[frameCount * channelCount];
usize samplesWritten = 0;
samplesWritten += samples->pop(audioBuffer, frameCount * channelCount);
// Get the last sample for underrun handling
if (samplesWritten != 0) {
std::memcpy(&lastStereoSample[0], &audioBuffer[(samplesWritten - 1) * 2], sizeof(lastStereoSample));
}
// If underruning, copy the last output sample
{
s16* pointer = &audioBuffer[samplesWritten * 2];
s16 l = lastStereoSample[0];
s16 r = lastStereoSample[1];
for (usize i = samplesWritten; i < frameCount; i++) {
*pointer++ = l;
*pointer++ = r;
}
}
callback(audioBuffer, sizeof(audioBuffer) / (channelCount * sizeof(s16)));
}
}
bool isInitialized() const { return initialized; }
};

View file

@ -3,39 +3,31 @@
#include <string>
#include <vector>
#include "config.hpp"
#include "helpers.hpp"
#include "audio/audio_device_interface.hpp"
#include "miniaudio.h"
#include "ring_buffer.hpp"
class MiniAudioDevice {
using Samples = Common::RingBuffer<ma_int16, 0x2000 * 2>;
class MiniAudioDevice final : public AudioDeviceInterface {
static constexpr ma_uint32 sampleRate = 32768; // 3DS sample rate
static constexpr ma_uint32 channelCount = 2; // Audio output is stereo
bool initialized = false;
ma_device device;
ma_context context;
ma_device_config deviceConfig;
Samples* samples = nullptr;
const AudioDeviceConfig& audioSettings;
bool initialized = false;
bool running = false;
// Store the last stereo sample we output. We play this when underruning to avoid pops.
std::array<s16, 2> lastStereoSample;
std::vector<std::string> audioDevices;
public:
MiniAudioDevice(const AudioDeviceConfig& audioSettings);
// If safe is on, we create a null audio device
void init(Samples& samples, bool safe = false);
void close();
void init(Samples& samples, bool safe = false) override;
void close() override;
void start();
void stop();
void start() override;
void stop() override;
bool isInitialized() const { return initialized; }
};

View file

@ -30,6 +30,7 @@ namespace Audio {
void runAudioFrame(u64 eventTimestamp) override;
u8* getDspMemory() override { return dspRam.data(); }
DSPCore::Type getType() override { return DSPCore::Type::Null; }
u16 recvData(u32 regId) override;
bool recvDataIsReady(u32 regId) override { return true; } // Treat data as always ready

View file

@ -15,7 +15,7 @@ namespace Audio {
bool signalledData;
bool signalledSemaphore;
uint audioFrameIndex = 0; // Index in our audio frame
uint audioFrameIndex = 0; // Index in our audio frame
std::array<s16, 160 * 2> audioFrame;
// Get a pointer to a data memory address
@ -90,6 +90,9 @@ namespace Audio {
void setAudioEnabled(bool enable) override;
u8* getDspMemory() override { return teakra.GetDspMemory().data(); }
void* getRegisters() override;
DSPCore::Type getType() override { return DSPCore::Type::Teakra; }
u16 readProgramWord(u32 address) override { return teakra.ProgramRead(address); }
u16 recvData(u32 regId) override { return teakra.RecvData(regId); }
bool recvDataIsReady(u32 regId) override { return teakra.RecvDataIsReady(regId); }

View file

@ -23,7 +23,7 @@ namespace Common {
// pc: program counter of the instruction to disassemble
// bytes: Byte representation of instruction
// buffer: text buffer to output the disassembly too
usize disassemble(std::string& buffer, u32 pc, std::span<u8> bytes, u64 offset = 0) {
usize disassemble(std::string& buffer, u32 pc, std::span<const u8> bytes, u64 offset = 0) {
if (!initialized) {
buffer = "Capstone was not properly initialized";
return 0;

View file

@ -2,6 +2,7 @@
#include <filesystem>
#include <string>
#include "screen_layout.hpp"
#include "audio/dsp_core.hpp"
#include "frontend_settings.hpp"
#include "renderer.hpp"
@ -55,9 +56,23 @@ struct EmulatorConfig {
static constexpr bool audioEnabledDefault = false;
#endif
// We default to OpenGL on all platforms other than iOS
#if defined(PANDA3DS_IOS)
static constexpr RendererType rendererDefault = RendererType::Metal;
#else
static constexpr RendererType rendererDefault = RendererType::OpenGL;
#endif
static constexpr bool hashTexturesDefault = false;
bool shaderJitEnabled = shaderJitDefault;
bool useUbershaders = ubershaderDefault;
bool accelerateShaders = accelerateShadersDefault;
bool hashTextures = hashTexturesDefault;
ScreenLayout::Layout screenLayout = ScreenLayout::Layout::Default;
float topScreenSize = 0.5;
bool accurateShaderMul = false;
bool discordRpcEnabled = false;
@ -65,11 +80,12 @@ struct EmulatorConfig {
bool forceShadergenForLights = true;
int lightShadergenThreshold = 1;
RendererType rendererType = RendererType::OpenGL;
RendererType rendererType = rendererDefault;
Audio::DSPCore::Type dspType = Audio::DSPCore::Type::HLE;
bool sdCardInserted = true;
bool sdWriteProtected = false;
bool circlePadProEnabled = true;
bool usePortableBuild = false;
bool audioEnabled = audioEnabledDefault;
@ -116,4 +132,4 @@ struct EmulatorConfig {
static LanguageCodes languageCodeFromString(std::string inString);
static const char* languageCodeToString(LanguageCodes code);
};
};

View file

@ -181,5 +181,7 @@ class CPU {
void addTicks(u64 ticks) { env.AddTicks(ticks); }
void clearCache() { jit->ClearCache(); }
void clearCacheRange(u32 start, u32 size) { jit->InvalidateCacheRange(start, size); }
void runFrame();
};

View file

@ -7,8 +7,8 @@
#include <span>
#include "PICA/gpu.hpp"
#include "audio/audio_device.hpp"
#include "audio/dsp_core.hpp"
#include "audio/miniaudio_device.hpp"
#include "cheats.hpp"
#include "config.hpp"
#include "cpu.hpp"
@ -48,14 +48,14 @@ class Emulator {
Scheduler scheduler;
Crypto::AESEngine aesEngine;
MiniAudioDevice audioDevice;
AudioDevice audioDevice;
Cheats cheats;
public:
static constexpr u32 width = 400;
static constexpr u32 height = 240 * 2; // * 2 because 2 screens
ROMType romType = ROMType::None;
bool running = false; // Is the emulator running a game?
bool running = false; // Is the emulator running a game?
private:
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
@ -115,17 +115,24 @@ class Emulator {
RomFS::DumpingResult dumpRomFS(const std::filesystem::path& path);
void setOutputSize(u32 width, u32 height) { gpu.setOutputSize(width, height); }
void reloadScreenLayout() { gpu.reloadScreenLayout(); }
void deinitGraphicsContext() { gpu.deinitGraphicsContext(); }
// Reloads some settings that require special handling, such as audio enable
void reloadSettings();
CPU& getCPU() { return cpu; }
Memory& getMemory() { return memory; }
Kernel& getKernel() { return kernel; }
Scheduler& getScheduler() { return scheduler; }
Audio::DSPCore* getDSP() { return dsp.get(); }
EmulatorConfig& getConfig() { return config; }
Cheats& getCheats() { return cheats; }
ServiceManager& getServiceManager() { return kernel.getServiceManager(); }
LuaManager& getLua() { return lua; }
Scheduler& getScheduler() { return scheduler; }
Memory& getMemory() { return memory; }
AudioDeviceInterface& getAudioDevice() { return audioDevice; }
RendererType getRendererType() const { return config.rendererType; }
Renderer* getRenderer() { return gpu.getRenderer(); }

View file

@ -11,6 +11,7 @@ struct FrontendSettings {
Dark = 2,
GreetingsCat = 3,
Cream = 4,
Oled = 5,
};
// Different panda-themed window icons
@ -20,6 +21,7 @@ struct FrontendSettings {
Rnap = 2,
Rcow = 3,
SkyEmu = 4,
Runpog = 5,
};
Theme theme = Theme::Dark;

View file

@ -271,7 +271,7 @@ class ArchiveBase {
bool isSafeTextPath(const FSPath& path) {
if (path.type == PathType::UTF16) {
return isPathSafe<PathType::UTF16>(path);
} else if (path.type == PathType::ASCII){
} else if (path.type == PathType::ASCII) {
return isPathSafe<PathType::ASCII>(path);
}

View file

@ -2,11 +2,13 @@
#include "archive_base.hpp"
class ExtSaveDataArchive : public ArchiveBase {
public:
ExtSaveDataArchive(Memory& mem, const std::string& folder, bool isShared = false) : ArchiveBase(mem),
isShared(isShared), backingFolder(folder) {}
public:
ExtSaveDataArchive(Memory& mem, const std::string& folder, bool isShared = false) : ArchiveBase(mem), isShared(isShared), backingFolder(folder) {}
u64 getFreeBytes() override { Helpers::panic("ExtSaveData::GetFreeBytes unimplemented"); return 0; }
u64 getFreeBytes() override {
Helpers::panic("ExtSaveData::GetFreeBytes unimplemented");
return 0;
}
std::string name() override { return "ExtSaveData::" + backingFolder; }
HorizonResult createDirectory(const FSPath& path) override;
@ -29,5 +31,5 @@ public:
std::string getExtSaveDataPathFromBinary(const FSPath& path);
bool isShared = false;
std::string backingFolder; // Backing folder for the archive. Can be NAND, Gamecard or SD depending on the archive path.
std::string backingFolder; // Backing folder for the archive. Can be NAND, Gamecard or SD depending on the archive path.
};

View file

@ -2,10 +2,13 @@
#include "archive_base.hpp"
class NCCHArchive : public ArchiveBase {
public:
public:
NCCHArchive(Memory& mem) : ArchiveBase(mem) {}
u64 getFreeBytes() override { Helpers::panic("NCCH::GetFreeBytes unimplemented"); return 0; }
u64 getFreeBytes() override {
Helpers::panic("NCCH::GetFreeBytes unimplemented");
return 0;
}
std::string name() override { return "NCCH"; }
HorizonResult createFile(const FSPath& path, u64 size) override;

View file

@ -2,7 +2,7 @@
#include "archive_base.hpp"
class SaveDataArchive : public ArchiveBase {
public:
public:
SaveDataArchive(Memory& mem) : ArchiveBase(mem) {}
u64 getFreeBytes() override { return 32_MB; }
@ -27,6 +27,6 @@ public:
// Returns whether the cart has save data or not
bool cartHasSaveData() {
auto cxi = mem.getCXI();
return (cxi != nullptr && cxi->hasSaveData()); // We need to have a CXI file with more than 0 bytes of save data
return (cxi != nullptr && cxi->hasSaveData()); // We need to have a CXI file with more than 0 bytes of save data
}
};

View file

@ -2,7 +2,7 @@
#include "archive_base.hpp"
class SelfNCCHArchive : public ArchiveBase {
public:
public:
SelfNCCHArchive(Memory& mem) : ArchiveBase(mem) {}
u64 getFreeBytes() override { return 0; }

View file

@ -3,6 +3,7 @@
class UserSaveDataArchive : public ArchiveBase {
u32 archiveID;
public:
UserSaveDataArchive(Memory& mem, u32 archiveID) : ArchiveBase(mem), archiveID(archiveID) {}

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -19,7 +19,7 @@ class Emulator;
namespace httplib {
class Server;
struct Response;
}
} // namespace httplib
// Wrapper for httplib::Response that allows the HTTP server to wait for the response to be ready
struct DeferredResponseWrapper {
@ -63,7 +63,7 @@ struct HttpServer {
std::thread httpServerThread;
std::queue<std::unique_ptr<HttpAction>> actionQueue;
std::mutex actionQueueMutex;
std::unique_ptr<HttpAction> currentStepAction {};
std::unique_ptr<HttpAction> currentStepAction{};
std::map<std::string, u32> keyMap;
bool paused = false;

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,10 @@
#pragma once
#include <filesystem>
#include <fstream>
#include <map>
#include <optional>
#include <toml.hpp>
#include <unordered_map>
#include "helpers.hpp"
@ -15,8 +20,108 @@ struct InputMappings {
}
void setMapping(Scancode scancode, u32 key) { container[scancode] = key; }
template <typename ScancodeToString>
void serialize(const std::filesystem::path& path, const std::string& frontend, ScancodeToString scancodeToString) const {
toml::basic_value<toml::preserve_comments, std::map> data;
std::error_code error;
if (std::filesystem::exists(path, error)) {
try {
data = toml::parse<toml::preserve_comments, std::map>(path);
} catch (const std::exception& ex) {
Helpers::warn("Exception trying to parse mappings file. Exception: %s\n", ex.what());
return;
}
} else {
if (error) {
Helpers::warn("Filesystem error accessing %s (error: %s)\n", path.string().c_str(), error.message().c_str());
}
printf("Saving new mappings file %s\n", path.string().c_str());
}
data["Metadata"]["Name"] = name.empty() ? "Unnamed Mappings" : name;
data["Metadata"]["Device"] = device.empty() ? "Unknown Device" : device;
data["Metadata"]["Frontend"] = frontend;
data["Mappings"] = toml::table{};
for (const auto& [scancode, key] : container) {
const std::string& keyName = HID::Keys::keyToName(key);
if (!data["Mappings"].contains(keyName)) {
data["Mappings"][keyName] = toml::array{};
}
data["Mappings"][keyName].push_back(scancodeToString(scancode));
}
std::ofstream file(path, std::ios::out);
file << data;
}
template <typename ScancodeFromString>
static std::optional<InputMappings> deserialize(
const std::filesystem::path& path, const std::string& wantFrontend, ScancodeFromString stringToScancode
) {
toml::basic_value<toml::preserve_comments, std::map> data;
std::error_code error;
if (!std::filesystem::exists(path, error)) {
if (error) {
Helpers::warn("Filesystem error accessing %s (error: %s)\n", path.string().c_str(), error.message().c_str());
}
return std::nullopt;
}
InputMappings mappings;
try {
data = toml::parse<toml::preserve_comments, std::map>(path);
const auto metadata = toml::find(data, "Metadata");
mappings.name = toml::find_or<std::string>(metadata, "Name", "Unnamed Mappings");
mappings.device = toml::find_or<std::string>(metadata, "Device", "Unknown Device");
std::string haveFrontend = toml::find_or<std::string>(metadata, "Frontend", "Unknown Frontend");
bool equal = std::equal(haveFrontend.begin(), haveFrontend.end(), wantFrontend.begin(), wantFrontend.end(), [](char a, char b) {
return std::tolower(a) == std::tolower(b);
});
if (!equal) {
Helpers::warn(
"Mappings file %s was created for frontend %s, but we are using frontend %s\n", path.string().c_str(), haveFrontend.c_str(),
wantFrontend.c_str()
);
return std::nullopt;
}
} catch (const std::exception& ex) {
Helpers::warn("Exception trying to parse config file. Exception: %s\n", ex.what());
return std::nullopt;
}
const auto& mappingsTable = toml::find_or<toml::table>(data, "Mappings", toml::table{});
for (const auto& [keyName, scancodes] : mappingsTable) {
for (const auto& scancodeVal : scancodes.as_array()) {
std::string scancodeStr = scancodeVal.as_string();
mappings.setMapping(stringToScancode(scancodeStr), HID::Keys::nameToKey(keyName));
}
}
return mappings;
}
static InputMappings defaultKeyboardMappings();
auto begin() { return container.begin(); }
auto end() { return container.end(); }
auto begin() const { return container.begin(); }
auto end() const { return container.end(); }
private:
Container container;
std::string name;
std::string device;
};

9
include/ios_driver.h Normal file
View file

@ -0,0 +1,9 @@
#pragma once
#include <Foundation/Foundation.h>
#include <QuartzCore/QuartzCore.h>
#include <stdint.h>
void iosCreateEmulator();
void iosLoadROM(NSString* pathNS);
void iosRunFrame(CAMetalLayer* layer);
void iosSetOutputSize(uint32_t width, uint32_t height);

View file

@ -15,6 +15,7 @@
#include "services/service_manager.hpp"
class CPU;
class LuaManager;
struct Scheduler;
class Kernel {
@ -47,12 +48,12 @@ class Kernel {
Handle currentProcess;
Handle mainThread;
int currentThreadIndex;
Handle srvHandle; // Handle for the special service manager port "srv:"
Handle errorPortHandle; // Handle for the err:f port used for displaying errors
Handle srvHandle; // Handle for the special service manager port "srv:"
Handle errorPortHandle; // Handle for the err:f port used for displaying errors
u32 arbiterCount;
u32 threadCount; // How many threads in our thread pool have been used as of now (Up to 32)
u32 aliveThreadCount; // How many of these threads are actually alive?
u32 threadCount; // How many threads in our thread pool have been used as of now (Up to 32)
u32 aliveThreadCount; // How many of these threads are actually alive?
ServiceManager serviceManager;
// Top 8 bits are the major version, bottom 8 are the minor version
@ -65,10 +66,10 @@ class Kernel {
Handle makeProcess(u32 id);
Handle makePort(const char* name);
Handle makeSession(Handle port);
Handle makeThread(u32 entrypoint, u32 initialSP, u32 priority, ProcessorID 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);
public:
public:
// Needs to be public to be accessible to the APT/HID services
Handle makeEvent(ResetType resetType, Event::CallbackType callback = Event::CallbackType::None);
// Needs to be public to be accessible to the APT/DSP services
@ -175,6 +176,8 @@ public:
void svcSignalEvent();
void svcSetTimer();
void svcSleepThread();
void svcInvalidateInstructionCacheRange();
void svcInvalidateEntireInstructionCache();
void connectToPort();
void outputDebugString();
void waitSynchronization1();
@ -196,8 +199,8 @@ public:
void closeDirectory(u32 messagePointer, Handle directory);
void readDirectory(u32 messagePointer, Handle directory);
public:
Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config);
public:
Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config, LuaManager& lua);
void initializeFS() { return serviceManager.initializeFS(); }
void setVersion(u8 major, u8 minor);
void serviceSVC(u32 svc);
@ -222,9 +225,7 @@ public:
return handleCounter++;
}
std::vector<KernelObject>& getObjects() {
return objects;
}
std::vector<KernelObject>& getObjects() { return objects; }
// Get pointer to the object with the specified handle
KernelObject* getObject(Handle handle) {
@ -250,4 +251,16 @@ public:
void sendGPUInterrupt(GPUInterrupt type) { serviceManager.sendGPUInterrupt(type); }
void clearInstructionCache();
void clearInstructionCacheRange(u32 start, u32 size);
u32 getSharedFontVaddr();
// For debuggers: Returns information about the main process' (alive) threads in a vector for the frontend to display
std::vector<Thread> getMainProcessThreads();
// For debuggers: Sets the entrypoint and initial SP for the main thread (thread 0) so that the debugger can display them
void setMainThreadEntrypointAndSP(u32 entrypoint, u32 initialSP) {
auto& t = threads[0];
t.entrypoint = entrypoint;
t.initialSP = initialSP;
}
};

View file

@ -19,7 +19,7 @@ struct ResourceLimitValues {
// APPLICATION resource limit
static constexpr ResourceLimitValues appResourceLimits = {
.maxPriority = 0x18,
.maxCommit = 0x4000000,
.maxCommit = 64_MB + 16_MB, // We're currently giving 80MB to all apps. TODO: Implement extended memory properly
.maxThreads = 0x20,
.maxEvents = 0x20,
.maxMutexes = 0x20,
@ -33,7 +33,7 @@ static constexpr ResourceLimitValues appResourceLimits = {
// SYS_APPLET resource limit
static constexpr ResourceLimitValues sysAppletResourceLimits = {
.maxPriority = 0x4,
.maxCommit = 0x5E00000,
.maxCommit = 0x5E00000 - 16_MB,
.maxThreads = 0x1D,
.maxEvents = 0xB,
.maxMutexes = 0x8,

View file

@ -47,6 +47,9 @@ class LuaManager {
signalEventInternal(e);
}
}
bool signalInterceptedService(const std::string& service, u32 function, u32 messagePointer, int callbackRef);
void removeInterceptedService(const std::string& service, u32 function, int callbackRef);
};
#else // Lua not enabled, Lua manager does nothing
@ -60,5 +63,7 @@ class LuaManager {
void loadString(const std::string& code) {}
void reset() {}
void signalEvent(LuaEvent e) {}
bool signalInterceptedService(const std::string& service, u32 function, u32 messagePointer, int callbackRef) { return false; }
void removeInterceptedService(const std::string& service, u32 function, int callbackRef) {}
};
#endif

View file

@ -132,7 +132,7 @@ public:
static constexpr u32 totalPageCount = 1 << (32 - pageShift);
static constexpr u32 FCRAM_SIZE = u32(128_MB);
static constexpr u32 FCRAM_APPLICATION_SIZE = u32(64_MB);
static constexpr u32 FCRAM_APPLICATION_SIZE = u32(80_MB);
static constexpr u32 FCRAM_PAGE_COUNT = FCRAM_SIZE / pageSize;
static constexpr u32 FCRAM_APPLICATION_PAGE_COUNT = FCRAM_APPLICATION_SIZE / pageSize;

View file

@ -13,4 +13,4 @@ namespace Helpers {
static constexpr void static_for(Func&& f) {
static_for_impl<T, Begin>(std::forward<Func>(f), std::make_integer_sequence<T, End - Begin>{});
}
}
} // namespace Helpers

View file

@ -16,6 +16,7 @@
#include "emulator.hpp"
#include "frontend_settings.hpp"
#include "panda_qt/input_window.hpp"
class ConfigWindow : public QDialog {
Q_OBJECT
@ -30,8 +31,9 @@ class ConfigWindow : public QDialog {
QTextEdit* helpText = nullptr;
QListWidget* widgetList = nullptr;
QStackedWidget* widgetContainer = nullptr;
InputWindow* inputWindow = nullptr;
static constexpr size_t settingWidgetCount = 6;
static constexpr size_t settingWidgetCount = 7;
std::array<QString, settingWidgetCount> helpTexts;
// The config class holds a copy of the emulator config which it edits and sends
@ -52,6 +54,7 @@ class ConfigWindow : public QDialog {
~ConfigWindow();
EmulatorConfig& getConfig() { return config; }
InputWindow* getInputWindow() { return inputWindow; }
private:
Emulator* emu;

View file

@ -0,0 +1,45 @@
#pragma once
#include <QLineEdit>
#include <QListWidget>
#include <QPlainTextEdit>
#include <QScrollBar>
#include <QWidget>
#include "capstone.hpp"
#include "emulator.hpp"
#include "panda_qt/disabled_widget_overlay.hpp"
class CPUDebugger : public QWidget {
Q_OBJECT
Emulator* emu;
QListWidget* disasmListWidget;
QScrollBar* verticalScrollBar;
QPlainTextEdit* registerTextEdit;
QTimer* updateTimer;
QLineEdit* addressInput;
DisabledWidgetOverlay* disabledOverlay;
bool enabled = false;
bool followPC = false;
Common::CapstoneDisassembler disassembler;
public:
CPUDebugger(Emulator* emulator, QWidget* parent = nullptr);
void enable();
void disable();
private:
// Update the state of the disassembler. Qt events should always call update, not updateDisasm/updateRegister
// As update properly handles thread safety
void update();
void updateDisasm();
void updateRegisters();
void scrollToPC();
bool eventFilter(QObject* obj, QEvent* event) override;
void showEvent(QShowEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
void keyPressEvent(QKeyEvent* event) override;
};

View file

@ -0,0 +1,28 @@
#pragma once
#include <QtWidgets>
class DisabledWidgetOverlay : public QWidget {
Q_OBJECT
public:
DisabledWidgetOverlay(QWidget *parent = nullptr, QString overlayText = tr("This widget is disabled")) : text(overlayText), QWidget(parent) {
setVisible(false);
}
private:
QString text;
void paintEvent(QPaintEvent *) override {
QPainter painter = QPainter(this);
painter.fillRect(rect(), QColor(60, 60, 60, 128));
painter.setPen(Qt::gray);
QFont font = painter.font();
font.setBold(true);
font.setPointSize(18);
painter.setFont(font);
painter.drawText(rect(), Qt::AlignCenter, text);
}
};

View file

@ -0,0 +1,47 @@
#pragma once
#include <QLineEdit>
#include <QListWidget>
#include <QPlainTextEdit>
#include <QScrollBar>
#include <QWidget>
#include "emulator.hpp"
#include "panda_qt/disabled_widget_overlay.hpp"
class DSPDebugger : public QWidget {
Q_OBJECT
Emulator* emu;
QListWidget* disasmListWidget;
QScrollBar* verticalScrollBar;
QPlainTextEdit* registerTextEdit;
QTimer* updateTimer;
QLineEdit* addressInput;
DisabledWidgetOverlay* disabledOverlay;
DisabledWidgetOverlay* disabledRegisterEditOverlay;
bool enabled = false;
bool followPC = false;
public:
DSPDebugger(Emulator* emulator, QWidget* parent = nullptr);
void enable();
void disable();
private:
// Get the full PC value of the DSP, including the current progrma page value
u32 getPC();
// Update the state of the disassembler. Qt events should always call update, not updateDisasm/updateRegister
// As update properly handles thread safety
void update();
void updateDisasm();
void updateRegisters();
void scrollToPC();
bool eventFilter(QObject* obj, QEvent* event) override;
void showEvent(QShowEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
void keyPressEvent(QKeyEvent* event) override;
};

View file

@ -0,0 +1,32 @@
#pragma once
#include <QDialog>
#include <QKeySequence>
#include <QMap>
#include <QPushButton>
#include "input_mappings.hpp"
class InputWindow : public QDialog {
Q_OBJECT
public:
InputWindow(QWidget* parent = nullptr);
void loadFromMappings(const InputMappings& mappings);
void applyToMappings(InputMappings& mappings) const;
signals:
void mappingsChanged();
protected:
bool eventFilter(QObject* obj, QEvent* event) override;
private:
QMap<QString, QPushButton*> buttonMap;
QMap<QString, QKeySequence> keyMappings;
QString waitingForAction;
void startKeyCapture(const QString& action);
};

View file

@ -17,10 +17,13 @@
#include "panda_qt/about_window.hpp"
#include "panda_qt/cheats_window.hpp"
#include "panda_qt/config_window.hpp"
#include "panda_qt/cpu_debugger.hpp"
#include "panda_qt/dsp_debugger.hpp"
#include "panda_qt/patch_window.hpp"
#include "panda_qt/screen.hpp"
#include "panda_qt/shader_editor.hpp"
#include "panda_qt/text_editor.hpp"
#include "panda_qt/thread_debugger.hpp"
#include "services/hid.hpp"
struct CheatMessage {
@ -40,7 +43,6 @@ class MainWindow : public QMainWindow {
Pause,
Resume,
TogglePause,
DumpRomFS,
PressKey,
ReleaseKey,
SetCirclePadX,
@ -109,6 +111,9 @@ class MainWindow : public QMainWindow {
TextEditorWindow* luaEditor;
PatchWindow* patchWindow;
ShaderEditorWindow* shaderEditor;
CPUDebugger* cpuDebugger;
DSPDebugger* dspDebugger;
ThreadDebugger* threadDebugger;
// We use SDL's game controller API since it's the sanest API that supports as many controllers as possible
SDL_GameController* gameController = nullptr;
@ -128,6 +133,9 @@ class MainWindow : public QMainWindow {
void dispatchMessage(const EmulatorMessage& message);
void loadTranslation();
void loadKeybindings();
void saveKeybindings();
// Tracks whether we are using an OpenGL-backed renderer or a Vulkan-backed renderer
bool usingGL = false;
bool usingVk = false;
@ -139,6 +147,9 @@ class MainWindow : public QMainWindow {
bool keyboardAnalogX = false;
bool keyboardAnalogY = false;
// Tracks if keybindings changed, in which case we should update the keybindings file when closing the emulator
bool keybindingsChanged = false;
public:
MainWindow(QApplication* app, QWidget* parent = nullptr);
~MainWindow();
@ -157,4 +168,7 @@ class MainWindow : public QMainWindow {
void handleScreenResize(u32 width, u32 height);
void handleTouchscreenPress(QMouseEvent* event);
signals:
void emulatorPaused();
};

View file

@ -4,6 +4,7 @@
#include <memory>
#include "gl/context.h"
#include "screen_layout.hpp"
#include "window_info.h"
// OpenGL widget for drawing the 3DS screen
@ -29,6 +30,15 @@ class ScreenWidget : public QWidget {
u32 previousWidth = 0;
u32 previousHeight = 0;
// Coordinates (x/y/width/height) for the two screens in window space, used for properly handling touchscreen regardless
// of layout or resizing
ScreenLayout::WindowCoordinates screenCoordinates;
// Screen layouts and sizes
ScreenLayout::Layout screenLayout = ScreenLayout::Layout::Default;
float topScreenSize = 0.5f;
void reloadScreenLayout(ScreenLayout::Layout newLayout, float newTopScreenSize);
private:
std::unique_ptr<GL::Context> glContext = nullptr;
ResizeCallback resizeCallback;
@ -39,4 +49,6 @@ class ScreenWidget : public QWidget {
int scaledWindowWidth() const;
int scaledWindowHeight() const;
std::optional<WindowInfo> getWindowInfo();
void reloadScreenCoordinates();
};

View file

@ -0,0 +1,25 @@
#pragma once
#include <QString>
#include <QTableWidget>
#include <QTimer>
#include <QVBoxLayout>
#include <QtWidgets>
#include "emulator.hpp"
class ThreadDebugger : public QWidget {
Q_OBJECT
Emulator* emu;
QVBoxLayout* mainLayout;
QTableWidget* threadTable;
public:
ThreadDebugger(Emulator* emu, QWidget* parent = nullptr);
void update();
private:
void setListItem(int row, int column, const QString& str);
void setListItem(int row, int column, const std::string& str);
};

View file

@ -6,6 +6,7 @@
#include "emulator.hpp"
#include "input_mappings.hpp"
#include "screen_layout.hpp"
class FrontendSDL {
Emulator emu;
@ -27,7 +28,11 @@ class FrontendSDL {
u32 windowHeight = 480;
int gameControllerID;
bool programRunning = true;
// Coordinates (x/y/width/height) for the two screens in window space, used for properly handling touchscreen regardless
// of layout or resizing
ScreenLayout::WindowCoordinates screenCoordinates;
// For tracking whether to update gyroscope
// We bind gyro to right click + mouse movement
bool holdingRightClick = false;
@ -38,5 +43,7 @@ class FrontendSDL {
bool keyboardAnalogX = false;
bool keyboardAnalogY = false;
private:
void setupControllerSensors(SDL_GameController* controller);
void handleLeftClick(int mouseX, int mouseY);
};

View file

@ -51,8 +51,14 @@ class Renderer {
u32 outputWindowWidth = 400;
u32 outputWindowHeight = 240 * 2;
// Should hw renderers hash textures? Stored separately from emulatorConfig because we'll be accessing it constantly, might be merged eventually
bool hashTextures = false;
bool outputSizeChanged = true;
EmulatorConfig* emulatorConfig = nullptr;
void doSoftwareTextureCopy(u32 inputAddr, u32 outputAddr, u32 copySize, u32 inputWidth, u32 inputGap, u32 outputWidth, u32 outputGap);
public:
Renderer(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs);
virtual ~Renderer();
@ -85,6 +91,10 @@ class Renderer {
// Called to notify the core to use OpenGL ES and not desktop GL
virtual void setupGLES() {}
// Only relevant for Metal renderer on iOS
// Passes a SwiftUI MTKView's layer (CAMetalLayer) to the renderer
virtual void setMTKLayer(void* layer) {};
// This function is called on every draw call before parsing vertex data.
// It is responsible for things like looking up which vertex/fragment shaders to use, recompiling them if they don't exist, choosing between
// ubershaders and shadergen, and so on.
@ -113,9 +123,12 @@ class Renderer {
void setDepthBufferLoc(u32 loc) { depthBufferLoc = loc; }
void setOutputSize(u32 width, u32 height) {
outputSizeChanged = true;
outputWindowWidth = width;
outputWindowHeight = height;
}
void setConfig(EmulatorConfig* config) { emulatorConfig = config; }
void setHashTextures(bool setting) { hashTextures = setting; }
void reloadScreenLayout() { outputSizeChanged = true; }
};

View file

@ -35,7 +35,7 @@ struct GLStateManager {
bool depthMask;
float clearRed, clearBlue, clearGreen, clearAlpha;
GLuint stencilMask;
GLuint boundVAO;
GLuint currentProgram;
@ -241,13 +241,12 @@ struct GLStateManager {
void setBlendFunc(GLenum sourceRGB, GLenum destRGB, GLenum sourceAlpha, GLenum destAlpha) {
if (blendFuncSourceRGB != sourceRGB || blendFuncDestRGB != destRGB || blendFuncSourceAlpha != sourceAlpha ||
blendFuncDestAlpha != destAlpha) {
blendFuncSourceRGB = sourceRGB;
blendFuncDestRGB = destRGB;
blendFuncSourceAlpha = sourceAlpha;
blendFuncDestAlpha = destAlpha;
glBlendFuncSeparate(sourceRGB, destRGB,sourceAlpha, destAlpha);
glBlendFuncSeparate(sourceRGB, destRGB, sourceAlpha, destAlpha);
}
}

View file

@ -40,7 +40,7 @@ class RendererGL final : public Renderer {
OpenGL::VertexArray hwShaderVAO;
OpenGL::VertexBuffer vbo;
// Data
// Data that will be uploaded to the ubershader
struct {
// TEV configuration uniform locations
GLint textureEnvSourceLoc = -1;
@ -146,6 +146,27 @@ class RendererGL final : public Renderer {
PICA::ShaderGen::FragmentGenerator fragShaderGen;
OpenGL::Driver driverInfo;
// Information about the final 3DS screen -> Window blit, accounting for things like scaling and shifting the output based on
// the window's dimensions. Updated whenever the screen size or layout changes.
struct {
int topScreenX = 0;
int topScreenY = 0;
int topScreenWidth = 400;
int topScreenHeight = 240;
int bottomScreenX = 40;
int bottomScreenY = 240;
int bottomScreenWidth = 320;
int bottomScreenHeight = 240;
// For optimizing the final screen blit into a single blit instead of 2 when possible:
int destX = 0;
int destY = 0;
int destWidth = 400;
int destHeight = 480;
bool canDoSingleBlit = true;
} blitInfo;
MAKE_LOG_FUNCTION(log, rendererLogger)
void setupBlending();
void setupStencilTest(bool stencilEnable);

View file

@ -1,6 +1,9 @@
#pragma once
#include <array>
#include <functional>
#include <map>
#include <optional>
#include "surfaces.hpp"
#include "textures.hpp"
@ -17,41 +20,69 @@
// - A "location" member which tells us which location in 3DS memory this surface occupies
template <typename SurfaceType, size_t capacity, bool evictOnOverflow = false>
class SurfaceCache {
// Vanilla std::optional can't hold actual references
using OptionalRef = std::optional<std::reference_wrapper<SurfaceType>>;
// Vanilla std::optional can't hold actual references
using OptionalRef = std::optional<std::reference_wrapper<SurfaceType>>;
size_t size;
size_t evictionIndex;
std::array<SurfaceType, capacity> buffer;
size_t size = 0;
size_t evictionIndex = 0;
std::array<SurfaceType, capacity> buffer;
public:
void reset() {
size = 0;
evictionIndex = 0;
for (auto& e : buffer) { // Free the VRAM of all surfaces
e.free();
}
}
// Map from address to a surface in the above buffer.
// Several cached surfaces may have the same starting address, so we use a multimap.
std::multimap<u32, SurfaceType*> surfaceMap;
OptionalRef find(SurfaceType& other) {
for (auto& e : buffer) {
if (e.matches(other) && e.valid)
return e;
}
// Adds a surface to our map
void indexSurface(SurfaceType& surface) { surfaceMap.emplace(surface.location, &surface); }
return std::nullopt;
}
// Removes a surface from our map
void unindexSurface(SurfaceType& surface) {
auto range = surfaceMap.equal_range(surface.location);
for (auto it = range.first; it != range.second; ++it) {
if (it->second == &surface) {
surfaceMap.erase(it);
break;
}
}
}
OptionalRef findFromAddress(u32 address) {
for (auto& e : buffer) {
if (e.location <= address && e.location + e.sizeInBytes() > address && e.valid)
return e;
}
public:
void reset() {
size = 0;
evictionIndex = 0;
surfaceMap.clear();
return std::nullopt;
}
// Free the memory of all surfaces
for (auto& e : buffer) {
e.free();
e.valid = false;
}
}
// Adds a surface object to the cache and returns it
// Use our map to only scan the surfaces with the same starting location
OptionalRef find(SurfaceType& other) {
auto range = surfaceMap.equal_range(other.location);
for (auto it = range.first; it != range.second; ++it) {
SurfaceType* candidate = it->second;
if (candidate->valid && candidate->matches(other)) {
return *candidate;
}
}
return std::nullopt;
}
OptionalRef findFromAddress(u32 address) {
for (auto it = surfaceMap.begin(); it != surfaceMap.end(); ++it) {
SurfaceType* surface = it->second;
if (surface->valid && surface->location <= address && surface->location + surface->sizeInBytes() > address) {
return *surface;
}
}
return std::nullopt;
}
// Adds a surface object to the cache and returns it
SurfaceType& add(const SurfaceType& surface) {
if (size >= capacity) {
if constexpr (evictOnOverflow) { // Do a ring buffer if evictOnOverflow is true
@ -60,12 +91,14 @@ public:
}
auto& e = buffer[evictionIndex];
unindexSurface(e);
evictionIndex = (evictionIndex + 1) % capacity;
e.valid = false;
e.free();
e = surface;
e.allocate();
indexSurface(e);
return e;
} else {
Helpers::panic("Surface cache full! Add emptying!");
@ -74,12 +107,14 @@ public:
size++;
// Find an existing surface we completely invalidate and overwrite it with the new surface
// See if any existing surface fully overlaps
for (auto& e : buffer) {
if (e.valid && e.range.lower() >= surface.range.lower() && e.range.upper() <= surface.range.upper()) {
unindexSurface(e);
e.free();
e = surface;
e.allocate();
indexSurface(e);
return e;
}
}
@ -89,6 +124,7 @@ public:
if (!e.valid) {
e = surface;
e.allocate();
indexSurface(e);
return e;
}
}
@ -97,11 +133,6 @@ public:
Helpers::panic("Couldn't add surface to cache\n");
}
SurfaceType& operator[](size_t i) {
return buffer[i];
}
const SurfaceType& operator[](size_t i) const {
return buffer[i];
}
};
SurfaceType& operator[](size_t i) { return buffer[i]; }
const SurfaceType& operator[](size_t i) const { return buffer[i]; }
};

View file

@ -133,7 +133,7 @@ struct DepthBuffer {
GL_DEPTH_COMPONENT,
GL_DEPTH_STENCIL,
};
static constexpr std::array<GLenum, 4> types = {
GL_UNSIGNED_SHORT,
GL_UNSIGNED_INT,
@ -172,4 +172,4 @@ struct DepthBuffer {
size_t sizeInBytes() {
return (size_t)size.x() * (size_t)size.y() * PICA::sizePerPixel(format);
}
};
};

View file

@ -1,6 +1,8 @@
#pragma once
#include <array>
#include <string>
#include "PICA/pica_hash.hpp"
#include "PICA/regs.hpp"
#include "boost/icl/interval.hpp"
#include "helpers.hpp"
@ -11,55 +13,55 @@ template <typename T>
using Interval = boost::icl::right_open_interval<T>;
struct Texture {
u32 location;
u32 config; // Magnification/minification filter, wrapping configs, etc
PICA::TextureFmt format;
OpenGL::uvec2 size;
bool valid;
using Hash = PICAHash::HashType;
// Range of VRAM taken up by buffer
Interval<u32> range;
// OpenGL resources allocated to buffer
OpenGL::Texture texture;
u32 location;
u32 config; // Magnification/minification filter, wrapping configs, etc
Hash hash = Hash(0);
Texture() : valid(false) {}
PICA::TextureFmt format;
OpenGL::uvec2 size;
bool valid;
Texture(u32 loc, PICA::TextureFmt format, u32 x, u32 y, u32 config, bool valid = true)
: location(loc), format(format), size({x, y}), config(config), valid(valid) {
// Range of VRAM taken up by buffer
Interval<u32> range;
// OpenGL resources allocated to buffer
OpenGL::Texture texture;
u64 endLoc = (u64)loc + sizeInBytes();
// Check if start and end are valid here
range = Interval<u32>(loc, (u32)endLoc);
}
Texture() : valid(false) {}
// For 2 textures to "match" we only care about their locations, formats, and dimensions to match
// For other things, such as filtering mode, etc, we can just switch the attributes of the cached texture
bool matches(Texture& other) {
return location == other.location && format == other.format &&
size.x() == other.size.x() && size.y() == other.size.y();
}
Texture(u32 loc, PICA::TextureFmt format, u32 x, u32 y, u32 config, bool valid = true)
: location(loc), format(format), size({x, y}), config(config), valid(valid) {
u64 endLoc = (u64)loc + sizeInBytes();
// Check if start and end are valid here
range = Interval<u32>(loc, (u32)endLoc);
}
void allocate();
void setNewConfig(u32 newConfig);
void decodeTexture(std::span<const u8> data);
void free();
u64 sizeInBytes();
// For 2 textures to "match" we only care about their locations, formats, and dimensions to match
// For other things, such as filtering mode, etc, we can just switch the attributes of the cached texture
bool matches(Texture& other) {
return location == other.location && hash == other.hash && format == other.format && size.x() == other.size.x() && size.y() == other.size.y();
}
u32 decodeTexel(u32 u, u32 v, PICA::TextureFmt fmt, std::span<const u8> data);
void allocate();
void setNewConfig(u32 newConfig);
void decodeTexture(std::span<const u8> data);
void free();
u64 sizeInBytes();
// Get the morton interleave offset of a texel based on its U and V values
static u32 mortonInterleave(u32 u, u32 v);
// Get the byte offset of texel (u, v) in the texture
static u32 getSwizzledOffset(u32 u, u32 v, u32 width, u32 bytesPerPixel);
static u32 getSwizzledOffset_4bpp(u32 u, u32 v, u32 width);
u32 decodeTexel(u32 u, u32 v, PICA::TextureFmt fmt, std::span<const u8> data);
// Returns the format of this texture as a string
std::string_view formatToString() {
return PICA::textureFormatToString(format);
}
// Get the morton interleave offset of a texel based on its U and V values
static u32 mortonInterleave(u32 u, u32 v);
// Get the byte offset of texel (u, v) in the texture
static u32 getSwizzledOffset(u32 u, u32 v, u32 width, u32 bytesPerPixel);
static u32 getSwizzledOffset_4bpp(u32 u, u32 v, u32 width);
// Returns the texel at coordinates (u, v) of an ETC1(A4) texture
// TODO: Make hasAlpha a template parameter
u32 getTexelETC(bool hasAlpha, u32 u, u32 v, u32 width, std::span<const u8> data);
u32 decodeETC(u32 alpha, u32 u, u32 v, u64 colourData);
// Returns the format of this texture as a string
std::string_view formatToString() { return PICA::textureFormatToString(format); }
// Returns the texel at coordinates (u, v) of an ETC1(A4) texture
// TODO: Make hasAlpha a template parameter
u32 getTexelETC(bool hasAlpha, u32 u, u32 v, u32 width, std::span<const u8> data);
u32 decodeETC(u32 alpha, u32 u, u32 v, u64 colourData);
};

View file

@ -57,7 +57,7 @@ namespace Metal {
} else if (std::is_same<Format_t, PICA::DepthFmt>::value) {
pixelFormat = PICA::toMTLPixelFormatDepth((PICA::DepthFmt)format);
} else {
panic("Invalid format type");
Helpers::panic("Invalid format type");
}
MTL::TextureDescriptor* descriptor = MTL::TextureDescriptor::alloc()->init();

View file

@ -4,22 +4,28 @@
#include <array>
#include <string>
#include "PICA/pica_hash.hpp"
#include "PICA/regs.hpp"
#include "boost/icl/interval.hpp"
#include "helpers.hpp"
#include "math_util.hpp"
#include "opengl.hpp"
#include "renderer_mtl/pica_to_mtl.hpp"
// TODO: remove dependency on OpenGL
#include "opengl.hpp"
template <typename T>
using Interval = boost::icl::right_open_interval<T>;
namespace Metal {
struct Texture {
using Hash = PICAHash::HashType;
MTL::Device* device;
u32 location;
u32 config; // Magnification/minification filter, wrapping configs, etc
Hash hash = Hash(0);
PICA::TextureFmt format;
OpenGL::uvec2 size;
bool valid;
@ -27,7 +33,8 @@ namespace Metal {
// Range of VRAM taken up by buffer
Interval<u32> range;
PICA::PixelFormatInfo formatInfo;
PICA::MTLPixelFormatInfo formatInfo;
MTL::Texture* base = nullptr;
MTL::Texture* texture = nullptr;
MTL::SamplerState* sampler = nullptr;
@ -43,7 +50,8 @@ namespace Metal {
// For 2 textures to "match" we only care about their locations, formats, and dimensions to match
// For other things, such as filtering mode, etc, we can just switch the attributes of the cached texture
bool matches(Texture& other) {
return location == other.location && format == other.format && size.x() == other.size.x() && size.y() == other.size.y();
return location == other.location && hash == other.hash && format == other.format && size.x() == other.size.x() &&
size.y() == other.size.y();
}
void allocate();
@ -52,22 +60,7 @@ namespace Metal {
void free();
u64 sizeInBytes();
u8 decodeTexelU8(u32 u, u32 v, PICA::TextureFmt fmt, std::span<const u8> data);
u16 decodeTexelU16(u32 u, u32 v, PICA::TextureFmt fmt, std::span<const u8> data);
u32 decodeTexelU32(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
static u32 mortonInterleave(u32 u, u32 v);
// Get the byte offset of texel (u, v) in the texture
static u32 getSwizzledOffset(u32 u, u32 v, u32 width, u32 bytesPerPixel);
static u32 getSwizzledOffset_4bpp(u32 u, u32 v, u32 width);
// Returns the format of this texture as a string
std::string_view formatToString() { return PICA::textureFormatToString(format); }
// Returns the texel at coordinates (u, v) of an ETC1(A4) texture
// TODO: Make hasAlpha a template parameter
u32 getTexelETC(bool hasAlpha, u32 u, u32 v, u32 width, std::span<const u8> data);
u32 decodeETC(u32 alpha, u32 u, u32 v, u64 colourData);
};
} // namespace Metal

View file

@ -3,31 +3,28 @@
#include <Metal/Metal.hpp>
#include "PICA/regs.hpp"
// TODO: remove dependency on OpenGL
#include "opengl.hpp"
namespace PICA {
struct PixelFormatInfo {
struct MTLPixelFormatInfo {
MTL::PixelFormat pixelFormat;
size_t bytesPerTexel;
void (*decoder)(OpenGL::uvec2, u32, u32, std::span<const u8>, u8*);
bool needsSwizzle = false;
MTL::TextureSwizzleChannels swizzle{
.red = MTL::TextureSwizzleRed,
.green = MTL::TextureSwizzleGreen,
.blue = MTL::TextureSwizzleBlue,
.alpha = MTL::TextureSwizzleAlpha,
};
};
constexpr PixelFormatInfo pixelFormatInfos[14] = {
{MTL::PixelFormatRGBA8Unorm, 4}, // RGBA8
{MTL::PixelFormatRGBA8Unorm, 4}, // RGB8
{MTL::PixelFormatBGR5A1Unorm, 2}, // RGBA5551
{MTL::PixelFormatB5G6R5Unorm, 2}, // RGB565
{MTL::PixelFormatABGR4Unorm, 2}, // RGBA4
{MTL::PixelFormatRGBA8Unorm, 4}, // IA8
{MTL::PixelFormatRG8Unorm, 2}, // RG8
{MTL::PixelFormatRGBA8Unorm, 4}, // I8
{MTL::PixelFormatA8Unorm, 1}, // A8
{MTL::PixelFormatABGR4Unorm, 2}, // IA4
{MTL::PixelFormatABGR4Unorm, 2}, // I4
{MTL::PixelFormatA8Unorm, 1}, // A4
{MTL::PixelFormatRGBA8Unorm, 4}, // ETC1
{MTL::PixelFormatRGBA8Unorm, 4}, // ETC1A4
};
extern MTLPixelFormatInfo mtlPixelFormatInfos[14];
inline PixelFormatInfo getPixelFormatInfo(TextureFmt format) { return pixelFormatInfos[static_cast<int>(format)]; }
void checkForMTLPixelFormatSupport(MTL::Device* device);
inline MTLPixelFormatInfo getMTLPixelFormatInfo(TextureFmt format) { return mtlPixelFormatInfos[static_cast<int>(format)]; }
inline MTL::PixelFormat toMTLPixelFormatColor(ColorFmt format) {
switch (format) {
@ -35,7 +32,11 @@ namespace PICA {
case ColorFmt::RGB8: return MTL::PixelFormatRGBA8Unorm;
case ColorFmt::RGBA5551: return MTL::PixelFormatRGBA8Unorm; // TODO: use MTL::PixelFormatBGR5A1Unorm?
case ColorFmt::RGB565: return MTL::PixelFormatRGBA8Unorm; // TODO: use MTL::PixelFormatB5G6R5Unorm?
#ifdef PANDA3DS_IOS
case ColorFmt::RGBA4: return MTL::PixelFormatRGBA8Unorm; // IOS + Metal doesn't support AGBR4 properly, at least on simulator
#else
case ColorFmt::RGBA4: return MTL::PixelFormatABGR4Unorm;
#endif
}
}

View file

@ -42,11 +42,13 @@ class RendererMTL final : public Renderer {
virtual void initGraphicsContext([[maybe_unused]] GL::Context* context) override {}
#endif
private:
CA::MetalLayer* metalLayer;
virtual void setMTKLayer(void* layer) override;
MTL::Device* device;
MTL::CommandQueue* commandQueue;
private:
CA::MetalLayer* metalLayer = nullptr;
MTL::Device* device = nullptr;
MTL::CommandQueue* commandQueue = nullptr;
Metal::CommandEncoder commandEncoder;
@ -86,6 +88,20 @@ class RendererMTL final : public Renderer {
MTL::Texture* lastColorTexture = nullptr;
MTL::Texture* lastDepthTexture = nullptr;
// Information about the final 3DS screen -> Window blit, accounting for things like scaling and shifting the output based on
// the window's dimensions. Updated whenever the screen size or layout changes.
struct {
float topScreenX = 0;
float topScreenY = 0;
float topScreenWidth = 400;
float topScreenHeight = 240;
float bottomScreenX = 40;
float bottomScreenY = 240;
float bottomScreenWidth = 320;
float bottomScreenHeight = 240;
} blitInfo;
// Debug
std::string nextRenderPassName;
@ -98,6 +114,7 @@ class RendererMTL final : public Renderer {
void endRenderPass() {
if (renderCommandEncoder) {
renderCommandEncoder->endEncoding();
renderCommandEncoder->release();
renderCommandEncoder = nullptr;
}
}

View file

@ -0,0 +1,24 @@
#pragma once
#include "helpers.hpp"
// TODO: remove dependency on OpenGL
#include "opengl.hpp"
void decodeTexelABGR8ToRGBA8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData);
void decodeTexelBGR8ToRGBA8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData);
void decodeTexelA1BGR5ToBGR5A1(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData);
void decodeTexelA1BGR5ToRGBA8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData);
void decodeTexelB5G6R5ToB5G6R5(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData);
void decodeTexelB5G6R5ToRGBA8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData);
void decodeTexelABGR4ToABGR4(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData);
void decodeTexelABGR4ToRGBA8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData);
void decodeTexelAI8ToRG8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData);
void decodeTexelGR8ToRG8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData);
void decodeTexelI8ToR8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData);
void decodeTexelA8ToA8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData);
void decodeTexelAI4ToABGR4(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData);
void decodeTexelAI4ToRG8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData);
void decodeTexelI4ToR8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData);
void decodeTexelA4ToA8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData);
void decodeTexelETC1ToRGBA8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData);
void decodeTexelETC1A4ToRGBA8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData);

View file

@ -9,8 +9,8 @@
namespace Vulkan {
VKAPI_ATTR VkBool32 VKAPI_CALL debugMessageCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* callbackData, void* userData
vk::DebugUtilsMessageSeverityFlagBitsEXT messageSeverity, vk::DebugUtilsMessageTypeFlagsEXT messageType,
const vk::DebugUtilsMessengerCallbackDataEXT* callbackData, void* userData
);
void setObjectName(vk::Device device, vk::ObjectType objectType, const void* objectHandle, const char* format, ...);

View file

@ -2,8 +2,9 @@
#include "result_cfg.hpp"
#include "result_common.hpp"
#include "result_kernel.hpp"
#include "result_os.hpp"
#include "result_fnd.hpp"
#include "result_fs.hpp"
#include "result_gsp.hpp"
#include "result_gsp.hpp"
#include "result_ir.hpp"
#include "result_kernel.hpp"
#include "result_os.hpp"

View file

@ -42,7 +42,7 @@ namespace Result {
OS = 6,
DBG = 7,
DMNT = 8,
PDN = 9 ,
PDN = 9,
GSP = 10,
I2C = 11,
GPIO = 12,
@ -132,7 +132,7 @@ namespace Result {
};
class HorizonResult {
private:
private:
static const uint32_t DescriptionBits = 10;
static const uint32_t ModuleBits = 8;
static const uint32_t ReservedBits = 3;
@ -156,7 +156,7 @@ namespace Result {
return (description << DescriptionOffset) | (module << ModuleOffset) | (summary << SummaryOffset) | (level << LevelOffset);
}
public:
public:
constexpr HorizonResult() : m_value(0) {}
constexpr HorizonResult(uint32_t value) : m_value(value) {}
constexpr HorizonResult(uint32_t description, HorizonResultModule module, HorizonResultSummary summary, HorizonResultLevel level) : m_value(makeValue(description, static_cast<uint32_t>(module), static_cast<uint32_t>(summary), static_cast<uint32_t>(level))) {}
@ -193,14 +193,14 @@ namespace Result {
static_assert(std::is_trivially_destructible<HorizonResult>::value, "std::is_trivially_destructible<HorizonResult>::value");
#define DEFINE_HORIZON_RESULT_MODULE(ns, value) \
namespace ns::Detail {\
static constexpr HorizonResultModule ModuleId = HorizonResultModule::value; \
}
#define DEFINE_HORIZON_RESULT_MODULE(ns, value) \
namespace ns::Detail { \
static constexpr HorizonResultModule ModuleId = HorizonResultModule::value; \
}
#define DEFINE_HORIZON_RESULT(name, description, summary, level) \
static constexpr HorizonResult name(description, Detail::ModuleId, HorizonResultSummary::summary, HorizonResultLevel::level);
#define DEFINE_HORIZON_RESULT(name, description, summary, level) \
static constexpr HorizonResult name(description, Detail::ModuleId, HorizonResultSummary::summary, HorizonResultLevel::level);
static constexpr HorizonResult Success(0);
static constexpr HorizonResult FailurePlaceholder(UINT32_MAX);
};
}; // namespace Result

View file

@ -0,0 +1,8 @@
#pragma once
#include "result_common.hpp"
DEFINE_HORIZON_RESULT_MODULE(Result::IR, IR);
namespace Result::IR {
DEFINE_HORIZON_RESULT(NoDeviceConnected, 13, InvalidState, Status);
};

View file

@ -12,7 +12,8 @@ struct Scheduler {
UpdateTimers = 1, // Update kernel timer objects
RunDSP = 2, // Make the emulated DSP run for one audio frame
SignalY2R = 3, // Signal that a Y2R conversion has finished
Panic = 4, // Dummy event that is always pending and should never be triggered (Timestamp = UINT64_MAX)
UpdateIR = 4, // Update an IR device (For now, just the CirclePad Pro/N3DS controls)
Panic = 5, // Dummy event that is always pending and should never be triggered (Timestamp = UINT64_MAX)
TotalNumberOfEvents // How many event types do we have in total?
};
static constexpr usize totalNumberOfEvents = static_cast<usize>(EventType::TotalNumberOfEvents);

58
include/screen_layout.hpp Normal file
View file

@ -0,0 +1,58 @@
#pragma once
#include <string>
#include "helpers.hpp"
namespace ScreenLayout {
static constexpr u32 TOP_SCREEN_WIDTH = 400;
static constexpr u32 BOTTOM_SCREEN_WIDTH = 320;
static constexpr u32 TOP_SCREEN_HEIGHT = 240;
static constexpr u32 BOTTOM_SCREEN_HEIGHT = 240;
// The bottom screen is less wide by 80 pixels, so we center it by offsetting it 40 pixels right
static constexpr u32 BOTTOM_SCREEN_X_OFFSET = 40;
static constexpr u32 CONSOLE_HEIGHT = TOP_SCREEN_HEIGHT + BOTTOM_SCREEN_HEIGHT;
enum class Layout {
Default = 0, // Top screen up, bottom screen down
DefaultFlipped, // Top screen down, bottom screen up
SideBySide, // Top screen left, bottom screen right,
SideBySideFlipped, // Top screen right, bottom screen left,
};
// For properly handling touchscreen, we have to remember what window coordinates our screens map to
// We also remember some more information that is useful to our renderers, particularly for the final screen blit.
struct WindowCoordinates {
u32 topScreenX = 0;
u32 topScreenY = 0;
u32 topScreenWidth = TOP_SCREEN_WIDTH;
u32 topScreenHeight = TOP_SCREEN_HEIGHT;
u32 bottomScreenX = BOTTOM_SCREEN_X_OFFSET;
u32 bottomScreenY = TOP_SCREEN_HEIGHT;
u32 bottomScreenWidth = BOTTOM_SCREEN_WIDTH;
u32 bottomScreenHeight = BOTTOM_SCREEN_HEIGHT;
u32 windowWidth = topScreenWidth;
u32 windowHeight = topScreenHeight + bottomScreenHeight;
// Information used when optimizing the final screen blit into a single blit
struct {
// Can we actually render both of the screens in a single blit?
bool canDoSingleBlit = false;
// Blit information used if we can
int destX = 0, destY = 0;
int destWidth = TOP_SCREEN_WIDTH;
int destHeight = CONSOLE_HEIGHT;
} singleBlitInfo;
};
// Calculate screen coordinates on the screen for a given layout & a given size for the output window
// Used in both the renderers and in the frontends (To eg calculate touch screen boundaries)
void calculateCoordinates(
WindowCoordinates& coordinates, u32 outputWindowWidth, u32 outputWindowHeight, float topScreenPercentage, Layout layout
);
Layout layoutFromString(std::string inString);
const char* layoutToString(Layout layout);
} // namespace ScreenLayout

View file

@ -1,6 +1,6 @@
#pragma once
#include <cmath>
#include <algorithm>
#include <glm/glm.hpp>
#include "helpers.hpp"
@ -10,29 +10,29 @@
// We use the same code for Android as well, since the values we get from Android are in the same format as SDL (m/s^2 for acceleration, rad/s for
// rotation)
namespace Sensors::SDL {
// Convert the rotation data we get from SDL sensor events to rotation data we can feed right to HID
// Returns [pitch, roll, yaw]
static glm::vec3 convertRotation(glm::vec3 rotation) {
// Annoyingly, Android doesn't support the <numbers> header yet so we define pi ourselves
static constexpr double pi = 3.141592653589793;
// Convert the rotation from rad/s to deg/s and scale by the gyroscope coefficient in HID
constexpr float scale = 180.f / pi * HIDService::gyroscopeCoeff;
// The axes are also inverted, so invert scale before the multiplication.
return rotation * -scale;
}
// Convert the rotation data we get from SDL sensor events to rotation data we can feed right to HID
// Returns [pitch, roll, yaw]
static glm::vec3 convertRotation(glm::vec3 rotation) {
// Annoyingly, Android doesn't support the <numbers> header yet so we define pi ourselves
static constexpr double pi = 3.141592653589793;
// Convert the rotation from rad/s to deg/s and scale by the gyroscope coefficient in HID
constexpr float scale = 180.f / pi * HIDService::gyroscopeCoeff;
// The axes are also inverted, so invert scale before the multiplication.
return rotation * -scale;
}
static glm::vec3 convertAcceleration(float* data) {
// Set our cap to ~9 m/s^2. The 3DS sensors cap at -930 and +930, so values above this value will get clamped to 930
// At rest (3DS laid flat on table), hardware reads around ~0 for x and z axis, and around ~480 for y axis due to gravity.
// This code tries to mimic this approximately, with offsets based on measurements from my DualShock 4.
static constexpr float accelMax = 9.f;
// We define standard gravity(g) ourself instead of using the SDL one in order for the code to work on Android too.
static constexpr float standardGravity = 9.80665f;
static glm::vec3 convertAcceleration(float* data) {
// Set our cap to ~9 m/s^2. The 3DS sensors cap at -930 and +930, so values above this value will get clamped to 930
// At rest (3DS laid flat on table), hardware reads around ~0 for x and z axis, and around ~480 for y axis due to gravity.
// This code tries to mimic this approximately, with offsets based on measurements from my DualShock 4.
static constexpr float accelMax = 9.f;
// We define standard gravity(g) ourself instead of using the SDL one in order for the code to work on Android too.
static constexpr float standardGravity = 9.80665f;
s16 x = std::clamp<s16>(s16(data[0] / accelMax * 930.f), -930, +930);
s16 y = std::clamp<s16>(s16(data[1] / (standardGravity * accelMax) * 930.f - 350.f), -930, +930);
s16 z = std::clamp<s16>(s16((data[2] - 2.1f) / accelMax * 930.f), -930, +930);
s16 x = std::clamp<s16>(s16(data[0] / accelMax * 930.f), -930, +930);
s16 y = std::clamp<s16>(s16(data[1] / (standardGravity * accelMax) * 930.f - 350.f), -930, +930);
s16 z = std::clamp<s16>(s16((data[2] - 2.1f) / accelMax * 930.f), -930, +930);
return glm::vec3(x, y, z);
}
return glm::vec3(x, y, z);
}
} // namespace Sensors::SDL

View file

@ -1,5 +1,6 @@
#pragma once
#include <array>
#include "helpers.hpp"
#include "io_file.hpp"
#include "nfc_types.hpp"

View file

@ -1,13 +1,13 @@
#pragma once
#include <optional>
#include "applets/applet_manager.hpp"
#include "helpers.hpp"
#include "kernel_types.hpp"
#include "logger.hpp"
#include "memory.hpp"
#include "result/result.hpp"
#include "applets/applet_manager.hpp"
// Yay, more circular dependencies
class Kernel;

View file

@ -18,10 +18,13 @@ class CECDService {
MAKE_LOG_FUNCTION(log, cecdLogger)
std::optional<Handle> infoEvent;
std::optional<Handle> changeStateEvent;
// Service commands
void getChangeStateEventHandle(u32 messagePointer);
void getInfoEventHandle(u32 messagePointer);
void openAndRead(u32 messagePointer);
void stop(u32 messagePointer);
public:
CECDService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}

View file

@ -23,6 +23,7 @@ class CFGService {
void getConfigInfoBlk2(u32 messagePointer);
void getConfigInfoBlk8(u32 messagePointer, u32 commandWord);
void getCountryCodeID(u32 messagePointer);
void getCountryCodeString(u32 messagePointer);
void getLocalFriendCodeSeed(u32 messagePointer);
void getRegionCanadaUSA(u32 messagePointer);
void getSystemModel(u32 messagePointer);

View file

@ -1,6 +1,7 @@
#pragma once
#include <array>
#include <optional>
#include <string>
#include "helpers.hpp"
#include "kernel_types.hpp"
@ -27,12 +28,21 @@ namespace HID::Keys {
GPIO0Inv = 1 << 12, // Inverted value of GPIO bit 0
GPIO14Inv = 1 << 13, // Inverted value of GPIO bit 14
// CirclePad Pro buttons. We store them in the HID service for ease, even though they're only used by the IR service
// Whenever the HID service writes to shared memory, we remember to mask them out
ZL = 1 << 14,
ZR = 1 << 15,
CirclePadProButtons = ZL | ZR,
CirclePadRight = 1 << 28, // X >= 41
CirclePadLeft = 1 << 29, // X <= -41
CirclePadUp = 1 << 30, // Y >= 41
CirclePadDown = 1u << 31 // Y <= -41
};
}
const char* keyToName(u32 key);
u32 nameToKey(std::string name);
} // namespace HID::Keys
// Circular dependency because we need HID to spawn events
class Kernel;
@ -58,6 +68,9 @@ class HIDService {
s16 roll, pitch, yaw; // Gyroscope state
s16 accelX, accelY, accelZ; // Accelerometer state
// New 3DS/CirclePad Pro C-stick state
s16 cStickX, cStickY;
bool accelerometerEnabled;
bool eventsInitialized;
bool gyroEnabled;
@ -113,11 +126,11 @@ class HIDService {
// Turn bits 28 and 29 off in the new button state, which indicate whether the circlepad is steering left or right
// Then, set them according to the new value of x
newButtons &= ~0x3000'0000;
newButtons &= ~(HID::Keys::CirclePadLeft | HID::Keys::CirclePadRight);
if (x >= 41) // Pressing right
newButtons |= 1 << 28;
newButtons |= HID::Keys::CirclePadRight;
else if (x <= -41) // Pressing left
newButtons |= 1 << 29;
newButtons |= HID::Keys::CirclePadLeft;
}
void setCirclepadY(s16 y) {
@ -125,13 +138,19 @@ class HIDService {
// Turn bits 30 and 31 off in the new button state, which indicate whether the circlepad is steering up or down
// Then, set them according to the new value of y
newButtons &= ~0xC000'0000;
newButtons &= ~(HID::Keys::CirclePadUp | HID::Keys::CirclePadDown);
if (y >= 41) // Pressing up
newButtons |= 1 << 30;
newButtons |= HID::Keys::CirclePadUp;
else if (y <= -41) // Pressing down
newButtons |= 1 << 31;
newButtons |= HID::Keys::CirclePadDown;
}
void setCStickX(s16 x) { cStickX = x; }
void setCStickY(s16 y) { cStickY = y; }
s16 getCStickX() { return cStickX; }
s16 getCStickY() { return cStickY; }
void setRoll(s16 value) { roll = value; }
void setPitch(s16 value) { pitch = value; }
void setYaw(s16 value) { yaw = value; }
@ -157,9 +176,6 @@ class HIDService {
touchScreenPressed = true;
}
void releaseTouchScreen() {
touchScreenPressed = false;
}
void releaseTouchScreen() { touchScreenPressed = false; }
bool isTouchScreenPressed() { return touchScreenPressed; }
};

View file

@ -0,0 +1,53 @@
#pragma once
#include "bitfield.hpp"
#include "services/ir/ir_device.hpp"
#include "services/ir/ir_types.hpp"
namespace IR {
class CirclePadPro : public IR::Device {
public:
struct ButtonState {
static constexpr int C_STICK_CENTER = 0x800;
union {
BitField<0, 8, u32> header;
BitField<8, 12, u32> x;
BitField<20, 12, u32> y;
} cStick;
union {
BitField<0, 5, u8> batteryLevel;
BitField<5, 1, u8> zlNotPressed;
BitField<6, 1, u8> zrNotPressed;
BitField<7, 1, u8> rNotPressed;
} buttons;
u8 unknown;
ButtonState() {
// Response header for button state reads
cStick.header = u8(CPPResponseID::PollButtons);
cStick.x = u32(CirclePadPro::ButtonState::C_STICK_CENTER);
cStick.y = u32(CirclePadPro::ButtonState::C_STICK_CENTER);
// Fully charged
buttons.batteryLevel = 0x1F;
buttons.zrNotPressed = 1;
buttons.zlNotPressed = 1;
buttons.rNotPressed = 1;
unknown = 0;
}
};
static_assert(sizeof(ButtonState) == 6, "Circlepad Pro button state has wrong size");
virtual void connect() override;
virtual void disconnect() override;
virtual void receivePayload(Payload payload) override;
CirclePadPro(SendCallback sendCallback, Scheduler& scheduler) : IR::Device(sendCallback, scheduler) {}
ButtonState state;
// The current polling period in cycles, configured via the ConfigurePolling command
s64 period = Scheduler::nsToCycles(16000);
};
} // namespace IR

View file

@ -0,0 +1,25 @@
#pragma once
#include <functional>
#include <span>
#include "helpers.hpp"
#include "scheduler.hpp"
namespace IR {
class Device {
public:
using Payload = std::span<const u8>;
protected:
using SendCallback = std::function<void(Payload)>; // Callback for sending data from IR device->3DS
Scheduler& scheduler;
SendCallback sendCallback;
public:
virtual void connect() = 0;
virtual void disconnect() = 0;
virtual void receivePayload(Payload payload) = 0;
Device(SendCallback sendCallback, Scheduler& scheduler) : sendCallback(sendCallback), scheduler(scheduler) {}
};
} // namespace IR

View file

@ -0,0 +1,132 @@
#pragma once
#include <array>
#include "bitfield.hpp"
#include "helpers.hpp"
#include "memory.hpp"
#include "swap.hpp"
// Type definitions for the IR service
// Based off https://github.com/azahar-emu/azahar/blob/de7b457ee466e432047c4ee8d37fca9eae7e031e/src/core/hle/service/ir/ir_user.cpp#L88
namespace IR {
class Buffer {
public:
Buffer(Memory& memory, u32 sharedMemBaseVaddr, u32 infoOffset, u32 bufferOffset, u32 maxPackets, u32 bufferSize)
: memory(memory), sharedMemBaseVaddr(sharedMemBaseVaddr), infoOffset(infoOffset), bufferOffset(bufferOffset), maxPackets(maxPackets),
maxDataSize(bufferSize - sizeof(PacketInfo) * maxPackets) {
updateBufferInfo();
}
u8* getPointer(u32 offset) { return (u8*)memory.getReadPointer(sharedMemBaseVaddr + offset); }
bool put(std::span<const u8> packet) {
if (info.packetCount == maxPackets) {
return false;
}
u32 offset;
// finds free space offset in data buffer
if (info.packetCount == 0) {
offset = 0;
if (packet.size() > maxDataSize) {
return false;
}
} else {
const u32 lastIndex = (info.endIndex + maxPackets - 1) % maxPackets;
const PacketInfo first = getPacketInfo(info.beginIndex);
const PacketInfo last = getPacketInfo(lastIndex);
offset = (last.offset + last.size) % maxDataSize;
const u32 freeSpace = (first.offset + maxDataSize - offset) % maxDataSize;
if (packet.size() > freeSpace) {
return false;
}
}
// Writes packet info
PacketInfo packet_info{offset, static_cast<u32>(packet.size())};
setPacketInfo(info.endIndex, packet_info);
// Writes packet data
for (std::size_t i = 0; i < packet.size(); ++i) {
*getDataBufferPointer((offset + i) % maxDataSize) = packet[i];
}
// Updates buffer info
info.endIndex++;
info.endIndex %= maxPackets;
info.packetCount++;
updateBufferInfo();
return true;
}
bool release(u32 count) {
if (info.packetCount < count) {
return false;
}
info.packetCount -= count;
info.beginIndex += count;
info.beginIndex %= maxPackets;
updateBufferInfo();
return true;
}
u32 getPacketCount() { return info.packetCount; }
private:
struct BufferInfo {
u32_le beginIndex;
u32_le endIndex;
u32_le packetCount;
u32_le unknown;
};
static_assert(sizeof(BufferInfo) == 16, "IR::BufferInfo has wrong size!");
struct PacketInfo {
u32_le offset;
u32_le size;
};
static_assert(sizeof(PacketInfo) == 8, "IR::PacketInfo has wrong size!");
u8* getPacketInfoPointer(u32 index) { return getPointer(bufferOffset + sizeof(PacketInfo) * index); }
void setPacketInfo(u32 index, const PacketInfo& packet_info) { std::memcpy(getPacketInfoPointer(index), &packet_info, sizeof(PacketInfo)); }
PacketInfo getPacketInfo(u32 index) {
PacketInfo packet_info;
std::memcpy(&packet_info, getPacketInfoPointer(index), sizeof(PacketInfo));
return packet_info;
}
u8* getDataBufferPointer(u32 offset) { return getPointer(bufferOffset + sizeof(PacketInfo) * maxPackets + offset); }
void updateBufferInfo() {
if (infoOffset) {
std::memcpy(getPointer(infoOffset), &info, sizeof(info));
}
}
BufferInfo info{0, 0, 0, 0};
Memory& memory;
u32 sharedMemBaseVaddr;
u32 infoOffset;
u32 bufferOffset;
u32 maxPackets;
u32 maxDataSize;
};
namespace CPPResponseID {
enum : u8 {
PollButtons = 0x10, // This response contains the current Circlepad Pro button/stick state
ReadCalibrationData = 0x11, // Responding to a ReadCalibrationData request
};
}
namespace CPPRequestID {
enum : u8 {
ConfigurePolling = 1, // Configures if & how often to poll the Circlepad Pro button/stick state
ReadCalibrationData = 2, // Reads the Circlepad Pro calibration data
};
}
} // namespace IR

View file

@ -1,16 +1,22 @@
#pragma once
#include <memory>
#include <optional>
#include <span>
#include "config.hpp"
#include "helpers.hpp"
#include "kernel_types.hpp"
#include "logger.hpp"
#include "memory.hpp"
#include "result/result.hpp"
#include "services/hid.hpp"
#include "services/ir/circlepad_pro.hpp"
// Circular dependencies in this project? Never
class Kernel;
class IRUserService {
using Payload = IR::Device::Payload;
using Handle = HorizonHandle;
enum class DeviceID : u8 {
@ -20,23 +26,34 @@ class IRUserService {
Handle handle = KernelHandles::IR_USER;
Memory& mem;
Kernel& kernel;
// The IR service has a reference to the HID service as that's where the frontends store CirclePad Pro button state in
HIDService& hid;
const EmulatorConfig& config;
MAKE_LOG_FUNCTION(log, irUserLogger)
// Service commands
void clearReceiveBuffer(u32 messagePointer);
void clearSendBuffer(u32 messagePointer);
void disconnect(u32 messagePointer);
void finalizeIrnop(u32 messagePointer);
void getConnectionStatusEvent(u32 messagePointer);
void getReceiveEvent(u32 messagePointer);
void getSendEvent(u32 messagePointer);
void initializeIrnopShared(u32 messagePointer);
void requireConnection(u32 messagePointer);
void sendIrnop(u32 messagePointer);
void releaseReceivedData(u32 messagePointer);
using IREvent = std::optional<Handle>;
IREvent connectionStatusEvent = std::nullopt;
IREvent receiveEvent = std::nullopt;
IREvent sendEvent = std::nullopt;
IR::CirclePadPro cpp;
std::optional<MemoryBlock> sharedMemory = std::nullopt;
std::unique_ptr<IR::Buffer> receiveBuffer;
bool connectedDevice = false;
// Header of the IR shared memory containing various bits of info
@ -56,8 +73,19 @@ class IRUserService {
};
static_assert(sizeof(SharedMemoryStatus) == 16);
// The IR service uses CRC8 with generator polynomial = 0x07 for verifying packets received from IR devices
static u8 crc8(std::span<const u8> data);
// IR devices call this to send a device->console payload
void sendPayload(Payload payload);
public:
IRUserService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
IRUserService(Memory& mem, HIDService& hid, const EmulatorConfig& config, Kernel& kernel);
void setCStickX(s16 value) { cpp.state.cStick.x = value; }
void setCStickY(s16 value) { cpp.state.cStick.y = value; }
void reset();
void handleSyncRequest(u32 messagePointer);
void updateCirclePadPro();
};

View file

@ -244,18 +244,18 @@ namespace Service::NFC {
#pragma pack(1)
struct EncryptedAmiiboFile {
u8 constant_value; // Must be A5
u16_be write_counter; // Number of times the amiibo has been written?
u8 amiibo_version; // Amiibo file version
AmiiboSettings settings; // Encrypted amiibo settings
HashData hmac_tag; // Hash
AmiiboModelInfo model_info; // Encrypted amiibo model info
HashData keygen_salt; // Salt
HashData hmac_data; // Hash
ChecksummedMiiData owner_mii; // Encrypted Mii data
u64_be application_id; // Encrypted Game id
u16_be application_write_counter; // Encrypted Counter
u32_be application_area_id; // Encrypted Game id
u8 constant_value; // Must be A5
u16_be write_counter; // Number of times the amiibo has been written?
u8 amiibo_version; // Amiibo file version
AmiiboSettings settings; // Encrypted amiibo settings
HashData hmac_tag; // Hash
AmiiboModelInfo model_info; // Encrypted amiibo model info
HashData keygen_salt; // Salt
HashData hmac_data; // Hash
ChecksummedMiiData owner_mii; // Encrypted Mii data
u64_be application_id; // Encrypted Game id
u16_be application_write_counter; // Encrypted Counter
u32_be application_area_id; // Encrypted Game id
u8 application_id_byte;
u8 unknown;
u64 mii_extension;
@ -274,9 +274,9 @@ namespace Service::NFC {
u16_be write_counter; // Number of times the amiibo has been written?
u8 amiibo_version; // Amiibo file version
AmiiboSettings settings;
ChecksummedMiiData owner_mii; // Mii data
u64_be application_id; // Game id
u16_be application_write_counter; // Counter
ChecksummedMiiData owner_mii; // Mii data
u64_be application_id; // Game id
u16_be application_write_counter; // Counter
u32_be application_area_id;
u8 application_id_byte;
u8 unknown;

View file

@ -0,0 +1,28 @@
#pragma once
#include <functional>
#include <string>
#include "helpers.hpp"
// We allow Lua scripts to intercept service calls and allow their own code to be ran on SyncRequests
// For example, if we want to intercept dsp::DSP ReadPipe (Header: 0x000E00C0), the "serviceName" field would be "dsp::DSP"
// and the "function" field would be 0x000E00C0
struct InterceptedService {
std::string serviceName; // Name of the service whose function
u32 function; // Header of the function to intercept
InterceptedService(const std::string& name, u32 header) : serviceName(name), function(header) {}
bool operator==(const InterceptedService& other) const { return serviceName == other.serviceName && function == other.function; }
};
// Define hashing function for InterceptedService to use it with unordered_map
namespace std {
template <>
struct hash<InterceptedService> {
usize operator()(const InterceptedService& s) const noexcept {
const usize hash1 = std::hash<std::string>{}(s.serviceName);
const usize hash2 = std::hash<u32>{}(s.function);
return hash1 ^ (hash2 << 1);
}
};
} // namespace std

View file

@ -2,9 +2,12 @@
#include <array>
#include <optional>
#include <span>
#include <string>
#include <unordered_map>
#include "kernel_types.hpp"
#include "logger.hpp"
#include "lua_manager.hpp"
#include "memory.hpp"
#include "services/ac.hpp"
#include "services/act.hpp"
@ -23,7 +26,7 @@
#include "services/gsp_lcd.hpp"
#include "services/hid.hpp"
#include "services/http.hpp"
#include "services/ir_user.hpp"
#include "services/ir/ir_user.hpp"
#include "services/ldr_ro.hpp"
#include "services/mcu/mcu_hwc.hpp"
#include "services/mic.hpp"
@ -34,6 +37,7 @@
#include "services/ns.hpp"
#include "services/nwm_uds.hpp"
#include "services/ptm.hpp"
#include "services/service_intercept.hpp"
#include "services/soc.hpp"
#include "services/ssl.hpp"
#include "services/y2r.hpp"
@ -86,16 +90,32 @@ class ServiceManager {
MCU::HWCService mcu_hwc;
// We allow Lua scripts to intercept service calls and allow their own code to be ran on SyncRequests
// For example, if we want to intercept dsp::DSP ReadPipe (Header: 0x000E00C0), the "serviceName" field would be "dsp::DSP"
// and the "function" field would be 0x000E00C0
LuaManager& lua;
// Map from service intercept entries to their corresponding Lua callbacks
std::unordered_map<InterceptedService, int> interceptedServices = {};
// Calling std::unordered_map<T>::size() compiles to a non-trivial function call on Clang, so we store this
// separately and check it on service calls, for performance reasons
bool haveServiceIntercepts = false;
// Checks for whether a service call is intercepted by Lua and handles it. Returns true if Lua told us not to handle the function,
// or false if we should handle it as normal
bool checkForIntercept(u32 messagePointer, Handle handle);
// "srv:" commands
void enableNotification(u32 messagePointer);
void getServiceHandle(u32 messagePointer);
void publishToSubscriber(u32 messagePointer);
void receiveNotification(u32 messagePointer);
void registerClient(u32 messagePointer);
void subscribe(u32 messagePointer);
void unsubscribe(u32 messagePointer);
public:
ServiceManager(std::span<u32, 16> regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel, const EmulatorConfig& config);
ServiceManager(std::span<u32, 16> regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel, const EmulatorConfig& config, LuaManager& lua);
void reset();
void initializeFS() { fs.initializeFilesystem(); }
void handleSyncRequest(u32 messagePointer);
@ -114,4 +134,26 @@ class ServiceManager {
NFCService& getNFC() { return nfc; }
DSPService& getDSP() { return dsp; }
Y2RService& getY2R() { return y2r; }
IRUserService& getIRUser() { return ir_user; }
void addServiceIntercept(const std::string& service, u32 function, int callbackRef) {
auto success = interceptedServices.try_emplace(InterceptedService(service, function), callbackRef);
if (!success.second) {
// An intercept for this service function already exists
// Remove the old callback and set the new one
lua.removeInterceptedService(service, function, success.first->second);
success.first->second = callbackRef;
}
haveServiceIntercepts = true;
}
void clearServiceIntercepts() {
for (const auto& [interceptedService, callbackRef] : interceptedServices) {
lua.removeInterceptedService(interceptedService.serviceName, interceptedService.function, callbackRef);
}
interceptedServices.clear();
haveServiceIntercepts = false;
}
};

View file

@ -0,0 +1,29 @@
#pragma once
#include <string>
#include <type_traits>
#include <utility>
#include "handles.hpp"
// Helpers for constructing std::maps to look up OS services.
// We want to be able to map both service names -> services (Used for OS emulation)
// And service handles -> services (For Lua service call intercepts)
using ServiceMapEntry = std::pair<std::string, HorizonHandle>;
// Comparator for constructing a name->handle service map
struct ServiceMapByNameComparator {
// The comparators must be transparent, as our search key is different from our set key
// Our set key is a ServiceMapEntry, while the comparator each time is either the name or the service handle
using is_transparent = std::true_type;
bool operator()(const ServiceMapEntry& lhs, std::string_view rhs) const { return lhs.first < rhs; }
bool operator()(std::string_view lhs, const ServiceMapEntry& rhs) const { return lhs < rhs.first; }
bool operator()(const ServiceMapEntry& lhs, const ServiceMapEntry& rhs) const { return lhs.first < rhs.first; }
};
// Comparator for constructing a handle->name service map
struct ServiceMapByHandleComparator {
using is_transparent = std::true_type;
bool operator()(const ServiceMapEntry& lhs, HorizonHandle rhs) const { return lhs.second < rhs; }
bool operator()(HorizonHandle lhs, const ServiceMapEntry& rhs) const { return lhs < rhs.second; }
bool operator()(const ServiceMapEntry& lhs, const ServiceMapEntry& rhs) const { return lhs.second < rhs.second; }
};

View file

@ -23,6 +23,7 @@
#include <cstdlib>
#endif
#include <cstring>
#include "helpers.hpp"
// GCC
@ -57,15 +58,9 @@
namespace Common {
#ifdef _MSC_VER
[[nodiscard]] inline u16 swap16(u16 data) noexcept {
return _byteswap_ushort(data);
}
[[nodiscard]] inline u32 swap32(u32 data) noexcept {
return _byteswap_ulong(data);
}
[[nodiscard]] inline u64 swap64(u64 data) noexcept {
return _byteswap_uint64(data);
}
[[nodiscard]] inline u16 swap16(u16 data) noexcept { return _byteswap_ushort(data); }
[[nodiscard]] inline u32 swap32(u32 data) noexcept { return _byteswap_ulong(data); }
[[nodiscard]] inline u64 swap64(u64 data) noexcept { return _byteswap_uint64(data); }
#elif defined(__clang__) || defined(__GNUC__)
#if defined(__Bitrig__) || defined(__OpenBSD__)
// redefine swap16, swap32, swap64 as inline functions
@ -73,523 +68,441 @@ namespace Common {
#undef swap32
#undef swap64
#endif
[[nodiscard]] inline u16 swap16(u16 data) noexcept {
return __builtin_bswap16(data);
}
[[nodiscard]] inline u32 swap32(u32 data) noexcept {
return __builtin_bswap32(data);
}
[[nodiscard]] inline u64 swap64(u64 data) noexcept {
return __builtin_bswap64(data);
}
[[nodiscard]] inline u16 swap16(u16 data) noexcept { return __builtin_bswap16(data); }
[[nodiscard]] inline u32 swap32(u32 data) noexcept { return __builtin_bswap32(data); }
[[nodiscard]] inline u64 swap64(u64 data) noexcept { return __builtin_bswap64(data); }
#else
// Generic implementation.
[[nodiscard]] inline u16 swap16(u16 data) noexcept {
return (data >> 8) | (data << 8);
}
[[nodiscard]] inline u32 swap32(u32 data) noexcept {
return ((data & 0xFF000000U) >> 24) | ((data & 0x00FF0000U) >> 8) |
((data & 0x0000FF00U) << 8) | ((data & 0x000000FFU) << 24);
}
[[nodiscard]] inline u64 swap64(u64 data) noexcept {
return ((data & 0xFF00000000000000ULL) >> 56) | ((data & 0x00FF000000000000ULL) >> 40) |
((data & 0x0000FF0000000000ULL) >> 24) | ((data & 0x000000FF00000000ULL) >> 8) |
((data & 0x00000000FF000000ULL) << 8) | ((data & 0x0000000000FF0000ULL) << 24) |
((data & 0x000000000000FF00ULL) << 40) | ((data & 0x00000000000000FFULL) << 56);
}
// Generic implementation.
[[nodiscard]] inline u16 swap16(u16 data) noexcept { return (data >> 8) | (data << 8); }
[[nodiscard]] inline u32 swap32(u32 data) noexcept {
return ((data & 0xFF000000U) >> 24) | ((data & 0x00FF0000U) >> 8) | ((data & 0x0000FF00U) << 8) | ((data & 0x000000FFU) << 24);
}
[[nodiscard]] inline u64 swap64(u64 data) noexcept {
return ((data & 0xFF00000000000000ULL) >> 56) | ((data & 0x00FF000000000000ULL) >> 40) | ((data & 0x0000FF0000000000ULL) >> 24) |
((data & 0x000000FF00000000ULL) >> 8) | ((data & 0x00000000FF000000ULL) << 8) | ((data & 0x0000000000FF0000ULL) << 24) |
((data & 0x000000000000FF00ULL) << 40) | ((data & 0x00000000000000FFULL) << 56);
}
#endif
[[nodiscard]] inline float swapf(float f) noexcept {
static_assert(sizeof(u32) == sizeof(float), "float must be the same size as uint32_t.");
[[nodiscard]] inline float swapf(float f) noexcept {
static_assert(sizeof(u32) == sizeof(float), "float must be the same size as uint32_t.");
u32 value;
std::memcpy(&value, &f, sizeof(u32));
u32 value;
std::memcpy(&value, &f, sizeof(u32));
value = swap32(value);
std::memcpy(&f, &value, sizeof(u32));
value = swap32(value);
std::memcpy(&f, &value, sizeof(u32));
return f;
}
return f;
}
[[nodiscard]] inline double swapd(double f) noexcept {
static_assert(sizeof(u64) == sizeof(double), "double must be the same size as uint64_t.");
[[nodiscard]] inline double swapd(double f) noexcept {
static_assert(sizeof(u64) == sizeof(double), "double must be the same size as uint64_t.");
u64 value;
std::memcpy(&value, &f, sizeof(u64));
u64 value;
std::memcpy(&value, &f, sizeof(u64));
value = swap64(value);
std::memcpy(&f, &value, sizeof(u64));
value = swap64(value);
std::memcpy(&f, &value, sizeof(u64));
return f;
}
return f;
}
} // Namespace Common
} // Namespace Common
template <typename T, typename F>
struct swap_struct_t {
using swapped_t = swap_struct_t;
using swapped_t = swap_struct_t;
protected:
T value;
protected:
T value;
static T swap(T v) {
return F::swap(v);
}
static T swap(T v) { return F::swap(v); }
public:
T swap() const {
return swap(value);
}
swap_struct_t() = default;
swap_struct_t(const T& v) : value(swap(v)) {}
public:
T swap() const { return swap(value); }
swap_struct_t() = default;
swap_struct_t(const T& v) : value(swap(v)) {}
template <typename S>
swapped_t& operator=(const S& source) {
value = swap(static_cast<T>(source));
return *this;
}
template <typename S>
swapped_t& operator=(const S& source) {
value = swap(static_cast<T>(source));
return *this;
}
operator s8() const {
return static_cast<s8>(swap());
}
operator u8() const {
return static_cast<u8>(swap());
}
operator s16() const {
return static_cast<s16>(swap());
}
operator u16() const {
return static_cast<u16>(swap());
}
operator s32() const {
return static_cast<s32>(swap());
}
operator u32() const {
return static_cast<u32>(swap());
}
operator s64() const {
return static_cast<s64>(swap());
}
operator u64() const {
return static_cast<u64>(swap());
}
operator float() const {
return static_cast<float>(swap());
}
operator double() const {
return static_cast<double>(swap());
}
operator s8() const { return static_cast<s8>(swap()); }
operator u8() const { return static_cast<u8>(swap()); }
operator s16() const { return static_cast<s16>(swap()); }
operator u16() const { return static_cast<u16>(swap()); }
operator s32() const { return static_cast<s32>(swap()); }
operator u32() const { return static_cast<u32>(swap()); }
operator s64() const { return static_cast<s64>(swap()); }
operator u64() const { return static_cast<u64>(swap()); }
operator float() const { return static_cast<float>(swap()); }
operator double() const { return static_cast<double>(swap()); }
// +v
swapped_t operator+() const {
return +swap();
}
// -v
swapped_t operator-() const {
return -swap();
}
// +v
swapped_t operator+() const { return +swap(); }
// -v
swapped_t operator-() const { return -swap(); }
// v / 5
swapped_t operator/(const swapped_t& i) const {
return swap() / i.swap();
}
template <typename S>
swapped_t operator/(const S& i) const {
return swap() / i;
}
// v / 5
swapped_t operator/(const swapped_t& i) const { return swap() / i.swap(); }
template <typename S>
swapped_t operator/(const S& i) const {
return swap() / i;
}
// v * 5
swapped_t operator*(const swapped_t& i) const {
return swap() * i.swap();
}
template <typename S>
swapped_t operator*(const S& i) const {
return swap() * i;
}
// v * 5
swapped_t operator*(const swapped_t& i) const { return swap() * i.swap(); }
template <typename S>
swapped_t operator*(const S& i) const {
return swap() * i;
}
// v + 5
swapped_t operator+(const swapped_t& i) const {
return swap() + i.swap();
}
template <typename S>
swapped_t operator+(const S& i) const {
return swap() + static_cast<T>(i);
}
// v - 5
swapped_t operator-(const swapped_t& i) const {
return swap() - i.swap();
}
template <typename S>
swapped_t operator-(const S& i) const {
return swap() - static_cast<T>(i);
}
// v + 5
swapped_t operator+(const swapped_t& i) const { return swap() + i.swap(); }
template <typename S>
swapped_t operator+(const S& i) const {
return swap() + static_cast<T>(i);
}
// v - 5
swapped_t operator-(const swapped_t& i) const { return swap() - i.swap(); }
template <typename S>
swapped_t operator-(const S& i) const {
return swap() - static_cast<T>(i);
}
// v += 5
swapped_t& operator+=(const swapped_t& i) {
value = swap(swap() + i.swap());
return *this;
}
template <typename S>
swapped_t& operator+=(const S& i) {
value = swap(swap() + static_cast<T>(i));
return *this;
}
// v -= 5
swapped_t& operator-=(const swapped_t& i) {
value = swap(swap() - i.swap());
return *this;
}
template <typename S>
swapped_t& operator-=(const S& i) {
value = swap(swap() - static_cast<T>(i));
return *this;
}
// v += 5
swapped_t& operator+=(const swapped_t& i) {
value = swap(swap() + i.swap());
return *this;
}
template <typename S>
swapped_t& operator+=(const S& i) {
value = swap(swap() + static_cast<T>(i));
return *this;
}
// v -= 5
swapped_t& operator-=(const swapped_t& i) {
value = swap(swap() - i.swap());
return *this;
}
template <typename S>
swapped_t& operator-=(const S& i) {
value = swap(swap() - static_cast<T>(i));
return *this;
}
// ++v
swapped_t& operator++() {
value = swap(swap() + 1);
return *this;
}
// --v
swapped_t& operator--() {
value = swap(swap() - 1);
return *this;
}
// ++v
swapped_t& operator++() {
value = swap(swap() + 1);
return *this;
}
// --v
swapped_t& operator--() {
value = swap(swap() - 1);
return *this;
}
// v++
swapped_t operator++(int) {
swapped_t old = *this;
value = swap(swap() + 1);
return old;
}
// v--
swapped_t operator--(int) {
swapped_t old = *this;
value = swap(swap() - 1);
return old;
}
// Comparaison
// v == i
bool operator==(const swapped_t& i) const {
return swap() == i.swap();
}
template <typename S>
bool operator==(const S& i) const {
return swap() == i;
}
// v++
swapped_t operator++(int) {
swapped_t old = *this;
value = swap(swap() + 1);
return old;
}
// v--
swapped_t operator--(int) {
swapped_t old = *this;
value = swap(swap() - 1);
return old;
}
// Comparaison
// v == i
bool operator==(const swapped_t& i) const { return swap() == i.swap(); }
template <typename S>
bool operator==(const S& i) const {
return swap() == i;
}
// v != i
bool operator!=(const swapped_t& i) const {
return swap() != i.swap();
}
template <typename S>
bool operator!=(const S& i) const {
return swap() != i;
}
// v != i
bool operator!=(const swapped_t& i) const { return swap() != i.swap(); }
template <typename S>
bool operator!=(const S& i) const {
return swap() != i;
}
// v > i
bool operator>(const swapped_t& i) const {
return swap() > i.swap();
}
template <typename S>
bool operator>(const S& i) const {
return swap() > i;
}
// v > i
bool operator>(const swapped_t& i) const { return swap() > i.swap(); }
template <typename S>
bool operator>(const S& i) const {
return swap() > i;
}
// v < i
bool operator<(const swapped_t& i) const {
return swap() < i.swap();
}
template <typename S>
bool operator<(const S& i) const {
return swap() < i;
}
// v < i
bool operator<(const swapped_t& i) const { return swap() < i.swap(); }
template <typename S>
bool operator<(const S& i) const {
return swap() < i;
}
// v >= i
bool operator>=(const swapped_t& i) const {
return swap() >= i.swap();
}
template <typename S>
bool operator>=(const S& i) const {
return swap() >= i;
}
// v >= i
bool operator>=(const swapped_t& i) const { return swap() >= i.swap(); }
template <typename S>
bool operator>=(const S& i) const {
return swap() >= i;
}
// v <= i
bool operator<=(const swapped_t& i) const {
return swap() <= i.swap();
}
template <typename S>
bool operator<=(const S& i) const {
return swap() <= i;
}
// v <= i
bool operator<=(const swapped_t& i) const { return swap() <= i.swap(); }
template <typename S>
bool operator<=(const S& i) const {
return swap() <= i;
}
// logical
swapped_t operator!() const {
return !swap();
}
// logical
swapped_t operator!() const { return !swap(); }
// bitmath
swapped_t operator~() const {
return ~swap();
}
// bitmath
swapped_t operator~() const { return ~swap(); }
swapped_t operator&(const swapped_t& b) const {
return swap() & b.swap();
}
template <typename S>
swapped_t operator&(const S& b) const {
return swap() & b;
}
swapped_t& operator&=(const swapped_t& b) {
value = swap(swap() & b.swap());
return *this;
}
template <typename S>
swapped_t& operator&=(const S b) {
value = swap(swap() & b);
return *this;
}
swapped_t operator&(const swapped_t& b) const { return swap() & b.swap(); }
template <typename S>
swapped_t operator&(const S& b) const {
return swap() & b;
}
swapped_t& operator&=(const swapped_t& b) {
value = swap(swap() & b.swap());
return *this;
}
template <typename S>
swapped_t& operator&=(const S b) {
value = swap(swap() & b);
return *this;
}
swapped_t operator|(const swapped_t& b) const {
return swap() | b.swap();
}
template <typename S>
swapped_t operator|(const S& b) const {
return swap() | b;
}
swapped_t& operator|=(const swapped_t& b) {
value = swap(swap() | b.swap());
return *this;
}
template <typename S>
swapped_t& operator|=(const S& b) {
value = swap(swap() | b);
return *this;
}
swapped_t operator|(const swapped_t& b) const { return swap() | b.swap(); }
template <typename S>
swapped_t operator|(const S& b) const {
return swap() | b;
}
swapped_t& operator|=(const swapped_t& b) {
value = swap(swap() | b.swap());
return *this;
}
template <typename S>
swapped_t& operator|=(const S& b) {
value = swap(swap() | b);
return *this;
}
swapped_t operator^(const swapped_t& b) const {
return swap() ^ b.swap();
}
template <typename S>
swapped_t operator^(const S& b) const {
return swap() ^ b;
}
swapped_t& operator^=(const swapped_t& b) {
value = swap(swap() ^ b.swap());
return *this;
}
template <typename S>
swapped_t& operator^=(const S& b) {
value = swap(swap() ^ b);
return *this;
}
swapped_t operator^(const swapped_t& b) const { return swap() ^ b.swap(); }
template <typename S>
swapped_t operator^(const S& b) const {
return swap() ^ b;
}
swapped_t& operator^=(const swapped_t& b) {
value = swap(swap() ^ b.swap());
return *this;
}
template <typename S>
swapped_t& operator^=(const S& b) {
value = swap(swap() ^ b);
return *this;
}
template <typename S>
swapped_t operator<<(const S& b) const {
return swap() << b;
}
template <typename S>
swapped_t& operator<<=(const S& b) const {
value = swap(swap() << b);
return *this;
}
template <typename S>
swapped_t operator<<(const S& b) const {
return swap() << b;
}
template <typename S>
swapped_t& operator<<=(const S& b) const {
value = swap(swap() << b);
return *this;
}
template <typename S>
swapped_t operator>>(const S& b) const {
return swap() >> b;
}
template <typename S>
swapped_t& operator>>=(const S& b) const {
value = swap(swap() >> b);
return *this;
}
template <typename S>
swapped_t operator>>(const S& b) const {
return swap() >> b;
}
template <typename S>
swapped_t& operator>>=(const S& b) const {
value = swap(swap() >> b);
return *this;
}
// Member
/** todo **/
// Member
/** todo **/
// Arithmetics
template <typename S, typename T2, typename F2>
friend S operator+(const S& p, const swapped_t v);
// Arithmetics
template <typename S, typename T2, typename F2>
friend S operator+(const S& p, const swapped_t v);
template <typename S, typename T2, typename F2>
friend S operator-(const S& p, const swapped_t v);
template <typename S, typename T2, typename F2>
friend S operator-(const S& p, const swapped_t v);
template <typename S, typename T2, typename F2>
friend S operator/(const S& p, const swapped_t v);
template <typename S, typename T2, typename F2>
friend S operator/(const S& p, const swapped_t v);
template <typename S, typename T2, typename F2>
friend S operator*(const S& p, const swapped_t v);
template <typename S, typename T2, typename F2>
friend S operator*(const S& p, const swapped_t v);
template <typename S, typename T2, typename F2>
friend S operator%(const S& p, const swapped_t v);
template <typename S, typename T2, typename F2>
friend S operator%(const S& p, const swapped_t v);
// Arithmetics + assignments
template <typename S, typename T2, typename F2>
friend S operator+=(const S& p, const swapped_t v);
// Arithmetics + assignments
template <typename S, typename T2, typename F2>
friend S operator+=(const S& p, const swapped_t v);
template <typename S, typename T2, typename F2>
friend S operator-=(const S& p, const swapped_t v);
template <typename S, typename T2, typename F2>
friend S operator-=(const S& p, const swapped_t v);
// Bitmath
template <typename S, typename T2, typename F2>
friend S operator&(const S& p, const swapped_t v);
// Bitmath
template <typename S, typename T2, typename F2>
friend S operator&(const S& p, const swapped_t v);
// Comparison
template <typename S, typename T2, typename F2>
friend bool operator<(const S& p, const swapped_t v);
// Comparison
template <typename S, typename T2, typename F2>
friend bool operator<(const S& p, const swapped_t v);
template <typename S, typename T2, typename F2>
friend bool operator>(const S& p, const swapped_t v);
template <typename S, typename T2, typename F2>
friend bool operator>(const S& p, const swapped_t v);
template <typename S, typename T2, typename F2>
friend bool operator<=(const S& p, const swapped_t v);
template <typename S, typename T2, typename F2>
friend bool operator<=(const S& p, const swapped_t v);
template <typename S, typename T2, typename F2>
friend bool operator>=(const S& p, const swapped_t v);
template <typename S, typename T2, typename F2>
friend bool operator>=(const S& p, const swapped_t v);
template <typename S, typename T2, typename F2>
friend bool operator!=(const S& p, const swapped_t v);
template <typename S, typename T2, typename F2>
friend bool operator!=(const S& p, const swapped_t v);
template <typename S, typename T2, typename F2>
friend bool operator==(const S& p, const swapped_t v);
template <typename S, typename T2, typename F2>
friend bool operator==(const S& p, const swapped_t v);
};
// Arithmetics
template <typename S, typename T, typename F>
S operator+(const S& i, const swap_struct_t<T, F> v) {
return i + v.swap();
return i + v.swap();
}
template <typename S, typename T, typename F>
S operator-(const S& i, const swap_struct_t<T, F> v) {
return i - v.swap();
return i - v.swap();
}
template <typename S, typename T, typename F>
S operator/(const S& i, const swap_struct_t<T, F> v) {
return i / v.swap();
return i / v.swap();
}
template <typename S, typename T, typename F>
S operator*(const S& i, const swap_struct_t<T, F> v) {
return i * v.swap();
return i * v.swap();
}
template <typename S, typename T, typename F>
S operator%(const S& i, const swap_struct_t<T, F> v) {
return i % v.swap();
return i % v.swap();
}
// Arithmetics + assignments
template <typename S, typename T, typename F>
S& operator+=(S& i, const swap_struct_t<T, F> v) {
i += v.swap();
return i;
i += v.swap();
return i;
}
template <typename S, typename T, typename F>
S& operator-=(S& i, const swap_struct_t<T, F> v) {
i -= v.swap();
return i;
i -= v.swap();
return i;
}
// Logical
template <typename S, typename T, typename F>
S operator&(const S& i, const swap_struct_t<T, F> v) {
return i & v.swap();
return i & v.swap();
}
template <typename S, typename T, typename F>
S operator&(const swap_struct_t<T, F> v, const S& i) {
return static_cast<S>(v.swap() & i);
return static_cast<S>(v.swap() & i);
}
// Comparaison
template <typename S, typename T, typename F>
bool operator<(const S& p, const swap_struct_t<T, F> v) {
return p < v.swap();
return p < v.swap();
}
template <typename S, typename T, typename F>
bool operator>(const S& p, const swap_struct_t<T, F> v) {
return p > v.swap();
return p > v.swap();
}
template <typename S, typename T, typename F>
bool operator<=(const S& p, const swap_struct_t<T, F> v) {
return p <= v.swap();
return p <= v.swap();
}
template <typename S, typename T, typename F>
bool operator>=(const S& p, const swap_struct_t<T, F> v) {
return p >= v.swap();
return p >= v.swap();
}
template <typename S, typename T, typename F>
bool operator!=(const S& p, const swap_struct_t<T, F> v) {
return p != v.swap();
return p != v.swap();
}
template <typename S, typename T, typename F>
bool operator==(const S& p, const swap_struct_t<T, F> v) {
return p == v.swap();
return p == v.swap();
}
template <typename T>
struct swap_64_t {
static T swap(T x) {
return static_cast<T>(Common::swap64(x));
}
static T swap(T x) { return static_cast<T>(Common::swap64(x)); }
};
template <typename T>
struct swap_32_t {
static T swap(T x) {
return static_cast<T>(Common::swap32(x));
}
static T swap(T x) { return static_cast<T>(Common::swap32(x)); }
};
template <typename T>
struct swap_16_t {
static T swap(T x) {
return static_cast<T>(Common::swap16(x));
}
static T swap(T x) { return static_cast<T>(Common::swap16(x)); }
};
template <typename T>
struct swap_float_t {
static T swap(T x) {
return static_cast<T>(Common::swapf(x));
}
static T swap(T x) { return static_cast<T>(Common::swapf(x)); }
};
template <typename T>
struct swap_double_t {
static T swap(T x) {
return static_cast<T>(Common::swapd(x));
}
static T swap(T x) { return static_cast<T>(Common::swapd(x)); }
};
template <typename T>
struct swap_enum_t {
static_assert(std::is_enum_v<T>);
using base = std::underlying_type_t<T>;
static_assert(std::is_enum_v<T>);
using base = std::underlying_type_t<T>;
public:
swap_enum_t() = default;
swap_enum_t(const T& v) : value(swap(v)) {}
public:
swap_enum_t() = default;
swap_enum_t(const T& v) : value(swap(v)) {}
swap_enum_t& operator=(const T& v) {
value = swap(v);
return *this;
}
swap_enum_t& operator=(const T& v) {
value = swap(v);
return *this;
}
operator T() const {
return swap(value);
}
operator T() const { return swap(value); }
explicit operator base() const {
return static_cast<base>(swap(value));
}
explicit operator base() const { return static_cast<base>(swap(value)); }
protected:
T value{};
// clang-format off
protected:
T value{};
// clang-format off
using swap_t = std::conditional_t<
std::is_same_v<base, u16>, swap_16_t<u16>, std::conditional_t<
std::is_same_v<base, s16>, swap_16_t<s16>, std::conditional_t<
@ -597,14 +510,12 @@ protected:
std::is_same_v<base, s32>, swap_32_t<s32>, std::conditional_t<
std::is_same_v<base, u64>, swap_64_t<u64>, std::conditional_t<
std::is_same_v<base, s64>, swap_64_t<s64>, void>>>>>>;
// clang-format on
static T swap(T x) {
return static_cast<T>(swap_t::swap(static_cast<base>(x)));
}
// clang-format on
static T swap(T x) { return static_cast<T>(swap_t::swap(static_cast<base>(x))); }
};
struct SwapTag {}; // Use the different endianness from the system
struct KeepTag {}; // Use the same endianness as the system
struct SwapTag {}; // Use the different endianness from the system
struct KeepTag {}; // Use the same endianness as the system
template <typename T, typename Tag>
struct AddEndian;
@ -613,65 +524,65 @@ struct AddEndian;
template <typename T>
struct AddEndian<T, KeepTag> {
using type = T;
using type = T;
};
// SwapTag specializations
template <>
struct AddEndian<u8, SwapTag> {
using type = u8;
using type = u8;
};
template <>
struct AddEndian<u16, SwapTag> {
using type = swap_struct_t<u16, swap_16_t<u16>>;
using type = swap_struct_t<u16, swap_16_t<u16>>;
};
template <>
struct AddEndian<u32, SwapTag> {
using type = swap_struct_t<u32, swap_32_t<u32>>;
using type = swap_struct_t<u32, swap_32_t<u32>>;
};
template <>
struct AddEndian<u64, SwapTag> {
using type = swap_struct_t<u64, swap_64_t<u64>>;
using type = swap_struct_t<u64, swap_64_t<u64>>;
};
template <>
struct AddEndian<s8, SwapTag> {
using type = s8;
using type = s8;
};
template <>
struct AddEndian<s16, SwapTag> {
using type = swap_struct_t<s16, swap_16_t<s16>>;
using type = swap_struct_t<s16, swap_16_t<s16>>;
};
template <>
struct AddEndian<s32, SwapTag> {
using type = swap_struct_t<s32, swap_32_t<s32>>;
using type = swap_struct_t<s32, swap_32_t<s32>>;
};
template <>
struct AddEndian<s64, SwapTag> {
using type = swap_struct_t<s64, swap_64_t<s64>>;
using type = swap_struct_t<s64, swap_64_t<s64>>;
};
template <>
struct AddEndian<float, SwapTag> {
using type = swap_struct_t<float, swap_float_t<float>>;
using type = swap_struct_t<float, swap_float_t<float>>;
};
template <>
struct AddEndian<double, SwapTag> {
using type = swap_struct_t<double, swap_double_t<double>>;
using type = swap_struct_t<double, swap_double_t<double>>;
};
template <typename T>
struct AddEndian<T, SwapTag> {
static_assert(std::is_enum_v<T>);
using type = swap_enum_t<T>;
static_assert(std::is_enum_v<T>);
using type = swap_enum_t<T>;
};
// Alias LETag/BETag as KeepTag/SwapTag depending on the system

View file

@ -18,6 +18,6 @@ namespace SystemModel {
KTR = NewNintendo3DS,
FTR = Nintendo2DS,
RED = NewNintendo3DS_XL,
JAN = NewNintendo2DS_XL
JAN = NewNintendo2DS_XL,
};
}

View file

@ -35,7 +35,7 @@ Panda3DS is still in the early stages of development. Many games boot, many don'
For documenting game compatibility, make sure to visit the [games list repository](https://github.com/Panda3DS-emu/Panda3DS-Games-List). For miscellaneous issues or more technical issues, feel free to use this repo's issues tab.
# 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 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.
@ -113,11 +113,12 @@ Panda3DS also supports controller input using the SDL2 GameController API.
- [SkyEmu](https://github.com/skylersaleh/SkyEmu): A seagull-themed low-level GameBoy, GameBoy Color, GameBoy Advance and Nintendo DS emulator that is designed to be easy to use, cross platform and accurate.
- [NanoBoyAdvance](https://github.com/nba-emu/NanoBoyAdvance): A Game Boy Advance emulator focusing on hardware research and cycle-accurate emulation
- [Dust](https://github.com/kelpsyberry/dust): Nintendo DS emulator for desktop devices and the web
- [felix86](https://github.com/OFFTKP/felix86): A new x86-64 → RISC-V Linux userspace emulator
- [ChonkyStation](https://github.com/liuk7071/ChonkyStation): Work-in-progress PlayStation emulator
- [ChonkyStation 3](https://github.com/liuk7071/ChonkyStation3): Experimental HLE PS3 emulator for Windows, MacOS and Linux
- [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
- [ChonkyStation](https://github.com/liuk7071/ChonkyStation): Work-in-progress PlayStation emulator
- [shadPS4](https://github.com/shadps4-emu/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
- [Tanuki3DS](https://github.com/burhanr13/Tanuki3DS/): A new 3DS emulator for MacOS and Linux
# Support
If you find this project exciting and want to support the founder, check out [his Patreon](https://www.patreon.com/wheremyfoodat) or [Ko-fi](https://ko-fi.com/wheremyfoodat)

View file

@ -47,6 +47,7 @@ void EmulatorConfig::load() {
defaultRomPath = toml::find_or<std::string>(general, "DefaultRomPath", "");
printAppVersion = toml::find_or<toml::boolean>(general, "PrintAppVersion", true);
circlePadProEnabled = toml::find_or<toml::boolean>(general, "EnableCirclePadPro", true);
systemLanguage = languageCodeFromString(toml::find_or<std::string>(general, "SystemLanguage", "en"));
}
}
@ -72,14 +73,14 @@ void EmulatorConfig::load() {
auto gpu = gpuResult.unwrap();
// Get renderer
auto rendererName = toml::find_or<std::string>(gpu, "Renderer", "OpenGL");
auto rendererName = toml::find_or<std::string>(gpu, "Renderer", Renderer::typeToString(rendererDefault));
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;
rendererType = rendererDefault;
}
shaderJitEnabled = toml::find_or<toml::boolean>(gpu, "EnableShaderJIT", shaderJitDefault);
@ -90,7 +91,12 @@ void EmulatorConfig::load() {
forceShadergenForLights = toml::find_or<toml::boolean>(gpu, "ForceShadergenForLighting", true);
lightShadergenThreshold = toml::find_or<toml::integer>(gpu, "ShadergenLightThreshold", 1);
hashTextures = toml::find_or<toml::boolean>(gpu, "HashTextures", hashTexturesDefault);
enableRenderdoc = toml::find_or<toml::boolean>(gpu, "EnableRenderdoc", false);
auto screenLayoutName = toml::find_or<std::string>(gpu, "ScreenLayout", "Default");
screenLayout = ScreenLayout::layoutFromString(screenLayoutName);
topScreenSize = float(std::clamp(toml::find_or<toml::floating>(gpu, "TopScreenSize", 0.5), 0.0, 1.0));
}
}
@ -101,7 +107,7 @@ void EmulatorConfig::load() {
auto dspCoreName = toml::find_or<std::string>(audio, "DSPEmulation", "HLE");
dspType = Audio::DSPCore::typeFromString(dspCoreName);
audioEnabled = toml::find_or<toml::boolean>(audio, "EnableAudio", audioEnabledDefault);
aacEnabled = toml::find_or<toml::boolean>(audio, "EnableAACAudio", true);
printDSPFirmware = toml::find_or<toml::boolean>(audio, "PrintDSPFirmware", false);
@ -173,6 +179,7 @@ void EmulatorConfig::save() {
data["General"]["DefaultRomPath"] = defaultRomPath.string();
data["General"]["PrintAppVersion"] = printAppVersion;
data["General"]["SystemLanguage"] = languageCodeToString(systemLanguage);
data["General"]["EnableCirclePadPro"] = circlePadProEnabled;
data["Window"]["AppVersionOnWindow"] = windowSettings.showAppVersion;
data["Window"]["RememberWindowPosition"] = windowSettings.rememberPosition;
@ -180,7 +187,7 @@ void EmulatorConfig::save() {
data["Window"]["WindowPosY"] = windowSettings.y;
data["Window"]["WindowWidth"] = windowSettings.width;
data["Window"]["WindowHeight"] = windowSettings.height;
data["GPU"]["EnableShaderJIT"] = shaderJitEnabled;
data["GPU"]["Renderer"] = std::string(Renderer::typeToString(rendererType));
data["GPU"]["EnableVSync"] = vsyncEnabled;
@ -190,6 +197,9 @@ void EmulatorConfig::save() {
data["GPU"]["ShadergenLightThreshold"] = lightShadergenThreshold;
data["GPU"]["AccelerateShaders"] = accelerateShaders;
data["GPU"]["EnableRenderdoc"] = enableRenderdoc;
data["GPU"]["HashTextures"] = hashTextures;
data["GPU"]["ScreenLayout"] = std::string(ScreenLayout::layoutToString(screenLayout));
data["GPU"]["TopScreenSize"] = double(topScreenSize);
data["Audio"]["DSPEmulation"] = std::string(Audio::DSPCore::typeToString(dspType));
data["Audio"]["EnableAudio"] = audioEnabled;

View file

@ -6,118 +6,114 @@
#include <bit>
#include <functional>
#include "cpu_dynarmic.hpp"
#include "helpers.hpp"
namespace {
template <size_t N>
struct StringLiteral {
constexpr StringLiteral(const char(&str)[N]) {
std::copy_n(str, N, value);
}
template <size_t N>
struct StringLiteral {
constexpr StringLiteral(const char (&str)[N]) { std::copy_n(str, N, value); }
static constexpr std::size_t strlen = N - 1;
static constexpr std::size_t size = N;
static constexpr std::size_t strlen = N - 1;
static constexpr std::size_t size = N;
char value[N];
};
char value[N];
};
template <StringLiteral haystack, StringLiteral needle>
constexpr u32 GetMatchingBitsFromStringLiteral() {
u32 result = 0;
for (size_t i = 0; i < haystack.strlen; i++) {
for (size_t a = 0; a < needle.strlen; a++) {
if (haystack.value[i] == needle.value[a]) {
result |= 1 << (haystack.strlen - 1 - i);
}
}
}
return result;
}
template <StringLiteral haystack, StringLiteral needle>
constexpr u32 GetMatchingBitsFromStringLiteral() {
u32 result = 0;
for (size_t i = 0; i < haystack.strlen; i++) {
for (size_t a = 0; a < needle.strlen; a++) {
if (haystack.value[i] == needle.value[a]) {
result |= 1 << (haystack.strlen - 1 - i);
}
}
}
return result;
}
template <u32 mask_>
constexpr u32 DepositBits(u32 val) {
u32 mask = mask_;
u32 res = 0;
for (u32 bb = 1; mask; bb += bb) {
u32 neg_mask = 0 - mask;
if (val & bb)
res |= mask & neg_mask;
mask &= mask - 1;
}
return res;
}
template <u32 mask_>
constexpr u32 DepositBits(u32 val) {
u32 mask = mask_;
u32 res = 0;
for (u32 bb = 1; mask; bb += bb) {
u32 neg_mask = 0 - mask;
if (val & bb) res |= mask & neg_mask;
mask &= mask - 1;
}
return res;
}
template <StringLiteral haystack>
struct MatcherArg {
template <StringLiteral needle>
u32 Get() {
return DepositBits<GetMatchingBitsFromStringLiteral<haystack, needle>()>(instruction);
}
template <StringLiteral haystack>
struct MatcherArg {
template <StringLiteral needle>
u32 Get() {
return DepositBits<GetMatchingBitsFromStringLiteral<haystack, needle>()>(instruction);
}
u32 instruction;
};
u32 instruction;
};
struct Matcher {
u32 mask;
u32 expect;
std::function<u64(u32)> fn;
};
struct Matcher {
u32 mask;
u32 expect;
std::function<u64(u32)> fn;
};
u64 DataProcessing_imm(auto i) {
if (i.template Get<"d">() == 15) {
return 7;
}
return 1;
}
u64 DataProcessing_reg(auto i) {
if (i.template Get<"d">() == 15) {
return 7;
}
return 1;
}
u64 DataProcessing_rsr(auto i) {
if (i.template Get<"d">() == 15) {
return 8;
}
return 2;
}
u64 LoadStoreSingle_imm(auto) {
return 2;
}
u64 LoadStoreSingle_reg(auto i) {
// TODO: Load PC
if (i.template Get<"u">() == 1 && i.template Get<"r">() == 0 &&
(i.template Get<"v">() == 0 || i.template Get<"v">() == 2)) {
return 2;
}
return 4;
}
u64 LoadStoreMultiple(auto i) {
// TODO: Load PC
return 1 + std::popcount(i.template Get<"x">()) / 2;
}
u64 DataProcessing_imm(auto i) {
if (i.template Get<"d">() == 15) {
return 7;
}
return 1;
}
u64 DataProcessing_reg(auto i) {
if (i.template Get<"d">() == 15) {
return 7;
}
return 1;
}
u64 DataProcessing_rsr(auto i) {
if (i.template Get<"d">() == 15) {
return 8;
}
return 2;
}
u64 LoadStoreSingle_imm(auto) { return 2; }
u64 LoadStoreSingle_reg(auto i) {
// TODO: Load PC
if (i.template Get<"u">() == 1 && i.template Get<"r">() == 0 && (i.template Get<"v">() == 0 || i.template Get<"v">() == 2)) {
return 2;
}
return 4;
}
u64 LoadStoreMultiple(auto i) {
// TODO: Load PC
return 1 + std::popcount(i.template Get<"x">()) / 2;
}
u64 SupervisorCall(auto i) {
// Consume extra cycles for the GetSystemTick SVC since some games wait with it in a loop rather than
// Properly sleeping until a VBlank interrupt
if (i.template Get<"v">() == 0x28) {
return 152;
}
u64 SupervisorCall(auto i) {
// Consume extra cycles for the GetSystemTick SVC since some games wait with it in a loop rather than
// Properly sleeping until a VBlank interrupt
if (i.template Get<"v">() == 0x28) {
return 152;
}
return 8;
}
return 8;
}
#define INST(NAME, BS, CYCLES) \
Matcher{GetMatchingBitsFromStringLiteral<BS, "01">(), \
GetMatchingBitsFromStringLiteral<BS, "1">(), \
std::function<u64(u32)>{[](u32 instruction) -> u64 { \
[[maybe_unused]] MatcherArg<BS> i{instruction}; \
return (CYCLES); \
}}},
Matcher{ \
GetMatchingBitsFromStringLiteral<BS, "01">(), GetMatchingBitsFromStringLiteral<BS, "1">(), \
std::function<u64(u32)>{[](u32 instruction) -> u64 { \
[[maybe_unused]] MatcherArg<BS> i{instruction}; \
return (CYCLES); \
}} \
},
const std::array arm_matchers{
// clang-format off
const std::array arm_matchers{
// clang-format off
// Branch instructions
INST("BLX (imm)", "1111101hvvvvvvvvvvvvvvvvvvvvvvvv", 5) // v5
@ -389,11 +385,11 @@ namespace {
INST("RFE", "1111100--0-1----0000101000000000", 9) // v6
INST("SRS", "1111100--1-0110100000101000-----", 1) // v6
// clang-format on
};
// clang-format on
};
const std::array thumb_matchers{
// clang-format off
const std::array thumb_matchers{
// clang-format off
// Shift (immediate) add, subtract, move and compare instructions
INST("LSL (imm)", "00000vvvvvmmmddd", 1)
@ -487,23 +483,21 @@ namespace {
INST("BL (imm)", "11110Svvvvvvvvvv11j1jvvvvvvvvvvv", 4) // v4T
INST("BLX (imm)", "11110Svvvvvvvvvv11j0jvvvvvvvvvvv", 5) // v5T
// clang-format on
};
// clang-format on
};
} // namespace
} // namespace
u64 MyEnvironment::getCyclesForInstruction(bool is_thumb, u32 instruction) {
if (is_thumb) {
return 1;
}
if (is_thumb) {
return 1;
}
const auto matches_instruction = [instruction](const auto& matcher) {
return (instruction & matcher.mask) == matcher.expect;
};
const auto matches_instruction = [instruction](const auto& matcher) { return (instruction & matcher.mask) == matcher.expect; };
auto iter = std::find_if(arm_matchers.begin(), arm_matchers.end(), matches_instruction);
if (iter != arm_matchers.end()) {
return iter->fn(instruction);
}
return 1;
auto iter = std::find_if(arm_matchers.begin(), arm_matchers.end(), matches_instruction);
if (iter != arm_matchers.end()) {
return iter->fn(instruction);
}
return 1;
}

View file

@ -27,6 +27,7 @@ static constexpr Xmm scratch2 = xmm1;
static constexpr Xmm src1_xmm = xmm2;
static constexpr Xmm src2_xmm = xmm3;
static constexpr Xmm src3_xmm = xmm4;
static constexpr Xmm scratch3 = xmm5;
#if defined(PANDA3DS_MS_ABI)
// Register that points to PICA state. Must be volatile for the aforementioned reasons
@ -382,20 +383,12 @@ void ShaderEmitter::storeRegister(Xmm source, const PICAShader& shader, u32 dest
(((writeMask & 0b0010) ? 0 : 1) << 4) |
(((writeMask & 0b0001) ? 2 : 3) << 6);
// Reorder instructions based on whether the source == scratch1. This is to avoid overwriting scratch1 if it's the source,
// While also having the memory load come first to mitigate execution hazards and give the load more time to complete before reading if possible
if (source != scratch1) {
movaps(scratch1, xword[statePointer + offset]);
movaps(scratch2, source);
} else {
movaps(scratch2, source);
movaps(scratch1, xword[statePointer + offset]);
}
unpckhps(scratch2, scratch1); // Unpack X/Y components of source and destination
unpcklps(scratch1, source); // Unpack Z/W components of source and destination
shufps(scratch1, scratch2, selector); // "merge-shuffle" dest and source using selecto
movaps(xword[statePointer + offset], scratch1); // Write back
movaps(scratch3, xword[statePointer + offset]);
movaps(scratch2, source);
unpckhps(scratch2, scratch3); // Unpack X/Y components of source and destination
unpcklps(scratch3, source); // Unpack Z/W components of source and destination
shufps(scratch3, scratch2, selector); // "merge-shuffle" dest and source using selecto
movaps(xword[statePointer + offset], scratch3); // Write back
}
}

View file

@ -441,7 +441,7 @@ void GPU::fireDMA(u32 dest, u32 source, u32 size) {
u8* fcram = mem.getFCRAM();
std::memcpy(&vram[dest - vramStart], &fcram[source - fcramStart], size);
} else {
printf("Non-trivially optimizable GPU DMA. Falling back to byte-by-byte transfer\n");
log("Non-trivially optimizable GPU DMA. Falling back to byte-by-byte transfer\n");
for (u32 i = 0; i < size; i++) {
mem.write8(dest + i, mem.read8(source + i));

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