mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-08 23:25:40 +12:00
Merge branch 'master' into sd-card
This commit is contained in:
commit
42bfcaf980
68 changed files with 3543 additions and 310 deletions
9
.github/mac-bundle.sh
vendored
9
.github/mac-bundle.sh
vendored
|
@ -36,6 +36,15 @@ PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleIconFile string AppIcon
|
||||||
PlistBuddy Alber.app/Contents/Info.plist -c "add NSHighResolutionCapable bool true"
|
PlistBuddy Alber.app/Contents/Info.plist -c "add NSHighResolutionCapable bool true"
|
||||||
PlistBuddy Alber.app/Contents/version.plist -c "add ProjectName string Alber"
|
PlistBuddy Alber.app/Contents/version.plist -c "add ProjectName string Alber"
|
||||||
|
|
||||||
|
PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleExecutable string Alber"
|
||||||
|
PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleDevelopmentRegion string en"
|
||||||
|
PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleInfoDictionaryVersion string 6.0"
|
||||||
|
PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleName string Panda3DS"
|
||||||
|
PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundlePackageType string APPL"
|
||||||
|
PlistBuddy Alber.app/Contents/Info.plist -c "add NSHumanReadableCopyright string Copyright 2023 Panda3DS Team"
|
||||||
|
|
||||||
|
PlistBuddy Alber.app/Contents/Info.plist -c "add LSMinimumSystemVersion string 10.15"
|
||||||
|
|
||||||
# Bundle dylibs
|
# Bundle dylibs
|
||||||
dylibbundler -od -b -x Alber.app/Contents/MacOS/Alber -d Alber.app/Contents/Frameworks/ -p @rpath -s /Users/runner/work/Panda3DS/Panda3DS/VULKAN_SDK/lib
|
dylibbundler -od -b -x Alber.app/Contents/MacOS/Alber -d Alber.app/Contents/Frameworks/ -p @rpath -s /Users/runner/work/Panda3DS/Panda3DS/VULKAN_SDK/lib
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# We need to be able to use enable_language(OBJC) on Mac, so we need CMake 3.16 vs the 3.10 we use otherwise. Blame Apple.
|
# We need to be able to use enable_language(OBJC) on Mac, so we need CMake 3.16 vs the 3.10 we use otherwise. Blame Apple.
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0" CACHE STRING "Minimum OS X deployment version")
|
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version")
|
||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
else()
|
else()
|
||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
@ -144,12 +144,13 @@ set(PICA_SOURCE_FILES src/core/PICA/gpu.cpp src/core/PICA/regs.cpp src/core/PICA
|
||||||
src/core/PICA/dynapica/shader_rec_emitter_x64.cpp src/core/PICA/pica_hash.cpp
|
src/core/PICA/dynapica/shader_rec_emitter_x64.cpp src/core/PICA/pica_hash.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(LOADER_SOURCE_FILES src/core/loader/elf.cpp src/core/loader/ncsd.cpp src/core/loader/ncch.cpp src/core/loader/lz77.cpp)
|
set(LOADER_SOURCE_FILES src/core/loader/elf.cpp src/core/loader/ncsd.cpp src/core/loader/ncch.cpp src/core/loader/3dsx.cpp src/core/loader/lz77.cpp)
|
||||||
set(FS_SOURCE_FILES src/core/fs/archive_self_ncch.cpp src/core/fs/archive_save_data.cpp src/core/fs/archive_sdmc.cpp
|
set(FS_SOURCE_FILES src/core/fs/archive_self_ncch.cpp src/core/fs/archive_save_data.cpp src/core/fs/archive_sdmc.cpp
|
||||||
src/core/fs/archive_ext_save_data.cpp src/core/fs/archive_ncch.cpp src/core/fs/romfs.cpp
|
src/core/fs/archive_ext_save_data.cpp src/core/fs/archive_ncch.cpp src/core/fs/romfs.cpp
|
||||||
src/core/fs/ivfc.cpp
|
src/core/fs/ivfc.cpp src/core/fs/archive_user_save_data.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(APPLET_SOURCE_FILES src/core/applets/applet.cpp src/core/applets/mii_selector.cpp src/core/applets/software_keyboard.cpp src/core/applets/applet_manager.cpp)
|
||||||
set(RENDERER_SW_SOURCE_FILES src/core/renderer_sw/renderer_sw.cpp)
|
set(RENDERER_SW_SOURCE_FILES src/core/renderer_sw/renderer_sw.cpp)
|
||||||
|
|
||||||
set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp
|
set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp
|
||||||
|
@ -160,7 +161,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp
|
||||||
include/services/gsp_gpu.hpp include/services/gsp_lcd.hpp include/arm_defs.hpp include/renderer_null/renderer_null.hpp
|
include/services/gsp_gpu.hpp include/services/gsp_lcd.hpp include/arm_defs.hpp include/renderer_null/renderer_null.hpp
|
||||||
include/PICA/gpu.hpp include/PICA/regs.hpp include/services/ndm.hpp
|
include/PICA/gpu.hpp include/PICA/regs.hpp include/services/ndm.hpp
|
||||||
include/PICA/shader.hpp include/PICA/shader_unit.hpp include/PICA/float_types.hpp
|
include/PICA/shader.hpp include/PICA/shader_unit.hpp include/PICA/float_types.hpp
|
||||||
include/logger.hpp include/loader/ncch.hpp include/loader/ncsd.hpp include/io_file.hpp
|
include/logger.hpp include/loader/ncch.hpp include/loader/ncsd.hpp include/loader/3dsx.hpp include/io_file.hpp
|
||||||
include/loader/lz77.hpp include/fs/archive_base.hpp include/fs/archive_self_ncch.hpp
|
include/loader/lz77.hpp include/fs/archive_base.hpp include/fs/archive_self_ncch.hpp
|
||||||
include/services/dsp.hpp include/services/cfg.hpp include/services/region_codes.hpp
|
include/services/dsp.hpp include/services/cfg.hpp include/services/region_codes.hpp
|
||||||
include/fs/archive_save_data.hpp include/fs/archive_sdmc.hpp include/services/ptm.hpp
|
include/fs/archive_save_data.hpp include/fs/archive_sdmc.hpp include/services/ptm.hpp
|
||||||
|
@ -178,7 +179,8 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp
|
||||||
include/config.hpp include/services/ir_user.hpp include/http_server.hpp include/cheats.hpp
|
include/config.hpp include/services/ir_user.hpp include/http_server.hpp include/cheats.hpp
|
||||||
include/action_replay.hpp include/renderer_sw/renderer_sw.hpp include/compiler_builtins.hpp
|
include/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/fs/romfs.hpp include/fs/ivfc.hpp include/discord_rpc.hpp include/services/http.hpp include/result/result_cfg.hpp
|
||||||
include/math_util.hpp include/services/soc.hpp include/services/news_u.hpp
|
include/applets/applet.hpp include/applets/mii_selector.hpp include/math_util.hpp include/services/soc.hpp
|
||||||
|
include/services/news_u.hpp include/applets/software_keyboard.hpp include/applets/applet_manager.hpp include/fs/archive_user_save_data.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
cmrc_add_resource_library(
|
cmrc_add_resource_library(
|
||||||
|
@ -203,6 +205,7 @@ source_group("Source Files\\Core\\Filesystem" FILES ${FS_SOURCE_FILES})
|
||||||
source_group("Source Files\\Core\\Kernel" FILES ${KERNEL_SOURCE_FILES})
|
source_group("Source Files\\Core\\Kernel" FILES ${KERNEL_SOURCE_FILES})
|
||||||
source_group("Source Files\\Core\\Loader" FILES ${LOADER_SOURCE_FILES})
|
source_group("Source Files\\Core\\Loader" FILES ${LOADER_SOURCE_FILES})
|
||||||
source_group("Source Files\\Core\\Services" FILES ${SERVICE_SOURCE_FILES})
|
source_group("Source Files\\Core\\Services" FILES ${SERVICE_SOURCE_FILES})
|
||||||
|
source_group("Source Files\\Core\\Applets" FILES ${APPLET_SOURCE_FILES})
|
||||||
source_group("Source Files\\Core\\PICA" FILES ${PICA_SOURCE_FILES})
|
source_group("Source Files\\Core\\PICA" FILES ${PICA_SOURCE_FILES})
|
||||||
source_group("Source Files\\Core\\Software Renderer" FILES ${RENDERER_SW_SOURCE_FILES})
|
source_group("Source Files\\Core\\Software Renderer" FILES ${RENDERER_SW_SOURCE_FILES})
|
||||||
source_group("Source Files\\Third Party" FILES ${THIRD_PARTY_SOURCE_FILES})
|
source_group("Source Files\\Third Party" FILES ${THIRD_PARTY_SOURCE_FILES})
|
||||||
|
@ -246,26 +249,64 @@ if(ENABLE_VULKAN)
|
||||||
)
|
)
|
||||||
|
|
||||||
set(RENDERER_VK_INCLUDE_FILES include/renderer_vk/renderer_vk.hpp
|
set(RENDERER_VK_INCLUDE_FILES include/renderer_vk/renderer_vk.hpp
|
||||||
include/renderer_vk/vulkan_api.hpp include/renderer_vk/vk_debug.hpp
|
include/renderer_vk/vk_api.hpp include/renderer_vk/vk_debug.hpp
|
||||||
|
include/renderer_vk/vk_descriptor_heap.hpp
|
||||||
|
include/renderer_vk/vk_descriptor_update_batch.hpp
|
||||||
|
include/renderer_vk/vk_sampler_cache.hpp
|
||||||
|
include/renderer_vk/vk_memory.hpp include/renderer_vk/vk_pica.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(RENDERER_VK_SOURCE_FILES src/core/renderer_vk/renderer_vk.cpp
|
set(RENDERER_VK_SOURCE_FILES src/core/renderer_vk/renderer_vk.cpp
|
||||||
src/core/renderer_vk/vulkan_api.cpp src/core/renderer_vk/vk_debug.cpp
|
src/core/renderer_vk/vk_api.cpp src/core/renderer_vk/vk_debug.cpp
|
||||||
|
src/core/renderer_vk/vk_descriptor_heap.cpp
|
||||||
|
src/core/renderer_vk/vk_descriptor_update_batch.cpp
|
||||||
|
src/core/renderer_vk/vk_sampler_cache.cpp
|
||||||
|
src/core/renderer_vk/vk_memory.cpp src/core/renderer_vk/vk_pica.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(HEADER_FILES ${HEADER_FILES} ${RENDERER_VK_INCLUDE_FILES})
|
set(HEADER_FILES ${HEADER_FILES} ${RENDERER_VK_INCLUDE_FILES})
|
||||||
source_group("Source Files\\Core\\Vulkan Renderer" FILES ${RENDERER_VK_SOURCE_FILES})
|
source_group("Source Files\\Core\\Vulkan Renderer" FILES ${RENDERER_VK_SOURCE_FILES})
|
||||||
|
|
||||||
|
|
||||||
|
set(RENDERER_VK_HOST_SHADERS_SOURCE
|
||||||
|
"src/host_shaders/vulkan_display.frag"
|
||||||
|
"src/host_shaders/vulkan_display.vert"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
set( RENDERER_VK_HOST_SHADERS_FLAGS -e main --target-env vulkan1.1)
|
||||||
|
|
||||||
|
if(GPU_DEBUG_INFO)
|
||||||
|
# generate nonsemantic shader debug information with source
|
||||||
|
set( RENDERER_VK_HOST_SHADERS_FLAGS ${RENDERER_VK_HOST_SHADERS_FLAGS} -gVS)
|
||||||
|
else()
|
||||||
|
set( RENDERER_VK_HOST_SHADERS_FLAGS ${RENDERER_VK_HOST_SHADERS_FLAGS} -g0)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Compile each vulkan shader into an .spv file
|
||||||
|
foreach( HOST_SHADER_SOURCE ${RENDERER_VK_HOST_SHADERS_SOURCE} )
|
||||||
|
get_filename_component( FILE_NAME ${HOST_SHADER_SOURCE} NAME )
|
||||||
|
set( HOST_SHADER_SPIRV "${PROJECT_BINARY_DIR}/host_shaders/${FILE_NAME}.spv" )
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${HOST_SHADER_SPIRV}
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${PROJECT_BINARY_DIR}/host_shaders/"
|
||||||
|
COMMAND Vulkan::glslangValidator ${RENDERER_VK_HOST_SHADERS_FLAGS} -V "${PROJECT_SOURCE_DIR}/${HOST_SHADER_SOURCE}" -o ${HOST_SHADER_SPIRV}
|
||||||
|
DEPENDS ${HOST_SHADER_SOURCE}
|
||||||
|
)
|
||||||
|
list( APPEND RENDERER_VK_HOST_SHADERS_SPIRV ${HOST_SHADER_SPIRV} )
|
||||||
|
endforeach()
|
||||||
|
|
||||||
cmrc_add_resource_library(
|
cmrc_add_resource_library(
|
||||||
resources_renderer_vk
|
resources_renderer_vk
|
||||||
NAMESPACE RendererVK
|
NAMESPACE RendererVK
|
||||||
WHENCE "src/host_shaders/"
|
WHENCE "${PROJECT_BINARY_DIR}/host_shaders/"
|
||||||
|
${RENDERER_VK_HOST_SHADERS_SPIRV}
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
source_group("Header Files\\Core" FILES ${HEADER_FILES})
|
source_group("Header Files\\Core" FILES ${HEADER_FILES})
|
||||||
set(ALL_SOURCES ${SOURCE_FILES} ${FS_SOURCE_FILES} ${CRYPTO_SOURCE_FILES} ${KERNEL_SOURCE_FILES} ${LOADER_SOURCE_FILES} ${SERVICE_SOURCE_FILES}
|
set(ALL_SOURCES ${SOURCE_FILES} ${FS_SOURCE_FILES} ${CRYPTO_SOURCE_FILES} ${KERNEL_SOURCE_FILES} ${LOADER_SOURCE_FILES} ${SERVICE_SOURCE_FILES}
|
||||||
${RENDERER_SW_SOURCE_FILES} ${PICA_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES} ${HEADER_FILES})
|
${APPLET_SOURCE_FILES} ${RENDERER_SW_SOURCE_FILES} ${PICA_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES} ${HEADER_FILES})
|
||||||
|
|
||||||
if(ENABLE_OPENGL)
|
if(ENABLE_OPENGL)
|
||||||
# Add the OpenGL source files to ALL_SOURCES
|
# Add the OpenGL source files to ALL_SOURCES
|
||||||
|
|
88
include/applets/applet.hpp
Normal file
88
include/applets/applet.hpp
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include "memory.hpp"
|
||||||
|
#include "result/result.hpp"
|
||||||
|
|
||||||
|
namespace Applets {
|
||||||
|
namespace AppletIDs {
|
||||||
|
enum : u32 {
|
||||||
|
None = 0,
|
||||||
|
SysAppletMask = 0x100,
|
||||||
|
HomeMenu = 0x101,
|
||||||
|
AltMenu = 0x103,
|
||||||
|
Camera = 0x110,
|
||||||
|
Friends = 0x112,
|
||||||
|
GameNotes = 0x113,
|
||||||
|
Browser = 0x114,
|
||||||
|
InstructionManual = 0x115,
|
||||||
|
Notifications = 0x116,
|
||||||
|
Miiverse = 0x117,
|
||||||
|
MiiversePosting = 0x118,
|
||||||
|
AmiiboSettings = 0x119,
|
||||||
|
SysLibraryAppletMask = 0x200,
|
||||||
|
SoftwareKeyboard = 0x201,
|
||||||
|
MiiSelector = 0x202,
|
||||||
|
PNote = 0x204, // TODO: What dis?
|
||||||
|
SNote = 0x205, // What is this too?
|
||||||
|
ErrDisp = 0x206,
|
||||||
|
EshopMint = 0x207,
|
||||||
|
CirclePadProCalib = 0x208,
|
||||||
|
Notepad = 0x209,
|
||||||
|
Application = 0x300,
|
||||||
|
EshopTiger = 0x301,
|
||||||
|
LibraryAppletMask = 0x400,
|
||||||
|
SoftwareKeyboard2 = 0x401,
|
||||||
|
MiiSelector2 = 0x402,
|
||||||
|
Pnote2 = 0x404,
|
||||||
|
SNote2 = 0x405,
|
||||||
|
ErrDisp2 = 0x406,
|
||||||
|
EshopMint2 = 0x407,
|
||||||
|
CirclePadProCalib2 = 0x408,
|
||||||
|
Notepad2 = 0x409,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class APTSignal : u32 {
|
||||||
|
None = 0x0,
|
||||||
|
Wakeup = 0x1,
|
||||||
|
Request = 0x2,
|
||||||
|
Response = 0x3,
|
||||||
|
Exit = 0x4,
|
||||||
|
Message = 0x5,
|
||||||
|
HomeButtonSingle = 0x6,
|
||||||
|
HomeButtonDouble = 0x7,
|
||||||
|
DspSleep = 0x8,
|
||||||
|
DspWakeup = 0x9,
|
||||||
|
WakeupByExit = 0xA,
|
||||||
|
WakeupByPause = 0xB,
|
||||||
|
WakeupByCancel = 0xC,
|
||||||
|
WakeupByCancelAll = 0xD,
|
||||||
|
WakeupByPowerButtonClick = 0xE,
|
||||||
|
WakeupToJumpHome = 0xF,
|
||||||
|
RequestForSysApplet = 0x10,
|
||||||
|
WakeupToLaunchApplication = 0x11,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Parameter {
|
||||||
|
u32 senderID;
|
||||||
|
u32 destID;
|
||||||
|
APTSignal signal;
|
||||||
|
std::vector<u8> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AppletBase {
|
||||||
|
Memory& mem;
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual const char* name() = 0;
|
||||||
|
|
||||||
|
// Called by APT::StartLibraryApplet and similar
|
||||||
|
virtual Result::HorizonResult start() = 0;
|
||||||
|
// Transfer parameters from application -> applet
|
||||||
|
virtual Result::HorizonResult receiveParameter() = 0;
|
||||||
|
virtual void reset() = 0;
|
||||||
|
|
||||||
|
AppletBase(Memory& mem) : mem(mem) {}
|
||||||
|
};
|
||||||
|
} // namespace Applets
|
17
include/applets/applet_manager.hpp
Normal file
17
include/applets/applet_manager.hpp
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#include "applets/mii_selector.hpp"
|
||||||
|
#include "applets/software_keyboard.hpp"
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include "memory.hpp"
|
||||||
|
#include "result/result.hpp"
|
||||||
|
|
||||||
|
namespace Applets {
|
||||||
|
class AppletManager {
|
||||||
|
MiiSelectorApplet miiSelector;
|
||||||
|
SoftwareKeyboardApplet swkbd;
|
||||||
|
|
||||||
|
public:
|
||||||
|
AppletManager(Memory& mem);
|
||||||
|
void reset();
|
||||||
|
AppletBase* getApplet(u32 id);
|
||||||
|
};
|
||||||
|
} // namespace Applets
|
13
include/applets/mii_selector.hpp
Normal file
13
include/applets/mii_selector.hpp
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#include "applets/applet.hpp"
|
||||||
|
|
||||||
|
namespace Applets {
|
||||||
|
class MiiSelectorApplet final : public AppletBase {
|
||||||
|
public:
|
||||||
|
virtual const char* name() override { return "Mii Selector"; }
|
||||||
|
virtual Result::HorizonResult start() override;
|
||||||
|
virtual Result::HorizonResult receiveParameter() override;
|
||||||
|
virtual void reset() override;
|
||||||
|
|
||||||
|
MiiSelectorApplet(Memory& memory) : AppletBase(memory) {}
|
||||||
|
};
|
||||||
|
} // namespace Applets
|
13
include/applets/software_keyboard.hpp
Normal file
13
include/applets/software_keyboard.hpp
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#include "applets/applet.hpp"
|
||||||
|
|
||||||
|
namespace Applets {
|
||||||
|
class SoftwareKeyboardApplet final : public AppletBase {
|
||||||
|
public:
|
||||||
|
virtual const char* name() override { return "Software Keyboard"; }
|
||||||
|
virtual Result::HorizonResult start() override;
|
||||||
|
virtual Result::HorizonResult receiveParameter() override;
|
||||||
|
virtual void reset() override;
|
||||||
|
|
||||||
|
SoftwareKeyboardApplet(Memory& memory) : AppletBase(memory) {}
|
||||||
|
};
|
||||||
|
} // namespace Applets
|
|
@ -24,6 +24,7 @@ enum class ROMType {
|
||||||
ELF,
|
ELF,
|
||||||
NCSD,
|
NCSD,
|
||||||
CXI,
|
CXI,
|
||||||
|
HB_3DSX,
|
||||||
};
|
};
|
||||||
|
|
||||||
class Emulator {
|
class Emulator {
|
||||||
|
@ -99,6 +100,7 @@ class Emulator {
|
||||||
|
|
||||||
bool loadROM(const std::filesystem::path& path);
|
bool loadROM(const std::filesystem::path& path);
|
||||||
bool loadNCSD(const std::filesystem::path& path, ROMType type);
|
bool loadNCSD(const std::filesystem::path& path, ROMType type);
|
||||||
|
bool load3DSX(const std::filesystem::path& path);
|
||||||
bool loadELF(const std::filesystem::path& path);
|
bool loadELF(const std::filesystem::path& path);
|
||||||
bool loadELF(std::ifstream& file);
|
bool loadELF(std::ifstream& file);
|
||||||
void initGraphicsContext();
|
void initGraphicsContext();
|
||||||
|
|
|
@ -25,17 +25,22 @@ namespace PathType {
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace ArchiveID {
|
namespace ArchiveID {
|
||||||
enum : u32 {
|
enum : u32 {
|
||||||
SelfNCCH = 3,
|
SelfNCCH = 3,
|
||||||
SaveData = 4,
|
SaveData = 4,
|
||||||
ExtSaveData = 6,
|
ExtSaveData = 6,
|
||||||
SharedExtSaveData = 7,
|
SharedExtSaveData = 7,
|
||||||
SystemSaveData = 8,
|
SystemSaveData = 8,
|
||||||
SDMC = 9,
|
SDMC = 9,
|
||||||
SDMCWriteOnly = 0xA,
|
SDMCWriteOnly = 0xA,
|
||||||
|
|
||||||
SavedataAndNcch = 0x2345678A
|
SavedataAndNcch = 0x2345678A,
|
||||||
};
|
// 3DBrew: This is the same as the regular SaveData archive, except with this the savedata ID and mediatype is loaded from the input archive
|
||||||
|
// lowpath.
|
||||||
|
UserSaveData1 = 0x567890B2,
|
||||||
|
// 3DBrew: Similar to 0x567890B2 but can only access Accessible Save specified in exheader?
|
||||||
|
UserSaveData2 = 0x567890B4,
|
||||||
|
};
|
||||||
|
|
||||||
static std::string toString(u32 id) {
|
static std::string toString(u32 id) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
|
@ -246,4 +251,11 @@ public:
|
||||||
virtual std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) = 0;
|
virtual std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) = 0;
|
||||||
|
|
||||||
ArchiveBase(Memory& mem) : mem(mem) {}
|
ArchiveBase(Memory& mem) : mem(mem) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ArchiveResource {
|
||||||
|
u32 sectorSize; // Size of a sector in bytes
|
||||||
|
u32 clusterSize; // Size of a cluster in bytes
|
||||||
|
u32 partitionCapacityInClusters;
|
||||||
|
u32 freeSpaceInClusters;
|
||||||
};
|
};
|
|
@ -18,7 +18,8 @@ public:
|
||||||
// Returns whether the cart has a RomFS
|
// Returns whether the cart has a RomFS
|
||||||
bool hasRomFS() {
|
bool hasRomFS() {
|
||||||
auto cxi = mem.getCXI();
|
auto cxi = mem.getCXI();
|
||||||
return (cxi != nullptr && cxi->hasRomFS());
|
auto hb3dsx = mem.get3DSX();
|
||||||
|
return (cxi != nullptr && cxi->hasRomFS()) || (hb3dsx != nullptr && hb3dsx->hasRomFs());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns whether the cart has an ExeFS (All executable carts should have an ExeFS. This is just here to be safe)
|
// Returns whether the cart has an ExeFS (All executable carts should have an ExeFS. This is just here to be safe)
|
||||||
|
|
|
@ -18,7 +18,8 @@ public:
|
||||||
// Returns whether the cart has a RomFS
|
// Returns whether the cart has a RomFS
|
||||||
bool hasRomFS() {
|
bool hasRomFS() {
|
||||||
auto cxi = mem.getCXI();
|
auto cxi = mem.getCXI();
|
||||||
return (cxi != nullptr && cxi->hasRomFS());
|
auto hb3dsx = mem.get3DSX();
|
||||||
|
return (cxi != nullptr && cxi->hasRomFS()) || (hb3dsx != nullptr && hb3dsx->hasRomFs());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns whether the cart has an ExeFS (All executable carts should have an ExeFS. This is just here to be safe)
|
// Returns whether the cart has an ExeFS (All executable carts should have an ExeFS. This is just here to be safe)
|
||||||
|
|
31
include/fs/archive_user_save_data.hpp
Normal file
31
include/fs/archive_user_save_data.hpp
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#pragma once
|
||||||
|
#include "archive_base.hpp"
|
||||||
|
|
||||||
|
class UserSaveDataArchive : public ArchiveBase {
|
||||||
|
u32 archiveID;
|
||||||
|
public:
|
||||||
|
UserSaveDataArchive(Memory& mem, u32 archiveID) : ArchiveBase(mem), archiveID(archiveID) {}
|
||||||
|
|
||||||
|
u64 getFreeBytes() override { return 32_MB; }
|
||||||
|
std::string name() override { return "UserSaveData"; }
|
||||||
|
|
||||||
|
HorizonResult createDirectory(const FSPath& path) override;
|
||||||
|
HorizonResult createFile(const FSPath& path, u64 size) override;
|
||||||
|
HorizonResult deleteFile(const FSPath& path) override;
|
||||||
|
|
||||||
|
Rust::Result<ArchiveBase*, HorizonResult> openArchive(const FSPath& path) override;
|
||||||
|
Rust::Result<DirectorySession, HorizonResult> openDirectory(const FSPath& path) override;
|
||||||
|
FileDescriptor openFile(const FSPath& path, const FilePerms& perms) override;
|
||||||
|
std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override;
|
||||||
|
|
||||||
|
void format(const FSPath& path, const FormatInfo& info) override;
|
||||||
|
Rust::Result<FormatInfo, HorizonResult> getFormatInfo(const FSPath& path) override;
|
||||||
|
|
||||||
|
std::filesystem::path getFormatInfoPath() { return IOFile::getAppData() / "FormatInfo" / "SaveData.format"; }
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
};
|
|
@ -83,6 +83,9 @@ private:
|
||||||
bool canThreadRun(const Thread& t);
|
bool canThreadRun(const Thread& t);
|
||||||
bool shouldWaitOnObject(KernelObject* object);
|
bool shouldWaitOnObject(KernelObject* object);
|
||||||
void releaseMutex(Mutex* moo);
|
void releaseMutex(Mutex* moo);
|
||||||
|
void cancelTimer(Timer* timer);
|
||||||
|
void signalTimer(Handle timerHandle, Timer* timer);
|
||||||
|
void updateTimer(Handle timerHandle, Timer* timer);
|
||||||
|
|
||||||
// Wake up the thread with the highest priority out of all threads in the waitlist
|
// Wake up the thread with the highest priority out of all threads in the waitlist
|
||||||
// Returns the index of the woken up thread
|
// Returns the index of the woken up thread
|
||||||
|
|
|
@ -173,6 +173,19 @@ struct Semaphore {
|
||||||
Semaphore(s32 initialCount, s32 maximumCount) : availableCount(initialCount), maximumCount(maximumCount), waitlist(0) {}
|
Semaphore(s32 initialCount, s32 maximumCount) : availableCount(initialCount), maximumCount(maximumCount), waitlist(0) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Timer {
|
||||||
|
u64 waitlist; // Refer to the getWaitlist function below for documentation
|
||||||
|
ResetType resetType = ResetType::OneShot;
|
||||||
|
|
||||||
|
u64 startTick; // CPU tick the timer started
|
||||||
|
u64 currentDelay; // Number of ns until the timer fires next time
|
||||||
|
u64 interval; // Number of ns until the timer fires for the second and future times
|
||||||
|
bool fired; // Has this timer been signalled?
|
||||||
|
bool running; // Is this timer running or stopped?
|
||||||
|
|
||||||
|
Timer(ResetType type) : resetType(type), startTick(0), currentDelay(0), interval(0), waitlist(0), fired(false), running(false) {}
|
||||||
|
};
|
||||||
|
|
||||||
struct MemoryBlock {
|
struct MemoryBlock {
|
||||||
u32 addr = 0;
|
u32 addr = 0;
|
||||||
u32 size = 0;
|
u32 size = 0;
|
||||||
|
@ -206,21 +219,23 @@ struct KernelObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieves a reference to the waitlist for a specified object
|
// Retrieves a reference to the waitlist for a specified object
|
||||||
// We return a reference because this function is only called in the kernel threading internals
|
// We return a reference because this function is only called in the kernel threading internals
|
||||||
// We want the kernel to be able to easily manage waitlists, by reading/parsing them or setting/clearing bits.
|
// We want the kernel to be able to easily manage waitlists, by reading/parsing them or setting/clearing bits.
|
||||||
// As we mention in the definition of the "Event" struct, the format for wailists is very simple and made to be efficient.
|
// As we mention in the definition of the "Event" struct, the format for wailists is very simple and made to be efficient.
|
||||||
// Each bit corresponds to a thread index and denotes whether the corresponding thread is waiting on this object
|
// Each bit corresponds to a thread index and denotes whether the corresponding thread is waiting on this object
|
||||||
// For example if bit 0 of the wait list is set, then the thread with index 0 is waiting on our object
|
// For example if bit 0 of the wait list is set, then the thread with index 0 is waiting on our object
|
||||||
u64& getWaitlist() {
|
u64& getWaitlist() {
|
||||||
// This code is actually kinda trash but eh good enough
|
// This code is actually kinda trash but eh good enough
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case KernelObjectType::Event: return getData<Event>()->waitlist;
|
case KernelObjectType::Event: return getData<Event>()->waitlist;
|
||||||
case KernelObjectType::Mutex: return getData<Mutex>()->waitlist;
|
case KernelObjectType::Mutex: return getData<Mutex>()->waitlist;
|
||||||
case KernelObjectType::Semaphore: return getData<Mutex>()->waitlist;
|
case KernelObjectType::Semaphore: return getData<Mutex>()->waitlist;
|
||||||
case KernelObjectType::Thread: return getData<Thread>()->threadsWaitingForTermination;
|
case KernelObjectType::Thread: return getData<Thread>()->threadsWaitingForTermination;
|
||||||
// This should be unreachable once we fully implement sync objects
|
case KernelObjectType::Timer: return getData<Timer>()->waitlist;
|
||||||
default: [[unlikely]]
|
|
||||||
|
// This should be unreachable once we fully implement sync objects
|
||||||
|
default: [[unlikely]]
|
||||||
Helpers::panic("Called GetWaitList on kernel object without a waitlist (Type: %s)", getTypeName());
|
Helpers::panic("Called GetWaitList on kernel object without a waitlist (Type: %s)", getTypeName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
80
include/loader/3dsx.hpp
Normal file
80
include/loader/3dsx.hpp
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
#pragma once
|
||||||
|
#include <array>
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include "io_file.hpp"
|
||||||
|
#include "loader/ncch.hpp"
|
||||||
|
|
||||||
|
struct HB3DSX {
|
||||||
|
// File layout:
|
||||||
|
// - File header
|
||||||
|
// - Code, rodata and data relocation table headers
|
||||||
|
// - Code segment
|
||||||
|
// - Rodata segment
|
||||||
|
// - Loadable (non-BSS) part of the data segment
|
||||||
|
// - Code relocation table
|
||||||
|
// - Rodata relocation table
|
||||||
|
// - Data relocation table
|
||||||
|
|
||||||
|
// Memory layout before relocations are applied:
|
||||||
|
// [0..codeSegSize) -> code segment
|
||||||
|
// [codeSegSize..rodataSegSize) -> rodata segment
|
||||||
|
// [rodataSegSize..dataSegSize) -> data segment
|
||||||
|
|
||||||
|
// Memory layout after relocations are applied: well, however the loader sets it up :)
|
||||||
|
// The entrypoint is always the start of the code segment.
|
||||||
|
// The BSS section must be cleared manually by the application.
|
||||||
|
|
||||||
|
// File header
|
||||||
|
struct Header {
|
||||||
|
// minus char magic[4]
|
||||||
|
u16 headerSize;
|
||||||
|
u16 relocHeaderSize;
|
||||||
|
u32 formatVer;
|
||||||
|
u32 flags;
|
||||||
|
|
||||||
|
// Sizes of the code, rodata and data segments +
|
||||||
|
// size of the BSS section (uninitialized latter half of the data segment)
|
||||||
|
u32 codeSegSize, rodataSegSize, dataSegSize, bssSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Relocation header: all fields (even extra unknown fields) are guaranteed to be relocation counts.
|
||||||
|
struct RelocHeader {
|
||||||
|
u32 absoluteCount; // # of absolute relocations (that is, fix address to post-relocation memory layout)
|
||||||
|
u32 relativeCount; // # of cross-segment relative relocations (that is, 32bit signed offsets that need to be patched)
|
||||||
|
// more?
|
||||||
|
|
||||||
|
// Relocations are written in this order:
|
||||||
|
// - Absolute relocs
|
||||||
|
// - Relative relocs
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class RelocType {
|
||||||
|
Absolute,
|
||||||
|
Relative,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Relocation entry: from the current pointer, skip X words and patch Y words
|
||||||
|
struct Reloc {
|
||||||
|
u16 skip, patch;
|
||||||
|
};
|
||||||
|
|
||||||
|
// _prm structure
|
||||||
|
static constexpr std::array<char, 4> PRM_MAGIC = {'_', 'P', 'R', 'M'};
|
||||||
|
struct PrmStruct {
|
||||||
|
char magic[4];
|
||||||
|
u32 pSrvOverride;
|
||||||
|
u32 aptAppId;
|
||||||
|
u32 heapSize, linearHeapSize;
|
||||||
|
u32 pArgList;
|
||||||
|
u32 runFlags;
|
||||||
|
};
|
||||||
|
|
||||||
|
IOFile file;
|
||||||
|
|
||||||
|
static constexpr u32 entrypoint = 0x00100000; // Initial ARM11 PC
|
||||||
|
u32 romFSSize = 0;
|
||||||
|
u32 romFSOffset = 0;
|
||||||
|
|
||||||
|
bool hasRomFs() const;
|
||||||
|
std::pair<bool, std::size_t> readRomFSBytes(void *dst, std::size_t offset, std::size_t size);
|
||||||
|
};
|
|
@ -11,6 +11,7 @@
|
||||||
#include "handles.hpp"
|
#include "handles.hpp"
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "loader/ncsd.hpp"
|
#include "loader/ncsd.hpp"
|
||||||
|
#include "loader/3dsx.hpp"
|
||||||
#include "services/region_codes.hpp"
|
#include "services/region_codes.hpp"
|
||||||
|
|
||||||
namespace PhysicalAddrs {
|
namespace PhysicalAddrs {
|
||||||
|
@ -167,10 +168,12 @@ public:
|
||||||
void* getReadPointer(u32 address);
|
void* getReadPointer(u32 address);
|
||||||
void* getWritePointer(u32 address);
|
void* getWritePointer(u32 address);
|
||||||
std::optional<u32> loadELF(std::ifstream& file);
|
std::optional<u32> loadELF(std::ifstream& file);
|
||||||
|
std::optional<u32> load3DSX(const std::filesystem::path& path);
|
||||||
std::optional<NCSD> loadNCSD(Crypto::AESEngine& aesEngine, const std::filesystem::path& path);
|
std::optional<NCSD> loadNCSD(Crypto::AESEngine& aesEngine, const std::filesystem::path& path);
|
||||||
std::optional<NCSD> loadCXI(Crypto::AESEngine& aesEngine, const std::filesystem::path& path);
|
std::optional<NCSD> loadCXI(Crypto::AESEngine& aesEngine, const std::filesystem::path& path);
|
||||||
|
|
||||||
bool mapCXI(NCSD& ncsd, NCCH& cxi);
|
bool mapCXI(NCSD& ncsd, NCCH& cxi);
|
||||||
|
bool map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header);
|
||||||
|
|
||||||
u8 read8(u32 vaddr);
|
u8 read8(u32 vaddr);
|
||||||
u16 read16(u32 vaddr);
|
u16 read16(u32 vaddr);
|
||||||
|
@ -221,6 +224,14 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HB3DSX* get3DSX() {
|
||||||
|
if (loaded3DSX.has_value()) {
|
||||||
|
return &loaded3DSX.value();
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Returns whether "addr" is aligned to a page (4096 byte) boundary
|
// Returns whether "addr" is aligned to a page (4096 byte) boundary
|
||||||
static constexpr bool isAligned(u32 addr) {
|
static constexpr bool isAligned(u32 addr) {
|
||||||
return (addr & pageMask) == 0;
|
return (addr & pageMask) == 0;
|
||||||
|
@ -256,6 +267,7 @@ public:
|
||||||
|
|
||||||
// Backup of the game's CXI partition info, if any
|
// Backup of the game's CXI partition info, if any
|
||||||
std::optional<NCCH> loadedCXI = std::nullopt;
|
std::optional<NCCH> loadedCXI = std::nullopt;
|
||||||
|
std::optional<HB3DSX> loaded3DSX = std::nullopt;
|
||||||
// File handle for reading the loaded ncch
|
// File handle for reading the loaded ncch
|
||||||
IOFile CXIFile;
|
IOFile CXIFile;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
|
#include <map>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "math_util.hpp"
|
||||||
#include "renderer.hpp"
|
#include "renderer.hpp"
|
||||||
#include "vulkan_api.hpp"
|
#include "vk_api.hpp"
|
||||||
|
#include "vk_descriptor_heap.hpp"
|
||||||
|
#include "vk_descriptor_update_batch.hpp"
|
||||||
|
#include "vk_sampler_cache.hpp"
|
||||||
|
|
||||||
class GPU;
|
class GPU;
|
||||||
|
|
||||||
|
@ -10,7 +17,7 @@ class RendererVK final : public Renderer {
|
||||||
vk::UniqueInstance instance = {};
|
vk::UniqueInstance instance = {};
|
||||||
vk::UniqueDebugUtilsMessengerEXT debugMessenger = {};
|
vk::UniqueDebugUtilsMessengerEXT debugMessenger = {};
|
||||||
|
|
||||||
vk::UniqueSurfaceKHR surface = {};
|
vk::SurfaceKHR swapchainSurface = {};
|
||||||
|
|
||||||
vk::PhysicalDevice physicalDevice = {};
|
vk::PhysicalDevice physicalDevice = {};
|
||||||
|
|
||||||
|
@ -32,17 +39,74 @@ class RendererVK final : public Renderer {
|
||||||
std::vector<vk::Image> swapchainImages = {};
|
std::vector<vk::Image> swapchainImages = {};
|
||||||
std::vector<vk::UniqueImageView> swapchainImageViews = {};
|
std::vector<vk::UniqueImageView> swapchainImageViews = {};
|
||||||
|
|
||||||
// Per-swapchain-image data
|
// This value is the degree of parallelism to allow multiple frames to be in-flight
|
||||||
// Each vector is `swapchainImageCount` in size
|
// aka: "double-buffer"/"triple-buffering"
|
||||||
std::vector<vk::UniqueCommandBuffer> presentCommandBuffers = {};
|
// Todo: make this a configuration option
|
||||||
|
static constexpr usize frameBufferingCount = 3;
|
||||||
|
|
||||||
|
// Frame-buffering data
|
||||||
|
// Each vector is `frameBufferingCount` in size
|
||||||
std::vector<vk::UniqueSemaphore> swapImageFreeSemaphore = {};
|
std::vector<vk::UniqueSemaphore> swapImageFreeSemaphore = {};
|
||||||
std::vector<vk::UniqueSemaphore> renderFinishedSemaphore = {};
|
std::vector<vk::UniqueSemaphore> renderFinishedSemaphore = {};
|
||||||
std::vector<vk::UniqueFence> frameFinishedFences = {};
|
std::vector<vk::UniqueFence> frameFinishedFences = {};
|
||||||
|
std::vector<std::vector<vk::UniqueFramebuffer>> frameFramebuffers = {};
|
||||||
|
std::vector<vk::UniqueCommandBuffer> frameCommandBuffers = {};
|
||||||
|
|
||||||
|
const vk::CommandBuffer& getCurrentCommandBuffer() const { return frameCommandBuffers[frameBufferingIndex].get(); }
|
||||||
|
|
||||||
|
// Todo:
|
||||||
|
// Use `{colourBuffer,depthBuffer}Loc` to maintain an std::map-cache of framebuffers
|
||||||
|
struct Texture {
|
||||||
|
u32 loc = 0;
|
||||||
|
u32 sizePerPixel = 0;
|
||||||
|
std::array<u32, 2> size = {};
|
||||||
|
|
||||||
|
vk::Format format;
|
||||||
|
vk::UniqueImage image;
|
||||||
|
vk::UniqueDeviceMemory imageMemory;
|
||||||
|
vk::UniqueImageView imageView;
|
||||||
|
|
||||||
|
Math::Rect<u32> getSubRect(u32 inputAddress, u32 width, u32 height) {
|
||||||
|
// PICA textures have top-left origin, same as Vulkan
|
||||||
|
const u32 startOffset = (inputAddress - loc) / sizePerPixel;
|
||||||
|
const u32 x0 = (startOffset % (size[0] * 8)) / 8;
|
||||||
|
const u32 y0 = (startOffset / (size[0] * 8)) * 8;
|
||||||
|
return Math::Rect<u32>{x0, y0, x0 + width, y0 + height};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Hash(loc, size, format) -> Texture
|
||||||
|
std::map<u64, Texture> textureCache;
|
||||||
|
|
||||||
|
Texture* findRenderTexture(u32 addr);
|
||||||
|
Texture& getColorRenderTexture(u32 addr, PICA::ColorFmt format, u32 width, u32 height);
|
||||||
|
Texture& getDepthRenderTexture(u32 addr, PICA::DepthFmt format, u32 width, u32 height);
|
||||||
|
|
||||||
|
// Framebuffer for the top/bottom image
|
||||||
|
std::vector<vk::UniqueImage> screenTexture = {};
|
||||||
|
std::vector<vk::UniqueImageView> screenTextureViews = {};
|
||||||
|
std::vector<vk::UniqueFramebuffer> screenTextureFramebuffers = {};
|
||||||
|
vk::UniqueDeviceMemory framebufferMemory = {};
|
||||||
|
|
||||||
|
std::map<u64, vk::UniqueRenderPass> renderPassCache;
|
||||||
|
|
||||||
|
vk::RenderPass getRenderPass(vk::Format colorFormat, std::optional<vk::Format> depthFormat);
|
||||||
|
vk::RenderPass getRenderPass(PICA::ColorFmt colorFormat, std::optional<PICA::DepthFmt> depthFormat);
|
||||||
|
|
||||||
|
std::unique_ptr<Vulkan::DescriptorUpdateBatch> descriptorUpdateBatch;
|
||||||
|
std::unique_ptr<Vulkan::SamplerCache> samplerCache;
|
||||||
|
|
||||||
|
// Display pipeline data
|
||||||
|
std::unique_ptr<Vulkan::DescriptorHeap> displayDescriptorHeap;
|
||||||
|
vk::UniquePipeline displayPipeline;
|
||||||
|
vk::UniquePipelineLayout displayPipelineLayout;
|
||||||
|
std::vector<vk::DescriptorSet> topDisplayPipelineDescriptorSet;
|
||||||
|
std::vector<vk::DescriptorSet> bottomDisplayPipelineDescriptorSet;
|
||||||
|
|
||||||
// Recreate the swapchain, possibly re-using the old one in the case of a resize
|
// Recreate the swapchain, possibly re-using the old one in the case of a resize
|
||||||
vk::Result recreateSwapchain(vk::SurfaceKHR surface, vk::Extent2D swapchainExtent);
|
vk::Result recreateSwapchain(vk::SurfaceKHR surface, vk::Extent2D swapchainExtent);
|
||||||
|
|
||||||
u64 currentFrame = 0;
|
u64 frameBufferingIndex = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RendererVK(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs);
|
RendererVK(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs);
|
||||||
~RendererVK() override;
|
~RendererVK() override;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "vulkan_api.hpp"
|
#include "vk_api.hpp"
|
||||||
|
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
|
||||||
|
|
49
include/renderer_vk/vk_descriptor_heap.hpp
Normal file
49
include/renderer_vk/vk_descriptor_heap.hpp
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include "vk_api.hpp"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
// Implements a basic heap of descriptor sets given a layout of particular
|
||||||
|
// bindings. Create a descriptor set by providing a list of bindings and it will
|
||||||
|
// automatically create both the pool, layout, and maintail a heap of descriptor
|
||||||
|
// sets. Descriptor sets will be reused and recycled. Assume that newly
|
||||||
|
// allocated descriptor sets are in an undefined state.
|
||||||
|
class DescriptorHeap {
|
||||||
|
private:
|
||||||
|
const vk::Device device;
|
||||||
|
|
||||||
|
vk::UniqueDescriptorPool descriptorPool;
|
||||||
|
vk::UniqueDescriptorSetLayout descriptorSetLayout;
|
||||||
|
std::vector<vk::UniqueDescriptorSet> descriptorSets;
|
||||||
|
|
||||||
|
std::vector<vk::DescriptorSetLayoutBinding> bindings;
|
||||||
|
|
||||||
|
std::vector<bool> allocationMap;
|
||||||
|
|
||||||
|
explicit DescriptorHeap(vk::Device device);
|
||||||
|
|
||||||
|
public:
|
||||||
|
~DescriptorHeap() = default;
|
||||||
|
|
||||||
|
DescriptorHeap(DescriptorHeap&&) = default;
|
||||||
|
|
||||||
|
const vk::DescriptorPool& getDescriptorPool() const { return descriptorPool.get(); };
|
||||||
|
|
||||||
|
const vk::DescriptorSetLayout& getDescriptorSetLayout() const { return descriptorSetLayout.get(); };
|
||||||
|
|
||||||
|
const std::span<const vk::UniqueDescriptorSet> getDescriptorSets() const { return descriptorSets; };
|
||||||
|
|
||||||
|
std::span<const vk::DescriptorSetLayoutBinding> getBindings() const { return bindings; };
|
||||||
|
|
||||||
|
std::optional<vk::DescriptorSet> allocateDescriptorSet();
|
||||||
|
bool freeDescriptorSet(vk::DescriptorSet set);
|
||||||
|
|
||||||
|
static std::optional<DescriptorHeap> create(
|
||||||
|
vk::Device device, std::span<const vk::DescriptorSetLayoutBinding> bindings, u16 descriptorHeapCount = 1024
|
||||||
|
);
|
||||||
|
};
|
||||||
|
} // namespace Vulkan
|
62
include/renderer_vk/vk_descriptor_update_batch.hpp
Normal file
62
include/renderer_vk/vk_descriptor_update_batch.hpp
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include "vk_api.hpp"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
// Implements a re-usable structure for batching up descriptor writes with a
|
||||||
|
// finite amount of space for both convenience and to reduce the overall amount
|
||||||
|
// of API calls to `vkUpdateDescriptorSets`
|
||||||
|
class DescriptorUpdateBatch {
|
||||||
|
private:
|
||||||
|
const vk::Device device;
|
||||||
|
|
||||||
|
const usize descriptorWriteMax;
|
||||||
|
const usize descriptorCopyMax;
|
||||||
|
|
||||||
|
using DescriptorInfoUnion = std::variant<vk::DescriptorImageInfo, vk::DescriptorBufferInfo, vk::BufferView>;
|
||||||
|
|
||||||
|
// Todo: Maybe some kind of hash so that these structures can be re-used
|
||||||
|
// among descriptor writes.
|
||||||
|
std::unique_ptr<DescriptorInfoUnion[]> descriptorInfos;
|
||||||
|
std::unique_ptr<vk::WriteDescriptorSet[]> descriptorWrites;
|
||||||
|
std::unique_ptr<vk::CopyDescriptorSet[]> descriptorCopies;
|
||||||
|
|
||||||
|
usize descriptorWriteEnd = 0;
|
||||||
|
usize descriptorCopyEnd = 0;
|
||||||
|
|
||||||
|
DescriptorUpdateBatch(vk::Device device, usize descriptorWriteMax, usize descriptorCopyMax)
|
||||||
|
: device(device), descriptorWriteMax(descriptorWriteMax), descriptorCopyMax(descriptorCopyMax) {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
~DescriptorUpdateBatch() = default;
|
||||||
|
|
||||||
|
DescriptorUpdateBatch(DescriptorUpdateBatch&&) = default;
|
||||||
|
|
||||||
|
void flush();
|
||||||
|
|
||||||
|
void addImage(
|
||||||
|
vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::ImageView imageView, vk::ImageLayout imageLayout = vk::ImageLayout::eGeneral
|
||||||
|
);
|
||||||
|
void addSampler(vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::Sampler sampler);
|
||||||
|
|
||||||
|
void addImageSampler(
|
||||||
|
vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::ImageView imageView, vk::Sampler sampler,
|
||||||
|
vk::ImageLayout imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal
|
||||||
|
);
|
||||||
|
void addBuffer(
|
||||||
|
vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::Buffer buffer, vk::DeviceSize offset, vk::DeviceSize size = VK_WHOLE_SIZE
|
||||||
|
);
|
||||||
|
|
||||||
|
void copyBinding(
|
||||||
|
vk::DescriptorSet sourceDescriptor, vk::DescriptorSet targetDescriptor, u8 sourceBinding, u8 targetBinding, u8 sourceArrayElement = 0,
|
||||||
|
u8 targetArrayElement = 0, u8 descriptorCount = 1
|
||||||
|
);
|
||||||
|
|
||||||
|
static std::optional<DescriptorUpdateBatch> create(vk::Device device, usize descriptorWriteMax = 256, usize descriptorCopyMax = 256);
|
||||||
|
};
|
||||||
|
} // namespace Vulkan
|
36
include/renderer_vk/vk_memory.hpp
Normal file
36
include/renderer_vk/vk_memory.hpp
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include "vk_api.hpp"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
// Will try to find a memory type that is suitable for the given requirements.
|
||||||
|
// Returns -1 if no suitable memory type was found.
|
||||||
|
s32 findMemoryTypeIndex(
|
||||||
|
vk::PhysicalDevice physicalDevice, u32 memoryTypeMask, vk::MemoryPropertyFlags memoryProperties,
|
||||||
|
vk::MemoryPropertyFlags memoryExcludeProperties = vk::MemoryPropertyFlagBits::eProtected
|
||||||
|
);
|
||||||
|
|
||||||
|
// Given an array of valid Vulkan image-handles or buffer-handles, these
|
||||||
|
// functions will allocate a single block of device-memory for all of them
|
||||||
|
// and bind them consecutively.
|
||||||
|
// There may be a case that all the buffers or images cannot be allocated
|
||||||
|
// to the same device memory due to their required memory-type.
|
||||||
|
std::tuple<vk::Result, vk::UniqueDeviceMemory> commitImageHeap(
|
||||||
|
vk::Device device, vk::PhysicalDevice physicalDevice, const std::span<const vk::Image> images,
|
||||||
|
vk::MemoryPropertyFlags memoryProperties = vk::MemoryPropertyFlagBits::eDeviceLocal,
|
||||||
|
vk::MemoryPropertyFlags memoryExcludeProperties = vk::MemoryPropertyFlagBits::eProtected
|
||||||
|
);
|
||||||
|
|
||||||
|
std::tuple<vk::Result, vk::UniqueDeviceMemory> commitBufferHeap(
|
||||||
|
vk::Device device, vk::PhysicalDevice physicalDevice, const std::span<const vk::Buffer> buffers,
|
||||||
|
vk::MemoryPropertyFlags memoryProperties = vk::MemoryPropertyFlagBits::eDeviceLocal,
|
||||||
|
vk::MemoryPropertyFlags memoryExcludeProperties = vk::MemoryPropertyFlagBits::eProtected
|
||||||
|
);
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
12
include/renderer_vk/vk_pica.hpp
Normal file
12
include/renderer_vk/vk_pica.hpp
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "PICA/gpu.hpp"
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include "vk_api.hpp"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
vk::Format colorFormatToVulkan(PICA::ColorFmt colorFormat);
|
||||||
|
vk::Format depthFormatToVulkan(PICA::DepthFmt depthFormat);
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
28
include/renderer_vk/vk_sampler_cache.hpp
Normal file
28
include/renderer_vk/vk_sampler_cache.hpp
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include "vk_api.hpp"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
// Implements a simple pool of reusable sampler objects
|
||||||
|
class SamplerCache {
|
||||||
|
private:
|
||||||
|
const vk::Device device;
|
||||||
|
|
||||||
|
std::unordered_map<std::size_t, vk::UniqueSampler> samplerMap;
|
||||||
|
|
||||||
|
explicit SamplerCache(vk::Device device);
|
||||||
|
|
||||||
|
public:
|
||||||
|
~SamplerCache() = default;
|
||||||
|
|
||||||
|
SamplerCache(SamplerCache&&) = default;
|
||||||
|
|
||||||
|
const vk::Sampler& getSampler(const vk::SamplerCreateInfo& samplerInfo);
|
||||||
|
|
||||||
|
static std::optional<SamplerCache> create(vk::Device device);
|
||||||
|
};
|
||||||
|
} // namespace Vulkan
|
|
@ -1,4 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "kernel_types.hpp"
|
#include "kernel_types.hpp"
|
||||||
#include "logger.hpp"
|
#include "logger.hpp"
|
||||||
|
@ -15,10 +17,14 @@ class ACService {
|
||||||
void closeAsync(u32 messagePointer);
|
void closeAsync(u32 messagePointer);
|
||||||
void createDefaultConfig(u32 messagePointer);
|
void createDefaultConfig(u32 messagePointer);
|
||||||
void getLastErrorCode(u32 messagePointer);
|
void getLastErrorCode(u32 messagePointer);
|
||||||
|
void isConnected(u32 messagePointer);
|
||||||
void registerDisconnectEvent(u32 messagePointer);
|
void registerDisconnectEvent(u32 messagePointer);
|
||||||
void setClientVersion(u32 messagePointer);
|
void setClientVersion(u32 messagePointer);
|
||||||
|
|
||||||
public:
|
bool connected = false;
|
||||||
|
std::optional<Handle> disconnectEvent = std::nullopt;
|
||||||
|
|
||||||
|
public:
|
||||||
ACService(Memory& mem) : mem(mem) {}
|
ACService(Memory& mem) : mem(mem) {}
|
||||||
void reset();
|
void reset();
|
||||||
void handleSyncRequest(u32 messagePointer);
|
void handleSyncRequest(u32 messagePointer);
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
#include "memory.hpp"
|
#include "memory.hpp"
|
||||||
#include "result/result.hpp"
|
#include "result/result.hpp"
|
||||||
|
|
||||||
|
#include "applets/applet_manager.hpp"
|
||||||
|
|
||||||
// Yay, more circular dependencies
|
// Yay, more circular dependencies
|
||||||
class Kernel;
|
class Kernel;
|
||||||
|
|
||||||
|
@ -23,6 +25,7 @@ class APTService {
|
||||||
std::optional<Handle> resumeEvent = std::nullopt;
|
std::optional<Handle> resumeEvent = std::nullopt;
|
||||||
|
|
||||||
ConsoleModel model = ConsoleModel::Old3DS;
|
ConsoleModel model = ConsoleModel::Old3DS;
|
||||||
|
Applets::AppletManager appletManager;
|
||||||
|
|
||||||
MAKE_LOG_FUNCTION(log, aptLogger)
|
MAKE_LOG_FUNCTION(log, aptLogger)
|
||||||
|
|
||||||
|
@ -33,17 +36,22 @@ class APTService {
|
||||||
void checkNew3DS(u32 messagePointer);
|
void checkNew3DS(u32 messagePointer);
|
||||||
void checkNew3DSApp(u32 messagePointer);
|
void checkNew3DSApp(u32 messagePointer);
|
||||||
void enable(u32 messagePointer);
|
void enable(u32 messagePointer);
|
||||||
|
void getAppletInfo(u32 messagePointer);
|
||||||
void getSharedFont(u32 messagePointer);
|
void getSharedFont(u32 messagePointer);
|
||||||
void getWirelessRebootInfo(u32 messagePointer);
|
void getWirelessRebootInfo(u32 messagePointer);
|
||||||
void glanceParameter(u32 messagePointer);
|
void glanceParameter(u32 messagePointer);
|
||||||
void initialize(u32 messagePointer);
|
void initialize(u32 messagePointer);
|
||||||
void inquireNotification(u32 messagePointer);
|
void inquireNotification(u32 messagePointer);
|
||||||
|
void isRegistered(u32 messagePointer);
|
||||||
void notifyToWait(u32 messagePointer);
|
void notifyToWait(u32 messagePointer);
|
||||||
void preloadLibraryApplet(u32 messagePointer);
|
void preloadLibraryApplet(u32 messagePointer);
|
||||||
|
void prepareToStartLibraryApplet(u32 messagePointer);
|
||||||
void receiveParameter(u32 messagePointer);
|
void receiveParameter(u32 messagePointer);
|
||||||
void replySleepQuery(u32 messagePointer);
|
void replySleepQuery(u32 messagePointer);
|
||||||
void setApplicationCpuTimeLimit(u32 messagePointer);
|
void setApplicationCpuTimeLimit(u32 messagePointer);
|
||||||
void setScreencapPostPermission(u32 messagePointer);
|
void setScreencapPostPermission(u32 messagePointer);
|
||||||
|
void sendParameter(u32 messagePointer);
|
||||||
|
void startLibraryApplet(u32 messagePointer);
|
||||||
void theSmashBrosFunction(u32 messagePointer);
|
void theSmashBrosFunction(u32 messagePointer);
|
||||||
|
|
||||||
// Percentage of the syscore available to the application, between 5% and 89%
|
// Percentage of the syscore available to the application, between 5% and 89%
|
||||||
|
@ -67,7 +75,7 @@ class APTService {
|
||||||
u32 screencapPostPermission;
|
u32 screencapPostPermission;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
APTService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
|
APTService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel), appletManager(mem) {}
|
||||||
void reset();
|
void reset();
|
||||||
void handleSyncRequest(u32 messagePointer);
|
void handleSyncRequest(u32 messagePointer);
|
||||||
};
|
};
|
|
@ -23,7 +23,10 @@ class FRDService {
|
||||||
|
|
||||||
// Service commands
|
// Service commands
|
||||||
void attachToEventNotification(u32 messagePointer);
|
void attachToEventNotification(u32 messagePointer);
|
||||||
|
void getFriendAttributeFlags(u32 messagePointer);
|
||||||
void getFriendKeyList(u32 messagePointer);
|
void getFriendKeyList(u32 messagePointer);
|
||||||
|
void getFriendPresence(u32 messagePointer);
|
||||||
|
void getFriendProfile(u32 messagePointer);
|
||||||
void getMyFriendKey(u32 messagePointer);
|
void getMyFriendKey(u32 messagePointer);
|
||||||
void getMyMii(u32 messagePointer);
|
void getMyMii(u32 messagePointer);
|
||||||
void getMyPresence(u32 messagePointer);
|
void getMyPresence(u32 messagePointer);
|
||||||
|
@ -35,6 +38,15 @@ class FRDService {
|
||||||
void setNotificationMask(u32 messagePointer);
|
void setNotificationMask(u32 messagePointer);
|
||||||
void updateGameModeDescription(u32 messagePointer);
|
void updateGameModeDescription(u32 messagePointer);
|
||||||
|
|
||||||
|
struct Profile {
|
||||||
|
u8 region;
|
||||||
|
u8 country;
|
||||||
|
u8 area;
|
||||||
|
u8 language;
|
||||||
|
u32 unknown;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(Profile) == 8);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FRDService(Memory& mem) : mem(mem) {}
|
FRDService(Memory& mem) : mem(mem) {}
|
||||||
void reset();
|
void reset();
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "fs/archive_save_data.hpp"
|
#include "fs/archive_save_data.hpp"
|
||||||
#include "fs/archive_sdmc.hpp"
|
#include "fs/archive_sdmc.hpp"
|
||||||
#include "fs/archive_self_ncch.hpp"
|
#include "fs/archive_self_ncch.hpp"
|
||||||
|
#include "fs/archive_user_save_data.hpp"
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "kernel_types.hpp"
|
#include "kernel_types.hpp"
|
||||||
#include "logger.hpp"
|
#include "logger.hpp"
|
||||||
|
@ -26,6 +27,10 @@ class FSService {
|
||||||
SDMCArchive sdmc;
|
SDMCArchive sdmc;
|
||||||
NCCHArchive ncch;
|
NCCHArchive ncch;
|
||||||
|
|
||||||
|
// UserSaveData archives
|
||||||
|
UserSaveDataArchive userSaveData1;
|
||||||
|
UserSaveDataArchive userSaveData2;
|
||||||
|
|
||||||
ExtSaveDataArchive extSaveData_sdmc;
|
ExtSaveDataArchive extSaveData_sdmc;
|
||||||
ExtSaveDataArchive sharedExtSaveData_nand;
|
ExtSaveDataArchive sharedExtSaveData_nand;
|
||||||
|
|
||||||
|
@ -38,6 +43,7 @@ class FSService {
|
||||||
const EmulatorConfig& config;
|
const EmulatorConfig& config;
|
||||||
|
|
||||||
// Service commands
|
// Service commands
|
||||||
|
void abnegateAccessRight(u32 messagePointer);
|
||||||
void createDirectory(u32 messagePointer);
|
void createDirectory(u32 messagePointer);
|
||||||
void createExtSaveData(u32 messagePointer);
|
void createExtSaveData(u32 messagePointer);
|
||||||
void createFile(u32 messagePointer);
|
void createFile(u32 messagePointer);
|
||||||
|
@ -47,9 +53,12 @@ class FSService {
|
||||||
void deleteFile(u32 messagePointer);
|
void deleteFile(u32 messagePointer);
|
||||||
void formatSaveData(u32 messagePointer);
|
void formatSaveData(u32 messagePointer);
|
||||||
void formatThisUserSaveData(u32 messagePointer);
|
void formatThisUserSaveData(u32 messagePointer);
|
||||||
|
void getArchiveResource(u32 messagePointer);
|
||||||
void getFreeBytes(u32 messagePointer);
|
void getFreeBytes(u32 messagePointer);
|
||||||
void getFormatInfo(u32 messagePointer);
|
void getFormatInfo(u32 messagePointer);
|
||||||
void getPriority(u32 messagePointer);
|
void getPriority(u32 messagePointer);
|
||||||
|
void getThisSaveDataSecureValue(u32 messagePointer);
|
||||||
|
void theGameboyVCFunction(u32 messagePointer);
|
||||||
void initialize(u32 messagePointer);
|
void initialize(u32 messagePointer);
|
||||||
void initializeWithSdkVersion(u32 messagePointer);
|
void initializeWithSdkVersion(u32 messagePointer);
|
||||||
void isSdmcDetected(u32 messagePointer);
|
void isSdmcDetected(u32 messagePointer);
|
||||||
|
@ -58,16 +67,17 @@ class FSService {
|
||||||
void openDirectory(u32 messagePointer);
|
void openDirectory(u32 messagePointer);
|
||||||
void openFile(u32 messagePointer);
|
void openFile(u32 messagePointer);
|
||||||
void openFileDirectly(u32 messagePointer);
|
void openFileDirectly(u32 messagePointer);
|
||||||
|
void setArchivePriority(u32 messagePointer);
|
||||||
void setPriority(u32 messagePointer);
|
void setPriority(u32 messagePointer);
|
||||||
|
void setThisSaveDataSecureValue(u32 messagePointer);
|
||||||
|
|
||||||
// Used for set/get priority: Not sure what sort of priority this is referring to
|
// Used for set/get priority: Not sure what sort of priority this is referring to
|
||||||
u32 priority;
|
u32 priority;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FSService(Memory& mem, Kernel& kernel, const EmulatorConfig& config) : mem(mem), saveData(mem),
|
FSService(Memory& mem, Kernel& kernel, const EmulatorConfig& config)
|
||||||
sharedExtSaveData_nand(mem, "../SharedFiles/NAND", true), extSaveData_sdmc(mem, "SDMC"), sdmc(mem), selfNcch(mem),
|
: mem(mem), saveData(mem), sharedExtSaveData_nand(mem, "../SharedFiles/NAND", true), extSaveData_sdmc(mem, "SDMC"), sdmc(mem), selfNcch(mem),
|
||||||
ncch(mem), kernel(kernel), config(config)
|
ncch(mem), userSaveData1(mem, ArchiveID::UserSaveData1), userSaveData2(mem, ArchiveID::UserSaveData2), kernel(kernel), config(config) {}
|
||||||
{}
|
|
||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
void handleSyncRequest(u32 messagePointer);
|
void handleSyncRequest(u32 messagePointer);
|
||||||
|
|
|
@ -63,7 +63,9 @@ class GPUService {
|
||||||
// Service commands
|
// Service commands
|
||||||
void acquireRight(u32 messagePointer);
|
void acquireRight(u32 messagePointer);
|
||||||
void flushDataCache(u32 messagePointer);
|
void flushDataCache(u32 messagePointer);
|
||||||
|
void importDisplayCaptureInfo(u32 messagePointer);
|
||||||
void registerInterruptRelayQueue(u32 messagePointer);
|
void registerInterruptRelayQueue(u32 messagePointer);
|
||||||
|
void saveVramSysArea(u32 messagePointer);
|
||||||
void setAxiConfigQoSMode(u32 messagePointer);
|
void setAxiConfigQoSMode(u32 messagePointer);
|
||||||
void setBufferSwap(u32 messagePointer);
|
void setBufferSwap(u32 messagePointer);
|
||||||
void setInternalPriorities(u32 messagePointer);
|
void setInternalPriorities(u32 messagePointer);
|
||||||
|
|
|
@ -5,13 +5,20 @@
|
||||||
#include "memory.hpp"
|
#include "memory.hpp"
|
||||||
#include "result/result.hpp"
|
#include "result/result.hpp"
|
||||||
|
|
||||||
|
// Circular dependencies, yay
|
||||||
|
class Kernel;
|
||||||
|
|
||||||
class MICService {
|
class MICService {
|
||||||
Handle handle = KernelHandles::MIC;
|
Handle handle = KernelHandles::MIC;
|
||||||
Memory& mem;
|
Memory& mem;
|
||||||
|
Kernel& kernel;
|
||||||
MAKE_LOG_FUNCTION(log, micLogger)
|
MAKE_LOG_FUNCTION(log, micLogger)
|
||||||
|
|
||||||
// Service commands
|
// Service commands
|
||||||
|
void getEventHandle(u32 messagePointer);
|
||||||
void getGain(u32 messagePointer);
|
void getGain(u32 messagePointer);
|
||||||
|
void getPower(u32 messagePointer);
|
||||||
|
void isSampling(u32 messagePointer);
|
||||||
void mapSharedMem(u32 messagePointer);
|
void mapSharedMem(u32 messagePointer);
|
||||||
void setClamp(u32 messagePointer);
|
void setClamp(u32 messagePointer);
|
||||||
void setGain(u32 messagePointer);
|
void setGain(u32 messagePointer);
|
||||||
|
@ -19,15 +26,18 @@ class MICService {
|
||||||
void setPower(u32 messagePointer);
|
void setPower(u32 messagePointer);
|
||||||
void startSampling(u32 messagePointer);
|
void startSampling(u32 messagePointer);
|
||||||
void stopSampling(u32 messagePointer);
|
void stopSampling(u32 messagePointer);
|
||||||
|
void unmapSharedMem(u32 messagePointer);
|
||||||
void theCaptainToadFunction(u32 messagePointer);
|
void theCaptainToadFunction(u32 messagePointer);
|
||||||
|
|
||||||
u8 gain = 0; // How loud our microphone input signal is
|
u8 gain = 0; // How loud our microphone input signal is
|
||||||
bool micEnabled = false;
|
bool micEnabled = false;
|
||||||
bool shouldClamp = false;
|
bool shouldClamp = false;
|
||||||
bool isSampling = false;
|
bool currentlySampling = false;
|
||||||
|
|
||||||
|
std::optional<Handle> eventHandle;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MICService(Memory& mem) : mem(mem) {}
|
MICService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
|
||||||
void reset();
|
void reset();
|
||||||
void handleSyncRequest(u32 messagePointer);
|
void handleSyncRequest(u32 messagePointer);
|
||||||
};
|
};
|
|
@ -44,7 +44,9 @@ class NFCService {
|
||||||
void getTagInRangeEvent(u32 messagePointer);
|
void getTagInRangeEvent(u32 messagePointer);
|
||||||
void getTagOutOfRangeEvent(u32 messagePointer);
|
void getTagOutOfRangeEvent(u32 messagePointer);
|
||||||
void getTagState(u32 messagePointer);
|
void getTagState(u32 messagePointer);
|
||||||
|
void shutdown(u32 messagePointer);
|
||||||
void startCommunication(u32 messagePointer);
|
void startCommunication(u32 messagePointer);
|
||||||
|
void startTagScanning(u32 messagePointer);
|
||||||
void stopCommunication(u32 messagePointer);
|
void stopCommunication(u32 messagePointer);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -64,10 +64,14 @@ class Y2RService {
|
||||||
// Service commands
|
// Service commands
|
||||||
void driverInitialize(u32 messagePointer);
|
void driverInitialize(u32 messagePointer);
|
||||||
void driverFinalize(u32 messagePointer);
|
void driverFinalize(u32 messagePointer);
|
||||||
|
void getTransferEndEvent(u32 messagePointer);
|
||||||
|
void getBlockAlignment(u32 messagePointer);
|
||||||
|
void getInputLines(u32 messagePointer);
|
||||||
|
void getInputLineWidth(u32 messagePointer);
|
||||||
|
void getOutputFormat(u32 messagePointer);
|
||||||
void isBusyConversion(u32 messagePointer);
|
void isBusyConversion(u32 messagePointer);
|
||||||
void pingProcess(u32 messagePointer);
|
void pingProcess(u32 messagePointer);
|
||||||
void setTransferEndInterrupt(u32 messagePointer);
|
void setTransferEndInterrupt(u32 messagePointer);
|
||||||
void getTransferEndEvent(u32 messagePointer);
|
|
||||||
|
|
||||||
void setAlpha(u32 messagePointer);
|
void setAlpha(u32 messagePointer);
|
||||||
void setBlockAlignment(u32 messagePointer);
|
void setBlockAlignment(u32 messagePointer);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# Panda3DS
|
# Panda3DS
|
||||||
[](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Windows_Build.yml) [](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/MacOS_Build.yml) [](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Linux_Build.yml)
|
[](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Windows_Build.yml) [](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/MacOS_Build.yml) [](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Linux_Build.yml) [](https://aur.archlinux.org/packages/panda3ds-git)
|
||||||
|
|
||||||
Panda3DS is an HLE, red-panda-themed Nintendo 3DS emulator written in C++ which started out as a fun project out of curiosity, but evolved into something that can sort of play games!
|
Panda3DS is an HLE, red-panda-themed Nintendo 3DS emulator written in C++ which started out as a fun project out of curiosity, but evolved into something that can sort of play games!
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ The 3DS emulation scene is already pretty mature, with offerings such as [Citra]
|
||||||
Keep in mind, these are all long-term plans. Until then, the main focus is just improving compatibility
|
Keep in mind, these are all long-term plans. Until then, the main focus is just improving compatibility
|
||||||
|
|
||||||
# How to build
|
# How to build
|
||||||
Panda3DS compiles on Windows, Linux and MacOS, with only 1 system dependency, the Vulkan SDK. However, if you don't want to install the Vulkan SDK you can always build the emulator with only OpenGL support, by adding `-DENABLE_VULKAN=OFF` to the `cmake` command
|
Panda3DS compiles on Windows, Linux and MacOS, with only 1 (optional) system dependency, the Vulkan SDK. If you don't want to install the Vulkan SDK you can always build the emulator with only OpenGL support, by adding `-DENABLE_VULKAN=OFF` to the `cmake` command
|
||||||
|
|
||||||
All you need is CMake and a generator of your choice (Make, Visual Studio, Ninja, etc). Simply clone the repo recursively and build it like your average CMake project.
|
All you need is CMake and a generator of your choice (Make, Visual Studio, Ninja, etc). Simply clone the repo recursively and build it like your average CMake project.
|
||||||
|
|
||||||
|
@ -61,6 +61,7 @@ Panda3DS can load ROMs in the following formats:
|
||||||
- .3ds/.cci
|
- .3ds/.cci
|
||||||
- .cxi/.app
|
- .cxi/.app
|
||||||
- .elf/.axf
|
- .elf/.axf
|
||||||
|
- .3dsx
|
||||||
|
|
||||||
Both decrypted and encrypted dumps are supported. However for encrypted dumps you must provide your AES keys file by adding a `sysdata` folder to the emulator's app data directory with a file called `aes_keys.txt` including your keys. Currently .cia files are not supported yet (support is planned for the future), however if you want you can usually use Citra to extract the .app/.cxi file out of your .cia and run that.
|
Both decrypted and encrypted dumps are supported. However for encrypted dumps you must provide your AES keys file by adding a `sysdata` folder to the emulator's app data directory with a file called `aes_keys.txt` including your keys. Currently .cia files are not supported yet (support is planned for the future), however if you want you can usually use Citra to extract the .app/.cxi file out of your .cia and run that.
|
||||||
|
|
||||||
|
|
0
src/core/applets/applet.cpp
Normal file
0
src/core/applets/applet.cpp
Normal file
21
src/core/applets/applet_manager.cpp
Normal file
21
src/core/applets/applet_manager.cpp
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#include "applets/applet_manager.hpp"
|
||||||
|
using namespace Applets;
|
||||||
|
|
||||||
|
AppletManager::AppletManager(Memory& mem) : miiSelector(mem), swkbd(mem) {}
|
||||||
|
|
||||||
|
void AppletManager::reset() {
|
||||||
|
miiSelector.reset();
|
||||||
|
swkbd.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
AppletBase* AppletManager::getApplet(u32 id) {
|
||||||
|
switch (id) {
|
||||||
|
case AppletIDs::MiiSelector:
|
||||||
|
case AppletIDs::MiiSelector2: return &miiSelector;
|
||||||
|
|
||||||
|
case AppletIDs::SoftwareKeyboard:
|
||||||
|
case AppletIDs::SoftwareKeyboard2: return &swkbd;
|
||||||
|
|
||||||
|
default: return nullptr;
|
||||||
|
}
|
||||||
|
}
|
11
src/core/applets/mii_selector.cpp
Normal file
11
src/core/applets/mii_selector.cpp
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#include "applets/mii_selector.hpp"
|
||||||
|
|
||||||
|
using namespace Applets;
|
||||||
|
|
||||||
|
void MiiSelectorApplet::reset() {}
|
||||||
|
Result::HorizonResult MiiSelectorApplet::start() { return Result::Success; }
|
||||||
|
|
||||||
|
Result::HorizonResult MiiSelectorApplet::receiveParameter() {
|
||||||
|
Helpers::warn("Mii Selector: Unimplemented ReceiveParameter");
|
||||||
|
return Result::Success;
|
||||||
|
}
|
11
src/core/applets/software_keyboard.cpp
Normal file
11
src/core/applets/software_keyboard.cpp
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#include "applets/software_keyboard.hpp"
|
||||||
|
|
||||||
|
using namespace Applets;
|
||||||
|
|
||||||
|
void SoftwareKeyboardApplet::reset() {}
|
||||||
|
Result::HorizonResult SoftwareKeyboardApplet::start() { return Result::Success; }
|
||||||
|
|
||||||
|
Result::HorizonResult SoftwareKeyboardApplet::receiveParameter() {
|
||||||
|
Helpers::warn("Software keyboard: Unimplemented ReceiveParameter");
|
||||||
|
return Result::Success;
|
||||||
|
}
|
|
@ -131,20 +131,20 @@ std::optional<u32> NCCHArchive::readFile(FileSession* file, u64 offset, u32 size
|
||||||
}
|
}
|
||||||
|
|
||||||
auto cxi = mem.getCXI();
|
auto cxi = mem.getCXI();
|
||||||
IOFile& ioFile = mem.CXIFile;
|
auto hb3dsx = mem.get3DSX();
|
||||||
|
|
||||||
NCCH::FSInfo fsInfo;
|
// If isCXI is true then we've loaded a CXI/3DS file, else it's 3DSX
|
||||||
|
bool isCXI = cxi != nullptr;
|
||||||
|
|
||||||
// Seek to file offset depending on if we're reading from RomFS, ExeFS, etc
|
// Seek to file offset depending on if we're reading from RomFS, ExeFS, etc
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case PathType::RomFS: {
|
case PathType::RomFS: {
|
||||||
const u64 romFSSize = cxi->romFS.size;
|
const u64 romFSSize = isCXI ? cxi->romFS.size : hb3dsx->romFSSize;
|
||||||
const u64 romFSOffset = cxi->romFS.offset;
|
const u64 romFSOffset = isCXI ? cxi->romFS.offset : hb3dsx->romFSOffset;
|
||||||
if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) {
|
if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) {
|
||||||
Helpers::panic("Tried to read from NCCH with too big of an offset");
|
Helpers::panic("Tried to read from NCCH with too big of an offset");
|
||||||
}
|
}
|
||||||
|
|
||||||
fsInfo = cxi->romFS;
|
|
||||||
offset += 0x1000;
|
offset += 0x1000;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -154,7 +154,8 @@ std::optional<u32> NCCHArchive::readFile(FileSession* file, u64 offset, u32 size
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<u8[]> data(new u8[size]);
|
std::unique_ptr<u8[]> data(new u8[size]);
|
||||||
auto [success, bytesRead] = cxi->readFromFile(ioFile, fsInfo, &data[0], offset, size);
|
auto [success, bytesRead] =
|
||||||
|
isCXI ? cxi->readFromFile(mem.CXIFile, cxi->romFS, &data[0], offset, size) : hb3dsx->readRomFSBytes(&data[0], offset, size);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
Helpers::panic("Failed to read from NCCH archive");
|
Helpers::panic("Failed to read from NCCH archive");
|
||||||
|
|
|
@ -69,57 +69,79 @@ std::optional<u32> SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto cxi = mem.getCXI();
|
bool success = false;
|
||||||
IOFile& ioFile = mem.CXIFile;
|
std::size_t bytesRead = 0;
|
||||||
|
std::vector<u8> data;
|
||||||
|
|
||||||
NCCH::FSInfo fsInfo;
|
if (auto cxi = mem.getCXI(); cxi != nullptr) {
|
||||||
|
IOFile& ioFile = mem.CXIFile;
|
||||||
|
|
||||||
// Seek to file offset depending on if we're reading from RomFS, ExeFS, etc
|
NCCH::FSInfo fsInfo;
|
||||||
switch (type) {
|
|
||||||
case PathType::RomFS: {
|
// Seek to file offset depending on if we're reading from RomFS, ExeFS, etc
|
||||||
const u64 romFSSize = cxi->romFS.size;
|
switch (type) {
|
||||||
const u64 romFSOffset = cxi->romFS.offset;
|
case PathType::RomFS: {
|
||||||
if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) {
|
const u64 romFSSize = cxi->romFS.size;
|
||||||
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
|
const u64 romFSOffset = cxi->romFS.offset;
|
||||||
|
if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) {
|
||||||
|
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
|
||||||
|
}
|
||||||
|
|
||||||
|
fsInfo = cxi->romFS;
|
||||||
|
offset += 0x1000;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
fsInfo = cxi->romFS;
|
case PathType::ExeFS: {
|
||||||
offset += 0x1000;
|
const u64 exeFSSize = cxi->exeFS.size;
|
||||||
break;
|
const u64 exeFSOffset = cxi->exeFS.offset;
|
||||||
}
|
if ((offset >> 32) || (offset >= exeFSSize) || (offset + size >= exeFSSize)) {
|
||||||
|
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
|
||||||
|
}
|
||||||
|
|
||||||
case PathType::ExeFS: {
|
fsInfo = cxi->exeFS;
|
||||||
const u64 exeFSSize = cxi->exeFS.size;
|
break;
|
||||||
const u64 exeFSOffset = cxi->exeFS.offset;
|
|
||||||
if ((offset >> 32) || (offset >= exeFSSize) || (offset + size >= exeFSSize)) {
|
|
||||||
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fsInfo = cxi->exeFS;
|
// Normally, the update RomFS should overlay the cartridge RomFS when reading from this and an update is installed.
|
||||||
break;
|
// So to support updates, we need to perform this overlaying. For now, read from the cartridge RomFS.
|
||||||
}
|
case PathType::UpdateRomFS: {
|
||||||
|
Helpers::warn("Reading from update RomFS but updates are currently not supported! Reading from regular RomFS instead\n");
|
||||||
|
|
||||||
// Normally, the update RomFS should overlay the cartridge RomFS when reading from this and an update is installed.
|
const u64 romFSSize = cxi->romFS.size;
|
||||||
// So to support updates, we need to perform this overlaying. For now, read from the cartridge RomFS.
|
const u64 romFSOffset = cxi->romFS.offset;
|
||||||
case PathType::UpdateRomFS: {
|
if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) {
|
||||||
Helpers::warn("Reading from update RomFS but updates are currently not supported! Reading from regular RomFS instead\n");
|
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
|
||||||
|
}
|
||||||
|
|
||||||
const u64 romFSSize = cxi->romFS.size;
|
fsInfo = cxi->romFS;
|
||||||
const u64 romFSOffset = cxi->romFS.offset;
|
offset += 0x1000;
|
||||||
if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) {
|
break;
|
||||||
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fsInfo = cxi->romFS;
|
default: Helpers::panic("Unimplemented file path type for SelfNCCH archive");
|
||||||
offset += 0x1000;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default: Helpers::panic("Unimplemented file path type for SelfNCCH archive");
|
data.resize(size);
|
||||||
|
std::tie(success, bytesRead) = cxi->readFromFile(ioFile, fsInfo, &data[0], offset, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<u8[]> data(new u8[size]);
|
else if (auto hb3dsx = mem.get3DSX(); hb3dsx != nullptr) {
|
||||||
auto [success, bytesRead] = cxi->readFromFile(ioFile, fsInfo, &data[0], offset, size);
|
switch (type) {
|
||||||
|
case PathType::RomFS: {
|
||||||
|
const u64 romFSSize = hb3dsx->romFSSize;
|
||||||
|
if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) {
|
||||||
|
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: Helpers::panic("Unimplemented file path type for 3DSX SelfNCCH archive");
|
||||||
|
}
|
||||||
|
|
||||||
|
data.resize(size);
|
||||||
|
std::tie(success, bytesRead) = hb3dsx->readRomFSBytes(&data[0], offset, size);
|
||||||
|
}
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
Helpers::panic("Failed to read from SelfNCCH archive");
|
Helpers::panic("Failed to read from SelfNCCH archive");
|
||||||
|
|
198
src/core/fs/archive_user_save_data.cpp
Normal file
198
src/core/fs/archive_user_save_data.cpp
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
#include <algorithm>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "fs/archive_user_save_data.hpp"
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
HorizonResult UserSaveDataArchive::createFile(const FSPath& path, u64 size) {
|
||||||
|
if (path.type == PathType::UTF16) {
|
||||||
|
if (!isPathSafe<PathType::UTF16>(path)) Helpers::panic("Unsafe path in UserSaveData::CreateFile");
|
||||||
|
|
||||||
|
fs::path p = IOFile::getAppData() / "SaveData";
|
||||||
|
p += fs::path(path.utf16_string).make_preferred();
|
||||||
|
|
||||||
|
if (fs::exists(p)) return Result::FS::AlreadyExists;
|
||||||
|
|
||||||
|
IOFile file(p.string().c_str(), "wb");
|
||||||
|
|
||||||
|
// If the size is 0, leave the file empty and return success
|
||||||
|
if (size == 0) {
|
||||||
|
file.close();
|
||||||
|
return Result::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it is not empty, seek to size - 1 and write a 0 to create a file of size "size"
|
||||||
|
else if (file.seek(size - 1, SEEK_SET) && file.writeBytes("", 1).second == 1) {
|
||||||
|
file.close();
|
||||||
|
return Result::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
return Result::FS::FileTooLarge;
|
||||||
|
}
|
||||||
|
|
||||||
|
Helpers::panic("UserSaveDataArchive::OpenFile: Failed");
|
||||||
|
return Result::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizonResult UserSaveDataArchive::createDirectory(const FSPath& path) {
|
||||||
|
if (path.type == PathType::UTF16) {
|
||||||
|
if (!isPathSafe<PathType::UTF16>(path)) Helpers::panic("Unsafe path in UserSaveData::OpenFile");
|
||||||
|
|
||||||
|
fs::path p = IOFile::getAppData() / "SaveData";
|
||||||
|
p += fs::path(path.utf16_string).make_preferred();
|
||||||
|
|
||||||
|
if (fs::is_directory(p)) return Result::FS::AlreadyExists;
|
||||||
|
if (fs::is_regular_file(p)) {
|
||||||
|
Helpers::panic("File path passed to UserSaveData::CreateDirectory");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = fs::create_directory(p);
|
||||||
|
return success ? Result::Success : Result::FS::UnexpectedFileOrDir;
|
||||||
|
} else {
|
||||||
|
Helpers::panic("Unimplemented UserSaveData::CreateDirectory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizonResult UserSaveDataArchive::deleteFile(const FSPath& path) {
|
||||||
|
if (path.type == PathType::UTF16) {
|
||||||
|
if (!isPathSafe<PathType::UTF16>(path)) Helpers::panic("Unsafe path in UserSaveData::DeleteFile");
|
||||||
|
|
||||||
|
fs::path p = IOFile::getAppData() / "SaveData";
|
||||||
|
p += fs::path(path.utf16_string).make_preferred();
|
||||||
|
|
||||||
|
if (fs::is_directory(p)) {
|
||||||
|
Helpers::panic("UserSaveData::DeleteFile: Tried to delete directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs::is_regular_file(p)) {
|
||||||
|
return Result::FS::FileNotFoundAlt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::error_code ec;
|
||||||
|
bool success = fs::remove(p, ec);
|
||||||
|
|
||||||
|
// It might still be possible for fs::remove to fail, if there's eg an open handle to a file being deleted
|
||||||
|
// In this case, print a warning, but still return success for now
|
||||||
|
if (!success) {
|
||||||
|
Helpers::warn("UserSaveData::DeleteFile: fs::remove failed\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
Helpers::panic("UserSaveDataArchive::DeleteFile: Unknown path type");
|
||||||
|
return Result::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileDescriptor UserSaveDataArchive::openFile(const FSPath& path, const FilePerms& perms) {
|
||||||
|
if (path.type == PathType::UTF16) {
|
||||||
|
if (!isPathSafe<PathType::UTF16>(path)) Helpers::panic("Unsafe path in UserSaveData::OpenFile");
|
||||||
|
|
||||||
|
if (perms.raw == 0 || (perms.create() && !perms.write())) Helpers::panic("[UserSaveData] Unsupported flags for OpenFile");
|
||||||
|
|
||||||
|
fs::path p = IOFile::getAppData() / "SaveData";
|
||||||
|
p += fs::path(path.utf16_string).make_preferred();
|
||||||
|
|
||||||
|
const char* permString = perms.write() ? "r+b" : "rb";
|
||||||
|
|
||||||
|
if (fs::exists(p)) { // Return file descriptor if the file exists
|
||||||
|
IOFile file(p.string().c_str(), permString);
|
||||||
|
return file.isOpen() ? file.getHandle() : FileError;
|
||||||
|
} else {
|
||||||
|
// If the file is not found, create it if the create flag is on
|
||||||
|
if (perms.create()) {
|
||||||
|
IOFile file(p.string().c_str(), "wb"); // Create file
|
||||||
|
file.close(); // Close it
|
||||||
|
|
||||||
|
file.open(p.string().c_str(), permString); // Reopen with proper perms
|
||||||
|
return file.isOpen() ? file.getHandle() : FileError;
|
||||||
|
} else {
|
||||||
|
return FileError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Helpers::panic("UserSaveDataArchive::OpenFile: Failed");
|
||||||
|
return FileError;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rust::Result<DirectorySession, HorizonResult> UserSaveDataArchive::openDirectory(const FSPath& path) {
|
||||||
|
if (path.type == PathType::UTF16) {
|
||||||
|
if (!isPathSafe<PathType::UTF16>(path)) Helpers::panic("Unsafe path in UserSaveData::OpenDirectory");
|
||||||
|
|
||||||
|
fs::path p = IOFile::getAppData() / "SaveData";
|
||||||
|
p += fs::path(path.utf16_string).make_preferred();
|
||||||
|
|
||||||
|
if (fs::is_regular_file(p)) {
|
||||||
|
printf("SaveData: OpenDirectory used with a file path");
|
||||||
|
return Err(Result::FS::UnexpectedFileOrDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs::is_directory(p)) {
|
||||||
|
return Ok(DirectorySession(this, p));
|
||||||
|
} else {
|
||||||
|
return Err(Result::FS::FileNotFoundAlt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Helpers::panic("UserSaveDataArchive::OpenDirectory: Unimplemented path type");
|
||||||
|
return Err(Result::Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
Rust::Result<ArchiveBase::FormatInfo, HorizonResult> UserSaveDataArchive::getFormatInfo(const FSPath& path) {
|
||||||
|
const fs::path formatInfoPath = getFormatInfoPath();
|
||||||
|
IOFile file(formatInfoPath, "rb");
|
||||||
|
|
||||||
|
// If the file failed to open somehow, we return that the archive is not formatted
|
||||||
|
if (!file.isOpen()) {
|
||||||
|
return Err(Result::FS::NotFormatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
FormatInfo ret;
|
||||||
|
auto [success, bytesRead] = file.readBytes(&ret, sizeof(FormatInfo));
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
if (!success || bytesRead != sizeof(FormatInfo)) {
|
||||||
|
Helpers::warn("UserSaveData::GetFormatInfo: Format file exists but was not properly read into the FormatInfo struct");
|
||||||
|
return Err(Result::FS::NotFormatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UserSaveDataArchive::format(const FSPath& path, const ArchiveBase::FormatInfo& info) {
|
||||||
|
const fs::path saveDataPath = IOFile::getAppData() / "SaveData";
|
||||||
|
const fs::path formatInfoPath = getFormatInfoPath();
|
||||||
|
|
||||||
|
// Delete all contents by deleting the directory then recreating it
|
||||||
|
fs::remove_all(saveDataPath);
|
||||||
|
fs::create_directories(saveDataPath);
|
||||||
|
|
||||||
|
// Write format info on disk
|
||||||
|
IOFile file(formatInfoPath, "wb");
|
||||||
|
file.writeBytes(&info, sizeof(info));
|
||||||
|
file.flush();
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Rust::Result<ArchiveBase*, HorizonResult> UserSaveDataArchive::openArchive(const FSPath& path) {
|
||||||
|
if (path.type != PathType::Binary) {
|
||||||
|
Helpers::panic("Unimplemented path type for UserSaveData archive: %d\n", path.type);
|
||||||
|
return Err(Result::FS::NotFoundInvalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fs::path formatInfoPath = getFormatInfoPath();
|
||||||
|
// Format info not found so the archive is not formatted
|
||||||
|
if (!fs::is_regular_file(formatInfoPath)) {
|
||||||
|
return Err(Result::FS::NotFormatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok((ArchiveBase*)this);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<u32> UserSaveDataArchive::readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) {
|
||||||
|
Helpers::panic("Unimplemented UserSaveData::ReadFile");
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -124,6 +124,7 @@ void Kernel::deleteObjectData(KernelObject& object) {
|
||||||
case KernelObjectType::Session: delete object.getData<Session>(); return;
|
case KernelObjectType::Session: delete object.getData<Session>(); return;
|
||||||
case KernelObjectType::Mutex: delete object.getData<Mutex>(); return;
|
case KernelObjectType::Mutex: delete object.getData<Mutex>(); return;
|
||||||
case KernelObjectType::Semaphore: delete object.getData<Semaphore>(); return;
|
case KernelObjectType::Semaphore: delete object.getData<Semaphore>(); return;
|
||||||
|
case KernelObjectType::Timer: delete object.getData<Timer>(); return;
|
||||||
case KernelObjectType::Thread: return;
|
case KernelObjectType::Thread: return;
|
||||||
case KernelObjectType::Dummy: return;
|
case KernelObjectType::Dummy: return;
|
||||||
default: [[unlikely]] Helpers::warn("unknown object type"); return;
|
default: [[unlikely]] Helpers::warn("unknown object type"); return;
|
||||||
|
|
|
@ -252,6 +252,14 @@ void Kernel::acquireSyncObject(KernelObject* object, const Thread& thread) {
|
||||||
case KernelObjectType::Thread:
|
case KernelObjectType::Thread:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case KernelObjectType::Timer: {
|
||||||
|
Timer* timer = object->getData<Timer>();
|
||||||
|
if (timer->resetType == ResetType::OneShot) { // One-shot timers automatically get cleared after waking up a thread
|
||||||
|
timer->fired = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default: Helpers::panic("Acquiring unimplemented sync object %s", object->getTypeName());
|
default: Helpers::panic("Acquiring unimplemented sync object %s", object->getTypeName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -652,6 +660,9 @@ bool Kernel::shouldWaitOnObject(KernelObject* object) {
|
||||||
case KernelObjectType::Thread: // Waiting on a thread waits until it's dead. If it's dead then no need to wait
|
case KernelObjectType::Thread: // Waiting on a thread waits until it's dead. If it's dead then no need to wait
|
||||||
return object->getData<Thread>()->status != ThreadStatus::Dead;
|
return object->getData<Thread>()->status != ThreadStatus::Dead;
|
||||||
|
|
||||||
|
case KernelObjectType::Timer: // We should wait on a timer only if it has not been signalled
|
||||||
|
return !object->getData<Timer>()->fired;
|
||||||
|
|
||||||
case KernelObjectType::Semaphore: // Wait if the semaphore count <= 0
|
case KernelObjectType::Semaphore: // Wait if the semaphore count <= 0
|
||||||
return object->getData<Semaphore>()->availableCount <= 0;
|
return object->getData<Semaphore>()->availableCount <= 0;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,127 @@
|
||||||
#include "kernel.hpp"
|
#include "kernel.hpp"
|
||||||
|
#include "cpu.hpp"
|
||||||
|
|
||||||
void Kernel::svcCreateTimer() { Helpers::panic("Kernel::CreateTimer"); }
|
Handle Kernel::makeTimer(ResetType type) {
|
||||||
void Kernel::svcSetTimer() { Helpers::panic("Kernel::SetTimer"); }
|
Handle ret = makeObject(KernelObjectType::Timer);
|
||||||
void Kernel::svcClearTimer() { Helpers::panic("Kernel::ClearTimer"); }
|
objects[ret].data = new Timer(type);
|
||||||
void Kernel::svcCancelTimer() { Helpers::panic("Kernel::CancelTimer"); }
|
|
||||||
|
if (type == ResetType::Pulse) {
|
||||||
|
Helpers::panic("Created pulse timer");
|
||||||
|
}
|
||||||
|
|
||||||
|
// timerHandles.push_back(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kernel::updateTimer(Handle handle, Timer* timer) {
|
||||||
|
if (timer->running) {
|
||||||
|
const u64 currentTicks = cpu.getTicks();
|
||||||
|
u64 elapsedTicks = currentTicks - timer->startTick;
|
||||||
|
|
||||||
|
constexpr double ticksPerSec = double(CPU::ticksPerSec);
|
||||||
|
constexpr double nsPerTick = ticksPerSec / 1000000000.0;
|
||||||
|
const s64 elapsedNs = s64(double(elapsedTicks) * nsPerTick);
|
||||||
|
|
||||||
|
// Timer has fired
|
||||||
|
if (elapsedNs >= timer->currentDelay) {
|
||||||
|
timer->startTick = currentTicks;
|
||||||
|
timer->currentDelay = timer->interval;
|
||||||
|
signalTimer(handle, timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kernel::cancelTimer(Timer* timer) {
|
||||||
|
timer->running = false;
|
||||||
|
// TODO: When we have a scheduler this should properly cancel timer events in the scheduler
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kernel::signalTimer(Handle timerHandle, Timer* timer) {
|
||||||
|
timer->fired = true;
|
||||||
|
requireReschedule();
|
||||||
|
|
||||||
|
// Check if there's any thread waiting on this event
|
||||||
|
if (timer->waitlist != 0) {
|
||||||
|
wakeupAllThreads(timer->waitlist, timerHandle);
|
||||||
|
timer->waitlist = 0; // No threads waiting;
|
||||||
|
|
||||||
|
switch (timer->resetType) {
|
||||||
|
case ResetType::OneShot: timer->fired = false; break;
|
||||||
|
case ResetType::Sticky: break;
|
||||||
|
case ResetType::Pulse: Helpers::panic("Signalled pulsing timer"); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kernel::svcCreateTimer() {
|
||||||
|
const u32 resetType = regs[1];
|
||||||
|
if (resetType > 2) {
|
||||||
|
Helpers::panic("Invalid reset type for event %d", resetType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Have a warning here until our timers don't suck
|
||||||
|
Helpers::warn("Called Kernel::CreateTimer. Timers are currently not updated nor triggered properly!");
|
||||||
|
|
||||||
|
logSVC("CreateTimer (resetType = %s)\n", resetTypeToString(resetType));
|
||||||
|
regs[0] = Result::Success;
|
||||||
|
regs[1] = makeTimer(static_cast<ResetType>(resetType));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kernel::svcSetTimer() {
|
||||||
|
Handle handle = regs[0];
|
||||||
|
// TODO: Is this actually s64 or u64? 3DBrew says s64, but u64 makes more sense
|
||||||
|
const s64 initial = s64(u64(regs[1]) | (u64(regs[2]) << 32));
|
||||||
|
const s64 interval = s64(u64(regs[3]) | (u64(regs[4]) << 32));
|
||||||
|
logSVC("SetTimer (handle = %X, initial delay = %llX, interval delay = %llX)\n", handle, initial, interval);
|
||||||
|
|
||||||
|
KernelObject* object = getObject(handle, KernelObjectType::Timer);
|
||||||
|
|
||||||
|
if (object == nullptr) {
|
||||||
|
Helpers::panic("Tried to set non-existent timer %X\n", handle);
|
||||||
|
regs[0] = Result::Kernel::InvalidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer* timer = object->getData<Timer>();
|
||||||
|
cancelTimer(timer);
|
||||||
|
timer->currentDelay = initial;
|
||||||
|
timer->interval = interval;
|
||||||
|
timer->running = true;
|
||||||
|
timer->startTick = cpu.getTicks();
|
||||||
|
|
||||||
|
// If the initial delay is 0 then instantly signal the timer
|
||||||
|
if (initial == 0) {
|
||||||
|
signalTimer(handle, timer);
|
||||||
|
} else {
|
||||||
|
// This should schedule an event in the scheduler when we have one
|
||||||
|
}
|
||||||
|
|
||||||
|
regs[0] = Result::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kernel::svcClearTimer() {
|
||||||
|
Handle handle = regs[0];
|
||||||
|
logSVC("ClearTimer (handle = %X)\n", handle);
|
||||||
|
KernelObject* object = getObject(handle, KernelObjectType::Timer);
|
||||||
|
|
||||||
|
if (object == nullptr) {
|
||||||
|
Helpers::panic("Tried to clear non-existent timer %X\n", handle);
|
||||||
|
regs[0] = Result::Kernel::InvalidHandle;
|
||||||
|
} else {
|
||||||
|
object->getData<Timer>()->fired = false;
|
||||||
|
regs[0] = Result::Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kernel::svcCancelTimer() {
|
||||||
|
Handle handle = regs[0];
|
||||||
|
logSVC("CancelTimer (handle = %X)\n", handle);
|
||||||
|
KernelObject* object = getObject(handle, KernelObjectType::Timer);
|
||||||
|
|
||||||
|
if (object == nullptr) {
|
||||||
|
Helpers::panic("Tried to cancel non-existent timer %X\n", handle);
|
||||||
|
regs[0] = Result::Kernel::InvalidHandle;
|
||||||
|
} else {
|
||||||
|
cancelTimer(object->getData<Timer>());
|
||||||
|
regs[0] = Result::Success;
|
||||||
|
}
|
||||||
|
}
|
302
src/core/loader/3dsx.cpp
Normal file
302
src/core/loader/3dsx.cpp
Normal file
|
@ -0,0 +1,302 @@
|
||||||
|
#include "loader/3dsx.hpp"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <optional>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
#include "memory.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct LoadInfo {
|
||||||
|
u32 codeSegSizeAligned;
|
||||||
|
u32 rodataSegSizeAligned;
|
||||||
|
u32 dataSegSizeAligned;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline u32 translateAddr(const u32 off, const u32* addrs, const u32* offsets) {
|
||||||
|
if (off < offsets[1]) {
|
||||||
|
return addrs[0] + off;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (off < offsets[2]) {
|
||||||
|
return addrs[1] + off - offsets[1];
|
||||||
|
}
|
||||||
|
return addrs[2] + off - offsets[2];
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool Memory::map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header) {
|
||||||
|
const LoadInfo hbInfo = {
|
||||||
|
.codeSegSizeAligned = (header.codeSegSize + 0xFFF) & ~0xFFF,
|
||||||
|
.rodataSegSizeAligned = (header.rodataSegSize + 0xFFF) & ~0xFFF,
|
||||||
|
.dataSegSizeAligned = (header.dataSegSize + 0xFFF) & ~0xFFF,
|
||||||
|
};
|
||||||
|
|
||||||
|
const u32 textSegAddr = HB3DSX::entrypoint;
|
||||||
|
const u32 rodataSegAddr = textSegAddr + hbInfo.codeSegSizeAligned;
|
||||||
|
const u32 dataSegAddr = rodataSegAddr + hbInfo.rodataSegSizeAligned;
|
||||||
|
const u32 extraPageAddr = dataSegAddr + hbInfo.dataSegSizeAligned;
|
||||||
|
|
||||||
|
printf("Text address = %08X, size = %08X\n", textSegAddr, hbInfo.codeSegSizeAligned);
|
||||||
|
printf("Rodata address = %08X, size = %08X\n", rodataSegAddr, hbInfo.rodataSegSizeAligned);
|
||||||
|
printf("Data address = %08X, size = %08X\n", dataSegAddr, hbInfo.dataSegSizeAligned);
|
||||||
|
|
||||||
|
// Allocate stack, 3dsx/libctru don't require anymore than this
|
||||||
|
if (!allocateMainThreadStack(4_KB)) {
|
||||||
|
// Should be unreachable
|
||||||
|
printf("Failed to allocate stack for 3DSX.\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map code file to memory
|
||||||
|
// Total memory to allocate for loading
|
||||||
|
// suum of aligned values is always aligned, have an extra RW page for libctru
|
||||||
|
const u32 totalSize = hbInfo.codeSegSizeAligned + hbInfo.rodataSegSizeAligned + hbInfo.dataSegSizeAligned + 4_KB;
|
||||||
|
|
||||||
|
const auto opt = findPaddr(totalSize);
|
||||||
|
if (!opt.has_value()) {
|
||||||
|
Helpers::panic("Failed to find paddr to map 3DSX file's code to");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map the ROM on the kernel side
|
||||||
|
const u32 textOffset = 0;
|
||||||
|
const u32 rodataOffset = textOffset + hbInfo.codeSegSizeAligned;
|
||||||
|
const u32 dataOffset = rodataOffset + hbInfo.rodataSegSizeAligned;
|
||||||
|
const u32 extraPageOffset = dataOffset + hbInfo.dataSegSizeAligned;
|
||||||
|
|
||||||
|
std::array<HB3DSX::RelocHeader, 3> relocHeaders;
|
||||||
|
auto [success, count] = hb3dsx.file.read(&relocHeaders[0], relocHeaders.size(), sizeof(HB3DSX::RelocHeader));
|
||||||
|
if (!success || count != relocHeaders.size()) {
|
||||||
|
Helpers::panic("Failed to read 3DSX relocation headers");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const u32 dataLoadsize = header.dataSegSize - header.bssSize; // 3DSX data size in header includes bss
|
||||||
|
std::vector<u8> code(totalSize, 0);
|
||||||
|
|
||||||
|
std::tie(success, count) = hb3dsx.file.readBytes(&code[textOffset], header.codeSegSize);
|
||||||
|
if (!success || count != header.codeSegSize) {
|
||||||
|
Helpers::panic("Failed to read 3DSX text segment");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tie(success, count) = hb3dsx.file.readBytes(&code[rodataOffset], header.rodataSegSize);
|
||||||
|
if (!success || count != header.rodataSegSize) {
|
||||||
|
Helpers::panic("Failed to read 3DSX rodata segment");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tie(success, count) = hb3dsx.file.readBytes(&code[dataOffset], dataLoadsize);
|
||||||
|
if (!success || count != dataLoadsize) {
|
||||||
|
Helpers::panic("Failed to read 3DSX data segment");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<HB3DSX::Reloc> currentRelocs;
|
||||||
|
|
||||||
|
const u32 segAddrs[] = {
|
||||||
|
textSegAddr,
|
||||||
|
rodataSegAddr,
|
||||||
|
dataSegAddr,
|
||||||
|
extraPageAddr,
|
||||||
|
};
|
||||||
|
|
||||||
|
const u32 segOffs[] = {
|
||||||
|
textOffset,
|
||||||
|
rodataOffset,
|
||||||
|
dataOffset,
|
||||||
|
extraPageOffset,
|
||||||
|
};
|
||||||
|
|
||||||
|
const u32 segSizes[] = {
|
||||||
|
header.codeSegSize,
|
||||||
|
header.rodataSegSize,
|
||||||
|
dataLoadsize,
|
||||||
|
0x1000,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& relocHeader : relocHeaders) {
|
||||||
|
currentRelocs.resize(relocHeader.absoluteCount + relocHeader.relativeCount);
|
||||||
|
std::tie(success, count) = hb3dsx.file.read(¤tRelocs[0], currentRelocs.size(), sizeof(HB3DSX::Reloc));
|
||||||
|
if (!success || count != currentRelocs.size()) {
|
||||||
|
Helpers::panic("Failed to read 3DSX relocations");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto allRelocs = std::span(currentRelocs);
|
||||||
|
const auto absoluteRelocs = allRelocs.subspan(0, relocHeader.absoluteCount);
|
||||||
|
const auto relativeRelocs = allRelocs.subspan(relocHeader.absoluteCount, relocHeader.relativeCount);
|
||||||
|
|
||||||
|
const auto currentSeg = &relocHeader - &relocHeaders[0];
|
||||||
|
const auto sectionDataStartAs = std::span(code).subspan(segOffs[currentSeg], segSizes[currentSeg]);
|
||||||
|
auto sectionData = sectionDataStartAs;
|
||||||
|
|
||||||
|
const auto RelocationAction = [&](const HB3DSX::Reloc& reloc, const HB3DSX::RelocType relocType) -> bool {
|
||||||
|
if (reloc.skip) {
|
||||||
|
sectionData = sectionData.subspan(reloc.skip * sizeof(u32)); // advance by `skip` words (32-bit values)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (u32 m = 0; m < reloc.patch && !sectionData.empty(); ++m) {
|
||||||
|
const u32 inAddr = textSegAddr + (sectionData.data() - code.data()); // byte offset -> word count
|
||||||
|
u32 origData = 0;
|
||||||
|
std::memcpy(&origData, §ionData[0], sizeof(u32));
|
||||||
|
const u32 subType = origData >> (32 - 4);
|
||||||
|
const u32 addr = translateAddr(origData & ~0xF0000000, segAddrs, segOffs);
|
||||||
|
|
||||||
|
switch (relocType) {
|
||||||
|
case HB3DSX::RelocType::Absolute: {
|
||||||
|
if (subType != 0) {
|
||||||
|
Helpers::panic("Unsupported absolute reloc subtype");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::memcpy(§ionData[0], &addr, sizeof(u32));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case HB3DSX::RelocType::Relative: {
|
||||||
|
u32 data = addr - inAddr;
|
||||||
|
switch (subType) {
|
||||||
|
case 1: // 31-bit signed offset
|
||||||
|
data &= ~(1u << 31);
|
||||||
|
case 0: // 32-bit signed offset
|
||||||
|
std::memcpy(§ionData[0], &data, sizeof(u32));
|
||||||
|
break;
|
||||||
|
default: Helpers::panic("Unsupported relative reloc subtype"); return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sectionData = sectionData.subspan(sizeof(u32));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& reloc : absoluteRelocs) {
|
||||||
|
if (!RelocationAction(reloc, HB3DSX::RelocType::Absolute)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sectionData = sectionDataStartAs; // restart from the beginning for the next part
|
||||||
|
for (const auto& reloc : relativeRelocs) {
|
||||||
|
if (!RelocationAction(reloc, HB3DSX::RelocType::Relative)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect and fill _prm structure
|
||||||
|
HB3DSX::PrmStruct pst;
|
||||||
|
std::memcpy(&pst, &code[4], sizeof(pst));
|
||||||
|
if (pst.magic[0] == '_' && pst.magic[1] == 'p' && pst.magic[2] == 'r' && pst.magic[3] == 'm') {
|
||||||
|
// if there was any argv to put, it would go there
|
||||||
|
// first u32: argc
|
||||||
|
// remaining: continuous argv string (NUL-char separated, ofc)
|
||||||
|
// std::memcpy(&code[extraPageOffset], argvBuffer, ...);
|
||||||
|
|
||||||
|
// setting to NULL (default) = run from system. load romfs from process.
|
||||||
|
// non-NULL = homebrew launcher. load romfs from 3dsx @ argv[0]
|
||||||
|
// pst.pSrvOverride = extraPageAddr + 0xFFC;
|
||||||
|
|
||||||
|
pst.pArgList = extraPageAddr;
|
||||||
|
|
||||||
|
// RUNFLAG_APTREINIT: Reinitialize APT.
|
||||||
|
// From libctru. Because there's no previously running software here
|
||||||
|
pst.runFlags |= 1 << 1;
|
||||||
|
|
||||||
|
/* s64 dummy;
|
||||||
|
bool isN3DS = svcGetSystemInfo(&dummy, 0x10001, 0) == 0;
|
||||||
|
if (isN3DS)
|
||||||
|
{
|
||||||
|
pst->heapSize = u32(48_MB);
|
||||||
|
pst->linearHeapSize = u32(64_MB);
|
||||||
|
} else */ {
|
||||||
|
pst.heapSize = u32(24_MB);
|
||||||
|
pst.linearHeapSize = u32(32_MB);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::memcpy(&code[4], &pst, sizeof(pst));
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto paddr = opt.value();
|
||||||
|
std::memcpy(&fcram[paddr], &code[0], totalSize); // Copy the 3 segments + BSS to FCRAM
|
||||||
|
|
||||||
|
allocateMemory(textSegAddr, paddr + textOffset, hbInfo.codeSegSizeAligned, true, true, false, true); // Text is R-X
|
||||||
|
allocateMemory(rodataSegAddr, paddr + rodataOffset, hbInfo.rodataSegSizeAligned, true, true, false, false); // Rodata is R--
|
||||||
|
allocateMemory(dataSegAddr, paddr + dataOffset, hbInfo.dataSegSizeAligned + 0x1000, true, true, true, false); // Data+BSS+Extra is RW-
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<u32> Memory::load3DSX(const std::filesystem::path& path) {
|
||||||
|
HB3DSX hb3dsx;
|
||||||
|
if (!hb3dsx.file.open(path, "rb")) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 magic[4]; // Must be "3DSX"
|
||||||
|
auto [success, bytes] = hb3dsx.file.readBytes(magic, 4);
|
||||||
|
|
||||||
|
if (!success || bytes != 4) {
|
||||||
|
printf("Failed to read 3DSX magic\n");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (magic[0] != '3' || magic[1] != 'D' || magic[2] != 'S' || magic[3] != 'X') {
|
||||||
|
printf("3DSX with wrong magic value\n");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
HB3DSX::Header hbHeader;
|
||||||
|
std::tie(success, bytes) = hb3dsx.file.readBytes(&hbHeader, sizeof(hbHeader));
|
||||||
|
if (!success || bytes != sizeof(hbHeader)) {
|
||||||
|
printf("Failed to read 3DSX header\n");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hbHeader.headerSize == 0x20 || hbHeader.headerSize == 0x2C) {
|
||||||
|
if (hbHeader.headerSize == 0x2C) {
|
||||||
|
hb3dsx.file.seek(8, SEEK_CUR); // skip SMDH info
|
||||||
|
std::tie(success, bytes) = hb3dsx.file.readBytes(&hb3dsx.romFSOffset, 4);
|
||||||
|
if (!success || bytes != 4) {
|
||||||
|
printf("Failed to read 3DSX romFS offset\n");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto fileSize = hb3dsx.file.size();
|
||||||
|
if (!fileSize) {
|
||||||
|
printf("Failed to get 3DSX size\n");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
hb3dsx.romFSSize = *fileSize - hb3dsx.romFSOffset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("Invalid 3DSX header size\n");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!map3DSX(hb3dsx, hbHeader)) {
|
||||||
|
printf("Failed to map 3DSX\n");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
loaded3DSX = std::move(hb3dsx);
|
||||||
|
return HB3DSX::entrypoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HB3DSX::hasRomFs() const { return romFSSize != 0 && romFSOffset != 0; }
|
||||||
|
|
||||||
|
std::pair<bool, std::size_t> HB3DSX::readRomFSBytes(void* dst, std::size_t offset, std::size_t size) {
|
||||||
|
if (!hasRomFs()) {
|
||||||
|
return {false, 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file.seek(romFSOffset + offset)) {
|
||||||
|
return {false, 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.readBytes(dst, size);
|
||||||
|
}
|
|
@ -150,6 +150,7 @@ u32 Memory::read32(u32 vaddr) {
|
||||||
return *(u32*)(pointer + offset);
|
return *(u32*)(pointer + offset);
|
||||||
} else {
|
} else {
|
||||||
switch (vaddr) {
|
switch (vaddr) {
|
||||||
|
case 0x1FF80000: return u32(kernelVersion) << 16;
|
||||||
case ConfigMem::Datetime0: return u32(timeSince3DSEpoch()); // ms elapsed since Jan 1 1900, bottom 32 bits
|
case ConfigMem::Datetime0: return u32(timeSince3DSEpoch()); // ms elapsed since Jan 1 1900, bottom 32 bits
|
||||||
case ConfigMem::Datetime0 + 4:
|
case ConfigMem::Datetime0 + 4:
|
||||||
return u32(timeSince3DSEpoch() >> 32); // top 32 bits
|
return u32(timeSince3DSEpoch() >> 32); // top 32 bits
|
||||||
|
|
File diff suppressed because it is too large
Load diff
3
src/core/renderer_vk/vk_api.cpp
Normal file
3
src/core/renderer_vk/vk_api.cpp
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
#include "renderer_vk/vk_api.hpp"
|
||||||
|
|
||||||
|
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE;
|
119
src/core/renderer_vk/vk_descriptor_heap.cpp
Normal file
119
src/core/renderer_vk/vk_descriptor_heap.cpp
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
#include "renderer_vk/vk_descriptor_heap.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <optional>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
DescriptorHeap::DescriptorHeap(vk::Device device) : device(device) {}
|
||||||
|
|
||||||
|
std::optional<vk::DescriptorSet> DescriptorHeap::allocateDescriptorSet() {
|
||||||
|
// Find a free slot
|
||||||
|
const auto freeSlot = std::find(allocationMap.begin(), allocationMap.end(), false);
|
||||||
|
|
||||||
|
// If there is no free slot, return
|
||||||
|
if (freeSlot == allocationMap.end()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark the slot as allocated
|
||||||
|
*freeSlot = true;
|
||||||
|
|
||||||
|
const u16 index = static_cast<u16>(std::distance(allocationMap.begin(), freeSlot));
|
||||||
|
|
||||||
|
vk::UniqueDescriptorSet& newDescriptorSet = descriptorSets[index];
|
||||||
|
|
||||||
|
if (!newDescriptorSet) {
|
||||||
|
// Descriptor set doesn't exist yet. Allocate a new one
|
||||||
|
vk::DescriptorSetAllocateInfo allocateInfo = {};
|
||||||
|
|
||||||
|
allocateInfo.descriptorPool = descriptorPool.get();
|
||||||
|
allocateInfo.pSetLayouts = &descriptorSetLayout.get();
|
||||||
|
allocateInfo.descriptorSetCount = 1;
|
||||||
|
|
||||||
|
if (auto AllocateResult = device.allocateDescriptorSetsUnique(allocateInfo); AllocateResult.result == vk::Result::eSuccess) {
|
||||||
|
newDescriptorSet = std::move(AllocateResult.value[0]);
|
||||||
|
} else {
|
||||||
|
// Error allocating descriptor set
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newDescriptorSet.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DescriptorHeap::freeDescriptorSet(vk::DescriptorSet Set) {
|
||||||
|
// Find the descriptor set
|
||||||
|
const auto found =
|
||||||
|
std::find_if(descriptorSets.begin(), descriptorSets.end(), [&Set](const auto& CurSet) -> bool { return CurSet.get() == Set; });
|
||||||
|
|
||||||
|
// If the descriptor set is not found, return
|
||||||
|
if (found == descriptorSets.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark the slot as free
|
||||||
|
const u16 index = static_cast<u16>(std::distance(descriptorSets.begin(), found));
|
||||||
|
|
||||||
|
allocationMap[index] = false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<DescriptorHeap> DescriptorHeap::create(
|
||||||
|
vk::Device device, std::span<const vk::DescriptorSetLayoutBinding> bindings, u16 descriptorHeapCount
|
||||||
|
) {
|
||||||
|
DescriptorHeap newDescriptorHeap(device);
|
||||||
|
|
||||||
|
// Create a histogram of each of the descriptor types and how many of each
|
||||||
|
// the pool should have
|
||||||
|
// Todo: maybe keep this around as a hash table to do more dynamic
|
||||||
|
// allocations of descriptor sets rather than allocating them all up-front
|
||||||
|
std::vector<vk::DescriptorPoolSize> poolSizes;
|
||||||
|
{
|
||||||
|
std::unordered_map<vk::DescriptorType, u16> descriptorTypeCounts;
|
||||||
|
|
||||||
|
for (const auto& binding : bindings) {
|
||||||
|
descriptorTypeCounts[binding.descriptorType] += binding.descriptorCount;
|
||||||
|
}
|
||||||
|
for (const auto& descriptorTypeCount : descriptorTypeCounts) {
|
||||||
|
poolSizes.push_back(vk::DescriptorPoolSize(descriptorTypeCount.first, descriptorTypeCount.second * descriptorHeapCount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create descriptor pool
|
||||||
|
{
|
||||||
|
vk::DescriptorPoolCreateInfo poolInfo;
|
||||||
|
poolInfo.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet;
|
||||||
|
poolInfo.maxSets = descriptorHeapCount;
|
||||||
|
poolInfo.pPoolSizes = poolSizes.data();
|
||||||
|
poolInfo.poolSizeCount = poolSizes.size();
|
||||||
|
if (auto createResult = device.createDescriptorPoolUnique(poolInfo); createResult.result == vk::Result::eSuccess) {
|
||||||
|
newDescriptorHeap.descriptorPool = std::move(createResult.value);
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create descriptor set layout
|
||||||
|
{
|
||||||
|
vk::DescriptorSetLayoutCreateInfo layoutInfo;
|
||||||
|
layoutInfo.pBindings = bindings.data();
|
||||||
|
layoutInfo.bindingCount = bindings.size();
|
||||||
|
|
||||||
|
if (auto createResult = device.createDescriptorSetLayoutUnique(layoutInfo); createResult.result == vk::Result::eSuccess) {
|
||||||
|
newDescriptorHeap.descriptorSetLayout = std::move(createResult.value);
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newDescriptorHeap.descriptorSets.resize(descriptorHeapCount);
|
||||||
|
newDescriptorHeap.allocationMap.resize(descriptorHeapCount);
|
||||||
|
|
||||||
|
newDescriptorHeap.bindings.assign(bindings.begin(), bindings.end());
|
||||||
|
|
||||||
|
return {std::move(newDescriptorHeap)};
|
||||||
|
}
|
||||||
|
} // namespace Vulkan
|
98
src/core/renderer_vk/vk_descriptor_update_batch.cpp
Normal file
98
src/core/renderer_vk/vk_descriptor_update_batch.cpp
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
#include "renderer_vk/vk_descriptor_update_batch.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
void DescriptorUpdateBatch::flush() {
|
||||||
|
device.updateDescriptorSets({std::span(descriptorWrites.get(), descriptorWriteEnd)}, {std::span(descriptorCopies.get(), descriptorCopyEnd)});
|
||||||
|
|
||||||
|
descriptorWriteEnd = 0;
|
||||||
|
descriptorCopyEnd = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DescriptorUpdateBatch::addImage(vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::ImageView imageView, vk::ImageLayout imageLayout) {
|
||||||
|
if (descriptorWriteEnd >= descriptorWriteMax) {
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& imageInfo = descriptorInfos[descriptorWriteEnd].emplace<vk::DescriptorImageInfo>(vk::Sampler(), imageView, imageLayout);
|
||||||
|
|
||||||
|
descriptorWrites[descriptorWriteEnd] =
|
||||||
|
vk::WriteDescriptorSet(targetDescriptor, targetBinding, 0, 1, vk::DescriptorType::eSampledImage, &imageInfo, nullptr, nullptr);
|
||||||
|
|
||||||
|
++descriptorWriteEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DescriptorUpdateBatch::addSampler(vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::Sampler sampler) {
|
||||||
|
if (descriptorWriteEnd >= descriptorWriteMax) {
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& imageInfo = descriptorInfos[descriptorWriteEnd].emplace<vk::DescriptorImageInfo>(sampler, vk::ImageView(), vk::ImageLayout());
|
||||||
|
|
||||||
|
descriptorWrites[descriptorWriteEnd] =
|
||||||
|
vk::WriteDescriptorSet(targetDescriptor, targetBinding, 0, 1, vk::DescriptorType::eSampler, &imageInfo, nullptr, nullptr);
|
||||||
|
|
||||||
|
++descriptorWriteEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DescriptorUpdateBatch::addImageSampler(
|
||||||
|
vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::ImageView imageView, vk::Sampler sampler, vk::ImageLayout imageLayout
|
||||||
|
) {
|
||||||
|
if (descriptorWriteEnd >= descriptorWriteMax) {
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& imageInfo = descriptorInfos[descriptorWriteEnd].emplace<vk::DescriptorImageInfo>(sampler, imageView, imageLayout);
|
||||||
|
|
||||||
|
descriptorWrites[descriptorWriteEnd] =
|
||||||
|
vk::WriteDescriptorSet(targetDescriptor, targetBinding, 0, 1, vk::DescriptorType::eCombinedImageSampler, &imageInfo, nullptr, nullptr);
|
||||||
|
|
||||||
|
++descriptorWriteEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DescriptorUpdateBatch::addBuffer(
|
||||||
|
vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::Buffer buffer, vk::DeviceSize offset, vk::DeviceSize size
|
||||||
|
) {
|
||||||
|
if (descriptorWriteEnd >= descriptorWriteMax) {
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& bufferInfo = descriptorInfos[descriptorWriteEnd].emplace<vk::DescriptorBufferInfo>(buffer, offset, size);
|
||||||
|
|
||||||
|
descriptorWrites[descriptorWriteEnd] =
|
||||||
|
vk::WriteDescriptorSet(targetDescriptor, targetBinding, 0, 1, vk::DescriptorType::eStorageImage, nullptr, &bufferInfo, nullptr);
|
||||||
|
|
||||||
|
++descriptorWriteEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DescriptorUpdateBatch::copyBinding(
|
||||||
|
vk::DescriptorSet sourceDescriptor, vk::DescriptorSet targetDescriptor, u8 sourceBinding, u8 targetBinding, u8 sourceArrayElement,
|
||||||
|
u8 targetArrayElement, u8 descriptorCount
|
||||||
|
) {
|
||||||
|
if (descriptorCopyEnd >= descriptorCopyMax) {
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
descriptorCopies[descriptorCopyEnd] = vk::CopyDescriptorSet(
|
||||||
|
sourceDescriptor, sourceBinding, sourceArrayElement, targetDescriptor, targetBinding, targetArrayElement, descriptorCount
|
||||||
|
);
|
||||||
|
|
||||||
|
++descriptorCopyEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<DescriptorUpdateBatch> DescriptorUpdateBatch::create(vk::Device device, usize descriptorWriteMax, usize descriptorCopyMax)
|
||||||
|
|
||||||
|
{
|
||||||
|
DescriptorUpdateBatch newDescriptorUpdateBatch(device, descriptorWriteMax, descriptorCopyMax);
|
||||||
|
|
||||||
|
newDescriptorUpdateBatch.descriptorInfos = std::make_unique<DescriptorInfoUnion[]>(descriptorWriteMax);
|
||||||
|
newDescriptorUpdateBatch.descriptorWrites = std::make_unique<vk::WriteDescriptorSet[]>(descriptorWriteMax);
|
||||||
|
newDescriptorUpdateBatch.descriptorCopies = std::make_unique<vk::CopyDescriptorSet[]>(descriptorCopyMax);
|
||||||
|
|
||||||
|
return {std::move(newDescriptorUpdateBatch)};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
174
src/core/renderer_vk/vk_memory.cpp
Normal file
174
src/core/renderer_vk/vk_memory.cpp
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
#include "renderer_vk/vk_memory.hpp"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
static constexpr vk::DeviceSize alignUp(vk::DeviceSize value, std::size_t size) {
|
||||||
|
const vk::DeviceSize mod = static_cast<vk::DeviceSize>(value % size);
|
||||||
|
value -= mod;
|
||||||
|
return static_cast<vk::DeviceSize>(mod == vk::DeviceSize{0} ? value : value + size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given a speculative heap-allocation, defined by its current size and
|
||||||
|
// memory-type bits, appends a memory requirements structure to it, updating
|
||||||
|
// both the size and the required memory-type-bits. Returns the offset within
|
||||||
|
// the heap for the current MemoryRequirements Todo: Sun Apr 23 13:28:25 PDT
|
||||||
|
// 2023 Rather than using a running-size of the heap, look at all of the memory
|
||||||
|
// requests and optimally create a packing for all of the offset and alignment
|
||||||
|
// requirements. Such as by satisfying all of the largest alignments first, and
|
||||||
|
// then the smallest, to reduce padding
|
||||||
|
static vk::DeviceSize commitMemoryRequestToHeap(
|
||||||
|
const vk::MemoryRequirements& curMemoryRequirements, vk::DeviceSize& curHeapEnd, u32& curMemoryTypeBits, vk::DeviceSize sizeAlignment
|
||||||
|
) {
|
||||||
|
// Accumulate a mask of all the memory types that satisfies each of the
|
||||||
|
// handles
|
||||||
|
curMemoryTypeBits &= curMemoryRequirements.memoryTypeBits;
|
||||||
|
|
||||||
|
// Pad up the memory sizes so they are not considered aliasing
|
||||||
|
const vk::DeviceSize curMemoryOffset = alignUp(curHeapEnd, curMemoryRequirements.alignment);
|
||||||
|
// Pad the size by the required size-alignment.
|
||||||
|
// Intended for BufferImageGranularity
|
||||||
|
const vk::DeviceSize curMemorySize = alignUp(curMemoryRequirements.size, sizeAlignment);
|
||||||
|
|
||||||
|
curHeapEnd = (curMemoryOffset + curMemorySize);
|
||||||
|
return curMemoryOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 findMemoryTypeIndex(
|
||||||
|
vk::PhysicalDevice physicalDevice, u32 memoryTypeMask, vk::MemoryPropertyFlags memoryProperties,
|
||||||
|
vk::MemoryPropertyFlags memoryExcludeProperties
|
||||||
|
) {
|
||||||
|
const vk::PhysicalDeviceMemoryProperties deviceMemoryProperties = physicalDevice.getMemoryProperties();
|
||||||
|
// Iterate the physical device's memory types until we find a match
|
||||||
|
for (std::size_t i = 0; i < deviceMemoryProperties.memoryTypeCount; i++) {
|
||||||
|
if(
|
||||||
|
// Is within memory type mask
|
||||||
|
(((memoryTypeMask >> i) & 0b1) == 0b1) &&
|
||||||
|
// Has property flags
|
||||||
|
(deviceMemoryProperties.memoryTypes[i].propertyFlags
|
||||||
|
& memoryProperties)
|
||||||
|
== memoryProperties
|
||||||
|
&&
|
||||||
|
// None of the excluded properties are enabled
|
||||||
|
!(deviceMemoryProperties.memoryTypes[i].propertyFlags
|
||||||
|
& memoryExcludeProperties) )
|
||||||
|
{
|
||||||
|
return static_cast<u32>(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<vk::Result, vk::UniqueDeviceMemory> commitImageHeap(
|
||||||
|
vk::Device device, vk::PhysicalDevice physicalDevice, const std::span<const vk::Image> images, vk::MemoryPropertyFlags memoryProperties,
|
||||||
|
vk::MemoryPropertyFlags memoryExcludeProperties
|
||||||
|
) {
|
||||||
|
vk::MemoryAllocateInfo imageHeapAllocInfo = {};
|
||||||
|
u32 imageHeapMemoryTypeBits = 0xFFFFFFFF;
|
||||||
|
std::vector<vk::BindImageMemoryInfo> imageHeapBinds;
|
||||||
|
|
||||||
|
const vk::DeviceSize bufferImageGranularity = physicalDevice.getProperties().limits.bufferImageGranularity;
|
||||||
|
|
||||||
|
for (const vk::Image& curImage : images) {
|
||||||
|
const vk::DeviceSize curBindOffset = commitMemoryRequestToHeap(
|
||||||
|
device.getImageMemoryRequirements(curImage), imageHeapAllocInfo.allocationSize, imageHeapMemoryTypeBits, bufferImageGranularity
|
||||||
|
);
|
||||||
|
|
||||||
|
if (imageHeapMemoryTypeBits == 0) {
|
||||||
|
// No possible memory heap for all of the images to share
|
||||||
|
return std::make_tuple(vk::Result::eErrorOutOfDeviceMemory, vk::UniqueDeviceMemory());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put nullptr for the device memory for now
|
||||||
|
imageHeapBinds.emplace_back(vk::BindImageMemoryInfo{curImage, nullptr, curBindOffset});
|
||||||
|
}
|
||||||
|
|
||||||
|
const s32 memoryTypeIndex = findMemoryTypeIndex(physicalDevice, imageHeapMemoryTypeBits, memoryProperties, memoryExcludeProperties);
|
||||||
|
|
||||||
|
if (memoryTypeIndex < 0) {
|
||||||
|
// Unable to find a memory heap that satisfies all the images
|
||||||
|
return std::make_tuple(vk::Result::eErrorOutOfDeviceMemory, vk::UniqueDeviceMemory());
|
||||||
|
}
|
||||||
|
|
||||||
|
imageHeapAllocInfo.memoryTypeIndex = memoryTypeIndex;
|
||||||
|
|
||||||
|
vk::UniqueDeviceMemory imageHeapMemory = {};
|
||||||
|
|
||||||
|
if (auto allocResult = device.allocateMemoryUnique(imageHeapAllocInfo); allocResult.result == vk::Result::eSuccess) {
|
||||||
|
imageHeapMemory = std::move(allocResult.value);
|
||||||
|
} else {
|
||||||
|
return std::make_tuple(allocResult.result, vk::UniqueDeviceMemory());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign the device memory to the bindings
|
||||||
|
for (vk::BindImageMemoryInfo& curBind : imageHeapBinds) {
|
||||||
|
curBind.memory = imageHeapMemory.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now bind them all in one call
|
||||||
|
if (const vk::Result bindResult = device.bindImageMemory2(imageHeapBinds); bindResult == vk::Result::eSuccess) {
|
||||||
|
// Binding memory succeeded
|
||||||
|
} else {
|
||||||
|
return std::make_tuple(bindResult, vk::UniqueDeviceMemory());
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_tuple(vk::Result::eSuccess, std::move(imageHeapMemory));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<vk::Result, vk::UniqueDeviceMemory> commitBufferHeap(
|
||||||
|
vk::Device device, vk::PhysicalDevice physicalDevice, const std::span<const vk::Buffer> buffers, vk::MemoryPropertyFlags memoryProperties,
|
||||||
|
vk::MemoryPropertyFlags memoryExcludeProperties
|
||||||
|
) {
|
||||||
|
vk::MemoryAllocateInfo bufferHeapAllocInfo = {};
|
||||||
|
u32 bufferHeapMemoryTypeBits = 0xFFFFFFFF;
|
||||||
|
std::vector<vk::BindBufferMemoryInfo> bufferHeapBinds;
|
||||||
|
|
||||||
|
const vk::DeviceSize bufferImageGranularity = physicalDevice.getProperties().limits.bufferImageGranularity;
|
||||||
|
|
||||||
|
for (const vk::Buffer& curBuffer : buffers) {
|
||||||
|
const vk::DeviceSize curBindOffset = commitMemoryRequestToHeap(
|
||||||
|
device.getBufferMemoryRequirements(curBuffer), bufferHeapAllocInfo.allocationSize, bufferHeapMemoryTypeBits, bufferImageGranularity
|
||||||
|
);
|
||||||
|
|
||||||
|
if (bufferHeapMemoryTypeBits == 0) {
|
||||||
|
// No possible memory heap for all of the buffers to share
|
||||||
|
return std::make_tuple(vk::Result::eErrorOutOfDeviceMemory, vk::UniqueDeviceMemory());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put nullptr for the device memory for now
|
||||||
|
bufferHeapBinds.emplace_back(vk::BindBufferMemoryInfo{curBuffer, nullptr, curBindOffset});
|
||||||
|
}
|
||||||
|
|
||||||
|
const s32 memoryTypeIndex = findMemoryTypeIndex(physicalDevice, bufferHeapMemoryTypeBits, memoryProperties, memoryExcludeProperties);
|
||||||
|
|
||||||
|
if (memoryTypeIndex < 0) {
|
||||||
|
// Unable to find a memory heap that satisfies all the buffers
|
||||||
|
return std::make_tuple(vk::Result::eErrorOutOfDeviceMemory, vk::UniqueDeviceMemory());
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferHeapAllocInfo.memoryTypeIndex = memoryTypeIndex;
|
||||||
|
|
||||||
|
vk::UniqueDeviceMemory bufferHeapMemory = {};
|
||||||
|
|
||||||
|
if (auto allocResult = device.allocateMemoryUnique(bufferHeapAllocInfo); allocResult.result == vk::Result::eSuccess) {
|
||||||
|
bufferHeapMemory = std::move(allocResult.value);
|
||||||
|
} else {
|
||||||
|
return std::make_tuple(allocResult.result, vk::UniqueDeviceMemory());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign the device memory to the bindings
|
||||||
|
for (vk::BindBufferMemoryInfo& curBind : bufferHeapBinds) {
|
||||||
|
curBind.memory = bufferHeapMemory.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now bind them all in one call
|
||||||
|
if (const vk::Result bindResult = device.bindBufferMemory2(bufferHeapBinds); bindResult == vk::Result::eSuccess) {
|
||||||
|
// Binding memory succeeded
|
||||||
|
} else {
|
||||||
|
return std::make_tuple(bindResult, vk::UniqueDeviceMemory());
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_tuple(vk::Result::eSuccess, std::move(bufferHeapMemory));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
39
src/core/renderer_vk/vk_pica.cpp
Normal file
39
src/core/renderer_vk/vk_pica.cpp
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
#include "renderer_vk/vk_pica.hpp"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
vk::Format colorFormatToVulkan(PICA::ColorFmt colorFormat) {
|
||||||
|
switch (colorFormat) {
|
||||||
|
case PICA::ColorFmt::RGBA8: return vk::Format::eR8G8B8A8Unorm;
|
||||||
|
// VK_FORMAT_R8G8B8A8_UNORM is mandated by the vulkan specification
|
||||||
|
// VK_FORMAT_R8G8B8_UNORM may not be supported
|
||||||
|
// TODO: Detect this!
|
||||||
|
// case PICA::ColorFmt::RGB8: return vk::Format::eR8G8B8Unorm;
|
||||||
|
case PICA::ColorFmt::RGB8: return vk::Format::eR8G8B8A8Unorm;
|
||||||
|
case PICA::ColorFmt::RGBA5551: return vk::Format::eR5G5B5A1UnormPack16;
|
||||||
|
case PICA::ColorFmt::RGB565: return vk::Format::eR5G6B5UnormPack16;
|
||||||
|
case PICA::ColorFmt::RGBA4: return vk::Format::eR4G4B4A4UnormPack16;
|
||||||
|
}
|
||||||
|
return vk::Format::eUndefined;
|
||||||
|
}
|
||||||
|
vk::Format depthFormatToVulkan(PICA::DepthFmt depthFormat) {
|
||||||
|
switch (depthFormat) {
|
||||||
|
// VK_FORMAT_D16_UNORM is mandated by the vulkan specification
|
||||||
|
case PICA::DepthFmt::Depth16: return vk::Format::eD16Unorm;
|
||||||
|
case PICA::DepthFmt::Unknown1: return vk::Format::eUndefined;
|
||||||
|
// The GPU may _not_ support these formats natively
|
||||||
|
// Only one of:
|
||||||
|
// VK_FORMAT_X8_D24_UNORM_PACK32 and VK_FORMAT_D32_SFLOAT
|
||||||
|
// and one of:
|
||||||
|
// VK_FORMAT_D24_UNORM_S8_UINT and VK_FORMAT_D32_SFLOAT_S8_UINT
|
||||||
|
// will be supported
|
||||||
|
// TODO: Detect this!
|
||||||
|
// case PICA::DepthFmt::Depth24: return vk::Format::eX8D24UnormPack32;
|
||||||
|
// case PICA::DepthFmt::Depth24Stencil8: return vk::Format::eD24UnormS8Uint;
|
||||||
|
case PICA::DepthFmt::Depth24: return vk::Format::eD32Sfloat;
|
||||||
|
case PICA::DepthFmt::Depth24Stencil8: return vk::Format::eD32SfloatS8Uint;
|
||||||
|
}
|
||||||
|
return vk::Format::eUndefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
31
src/core/renderer_vk/vk_sampler_cache.cpp
Normal file
31
src/core/renderer_vk/vk_sampler_cache.cpp
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#include "renderer_vk/vk_sampler_cache.hpp"
|
||||||
|
|
||||||
|
#include <vulkan/vulkan_hash.hpp>
|
||||||
|
|
||||||
|
#include "helpers.hpp"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
|
||||||
|
SamplerCache::SamplerCache(vk::Device device) : device(device) {}
|
||||||
|
|
||||||
|
const vk::Sampler& SamplerCache::getSampler(const vk::SamplerCreateInfo& samplerInfo) {
|
||||||
|
const std::size_t samplerHash = std::hash<vk::SamplerCreateInfo>()(samplerInfo);
|
||||||
|
|
||||||
|
// Cache hit
|
||||||
|
if (samplerMap.contains(samplerHash)) {
|
||||||
|
return samplerMap.at(samplerHash).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto createResult = device.createSamplerUnique(samplerInfo); createResult.result == vk::Result::eSuccess) {
|
||||||
|
return (samplerMap[samplerHash] = std::move(createResult.value)).get();
|
||||||
|
} else {
|
||||||
|
Helpers::panic("Error creating sampler: %s\n", vk::to_string(createResult.result).c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<SamplerCache> SamplerCache::create(vk::Device device) {
|
||||||
|
SamplerCache newSamplerCache(device);
|
||||||
|
|
||||||
|
return {std::move(newSamplerCache)};
|
||||||
|
}
|
||||||
|
} // namespace Vulkan
|
|
@ -1,3 +0,0 @@
|
||||||
#include "renderer_vk/vulkan_api.hpp"
|
|
||||||
|
|
||||||
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE;
|
|
|
@ -8,11 +8,15 @@ namespace ACCommands {
|
||||||
CloseAsync = 0x00080004,
|
CloseAsync = 0x00080004,
|
||||||
GetLastErrorCode = 0x000A0000,
|
GetLastErrorCode = 0x000A0000,
|
||||||
RegisterDisconnectEvent = 0x00300004,
|
RegisterDisconnectEvent = 0x00300004,
|
||||||
|
IsConnected = 0x003E0042,
|
||||||
SetClientVersion = 0x00400042,
|
SetClientVersion = 0x00400042,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void ACService::reset() {}
|
void ACService::reset() {
|
||||||
|
connected = false;
|
||||||
|
disconnectEvent = std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
void ACService::handleSyncRequest(u32 messagePointer) {
|
void ACService::handleSyncRequest(u32 messagePointer) {
|
||||||
const u32 command = mem.read32(messagePointer);
|
const u32 command = mem.read32(messagePointer);
|
||||||
|
@ -21,6 +25,7 @@ void ACService::handleSyncRequest(u32 messagePointer) {
|
||||||
case ACCommands::CloseAsync: closeAsync(messagePointer); break;
|
case ACCommands::CloseAsync: closeAsync(messagePointer); break;
|
||||||
case ACCommands::CreateDefaultConfig: createDefaultConfig(messagePointer); break;
|
case ACCommands::CreateDefaultConfig: createDefaultConfig(messagePointer); break;
|
||||||
case ACCommands::GetLastErrorCode: getLastErrorCode(messagePointer); break;
|
case ACCommands::GetLastErrorCode: getLastErrorCode(messagePointer); break;
|
||||||
|
case ACCommands::IsConnected: isConnected(messagePointer); break;
|
||||||
case ACCommands::RegisterDisconnectEvent: registerDisconnectEvent(messagePointer); break;
|
case ACCommands::RegisterDisconnectEvent: registerDisconnectEvent(messagePointer); break;
|
||||||
case ACCommands::SetClientVersion: setClientVersion(messagePointer); break;
|
case ACCommands::SetClientVersion: setClientVersion(messagePointer); break;
|
||||||
default: Helpers::panic("AC service requested. Command: %08X\n", command);
|
default: Helpers::panic("AC service requested. Command: %08X\n", command);
|
||||||
|
@ -37,6 +42,11 @@ void ACService::cancelConnectAsync(u32 messagePointer) {
|
||||||
|
|
||||||
void ACService::closeAsync(u32 messagePointer) {
|
void ACService::closeAsync(u32 messagePointer) {
|
||||||
log("AC::CloseAsync (stubbed)\n");
|
log("AC::CloseAsync (stubbed)\n");
|
||||||
|
connected = false;
|
||||||
|
|
||||||
|
if (disconnectEvent.has_value()) {
|
||||||
|
Helpers::warn("AC::DisconnectEvent should be signalled but isn't implemented yet");
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Verify if this response header is correct on hardware
|
// TODO: Verify if this response header is correct on hardware
|
||||||
mem.write32(messagePointer, IPC::responseHeader(0x8, 1, 0));
|
mem.write32(messagePointer, IPC::responseHeader(0x8, 1, 0));
|
||||||
|
@ -59,6 +69,15 @@ void ACService::getLastErrorCode(u32 messagePointer) {
|
||||||
mem.write32(messagePointer + 8, 0); // Hopefully this means no error?
|
mem.write32(messagePointer + 8, 0); // Hopefully this means no error?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ACService::isConnected(u32 messagePointer) {
|
||||||
|
log("AC::IsConnected\n");
|
||||||
|
// This has parameters according to the command word but it's unknown what they are
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x3E, 2, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
mem.write8(messagePointer + 8, connected ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
void ACService::setClientVersion(u32 messagePointer) {
|
void ACService::setClientVersion(u32 messagePointer) {
|
||||||
u32 version = mem.read32(messagePointer + 4);
|
u32 version = mem.read32(messagePointer + 4);
|
||||||
log("AC::SetClientVersion (version = %d)\n", version);
|
log("AC::SetClientVersion (version = %d)\n", version);
|
||||||
|
@ -71,9 +90,11 @@ void ACService::registerDisconnectEvent(u32 messagePointer) {
|
||||||
log("AC::RegisterDisconnectEvent (stubbed)\n");
|
log("AC::RegisterDisconnectEvent (stubbed)\n");
|
||||||
const u32 pidHeader = mem.read32(messagePointer + 4);
|
const u32 pidHeader = mem.read32(messagePointer + 4);
|
||||||
const u32 copyHandleHeader = mem.read32(messagePointer + 12);
|
const u32 copyHandleHeader = mem.read32(messagePointer + 12);
|
||||||
// Event signaled when disconnecting from AC
|
// Event signaled when disconnecting from AC. TODO: Properly implement it.
|
||||||
const Handle eventHandle = mem.read32(messagePointer + 16);
|
const Handle eventHandle = mem.read32(messagePointer + 16);
|
||||||
|
|
||||||
|
disconnectEvent = eventHandle;
|
||||||
|
|
||||||
mem.write32(messagePointer, IPC::responseHeader(0x30, 1, 0));
|
mem.write32(messagePointer, IPC::responseHeader(0x30, 1, 0));
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
}
|
}
|
|
@ -17,7 +17,10 @@ void ACTService::handleSyncRequest(u32 messagePointer) {
|
||||||
case ACTCommands::GenerateUUID: generateUUID(messagePointer); break;
|
case ACTCommands::GenerateUUID: generateUUID(messagePointer); break;
|
||||||
case ACTCommands::GetAccountDataBlock: getAccountDataBlock(messagePointer); break;
|
case ACTCommands::GetAccountDataBlock: getAccountDataBlock(messagePointer); break;
|
||||||
case ACTCommands::Initialize: initialize(messagePointer); break;
|
case ACTCommands::Initialize: initialize(messagePointer); break;
|
||||||
default: Helpers::panic("ACT service requested. Command: %08X\n", command);
|
default:
|
||||||
|
Helpers::warn("Undocumented ACT service requested. Command: %08X", command);
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,15 @@ namespace APTCommands {
|
||||||
GetLockHandle = 0x00010040,
|
GetLockHandle = 0x00010040,
|
||||||
Initialize = 0x00020080,
|
Initialize = 0x00020080,
|
||||||
Enable = 0x00030040,
|
Enable = 0x00030040,
|
||||||
|
GetAppletInfo = 0x00060040,
|
||||||
|
IsRegistered = 0x00090040,
|
||||||
InquireNotification = 0x000B0040,
|
InquireNotification = 0x000B0040,
|
||||||
|
SendParameter = 0x000C0104,
|
||||||
ReceiveParameter = 0x000D0080,
|
ReceiveParameter = 0x000D0080,
|
||||||
GlanceParameter = 0x000E0080,
|
GlanceParameter = 0x000E0080,
|
||||||
PreloadLibraryApplet = 0x00160040,
|
PreloadLibraryApplet = 0x00160040,
|
||||||
|
PrepareToStartLibraryApplet = 0x00180040,
|
||||||
|
StartLibraryApplet = 0x001E0084,
|
||||||
ReplySleepQuery = 0x003E0080,
|
ReplySleepQuery = 0x003E0080,
|
||||||
NotifyToWait = 0x00430040,
|
NotifyToWait = 0x00430040,
|
||||||
GetSharedFont = 0x00440000,
|
GetSharedFont = 0x00440000,
|
||||||
|
@ -60,6 +65,8 @@ void APTService::reset() {
|
||||||
lockHandle = std::nullopt;
|
lockHandle = std::nullopt;
|
||||||
notificationEvent = std::nullopt;
|
notificationEvent = std::nullopt;
|
||||||
resumeEvent = std::nullopt;
|
resumeEvent = std::nullopt;
|
||||||
|
|
||||||
|
appletManager.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void APTService::handleSyncRequest(u32 messagePointer) {
|
void APTService::handleSyncRequest(u32 messagePointer) {
|
||||||
|
@ -69,18 +76,22 @@ void APTService::handleSyncRequest(u32 messagePointer) {
|
||||||
case APTCommands::CheckNew3DS: checkNew3DS(messagePointer); break;
|
case APTCommands::CheckNew3DS: checkNew3DS(messagePointer); break;
|
||||||
case APTCommands::CheckNew3DSApp: checkNew3DSApp(messagePointer); break;
|
case APTCommands::CheckNew3DSApp: checkNew3DSApp(messagePointer); break;
|
||||||
case APTCommands::Enable: enable(messagePointer); break;
|
case APTCommands::Enable: enable(messagePointer); break;
|
||||||
|
case APTCommands::GetAppletInfo: getAppletInfo(messagePointer); break;
|
||||||
case APTCommands::GetSharedFont: getSharedFont(messagePointer); break;
|
case APTCommands::GetSharedFont: getSharedFont(messagePointer); break;
|
||||||
case APTCommands::Initialize: initialize(messagePointer); break;
|
case APTCommands::Initialize: initialize(messagePointer); break;
|
||||||
case APTCommands::InquireNotification: [[likely]] inquireNotification(messagePointer); break;
|
case APTCommands::InquireNotification: [[likely]] inquireNotification(messagePointer); break;
|
||||||
|
case APTCommands::IsRegistered: isRegistered(messagePointer); break;
|
||||||
case APTCommands::GetApplicationCpuTimeLimit: getApplicationCpuTimeLimit(messagePointer); break;
|
case APTCommands::GetApplicationCpuTimeLimit: getApplicationCpuTimeLimit(messagePointer); break;
|
||||||
case APTCommands::GetLockHandle: getLockHandle(messagePointer); break;
|
case APTCommands::GetLockHandle: getLockHandle(messagePointer); break;
|
||||||
case APTCommands::GetWirelessRebootInfo: getWirelessRebootInfo(messagePointer); break;
|
case APTCommands::GetWirelessRebootInfo: getWirelessRebootInfo(messagePointer); break;
|
||||||
case APTCommands::GlanceParameter: glanceParameter(messagePointer); break;
|
case APTCommands::GlanceParameter: glanceParameter(messagePointer); break;
|
||||||
case APTCommands::NotifyToWait: notifyToWait(messagePointer); break;
|
case APTCommands::NotifyToWait: notifyToWait(messagePointer); break;
|
||||||
case APTCommands::PreloadLibraryApplet: preloadLibraryApplet(messagePointer); break;
|
case APTCommands::PreloadLibraryApplet: preloadLibraryApplet(messagePointer); break;
|
||||||
|
case APTCommands::PrepareToStartLibraryApplet: prepareToStartLibraryApplet(messagePointer); break;
|
||||||
case APTCommands::ReceiveParameter: [[likely]] receiveParameter(messagePointer); break;
|
case APTCommands::ReceiveParameter: [[likely]] receiveParameter(messagePointer); break;
|
||||||
case APTCommands::ReplySleepQuery: replySleepQuery(messagePointer); break;
|
case APTCommands::ReplySleepQuery: replySleepQuery(messagePointer); break;
|
||||||
case APTCommands::SetApplicationCpuTimeLimit: setApplicationCpuTimeLimit(messagePointer); break;
|
case APTCommands::SetApplicationCpuTimeLimit: setApplicationCpuTimeLimit(messagePointer); break;
|
||||||
|
case APTCommands::SendParameter: sendParameter(messagePointer); break;
|
||||||
case APTCommands::SetScreencapPostPermission: setScreencapPostPermission(messagePointer); break;
|
case APTCommands::SetScreencapPostPermission: setScreencapPostPermission(messagePointer); break;
|
||||||
case APTCommands::TheSmashBrosFunction: theSmashBrosFunction(messagePointer); break;
|
case APTCommands::TheSmashBrosFunction: theSmashBrosFunction(messagePointer); break;
|
||||||
default:
|
default:
|
||||||
|
@ -116,9 +127,38 @@ void APTService::appletUtility(u32 messagePointer) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void APTService::getAppletInfo(u32 messagePointer) {
|
||||||
|
const u32 appID = mem.read32(messagePointer + 4);
|
||||||
|
Helpers::warn("APT::GetAppletInfo (appID = %X)\n", appID);
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x06, 7, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
|
||||||
|
mem.write8(messagePointer + 20, 1); // 1 = registered
|
||||||
|
mem.write8(messagePointer + 24, 1); // 1 = loaded
|
||||||
|
// TODO: The rest of this
|
||||||
|
}
|
||||||
|
|
||||||
|
void APTService::isRegistered(u32 messagePointer) {
|
||||||
|
const u32 appID = mem.read32(messagePointer + 4);
|
||||||
|
Helpers::warn("APT::IsRegistered (appID = %X)", appID);
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x09, 2, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
mem.write8(messagePointer + 8, 1); // Return that the app is always registered. This might break with home menu?
|
||||||
|
}
|
||||||
|
|
||||||
void APTService::preloadLibraryApplet(u32 messagePointer) {
|
void APTService::preloadLibraryApplet(u32 messagePointer) {
|
||||||
const u32 appID = mem.read32(messagePointer + 4);
|
const u32 appID = mem.read32(messagePointer + 4);
|
||||||
log("APT::PreloadLibraryApplet (app ID = %d) (stubbed)\n", appID);
|
log("APT::PreloadLibraryApplet (app ID = %X) (stubbed)\n", appID);
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x16, 1, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
void APTService::prepareToStartLibraryApplet(u32 messagePointer) {
|
||||||
|
const u32 appID = mem.read32(messagePointer + 4);
|
||||||
|
log("APT::PrepareToStartLibraryApplet (app ID = %X) (stubbed)\n", appID);
|
||||||
|
|
||||||
mem.write32(messagePointer, IPC::responseHeader(0x16, 1, 0));
|
mem.write32(messagePointer, IPC::responseHeader(0x16, 1, 0));
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
@ -193,6 +233,35 @@ void APTService::notifyToWait(u32 messagePointer) {
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void APTService::sendParameter(u32 messagePointer) {
|
||||||
|
const u32 sourceAppID = mem.read32(messagePointer + 4);
|
||||||
|
const u32 destAppID = mem.read32(messagePointer + 8);
|
||||||
|
const u32 cmd = mem.read32(messagePointer + 12);
|
||||||
|
const u32 paramSize = mem.read32(messagePointer + 16);
|
||||||
|
|
||||||
|
const u32 parameterHandle = mem.read32(messagePointer + 24); // What dis?
|
||||||
|
const u32 parameterPointer = mem.read32(messagePointer + 32);
|
||||||
|
log("APT::SendParameter (source app = %X, dest app = %X, cmd = %X, size = %X) (Stubbed)", sourceAppID, destAppID, cmd, paramSize);
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x0C, 1, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
|
||||||
|
if (sourceAppID != Applets::AppletIDs::Application) {
|
||||||
|
Helpers::warn("APT::SendParameter: Unimplemented source applet ID");
|
||||||
|
}
|
||||||
|
|
||||||
|
Applets::AppletBase* destApplet = appletManager.getApplet(destAppID);
|
||||||
|
if (destApplet == nullptr) {
|
||||||
|
Helpers::warn("APT::SendParameter: Unimplemented dest applet ID");
|
||||||
|
} else {
|
||||||
|
auto result = destApplet->receiveParameter();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resumeEvent.has_value()) {
|
||||||
|
kernel.signalEvent(resumeEvent.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void APTService::receiveParameter(u32 messagePointer) {
|
void APTService::receiveParameter(u32 messagePointer) {
|
||||||
const u32 app = mem.read32(messagePointer + 4);
|
const u32 app = mem.read32(messagePointer + 4);
|
||||||
const u32 size = mem.read32(messagePointer + 8);
|
const u32 size = mem.read32(messagePointer + 8);
|
||||||
|
|
|
@ -18,6 +18,9 @@ namespace FRDCommands {
|
||||||
GetMyScreenName = 0x00090000,
|
GetMyScreenName = 0x00090000,
|
||||||
GetMyMii = 0x000A0000,
|
GetMyMii = 0x000A0000,
|
||||||
GetFriendKeyList = 0x00110080,
|
GetFriendKeyList = 0x00110080,
|
||||||
|
GetFriendPresence = 0x00120042,
|
||||||
|
GetFriendProfile = 0x00150042,
|
||||||
|
GetFriendAttributeFlags = 0x00170042,
|
||||||
UpdateGameModeDescription = 0x001D0002,
|
UpdateGameModeDescription = 0x001D0002,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -28,7 +31,10 @@ void FRDService::handleSyncRequest(u32 messagePointer) {
|
||||||
const u32 command = mem.read32(messagePointer);
|
const u32 command = mem.read32(messagePointer);
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case FRDCommands::AttachToEventNotification: attachToEventNotification(messagePointer); break;
|
case FRDCommands::AttachToEventNotification: attachToEventNotification(messagePointer); break;
|
||||||
|
case FRDCommands::GetFriendAttributeFlags: getFriendAttributeFlags(messagePointer); break;
|
||||||
case FRDCommands::GetFriendKeyList: getFriendKeyList(messagePointer); break;
|
case FRDCommands::GetFriendKeyList: getFriendKeyList(messagePointer); break;
|
||||||
|
case FRDCommands::GetFriendPresence: getFriendPresence(messagePointer); break;
|
||||||
|
case FRDCommands::GetFriendProfile: getFriendProfile(messagePointer); break;
|
||||||
case FRDCommands::GetMyFriendKey: getMyFriendKey(messagePointer); break;
|
case FRDCommands::GetMyFriendKey: getMyFriendKey(messagePointer); break;
|
||||||
case FRDCommands::GetMyMii: getMyMii(messagePointer); break;
|
case FRDCommands::GetMyMii: getMyMii(messagePointer); break;
|
||||||
case FRDCommands::GetMyPresence: getMyPresence(messagePointer); break;
|
case FRDCommands::GetMyPresence: getMyPresence(messagePointer); break;
|
||||||
|
@ -83,6 +89,41 @@ void FRDService::getFriendKeyList(u32 messagePointer) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FRDService::getFriendProfile(u32 messagePointer) {
|
||||||
|
log("FRD::GetFriendProfile\n");
|
||||||
|
|
||||||
|
const u32 count = mem.read32(messagePointer + 4);
|
||||||
|
const u32 friendKeyList = mem.read32(messagePointer + 12); // Pointer to list of friend keys
|
||||||
|
const u32 profile = mem.read32(messagePointer + 0x104); // Pointer to friend profile where we'll write info to
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x15, 1, 2));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
|
||||||
|
// Clear all profiles
|
||||||
|
for (u32 i = 0; i < count; i++) {
|
||||||
|
const u32 pointer = profile + (i * sizeof(Profile));
|
||||||
|
for (u32 j = 0; j < sizeof(Profile); j++) {
|
||||||
|
mem.write8(pointer + j, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FRDService::getFriendAttributeFlags(u32 messagePointer) {
|
||||||
|
log("FRD::GetFriendAttributeFlags\n");
|
||||||
|
|
||||||
|
const u32 count = mem.read32(messagePointer + 4);
|
||||||
|
const u32 friendKeyList = mem.read32(messagePointer + 12); // Pointer to list of friend keys
|
||||||
|
const u32 profile = mem.read32(messagePointer + 0x104); // Pointer to friend profile where we'll write info to
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x17, 1, 2));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
|
||||||
|
// Clear flags
|
||||||
|
for (u32 i = 0; i < count; i++) {
|
||||||
|
mem.write8(profile + i, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void FRDService::getMyPresence(u32 messagePointer) {
|
void FRDService::getMyPresence(u32 messagePointer) {
|
||||||
static constexpr u32 presenceSize = 0x12C; // A presence seems to be 12C bytes of data, not sure what it contains
|
static constexpr u32 presenceSize = 0x12C; // A presence seems to be 12C bytes of data, not sure what it contains
|
||||||
log("FRD::GetMyPresence\n");
|
log("FRD::GetMyPresence\n");
|
||||||
|
@ -96,6 +137,14 @@ void FRDService::getMyPresence(u32 messagePointer) {
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FRDService::getFriendPresence(u32 messagePointer) {
|
||||||
|
Helpers::warn("FRD::GetFriendPresence (stubbed)");
|
||||||
|
|
||||||
|
// TODO: Implement and document this,
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x12, 1, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
}
|
||||||
|
|
||||||
void FRDService::getMyProfile(u32 messagePointer) {
|
void FRDService::getMyProfile(u32 messagePointer) {
|
||||||
mem.write32(messagePointer, IPC::responseHeader(0x7, 3, 0)); // Not sure if the header here has the correct # of responses?
|
mem.write32(messagePointer, IPC::responseHeader(0x7, 3, 0)); // Not sure if the header here has the correct # of responses?
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
|
|
@ -26,13 +26,19 @@ namespace FSCommands {
|
||||||
GetFreeBytes = 0x08120080,
|
GetFreeBytes = 0x08120080,
|
||||||
IsSdmcDetected = 0x08170000,
|
IsSdmcDetected = 0x08170000,
|
||||||
IsSdmcWritable = 0x08180000,
|
IsSdmcWritable = 0x08180000,
|
||||||
|
AbnegateAccessRight = 0x08400040,
|
||||||
GetFormatInfo = 0x084500C2,
|
GetFormatInfo = 0x084500C2,
|
||||||
|
GetArchiveResource = 0x08490040,
|
||||||
FormatSaveData = 0x084C0242,
|
FormatSaveData = 0x084C0242,
|
||||||
CreateExtSaveData = 0x08510242,
|
CreateExtSaveData = 0x08510242,
|
||||||
DeleteExtSaveData = 0x08520100,
|
DeleteExtSaveData = 0x08520100,
|
||||||
|
SetArchivePriority = 0x085A00C0,
|
||||||
InitializeWithSdkVersion = 0x08610042,
|
InitializeWithSdkVersion = 0x08610042,
|
||||||
SetPriority = 0x08620040,
|
SetPriority = 0x08620040,
|
||||||
GetPriority = 0x08630000
|
GetPriority = 0x08630000,
|
||||||
|
SetThisSaveDataSecureValue = 0x086E00C0,
|
||||||
|
GetThisSaveDataSecureValue = 0x086F0040,
|
||||||
|
TheGameboyVCFunction = 0x08750180,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +77,8 @@ ArchiveBase* FSService::getArchiveFromID(u32 id, const FSPath& archivePath) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case ArchiveID::SelfNCCH: return &selfNcch;
|
case ArchiveID::SelfNCCH: return &selfNcch;
|
||||||
case ArchiveID::SaveData: return &saveData;
|
case ArchiveID::SaveData: return &saveData;
|
||||||
|
case ArchiveID::UserSaveData2: return &userSaveData2;
|
||||||
|
|
||||||
case ArchiveID::ExtSaveData:
|
case ArchiveID::ExtSaveData:
|
||||||
return &extSaveData_sdmc;
|
return &extSaveData_sdmc;
|
||||||
|
|
||||||
|
@ -155,9 +163,11 @@ void FSService::handleSyncRequest(u32 messagePointer) {
|
||||||
case FSCommands::DeleteFile: deleteFile(messagePointer); break;
|
case FSCommands::DeleteFile: deleteFile(messagePointer); break;
|
||||||
case FSCommands::FormatSaveData: formatSaveData(messagePointer); break;
|
case FSCommands::FormatSaveData: formatSaveData(messagePointer); break;
|
||||||
case FSCommands::FormatThisUserSaveData: formatThisUserSaveData(messagePointer); break;
|
case FSCommands::FormatThisUserSaveData: formatThisUserSaveData(messagePointer); break;
|
||||||
|
case FSCommands::GetArchiveResource: getArchiveResource(messagePointer); break;
|
||||||
case FSCommands::GetFreeBytes: getFreeBytes(messagePointer); break;
|
case FSCommands::GetFreeBytes: getFreeBytes(messagePointer); break;
|
||||||
case FSCommands::GetFormatInfo: getFormatInfo(messagePointer); break;
|
case FSCommands::GetFormatInfo: getFormatInfo(messagePointer); break;
|
||||||
case FSCommands::GetPriority: getPriority(messagePointer); break;
|
case FSCommands::GetPriority: getPriority(messagePointer); break;
|
||||||
|
case FSCommands::GetThisSaveDataSecureValue: getThisSaveDataSecureValue(messagePointer); break;
|
||||||
case FSCommands::Initialize: initialize(messagePointer); break;
|
case FSCommands::Initialize: initialize(messagePointer); break;
|
||||||
case FSCommands::InitializeWithSdkVersion: initializeWithSdkVersion(messagePointer); break;
|
case FSCommands::InitializeWithSdkVersion: initializeWithSdkVersion(messagePointer); break;
|
||||||
case FSCommands::IsSdmcDetected: isSdmcDetected(messagePointer); break;
|
case FSCommands::IsSdmcDetected: isSdmcDetected(messagePointer); break;
|
||||||
|
@ -166,7 +176,11 @@ void FSService::handleSyncRequest(u32 messagePointer) {
|
||||||
case FSCommands::OpenDirectory: openDirectory(messagePointer); break;
|
case FSCommands::OpenDirectory: openDirectory(messagePointer); break;
|
||||||
case FSCommands::OpenFile: [[likely]] openFile(messagePointer); break;
|
case FSCommands::OpenFile: [[likely]] openFile(messagePointer); break;
|
||||||
case FSCommands::OpenFileDirectly: [[likely]] openFileDirectly(messagePointer); break;
|
case FSCommands::OpenFileDirectly: [[likely]] openFileDirectly(messagePointer); break;
|
||||||
|
case FSCommands::SetArchivePriority: setArchivePriority(messagePointer); break;
|
||||||
case FSCommands::SetPriority: setPriority(messagePointer); break;
|
case FSCommands::SetPriority: setPriority(messagePointer); break;
|
||||||
|
case FSCommands::SetThisSaveDataSecureValue: setThisSaveDataSecureValue(messagePointer); break;
|
||||||
|
case FSCommands::AbnegateAccessRight: abnegateAccessRight(messagePointer); break;
|
||||||
|
case FSCommands::TheGameboyVCFunction: theGameboyVCFunction(messagePointer); break;
|
||||||
default: Helpers::panic("FS service requested. Command: %08X\n", command);
|
default: Helpers::panic("FS service requested. Command: %08X\n", command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -575,6 +589,36 @@ void FSService::getPriority(u32 messagePointer) {
|
||||||
mem.write32(messagePointer + 8, priority);
|
mem.write32(messagePointer + 8, priority);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FSService::getArchiveResource(u32 messagePointer) {
|
||||||
|
const u32 mediaType = mem.read32(messagePointer + 4);
|
||||||
|
log("FS::GetArchiveResource (media type = %d) (stubbed)\n");
|
||||||
|
|
||||||
|
// For the time being, return the same stubbed archive resource for every media type
|
||||||
|
static constexpr ArchiveResource resource = {
|
||||||
|
.sectorSize = 512,
|
||||||
|
.clusterSize = 16_KB,
|
||||||
|
.partitionCapacityInClusters = 0x80000, // 0x80000 * 16 KB = 8GB
|
||||||
|
.freeSpaceInClusters = 0x80000, // Same here
|
||||||
|
};
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x849, 5, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
|
||||||
|
mem.write32(messagePointer + 8, resource.sectorSize);
|
||||||
|
mem.write32(messagePointer + 12, resource.clusterSize);
|
||||||
|
mem.write32(messagePointer + 16, resource.partitionCapacityInClusters);
|
||||||
|
mem.write32(messagePointer + 20, resource.freeSpaceInClusters);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FSService::setArchivePriority(u32 messagePointer) {
|
||||||
|
Handle archive = mem.read64(messagePointer + 4);
|
||||||
|
const u32 value = mem.read32(messagePointer + 12);
|
||||||
|
log("FS::SetArchivePriority (priority = %d, archive handle = %X)\n", value, handle);
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x85A, 1, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
}
|
||||||
|
|
||||||
void FSService::setPriority(u32 messagePointer) {
|
void FSService::setPriority(u32 messagePointer) {
|
||||||
const u32 value = mem.read32(messagePointer + 4);
|
const u32 value = mem.read32(messagePointer + 4);
|
||||||
log("FS::SetPriority (priority = %d)\n", value);
|
log("FS::SetPriority (priority = %d)\n", value);
|
||||||
|
@ -584,6 +628,45 @@ void FSService::setPriority(u32 messagePointer) {
|
||||||
priority = value;
|
priority = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FSService::abnegateAccessRight(u32 messagePointer) {
|
||||||
|
const u32 right = mem.read32(messagePointer + 4);
|
||||||
|
log("FS::AbnegateAccessRight (right = %d)\n", right);
|
||||||
|
|
||||||
|
if (right >= 0x38) {
|
||||||
|
Helpers::warn("FS::AbnegateAccessRight: Invalid access right");
|
||||||
|
}
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x840, 1, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FSService::getThisSaveDataSecureValue(u32 messagePointer) {
|
||||||
|
Helpers::warn("Unimplemented FS::GetThisSaveDataSecureValue");
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x86F, 1, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FSService::setThisSaveDataSecureValue(u32 messagePointer) {
|
||||||
|
const u64 value = mem.read32(messagePointer + 4);
|
||||||
|
const u32 slot = mem.read32(messagePointer + 12);
|
||||||
|
const u32 id = mem.read32(messagePointer + 16);
|
||||||
|
const u8 variation = mem.read8(messagePointer + 20);
|
||||||
|
|
||||||
|
// TODO: Actually do something with this.
|
||||||
|
Helpers::warn("Unimplemented FS::SetThisSaveDataSecureValue");
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x86E, 1, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FSService::theGameboyVCFunction(u32 messagePointer) {
|
||||||
|
Helpers::warn("Unimplemented FS: function: 0x08750180");
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x875, 1, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
}
|
||||||
|
|
||||||
void FSService::isSdmcDetected(u32 messagePointer) {
|
void FSService::isSdmcDetected(u32 messagePointer) {
|
||||||
log("FS::IsSdmcDetected\n");
|
log("FS::IsSdmcDetected\n");
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,8 @@ namespace ServiceCommands {
|
||||||
FlushDataCache = 0x00080082,
|
FlushDataCache = 0x00080082,
|
||||||
SetLCDForceBlack = 0x000B0040,
|
SetLCDForceBlack = 0x000B0040,
|
||||||
TriggerCmdReqQueue = 0x000C0000,
|
TriggerCmdReqQueue = 0x000C0000,
|
||||||
|
ImportDisplayCaptureInfo = 0x00180000,
|
||||||
|
SaveVramSysArea = 0x00190000,
|
||||||
SetInternalPriorities = 0x001E0080,
|
SetInternalPriorities = 0x001E0080,
|
||||||
StoreDataCache = 0x001F0082
|
StoreDataCache = 0x001F0082
|
||||||
};
|
};
|
||||||
|
@ -42,15 +44,17 @@ void GPUService::reset() {
|
||||||
void GPUService::handleSyncRequest(u32 messagePointer) {
|
void GPUService::handleSyncRequest(u32 messagePointer) {
|
||||||
const u32 command = mem.read32(messagePointer);
|
const u32 command = mem.read32(messagePointer);
|
||||||
switch (command) {
|
switch (command) {
|
||||||
|
case ServiceCommands::TriggerCmdReqQueue: [[likely]] triggerCmdReqQueue(messagePointer); break;
|
||||||
case ServiceCommands::AcquireRight: acquireRight(messagePointer); break;
|
case ServiceCommands::AcquireRight: acquireRight(messagePointer); break;
|
||||||
case ServiceCommands::FlushDataCache: flushDataCache(messagePointer); break;
|
case ServiceCommands::FlushDataCache: flushDataCache(messagePointer); break;
|
||||||
|
case ServiceCommands::ImportDisplayCaptureInfo: importDisplayCaptureInfo(messagePointer); break;
|
||||||
case ServiceCommands::RegisterInterruptRelayQueue: registerInterruptRelayQueue(messagePointer); break;
|
case ServiceCommands::RegisterInterruptRelayQueue: registerInterruptRelayQueue(messagePointer); break;
|
||||||
|
case ServiceCommands::SaveVramSysArea: saveVramSysArea(messagePointer); break;
|
||||||
case ServiceCommands::SetAxiConfigQoSMode: setAxiConfigQoSMode(messagePointer); break;
|
case ServiceCommands::SetAxiConfigQoSMode: setAxiConfigQoSMode(messagePointer); break;
|
||||||
case ServiceCommands::SetBufferSwap: setBufferSwap(messagePointer); break;
|
case ServiceCommands::SetBufferSwap: setBufferSwap(messagePointer); break;
|
||||||
case ServiceCommands::SetInternalPriorities: setInternalPriorities(messagePointer); break;
|
case ServiceCommands::SetInternalPriorities: setInternalPriorities(messagePointer); break;
|
||||||
case ServiceCommands::SetLCDForceBlack: setLCDForceBlack(messagePointer); break;
|
case ServiceCommands::SetLCDForceBlack: setLCDForceBlack(messagePointer); break;
|
||||||
case ServiceCommands::StoreDataCache: storeDataCache(messagePointer); break;
|
case ServiceCommands::StoreDataCache: storeDataCache(messagePointer); break;
|
||||||
case ServiceCommands::TriggerCmdReqQueue: [[likely]] triggerCmdReqQueue(messagePointer); break;
|
|
||||||
case ServiceCommands::WriteHwRegs: writeHwRegs(messagePointer); break;
|
case ServiceCommands::WriteHwRegs: writeHwRegs(messagePointer); break;
|
||||||
case ServiceCommands::WriteHwRegsWithMask: writeHwRegsWithMask(messagePointer); break;
|
case ServiceCommands::WriteHwRegsWithMask: writeHwRegsWithMask(messagePointer); break;
|
||||||
default: Helpers::panic("GPU service requested. Command: %08X\n", command);
|
default: Helpers::panic("GPU service requested. Command: %08X\n", command);
|
||||||
|
@ -456,3 +460,20 @@ void GPUService::triggerTextureCopy(u32* cmd) {
|
||||||
// NSMB2 relies on this
|
// NSMB2 relies on this
|
||||||
requestInterrupt(GPUInterrupt::PPF);
|
requestInterrupt(GPUInterrupt::PPF);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used when transitioning from the app to an OS applet, such as software keyboard, mii maker, mii selector, etc
|
||||||
|
// Stubbed until we decide to support LLE applets
|
||||||
|
void GPUService::saveVramSysArea(u32 messagePointer) {
|
||||||
|
Helpers::warn("GSP::GPU::SaveVramSysArea (stubbed)");
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x19, 1, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used in similar fashion to the SaveVramSysArea function
|
||||||
|
void GPUService::importDisplayCaptureInfo(u32 messagePointer) {
|
||||||
|
Helpers::warn("GSP::GPU::ImportDisplayCaptureInfo (stubbed)");
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x18, 9, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
}
|
|
@ -1,14 +1,19 @@
|
||||||
#include "services/mic.hpp"
|
#include "services/mic.hpp"
|
||||||
#include "ipc.hpp"
|
#include "ipc.hpp"
|
||||||
|
#include "kernel/kernel.hpp"
|
||||||
|
|
||||||
namespace MICCommands {
|
namespace MICCommands {
|
||||||
enum : u32 {
|
enum : u32 {
|
||||||
MapSharedMem = 0x00010042,
|
MapSharedMem = 0x00010042,
|
||||||
|
UnmapSharedMem = 0x00020000,
|
||||||
StartSampling = 0x00030140,
|
StartSampling = 0x00030140,
|
||||||
StopSampling = 0x00050000,
|
StopSampling = 0x00050000,
|
||||||
|
IsSampling = 0x00060000,
|
||||||
|
GetEventHandle = 0x00070000,
|
||||||
SetGain = 0x00080040,
|
SetGain = 0x00080040,
|
||||||
GetGain = 0x00090000,
|
GetGain = 0x00090000,
|
||||||
SetPower = 0x000A0040,
|
SetPower = 0x000A0040,
|
||||||
|
GetPower = 0x000B0000,
|
||||||
SetIirFilter = 0x000C0042,
|
SetIirFilter = 0x000C0042,
|
||||||
SetClamp = 0x000D0040,
|
SetClamp = 0x000D0040,
|
||||||
CaptainToadFunction = 0x00100040,
|
CaptainToadFunction = 0x00100040,
|
||||||
|
@ -18,14 +23,19 @@ namespace MICCommands {
|
||||||
void MICService::reset() {
|
void MICService::reset() {
|
||||||
micEnabled = false;
|
micEnabled = false;
|
||||||
shouldClamp = false;
|
shouldClamp = false;
|
||||||
isSampling = false;
|
currentlySampling = false;
|
||||||
gain = 0;
|
gain = 0;
|
||||||
|
|
||||||
|
eventHandle = std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MICService::handleSyncRequest(u32 messagePointer) {
|
void MICService::handleSyncRequest(u32 messagePointer) {
|
||||||
const u32 command = mem.read32(messagePointer);
|
const u32 command = mem.read32(messagePointer);
|
||||||
switch (command) {
|
switch (command) {
|
||||||
|
case MICCommands::GetEventHandle: getEventHandle(messagePointer); break;
|
||||||
case MICCommands::GetGain: getGain(messagePointer); break;
|
case MICCommands::GetGain: getGain(messagePointer); break;
|
||||||
|
case MICCommands::GetPower: getPower(messagePointer); break;
|
||||||
|
case MICCommands::IsSampling: isSampling(messagePointer); break;
|
||||||
case MICCommands::MapSharedMem: mapSharedMem(messagePointer); break;
|
case MICCommands::MapSharedMem: mapSharedMem(messagePointer); break;
|
||||||
case MICCommands::SetClamp: setClamp(messagePointer); break;
|
case MICCommands::SetClamp: setClamp(messagePointer); break;
|
||||||
case MICCommands::SetGain: setGain(messagePointer); break;
|
case MICCommands::SetGain: setGain(messagePointer); break;
|
||||||
|
@ -33,6 +43,7 @@ void MICService::handleSyncRequest(u32 messagePointer) {
|
||||||
case MICCommands::SetPower: setPower(messagePointer); break;
|
case MICCommands::SetPower: setPower(messagePointer); break;
|
||||||
case MICCommands::StartSampling: startSampling(messagePointer); break;
|
case MICCommands::StartSampling: startSampling(messagePointer); break;
|
||||||
case MICCommands::StopSampling: stopSampling(messagePointer); break;
|
case MICCommands::StopSampling: stopSampling(messagePointer); break;
|
||||||
|
case MICCommands::UnmapSharedMem: unmapSharedMem(messagePointer); break;
|
||||||
case MICCommands::CaptainToadFunction: theCaptainToadFunction(messagePointer); break;
|
case MICCommands::CaptainToadFunction: theCaptainToadFunction(messagePointer); break;
|
||||||
default: Helpers::panic("MIC service requested. Command: %08X\n", command);
|
default: Helpers::panic("MIC service requested. Command: %08X\n", command);
|
||||||
}
|
}
|
||||||
|
@ -47,6 +58,27 @@ void MICService::mapSharedMem(u32 messagePointer) {
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MICService::unmapSharedMem(u32 messagePointer) {
|
||||||
|
log("MIC::UnmapSharedMem (stubbed)\n");
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x2, 1, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MICService::getEventHandle(u32 messagePointer) {
|
||||||
|
log("MIC::GetEventHandle\n");
|
||||||
|
Helpers::warn("Acquire MIC event handle");
|
||||||
|
|
||||||
|
if (!eventHandle.has_value()) {
|
||||||
|
eventHandle = kernel.makeEvent(ResetType::OneShot);
|
||||||
|
}
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x7, 1, 2));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
// TODO: Translation descriptor
|
||||||
|
mem.write32(messagePointer + 12, eventHandle.value());
|
||||||
|
}
|
||||||
|
|
||||||
void MICService::getGain(u32 messagePointer) {
|
void MICService::getGain(u32 messagePointer) {
|
||||||
log("MIC::GetGain\n");
|
log("MIC::GetGain\n");
|
||||||
mem.write32(messagePointer, IPC::responseHeader(0x9, 2, 0));
|
mem.write32(messagePointer, IPC::responseHeader(0x9, 2, 0));
|
||||||
|
@ -71,6 +103,14 @@ void MICService::setPower(u32 messagePointer) {
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MICService::getPower(u32 messagePointer) {
|
||||||
|
log("MIC::GetPower\n");
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0xB, 2, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
mem.write8(messagePointer + 8, micEnabled ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
void MICService::setClamp(u32 messagePointer) {
|
void MICService::setClamp(u32 messagePointer) {
|
||||||
u8 val = mem.read8(messagePointer + 4);
|
u8 val = mem.read8(messagePointer + 4);
|
||||||
log("MIC::SetClamp (value = %d)\n", val);
|
log("MIC::SetClamp (value = %d)\n", val);
|
||||||
|
@ -91,19 +131,27 @@ void MICService::startSampling(u32 messagePointer) {
|
||||||
encoding, sampleRate, offset, dataSize, loop ? "yes" : "no"
|
encoding, sampleRate, offset, dataSize, loop ? "yes" : "no"
|
||||||
);
|
);
|
||||||
|
|
||||||
isSampling = true;
|
currentlySampling = true;
|
||||||
mem.write32(messagePointer, IPC::responseHeader(0x3, 1, 0));
|
mem.write32(messagePointer, IPC::responseHeader(0x3, 1, 0));
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MICService::stopSampling(u32 messagePointer) {
|
void MICService::stopSampling(u32 messagePointer) {
|
||||||
log("MIC::StopSampling\n");
|
log("MIC::StopSampling\n");
|
||||||
isSampling = false;
|
currentlySampling = false;
|
||||||
|
|
||||||
mem.write32(messagePointer, IPC::responseHeader(0x5, 1, 0));
|
mem.write32(messagePointer, IPC::responseHeader(0x5, 1, 0));
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MICService::isSampling(u32 messagePointer) {
|
||||||
|
log("MIC::IsSampling");
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x6, 2, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
mem.write8(messagePointer + 8, currentlySampling ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
void MICService::setIirFilter(u32 messagePointer) {
|
void MICService::setIirFilter(u32 messagePointer) {
|
||||||
const u32 size = mem.read32(messagePointer + 4);
|
const u32 size = mem.read32(messagePointer + 4);
|
||||||
const u32 pointer = mem.read32(messagePointer + 12);
|
const u32 pointer = mem.read32(messagePointer + 12);
|
||||||
|
|
|
@ -5,8 +5,10 @@
|
||||||
namespace NFCCommands {
|
namespace NFCCommands {
|
||||||
enum : u32 {
|
enum : u32 {
|
||||||
Initialize = 0x00010040,
|
Initialize = 0x00010040,
|
||||||
|
Shutdown = 0x00020040,
|
||||||
StartCommunication = 0x00030000,
|
StartCommunication = 0x00030000,
|
||||||
StopCommunication = 0x00040000,
|
StopCommunication = 0x00040000,
|
||||||
|
StartTagScanning = 0x00050040,
|
||||||
GetTagInRangeEvent = 0x000B0000,
|
GetTagInRangeEvent = 0x000B0000,
|
||||||
GetTagOutOfRangeEvent = 0x000C0000,
|
GetTagOutOfRangeEvent = 0x000C0000,
|
||||||
GetTagState = 0x000D0000,
|
GetTagState = 0x000D0000,
|
||||||
|
@ -32,7 +34,9 @@ void NFCService::handleSyncRequest(u32 messagePointer) {
|
||||||
case NFCCommands::GetTagInRangeEvent: getTagInRangeEvent(messagePointer); break;
|
case NFCCommands::GetTagInRangeEvent: getTagInRangeEvent(messagePointer); break;
|
||||||
case NFCCommands::GetTagOutOfRangeEvent: getTagOutOfRangeEvent(messagePointer); break;
|
case NFCCommands::GetTagOutOfRangeEvent: getTagOutOfRangeEvent(messagePointer); break;
|
||||||
case NFCCommands::GetTagState: getTagState(messagePointer); break;
|
case NFCCommands::GetTagState: getTagState(messagePointer); break;
|
||||||
|
case NFCCommands::Shutdown: shutdown(messagePointer); break;
|
||||||
case NFCCommands::StartCommunication: startCommunication(messagePointer); break;
|
case NFCCommands::StartCommunication: startCommunication(messagePointer); break;
|
||||||
|
case NFCCommands::StartTagScanning: startTagScanning(messagePointer); break;
|
||||||
case NFCCommands::StopCommunication: stopCommunication(messagePointer); break;
|
case NFCCommands::StopCommunication: stopCommunication(messagePointer); break;
|
||||||
default: Helpers::panic("NFC service requested. Command: %08X\n", command);
|
default: Helpers::panic("NFC service requested. Command: %08X\n", command);
|
||||||
}
|
}
|
||||||
|
@ -50,6 +54,16 @@ void NFCService::initialize(u32 messagePointer) {
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NFCService::shutdown(u32 messagePointer) {
|
||||||
|
log("MFC::Shutdown");
|
||||||
|
const u8 mode = mem.read8(messagePointer + 4);
|
||||||
|
|
||||||
|
Helpers::warn("NFC::Shutdown: Unimplemented mode: %d", mode);
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x2, 1, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The NFC service provides userland with 2 events. One that is signaled when an NFC tag gets in range,
|
The NFC service provides userland with 2 events. One that is signaled when an NFC tag gets in range,
|
||||||
And one that is signaled when it gets out of range. Userland can have a thread sleep on this so it will be alerted
|
And one that is signaled when it gets out of range. Userland can have a thread sleep on this so it will be alerted
|
||||||
|
@ -114,6 +128,14 @@ void NFCService::startCommunication(u32 messagePointer) {
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NFCService::startTagScanning(u32 messagePointer) {
|
||||||
|
log("NFC::StartTagScanning\n");
|
||||||
|
tagStatus = TagStatus::Scanning;
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x5, 1, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
}
|
||||||
|
|
||||||
void NFCService::stopCommunication(u32 messagePointer) {
|
void NFCService::stopCommunication(u32 messagePointer) {
|
||||||
log("NFC::StopCommunication\n");
|
log("NFC::StopCommunication\n");
|
||||||
adapterStatus = Old3DSAdapterStatus::InitializationComplete;
|
adapterStatus = Old3DSAdapterStatus::InitializationComplete;
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
ServiceManager::ServiceManager(std::span<u32, 16> regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel, const EmulatorConfig& config)
|
ServiceManager::ServiceManager(std::span<u32, 16> regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel, const EmulatorConfig& config)
|
||||||
: regs(regs), mem(mem), kernel(kernel), ac(mem), am(mem), boss(mem), act(mem), apt(mem, kernel), cam(mem, kernel), cecd(mem, kernel), cfg(mem),
|
: regs(regs), mem(mem), kernel(kernel), ac(mem), am(mem), boss(mem), act(mem), apt(mem, kernel), cam(mem, kernel), cecd(mem, kernel), cfg(mem),
|
||||||
dlp_srvr(mem), dsp(mem, kernel), hid(mem, kernel), http(mem), ir_user(mem, kernel), frd(mem), fs(mem, kernel, config),
|
dlp_srvr(mem), dsp(mem, kernel), hid(mem, kernel), http(mem), ir_user(mem, kernel), frd(mem), fs(mem, kernel, config),
|
||||||
gsp_gpu(mem, gpu, kernel, currentPID), gsp_lcd(mem), ldr(mem), mcu_hwc(mem, config), mic(mem), nfc(mem, kernel), nim(mem), ndm(mem),
|
gsp_gpu(mem, gpu, kernel, currentPID), gsp_lcd(mem), ldr(mem), mcu_hwc(mem, config), mic(mem, kernel), nfc(mem, kernel), nim(mem), ndm(mem),
|
||||||
news_u(mem), ptm(mem, config), soc(mem), ssl(mem), y2r(mem, kernel) {}
|
news_u(mem), ptm(mem, config), soc(mem), ssl(mem), y2r(mem, kernel) {}
|
||||||
|
|
||||||
static constexpr int MAX_NOTIFICATION_COUNT = 16;
|
static constexpr int MAX_NOTIFICATION_COUNT = 16;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "services/y2r.hpp"
|
#include "services/y2r.hpp"
|
||||||
|
|
||||||
#include "ipc.hpp"
|
#include "ipc.hpp"
|
||||||
#include "kernel.hpp"
|
#include "kernel.hpp"
|
||||||
|
|
||||||
|
@ -6,8 +7,10 @@ namespace Y2RCommands {
|
||||||
enum : u32 {
|
enum : u32 {
|
||||||
SetInputFormat = 0x00010040,
|
SetInputFormat = 0x00010040,
|
||||||
SetOutputFormat = 0x00030040,
|
SetOutputFormat = 0x00030040,
|
||||||
|
GetOutputFormat = 0x00040000,
|
||||||
SetRotation = 0x00050040,
|
SetRotation = 0x00050040,
|
||||||
SetBlockAlignment = 0x00070040,
|
SetBlockAlignment = 0x00070040,
|
||||||
|
GetBlockAlignment = 0x00080000,
|
||||||
SetSpacialDithering = 0x00090040,
|
SetSpacialDithering = 0x00090040,
|
||||||
SetTemporalDithering = 0x000B0040,
|
SetTemporalDithering = 0x000B0040,
|
||||||
SetTransferEndInterrupt = 0x000D0040,
|
SetTransferEndInterrupt = 0x000D0040,
|
||||||
|
@ -17,7 +20,9 @@ namespace Y2RCommands {
|
||||||
SetSendingV = 0x00120102,
|
SetSendingV = 0x00120102,
|
||||||
SetReceiving = 0x00180102,
|
SetReceiving = 0x00180102,
|
||||||
SetInputLineWidth = 0x001A0040,
|
SetInputLineWidth = 0x001A0040,
|
||||||
|
GetInputLineWidth = 0x001B0000,
|
||||||
SetInputLines = 0x001C0040,
|
SetInputLines = 0x001C0040,
|
||||||
|
GetInputLines = 0x001D0000,
|
||||||
SetStandardCoeff = 0x00200040,
|
SetStandardCoeff = 0x00200040,
|
||||||
SetAlpha = 0x00220040,
|
SetAlpha = 0x00220040,
|
||||||
StartConversion = 0x00260000,
|
StartConversion = 0x00260000,
|
||||||
|
@ -26,7 +31,7 @@ namespace Y2RCommands {
|
||||||
SetPackageParameter = 0x002901C0,
|
SetPackageParameter = 0x002901C0,
|
||||||
PingProcess = 0x002A0000,
|
PingProcess = 0x002A0000,
|
||||||
DriverInitialize = 0x002B0000,
|
DriverInitialize = 0x002B0000,
|
||||||
DriverFinalize = 0x002C0000
|
DriverFinalize = 0x002C0000,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +57,10 @@ void Y2RService::handleSyncRequest(u32 messagePointer) {
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case Y2RCommands::DriverInitialize: driverInitialize(messagePointer); break;
|
case Y2RCommands::DriverInitialize: driverInitialize(messagePointer); break;
|
||||||
case Y2RCommands::DriverFinalize: driverFinalize(messagePointer); break;
|
case Y2RCommands::DriverFinalize: driverFinalize(messagePointer); break;
|
||||||
|
case Y2RCommands::GetBlockAlignment: getBlockAlignment(messagePointer); break;
|
||||||
|
case Y2RCommands::GetInputLines: getInputLines(messagePointer); break;
|
||||||
|
case Y2RCommands::GetInputLineWidth: getInputLineWidth(messagePointer); break;
|
||||||
|
case Y2RCommands::GetOutputFormat: getOutputFormat(messagePointer); break;
|
||||||
case Y2RCommands::GetTransferEndEvent: getTransferEndEvent(messagePointer); break;
|
case Y2RCommands::GetTransferEndEvent: getTransferEndEvent(messagePointer); break;
|
||||||
case Y2RCommands::IsBusyConversion: isBusyConversion(messagePointer); break;
|
case Y2RCommands::IsBusyConversion: isBusyConversion(messagePointer); break;
|
||||||
case Y2RCommands::PingProcess: pingProcess(messagePointer); break;
|
case Y2RCommands::PingProcess: pingProcess(messagePointer); break;
|
||||||
|
@ -81,7 +90,7 @@ void Y2RService::pingProcess(u32 messagePointer) {
|
||||||
log("Y2R::PingProcess\n");
|
log("Y2R::PingProcess\n");
|
||||||
mem.write32(messagePointer, IPC::responseHeader(0x2A, 2, 0));
|
mem.write32(messagePointer, IPC::responseHeader(0x2A, 2, 0));
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
mem.write32(messagePointer + 8, 0); // Connected number
|
mem.write32(messagePointer + 8, 0); // Connected number
|
||||||
}
|
}
|
||||||
|
|
||||||
void Y2RService::driverInitialize(u32 messagePointer) {
|
void Y2RService::driverInitialize(u32 messagePointer) {
|
||||||
|
@ -98,8 +107,9 @@ void Y2RService::driverFinalize(u32 messagePointer) {
|
||||||
|
|
||||||
void Y2RService::getTransferEndEvent(u32 messagePointer) {
|
void Y2RService::getTransferEndEvent(u32 messagePointer) {
|
||||||
log("Y2R::GetTransferEndEvent\n");
|
log("Y2R::GetTransferEndEvent\n");
|
||||||
if (!transferEndEvent.has_value())
|
if (!transferEndEvent.has_value()) {
|
||||||
transferEndEvent = kernel.makeEvent(ResetType::OneShot);
|
transferEndEvent = kernel.makeEvent(ResetType::OneShot);
|
||||||
|
}
|
||||||
|
|
||||||
mem.write32(messagePointer, IPC::responseHeader(0xF, 1, 2));
|
mem.write32(messagePointer, IPC::responseHeader(0xF, 1, 2));
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
@ -150,6 +160,14 @@ void Y2RService::setBlockAlignment(u32 messagePointer) {
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Y2RService::getBlockAlignment(u32 messagePointer) {
|
||||||
|
log("Y2R::GetBlockAlignment\n");
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x8, 2, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
mem.write32(messagePointer + 8, static_cast<u32>(alignment));
|
||||||
|
}
|
||||||
|
|
||||||
void Y2RService::setInputFormat(u32 messagePointer) {
|
void Y2RService::setInputFormat(u32 messagePointer) {
|
||||||
const u32 format = mem.read32(messagePointer + 4);
|
const u32 format = mem.read32(messagePointer + 4);
|
||||||
log("Y2R::SetInputFormat (format = %d)\n", format);
|
log("Y2R::SetInputFormat (format = %d)\n", format);
|
||||||
|
@ -178,6 +196,14 @@ void Y2RService::setOutputFormat(u32 messagePointer) {
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Y2RService::getOutputFormat(u32 messagePointer) {
|
||||||
|
log("Y2R::GetOutputFormat\n");
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x4, 2, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
mem.write32(messagePointer + 8, static_cast<u32>(outputFmt));
|
||||||
|
}
|
||||||
|
|
||||||
void Y2RService::setPackageParameter(u32 messagePointer) {
|
void Y2RService::setPackageParameter(u32 messagePointer) {
|
||||||
// Package parameter is 3 words
|
// Package parameter is 3 words
|
||||||
const u32 word1 = mem.read32(messagePointer + 4);
|
const u32 word1 = mem.read32(messagePointer + 4);
|
||||||
|
@ -243,6 +269,13 @@ void Y2RService::setInputLineWidth(u32 messagePointer) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Y2RService::getInputLineWidth(u32 messagePointer) {
|
||||||
|
log("Y2R::GetInputLineWidth\n");
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x1B, 2, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
mem.write32(messagePointer + 8, inputLineWidth);
|
||||||
|
}
|
||||||
void Y2RService::setInputLines(u32 messagePointer) {
|
void Y2RService::setInputLines(u32 messagePointer) {
|
||||||
const u16 lines = mem.read16(messagePointer + 4);
|
const u16 lines = mem.read16(messagePointer + 4);
|
||||||
log("Y2R::SetInputLines (lines = %d)\n", lines);
|
log("Y2R::SetInputLines (lines = %d)\n", lines);
|
||||||
|
@ -253,19 +286,30 @@ void Y2RService::setInputLines(u32 messagePointer) {
|
||||||
Helpers::panic("Y2R: Invalid input line count");
|
Helpers::panic("Y2R: Invalid input line count");
|
||||||
} else {
|
} else {
|
||||||
// According to Citra, the Y2R module seems to accidentally skip setting the line # if it's 1024
|
// According to Citra, the Y2R module seems to accidentally skip setting the line # if it's 1024
|
||||||
if (lines != 1024)
|
if (lines != 1024) {
|
||||||
inputLines = lines;
|
inputLines = lines;
|
||||||
|
}
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Y2RService::getInputLines(u32 messagePointer) {
|
||||||
|
log("Y2R::GetInputLines\n");
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x1D, 2, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
mem.write32(messagePointer + 8, inputLines);
|
||||||
|
}
|
||||||
|
|
||||||
void Y2RService::setStandardCoeff(u32 messagePointer) {
|
void Y2RService::setStandardCoeff(u32 messagePointer) {
|
||||||
const u32 coeff = mem.read32(messagePointer + 4);
|
const u32 coeff = mem.read32(messagePointer + 4);
|
||||||
log("Y2R::SetStandardCoeff (coefficient = %d)\n", coeff);
|
log("Y2R::SetStandardCoeff (coefficient = %d)\n", coeff);
|
||||||
mem.write32(messagePointer, IPC::responseHeader(0x20, 1, 0));
|
mem.write32(messagePointer, IPC::responseHeader(0x20, 1, 0));
|
||||||
|
|
||||||
if (coeff > 3)
|
if (coeff > 3) {
|
||||||
Helpers::panic("Y2R: Invalid standard coefficient");
|
Helpers::panic("Y2R: Invalid standard coefficient (coefficient = %d)\n", coeff);
|
||||||
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
Helpers::warn("Unimplemented: Y2R standard coefficient");
|
Helpers::warn("Unimplemented: Y2R standard coefficient");
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
@ -316,4 +360,4 @@ void Y2RService::startConversion(u32 messagePointer) {
|
||||||
if (transferEndEvent.has_value()) {
|
if (transferEndEvent.has_value()) {
|
||||||
kernel.signalEvent(transferEndEvent.value());
|
kernel.signalEvent(transferEndEvent.value());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -426,6 +426,10 @@ bool Emulator::loadROM(const std::filesystem::path& path) {
|
||||||
reset(ReloadOption::NoReload);
|
reset(ReloadOption::NoReload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset whatever state needs to be reset before loading a new ROM
|
||||||
|
memory.loadedCXI = std::nullopt;
|
||||||
|
memory.loaded3DSX = std::nullopt;
|
||||||
|
|
||||||
// Get path for saving files (AppData on Windows, /home/user/.local/share/ApplcationName on Linux, etc)
|
// Get path for saving files (AppData on Windows, /home/user/.local/share/ApplcationName on Linux, etc)
|
||||||
// Inside that path, we be use a game-specific folder as well. Eg if we were loading a ROM called PenguinDemo.3ds, the savedata would be in
|
// Inside that path, we be use a game-specific folder as well. Eg if we were loading a ROM called PenguinDemo.3ds, the savedata would be in
|
||||||
// %APPDATA%/Alber/PenguinDemo/SaveData on Windows, and so on. We do this because games save data in their own filesystem on the cart
|
// %APPDATA%/Alber/PenguinDemo/SaveData on Windows, and so on. We do this because games save data in their own filesystem on the cart
|
||||||
|
@ -453,6 +457,8 @@ bool Emulator::loadROM(const std::filesystem::path& path) {
|
||||||
success = loadNCSD(path, ROMType::NCSD);
|
success = loadNCSD(path, ROMType::NCSD);
|
||||||
else if (extension == ".cxi" || extension == ".app")
|
else if (extension == ".cxi" || extension == ".app")
|
||||||
success = loadNCSD(path, ROMType::CXI);
|
success = loadNCSD(path, ROMType::CXI);
|
||||||
|
else if (extension == ".3dsx")
|
||||||
|
success = load3DSX(path);
|
||||||
else {
|
else {
|
||||||
printf("Unknown file type\n");
|
printf("Unknown file type\n");
|
||||||
success = false;
|
success = false;
|
||||||
|
@ -492,6 +498,19 @@ bool Emulator::loadNCSD(const std::filesystem::path& path, ROMType type) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Emulator::load3DSX(const std::filesystem::path& path) {
|
||||||
|
std::optional<u32> entrypoint = memory.load3DSX(path);
|
||||||
|
romType = ROMType::HB_3DSX;
|
||||||
|
|
||||||
|
if (!entrypoint.has_value()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cpu.setReg(15, entrypoint.value()); // Set initial PC
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool Emulator::loadELF(const std::filesystem::path& path) {
|
bool Emulator::loadELF(const std::filesystem::path& path) {
|
||||||
loadedELF.open(path, std::ios_base::binary); // Open ROM in binary mode
|
loadedELF.open(path, std::ios_base::binary); // Open ROM in binary mode
|
||||||
romType = ROMType::ELF;
|
romType = ROMType::ELF;
|
||||||
|
|
|
@ -345,7 +345,7 @@ void main() {
|
||||||
if ((textureConfig & 2u) != 0u) tevSources[4] = texture(u_tex1, v_texcoord1);
|
if ((textureConfig & 2u) != 0u) tevSources[4] = texture(u_tex1, v_texcoord1);
|
||||||
if ((textureConfig & 4u) != 0u) tevSources[5] = texture(u_tex2, tex2UV);
|
if ((textureConfig & 4u) != 0u) tevSources[5] = texture(u_tex2, tex2UV);
|
||||||
tevSources[13] = vec4(0.0); // Previous buffer
|
tevSources[13] = vec4(0.0); // Previous buffer
|
||||||
tevSources[15] = vec4(0.0); // Previous combiner
|
tevSources[15] = v_colour; // Previous combiner
|
||||||
|
|
||||||
tevNextPreviousBuffer = v_textureEnvBufferColor;
|
tevNextPreviousBuffer = v_textureEnvBufferColor;
|
||||||
uint textureEnvUpdateBuffer = readPicaReg(0xE0);
|
uint textureEnvUpdateBuffer = readPicaReg(0xE0);
|
||||||
|
|
|
@ -64,7 +64,8 @@ float decodeFP(uint hex, uint E, uint M) {
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
gl_Position = a_coords;
|
gl_Position = a_coords;
|
||||||
v_colour = a_vertexColour;
|
vec4 colourAbs = abs(a_vertexColour);
|
||||||
|
v_colour = min(colourAbs, vec4(1.f));
|
||||||
|
|
||||||
// Flip y axis of UVs because OpenGL uses an inverted y for texture sampling compared to the PICA
|
// Flip y axis of UVs because OpenGL uses an inverted y for texture sampling compared to the PICA
|
||||||
v_texcoord0 = vec3(a_texcoord0.x, 1.0 - a_texcoord0.y, a_texcoord0_w);
|
v_texcoord0 = vec3(a_texcoord0.x, 1.0 - a_texcoord0.y, a_texcoord0_w);
|
||||||
|
@ -94,4 +95,4 @@ void main() {
|
||||||
// There's also another, always-on clipping plane based on vertex z
|
// There's also another, always-on clipping plane based on vertex z
|
||||||
gl_ClipDistance[0] = -a_coords.z;
|
gl_ClipDistance[0] = -a_coords.z;
|
||||||
gl_ClipDistance[1] = dot(clipData, a_coords);
|
gl_ClipDistance[1] = dot(clipData, a_coords);
|
||||||
}
|
}
|
||||||
|
|
7
src/host_shaders/vulkan_display.frag
Normal file
7
src/host_shaders/vulkan_display.frag
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#version 460 core
|
||||||
|
layout(location = 0) in vec2 UV;
|
||||||
|
layout(location = 0) out vec4 FragColor;
|
||||||
|
|
||||||
|
layout(binding = 0) uniform sampler2D u_texture;
|
||||||
|
|
||||||
|
void main() { FragColor = texture(u_texture, UV); }
|
7
src/host_shaders/vulkan_display.vert
Normal file
7
src/host_shaders/vulkan_display.vert
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#version 460 core
|
||||||
|
layout(location = 0) out vec2 UV;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
UV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
|
||||||
|
gl_Position = vec4(UV * 2.0f + -1.0f, 0.0f, 1.0f);
|
||||||
|
}
|
2
third_party/dynarmic
vendored
2
third_party/dynarmic
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit daa3dedf88cad90aaf6f6ef5e26f12884b90ca13
|
Subproject commit 96e179465884be74987d5847d6741cdabdfe1b48
|
Loading…
Add table
Reference in a new issue