mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-06 06:05:40 +12:00
Merge branch 'master' into FCRAM_Size_Stuff
This commit is contained in:
commit
5b42834f8e
66 changed files with 6250 additions and 3387 deletions
15
.github/Alber.desktop
vendored
Normal file
15
.github/Alber.desktop
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Type=Application
|
||||
Name=Alber
|
||||
GenericName=3DS Emulator
|
||||
GenericName[fr]=Émulateur 3DS
|
||||
Comment=Nintendo 3DS video game console emulator
|
||||
Comment[fr]=Émulateur de console de jeu Nintendo 3DS
|
||||
Icon=Alber
|
||||
TryExec=Alber
|
||||
Exec=Alber %f
|
||||
Categories=Game;Emulator;
|
||||
MimeType=application/x-ctr-3dsx;application/x-ctr-cci;application/x-ctr-cia;application/x-ctr-cxi;
|
||||
Keywords=3DS;Nintendo;
|
||||
PrefersNonDefaultGPU=true
|
6
.github/linux-appimage.sh
vendored
Executable file
6
.github/linux-appimage.sh
vendored
Executable file
|
@ -0,0 +1,6 @@
|
|||
# Prepare Tools for building the AppImage
|
||||
wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
|
||||
chmod a+x linuxdeploy-x86_64.AppImage
|
||||
|
||||
# Build AppImage
|
||||
./linuxdeploy-x86_64.AppImage --appdir AppDir -d ./.github/Alber.desktop -e ./build/Alber -i ./docs/img/Alber.png --output appimage
|
66
.github/workflows/Linux_AppImage_Build.yml
vendored
Normal file
66
.github/workflows/Linux_AppImage_Build.yml
vendored
Normal file
|
@ -0,0 +1,66 @@
|
|||
name: Linux AppImage Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# The CMake configure and build commands are platform agnostic and should work equally
|
||||
# well on Windows or Mac. You can convert this to a matrix build if you need
|
||||
# cross-platform coverage.
|
||||
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Fetch submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Install misc packages
|
||||
run: sudo apt-get update && sudo apt install libx11-dev libgl1-mesa-glx mesa-common-dev libfuse2
|
||||
|
||||
- name: Install newer Clang
|
||||
run: |
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x ./llvm.sh
|
||||
sudo ./llvm.sh 16
|
||||
|
||||
- name: Install newer CMake
|
||||
run: |
|
||||
sudo curl -s https://apt.kitware.com/keys/kitware-archive-latest.asc | gpg --dearmor | tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null
|
||||
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 42D5A192B819C5DA
|
||||
sudo add-apt-repository -y 'deb https://apt.kitware.com/ubuntu/ focal main'
|
||||
sudo apt-get update
|
||||
sudo apt-get install cmake
|
||||
|
||||
- name: Setup Vulkan SDK
|
||||
run: |
|
||||
wget -qO - http://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo apt-key add -
|
||||
sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-focal.list http://packages.lunarg.com/vulkan/lunarg-vulkan-focal.list
|
||||
sudo apt update
|
||||
sudo apt install vulkan-sdk
|
||||
|
||||
- name: Configure CMake
|
||||
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
|
||||
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
|
||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang-16 -DCMAKE_CXX_COMPILER=clang++-16 -DENABLE_USER_BUILD=ON
|
||||
|
||||
- name: Build
|
||||
# Build your program with the given configuration
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Run AppImage packaging script
|
||||
run: ./.github/linux-appimage.sh
|
||||
|
||||
- name: Upload executable
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Linux executable
|
||||
path: './Alber-x86_64.AppImage'
|
|
@ -144,7 +144,7 @@ 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
|
||||
)
|
||||
|
||||
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
|
||||
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/archive_user_save_data.cpp
|
||||
|
@ -161,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/PICA/gpu.hpp include/PICA/regs.hpp include/services/ndm.hpp
|
||||
include/PICA/shader.hpp include/PICA/shader_unit.hpp include/PICA/float_types.hpp
|
||||
include/logger.hpp include/loader/ncch.hpp include/loader/ncsd.hpp include/io_file.hpp
|
||||
include/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/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
|
||||
|
@ -249,20 +249,58 @@ if(ENABLE_VULKAN)
|
|||
)
|
||||
|
||||
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
|
||||
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})
|
||||
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(
|
||||
resources_renderer_vk
|
||||
NAMESPACE RendererVK
|
||||
WHENCE "src/host_shaders/"
|
||||
WHENCE "${PROJECT_BINARY_DIR}/host_shaders/"
|
||||
${RENDERER_VK_HOST_SHADERS_SPIRV}
|
||||
)
|
||||
endif()
|
||||
|
||||
|
|
BIN
docs/img/Alber.png
Normal file
BIN
docs/img/Alber.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1 MiB |
|
@ -170,6 +170,8 @@ public:
|
|||
return env.totalTicks;
|
||||
}
|
||||
|
||||
void clearCache() { jit->ClearCache(); }
|
||||
|
||||
void runFrame() {
|
||||
env.ticksLeft = ticksPerSec / 60;
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ enum class ROMType {
|
|||
ELF,
|
||||
NCSD,
|
||||
CXI,
|
||||
HB_3DSX,
|
||||
};
|
||||
|
||||
class Emulator {
|
||||
|
@ -99,6 +100,7 @@ class Emulator {
|
|||
|
||||
bool loadROM(const std::filesystem::path& path);
|
||||
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(std::ifstream& file);
|
||||
void initGraphicsContext();
|
||||
|
|
|
@ -251,4 +251,11 @@ public:
|
|||
virtual std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) = 0;
|
||||
|
||||
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;
|
||||
};
|
|
@ -9,6 +9,7 @@ public:
|
|||
u64 getFreeBytes() override { Helpers::panic("ExtSaveData::GetFreeBytes unimplemented"); return 0; }
|
||||
std::string name() override { return "ExtSaveData::" + backingFolder; }
|
||||
|
||||
HorizonResult createDirectory(const FSPath& path) override;
|
||||
HorizonResult createFile(const FSPath& path, u64 size) override;
|
||||
HorizonResult deleteFile(const FSPath& path) override;
|
||||
|
||||
|
|
|
@ -8,13 +8,16 @@ class SDMCArchive : public ArchiveBase {
|
|||
public:
|
||||
SDMCArchive(Memory& mem) : ArchiveBase(mem) {}
|
||||
|
||||
u64 getFreeBytes() override { Helpers::panic("SDMC::GetFreeBytes unimplemented"); return 0; }
|
||||
u64 getFreeBytes() override { return 1_GB; }
|
||||
std::string name() override { return "SDMC"; }
|
||||
|
||||
HorizonResult createFile(const FSPath& path, u64 size) override;
|
||||
HorizonResult deleteFile(const FSPath& path) override;
|
||||
HorizonResult createDirectory(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;
|
||||
};
|
|
@ -18,7 +18,8 @@ public:
|
|||
// Returns whether the cart has a RomFS
|
||||
bool hasRomFS() {
|
||||
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)
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -137,7 +137,7 @@ namespace Helpers {
|
|||
return getBits<offset, bits, ValueT, ValueT>(value);
|
||||
}
|
||||
|
||||
#if defined(HELPERS_APPLE_CLANG) || defined(__ANDROID__)
|
||||
#if defined(HELPERS_APPLE_CLANG) || defined(__ANDROID__) || !defined(__cpp_lib_bit_cast)
|
||||
template <class To, class From>
|
||||
constexpr To bit_cast(const From& from) noexcept {
|
||||
return *reinterpret_cast<const To*>(&from);
|
||||
|
|
|
@ -83,6 +83,9 @@ private:
|
|||
bool canThreadRun(const Thread& t);
|
||||
bool shouldWaitOnObject(KernelObject* object);
|
||||
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
|
||||
// Returns the index of the woken up thread
|
||||
|
@ -226,4 +229,5 @@ public:
|
|||
|
||||
void sendGPUInterrupt(GPUInterrupt type) { serviceManager.sendGPUInterrupt(type); }
|
||||
void signalDSPEvents() { serviceManager.signalDSPEvents(); }
|
||||
void clearInstructionCache();
|
||||
};
|
|
@ -173,6 +173,19 @@ struct Semaphore {
|
|||
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 {
|
||||
u32 addr = 0;
|
||||
u32 size = 0;
|
||||
|
@ -206,21 +219,23 @@ struct KernelObject {
|
|||
}
|
||||
|
||||
// Retrieves a reference to the waitlist for a specified object
|
||||
// We return a reference because this function is only called in the kernel threading internals
|
||||
// We want the kernel to be able to easily manage waitlists, by reading/parsing them or setting/clearing bits.
|
||||
// As we mention in the definition of the "Event" struct, the format for wailists is very simple and made to be efficient.
|
||||
// Each bit corresponds to a thread index and denotes whether the corresponding thread is waiting on this object
|
||||
// For example if bit 0 of the wait list is set, then the thread with index 0 is waiting on our object
|
||||
u64& getWaitlist() {
|
||||
// This code is actually kinda trash but eh good enough
|
||||
switch (type) {
|
||||
case KernelObjectType::Event: return getData<Event>()->waitlist;
|
||||
case KernelObjectType::Mutex: return getData<Mutex>()->waitlist;
|
||||
case KernelObjectType::Semaphore: return getData<Mutex>()->waitlist;
|
||||
case KernelObjectType::Thread: return getData<Thread>()->threadsWaitingForTermination;
|
||||
// This should be unreachable once we fully implement sync objects
|
||||
default: [[unlikely]]
|
||||
// We return a reference because this function is only called in the kernel threading internals
|
||||
// We want the kernel to be able to easily manage waitlists, by reading/parsing them or setting/clearing bits.
|
||||
// As we mention in the definition of the "Event" struct, the format for wailists is very simple and made to be efficient.
|
||||
// Each bit corresponds to a thread index and denotes whether the corresponding thread is waiting on this object
|
||||
// For example if bit 0 of the wait list is set, then the thread with index 0 is waiting on our object
|
||||
u64& getWaitlist() {
|
||||
// This code is actually kinda trash but eh good enough
|
||||
switch (type) {
|
||||
case KernelObjectType::Event: return getData<Event>()->waitlist;
|
||||
case KernelObjectType::Mutex: return getData<Mutex>()->waitlist;
|
||||
case KernelObjectType::Semaphore: return getData<Mutex>()->waitlist;
|
||||
case KernelObjectType::Thread: return getData<Thread>()->threadsWaitingForTermination;
|
||||
case KernelObjectType::Timer: return getData<Timer>()->waitlist;
|
||||
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
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 "helpers.hpp"
|
||||
#include "loader/ncsd.hpp"
|
||||
#include "loader/3dsx.hpp"
|
||||
#include "services/region_codes.hpp"
|
||||
|
||||
namespace PhysicalAddrs {
|
||||
|
@ -172,10 +173,12 @@ public:
|
|||
void* getReadPointer(u32 address);
|
||||
void* getWritePointer(u32 address);
|
||||
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> loadCXI(Crypto::AESEngine& aesEngine, const std::filesystem::path& path);
|
||||
|
||||
bool mapCXI(NCSD& ncsd, NCCH& cxi);
|
||||
bool map3DSX(HB3DSX& hb3dsx, const HB3DSX::Header& header);
|
||||
|
||||
u8 read8(u32 vaddr);
|
||||
u16 read16(u32 vaddr);
|
||||
|
@ -240,6 +243,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
|
||||
static constexpr bool isAligned(u32 addr) {
|
||||
return (addr & pageMask) == 0;
|
||||
|
@ -275,6 +286,7 @@ public:
|
|||
|
||||
// Backup of the game's CXI partition info, if any
|
||||
std::optional<NCCH> loadedCXI = std::nullopt;
|
||||
std::optional<HB3DSX> loaded3DSX = std::nullopt;
|
||||
// File handle for reading the loaded ncch
|
||||
IOFile CXIFile;
|
||||
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
#include <map>
|
||||
#include <optional>
|
||||
|
||||
#include "math_util.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;
|
||||
|
||||
|
@ -10,7 +17,7 @@ class RendererVK final : public Renderer {
|
|||
vk::UniqueInstance instance = {};
|
||||
vk::UniqueDebugUtilsMessengerEXT debugMessenger = {};
|
||||
|
||||
vk::UniqueSurfaceKHR surface = {};
|
||||
vk::SurfaceKHR swapchainSurface = {};
|
||||
|
||||
vk::PhysicalDevice physicalDevice = {};
|
||||
|
||||
|
@ -32,17 +39,74 @@ class RendererVK final : public Renderer {
|
|||
std::vector<vk::Image> swapchainImages = {};
|
||||
std::vector<vk::UniqueImageView> swapchainImageViews = {};
|
||||
|
||||
// Per-swapchain-image data
|
||||
// Each vector is `swapchainImageCount` in size
|
||||
std::vector<vk::UniqueCommandBuffer> presentCommandBuffers = {};
|
||||
// This value is the degree of parallelism to allow multiple frames to be in-flight
|
||||
// aka: "double-buffer"/"triple-buffering"
|
||||
// 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> renderFinishedSemaphore = {};
|
||||
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
|
||||
vk::Result recreateSwapchain(vk::SurfaceKHR surface, vk::Extent2D swapchainExtent);
|
||||
|
||||
u64 currentFrame = 0;
|
||||
u64 frameBufferingIndex = 0;
|
||||
|
||||
public:
|
||||
RendererVK(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs);
|
||||
~RendererVK() override;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "vulkan_api.hpp"
|
||||
#include "vk_api.hpp"
|
||||
|
||||
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,5 @@
|
|||
#pragma once
|
||||
#include "config.hpp"
|
||||
#include "fs/archive_ext_save_data.hpp"
|
||||
#include "fs/archive_ncch.hpp"
|
||||
#include "fs/archive_save_data.hpp"
|
||||
|
@ -9,7 +10,6 @@
|
|||
#include "kernel_types.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "memory.hpp"
|
||||
#include "result/result.hpp"
|
||||
|
||||
// Yay, more circular dependencies
|
||||
class Kernel;
|
||||
|
@ -40,7 +40,10 @@ class FSService {
|
|||
std::optional<Handle> openFileHandle(ArchiveBase* archive, const FSPath& path, const FSPath& archivePath, const FilePerms& perms);
|
||||
FSPath readPath(u32 type, u32 pointer, u32 size);
|
||||
|
||||
const EmulatorConfig& config;
|
||||
|
||||
// Service commands
|
||||
void abnegateAccessRight(u32 messagePointer);
|
||||
void createDirectory(u32 messagePointer);
|
||||
void createExtSaveData(u32 messagePointer);
|
||||
void createFile(u32 messagePointer);
|
||||
|
@ -50,9 +53,12 @@ class FSService {
|
|||
void deleteFile(u32 messagePointer);
|
||||
void formatSaveData(u32 messagePointer);
|
||||
void formatThisUserSaveData(u32 messagePointer);
|
||||
void getArchiveResource(u32 messagePointer);
|
||||
void getFreeBytes(u32 messagePointer);
|
||||
void getFormatInfo(u32 messagePointer);
|
||||
void getPriority(u32 messagePointer);
|
||||
void getThisSaveDataSecureValue(u32 messagePointer);
|
||||
void theGameboyVCFunction(u32 messagePointer);
|
||||
void initialize(u32 messagePointer);
|
||||
void initializeWithSdkVersion(u32 messagePointer);
|
||||
void isSdmcDetected(u32 messagePointer);
|
||||
|
@ -61,15 +67,17 @@ class FSService {
|
|||
void openDirectory(u32 messagePointer);
|
||||
void openFile(u32 messagePointer);
|
||||
void openFileDirectly(u32 messagePointer);
|
||||
void setArchivePriority(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
|
||||
u32 priority;
|
||||
|
||||
public:
|
||||
FSService(Memory& mem, Kernel& kernel)
|
||||
FSService(Memory& mem, Kernel& kernel, const EmulatorConfig& config)
|
||||
: mem(mem), saveData(mem), sharedExtSaveData_nand(mem, "../SharedFiles/NAND", true), extSaveData_sdmc(mem, "SDMC"), sdmc(mem), selfNcch(mem),
|
||||
ncch(mem), userSaveData1(mem, ArchiveID::UserSaveData1), userSaveData2(mem, ArchiveID::UserSaveData2), kernel(kernel) {}
|
||||
ncch(mem), userSaveData1(mem, ArchiveID::UserSaveData1), userSaveData2(mem, ArchiveID::UserSaveData2), kernel(kernel), config(config) {}
|
||||
|
||||
void reset();
|
||||
void handleSyncRequest(u32 messagePointer);
|
||||
|
|
|
@ -12,7 +12,9 @@ class HTTPService {
|
|||
bool initialized = false;
|
||||
|
||||
// Service commands
|
||||
void createRootCertChain(u32 messagePointer);
|
||||
void initialize(u32 messagePointer);
|
||||
void rootCertChainAddDefaultCert(u32 messagePointer);
|
||||
|
||||
public:
|
||||
HTTPService(Memory& mem) : mem(mem) {}
|
||||
|
|
|
@ -17,6 +17,7 @@ class MICService {
|
|||
// Service commands
|
||||
void getEventHandle(u32 messagePointer);
|
||||
void getGain(u32 messagePointer);
|
||||
void getPower(u32 messagePointer);
|
||||
void isSampling(u32 messagePointer);
|
||||
void mapSharedMem(u32 messagePointer);
|
||||
void setClamp(u32 messagePointer);
|
||||
|
|
|
@ -44,7 +44,9 @@ class NFCService {
|
|||
void getTagInRangeEvent(u32 messagePointer);
|
||||
void getTagOutOfRangeEvent(u32 messagePointer);
|
||||
void getTagState(u32 messagePointer);
|
||||
void shutdown(u32 messagePointer);
|
||||
void startCommunication(u32 messagePointer);
|
||||
void startTagScanning(u32 messagePointer);
|
||||
void stopCommunication(u32 messagePointer);
|
||||
|
||||
public:
|
||||
|
|
|
@ -16,6 +16,7 @@ class PTMService {
|
|||
// Service commands
|
||||
void configureNew3DSCPU(u32 messagePointer);
|
||||
void getAdapterState(u32 messagePointer);
|
||||
void getBatteryChargeState(u32 messagePointer);
|
||||
void getBatteryLevel(u32 messagePointer);
|
||||
void getStepHistory(u32 messagePointer);
|
||||
void getTotalStepCount(u32 messagePointer);
|
||||
|
|
|
@ -84,6 +84,7 @@ class ServiceManager {
|
|||
void receiveNotification(u32 messagePointer);
|
||||
void registerClient(u32 messagePointer);
|
||||
void subscribe(u32 messagePointer);
|
||||
void unsubscribe(u32 messagePointer);
|
||||
|
||||
public:
|
||||
ServiceManager(std::span<u32, 16> regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel, const EmulatorConfig& config);
|
||||
|
|
|
@ -64,10 +64,14 @@ class Y2RService {
|
|||
// Service commands
|
||||
void driverInitialize(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 pingProcess(u32 messagePointer);
|
||||
void setTransferEndInterrupt(u32 messagePointer);
|
||||
void getTransferEndEvent(u32 messagePointer);
|
||||
|
||||
void setAlpha(u32 messagePointer);
|
||||
void setBlockAlignment(u32 messagePointer);
|
||||
|
|
|
@ -17,14 +17,13 @@ You can download stable builds from the Releases tab, or you can download the la
|
|||
|--------|------------|--------|
|
||||
|Windows build|[](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Windows_Build.yml)|[Windows Executable](https://nightly.link/wheremyfoodat/Panda3DS/workflows/Windows_Build/master/Windows%20executable.zip)|
|
||||
|MacOS build|[](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/MacOS_Build.yml)|[MacOS App Bundle](https://nightly.link/wheremyfoodat/Panda3DS/workflows/MacOS_Build/master/MacOS%20Alber%20App%20Bundle.zip)|
|
||||
|Linux build|[](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Linux_Build.yml)|[Linux Executable](https://nightly.link/wheremyfoodat/Panda3DS/workflows/Linux_Build/master/Linux%20executable.zip)|
|
||||
|Linux build|[](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Linux_Build.yml)|[Linux AppImage](https://nightly.link/wheremyfoodat/Panda3DS/workflows/Linux_AppImage_Build/master/Linux%20executable.zip)|
|
||||
|
||||
# Compatibility
|
||||
Panda3DS is still in the early stages of development. Many games boot, many don't. Most games have at least some hilariously broken graphics, audio is not supported, and some QoL features (including a GUI) are missing.
|
||||
|
||||
In addition, some games don't quiiite work with the upstream code. A lot of them might need some panics in the source code to be commented out before they work, etc. However, just the fact things can work as well as they do now is promising in itself.
|
||||
|
||||
Check out [this Google spreadsheet](https://docs.google.com/spreadsheets/d/1nWZTzfaMPkZdyhqHEawMRBaP0qSMmQdxrVfAbgapYrM/edit?usp=sharing) for an unofficial compatibility list.
|
||||
# Why?
|
||||
The 3DS emulation scene is already pretty mature, with offerings such as [Citra](https://github.com/citra-emu/citra) which can offer a great playing experience for most games in the library, [Corgi3DS](https://github.com/PSI-Rockin/Corgi3DS), an innovative LLE emulator, or [Mikage](https://mikage.app/). However, there's always room for more emulators! While Panda3DS was initially a mere curiosity, there's many different concepts I would like to explore with it in the future, such as:
|
||||
|
||||
|
@ -61,6 +60,7 @@ Panda3DS can load ROMs in the following formats:
|
|||
- .3ds/.cci
|
||||
- .cxi/.app
|
||||
- .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.
|
||||
|
||||
|
@ -110,7 +110,7 @@ Panda3DS also supports controller input using the SDL2 GameController API.
|
|||
# Support
|
||||
If you find this project exciting and want to support the founder, check out [his Patreon page](https://www.patreon.com/wheremyfoodat)
|
||||
<br>
|
||||
Keep in mind, funding is only aimed to cover various life costs and support development. Panda3DS is a free product and on no occasion will official builds ever be made private or limited to sponsors! Any donation is much appreciated!
|
||||
Keep in mind that funding is only aimed to cover various life costs and support development. Panda3DS is a free product and on no occasion will official builds ever be made private or limited to sponsors! Any donation is much appreciated!
|
||||
|
||||
Nintendo 3DS is a registered trademark of Nintendo Co., Ltd.
|
||||
|
||||
|
|
|
@ -87,6 +87,27 @@ FileDescriptor ExtSaveDataArchive::openFile(const FSPath& path, const FilePerms&
|
|||
return FileError;
|
||||
}
|
||||
|
||||
HorizonResult ExtSaveDataArchive::createDirectory(const FSPath& path) {
|
||||
if (path.type == PathType::UTF16) {
|
||||
if (!isPathSafe<PathType::UTF16>(path)) {
|
||||
Helpers::panic("Unsafe path in ExtSaveData::OpenFile");
|
||||
}
|
||||
|
||||
fs::path p = IOFile::getAppData() / backingFolder;
|
||||
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 ExtSaveData::CreateDirectory");
|
||||
}
|
||||
|
||||
bool success = fs::create_directory(p);
|
||||
return success ? Result::Success : Result::FS::UnexpectedFileOrDir;
|
||||
} else {
|
||||
Helpers::panic("Unimplemented ExtSaveData::CreateDirectory");
|
||||
}
|
||||
}
|
||||
|
||||
std::string ExtSaveDataArchive::getExtSaveDataPathFromBinary(const FSPath& path) {
|
||||
// TODO: Remove punning here
|
||||
const u32 mediaType = *(u32*)&path.binary[0];
|
||||
|
|
|
@ -131,9 +131,6 @@ std::optional<u32> NCCHArchive::readFile(FileSession* file, u64 offset, u32 size
|
|||
}
|
||||
|
||||
auto cxi = mem.getCXI();
|
||||
IOFile& ioFile = mem.CXIFile;
|
||||
|
||||
NCCH::FSInfo fsInfo;
|
||||
|
||||
// Seek to file offset depending on if we're reading from RomFS, ExeFS, etc
|
||||
switch (type) {
|
||||
|
@ -144,7 +141,6 @@ std::optional<u32> NCCHArchive::readFile(FileSession* file, u64 offset, u32 size
|
|||
Helpers::panic("Tried to read from NCCH with too big of an offset");
|
||||
}
|
||||
|
||||
fsInfo = cxi->romFS;
|
||||
offset += 0x1000;
|
||||
break;
|
||||
}
|
||||
|
@ -154,7 +150,7 @@ std::optional<u32> NCCHArchive::readFile(FileSession* file, u64 offset, u32 size
|
|||
}
|
||||
|
||||
std::unique_ptr<u8[]> data(new u8[size]);
|
||||
auto [success, bytesRead] = cxi->readFromFile(ioFile, fsInfo, &data[0], offset, size);
|
||||
auto [success, bytesRead] = cxi->readFromFile(mem.CXIFile, cxi->romFS, &data[0], offset, size);
|
||||
|
||||
if (!success) {
|
||||
Helpers::panic("Failed to read from NCCH archive");
|
||||
|
|
|
@ -39,14 +39,17 @@ HorizonResult SaveDataArchive::createFile(const FSPath& path, u64 size) {
|
|||
|
||||
HorizonResult SaveDataArchive::createDirectory(const FSPath& path) {
|
||||
if (path.type == PathType::UTF16) {
|
||||
if (!isPathSafe<PathType::UTF16>(path))
|
||||
if (!isPathSafe<PathType::UTF16>(path)) {
|
||||
Helpers::panic("Unsafe path in SaveData::OpenFile");
|
||||
}
|
||||
|
||||
fs::path p = IOFile::getAppData() / "SaveData";
|
||||
p += fs::path(path.utf16_string).make_preferred();
|
||||
|
||||
if (fs::is_directory(p))
|
||||
if (fs::is_directory(p)) {
|
||||
return Result::FS::AlreadyExists;
|
||||
}
|
||||
|
||||
if (fs::is_regular_file(p)) {
|
||||
Helpers::panic("File path passed to SaveData::CreateDirectory");
|
||||
}
|
||||
|
@ -92,11 +95,13 @@ HorizonResult SaveDataArchive::deleteFile(const FSPath& path) {
|
|||
|
||||
FileDescriptor SaveDataArchive::openFile(const FSPath& path, const FilePerms& perms) {
|
||||
if (path.type == PathType::UTF16) {
|
||||
if (!isPathSafe<PathType::UTF16>(path))
|
||||
if (!isPathSafe<PathType::UTF16>(path)) {
|
||||
Helpers::panic("Unsafe path in SaveData::OpenFile");
|
||||
}
|
||||
|
||||
if (perms.raw == 0 || (perms.create() && !perms.write()))
|
||||
if (perms.raw == 0 || (perms.create() && !perms.write())) {
|
||||
Helpers::panic("[SaveData] Unsupported flags for OpenFile");
|
||||
}
|
||||
|
||||
fs::path p = IOFile::getAppData() / "SaveData";
|
||||
p += fs::path(path.utf16_string).make_preferred();
|
||||
|
@ -126,8 +131,9 @@ FileDescriptor SaveDataArchive::openFile(const FSPath& path, const FilePerms& pe
|
|||
|
||||
Rust::Result<DirectorySession, HorizonResult> SaveDataArchive::openDirectory(const FSPath& path) {
|
||||
if (path.type == PathType::UTF16) {
|
||||
if (!isPathSafe<PathType::UTF16>(path))
|
||||
if (!isPathSafe<PathType::UTF16>(path)) {
|
||||
Helpers::panic("Unsafe path in SaveData::OpenDirectory");
|
||||
}
|
||||
|
||||
fs::path p = IOFile::getAppData() / "SaveData";
|
||||
p += fs::path(path.utf16_string).make_preferred();
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#include "fs/archive_sdmc.hpp"
|
||||
#include <memory>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
HorizonResult SDMCArchive::createFile(const FSPath& path, u64 size) {
|
||||
Helpers::panic("[SDMC] CreateFile not yet supported");
|
||||
return Result::Success;
|
||||
|
@ -12,13 +14,123 @@ HorizonResult SDMCArchive::deleteFile(const FSPath& path) {
|
|||
}
|
||||
|
||||
FileDescriptor SDMCArchive::openFile(const FSPath& path, const FilePerms& perms) {
|
||||
printf("SDMCArchive::OpenFile: Failed");
|
||||
return FileError;
|
||||
FilePerms realPerms = perms;
|
||||
// SD card always has read permission
|
||||
realPerms.raw |= (1 << 0);
|
||||
|
||||
if ((realPerms.create() && !realPerms.write())) {
|
||||
Helpers::panic("[SDMC] Unsupported flags for OpenFile");
|
||||
}
|
||||
|
||||
std::filesystem::path p = IOFile::getAppData() / "SDMC";
|
||||
|
||||
switch (path.type) {
|
||||
case PathType::ASCII:
|
||||
if (!isPathSafe<PathType::ASCII>(path)) {
|
||||
Helpers::panic("Unsafe path in SDMCArchive::OpenFile");
|
||||
}
|
||||
|
||||
p += fs::path(path.string).make_preferred();
|
||||
break;
|
||||
|
||||
case PathType::UTF16:
|
||||
if (!isPathSafe<PathType::UTF16>(path)) {
|
||||
Helpers::panic("Unsafe path in SDMCArchive::OpenFile");
|
||||
}
|
||||
|
||||
p += fs::path(path.utf16_string).make_preferred();
|
||||
break;
|
||||
|
||||
default: Helpers::panic("SDMCArchive::OpenFile: Failed. Path type: %d", path.type); return FileError;
|
||||
}
|
||||
|
||||
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 (realPerms.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HorizonResult SDMCArchive::createDirectory(const FSPath& path) {
|
||||
std::filesystem::path p = IOFile::getAppData() / "SDMC";
|
||||
|
||||
switch (path.type) {
|
||||
case PathType::ASCII:
|
||||
if (!isPathSafe<PathType::ASCII>(path)) {
|
||||
Helpers::panic("Unsafe path in SDMCArchive::OpenFile");
|
||||
}
|
||||
|
||||
p += fs::path(path.string).make_preferred();
|
||||
break;
|
||||
|
||||
case PathType::UTF16:
|
||||
if (!isPathSafe<PathType::UTF16>(path)) {
|
||||
Helpers::panic("Unsafe path in SDMCArchive::OpenFile");
|
||||
}
|
||||
|
||||
p += fs::path(path.utf16_string).make_preferred();
|
||||
break;
|
||||
|
||||
default: Helpers::panic("SDMCArchive::CreateDirectory: Failed. Path type: %d", path.type); return Result::FailurePlaceholder;
|
||||
}
|
||||
|
||||
if (fs::is_directory(p)) {
|
||||
return Result::FS::AlreadyExists;
|
||||
}
|
||||
|
||||
if (fs::is_regular_file(p)) {
|
||||
Helpers::panic("File path passed to SDMCArchive::CreateDirectory");
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
bool success = fs::create_directory(p, ec);
|
||||
return success ? Result::Success : Result::FS::UnexpectedFileOrDir;
|
||||
}
|
||||
|
||||
Rust::Result<DirectorySession, HorizonResult> SDMCArchive::openDirectory(const FSPath& path) {
|
||||
if (path.type == PathType::UTF16) {
|
||||
if (!isPathSafe<PathType::UTF16>(path)) {
|
||||
Helpers::panic("Unsafe path in SaveData::OpenDirectory");
|
||||
}
|
||||
|
||||
fs::path p = IOFile::getAppData() / "SDMC";
|
||||
p += fs::path(path.utf16_string).make_preferred();
|
||||
|
||||
if (fs::is_regular_file(p)) {
|
||||
printf("SDMC: 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("SDMCArchive::OpenDirectory: Unimplemented path type");
|
||||
return Err(Result::Success);
|
||||
}
|
||||
|
||||
Rust::Result<ArchiveBase*, HorizonResult> SDMCArchive::openArchive(const FSPath& path) {
|
||||
printf("SDMCArchive::OpenArchive: Failed\n");
|
||||
return Err(Result::FS::NotFormatted);
|
||||
// TODO: Fail here if the SD is disabled in the connfig.
|
||||
if (path.type != PathType::Empty) {
|
||||
Helpers::panic("Unimplemented path type for SDMC::OpenArchive");
|
||||
}
|
||||
|
||||
return Ok((ArchiveBase*)this);
|
||||
}
|
||||
|
||||
std::optional<u32> SDMCArchive::readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) {
|
||||
|
|
|
@ -69,57 +69,77 @@ std::optional<u32> SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto cxi = mem.getCXI();
|
||||
IOFile& ioFile = mem.CXIFile;
|
||||
bool success = false;
|
||||
std::size_t bytesRead = 0;
|
||||
std::unique_ptr<u8[]> data(new u8[size]);
|
||||
|
||||
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
|
||||
switch (type) {
|
||||
case PathType::RomFS: {
|
||||
const u64 romFSSize = cxi->romFS.size;
|
||||
const u64 romFSOffset = cxi->romFS.offset;
|
||||
if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) {
|
||||
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
|
||||
NCCH::FSInfo fsInfo;
|
||||
|
||||
// Seek to file offset depending on if we're reading from RomFS, ExeFS, etc
|
||||
switch (type) {
|
||||
case PathType::RomFS: {
|
||||
const u64 romFSSize = cxi->romFS.size;
|
||||
const u64 romFSOffset = cxi->romFS.offset;
|
||||
if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) {
|
||||
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
|
||||
}
|
||||
|
||||
fsInfo = cxi->romFS;
|
||||
offset += 0x1000;
|
||||
break;
|
||||
}
|
||||
|
||||
fsInfo = cxi->romFS;
|
||||
offset += 0x1000;
|
||||
break;
|
||||
}
|
||||
case PathType::ExeFS: {
|
||||
const u64 exeFSSize = cxi->exeFS.size;
|
||||
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: {
|
||||
const u64 exeFSSize = cxi->exeFS.size;
|
||||
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;
|
||||
break;
|
||||
}
|
||||
|
||||
fsInfo = cxi->exeFS;
|
||||
break;
|
||||
}
|
||||
// Normally, the update RomFS should overlay the cartridge RomFS when reading from this and an update is installed.
|
||||
// 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.
|
||||
// So to support updates, we need to perform this overlaying. For now, read from the cartridge RomFS.
|
||||
case PathType::UpdateRomFS: {
|
||||
Helpers::warn("Reading from update RomFS but updates are currently not supported! Reading from regular RomFS instead\n");
|
||||
const u64 romFSSize = cxi->romFS.size;
|
||||
const u64 romFSOffset = cxi->romFS.offset;
|
||||
if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) {
|
||||
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
|
||||
}
|
||||
|
||||
const u64 romFSSize = cxi->romFS.size;
|
||||
const u64 romFSOffset = cxi->romFS.offset;
|
||||
if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) {
|
||||
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
|
||||
fsInfo = cxi->romFS;
|
||||
offset += 0x1000;
|
||||
break;
|
||||
}
|
||||
|
||||
fsInfo = cxi->romFS;
|
||||
offset += 0x1000;
|
||||
break;
|
||||
default: Helpers::panic("Unimplemented file path type for SelfNCCH archive");
|
||||
}
|
||||
|
||||
default: Helpers::panic("Unimplemented file path type for SelfNCCH archive");
|
||||
std::tie(success, bytesRead) = cxi->readFromFile(ioFile, fsInfo, &data[0], offset, size);
|
||||
}
|
||||
|
||||
std::unique_ptr<u8[]> data(new u8[size]);
|
||||
auto [success, bytesRead] = cxi->readFromFile(ioFile, fsInfo, &data[0], offset, size);
|
||||
else if (auto hb3dsx = mem.get3DSX(); hb3dsx != nullptr) {
|
||||
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");
|
||||
}
|
||||
|
||||
std::tie(success, bytesRead) = hb3dsx->readRomFSBytes(&data[0], offset, size);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
Helpers::panic("Failed to read from SelfNCCH archive");
|
||||
|
|
|
@ -124,6 +124,7 @@ void Kernel::deleteObjectData(KernelObject& object) {
|
|||
case KernelObjectType::Session: delete object.getData<Session>(); return;
|
||||
case KernelObjectType::Mutex: delete object.getData<Mutex>(); return;
|
||||
case KernelObjectType::Semaphore: delete object.getData<Semaphore>(); return;
|
||||
case KernelObjectType::Timer: delete object.getData<Timer>(); return;
|
||||
case KernelObjectType::Thread: return;
|
||||
case KernelObjectType::Dummy: return;
|
||||
default: [[unlikely]] Helpers::warn("unknown object type"); return;
|
||||
|
@ -262,6 +263,8 @@ void Kernel::duplicateHandle() {
|
|||
}
|
||||
}
|
||||
|
||||
void Kernel::clearInstructionCache() { cpu.clearCache(); }
|
||||
|
||||
namespace SystemInfoType {
|
||||
enum : u32 {
|
||||
MemoryInformation = 0,
|
||||
|
|
|
@ -252,6 +252,14 @@ void Kernel::acquireSyncObject(KernelObject* object, const Thread& thread) {
|
|||
case KernelObjectType::Thread:
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
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
|
||||
return object->getData<Semaphore>()->availableCount <= 0;
|
||||
|
||||
|
|
|
@ -1,6 +1,127 @@
|
|||
#include "kernel.hpp"
|
||||
#include "cpu.hpp"
|
||||
|
||||
void Kernel::svcCreateTimer() { Helpers::panic("Kernel::CreateTimer"); }
|
||||
void Kernel::svcSetTimer() { Helpers::panic("Kernel::SetTimer"); }
|
||||
void Kernel::svcClearTimer() { Helpers::panic("Kernel::ClearTimer"); }
|
||||
void Kernel::svcCancelTimer() { Helpers::panic("Kernel::CancelTimer"); }
|
||||
Handle Kernel::makeTimer(ResetType type) {
|
||||
Handle ret = makeObject(KernelObjectType::Timer);
|
||||
objects[ret].data = new Timer(type);
|
||||
|
||||
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);
|
||||
}
|
|
@ -143,6 +143,7 @@ u32 Memory::read32(u32 vaddr) {
|
|||
return *(u32*)(pointer + offset);
|
||||
} else {
|
||||
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 + 4:
|
||||
return u32(timeSince3DSEpoch() >> 32); // top 32 bits
|
||||
|
|
|
@ -695,6 +695,7 @@ void RendererGL::textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32
|
|||
if (inputGap != 0 || outputGap != 0) {
|
||||
// Helpers::warn("Strided texture copy\n");
|
||||
}
|
||||
|
||||
if (inputWidth != outputWidth) {
|
||||
Helpers::warn("Input width does not match output width, cannot accelerate texture copy!");
|
||||
return;
|
||||
|
@ -716,7 +717,12 @@ void RendererGL::textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32
|
|||
// inputHeight/outputHeight are typically set to zero so they cannot be used to get the height of the copy region
|
||||
// in contrast to display transfer. Compute height manually by dividing the copy size with the copy width. The result
|
||||
// is the number of vertical tiles so multiply that by eight to get the actual copy height.
|
||||
const u32 copyHeight = (copySize / inputWidth) * 8;
|
||||
u32 copyHeight;
|
||||
if (inputWidth != 0) [[likely]] {
|
||||
copyHeight = (copySize / inputWidth) * 8;
|
||||
} else {
|
||||
copyHeight = 0;
|
||||
}
|
||||
|
||||
// Find the source surface.
|
||||
auto srcFramebuffer = getColourBuffer(inputAddr, PICA::ColorFmt::RGBA8, copyStride, copyHeight, false);
|
||||
|
|
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;
|
|
@ -17,7 +17,10 @@ void ACTService::handleSyncRequest(u32 messagePointer) {
|
|||
case ACTCommands::GenerateUUID: generateUUID(messagePointer); break;
|
||||
case ACTCommands::GetAccountDataBlock: getAccountDataBlock(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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "kernel/kernel.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "ipc.hpp"
|
||||
#include "result/result.hpp"
|
||||
|
||||
#ifdef CreateFile // windows.h defines CreateFile & DeleteFile because of course it does.
|
||||
#undef CreateDirectory
|
||||
|
@ -25,13 +26,19 @@ namespace FSCommands {
|
|||
GetFreeBytes = 0x08120080,
|
||||
IsSdmcDetected = 0x08170000,
|
||||
IsSdmcWritable = 0x08180000,
|
||||
AbnegateAccessRight = 0x08400040,
|
||||
GetFormatInfo = 0x084500C2,
|
||||
GetArchiveResource = 0x08490040,
|
||||
FormatSaveData = 0x084C0242,
|
||||
CreateExtSaveData = 0x08510242,
|
||||
DeleteExtSaveData = 0x08520100,
|
||||
SetArchivePriority = 0x085A00C0,
|
||||
InitializeWithSdkVersion = 0x08610042,
|
||||
SetPriority = 0x08620040,
|
||||
GetPriority = 0x08630000
|
||||
GetPriority = 0x08630000,
|
||||
SetThisSaveDataSecureValue = 0x086E00C0,
|
||||
GetThisSaveDataSecureValue = 0x086F0040,
|
||||
TheGameboyVCFunction = 0x08750180,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -156,9 +163,11 @@ void FSService::handleSyncRequest(u32 messagePointer) {
|
|||
case FSCommands::DeleteFile: deleteFile(messagePointer); break;
|
||||
case FSCommands::FormatSaveData: formatSaveData(messagePointer); break;
|
||||
case FSCommands::FormatThisUserSaveData: formatThisUserSaveData(messagePointer); break;
|
||||
case FSCommands::GetArchiveResource: getArchiveResource(messagePointer); break;
|
||||
case FSCommands::GetFreeBytes: getFreeBytes(messagePointer); break;
|
||||
case FSCommands::GetFormatInfo: getFormatInfo(messagePointer); break;
|
||||
case FSCommands::GetPriority: getPriority(messagePointer); break;
|
||||
case FSCommands::GetThisSaveDataSecureValue: getThisSaveDataSecureValue(messagePointer); break;
|
||||
case FSCommands::Initialize: initialize(messagePointer); break;
|
||||
case FSCommands::InitializeWithSdkVersion: initializeWithSdkVersion(messagePointer); break;
|
||||
case FSCommands::IsSdmcDetected: isSdmcDetected(messagePointer); break;
|
||||
|
@ -167,7 +176,11 @@ void FSService::handleSyncRequest(u32 messagePointer) {
|
|||
case FSCommands::OpenDirectory: openDirectory(messagePointer); break;
|
||||
case FSCommands::OpenFile: [[likely]] openFile(messagePointer); break;
|
||||
case FSCommands::OpenFileDirectly: [[likely]] openFileDirectly(messagePointer); break;
|
||||
case FSCommands::SetArchivePriority: setArchivePriority(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);
|
||||
}
|
||||
}
|
||||
|
@ -340,7 +353,8 @@ void FSService::openFileDirectly(u32 messagePointer) {
|
|||
std::optional<Handle> handle = openFileHandle(archive, filePath, archivePath, perms);
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x803, 1, 2));
|
||||
if (!handle.has_value()) {
|
||||
Helpers::panic("OpenFileDirectly: Failed to open file with given path");
|
||||
printf("OpenFileDirectly failed\n");
|
||||
mem.write32(messagePointer + 4, Result::FS::FileNotFound);
|
||||
} else {
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write32(messagePointer + 12, handle.value());
|
||||
|
@ -575,6 +589,36 @@ void FSService::getPriority(u32 messagePointer) {
|
|||
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) {
|
||||
const u32 value = mem.read32(messagePointer + 4);
|
||||
log("FS::SetPriority (priority = %d)\n", value);
|
||||
|
@ -584,22 +628,60 @@ void FSService::setPriority(u32 messagePointer) {
|
|||
priority = value;
|
||||
}
|
||||
|
||||
void FSService::abnegateAccessRight(u32 messagePointer) {
|
||||
const u32 right = mem.read32(messagePointer + 4);
|
||||
log("FS::AbnegateAccessRight (right = %d)\n", right);
|
||||
|
||||
// Shows whether an SD card is inserted. At the moment stubbed to no
|
||||
constexpr bool sdInserted = false;
|
||||
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) {
|
||||
log("FS::IsSdmcDetected\n");
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x817, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write8(messagePointer + 8, sdInserted ? 1 : 0);
|
||||
mem.write8(messagePointer + 8, config.sdCardInserted ? 1 : 0);
|
||||
}
|
||||
|
||||
// We consider our SD card to always be writable if oen is inserted for now
|
||||
// So isSdmcWritable returns 1 if an SD card is inserted (because it's always writable) and 0 if not.
|
||||
// We consider our SD card to always be writable if one is inserted for now
|
||||
// However we do make sure to respect the configs and properly return the correct value here
|
||||
void FSService::isSdmcWritable(u32 messagePointer) {
|
||||
log("FS::isSdmcWritable\n");
|
||||
const bool writeProtected = (!config.sdCardInserted) || (config.sdCardInserted && config.sdWriteProtected);
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x818, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write8(messagePointer + 8, sdInserted ? 1 : 0);
|
||||
mem.write8(messagePointer + 8, writeProtected ? 0 : 1);
|
||||
}
|
|
@ -6,6 +6,8 @@
|
|||
namespace HTTPCommands {
|
||||
enum : u32 {
|
||||
Initialize = 0x00010044,
|
||||
CreateRootCertChain = 0x002D0000,
|
||||
RootCertChainAddDefaultCert = 0x00300080,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -14,7 +16,9 @@ void HTTPService::reset() { initialized = false; }
|
|||
void HTTPService::handleSyncRequest(u32 messagePointer) {
|
||||
const u32 command = mem.read32(messagePointer);
|
||||
switch (command) {
|
||||
case HTTPCommands::CreateRootCertChain: createRootCertChain(messagePointer); break;
|
||||
case HTTPCommands::Initialize: initialize(messagePointer); break;
|
||||
case HTTPCommands::RootCertChainAddDefaultCert: rootCertChainAddDefaultCert(messagePointer); break;
|
||||
default: Helpers::panic("HTTP service requested. Command: %08X\n", command);
|
||||
}
|
||||
}
|
||||
|
@ -39,4 +43,28 @@ void HTTPService::initialize(u32 messagePointer) {
|
|||
initialized = true;
|
||||
// We currently don't emulate HTTP properly. TODO: Prepare POST buffer here
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void HTTPService::createRootCertChain(u32 messagePointer) {
|
||||
log("HTTP::CreateRootCertChain (Unimplemented)\n");
|
||||
|
||||
// TODO: Verify response header
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x2D, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
|
||||
// RootCertChain context handle. No need to emulate this yet
|
||||
mem.write32(messagePointer + 8, 0x66666666);
|
||||
}
|
||||
|
||||
void HTTPService::rootCertChainAddDefaultCert(u32 messagePointer) {
|
||||
log("HTTP::RootCertChainAddDefaultCert (Unimplemented)\n");
|
||||
const u32 contextHandle = mem.read32(messagePointer + 4);
|
||||
const u32 certID = mem.read32(messagePointer + 8);
|
||||
|
||||
// TODO: Verify response header
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x30, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
|
||||
// Cert context handle. No need to emulate this yet
|
||||
mem.write32(messagePointer + 8, 0x66666666);
|
||||
}
|
|
@ -13,6 +13,7 @@ namespace MICCommands {
|
|||
SetGain = 0x00080040,
|
||||
GetGain = 0x00090000,
|
||||
SetPower = 0x000A0040,
|
||||
GetPower = 0x000B0000,
|
||||
SetIirFilter = 0x000C0042,
|
||||
SetClamp = 0x000D0040,
|
||||
CaptainToadFunction = 0x00100040,
|
||||
|
@ -33,6 +34,7 @@ void MICService::handleSyncRequest(u32 messagePointer) {
|
|||
switch (command) {
|
||||
case MICCommands::GetEventHandle: getEventHandle(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::SetClamp: setClamp(messagePointer); break;
|
||||
|
@ -101,6 +103,14 @@ void MICService::setPower(u32 messagePointer) {
|
|||
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) {
|
||||
u8 val = mem.read8(messagePointer + 4);
|
||||
log("MIC::SetClamp (value = %d)\n", val);
|
||||
|
|
|
@ -5,8 +5,10 @@
|
|||
namespace NFCCommands {
|
||||
enum : u32 {
|
||||
Initialize = 0x00010040,
|
||||
Shutdown = 0x00020040,
|
||||
StartCommunication = 0x00030000,
|
||||
StopCommunication = 0x00040000,
|
||||
StartTagScanning = 0x00050040,
|
||||
GetTagInRangeEvent = 0x000B0000,
|
||||
GetTagOutOfRangeEvent = 0x000C0000,
|
||||
GetTagState = 0x000D0000,
|
||||
|
@ -32,7 +34,9 @@ void NFCService::handleSyncRequest(u32 messagePointer) {
|
|||
case NFCCommands::GetTagInRangeEvent: getTagInRangeEvent(messagePointer); break;
|
||||
case NFCCommands::GetTagOutOfRangeEvent: getTagOutOfRangeEvent(messagePointer); break;
|
||||
case NFCCommands::GetTagState: getTagState(messagePointer); break;
|
||||
case NFCCommands::Shutdown: shutdown(messagePointer); break;
|
||||
case NFCCommands::StartCommunication: startCommunication(messagePointer); break;
|
||||
case NFCCommands::StartTagScanning: startTagScanning(messagePointer); break;
|
||||
case NFCCommands::StopCommunication: stopCommunication(messagePointer); break;
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
log("NFC::StopCommunication\n");
|
||||
adapterStatus = Old3DSAdapterStatus::InitializationComplete;
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace PTMCommands {
|
|||
enum : u32 {
|
||||
GetAdapterState = 0x00050000,
|
||||
GetBatteryLevel = 0x00070000,
|
||||
GetBatteryChargeState = 0x00080000,
|
||||
GetStepHistory = 0x000B00C2,
|
||||
GetTotalStepCount = 0x000C0000,
|
||||
ConfigureNew3DSCPU = 0x08180040,
|
||||
|
@ -18,6 +19,7 @@ void PTMService::handleSyncRequest(u32 messagePointer) {
|
|||
switch (command) {
|
||||
case PTMCommands::ConfigureNew3DSCPU: configureNew3DSCPU(messagePointer); break;
|
||||
case PTMCommands::GetAdapterState: getAdapterState(messagePointer); break;
|
||||
case PTMCommands::GetBatteryChargeState: getBatteryChargeState(messagePointer); break;
|
||||
case PTMCommands::GetBatteryLevel: getBatteryLevel(messagePointer); break;
|
||||
case PTMCommands::GetStepHistory: getStepHistory(messagePointer); break;
|
||||
case PTMCommands::GetTotalStepCount: getTotalStepCount(messagePointer); break;
|
||||
|
@ -33,6 +35,16 @@ void PTMService::getAdapterState(u32 messagePointer) {
|
|||
mem.write8(messagePointer + 8, config.chargerPlugged ? 1 : 0);
|
||||
}
|
||||
|
||||
void PTMService::getBatteryChargeState(u32 messagePointer) {
|
||||
log("PTM::GetBatteryChargeState");
|
||||
// We're only charging if the battery is not already full
|
||||
const bool charging = config.chargerPlugged && (config.batteryPercentage < 100);
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x7, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write8(messagePointer + 8, charging ? 1 : 0);
|
||||
}
|
||||
|
||||
void PTMService::getBatteryLevel(u32 messagePointer) {
|
||||
log("PTM::GetBatteryLevel");
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
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),
|
||||
dlp_srvr(mem), dsp(mem, kernel), hid(mem, kernel), http(mem), ir_user(mem, kernel), frd(mem), fs(mem, kernel),
|
||||
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, kernel), nfc(mem, kernel), nim(mem), ndm(mem),
|
||||
news_u(mem), ptm(mem, config), soc(mem), ssl(mem), y2r(mem, kernel) {}
|
||||
|
||||
|
@ -79,6 +79,7 @@ void ServiceManager::handleSyncRequest(u32 messagePointer) {
|
|||
case Commands::RegisterClient: registerClient(messagePointer); break;
|
||||
case Commands::GetServiceHandle: getServiceHandle(messagePointer); break;
|
||||
case Commands::Subscribe: subscribe(messagePointer); break;
|
||||
case Commands::Unsubscribe: unsubscribe(messagePointer); break;
|
||||
default: Helpers::panic("Unknown \"srv:\" command: %08X", header);
|
||||
}
|
||||
}
|
||||
|
@ -178,6 +179,14 @@ void ServiceManager::subscribe(u32 messagePointer) {
|
|||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void ServiceManager::unsubscribe(u32 messagePointer) {
|
||||
u32 id = mem.read32(messagePointer + 4);
|
||||
log("srv::Unsubscribe (id = %d) (stubbed)\n", id);
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0xA, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
||||
void ServiceManager::sendCommandToService(u32 messagePointer, Handle handle) {
|
||||
switch (handle) {
|
||||
// Breaking alphabetical order a bit to place the ones I think are most common at the top
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "services/y2r.hpp"
|
||||
|
||||
#include "ipc.hpp"
|
||||
#include "kernel.hpp"
|
||||
|
||||
|
@ -6,8 +7,10 @@ namespace Y2RCommands {
|
|||
enum : u32 {
|
||||
SetInputFormat = 0x00010040,
|
||||
SetOutputFormat = 0x00030040,
|
||||
GetOutputFormat = 0x00040000,
|
||||
SetRotation = 0x00050040,
|
||||
SetBlockAlignment = 0x00070040,
|
||||
GetBlockAlignment = 0x00080000,
|
||||
SetSpacialDithering = 0x00090040,
|
||||
SetTemporalDithering = 0x000B0040,
|
||||
SetTransferEndInterrupt = 0x000D0040,
|
||||
|
@ -17,7 +20,9 @@ namespace Y2RCommands {
|
|||
SetSendingV = 0x00120102,
|
||||
SetReceiving = 0x00180102,
|
||||
SetInputLineWidth = 0x001A0040,
|
||||
GetInputLineWidth = 0x001B0000,
|
||||
SetInputLines = 0x001C0040,
|
||||
GetInputLines = 0x001D0000,
|
||||
SetStandardCoeff = 0x00200040,
|
||||
SetAlpha = 0x00220040,
|
||||
StartConversion = 0x00260000,
|
||||
|
@ -26,7 +31,7 @@ namespace Y2RCommands {
|
|||
SetPackageParameter = 0x002901C0,
|
||||
PingProcess = 0x002A0000,
|
||||
DriverInitialize = 0x002B0000,
|
||||
DriverFinalize = 0x002C0000
|
||||
DriverFinalize = 0x002C0000,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -52,6 +57,10 @@ void Y2RService::handleSyncRequest(u32 messagePointer) {
|
|||
switch (command) {
|
||||
case Y2RCommands::DriverInitialize: driverInitialize(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::IsBusyConversion: isBusyConversion(messagePointer); break;
|
||||
case Y2RCommands::PingProcess: pingProcess(messagePointer); break;
|
||||
|
@ -81,7 +90,7 @@ void Y2RService::pingProcess(u32 messagePointer) {
|
|||
log("Y2R::PingProcess\n");
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x2A, 2, 0));
|
||||
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) {
|
||||
|
@ -98,8 +107,9 @@ void Y2RService::driverFinalize(u32 messagePointer) {
|
|||
|
||||
void Y2RService::getTransferEndEvent(u32 messagePointer) {
|
||||
log("Y2R::GetTransferEndEvent\n");
|
||||
if (!transferEndEvent.has_value())
|
||||
if (!transferEndEvent.has_value()) {
|
||||
transferEndEvent = kernel.makeEvent(ResetType::OneShot);
|
||||
}
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0xF, 1, 2));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
|
@ -150,6 +160,14 @@ void Y2RService::setBlockAlignment(u32 messagePointer) {
|
|||
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) {
|
||||
const u32 format = mem.read32(messagePointer + 4);
|
||||
log("Y2R::SetInputFormat (format = %d)\n", format);
|
||||
|
@ -178,6 +196,14 @@ void Y2RService::setOutputFormat(u32 messagePointer) {
|
|||
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) {
|
||||
// Package parameter is 3 words
|
||||
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) {
|
||||
const u16 lines = mem.read16(messagePointer + 4);
|
||||
log("Y2R::SetInputLines (lines = %d)\n", lines);
|
||||
|
@ -253,19 +286,30 @@ void Y2RService::setInputLines(u32 messagePointer) {
|
|||
Helpers::panic("Y2R: Invalid input line count");
|
||||
} else {
|
||||
// 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;
|
||||
}
|
||||
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) {
|
||||
const u32 coeff = mem.read32(messagePointer + 4);
|
||||
log("Y2R::SetStandardCoeff (coefficient = %d)\n", coeff);
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x20, 1, 0));
|
||||
|
||||
if (coeff > 3)
|
||||
Helpers::panic("Y2R: Invalid standard coefficient");
|
||||
if (coeff > 3) {
|
||||
Helpers::panic("Y2R: Invalid standard coefficient (coefficient = %d)\n", coeff);
|
||||
}
|
||||
|
||||
else {
|
||||
Helpers::warn("Unimplemented: Y2R standard coefficient");
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
|
@ -316,4 +360,4 @@ void Y2RService::startConversion(u32 messagePointer) {
|
|||
if (transferEndEvent.has_value()) {
|
||||
kernel.signalEvent(transferEndEvent.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -426,6 +426,10 @@ bool Emulator::loadROM(const std::filesystem::path& path) {
|
|||
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)
|
||||
// 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
|
||||
|
@ -455,6 +459,8 @@ bool Emulator::loadROM(const std::filesystem::path& path) {
|
|||
success = loadNCSD(path, ROMType::NCSD);
|
||||
else if (extension == ".cxi" || extension == ".app")
|
||||
success = loadNCSD(path, ROMType::CXI);
|
||||
else if (extension == ".3dsx")
|
||||
success = load3DSX(path);
|
||||
else {
|
||||
printf("Unknown file type\n");
|
||||
success = false;
|
||||
|
@ -494,6 +500,19 @@ bool Emulator::loadNCSD(const std::filesystem::path& path, ROMType type) {
|
|||
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) {
|
||||
loadedELF.open(path, std::ios_base::binary); // Open ROM in binary mode
|
||||
romType = ROMType::ELF;
|
||||
|
|
|
@ -345,7 +345,7 @@ void main() {
|
|||
if ((textureConfig & 2u) != 0u) tevSources[4] = texture(u_tex1, v_texcoord1);
|
||||
if ((textureConfig & 4u) != 0u) tevSources[5] = texture(u_tex2, tex2UV);
|
||||
tevSources[13] = vec4(0.0); // Previous buffer
|
||||
tevSources[15] = vec4(0.0); // Previous combiner
|
||||
tevSources[15] = v_colour; // Previous combiner
|
||||
|
||||
tevNextPreviousBuffer = v_textureEnvBufferColor;
|
||||
uint textureEnvUpdateBuffer = readPicaReg(0xE0);
|
||||
|
|
|
@ -64,7 +64,8 @@ float decodeFP(uint hex, uint E, uint M) {
|
|||
|
||||
void main() {
|
||||
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
|
||||
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
|
||||
gl_ClipDistance[0] = -a_coords.z;
|
||||
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