mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-07-06 15:22:57 +12:00
Merge pull request #769 from wheremyfoodat/lua-services
Some checks are pending
Android Build / x64 (release) (push) Waiting to run
Android Build / arm64 (release) (push) Waiting to run
HTTP Server Build / build (push) Waiting to run
Hydra Core Build / Windows (push) Waiting to run
Hydra Core Build / MacOS (push) Waiting to run
Hydra Core Build / Linux (push) Waiting to run
Hydra Core Build / Android-x64 (push) Waiting to run
Hydra Core Build / ARM-Libretro (push) Waiting to run
Linux AppImage Build / build (push) Waiting to run
Linux Build / build (push) Waiting to run
MacOS Build / MacOS-arm64 (push) Waiting to run
MacOS Build / MacOS-x86_64 (push) Waiting to run
MacOS Build / MacOS-Universal (push) Blocked by required conditions
Qt Build / Windows (push) Waiting to run
Qt Build / MacOS-arm64 (push) Waiting to run
Qt Build / MacOS-x86_64 (push) Waiting to run
Qt Build / MacOS-Universal (push) Blocked by required conditions
Qt Build / Linux (push) Waiting to run
Windows Build / build (push) Waiting to run
iOS Simulator Build / build (push) Waiting to run
Some checks are pending
Android Build / x64 (release) (push) Waiting to run
Android Build / arm64 (release) (push) Waiting to run
HTTP Server Build / build (push) Waiting to run
Hydra Core Build / Windows (push) Waiting to run
Hydra Core Build / MacOS (push) Waiting to run
Hydra Core Build / Linux (push) Waiting to run
Hydra Core Build / Android-x64 (push) Waiting to run
Hydra Core Build / ARM-Libretro (push) Waiting to run
Linux AppImage Build / build (push) Waiting to run
Linux Build / build (push) Waiting to run
MacOS Build / MacOS-arm64 (push) Waiting to run
MacOS Build / MacOS-x86_64 (push) Waiting to run
MacOS Build / MacOS-Universal (push) Blocked by required conditions
Qt Build / Windows (push) Waiting to run
Qt Build / MacOS-arm64 (push) Waiting to run
Qt Build / MacOS-x86_64 (push) Waiting to run
Qt Build / MacOS-Universal (push) Blocked by required conditions
Qt Build / Linux (push) Waiting to run
Windows Build / build (push) Waiting to run
iOS Simulator Build / build (push) Waiting to run
Add service call intercepts via Lua & thread debugger draft
This commit is contained in:
commit
80840b6c5e
16 changed files with 358 additions and 48 deletions
|
@ -415,7 +415,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp
|
||||||
include/services/dsp_firmware_db.hpp include/frontend_settings.hpp include/fs/archive_twl_photo.hpp
|
include/services/dsp_firmware_db.hpp include/frontend_settings.hpp include/fs/archive_twl_photo.hpp
|
||||||
include/fs/archive_twl_sound.hpp include/fs/archive_card_spi.hpp include/services/ns.hpp include/audio/audio_device.hpp
|
include/fs/archive_twl_sound.hpp include/fs/archive_card_spi.hpp include/services/ns.hpp include/audio/audio_device.hpp
|
||||||
include/audio/audio_device_interface.hpp include/audio/libretro_audio_device.hpp include/services/ir/ir_types.hpp
|
include/audio/audio_device_interface.hpp include/audio/libretro_audio_device.hpp include/services/ir/ir_types.hpp
|
||||||
include/services/ir/ir_device.hpp include/services/ir/circlepad_pro.hpp
|
include/services/ir/ir_device.hpp include/services/ir/circlepad_pro.hpp include/services/service_intercept.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
if(IOS)
|
if(IOS)
|
||||||
|
@ -732,10 +732,12 @@ if(NOT BUILD_HYDRA_CORE AND NOT BUILD_LIBRETRO_CORE)
|
||||||
set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp src/panda_qt/screen.cpp src/panda_qt/main_window.cpp src/panda_qt/about_window.cpp
|
set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp src/panda_qt/screen.cpp src/panda_qt/main_window.cpp src/panda_qt/about_window.cpp
|
||||||
src/panda_qt/config_window.cpp src/panda_qt/zep.cpp src/panda_qt/text_editor.cpp src/panda_qt/cheats_window.cpp src/panda_qt/mappings.cpp
|
src/panda_qt/config_window.cpp src/panda_qt/zep.cpp src/panda_qt/text_editor.cpp src/panda_qt/cheats_window.cpp src/panda_qt/mappings.cpp
|
||||||
src/panda_qt/patch_window.cpp src/panda_qt/elided_label.cpp src/panda_qt/shader_editor.cpp src/panda_qt/translations.cpp
|
src/panda_qt/patch_window.cpp src/panda_qt/elided_label.cpp src/panda_qt/shader_editor.cpp src/panda_qt/translations.cpp
|
||||||
|
src/panda_qt/thread_debugger.cpp
|
||||||
)
|
)
|
||||||
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/cheats_window.hpp
|
include/panda_qt/config_window.hpp include/panda_qt/text_editor.hpp include/panda_qt/cheats_window.hpp
|
||||||
include/panda_qt/patch_window.hpp include/panda_qt/elided_label.hpp include/panda_qt/shader_editor.hpp
|
include/panda_qt/patch_window.hpp include/panda_qt/elided_label.hpp include/panda_qt/shader_editor.hpp
|
||||||
|
include/panda_qt/thread_debugger.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
source_group("Source Files\\Qt" FILES ${FRONTEND_SOURCE_FILES})
|
source_group("Source Files\\Qt" FILES ${FRONTEND_SOURCE_FILES})
|
||||||
|
|
|
@ -127,6 +127,7 @@ class Emulator {
|
||||||
Scheduler& getScheduler() { return scheduler; }
|
Scheduler& getScheduler() { return scheduler; }
|
||||||
Memory& getMemory() { return memory; }
|
Memory& getMemory() { return memory; }
|
||||||
AudioDeviceInterface& getAudioDevice() { return audioDevice; }
|
AudioDeviceInterface& getAudioDevice() { return audioDevice; }
|
||||||
|
Kernel& getKernel() { return kernel; }
|
||||||
|
|
||||||
RendererType getRendererType() const { return config.rendererType; }
|
RendererType getRendererType() const { return config.rendererType; }
|
||||||
Renderer* getRenderer() { return gpu.getRenderer(); }
|
Renderer* getRenderer() { return gpu.getRenderer(); }
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include "services/service_manager.hpp"
|
#include "services/service_manager.hpp"
|
||||||
|
|
||||||
class CPU;
|
class CPU;
|
||||||
|
class LuaManager;
|
||||||
struct Scheduler;
|
struct Scheduler;
|
||||||
|
|
||||||
class Kernel {
|
class Kernel {
|
||||||
|
@ -65,10 +66,10 @@ class Kernel {
|
||||||
Handle makeProcess(u32 id);
|
Handle makeProcess(u32 id);
|
||||||
Handle makePort(const char* name);
|
Handle makePort(const char* name);
|
||||||
Handle makeSession(Handle port);
|
Handle makeSession(Handle port);
|
||||||
Handle makeThread(u32 entrypoint, u32 initialSP, u32 priority, ProcessorID id, u32 arg,ThreadStatus status = ThreadStatus::Dormant);
|
Handle makeThread(u32 entrypoint, u32 initialSP, u32 priority, ProcessorID id, u32 arg, ThreadStatus status = ThreadStatus::Dormant);
|
||||||
Handle makeMemoryBlock(u32 addr, u32 size, u32 myPermission, u32 otherPermission);
|
Handle makeMemoryBlock(u32 addr, u32 size, u32 myPermission, u32 otherPermission);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Needs to be public to be accessible to the APT/HID services
|
// Needs to be public to be accessible to the APT/HID services
|
||||||
Handle makeEvent(ResetType resetType, Event::CallbackType callback = Event::CallbackType::None);
|
Handle makeEvent(ResetType resetType, Event::CallbackType callback = Event::CallbackType::None);
|
||||||
// Needs to be public to be accessible to the APT/DSP services
|
// Needs to be public to be accessible to the APT/DSP services
|
||||||
|
@ -198,8 +199,8 @@ public:
|
||||||
void closeDirectory(u32 messagePointer, Handle directory);
|
void closeDirectory(u32 messagePointer, Handle directory);
|
||||||
void readDirectory(u32 messagePointer, Handle directory);
|
void readDirectory(u32 messagePointer, Handle directory);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config);
|
Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config, LuaManager& lua);
|
||||||
void initializeFS() { return serviceManager.initializeFS(); }
|
void initializeFS() { return serviceManager.initializeFS(); }
|
||||||
void setVersion(u8 major, u8 minor);
|
void setVersion(u8 major, u8 minor);
|
||||||
void serviceSVC(u32 svc);
|
void serviceSVC(u32 svc);
|
||||||
|
@ -224,9 +225,7 @@ public:
|
||||||
return handleCounter++;
|
return handleCounter++;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<KernelObject>& getObjects() {
|
std::vector<KernelObject>& getObjects() { return objects; }
|
||||||
return objects;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get pointer to the object with the specified handle
|
// Get pointer to the object with the specified handle
|
||||||
KernelObject* getObject(Handle handle) {
|
KernelObject* getObject(Handle handle) {
|
||||||
|
@ -254,4 +253,14 @@ public:
|
||||||
void clearInstructionCache();
|
void clearInstructionCache();
|
||||||
void clearInstructionCacheRange(u32 start, u32 size);
|
void clearInstructionCacheRange(u32 start, u32 size);
|
||||||
u32 getSharedFontVaddr();
|
u32 getSharedFontVaddr();
|
||||||
|
|
||||||
|
// For debuggers: Returns information about the main process' (alive) threads in a vector for the frontend to display
|
||||||
|
std::vector<Thread> getMainProcessThreads();
|
||||||
|
|
||||||
|
// For debuggers: Sets the entrypoint and initial SP for the main thread (thread 0) so that the debugger can display them
|
||||||
|
void setMainThreadEntrypointAndSP(u32 entrypoint, u32 initialSP) {
|
||||||
|
auto& t = threads[0];
|
||||||
|
t.entrypoint = entrypoint;
|
||||||
|
t.initialSP = initialSP;
|
||||||
|
}
|
||||||
};
|
};
|
|
@ -47,6 +47,8 @@ class LuaManager {
|
||||||
signalEventInternal(e);
|
signalEventInternal(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool signalInterceptedService(const std::string& service, u32 function, u32 messagePointer);
|
||||||
};
|
};
|
||||||
|
|
||||||
#else // Lua not enabled, Lua manager does nothing
|
#else // Lua not enabled, Lua manager does nothing
|
||||||
|
@ -60,5 +62,6 @@ class LuaManager {
|
||||||
void loadString(const std::string& code) {}
|
void loadString(const std::string& code) {}
|
||||||
void reset() {}
|
void reset() {}
|
||||||
void signalEvent(LuaEvent e) {}
|
void signalEvent(LuaEvent e) {}
|
||||||
|
bool signalInterceptedService(const std::string& service, u32 function, u32 messagePointer) { return false; }
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include "panda_qt/screen.hpp"
|
#include "panda_qt/screen.hpp"
|
||||||
#include "panda_qt/shader_editor.hpp"
|
#include "panda_qt/shader_editor.hpp"
|
||||||
#include "panda_qt/text_editor.hpp"
|
#include "panda_qt/text_editor.hpp"
|
||||||
|
#include "panda_qt/thread_debugger.hpp"
|
||||||
#include "services/hid.hpp"
|
#include "services/hid.hpp"
|
||||||
|
|
||||||
struct CheatMessage {
|
struct CheatMessage {
|
||||||
|
@ -109,6 +110,7 @@ class MainWindow : public QMainWindow {
|
||||||
TextEditorWindow* luaEditor;
|
TextEditorWindow* luaEditor;
|
||||||
PatchWindow* patchWindow;
|
PatchWindow* patchWindow;
|
||||||
ShaderEditorWindow* shaderEditor;
|
ShaderEditorWindow* shaderEditor;
|
||||||
|
ThreadDebugger* threadDebugger;
|
||||||
|
|
||||||
// We use SDL's game controller API since it's the sanest API that supports as many controllers as possible
|
// We use SDL's game controller API since it's the sanest API that supports as many controllers as possible
|
||||||
SDL_GameController* gameController = nullptr;
|
SDL_GameController* gameController = nullptr;
|
||||||
|
@ -157,4 +159,7 @@ class MainWindow : public QMainWindow {
|
||||||
|
|
||||||
void handleScreenResize(u32 width, u32 height);
|
void handleScreenResize(u32 width, u32 height);
|
||||||
void handleTouchscreenPress(QMouseEvent* event);
|
void handleTouchscreenPress(QMouseEvent* event);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void emulatorPaused();
|
||||||
};
|
};
|
||||||
|
|
28
include/panda_qt/thread_debugger.hpp
Normal file
28
include/panda_qt/thread_debugger.hpp
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QTableWidget>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QtWidgets>
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "emulator.hpp"
|
||||||
|
|
||||||
|
class ThreadDebugger : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
Emulator* emu;
|
||||||
|
|
||||||
|
QVBoxLayout* mainLayout;
|
||||||
|
QTableWidget* threadTable;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ThreadDebugger(Emulator* emu, QWidget* parent = nullptr);
|
||||||
|
void update();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setListItem(int row, int column, const QString& str);
|
||||||
|
void setListItem(int row, int column, const std::string& str);
|
||||||
|
};
|
28
include/services/service_intercept.hpp
Normal file
28
include/services/service_intercept.hpp
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "helpers.hpp"
|
||||||
|
|
||||||
|
// We allow Lua scripts to intercept service calls and allow their own code to be ran on SyncRequests
|
||||||
|
// For example, if we want to intercept dsp::DSP ReadPipe (Header: 0x000E00C0), the "serviceName" field would be "dsp::DSP"
|
||||||
|
// and the "function" field would be 0x000E00C0
|
||||||
|
struct InterceptedService {
|
||||||
|
std::string serviceName; // Name of the service whose function
|
||||||
|
u32 function; // Header of the function to intercept
|
||||||
|
|
||||||
|
InterceptedService(const std::string& name, u32 header) : serviceName(name), function(header) {}
|
||||||
|
bool operator==(const InterceptedService& other) const { return serviceName == other.serviceName && function == other.function; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define hashing function for InterceptedService to use it with unordered_map
|
||||||
|
namespace std {
|
||||||
|
template <>
|
||||||
|
struct hash<InterceptedService> {
|
||||||
|
usize operator()(const InterceptedService& s) const noexcept {
|
||||||
|
const usize hash1 = std::hash<std::string>{}(s.serviceName);
|
||||||
|
const usize hash2 = std::hash<u32>{}(s.function);
|
||||||
|
return hash1 ^ (hash2 << 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace std
|
|
@ -2,9 +2,12 @@
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <span>
|
#include <span>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
#include "kernel_types.hpp"
|
#include "kernel_types.hpp"
|
||||||
#include "logger.hpp"
|
#include "logger.hpp"
|
||||||
|
#include "lua_manager.hpp"
|
||||||
#include "memory.hpp"
|
#include "memory.hpp"
|
||||||
#include "services/ac.hpp"
|
#include "services/ac.hpp"
|
||||||
#include "services/act.hpp"
|
#include "services/act.hpp"
|
||||||
|
@ -34,6 +37,7 @@
|
||||||
#include "services/ns.hpp"
|
#include "services/ns.hpp"
|
||||||
#include "services/nwm_uds.hpp"
|
#include "services/nwm_uds.hpp"
|
||||||
#include "services/ptm.hpp"
|
#include "services/ptm.hpp"
|
||||||
|
#include "services/service_intercept.hpp"
|
||||||
#include "services/soc.hpp"
|
#include "services/soc.hpp"
|
||||||
#include "services/ssl.hpp"
|
#include "services/ssl.hpp"
|
||||||
#include "services/y2r.hpp"
|
#include "services/y2r.hpp"
|
||||||
|
@ -86,6 +90,19 @@ class ServiceManager {
|
||||||
|
|
||||||
MCU::HWCService mcu_hwc;
|
MCU::HWCService mcu_hwc;
|
||||||
|
|
||||||
|
// We allow Lua scripts to intercept service calls and allow their own code to be ran on SyncRequests
|
||||||
|
// For example, if we want to intercept dsp::DSP ReadPipe (Header: 0x000E00C0), the "serviceName" field would be "dsp::DSP"
|
||||||
|
// and the "function" field would be 0x000E00C0
|
||||||
|
LuaManager& lua;
|
||||||
|
std::unordered_set<InterceptedService> interceptedServices = {};
|
||||||
|
// Calling std::unordered_set<T>::size() compiles to a fairly non-trivial function call on Clang, so we store this
|
||||||
|
// separately and check it on service calls, for performance reasons
|
||||||
|
bool haveServiceIntercepts = false;
|
||||||
|
|
||||||
|
// Checks for whether a service call is intercepted by Lua and handles it. Returns true if Lua told us not to handle the function,
|
||||||
|
// or false if we should handle it as normal
|
||||||
|
bool checkForIntercept(u32 messagePointer, Handle handle);
|
||||||
|
|
||||||
// "srv:" commands
|
// "srv:" commands
|
||||||
void enableNotification(u32 messagePointer);
|
void enableNotification(u32 messagePointer);
|
||||||
void getServiceHandle(u32 messagePointer);
|
void getServiceHandle(u32 messagePointer);
|
||||||
|
@ -96,7 +113,7 @@ class ServiceManager {
|
||||||
void unsubscribe(u32 messagePointer);
|
void unsubscribe(u32 messagePointer);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ServiceManager(std::span<u32, 16> regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel, const EmulatorConfig& config);
|
ServiceManager(std::span<u32, 16> regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel, const EmulatorConfig& config, LuaManager& lua);
|
||||||
void reset();
|
void reset();
|
||||||
void initializeFS() { fs.initializeFilesystem(); }
|
void initializeFS() { fs.initializeFilesystem(); }
|
||||||
void handleSyncRequest(u32 messagePointer);
|
void handleSyncRequest(u32 messagePointer);
|
||||||
|
@ -116,4 +133,14 @@ class ServiceManager {
|
||||||
DSPService& getDSP() { return dsp; }
|
DSPService& getDSP() { return dsp; }
|
||||||
Y2RService& getY2R() { return y2r; }
|
Y2RService& getY2R() { return y2r; }
|
||||||
IRUserService& getIRUser() { return ir_user; }
|
IRUserService& getIRUser() { return ir_user; }
|
||||||
|
|
||||||
|
void addServiceIntercept(const std::string& service, u32 function) {
|
||||||
|
interceptedServices.insert(InterceptedService(service, function));
|
||||||
|
haveServiceIntercepts = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearServiceIntercepts() {
|
||||||
|
interceptedServices.clear();
|
||||||
|
haveServiceIntercepts = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#include "arm_defs.hpp"
|
#include "arm_defs.hpp"
|
||||||
#include "kernel.hpp"
|
#include "kernel.hpp"
|
||||||
|
|
||||||
|
@ -40,6 +41,7 @@ void Kernel::setupIdleThread() {
|
||||||
std::memcpy(&mem.getFCRAM()[fcramIndex], idleThreadCode, sizeof(idleThreadCode));
|
std::memcpy(&mem.getFCRAM()[fcramIndex], idleThreadCode, sizeof(idleThreadCode));
|
||||||
|
|
||||||
t.entrypoint = codeAddress;
|
t.entrypoint = codeAddress;
|
||||||
|
t.initialSP = 0;
|
||||||
t.tlsBase = 0;
|
t.tlsBase = 0;
|
||||||
t.gprs[13] = 0; // Set SP & LR to 0 just in case. The idle thread should never access memory, but let's be safe
|
t.gprs[13] = 0; // Set SP & LR to 0 just in case. The idle thread should never access memory, but let's be safe
|
||||||
t.gprs[14] = 0;
|
t.gprs[14] = 0;
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
#include "kernel_types.hpp"
|
#include "kernel_types.hpp"
|
||||||
#include "cpu.hpp"
|
#include "cpu.hpp"
|
||||||
|
|
||||||
Kernel::Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config)
|
Kernel::Kernel(CPU& cpu, Memory& mem, GPU& gpu, const EmulatorConfig& config, LuaManager& lua)
|
||||||
: cpu(cpu), regs(cpu.regs()), mem(mem), handleCounter(0), serviceManager(regs, mem, gpu, currentProcess, *this, config) {
|
: cpu(cpu), regs(cpu.regs()), mem(mem), handleCounter(0), serviceManager(regs, mem, gpu, currentProcess, *this, config, lua) {
|
||||||
objects.reserve(512); // Make room for a few objects to avoid further memory allocs later
|
objects.reserve(512); // Make room for a few objects to avoid further memory allocs later
|
||||||
mutexHandles.reserve(8);
|
mutexHandles.reserve(8);
|
||||||
portHandles.reserve(32);
|
portHandles.reserve(32);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#include <algorithm>
|
||||||
#include <bit>
|
#include <bit>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
@ -697,3 +698,18 @@ bool Kernel::shouldWaitOnObject(KernelObject* object) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<Thread> Kernel::getMainProcessThreads() {
|
||||||
|
// Sort the thread indices so that they appear nicer in the debugger
|
||||||
|
auto indices = threadIndices;
|
||||||
|
std::sort(indices.begin(), indices.end());
|
||||||
|
|
||||||
|
std::vector<Thread> ret;
|
||||||
|
ret.reserve(indices.size());
|
||||||
|
|
||||||
|
for (const auto& index : indices) {
|
||||||
|
ret.push_back(threads[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
|
@ -5,8 +5,10 @@
|
||||||
#include "ipc.hpp"
|
#include "ipc.hpp"
|
||||||
#include "kernel.hpp"
|
#include "kernel.hpp"
|
||||||
|
|
||||||
ServiceManager::ServiceManager(std::span<u32, 16> regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel, const EmulatorConfig& config)
|
ServiceManager::ServiceManager(
|
||||||
: regs(regs), mem(mem), kernel(kernel), ac(mem), am(mem), boss(mem), act(mem), apt(mem, kernel), cam(mem, kernel), cecd(mem, kernel),
|
std::span<u32, 16> regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel, const EmulatorConfig& config, LuaManager& lua
|
||||||
|
)
|
||||||
|
: regs(regs), mem(mem), kernel(kernel), lua(lua), ac(mem), am(mem), boss(mem), act(mem), apt(mem, kernel), cam(mem, kernel), cecd(mem, kernel),
|
||||||
cfg(mem, config), csnd(mem, kernel), dlp_srvr(mem), dsp(mem, kernel, config), hid(mem, kernel), http(mem), ir_user(mem, hid, config, kernel),
|
cfg(mem, config), csnd(mem, kernel), dlp_srvr(mem), dsp(mem, kernel, config), hid(mem, kernel), http(mem), ir_user(mem, hid, config, kernel),
|
||||||
frd(mem), fs(mem, kernel, config), gsp_gpu(mem, gpu, kernel, currentPID), gsp_lcd(mem), ldr(mem, kernel), mcu_hwc(mem, config),
|
frd(mem), fs(mem, kernel, config), gsp_gpu(mem, gpu, kernel, currentPID), gsp_lcd(mem), ldr(mem, kernel), mcu_hwc(mem, config),
|
||||||
mic(mem, kernel), nfc(mem, kernel), nim(mem), ndm(mem), news_u(mem), ns(mem), nwm_uds(mem, kernel), ptm(mem, config), soc(mem), ssl(mem),
|
mic(mem, kernel), nfc(mem, kernel), nim(mem), ndm(mem), news_u(mem), ns(mem), nwm_uds(mem, kernel), ptm(mem, config), soc(mem), ssl(mem),
|
||||||
|
@ -214,6 +216,12 @@ void ServiceManager::publishToSubscriber(u32 messagePointer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ServiceManager::sendCommandToService(u32 messagePointer, Handle handle) {
|
void ServiceManager::sendCommandToService(u32 messagePointer, Handle handle) {
|
||||||
|
if (haveServiceIntercepts) [[unlikely]] {
|
||||||
|
if (checkForIntercept(messagePointer, handle)) [[unlikely]] {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (handle) {
|
switch (handle) {
|
||||||
// Breaking alphabetical order a bit to place the ones I think are most common at the top
|
// Breaking alphabetical order a bit to place the ones I think are most common at the top
|
||||||
case KernelHandles::GPU: [[likely]] gsp_gpu.handleSyncRequest(messagePointer); break;
|
case KernelHandles::GPU: [[likely]] gsp_gpu.handleSyncRequest(messagePointer); break;
|
||||||
|
@ -258,3 +266,24 @@ void ServiceManager::sendCommandToService(u32 messagePointer, Handle handle) {
|
||||||
default: Helpers::panic("Sent IPC message to unknown service %08X\n Command: %08X", handle, mem.read32(messagePointer));
|
default: Helpers::panic("Sent IPC message to unknown service %08X\n Command: %08X", handle, mem.read32(messagePointer));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ServiceManager::checkForIntercept(u32 messagePointer, Handle handle) {
|
||||||
|
// Check if there's a Lua handler for this function and call it
|
||||||
|
const u32 function = mem.read32(messagePointer);
|
||||||
|
|
||||||
|
for (auto [serviceName, serviceHandle] : serviceMap) {
|
||||||
|
if (serviceHandle == handle) {
|
||||||
|
auto intercept = InterceptedService(std::string(serviceName), function);
|
||||||
|
if (interceptedServices.contains(intercept)) {
|
||||||
|
// If the Lua handler returns true, it means the service is handled entirely
|
||||||
|
// From Lua, and we shouldn't do anything else here.
|
||||||
|
return lua.signalInterceptedService(intercept.serviceName, function, messagePointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lua did not intercept the service, so emulate it normally
|
||||||
|
return false;
|
||||||
|
}
|
|
@ -19,8 +19,9 @@ __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Emulator::Emulator()
|
Emulator::Emulator()
|
||||||
: config(getConfigPath()), kernel(cpu, memory, gpu, config), cpu(memory, kernel, *this), gpu(memory, config), memory(cpu.getTicksRef(), config),
|
: config(getConfigPath()), kernel(cpu, memory, gpu, config, lua), cpu(memory, kernel, *this), gpu(memory, config),
|
||||||
cheats(memory, kernel.getServiceManager().getHID()), audioDevice(config.audioDeviceConfig), lua(*this), running(false)
|
memory(cpu.getTicksRef(), config), cheats(memory, kernel.getServiceManager().getHID()), audioDevice(config.audioDeviceConfig), lua(*this),
|
||||||
|
running(false)
|
||||||
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
||||||
,
|
,
|
||||||
httpServer(this)
|
httpServer(this)
|
||||||
|
@ -271,6 +272,11 @@ bool Emulator::loadROM(const std::filesystem::path& path) {
|
||||||
romType = ROMType::None;
|
romType = ROMType::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
// Update the main thread entrypoint and SP so that the thread debugger can display them.
|
||||||
|
kernel.setMainThreadEntrypointAndSP(cpu.getReg(15), cpu.getReg(13));
|
||||||
|
}
|
||||||
|
|
||||||
resume(); // Start the emulator
|
resume(); // Start the emulator
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
87
src/lua.cpp
87
src/lua.cpp
|
@ -95,12 +95,40 @@ void LuaManager::loadString(const std::string& code) {
|
||||||
|
|
||||||
void LuaManager::signalEventInternal(LuaEvent e) {
|
void LuaManager::signalEventInternal(LuaEvent e) {
|
||||||
lua_getglobal(L, "eventHandler"); // We want to call the event handler
|
lua_getglobal(L, "eventHandler"); // We want to call the event handler
|
||||||
lua_pushnumber(L, static_cast<int>(e)); // Push event type
|
lua_pushinteger(L, static_cast<int>(e)); // Push event type
|
||||||
|
|
||||||
// Call the function with 1 argument and 0 outputs, without an error handler
|
// Call the function with 1 argument and 0 outputs, without an error handler
|
||||||
lua_pcall(L, 1, 0, 0);
|
lua_pcall(L, 1, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calls the "interceptService" function, if it exists, when a service call is intercepted
|
||||||
|
// It passes the service name, the function header, and a pointer to the call's TLS buffer as parameters
|
||||||
|
// interceptService is expected to return a bool, which indicates whether the C++ code should proceed to handle the service call
|
||||||
|
// or if the Lua code handles it entirely.
|
||||||
|
// If the bool is true, the Lua code handles the service call entirely and the C++ code doesn't do anything extra
|
||||||
|
// Otherwise, then the C++ code calls its service call handling code as usual.
|
||||||
|
bool LuaManager::signalInterceptedService(const std::string& service, u32 function, u32 messagePointer) {
|
||||||
|
lua_getglobal(L, "interceptService");
|
||||||
|
lua_pushstring(L, service.c_str()); // Push service name
|
||||||
|
lua_pushinteger(L, function); // Push function header
|
||||||
|
lua_pushinteger(L, messagePointer); // Push pointer to TLS buffer
|
||||||
|
|
||||||
|
// Call the function with 3 arguments and 1 output, without an error handler
|
||||||
|
const int status = lua_pcall(L, 3, 1, 0);
|
||||||
|
|
||||||
|
if (status != LUA_OK) {
|
||||||
|
const char* err = lua_tostring(L, -1);
|
||||||
|
fprintf(stderr, "Lua: Error in interceptService: %s\n", err);
|
||||||
|
lua_pop(L, 1); // Pop error message from stack
|
||||||
|
return false; // Have the C++ handle the service call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the value interceptService returned
|
||||||
|
const bool ret = lua_toboolean(L, -1);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
void LuaManager::reset() {
|
void LuaManager::reset() {
|
||||||
// Reset scripts
|
// Reset scripts
|
||||||
haveScript = false;
|
haveScript = false;
|
||||||
|
@ -113,13 +141,13 @@ Emulator* LuaManager::g_emulator = nullptr;
|
||||||
|
|
||||||
#define MAKE_MEMORY_FUNCTIONS(size) \
|
#define MAKE_MEMORY_FUNCTIONS(size) \
|
||||||
static int read##size##Thunk(lua_State* L) { \
|
static int read##size##Thunk(lua_State* L) { \
|
||||||
const u32 vaddr = (u32)lua_tonumber(L, 1); \
|
const u32 vaddr = (u32)lua_tointeger(L, 1); \
|
||||||
lua_pushnumber(L, LuaManager::g_emulator->getMemory().read##size(vaddr)); \
|
lua_pushinteger(L, LuaManager::g_emulator->getMemory().read##size(vaddr)); \
|
||||||
return 1; \
|
return 1; \
|
||||||
} \
|
} \
|
||||||
static int write##size##Thunk(lua_State* L) { \
|
static int write##size##Thunk(lua_State* L) { \
|
||||||
const u32 vaddr = (u32)lua_tonumber(L, 1); \
|
const u32 vaddr = (u32)lua_tointeger(L, 1); \
|
||||||
const u##size value = (u##size)lua_tonumber(L, 2); \
|
const u##size value = (u##size)lua_tointeger(L, 2); \
|
||||||
LuaManager::g_emulator->getMemory().write##size(vaddr, value); \
|
LuaManager::g_emulator->getMemory().write##size(vaddr, value); \
|
||||||
return 0; \
|
return 0; \
|
||||||
}
|
}
|
||||||
|
@ -131,26 +159,26 @@ MAKE_MEMORY_FUNCTIONS(64)
|
||||||
#undef MAKE_MEMORY_FUNCTIONS
|
#undef MAKE_MEMORY_FUNCTIONS
|
||||||
|
|
||||||
static int readFloatThunk(lua_State* L) {
|
static int readFloatThunk(lua_State* L) {
|
||||||
const u32 vaddr = (u32)lua_tonumber(L, 1);
|
const u32 vaddr = (u32)lua_tointeger(L, 1);
|
||||||
lua_pushnumber(L, (lua_Number)Helpers::bit_cast<float, u32>(LuaManager::g_emulator->getMemory().read32(vaddr)));
|
lua_pushnumber(L, (lua_Number)Helpers::bit_cast<float, u32>(LuaManager::g_emulator->getMemory().read32(vaddr)));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int writeFloatThunk(lua_State* L) {
|
static int writeFloatThunk(lua_State* L) {
|
||||||
const u32 vaddr = (u32)lua_tonumber(L, 1);
|
const u32 vaddr = (u32)lua_tointeger(L, 1);
|
||||||
const float value = (float)lua_tonumber(L, 2);
|
const float value = (float)lua_tonumber(L, 2);
|
||||||
LuaManager::g_emulator->getMemory().write32(vaddr, Helpers::bit_cast<u32, float>(value));
|
LuaManager::g_emulator->getMemory().write32(vaddr, Helpers::bit_cast<u32, float>(value));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int readDoubleThunk(lua_State* L) {
|
static int readDoubleThunk(lua_State* L) {
|
||||||
const u32 vaddr = (u32)lua_tonumber(L, 1);
|
const u32 vaddr = (u32)lua_tointeger(L, 1);
|
||||||
lua_pushnumber(L, (lua_Number)Helpers::bit_cast<double, u64>(LuaManager::g_emulator->getMemory().read64(vaddr)));
|
lua_pushnumber(L, (lua_Number)Helpers::bit_cast<double, u64>(LuaManager::g_emulator->getMemory().read64(vaddr)));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int writeDoubleThunk(lua_State* L) {
|
static int writeDoubleThunk(lua_State* L) {
|
||||||
const u32 vaddr = (u32)lua_tonumber(L, 1);
|
const u32 vaddr = (u32)lua_tointeger(L, 1);
|
||||||
const double value = (double)lua_tonumber(L, 2);
|
const double value = (double)lua_tonumber(L, 2);
|
||||||
LuaManager::g_emulator->getMemory().write64(vaddr, Helpers::bit_cast<u64, double>(value));
|
LuaManager::g_emulator->getMemory().write64(vaddr, Helpers::bit_cast<u64, double>(value));
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -192,13 +220,14 @@ static int resetThunk(lua_State* L) {
|
||||||
|
|
||||||
static int loadROMThunk(lua_State* L) {
|
static int loadROMThunk(lua_State* L) {
|
||||||
// Path argument is invalid, report that loading failed and exit
|
// Path argument is invalid, report that loading failed and exit
|
||||||
if (lua_type(L, -1) != LUA_TSTRING) {
|
if (lua_type(L, 1) != LUA_TSTRING) {
|
||||||
lua_pushboolean(L, 0);
|
lua_pushboolean(L, 0);
|
||||||
|
lua_error(L);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t pathLength;
|
usize pathLength;
|
||||||
const char* const str = lua_tolstring(L, -1, &pathLength);
|
const char* const str = lua_tolstring(L, 1, &pathLength);
|
||||||
|
|
||||||
const auto path = std::filesystem::path(std::string(str, pathLength));
|
const auto path = std::filesystem::path(std::string(str, pathLength));
|
||||||
// Load ROM and reply if it succeeded or not
|
// Load ROM and reply if it succeeded or not
|
||||||
|
@ -206,6 +235,34 @@ static int loadROMThunk(lua_State* L) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int addServiceInterceptThunk(lua_State* L) {
|
||||||
|
// Service name argument is invalid, report that loading failed and exit
|
||||||
|
if (lua_type(L, 1) != LUA_TSTRING) {
|
||||||
|
lua_pushboolean(L, 0);
|
||||||
|
lua_error(L);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lua_type(L, 2) != LUA_TNUMBER) {
|
||||||
|
lua_pushboolean(L, 0);
|
||||||
|
lua_error(L);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the name of the service we want to intercept, as well as the header of the function to intercept
|
||||||
|
usize nameLength;
|
||||||
|
const char* const str = lua_tolstring(L, 1, &nameLength);
|
||||||
|
const u32 function = (u32)lua_tointeger(L, 2);
|
||||||
|
const auto serviceName = std::string(str, nameLength);
|
||||||
|
LuaManager::g_emulator->getServiceManager().addServiceIntercept(serviceName, function);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int clearServiceInterceptsThunk(lua_State* L) {
|
||||||
|
LuaManager::g_emulator->getServiceManager().clearServiceIntercepts();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int getButtonsThunk(lua_State* L) {
|
static int getButtonsThunk(lua_State* L) {
|
||||||
auto buttons = LuaManager::g_emulator->getServiceManager().getHID().getOldButtons();
|
auto buttons = LuaManager::g_emulator->getServiceManager().getHID().getOldButtons();
|
||||||
lua_pushinteger(L, static_cast<lua_Integer>(buttons));
|
lua_pushinteger(L, static_cast<lua_Integer>(buttons));
|
||||||
|
@ -292,6 +349,8 @@ static constexpr luaL_Reg functions[] = {
|
||||||
{ "__getButton", getButtonThunk },
|
{ "__getButton", getButtonThunk },
|
||||||
{ "__disassembleARM", disassembleARMThunk },
|
{ "__disassembleARM", disassembleARMThunk },
|
||||||
{ "__disassembleTeak", disassembleTeakThunk },
|
{ "__disassembleTeak", disassembleTeakThunk },
|
||||||
|
{"__addServiceIntercept", addServiceInterceptThunk },
|
||||||
|
{"__clearServiceIntercepts", clearServiceInterceptsThunk },
|
||||||
{ nullptr, nullptr },
|
{ nullptr, nullptr },
|
||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
@ -332,6 +391,8 @@ void LuaManager::initializeThunks() {
|
||||||
|
|
||||||
disassembleARM = function(pc, instruction) return GLOBALS.__disassembleARM(pc, instruction) end,
|
disassembleARM = function(pc, instruction) return GLOBALS.__disassembleARM(pc, instruction) end,
|
||||||
disassembleTeak = function(opcode, exp) return GLOBALS.__disassembleTeak(opcode, exp or 0) end,
|
disassembleTeak = function(opcode, exp) return GLOBALS.__disassembleTeak(opcode, exp or 0) end,
|
||||||
|
addServiceIntercept = function(service, func) return GLOBALS.__addServiceIntercept(service, func) end,
|
||||||
|
clearServiceIntercepts = function() return GLOBALS.__clearServiceIntercepts() end,
|
||||||
|
|
||||||
Frame = __Frame,
|
Frame = __Frame,
|
||||||
ButtonA = __ButtonA,
|
ButtonA = __ButtonA,
|
||||||
|
@ -340,6 +401,8 @@ void LuaManager::initializeThunks() {
|
||||||
ButtonY = __ButtonY,
|
ButtonY = __ButtonY,
|
||||||
ButtonL = __ButtonL,
|
ButtonL = __ButtonL,
|
||||||
ButtonR = __ButtonR,
|
ButtonR = __ButtonR,
|
||||||
|
ButtonZL = __ButtonZL,
|
||||||
|
ButtonZR = __ButtonZR,
|
||||||
ButtonUp = __ButtonUp,
|
ButtonUp = __ButtonUp,
|
||||||
ButtonDown = __ButtonDown,
|
ButtonDown = __ButtonDown,
|
||||||
ButtonLeft = __ButtonLeft,
|
ButtonLeft = __ButtonLeft,
|
||||||
|
|
|
@ -66,6 +66,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
|
||||||
auto cheatsEditorAction = toolsMenu->addAction(tr("Open Cheats Editor"));
|
auto cheatsEditorAction = toolsMenu->addAction(tr("Open Cheats Editor"));
|
||||||
auto patchWindowAction = toolsMenu->addAction(tr("Open Patch Window"));
|
auto patchWindowAction = toolsMenu->addAction(tr("Open Patch Window"));
|
||||||
auto shaderEditorAction = toolsMenu->addAction(tr("Open Shader Editor"));
|
auto shaderEditorAction = toolsMenu->addAction(tr("Open Shader Editor"));
|
||||||
|
auto threadDebuggerAction = toolsMenu->addAction(tr("Open Thread Debugger"));
|
||||||
auto dumpDspFirmware = toolsMenu->addAction(tr("Dump loaded DSP firmware"));
|
auto dumpDspFirmware = toolsMenu->addAction(tr("Dump loaded DSP firmware"));
|
||||||
|
|
||||||
connect(dumpRomFSAction, &QAction::triggered, this, &MainWindow::dumpRomFS);
|
connect(dumpRomFSAction, &QAction::triggered, this, &MainWindow::dumpRomFS);
|
||||||
|
@ -73,8 +74,11 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
|
||||||
connect(shaderEditorAction, &QAction::triggered, this, [this]() { shaderEditor->show(); });
|
connect(shaderEditorAction, &QAction::triggered, this, [this]() { shaderEditor->show(); });
|
||||||
connect(cheatsEditorAction, &QAction::triggered, this, [this]() { cheatsEditor->show(); });
|
connect(cheatsEditorAction, &QAction::triggered, this, [this]() { cheatsEditor->show(); });
|
||||||
connect(patchWindowAction, &QAction::triggered, this, [this]() { patchWindow->show(); });
|
connect(patchWindowAction, &QAction::triggered, this, [this]() { patchWindow->show(); });
|
||||||
|
connect(threadDebuggerAction, &QAction::triggered, this, [this]() { threadDebugger->show(); });
|
||||||
connect(dumpDspFirmware, &QAction::triggered, this, &MainWindow::dumpDspFirmware);
|
connect(dumpDspFirmware, &QAction::triggered, this, &MainWindow::dumpDspFirmware);
|
||||||
|
|
||||||
|
connect(this, &MainWindow::emulatorPaused, this, [this]() { threadDebugger->update(); }, Qt::BlockingQueuedConnection);
|
||||||
|
|
||||||
auto aboutAction = aboutMenu->addAction(tr("About Panda3DS"));
|
auto aboutAction = aboutMenu->addAction(tr("About Panda3DS"));
|
||||||
aboutAction->setMenuRole(QAction::AboutRole);
|
aboutAction->setMenuRole(QAction::AboutRole);
|
||||||
connect(aboutAction, &QAction::triggered, this, &MainWindow::showAboutMenu);
|
connect(aboutAction, &QAction::triggered, this, &MainWindow::showAboutMenu);
|
||||||
|
@ -89,6 +93,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
|
||||||
patchWindow = new PatchWindow(this);
|
patchWindow = new PatchWindow(this);
|
||||||
luaEditor = new TextEditorWindow(this, "script.lua", "");
|
luaEditor = new TextEditorWindow(this, "script.lua", "");
|
||||||
shaderEditor = new ShaderEditorWindow(this, "shader.glsl", "");
|
shaderEditor = new ShaderEditorWindow(this, "shader.glsl", "");
|
||||||
|
threadDebugger = new ThreadDebugger(emu, this);
|
||||||
|
|
||||||
shaderEditor->setEnable(emu->getRenderer()->supportsShaderReload());
|
shaderEditor->setEnable(emu->getRenderer()->supportsShaderReload());
|
||||||
if (shaderEditor->supported) {
|
if (shaderEditor->supported) {
|
||||||
|
@ -381,9 +386,19 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) {
|
||||||
delete message.cheat.c;
|
delete message.cheat.c;
|
||||||
} break;
|
} 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::Pause:
|
||||||
|
emu->pause();
|
||||||
|
emit emulatorPaused();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MessageType::TogglePause:
|
||||||
|
emu->togglePause();
|
||||||
|
if (!emu->running) {
|
||||||
|
emit emulatorPaused();
|
||||||
|
};
|
||||||
|
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;
|
||||||
|
|
76
src/panda_qt/thread_debugger.cpp
Normal file
76
src/panda_qt/thread_debugger.cpp
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
#include "panda_qt/thread_debugger.hpp"
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
static QString threadStatusToQString(ThreadStatus status) {
|
||||||
|
switch (status) {
|
||||||
|
case ThreadStatus::Running: return QObject::tr("Running");
|
||||||
|
case ThreadStatus::Ready: return QObject::tr("Ready");
|
||||||
|
case ThreadStatus::WaitArbiter: return QObject::tr("Waiting on arbiter");
|
||||||
|
case ThreadStatus::WaitSleep: return QObject::tr("Sleeping");
|
||||||
|
case ThreadStatus::WaitSync1: return QObject::tr("WaitSync (1)");
|
||||||
|
case ThreadStatus::WaitSyncAny: return QObject::tr("WaitSync (Any)");
|
||||||
|
case ThreadStatus::WaitSyncAll: return QObject::tr("WaitSync (All)");
|
||||||
|
case ThreadStatus::WaitIPC: return QObject::tr("Waiting for IPC");
|
||||||
|
case ThreadStatus::Dormant: return QObject::tr("Dormant");
|
||||||
|
case ThreadStatus::Dead: return QObject::tr("Dead");
|
||||||
|
default: return QObject::tr("Unknown thread status");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadDebugger::ThreadDebugger(Emulator* emu, QWidget* parent) : emu(emu), QWidget(parent, Qt::Window) {
|
||||||
|
setWindowTitle(tr("Thread Debugger"));
|
||||||
|
resize(700, 600);
|
||||||
|
|
||||||
|
mainLayout = new QVBoxLayout(this);
|
||||||
|
threadTable = new QTableWidget(this);
|
||||||
|
threadTable->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
||||||
|
|
||||||
|
mainLayout->addWidget(threadTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThreadDebugger::update() {
|
||||||
|
threadTable->clear();
|
||||||
|
|
||||||
|
threadTable->setSelectionMode(QAbstractItemView::NoSelection);
|
||||||
|
threadTable->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||||
|
threadTable->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||||
|
threadTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
|
|
||||||
|
threadTable->setColumnCount(5);
|
||||||
|
threadTable->verticalHeader()->setVisible(false);
|
||||||
|
threadTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
||||||
|
threadTable->setHorizontalHeaderLabels(QStringList({"ID", "Status", "PC", "Entrypoint", "Stack Top"}));
|
||||||
|
|
||||||
|
const auto threads = emu->getKernel().getMainProcessThreads();
|
||||||
|
const usize count = threads.size();
|
||||||
|
threadTable->setRowCount(count);
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
const auto& thread = threads[i];
|
||||||
|
setListItem(i, 0, fmt::format("{}", thread.index));
|
||||||
|
setListItem(i, 1, threadStatusToQString(thread.status));
|
||||||
|
setListItem(i, 2, fmt::format("{:08X}", thread.gprs[15]));
|
||||||
|
setListItem(i, 3, fmt::format("{:08X}", thread.entrypoint));
|
||||||
|
setListItem(i, 4, fmt::format("{:08X}", thread.initialSP));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThreadDebugger::setListItem(int row, int column, const std::string& str) { setListItem(row, column, QString::fromStdString(str)); }
|
||||||
|
|
||||||
|
void ThreadDebugger::setListItem(int row, int column, const QString& str) {
|
||||||
|
QTableWidgetItem* item = new QTableWidgetItem();
|
||||||
|
QWidget* widget = new QWidget(this);
|
||||||
|
QVBoxLayout* layout = new QVBoxLayout(widget);
|
||||||
|
QLabel* label = new QLabel(widget);
|
||||||
|
|
||||||
|
layout->setAlignment(Qt::AlignVCenter);
|
||||||
|
layout->setContentsMargins(5, 0, 5, 0);
|
||||||
|
|
||||||
|
label->setText(str);
|
||||||
|
layout->addWidget(label);
|
||||||
|
widget->setLayout(layout);
|
||||||
|
|
||||||
|
threadTable->setItem(row, column, item);
|
||||||
|
threadTable->setCellWidget(row, column, widget);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue