Compare commits

...

19 Commits

Author SHA1 Message Date
emoose 4950de5d32
Merge 5917554324 into 11f14e8488 2024-12-21 22:03:59 +01:00
Gliniak 11f14e8488 [HID] Fixed spam caused by incorrect controller visibility
- Little cleanup in HID related code
2024-12-21 19:51:01 +01:00
Adrian 3d79874828 [XAM] Fixed profile setting for preferred color
Fixes Supreme Commander from crashing.
2024-12-21 14:58:16 +01:00
emoose 5917554324 [App] Update gdbstub setting description 2024-10-24 00:28:38 +01:00
emoose 2e9c9f403d [GDBStub] Forward `DbgPrint` & `DmSendNotificationString` messages
TODO: see if there's any other debug-string-output functions that we can
pass to debugger?

Might be worth adding a custom GDB command for toggling these too, some
games could be spammy
2024-10-24 00:27:59 +01:00
emoose 220324b463 [GDBStub] Fix debugger showing invalid symbols after pausing/continuing 2024-10-24 00:04:38 +01:00
emoose c17261abf7 [GDBStub] Return error for code / unimpl. reg writes, small fixups 2024-10-24 00:04:38 +01:00
emoose e260d0a110 [GDBStub] Provide exception type to debugger 2024-10-24 00:04:38 +01:00
emoose a4187736ad [GDBStub] Add RegisterWriteAll 2024-10-24 00:04:38 +01:00
emoose ea9cf0c8f9 [GDBStub] Allow register writes, disable ctx promotion for --debug
Register changes won't always register if context promotion pass is enabled,
fortunately this was noted by benvanik at
3d30b2eec3/src/xenia/debug/ui/debug_window.cc (L1004)

Disabling context promotion pass if --debug is set seems to allow register
updates fine, without too much performance loss on my end.

Still need to implement PC and CR reg writes, and the WriteRegistersAll cmd

Not completely sure if all register writes actually take effect yet, seems
GPR are fine at least but unsure about others
2024-10-24 00:04:32 +01:00
emoose 0acc46e52e [GDBStub] Add MemoryWrite, extra checks in memory cmds 2024-10-24 00:00:33 +01:00
emoose 2ee35a2054 [GDBStub] Remove duplicated code, minor fixups 2024-10-24 00:00:33 +01:00
emoose c9778a4032 [GDBStub] Use main thread as default thread ID 2024-10-24 00:00:33 +01:00
emoose 26cfa69497 [GDBStub] Add memory map, MSR/FPSCR, handle Kill request
IDA seems to ignore the map though, sad!
2024-10-24 00:00:33 +01:00
emoose 0469ae2aee [GDBStub] Use xe::SocketServer instead of winsock, allow detaching
Detach will clear breakpoints and resume execution
Reconnecting should now work fine, could probably have multiple connections
too (may be some threading issues with that though...)
2024-10-24 00:00:33 +01:00
emoose b7ab7e966f [GDBStub] Prevent adding BP if other BP shares same host addr
Fixes assert error with BPs set very close to each other
(in IDA the failed BP will also show different color)
2024-10-24 00:00:33 +01:00
emoose bfa6eee936 [Debug] Processor: breakpoint address fixes
- Update OnThreadBreakpointHit to use bp->ContainsHostAddress

Sometimes guest PC doesn't map to the x64 exactly, so ResolveStack may return
a guest PC different to the breakpoint that triggered it

Processor code would just skip the breakpoint entirely if PC didn't match
so any debugger wouldn't know what BP caused it

Luckily BP does have a ContainsHostAddress func which seems perfect, but was
somehow left unused

Also added a hack to nudge the PC we tell GDB about to the BP's PC

- Update StepGuest/HostInstruction to add step BP to front of map

Fixes an issue where a different BP might get triggered before the step BP
which could cause debugger to step again, causing assert error as a step BP
already existed

I noticed one more issue, if BPs are set on instructions very close together
it might act on the same x64 instruction, causing assert when a BP is already
placed and another BP tries to set on the same addr...

Not really sure what best way to fix that is yet, for now just don't place
BPs too close together!
2024-10-24 00:00:33 +01:00
emoose 2204259781 [Debug] GDBStub: add info for each command 2024-10-24 00:00:33 +01:00
emoose 733fe04426 [Debug] Add PoC GDBStub server, fix breakpoints
Fixed issue in processor.cc which prevented resuming from a breakpoint

BPs would set 2 bytes of the x64 JIT opcode to an invalid opcode & then catch
the exception from it - BP handler then restored the original opcode, but
then advanced RIP by 2 bytes into that opcode for some reason?

It would also suspend the thread just prior to this RIP change,
causing some strange effects... moving suspend after it (and removing the
offset) appears to work fine, though I didn't test the imgui debugger yet

To use GDBStub run xenia with "--debug --gdbport 12345" args

Tested with IDA 9 + idaxex loader, BPs can be set and resumed fine, suspend
& resume also works - memory/registers are viewable but can't be changed yet

The socket code here is very basic and only allows a single connection for
the session, if you disconnect you'll need to restart emulator
2024-10-24 00:00:33 +01:00
21 changed files with 1527 additions and 45 deletions

View File

@ -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")

View File

@ -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",

View File

@ -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) {

View File

@ -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;

View File

@ -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_);

View File

@ -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

View File

@ -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>());
} }

View File

@ -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

View File

@ -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_

View File

@ -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()

View File

@ -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(); });
} }

View File

@ -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 {

View File

@ -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,

View File

@ -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_; }

View File

@ -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;
} }

View File

@ -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;
} }

View File

@ -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

View File

@ -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;

View File

@ -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,

View File

@ -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);
} }