mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-07 14:45:41 +12:00
Merge branch 'master' into specialized-shaderz
This commit is contained in:
commit
9dc12e0681
77 changed files with 2403 additions and 305 deletions
56
.github/workflows/Android_Build.yml
vendored
56
.github/workflows/Android_Build.yml
vendored
|
@ -6,15 +6,19 @@ on:
|
||||||
- master
|
- master
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
env:
|
|
||||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
|
||||||
BUILD_TYPE: Release
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
x64:
|
x64:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
build_type:
|
||||||
|
- release
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Set BUILD_TYPE variable
|
||||||
|
run: echo "BUILD_TYPE=${{ matrix.build_type }}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Fetch submodules
|
- name: Fetch submodules
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
|
@ -29,7 +33,7 @@ jobs:
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'zulu' # See 'Supported distributions' for available options
|
distribution: 'zulu'
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
|
|
||||||
- name: Configure CMake
|
- name: Configure CMake
|
||||||
|
@ -37,23 +41,36 @@ jobs:
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
|
# Apply patch for GLES compatibility
|
||||||
git apply ./.github/gles.patch
|
git apply ./.github/gles.patch
|
||||||
cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
# Build the project with CMake
|
||||||
|
cmake --build ${{github.workspace}}/build --config ${{ env.BUILD_TYPE }}
|
||||||
|
# Move the generated library to the appropriate location
|
||||||
mv ./build/libAlber.so ./src/pandroid/app/src/main/jniLibs/x86_64/
|
mv ./build/libAlber.so ./src/pandroid/app/src/main/jniLibs/x86_64/
|
||||||
|
# Build the Android app with Gradle
|
||||||
cd src/pandroid
|
cd src/pandroid
|
||||||
./gradlew assembleDebug
|
./gradlew assemble${{ env.BUILD_TYPE }}
|
||||||
cd ../..
|
cd ../..
|
||||||
|
|
||||||
- name: Upload executable
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: Android APK (x86-64)
|
name: Android APKs (x86-64)
|
||||||
path: './src/pandroid/app/build/outputs/apk/debug/app-debug.apk'
|
path: |
|
||||||
|
./src/pandroid/app/build/outputs/apk/${{ env.BUILD_TYPE }}/app-${{ env.BUILD_TYPE }}.apk
|
||||||
|
|
||||||
arm64:
|
arm64:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
build_type:
|
||||||
|
- release
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Set BUILD_TYPE variable
|
||||||
|
run: echo "BUILD_TYPE=${{ matrix.build_type }}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Fetch submodules
|
- name: Fetch submodules
|
||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
|
@ -68,7 +85,7 @@ jobs:
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'zulu' # See 'Supported distributions' for available options
|
distribution: 'zulu'
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
|
|
||||||
- name: Configure CMake
|
- name: Configure CMake
|
||||||
|
@ -76,16 +93,21 @@ jobs:
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
|
# Apply patch for GLES compatibility
|
||||||
git apply ./.github/gles.patch
|
git apply ./.github/gles.patch
|
||||||
cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
# Build the project with CMake
|
||||||
|
cmake --build ${{github.workspace}}/build --config ${{ env.BUILD_TYPE }}
|
||||||
|
# Move the generated library to the appropriate location
|
||||||
mv ./build/libAlber.so ./src/pandroid/app/src/main/jniLibs/arm64-v8a/
|
mv ./build/libAlber.so ./src/pandroid/app/src/main/jniLibs/arm64-v8a/
|
||||||
|
# Build the Android app with Gradle
|
||||||
cd src/pandroid
|
cd src/pandroid
|
||||||
./gradlew assembleDebug
|
./gradlew assemble${{ env.BUILD_TYPE }}
|
||||||
|
ls -R app/build/outputs
|
||||||
cd ../..
|
cd ../..
|
||||||
|
|
||||||
- name: Upload executable
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: Android APK (arm64)
|
name: Android APKs (arm64)
|
||||||
path: './src/pandroid/app/build/outputs/apk/debug/app-debug.apk'
|
path: |
|
||||||
|
./src/pandroid/app/build/outputs/apk/${{ env.BUILD_TYPE }}/app-${{ env.BUILD_TYPE }}.apk
|
||||||
|
|
6
.gitmodules
vendored
6
.gitmodules
vendored
|
@ -49,3 +49,9 @@
|
||||||
[submodule "third_party/oaknut"]
|
[submodule "third_party/oaknut"]
|
||||||
path = third_party/oaknut
|
path = third_party/oaknut
|
||||||
url = https://github.com/merryhime/oaknut
|
url = https://github.com/merryhime/oaknut
|
||||||
|
[submodule "third_party/luv"]
|
||||||
|
path = third_party/luv
|
||||||
|
url = https://github.com/luvit/luv
|
||||||
|
[submodule "third_party/libuv"]
|
||||||
|
path = third_party/libuv
|
||||||
|
url = https://github.com/libuv/libuv
|
||||||
|
|
|
@ -188,17 +188,19 @@ set(FS_SOURCE_FILES src/core/fs/archive_self_ncch.cpp src/core/fs/archive_save_d
|
||||||
src/core/fs/ivfc.cpp src/core/fs/archive_user_save_data.cpp src/core/fs/archive_system_save_data.cpp
|
src/core/fs/ivfc.cpp src/core/fs/archive_user_save_data.cpp src/core/fs/archive_system_save_data.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(APPLET_SOURCE_FILES src/core/applets/applet.cpp src/core/applets/mii_selector.cpp src/core/applets/software_keyboard.cpp src/core/applets/applet_manager.cpp)
|
set(APPLET_SOURCE_FILES src/core/applets/applet.cpp src/core/applets/mii_selector.cpp src/core/applets/software_keyboard.cpp src/core/applets/applet_manager.cpp
|
||||||
|
src/core/applets/error_applet.cpp
|
||||||
|
)
|
||||||
set(RENDERER_SW_SOURCE_FILES src/core/renderer_sw/renderer_sw.cpp)
|
set(RENDERER_SW_SOURCE_FILES src/core/renderer_sw/renderer_sw.cpp)
|
||||||
|
|
||||||
# Frontend source files
|
# Frontend source files
|
||||||
if(NOT ANDROID)
|
if(NOT ANDROID)
|
||||||
if(ENABLE_QT_GUI)
|
if(ENABLE_QT_GUI)
|
||||||
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
|
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/config_window.cpp src/panda_qt/zep.cpp src/panda_qt/text_editor.cpp src/panda_qt/cheats_window.cpp
|
||||||
)
|
)
|
||||||
set(FRONTEND_HEADER_FILES include/panda_qt/screen.hpp include/panda_qt/main_window.hpp include/panda_qt/about_window.hpp
|
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/config_window.hpp include/panda_qt/text_editor.hpp include/panda_qt/cheats_window.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
source_group("Source Files\\Qt" FILES ${FRONTEND_SOURCE_FILES})
|
source_group("Source Files\\Qt" FILES ${FRONTEND_SOURCE_FILES})
|
||||||
|
@ -244,7 +246,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp
|
||||||
include/services/news_u.hpp include/applets/software_keyboard.hpp include/applets/applet_manager.hpp include/fs/archive_user_save_data.hpp
|
include/services/news_u.hpp include/applets/software_keyboard.hpp include/applets/applet_manager.hpp include/fs/archive_user_save_data.hpp
|
||||||
include/services/amiibo_device.hpp include/services/nfc_types.hpp include/swap.hpp include/services/csnd.hpp include/services/nwm_uds.hpp
|
include/services/amiibo_device.hpp include/services/nfc_types.hpp include/swap.hpp include/services/csnd.hpp include/services/nwm_uds.hpp
|
||||||
include/fs/archive_system_save_data.hpp include/lua_manager.hpp include/memory_mapped_file.hpp include/hydra_icon.hpp
|
include/fs/archive_system_save_data.hpp include/lua_manager.hpp include/memory_mapped_file.hpp include/hydra_icon.hpp
|
||||||
include/PICA/dynapica/shader_rec_emitter_arm64.hpp include/PICA/shader_gen.hpp
|
include/PICA/dynapica/shader_rec_emitter_arm64.hpp include/scheduler.hpp include/applets/error_applet.hpp include/PICA/shader_gen.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
cmrc_add_resource_library(
|
cmrc_add_resource_library(
|
||||||
|
@ -264,6 +266,16 @@ set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp
|
||||||
third_party/xxhash/xxhash.c
|
third_party/xxhash/xxhash.c
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(ENABLE_LUAJIT AND NOT ANDROID)
|
||||||
|
# Build luv and libuv for Lua TCP server usage if we're not on Android
|
||||||
|
include_directories(third_party/luv/src)
|
||||||
|
include_directories(third_party/luv/deps/lua-compat-5.3/c-api)
|
||||||
|
include_directories(third_party/libuv/include)
|
||||||
|
set(THIRD_PARTY_SOURCE_FILES ${THIRD_PARTY_SOURCE_FILES} third_party/luv/src/luv.c)
|
||||||
|
set(LIBUV_BUILD_SHARED OFF)
|
||||||
|
|
||||||
|
add_subdirectory(third_party/libuv)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(ENABLE_QT_GUI)
|
if(ENABLE_QT_GUI)
|
||||||
include_directories(third_party/duckstation)
|
include_directories(third_party/duckstation)
|
||||||
|
@ -431,6 +443,11 @@ endif()
|
||||||
if(ENABLE_LUAJIT)
|
if(ENABLE_LUAJIT)
|
||||||
target_compile_definitions(Alber PUBLIC "PANDA3DS_ENABLE_LUA=1")
|
target_compile_definitions(Alber PUBLIC "PANDA3DS_ENABLE_LUA=1")
|
||||||
target_link_libraries(Alber PRIVATE libluajit)
|
target_link_libraries(Alber PRIVATE libluajit)
|
||||||
|
|
||||||
|
# If we're not on Android, link libuv too
|
||||||
|
if (NOT ANDROID)
|
||||||
|
target_link_libraries(Alber PRIVATE uv_a)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(ENABLE_OPENGL)
|
if(ENABLE_OPENGL)
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
|
#include "kernel/kernel_types.hpp"
|
||||||
#include "memory.hpp"
|
#include "memory.hpp"
|
||||||
#include "result/result.hpp"
|
#include "result/result.hpp"
|
||||||
|
|
||||||
|
@ -65,10 +68,11 @@ namespace Applets {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Parameter {
|
struct Parameter {
|
||||||
u32 senderID;
|
u32 senderID; // ID of the parameter sender
|
||||||
u32 destID;
|
u32 destID; // ID of the app to receive parameter
|
||||||
u32 signal;
|
u32 signal; // Signal type (eg request)
|
||||||
std::vector<u8> data;
|
u32 object; // Some applets will also respond with shared memory handles for transferring data between the sender and called
|
||||||
|
std::vector<u8> data; // Misc data
|
||||||
};
|
};
|
||||||
|
|
||||||
class AppletBase {
|
class AppletBase {
|
||||||
|
@ -80,7 +84,7 @@ namespace Applets {
|
||||||
virtual const char* name() = 0;
|
virtual const char* name() = 0;
|
||||||
|
|
||||||
// Called by APT::StartLibraryApplet and similar
|
// Called by APT::StartLibraryApplet and similar
|
||||||
virtual Result::HorizonResult start() = 0;
|
virtual Result::HorizonResult start(const MemoryBlock* sharedMem, const std::vector<u8>& parameters, u32 appID) = 0;
|
||||||
// Transfer parameters from application -> applet
|
// Transfer parameters from application -> applet
|
||||||
virtual Result::HorizonResult receiveParameter(const Parameter& parameter) = 0;
|
virtual Result::HorizonResult receiveParameter(const Parameter& parameter) = 0;
|
||||||
virtual void reset() = 0;
|
virtual void reset() = 0;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
|
#include "applets/error_applet.hpp"
|
||||||
#include "applets/mii_selector.hpp"
|
#include "applets/mii_selector.hpp"
|
||||||
#include "applets/software_keyboard.hpp"
|
#include "applets/software_keyboard.hpp"
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
|
@ -11,6 +12,7 @@ namespace Applets {
|
||||||
class AppletManager {
|
class AppletManager {
|
||||||
MiiSelectorApplet miiSelector;
|
MiiSelectorApplet miiSelector;
|
||||||
SoftwareKeyboardApplet swkbd;
|
SoftwareKeyboardApplet swkbd;
|
||||||
|
ErrorApplet error;
|
||||||
std::optional<Applets::Parameter> nextParameter = std::nullopt;
|
std::optional<Applets::Parameter> nextParameter = std::nullopt;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
15
include/applets/error_applet.hpp
Normal file
15
include/applets/error_applet.hpp
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "applets/applet.hpp"
|
||||||
|
|
||||||
|
namespace Applets {
|
||||||
|
class ErrorApplet final : public AppletBase {
|
||||||
|
public:
|
||||||
|
virtual const char* name() override { return "Error/EULA Agreement"; }
|
||||||
|
virtual Result::HorizonResult start(const MemoryBlock* sharedMem, const std::vector<u8>& parameters, u32 appID) override;
|
||||||
|
virtual Result::HorizonResult receiveParameter(const Applets::Parameter& parameter) override;
|
||||||
|
virtual void reset() override;
|
||||||
|
|
||||||
|
ErrorApplet(Memory& memory, std::optional<Parameter>& nextParam) : AppletBase(memory, nextParam) {}
|
||||||
|
};
|
||||||
|
} // namespace Applets
|
|
@ -1,13 +1,83 @@
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "applets/applet.hpp"
|
#include "applets/applet.hpp"
|
||||||
|
#include "swap.hpp"
|
||||||
|
|
||||||
namespace Applets {
|
namespace Applets {
|
||||||
|
struct MiiConfig {
|
||||||
|
u8 enableCancelButton;
|
||||||
|
u8 enableGuestMii;
|
||||||
|
u8 showOnTopScreen;
|
||||||
|
std::array<u8, 0x5> pad1;
|
||||||
|
std::array<u16_le, 0x40> title;
|
||||||
|
std::array<u8, 0x4> pad2;
|
||||||
|
u8 showGuestMiis;
|
||||||
|
std::array<u8, 0x3> pad3;
|
||||||
|
u32 initiallySelectedIndex;
|
||||||
|
std::array<u8, 0x6> guestMiiWhitelist;
|
||||||
|
std::array<u8, 0x64> userMiiWhitelist;
|
||||||
|
std::array<u8, 0x2> pad4;
|
||||||
|
u32 magicValue;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(MiiConfig) == 0x104, "Mii config size is wrong");
|
||||||
|
|
||||||
|
// Some members of this struct are not properly aligned so we need pragma pack
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct MiiData {
|
||||||
|
u8 version;
|
||||||
|
u8 miiOptions;
|
||||||
|
u8 miiPos;
|
||||||
|
u8 consoleID;
|
||||||
|
|
||||||
|
u64_be systemID;
|
||||||
|
u32_be miiID;
|
||||||
|
std::array<u8, 0x6> creatorMAC;
|
||||||
|
u16 padding;
|
||||||
|
|
||||||
|
u16_be miiDetails;
|
||||||
|
std::array<char16_t, 0xA> miiName;
|
||||||
|
u8 height;
|
||||||
|
u8 width;
|
||||||
|
|
||||||
|
u8 faceStyle;
|
||||||
|
u8 faceDetails;
|
||||||
|
u8 hairStyle;
|
||||||
|
u8 hairDetails;
|
||||||
|
u32_be eyeDetails;
|
||||||
|
u32_be eyebrowDetails;
|
||||||
|
u16_be noseDetails;
|
||||||
|
u16_be mouthDetails;
|
||||||
|
u16_be moustacheDetails;
|
||||||
|
u16_be beardDetails;
|
||||||
|
u16_be glassesDetails;
|
||||||
|
u16_be moleDetails;
|
||||||
|
|
||||||
|
std::array<char16_t, 0xA> authorName;
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
static_assert(sizeof(MiiData) == 0x5C, "MiiData structure has incorrect size");
|
||||||
|
|
||||||
|
struct MiiResult {
|
||||||
|
u32_be returnCode;
|
||||||
|
u32_be isGuestMiiSelected;
|
||||||
|
u32_be selectedGuestMiiIndex;
|
||||||
|
MiiData selectedMiiData;
|
||||||
|
u16_be unknown1;
|
||||||
|
u16_be miiChecksum;
|
||||||
|
std::array<u16_le, 0xC> guestMiiName;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(MiiResult) == 0x84, "MiiResult structure has incorrect size");
|
||||||
|
|
||||||
class MiiSelectorApplet final : public AppletBase {
|
class MiiSelectorApplet final : public AppletBase {
|
||||||
public:
|
public:
|
||||||
virtual const char* name() override { return "Mii Selector"; }
|
virtual const char* name() override { return "Mii Selector"; }
|
||||||
virtual Result::HorizonResult start() override;
|
virtual Result::HorizonResult start(const MemoryBlock* sharedMem, const std::vector<u8>& parameters, u32 appID) override;
|
||||||
virtual Result::HorizonResult receiveParameter(const Applets::Parameter& parameter) override;
|
virtual Result::HorizonResult receiveParameter(const Applets::Parameter& parameter) override;
|
||||||
virtual void reset() override;
|
virtual void reset() override;
|
||||||
|
|
||||||
|
MiiResult output;
|
||||||
|
MiiConfig config;
|
||||||
|
MiiResult getDefaultMii();
|
||||||
MiiSelectorApplet(Memory& memory, std::optional<Parameter>& nextParam) : AppletBase(memory, nextParam) {}
|
MiiSelectorApplet(Memory& memory, std::optional<Parameter>& nextParam) : AppletBase(memory, nextParam) {}
|
||||||
};
|
};
|
||||||
} // namespace Applets
|
} // namespace Applets
|
|
@ -1,13 +1,162 @@
|
||||||
|
#include <array>
|
||||||
|
|
||||||
#include "applets/applet.hpp"
|
#include "applets/applet.hpp"
|
||||||
|
#include "swap.hpp"
|
||||||
|
|
||||||
namespace Applets {
|
namespace Applets {
|
||||||
|
// Software keyboard definitions adapted from libctru/Citra
|
||||||
|
// Keyboard input filtering flags. Allows the caller to specify what input is explicitly not allowed
|
||||||
|
namespace SoftwareKeyboardFilter {
|
||||||
|
enum Filter : u32 {
|
||||||
|
Digits = 1, // Disallow the use of more than a certain number of digits (0 or more)
|
||||||
|
At = 1 << 1, // Disallow the use of the @ sign.
|
||||||
|
Percent = 1 << 2, // Disallow the use of the % sign.
|
||||||
|
Backslash = 1 << 3, // Disallow the use of the \ sign.
|
||||||
|
Profanity = 1 << 4, // Disallow profanity using Nintendo's profanity filter.
|
||||||
|
Callback = 1 << 5, // Use a callback in order to check the input.
|
||||||
|
};
|
||||||
|
} // namespace SoftwareKeyboardFilter
|
||||||
|
|
||||||
|
// Keyboard features.
|
||||||
|
namespace SoftwareKeyboardFeature {
|
||||||
|
enum Feature {
|
||||||
|
Parental = 1, // Parental PIN mode.
|
||||||
|
DarkenTopScreen = 1 << 1, // Darken the top screen when the keyboard is shown.
|
||||||
|
PredictiveInput = 1 << 2, // Enable predictive input (necessary for Kanji input in JPN systems).
|
||||||
|
Multiline = 1 << 3, // Enable multiline input.
|
||||||
|
FixedWidth = 1 << 4, // Enable fixed-width mode.
|
||||||
|
AllowHome = 1 << 5, // Allow the usage of the HOME button.
|
||||||
|
AllowReset = 1 << 6, // Allow the usage of a software-reset combination.
|
||||||
|
AllowPower = 1 << 7, // Allow the usage of the POWER button.
|
||||||
|
DefaultQWERTY = 1 << 9, // Default to the QWERTY page when the keyboard is shown.
|
||||||
|
};
|
||||||
|
} // namespace SoftwareKeyboardFeature
|
||||||
|
|
||||||
class SoftwareKeyboardApplet final : public AppletBase {
|
class SoftwareKeyboardApplet final : public AppletBase {
|
||||||
public:
|
public:
|
||||||
|
static constexpr int MAX_BUTTON = 3; // Maximum number of buttons that can be in the keyboard.
|
||||||
|
static constexpr int MAX_BUTTON_TEXT_LEN = 16; // Maximum button text length, in UTF-16 code units.
|
||||||
|
static constexpr int MAX_HINT_TEXT_LEN = 64; // Maximum hint text length, in UTF-16 code units.
|
||||||
|
static constexpr int MAX_CALLBACK_MSG_LEN = 256; // Maximum filter callback error message length, in UTF-16 code units.
|
||||||
|
|
||||||
|
// Keyboard types
|
||||||
|
enum class SoftwareKeyboardType : u32 {
|
||||||
|
Normal, // Normal keyboard with several pages (QWERTY/accents/symbol/mobile)
|
||||||
|
QWERTY, // QWERTY keyboard only.
|
||||||
|
NumPad, // Number pad.
|
||||||
|
Western, // On JPN systems, a text keyboard without Japanese input capabilities, otherwise same as SWKBD_TYPE_NORMAL.
|
||||||
|
};
|
||||||
|
|
||||||
|
// Keyboard dialog buttons.
|
||||||
|
enum class SoftwareKeyboardButtonConfig : u32 {
|
||||||
|
SingleButton, // Ok button
|
||||||
|
DualButton, // Cancel | Ok buttons
|
||||||
|
TripleButton, // Cancel | I Forgot | Ok buttons
|
||||||
|
NoButton, // No button (returned by swkbdInputText in special cases)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Accepted input types.
|
||||||
|
enum class SoftwareKeyboardValidInput : u32 {
|
||||||
|
Anything, // All inputs are accepted.
|
||||||
|
NotEmpty, // Empty inputs are not accepted.
|
||||||
|
NotEmptyNotBlank, // Empty or blank inputs (consisting solely of whitespace) are not accepted.
|
||||||
|
NotBlank, // Blank inputs (consisting solely of whitespace) are not accepted, but empty inputs are.
|
||||||
|
FixedLen, // The input must have a fixed length (specified by maxTextLength in swkbdInit)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Keyboard password modes.
|
||||||
|
enum class SoftwareKeyboardPasswordMode : u32 {
|
||||||
|
None, // Characters are not concealed.
|
||||||
|
Hide, // Characters are concealed immediately.
|
||||||
|
HideDelay, // Characters are concealed a second after they've been typed.
|
||||||
|
};
|
||||||
|
|
||||||
|
// Keyboard filter callback return values.
|
||||||
|
enum class SoftwareKeyboardCallbackResult : u32 {
|
||||||
|
OK, // Specifies that the input is valid.
|
||||||
|
Close, // Displays an error message, then closes the keyboard.
|
||||||
|
Continue, // Displays an error message and continues displaying the keyboard.
|
||||||
|
};
|
||||||
|
|
||||||
|
// Keyboard return values.
|
||||||
|
enum class SoftwareKeyboardResult : s32 {
|
||||||
|
None = -1, // Dummy/unused.
|
||||||
|
InvalidInput = -2, // Invalid parameters to swkbd.
|
||||||
|
OutOfMem = -3, // Out of memory.
|
||||||
|
|
||||||
|
D0Click = 0, // The button was clicked in 1-button dialogs.
|
||||||
|
D1Click0, // The left button was clicked in 2-button dialogs.
|
||||||
|
D1Click1, // The right button was clicked in 2-button dialogs.
|
||||||
|
D2Click0, // The left button was clicked in 3-button dialogs.
|
||||||
|
D2Click1, // The middle button was clicked in 3-button dialogs.
|
||||||
|
D2Click2, // The right button was clicked in 3-button dialogs.
|
||||||
|
|
||||||
|
HomePressed = 10, // The HOME button was pressed.
|
||||||
|
ResetPressed, // The soft-reset key combination was pressed.
|
||||||
|
PowerPressed, // The POWER button was pressed.
|
||||||
|
|
||||||
|
ParentalOK = 20, // The parental PIN was verified successfully.
|
||||||
|
ParentalFail, // The parental PIN was incorrect.
|
||||||
|
|
||||||
|
BannedInput = 30, // The filter callback returned SoftwareKeyboardCallback::CLOSE.
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SoftwareKeyboardConfig {
|
||||||
|
enum_le<SoftwareKeyboardType> type;
|
||||||
|
enum_le<SoftwareKeyboardButtonConfig> numButtonsM1;
|
||||||
|
enum_le<SoftwareKeyboardValidInput> validInput;
|
||||||
|
enum_le<SoftwareKeyboardPasswordMode> passwordMode;
|
||||||
|
s32_le isParentalScreen;
|
||||||
|
s32_le darkenTopScreen;
|
||||||
|
u32_le filterFlags;
|
||||||
|
u32_le saveStateFlags;
|
||||||
|
u16_le maxTextLength;
|
||||||
|
u16_le dictWordCount;
|
||||||
|
u16_le maxDigits;
|
||||||
|
std::array<std::array<u16_le, MAX_BUTTON_TEXT_LEN + 1>, MAX_BUTTON> buttonText;
|
||||||
|
std::array<u16_le, 2> numpadKeys;
|
||||||
|
std::array<u16_le, MAX_HINT_TEXT_LEN + 1> hintText; // Text to display when asking the user for input
|
||||||
|
bool predictiveInput;
|
||||||
|
bool multiline;
|
||||||
|
bool fixedWidth;
|
||||||
|
bool allowHome;
|
||||||
|
bool allowReset;
|
||||||
|
bool allowPower;
|
||||||
|
bool unknown;
|
||||||
|
bool defaultQwerty;
|
||||||
|
std::array<bool, 4> buttonSubmitsText;
|
||||||
|
u16_le language;
|
||||||
|
|
||||||
|
u32_le initialTextOffset; // Offset of the default text in the output SharedMemory
|
||||||
|
u32_le dictOffset;
|
||||||
|
u32_le initialStatusOffset;
|
||||||
|
u32_le initialLearningOffset;
|
||||||
|
u32_le sharedMemorySize; // Size of the SharedMemory
|
||||||
|
u32_le version;
|
||||||
|
|
||||||
|
enum_le<SoftwareKeyboardResult> returnCode;
|
||||||
|
|
||||||
|
u32_le statusOffset;
|
||||||
|
u32_le learningOffset;
|
||||||
|
|
||||||
|
u32_le textOffset; // Offset in the SharedMemory where the output text starts
|
||||||
|
u16_le textLength; // Length in characters of the output text
|
||||||
|
|
||||||
|
enum_le<SoftwareKeyboardCallbackResult> callbackResult;
|
||||||
|
std::array<u16_le, MAX_CALLBACK_MSG_LEN + 1> callbackMessage;
|
||||||
|
bool skipAtCheck;
|
||||||
|
std::array<u8, 0xAB> pad;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(SoftwareKeyboardConfig) == 0x400, "Software keyboard config size is wrong");
|
||||||
|
|
||||||
virtual const char* name() override { return "Software Keyboard"; }
|
virtual const char* name() override { return "Software Keyboard"; }
|
||||||
virtual Result::HorizonResult start() override;
|
virtual Result::HorizonResult start(const MemoryBlock* sharedMem, const std::vector<u8>& parameters, u32 appID) override;
|
||||||
virtual Result::HorizonResult receiveParameter(const Applets::Parameter& parameter) override;
|
virtual Result::HorizonResult receiveParameter(const Applets::Parameter& parameter) override;
|
||||||
virtual void reset() override;
|
virtual void reset() override;
|
||||||
|
|
||||||
SoftwareKeyboardApplet(Memory& memory, std::optional<Parameter>& nextParam) : AppletBase(memory, nextParam) {}
|
SoftwareKeyboardApplet(Memory& memory, std::optional<Parameter>& nextParam) : AppletBase(memory, nextParam) {}
|
||||||
|
void closeKeyboard(u32 appID);
|
||||||
|
|
||||||
|
SoftwareKeyboardConfig config;
|
||||||
};
|
};
|
||||||
} // namespace Applets
|
} // namespace Applets
|
|
@ -24,6 +24,7 @@ class Cheats {
|
||||||
|
|
||||||
Cheats(Memory& mem, HIDService& hid);
|
Cheats(Memory& mem, HIDService& hid);
|
||||||
u32 addCheat(const Cheat& cheat);
|
u32 addCheat(const Cheat& cheat);
|
||||||
|
u32 addCheat(const u8* data, size_t size);
|
||||||
void removeCheat(u32 id);
|
void removeCheat(u32 id);
|
||||||
void enableCheat(u32 id);
|
void enableCheat(u32 id);
|
||||||
void disableCheat(u32 id);
|
void disableCheat(u32 id);
|
||||||
|
@ -32,6 +33,7 @@ class Cheats {
|
||||||
|
|
||||||
void clear();
|
void clear();
|
||||||
bool haveCheats() const { return cheatsLoaded; }
|
bool haveCheats() const { return cheatsLoaded; }
|
||||||
|
static constexpr u32 badCheatHandle = 0xFFFFFFFF;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ActionReplay ar; // An ActionReplay cheat machine for executing CTRPF codes
|
ActionReplay ar; // An ActionReplay cheat machine for executing CTRPF codes
|
||||||
|
|
|
@ -9,15 +9,18 @@
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "kernel.hpp"
|
#include "kernel.hpp"
|
||||||
#include "memory.hpp"
|
#include "memory.hpp"
|
||||||
|
#include "scheduler.hpp"
|
||||||
|
|
||||||
|
class Emulator;
|
||||||
class CPU;
|
class CPU;
|
||||||
|
|
||||||
class MyEnvironment final : public Dynarmic::A32::UserCallbacks {
|
class MyEnvironment final : public Dynarmic::A32::UserCallbacks {
|
||||||
public:
|
public:
|
||||||
u64 ticksLeft = 0;
|
u64 ticksLeft = 0;
|
||||||
u64 totalTicks = 0;
|
u64 totalTicks = 0;
|
||||||
Memory& mem;
|
Memory& mem;
|
||||||
Kernel& kernel;
|
Kernel& kernel;
|
||||||
|
Scheduler& scheduler;
|
||||||
|
|
||||||
u64 getCyclesForInstruction(bool isThumb, u32 instruction);
|
u64 getCyclesForInstruction(bool isThumb, u32 instruction);
|
||||||
|
|
||||||
|
@ -76,54 +79,56 @@ public:
|
||||||
std::terminate();
|
std::terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CallSVC(u32 swi) override {
|
void CallSVC(u32 swi) override {
|
||||||
kernel.serviceSVC(swi);
|
kernel.serviceSVC(swi);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override {
|
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override {
|
||||||
switch (exception) {
|
switch (exception) {
|
||||||
case Dynarmic::A32::Exception::UnpredictableInstruction:
|
case Dynarmic::A32::Exception::UnpredictableInstruction:
|
||||||
Helpers::panic("Unpredictable instruction at pc = %08X", pc);
|
Helpers::panic("Unpredictable instruction at pc = %08X", pc);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: Helpers::panic("Fired exception oops");
|
default: Helpers::panic("Fired exception oops");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddTicks(u64 ticks) override {
|
void AddTicks(u64 ticks) override {
|
||||||
totalTicks += ticks;
|
scheduler.currentTimestamp += ticks;
|
||||||
|
|
||||||
if (ticks > ticksLeft) {
|
if (ticks > ticksLeft) {
|
||||||
ticksLeft = 0;
|
ticksLeft = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ticksLeft -= ticks;
|
ticksLeft -= ticks;
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 GetTicksRemaining() override {
|
u64 GetTicksRemaining() override {
|
||||||
return ticksLeft;
|
return ticksLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 GetTicksForCode(bool isThumb, u32 vaddr, u32 instruction) override {
|
u64 GetTicksForCode(bool isThumb, u32 vaddr, u32 instruction) override {
|
||||||
return getCyclesForInstruction(isThumb, instruction);
|
return getCyclesForInstruction(isThumb, instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
MyEnvironment(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
|
MyEnvironment(Memory& mem, Kernel& kernel, Scheduler& scheduler) : mem(mem), kernel(kernel), scheduler(scheduler) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class CPU {
|
class CPU {
|
||||||
std::unique_ptr<Dynarmic::A32::Jit> jit;
|
std::unique_ptr<Dynarmic::A32::Jit> jit;
|
||||||
std::shared_ptr<CP15> cp15;
|
std::shared_ptr<CP15> cp15;
|
||||||
|
|
||||||
// Make exclusive monitor with only 1 CPU core
|
// Make exclusive monitor with only 1 CPU core
|
||||||
Dynarmic::ExclusiveMonitor exclusiveMonitor{1};
|
Dynarmic::ExclusiveMonitor exclusiveMonitor{1};
|
||||||
MyEnvironment env;
|
MyEnvironment env;
|
||||||
Memory& mem;
|
Memory& mem;
|
||||||
|
Scheduler& scheduler;
|
||||||
|
Emulator& emu;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static constexpr u64 ticksPerSec = 268111856;
|
static constexpr u64 ticksPerSec = Scheduler::arm11Clock;
|
||||||
|
|
||||||
CPU(Memory& mem, Kernel& kernel);
|
CPU(Memory& mem, Kernel& kernel, Emulator& emu);
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
void setReg(int index, u32 value) {
|
void setReg(int index, u32 value) {
|
||||||
|
@ -162,29 +167,20 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 getTicks() {
|
u64 getTicks() {
|
||||||
return env.totalTicks;
|
return scheduler.currentTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get reference to tick count. Memory needs access to this
|
// Get reference to tick count. Memory needs access to this
|
||||||
u64& getTicksRef() {
|
u64& getTicksRef() {
|
||||||
return env.totalTicks;
|
return scheduler.currentTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearCache() { jit->ClearCache(); }
|
Scheduler& getScheduler() {
|
||||||
|
return scheduler;
|
||||||
void runFrame() {
|
|
||||||
env.ticksLeft = ticksPerSec / 60;
|
|
||||||
execute:
|
|
||||||
const auto exitReason = jit->Run();
|
|
||||||
|
|
||||||
if (static_cast<u32>(exitReason) != 0) [[unlikely]] {
|
|
||||||
// Cache invalidation needs to exit the JIT so it returns a CacheInvalidation HaltReason. In our case, we just go back to executing
|
|
||||||
// The goto might be terrible but it does guarantee that this does not recursively call run and crash, instead getting optimized to a jump
|
|
||||||
if (Dynarmic::Has(exitReason, Dynarmic::HaltReason::CacheInvalidation)) {
|
|
||||||
goto execute;
|
|
||||||
} else {
|
|
||||||
Helpers::panic("Exit reason: %d\nPC: %08X", static_cast<u32>(exitReason), getReg(15));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addTicks(u64 ticks) { env.AddTicks(ticks); }
|
||||||
|
|
||||||
|
void clearCache() { jit->ClearCache(); }
|
||||||
|
void runFrame();
|
||||||
};
|
};
|
|
@ -15,6 +15,7 @@
|
||||||
#include "io_file.hpp"
|
#include "io_file.hpp"
|
||||||
#include "lua_manager.hpp"
|
#include "lua_manager.hpp"
|
||||||
#include "memory.hpp"
|
#include "memory.hpp"
|
||||||
|
#include "scheduler.hpp"
|
||||||
|
|
||||||
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
||||||
#include "http_server.hpp"
|
#include "http_server.hpp"
|
||||||
|
@ -42,6 +43,7 @@ class Emulator {
|
||||||
Kernel kernel;
|
Kernel kernel;
|
||||||
Crypto::AESEngine aesEngine;
|
Crypto::AESEngine aesEngine;
|
||||||
Cheats cheats;
|
Cheats cheats;
|
||||||
|
Scheduler scheduler;
|
||||||
|
|
||||||
// Variables to keep track of whether the user is controlling the 3DS analog stick with their keyboard
|
// Variables to keep track of whether the user is controlling the 3DS analog stick with their keyboard
|
||||||
// This is done so when a gamepad is connected, we won't automatically override the 3DS analog stick settings with the gamepad's state
|
// This is done so when a gamepad is connected, we won't automatically override the 3DS analog stick settings with the gamepad's state
|
||||||
|
@ -85,6 +87,8 @@ class Emulator {
|
||||||
// change ROMs. If Reload is selected, the emulator will reload its selected ROM. This is useful for eg a "reset" button that keeps the current
|
// change ROMs. If Reload is selected, the emulator will reload its selected ROM. This is useful for eg a "reset" button that keeps the current
|
||||||
// ROM and just resets the emu
|
// ROM and just resets the emu
|
||||||
enum class ReloadOption { NoReload, Reload };
|
enum class ReloadOption { NoReload, Reload };
|
||||||
|
// Used in CPU::runFrame
|
||||||
|
bool frameDone = false;
|
||||||
|
|
||||||
Emulator();
|
Emulator();
|
||||||
~Emulator();
|
~Emulator();
|
||||||
|
@ -94,6 +98,8 @@ class Emulator {
|
||||||
void reset(ReloadOption reload);
|
void reset(ReloadOption reload);
|
||||||
void run(void* frontend = nullptr);
|
void run(void* frontend = nullptr);
|
||||||
void runFrame();
|
void runFrame();
|
||||||
|
// Poll the scheduler for events
|
||||||
|
void pollScheduler();
|
||||||
|
|
||||||
void resume(); // Resume the emulator
|
void resume(); // Resume the emulator
|
||||||
void pause(); // Pause the emulator
|
void pause(); // Pause the emulator
|
||||||
|
@ -121,6 +127,7 @@ class Emulator {
|
||||||
Cheats& getCheats() { return cheats; }
|
Cheats& getCheats() { return cheats; }
|
||||||
ServiceManager& getServiceManager() { return kernel.getServiceManager(); }
|
ServiceManager& getServiceManager() { return kernel.getServiceManager(); }
|
||||||
LuaManager& getLua() { return lua; }
|
LuaManager& getLua() { return lua; }
|
||||||
|
Scheduler& getScheduler() { return scheduler; }
|
||||||
|
|
||||||
RendererType getRendererType() const { return config.rendererType; }
|
RendererType getRendererType() const { return config.rendererType; }
|
||||||
Renderer* getRenderer() { return gpu.getRenderer(); }
|
Renderer* getRenderer() { return gpu.getRenderer(); }
|
||||||
|
@ -128,6 +135,8 @@ class Emulator {
|
||||||
|
|
||||||
std::filesystem::path getConfigPath();
|
std::filesystem::path getConfigPath();
|
||||||
std::filesystem::path getAndroidAppPath();
|
std::filesystem::path getAndroidAppPath();
|
||||||
|
// Get the root path for the emulator's app data
|
||||||
|
std::filesystem::path getAppDataRoot();
|
||||||
|
|
||||||
std::span<u8> getSMDH();
|
std::span<u8> getSMDH();
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,8 +5,10 @@
|
||||||
using Result::HorizonResult;
|
using Result::HorizonResult;
|
||||||
|
|
||||||
class SDMCArchive : public ArchiveBase {
|
class SDMCArchive : public ArchiveBase {
|
||||||
public:
|
bool isWriteOnly = false; // There's 2 variants of the SDMC archive: Regular one (Read/Write) and write-only
|
||||||
SDMCArchive(Memory& mem) : ArchiveBase(mem) {}
|
|
||||||
|
public:
|
||||||
|
SDMCArchive(Memory& mem, bool writeOnly = false) : ArchiveBase(mem), isWriteOnly(writeOnly) {}
|
||||||
|
|
||||||
u64 getFreeBytes() override { return 1_GB; }
|
u64 getFreeBytes() override { return 1_GB; }
|
||||||
std::string name() override { return "SDMC"; }
|
std::string name() override { return "SDMC"; }
|
||||||
|
|
|
@ -53,6 +53,7 @@ namespace KernelHandles {
|
||||||
GSPSharedMemHandle = MaxServiceHandle + 1, // Handle for the GSP shared memory
|
GSPSharedMemHandle = MaxServiceHandle + 1, // Handle for the GSP shared memory
|
||||||
FontSharedMemHandle,
|
FontSharedMemHandle,
|
||||||
CSNDSharedMemHandle,
|
CSNDSharedMemHandle,
|
||||||
|
APTCaptureSharedMemHandle, // Shared memory for display capture info,
|
||||||
HIDSharedMemHandle,
|
HIDSharedMemHandle,
|
||||||
|
|
||||||
MinSharedMemHandle = GSPSharedMemHandle,
|
MinSharedMemHandle = GSPSharedMemHandle,
|
||||||
|
|
|
@ -70,6 +70,7 @@ public:
|
||||||
Handle makeMutex(bool locked = false); // Needs to be public to be accessible to the APT/DSP services
|
Handle makeMutex(bool locked = false); // Needs to be public to be accessible to the APT/DSP services
|
||||||
Handle makeSemaphore(u32 initialCount, u32 maximumCount); // Needs to be public to be accessible to the service manager port
|
Handle makeSemaphore(u32 initialCount, u32 maximumCount); // Needs to be public to be accessible to the service manager port
|
||||||
Handle makeTimer(ResetType resetType);
|
Handle makeTimer(ResetType resetType);
|
||||||
|
void pollTimers();
|
||||||
|
|
||||||
// Signals an event, returns true on success or false if the event does not exist
|
// Signals an event, returns true on success or false if the event does not exist
|
||||||
bool signalEvent(Handle e);
|
bool signalEvent(Handle e);
|
||||||
|
@ -94,7 +95,7 @@ public:
|
||||||
void releaseMutex(Mutex* moo);
|
void releaseMutex(Mutex* moo);
|
||||||
void cancelTimer(Timer* timer);
|
void cancelTimer(Timer* timer);
|
||||||
void signalTimer(Handle timerHandle, Timer* timer);
|
void signalTimer(Handle timerHandle, Timer* timer);
|
||||||
void updateTimer(Handle timerHandle, Timer* timer);
|
u64 getWakeupTick(s64 ns);
|
||||||
|
|
||||||
// Wake up the thread with the highest priority out of all threads in the waitlist
|
// Wake up the thread with the highest priority out of all threads in the waitlist
|
||||||
// Returns the index of the woken up thread
|
// Returns the index of the woken up thread
|
||||||
|
@ -137,6 +138,7 @@ public:
|
||||||
void duplicateHandle();
|
void duplicateHandle();
|
||||||
void exitThread();
|
void exitThread();
|
||||||
void mapMemoryBlock();
|
void mapMemoryBlock();
|
||||||
|
void unmapMemoryBlock();
|
||||||
void queryMemory();
|
void queryMemory();
|
||||||
void getCurrentProcessorNumber();
|
void getCurrentProcessorNumber();
|
||||||
void getProcessID();
|
void getProcessID();
|
||||||
|
|
|
@ -83,56 +83,53 @@ struct Port {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Session {
|
struct Session {
|
||||||
Handle portHandle; // The port this session is subscribed to
|
Handle portHandle; // The port this session is subscribed to
|
||||||
Session(Handle portHandle) : portHandle(portHandle) {}
|
Session(Handle portHandle) : portHandle(portHandle) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class ThreadStatus {
|
enum class ThreadStatus {
|
||||||
Running, // Currently running
|
Running, // Currently running
|
||||||
Ready, // Ready to run
|
Ready, // Ready to run
|
||||||
WaitArbiter, // Waiting on an address arbiter
|
WaitArbiter, // Waiting on an address arbiter
|
||||||
WaitSleep, // Waiting due to a SleepThread SVC
|
WaitSleep, // Waiting due to a SleepThread SVC
|
||||||
WaitSync1, // Waiting for the single object in the wait list to be ready
|
WaitSync1, // Waiting for the single object in the wait list to be ready
|
||||||
WaitSyncAny, // Wait for one object of the many that might be in the wait list to be ready
|
WaitSyncAny, // Wait for one object of the many that might be in the wait list to be ready
|
||||||
WaitSyncAll, // Waiting for ALL sync objects in its wait list to be ready
|
WaitSyncAll, // Waiting for ALL sync objects in its wait list to be ready
|
||||||
WaitIPC, // Waiting for the reply from an IPC request
|
WaitIPC, // Waiting for the reply from an IPC request
|
||||||
Dormant, // Created but not yet made ready
|
Dormant, // Created but not yet made ready
|
||||||
Dead // Run to completion, or forcefully terminated
|
Dead // Run to completion, or forcefully terminated
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Thread {
|
struct Thread {
|
||||||
u32 initialSP; // Initial r13 value
|
u32 initialSP; // Initial r13 value
|
||||||
u32 entrypoint; // Initial r15 value
|
u32 entrypoint; // Initial r15 value
|
||||||
u32 priority;
|
u32 priority;
|
||||||
u32 arg;
|
u32 arg;
|
||||||
ProcessorID processorID;
|
ProcessorID processorID;
|
||||||
ThreadStatus status;
|
ThreadStatus status;
|
||||||
Handle handle; // OS handle for this thread
|
Handle handle; // OS handle for this thread
|
||||||
int index; // Index of the thread. 0 for the first thread, 1 for the second, and so on
|
int index; // Index of the thread. 0 for the first thread, 1 for the second, and so on
|
||||||
|
|
||||||
// The waiting address for threads that are waiting on an AddressArbiter
|
// The waiting address for threads that are waiting on an AddressArbiter
|
||||||
u32 waitingAddress;
|
u32 waitingAddress;
|
||||||
|
|
||||||
// The nanoseconds until a thread wakes up from being asleep or from timing out while waiting on an arbiter
|
// For WaitSynchronization(N): A vector of objects this thread is waiting for
|
||||||
u64 waitingNanoseconds;
|
std::vector<Handle> waitList;
|
||||||
// The tick this thread went to sleep on
|
// For WaitSynchronizationN: Shows whether the object should wait for all objects in the wait list or just one
|
||||||
u64 sleepTick;
|
bool waitAll;
|
||||||
// For WaitSynchronization(N): A vector of objects this thread is waiting for
|
// For WaitSynchronizationN: The "out" pointer
|
||||||
std::vector<Handle> waitList;
|
u32 outPointer;
|
||||||
// For WaitSynchronizationN: Shows whether the object should wait for all objects in the wait list or just one
|
u64 wakeupTick;
|
||||||
bool waitAll;
|
|
||||||
// For WaitSynchronizationN: The "out" pointer
|
|
||||||
u32 outPointer;
|
|
||||||
|
|
||||||
// Thread context used for switching between threads
|
// Thread context used for switching between threads
|
||||||
std::array<u32, 16> gprs;
|
std::array<u32, 16> gprs;
|
||||||
std::array<u32, 32> fprs; // Stored as u32 because dynarmic does it
|
std::array<u32, 32> fprs; // Stored as u32 because dynarmic does it
|
||||||
u32 cpsr;
|
u32 cpsr;
|
||||||
u32 fpscr;
|
u32 fpscr;
|
||||||
u32 tlsBase; // Base pointer for thread-local storage
|
u32 tlsBase; // Base pointer for thread-local storage
|
||||||
|
|
||||||
// A list of threads waiting for this thread to terminate. Yes, threads are sync objects too.
|
// A list of threads waiting for this thread to terminate. Yes, threads are sync objects too.
|
||||||
u64 threadsWaitingForTermination;
|
u64 threadsWaitingForTermination;
|
||||||
};
|
};
|
||||||
|
|
||||||
static const char* kernelObjectTypeToString(KernelObjectType t) {
|
static const char* kernelObjectTypeToString(KernelObjectType t) {
|
||||||
|
@ -177,13 +174,12 @@ struct Timer {
|
||||||
u64 waitlist; // Refer to the getWaitlist function below for documentation
|
u64 waitlist; // Refer to the getWaitlist function below for documentation
|
||||||
ResetType resetType = ResetType::OneShot;
|
ResetType resetType = ResetType::OneShot;
|
||||||
|
|
||||||
u64 startTick; // CPU tick the timer started
|
u64 fireTick; // CPU tick the timer will be fired
|
||||||
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
|
u64 interval; // Number of ns until the timer fires for the second and future times
|
||||||
bool fired; // Has this timer been signalled?
|
bool fired; // Has this timer been signalled?
|
||||||
bool running; // Is this timer running or stopped?
|
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) {}
|
Timer(ResetType type) : resetType(type), fireTick(0), interval(0), waitlist(0), fired(false), running(false) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MemoryBlock {
|
struct MemoryBlock {
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
#include <android/log.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Log {
|
namespace Log {
|
||||||
// Our logger class
|
// Our logger class
|
||||||
template <bool enabled>
|
template <bool enabled>
|
||||||
|
@ -12,7 +16,11 @@ namespace Log {
|
||||||
|
|
||||||
std::va_list args;
|
std::va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
__android_log_vprint(ANDROID_LOG_DEFAULT, "Panda3DS", fmt, args);
|
||||||
|
#else
|
||||||
std::vprintf(fmt, args);
|
std::vprintf(fmt, args);
|
||||||
|
#endif
|
||||||
va_end(args);
|
va_end(args);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -81,4 +89,4 @@ namespace Log {
|
||||||
#else
|
#else
|
||||||
#define MAKE_LOG_FUNCTION(functionName, logger) MAKE_LOG_FUNCTION_USER(functionName, logger)
|
#define MAKE_LOG_FUNCTION(functionName, logger) MAKE_LOG_FUNCTION_USER(functionName, logger)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
|
@ -112,11 +112,12 @@ class Memory {
|
||||||
// This tracks our OS' memory allocations
|
// This tracks our OS' memory allocations
|
||||||
std::vector<KernelMemoryTypes::MemoryInfo> memoryInfo;
|
std::vector<KernelMemoryTypes::MemoryInfo> memoryInfo;
|
||||||
|
|
||||||
std::array<SharedMemoryBlock, 4> sharedMemBlocks = {
|
std::array<SharedMemoryBlock, 5> sharedMemBlocks = {
|
||||||
SharedMemoryBlock(0, 0, KernelHandles::FontSharedMemHandle), // Shared memory for the system font (size is 0 because we read the size from the cmrc filesystem
|
SharedMemoryBlock(0, 0, KernelHandles::FontSharedMemHandle), // Shared memory for the system font (size is 0 because we read the size from the cmrc filesystem
|
||||||
SharedMemoryBlock(0, 0x1000, KernelHandles::GSPSharedMemHandle), // GSP shared memory
|
SharedMemoryBlock(0, 0x1000, KernelHandles::GSPSharedMemHandle), // GSP shared memory
|
||||||
SharedMemoryBlock(0, 0x1000, KernelHandles::HIDSharedMemHandle), // HID shared memory
|
SharedMemoryBlock(0, 0x1000, KernelHandles::HIDSharedMemHandle), // HID shared memory
|
||||||
SharedMemoryBlock(0, 0x3000, KernelHandles::CSNDSharedMemHandle), // CSND shared memory
|
SharedMemoryBlock(0, 0x3000, KernelHandles::CSNDSharedMemHandle), // CSND shared memory
|
||||||
|
SharedMemoryBlock(0, 0xE7000, KernelHandles::APTCaptureSharedMemHandle), // APT Capture Buffer memory
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
26
include/panda_qt/cheats_window.hpp
Normal file
26
include/panda_qt/cheats_window.hpp
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QAction>
|
||||||
|
#include <QWidget>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "emulator.hpp"
|
||||||
|
|
||||||
|
class QListWidget;
|
||||||
|
|
||||||
|
class CheatsWindow final : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
CheatsWindow(Emulator* emu, const std::filesystem::path& path, QWidget* parent = nullptr);
|
||||||
|
~CheatsWindow() = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void addEntry();
|
||||||
|
void removeClicked();
|
||||||
|
|
||||||
|
QListWidget* cheatList;
|
||||||
|
std::filesystem::path cheatPath;
|
||||||
|
Emulator* emu;
|
||||||
|
};
|
|
@ -5,6 +5,7 @@
|
||||||
#include <QtWidgets>
|
#include <QtWidgets>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <functional>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -12,16 +13,38 @@
|
||||||
#include "emulator.hpp"
|
#include "emulator.hpp"
|
||||||
#include "panda_qt/about_window.hpp"
|
#include "panda_qt/about_window.hpp"
|
||||||
#include "panda_qt/config_window.hpp"
|
#include "panda_qt/config_window.hpp"
|
||||||
|
#include "panda_qt/cheats_window.hpp"
|
||||||
#include "panda_qt/screen.hpp"
|
#include "panda_qt/screen.hpp"
|
||||||
#include "panda_qt/text_editor.hpp"
|
#include "panda_qt/text_editor.hpp"
|
||||||
#include "services/hid.hpp"
|
#include "services/hid.hpp"
|
||||||
|
|
||||||
|
struct CheatMessage {
|
||||||
|
u32 handle;
|
||||||
|
std::vector<uint8_t> cheat;
|
||||||
|
std::function<void(u32)> callback;
|
||||||
|
};
|
||||||
|
|
||||||
class MainWindow : public QMainWindow {
|
class MainWindow : public QMainWindow {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Types of messages we might send from the GUI thread to the emulator thread
|
// Types of messages we might send from the GUI thread to the emulator thread
|
||||||
enum class MessageType { LoadROM, Reset, Pause, Resume, TogglePause, DumpRomFS, PressKey, ReleaseKey, LoadLuaScript };
|
enum class MessageType {
|
||||||
|
LoadROM,
|
||||||
|
Reset,
|
||||||
|
Pause,
|
||||||
|
Resume,
|
||||||
|
TogglePause,
|
||||||
|
DumpRomFS,
|
||||||
|
PressKey,
|
||||||
|
ReleaseKey,
|
||||||
|
SetCirclePadX,
|
||||||
|
SetCirclePadY,
|
||||||
|
LoadLuaScript,
|
||||||
|
EditCheat,
|
||||||
|
PressTouchscreen,
|
||||||
|
ReleaseTouchscreen,
|
||||||
|
};
|
||||||
|
|
||||||
// Tagged union representing our message queue messages
|
// Tagged union representing our message queue messages
|
||||||
struct EmulatorMessage {
|
struct EmulatorMessage {
|
||||||
|
@ -36,9 +59,22 @@ class MainWindow : public QMainWindow {
|
||||||
u32 key;
|
u32 key;
|
||||||
} key;
|
} key;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
s16 value;
|
||||||
|
} circlepad;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
std::string* str;
|
std::string* str;
|
||||||
} string;
|
} string;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
CheatMessage* c;
|
||||||
|
} cheat;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
u16 x;
|
||||||
|
u16 y;
|
||||||
|
} touchscreen;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -54,6 +90,7 @@ class MainWindow : public QMainWindow {
|
||||||
ScreenWidget screen;
|
ScreenWidget screen;
|
||||||
AboutWindow* aboutWindow;
|
AboutWindow* aboutWindow;
|
||||||
ConfigWindow* configWindow;
|
ConfigWindow* configWindow;
|
||||||
|
CheatsWindow* cheatsEditor;
|
||||||
TextEditorWindow* luaEditor;
|
TextEditorWindow* luaEditor;
|
||||||
QMenuBar* menuBar = nullptr;
|
QMenuBar* menuBar = nullptr;
|
||||||
|
|
||||||
|
@ -63,6 +100,7 @@ class MainWindow : public QMainWindow {
|
||||||
void selectROM();
|
void selectROM();
|
||||||
void dumpRomFS();
|
void dumpRomFS();
|
||||||
void openLuaEditor();
|
void openLuaEditor();
|
||||||
|
void openCheatsEditor();
|
||||||
void showAboutMenu();
|
void showAboutMenu();
|
||||||
void sendMessage(const EmulatorMessage& message);
|
void sendMessage(const EmulatorMessage& message);
|
||||||
void dispatchMessage(const EmulatorMessage& message);
|
void dispatchMessage(const EmulatorMessage& message);
|
||||||
|
@ -77,5 +115,9 @@ class MainWindow : public QMainWindow {
|
||||||
|
|
||||||
void keyPressEvent(QKeyEvent* event) override;
|
void keyPressEvent(QKeyEvent* event) override;
|
||||||
void keyReleaseEvent(QKeyEvent* event) override;
|
void keyReleaseEvent(QKeyEvent* event) override;
|
||||||
|
void mousePressEvent(QMouseEvent* event) override;
|
||||||
|
void mouseReleaseEvent(QMouseEvent* event) override;
|
||||||
|
|
||||||
void loadLuaScript(const std::string& code);
|
void loadLuaScript(const std::string& code);
|
||||||
|
void editCheat(u32 handle, const std::vector<uint8_t>& cheat, const std::function<void(u32)>& callback);
|
||||||
};
|
};
|
89
include/scheduler.hpp
Normal file
89
include/scheduler.hpp
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
#pragma once
|
||||||
|
#include <boost/container/flat_map.hpp>
|
||||||
|
#include <boost/container/static_vector.hpp>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include "helpers.hpp"
|
||||||
|
#include "logger.hpp"
|
||||||
|
|
||||||
|
struct Scheduler {
|
||||||
|
enum class EventType {
|
||||||
|
VBlank = 0, // End of frame event
|
||||||
|
UpdateTimers = 1, // Update kernel timer objects
|
||||||
|
Panic = 2, // 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);
|
||||||
|
static constexpr u64 arm11Clock = 268111856;
|
||||||
|
|
||||||
|
template <typename Key, typename Val, usize size>
|
||||||
|
using EventMap = boost::container::flat_multimap<Key, Val, std::less<Key>, boost::container::static_vector<std::pair<Key, Val>, size>>;
|
||||||
|
|
||||||
|
EventMap<u64, EventType, totalNumberOfEvents> events;
|
||||||
|
u64 currentTimestamp = 0;
|
||||||
|
u64 nextTimestamp = 0;
|
||||||
|
|
||||||
|
// Set nextTimestamp to the timestamp of the next event
|
||||||
|
void updateNextTimestamp() { nextTimestamp = events.cbegin()->first; }
|
||||||
|
|
||||||
|
void addEvent(EventType type, u64 timestamp) {
|
||||||
|
events.emplace(timestamp, type);
|
||||||
|
updateNextTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeEvent(EventType type) {
|
||||||
|
for (auto it = events.begin(); it != events.end(); it++) {
|
||||||
|
// Find first event of type "type" and remove it.
|
||||||
|
// Our scheduler shouldn't have duplicate events, so it's safe to exit when an event is found
|
||||||
|
if (it->second == type) {
|
||||||
|
events.erase(it);
|
||||||
|
updateNextTimestamp();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
currentTimestamp = 0;
|
||||||
|
|
||||||
|
// Clear any pending events
|
||||||
|
events.clear();
|
||||||
|
addEvent(Scheduler::EventType::VBlank, arm11Clock / 60);
|
||||||
|
|
||||||
|
// Add a dummy event to always keep the scheduler non-empty
|
||||||
|
addEvent(EventType::Panic, std::numeric_limits<u64>::max());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr u64 MAX_VALUE_TO_MULTIPLY = std::numeric_limits<s64>::max() / arm11Clock;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Function for converting time units to cycles for various kernel functions
|
||||||
|
// Thank you Citra
|
||||||
|
static constexpr s64 nsToCycles(float ns) { return s64(arm11Clock * (0.000000001f) * ns); }
|
||||||
|
static constexpr s64 nsToCycles(int ns) { return arm11Clock * s64(ns) / 1000000000; }
|
||||||
|
|
||||||
|
static constexpr s64 nsToCycles(s64 ns) {
|
||||||
|
if (ns / 1000000000 > static_cast<s64>(MAX_VALUE_TO_MULTIPLY)) {
|
||||||
|
return std::numeric_limits<s64>::max();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ns > static_cast<s64>(MAX_VALUE_TO_MULTIPLY)) {
|
||||||
|
return arm11Clock * (ns / 1000000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (arm11Clock * ns) / 1000000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr s64 nsToCycles(u64 ns) {
|
||||||
|
if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) {
|
||||||
|
return std::numeric_limits<s64>::max();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ns > MAX_VALUE_TO_MULTIPLY) {
|
||||||
|
return arm11Clock * (s64(ns) / 1000000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (arm11Clock * s64(ns)) / 1000000000;
|
||||||
|
}
|
||||||
|
};
|
|
@ -14,6 +14,7 @@ class BOSSService {
|
||||||
void cancelTask(u32 messagePointer);
|
void cancelTask(u32 messagePointer);
|
||||||
void initializeSession(u32 messagePointer);
|
void initializeSession(u32 messagePointer);
|
||||||
void getErrorCode(u32 messagePointer);
|
void getErrorCode(u32 messagePointer);
|
||||||
|
void getNewArrivalFlag(u32 messagePointer);
|
||||||
void getNsDataIdList(u32 messagePointer, u32 commandWord);
|
void getNsDataIdList(u32 messagePointer, u32 commandWord);
|
||||||
void getOptoutFlag(u32 messagePointer);
|
void getOptoutFlag(u32 messagePointer);
|
||||||
void getStorageEntryInfo(u32 messagePointer); // Unknown what this is, name taken from Citra
|
void getStorageEntryInfo(u32 messagePointer); // Unknown what this is, name taken from Citra
|
||||||
|
|
|
@ -12,22 +12,45 @@
|
||||||
class Kernel;
|
class Kernel;
|
||||||
|
|
||||||
class CAMService {
|
class CAMService {
|
||||||
|
using Event = std::optional<Handle>;
|
||||||
|
|
||||||
|
struct Port {
|
||||||
|
Event bufferErrorInterruptEvent = std::nullopt;
|
||||||
|
Event receiveEvent = std::nullopt;
|
||||||
|
u16 transferBytes;
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
bufferErrorInterruptEvent = std::nullopt;
|
||||||
|
receiveEvent = std::nullopt;
|
||||||
|
transferBytes = 256;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Handle handle = KernelHandles::CAM;
|
Handle handle = KernelHandles::CAM;
|
||||||
Memory& mem;
|
Memory& mem;
|
||||||
Kernel& kernel;
|
Kernel& kernel;
|
||||||
MAKE_LOG_FUNCTION(log, camLogger)
|
MAKE_LOG_FUNCTION(log, camLogger)
|
||||||
|
|
||||||
using Event = std::optional<Handle>;
|
static constexpr size_t portCount = 2;
|
||||||
static constexpr size_t portCount = 4; // PORT_NONE, PORT_CAM1, PORT_CAM2, PORT_BOTH
|
std::array<Port, portCount> ports;
|
||||||
std::array<Event, portCount> bufferErrorInterruptEvents;
|
|
||||||
|
|
||||||
// Service commands
|
// Service commands
|
||||||
void driverInitialize(u32 messagePointer);
|
void driverInitialize(u32 messagePointer);
|
||||||
|
void driverFinalize(u32 messagePointer);
|
||||||
|
void getMaxBytes(u32 messagePointer);
|
||||||
void getMaxLines(u32 messagePointer);
|
void getMaxLines(u32 messagePointer);
|
||||||
void getBufferErrorInterruptEvent(u32 messagePointer);
|
void getBufferErrorInterruptEvent(u32 messagePointer);
|
||||||
|
void getSuitableY2RCoefficients(u32 messagePointer);
|
||||||
|
void getTransferBytes(u32 messagePointer);
|
||||||
void setContrast(u32 messagePointer);
|
void setContrast(u32 messagePointer);
|
||||||
void setFrameRate(u32 messagePointer);
|
void setFrameRate(u32 messagePointer);
|
||||||
|
void setReceiving(u32 messagePointer);
|
||||||
|
void setSize(u32 messagePointer);
|
||||||
|
void setTransferBytes(u32 messagePointer);
|
||||||
void setTransferLines(u32 messagePointer);
|
void setTransferLines(u32 messagePointer);
|
||||||
|
void setTrimming(u32 messagePointer);
|
||||||
|
void setTrimmingParamsCenter(u32 messagePointer);
|
||||||
|
void startCapture(u32 messagePointer);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CAMService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
|
CAMService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
|
||||||
|
|
|
@ -26,6 +26,7 @@ class FSService {
|
||||||
SelfNCCHArchive selfNcch;
|
SelfNCCHArchive selfNcch;
|
||||||
SaveDataArchive saveData;
|
SaveDataArchive saveData;
|
||||||
SDMCArchive sdmc;
|
SDMCArchive sdmc;
|
||||||
|
SDMCArchive sdmcWriteOnly;
|
||||||
NCCHArchive ncch;
|
NCCHArchive ncch;
|
||||||
|
|
||||||
// UserSaveData archives
|
// UserSaveData archives
|
||||||
|
@ -61,6 +62,7 @@ class FSService {
|
||||||
void getFreeBytes(u32 messagePointer);
|
void getFreeBytes(u32 messagePointer);
|
||||||
void getFormatInfo(u32 messagePointer);
|
void getFormatInfo(u32 messagePointer);
|
||||||
void getPriority(u32 messagePointer);
|
void getPriority(u32 messagePointer);
|
||||||
|
void getSdmcArchiveResource(u32 messagePointer);
|
||||||
void getThisSaveDataSecureValue(u32 messagePointer);
|
void getThisSaveDataSecureValue(u32 messagePointer);
|
||||||
void theGameboyVCFunction(u32 messagePointer);
|
void theGameboyVCFunction(u32 messagePointer);
|
||||||
void initialize(u32 messagePointer);
|
void initialize(u32 messagePointer);
|
||||||
|
@ -81,9 +83,9 @@ class FSService {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FSService(Memory& mem, Kernel& kernel, const EmulatorConfig& config)
|
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),
|
: mem(mem), saveData(mem), sharedExtSaveData_nand(mem, "../SharedFiles/NAND", true), extSaveData_sdmc(mem, "SDMC"), sdmc(mem),
|
||||||
ncch(mem), userSaveData1(mem, ArchiveID::UserSaveData1), userSaveData2(mem, ArchiveID::UserSaveData2), kernel(kernel), config(config),
|
sdmcWriteOnly(mem, true), selfNcch(mem), ncch(mem), userSaveData1(mem, ArchiveID::UserSaveData1),
|
||||||
systemSaveData(mem) {}
|
userSaveData2(mem, ArchiveID::UserSaveData2), kernel(kernel), config(config), systemSaveData(mem) {}
|
||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
void handleSyncRequest(u32 messagePointer);
|
void handleSyncRequest(u32 messagePointer);
|
||||||
|
|
|
@ -60,12 +60,22 @@ class GPUService {
|
||||||
};
|
};
|
||||||
static_assert(sizeof(FramebufferUpdate) == 64, "GSP::GPU::FramebufferUpdate has the wrong size");
|
static_assert(sizeof(FramebufferUpdate) == 64, "GSP::GPU::FramebufferUpdate has the wrong size");
|
||||||
|
|
||||||
|
// Used for saving and restoring GPU state via ImportDisplayCaptureInfo
|
||||||
|
struct CaptureInfo {
|
||||||
|
u32 leftFramebuffer; // Left framebuffer VA
|
||||||
|
u32 rightFramebuffer; // Right framebuffer VA (Top screen only)
|
||||||
|
u32 format;
|
||||||
|
u32 stride;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(CaptureInfo) == 16, "GSP::GPU::CaptureInfo has the wrong size");
|
||||||
|
|
||||||
// Service commands
|
// Service commands
|
||||||
void acquireRight(u32 messagePointer);
|
void acquireRight(u32 messagePointer);
|
||||||
void flushDataCache(u32 messagePointer);
|
void flushDataCache(u32 messagePointer);
|
||||||
void importDisplayCaptureInfo(u32 messagePointer);
|
void importDisplayCaptureInfo(u32 messagePointer);
|
||||||
void registerInterruptRelayQueue(u32 messagePointer);
|
void registerInterruptRelayQueue(u32 messagePointer);
|
||||||
void releaseRight(u32 messagePointer);
|
void releaseRight(u32 messagePointer);
|
||||||
|
void restoreVramSysArea(u32 messagePointer);
|
||||||
void saveVramSysArea(u32 messagePointer);
|
void saveVramSysArea(u32 messagePointer);
|
||||||
void setAxiConfigQoSMode(u32 messagePointer);
|
void setAxiConfigQoSMode(u32 messagePointer);
|
||||||
void setBufferSwap(u32 messagePointer);
|
void setBufferSwap(u32 messagePointer);
|
||||||
|
@ -86,6 +96,15 @@ class GPUService {
|
||||||
|
|
||||||
void setBufferSwapImpl(u32 screen_id, const FramebufferInfo& info);
|
void setBufferSwapImpl(u32 screen_id, const FramebufferInfo& info);
|
||||||
|
|
||||||
|
// Get the framebuffer info in shared memory for a given screen
|
||||||
|
FramebufferUpdate* getFramebufferInfo(int screen) {
|
||||||
|
// TODO: Offset depends on GSP thread being triggered
|
||||||
|
return reinterpret_cast<FramebufferUpdate*>(&sharedMem[0x200 + screen * sizeof(FramebufferUpdate)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
FramebufferUpdate* getTopFramebufferInfo() { return getFramebufferInfo(0); }
|
||||||
|
FramebufferUpdate* getBottomFramebufferInfo() { return getFramebufferInfo(1); }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GPUService(Memory& mem, GPU& gpu, Kernel& kernel, u32& currentPID) : mem(mem), gpu(gpu),
|
GPUService(Memory& mem, GPU& gpu, Kernel& kernel, u32& currentPID) : mem(mem), gpu(gpu),
|
||||||
kernel(kernel), currentPID(currentPID) {}
|
kernel(kernel), currentPID(currentPID) {}
|
||||||
|
|
|
@ -17,6 +17,7 @@ class PTMService {
|
||||||
void getAdapterState(u32 messagePointer);
|
void getAdapterState(u32 messagePointer);
|
||||||
void getBatteryChargeState(u32 messagePointer);
|
void getBatteryChargeState(u32 messagePointer);
|
||||||
void getBatteryLevel(u32 messagePointer);
|
void getBatteryLevel(u32 messagePointer);
|
||||||
|
void getPedometerState(u32 messagePointer);
|
||||||
void getStepHistory(u32 messagePointer);
|
void getStepHistory(u32 messagePointer);
|
||||||
void getStepHistoryAll(u32 messagePointer);
|
void getStepHistoryAll(u32 messagePointer);
|
||||||
void getTotalStepCount(u32 messagePointer);
|
void getTotalStepCount(u32 messagePointer);
|
||||||
|
|
|
@ -98,6 +98,7 @@ class Y2RService {
|
||||||
void setSendingY(u32 messagePointer);
|
void setSendingY(u32 messagePointer);
|
||||||
void setSendingU(u32 messagePointer);
|
void setSendingU(u32 messagePointer);
|
||||||
void setSendingV(u32 messagePointer);
|
void setSendingV(u32 messagePointer);
|
||||||
|
void setSendingYUV(u32 messagePointer);
|
||||||
void setSpacialDithering(u32 messagePointer);
|
void setSpacialDithering(u32 messagePointer);
|
||||||
void setStandardCoeff(u32 messagePointer);
|
void setStandardCoeff(u32 messagePointer);
|
||||||
void setTemporalDithering(u32 messagePointer);
|
void setTemporalDithering(u32 messagePointer);
|
||||||
|
|
|
@ -1,32 +1,59 @@
|
||||||
#ifdef CPU_DYNARMIC
|
#ifdef CPU_DYNARMIC
|
||||||
#include "cpu_dynarmic.hpp"
|
#include "cpu_dynarmic.hpp"
|
||||||
|
|
||||||
#include "arm_defs.hpp"
|
#include "arm_defs.hpp"
|
||||||
|
#include "emulator.hpp"
|
||||||
|
|
||||||
CPU::CPU(Memory& mem, Kernel& kernel) : mem(mem), env(mem, kernel) {
|
CPU::CPU(Memory& mem, Kernel& kernel, Emulator& emu) : mem(mem), emu(emu), scheduler(emu.getScheduler()), env(mem, kernel, emu.getScheduler()) {
|
||||||
cp15 = std::make_shared<CP15>();
|
cp15 = std::make_shared<CP15>();
|
||||||
|
|
||||||
Dynarmic::A32::UserConfig config;
|
Dynarmic::A32::UserConfig config;
|
||||||
config.arch_version = Dynarmic::A32::ArchVersion::v6K;
|
config.arch_version = Dynarmic::A32::ArchVersion::v6K;
|
||||||
config.callbacks = &env;
|
config.callbacks = &env;
|
||||||
config.coprocessors[15] = cp15;
|
config.coprocessors[15] = cp15;
|
||||||
config.define_unpredictable_behaviour = true;
|
config.define_unpredictable_behaviour = true;
|
||||||
config.global_monitor = &exclusiveMonitor;
|
config.global_monitor = &exclusiveMonitor;
|
||||||
config.processor_id = 0;
|
config.processor_id = 0;
|
||||||
|
|
||||||
jit = std::make_unique<Dynarmic::A32::Jit>(config);
|
jit = std::make_unique<Dynarmic::A32::Jit>(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::reset() {
|
void CPU::reset() {
|
||||||
setCPSR(CPSR::UserMode);
|
setCPSR(CPSR::UserMode);
|
||||||
setFPSCR(FPSCR::MainThreadDefault);
|
setFPSCR(FPSCR::MainThreadDefault);
|
||||||
env.totalTicks = 0;
|
env.totalTicks = 0;
|
||||||
|
|
||||||
cp15->reset();
|
cp15->reset();
|
||||||
cp15->setTLSBase(VirtualAddrs::TLSBase); // Set cp15 TLS pointer to the main thread's thread-local storage
|
cp15->setTLSBase(VirtualAddrs::TLSBase); // Set cp15 TLS pointer to the main thread's thread-local storage
|
||||||
jit->Reset();
|
jit->Reset();
|
||||||
jit->ClearCache();
|
jit->ClearCache();
|
||||||
jit->Regs().fill(0);
|
jit->Regs().fill(0);
|
||||||
jit->ExtRegs().fill(0);
|
jit->ExtRegs().fill(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // CPU_DYNARMIC
|
void CPU::runFrame() {
|
||||||
|
emu.frameDone = false;
|
||||||
|
|
||||||
|
while (!emu.frameDone) {
|
||||||
|
// Run CPU until the next scheduler event
|
||||||
|
env.ticksLeft = scheduler.nextTimestamp - scheduler.currentTimestamp;
|
||||||
|
|
||||||
|
execute:
|
||||||
|
const auto exitReason = jit->Run();
|
||||||
|
|
||||||
|
// Handle any scheduler events that need handling.
|
||||||
|
emu.pollScheduler();
|
||||||
|
|
||||||
|
if (static_cast<u32>(exitReason) != 0) [[unlikely]] {
|
||||||
|
// Cache invalidation needs to exit the JIT so it returns a CacheInvalidation HaltReason. In our case, we just go back to executing
|
||||||
|
// The goto might be terrible but it does guarantee that this does not recursively call run and crash, instead getting optimized to a jump
|
||||||
|
if (Dynarmic::Has(exitReason, Dynarmic::HaltReason::CacheInvalidation)) {
|
||||||
|
goto execute;
|
||||||
|
} else {
|
||||||
|
Helpers::panic("Exit reason: %d\nPC: %08X", static_cast<u32>(exitReason), getReg(15));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CPU_DYNARMIC
|
|
@ -139,6 +139,65 @@ void ActionReplay::executeDType(const Cheat& cheat, u32 instruction) {
|
||||||
switch (instruction) {
|
switch (instruction) {
|
||||||
case 0xD3000000: offset1 = cheat[pc++]; break;
|
case 0xD3000000: offset1 = cheat[pc++]; break;
|
||||||
case 0xD3000001: offset2 = cheat[pc++]; break;
|
case 0xD3000001: offset2 = cheat[pc++]; break;
|
||||||
|
|
||||||
|
case 0xD6000000:
|
||||||
|
write32(*activeOffset + cheat[pc++], u32(*activeData));
|
||||||
|
*activeOffset += 4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xD6000001:
|
||||||
|
write32(*activeOffset + cheat[pc++], u32(data1));
|
||||||
|
*activeOffset += 4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xD6000002:
|
||||||
|
write32(*activeOffset + cheat[pc++], u32(data2));
|
||||||
|
*activeOffset += 4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xD7000000:
|
||||||
|
write16(*activeOffset + cheat[pc++], u16(*activeData));
|
||||||
|
*activeOffset += 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xD7000001:
|
||||||
|
write16(*activeOffset + cheat[pc++], u16(data1));
|
||||||
|
*activeOffset += 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xD7000002:
|
||||||
|
write16(*activeOffset + cheat[pc++], u16(data2));
|
||||||
|
*activeOffset += 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xD8000000:
|
||||||
|
write8(*activeOffset + cheat[pc++], u8(*activeData));
|
||||||
|
*activeOffset += 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xD8000001:
|
||||||
|
write8(*activeOffset + cheat[pc++], u8(data1));
|
||||||
|
*activeOffset += 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xD8000002:
|
||||||
|
write8(*activeOffset + cheat[pc++], u8(data2));
|
||||||
|
*activeOffset += 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 0xD9000000: *activeData = read32(cheat[pc++] + *activeOffset); break;
|
||||||
|
case 0xD9000001: data1 = read32(cheat[pc++] + *activeOffset); break;
|
||||||
|
case 0xD9000002: data2 = read32(cheat[pc++] + *activeOffset); break;
|
||||||
|
|
||||||
|
case 0xDA000000: *activeData = read16(cheat[pc++] + *activeOffset); break;
|
||||||
|
case 0xDA000001: data1 = read16(cheat[pc++] + *activeOffset); break;
|
||||||
|
case 0xDA000002: data2 = read16(cheat[pc++] + *activeOffset); break;
|
||||||
|
|
||||||
|
case 0xDB000000: *activeData = read8(cheat[pc++] + *activeOffset); break;
|
||||||
|
case 0xDB000001: data1 = read8(cheat[pc++] + *activeOffset); break;
|
||||||
|
case 0xDB000002: data2 = read8(cheat[pc++] + *activeOffset); break;
|
||||||
|
|
||||||
case 0xDC000000: *activeOffset += cheat[pc++]; break;
|
case 0xDC000000: *activeOffset += cheat[pc++]; break;
|
||||||
|
|
||||||
// DD000000 XXXXXXXX - if KEYPAD has value XXXXXXXX execute next block
|
// DD000000 XXXXXXXX - if KEYPAD has value XXXXXXXX execute next block
|
||||||
|
|
|
@ -4,13 +4,14 @@
|
||||||
|
|
||||||
using namespace Applets;
|
using namespace Applets;
|
||||||
|
|
||||||
AppletManager::AppletManager(Memory& mem) : miiSelector(mem, nextParameter), swkbd(mem, nextParameter) {}
|
AppletManager::AppletManager(Memory& mem) : miiSelector(mem, nextParameter), swkbd(mem, nextParameter), error(mem, nextParameter) {}
|
||||||
|
|
||||||
void AppletManager::reset() {
|
void AppletManager::reset() {
|
||||||
nextParameter = std::nullopt;
|
nextParameter = std::nullopt;
|
||||||
|
|
||||||
miiSelector.reset();
|
miiSelector.reset();
|
||||||
swkbd.reset();
|
swkbd.reset();
|
||||||
|
error.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
AppletBase* AppletManager::getApplet(u32 id) {
|
AppletBase* AppletManager::getApplet(u32 id) {
|
||||||
|
@ -21,6 +22,9 @@ AppletBase* AppletManager::getApplet(u32 id) {
|
||||||
case AppletIDs::SoftwareKeyboard:
|
case AppletIDs::SoftwareKeyboard:
|
||||||
case AppletIDs::SoftwareKeyboard2: return &swkbd;
|
case AppletIDs::SoftwareKeyboard2: return &swkbd;
|
||||||
|
|
||||||
|
case AppletIDs::ErrDisp:
|
||||||
|
case AppletIDs::ErrDisp2: return &error;
|
||||||
|
|
||||||
default: return nullptr;
|
default: return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
32
src/core/applets/error_applet.cpp
Normal file
32
src/core/applets/error_applet.cpp
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
#include "applets/error_applet.hpp"
|
||||||
|
#include "kernel/handles.hpp"
|
||||||
|
|
||||||
|
using namespace Applets;
|
||||||
|
|
||||||
|
void ErrorApplet::reset() {}
|
||||||
|
|
||||||
|
Result::HorizonResult ErrorApplet::start(const MemoryBlock* sharedMem, const std::vector<u8>& parameters, u32 appID) {
|
||||||
|
Applets::Parameter param = Applets::Parameter{
|
||||||
|
.senderID = appID,
|
||||||
|
.destID = AppletIDs::Application,
|
||||||
|
.signal = static_cast<u32>(APTSignal::WakeupByExit),
|
||||||
|
.object = 0,
|
||||||
|
.data = parameters, // TODO: Figure out how the data format for this applet
|
||||||
|
};
|
||||||
|
|
||||||
|
nextParameter = param;
|
||||||
|
return Result::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result::HorizonResult ErrorApplet::receiveParameter(const Applets::Parameter& parameter) {
|
||||||
|
Applets::Parameter param = Applets::Parameter{
|
||||||
|
.senderID = parameter.destID,
|
||||||
|
.destID = AppletIDs::Application,
|
||||||
|
.signal = static_cast<u32>(APTSignal::Response),
|
||||||
|
.object = KernelHandles::APTCaptureSharedMemHandle,
|
||||||
|
.data = {},
|
||||||
|
};
|
||||||
|
|
||||||
|
nextParameter = param;
|
||||||
|
return Result::Success;
|
||||||
|
}
|
|
@ -1,11 +1,86 @@
|
||||||
#include "applets/mii_selector.hpp"
|
#include "applets/mii_selector.hpp"
|
||||||
|
|
||||||
|
#include <boost/crc.hpp>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include "kernel/handles.hpp"
|
||||||
|
|
||||||
using namespace Applets;
|
using namespace Applets;
|
||||||
|
|
||||||
void MiiSelectorApplet::reset() {}
|
void MiiSelectorApplet::reset() {}
|
||||||
Result::HorizonResult MiiSelectorApplet::start() { return Result::Success; }
|
Result::HorizonResult MiiSelectorApplet::start(const MemoryBlock* sharedMem, const std::vector<u8>& parameters, u32 appID) {
|
||||||
|
// Get mii configuration from the application
|
||||||
|
std::memcpy(&config, ¶meters[0], sizeof(config));
|
||||||
|
|
||||||
|
Applets::Parameter param = Applets::Parameter{
|
||||||
|
.senderID = appID,
|
||||||
|
.destID = AppletIDs::Application,
|
||||||
|
.signal = static_cast<u32>(APTSignal::WakeupByExit),
|
||||||
|
.object = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Thanks to Citra devs as always for the default mii data and other applet help
|
||||||
|
output = getDefaultMii();
|
||||||
|
output.returnCode = 0; // Success
|
||||||
|
output.selectedGuestMiiIndex = std::numeric_limits<u32>::max();
|
||||||
|
output.miiChecksum = boost::crc<16, 0x1021, 0, 0, false, false>(&output.selectedMiiData, sizeof(MiiData) + sizeof(output.unknown1));
|
||||||
|
|
||||||
|
// Copy output into the response parameter
|
||||||
|
param.data.resize(sizeof(output));
|
||||||
|
std::memcpy(¶m.data[0], &output, sizeof(output));
|
||||||
|
|
||||||
|
nextParameter = param;
|
||||||
|
return Result::Success;
|
||||||
|
}
|
||||||
|
|
||||||
Result::HorizonResult MiiSelectorApplet::receiveParameter(const Applets::Parameter& parameter) {
|
Result::HorizonResult MiiSelectorApplet::receiveParameter(const Applets::Parameter& parameter) {
|
||||||
Helpers::warn("Mii Selector: Unimplemented ReceiveParameter");
|
Applets::Parameter param = Applets::Parameter{
|
||||||
|
.senderID = parameter.destID,
|
||||||
|
.destID = AppletIDs::Application,
|
||||||
|
.signal = static_cast<u32>(APTSignal::Response),
|
||||||
|
.object = KernelHandles::APTCaptureSharedMemHandle,
|
||||||
|
.data = {},
|
||||||
|
};
|
||||||
|
|
||||||
|
nextParameter = param;
|
||||||
return Result::Success;
|
return Result::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MiiResult MiiSelectorApplet::getDefaultMii() {
|
||||||
|
// This data was obtained from Citra
|
||||||
|
MiiData miiData;
|
||||||
|
miiData.version = 0x03;
|
||||||
|
miiData.miiOptions = 0x00;
|
||||||
|
miiData.miiPos = 0x10;
|
||||||
|
miiData.consoleID = 0x30;
|
||||||
|
miiData.systemID = 0xD285B6B300C8850A;
|
||||||
|
miiData.miiID = 0x98391EE4;
|
||||||
|
miiData.creatorMAC = {0x40, 0xF4, 0x07, 0xB7, 0x37, 0x10};
|
||||||
|
miiData.padding = 0x0000;
|
||||||
|
miiData.miiDetails = 0xA600;
|
||||||
|
miiData.miiName = {'P', 'a', 'n', 'd', 'a', '3', 'D', 'S', 0x0, 0x0};
|
||||||
|
miiData.height = 0x40;
|
||||||
|
miiData.width = 0x40;
|
||||||
|
miiData.faceStyle = 0x00;
|
||||||
|
miiData.faceDetails = 0x00;
|
||||||
|
miiData.hairStyle = 0x21;
|
||||||
|
miiData.hairDetails = 0x01;
|
||||||
|
miiData.eyeDetails = 0x02684418;
|
||||||
|
miiData.eyebrowDetails = 0x26344614;
|
||||||
|
miiData.noseDetails = 0x8112;
|
||||||
|
miiData.mouthDetails = 0x1768;
|
||||||
|
miiData.moustacheDetails = 0x0D00;
|
||||||
|
miiData.beardDetails = 0x0029;
|
||||||
|
miiData.glassesDetails = 0x0052;
|
||||||
|
miiData.moleDetails = 0x4850;
|
||||||
|
miiData.authorName = {u'B', u'O', u'N', u'K', u'E', u'R'};
|
||||||
|
|
||||||
|
MiiResult result;
|
||||||
|
result.returnCode = 0x0;
|
||||||
|
result.isGuestMiiSelected = 0x0;
|
||||||
|
result.selectedGuestMiiIndex = std::numeric_limits<u32>::max();
|
||||||
|
result.selectedMiiData = miiData;
|
||||||
|
result.guestMiiName.fill(0x0);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
|
@ -1,20 +1,93 @@
|
||||||
#include "applets/software_keyboard.hpp"
|
#include "applets/software_keyboard.hpp"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "kernel/handles.hpp"
|
||||||
|
|
||||||
using namespace Applets;
|
using namespace Applets;
|
||||||
|
|
||||||
void SoftwareKeyboardApplet::reset() {}
|
void SoftwareKeyboardApplet::reset() {}
|
||||||
Result::HorizonResult SoftwareKeyboardApplet::start() { return Result::Success; }
|
|
||||||
|
|
||||||
Result::HorizonResult SoftwareKeyboardApplet::receiveParameter(const Applets::Parameter& parameter) {
|
Result::HorizonResult SoftwareKeyboardApplet::receiveParameter(const Applets::Parameter& parameter) {
|
||||||
Helpers::warn("Software keyboard: Unimplemented ReceiveParameter");
|
switch (parameter.signal) {
|
||||||
|
// Signal == request -> Applet is asking swkbd for a shared memory handle for backing up the framebuffer before opening the applet
|
||||||
|
case u32(APTSignal::Request): {
|
||||||
|
Applets::Parameter param = Applets::Parameter{
|
||||||
|
.senderID = parameter.destID,
|
||||||
|
.destID = AppletIDs::Application,
|
||||||
|
.signal = static_cast<u32>(APTSignal::Response),
|
||||||
|
.object = KernelHandles::APTCaptureSharedMemHandle,
|
||||||
|
.data = {},
|
||||||
|
};
|
||||||
|
|
||||||
|
nextParameter = param;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: Helpers::panic("Unimplemented swkbd signal %d\n", parameter.signal);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result::HorizonResult SoftwareKeyboardApplet::start(const MemoryBlock* sharedMem, const std::vector<u8>& parameters, u32 appID) {
|
||||||
|
if (parameters.size() < sizeof(SoftwareKeyboardConfig)) {
|
||||||
|
Helpers::warn("SoftwareKeyboard::Start: Invalid size for keyboard configuration");
|
||||||
|
return Result::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sharedMem == nullptr) {
|
||||||
|
Helpers::warn("SoftwareKeyboard: Missing shared memory");
|
||||||
|
return Result::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get keyboard configuration from the application
|
||||||
|
std::memcpy(&config, ¶meters[0], sizeof(config));
|
||||||
|
|
||||||
|
const std::u16string text = u"Pand";
|
||||||
|
u32 textAddress = sharedMem->addr;
|
||||||
|
|
||||||
|
// Copy text to shared memory the app gave us
|
||||||
|
for (u32 i = 0; i < text.size(); i++) {
|
||||||
|
mem.write16(textAddress, u16(text[i]));
|
||||||
|
textAddress += sizeof(u16);
|
||||||
|
}
|
||||||
|
mem.write16(textAddress, 0); // Write UTF-16 null terminator
|
||||||
|
|
||||||
|
// Temporarily hardcode the pressed button to be the firs tone
|
||||||
|
switch (config.numButtonsM1) {
|
||||||
|
case SoftwareKeyboardButtonConfig::SingleButton: config.returnCode = SoftwareKeyboardResult::D0Click; break;
|
||||||
|
case SoftwareKeyboardButtonConfig::DualButton: config.returnCode = SoftwareKeyboardResult::D1Click1; break;
|
||||||
|
case SoftwareKeyboardButtonConfig::TripleButton: config.returnCode = SoftwareKeyboardResult::D2Click2; break;
|
||||||
|
case SoftwareKeyboardButtonConfig::NoButton: config.returnCode = SoftwareKeyboardResult::None; break;
|
||||||
|
default: Helpers::warn("Software keyboard: Invalid button mode specification"); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.textOffset = 0;
|
||||||
|
config.textLength = static_cast<u16>(text.size());
|
||||||
|
static_assert(offsetof(SoftwareKeyboardConfig, textOffset) == 324);
|
||||||
|
static_assert(offsetof(SoftwareKeyboardConfig, textLength) == 328);
|
||||||
|
|
||||||
|
if (config.filterFlags & SoftwareKeyboardFilter::Callback) {
|
||||||
|
Helpers::warn("Unimplemented software keyboard profanity callback");
|
||||||
|
}
|
||||||
|
|
||||||
|
closeKeyboard(appID);
|
||||||
|
return Result::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoftwareKeyboardApplet::closeKeyboard(u32 appID) {
|
||||||
Applets::Parameter param = Applets::Parameter{
|
Applets::Parameter param = Applets::Parameter{
|
||||||
.senderID = parameter.destID,
|
.senderID = appID,
|
||||||
.destID = AppletIDs::Application,
|
.destID = AppletIDs::Application,
|
||||||
.signal = static_cast<u32>(APTSignal::Response),
|
.signal = static_cast<u32>(APTSignal::WakeupByExit),
|
||||||
.data = {},
|
.object = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Copy software keyboard configuration into the response parameter
|
||||||
|
param.data.resize(sizeof(config));
|
||||||
|
std::memcpy(¶m.data[0], &config, sizeof(config));
|
||||||
|
|
||||||
nextParameter = param;
|
nextParameter = param;
|
||||||
return Result::Success;
|
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
#include "cheats.hpp"
|
#include "cheats.hpp"
|
||||||
|
#include "swap.hpp"
|
||||||
|
|
||||||
Cheats::Cheats(Memory& mem, HIDService& hid) : ar(mem, hid) { reset(); }
|
Cheats::Cheats(Memory& mem, HIDService& hid) : ar(mem, hid) { reset(); }
|
||||||
|
|
||||||
|
@ -23,6 +24,27 @@ u32 Cheats::addCheat(const Cheat& cheat) {
|
||||||
return cheats.size() - 1;
|
return cheats.size() - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u32 Cheats::addCheat(const u8* data, size_t size) {
|
||||||
|
if ((size % 8) != 0) {
|
||||||
|
return badCheatHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cheats::Cheat cheat;
|
||||||
|
cheat.enabled = true;
|
||||||
|
cheat.type = Cheats::CheatType::ActionReplay;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < size; i += 8) {
|
||||||
|
auto read32 = [](const u8* ptr) { return (u32(ptr[3]) << 24) | (u32(ptr[2]) << 16) | (u32(ptr[1]) << 8) | u32(ptr[0]); };
|
||||||
|
|
||||||
|
// Data is passed to us in big endian so we bswap
|
||||||
|
u32 firstWord = Common::swap32(read32(data + i));
|
||||||
|
u32 secondWord = Common::swap32(read32(data + i + 4));
|
||||||
|
cheat.instructions.insert(cheat.instructions.end(), {firstWord, secondWord});
|
||||||
|
}
|
||||||
|
|
||||||
|
return addCheat(cheat);
|
||||||
|
}
|
||||||
|
|
||||||
void Cheats::removeCheat(u32 id) {
|
void Cheats::removeCheat(u32 id) {
|
||||||
if (id >= cheats.size()) {
|
if (id >= cheats.size()) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -45,8 +45,16 @@ HorizonResult SDMCArchive::deleteFile(const FSPath& path) {
|
||||||
|
|
||||||
FileDescriptor SDMCArchive::openFile(const FSPath& path, const FilePerms& perms) {
|
FileDescriptor SDMCArchive::openFile(const FSPath& path, const FilePerms& perms) {
|
||||||
FilePerms realPerms = perms;
|
FilePerms realPerms = perms;
|
||||||
// SD card always has read permission
|
|
||||||
realPerms.raw |= (1 << 0);
|
if (isWriteOnly) {
|
||||||
|
if (perms.read()) {
|
||||||
|
Helpers::warn("SDMC: Read flag is not allowed in SDMC Write-Only archive");
|
||||||
|
return FileError;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Regular SDMC archive always has read permission
|
||||||
|
realPerms.raw |= (1 << 0);
|
||||||
|
}
|
||||||
|
|
||||||
if ((realPerms.create() && !realPerms.write())) {
|
if ((realPerms.create() && !realPerms.write())) {
|
||||||
Helpers::panic("[SDMC] Unsupported flags for OpenFile");
|
Helpers::panic("[SDMC] Unsupported flags for OpenFile");
|
||||||
|
@ -130,6 +138,11 @@ HorizonResult SDMCArchive::createDirectory(const FSPath& path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Rust::Result<DirectorySession, HorizonResult> SDMCArchive::openDirectory(const FSPath& path) {
|
Rust::Result<DirectorySession, HorizonResult> SDMCArchive::openDirectory(const FSPath& path) {
|
||||||
|
if (isWriteOnly) {
|
||||||
|
Helpers::warn("SDMC: OpenDirectory is not allowed in SDMC Write-Only archive");
|
||||||
|
return Err(Result::FS::UnexpectedFileOrDir);
|
||||||
|
}
|
||||||
|
|
||||||
if (path.type == PathType::UTF16) {
|
if (path.type == PathType::UTF16) {
|
||||||
if (!isPathSafe<PathType::UTF16>(path)) {
|
if (!isPathSafe<PathType::UTF16>(path)) {
|
||||||
Helpers::panic("Unsafe path in SaveData::OpenDirectory");
|
Helpers::panic("Unsafe path in SaveData::OpenDirectory");
|
||||||
|
|
|
@ -126,8 +126,7 @@ void Kernel::waitSynchronization1() {
|
||||||
auto& t = threads[currentThreadIndex];
|
auto& t = threads[currentThreadIndex];
|
||||||
t.waitList.resize(1);
|
t.waitList.resize(1);
|
||||||
t.status = ThreadStatus::WaitSync1;
|
t.status = ThreadStatus::WaitSync1;
|
||||||
t.sleepTick = cpu.getTicks();
|
t.wakeupTick = getWakeupTick(ns);
|
||||||
t.waitingNanoseconds = ns;
|
|
||||||
t.waitList[0] = handle;
|
t.waitList[0] = handle;
|
||||||
|
|
||||||
// Add the current thread to the object's wait list
|
// Add the current thread to the object's wait list
|
||||||
|
@ -220,8 +219,7 @@ void Kernel::waitSynchronizationN() {
|
||||||
t.waitList.resize(handleCount);
|
t.waitList.resize(handleCount);
|
||||||
t.status = ThreadStatus::WaitSyncAny;
|
t.status = ThreadStatus::WaitSyncAny;
|
||||||
t.outPointer = outPointer;
|
t.outPointer = outPointer;
|
||||||
t.waitingNanoseconds = ns;
|
t.wakeupTick = getWakeupTick(ns);
|
||||||
t.sleepTick = cpu.getTicks();
|
|
||||||
|
|
||||||
for (s32 i = 0; i < handleCount; i++) {
|
for (s32 i = 0; i < handleCount; i++) {
|
||||||
t.waitList[i] = waitObjects[i].first; // Add object to this thread's waitlist
|
t.waitList[i] = waitObjects[i].first; // Add object to this thread's waitlist
|
||||||
|
|
|
@ -8,13 +8,6 @@
|
||||||
The code for our idle thread looks like this
|
The code for our idle thread looks like this
|
||||||
|
|
||||||
idle_thread_main:
|
idle_thread_main:
|
||||||
mov r0, #4096 @ Loop counter
|
|
||||||
|
|
||||||
.loop:
|
|
||||||
nop; nop; nop; nop @ NOP 4 times to waste some cycles
|
|
||||||
subs r0, #1 @ Decrement counter by 1, go back to looping if loop counter != 0
|
|
||||||
bne .loop
|
|
||||||
|
|
||||||
// Sleep for 0 seconds with the SleepThread SVC, which just yields execution
|
// Sleep for 0 seconds with the SleepThread SVC, which just yields execution
|
||||||
mov r0, #0
|
mov r0, #0
|
||||||
mov r1, #0
|
mov r1, #0
|
||||||
|
@ -24,14 +17,10 @@ idle_thread_main:
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static constexpr u8 idleThreadCode[] = {
|
static constexpr u8 idleThreadCode[] = {
|
||||||
0x01, 0x0A, 0xA0, 0xE3, // mov r0, #4096
|
|
||||||
0x00, 0xF0, 0x20, 0xE3, 0x00, 0xF0, 0x20, 0xE3, 0x00, 0xF0, 0x20, 0xE3, 0x00, 0xF0, 0x20, 0xE3, // nop (4 times)
|
|
||||||
0x01, 0x00, 0x50, 0xE2, // subs r0, #1
|
|
||||||
0xF9, 0xFF, 0xFF, 0x1A, // bne loop
|
|
||||||
0x00, 0x00, 0xA0, 0xE3, // mov r0, #0
|
0x00, 0x00, 0xA0, 0xE3, // mov r0, #0
|
||||||
0x00, 0x10, 0xA0, 0xE3, // mov r1, #0
|
0x00, 0x10, 0xA0, 0xE3, // mov r1, #0
|
||||||
0x0A, 0x00, 0x00, 0xEF, // svc SleepThread
|
0x0A, 0x00, 0x00, 0xEF, // svc SleepThread
|
||||||
0xF4, 0xFF, 0xFF, 0xEA // b idle_thread_main
|
0xFB, 0xFF, 0xFF, 0xEA // b idle_thread_main
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set up an idle thread to run when no thread is able to run
|
// Set up an idle thread to run when no thread is able to run
|
||||||
|
|
|
@ -50,6 +50,7 @@ void Kernel::serviceSVC(u32 svc) {
|
||||||
case 0x1D: svcClearTimer(); break;
|
case 0x1D: svcClearTimer(); break;
|
||||||
case 0x1E: createMemoryBlock(); break;
|
case 0x1E: createMemoryBlock(); break;
|
||||||
case 0x1F: mapMemoryBlock(); break;
|
case 0x1F: mapMemoryBlock(); break;
|
||||||
|
case 0x20: unmapMemoryBlock(); break;
|
||||||
case 0x21: createAddressArbiter(); break;
|
case 0x21: createAddressArbiter(); break;
|
||||||
case 0x22: arbitrateAddress(); break;
|
case 0x22: arbitrateAddress(); break;
|
||||||
case 0x23: svcCloseHandle(); break;
|
case 0x23: svcCloseHandle(); break;
|
||||||
|
|
|
@ -144,6 +144,7 @@ void Kernel::mapMemoryBlock() {
|
||||||
printf("Mapping CSND memory block\n");
|
printf("Mapping CSND memory block\n");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case KernelHandles::APTCaptureSharedMemHandle: break;
|
||||||
default: Helpers::panic("Mapping unknown shared memory block: %X", block);
|
default: Helpers::panic("Mapping unknown shared memory block: %X", block);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -206,3 +207,12 @@ void Kernel::createMemoryBlock() {
|
||||||
regs[0] = Result::Success;
|
regs[0] = Result::Success;
|
||||||
regs[1] = makeMemoryBlock(addr, size, myPermission, otherPermission);
|
regs[1] = makeMemoryBlock(addr, size, myPermission, otherPermission);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Kernel::unmapMemoryBlock() {
|
||||||
|
Handle block = regs[0];
|
||||||
|
u32 addr = regs[1];
|
||||||
|
logSVC("Unmap memory block (block handle = %X, addr = %08X)\n", block, addr);
|
||||||
|
|
||||||
|
Helpers::warn("Stubbed svcUnmapMemoryBlock!");
|
||||||
|
regs[0] = Result::Success;
|
||||||
|
}
|
||||||
|
|
|
@ -52,14 +52,8 @@ bool Kernel::canThreadRun(const Thread& t) {
|
||||||
return true;
|
return true;
|
||||||
} else if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1
|
} else if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1
|
||||||
|| t.status == ThreadStatus::WaitSyncAny || t.status == ThreadStatus::WaitSyncAll) {
|
|| t.status == ThreadStatus::WaitSyncAny || t.status == ThreadStatus::WaitSyncAll) {
|
||||||
const u64 elapsedTicks = cpu.getTicks() - t.sleepTick;
|
|
||||||
|
|
||||||
constexpr double ticksPerSec = double(CPU::ticksPerSec);
|
|
||||||
constexpr double nsPerTick = ticksPerSec / 1000000000.0;
|
|
||||||
|
|
||||||
// TODO: Set r0 to the correct error code on timeout for WaitSync{1/Any/All}
|
// TODO: Set r0 to the correct error code on timeout for WaitSync{1/Any/All}
|
||||||
const s64 elapsedNs = s64(double(elapsedTicks) * nsPerTick);
|
return cpu.getTicks() >= t.wakeupTick;
|
||||||
return elapsedNs >= t.waitingNanoseconds;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle timeouts and stuff here
|
// Handle timeouts and stuff here
|
||||||
|
@ -82,6 +76,15 @@ std::optional<int> Kernel::getNextThread() {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u64 Kernel::getWakeupTick(s64 ns) {
|
||||||
|
// Timeout == -1 means that the thread doesn't plan on waking up automatically
|
||||||
|
if (ns == -1) {
|
||||||
|
return std::numeric_limits<u64>::max();
|
||||||
|
}
|
||||||
|
|
||||||
|
return cpu.getTicks() + Scheduler::nsToCycles(ns);
|
||||||
|
}
|
||||||
|
|
||||||
// See if there is a higher priority, ready thread and switch to that
|
// See if there is a higher priority, ready thread and switch to that
|
||||||
void Kernel::rescheduleThreads() {
|
void Kernel::rescheduleThreads() {
|
||||||
Thread& current = threads[currentThreadIndex]; // Current running thread
|
Thread& current = threads[currentThreadIndex]; // Current running thread
|
||||||
|
@ -368,13 +371,30 @@ void Kernel::sleepThread(s64 ns) {
|
||||||
if (index != idleThreadIndex) {
|
if (index != idleThreadIndex) {
|
||||||
switchThread(index);
|
switchThread(index);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (currentThreadIndex == idleThreadIndex) {
|
||||||
|
const Scheduler& scheduler = cpu.getScheduler();
|
||||||
|
u64 timestamp = scheduler.nextTimestamp;
|
||||||
|
|
||||||
|
for (auto i : threadIndices) {
|
||||||
|
const Thread& t = threads[i];
|
||||||
|
if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1 || t.status == ThreadStatus::WaitSyncAny ||
|
||||||
|
t.status == ThreadStatus::WaitSyncAll) {
|
||||||
|
timestamp = std::min<u64>(timestamp, t.wakeupTick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timestamp > scheduler.currentTimestamp) {
|
||||||
|
u64 idleCycles = timestamp - scheduler.currentTimestamp;
|
||||||
|
cpu.addTicks(idleCycles);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else { // If we're sleeping for >= 0 ns
|
} else { // If we're sleeping for >= 0 ns
|
||||||
Thread& t = threads[currentThreadIndex];
|
Thread& t = threads[currentThreadIndex];
|
||||||
|
|
||||||
t.status = ThreadStatus::WaitSleep;
|
t.status = ThreadStatus::WaitSleep;
|
||||||
t.waitingNanoseconds = ns;
|
t.wakeupTick = getWakeupTick(ns);
|
||||||
t.sleepTick = cpu.getTicks();
|
|
||||||
|
|
||||||
requireReschedule();
|
requireReschedule();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
#include "kernel.hpp"
|
#include <limits>
|
||||||
|
|
||||||
#include "cpu.hpp"
|
#include "cpu.hpp"
|
||||||
|
#include "kernel.hpp"
|
||||||
|
#include "scheduler.hpp"
|
||||||
|
|
||||||
Handle Kernel::makeTimer(ResetType type) {
|
Handle Kernel::makeTimer(ResetType type) {
|
||||||
Handle ret = makeObject(KernelObjectType::Timer);
|
Handle ret = makeObject(KernelObjectType::Timer);
|
||||||
|
@ -13,27 +16,44 @@ Handle Kernel::makeTimer(ResetType type) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Kernel::updateTimer(Handle handle, Timer* timer) {
|
void Kernel::pollTimers() {
|
||||||
if (timer->running) {
|
u64 currentTick = cpu.getTicks();
|
||||||
const u64 currentTicks = cpu.getTicks();
|
|
||||||
u64 elapsedTicks = currentTicks - timer->startTick;
|
|
||||||
|
|
||||||
constexpr double ticksPerSec = double(CPU::ticksPerSec);
|
// Find the next timestamp we'll poll KTimers on. To do this, we find the minimum tick one of our timers will fire
|
||||||
constexpr double nsPerTick = ticksPerSec / 1000000000.0;
|
u64 nextTimestamp = std::numeric_limits<u64>::max();
|
||||||
const s64 elapsedNs = s64(double(elapsedTicks) * nsPerTick);
|
// Do we have any active timers anymore? If not, then we won't need to schedule a new timer poll event
|
||||||
|
bool haveActiveTimers = false;
|
||||||
|
|
||||||
// Timer has fired
|
for (auto handle : timerHandles) {
|
||||||
if (elapsedNs >= timer->currentDelay) {
|
KernelObject* object = getObject(handle, KernelObjectType::Timer);
|
||||||
timer->startTick = currentTicks;
|
if (object != nullptr) {
|
||||||
timer->currentDelay = timer->interval;
|
Timer* timer = object->getData<Timer>();
|
||||||
signalTimer(handle, timer);
|
|
||||||
|
if (timer->running) {
|
||||||
|
// If timer has fired, signal it and set the tick it will next time
|
||||||
|
if (currentTick >= timer->fireTick) {
|
||||||
|
signalTimer(handle, timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update our next timer fire timestamp and mark that we should schedule a new event to poll timers
|
||||||
|
// We recheck timer->running because signalling a timer stops it if interval == 0
|
||||||
|
if (timer->running) {
|
||||||
|
nextTimestamp = std::min<u64>(nextTimestamp, timer->fireTick);
|
||||||
|
haveActiveTimers = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we still have active timers, schedule next poll event
|
||||||
|
if (haveActiveTimers) {
|
||||||
|
Scheduler& scheduler = cpu.getScheduler();
|
||||||
|
scheduler.addEvent(Scheduler::EventType::UpdateTimers, nextTimestamp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Kernel::cancelTimer(Timer* timer) {
|
void Kernel::cancelTimer(Timer* timer) {
|
||||||
timer->running = false;
|
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) {
|
void Kernel::signalTimer(Handle timerHandle, Timer* timer) {
|
||||||
|
@ -54,6 +74,8 @@ void Kernel::signalTimer(Handle timerHandle, Timer* timer) {
|
||||||
|
|
||||||
if (timer->interval == 0) {
|
if (timer->interval == 0) {
|
||||||
cancelTimer(timer);
|
cancelTimer(timer);
|
||||||
|
} else {
|
||||||
|
timer->fireTick = cpu.getTicks() + Scheduler::nsToCycles(timer->interval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,18 +109,20 @@ void Kernel::svcSetTimer() {
|
||||||
|
|
||||||
Timer* timer = object->getData<Timer>();
|
Timer* timer = object->getData<Timer>();
|
||||||
cancelTimer(timer);
|
cancelTimer(timer);
|
||||||
timer->currentDelay = initial;
|
|
||||||
timer->interval = interval;
|
timer->interval = interval;
|
||||||
timer->running = true;
|
timer->running = true;
|
||||||
timer->startTick = cpu.getTicks();
|
timer->fireTick = cpu.getTicks() + Scheduler::nsToCycles(initial);
|
||||||
|
|
||||||
|
Scheduler& scheduler = cpu.getScheduler();
|
||||||
|
// Signal an event to poll timers as soon as possible
|
||||||
|
scheduler.removeEvent(Scheduler::EventType::UpdateTimers);
|
||||||
|
scheduler.addEvent(Scheduler::EventType::UpdateTimers, cpu.getTicks() + 1);
|
||||||
|
|
||||||
// If the initial delay is 0 then instantly signal the timer
|
// If the initial delay is 0 then instantly signal the timer
|
||||||
if (initial == 0) {
|
if (initial == 0) {
|
||||||
signalTimer(handle, timer);
|
signalTimer(handle, timer);
|
||||||
} else {
|
|
||||||
// This should schedule an event in the scheduler when we have one
|
|
||||||
}
|
}
|
||||||
|
|
||||||
regs[0] = Result::Success;
|
regs[0] = Result::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,7 @@ void APTService::handleSyncRequest(u32 messagePointer) {
|
||||||
case APTCommands::NotifyToWait: notifyToWait(messagePointer); break;
|
case APTCommands::NotifyToWait: notifyToWait(messagePointer); break;
|
||||||
case APTCommands::PreloadLibraryApplet: preloadLibraryApplet(messagePointer); break;
|
case APTCommands::PreloadLibraryApplet: preloadLibraryApplet(messagePointer); break;
|
||||||
case APTCommands::PrepareToStartLibraryApplet: prepareToStartLibraryApplet(messagePointer); break;
|
case APTCommands::PrepareToStartLibraryApplet: prepareToStartLibraryApplet(messagePointer); break;
|
||||||
|
case APTCommands::StartLibraryApplet: startLibraryApplet(messagePointer); break;
|
||||||
case APTCommands::ReceiveParameter: [[likely]] receiveParameter(messagePointer); break;
|
case APTCommands::ReceiveParameter: [[likely]] receiveParameter(messagePointer); break;
|
||||||
case APTCommands::ReplySleepQuery: replySleepQuery(messagePointer); break;
|
case APTCommands::ReplySleepQuery: replySleepQuery(messagePointer); break;
|
||||||
case APTCommands::SetApplicationCpuTimeLimit: setApplicationCpuTimeLimit(messagePointer); break;
|
case APTCommands::SetApplicationCpuTimeLimit: setApplicationCpuTimeLimit(messagePointer); break;
|
||||||
|
@ -140,6 +141,39 @@ void APTService::prepareToStartLibraryApplet(u32 messagePointer) {
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void APTService::startLibraryApplet(u32 messagePointer) {
|
||||||
|
const u32 appID = mem.read32(messagePointer + 4);
|
||||||
|
const u32 bufferSize = mem.read32(messagePointer + 8);
|
||||||
|
const Handle parameters = mem.read32(messagePointer + 16);
|
||||||
|
const u32 buffer = mem.read32(messagePointer + 24);
|
||||||
|
log("APT::StartLibraryApplet (app ID = %X)\n", appID);
|
||||||
|
|
||||||
|
Applets::AppletBase* destApplet = appletManager.getApplet(appID);
|
||||||
|
if (destApplet == nullptr) {
|
||||||
|
Helpers::warn("APT::StartLibraryApplet: Unimplemented dest applet ID");
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x1E, 1, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
} else {
|
||||||
|
KernelObject* sharedMemObject = kernel.getObject(parameters);
|
||||||
|
|
||||||
|
const MemoryBlock* sharedMem = sharedMemObject ? sharedMemObject->getData<MemoryBlock>() : nullptr;
|
||||||
|
std::vector<u8> data;
|
||||||
|
data.reserve(bufferSize);
|
||||||
|
|
||||||
|
for (u32 i = 0; i < bufferSize; i++) {
|
||||||
|
data.push_back(mem.read8(buffer + i));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result::HorizonResult result = destApplet->start(sharedMem, data, appID);
|
||||||
|
if (resumeEvent.has_value()) {
|
||||||
|
kernel.signalEvent(resumeEvent.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x1E, 1, 0));
|
||||||
|
mem.write32(messagePointer + 4, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void APTService::checkNew3DS(u32 messagePointer) {
|
void APTService::checkNew3DS(u32 messagePointer) {
|
||||||
log("APT::CheckNew3DS\n");
|
log("APT::CheckNew3DS\n");
|
||||||
mem.write32(messagePointer, IPC::responseHeader(0x102, 2, 0));
|
mem.write32(messagePointer, IPC::responseHeader(0x102, 2, 0));
|
||||||
|
@ -222,7 +256,7 @@ void APTService::sendParameter(u32 messagePointer) {
|
||||||
|
|
||||||
const u32 parameterHandle = mem.read32(messagePointer + 24); // What dis?
|
const u32 parameterHandle = mem.read32(messagePointer + 24); // What dis?
|
||||||
const u32 parameterPointer = mem.read32(messagePointer + 32);
|
const u32 parameterPointer = mem.read32(messagePointer + 32);
|
||||||
log("APT::SendParameter (source app = %X, dest app = %X, cmd = %X, size = %X) (Stubbed)", sourceAppID, destAppID, cmd, paramSize);
|
log("APT::SendParameter (source app = %X, dest app = %X, cmd = %X, size = %X)", sourceAppID, destAppID, cmd, paramSize);
|
||||||
|
|
||||||
mem.write32(messagePointer, IPC::responseHeader(0x0C, 1, 0));
|
mem.write32(messagePointer, IPC::responseHeader(0x0C, 1, 0));
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
@ -260,7 +294,9 @@ void APTService::sendParameter(u32 messagePointer) {
|
||||||
void APTService::receiveParameter(u32 messagePointer) {
|
void APTService::receiveParameter(u32 messagePointer) {
|
||||||
const u32 app = mem.read32(messagePointer + 4);
|
const u32 app = mem.read32(messagePointer + 4);
|
||||||
const u32 size = mem.read32(messagePointer + 8);
|
const u32 size = mem.read32(messagePointer + 8);
|
||||||
log("APT::ReceiveParameter(app ID = %X, size = %04X) (STUBBED)\n", app, size);
|
// Parameter data pointer is in the thread static buffer, which starts 0x100 bytes after the command buffer
|
||||||
|
const u32 buffer = mem.read32(messagePointer + 0x100 + 4);
|
||||||
|
log("APT::ReceiveParameter(app ID = %X, size = %04X)\n", app, size);
|
||||||
|
|
||||||
if (size > 0x1000) Helpers::panic("APT::ReceiveParameter with size > 0x1000");
|
if (size > 0x1000) Helpers::panic("APT::ReceiveParameter with size > 0x1000");
|
||||||
auto parameter = appletManager.receiveParameter();
|
auto parameter = appletManager.receiveParameter();
|
||||||
|
@ -274,14 +310,21 @@ void APTService::receiveParameter(u32 messagePointer) {
|
||||||
// Size of parameter data
|
// Size of parameter data
|
||||||
mem.write32(messagePointer + 16, parameter.data.size());
|
mem.write32(messagePointer + 16, parameter.data.size());
|
||||||
mem.write32(messagePointer + 20, 0x10);
|
mem.write32(messagePointer + 20, 0x10);
|
||||||
mem.write32(messagePointer + 24, 0);
|
mem.write32(messagePointer + 24, parameter.object);
|
||||||
mem.write32(messagePointer + 28, 0);
|
mem.write32(messagePointer + 28, 0);
|
||||||
|
|
||||||
|
const u32 transferSize = std::min<u32>(size, parameter.data.size());
|
||||||
|
for (u32 i = 0; i < transferSize; i++) {
|
||||||
|
mem.write8(buffer + i, parameter.data[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void APTService::glanceParameter(u32 messagePointer) {
|
void APTService::glanceParameter(u32 messagePointer) {
|
||||||
const u32 app = mem.read32(messagePointer + 4);
|
const u32 app = mem.read32(messagePointer + 4);
|
||||||
const u32 size = mem.read32(messagePointer + 8);
|
const u32 size = mem.read32(messagePointer + 8);
|
||||||
log("APT::GlanceParameter(app ID = %X, size = %04X) (STUBBED)\n", app, size);
|
// Parameter data pointer is in the thread static buffer, which starts 0x100 bytes after the command buffer
|
||||||
|
const u32 buffer = mem.read32(messagePointer + 0x100 + 4);
|
||||||
|
log("APT::GlanceParameter(app ID = %X, size = %04X)\n", app, size);
|
||||||
|
|
||||||
if (size > 0x1000) Helpers::panic("APT::GlanceParameter with size > 0x1000");
|
if (size > 0x1000) Helpers::panic("APT::GlanceParameter with size > 0x1000");
|
||||||
auto parameter = appletManager.glanceParameter();
|
auto parameter = appletManager.glanceParameter();
|
||||||
|
@ -296,8 +339,13 @@ void APTService::glanceParameter(u32 messagePointer) {
|
||||||
// Size of parameter data
|
// Size of parameter data
|
||||||
mem.write32(messagePointer + 16, parameter.data.size());
|
mem.write32(messagePointer + 16, parameter.data.size());
|
||||||
mem.write32(messagePointer + 20, 0);
|
mem.write32(messagePointer + 20, 0);
|
||||||
mem.write32(messagePointer + 24, 0);
|
mem.write32(messagePointer + 24, parameter.object);
|
||||||
mem.write32(messagePointer + 28, 0);
|
mem.write32(messagePointer + 28, 0);
|
||||||
|
|
||||||
|
const u32 transferSize = std::min<u32>(size, parameter.data.size());
|
||||||
|
for (u32 i = 0; i < transferSize; i++) {
|
||||||
|
mem.write8(buffer + i, parameter.data[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void APTService::replySleepQuery(u32 messagePointer) {
|
void APTService::replySleepQuery(u32 messagePointer) {
|
||||||
|
|
|
@ -6,6 +6,7 @@ namespace BOSSCommands {
|
||||||
InitializeSession = 0x00010082,
|
InitializeSession = 0x00010082,
|
||||||
UnregisterStorage = 0x00030000,
|
UnregisterStorage = 0x00030000,
|
||||||
GetTaskStorageInfo = 0x00040000,
|
GetTaskStorageInfo = 0x00040000,
|
||||||
|
GetNewArrivalFlag = 0x00070000,
|
||||||
RegisterNewArrivalEvent = 0x00080002,
|
RegisterNewArrivalEvent = 0x00080002,
|
||||||
SetOptoutFlag = 0x00090040,
|
SetOptoutFlag = 0x00090040,
|
||||||
GetOptoutFlag = 0x000A0000,
|
GetOptoutFlag = 0x000A0000,
|
||||||
|
@ -37,6 +38,7 @@ void BOSSService::handleSyncRequest(u32 messagePointer) {
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case BOSSCommands::CancelTask: cancelTask(messagePointer); break;
|
case BOSSCommands::CancelTask: cancelTask(messagePointer); break;
|
||||||
case BOSSCommands::GetErrorCode: getErrorCode(messagePointer); break;
|
case BOSSCommands::GetErrorCode: getErrorCode(messagePointer); break;
|
||||||
|
case BOSSCommands::GetNewArrivalFlag: getNewArrivalFlag(messagePointer); break;
|
||||||
case BOSSCommands::GetNsDataIdList:
|
case BOSSCommands::GetNsDataIdList:
|
||||||
case BOSSCommands::GetNsDataIdList1:
|
case BOSSCommands::GetNsDataIdList1:
|
||||||
getNsDataIdList(messagePointer, command); break;
|
getNsDataIdList(messagePointer, command); break;
|
||||||
|
@ -240,4 +242,11 @@ void BOSSService::unregisterStorage(u32 messagePointer) {
|
||||||
log("BOSS::UnregisterStorage (stubbed)\n");
|
log("BOSS::UnregisterStorage (stubbed)\n");
|
||||||
mem.write32(messagePointer, IPC::responseHeader(0x3, 1, 0));
|
mem.write32(messagePointer, IPC::responseHeader(0x3, 1, 0));
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BOSSService::getNewArrivalFlag(u32 messagePointer) {
|
||||||
|
log("BOSS::GetNewArrivalFlag (stubbed)\n");
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x7, 2, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
mem.write8(messagePointer + 8, 0); // Flag
|
||||||
}
|
}
|
|
@ -1,31 +1,96 @@
|
||||||
#include "services/cam.hpp"
|
#include "services/cam.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "ipc.hpp"
|
#include "ipc.hpp"
|
||||||
#include "kernel.hpp"
|
#include "kernel.hpp"
|
||||||
|
|
||||||
namespace CAMCommands {
|
namespace CAMCommands {
|
||||||
enum : u32 {
|
enum : u32 {
|
||||||
|
StartCapture = 0x00010040,
|
||||||
GetBufferErrorInterruptEvent = 0x00060040,
|
GetBufferErrorInterruptEvent = 0x00060040,
|
||||||
|
SetReceiving = 0x00070102,
|
||||||
DriverInitialize = 0x00390000,
|
DriverInitialize = 0x00390000,
|
||||||
|
DriverFinalize = 0x003A0000,
|
||||||
SetTransferLines = 0x00090100,
|
SetTransferLines = 0x00090100,
|
||||||
GetMaxLines = 0x000A0080,
|
GetMaxLines = 0x000A0080,
|
||||||
|
SetTransferBytes = 0x000B0100,
|
||||||
|
GetTransferBytes = 0x000C0040,
|
||||||
|
GetMaxBytes = 0x000D0080,
|
||||||
|
SetTrimming = 0x000E0080,
|
||||||
|
SetTrimmingParamsCenter = 0x00120140,
|
||||||
|
SetSize = 0x001F00C0, // Set size has different headers between cam:u and New3DS QTM module
|
||||||
SetFrameRate = 0x00200080,
|
SetFrameRate = 0x00200080,
|
||||||
SetContrast = 0x00230080,
|
SetContrast = 0x00230080,
|
||||||
|
GetSuitableY2rStandardCoefficient = 0x00360000,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void CAMService::reset() { bufferErrorInterruptEvents.fill(std::nullopt); }
|
// Helper struct for working with camera ports
|
||||||
|
class PortSelect {
|
||||||
|
u32 value;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PortSelect(u32 val) : value(val) {}
|
||||||
|
bool isValid() const { return value < 4; }
|
||||||
|
|
||||||
|
bool isSinglePort() const {
|
||||||
|
// 1 corresponds to the first camera port and 2 corresponds to the second port
|
||||||
|
return value == 1 || value == 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isBothPorts() const {
|
||||||
|
// 3 corresponds to both ports
|
||||||
|
return value == 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the index of the camera port, assuming that it's only a single port
|
||||||
|
int getSingleIndex() const {
|
||||||
|
if (!isSinglePort()) [[unlikely]] {
|
||||||
|
Helpers::panic("Camera: getSingleIndex called for port with invalid value");
|
||||||
|
}
|
||||||
|
|
||||||
|
return value - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<int> getPortIndices() const {
|
||||||
|
switch (value) {
|
||||||
|
case 1: return {0}; // Only port 1
|
||||||
|
case 2: return {1}; // Only port 2
|
||||||
|
case 3: return {0, 1}; // Both port 1 and port 2
|
||||||
|
default: return {}; // No ports or invalid ports
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void CAMService::reset() {
|
||||||
|
for (auto& port : ports) {
|
||||||
|
port.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CAMService::handleSyncRequest(u32 messagePointer) {
|
void CAMService::handleSyncRequest(u32 messagePointer) {
|
||||||
const u32 command = mem.read32(messagePointer);
|
const u32 command = mem.read32(messagePointer);
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case CAMCommands::DriverInitialize: driverInitialize(messagePointer); break;
|
case CAMCommands::DriverInitialize: driverInitialize(messagePointer); break;
|
||||||
|
case CAMCommands::DriverFinalize: driverFinalize(messagePointer); break;
|
||||||
case CAMCommands::GetBufferErrorInterruptEvent: getBufferErrorInterruptEvent(messagePointer); break;
|
case CAMCommands::GetBufferErrorInterruptEvent: getBufferErrorInterruptEvent(messagePointer); break;
|
||||||
|
case CAMCommands::GetMaxBytes: getMaxBytes(messagePointer); break;
|
||||||
case CAMCommands::GetMaxLines: getMaxLines(messagePointer); break;
|
case CAMCommands::GetMaxLines: getMaxLines(messagePointer); break;
|
||||||
|
case CAMCommands::GetSuitableY2rStandardCoefficient: getSuitableY2RCoefficients(messagePointer); break;
|
||||||
|
case CAMCommands::GetTransferBytes: getTransferBytes(messagePointer); break;
|
||||||
case CAMCommands::SetContrast: setContrast(messagePointer); break;
|
case CAMCommands::SetContrast: setContrast(messagePointer); break;
|
||||||
case CAMCommands::SetFrameRate: setFrameRate(messagePointer); break;
|
case CAMCommands::SetFrameRate: setFrameRate(messagePointer); break;
|
||||||
|
case CAMCommands::SetReceiving: setReceiving(messagePointer); break;
|
||||||
|
case CAMCommands::SetSize: setSize(messagePointer); break;
|
||||||
case CAMCommands::SetTransferLines: setTransferLines(messagePointer); break;
|
case CAMCommands::SetTransferLines: setTransferLines(messagePointer); break;
|
||||||
|
case CAMCommands::SetTrimming: setTrimming(messagePointer); break;
|
||||||
|
case CAMCommands::SetTrimmingParamsCenter: setTrimmingParamsCenter(messagePointer); break;
|
||||||
|
case CAMCommands::StartCapture: startCapture(messagePointer); break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Helpers::panic("Unimplemented CAM service requested. Command: %08X\n", command);
|
Helpers::warn("Unimplemented CAM service requested. Command: %08X\n", command);
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,6 +101,12 @@ void CAMService::driverInitialize(u32 messagePointer) {
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CAMService::driverFinalize(u32 messagePointer) {
|
||||||
|
log("CAM::DriverFinalize\n");
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x3A, 1, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
}
|
||||||
|
|
||||||
void CAMService::setContrast(u32 messagePointer) {
|
void CAMService::setContrast(u32 messagePointer) {
|
||||||
const u32 cameraSelect = mem.read32(messagePointer + 4);
|
const u32 cameraSelect = mem.read32(messagePointer + 4);
|
||||||
const u32 contrast = mem.read32(messagePointer + 8);
|
const u32 contrast = mem.read32(messagePointer + 8);
|
||||||
|
@ -46,13 +117,46 @@ void CAMService::setContrast(u32 messagePointer) {
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CAMService::setTransferLines(u32 messagePointer) {
|
void CAMService::setTransferBytes(u32 messagePointer) {
|
||||||
const u32 port = mem.read32(messagePointer + 4);
|
const u32 portIndex = mem.read8(messagePointer + 4);
|
||||||
const s16 lines = mem.read16(messagePointer + 8);
|
const u32 bytes = mem.read16(messagePointer + 8);
|
||||||
const s16 width = mem.read16(messagePointer + 12);
|
// ...why do these parameters even exist?
|
||||||
const s16 height = mem.read16(messagePointer + 16);
|
const u16 width = mem.read16(messagePointer + 12);
|
||||||
|
const u16 height = mem.read16(messagePointer + 16);
|
||||||
|
const PortSelect port(portIndex);
|
||||||
|
|
||||||
log("CAM::SetTransferLines (port = %d, lines = %d, width = %d, height = %d)\n", port, lines, width, height);
|
if (port.isValid()) {
|
||||||
|
for (int i : port.getPortIndices()) {
|
||||||
|
ports[i].transferBytes = bytes;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Helpers::warn("CAM::SetTransferBytes: Invalid port\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
log("CAM::SetTransferBytes (port = %d, bytes = %d, width = %d, height = %d)\n", portIndex, bytes, width, height);
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x9, 1, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAMService::setTransferLines(u32 messagePointer) {
|
||||||
|
const u32 portIndex = mem.read8(messagePointer + 4);
|
||||||
|
const u16 lines = mem.read16(messagePointer + 8);
|
||||||
|
const u16 width = mem.read16(messagePointer + 12);
|
||||||
|
const u16 height = mem.read16(messagePointer + 16);
|
||||||
|
const PortSelect port(portIndex);
|
||||||
|
|
||||||
|
if (port.isValid()) {
|
||||||
|
const u32 transferBytes = lines * width * 2;
|
||||||
|
|
||||||
|
for (int i : port.getPortIndices()) {
|
||||||
|
ports[i].transferBytes = transferBytes;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Helpers::warn("CAM::SetTransferLines: Invalid port\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
log("CAM::SetTransferLines (port = %d, lines = %d, width = %d, height = %d)\n", portIndex, lines, width, height);
|
||||||
|
|
||||||
mem.write32(messagePointer, IPC::responseHeader(0x9, 1, 0));
|
mem.write32(messagePointer, IPC::responseHeader(0x9, 1, 0));
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
@ -68,6 +172,41 @@ void CAMService::setFrameRate(u32 messagePointer) {
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CAMService::setSize(u32 messagePointer) {
|
||||||
|
const u32 cameraSelect = mem.read32(messagePointer + 4);
|
||||||
|
const u32 size = mem.read32(messagePointer + 8);
|
||||||
|
const u32 context = mem.read32(messagePointer + 12);
|
||||||
|
|
||||||
|
log("CAM::SetSize (camera select = %d, size = %d, context = %d)\n", cameraSelect, size, context);
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x1F, 1, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAMService::setTrimming(u32 messagePointer) {
|
||||||
|
const u32 port = mem.read8(messagePointer + 4);
|
||||||
|
const bool trim = mem.read8(messagePointer + 8) != 0;
|
||||||
|
|
||||||
|
log("CAM::SetTrimming (port = %d, trimming = %s)\n", port, trim ? "enabled" : "disabled");
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x0E, 1, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAMService::setTrimmingParamsCenter(u32 messagePointer) {
|
||||||
|
const u32 port = mem.read8(messagePointer + 4);
|
||||||
|
const s16 trimWidth = s16(mem.read16(messagePointer + 8));
|
||||||
|
const s16 trimHeight = s16(mem.read16(messagePointer + 12));
|
||||||
|
const s16 cameraWidth = s16(mem.read16(messagePointer + 16));
|
||||||
|
const s16 cameraHeight = s16(mem.read16(messagePointer + 20));
|
||||||
|
|
||||||
|
log("CAM::SetTrimmingParamsCenter (port = %d), trim size = (%d, %d), camera size = (%d, %d)\n", port, trimWidth, trimHeight, cameraWidth,
|
||||||
|
cameraHeight);
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x12, 1, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
}
|
||||||
|
|
||||||
// Algorithm taken from Citra
|
// Algorithm taken from Citra
|
||||||
// https://github.com/citra-emu/citra/blob/master/src/core/hle/service/cam/cam.cpp#L465
|
// https://github.com/citra-emu/citra/blob/master/src/core/hle/service/cam/cam.cpp#L465
|
||||||
void CAMService::getMaxLines(u32 messagePointer) {
|
void CAMService::getMaxLines(u32 messagePointer) {
|
||||||
|
@ -100,16 +239,62 @@ void CAMService::getMaxLines(u32 messagePointer) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CAMService::getMaxBytes(u32 messagePointer) {
|
||||||
|
const u16 width = mem.read16(messagePointer + 4);
|
||||||
|
const u16 height = mem.read16(messagePointer + 8);
|
||||||
|
log("CAM::GetMaxBytes (width = %d, height = %d)\n", width, height);
|
||||||
|
|
||||||
|
constexpr u32 MIN_TRANSFER_UNIT = 256;
|
||||||
|
constexpr u32 MAX_BUFFER_SIZE = 2560;
|
||||||
|
if (width * height * 2 % MIN_TRANSFER_UNIT != 0) {
|
||||||
|
Helpers::panic("CAM::GetMaxLines out of range");
|
||||||
|
} else {
|
||||||
|
u32 bytes = MAX_BUFFER_SIZE;
|
||||||
|
|
||||||
|
while (width * height * 2 % bytes != 0) {
|
||||||
|
bytes -= MIN_TRANSFER_UNIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0xA, 2, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
mem.write32(messagePointer + 8, bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAMService::getSuitableY2RCoefficients(u32 messagePointer) {
|
||||||
|
log("CAM::GetSuitableY2RCoefficients\n");
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x36, 2, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
// Y2R standard coefficient value
|
||||||
|
mem.write32(messagePointer + 8, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAMService::getTransferBytes(u32 messagePointer) {
|
||||||
|
const u32 portIndex = mem.read8(messagePointer + 4);
|
||||||
|
const PortSelect port(portIndex);
|
||||||
|
log("CAM::GetTransferBytes (port = %d)\n", portIndex);
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x0C, 2, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
|
||||||
|
if (port.isSinglePort()) {
|
||||||
|
mem.write32(messagePointer + 8, ports[port.getSingleIndex()].transferBytes);
|
||||||
|
} else {
|
||||||
|
// TODO: This should return the proper error code
|
||||||
|
Helpers::warn("CAM::GetTransferBytes: Invalid port index");
|
||||||
|
mem.write32(messagePointer + 8, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CAMService::getBufferErrorInterruptEvent(u32 messagePointer) {
|
void CAMService::getBufferErrorInterruptEvent(u32 messagePointer) {
|
||||||
const u32 port = mem.read32(messagePointer + 4);
|
const u32 portIndex = mem.read8(messagePointer + 4);
|
||||||
log("CAM::GetBufferErrorInterruptEvent (port = %d)\n", port);
|
const PortSelect port(portIndex);
|
||||||
|
log("CAM::GetBufferErrorInterruptEvent (port = %d)\n", portIndex);
|
||||||
|
|
||||||
mem.write32(messagePointer, IPC::responseHeader(0x6, 1, 2));
|
mem.write32(messagePointer, IPC::responseHeader(0x6, 1, 2));
|
||||||
|
|
||||||
if (port >= portCount) {
|
if (port.isSinglePort()) {
|
||||||
Helpers::panic("CAM::GetBufferErrorInterruptEvent: Invalid port");
|
auto& event = ports[port.getSingleIndex()].bufferErrorInterruptEvent;
|
||||||
} else {
|
|
||||||
auto& event = bufferErrorInterruptEvents[port];
|
|
||||||
if (!event.has_value()) {
|
if (!event.has_value()) {
|
||||||
event = kernel.makeEvent(ResetType::OneShot);
|
event = kernel.makeEvent(ResetType::OneShot);
|
||||||
}
|
}
|
||||||
|
@ -117,5 +302,55 @@ void CAMService::getBufferErrorInterruptEvent(u32 messagePointer) {
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
mem.write32(messagePointer + 8, 0);
|
mem.write32(messagePointer + 8, 0);
|
||||||
mem.write32(messagePointer + 12, event.value());
|
mem.write32(messagePointer + 12, event.value());
|
||||||
|
} else {
|
||||||
|
Helpers::panic("CAM::GetBufferErrorInterruptEvent: Invalid port");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CAMService::setReceiving(u32 messagePointer) {
|
||||||
|
const u32 destination = mem.read32(messagePointer + 4);
|
||||||
|
const u32 portIndex = mem.read8(messagePointer + 8);
|
||||||
|
const u32 size = mem.read32(messagePointer + 12);
|
||||||
|
const u16 transferUnit = mem.read16(messagePointer + 16);
|
||||||
|
const Handle process = mem.read32(messagePointer + 24);
|
||||||
|
|
||||||
|
const PortSelect port(portIndex);
|
||||||
|
log("CAM::SetReceiving (port = %d)\n", portIndex);
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x7, 1, 2));
|
||||||
|
|
||||||
|
if (port.isSinglePort()) {
|
||||||
|
auto& event = ports[port.getSingleIndex()].receiveEvent;
|
||||||
|
if (!event.has_value()) {
|
||||||
|
event = kernel.makeEvent(ResetType::OneShot);
|
||||||
|
}
|
||||||
|
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
mem.write32(messagePointer + 8, 0);
|
||||||
|
mem.write32(messagePointer + 12, event.value());
|
||||||
|
} else {
|
||||||
|
Helpers::panic("CAM::SetReceiving: Invalid port");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAMService::startCapture(u32 messagePointer) {
|
||||||
|
const u32 portIndex = mem.read8(messagePointer + 4);
|
||||||
|
const PortSelect port(portIndex);
|
||||||
|
log("CAM::StartCapture (port = %d)\n", portIndex);
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x01, 1, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
|
||||||
|
if (port.isValid()) {
|
||||||
|
for (int i : port.getPortIndices()) {
|
||||||
|
auto& event = ports[port.getSingleIndex()].receiveEvent;
|
||||||
|
|
||||||
|
// Until we properly implement cameras, immediately signal the receive event
|
||||||
|
if (event.has_value()) {
|
||||||
|
kernel.signalEvent(event.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Helpers::warn("CAM::StartCapture: Invalid port index");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ namespace FSCommands {
|
||||||
CloseArchive = 0x080E0080,
|
CloseArchive = 0x080E0080,
|
||||||
FormatThisUserSaveData = 0x080F0180,
|
FormatThisUserSaveData = 0x080F0180,
|
||||||
GetFreeBytes = 0x08120080,
|
GetFreeBytes = 0x08120080,
|
||||||
|
GetSdmcArchiveResource = 0x08140000,
|
||||||
IsSdmcDetected = 0x08170000,
|
IsSdmcDetected = 0x08170000,
|
||||||
IsSdmcWritable = 0x08180000,
|
IsSdmcWritable = 0x08180000,
|
||||||
CardSlotIsInserted = 0x08210000,
|
CardSlotIsInserted = 0x08210000,
|
||||||
|
@ -96,6 +97,7 @@ ArchiveBase* FSService::getArchiveFromID(u32 id, const FSPath& archivePath) {
|
||||||
|
|
||||||
case ArchiveID::SystemSaveData: return &systemSaveData;
|
case ArchiveID::SystemSaveData: return &systemSaveData;
|
||||||
case ArchiveID::SDMC: return &sdmc;
|
case ArchiveID::SDMC: return &sdmc;
|
||||||
|
case ArchiveID::SDMCWriteOnly: return &sdmcWriteOnly;
|
||||||
case ArchiveID::SavedataAndNcch: return &ncch; // This can only access NCCH outside of FSPXI
|
case ArchiveID::SavedataAndNcch: return &ncch; // This can only access NCCH outside of FSPXI
|
||||||
default:
|
default:
|
||||||
Helpers::panic("Unknown archive. ID: %d\n", id);
|
Helpers::panic("Unknown archive. ID: %d\n", id);
|
||||||
|
@ -179,6 +181,7 @@ void FSService::handleSyncRequest(u32 messagePointer) {
|
||||||
case FSCommands::GetFreeBytes: getFreeBytes(messagePointer); break;
|
case FSCommands::GetFreeBytes: getFreeBytes(messagePointer); break;
|
||||||
case FSCommands::GetFormatInfo: getFormatInfo(messagePointer); break;
|
case FSCommands::GetFormatInfo: getFormatInfo(messagePointer); break;
|
||||||
case FSCommands::GetPriority: getPriority(messagePointer); break;
|
case FSCommands::GetPriority: getPriority(messagePointer); break;
|
||||||
|
case FSCommands::GetSdmcArchiveResource: getSdmcArchiveResource(messagePointer); break;
|
||||||
case FSCommands::GetThisSaveDataSecureValue: getThisSaveDataSecureValue(messagePointer); break;
|
case FSCommands::GetThisSaveDataSecureValue: getThisSaveDataSecureValue(messagePointer); break;
|
||||||
case FSCommands::Initialize: initialize(messagePointer); break;
|
case FSCommands::Initialize: initialize(messagePointer); break;
|
||||||
case FSCommands::InitializeWithSdkVersion: initializeWithSdkVersion(messagePointer); break;
|
case FSCommands::InitializeWithSdkVersion: initializeWithSdkVersion(messagePointer); break;
|
||||||
|
@ -764,3 +767,22 @@ void FSService::renameFile(u32 messagePointer) {
|
||||||
const HorizonResult res = sourceArchive->archive->renameFile(sourcePath, destPath);
|
const HorizonResult res = sourceArchive->archive->renameFile(sourcePath, destPath);
|
||||||
mem.write32(messagePointer + 4, static_cast<u32>(res));
|
mem.write32(messagePointer + 4, static_cast<u32>(res));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FSService::getSdmcArchiveResource(u32 messagePointer) {
|
||||||
|
log("FS::GetSdmcArchiveResource"); // 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(0x814, 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);
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ namespace ServiceCommands {
|
||||||
ReleaseRight = 0x00170000,
|
ReleaseRight = 0x00170000,
|
||||||
ImportDisplayCaptureInfo = 0x00180000,
|
ImportDisplayCaptureInfo = 0x00180000,
|
||||||
SaveVramSysArea = 0x00190000,
|
SaveVramSysArea = 0x00190000,
|
||||||
|
RestoreVramSysArea = 0x001A0000,
|
||||||
SetInternalPriorities = 0x001E0080,
|
SetInternalPriorities = 0x001E0080,
|
||||||
StoreDataCache = 0x001F0082
|
StoreDataCache = 0x001F0082
|
||||||
};
|
};
|
||||||
|
@ -51,6 +52,7 @@ void GPUService::handleSyncRequest(u32 messagePointer) {
|
||||||
case ServiceCommands::ImportDisplayCaptureInfo: importDisplayCaptureInfo(messagePointer); break;
|
case ServiceCommands::ImportDisplayCaptureInfo: importDisplayCaptureInfo(messagePointer); break;
|
||||||
case ServiceCommands::RegisterInterruptRelayQueue: registerInterruptRelayQueue(messagePointer); break;
|
case ServiceCommands::RegisterInterruptRelayQueue: registerInterruptRelayQueue(messagePointer); break;
|
||||||
case ServiceCommands::ReleaseRight: releaseRight(messagePointer); break;
|
case ServiceCommands::ReleaseRight: releaseRight(messagePointer); break;
|
||||||
|
case ServiceCommands::RestoreVramSysArea: restoreVramSysArea(messagePointer); break;
|
||||||
case ServiceCommands::SaveVramSysArea: saveVramSysArea(messagePointer); break;
|
case ServiceCommands::SaveVramSysArea: saveVramSysArea(messagePointer); break;
|
||||||
case ServiceCommands::SetAxiConfigQoSMode: setAxiConfigQoSMode(messagePointer); break;
|
case ServiceCommands::SetAxiConfigQoSMode: setAxiConfigQoSMode(messagePointer); break;
|
||||||
case ServiceCommands::SetBufferSwap: setBufferSwap(messagePointer); break;
|
case ServiceCommands::SetBufferSwap: setBufferSwap(messagePointer); break;
|
||||||
|
@ -143,8 +145,7 @@ void GPUService::requestInterrupt(GPUInterrupt type) {
|
||||||
// Not emulating this causes Yoshi's Wooly World, Captain Toad, Metroid 2 et al to hang
|
// Not emulating this causes Yoshi's Wooly World, Captain Toad, Metroid 2 et al to hang
|
||||||
if (type == GPUInterrupt::VBlank0 || type == GPUInterrupt::VBlank1) {
|
if (type == GPUInterrupt::VBlank0 || type == GPUInterrupt::VBlank1) {
|
||||||
int screen = static_cast<u32>(type) - static_cast<u32>(GPUInterrupt::VBlank0); // 0 for top screen, 1 for bottom
|
int screen = static_cast<u32>(type) - static_cast<u32>(GPUInterrupt::VBlank0); // 0 for top screen, 1 for bottom
|
||||||
// TODO: Offset depends on GSP thread being triggered
|
FramebufferUpdate* update = getFramebufferInfo(screen);
|
||||||
FramebufferUpdate* update = reinterpret_cast<FramebufferUpdate*>(&sharedMem[0x200 + screen * sizeof(FramebufferUpdate)]);
|
|
||||||
|
|
||||||
if (update->dirtyFlag & 1) {
|
if (update->dirtyFlag & 1) {
|
||||||
setBufferSwapImpl(screen, update->framebufferInfo[update->index]);
|
setBufferSwapImpl(screen, update->framebufferInfo[update->index]);
|
||||||
|
@ -482,10 +483,50 @@ void GPUService::saveVramSysArea(u32 messagePointer) {
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GPUService::restoreVramSysArea(u32 messagePointer) {
|
||||||
|
Helpers::warn("GSP::GPU::RestoreVramSysArea (stubbed)");
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x1A, 1, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
}
|
||||||
|
|
||||||
// Used in similar fashion to the SaveVramSysArea function
|
// Used in similar fashion to the SaveVramSysArea function
|
||||||
void GPUService::importDisplayCaptureInfo(u32 messagePointer) {
|
void GPUService::importDisplayCaptureInfo(u32 messagePointer) {
|
||||||
Helpers::warn("GSP::GPU::ImportDisplayCaptureInfo (stubbed)");
|
Helpers::warn("GSP::GPU::ImportDisplayCaptureInfo (stubbed)");
|
||||||
|
|
||||||
mem.write32(messagePointer, IPC::responseHeader(0x18, 9, 0));
|
mem.write32(messagePointer, IPC::responseHeader(0x18, 9, 0));
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
|
||||||
|
if (sharedMem == nullptr) {
|
||||||
|
Helpers::warn("GSP::GPU::ImportDisplayCaptureInfo called without GSP module being properly initialized!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FramebufferUpdate* topScreen = getTopFramebufferInfo();
|
||||||
|
FramebufferUpdate* bottomScreen = getBottomFramebufferInfo();
|
||||||
|
|
||||||
|
// Capture the relevant data for both screens and return them to the caller
|
||||||
|
CaptureInfo topScreenCapture = {
|
||||||
|
.leftFramebuffer = topScreen->framebufferInfo[topScreen->index].leftFramebufferVaddr,
|
||||||
|
.rightFramebuffer = topScreen->framebufferInfo[topScreen->index].rightFramebufferVaddr,
|
||||||
|
.format = topScreen->framebufferInfo[topScreen->index].format,
|
||||||
|
.stride = topScreen->framebufferInfo[topScreen->index].stride,
|
||||||
|
};
|
||||||
|
|
||||||
|
CaptureInfo bottomScreenCapture = {
|
||||||
|
.leftFramebuffer = bottomScreen->framebufferInfo[bottomScreen->index].leftFramebufferVaddr,
|
||||||
|
.rightFramebuffer = bottomScreen->framebufferInfo[bottomScreen->index].rightFramebufferVaddr,
|
||||||
|
.format = bottomScreen->framebufferInfo[bottomScreen->index].format,
|
||||||
|
.stride = bottomScreen->framebufferInfo[bottomScreen->index].stride,
|
||||||
|
};
|
||||||
|
|
||||||
|
mem.write32(messagePointer + 8, topScreenCapture.leftFramebuffer);
|
||||||
|
mem.write32(messagePointer + 12, topScreenCapture.rightFramebuffer);
|
||||||
|
mem.write32(messagePointer + 16, topScreenCapture.format);
|
||||||
|
mem.write32(messagePointer + 20, topScreenCapture.stride);
|
||||||
|
|
||||||
|
mem.write32(messagePointer + 24, bottomScreenCapture.leftFramebuffer);
|
||||||
|
mem.write32(messagePointer + 28, bottomScreenCapture.rightFramebuffer);
|
||||||
|
mem.write32(messagePointer + 32, bottomScreenCapture.format);
|
||||||
|
mem.write32(messagePointer + 36, bottomScreenCapture.stride);
|
||||||
}
|
}
|
|
@ -6,6 +6,7 @@ namespace PTMCommands {
|
||||||
GetAdapterState = 0x00050000,
|
GetAdapterState = 0x00050000,
|
||||||
GetBatteryLevel = 0x00070000,
|
GetBatteryLevel = 0x00070000,
|
||||||
GetBatteryChargeState = 0x00080000,
|
GetBatteryChargeState = 0x00080000,
|
||||||
|
GetPedometerState = 0x00090000,
|
||||||
GetStepHistory = 0x000B00C2,
|
GetStepHistory = 0x000B00C2,
|
||||||
GetTotalStepCount = 0x000C0000,
|
GetTotalStepCount = 0x000C0000,
|
||||||
GetStepHistoryAll = 0x000F0084,
|
GetStepHistoryAll = 0x000F0084,
|
||||||
|
@ -30,6 +31,7 @@ void PTMService::handleSyncRequest(u32 messagePointer, PTMService::Type type) {
|
||||||
case PTMCommands::GetAdapterState: getAdapterState(messagePointer); break;
|
case PTMCommands::GetAdapterState: getAdapterState(messagePointer); break;
|
||||||
case PTMCommands::GetBatteryChargeState: getBatteryChargeState(messagePointer); break;
|
case PTMCommands::GetBatteryChargeState: getBatteryChargeState(messagePointer); break;
|
||||||
case PTMCommands::GetBatteryLevel: getBatteryLevel(messagePointer); break;
|
case PTMCommands::GetBatteryLevel: getBatteryLevel(messagePointer); break;
|
||||||
|
case PTMCommands::GetPedometerState: getPedometerState(messagePointer); break;
|
||||||
case PTMCommands::GetStepHistory: getStepHistory(messagePointer); break;
|
case PTMCommands::GetStepHistory: getStepHistory(messagePointer); break;
|
||||||
case PTMCommands::GetStepHistoryAll: getStepHistoryAll(messagePointer); break;
|
case PTMCommands::GetStepHistoryAll: getStepHistoryAll(messagePointer); break;
|
||||||
case PTMCommands::GetTotalStepCount: getTotalStepCount(messagePointer); break;
|
case PTMCommands::GetTotalStepCount: getTotalStepCount(messagePointer); break;
|
||||||
|
@ -67,11 +69,20 @@ void PTMService::getBatteryChargeState(u32 messagePointer) {
|
||||||
// We're only charging if the battery is not already full
|
// We're only charging if the battery is not already full
|
||||||
const bool charging = config.chargerPlugged && (config.batteryPercentage < 100);
|
const bool charging = config.chargerPlugged && (config.batteryPercentage < 100);
|
||||||
|
|
||||||
mem.write32(messagePointer, IPC::responseHeader(0x7, 2, 0));
|
mem.write32(messagePointer, IPC::responseHeader(0x8, 2, 0));
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
mem.write8(messagePointer + 8, charging ? 1 : 0);
|
mem.write8(messagePointer + 8, charging ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PTMService::getPedometerState(u32 messagePointer) {
|
||||||
|
log("PTM::GetPedometerState");
|
||||||
|
constexpr bool countingSteps = true;
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x9, 2, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
mem.write8(messagePointer + 8, countingSteps ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
void PTMService::getBatteryLevel(u32 messagePointer) {
|
void PTMService::getBatteryLevel(u32 messagePointer) {
|
||||||
log("PTM::GetBatteryLevel");
|
log("PTM::GetBatteryLevel");
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ namespace Y2RCommands {
|
||||||
SetSendingY = 0x00100102,
|
SetSendingY = 0x00100102,
|
||||||
SetSendingU = 0x00110102,
|
SetSendingU = 0x00110102,
|
||||||
SetSendingV = 0x00120102,
|
SetSendingV = 0x00120102,
|
||||||
|
SetSendingYUV = 0x00130102,
|
||||||
SetReceiving = 0x00180102,
|
SetReceiving = 0x00180102,
|
||||||
SetInputLineWidth = 0x001A0040,
|
SetInputLineWidth = 0x001A0040,
|
||||||
GetInputLineWidth = 0x001B0000,
|
GetInputLineWidth = 0x001B0000,
|
||||||
|
@ -82,6 +83,7 @@ void Y2RService::handleSyncRequest(u32 messagePointer) {
|
||||||
case Y2RCommands::SetSendingY: setSendingY(messagePointer); break;
|
case Y2RCommands::SetSendingY: setSendingY(messagePointer); break;
|
||||||
case Y2RCommands::SetSendingU: setSendingU(messagePointer); break;
|
case Y2RCommands::SetSendingU: setSendingU(messagePointer); break;
|
||||||
case Y2RCommands::SetSendingV: setSendingV(messagePointer); break;
|
case Y2RCommands::SetSendingV: setSendingV(messagePointer); break;
|
||||||
|
case Y2RCommands::SetSendingYUV: setSendingYUV(messagePointer); break;
|
||||||
case Y2RCommands::SetSpacialDithering: setSpacialDithering(messagePointer); break;
|
case Y2RCommands::SetSpacialDithering: setSpacialDithering(messagePointer); break;
|
||||||
case Y2RCommands::SetStandardCoeff: setStandardCoeff(messagePointer); break;
|
case Y2RCommands::SetStandardCoeff: setStandardCoeff(messagePointer); break;
|
||||||
case Y2RCommands::SetTemporalDithering: setTemporalDithering(messagePointer); break;
|
case Y2RCommands::SetTemporalDithering: setTemporalDithering(messagePointer); break;
|
||||||
|
@ -399,6 +401,14 @@ void Y2RService::setSendingV(u32 messagePointer) {
|
||||||
mem.write32(messagePointer + 4, Result::Success);
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Y2RService::setSendingYUV(u32 messagePointer) {
|
||||||
|
log("Y2R::SetSendingYUV\n");
|
||||||
|
Helpers::warn("Unimplemented Y2R::SetSendingYUV");
|
||||||
|
|
||||||
|
mem.write32(messagePointer, IPC::responseHeader(0x13, 1, 0));
|
||||||
|
mem.write32(messagePointer + 4, Result::Success);
|
||||||
|
}
|
||||||
|
|
||||||
void Y2RService::setReceiving(u32 messagePointer) {
|
void Y2RService::setReceiving(u32 messagePointer) {
|
||||||
log("Y2R::SetReceiving\n");
|
log("Y2R::SetReceiving\n");
|
||||||
Helpers::warn("Unimplemented Y2R::setReceiving");
|
Helpers::warn("Unimplemented Y2R::setReceiving");
|
||||||
|
|
|
@ -17,10 +17,11 @@ __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Emulator::Emulator()
|
Emulator::Emulator()
|
||||||
: config(getConfigPath()), kernel(cpu, memory, gpu, config), cpu(memory, kernel), gpu(memory, config),
|
: config(getConfigPath()), kernel(cpu, memory, gpu, config), cpu(memory, kernel, *this), gpu(memory, config), memory(cpu.getTicksRef(), config),
|
||||||
memory(cpu.getTicksRef(), config), cheats(memory, kernel.getServiceManager().getHID()), lua(memory), running(false), programRunning(false)
|
cheats(memory, kernel.getServiceManager().getHID()), lua(memory), running(false), programRunning(false)
|
||||||
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
||||||
, httpServer(this)
|
,
|
||||||
|
httpServer(this)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
#ifdef PANDA3DS_ENABLE_DISCORD_RPC
|
#ifdef PANDA3DS_ENABLE_DISCORD_RPC
|
||||||
|
@ -45,6 +46,9 @@ void Emulator::reset(ReloadOption reload) {
|
||||||
cpu.reset();
|
cpu.reset();
|
||||||
gpu.reset();
|
gpu.reset();
|
||||||
memory.reset();
|
memory.reset();
|
||||||
|
// Reset scheduler and add a VBlank event
|
||||||
|
scheduler.reset();
|
||||||
|
|
||||||
// Kernel must be reset last because it depends on CPU/Memory state
|
// Kernel must be reset last because it depends on CPU/Memory state
|
||||||
kernel.reset();
|
kernel.reset();
|
||||||
|
|
||||||
|
@ -99,12 +103,6 @@ void Emulator::runFrame() {
|
||||||
if (running) {
|
if (running) {
|
||||||
cpu.runFrame(); // Run 1 frame of instructions
|
cpu.runFrame(); // Run 1 frame of instructions
|
||||||
gpu.display(); // Display graphics
|
gpu.display(); // Display graphics
|
||||||
lua.signalEvent(LuaEvent::Frame);
|
|
||||||
|
|
||||||
// Send VBlank interrupts
|
|
||||||
ServiceManager& srv = kernel.getServiceManager();
|
|
||||||
srv.sendGPUInterrupt(GPUInterrupt::VBlank0);
|
|
||||||
srv.sendGPUInterrupt(GPUInterrupt::VBlank1);
|
|
||||||
|
|
||||||
// Run cheats if any are loaded
|
// Run cheats if any are loaded
|
||||||
if (cheats.haveCheats()) [[unlikely]] {
|
if (cheats.haveCheats()) [[unlikely]] {
|
||||||
|
@ -117,6 +115,67 @@ void Emulator::runFrame() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Emulator::pollScheduler() {
|
||||||
|
auto& events = scheduler.events;
|
||||||
|
|
||||||
|
// Pop events until there's none pending anymore
|
||||||
|
while (scheduler.currentTimestamp >= scheduler.nextTimestamp) {
|
||||||
|
// Read event timestamp and type, pop it from the scheduler and handle it
|
||||||
|
auto [time, eventType] = std::move(*events.begin());
|
||||||
|
events.erase(events.begin());
|
||||||
|
|
||||||
|
scheduler.updateNextTimestamp();
|
||||||
|
|
||||||
|
switch (eventType) {
|
||||||
|
case Scheduler::EventType::VBlank: [[likely]] {
|
||||||
|
// Signal that we've reached the end of a frame
|
||||||
|
frameDone = true;
|
||||||
|
lua.signalEvent(LuaEvent::Frame);
|
||||||
|
|
||||||
|
// Send VBlank interrupts
|
||||||
|
ServiceManager& srv = kernel.getServiceManager();
|
||||||
|
srv.sendGPUInterrupt(GPUInterrupt::VBlank0);
|
||||||
|
srv.sendGPUInterrupt(GPUInterrupt::VBlank1);
|
||||||
|
|
||||||
|
// Queue next VBlank event
|
||||||
|
scheduler.addEvent(Scheduler::EventType::VBlank, time + CPU::ticksPerSec / 60);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Scheduler::EventType::UpdateTimers: kernel.pollTimers(); break;
|
||||||
|
|
||||||
|
default: {
|
||||||
|
Helpers::panic("Scheduler: Unimplemented event type received: %d\n", static_cast<int>(eventType));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// If the portable build setting is enabled, then those saves go in the executable directory instead
|
||||||
|
std::filesystem::path Emulator::getAppDataRoot() {
|
||||||
|
std::filesystem::path appDataPath;
|
||||||
|
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
appDataPath = getAndroidAppPath();
|
||||||
|
#else
|
||||||
|
char* appData;
|
||||||
|
if (!config.usePortableBuild) {
|
||||||
|
appData = SDL_GetPrefPath(nullptr, "Alber");
|
||||||
|
appDataPath = std::filesystem::path(appData);
|
||||||
|
} else {
|
||||||
|
appData = SDL_GetBasePath();
|
||||||
|
appDataPath = std::filesystem::path(appData) / "Emulator Files";
|
||||||
|
}
|
||||||
|
SDL_free(appData);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return appDataPath;
|
||||||
|
}
|
||||||
|
|
||||||
bool Emulator::loadROM(const std::filesystem::path& path) {
|
bool Emulator::loadROM(const std::filesystem::path& path) {
|
||||||
// Reset the emulator if we've already loaded a ROM
|
// Reset the emulator if we've already loaded a ROM
|
||||||
if (romType != ROMType::None) {
|
if (romType != ROMType::None) {
|
||||||
|
@ -127,26 +186,7 @@ bool Emulator::loadROM(const std::filesystem::path& path) {
|
||||||
memory.loadedCXI = std::nullopt;
|
memory.loadedCXI = std::nullopt;
|
||||||
memory.loaded3DSX = std::nullopt;
|
memory.loaded3DSX = std::nullopt;
|
||||||
|
|
||||||
// Get path for saving files (AppData on Windows, /home/user/.local/share/ApplicationName on Linux, etc)
|
const std::filesystem::path appDataPath = getAppDataRoot();
|
||||||
// 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.
|
|
||||||
// If the portable build setting is enabled, then those saves go in the executable directory instead
|
|
||||||
std::filesystem::path appDataPath;
|
|
||||||
|
|
||||||
#ifdef __ANDROID__
|
|
||||||
appDataPath = getAndroidAppPath();
|
|
||||||
#else
|
|
||||||
char* appData;
|
|
||||||
if (!config.usePortableBuild) {
|
|
||||||
appData = SDL_GetPrefPath(nullptr, "Alber");
|
|
||||||
appDataPath = std::filesystem::path(appData);
|
|
||||||
} else {
|
|
||||||
appData = SDL_GetBasePath();
|
|
||||||
appDataPath = std::filesystem::path(appData) / "Emulator Files";
|
|
||||||
}
|
|
||||||
SDL_free(appData);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const std::filesystem::path dataPath = appDataPath / path.filename().stem();
|
const std::filesystem::path dataPath = appDataPath / path.filename().stem();
|
||||||
const std::filesystem::path aesKeysPath = appDataPath / "sysdata" / "aes_keys.txt";
|
const std::filesystem::path aesKeysPath = appDataPath / "sysdata" / "aes_keys.txt";
|
||||||
IOFile::setAppDataDir(dataPath);
|
IOFile::setAppDataDir(dataPath);
|
||||||
|
|
|
@ -134,25 +134,7 @@ void HydraCore::setPollInputCallback(void (*callback)()) { pollInputCallback = c
|
||||||
void HydraCore::setCheckButtonCallback(s32 (*callback)(u32 player, hydra::ButtonType button)) { checkButtonCallback = callback; }
|
void HydraCore::setCheckButtonCallback(s32 (*callback)(u32 player, hydra::ButtonType button)) { checkButtonCallback = callback; }
|
||||||
|
|
||||||
u32 HydraCore::addCheat(const u8* data, u32 size) {
|
u32 HydraCore::addCheat(const u8* data, u32 size) {
|
||||||
// Every 3DS cheat is a multiple of 64 bits == 8 bytes
|
return emulator->getCheats().addCheat(data, size);
|
||||||
if ((size % 8) != 0) {
|
|
||||||
return hydra::BAD_CHEAT;
|
|
||||||
}
|
|
||||||
|
|
||||||
Cheats::Cheat cheat;
|
|
||||||
cheat.enabled = true;
|
|
||||||
cheat.type = Cheats::CheatType::ActionReplay;
|
|
||||||
|
|
||||||
for (u32 i = 0; i < size; i += 8) {
|
|
||||||
auto read32 = [](const u8* ptr) { return (u32(ptr[3]) << 24) | (u32(ptr[2]) << 16) | (u32(ptr[1]) << 8) | u32(ptr[0]); };
|
|
||||||
|
|
||||||
// Data is passed to us in big endian so we bswap
|
|
||||||
u32 firstWord = Common::swap32(read32(data + i));
|
|
||||||
u32 secondWord = Common::swap32(read32(data + i + 4));
|
|
||||||
cheat.instructions.insert(cheat.instructions.end(), {firstWord, secondWord});
|
|
||||||
}
|
|
||||||
|
|
||||||
return emulator->getCheats().addCheat(cheat);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void HydraCore::removeCheat(u32 id) { emulator->getCheats().removeCheat(id); }
|
void HydraCore::removeCheat(u32 id) { emulator->getCheats().removeCheat(id); }
|
||||||
|
|
|
@ -35,6 +35,13 @@ JNIEnv* jniEnv() {
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
|
#define MAKE_SETTING(functionName, type, settingName) \
|
||||||
|
AlberFunction(void, functionName) (JNIEnv* env, jobject obj, type value) { emulator->getConfig().settingName = value; }
|
||||||
|
|
||||||
|
MAKE_SETTING(setShaderJitEnabled, jboolean, shaderJitEnabled)
|
||||||
|
|
||||||
|
#undef MAKE_SETTING
|
||||||
|
|
||||||
AlberFunction(void, Setup)(JNIEnv* env, jobject obj) { env->GetJavaVM(&jvm); }
|
AlberFunction(void, Setup)(JNIEnv* env, jobject obj) { env->GetJavaVM(&jvm); }
|
||||||
AlberFunction(void, Pause)(JNIEnv* env, jobject obj) { emulator->pause(); }
|
AlberFunction(void, Pause)(JNIEnv* env, jobject obj) { emulator->pause(); }
|
||||||
AlberFunction(void, Resume)(JNIEnv* env, jobject obj) { emulator->resume(); }
|
AlberFunction(void, Resume)(JNIEnv* env, jobject obj) { emulator->resume(); }
|
||||||
|
|
15
src/lua.cpp
15
src/lua.cpp
|
@ -1,6 +1,12 @@
|
||||||
#ifdef PANDA3DS_ENABLE_LUA
|
#ifdef PANDA3DS_ENABLE_LUA
|
||||||
#include "lua_manager.hpp"
|
#include "lua_manager.hpp"
|
||||||
|
|
||||||
|
#ifndef __ANDROID__
|
||||||
|
extern "C" {
|
||||||
|
#include "luv.h"
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void LuaManager::initialize() {
|
void LuaManager::initialize() {
|
||||||
L = luaL_newstate(); // Open Lua
|
L = luaL_newstate(); // Open Lua
|
||||||
|
|
||||||
|
@ -9,10 +15,15 @@ void LuaManager::initialize() {
|
||||||
initialized = false;
|
initialized = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
luaL_openlibs(L);
|
luaL_openlibs(L);
|
||||||
initializeThunks();
|
|
||||||
|
|
||||||
|
#ifndef __ANDROID__
|
||||||
|
lua_pushstring(L, "luv");
|
||||||
|
luaopen_luv(L);
|
||||||
|
lua_settable(L, LUA_GLOBALSINDEX);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
initializeThunks();
|
||||||
initialized = true;
|
initialized = true;
|
||||||
haveScript = false;
|
haveScript = false;
|
||||||
}
|
}
|
||||||
|
|
268
src/panda_qt/cheats_window.cpp
Normal file
268
src/panda_qt/cheats_window.cpp
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
#include "panda_qt/cheats_window.hpp"
|
||||||
|
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QListWidget>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QTextEdit>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include "cheats.hpp"
|
||||||
|
#include "emulator.hpp"
|
||||||
|
#include "panda_qt/main_window.hpp"
|
||||||
|
|
||||||
|
MainWindow* mainWindow = nullptr;
|
||||||
|
|
||||||
|
struct CheatMetadata {
|
||||||
|
u32 handle = Cheats::badCheatHandle;
|
||||||
|
std::string name = "New cheat";
|
||||||
|
std::string code;
|
||||||
|
bool enabled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
void dispatchToMainThread(std::function<void()> callback) {
|
||||||
|
QTimer* timer = new QTimer();
|
||||||
|
timer->moveToThread(qApp->thread());
|
||||||
|
timer->setSingleShot(true);
|
||||||
|
QObject::connect(timer, &QTimer::timeout, [=]()
|
||||||
|
{
|
||||||
|
callback();
|
||||||
|
timer->deleteLater();
|
||||||
|
});
|
||||||
|
QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection, Q_ARG(int, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
class CheatEntryWidget : public QWidget {
|
||||||
|
public:
|
||||||
|
CheatEntryWidget(Emulator* emu, CheatMetadata metadata, QListWidget* parent);
|
||||||
|
|
||||||
|
void Update() {
|
||||||
|
name->setText(metadata.name.c_str());
|
||||||
|
enabled->setChecked(metadata.enabled);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Remove() {
|
||||||
|
emu->getCheats().removeCheat(metadata.handle);
|
||||||
|
cheatList->takeItem(cheatList->row(listItem));
|
||||||
|
deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
const CheatMetadata& getMetadata() { return metadata; }
|
||||||
|
void setMetadata(const CheatMetadata& metadata) { this->metadata = metadata; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void checkboxChanged(int state);
|
||||||
|
void editClicked();
|
||||||
|
|
||||||
|
Emulator* emu;
|
||||||
|
CheatMetadata metadata;
|
||||||
|
u32 handle;
|
||||||
|
QLabel* name;
|
||||||
|
QCheckBox* enabled;
|
||||||
|
QListWidget* cheatList;
|
||||||
|
QListWidgetItem* listItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CheatEditDialog : public QDialog {
|
||||||
|
public:
|
||||||
|
CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry);
|
||||||
|
|
||||||
|
void accepted();
|
||||||
|
void rejected();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Emulator* emu;
|
||||||
|
CheatEntryWidget& cheatEntry;
|
||||||
|
QTextEdit* codeEdit;
|
||||||
|
QLineEdit* nameEdit;
|
||||||
|
};
|
||||||
|
|
||||||
|
CheatEntryWidget::CheatEntryWidget(Emulator* emu, CheatMetadata metadata, QListWidget* parent)
|
||||||
|
: QWidget(), emu(emu), metadata(metadata), cheatList(parent) {
|
||||||
|
QHBoxLayout* layout = new QHBoxLayout;
|
||||||
|
|
||||||
|
enabled = new QCheckBox;
|
||||||
|
enabled->setChecked(metadata.enabled);
|
||||||
|
|
||||||
|
name = new QLabel(metadata.name.c_str());
|
||||||
|
QPushButton* buttonEdit = new QPushButton(tr("Edit"));
|
||||||
|
|
||||||
|
connect(enabled, &QCheckBox::stateChanged, this, &CheatEntryWidget::checkboxChanged);
|
||||||
|
connect(buttonEdit, &QPushButton::clicked, this, &CheatEntryWidget::editClicked);
|
||||||
|
|
||||||
|
layout->addWidget(enabled);
|
||||||
|
layout->addWidget(name);
|
||||||
|
layout->addWidget(buttonEdit);
|
||||||
|
setLayout(layout);
|
||||||
|
|
||||||
|
listItem = new QListWidgetItem;
|
||||||
|
listItem->setSizeHint(sizeHint());
|
||||||
|
parent->addItem(listItem);
|
||||||
|
parent->setItemWidget(listItem, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheatEntryWidget::checkboxChanged(int state) {
|
||||||
|
bool enabled = state == Qt::Checked;
|
||||||
|
if (metadata.handle == Cheats::badCheatHandle) {
|
||||||
|
printf("Cheat handle is bad, this shouldn't happen\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enabled) {
|
||||||
|
emu->getCheats().enableCheat(metadata.handle);
|
||||||
|
metadata.enabled = true;
|
||||||
|
} else {
|
||||||
|
emu->getCheats().disableCheat(metadata.handle);
|
||||||
|
metadata.enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheatEntryWidget::editClicked() {
|
||||||
|
CheatEditDialog* dialog = new CheatEditDialog(emu, *this);
|
||||||
|
dialog->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
CheatEditDialog::CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry) : QDialog(), emu(emu), cheatEntry(cheatEntry) {
|
||||||
|
setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
setModal(true);
|
||||||
|
|
||||||
|
QVBoxLayout* layout = new QVBoxLayout;
|
||||||
|
const CheatMetadata& metadata = cheatEntry.getMetadata();
|
||||||
|
codeEdit = new QTextEdit;
|
||||||
|
nameEdit = new QLineEdit;
|
||||||
|
nameEdit->setText(metadata.name.c_str());
|
||||||
|
nameEdit->setPlaceholderText(tr("Cheat name"));
|
||||||
|
layout->addWidget(nameEdit);
|
||||||
|
|
||||||
|
QFont font;
|
||||||
|
font.setFamily("Courier");
|
||||||
|
font.setFixedPitch(true);
|
||||||
|
font.setPointSize(10);
|
||||||
|
codeEdit->setFont(font);
|
||||||
|
|
||||||
|
if (metadata.code.size() != 0) {
|
||||||
|
// Nicely format it like so:
|
||||||
|
// 01234567 89ABCDEF
|
||||||
|
// 01234567 89ABCDEF
|
||||||
|
std::string formattedCode;
|
||||||
|
for (size_t i = 0; i < metadata.code.size(); i += 2) {
|
||||||
|
if (i != 0) {
|
||||||
|
if (i % 8 == 0 && i % 16 != 0) {
|
||||||
|
formattedCode += " ";
|
||||||
|
} else if (i % 16 == 0) {
|
||||||
|
formattedCode += "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formattedCode += metadata.code[i];
|
||||||
|
formattedCode += metadata.code[i + 1];
|
||||||
|
}
|
||||||
|
codeEdit->setText(formattedCode.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
layout->addWidget(codeEdit);
|
||||||
|
setLayout(layout);
|
||||||
|
|
||||||
|
auto buttons = QDialogButtonBox::Ok | QDialogButtonBox::Cancel;
|
||||||
|
QDialogButtonBox* buttonBox = new QDialogButtonBox(buttons);
|
||||||
|
layout->addWidget(buttonBox);
|
||||||
|
|
||||||
|
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||||
|
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||||
|
connect(this, &QDialog::rejected, this, &CheatEditDialog::rejected);
|
||||||
|
connect(this, &QDialog::accepted, this, &CheatEditDialog::accepted);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheatEditDialog::accepted() {
|
||||||
|
QString code = codeEdit->toPlainText();
|
||||||
|
code.replace(QRegularExpression("[^0-9a-fA-F]"), "");
|
||||||
|
|
||||||
|
CheatMetadata metadata = cheatEntry.getMetadata();
|
||||||
|
metadata.name = nameEdit->text().toStdString();
|
||||||
|
metadata.code = code.toStdString();
|
||||||
|
cheatEntry.setMetadata(metadata);
|
||||||
|
|
||||||
|
std::vector<u8> bytes;
|
||||||
|
for (size_t i = 0; i < metadata.code.size(); i += 2) {
|
||||||
|
std::string hex = metadata.code.substr(i, 2);
|
||||||
|
bytes.push_back((u8)std::stoul(hex, nullptr, 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
mainWindow->editCheat(cheatEntry.getMetadata().handle, bytes, [this](u32 handle) {
|
||||||
|
dispatchToMainThread([this, handle]() {
|
||||||
|
if (handle == Cheats::badCheatHandle) {
|
||||||
|
cheatEntry.Remove();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
CheatMetadata metadata = cheatEntry.getMetadata();
|
||||||
|
metadata.handle = handle;
|
||||||
|
cheatEntry.setMetadata(metadata);
|
||||||
|
cheatEntry.Update();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheatEditDialog::rejected() {
|
||||||
|
bool isEditing = cheatEntry.getMetadata().handle != Cheats::badCheatHandle;
|
||||||
|
if (!isEditing) {
|
||||||
|
// Was adding a cheat but user pressed cancel
|
||||||
|
cheatEntry.Remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CheatsWindow::CheatsWindow(Emulator* emu, const std::filesystem::path& cheatPath, QWidget* parent)
|
||||||
|
: QWidget(parent, Qt::Window), emu(emu), cheatPath(cheatPath) {
|
||||||
|
mainWindow = static_cast<MainWindow*>(parent);
|
||||||
|
|
||||||
|
QVBoxLayout* layout = new QVBoxLayout;
|
||||||
|
layout->setContentsMargins(6, 6, 6, 6);
|
||||||
|
setLayout(layout);
|
||||||
|
|
||||||
|
cheatList = new QListWidget;
|
||||||
|
layout->addWidget(cheatList);
|
||||||
|
|
||||||
|
QWidget* buttonBox = new QWidget;
|
||||||
|
QHBoxLayout* buttonLayout = new QHBoxLayout;
|
||||||
|
|
||||||
|
QPushButton* buttonAdd = new QPushButton(tr("Add"));
|
||||||
|
QPushButton* buttonRemove = new QPushButton(tr("Remove"));
|
||||||
|
|
||||||
|
connect(buttonAdd, &QPushButton::clicked, this, &CheatsWindow::addEntry);
|
||||||
|
connect(buttonRemove, &QPushButton::clicked, this, &CheatsWindow::removeClicked);
|
||||||
|
|
||||||
|
buttonLayout->addWidget(buttonAdd);
|
||||||
|
buttonLayout->addWidget(buttonRemove);
|
||||||
|
buttonBox->setLayout(buttonLayout);
|
||||||
|
|
||||||
|
layout->addWidget(buttonBox);
|
||||||
|
|
||||||
|
// TODO: load cheats from saved cheats per game
|
||||||
|
// for (const CheatMetadata& metadata : getSavedCheats())
|
||||||
|
// {
|
||||||
|
// new CheatEntryWidget(emu, metadata, cheatList);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheatsWindow::addEntry() {
|
||||||
|
// CheatEntryWidget is added to the list when it's created
|
||||||
|
CheatEntryWidget* entry = new CheatEntryWidget(emu, {Cheats::badCheatHandle, "New cheat", "", true}, cheatList);
|
||||||
|
CheatEditDialog* dialog = new CheatEditDialog(emu, *entry);
|
||||||
|
dialog->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheatsWindow::removeClicked() {
|
||||||
|
QListWidgetItem* item = cheatList->currentItem();
|
||||||
|
if (item == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CheatEntryWidget* entry = static_cast<CheatEntryWidget*>(cheatList->itemWidget(item));
|
||||||
|
entry->Remove();
|
||||||
|
}
|
|
@ -1,9 +1,14 @@
|
||||||
#include "panda_qt/main_window.hpp"
|
#include "panda_qt/main_window.hpp"
|
||||||
|
|
||||||
|
#include <QDesktopServices>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
|
#include <QString>
|
||||||
|
#include <cmath>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
|
#include "cheats.hpp"
|
||||||
|
|
||||||
MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent), screen(this) {
|
MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent), screen(this) {
|
||||||
setWindowTitle("Alber");
|
setWindowTitle("Alber");
|
||||||
// Enable drop events for loading ROMs
|
// Enable drop events for loading ROMs
|
||||||
|
@ -26,8 +31,14 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
|
||||||
// Create and bind actions for them
|
// Create and bind actions for them
|
||||||
auto loadGameAction = fileMenu->addAction(tr("Load game"));
|
auto loadGameAction = fileMenu->addAction(tr("Load game"));
|
||||||
auto loadLuaAction = fileMenu->addAction(tr("Load Lua script"));
|
auto loadLuaAction = fileMenu->addAction(tr("Load Lua script"));
|
||||||
|
auto openAppFolderAction = fileMenu->addAction(tr("Open Panda3DS folder"));
|
||||||
|
|
||||||
connect(loadGameAction, &QAction::triggered, this, &MainWindow::selectROM);
|
connect(loadGameAction, &QAction::triggered, this, &MainWindow::selectROM);
|
||||||
connect(loadLuaAction, &QAction::triggered, this, &MainWindow::selectLuaFile);
|
connect(loadLuaAction, &QAction::triggered, this, &MainWindow::selectLuaFile);
|
||||||
|
connect(openAppFolderAction, &QAction::triggered, this, [this]() {
|
||||||
|
QString path = QString::fromStdU16String(emu->getAppDataRoot().u16string());
|
||||||
|
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||||
|
});
|
||||||
|
|
||||||
auto pauseAction = emulationMenu->addAction(tr("Pause"));
|
auto pauseAction = emulationMenu->addAction(tr("Pause"));
|
||||||
auto resumeAction = emulationMenu->addAction(tr("Resume"));
|
auto resumeAction = emulationMenu->addAction(tr("Resume"));
|
||||||
|
@ -40,20 +51,23 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
|
||||||
|
|
||||||
auto dumpRomFSAction = toolsMenu->addAction(tr("Dump RomFS"));
|
auto dumpRomFSAction = toolsMenu->addAction(tr("Dump RomFS"));
|
||||||
auto luaEditorAction = toolsMenu->addAction(tr("Open Lua Editor"));
|
auto luaEditorAction = toolsMenu->addAction(tr("Open Lua Editor"));
|
||||||
|
auto cheatsEditorAction = toolsMenu->addAction(tr("Open Cheats Editor"));
|
||||||
connect(dumpRomFSAction, &QAction::triggered, this, &MainWindow::dumpRomFS);
|
connect(dumpRomFSAction, &QAction::triggered, this, &MainWindow::dumpRomFS);
|
||||||
connect(luaEditorAction, &QAction::triggered, this, &MainWindow::openLuaEditor);
|
connect(luaEditorAction, &QAction::triggered, this, &MainWindow::openLuaEditor);
|
||||||
|
connect(cheatsEditorAction, &QAction::triggered, this, &MainWindow::openCheatsEditor);
|
||||||
|
|
||||||
auto aboutAction = aboutMenu->addAction(tr("About Panda3DS"));
|
auto aboutAction = aboutMenu->addAction(tr("About Panda3DS"));
|
||||||
connect(aboutAction, &QAction::triggered, this, &MainWindow::showAboutMenu);
|
connect(aboutAction, &QAction::triggered, this, &MainWindow::showAboutMenu);
|
||||||
|
|
||||||
|
emu = new Emulator();
|
||||||
|
emu->setOutputSize(screen.surfaceWidth, screen.surfaceHeight);
|
||||||
|
|
||||||
// Set up misc objects
|
// Set up misc objects
|
||||||
aboutWindow = new AboutWindow(nullptr);
|
aboutWindow = new AboutWindow(nullptr);
|
||||||
configWindow = new ConfigWindow(this);
|
configWindow = new ConfigWindow(this);
|
||||||
|
cheatsEditor = new CheatsWindow(emu, {}, this);
|
||||||
luaEditor = new TextEditorWindow(this, "script.lua", "");
|
luaEditor = new TextEditorWindow(this, "script.lua", "");
|
||||||
|
|
||||||
emu = new Emulator();
|
|
||||||
emu->setOutputSize(screen.surfaceWidth, screen.surfaceHeight);
|
|
||||||
|
|
||||||
auto args = QCoreApplication::arguments();
|
auto args = QCoreApplication::arguments();
|
||||||
if (args.size() > 1) {
|
if (args.size() > 1) {
|
||||||
auto romPath = std::filesystem::current_path() / args.at(1).toStdU16String();
|
auto romPath = std::filesystem::current_path() / args.at(1).toStdU16String();
|
||||||
|
@ -176,6 +190,7 @@ MainWindow::~MainWindow() {
|
||||||
delete menuBar;
|
delete menuBar;
|
||||||
delete aboutWindow;
|
delete aboutWindow;
|
||||||
delete configWindow;
|
delete configWindow;
|
||||||
|
delete cheatsEditor;
|
||||||
delete luaEditor;
|
delete luaEditor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,8 +209,7 @@ void MainWindow::dumpRomFS() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::filesystem::path path(folder.toStdU16String());
|
std::filesystem::path path(folder.toStdU16String());
|
||||||
|
|
||||||
// TODO: This might break if the game accesses RomFS while we're dumping, we should move it to the emulator thread when we've got a message queue going
|
|
||||||
messageQueueMutex.lock();
|
messageQueueMutex.lock();
|
||||||
RomFS::DumpingResult res = emu->dumpRomFS(path);
|
RomFS::DumpingResult res = emu->dumpRomFS(path);
|
||||||
messageQueueMutex.unlock();
|
messageQueueMutex.unlock();
|
||||||
|
@ -226,6 +240,7 @@ void MainWindow::showAboutMenu() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::openLuaEditor() { luaEditor->show(); }
|
void MainWindow::openLuaEditor() { luaEditor->show(); }
|
||||||
|
void MainWindow::openCheatsEditor() { cheatsEditor->show(); }
|
||||||
|
|
||||||
void MainWindow::dispatchMessage(const EmulatorMessage& message) {
|
void MainWindow::dispatchMessage(const EmulatorMessage& message) {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
|
@ -240,12 +255,33 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) {
|
||||||
delete message.string.str;
|
delete message.string.str;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case MessageType::EditCheat: {
|
||||||
|
u32 handle = message.cheat.c->handle;
|
||||||
|
const std::vector<uint8_t>& cheat = message.cheat.c->cheat;
|
||||||
|
const std::function<void(u32)>& callback = message.cheat.c->callback;
|
||||||
|
bool isEditing = handle != Cheats::badCheatHandle;
|
||||||
|
if (isEditing) {
|
||||||
|
emu->getCheats().removeCheat(handle);
|
||||||
|
u32 handle = emu->getCheats().addCheat(cheat.data(), cheat.size());
|
||||||
|
} else {
|
||||||
|
u32 handle = emu->getCheats().addCheat(cheat.data(), cheat.size());
|
||||||
|
callback(handle);
|
||||||
|
}
|
||||||
|
delete message.cheat.c;
|
||||||
|
} break;
|
||||||
|
|
||||||
case MessageType::Pause: emu->pause(); break;
|
case MessageType::Pause: emu->pause(); break;
|
||||||
case MessageType::Resume: emu->resume(); break;
|
case MessageType::Resume: emu->resume(); break;
|
||||||
case MessageType::TogglePause: emu->togglePause(); break;
|
case MessageType::TogglePause: emu->togglePause(); break;
|
||||||
case MessageType::Reset: emu->reset(Emulator::ReloadOption::Reload); break;
|
case MessageType::Reset: emu->reset(Emulator::ReloadOption::Reload); break;
|
||||||
case MessageType::PressKey: emu->getServiceManager().getHID().pressKey(message.key.key); break;
|
case MessageType::PressKey: emu->getServiceManager().getHID().pressKey(message.key.key); break;
|
||||||
case MessageType::ReleaseKey: emu->getServiceManager().getHID().releaseKey(message.key.key); break;
|
case MessageType::ReleaseKey: emu->getServiceManager().getHID().releaseKey(message.key.key); break;
|
||||||
|
case MessageType::SetCirclePadX: emu->getServiceManager().getHID().setCirclepadX(message.circlepad.value); break;
|
||||||
|
case MessageType::SetCirclePadY: emu->getServiceManager().getHID().setCirclepadY(message.circlepad.value); break;
|
||||||
|
case MessageType::PressTouchscreen:
|
||||||
|
emu->getServiceManager().getHID().setTouchScreenPress(message.touchscreen.x, message.touchscreen.y);
|
||||||
|
break;
|
||||||
|
case MessageType::ReleaseTouchscreen: emu->getServiceManager().getHID().releaseTouchScreen(); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,7 +289,12 @@ void MainWindow::keyPressEvent(QKeyEvent* event) {
|
||||||
auto pressKey = [this](u32 key) {
|
auto pressKey = [this](u32 key) {
|
||||||
EmulatorMessage message{.type = MessageType::PressKey};
|
EmulatorMessage message{.type = MessageType::PressKey};
|
||||||
message.key.key = key;
|
message.key.key = key;
|
||||||
|
sendMessage(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto setCirclePad = [this](MessageType type, s16 value) {
|
||||||
|
EmulatorMessage message{.type = type};
|
||||||
|
message.circlepad.value = value;
|
||||||
sendMessage(message);
|
sendMessage(message);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -266,6 +307,11 @@ void MainWindow::keyPressEvent(QKeyEvent* event) {
|
||||||
case Qt::Key_Q: pressKey(HID::Keys::L); break;
|
case Qt::Key_Q: pressKey(HID::Keys::L); break;
|
||||||
case Qt::Key_P: pressKey(HID::Keys::R); break;
|
case Qt::Key_P: pressKey(HID::Keys::R); break;
|
||||||
|
|
||||||
|
case Qt::Key_W: setCirclePad(MessageType::SetCirclePadY, 0x9C); break;
|
||||||
|
case Qt::Key_A: setCirclePad(MessageType::SetCirclePadX, -0x9C); break;
|
||||||
|
case Qt::Key_S: setCirclePad(MessageType::SetCirclePadY, -0x9C); break;
|
||||||
|
case Qt::Key_D: setCirclePad(MessageType::SetCirclePadX, 0x9C); break;
|
||||||
|
|
||||||
case Qt::Key_Right: pressKey(HID::Keys::Right); break;
|
case Qt::Key_Right: pressKey(HID::Keys::Right); break;
|
||||||
case Qt::Key_Left: pressKey(HID::Keys::Left); break;
|
case Qt::Key_Left: pressKey(HID::Keys::Left); break;
|
||||||
case Qt::Key_Up: pressKey(HID::Keys::Up); break;
|
case Qt::Key_Up: pressKey(HID::Keys::Up); break;
|
||||||
|
@ -282,7 +328,12 @@ void MainWindow::keyReleaseEvent(QKeyEvent* event) {
|
||||||
auto releaseKey = [this](u32 key) {
|
auto releaseKey = [this](u32 key) {
|
||||||
EmulatorMessage message{.type = MessageType::ReleaseKey};
|
EmulatorMessage message{.type = MessageType::ReleaseKey};
|
||||||
message.key.key = key;
|
message.key.key = key;
|
||||||
|
sendMessage(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto releaseCirclePad = [this](MessageType type) {
|
||||||
|
EmulatorMessage message{.type = type};
|
||||||
|
message.circlepad.value = 0;
|
||||||
sendMessage(message);
|
sendMessage(message);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -295,6 +346,12 @@ void MainWindow::keyReleaseEvent(QKeyEvent* event) {
|
||||||
case Qt::Key_Q: releaseKey(HID::Keys::L); break;
|
case Qt::Key_Q: releaseKey(HID::Keys::L); break;
|
||||||
case Qt::Key_P: releaseKey(HID::Keys::R); break;
|
case Qt::Key_P: releaseKey(HID::Keys::R); break;
|
||||||
|
|
||||||
|
case Qt::Key_W:
|
||||||
|
case Qt::Key_S: releaseCirclePad(MessageType::SetCirclePadY); break;
|
||||||
|
|
||||||
|
case Qt::Key_A:
|
||||||
|
case Qt::Key_D: releaseCirclePad(MessageType::SetCirclePadX); break;
|
||||||
|
|
||||||
case Qt::Key_Right: releaseKey(HID::Keys::Right); break;
|
case Qt::Key_Right: releaseKey(HID::Keys::Right); break;
|
||||||
case Qt::Key_Left: releaseKey(HID::Keys::Left); break;
|
case Qt::Key_Left: releaseKey(HID::Keys::Left); break;
|
||||||
case Qt::Key_Up: releaseKey(HID::Keys::Up); break;
|
case Qt::Key_Up: releaseKey(HID::Keys::Up); break;
|
||||||
|
@ -305,10 +362,56 @@ void MainWindow::keyReleaseEvent(QKeyEvent* event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::mousePressEvent(QMouseEvent* event) {
|
||||||
|
if (event->button() == Qt::MouseButton::LeftButton) {
|
||||||
|
const QPointF clickPos = event->globalPosition();
|
||||||
|
const QPointF widgetPos = screen.mapFromGlobal(clickPos);
|
||||||
|
|
||||||
|
// Press is inside the screen area
|
||||||
|
if (widgetPos.x() >= 0 && widgetPos.x() < screen.width() && widgetPos.y() >= 0 && widgetPos.y() < screen.height()) {
|
||||||
|
// Go from widget positions to [0, 400) for x and [0, 480) for y
|
||||||
|
uint x = (uint)std::round(widgetPos.x() / screen.width() * 400.f);
|
||||||
|
uint y = (uint)std::round(widgetPos.y() / screen.height() * 480.f);
|
||||||
|
|
||||||
|
// Check if touch falls in the touch screen area
|
||||||
|
if (y >= 240 && y <= 480 && x >= 40 && x < 40 + 320) {
|
||||||
|
// Convert to 3DS coordinates
|
||||||
|
u16 x_converted = static_cast<u16>(x) - 40;
|
||||||
|
u16 y_converted = static_cast<u16>(y) - 240;
|
||||||
|
|
||||||
|
EmulatorMessage message{.type = MessageType::PressTouchscreen};
|
||||||
|
message.touchscreen.x = x_converted;
|
||||||
|
message.touchscreen.y = y_converted;
|
||||||
|
sendMessage(message);
|
||||||
|
} else {
|
||||||
|
sendMessage(EmulatorMessage{.type = MessageType::ReleaseTouchscreen});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::mouseReleaseEvent(QMouseEvent* event) {
|
||||||
|
if (event->button() == Qt::MouseButton::LeftButton) {
|
||||||
|
sendMessage(EmulatorMessage{.type = MessageType::ReleaseTouchscreen});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::loadLuaScript(const std::string& code) {
|
void MainWindow::loadLuaScript(const std::string& code) {
|
||||||
EmulatorMessage message{.type = MessageType::LoadLuaScript};
|
EmulatorMessage message{.type = MessageType::LoadLuaScript};
|
||||||
|
|
||||||
// Make a copy of the code on the heap to send via the message queue
|
// Make a copy of the code on the heap to send via the message queue
|
||||||
message.string.str = new std::string(code);
|
message.string.str = new std::string(code);
|
||||||
sendMessage(message);
|
sendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::editCheat(u32 handle, const std::vector<uint8_t>& cheat, const std::function<void(u32)>& callback) {
|
||||||
|
EmulatorMessage message{.type = MessageType::EditCheat};
|
||||||
|
|
||||||
|
CheatMessage* c = new CheatMessage();
|
||||||
|
c->handle = handle;
|
||||||
|
c->cheat = cheat;
|
||||||
|
c->callback = callback;
|
||||||
|
|
||||||
|
message.cheat.c = c;
|
||||||
|
sendMessage(message);
|
||||||
}
|
}
|
|
@ -16,8 +16,8 @@ TextEditorWindow::TextEditorWindow(QWidget* parent, const std::string& filename,
|
||||||
ZepReplExCommand::Register(zepWidget.GetEditor(), &replProvider);
|
ZepReplExCommand::Register(zepWidget.GetEditor(), &replProvider);
|
||||||
|
|
||||||
// Default to standard mode instead of vim mode, initialize text box
|
// Default to standard mode instead of vim mode, initialize text box
|
||||||
zepWidget.GetEditor().SetGlobalMode(Zep::ZepMode_Standard::StaticName());
|
|
||||||
zepWidget.GetEditor().InitWithText(filename, initialText);
|
zepWidget.GetEditor().InitWithText(filename, initialText);
|
||||||
|
zepWidget.GetEditor().SetGlobalMode(Zep::ZepMode_Standard::StaticName());
|
||||||
|
|
||||||
// Layout for widgets
|
// Layout for widgets
|
||||||
QVBoxLayout* mainLayout = new QVBoxLayout();
|
QVBoxLayout* mainLayout = new QVBoxLayout();
|
||||||
|
@ -41,4 +41,4 @@ TextEditorWindow::TextEditorWindow(QWidget* parent, const std::string& filename,
|
||||||
|
|
||||||
mainLayout->addWidget(button);
|
mainLayout->addWidget(button);
|
||||||
mainLayout->addWidget(&zepWidget);
|
mainLayout->addWidget(&zepWidget);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,20 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
getByName("release") {
|
||||||
isMinifyEnabled = false
|
isMinifyEnabled = false
|
||||||
|
isShrinkResources = false
|
||||||
|
isDebuggable = false
|
||||||
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
|
"proguard-rules.pro"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
getByName("debug") {
|
||||||
|
isMinifyEnabled = false
|
||||||
|
isShrinkResources = false
|
||||||
|
isDebuggable = true
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
"proguard-rules.pro"
|
"proguard-rules.pro"
|
||||||
|
@ -41,4 +53,4 @@ dependencies {
|
||||||
implementation("androidx.preference:preference:1.2.1")
|
implementation("androidx.preference:preference:1.2.1")
|
||||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||||
implementation("com.google.code.gson:gson:2.10.1")
|
implementation("com.google.code.gson:gson:2.10.1")
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
|
android:isGame="true"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
android:theme="@style/Theme.Pandroid"
|
android:theme="@style/Theme.Pandroid"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<activity
|
<activity
|
||||||
|
@ -46,5 +48,7 @@
|
||||||
|
|
||||||
<activity android:name=".app.preferences.InputMapActivity"
|
<activity android:name=".app.preferences.InputMapActivity"
|
||||||
android:configChanges="density|orientation|screenSize"/>
|
android:configChanges="density|orientation|screenSize"/>
|
||||||
|
|
||||||
|
<service android:name=".app.services.LoggerService" android:process=":logger_service"/>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -22,5 +22,7 @@ public class AlberDriver {
|
||||||
public static native void LoadLuaScript(String script);
|
public static native void LoadLuaScript(String script);
|
||||||
public static native byte[] GetSmdh();
|
public static native byte[] GetSmdh();
|
||||||
|
|
||||||
|
public static native void setShaderJitEnabled(boolean enable);
|
||||||
|
|
||||||
static { System.loadLibrary("Alber"); }
|
static { System.loadLibrary("Alber"); }
|
||||||
}
|
}
|
|
@ -21,6 +21,7 @@ import com.panda3ds.pandroid.input.InputMap;
|
||||||
import com.panda3ds.pandroid.utils.Constants;
|
import com.panda3ds.pandroid.utils.Constants;
|
||||||
import com.panda3ds.pandroid.view.PandaGlSurfaceView;
|
import com.panda3ds.pandroid.view.PandaGlSurfaceView;
|
||||||
import com.panda3ds.pandroid.view.PandaLayoutController;
|
import com.panda3ds.pandroid.view.PandaLayoutController;
|
||||||
|
import com.panda3ds.pandroid.view.utils.PerformanceView;
|
||||||
|
|
||||||
public class GameActivity extends BaseActivity {
|
public class GameActivity extends BaseActivity {
|
||||||
private final DrawerFragment drawerFragment = new DrawerFragment();
|
private final DrawerFragment drawerFragment = new DrawerFragment();
|
||||||
|
@ -56,6 +57,11 @@ public class GameActivity extends BaseActivity {
|
||||||
((CheckBox) findViewById(R.id.hide_screen_controller)).setChecked(GlobalConfig.get(GlobalConfig.KEY_SCREEN_GAMEPAD_VISIBLE));
|
((CheckBox) findViewById(R.id.hide_screen_controller)).setChecked(GlobalConfig.get(GlobalConfig.KEY_SCREEN_GAMEPAD_VISIBLE));
|
||||||
|
|
||||||
getSupportFragmentManager().beginTransaction().replace(R.id.drawer_fragment, drawerFragment).commitNow();
|
getSupportFragmentManager().beginTransaction().replace(R.id.drawer_fragment, drawerFragment).commitNow();
|
||||||
|
|
||||||
|
if (GlobalConfig.get(GlobalConfig.KEY_SHOW_PERFORMANCE_OVERLAY)) {
|
||||||
|
PerformanceView view = new PerformanceView(this);
|
||||||
|
((FrameLayout) findViewById(R.id.panda_gl_frame)).addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -2,11 +2,13 @@ package com.panda3ds.pandroid.app;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
|
||||||
import com.panda3ds.pandroid.AlberDriver;
|
import com.panda3ds.pandroid.AlberDriver;
|
||||||
import com.panda3ds.pandroid.R;
|
import com.panda3ds.pandroid.R;
|
||||||
|
import com.panda3ds.pandroid.app.services.LoggerService;
|
||||||
import com.panda3ds.pandroid.data.config.GlobalConfig;
|
import com.panda3ds.pandroid.data.config.GlobalConfig;
|
||||||
import com.panda3ds.pandroid.input.InputMap;
|
import com.panda3ds.pandroid.input.InputMap;
|
||||||
import com.panda3ds.pandroid.utils.GameUtils;
|
import com.panda3ds.pandroid.utils.GameUtils;
|
||||||
|
@ -24,6 +26,10 @@ public class PandroidApplication extends Application {
|
||||||
GameUtils.initialize();
|
GameUtils.initialize();
|
||||||
InputMap.initialize();
|
InputMap.initialize();
|
||||||
AlberDriver.Setup();
|
AlberDriver.Setup();
|
||||||
|
|
||||||
|
if (GlobalConfig.get(GlobalConfig.KEY_LOGGER_SERVICE)) {
|
||||||
|
startService(new Intent(this, LoggerService.class));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getThemeId() {
|
public static int getThemeId() {
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
package com.panda3ds.pandroid.app.base;
|
package com.panda3ds.pandroid.app.base;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
import androidx.preference.PreferenceFragmentCompat;
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
|
import androidx.preference.SwitchPreference;
|
||||||
|
|
||||||
import com.panda3ds.pandroid.lang.Function;
|
import com.panda3ds.pandroid.lang.Function;
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,4 +20,8 @@ public abstract class BasePreferenceFragment extends PreferenceFragmentCompat {
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void setActivityTitle(@StringRes int titleId) {
|
||||||
|
((AppCompatActivity) requireActivity()).getSupportActionBar().setTitle(titleId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import com.panda3ds.pandroid.R;
|
||||||
import com.panda3ds.pandroid.app.PreferenceActivity;
|
import com.panda3ds.pandroid.app.PreferenceActivity;
|
||||||
import com.panda3ds.pandroid.app.base.BasePreferenceFragment;
|
import com.panda3ds.pandroid.app.base.BasePreferenceFragment;
|
||||||
import com.panda3ds.pandroid.app.preferences.AppearancePreferences;
|
import com.panda3ds.pandroid.app.preferences.AppearancePreferences;
|
||||||
|
import com.panda3ds.pandroid.app.preferences.DeveloperPreferences;
|
||||||
import com.panda3ds.pandroid.app.preferences.InputPreferences;
|
import com.panda3ds.pandroid.app.preferences.InputPreferences;
|
||||||
|
|
||||||
public class SettingsFragment extends BasePreferenceFragment {
|
public class SettingsFragment extends BasePreferenceFragment {
|
||||||
|
@ -16,5 +17,6 @@ public class SettingsFragment extends BasePreferenceFragment {
|
||||||
setPreferencesFromResource(R.xml.start_preferences, rootKey);
|
setPreferencesFromResource(R.xml.start_preferences, rootKey);
|
||||||
setItemClick("input", (item) -> PreferenceActivity.launch(requireContext(), InputPreferences.class));
|
setItemClick("input", (item) -> PreferenceActivity.launch(requireContext(), InputPreferences.class));
|
||||||
setItemClick("appearance", (item)-> PreferenceActivity.launch(requireContext(), AppearancePreferences.class));
|
setItemClick("appearance", (item)-> PreferenceActivity.launch(requireContext(), AppearancePreferences.class));
|
||||||
|
setItemClick("developer", (item)-> PreferenceActivity.launch(requireContext(), DeveloperPreferences.class));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
package com.panda3ds.pandroid.app.preferences;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.preference.SwitchPreference;
|
||||||
|
|
||||||
|
import com.panda3ds.pandroid.R;
|
||||||
|
import com.panda3ds.pandroid.app.PandroidApplication;
|
||||||
|
import com.panda3ds.pandroid.app.base.BasePreferenceFragment;
|
||||||
|
import com.panda3ds.pandroid.app.services.LoggerService;
|
||||||
|
import com.panda3ds.pandroid.data.config.GlobalConfig;
|
||||||
|
|
||||||
|
public class DeveloperPreferences extends BasePreferenceFragment {
|
||||||
|
@Override
|
||||||
|
public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
|
||||||
|
setPreferencesFromResource(R.xml.developer_preferences, rootKey);
|
||||||
|
setActivityTitle(R.string.developer_options);
|
||||||
|
|
||||||
|
setItemClick("performanceMonitor", pref -> GlobalConfig.set(GlobalConfig.KEY_SHOW_PERFORMANCE_OVERLAY, ((SwitchPreference) pref).isChecked()));
|
||||||
|
setItemClick("shaderJit", pref -> GlobalConfig.set(GlobalConfig.KEY_SHADER_JIT, ((SwitchPreference) pref).isChecked()));
|
||||||
|
setItemClick("loggerService", pref -> {
|
||||||
|
boolean checked = ((SwitchPreference) pref).isChecked();
|
||||||
|
Context ctx = PandroidApplication.getAppContext();
|
||||||
|
if (checked) {
|
||||||
|
ctx.startService(new Intent(ctx, LoggerService.class));
|
||||||
|
} else {
|
||||||
|
ctx.stopService(new Intent(ctx, LoggerService.class));
|
||||||
|
}
|
||||||
|
GlobalConfig.set(GlobalConfig.KEY_LOGGER_SERVICE, checked);
|
||||||
|
});
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refresh() {
|
||||||
|
((SwitchPreference) findPreference("performanceMonitor")).setChecked(GlobalConfig.get(GlobalConfig.KEY_SHOW_PERFORMANCE_OVERLAY));
|
||||||
|
((SwitchPreference) findPreference("loggerService")).setChecked(GlobalConfig.get(GlobalConfig.KEY_LOGGER_SERVICE));
|
||||||
|
((SwitchPreference) findPreference("shaderJit")).setChecked(GlobalConfig.get(GlobalConfig.KEY_SHADER_JIT));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
package com.panda3ds.pandroid.app.services;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.panda3ds.pandroid.lang.PipeStreamTask;
|
||||||
|
import com.panda3ds.pandroid.lang.Task;
|
||||||
|
import com.panda3ds.pandroid.utils.Constants;
|
||||||
|
import com.panda3ds.pandroid.utils.FileUtils;
|
||||||
|
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class LoggerService extends Service {
|
||||||
|
private static final long MAX_LOG_SIZE = 1024 * 1024 * 4; // 4MB
|
||||||
|
|
||||||
|
private PipeStreamTask errorTask;
|
||||||
|
private PipeStreamTask outputTask;
|
||||||
|
private Process logcat;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
try {
|
||||||
|
Runtime.getRuntime().exec(new String[]{"logcat", "-c"}).waitFor();
|
||||||
|
logcat = Runtime.getRuntime().exec(new String[]{"logcat"});
|
||||||
|
String logPath = getExternalMediaDirs()[0].getAbsolutePath();
|
||||||
|
FileUtils.createDir(logPath, "logs");
|
||||||
|
logPath = logPath + "/logs";
|
||||||
|
|
||||||
|
if (FileUtils.exists(logPath + "/last.txt")) {
|
||||||
|
FileUtils.delete(logPath + "/last.txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FileUtils.exists(logPath + "/current.txt")) {
|
||||||
|
FileUtils.rename(logPath + "/current.txt", "last.txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputStream stream = FileUtils.getOutputStream(logPath + "/current.txt");
|
||||||
|
errorTask = new PipeStreamTask(logcat.getErrorStream(), stream, MAX_LOG_SIZE);
|
||||||
|
outputTask = new PipeStreamTask(logcat.getInputStream(), stream, MAX_LOG_SIZE);
|
||||||
|
|
||||||
|
errorTask.start();
|
||||||
|
outputTask.start();
|
||||||
|
|
||||||
|
Log.i(Constants.LOG_TAG, "Started logger service");
|
||||||
|
logDeviceInfo();
|
||||||
|
} catch (Exception e) {
|
||||||
|
stopSelf();
|
||||||
|
Log.e(Constants.LOG_TAG, "Failed to start logger service");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logDeviceInfo() {
|
||||||
|
Log.i(Constants.LOG_TAG, "----------------------");
|
||||||
|
Log.i(Constants.LOG_TAG, "Android SDK: " + Build.VERSION.SDK_INT);
|
||||||
|
Log.i(Constants.LOG_TAG, "Device: " + Build.DEVICE);
|
||||||
|
Log.i(Constants.LOG_TAG, "Model: " + Build.MANUFACTURER + " " + Build.MODEL);
|
||||||
|
Log.i(Constants.LOG_TAG, "ABIs: " + Arrays.toString(Build.SUPPORTED_ABIS));
|
||||||
|
try {
|
||||||
|
PackageInfo info = getPackageManager().getPackageInfo(getPackageName(), 0);
|
||||||
|
Log.i(Constants.LOG_TAG, "");
|
||||||
|
Log.i(Constants.LOG_TAG, "Package: " + info.packageName);
|
||||||
|
Log.i(Constants.LOG_TAG, "Install location: " + info.installLocation);
|
||||||
|
Log.i(Constants.LOG_TAG, "App version: " + info.versionName + " (" + info.versionCode + ")");
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(Constants.LOG_TAG, "Error obtaining package info: " + e);
|
||||||
|
}
|
||||||
|
Log.i(Constants.LOG_TAG, "----------------------");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTaskRemoved(Intent rootIntent) {
|
||||||
|
stopSelf();
|
||||||
|
//This is a time for app save save log file
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (Exception e) {}
|
||||||
|
super.onTaskRemoved(rootIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
Log.i(Constants.LOG_TAG, "Logger service terminating");
|
||||||
|
errorTask.close();
|
||||||
|
outputTask.close();
|
||||||
|
try {
|
||||||
|
logcat.destroy();
|
||||||
|
} catch (Throwable t) {}
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,6 @@ import com.panda3ds.pandroid.data.GsonConfigParser;
|
||||||
import com.panda3ds.pandroid.utils.Constants;
|
import com.panda3ds.pandroid.utils.Constants;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class GlobalConfig {
|
public class GlobalConfig {
|
||||||
|
@ -19,6 +18,9 @@ public class GlobalConfig {
|
||||||
|
|
||||||
public static DataModel data;
|
public static DataModel data;
|
||||||
|
|
||||||
|
public static final Key<Boolean> KEY_SHADER_JIT = new Key<>("emu.shader_jit", 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);
|
||||||
public static final Key<Integer> KEY_APP_THEME = new Key<>("app.theme", THEME_ANDROID);
|
public static final Key<Integer> KEY_APP_THEME = new Key<>("app.theme", THEME_ANDROID);
|
||||||
public static final Key<Boolean> KEY_SCREEN_GAMEPAD_VISIBLE = new Key<>("app.screen_gamepad.visible", true);
|
public static final Key<Boolean> KEY_SCREEN_GAMEPAD_VISIBLE = new Key<>("app.screen_gamepad.visible", true);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
package com.panda3ds.pandroid.lang;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public class PipeStreamTask extends Task {
|
||||||
|
private final InputStream input;
|
||||||
|
private final OutputStream output;
|
||||||
|
private final long limit;
|
||||||
|
private long size;
|
||||||
|
|
||||||
|
public PipeStreamTask(InputStream input, OutputStream output, long limit) {
|
||||||
|
this.input = input;
|
||||||
|
this.output = output;
|
||||||
|
this.limit = limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
super.run();
|
||||||
|
int data;
|
||||||
|
try {
|
||||||
|
while ((data = input.read()) != -1) {
|
||||||
|
output.write(data);
|
||||||
|
if (++size > limit) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {}
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
try {
|
||||||
|
output.flush();
|
||||||
|
output.close();
|
||||||
|
input.close();
|
||||||
|
} catch (Exception e) {}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,8 @@ public class Task extends Thread {
|
||||||
super(runnable);
|
super(runnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Task() {}
|
||||||
|
|
||||||
public void runSync() {
|
public void runSync() {
|
||||||
start();
|
start();
|
||||||
waitFinish();
|
waitFinish();
|
||||||
|
|
|
@ -70,6 +70,25 @@ public class FileUtils {
|
||||||
return parseFile(path).exists();
|
return parseFile(path).exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void rename(String path, String newName){
|
||||||
|
parseFile(path).renameTo(newName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void delete(String path) {
|
||||||
|
DocumentFile file = parseFile(path);
|
||||||
|
|
||||||
|
if (file.exists()) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
String[] children = listFiles(path);
|
||||||
|
for (String child : children) {
|
||||||
|
delete(path + "/" + child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean createDir(String path, String name) {
|
public static boolean createDir(String path, String name) {
|
||||||
DocumentFile folder = parseFile(path);
|
DocumentFile folder = parseFile(path);
|
||||||
if (folder.findFile(name) != null) {
|
if (folder.findFile(name) != null) {
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
package com.panda3ds.pandroid.utils;
|
||||||
|
|
||||||
|
import android.app.ActivityManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Debug;
|
||||||
|
import android.os.Process;
|
||||||
|
|
||||||
|
import com.panda3ds.pandroid.app.PandroidApplication;
|
||||||
|
import com.panda3ds.pandroid.data.config.GlobalConfig;
|
||||||
|
|
||||||
|
public class PerformanceMonitor {
|
||||||
|
private static int fps = 1;
|
||||||
|
private static String backend = "";
|
||||||
|
private static int frames = 0;
|
||||||
|
private static long lastUpdate = 0;
|
||||||
|
private static long totalMemory = 1;
|
||||||
|
private static long availableMemory = 0;
|
||||||
|
|
||||||
|
public static void initialize(String backendName) {
|
||||||
|
fps = 1;
|
||||||
|
backend = backendName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void runFrame() {
|
||||||
|
if (GlobalConfig.get(GlobalConfig.KEY_SHOW_PERFORMANCE_OVERLAY)) {
|
||||||
|
frames++;
|
||||||
|
if (System.currentTimeMillis() - lastUpdate > 1000) {
|
||||||
|
lastUpdate = System.currentTimeMillis();
|
||||||
|
fps = frames;
|
||||||
|
frames = 0;
|
||||||
|
try {
|
||||||
|
Context ctx = PandroidApplication.getAppContext();
|
||||||
|
ActivityManager manager = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
|
||||||
|
ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo();
|
||||||
|
manager.getMemoryInfo(info);
|
||||||
|
totalMemory = info.totalMem;
|
||||||
|
availableMemory = info.availMem;
|
||||||
|
} catch (Exception e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getUsedMemory() {
|
||||||
|
return Math.max(1, totalMemory - availableMemory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getTotalMemory() {
|
||||||
|
return totalMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getAvailableMemory() {
|
||||||
|
return availableMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getFps() {
|
||||||
|
return fps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getBackend() {
|
||||||
|
return backend;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void destroy() {}
|
||||||
|
}
|
|
@ -7,8 +7,10 @@ import android.graphics.Rect;
|
||||||
import android.opengl.GLSurfaceView;
|
import android.opengl.GLSurfaceView;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import com.panda3ds.pandroid.AlberDriver;
|
import com.panda3ds.pandroid.AlberDriver;
|
||||||
|
import com.panda3ds.pandroid.data.config.GlobalConfig;
|
||||||
import com.panda3ds.pandroid.utils.Constants;
|
import com.panda3ds.pandroid.utils.Constants;
|
||||||
import com.panda3ds.pandroid.utils.GameUtils;
|
import com.panda3ds.pandroid.utils.GameUtils;
|
||||||
|
import com.panda3ds.pandroid.utils.PerformanceMonitor;
|
||||||
import com.panda3ds.pandroid.view.renderer.ConsoleRenderer;
|
import com.panda3ds.pandroid.view.renderer.ConsoleRenderer;
|
||||||
import com.panda3ds.pandroid.view.renderer.layout.ConsoleLayout;
|
import com.panda3ds.pandroid.view.renderer.layout.ConsoleLayout;
|
||||||
import com.panda3ds.pandroid.view.renderer.layout.DefaultScreenLayout;
|
import com.panda3ds.pandroid.view.renderer.layout.DefaultScreenLayout;
|
||||||
|
@ -38,9 +40,12 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer
|
||||||
if (screenTexture != 0) {
|
if (screenTexture != 0) {
|
||||||
glDeleteTextures(1, new int[] {screenTexture}, 0);
|
glDeleteTextures(1, new int[] {screenTexture}, 0);
|
||||||
}
|
}
|
||||||
if (screenFbo != 0) {
|
|
||||||
|
if (screenFbo != 0) {
|
||||||
glDeleteFramebuffers(1, new int[] {screenFbo}, 0);
|
glDeleteFramebuffers(1, new int[] {screenFbo}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PerformanceMonitor.destroy();
|
||||||
super.finalize();
|
super.finalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +83,7 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
|
||||||
AlberDriver.Initialize();
|
AlberDriver.Initialize();
|
||||||
|
AlberDriver.setShaderJitEnabled(GlobalConfig.get(GlobalConfig.KEY_SHADER_JIT));
|
||||||
AlberDriver.LoadRom(romPath);
|
AlberDriver.LoadRom(romPath);
|
||||||
|
|
||||||
// Load the SMDH
|
// Load the SMDH
|
||||||
|
@ -92,6 +98,8 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer
|
||||||
GameUtils.removeGame(game);
|
GameUtils.removeGame(game);
|
||||||
GameUtils.addGame(GameMetadata.applySMDH(game, smdh));
|
GameUtils.addGame(GameMetadata.applySMDH(game, smdh));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PerformanceMonitor.initialize(getBackendName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onDrawFrame(GL10 unused) {
|
public void onDrawFrame(GL10 unused) {
|
||||||
|
@ -114,6 +122,8 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer
|
||||||
screenHeight - bottomScreen.bottom, GL_COLOR_BUFFER_BIT, GL_LINEAR
|
screenHeight - bottomScreen.bottom, GL_COLOR_BUFFER_BIT, GL_LINEAR
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PerformanceMonitor.runFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onSurfaceChanged(GL10 unused, int width, int height) {
|
public void onSurfaceChanged(GL10 unused, int width, int height) {
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
package com.panda3ds.pandroid.view.utils;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.text.Html;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.widget.AppCompatTextView;
|
||||||
|
|
||||||
|
import com.panda3ds.pandroid.data.config.GlobalConfig;
|
||||||
|
import com.panda3ds.pandroid.utils.PerformanceMonitor;
|
||||||
|
|
||||||
|
public class PerformanceView extends AppCompatTextView {
|
||||||
|
private boolean running = false;
|
||||||
|
|
||||||
|
public PerformanceView(@NonNull Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PerformanceView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||||
|
this(context, attrs,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PerformanceView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
|
||||||
|
int padding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics());
|
||||||
|
setPadding(padding,padding,padding,padding);
|
||||||
|
setTextColor(Color.WHITE);
|
||||||
|
setShadowLayer(padding,0,0,Color.BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refresh(){
|
||||||
|
running = isShown();
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String debug = "";
|
||||||
|
|
||||||
|
// Calculate total memory in MB and the current memory usage
|
||||||
|
int memoryTotalMb = (int) Math.round(PerformanceMonitor.getTotalMemory() / (1024.0 * 1024.0));
|
||||||
|
int memoryUsageMb = (int) Math.round(PerformanceMonitor.getUsedMemory() / (1024.0 * 1024.0));
|
||||||
|
|
||||||
|
debug += "<b>FPS: </b>" + PerformanceMonitor.getFps() + "<br>";
|
||||||
|
debug += "<b>RAM: </b>" + Math.round(((float) memoryUsageMb / memoryTotalMb) * 100) + "% (" + memoryUsageMb + "MB/" + memoryTotalMb + "MB)<br>";
|
||||||
|
debug += "<b>BACKEND: </b>" + PerformanceMonitor.getBackend() + (GlobalConfig.get(GlobalConfig.KEY_SHADER_JIT) ? " + JIT" : "") + "<br>";
|
||||||
|
setText(Html.fromHtml(debug, Html.FROM_HTML_MODE_COMPACT));
|
||||||
|
postDelayed(this::refresh, 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas) {
|
||||||
|
super.onDraw(canvas);
|
||||||
|
|
||||||
|
if (!running) {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,4 +45,13 @@
|
||||||
<string name="open_file">Abrir arquivo</string>
|
<string name="open_file">Abrir arquivo</string>
|
||||||
<string name="create_new">Criar novo</string>
|
<string name="create_new">Criar novo</string>
|
||||||
<string name="running_ff">Executando \"%s\" ...</string>
|
<string name="running_ff">Executando \"%s\" ...</string>
|
||||||
</resources>
|
<string name="developer_options">Opções de desenvolvedor</string>
|
||||||
|
<string name="pref_developer_summary">Depuração, mostrar fps, etc.</string>
|
||||||
|
<string name="pref_performance_monitor_title">Monitor de desempenho</string>
|
||||||
|
<string name="pref_performance_monitor_summary">Mostrar um overlay com fps, memoria, etc.</string>
|
||||||
|
<string name="pref_logger_service_title">Depuração</string>
|
||||||
|
<string name="pref_logger_service_summary">Grave os registros para um arquivo.</string>
|
||||||
|
<string name="pref_shader_jit_title">Shader Jit</string>
|
||||||
|
<string name="pref_shader_jit_summary">Usar recompilador de shaders.</string>
|
||||||
|
<string name="graphics">Gráficos</string>
|
||||||
|
</resources>
|
||||||
|
|
|
@ -46,4 +46,13 @@
|
||||||
<string name="open_file">Open file</string>
|
<string name="open_file">Open file</string>
|
||||||
<string name="create_new">Create new</string>
|
<string name="create_new">Create new</string>
|
||||||
<string name="running_ff">Running \"%s\" ...</string>
|
<string name="running_ff">Running \"%s\" ...</string>
|
||||||
|
<string name="developer_options">Developer options</string>
|
||||||
|
<string name="pref_developer_summary">Logger, FPS Counter, etc.</string>
|
||||||
|
<string name="pref_performance_monitor_title">Performance monitor</string>
|
||||||
|
<string name="pref_performance_monitor_summary">Show overlay with fps, memory, etc.</string>
|
||||||
|
<string name="pref_logger_service_title">Logger</string>
|
||||||
|
<string name="pref_logger_service_summary">Store application logs to file.</string>
|
||||||
|
<string name="pref_shader_jit_title">Shader JIT</string>
|
||||||
|
<string name="pref_shader_jit_summary">Use shader recompiler.</string>
|
||||||
|
<string name="graphics">Graphics</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -24,7 +24,11 @@
|
||||||
<item name="android:textSize">32sp</item>
|
<item name="android:textSize">32sp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Pandroid" parent="Base.Theme.Pandroid" />
|
<style name="Theme.Pandroid" parent="Base.Theme.Pandroid">
|
||||||
|
<item name="android:enforceNavigationBarContrast">false</item>
|
||||||
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
|
<item name="android:windowTranslucentNavigation">false</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Pandroid.Custom" parent="Theme.Pandroid">
|
<style name="Theme.Pandroid.Custom" parent="Theme.Pandroid">
|
||||||
<item name="android:textColor">?colorOnSurface</item>
|
<item name="android:textColor">?colorOnSurface</item>
|
||||||
|
@ -54,6 +58,7 @@
|
||||||
<item name="android:textColorPrimary">@color/text_secondary_light</item>
|
<item name="android:textColorPrimary">@color/text_secondary_light</item>
|
||||||
<item name="android:textColorSecondary">@color/text_secondary_light</item>
|
<item name="android:textColorSecondary">@color/text_secondary_light</item>
|
||||||
<item name="android:windowLightStatusBar">false</item>
|
<item name="android:windowLightStatusBar">false</item>
|
||||||
|
<item name="android:windowLightNavigationBar">false</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Pandroid.Black" parent="Theme.Pandroid.Custom">
|
<style name="Theme.Pandroid.Black" parent="Theme.Pandroid.Custom">
|
||||||
|
@ -72,6 +77,7 @@
|
||||||
<item name="android:textColorPrimary">@color/text_secondary_light</item>
|
<item name="android:textColorPrimary">@color/text_secondary_light</item>
|
||||||
<item name="android:textColorSecondary">@color/text_secondary_light</item>
|
<item name="android:textColorSecondary">@color/text_secondary_light</item>
|
||||||
<item name="android:windowLightStatusBar">false</item>
|
<item name="android:windowLightStatusBar">false</item>
|
||||||
|
<item name="android:windowLightNavigationBar">false</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Pandroid.Light" parent="Theme.Pandroid.Custom">
|
<style name="Theme.Pandroid.Light" parent="Theme.Pandroid.Custom">
|
||||||
|
@ -90,6 +96,7 @@
|
||||||
<item name="android:windowLightStatusBar">true</item>
|
<item name="android:windowLightStatusBar">true</item>
|
||||||
<item name="android:textColorPrimary">@color/text_secondary_dark</item>
|
<item name="android:textColorPrimary">@color/text_secondary_dark</item>
|
||||||
<item name="android:textColorSecondary">@color/text_secondary_dark</item>
|
<item name="android:textColorSecondary">@color/text_secondary_dark</item>
|
||||||
|
<item name="android:windowLightNavigationBar">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
29
src/pandroid/app/src/main/res/xml/developer_preferences.xml
Normal file
29
src/pandroid/app/src/main/res/xml/developer_preferences.xml
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<SwitchPreference
|
||||||
|
android:key="performanceMonitor"
|
||||||
|
app:title="@string/pref_performance_monitor_title"
|
||||||
|
app:summary="@string/pref_performance_monitor_summary"
|
||||||
|
app:iconSpaceReserved="false"/>
|
||||||
|
|
||||||
|
<SwitchPreference
|
||||||
|
android:key="loggerService"
|
||||||
|
app:iconSpaceReserved="false"
|
||||||
|
app:title="@string/pref_logger_service_title"
|
||||||
|
android:summary="@string/pref_logger_service_summary"/>
|
||||||
|
|
||||||
|
<PreferenceCategory
|
||||||
|
app:iconSpaceReserved="false"
|
||||||
|
app:title="@string/graphics">
|
||||||
|
|
||||||
|
<SwitchPreference
|
||||||
|
app:key="shaderJit"
|
||||||
|
app:title="@string/pref_shader_jit_title"
|
||||||
|
app:summary="@string/pref_shader_jit_summary"
|
||||||
|
app:iconSpaceReserved="false"/>
|
||||||
|
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
</PreferenceScreen>
|
|
@ -23,4 +23,11 @@
|
||||||
app:summary="@string/pref_appearance_summary"
|
app:summary="@string/pref_appearance_summary"
|
||||||
app:layout="@layout/preference_start_item"/>
|
app:layout="@layout/preference_start_item"/>
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
app:key="developer"
|
||||||
|
app:icon="@drawable/ic_code"
|
||||||
|
app:title="@string/developer_options"
|
||||||
|
app:summary="@string/pref_developer_summary"
|
||||||
|
app:layout="@layout/preference_start_item"/>
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
1
third_party/libuv
vendored
Submodule
1
third_party/libuv
vendored
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit b8368a1441fd4ebdaaae70b67136c80b1a98be32
|
1
third_party/luv
vendored
Submodule
1
third_party/luv
vendored
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 3e55ac4331d06aa5f43016a142aa2aaa23264105
|
Loading…
Add table
Reference in a new issue