mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-12 09:09:47 +12:00
Merge branch 'master' into open-bp-cpp
This commit is contained in:
commit
13ee3a1bae
37 changed files with 5520 additions and 92 deletions
65
.github/workflows/Hydra_Build.yml
vendored
65
.github/workflows/Hydra_Build.yml
vendored
|
@ -32,12 +32,27 @@ jobs:
|
|||
- name: Build
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Upload core
|
||||
uses: actions/upload-artifact@v2
|
||||
- name: Upload Hydra core
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Windows core
|
||||
name: Windows Hydra core
|
||||
path: '${{github.workspace}}/build/${{ env.BUILD_TYPE }}/Alber.dll'
|
||||
|
||||
- name: Configure CMake (Again)
|
||||
run: |
|
||||
rm -r -fo ${{github.workspace}}/build
|
||||
cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_USER_BUILD=ON -DBUILD_LIBRETRO_CORE=ON
|
||||
|
||||
- name: Build (Again)
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Upload Libretro core
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Windows Libretro core
|
||||
path: |
|
||||
${{github.workspace}}/build/panda3ds_libretro.dll
|
||||
${{github.workspace}}/docs/libretro/panda3ds_libretro.info
|
||||
|
||||
MacOS:
|
||||
runs-on: macos-13
|
||||
|
@ -61,11 +76,27 @@ jobs:
|
|||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Upload core
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: MacOS core
|
||||
name: MacOS Hydra core
|
||||
path: '${{github.workspace}}/build/libAlber.dylib'
|
||||
|
||||
- name: Configure CMake (Again)
|
||||
run: |
|
||||
rm -rf ${{github.workspace}}/build
|
||||
cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_USER_BUILD=ON -DBUILD_LIBRETRO_CORE=ON
|
||||
|
||||
- name: Build (Again)
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} && ls -R ${{github.workspace}}/build
|
||||
|
||||
- name: Upload Libretro core
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: MacOS Libretro core
|
||||
path: |
|
||||
${{github.workspace}}/build/panda3ds_libretro.dylib
|
||||
${{github.workspace}}/docs/libretro/panda3ds_libretro.info
|
||||
|
||||
Linux:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
@ -98,11 +129,27 @@ jobs:
|
|||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Upload core
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Linux core
|
||||
name: Linux Hydra core
|
||||
path: '${{github.workspace}}/build/libAlber.so'
|
||||
|
||||
- name: Configure CMake (Again)
|
||||
run: |
|
||||
rm -rf ${{github.workspace}}/build
|
||||
cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17 -DENABLE_USER_BUILD=ON -DBUILD_LIBRETRO_CORE=ON
|
||||
|
||||
- name: Build (Again)
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Upload Libretro core
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Linux Libretro core
|
||||
path: |
|
||||
${{github.workspace}}/build/panda3ds_libretro.so
|
||||
${{github.workspace}}/docs/libretro/panda3ds_libretro.info
|
||||
|
||||
Android-x64:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
@ -129,7 +176,7 @@ jobs:
|
|||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Upload core
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Android core
|
||||
name: Android Hydra core
|
||||
path: '${{github.workspace}}/build/libAlber.so'
|
||||
|
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -73,3 +73,6 @@
|
|||
[submodule "third_party/open-bp-cpp/third_party/IXWebSocket"]
|
||||
path = third_party/open-bp-cpp/third_party/IXWebSocket
|
||||
url = https://github.com/machinezone/IXWebSocket
|
||||
[submodule "third_party/hips"]
|
||||
path = third_party/hips
|
||||
url = https://github.com/wheremyfoodat/Hips
|
|
@ -40,11 +40,17 @@ option(ENABLE_DISCORD_RPC "Compile with Discord RPC support (disabled by default
|
|||
option(ENABLE_LUAJIT "Enable scripting with the Lua programming language" ON)
|
||||
option(ENABLE_QT_GUI "Enable the Qt GUI. If not selected then the emulator uses a minimal SDL-based UI instead" OFF)
|
||||
option(BUILD_HYDRA_CORE "Build a Hydra core" OFF)
|
||||
option(BUILD_LIBRETRO_CORE "Build a Libretro core" OFF)
|
||||
|
||||
if(BUILD_HYDRA_CORE)
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
|
||||
if(BUILD_LIBRETRO_CORE)
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
add_compile_definitions(__LIBRETRO__)
|
||||
endif()
|
||||
|
||||
add_library(AlberCore STATIC)
|
||||
|
||||
include_directories(${PROJECT_SOURCE_DIR}/include/)
|
||||
|
@ -52,6 +58,7 @@ include_directories(${PROJECT_SOURCE_DIR}/include/kernel)
|
|||
include_directories(${FMT_INCLUDE_DIR})
|
||||
include_directories(third_party/boost/)
|
||||
include_directories(third_party/elfio/)
|
||||
include_directories(third_party/hips/include/)
|
||||
include_directories(third_party/imgui/)
|
||||
include_directories(third_party/dynarmic/src)
|
||||
include_directories(third_party/cryptopp/)
|
||||
|
@ -342,8 +349,8 @@ endif()
|
|||
|
||||
if(ENABLE_VULKAN)
|
||||
find_package(
|
||||
Vulkan 1.3.206 REQUIRED
|
||||
COMPONENTS glslangValidator
|
||||
Vulkan REQUIRED
|
||||
COMPONENTS glslang
|
||||
)
|
||||
|
||||
set(RENDERER_VK_INCLUDE_FILES include/renderer_vk/renderer_vk.hpp
|
||||
|
@ -386,7 +393,7 @@ if(ENABLE_VULKAN)
|
|||
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}
|
||||
COMMAND glslang ${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} )
|
||||
|
@ -442,7 +449,7 @@ else()
|
|||
target_compile_definitions(AlberCore PUBLIC "PANDA3DS_FRONTEND_SDL=1")
|
||||
endif()
|
||||
|
||||
if(NOT BUILD_HYDRA_CORE)
|
||||
if(NOT BUILD_HYDRA_CORE AND NOT BUILD_LIBRETRO_CORE)
|
||||
add_executable(Alber)
|
||||
|
||||
if(ENABLE_QT_GUI)
|
||||
|
@ -453,9 +460,11 @@ if(NOT BUILD_HYDRA_CORE)
|
|||
|
||||
set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp src/panda_qt/screen.cpp src/panda_qt/main_window.cpp src/panda_qt/about_window.cpp
|
||||
src/panda_qt/config_window.cpp src/panda_qt/zep.cpp src/panda_qt/text_editor.cpp src/panda_qt/cheats_window.cpp src/panda_qt/mappings.cpp
|
||||
)
|
||||
src/panda_qt/patch_window.cpp src/panda_qt/elided_label.cpp src/panda_qt/shader_editor.cpp
|
||||
)
|
||||
set(FRONTEND_HEADER_FILES include/panda_qt/screen.hpp include/panda_qt/main_window.hpp include/panda_qt/about_window.hpp
|
||||
include/panda_qt/config_window.hpp include/panda_qt/text_editor.hpp include/panda_qt/cheats_window.hpp
|
||||
include/panda_qt/patch_window.hpp include/panda_qt/elided_label.hpp include/panda_qt/shader_editor.hpp
|
||||
)
|
||||
|
||||
source_group("Source Files\\Qt" FILES ${FRONTEND_SOURCE_FILES})
|
||||
|
@ -488,7 +497,7 @@ if(NOT BUILD_HYDRA_CORE)
|
|||
qt_add_resources(AlberCore "app_images"
|
||||
PREFIX "/"
|
||||
FILES
|
||||
docs/img/rsob_icon.png docs/img/rstarstruck_icon.png
|
||||
docs/img/rsob_icon.png docs/img/rstarstruck_icon.png docs/img/rpog_icon.png
|
||||
)
|
||||
else()
|
||||
set(FRONTEND_SOURCE_FILES src/panda_sdl/main.cpp src/panda_sdl/frontend_sdl.cpp src/panda_sdl/mappings.cpp)
|
||||
|
@ -502,6 +511,15 @@ elseif(BUILD_HYDRA_CORE)
|
|||
include_directories(third_party/hydra_core/include)
|
||||
add_library(Alber SHARED src/hydra_core.cpp)
|
||||
target_link_libraries(Alber PUBLIC AlberCore)
|
||||
elseif(BUILD_LIBRETRO_CORE)
|
||||
include_directories(third_party/libretro/include)
|
||||
add_library(Alber SHARED src/libretro_core.cpp)
|
||||
target_link_libraries(Alber PUBLIC AlberCore)
|
||||
|
||||
set_target_properties(Alber PROPERTIES
|
||||
OUTPUT_NAME "panda3ds_libretro"
|
||||
PREFIX ""
|
||||
)
|
||||
endif()
|
||||
|
||||
if(ENABLE_LTO OR ENABLE_USER_BUILD)
|
||||
|
|
BIN
docs/img/rpog_icon.png
Normal file
BIN
docs/img/rpog_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
34
docs/libretro/panda3ds_libretro.info
Normal file
34
docs/libretro/panda3ds_libretro.info
Normal file
|
@ -0,0 +1,34 @@
|
|||
# Software Information
|
||||
display_name = "Nintendo - 3DS (Panda3DS)"
|
||||
authors = "Panda3DS Authors (tm)"
|
||||
supported_extensions = "3ds|3dsx|elf|axf|cci|cxi|app"
|
||||
corename = "Panda3DS"
|
||||
categories = "Emulator"
|
||||
license = "GPLv3"
|
||||
permissions = ""
|
||||
display_version = "Git"
|
||||
|
||||
# Hardware Information
|
||||
manufacturer = "Nintendo"
|
||||
systemname = "3DS"
|
||||
systemid = "3ds"
|
||||
|
||||
# Libretro Information
|
||||
database = "Nintendo - Nintendo 3DS"
|
||||
supports_no_game = "false"
|
||||
savestate = "true"
|
||||
savestate_features = "basic"
|
||||
cheats = "false"
|
||||
input_descriptors = "true"
|
||||
memory_descriptors = "false"
|
||||
libretro_saves = "true"
|
||||
core_options = "true"
|
||||
core_options_version = "1.0"
|
||||
load_subsystem = "false"
|
||||
hw_render = "true"
|
||||
required_hw_api = "OpenGL Core >= 4.1"
|
||||
needs_fullpath = "true"
|
||||
disk_control = "false"
|
||||
is_experimental = "true"
|
||||
|
||||
description = "Panda3DS !"
|
|
@ -176,6 +176,7 @@ namespace Audio {
|
|||
// Decode an entire buffer worth of audio
|
||||
void decodeBuffer(DSPSource& source);
|
||||
|
||||
SampleBuffer decodePCM8(const u8* data, usize sampleCount, Source& source);
|
||||
SampleBuffer decodePCM16(const u8* data, usize sampleCount, Source& source);
|
||||
SampleBuffer decodeADPCM(const u8* data, usize sampleCount, Source& source);
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
// Remember to initialize every field here to its default value otherwise bad things will happen
|
||||
struct EmulatorConfig {
|
||||
// Only enable the shader JIT by default on platforms where it's completely tested
|
||||
#ifdef PANDA3DS_X64_HOST
|
||||
#if defined(PANDA3DS_X64_HOST) || defined(PANDA3DS_ARM64_HOST)
|
||||
static constexpr bool shaderJitDefault = true;
|
||||
#else
|
||||
static constexpr bool shaderJitDefault = false;
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "services/service_manager.hpp"
|
||||
|
||||
class CPU;
|
||||
struct Scheduler;
|
||||
|
||||
class Kernel {
|
||||
std::span<u32, 16> regs;
|
||||
|
@ -243,6 +244,7 @@ public:
|
|||
}
|
||||
|
||||
ServiceManager& getServiceManager() { return serviceManager; }
|
||||
Scheduler& getScheduler();
|
||||
|
||||
void sendGPUInterrupt(GPUInterrupt type) { serviceManager.sendGPUInterrupt(type); }
|
||||
void clearInstructionCache();
|
||||
|
|
|
@ -60,6 +60,8 @@ struct NCCH {
|
|||
CodeSetInfo text, data, rodata;
|
||||
FSInfo partitionInfo;
|
||||
|
||||
std::optional<Crypto::AESKey> primaryKey, secondaryKey;
|
||||
|
||||
// Contents of the .code file in the ExeFS
|
||||
std::vector<u8> codeFile;
|
||||
// Contains of the cart's save data
|
||||
|
|
21
include/panda_qt/elided_label.hpp
Normal file
21
include/panda_qt/elided_label.hpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
#include <QFontMetrics>
|
||||
#include <QLabel>
|
||||
#include <QString>
|
||||
#include <QWidget>
|
||||
|
||||
class ElidedLabel : public QLabel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ElidedLabel(Qt::TextElideMode elideMode = Qt::ElideLeft, QWidget* parent = nullptr);
|
||||
explicit ElidedLabel(QString text, Qt::TextElideMode elideMode = Qt::ElideLeft, QWidget* parent = nullptr);
|
||||
void setText(QString text);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent* event);
|
||||
|
||||
private:
|
||||
void updateText();
|
||||
QString m_text;
|
||||
Qt::TextElideMode m_elideMode;
|
||||
};
|
|
@ -17,7 +17,9 @@
|
|||
#include "panda_qt/about_window.hpp"
|
||||
#include "panda_qt/cheats_window.hpp"
|
||||
#include "panda_qt/config_window.hpp"
|
||||
#include "panda_qt/patch_window.hpp"
|
||||
#include "panda_qt/screen.hpp"
|
||||
#include "panda_qt/shader_editor.hpp"
|
||||
#include "panda_qt/text_editor.hpp"
|
||||
#include "services/hid.hpp"
|
||||
|
||||
|
@ -47,6 +49,7 @@ class MainWindow : public QMainWindow {
|
|||
EditCheat,
|
||||
PressTouchscreen,
|
||||
ReleaseTouchscreen,
|
||||
ReloadUbershader,
|
||||
};
|
||||
|
||||
// Tagged union representing our message queue messages
|
||||
|
@ -90,13 +93,15 @@ class MainWindow : public QMainWindow {
|
|||
std::mutex messageQueueMutex;
|
||||
std::vector<EmulatorMessage> messageQueue;
|
||||
|
||||
QMenuBar* menuBar = nullptr;
|
||||
InputMappings keyboardMappings;
|
||||
ScreenWidget screen;
|
||||
AboutWindow* aboutWindow;
|
||||
ConfigWindow* configWindow;
|
||||
CheatsWindow* cheatsEditor;
|
||||
TextEditorWindow* luaEditor;
|
||||
QMenuBar* menuBar = nullptr;
|
||||
PatchWindow* patchWindow;
|
||||
ShaderEditorWindow* shaderEditor;
|
||||
|
||||
// We use SDL's game controller API since it's the sanest API that supports as many controllers as possible
|
||||
SDL_GameController* gameController = nullptr;
|
||||
|
@ -108,8 +113,6 @@ class MainWindow : public QMainWindow {
|
|||
void selectROM();
|
||||
void dumpDspFirmware();
|
||||
void dumpRomFS();
|
||||
void openLuaEditor();
|
||||
void openCheatsEditor();
|
||||
void showAboutMenu();
|
||||
void initControllers();
|
||||
void pollControllers();
|
||||
|
@ -136,5 +139,6 @@ class MainWindow : public QMainWindow {
|
|||
void mouseReleaseEvent(QMouseEvent* event) override;
|
||||
|
||||
void loadLuaScript(const std::string& code);
|
||||
void reloadShader(const std::string& shader);
|
||||
void editCheat(u32 handle, const std::vector<uint8_t>& cheat, const std::function<void(u32)>& callback);
|
||||
};
|
||||
|
|
31
include/panda_qt/patch_window.hpp
Normal file
31
include/panda_qt/patch_window.hpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QWidget>
|
||||
#include <filesystem>
|
||||
|
||||
#include "panda_qt/elided_label.hpp"
|
||||
|
||||
class PatchWindow final : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PatchWindow(QWidget* parent = nullptr);
|
||||
~PatchWindow() = default;
|
||||
|
||||
private:
|
||||
// Show a message box
|
||||
// Title: Title of the message box to display
|
||||
// Message: Message to display
|
||||
// Icon: The type of icon (error, warning, information, etc) to display
|
||||
// IconPath: If non-null, then a path to an icon in our assets to display on the OK button
|
||||
void displayMessage(
|
||||
const QString& title, const QString& message, QMessageBox::Icon icon = QMessageBox::Icon::Warning, const char* iconPath = nullptr
|
||||
);
|
||||
|
||||
std::filesystem::path inputPath = "";
|
||||
std::filesystem::path patchPath = "";
|
||||
|
||||
ElidedLabel* inputPathLabel = nullptr;
|
||||
ElidedLabel* patchPathLabel = nullptr;
|
||||
};
|
27
include/panda_qt/shader_editor.hpp
Normal file
27
include/panda_qt/shader_editor.hpp
Normal file
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDialog>
|
||||
#include <QWidget>
|
||||
#include <string>
|
||||
|
||||
#include "zep.h"
|
||||
#include "zep/mode_repl.h"
|
||||
#include "zep/regress.h"
|
||||
|
||||
class ShaderEditorWindow : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
Zep::ZepWidget_Qt zepWidget;
|
||||
Zep::IZepReplProvider replProvider;
|
||||
static constexpr float fontSize = 14.0f;
|
||||
|
||||
public:
|
||||
// Whether this backend supports shader editor
|
||||
bool supported = true;
|
||||
|
||||
ShaderEditorWindow(QWidget* parent, const std::string& filename, const std::string& initialText);
|
||||
void setText(const std::string& text) { zepWidget.GetEditor().GetMRUBuffer()->SetText(text); }
|
||||
void setEnable(bool enable);
|
||||
};
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
#include "PICA/pica_vertex.hpp"
|
||||
|
@ -66,6 +67,13 @@ class Renderer {
|
|||
// This function does things like write back or cache necessary state before we delete our context
|
||||
virtual void deinitGraphicsContext() = 0;
|
||||
|
||||
// Functions for hooking up the renderer core to the frontend's shader editor for editing ubershaders in real time
|
||||
// SupportsShaderReload: Indicates whether the backend offers ubershader reload support or not
|
||||
// GetUbershader/SetUbershader: Gets or sets the renderer's current ubershader
|
||||
virtual bool supportsShaderReload() { return false; }
|
||||
virtual std::string getUbershader() { return ""; }
|
||||
virtual void setUbershader(const std::string& shader) {}
|
||||
|
||||
// Functions for initializing the graphics context for the Qt frontend, where we don't have the convenience of SDL_Window
|
||||
#ifdef PANDA3DS_FRONTEND_QT
|
||||
virtual void initGraphicsContext(GL::Context* context) { Helpers::panic("Tried to initialize incompatible renderer with GL context"); }
|
||||
|
|
|
@ -82,12 +82,17 @@ class RendererGL final : public Renderer {
|
|||
void textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override;
|
||||
void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) override; // Draw the given vertices
|
||||
void deinitGraphicsContext() override;
|
||||
|
||||
|
||||
virtual bool supportsShaderReload() override { return true; }
|
||||
virtual std::string getUbershader() override;
|
||||
virtual void setUbershader(const std::string& shader) override;
|
||||
|
||||
std::optional<ColourBuffer> getColourBuffer(u32 addr, PICA::ColorFmt format, u32 width, u32 height, bool createIfnotFound = true);
|
||||
|
||||
// Note: The caller is responsible for deleting the currently bound FBO before calling this
|
||||
void setFBO(uint handle) { screenFramebuffer.m_handle = handle; }
|
||||
void resetStateManager() { gl.reset(); }
|
||||
void initUbershader(OpenGL::Program& program);
|
||||
|
||||
#ifdef PANDA3DS_FRONTEND_QT
|
||||
virtual void initGraphicsContext([[maybe_unused]] GL::Context* context) override { initGraphicsContextInternal(); }
|
||||
|
|
|
@ -11,7 +11,8 @@ struct Scheduler {
|
|||
VBlank = 0, // End of frame event
|
||||
UpdateTimers = 1, // Update kernel timer objects
|
||||
RunDSP = 2, // Make the emulated DSP run for one audio frame
|
||||
Panic = 3, // Dummy event that is always pending and should never be triggered (Timestamp = UINT64_MAX)
|
||||
SignalY2R = 3, // Signal that a Y2R conversion has finished
|
||||
Panic = 4, // Dummy event that is always pending and should never be triggered (Timestamp = UINT64_MAX)
|
||||
TotalNumberOfEvents // How many event types do we have in total?
|
||||
};
|
||||
static constexpr usize totalNumberOfEvents = static_cast<usize>(EventType::TotalNumberOfEvents);
|
||||
|
|
|
@ -109,4 +109,5 @@ class ServiceManager {
|
|||
HIDService& getHID() { return hid; }
|
||||
NFCService& getNFC() { return nfc; }
|
||||
DSPService& getDSP() { return dsp; }
|
||||
Y2RService& getY2R() { return y2r; }
|
||||
};
|
||||
|
|
|
@ -113,8 +113,12 @@ class Y2RService {
|
|||
void startConversion(u32 messagePointer);
|
||||
void stopConversion(u32 messagePointer);
|
||||
|
||||
public:
|
||||
bool isBusy;
|
||||
|
||||
public:
|
||||
Y2RService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
|
||||
void reset();
|
||||
void handleSyncRequest(u32 messagePointer);
|
||||
|
||||
void signalConversionDone();
|
||||
};
|
|
@ -144,8 +144,8 @@ void ShaderEmitter::compileInstruction(const PICAShader& shaderUnit) {
|
|||
case ShaderOpcodes::CMP2: recCMP(shaderUnit, instruction); break;
|
||||
case ShaderOpcodes::DP3: recDP3(shaderUnit, instruction); break;
|
||||
case ShaderOpcodes::DP4: recDP4(shaderUnit, instruction); break;
|
||||
// case ShaderOpcodes::DPH:
|
||||
// case ShaderOpcodes::DPHI: recDPH(shaderUnit, instruction); break;
|
||||
case ShaderOpcodes::DPH:
|
||||
case ShaderOpcodes::DPHI: recDPH(shaderUnit, instruction); break;
|
||||
case ShaderOpcodes::END: recEND(shaderUnit, instruction); break;
|
||||
case ShaderOpcodes::EX2: recEX2(shaderUnit, instruction); break;
|
||||
case ShaderOpcodes::FLR: recFLR(shaderUnit, instruction); break;
|
||||
|
@ -533,6 +533,39 @@ void ShaderEmitter::recDP4(const PICAShader& shader, u32 instruction) {
|
|||
storeRegister(src1Vec, shader, dest, operandDescriptor);
|
||||
}
|
||||
|
||||
void ShaderEmitter::recDPH(const PICAShader& shader, u32 instruction) {
|
||||
const bool isDPHI = (instruction >> 26) == ShaderOpcodes::DPHI;
|
||||
|
||||
const u32 operandDescriptor = shader.operandDescriptors[instruction & 0x7f];
|
||||
const u32 src1 = isDPHI ? getBits<14, 5>(instruction) : getBits<12, 7>(instruction);
|
||||
const u32 src2 = isDPHI ? getBits<7, 7>(instruction) : getBits<7, 5>(instruction);
|
||||
const u32 idx = getBits<19, 2>(instruction);
|
||||
const u32 dest = getBits<21, 5>(instruction);
|
||||
const u32 writeMask = getBits<0, 4>(operandDescriptor);
|
||||
|
||||
// TODO: Safe multiplication equivalent (Multiplication is not IEEE compliant on the PICA)
|
||||
loadRegister<1>(src1Vec, shader, src1, isDPHI ? 0 : idx, operandDescriptor);
|
||||
loadRegister<2>(src2Vec, shader, src2, isDPHI ? idx : 0, operandDescriptor);
|
||||
// // Attach 1.0 to the w component of src1
|
||||
MOV(src1Vec.Selem()[3], onesVector.Selem()[0]);
|
||||
|
||||
// Now perform a DP4
|
||||
// Do a piecewise multiplication of the vectors first
|
||||
if constexpr (useSafeMUL) {
|
||||
emitSafeMUL(src1Vec, src2Vec, scratch1Vec);
|
||||
} else {
|
||||
FMUL(src1Vec.S4(), src1Vec.S4(), src2Vec.S4());
|
||||
}
|
||||
FADDP(src1Vec.S4(), src1Vec.S4(), src1Vec.S4()); // Now add the adjacent components together
|
||||
FADDP(src1Vec.toS(), src1Vec.toD().S2()); // Again for the bottom 2 lanes. Now the bottom lane contains the dot product
|
||||
|
||||
if (writeMask != 0x8) { // Copy bottom lane to all lanes if we're not simply writing back x
|
||||
DUP(src1Vec.S4(), src1Vec.Selem()[0]); // src1Vec = src1Vec.xxxx
|
||||
}
|
||||
|
||||
storeRegister(src1Vec, shader, dest, operandDescriptor);
|
||||
}
|
||||
|
||||
oaknut::Label ShaderEmitter::emitLog2Func() {
|
||||
oaknut::Label funcStart;
|
||||
|
||||
|
|
|
@ -355,7 +355,7 @@ namespace Audio {
|
|||
}
|
||||
|
||||
switch (buffer.format) {
|
||||
case SampleFormat::PCM8: Helpers::warn("Unimplemented sample format!"); break;
|
||||
case SampleFormat::PCM8: source.currentSamples = decodePCM8(data, buffer.sampleCount, source); break;
|
||||
case SampleFormat::PCM16: source.currentSamples = decodePCM16(data, buffer.sampleCount, source); break;
|
||||
case SampleFormat::ADPCM: source.currentSamples = decodeADPCM(data, buffer.sampleCount, source); break;
|
||||
|
||||
|
@ -406,6 +406,26 @@ namespace Audio {
|
|||
}
|
||||
}
|
||||
|
||||
HLE_DSP::SampleBuffer HLE_DSP::decodePCM8(const u8* data, usize sampleCount, Source& source) {
|
||||
SampleBuffer decodedSamples(sampleCount);
|
||||
|
||||
if (source.sourceType == SourceType::Stereo) {
|
||||
for (usize i = 0; i < sampleCount; i++) {
|
||||
const s16 left = s16(u16(*data++) << 8);
|
||||
const s16 right = s16(u16(*data++) << 8);
|
||||
decodedSamples[i] = {left, right};
|
||||
}
|
||||
} else {
|
||||
// Mono
|
||||
for (usize i = 0; i < sampleCount; i++) {
|
||||
const s16 sample = s16(u16(*data++) << 8);
|
||||
decodedSamples[i] = {sample, sample};
|
||||
}
|
||||
}
|
||||
|
||||
return decodedSamples;
|
||||
}
|
||||
|
||||
HLE_DSP::SampleBuffer HLE_DSP::decodePCM16(const u8* data, usize sampleCount, Source& source) {
|
||||
SampleBuffer decodedSamples(sampleCount);
|
||||
const s16* data16 = reinterpret_cast<const s16*>(data);
|
||||
|
|
|
@ -39,7 +39,35 @@ HorizonResult SDMCArchive::createFile(const FSPath& path, u64 size) {
|
|||
}
|
||||
|
||||
HorizonResult SDMCArchive::deleteFile(const FSPath& path) {
|
||||
Helpers::panic("[SDMC] Unimplemented DeleteFile");
|
||||
if (path.type == PathType::UTF16) {
|
||||
if (!isPathSafe<PathType::UTF16>(path)) {
|
||||
Helpers::panic("Unsafe path in SDMC::DeleteFile");
|
||||
}
|
||||
|
||||
fs::path p = IOFile::getAppData() / "SDMC";
|
||||
p += fs::path(path.utf16_string).make_preferred();
|
||||
|
||||
if (fs::is_directory(p)) {
|
||||
Helpers::panic("SDMC::DeleteFile: Tried to delete directory");
|
||||
}
|
||||
|
||||
if (!fs::is_regular_file(p)) {
|
||||
return Result::FS::FileNotFoundAlt;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
bool success = fs::remove(p, ec);
|
||||
|
||||
// It might still be possible for fs::remove to fail, if there's eg an open handle to a file being deleted
|
||||
// In this case, print a warning, but still return success for now
|
||||
if (!success) {
|
||||
Helpers::warn("SDMC::DeleteFile: fs::remove failed\n");
|
||||
}
|
||||
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
Helpers::panic("SDMCArchive::DeleteFile: Unknown path type");
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
|
@ -145,7 +173,7 @@ Rust::Result<DirectorySession, HorizonResult> SDMCArchive::openDirectory(const F
|
|||
|
||||
if (path.type == PathType::UTF16) {
|
||||
if (!isPathSafe<PathType::UTF16>(path)) {
|
||||
Helpers::panic("Unsafe path in SaveData::OpenDirectory");
|
||||
Helpers::panic("Unsafe path in SDMC::OpenDirectory");
|
||||
}
|
||||
|
||||
fs::path p = IOFile::getAppData() / "SDMC";
|
||||
|
|
|
@ -184,7 +184,8 @@ void Kernel::setFileSize(u32 messagePointer, Handle fileHandle) {
|
|||
if (success) {
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
} else {
|
||||
Helpers::panic("FileOp::SetFileSize failed");
|
||||
Helpers::warn("FileOp::SetFileSize failed");
|
||||
mem.write32(messagePointer + 4, Result::FailurePlaceholder);
|
||||
}
|
||||
} else {
|
||||
Helpers::panic("Tried to set file size of file without file descriptor");
|
||||
|
|
|
@ -399,3 +399,5 @@ std::string Kernel::getProcessName(u32 pid) {
|
|||
Helpers::panic("Attempted to name non-current process");
|
||||
}
|
||||
}
|
||||
|
||||
Scheduler& Kernel::getScheduler() { return cpu.getScheduler(); }
|
||||
|
|
|
@ -29,6 +29,9 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn
|
|||
smdh.clear();
|
||||
partitionInfo = info;
|
||||
|
||||
primaryKey = {};
|
||||
secondaryKey = {};
|
||||
|
||||
size = u64(*(u32*)&header[0x104]) * mediaUnit; // TODO: Maybe don't type pun because big endian will break
|
||||
exheaderSize = *(u32*)&header[0x180];
|
||||
|
||||
|
@ -78,11 +81,11 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn
|
|||
if (!primaryResult.first || !secondaryResult.first) {
|
||||
gotCryptoKeys = false;
|
||||
} else {
|
||||
Crypto::AESKey primaryKey = primaryResult.second;
|
||||
Crypto::AESKey secondaryKey = secondaryResult.second;
|
||||
primaryKey = primaryResult.second;
|
||||
secondaryKey = secondaryResult.second;
|
||||
|
||||
EncryptionInfo encryptionInfoTmp;
|
||||
encryptionInfoTmp.normalKey = primaryKey;
|
||||
encryptionInfoTmp.normalKey = *primaryKey;
|
||||
encryptionInfoTmp.initialCounter.fill(0);
|
||||
|
||||
for (std::size_t i = 1; i <= sizeof(std::uint64_t) - 1; i++) {
|
||||
|
@ -94,7 +97,7 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn
|
|||
encryptionInfoTmp.initialCounter[8] = 2;
|
||||
exeFS.encryptionInfo = encryptionInfoTmp;
|
||||
|
||||
encryptionInfoTmp.normalKey = secondaryKey;
|
||||
encryptionInfoTmp.normalKey = *secondaryKey;
|
||||
encryptionInfoTmp.initialCounter[8] = 3;
|
||||
romFS.encryptionInfo = encryptionInfoTmp;
|
||||
}
|
||||
|
@ -201,13 +204,20 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn
|
|||
Helpers::panic("Second code file in a single NCCH partition. What should this do?\n");
|
||||
}
|
||||
|
||||
// All files in ExeFS use the same IV, though .code uses the secondary key for decryption
|
||||
// whereas .icon/.banner use the primary key.
|
||||
FSInfo info = exeFS;
|
||||
if (encrypted && secondaryKey.has_value() && info.encryptionInfo.has_value()) {
|
||||
info.encryptionInfo->normalKey = *secondaryKey;
|
||||
}
|
||||
|
||||
if (compressCode) {
|
||||
std::vector<u8> tmp;
|
||||
tmp.resize(fileSize);
|
||||
|
||||
// A file offset of 0 means our file is located right after the ExeFS header
|
||||
// So in the ROM, files are located at (file offset + exeFS offset + exeFS header size)
|
||||
readFromFile(file, exeFS, tmp.data(), fileOffset + exeFSHeaderSize, fileSize);
|
||||
readFromFile(file, info, tmp.data(), fileOffset + exeFSHeaderSize, fileSize);
|
||||
|
||||
// Decompress .code file from the tmp vector to the "code" vector
|
||||
if (!CartLZ77::decompress(codeFile, tmp)) {
|
||||
|
@ -216,7 +226,7 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn
|
|||
}
|
||||
} else {
|
||||
codeFile.resize(fileSize);
|
||||
readFromFile(file, exeFS, codeFile.data(), fileOffset + exeFSHeaderSize, fileSize);
|
||||
readFromFile(file, info, codeFile.data(), fileOffset + exeFSHeaderSize, fileSize);
|
||||
}
|
||||
} else if (std::strcmp(name, "icon") == 0) {
|
||||
// Parse icon file to extract region info and more in the future (logo, etc)
|
||||
|
|
|
@ -57,24 +57,7 @@ void RendererGL::initGraphicsContextInternal() {
|
|||
OpenGL::Shader vert({vertexShaderSource.begin(), vertexShaderSource.size()}, OpenGL::Vertex);
|
||||
OpenGL::Shader frag({fragmentShaderSource.begin(), fragmentShaderSource.size()}, OpenGL::Fragment);
|
||||
triangleProgram.create({vert, frag});
|
||||
gl.useProgram(triangleProgram);
|
||||
|
||||
textureEnvSourceLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvSource");
|
||||
textureEnvOperandLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvOperand");
|
||||
textureEnvCombinerLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvCombiner");
|
||||
textureEnvColorLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvColor");
|
||||
textureEnvScaleLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvScale");
|
||||
|
||||
depthScaleLoc = OpenGL::uniformLocation(triangleProgram, "u_depthScale");
|
||||
depthOffsetLoc = OpenGL::uniformLocation(triangleProgram, "u_depthOffset");
|
||||
depthmapEnableLoc = OpenGL::uniformLocation(triangleProgram, "u_depthmapEnable");
|
||||
picaRegLoc = OpenGL::uniformLocation(triangleProgram, "u_picaRegs");
|
||||
|
||||
// Init sampler objects. Texture 0 goes in texture unit 0, texture 1 in TU 1, texture 2 in TU 2, and the light maps go in TU 3
|
||||
glUniform1i(OpenGL::uniformLocation(triangleProgram, "u_tex0"), 0);
|
||||
glUniform1i(OpenGL::uniformLocation(triangleProgram, "u_tex1"), 1);
|
||||
glUniform1i(OpenGL::uniformLocation(triangleProgram, "u_tex2"), 2);
|
||||
glUniform1i(OpenGL::uniformLocation(triangleProgram, "u_tex_lighting_lut"), 3);
|
||||
initUbershader(triangleProgram);
|
||||
|
||||
auto displayVertexShaderSource = gl_resources.open("opengl_display.vert");
|
||||
auto displayFragmentShaderSource = gl_resources.open("opengl_display.frag");
|
||||
|
@ -812,4 +795,47 @@ void RendererGL::deinitGraphicsContext() {
|
|||
// All other GL objects should be invalidated automatically and be recreated by the next call to initGraphicsContext
|
||||
// TODO: Make it so that depth and colour buffers get written back to 3DS memory
|
||||
printf("RendererGL::DeinitGraphicsContext called\n");
|
||||
}
|
||||
}
|
||||
|
||||
std::string RendererGL::getUbershader() {
|
||||
auto gl_resources = cmrc::RendererGL::get_filesystem();
|
||||
auto fragmentShader = gl_resources.open("opengl_fragment_shader.frag");
|
||||
|
||||
return std::string(fragmentShader.begin(), fragmentShader.end());
|
||||
}
|
||||
|
||||
void RendererGL::setUbershader(const std::string& shader) {
|
||||
auto gl_resources = cmrc::RendererGL::get_filesystem();
|
||||
auto vertexShaderSource = gl_resources.open("opengl_vertex_shader.vert");
|
||||
|
||||
OpenGL::Shader vert({vertexShaderSource.begin(), vertexShaderSource.size()}, OpenGL::Vertex);
|
||||
OpenGL::Shader frag(shader, OpenGL::Fragment);
|
||||
triangleProgram.create({vert, frag});
|
||||
|
||||
initUbershader(triangleProgram);
|
||||
|
||||
glUniform1f(depthScaleLoc, oldDepthScale);
|
||||
glUniform1f(depthOffsetLoc, oldDepthOffset);
|
||||
glUniform1i(depthmapEnableLoc, oldDepthmapEnable);
|
||||
}
|
||||
|
||||
void RendererGL::initUbershader(OpenGL::Program& program) {
|
||||
gl.useProgram(program);
|
||||
|
||||
textureEnvSourceLoc = OpenGL::uniformLocation(program, "u_textureEnvSource");
|
||||
textureEnvOperandLoc = OpenGL::uniformLocation(program, "u_textureEnvOperand");
|
||||
textureEnvCombinerLoc = OpenGL::uniformLocation(program, "u_textureEnvCombiner");
|
||||
textureEnvColorLoc = OpenGL::uniformLocation(program, "u_textureEnvColor");
|
||||
textureEnvScaleLoc = OpenGL::uniformLocation(program, "u_textureEnvScale");
|
||||
|
||||
depthScaleLoc = OpenGL::uniformLocation(program, "u_depthScale");
|
||||
depthOffsetLoc = OpenGL::uniformLocation(program, "u_depthOffset");
|
||||
depthmapEnableLoc = OpenGL::uniformLocation(program, "u_depthmapEnable");
|
||||
picaRegLoc = OpenGL::uniformLocation(program, "u_picaRegs");
|
||||
|
||||
// Init sampler objects. Texture 0 goes in texture unit 0, texture 1 in TU 1, texture 2 in TU 2, and the light maps go in TU 3
|
||||
glUniform1i(OpenGL::uniformLocation(program, "u_tex0"), 0);
|
||||
glUniform1i(OpenGL::uniformLocation(program, "u_tex1"), 1);
|
||||
glUniform1i(OpenGL::uniformLocation(program, "u_tex2"), 2);
|
||||
glUniform1i(OpenGL::uniformLocation(program, "u_tex_lighting_lut"), 3);
|
||||
}
|
||||
|
|
|
@ -343,7 +343,7 @@ void CAMService::startCapture(u32 messagePointer) {
|
|||
|
||||
if (port.isValid()) {
|
||||
for (int i : port.getPortIndices()) {
|
||||
auto& event = ports[port.getSingleIndex()].receiveEvent;
|
||||
auto& event = ports[i].receiveEvent;
|
||||
|
||||
// Until we properly implement cameras, immediately signal the receive event
|
||||
if (event.has_value()) {
|
||||
|
|
|
@ -61,6 +61,7 @@ void Y2RService::reset() {
|
|||
inputLineWidth = 420;
|
||||
|
||||
conversionCoefficients.fill(0);
|
||||
isBusy = false;
|
||||
}
|
||||
|
||||
void Y2RService::handleSyncRequest(u32 messagePointer) {
|
||||
|
@ -156,6 +157,11 @@ void Y2RService::setTransferEndInterrupt(u32 messagePointer) {
|
|||
void Y2RService::stopConversion(u32 messagePointer) {
|
||||
log("Y2R::StopConversion\n");
|
||||
|
||||
if (isBusy) {
|
||||
isBusy = false;
|
||||
kernel.getScheduler().removeEvent(Scheduler::EventType::SignalY2R);
|
||||
}
|
||||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x27, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
}
|
||||
|
@ -167,7 +173,7 @@ void Y2RService::isBusyConversion(u32 messagePointer) {
|
|||
|
||||
mem.write32(messagePointer, IPC::responseHeader(0x28, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write32(messagePointer + 8, static_cast<u32>(BusyStatus::NotBusy));
|
||||
mem.write32(messagePointer + 8, static_cast<u32>(isBusy ? BusyStatus::Busy : BusyStatus::NotBusy));
|
||||
}
|
||||
|
||||
void Y2RService::setBlockAlignment(u32 messagePointer) {
|
||||
|
@ -434,11 +440,15 @@ void Y2RService::startConversion(u32 messagePointer) {
|
|||
mem.write32(messagePointer, IPC::responseHeader(0x26, 1, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
|
||||
// Make Y2R conversion end instantly.
|
||||
// Signal the transfer end event if it's been created. TODO: Is this affected by SetTransferEndInterrupt?
|
||||
if (transferEndEvent.has_value()) {
|
||||
kernel.signalEvent(transferEndEvent.value());
|
||||
}
|
||||
// Schedule Y2R conversion end event.
|
||||
// The tick value is tweaked based on the minimum delay needed to get FIFA 15 to not hang due to a race condition on its title screen
|
||||
static constexpr u64 delayTicks = 1'350'000;
|
||||
isBusy = true;
|
||||
|
||||
// Remove any potential pending Y2R event and schedule a new one
|
||||
Scheduler& scheduler = kernel.getScheduler();
|
||||
scheduler.removeEvent(Scheduler::EventType::SignalY2R);
|
||||
scheduler.addEvent(Scheduler::EventType::SignalY2R, scheduler.currentTimestamp + delayTicks);
|
||||
}
|
||||
|
||||
void Y2RService::isFinishedSendingYUV(u32 messagePointer) {
|
||||
|
@ -484,4 +494,15 @@ void Y2RService::isFinishedReceiving(u32 messagePointer) {
|
|||
mem.write32(messagePointer, IPC::responseHeader(0x17, 2, 0));
|
||||
mem.write32(messagePointer + 4, Result::Success);
|
||||
mem.write32(messagePointer + 8, finished ? 1 : 0);
|
||||
}
|
||||
|
||||
void Y2RService::signalConversionDone() {
|
||||
if (isBusy) {
|
||||
isBusy = false;
|
||||
|
||||
// Signal the transfer end event if it's been created. TODO: Is this affected by SetTransferEndInterrupt?
|
||||
if (transferEndEvent.has_value()) {
|
||||
kernel.signalEvent(transferEndEvent.value());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -84,6 +84,7 @@ void Emulator::reset(ReloadOption reload) {
|
|||
}
|
||||
}
|
||||
|
||||
#ifndef __LIBRETRO__
|
||||
std::filesystem::path Emulator::getAndroidAppPath() {
|
||||
// SDL_GetPrefPath fails to get the path due to no JNI environment
|
||||
std::ifstream cmdline("/proc/self/cmdline");
|
||||
|
@ -100,6 +101,7 @@ std::filesystem::path Emulator::getConfigPath() {
|
|||
return std::filesystem::current_path() / "config.toml";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void Emulator::step() {}
|
||||
void Emulator::render() {}
|
||||
|
@ -169,6 +171,8 @@ void Emulator::pollScheduler() {
|
|||
break;
|
||||
}
|
||||
|
||||
case Scheduler::EventType::SignalY2R: kernel.getServiceManager().getY2R().signalConversionDone(); break;
|
||||
|
||||
default: {
|
||||
Helpers::panic("Scheduler: Unimplemented event type received: %d\n", static_cast<int>(eventType));
|
||||
break;
|
||||
|
@ -177,6 +181,7 @@ void Emulator::pollScheduler() {
|
|||
}
|
||||
}
|
||||
|
||||
#ifndef __LIBRETRO__
|
||||
// Get path for saving files (AppData on Windows, /home/user/.local/share/ApplicationName 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.
|
||||
|
@ -200,6 +205,7 @@ std::filesystem::path Emulator::getAppDataRoot() {
|
|||
|
||||
return appDataPath;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool Emulator::loadROM(const std::filesystem::path& path) {
|
||||
// Reset the emulator if we've already loaded a ROM
|
||||
|
|
366
src/libretro_core.cpp
Normal file
366
src/libretro_core.cpp
Normal file
|
@ -0,0 +1,366 @@
|
|||
#include <stdexcept>
|
||||
#include <cstdio>
|
||||
|
||||
#include <libretro.h>
|
||||
|
||||
#include <emulator.hpp>
|
||||
#include <renderer_gl/renderer_gl.hpp>
|
||||
|
||||
static retro_environment_t envCallbacks;
|
||||
static retro_video_refresh_t videoCallbacks;
|
||||
static retro_audio_sample_batch_t audioBatchCallback;
|
||||
static retro_input_poll_t inputPollCallback;
|
||||
static retro_input_state_t inputStateCallback;
|
||||
|
||||
static retro_hw_render_callback hw_render;
|
||||
static std::filesystem::path savePath;
|
||||
|
||||
std::unique_ptr<Emulator> emulator;
|
||||
RendererGL* renderer;
|
||||
|
||||
std::filesystem::path Emulator::getConfigPath() {
|
||||
return std::filesystem::path(savePath / "config.toml");
|
||||
}
|
||||
|
||||
std::filesystem::path Emulator::getAppDataRoot() {
|
||||
return std::filesystem::path(savePath / "Emulator Files");
|
||||
}
|
||||
|
||||
static void* GetGLProcAddress(const char* name) {
|
||||
return (void*)hw_render.get_proc_address(name);
|
||||
}
|
||||
|
||||
static void VideoResetContext() {
|
||||
#ifdef USING_GLES
|
||||
if (!gladLoadGLES2Loader(reinterpret_cast<GLADloadproc>(GetGLProcAddress))) {
|
||||
Helpers::panic("OpenGL ES init failed");
|
||||
}
|
||||
#else
|
||||
if (!gladLoadGLLoader(reinterpret_cast<GLADloadproc>(GetGLProcAddress))) {
|
||||
Helpers::panic("OpenGL init failed");
|
||||
}
|
||||
#endif
|
||||
|
||||
emulator->initGraphicsContext(nullptr);
|
||||
}
|
||||
|
||||
static void VideoDestroyContext() {
|
||||
emulator->deinitGraphicsContext();
|
||||
}
|
||||
|
||||
static bool SetHWRender(retro_hw_context_type type) {
|
||||
hw_render.context_type = type;
|
||||
hw_render.context_reset = VideoResetContext;
|
||||
hw_render.context_destroy = VideoDestroyContext;
|
||||
hw_render.bottom_left_origin = true;
|
||||
|
||||
switch (type) {
|
||||
case RETRO_HW_CONTEXT_OPENGL_CORE:
|
||||
hw_render.version_major = 4;
|
||||
hw_render.version_minor = 1;
|
||||
|
||||
if (envCallbacks(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case RETRO_HW_CONTEXT_OPENGLES3:
|
||||
case RETRO_HW_CONTEXT_OPENGL:
|
||||
hw_render.version_major = 3;
|
||||
hw_render.version_minor = 1;
|
||||
|
||||
if (envCallbacks(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void videoInit() {
|
||||
retro_hw_context_type preferred = RETRO_HW_CONTEXT_NONE;
|
||||
envCallbacks(RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER, &preferred);
|
||||
|
||||
if (preferred && SetHWRender(preferred)) return;
|
||||
if (SetHWRender(RETRO_HW_CONTEXT_OPENGL_CORE)) return;
|
||||
if (SetHWRender(RETRO_HW_CONTEXT_OPENGL)) return;
|
||||
if (SetHWRender(RETRO_HW_CONTEXT_OPENGLES3)) return;
|
||||
|
||||
hw_render.context_type = RETRO_HW_CONTEXT_NONE;
|
||||
}
|
||||
|
||||
static bool GetButtonState(uint id) { return inputStateCallback(0, RETRO_DEVICE_JOYPAD, 0, id); }
|
||||
static float GetAxisState(uint index, uint id) { return inputStateCallback(0, RETRO_DEVICE_ANALOG, index, id); }
|
||||
|
||||
static void inputInit() {
|
||||
static const retro_controller_description controllers[] = {
|
||||
{"Nintendo 3DS", RETRO_DEVICE_JOYPAD},
|
||||
{NULL, 0},
|
||||
};
|
||||
|
||||
static const retro_controller_info ports[] = {
|
||||
{controllers, 1},
|
||||
{NULL, 0},
|
||||
};
|
||||
|
||||
envCallbacks(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports);
|
||||
|
||||
retro_input_descriptor desc[] = {
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "R"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "L"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "X"},
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Y"},
|
||||
{0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X, "Circle Pad X"},
|
||||
{0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y, "Circle Pad Y"},
|
||||
{0},
|
||||
};
|
||||
|
||||
envCallbacks(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, &desc);
|
||||
}
|
||||
|
||||
static std::string FetchVariable(std::string key, std::string def) {
|
||||
retro_variable var = {nullptr};
|
||||
var.key = key.c_str();
|
||||
|
||||
if (!envCallbacks(RETRO_ENVIRONMENT_GET_VARIABLE, &var) || var.value == nullptr) {
|
||||
Helpers::warn("Fetching variable %s failed.", key.c_str());
|
||||
return def;
|
||||
}
|
||||
|
||||
return std::string(var.value);
|
||||
}
|
||||
|
||||
static bool FetchVariableBool(std::string key, bool def) {
|
||||
return FetchVariable(key, def ? "enabled" : "disabled") == "enabled";
|
||||
}
|
||||
|
||||
static void configInit() {
|
||||
static const retro_variable values[] = {
|
||||
{"panda3ds_use_shader_jit", "Enable shader JIT; enabled|disabled"},
|
||||
{"panda3ds_use_vsync", "Enable VSync; enabled|disabled"},
|
||||
{"panda3ds_dsp_emulation", "DSP emulation; Null|HLE|LLE"},
|
||||
{"panda3ds_use_audio", "Enable audio; disabled|enabled"},
|
||||
{"panda3ds_use_virtual_sd", "Enable virtual SD card; enabled|disabled"},
|
||||
{"panda3ds_write_protect_virtual_sd", "Write protect virtual SD card; disabled|enabled"},
|
||||
{"panda3ds_battery_level", "Battery percentage; 5|10|20|30|50|70|90|100"},
|
||||
{"panda3ds_use_charger", "Charger plugged; enabled|disabled"},
|
||||
{nullptr, nullptr}
|
||||
};
|
||||
|
||||
envCallbacks(RETRO_ENVIRONMENT_SET_VARIABLES, (void*)values);
|
||||
}
|
||||
|
||||
static void configUpdate() {
|
||||
EmulatorConfig& config = emulator->getConfig();
|
||||
|
||||
config.rendererType = RendererType::OpenGL;
|
||||
config.vsyncEnabled = FetchVariableBool("panda3ds_use_vsync", true);
|
||||
config.shaderJitEnabled = FetchVariableBool("panda3ds_use_shader_jit", true);
|
||||
config.chargerPlugged = FetchVariableBool("panda3ds_use_charger", true);
|
||||
config.batteryPercentage = std::clamp(std::stoi(FetchVariable("panda3ds_battery_level", "5")), 0, 100);
|
||||
config.dspType = Audio::DSPCore::typeFromString(FetchVariable("panda3ds_dsp_emulation", "null"));
|
||||
config.audioEnabled = FetchVariableBool("panda3ds_use_audio", false);
|
||||
config.sdCardInserted = FetchVariableBool("panda3ds_use_virtual_sd", true);
|
||||
config.sdWriteProtected = FetchVariableBool("panda3ds_write_protect_virtual_sd", false);
|
||||
config.discordRpcEnabled = false;
|
||||
|
||||
config.save();
|
||||
}
|
||||
|
||||
static void ConfigCheckVariables() {
|
||||
bool updated = false;
|
||||
envCallbacks(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated);
|
||||
|
||||
if (updated) {
|
||||
configUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void retro_get_system_info(retro_system_info* info) {
|
||||
info->need_fullpath = true;
|
||||
info->valid_extensions = "3ds|3dsx|elf|axf|cci|cxi|app";
|
||||
info->library_version = "0.8";
|
||||
info->library_name = "Panda3DS";
|
||||
info->block_extract = true;
|
||||
}
|
||||
|
||||
void retro_get_system_av_info(retro_system_av_info* info) {
|
||||
info->geometry.base_width = emulator->width;
|
||||
info->geometry.base_height = emulator->height;
|
||||
|
||||
info->geometry.max_width = info->geometry.base_width;
|
||||
info->geometry.max_height = info->geometry.base_height;
|
||||
|
||||
info->geometry.aspect_ratio = float(5.0 / 6.0);
|
||||
info->timing.fps = 60.0;
|
||||
info->timing.sample_rate = 32768;
|
||||
}
|
||||
|
||||
void retro_set_environment(retro_environment_t cb) {
|
||||
envCallbacks = cb;
|
||||
}
|
||||
|
||||
void retro_set_video_refresh(retro_video_refresh_t cb) {
|
||||
videoCallbacks = cb;
|
||||
}
|
||||
|
||||
void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) {
|
||||
audioBatchCallback = cb;
|
||||
}
|
||||
|
||||
void retro_set_audio_sample(retro_audio_sample_t cb) {}
|
||||
|
||||
void retro_set_input_poll(retro_input_poll_t cb) {
|
||||
inputPollCallback = cb;
|
||||
}
|
||||
|
||||
void retro_set_input_state(retro_input_state_t cb) {
|
||||
inputStateCallback = cb;
|
||||
}
|
||||
|
||||
void retro_init() {
|
||||
enum retro_pixel_format xrgb888 = RETRO_PIXEL_FORMAT_XRGB8888;
|
||||
envCallbacks(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &xrgb888);
|
||||
|
||||
char* save_dir = nullptr;
|
||||
|
||||
if (!envCallbacks(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &save_dir) || save_dir == nullptr) {
|
||||
Helpers::warn("No save directory provided by LibRetro.");
|
||||
savePath = std::filesystem::current_path();
|
||||
} else {
|
||||
savePath = std::filesystem::path(save_dir);
|
||||
}
|
||||
|
||||
emulator = std::make_unique<Emulator>();
|
||||
}
|
||||
|
||||
void retro_deinit() {
|
||||
emulator = nullptr;
|
||||
}
|
||||
|
||||
bool retro_load_game(const retro_game_info* game) {
|
||||
configInit();
|
||||
configUpdate();
|
||||
|
||||
if (emulator->getRendererType() != RendererType::OpenGL) {
|
||||
Helpers::panic("Libretro: Renderer is not OpenGL");
|
||||
}
|
||||
|
||||
renderer = static_cast<RendererGL*>(emulator->getRenderer());
|
||||
emulator->setOutputSize(emulator->width, emulator->height);
|
||||
|
||||
inputInit();
|
||||
videoInit();
|
||||
|
||||
return emulator->loadROM(game->path);
|
||||
}
|
||||
|
||||
bool retro_load_game_special(uint type, const retro_game_info* info, usize num) { return false; }
|
||||
|
||||
void retro_unload_game() {
|
||||
renderer->setFBO(0);
|
||||
renderer = nullptr;
|
||||
}
|
||||
|
||||
void retro_reset() {
|
||||
emulator->reset(Emulator::ReloadOption::Reload);
|
||||
}
|
||||
|
||||
void retro_run() {
|
||||
ConfigCheckVariables();
|
||||
|
||||
renderer->setFBO(hw_render.get_current_framebuffer());
|
||||
renderer->resetStateManager();
|
||||
|
||||
inputPollCallback();
|
||||
|
||||
HIDService& hid = emulator->getServiceManager().getHID();
|
||||
|
||||
hid.setKey(HID::Keys::A, GetButtonState(RETRO_DEVICE_ID_JOYPAD_A));
|
||||
hid.setKey(HID::Keys::B, GetButtonState(RETRO_DEVICE_ID_JOYPAD_B));
|
||||
hid.setKey(HID::Keys::X, GetButtonState(RETRO_DEVICE_ID_JOYPAD_X));
|
||||
hid.setKey(HID::Keys::Y, GetButtonState(RETRO_DEVICE_ID_JOYPAD_Y));
|
||||
hid.setKey(HID::Keys::L, GetButtonState(RETRO_DEVICE_ID_JOYPAD_L));
|
||||
hid.setKey(HID::Keys::R, GetButtonState(RETRO_DEVICE_ID_JOYPAD_R));
|
||||
hid.setKey(HID::Keys::Start, GetButtonState(RETRO_DEVICE_ID_JOYPAD_START));
|
||||
hid.setKey(HID::Keys::Select, GetButtonState(RETRO_DEVICE_ID_JOYPAD_SELECT));
|
||||
hid.setKey(HID::Keys::Up, GetButtonState(RETRO_DEVICE_ID_JOYPAD_UP));
|
||||
hid.setKey(HID::Keys::Down, GetButtonState(RETRO_DEVICE_ID_JOYPAD_DOWN));
|
||||
hid.setKey(HID::Keys::Left, GetButtonState(RETRO_DEVICE_ID_JOYPAD_LEFT));
|
||||
hid.setKey(HID::Keys::Right, GetButtonState(RETRO_DEVICE_ID_JOYPAD_RIGHT));
|
||||
|
||||
// Get analog values for the left analog stick (Right analog stick is N3DS-only and unimplemented)
|
||||
float xLeft = GetAxisState(RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X);
|
||||
float yLeft = GetAxisState(RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y);
|
||||
|
||||
hid.setCirclepadX((xLeft / +32767) * 0x9C);
|
||||
hid.setCirclepadY((yLeft / -32767) * 0x9C);
|
||||
|
||||
bool touch = inputStateCallback(0, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_LEFT);
|
||||
const int posX = inputStateCallback(0, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_X);
|
||||
const int posY = inputStateCallback(0, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_Y);
|
||||
|
||||
const int newX = static_cast<int>((posX + 0x7fff) / (float)(0x7fff * 2) * emulator->width);
|
||||
const int newY = static_cast<int>((posY + 0x7fff) / (float)(0x7fff * 2) * emulator->height);
|
||||
|
||||
const int offsetX = 40;
|
||||
const int offsetY = emulator->height / 2;
|
||||
|
||||
const bool inScreenX = newX >= offsetX && newX < emulator->width - offsetX;
|
||||
const bool inScreenY = newY >= offsetY && newY <= emulator->height;
|
||||
|
||||
if (touch && inScreenX && inScreenY) {
|
||||
u16 x = static_cast<u16>(newX - offsetX);
|
||||
u16 y = static_cast<u16>(newY - offsetY);
|
||||
|
||||
hid.setTouchScreenPress(x, y);
|
||||
} else {
|
||||
hid.releaseTouchScreen();
|
||||
}
|
||||
|
||||
hid.updateInputs(emulator->getTicks());
|
||||
emulator->runFrame();
|
||||
|
||||
videoCallbacks(RETRO_HW_FRAME_BUFFER_VALID, emulator->width, emulator->height, 0);
|
||||
}
|
||||
|
||||
void retro_set_controller_port_device(uint port, uint device) {}
|
||||
|
||||
usize retro_serialize_size() {
|
||||
usize size = 0;
|
||||
return size;
|
||||
}
|
||||
|
||||
bool retro_serialize(void* data, usize size) { return false; }
|
||||
bool retro_unserialize(const void* data, usize size) { return false; }
|
||||
|
||||
uint retro_get_region() { return RETRO_REGION_NTSC; }
|
||||
uint retro_api_version() { return RETRO_API_VERSION; }
|
||||
|
||||
usize retro_get_memory_size(uint id) {
|
||||
if (id == RETRO_MEMORY_SYSTEM_RAM) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* retro_get_memory_data(uint id) {
|
||||
if (id == RETRO_MEMORY_SYSTEM_RAM) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void retro_cheat_set(uint index, bool enabled, const char* code) {}
|
||||
void retro_cheat_reset() {}
|
25
src/panda_qt/elided_label.cpp
Normal file
25
src/panda_qt/elided_label.cpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#include "panda_qt/elided_label.hpp"
|
||||
|
||||
// Based on https://stackoverflow.com/questions/7381100/text-overflow-for-a-qlabel-s-text-rendering-in-qt
|
||||
ElidedLabel::ElidedLabel(Qt::TextElideMode elideMode, QWidget* parent) : ElidedLabel("", elideMode, parent) {}
|
||||
|
||||
ElidedLabel::ElidedLabel(QString text, Qt::TextElideMode elideMode, QWidget* parent) : QLabel(parent) {
|
||||
m_elideMode = elideMode;
|
||||
setText(text);
|
||||
}
|
||||
|
||||
void ElidedLabel::setText(QString text) {
|
||||
m_text = text;
|
||||
updateText();
|
||||
}
|
||||
|
||||
void ElidedLabel::resizeEvent(QResizeEvent* event) {
|
||||
QLabel::resizeEvent(event);
|
||||
updateText();
|
||||
}
|
||||
|
||||
void ElidedLabel::updateText() {
|
||||
QFontMetrics metrics(font());
|
||||
QString elided = metrics.elidedText(m_text, m_elideMode, width());
|
||||
QLabel::setText(elided);
|
||||
}
|
|
@ -54,11 +54,15 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
|
|||
auto dumpRomFSAction = toolsMenu->addAction(tr("Dump RomFS"));
|
||||
auto luaEditorAction = toolsMenu->addAction(tr("Open Lua Editor"));
|
||||
auto cheatsEditorAction = toolsMenu->addAction(tr("Open Cheats Editor"));
|
||||
auto patchWindowAction = toolsMenu->addAction(tr("Open Patch Window"));
|
||||
auto shaderEditorAction = toolsMenu->addAction(tr("Open Shader Editor"));
|
||||
auto dumpDspFirmware = toolsMenu->addAction(tr("Dump loaded DSP firmware"));
|
||||
|
||||
connect(dumpRomFSAction, &QAction::triggered, this, &MainWindow::dumpRomFS);
|
||||
connect(luaEditorAction, &QAction::triggered, this, &MainWindow::openLuaEditor);
|
||||
connect(cheatsEditorAction, &QAction::triggered, this, &MainWindow::openCheatsEditor);
|
||||
connect(luaEditorAction, &QAction::triggered, this, [this]() { luaEditor->show(); });
|
||||
connect(shaderEditorAction, &QAction::triggered, this, [this]() { shaderEditor->show(); });
|
||||
connect(cheatsEditorAction, &QAction::triggered, this, [this]() { cheatsEditor->show(); });
|
||||
connect(patchWindowAction, &QAction::triggered, this, [this]() { patchWindow->show(); });
|
||||
connect(dumpDspFirmware, &QAction::triggered, this, &MainWindow::dumpDspFirmware);
|
||||
|
||||
auto aboutAction = aboutMenu->addAction(tr("About Panda3DS"));
|
||||
|
@ -71,7 +75,14 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
|
|||
aboutWindow = new AboutWindow(nullptr);
|
||||
configWindow = new ConfigWindow(this);
|
||||
cheatsEditor = new CheatsWindow(emu, {}, this);
|
||||
patchWindow = new PatchWindow(this);
|
||||
luaEditor = new TextEditorWindow(this, "script.lua", "");
|
||||
shaderEditor = new ShaderEditorWindow(this, "shader.glsl", "");
|
||||
|
||||
shaderEditor->setEnable(emu->getRenderer()->supportsShaderReload());
|
||||
if (shaderEditor->supported) {
|
||||
shaderEditor->setText(emu->getRenderer()->getUbershader());
|
||||
}
|
||||
|
||||
auto args = QCoreApplication::arguments();
|
||||
if (args.size() > 1) {
|
||||
|
@ -291,9 +302,6 @@ void MainWindow::showAboutMenu() {
|
|||
about.exec();
|
||||
}
|
||||
|
||||
void MainWindow::openLuaEditor() { luaEditor->show(); }
|
||||
void MainWindow::openCheatsEditor() { cheatsEditor->show(); }
|
||||
|
||||
void MainWindow::dispatchMessage(const EmulatorMessage& message) {
|
||||
switch (message.type) {
|
||||
case MessageType::LoadROM:
|
||||
|
@ -347,6 +355,11 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) {
|
|||
emu->getServiceManager().getHID().setTouchScreenPress(message.touchscreen.x, message.touchscreen.y);
|
||||
break;
|
||||
case MessageType::ReleaseTouchscreen: emu->getServiceManager().getHID().releaseTouchScreen(); break;
|
||||
|
||||
case MessageType::ReloadUbershader:
|
||||
emu->getRenderer()->setUbershader(*message.string.str);
|
||||
delete message.string.str;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -449,6 +462,14 @@ void MainWindow::loadLuaScript(const std::string& code) {
|
|||
sendMessage(message);
|
||||
}
|
||||
|
||||
void MainWindow::reloadShader(const std::string& shader) {
|
||||
EmulatorMessage message{.type = MessageType::ReloadUbershader};
|
||||
|
||||
// Make a copy of the code on the heap to send via the message queue
|
||||
message.string.str = new std::string(shader);
|
||||
sendMessage(message);
|
||||
}
|
||||
|
||||
void MainWindow::editCheat(u32 handle, const std::vector<uint8_t>& cheat, const std::function<void(u32)>& callback) {
|
||||
EmulatorMessage message{.type = MessageType::EditCheat};
|
||||
|
||||
|
|
158
src/panda_qt/patch_window.cpp
Normal file
158
src/panda_qt/patch_window.cpp
Normal file
|
@ -0,0 +1,158 @@
|
|||
#include "panda_qt/patch_window.hpp"
|
||||
|
||||
#include <QAbstractButton>
|
||||
#include <QFileDialog>
|
||||
#include <QHBoxLayout>
|
||||
#include <QIcon>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <memory>
|
||||
|
||||
#include "hips.hpp"
|
||||
#include "io_file.hpp"
|
||||
|
||||
PatchWindow::PatchWindow(QWidget* parent) : QWidget(parent, Qt::Window) {
|
||||
QVBoxLayout* layout = new QVBoxLayout;
|
||||
layout->setContentsMargins(6, 6, 6, 6);
|
||||
setLayout(layout);
|
||||
|
||||
QWidget* inputBox = new QWidget;
|
||||
QHBoxLayout* inputLayout = new QHBoxLayout;
|
||||
QLabel* inputText = new QLabel(tr("Select input file"));
|
||||
QPushButton* inputButton = new QPushButton(tr("Select"));
|
||||
inputPathLabel = new ElidedLabel("");
|
||||
inputPathLabel->setFixedWidth(200);
|
||||
|
||||
inputLayout->addWidget(inputText);
|
||||
inputLayout->addWidget(inputButton);
|
||||
inputLayout->addWidget(inputPathLabel);
|
||||
inputBox->setLayout(inputLayout);
|
||||
|
||||
QWidget* patchBox = new QWidget;
|
||||
QHBoxLayout* patchLayout = new QHBoxLayout;
|
||||
QLabel* patchText = new QLabel(tr("Select patch file"));
|
||||
QPushButton* patchButton = new QPushButton(tr("Select"));
|
||||
patchPathLabel = new ElidedLabel("");
|
||||
patchPathLabel->setFixedWidth(200);
|
||||
|
||||
patchLayout->addWidget(patchText);
|
||||
patchLayout->addWidget(patchButton);
|
||||
patchLayout->addWidget(patchPathLabel);
|
||||
patchBox->setLayout(patchLayout);
|
||||
|
||||
QWidget* actionBox = new QWidget;
|
||||
QHBoxLayout* actionLayout = new QHBoxLayout;
|
||||
QPushButton* applyPatchButton = new QPushButton(tr("Apply patch"));
|
||||
actionLayout->addWidget(applyPatchButton);
|
||||
actionBox->setLayout(actionLayout);
|
||||
|
||||
layout->addWidget(inputBox);
|
||||
layout->addWidget(patchBox);
|
||||
layout->addWidget(actionBox);
|
||||
|
||||
connect(inputButton, &QPushButton::clicked, this, [this]() {
|
||||
auto path = QFileDialog::getOpenFileName(this, tr("Select file to patch"), "", tr("All files (*.*)"));
|
||||
inputPath = std::filesystem::path(path.toStdU16String());
|
||||
|
||||
inputPathLabel->setText(path);
|
||||
});
|
||||
|
||||
connect(patchButton, &QPushButton::clicked, this, [this]() {
|
||||
auto path = QFileDialog::getOpenFileName(this, tr("Select patch file"), "", tr("Patch files (*.ips *.ups *.bps)"));
|
||||
patchPath = std::filesystem::path(path.toStdU16String());
|
||||
|
||||
patchPathLabel->setText(path);
|
||||
});
|
||||
|
||||
connect(applyPatchButton, &QPushButton::clicked, this, [this]() {
|
||||
if (inputPath.empty() || patchPath.empty()) {
|
||||
displayMessage(tr("Paths not provided correctly"), tr("Please provide paths for both the input file and the patch file"));
|
||||
return;
|
||||
}
|
||||
|
||||
// std::filesystem::path only has += and not + for reasons unknown to humanity
|
||||
auto defaultPath = inputPath.parent_path() / inputPath.stem();
|
||||
defaultPath += "-patched";
|
||||
defaultPath += inputPath.extension();
|
||||
|
||||
auto path = QFileDialog::getSaveFileName(this, tr("Select file"), QString::fromStdU16String(defaultPath.u16string()), tr("All files (*.*)"));
|
||||
std::filesystem::path outputPath = std::filesystem::path(path.toStdU16String());
|
||||
|
||||
if (outputPath.empty()) {
|
||||
displayMessage(tr("No output path"), tr("No path was provided for the output file, no patching was done"));
|
||||
return;
|
||||
}
|
||||
|
||||
Hips::PatchType patchType;
|
||||
auto extension = patchPath.extension();
|
||||
|
||||
// Figure out what sort of patch we're dealing with
|
||||
if (extension == ".ips") {
|
||||
patchType = Hips::PatchType::IPS;
|
||||
} else if (extension == ".ups") {
|
||||
patchType = Hips::PatchType::UPS;
|
||||
} else if (extension == ".bps") {
|
||||
patchType = Hips::PatchType::BPS;
|
||||
} else {
|
||||
displayMessage(tr("Unknown patch format"), tr("Unknown format for patch file. Currently IPS, UPS and BPS are supported"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Read input and patch files into buffers
|
||||
IOFile input(inputPath, "rb");
|
||||
IOFile patch(patchPath, "rb");
|
||||
|
||||
if (!input.isOpen() || !patch.isOpen()) {
|
||||
displayMessage(tr("Failed to open input files"), tr("Make sure they're in a directory Panda3DS has access to"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the files into arrays
|
||||
const auto inputSize = *input.size();
|
||||
const auto patchSize = *patch.size();
|
||||
|
||||
std::unique_ptr<uint8_t[]> inputData(new uint8_t[inputSize]);
|
||||
std::unique_ptr<uint8_t[]> patchData(new uint8_t[patchSize]);
|
||||
|
||||
input.rewind();
|
||||
patch.rewind();
|
||||
input.readBytes(inputData.get(), inputSize);
|
||||
patch.readBytes(patchData.get(), patchSize);
|
||||
|
||||
auto [bytes, result] = Hips::patch(inputData.get(), inputSize, patchData.get(), patchSize, patchType);
|
||||
|
||||
// Write patched file
|
||||
if (!bytes.empty()) {
|
||||
IOFile output(outputPath, "wb");
|
||||
output.writeBytes(bytes.data(), bytes.size());
|
||||
}
|
||||
|
||||
switch (result) {
|
||||
case Hips::Result::Success:
|
||||
displayMessage(
|
||||
tr("Patching Success"), tr("Your file was patched successfully."), QMessageBox::Icon::Information, ":/docs/img/rpog_icon.png"
|
||||
);
|
||||
break;
|
||||
|
||||
case Hips::Result::ChecksumMismatch:
|
||||
displayMessage(
|
||||
tr("Checksum mismatch"),
|
||||
tr("Patch was applied successfully but a checksum mismatch was detected. The input or output files might not be correct")
|
||||
);
|
||||
break;
|
||||
|
||||
default: displayMessage(tr("Patching error"), tr("An error occured while patching")); break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void PatchWindow::PatchWindow::displayMessage(const QString& title, const QString& message, QMessageBox::Icon icon, const char* iconPath) {
|
||||
QMessageBox messageBox(icon, title, message);
|
||||
QAbstractButton* button = messageBox.addButton(tr("OK"), QMessageBox::ButtonRole::YesRole);
|
||||
|
||||
if (iconPath != nullptr) {
|
||||
button->setIcon(QIcon(iconPath));
|
||||
}
|
||||
|
||||
messageBox.exec();
|
||||
}
|
54
src/panda_qt/shader_editor.cpp
Normal file
54
src/panda_qt/shader_editor.cpp
Normal file
|
@ -0,0 +1,54 @@
|
|||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "panda_qt/main_window.hpp"
|
||||
#include "panda_qt/shader_editor.hpp"
|
||||
|
||||
using namespace Zep;
|
||||
|
||||
ShaderEditorWindow::ShaderEditorWindow(QWidget* parent, const std::string& filename, const std::string& initialText)
|
||||
: QDialog(parent), zepWidget(this, qApp->applicationDirPath().toStdString(), fontSize) {
|
||||
resize(600, 600);
|
||||
|
||||
// Register our extensions
|
||||
ZepRegressExCommand::Register(zepWidget.GetEditor());
|
||||
ZepReplExCommand::Register(zepWidget.GetEditor(), &replProvider);
|
||||
|
||||
// Default to standard mode instead of vim mode, initialize text box
|
||||
zepWidget.GetEditor().InitWithText(filename, initialText);
|
||||
zepWidget.GetEditor().SetGlobalMode(Zep::ZepMode_Standard::StaticName());
|
||||
|
||||
// Layout for widgets
|
||||
QVBoxLayout* mainLayout = new QVBoxLayout();
|
||||
setLayout(mainLayout);
|
||||
|
||||
QPushButton* button = new QPushButton(tr("Reload shader"), this);
|
||||
button->setFixedSize(100, 20);
|
||||
|
||||
// When the Load Script button is pressed, send the current text to the MainWindow, which will upload it to the emulator's lua object
|
||||
connect(button, &QPushButton::pressed, this, [this]() {
|
||||
if (parentWidget()) {
|
||||
auto buffer = zepWidget.GetEditor().GetMRUBuffer();
|
||||
const std::string text = buffer->GetBufferText(buffer->Begin(), buffer->End());
|
||||
|
||||
static_cast<MainWindow*>(parentWidget())->reloadShader(text);
|
||||
} else {
|
||||
// This should be unreachable, only here for safety purposes
|
||||
printf("Text editor does not have any parent widget, click doesn't work :(\n");
|
||||
}
|
||||
});
|
||||
|
||||
mainLayout->addWidget(button);
|
||||
mainLayout->addWidget(&zepWidget);
|
||||
}
|
||||
|
||||
void ShaderEditorWindow::setEnable(bool enable) {
|
||||
supported = enable;
|
||||
|
||||
if (enable) {
|
||||
setDisabled(false);
|
||||
} else {
|
||||
setDisabled(true);
|
||||
setText("Shader editor window is not available for this renderer backend");
|
||||
}
|
||||
}
|
|
@ -21,7 +21,7 @@ public class GlobalConfig {
|
|||
|
||||
public static DataModel data;
|
||||
|
||||
public static final Key<Boolean> KEY_SHADER_JIT = new Key<>("emu.shader_jit", false);
|
||||
public static final Key<Boolean> KEY_SHADER_JIT = new Key<>("emu.shader_jit", true);
|
||||
public static final Key<Boolean> KEY_PICTURE_IN_PICTURE = new Key<>("app.behavior.pictureInPicture", false);
|
||||
public static final Key<Boolean> KEY_SHOW_PERFORMANCE_OVERLAY = new Key<>("dev.performanceOverlay", false);
|
||||
public static final Key<Boolean> KEY_LOGGER_SERVICE = new Key<>("dev.loggerService", false);
|
||||
|
|
1
third_party/hips
vendored
Submodule
1
third_party/hips
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit bbe8faf149c4e10aaa45e2454fdb386e4cabf0cb
|
4405
third_party/libretro/include/libretro.h
vendored
Normal file
4405
third_party/libretro/include/libretro.h
vendored
Normal file
File diff suppressed because it is too large
Load diff
94
third_party/opengl/opengl.hpp
vendored
94
third_party/opengl/opengl.hpp
vendored
|
@ -430,36 +430,36 @@ namespace OpenGL {
|
|||
glDispatchCompute(groupsX, groupsY, groupsZ);
|
||||
}
|
||||
|
||||
struct VertexBuffer {
|
||||
GLuint m_handle = 0;
|
||||
struct VertexBuffer {
|
||||
GLuint m_handle = 0;
|
||||
|
||||
void create() {
|
||||
if (m_handle == 0) {
|
||||
glGenBuffers(1, &m_handle);
|
||||
}
|
||||
}
|
||||
void create() {
|
||||
if (m_handle == 0) {
|
||||
glGenBuffers(1, &m_handle);
|
||||
}
|
||||
}
|
||||
|
||||
void createFixedSize(GLsizei size, GLenum usage = GL_DYNAMIC_DRAW) {
|
||||
create();
|
||||
bind();
|
||||
glBufferData(GL_ARRAY_BUFFER, size, nullptr, usage);
|
||||
}
|
||||
void createFixedSize(GLsizei size, GLenum usage = GL_DYNAMIC_DRAW) {
|
||||
create();
|
||||
bind();
|
||||
glBufferData(GL_ARRAY_BUFFER, size, nullptr, usage);
|
||||
}
|
||||
|
||||
VertexBuffer(bool shouldCreate = false) {
|
||||
if (shouldCreate) {
|
||||
create();
|
||||
}
|
||||
}
|
||||
VertexBuffer(bool shouldCreate = false) {
|
||||
if (shouldCreate) {
|
||||
create();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef OPENGL_DESTRUCTORS
|
||||
~VertexBuffer() { free(); }
|
||||
#endif
|
||||
GLuint handle() const { return m_handle; }
|
||||
bool exists() const { return m_handle != 0; }
|
||||
void bind() const { glBindBuffer(GL_ARRAY_BUFFER, m_handle); }
|
||||
void free() { glDeleteBuffers(1, &m_handle); }
|
||||
~VertexBuffer() { free(); }
|
||||
#endif
|
||||
GLuint handle() const { return m_handle; }
|
||||
bool exists() const { return m_handle != 0; }
|
||||
void bind() const { glBindBuffer(GL_ARRAY_BUFFER, m_handle); }
|
||||
void free() { glDeleteBuffers(1, &m_handle); }
|
||||
|
||||
// Reallocates the buffer on every call. Prefer the sub version if possible.
|
||||
// Reallocates the buffer on every call. Prefer the sub version if possible.
|
||||
template <typename VertType>
|
||||
void bufferVerts(VertType* vertices, int vertCount, GLenum usage = GL_DYNAMIC_DRAW) {
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(VertType) * vertCount, vertices, usage);
|
||||
|
@ -471,7 +471,7 @@ namespace OpenGL {
|
|||
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(VertType) * vertCount, vertices);
|
||||
}
|
||||
|
||||
// If C++20 is available, add overloads that take std::span instead of raw pointers
|
||||
// If C++20 is available, add overloads that take std::span instead of raw pointers
|
||||
#ifdef OPENGL_HAVE_CPP20
|
||||
template <typename VertType>
|
||||
void bufferVerts(std::span<const VertType> vertices, GLenum usage = GL_DYNAMIC_DRAW) {
|
||||
|
@ -485,6 +485,48 @@ namespace OpenGL {
|
|||
#endif
|
||||
};
|
||||
|
||||
struct UniformBuffer {
|
||||
GLuint m_handle = 0;
|
||||
|
||||
void create() {
|
||||
if (m_handle == 0) {
|
||||
glGenBuffers(1, &m_handle);
|
||||
}
|
||||
}
|
||||
|
||||
void createFixedSize(GLsizei size, GLenum usage = GL_DYNAMIC_DRAW) {
|
||||
create();
|
||||
bind();
|
||||
glBufferData(GL_UNIFORM_BUFFER, size, nullptr, usage);
|
||||
}
|
||||
|
||||
UniformBuffer(bool shouldCreate = false) {
|
||||
if (shouldCreate) {
|
||||
create();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef OPENGL_DESTRUCTORS
|
||||
~UniformBuffer() { free(); }
|
||||
#endif
|
||||
GLuint handle() const { return m_handle; }
|
||||
bool exists() const { return m_handle != 0; }
|
||||
void bind() const { glBindBuffer(GL_UNIFORM_BUFFER, m_handle); }
|
||||
void free() { glDeleteBuffers(1, &m_handle); }
|
||||
|
||||
// Reallocates the buffer on every call. Prefer the sub version if possible.
|
||||
template <typename UniformType>
|
||||
void buffer(const UniformType& uniformData, GLenum usage = GL_DYNAMIC_DRAW) {
|
||||
glBufferData(GL_UNIFORM_BUFFER, sizeof(uniformData), &uniformData, usage);
|
||||
}
|
||||
|
||||
// Only use if you used createFixedSize
|
||||
template <typename UniformType>
|
||||
void bufferSub(const UniformType& uniformData, int vertCount, GLintptr offset = 0) {
|
||||
glBufferSubData(GL_UNIFORM_BUFFER, offset, sizeof(uniformData), &uniformData);
|
||||
}
|
||||
};
|
||||
|
||||
enum DepthFunc {
|
||||
Never = GL_NEVER, // Depth test never passes
|
||||
Always = GL_ALWAYS, // Depth test always passes
|
||||
|
@ -693,4 +735,4 @@ namespace OpenGL {
|
|||
|
||||
using Rect = Rectangle<GLuint>;
|
||||
|
||||
} // end namespace OpenGL
|
||||
} // end namespace OpenGL
|
||||
|
|
Loading…
Add table
Reference in a new issue