Compare commits
19 Commits
dfe1f30a64
...
4950de5d32
Author | SHA1 | Date |
---|---|---|
emoose | 4950de5d32 | |
Gliniak | 11f14e8488 | |
Adrian | 3d79874828 | |
emoose | 5917554324 | |
emoose | 2e9c9f403d | |
emoose | 220324b463 | |
emoose | c17261abf7 | |
emoose | e260d0a110 | |
emoose | a4187736ad | |
emoose | ea9cf0c8f9 | |
emoose | 0acc46e52e | |
emoose | 2ee35a2054 | |
emoose | c9778a4032 | |
emoose | 26cfa69497 | |
emoose | 0469ae2aee | |
emoose | b7ab7e966f | |
emoose | bfa6eee936 | |
emoose | 2204259781 | |
emoose | 733fe04426 |
|
@ -295,6 +295,7 @@ workspace("xenia")
|
||||||
include("src/xenia/base")
|
include("src/xenia/base")
|
||||||
include("src/xenia/cpu")
|
include("src/xenia/cpu")
|
||||||
include("src/xenia/cpu/backend/x64")
|
include("src/xenia/cpu/backend/x64")
|
||||||
|
include("src/xenia/debug/gdb")
|
||||||
include("src/xenia/debug/ui")
|
include("src/xenia/debug/ui")
|
||||||
include("src/xenia/gpu")
|
include("src/xenia/gpu")
|
||||||
include("src/xenia/gpu/null")
|
include("src/xenia/gpu/null")
|
||||||
|
|
|
@ -92,6 +92,7 @@ project("xenia-app")
|
||||||
"xenia-app-discord",
|
"xenia-app-discord",
|
||||||
"xenia-apu-sdl",
|
"xenia-apu-sdl",
|
||||||
-- TODO(Triang3l): CPU debugger on Android.
|
-- TODO(Triang3l): CPU debugger on Android.
|
||||||
|
"xenia-debug-gdb",
|
||||||
"xenia-debug-ui",
|
"xenia-debug-ui",
|
||||||
"xenia-helper-sdl",
|
"xenia-helper-sdl",
|
||||||
"xenia-hid-sdl",
|
"xenia-hid-sdl",
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#include "xenia/base/profiling.h"
|
#include "xenia/base/profiling.h"
|
||||||
#include "xenia/base/threading.h"
|
#include "xenia/base/threading.h"
|
||||||
#include "xenia/config.h"
|
#include "xenia/config.h"
|
||||||
|
#include "xenia/debug/gdb/gdbstub.h"
|
||||||
#include "xenia/debug/ui/debug_window.h"
|
#include "xenia/debug/ui/debug_window.h"
|
||||||
#include "xenia/emulator.h"
|
#include "xenia/emulator.h"
|
||||||
#include "xenia/kernel/xam/xam_module.h"
|
#include "xenia/kernel/xam/xam_module.h"
|
||||||
|
@ -100,6 +101,10 @@ DEFINE_transient_bool(portable, true,
|
||||||
"General");
|
"General");
|
||||||
|
|
||||||
DECLARE_bool(debug);
|
DECLARE_bool(debug);
|
||||||
|
DEFINE_int32(
|
||||||
|
gdbport, 0,
|
||||||
|
"Port for GDBStub debugger to listen on, requires --debug (0 = disable)",
|
||||||
|
"General");
|
||||||
|
|
||||||
DEFINE_bool(discord, true, "Enable Discord rich presence", "General");
|
DEFINE_bool(discord, true, "Enable Discord rich presence", "General");
|
||||||
|
|
||||||
|
@ -228,6 +233,7 @@ class EmulatorApp final : public xe::ui::WindowedApp {
|
||||||
|
|
||||||
// Created on demand, used by the emulator.
|
// Created on demand, used by the emulator.
|
||||||
std::unique_ptr<xe::debug::ui::DebugWindow> debug_window_;
|
std::unique_ptr<xe::debug::ui::DebugWindow> debug_window_;
|
||||||
|
std::unique_ptr<xe::debug::gdb::GDBStub> debug_gdbstub_;
|
||||||
|
|
||||||
// Refreshing the emulator - placed after its dependencies.
|
// Refreshing the emulator - placed after its dependencies.
|
||||||
std::atomic<bool> emulator_thread_quit_requested_;
|
std::atomic<bool> emulator_thread_quit_requested_;
|
||||||
|
@ -566,20 +572,33 @@ void EmulatorApp::EmulatorThread() {
|
||||||
// Set a debug handler.
|
// Set a debug handler.
|
||||||
// This will respond to debugging requests so we can open the debug UI.
|
// This will respond to debugging requests so we can open the debug UI.
|
||||||
if (cvars::debug) {
|
if (cvars::debug) {
|
||||||
emulator_->processor()->set_debug_listener_request_handler(
|
if (cvars::gdbport > 0) {
|
||||||
[this](xe::cpu::Processor* processor) {
|
emulator_->processor()->set_debug_listener_request_handler(
|
||||||
if (debug_window_) {
|
[this](xe::cpu::Processor* processor) {
|
||||||
return debug_window_.get();
|
if (debug_gdbstub_) {
|
||||||
}
|
return debug_gdbstub_.get();
|
||||||
app_context().CallInUIThreadSynchronous([this]() {
|
}
|
||||||
debug_window_ = xe::debug::ui::DebugWindow::Create(emulator_.get(),
|
debug_gdbstub_ = xe::debug::gdb::GDBStub::Create(emulator_.get(),
|
||||||
app_context());
|
cvars::gdbport);
|
||||||
debug_window_->window()->AddListener(
|
return debug_gdbstub_.get();
|
||||||
&debug_window_closed_listener_);
|
|
||||||
});
|
});
|
||||||
// If failed to enqueue the UI thread call, this will just be null.
|
emulator_->processor()->ShowDebugger();
|
||||||
return debug_window_.get();
|
} else {
|
||||||
});
|
emulator_->processor()->set_debug_listener_request_handler(
|
||||||
|
[this](xe::cpu::Processor* processor) {
|
||||||
|
if (debug_window_) {
|
||||||
|
return debug_window_.get();
|
||||||
|
}
|
||||||
|
app_context().CallInUIThreadSynchronous([this]() {
|
||||||
|
debug_window_ = xe::debug::ui::DebugWindow::Create(
|
||||||
|
emulator_.get(), app_context());
|
||||||
|
debug_window_->window()->AddListener(
|
||||||
|
&debug_window_closed_listener_);
|
||||||
|
});
|
||||||
|
// If failed to enqueue the UI thread call, this will just be null.
|
||||||
|
return debug_window_.get();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emulator_->on_launch.AddListener([&](auto title_id, const auto& game_title) {
|
emulator_->on_launch.AddListener([&](auto title_id, const auto& game_title) {
|
||||||
|
|
|
@ -46,6 +46,9 @@ class Socket {
|
||||||
// Returns true if the client is connected and can send/receive data.
|
// Returns true if the client is connected and can send/receive data.
|
||||||
virtual bool is_connected() = 0;
|
virtual bool is_connected() = 0;
|
||||||
|
|
||||||
|
// Sets socket non-blocking mode
|
||||||
|
virtual void set_nonblocking(bool nonblocking) = 0;
|
||||||
|
|
||||||
// Closes the socket.
|
// Closes the socket.
|
||||||
// This will signal the wait handle.
|
// This will signal the wait handle.
|
||||||
virtual void Close() = 0;
|
virtual void Close() = 0;
|
||||||
|
|
|
@ -113,6 +113,15 @@ class Win32Socket : public Socket {
|
||||||
return socket_ != INVALID_SOCKET;
|
return socket_ != INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_nonblocking(bool nonblocking) override {
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||||
|
if (socket_ == INVALID_SOCKET) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
u_long val = nonblocking ? 1 : 0;
|
||||||
|
ioctlsocket(socket_, FIONBIO, &val);
|
||||||
|
}
|
||||||
|
|
||||||
void Close() override {
|
void Close() override {
|
||||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,9 @@ class DebugListener {
|
||||||
// end.
|
// end.
|
||||||
virtual void OnDetached() = 0;
|
virtual void OnDetached() = 0;
|
||||||
|
|
||||||
|
// Handles exception info (will be followed by OnExecutionPaused)
|
||||||
|
virtual void OnUnhandledException(Exception* ex) = 0;
|
||||||
|
|
||||||
// Handles execution being interrupted and transitioning to
|
// Handles execution being interrupted and transitioning to
|
||||||
// ExceutionState::kPaused.
|
// ExceutionState::kPaused.
|
||||||
virtual void OnExecutionPaused() = 0;
|
virtual void OnExecutionPaused() = 0;
|
||||||
|
@ -49,6 +52,9 @@ class DebugListener {
|
||||||
// Breakpoints may be hit during stepping.
|
// Breakpoints may be hit during stepping.
|
||||||
virtual void OnBreakpointHit(Breakpoint* breakpoint,
|
virtual void OnBreakpointHit(Breakpoint* breakpoint,
|
||||||
ThreadDebugInfo* thread_info) = 0;
|
ThreadDebugInfo* thread_info) = 0;
|
||||||
|
|
||||||
|
// Handles any debug messages from the guest
|
||||||
|
virtual void OnDebugPrint(const std::string_view message) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace cpu
|
} // namespace cpu
|
||||||
|
|
|
@ -33,6 +33,8 @@ DEFINE_bool(disable_context_promotion, false,
|
||||||
"some sports games, but will reduce performance.",
|
"some sports games, but will reduce performance.",
|
||||||
"CPU");
|
"CPU");
|
||||||
|
|
||||||
|
DECLARE_bool(debug);
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace cpu {
|
namespace cpu {
|
||||||
namespace ppc {
|
namespace ppc {
|
||||||
|
@ -59,7 +61,10 @@ PPCTranslator::PPCTranslator(PPCFrontend* frontend) : frontend_(frontend) {
|
||||||
|
|
||||||
// Passes are executed in the order they are added. Multiple of the same
|
// Passes are executed in the order they are added. Multiple of the same
|
||||||
// pass type may be used.
|
// pass type may be used.
|
||||||
if (!cvars::disable_context_promotion) {
|
|
||||||
|
// Disable context promotion for debug, otherwise register changes won't apply
|
||||||
|
// correctly
|
||||||
|
if (!cvars::disable_context_promotion && !cvars::debug) {
|
||||||
if (validate) {
|
if (validate) {
|
||||||
compiler_->AddPass(std::make_unique<passes::ValidationPass>());
|
compiler_->AddPass(std::make_unique<passes::ValidationPass>());
|
||||||
}
|
}
|
||||||
|
|
|
@ -644,7 +644,8 @@ bool Processor::OnThreadBreakpointHit(Exception* ex) {
|
||||||
if ((scan_breakpoint->address_type() == Breakpoint::AddressType::kGuest &&
|
if ((scan_breakpoint->address_type() == Breakpoint::AddressType::kGuest &&
|
||||||
scan_breakpoint->guest_address() == frame.guest_pc) ||
|
scan_breakpoint->guest_address() == frame.guest_pc) ||
|
||||||
(scan_breakpoint->address_type() == Breakpoint::AddressType::kHost &&
|
(scan_breakpoint->address_type() == Breakpoint::AddressType::kHost &&
|
||||||
scan_breakpoint->host_address() == frame.host_pc)) {
|
scan_breakpoint->host_address() == frame.host_pc) ||
|
||||||
|
scan_breakpoint->ContainsHostAddress(frame.host_pc)) {
|
||||||
breakpoint = scan_breakpoint;
|
breakpoint = scan_breakpoint;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -670,18 +671,19 @@ bool Processor::OnThreadBreakpointHit(Exception* ex) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ResumeAllThreads();
|
ResumeAllThreads();
|
||||||
thread_info->thread->thread()->Suspend();
|
|
||||||
|
|
||||||
// Apply thread context changes.
|
// Apply thread context changes.
|
||||||
// TODO(benvanik): apply to all threads?
|
// TODO(benvanik): apply to all threads?
|
||||||
#if XE_ARCH_AMD64
|
#if XE_ARCH_AMD64
|
||||||
ex->set_resume_pc(thread_info->host_context.rip + 2);
|
ex->set_resume_pc(thread_info->host_context.rip);
|
||||||
#elif XE_ARCH_ARM64
|
#elif XE_ARCH_ARM64
|
||||||
ex->set_resume_pc(thread_info->host_context.pc + 2);
|
ex->set_resume_pc(thread_info->host_context.pc);
|
||||||
#else
|
#else
|
||||||
#error Instruction pointer not specified for the target CPU architecture.
|
#error Instruction pointer not specified for the target CPU architecture.
|
||||||
#endif // XE_ARCH
|
#endif // XE_ARCH
|
||||||
|
|
||||||
|
thread_info->thread->thread()->Suspend();
|
||||||
|
|
||||||
// Resume execution.
|
// Resume execution.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -722,7 +724,7 @@ bool Processor::OnUnhandledException(Exception* ex) {
|
||||||
execution_state_ = ExecutionState::kPaused;
|
execution_state_ = ExecutionState::kPaused;
|
||||||
|
|
||||||
// Notify debugger that exceution stopped.
|
// Notify debugger that exceution stopped.
|
||||||
// debug_listener_->OnException(info);
|
debug_listener_->OnUnhandledException(ex);
|
||||||
debug_listener_->OnExecutionPaused();
|
debug_listener_->OnExecutionPaused();
|
||||||
|
|
||||||
// Suspend self.
|
// Suspend self.
|
||||||
|
@ -936,7 +938,10 @@ void Processor::StepHostInstruction(uint32_t thread_id) {
|
||||||
thread_info->step_breakpoint.reset();
|
thread_info->step_breakpoint.reset();
|
||||||
OnStepCompleted(thread_info);
|
OnStepCompleted(thread_info);
|
||||||
}));
|
}));
|
||||||
AddBreakpoint(thread_info->step_breakpoint.get());
|
|
||||||
|
// Add to front of breakpoints map, so this should get evaluated first
|
||||||
|
breakpoints_.insert(breakpoints_.begin(), thread_info->step_breakpoint.get());
|
||||||
|
|
||||||
thread_info->step_breakpoint->Resume();
|
thread_info->step_breakpoint->Resume();
|
||||||
|
|
||||||
// ResumeAllBreakpoints();
|
// ResumeAllBreakpoints();
|
||||||
|
@ -969,7 +974,10 @@ void Processor::StepGuestInstruction(uint32_t thread_id) {
|
||||||
thread_info->step_breakpoint.reset();
|
thread_info->step_breakpoint.reset();
|
||||||
OnStepCompleted(thread_info);
|
OnStepCompleted(thread_info);
|
||||||
}));
|
}));
|
||||||
AddBreakpoint(thread_info->step_breakpoint.get());
|
|
||||||
|
// Add to front of breakpoints map, so this should get evaluated first
|
||||||
|
breakpoints_.insert(breakpoints_.begin(), thread_info->step_breakpoint.get());
|
||||||
|
|
||||||
thread_info->step_breakpoint->Resume();
|
thread_info->step_breakpoint->Resume();
|
||||||
|
|
||||||
// ResumeAllBreakpoints();
|
// ResumeAllBreakpoints();
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,169 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2024 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_DEBUG_GDB_GDBSTUB_H_
|
||||||
|
#define XENIA_DEBUG_GDB_GDBSTUB_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <queue>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/base/host_thread_context.h"
|
||||||
|
#include "xenia/base/socket.h"
|
||||||
|
#include "xenia/cpu/breakpoint.h"
|
||||||
|
#include "xenia/cpu/debug_listener.h"
|
||||||
|
#include "xenia/cpu/processor.h"
|
||||||
|
#include "xenia/emulator.h"
|
||||||
|
#include "xenia/xbox.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace debug {
|
||||||
|
namespace gdb {
|
||||||
|
|
||||||
|
class GDBStub : public cpu::DebugListener {
|
||||||
|
enum class ControlCode : char {
|
||||||
|
Ack = '+',
|
||||||
|
Nack = '-',
|
||||||
|
PacketStart = '$',
|
||||||
|
PacketEnd = '#',
|
||||||
|
Interrupt = '\03',
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class SignalCode : uint8_t { SIGILL = 4, SIGTRAP = 5, SIGSEGV = 11 };
|
||||||
|
|
||||||
|
enum class RegisterIndex : int {
|
||||||
|
GPR0 = 0,
|
||||||
|
FPR0 = 32,
|
||||||
|
PC = 64,
|
||||||
|
MSR = 65,
|
||||||
|
CR = 66,
|
||||||
|
LR = 67,
|
||||||
|
CTR = 68,
|
||||||
|
XER = 69,
|
||||||
|
FPSCR = 70
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual ~GDBStub();
|
||||||
|
|
||||||
|
static std::unique_ptr<GDBStub> Create(Emulator* emulator, int listen_port);
|
||||||
|
|
||||||
|
Emulator* emulator() const { return emulator_; }
|
||||||
|
|
||||||
|
void OnFocus() override;
|
||||||
|
void OnDetached() override;
|
||||||
|
void OnUnhandledException(Exception* ex) override;
|
||||||
|
void OnExecutionPaused() override;
|
||||||
|
void OnExecutionContinued() override;
|
||||||
|
void OnExecutionEnded() override;
|
||||||
|
void OnStepCompleted(cpu::ThreadDebugInfo* thread_info) override;
|
||||||
|
void OnBreakpointHit(cpu::Breakpoint* breakpoint,
|
||||||
|
cpu::ThreadDebugInfo* thread_info) override;
|
||||||
|
void OnDebugPrint(const std::string_view message) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct GDBCommand {
|
||||||
|
std::string cmd{};
|
||||||
|
std::string data{};
|
||||||
|
uint8_t checksum{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GDBClient {
|
||||||
|
std::unique_ptr<Socket> socket;
|
||||||
|
bool no_ack_mode = false;
|
||||||
|
std::string receive_buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit GDBStub(Emulator* emulator, int listen_port);
|
||||||
|
bool Initialize();
|
||||||
|
|
||||||
|
void Listen(GDBClient& client);
|
||||||
|
void SendPacket(GDBClient& client, const std::string& data);
|
||||||
|
bool ProcessIncomingData(GDBClient& client);
|
||||||
|
bool ParsePacket(const std::string& packet, GDBCommand& out_cmd);
|
||||||
|
std::string HandleGDBCommand(GDBClient& client, const GDBCommand& command);
|
||||||
|
|
||||||
|
void UpdateCache();
|
||||||
|
|
||||||
|
std::string DebuggerDetached();
|
||||||
|
std::string RegisterRead(xe::cpu::ThreadDebugInfo* thread, uint32_t rid);
|
||||||
|
std::string RegisterWrite(xe::cpu::ThreadDebugInfo* thread, uint32_t rid,
|
||||||
|
const std::string_view value);
|
||||||
|
std::string RegisterRead(const std::string& data);
|
||||||
|
std::string RegisterWrite(const std::string& data);
|
||||||
|
std::string RegisterReadAll();
|
||||||
|
std::string RegisterWriteAll(const std::string& data);
|
||||||
|
std::string ExecutionPause();
|
||||||
|
std::string ExecutionContinue();
|
||||||
|
std::string ExecutionStep();
|
||||||
|
std::string MemoryRead(const std::string& data);
|
||||||
|
std::string MemoryWrite(const std::string& data);
|
||||||
|
std::string BuildThreadList();
|
||||||
|
std::string QueryPacket(GDBClient& client, const std::string& data);
|
||||||
|
|
||||||
|
std::string GetThreadStateReply(uint32_t thread_id, SignalCode signal);
|
||||||
|
|
||||||
|
bool CreateCodeBreakpoint(uint64_t address);
|
||||||
|
void DeleteCodeBreakpoint(uint64_t address);
|
||||||
|
void DeleteCodeBreakpoint(cpu::Breakpoint* breakpoint);
|
||||||
|
cpu::Breakpoint* LookupBreakpointAtAddress(uint64_t address);
|
||||||
|
|
||||||
|
Emulator* emulator_ = nullptr;
|
||||||
|
cpu::Processor* processor_ = nullptr;
|
||||||
|
|
||||||
|
int listen_port_;
|
||||||
|
std::unique_ptr<xe::SocketServer> socket_;
|
||||||
|
|
||||||
|
std::mutex mtx_;
|
||||||
|
bool stop_thread_ = false;
|
||||||
|
|
||||||
|
xe::global_critical_region global_critical_region_;
|
||||||
|
|
||||||
|
struct EmulatorStateCache {
|
||||||
|
uint32_t cur_thread_id = -1;
|
||||||
|
uint32_t last_bp_thread_id = -1;
|
||||||
|
|
||||||
|
uint64_t notify_bp_guest_address = -1;
|
||||||
|
uint32_t notify_thread_id = -1;
|
||||||
|
bool notify_stopped = false;
|
||||||
|
std::optional<Exception::AccessViolationOperation>
|
||||||
|
notify_exception_access_violation;
|
||||||
|
std::optional<Exception::Code> notify_exception_code;
|
||||||
|
|
||||||
|
std::queue<std::string> notify_debug_prints;
|
||||||
|
|
||||||
|
bool is_stopped = false;
|
||||||
|
std::vector<kernel::object_ref<kernel::XModule>> modules;
|
||||||
|
std::vector<cpu::ThreadDebugInfo*> thread_debug_infos;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
std::vector<std::unique_ptr<cpu::Breakpoint>> all_breakpoints;
|
||||||
|
std::unordered_map<uint32_t, cpu::Breakpoint*>
|
||||||
|
code_breakpoints_by_guest_address;
|
||||||
|
} breakpoints;
|
||||||
|
|
||||||
|
cpu::ThreadDebugInfo* thread_info(int threadId) {
|
||||||
|
for (auto& thread : thread_debug_infos) {
|
||||||
|
if (thread->thread_id == threadId) {
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
cpu::ThreadDebugInfo* cur_thread_info() {
|
||||||
|
return thread_info(cur_thread_id);
|
||||||
|
}
|
||||||
|
} cache_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gdb
|
||||||
|
} // namespace debug
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_DEBUG_UI_DEBUG_WINDOW_H_
|
|
@ -0,0 +1,25 @@
|
||||||
|
project_root = "../../../.."
|
||||||
|
include(project_root.."/tools/build")
|
||||||
|
|
||||||
|
group("src")
|
||||||
|
project("xenia-debug-gdb")
|
||||||
|
uuid("9193a274-f4c2-4746-bd85-93fcfc5c3e39")
|
||||||
|
kind("StaticLib")
|
||||||
|
language("C++")
|
||||||
|
links({
|
||||||
|
"imgui",
|
||||||
|
"xenia-base",
|
||||||
|
"xenia-cpu",
|
||||||
|
"xenia-ui",
|
||||||
|
})
|
||||||
|
filter({"configurations:Release", "platforms:Windows"})
|
||||||
|
buildoptions({
|
||||||
|
"/Os",
|
||||||
|
"/O1"
|
||||||
|
})
|
||||||
|
filter{}
|
||||||
|
defines({
|
||||||
|
})
|
||||||
|
includedirs({
|
||||||
|
})
|
||||||
|
local_platform_files()
|
|
@ -1576,6 +1576,8 @@ void DebugWindow::OnDetached() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DebugWindow::OnUnhandledException(Exception* ex) {}
|
||||||
|
|
||||||
void DebugWindow::OnExecutionPaused() {
|
void DebugWindow::OnExecutionPaused() {
|
||||||
UpdateCache();
|
UpdateCache();
|
||||||
Focus();
|
Focus();
|
||||||
|
@ -1604,6 +1606,8 @@ void DebugWindow::OnBreakpointHit(Breakpoint* breakpoint,
|
||||||
Focus();
|
Focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DebugWindow::OnDebugPrint(const std::string_view message) {}
|
||||||
|
|
||||||
void DebugWindow::Focus() const {
|
void DebugWindow::Focus() const {
|
||||||
app_context_.CallInUIThread([this]() { window_->Focus(); });
|
app_context_.CallInUIThread([this]() { window_->Focus(); });
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,12 +44,14 @@ class DebugWindow : public cpu::DebugListener {
|
||||||
|
|
||||||
void OnFocus() override;
|
void OnFocus() override;
|
||||||
void OnDetached() override;
|
void OnDetached() override;
|
||||||
|
void OnUnhandledException(Exception* ex) override;
|
||||||
void OnExecutionPaused() override;
|
void OnExecutionPaused() override;
|
||||||
void OnExecutionContinued() override;
|
void OnExecutionContinued() override;
|
||||||
void OnExecutionEnded() override;
|
void OnExecutionEnded() override;
|
||||||
void OnStepCompleted(cpu::ThreadDebugInfo* thread_info) override;
|
void OnStepCompleted(cpu::ThreadDebugInfo* thread_info) override;
|
||||||
void OnBreakpointHit(cpu::Breakpoint* breakpoint,
|
void OnBreakpointHit(cpu::Breakpoint* breakpoint,
|
||||||
cpu::ThreadDebugInfo* thread_info) override;
|
cpu::ThreadDebugInfo* thread_info) override;
|
||||||
|
void OnDebugPrint(const std::string_view message) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class DebugDialog final : public xe::ui::ImGuiDialog {
|
class DebugDialog final : public xe::ui::ImGuiDialog {
|
||||||
|
|
|
@ -189,6 +189,10 @@ Emulator::~Emulator() {
|
||||||
ExceptionHandler::Uninstall(Emulator::ExceptionCallbackThunk, this);
|
ExceptionHandler::Uninstall(Emulator::ExceptionCallbackThunk, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t Emulator::main_thread_id() {
|
||||||
|
return main_thread_ ? main_thread_->thread_id() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
X_STATUS Emulator::Setup(
|
X_STATUS Emulator::Setup(
|
||||||
ui::Window* display_window, ui::ImGuiDrawer* imgui_drawer,
|
ui::Window* display_window, ui::ImGuiDrawer* imgui_drawer,
|
||||||
bool require_cpu_backend,
|
bool require_cpu_backend,
|
||||||
|
|
|
@ -125,6 +125,8 @@ class Emulator {
|
||||||
// Are we currently running a title?
|
// Are we currently running a title?
|
||||||
bool is_title_open() const { return title_id_.has_value(); }
|
bool is_title_open() const { return title_id_.has_value(); }
|
||||||
|
|
||||||
|
uint32_t main_thread_id();
|
||||||
|
|
||||||
// Window used for displaying graphical output. Can be null.
|
// Window used for displaying graphical output. Can be null.
|
||||||
ui::Window* display_window() const { return display_window_; }
|
ui::Window* display_window() const { return display_window_; }
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,8 @@ void InputSystem::AddDriver(std::unique_ptr<InputDriver> driver) {
|
||||||
void InputSystem::UpdateUsedSlot(InputDriver* driver, uint8_t slot,
|
void InputSystem::UpdateUsedSlot(InputDriver* driver, uint8_t slot,
|
||||||
bool connected) {
|
bool connected) {
|
||||||
if (slot == XUserIndexAny) {
|
if (slot == XUserIndexAny) {
|
||||||
slot = 0;
|
XELOGW("{} received requrest for slot any! Unsupported", __func__);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connected_slots.test(slot) == connected) {
|
if (connected_slots.test(slot) == connected) {
|
||||||
|
@ -136,18 +137,16 @@ X_RESULT InputSystem::GetKeystroke(uint32_t user_index, uint32_t flags,
|
||||||
|
|
||||||
bool any_connected = false;
|
bool any_connected = false;
|
||||||
for (auto& driver : drivers_) {
|
for (auto& driver : drivers_) {
|
||||||
|
// connected_slots
|
||||||
X_RESULT result = driver->GetKeystroke(user_index, flags, out_keystroke);
|
X_RESULT result = driver->GetKeystroke(user_index, flags, out_keystroke);
|
||||||
if (result == X_ERROR_INVALID_PARAMETER) {
|
if (result == X_ERROR_INVALID_PARAMETER ||
|
||||||
|
result == X_ERROR_DEVICE_NOT_CONNECTED) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result != X_ERROR_DEVICE_NOT_CONNECTED) {
|
any_connected = true;
|
||||||
any_connected = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result == X_ERROR_SUCCESS || result == X_ERROR_EMPTY) {
|
if (result == X_ERROR_SUCCESS || result == X_ERROR_EMPTY) {
|
||||||
UpdateUsedSlot(driver.get(), user_index, any_connected);
|
|
||||||
|
|
||||||
if (result == X_ERROR_SUCCESS) {
|
if (result == X_ERROR_SUCCESS) {
|
||||||
last_used_slot = user_index;
|
last_used_slot = user_index;
|
||||||
}
|
}
|
||||||
|
@ -158,7 +157,6 @@ X_RESULT InputSystem::GetKeystroke(uint32_t user_index, uint32_t flags,
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UpdateUsedSlot(nullptr, user_index, any_connected);
|
|
||||||
return any_connected ? X_ERROR_EMPTY : X_ERROR_DEVICE_NOT_CONNECTED;
|
return any_connected ? X_ERROR_EMPTY : X_ERROR_DEVICE_NOT_CONNECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ namespace xe {
|
||||||
namespace hid {
|
namespace hid {
|
||||||
namespace winkey {
|
namespace winkey {
|
||||||
|
|
||||||
bool static IsPassthroughEnabled(uint32_t user_index) {
|
bool static IsPassthroughEnabled() {
|
||||||
return static_cast<KeyboardMode>(cvars::keyboard_mode) ==
|
return static_cast<KeyboardMode>(cvars::keyboard_mode) ==
|
||||||
KeyboardMode::Passthrough;
|
KeyboardMode::Passthrough;
|
||||||
}
|
}
|
||||||
|
@ -272,6 +272,14 @@ X_RESULT WinKeyInputDriver::SetState(uint32_t user_index,
|
||||||
|
|
||||||
X_RESULT WinKeyInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags,
|
X_RESULT WinKeyInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags,
|
||||||
X_INPUT_KEYSTROKE* out_keystroke) {
|
X_INPUT_KEYSTROKE* out_keystroke) {
|
||||||
|
if (!is_active()) {
|
||||||
|
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsKeyboardForUserEnabled(user_index) && !IsPassthroughEnabled()) {
|
||||||
|
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
// Pop from the queue.
|
// Pop from the queue.
|
||||||
KeyEvent evt;
|
KeyEvent evt;
|
||||||
{
|
{
|
||||||
|
@ -284,15 +292,6 @@ X_RESULT WinKeyInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags,
|
||||||
key_events_.pop();
|
key_events_.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsKeyboardForUserEnabled(user_index) &&
|
|
||||||
!IsPassthroughEnabled(user_index)) {
|
|
||||||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_active()) {
|
|
||||||
return X_ERROR_EMPTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
X_RESULT result = X_ERROR_EMPTY;
|
X_RESULT result = X_ERROR_EMPTY;
|
||||||
|
|
||||||
ui::VirtualKey xinput_virtual_key = ui::VirtualKey::kNone;
|
ui::VirtualKey xinput_virtual_key = ui::VirtualKey::kNone;
|
||||||
|
@ -302,7 +301,7 @@ X_RESULT WinKeyInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags,
|
||||||
|
|
||||||
bool capital = IsKeyToggled(VK_CAPITAL) || IsKeyDown(VK_SHIFT);
|
bool capital = IsKeyToggled(VK_CAPITAL) || IsKeyDown(VK_SHIFT);
|
||||||
|
|
||||||
if (!IsPassthroughEnabled(user_index)) {
|
if (!IsPassthroughEnabled()) {
|
||||||
if (IsKeyboardForUserEnabled(user_index)) {
|
if (IsKeyboardForUserEnabled(user_index)) {
|
||||||
for (const KeyBinding& b : key_bindings_) {
|
for (const KeyBinding& b : key_bindings_) {
|
||||||
if (b.input_key == evt.virtual_key &&
|
if (b.input_key == evt.virtual_key &&
|
||||||
|
@ -338,7 +337,7 @@ X_RESULT WinKeyInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags,
|
||||||
keystroke_flags |= 0x0002; // XINPUT_KEYSTROKE_KEYUP
|
keystroke_flags |= 0x0002; // XINPUT_KEYSTROKE_KEYUP
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsPassthroughEnabled(user_index)) {
|
if (IsPassthroughEnabled()) {
|
||||||
if (GetKeyboardState(key_map_)) {
|
if (GetKeyboardState(key_map_)) {
|
||||||
WCHAR buf;
|
WCHAR buf;
|
||||||
if (ToUnicode(uint8_t(xinput_virtual_key), 0, key_map_, &buf, 1, 0) ==
|
if (ToUnicode(uint8_t(xinput_virtual_key), 0, key_map_, &buf, 1, 0) ==
|
||||||
|
@ -373,7 +372,8 @@ void WinKeyInputDriver::WinKeyWindowInputListener::OnKeyUp(ui::KeyEvent& e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void WinKeyInputDriver::OnKey(ui::KeyEvent& e, bool is_down) {
|
void WinKeyInputDriver::OnKey(ui::KeyEvent& e, bool is_down) {
|
||||||
if (!is_active()) {
|
if (!is_active() || static_cast<KeyboardMode>(cvars::keyboard_mode) ==
|
||||||
|
KeyboardMode::Disabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,9 +52,9 @@ UserProfile::UserProfile(uint64_t xuid, X_XAMACCOUNTINFO* account_info)
|
||||||
// XPROFILE_GAMER_CONTROL_SENSITIVITY
|
// XPROFILE_GAMER_CONTROL_SENSITIVITY
|
||||||
AddSetting(std::make_unique<UserSetting>(0x10040018, 0));
|
AddSetting(std::make_unique<UserSetting>(0x10040018, 0));
|
||||||
// Preferred color 1
|
// Preferred color 1
|
||||||
AddSetting(std::make_unique<UserSetting>(0x1004001D, 0xFFFF0000u));
|
AddSetting(std::make_unique<UserSetting>(0x1004001D, PREFERRED_COLOR_NONE));
|
||||||
// Preferred color 2
|
// Preferred color 2
|
||||||
AddSetting(std::make_unique<UserSetting>(0x1004001E, 0xFF00FF00u));
|
AddSetting(std::make_unique<UserSetting>(0x1004001E, PREFERRED_COLOR_NONE));
|
||||||
// XPROFILE_GAMER_ACTION_AUTO_AIM
|
// XPROFILE_GAMER_ACTION_AUTO_AIM
|
||||||
AddSetting(std::make_unique<UserSetting>(0x10040022, 1));
|
AddSetting(std::make_unique<UserSetting>(0x10040022, 1));
|
||||||
// XPROFILE_GAMER_ACTION_AUTO_CENTER
|
// XPROFILE_GAMER_ACTION_AUTO_CENTER
|
||||||
|
|
|
@ -35,6 +35,21 @@ enum class X_USER_PROFILE_SETTING_SOURCE : uint32_t {
|
||||||
UNKNOWN = 3,
|
UNKNOWN = 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum PREFERRED_COLOR_OPTIONS : uint32_t {
|
||||||
|
PREFERRED_COLOR_NONE,
|
||||||
|
PREFERRED_COLOR_BLACK,
|
||||||
|
PREFERRED_COLOR_WHITE,
|
||||||
|
PREFERRED_COLOR_YELLOW,
|
||||||
|
PREFERRED_COLOR_ORANGE,
|
||||||
|
PREFERRED_COLOR_PINK,
|
||||||
|
PREFERRED_COLOR_RED,
|
||||||
|
PREFERRED_COLOR_PURPLE,
|
||||||
|
PREFERRED_COLOR_BLUE,
|
||||||
|
PREFERRED_COLOR_GREEN,
|
||||||
|
PREFERRED_COLOR_BROWN,
|
||||||
|
PREFERRED_COLOR_SILVER
|
||||||
|
};
|
||||||
|
|
||||||
// Each setting contains 0x18 bytes long header
|
// Each setting contains 0x18 bytes long header
|
||||||
struct X_USER_PROFILE_SETTING_HEADER {
|
struct X_USER_PROFILE_SETTING_HEADER {
|
||||||
xe::be<uint32_t> setting_id;
|
xe::be<uint32_t> setting_id;
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
|
#include "xenia/cpu/processor.h"
|
||||||
#include "xenia/kernel/kernel_state.h"
|
#include "xenia/kernel/kernel_state.h"
|
||||||
#include "xenia/kernel/util/shim_utils.h"
|
#include "xenia/kernel/util/shim_utils.h"
|
||||||
#include "xenia/kernel/xbdm/xbdm_private.h"
|
#include "xenia/kernel/xbdm/xbdm_private.h"
|
||||||
|
@ -69,7 +70,14 @@ DECLARE_XBDM_EXPORT1(DmGetXboxName, kDebug, kImplemented)
|
||||||
dword_result_t DmIsDebuggerPresent_entry() { return 0; }
|
dword_result_t DmIsDebuggerPresent_entry() { return 0; }
|
||||||
DECLARE_XBDM_EXPORT1(DmIsDebuggerPresent, kDebug, kStub);
|
DECLARE_XBDM_EXPORT1(DmIsDebuggerPresent, kDebug, kStub);
|
||||||
|
|
||||||
void DmSendNotificationString_entry(lpdword_t unk0_ptr) {}
|
void DmSendNotificationString_entry(lpstring_t message) {
|
||||||
|
XELOGI("(DmSendNotificationString) {}", message.value());
|
||||||
|
|
||||||
|
if (cpu::DebugListener* listener =
|
||||||
|
kernel_state()->processor()->debug_listener()) {
|
||||||
|
listener->OnDebugPrint(message.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
DECLARE_XBDM_EXPORT1(DmSendNotificationString, kDebug, kStub);
|
DECLARE_XBDM_EXPORT1(DmSendNotificationString, kDebug, kStub);
|
||||||
|
|
||||||
dword_result_t DmRegisterCommandProcessor_entry(lpdword_t name_ptr,
|
dword_result_t DmRegisterCommandProcessor_entry(lpdword_t name_ptr,
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
|
#include "xenia/cpu/processor.h"
|
||||||
#include "xenia/kernel/kernel_state.h"
|
#include "xenia/kernel/kernel_state.h"
|
||||||
#include "xenia/kernel/user_module.h"
|
#include "xenia/kernel/user_module.h"
|
||||||
#include "xenia/kernel/util/shim_utils.h"
|
#include "xenia/kernel/util/shim_utils.h"
|
||||||
|
@ -850,6 +851,10 @@ SHIM_CALL DbgPrint_entry(PPCContext* ppc_context) {
|
||||||
|
|
||||||
XELOGI("(DbgPrint) {}", str);
|
XELOGI("(DbgPrint) {}", str);
|
||||||
|
|
||||||
|
if (cpu::DebugListener* listener = ppc_context->processor->debug_listener()) {
|
||||||
|
listener->OnDebugPrint(str);
|
||||||
|
}
|
||||||
|
|
||||||
SHIM_SET_RETURN_32(X_STATUS_SUCCESS);
|
SHIM_SET_RETURN_32(X_STATUS_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue