Merge branch 'master' into vulkan
This commit is contained in:
commit
c14e3770a2
|
@ -69,6 +69,11 @@ class global_critical_region {
|
|||
return std::unique_lock<std::recursive_mutex>(mutex());
|
||||
}
|
||||
|
||||
// Acquires a deferred lock on the global critical section.
|
||||
inline std::unique_lock<std::recursive_mutex> AcquireDeferred() {
|
||||
return std::unique_lock<std::recursive_mutex>(mutex(), std::defer_lock);
|
||||
}
|
||||
|
||||
// Tries to acquire a lock on the glboal critical section.
|
||||
// Check owns_lock() to see if the lock was successfully acquired.
|
||||
inline std::unique_lock<std::recursive_mutex> TryAcquire() {
|
||||
|
|
|
@ -34,6 +34,11 @@
|
|||
namespace xe {
|
||||
namespace string_util {
|
||||
|
||||
enum class Safety {
|
||||
IDontKnowWhatIAmDoing,
|
||||
IKnowWhatIAmDoing,
|
||||
};
|
||||
|
||||
inline size_t copy_truncating(char* dest, const std::string_view source,
|
||||
size_t dest_buffer_count) {
|
||||
if (!dest_buffer_count) {
|
||||
|
@ -68,6 +73,44 @@ inline size_t copy_and_swap_truncating(char16_t* dest,
|
|||
return chars_copied;
|
||||
}
|
||||
|
||||
template <Safety safety = Safety::IDontKnowWhatIAmDoing>
|
||||
inline size_t copy_maybe_truncating(char* dest, const std::string_view source,
|
||||
size_t dest_buffer_count) {
|
||||
static_assert(safety == Safety::IKnowWhatIAmDoing);
|
||||
if (!dest_buffer_count) {
|
||||
return 0;
|
||||
}
|
||||
size_t chars_copied = std::min(source.size(), dest_buffer_count);
|
||||
std::memcpy(dest, source.data(), chars_copied);
|
||||
return chars_copied;
|
||||
}
|
||||
|
||||
template <Safety safety = Safety::IDontKnowWhatIAmDoing>
|
||||
inline size_t copy_maybe_truncating(char16_t* dest,
|
||||
const std::u16string_view source,
|
||||
size_t dest_buffer_count) {
|
||||
static_assert(safety == Safety::IKnowWhatIAmDoing);
|
||||
if (!dest_buffer_count) {
|
||||
return 0;
|
||||
}
|
||||
size_t chars_copied = std::min(source.size(), dest_buffer_count);
|
||||
std::memcpy(dest, source.data(), chars_copied * sizeof(char16_t));
|
||||
return chars_copied;
|
||||
}
|
||||
|
||||
template <Safety safety = Safety::IDontKnowWhatIAmDoing>
|
||||
inline size_t copy_and_swap_maybe_truncating(char16_t* dest,
|
||||
const std::u16string_view source,
|
||||
size_t dest_buffer_count) {
|
||||
static_assert(safety == Safety::IKnowWhatIAmDoing);
|
||||
if (!dest_buffer_count) {
|
||||
return 0;
|
||||
}
|
||||
size_t chars_copied = std::min(source.size(), dest_buffer_count);
|
||||
xe::copy_and_swap(dest, source.data(), chars_copied);
|
||||
return chars_copied;
|
||||
}
|
||||
|
||||
inline std::string to_hex_string(uint32_t value) {
|
||||
return fmt::format("{:08X}", value);
|
||||
}
|
||||
|
|
|
@ -2460,7 +2460,11 @@ struct LOG2_F32 : Sequence<LOG2_F32, I<OPCODE_LOG2, F32Op, F32Op>> {
|
|||
}
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
assert_always();
|
||||
e.lea(e.GetNativeParam(0), e.StashXmm(0, i.src1));
|
||||
if (i.src1.is_constant) {
|
||||
e.lea(e.GetNativeParam(0), e.StashConstantXmm(0, i.src1.constant()));
|
||||
} else {
|
||||
e.lea(e.GetNativeParam(0), e.StashXmm(0, i.src1));
|
||||
}
|
||||
e.CallNativeSafe(reinterpret_cast<void*>(EmulateLog2));
|
||||
e.vmovaps(i.dest, e.xmm0);
|
||||
}
|
||||
|
@ -2474,7 +2478,11 @@ struct LOG2_F64 : Sequence<LOG2_F64, I<OPCODE_LOG2, F64Op, F64Op>> {
|
|||
}
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
assert_always();
|
||||
e.lea(e.GetNativeParam(0), e.StashXmm(0, i.src1));
|
||||
if (i.src1.is_constant) {
|
||||
e.lea(e.GetNativeParam(0), e.StashConstantXmm(0, i.src1.constant()));
|
||||
} else {
|
||||
e.lea(e.GetNativeParam(0), e.StashXmm(0, i.src1));
|
||||
}
|
||||
e.CallNativeSafe(reinterpret_cast<void*>(EmulateLog2));
|
||||
e.vmovaps(i.dest, e.xmm0);
|
||||
}
|
||||
|
@ -2489,7 +2497,11 @@ struct LOG2_V128 : Sequence<LOG2_V128, I<OPCODE_LOG2, V128Op, V128Op>> {
|
|||
return _mm_load_ps(values);
|
||||
}
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.lea(e.GetNativeParam(0), e.StashXmm(0, i.src1));
|
||||
if (i.src1.is_constant) {
|
||||
e.lea(e.GetNativeParam(0), e.StashConstantXmm(0, i.src1.constant()));
|
||||
} else {
|
||||
e.lea(e.GetNativeParam(0), e.StashXmm(0, i.src1));
|
||||
}
|
||||
e.CallNativeSafe(reinterpret_cast<void*>(EmulateLog2));
|
||||
e.vmovaps(i.dest, e.xmm0);
|
||||
}
|
||||
|
|
|
@ -184,7 +184,10 @@ X_STATUS Emulator::Setup(
|
|||
if (input_driver_factory) {
|
||||
auto input_drivers = input_driver_factory(display_window_);
|
||||
for (size_t i = 0; i < input_drivers.size(); ++i) {
|
||||
input_system_->AddDriver(std::move(input_drivers[i]));
|
||||
auto& input_driver = input_drivers[i];
|
||||
input_driver->set_is_active_callback(
|
||||
[]() -> bool { return !xe::kernel::xam::xeXamIsUIActive(); });
|
||||
input_system_->AddDriver(std::move(input_driver));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
#ifndef XENIA_HID_INPUT_DRIVER_H_
|
||||
#define XENIA_HID_INPUT_DRIVER_H_
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "xenia/hid/input.h"
|
||||
#include "xenia/ui/window.h"
|
||||
#include "xenia/xbox.h"
|
||||
|
||||
namespace xe {
|
||||
|
@ -38,10 +41,22 @@ class InputDriver {
|
|||
virtual X_RESULT GetKeystroke(uint32_t user_index, uint32_t flags,
|
||||
X_INPUT_KEYSTROKE* out_keystroke) = 0;
|
||||
|
||||
void set_is_active_callback(std::function<bool()> is_active_callback) {
|
||||
is_active_callback_ = is_active_callback;
|
||||
}
|
||||
|
||||
private:
|
||||
xe::ui::Window* window_ = nullptr;
|
||||
std::function<bool()> is_active_callback_ = nullptr;
|
||||
|
||||
protected:
|
||||
explicit InputDriver(xe::ui::Window* window);
|
||||
|
||||
xe::ui::Window* window_ = nullptr;
|
||||
xe::ui::Window* window() const { return window_; }
|
||||
|
||||
bool is_active() const {
|
||||
return !is_active_callback_ || is_active_callback_();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace hid
|
||||
|
|
|
@ -65,7 +65,7 @@ X_STATUS SDLInputDriver::Setup() {
|
|||
|
||||
// SDL_PumpEvents should only be run in the thread that initialized SDL - we
|
||||
// are hijacking the window loop thread for that.
|
||||
window_->loop()->PostSynchronous([&]() {
|
||||
window()->loop()->PostSynchronous([&]() {
|
||||
if (!xe::helper::sdl::SDLHelper::Prepare()) {
|
||||
return;
|
||||
}
|
||||
|
@ -385,7 +385,7 @@ X_RESULT SDLInputDriver::GetKeystroke(uint32_t users, uint32_t flags,
|
|||
}
|
||||
|
||||
void SDLInputDriver::OnControllerDeviceAdded(SDL_Event* event) {
|
||||
assert(window_->loop()->is_on_loop_thread());
|
||||
assert(window()->loop()->is_on_loop_thread());
|
||||
std::unique_lock<std::mutex> guard(controllers_mutex_);
|
||||
|
||||
// Open the controller.
|
||||
|
@ -424,7 +424,7 @@ void SDLInputDriver::OnControllerDeviceAdded(SDL_Event* event) {
|
|||
}
|
||||
|
||||
void SDLInputDriver::OnControllerDeviceRemoved(SDL_Event* event) {
|
||||
assert(window_->loop()->is_on_loop_thread());
|
||||
assert(window()->loop()->is_on_loop_thread());
|
||||
std::unique_lock<std::mutex> guard(controllers_mutex_);
|
||||
|
||||
// Find the disconnected gamecontroller and close it.
|
||||
|
@ -436,7 +436,7 @@ void SDLInputDriver::OnControllerDeviceRemoved(SDL_Event* event) {
|
|||
}
|
||||
|
||||
void SDLInputDriver::OnControllerDeviceAxisMotion(SDL_Event* event) {
|
||||
assert(window_->loop()->is_on_loop_thread());
|
||||
assert(window()->loop()->is_on_loop_thread());
|
||||
std::unique_lock<std::mutex> guard(controllers_mutex_);
|
||||
|
||||
auto [found, i] = GetControllerIndexFromInstanceID(event->caxis.which);
|
||||
|
@ -469,7 +469,7 @@ void SDLInputDriver::OnControllerDeviceAxisMotion(SDL_Event* event) {
|
|||
}
|
||||
|
||||
void SDLInputDriver::OnControllerDeviceButtonChanged(SDL_Event* event) {
|
||||
assert(window_->loop()->is_on_loop_thread());
|
||||
assert(window()->loop()->is_on_loop_thread());
|
||||
std::unique_lock<std::mutex> guard(controllers_mutex_);
|
||||
|
||||
// Define a lookup table to map between SDL and XInput button codes.
|
||||
|
@ -569,7 +569,7 @@ void SDLInputDriver::QueueControllerUpdate() {
|
|||
bool is_queued = false;
|
||||
sdl_pumpevents_queued_.compare_exchange_strong(is_queued, true);
|
||||
if (!is_queued) {
|
||||
window_->loop()->Post([this]() {
|
||||
window()->loop()->Post([this]() {
|
||||
SDL_PumpEvents();
|
||||
sdl_pumpevents_queued_ = false;
|
||||
});
|
||||
|
|
|
@ -21,7 +21,11 @@ namespace winkey {
|
|||
WinKeyInputDriver::WinKeyInputDriver(xe::ui::Window* window)
|
||||
: InputDriver(window), packet_number_(1) {
|
||||
// Register a key listener.
|
||||
window_->on_key_down.AddListener([this](ui::KeyEvent* evt) {
|
||||
window->on_key_down.AddListener([this](ui::KeyEvent* evt) {
|
||||
if (!is_active()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
|
||||
KeyEvent key;
|
||||
|
@ -31,7 +35,11 @@ WinKeyInputDriver::WinKeyInputDriver(xe::ui::Window* window)
|
|||
key.repeat_count = evt->repeat_count();
|
||||
key_events_.push(key);
|
||||
});
|
||||
window_->on_key_up.AddListener([this](ui::KeyEvent* evt) {
|
||||
window->on_key_up.AddListener([this](ui::KeyEvent* evt) {
|
||||
if (!is_active()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
|
||||
KeyEvent key;
|
||||
|
@ -88,7 +96,7 @@ X_RESULT WinKeyInputDriver::GetState(uint32_t user_index,
|
|||
int16_t thumb_rx = 0;
|
||||
int16_t thumb_ry = 0;
|
||||
|
||||
if (window_->has_focus()) {
|
||||
if (window()->has_focus() && is_active()) {
|
||||
if (IS_KEY_TOGGLED(VK_CAPITAL) || IS_KEY_DOWN(VK_SHIFT)) {
|
||||
// dpad toggled
|
||||
if (IS_KEY_DOWN('A')) {
|
||||
|
@ -227,6 +235,10 @@ X_RESULT WinKeyInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags,
|
|||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
if (!is_active()) {
|
||||
return X_ERROR_EMPTY;
|
||||
}
|
||||
|
||||
X_RESULT result = X_ERROR_EMPTY;
|
||||
|
||||
uint16_t virtual_key = 0;
|
||||
|
|
|
@ -326,16 +326,20 @@ void KernelState::SetExecutableModule(object_ref<UserModule> module) {
|
|||
// As we run guest callbacks the debugger must be able to suspend us.
|
||||
dispatch_thread_->set_can_debugger_suspend(true);
|
||||
|
||||
auto global_lock = global_critical_region_.AcquireDeferred();
|
||||
while (dispatch_thread_running_) {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
global_lock.lock();
|
||||
if (dispatch_queue_.empty()) {
|
||||
dispatch_cond_.wait(global_lock);
|
||||
if (!dispatch_thread_running_) {
|
||||
global_lock.unlock();
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto fn = std::move(dispatch_queue_.front());
|
||||
dispatch_queue_.pop_front();
|
||||
global_lock.unlock();
|
||||
|
||||
fn();
|
||||
}
|
||||
return 0;
|
||||
|
@ -637,9 +641,8 @@ void KernelState::UnregisterNotifyListener(XNotifyListener* listener) {
|
|||
|
||||
void KernelState::BroadcastNotification(XNotificationID id, uint32_t data) {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
for (auto it = notify_listeners_.begin(); it != notify_listeners_.end();
|
||||
++it) {
|
||||
(*it)->EnqueueNotification(id, data);
|
||||
for (const auto& notify_listener : notify_listeners_) {
|
||||
notify_listener->EnqueueNotification(id, data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -657,6 +660,7 @@ void KernelState::CompleteOverlappedEx(uint32_t overlapped_ptr, X_RESULT result,
|
|||
X_HANDLE event_handle = XOverlappedGetEvent(ptr);
|
||||
if (event_handle) {
|
||||
auto ev = object_table()->LookupObject<XEvent>(event_handle);
|
||||
assert_not_null(ev);
|
||||
if (ev) {
|
||||
ev->Set(0, false);
|
||||
}
|
||||
|
@ -692,24 +696,62 @@ void KernelState::CompleteOverlappedImmediateEx(uint32_t overlapped_ptr,
|
|||
|
||||
void KernelState::CompleteOverlappedDeferred(
|
||||
std::function<void()> completion_callback, uint32_t overlapped_ptr,
|
||||
X_RESULT result) {
|
||||
X_RESULT result, std::function<void()> pre_callback,
|
||||
std::function<void()> post_callback) {
|
||||
CompleteOverlappedDeferredEx(std::move(completion_callback), overlapped_ptr,
|
||||
result, result, 0);
|
||||
result, result, 0, pre_callback, post_callback);
|
||||
}
|
||||
|
||||
void KernelState::CompleteOverlappedDeferredEx(
|
||||
std::function<void()> completion_callback, uint32_t overlapped_ptr,
|
||||
X_RESULT result, uint32_t extended_error, uint32_t length) {
|
||||
X_RESULT result, uint32_t extended_error, uint32_t length,
|
||||
std::function<void()> pre_callback, std::function<void()> post_callback) {
|
||||
CompleteOverlappedDeferredEx(
|
||||
[completion_callback, result, extended_error, length](
|
||||
uint32_t& cb_extended_error, uint32_t& cb_length) -> X_RESULT {
|
||||
completion_callback();
|
||||
cb_extended_error = extended_error;
|
||||
cb_length = length;
|
||||
return result;
|
||||
},
|
||||
overlapped_ptr, pre_callback, post_callback);
|
||||
}
|
||||
|
||||
void KernelState::CompleteOverlappedDeferred(
|
||||
std::function<X_RESULT()> completion_callback, uint32_t overlapped_ptr,
|
||||
std::function<void()> pre_callback, std::function<void()> post_callback) {
|
||||
CompleteOverlappedDeferredEx(
|
||||
[completion_callback](uint32_t& extended_error,
|
||||
uint32_t& length) -> X_RESULT {
|
||||
auto result = completion_callback();
|
||||
extended_error = static_cast<uint32_t>(result);
|
||||
length = 0;
|
||||
return result;
|
||||
},
|
||||
overlapped_ptr, pre_callback, post_callback);
|
||||
}
|
||||
|
||||
void KernelState::CompleteOverlappedDeferredEx(
|
||||
std::function<X_RESULT(uint32_t&, uint32_t&)> completion_callback,
|
||||
uint32_t overlapped_ptr, std::function<void()> pre_callback,
|
||||
std::function<void()> post_callback) {
|
||||
auto ptr = memory()->TranslateVirtual(overlapped_ptr);
|
||||
XOverlappedSetResult(ptr, X_ERROR_IO_PENDING);
|
||||
XOverlappedSetContext(ptr, XThread::GetCurrentThreadHandle());
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
dispatch_queue_.push_back([this, completion_callback, overlapped_ptr, result,
|
||||
extended_error, length]() {
|
||||
dispatch_queue_.push_back([this, completion_callback, overlapped_ptr,
|
||||
pre_callback, post_callback]() {
|
||||
if (pre_callback) {
|
||||
pre_callback();
|
||||
}
|
||||
xe::threading::Sleep(
|
||||
std::chrono::milliseconds(kDeferredOverlappedDelayMillis));
|
||||
completion_callback();
|
||||
uint32_t extended_error, length;
|
||||
auto result = completion_callback(extended_error, length);
|
||||
CompleteOverlappedEx(overlapped_ptr, result, extended_error, length);
|
||||
if (post_callback) {
|
||||
post_callback();
|
||||
}
|
||||
});
|
||||
dispatch_cond_.notify_all();
|
||||
}
|
||||
|
|
|
@ -167,14 +167,29 @@ class KernelState {
|
|||
void CompleteOverlapped(uint32_t overlapped_ptr, X_RESULT result);
|
||||
void CompleteOverlappedEx(uint32_t overlapped_ptr, X_RESULT result,
|
||||
uint32_t extended_error, uint32_t length);
|
||||
|
||||
void CompleteOverlappedImmediate(uint32_t overlapped_ptr, X_RESULT result);
|
||||
void CompleteOverlappedImmediateEx(uint32_t overlapped_ptr, X_RESULT result,
|
||||
uint32_t extended_error, uint32_t length);
|
||||
void CompleteOverlappedDeferred(std::function<void()> completion_callback,
|
||||
uint32_t overlapped_ptr, X_RESULT result);
|
||||
void CompleteOverlappedDeferredEx(std::function<void()> completion_callback,
|
||||
uint32_t overlapped_ptr, X_RESULT result,
|
||||
uint32_t extended_error, uint32_t length);
|
||||
|
||||
void CompleteOverlappedDeferred(
|
||||
std::function<void()> completion_callback, uint32_t overlapped_ptr,
|
||||
X_RESULT result, std::function<void()> pre_callback = nullptr,
|
||||
std::function<void()> post_callback = nullptr);
|
||||
void CompleteOverlappedDeferredEx(
|
||||
std::function<void()> completion_callback, uint32_t overlapped_ptr,
|
||||
X_RESULT result, uint32_t extended_error, uint32_t length,
|
||||
std::function<void()> pre_callback = nullptr,
|
||||
std::function<void()> post_callback = nullptr);
|
||||
|
||||
void CompleteOverlappedDeferred(
|
||||
std::function<X_RESULT()> completion_callback, uint32_t overlapped_ptr,
|
||||
std::function<void()> pre_callback = nullptr,
|
||||
std::function<void()> post_callback = nullptr);
|
||||
void CompleteOverlappedDeferredEx(
|
||||
std::function<X_RESULT(uint32_t&, uint32_t&)> completion_callback,
|
||||
uint32_t overlapped_ptr, std::function<void()> pre_callback = nullptr,
|
||||
std::function<void()> post_callback = nullptr);
|
||||
|
||||
bool Save(ByteStream* stream);
|
||||
bool Restore(ByteStream* stream);
|
||||
|
|
|
@ -37,22 +37,22 @@ void AppManager::RegisterApp(std::unique_ptr<App> app) {
|
|||
apps_.push_back(std::move(app));
|
||||
}
|
||||
|
||||
X_RESULT AppManager::DispatchMessageSync(uint32_t app_id, uint32_t message,
|
||||
uint32_t buffer_ptr,
|
||||
uint32_t buffer_length) {
|
||||
const auto& it = app_lookup_.find(app_id);
|
||||
if (it == app_lookup_.end()) {
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
return it->second->DispatchMessageSync(message, buffer_ptr, buffer_length);
|
||||
}
|
||||
|
||||
X_RESULT AppManager::DispatchMessageAsync(uint32_t app_id, uint32_t message,
|
||||
X_HRESULT AppManager::DispatchMessageSync(uint32_t app_id, uint32_t message,
|
||||
uint32_t buffer_ptr,
|
||||
uint32_t buffer_length) {
|
||||
const auto& it = app_lookup_.find(app_id);
|
||||
if (it == app_lookup_.end()) {
|
||||
return X_ERROR_NOT_FOUND;
|
||||
return X_E_NOTFOUND;
|
||||
}
|
||||
return it->second->DispatchMessageSync(message, buffer_ptr, buffer_length);
|
||||
}
|
||||
|
||||
X_HRESULT AppManager::DispatchMessageAsync(uint32_t app_id, uint32_t message,
|
||||
uint32_t buffer_ptr,
|
||||
uint32_t buffer_length) {
|
||||
const auto& it = app_lookup_.find(app_id);
|
||||
if (it == app_lookup_.end()) {
|
||||
return X_E_NOTFOUND;
|
||||
}
|
||||
return it->second->DispatchMessageSync(message, buffer_ptr, buffer_length);
|
||||
}
|
||||
|
|
|
@ -32,8 +32,8 @@ class App {
|
|||
public:
|
||||
uint32_t app_id() const { return app_id_; }
|
||||
|
||||
virtual X_RESULT DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
||||
uint32_t buffer_length) = 0;
|
||||
virtual X_HRESULT DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
||||
uint32_t buffer_length) = 0;
|
||||
|
||||
virtual ~App() = default;
|
||||
|
||||
|
@ -51,10 +51,10 @@ class AppManager {
|
|||
|
||||
void RegisterApp(std::unique_ptr<App> app);
|
||||
|
||||
X_RESULT DispatchMessageSync(uint32_t app_id, uint32_t message,
|
||||
uint32_t buffer_ptr, uint32_t buffer_length);
|
||||
X_RESULT DispatchMessageAsync(uint32_t app_id, uint32_t message,
|
||||
X_HRESULT DispatchMessageSync(uint32_t app_id, uint32_t message,
|
||||
uint32_t buffer_ptr, uint32_t buffer_length);
|
||||
X_HRESULT DispatchMessageAsync(uint32_t app_id, uint32_t message,
|
||||
uint32_t buffer_ptr, uint32_t buffer_length);
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<App>> apps_;
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/threading.h"
|
||||
#include "xenia/kernel/kernel_state.h"
|
||||
#include "xenia/kernel/xenumerator.h"
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
|
@ -19,11 +21,55 @@ namespace apps {
|
|||
|
||||
XamApp::XamApp(KernelState* kernel_state) : App(kernel_state, 0xFE) {}
|
||||
|
||||
X_RESULT XamApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
||||
uint32_t buffer_length) {
|
||||
X_HRESULT XamApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
||||
uint32_t buffer_length) {
|
||||
// NOTE: buffer_length may be zero or valid.
|
||||
auto buffer = memory_->TranslateVirtual(buffer_ptr);
|
||||
switch (message) {
|
||||
case 0x0002000E: {
|
||||
struct message_data {
|
||||
xe::be<uint32_t> user_index;
|
||||
xe::be<uint32_t> unk_04;
|
||||
xe::be<uint32_t> extra_ptr;
|
||||
xe::be<uint32_t> buffer_ptr;
|
||||
xe::be<uint32_t> buffer_size;
|
||||
xe::be<uint32_t> unk_14;
|
||||
xe::be<uint32_t> length_ptr;
|
||||
xe::be<uint32_t> unk_1C;
|
||||
}* data = reinterpret_cast<message_data*>(buffer);
|
||||
XELOGD(
|
||||
"XamAppEnumerateContentAggregate({}, {:08X}, {:08X}, {:08X}, {}, "
|
||||
"{:08X}, {:08X}, {:08X})",
|
||||
(uint32_t)data->user_index, (uint32_t)data->unk_04,
|
||||
(uint32_t)data->extra_ptr, (uint32_t)data->buffer_ptr,
|
||||
(uint32_t)data->buffer_size, (uint32_t)data->unk_14,
|
||||
(uint32_t)data->length_ptr, (uint32_t)data->unk_1C);
|
||||
auto extra = memory_->TranslateVirtual<X_KENUMERATOR_CONTENT_AGGREGATE*>(
|
||||
data->extra_ptr);
|
||||
auto buffer = memory_->TranslateVirtual(data->buffer_ptr);
|
||||
auto e = kernel_state_->object_table()->LookupObject<XEnumerator>(
|
||||
extra->handle);
|
||||
if (!e || !buffer || !extra) {
|
||||
return X_E_INVALIDARG;
|
||||
}
|
||||
assert_true(extra->magic == 'XEN\0');
|
||||
if (data->buffer_size) {
|
||||
std::memset(buffer, 0, data->buffer_size);
|
||||
}
|
||||
if (e->WriteItem(buffer)) {
|
||||
auto content_data = reinterpret_cast<XCONTENT_AGGREGATE_DATA*>(buffer);
|
||||
// TODO(gibbed): WTF?
|
||||
*reinterpret_cast<be<uint32_t>*>(&buffer[0x140]) =
|
||||
content_data->title_id;
|
||||
if (data->length_ptr) {
|
||||
auto length_ptr =
|
||||
memory_->TranslateVirtual<be<uint32_t>*>(data->length_ptr);
|
||||
*length_ptr = 1;
|
||||
}
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
return X_E_NO_MORE_FILES;
|
||||
}
|
||||
case 0x00020021: {
|
||||
struct message_data {
|
||||
char unk_00[64];
|
||||
|
@ -37,11 +83,11 @@ X_RESULT XamApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
|||
XELOGD("XamApp(0x00020021)('{}', {:08X}, {:08X}, {:08X})", data->unk_00,
|
||||
(uint32_t)data->unk_40, (uint32_t)data->unk_44,
|
||||
(uint32_t)data->unk_48);
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
case 0x00021012: {
|
||||
XELOGD("XamApp(0x00021012)");
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
case 0x00022005: {
|
||||
struct message_data {
|
||||
|
@ -53,14 +99,14 @@ X_RESULT XamApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
|||
auto adr = *unk;
|
||||
XELOGD("XamApp(0x00022005)(%.8X, %.8X)", (uint32_t)data->unk_00,
|
||||
(uint32_t)data->unk_04);
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
}
|
||||
XELOGE(
|
||||
"Unimplemented XAM message app={:08X}, msg={:08X}, arg1={:08X}, "
|
||||
"arg2={:08X}",
|
||||
app_id(), message, buffer_ptr, buffer_length);
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
return X_E_FAIL;
|
||||
}
|
||||
|
||||
} // namespace apps
|
||||
|
|
|
@ -22,8 +22,8 @@ class XamApp : public App {
|
|||
public:
|
||||
explicit XamApp(KernelState* kernel_state);
|
||||
|
||||
X_RESULT DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
||||
uint32_t buffer_length) override;
|
||||
X_HRESULT DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
||||
uint32_t buffer_length) override;
|
||||
};
|
||||
|
||||
} // namespace apps
|
||||
|
|
|
@ -21,8 +21,8 @@ XgiApp::XgiApp(KernelState* kernel_state) : App(kernel_state, 0xFB) {}
|
|||
|
||||
// http://mb.mirage.org/bugzilla/xliveless/main.c
|
||||
|
||||
X_RESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
||||
uint32_t buffer_length) {
|
||||
X_HRESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
||||
uint32_t buffer_length) {
|
||||
// NOTE: buffer_length may be zero or valid.
|
||||
auto buffer = memory_->TranslateVirtual(buffer_ptr);
|
||||
switch (message) {
|
||||
|
@ -38,7 +38,7 @@ X_RESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
|||
uint32_t context_value = xe::load_and_swap<uint32_t>(buffer + 20);
|
||||
XELOGD("XGIUserSetContextEx({:08X}, {:08X}, {:08X})", user_index,
|
||||
context_id, context_value);
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
case 0x000B0007: {
|
||||
uint32_t user_index = xe::load_and_swap<uint32_t>(buffer + 0);
|
||||
|
@ -47,7 +47,7 @@ X_RESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
|||
uint32_t value_ptr = xe::load_and_swap<uint32_t>(buffer + 24);
|
||||
XELOGD("XGIUserSetPropertyEx({:08X}, {:08X}, {}, {:08X})", user_index,
|
||||
property_id, value_size, value_ptr);
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
case 0x000B0008: {
|
||||
assert_true(!buffer_length || buffer_length == 8);
|
||||
|
@ -55,7 +55,7 @@ X_RESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
|||
uint32_t achievements_ptr = xe::load_and_swap<uint32_t>(buffer + 4);
|
||||
XELOGD("XGIUserWriteAchievements({:08X}, {:08X})", achievement_count,
|
||||
achievements_ptr);
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
case 0x000B0010: {
|
||||
assert_true(!buffer_length || buffer_length == 28);
|
||||
|
@ -77,7 +77,7 @@ X_RESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
|||
"{:08X})",
|
||||
session_ptr, flags, num_slots_public, num_slots_private, user_xuid,
|
||||
session_info_ptr, nonce_ptr);
|
||||
return X_STATUS_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
case 0x000B0011: {
|
||||
// TODO(DrChat): Figure out what this is again
|
||||
|
@ -93,7 +93,7 @@ X_RESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
|||
assert_zero(unk_0);
|
||||
XELOGD("XGISessionJoinLocal({:08X}, {}, {}, {:08X}, {:08X})", session_ptr,
|
||||
user_count, unk_0, user_index_array, private_slots_array);
|
||||
return X_STATUS_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
case 0x000B0041: {
|
||||
assert_true(!buffer_length || buffer_length == 32);
|
||||
|
@ -110,18 +110,18 @@ X_RESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
|||
if (context) {
|
||||
xe::store_and_swap<uint32_t>(context + 4, value);
|
||||
}
|
||||
return X_ERROR_FUNCTION_FAILED;
|
||||
return X_E_FAIL;
|
||||
}
|
||||
case 0x000B0071: {
|
||||
XELOGD("XGI 0x000B0071, unimplemented");
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
}
|
||||
XELOGE(
|
||||
"Unimplemented XGI message app={:08X}, msg={:08X}, arg1={:08X}, "
|
||||
"arg2={:08X}",
|
||||
app_id(), message, buffer_ptr, buffer_length);
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
return X_E_FAIL;
|
||||
}
|
||||
|
||||
} // namespace apps
|
||||
|
|
|
@ -22,8 +22,8 @@ class XgiApp : public App {
|
|||
public:
|
||||
explicit XgiApp(KernelState* kernel_state);
|
||||
|
||||
X_RESULT DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
||||
uint32_t buffer_length) override;
|
||||
X_HRESULT DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
||||
uint32_t buffer_length) override;
|
||||
};
|
||||
|
||||
} // namespace apps
|
||||
|
|
|
@ -22,9 +22,9 @@ XLiveBaseApp::XLiveBaseApp(KernelState* kernel_state)
|
|||
|
||||
// http://mb.mirage.org/bugzilla/xliveless/main.c
|
||||
|
||||
X_RESULT XLiveBaseApp::DispatchMessageSync(uint32_t message,
|
||||
uint32_t buffer_ptr,
|
||||
uint32_t buffer_length) {
|
||||
X_HRESULT XLiveBaseApp::DispatchMessageSync(uint32_t message,
|
||||
uint32_t buffer_ptr,
|
||||
uint32_t buffer_length) {
|
||||
// NOTE: buffer_length may be zero or valid.
|
||||
auto buffer = memory_->TranslateVirtual(buffer_ptr);
|
||||
switch (message) {
|
||||
|
@ -33,13 +33,13 @@ X_RESULT XLiveBaseApp::DispatchMessageSync(uint32_t message,
|
|||
assert_true(!buffer_length || buffer_length == 4);
|
||||
XELOGD("XLiveBaseGetLogonId({:08X})", buffer_ptr);
|
||||
xe::store_and_swap<uint32_t>(buffer + 0, 1); // ?
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
case 0x00058006: {
|
||||
assert_true(!buffer_length || buffer_length == 4);
|
||||
XELOGD("XLiveBaseGetNatType({:08X})", buffer_ptr);
|
||||
xe::store_and_swap<uint32_t>(buffer + 0, 1); // XONLINE_NAT_OPEN
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
case 0x00058020: {
|
||||
// 0x00058004 is called right before this.
|
||||
|
@ -48,12 +48,12 @@ X_RESULT XLiveBaseApp::DispatchMessageSync(uint32_t message,
|
|||
// buffer_length seems to be the same ptr sent to 0x00058004.
|
||||
XELOGD("XLiveBaseFriendsCreateEnumerator({:08X}, {:08X}) unimplemented",
|
||||
buffer_ptr, buffer_length);
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
return X_E_FAIL;
|
||||
}
|
||||
case 0x00058023: {
|
||||
XELOGD("XliveBaseUnk58023({:08X}, {:08X}) unimplemented", buffer_ptr,
|
||||
buffer_length);
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
return X_E_FAIL;
|
||||
}
|
||||
case 0x00058046: {
|
||||
// Required to be successful for Forza 4 to detect signed-in profile
|
||||
|
@ -61,14 +61,14 @@ X_RESULT XLiveBaseApp::DispatchMessageSync(uint32_t message,
|
|||
// input
|
||||
XELOGD("XLiveBaseUnk58046({:08X}, {:08X}) unimplemented", buffer_ptr,
|
||||
buffer_length);
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
}
|
||||
XELOGE(
|
||||
"Unimplemented XLIVEBASE message app={:08X}, msg={:08X}, arg1={:08X}, "
|
||||
"arg2={:08X}",
|
||||
app_id(), message, buffer_ptr, buffer_length);
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
return X_E_FAIL;
|
||||
}
|
||||
|
||||
} // namespace apps
|
||||
|
|
|
@ -22,8 +22,8 @@ class XLiveBaseApp : public App {
|
|||
public:
|
||||
explicit XLiveBaseApp(KernelState* kernel_state);
|
||||
|
||||
X_RESULT DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
||||
uint32_t buffer_length) override;
|
||||
X_HRESULT DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
||||
uint32_t buffer_length) override;
|
||||
};
|
||||
|
||||
} // namespace apps
|
||||
|
|
|
@ -30,7 +30,7 @@ XmpApp::XmpApp(KernelState* kernel_state)
|
|||
next_playlist_handle_(1),
|
||||
next_song_handle_(1) {}
|
||||
|
||||
X_RESULT XmpApp::XMPGetStatus(uint32_t state_ptr) {
|
||||
X_HRESULT XmpApp::XMPGetStatus(uint32_t state_ptr) {
|
||||
// Some stupid games will hammer this on a thread - induce a delay
|
||||
// here to keep from starving real threads.
|
||||
xe::threading::Sleep(std::chrono::milliseconds(1));
|
||||
|
@ -38,15 +38,13 @@ X_RESULT XmpApp::XMPGetStatus(uint32_t state_ptr) {
|
|||
XELOGD("XMPGetStatus({:08X})", state_ptr);
|
||||
xe::store_and_swap<uint32_t>(memory_->TranslateVirtual(state_ptr),
|
||||
static_cast<uint32_t>(state_));
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
|
||||
X_RESULT XmpApp::XMPCreateTitlePlaylist(uint32_t songs_ptr, uint32_t song_count,
|
||||
uint32_t playlist_name_ptr,
|
||||
const std::u16string& playlist_name,
|
||||
uint32_t flags,
|
||||
uint32_t out_song_handles,
|
||||
uint32_t out_playlist_handle) {
|
||||
X_HRESULT XmpApp::XMPCreateTitlePlaylist(
|
||||
uint32_t songs_ptr, uint32_t song_count, uint32_t playlist_name_ptr,
|
||||
const std::u16string& playlist_name, uint32_t flags,
|
||||
uint32_t out_song_handles, uint32_t out_playlist_handle) {
|
||||
XELOGD(
|
||||
"XMPCreateTitlePlaylist({:08X}, {:08X}, {:08X}({}), {:08X}, {:08X}, "
|
||||
"{:08X})",
|
||||
|
@ -96,16 +94,16 @@ X_RESULT XmpApp::XMPCreateTitlePlaylist(uint32_t songs_ptr, uint32_t song_count,
|
|||
auto global_lock = global_critical_region_.Acquire();
|
||||
playlists_.insert({playlist->handle, playlist.get()});
|
||||
playlist.release();
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
|
||||
X_RESULT XmpApp::XMPDeleteTitlePlaylist(uint32_t playlist_handle) {
|
||||
X_HRESULT XmpApp::XMPDeleteTitlePlaylist(uint32_t playlist_handle) {
|
||||
XELOGD("XMPDeleteTitlePlaylist({:08X})", playlist_handle);
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
auto it = playlists_.find(playlist_handle);
|
||||
if (it == playlists_.end()) {
|
||||
XELOGE("Playlist {:08X} not found", playlist_handle);
|
||||
return X_ERROR_NOT_FOUND;
|
||||
return X_E_NOTFOUND;
|
||||
}
|
||||
auto playlist = it->second;
|
||||
if (playlist == active_playlist_) {
|
||||
|
@ -113,11 +111,11 @@ X_RESULT XmpApp::XMPDeleteTitlePlaylist(uint32_t playlist_handle) {
|
|||
}
|
||||
playlists_.erase(it);
|
||||
delete playlist;
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
|
||||
X_RESULT XmpApp::XMPPlayTitlePlaylist(uint32_t playlist_handle,
|
||||
uint32_t song_handle) {
|
||||
X_HRESULT XmpApp::XMPPlayTitlePlaylist(uint32_t playlist_handle,
|
||||
uint32_t song_handle) {
|
||||
XELOGD("XMPPlayTitlePlaylist({:08X}, {:08X})", playlist_handle, song_handle);
|
||||
Playlist* playlist = nullptr;
|
||||
{
|
||||
|
@ -125,7 +123,7 @@ X_RESULT XmpApp::XMPPlayTitlePlaylist(uint32_t playlist_handle,
|
|||
auto it = playlists_.find(playlist_handle);
|
||||
if (it == playlists_.end()) {
|
||||
XELOGE("Playlist {:08X} not found", playlist_handle);
|
||||
return X_ERROR_NOT_FOUND;
|
||||
return X_E_NOTFOUND;
|
||||
}
|
||||
playlist = it->second;
|
||||
}
|
||||
|
@ -133,7 +131,7 @@ X_RESULT XmpApp::XMPPlayTitlePlaylist(uint32_t playlist_handle,
|
|||
if (disabled_) {
|
||||
// Ignored because we aren't enabled?
|
||||
XELOGW("Ignoring XMPPlayTitlePlaylist because disabled");
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
|
||||
// Start playlist?
|
||||
|
@ -143,53 +141,53 @@ X_RESULT XmpApp::XMPPlayTitlePlaylist(uint32_t playlist_handle,
|
|||
state_ = State::kPlaying;
|
||||
OnStateChanged();
|
||||
kernel_state_->BroadcastNotification(kMsgPlaybackBehaviorChanged, 1);
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
|
||||
X_RESULT XmpApp::XMPContinue() {
|
||||
X_HRESULT XmpApp::XMPContinue() {
|
||||
XELOGD("XMPContinue()");
|
||||
if (state_ == State::kPaused) {
|
||||
state_ = State::kPlaying;
|
||||
}
|
||||
OnStateChanged();
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
|
||||
X_RESULT XmpApp::XMPStop(uint32_t unk) {
|
||||
X_HRESULT XmpApp::XMPStop(uint32_t unk) {
|
||||
assert_zero(unk);
|
||||
XELOGD("XMPStop({:08X})", unk);
|
||||
active_playlist_ = nullptr; // ?
|
||||
active_song_index_ = 0;
|
||||
state_ = State::kIdle;
|
||||
OnStateChanged();
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
|
||||
X_RESULT XmpApp::XMPPause() {
|
||||
X_HRESULT XmpApp::XMPPause() {
|
||||
XELOGD("XMPPause()");
|
||||
if (state_ == State::kPlaying) {
|
||||
state_ = State::kPaused;
|
||||
}
|
||||
OnStateChanged();
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
|
||||
X_RESULT XmpApp::XMPNext() {
|
||||
X_HRESULT XmpApp::XMPNext() {
|
||||
XELOGD("XMPNext()");
|
||||
if (!active_playlist_) {
|
||||
return X_ERROR_NOT_FOUND;
|
||||
return X_E_NOTFOUND;
|
||||
}
|
||||
state_ = State::kPlaying;
|
||||
active_song_index_ =
|
||||
(active_song_index_ + 1) % active_playlist_->songs.size();
|
||||
OnStateChanged();
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
|
||||
X_RESULT XmpApp::XMPPrevious() {
|
||||
X_HRESULT XmpApp::XMPPrevious() {
|
||||
XELOGD("XMPPrevious()");
|
||||
if (!active_playlist_) {
|
||||
return X_ERROR_NOT_FOUND;
|
||||
return X_E_NOTFOUND;
|
||||
}
|
||||
state_ = State::kPlaying;
|
||||
if (!active_song_index_) {
|
||||
|
@ -198,7 +196,7 @@ X_RESULT XmpApp::XMPPrevious() {
|
|||
--active_song_index_;
|
||||
}
|
||||
OnStateChanged();
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
|
||||
void XmpApp::OnStateChanged() {
|
||||
|
@ -206,8 +204,8 @@ void XmpApp::OnStateChanged() {
|
|||
static_cast<uint32_t>(state_));
|
||||
}
|
||||
|
||||
X_RESULT XmpApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
||||
uint32_t buffer_length) {
|
||||
X_HRESULT XmpApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
||||
uint32_t buffer_length) {
|
||||
// NOTE: buffer_length may be zero or valid.
|
||||
auto buffer = memory_->TranslateVirtual(buffer_ptr);
|
||||
switch (message) {
|
||||
|
@ -271,7 +269,7 @@ X_RESULT XmpApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
|||
repeat_mode_ = static_cast<RepeatMode>(uint32_t(args->repeat_mode));
|
||||
unknown_flags_ = args->flags;
|
||||
kernel_state_->BroadcastNotification(kMsgPlaybackBehaviorChanged, 0);
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
case 0x00070009: {
|
||||
assert_true(!buffer_length || buffer_length == 8);
|
||||
|
@ -293,7 +291,7 @@ X_RESULT XmpApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
|||
XELOGD("XMPGetVolume({:08X})", uint32_t(args->volume_ptr));
|
||||
xe::store_and_swap<float>(memory_->TranslateVirtual(args->volume_ptr),
|
||||
volume_);
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
case 0x0007000C: {
|
||||
assert_true(!buffer_length || buffer_length == 8);
|
||||
|
@ -306,7 +304,7 @@ X_RESULT XmpApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
|||
assert_true(args->xmp_client == 0x00000002);
|
||||
XELOGD("XMPSetVolume({:g})", float(args->value));
|
||||
volume_ = args->value;
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
case 0x0007000D: {
|
||||
assert_true(!buffer_length || buffer_length == 36);
|
||||
|
@ -358,7 +356,7 @@ X_RESULT XmpApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
|||
XELOGE("XMPGetInfo?({:08X}, {:08X})", uint32_t(args->unk_ptr),
|
||||
uint32_t(args->info_ptr));
|
||||
if (!active_playlist_) {
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
return X_E_FAIL;
|
||||
}
|
||||
auto& song = active_playlist_->songs[active_song_index_];
|
||||
xe::store_and_swap<uint32_t>(info + 0, song->handle);
|
||||
|
@ -372,7 +370,7 @@ X_RESULT XmpApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
|||
xe::store_and_swap<uint32_t>(info + 4 + 572 + 204, song->duration_ms);
|
||||
xe::store_and_swap<uint32_t>(info + 4 + 572 + 208,
|
||||
static_cast<uint32_t>(song->format));
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
case 0x00070013: {
|
||||
assert_true(!buffer_length || buffer_length == 8);
|
||||
|
@ -409,7 +407,7 @@ X_RESULT XmpApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
|||
XMPStop(0);
|
||||
}
|
||||
kernel_state_->BroadcastNotification(kMsgDisableChanged, disabled_);
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
case 0x0007001B: {
|
||||
// XMPGetPlaybackController
|
||||
|
@ -432,7 +430,7 @@ X_RESULT XmpApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
|||
|
||||
// Atrain spawns a thread 82437FD0 to call this in a tight loop forever.
|
||||
xe::threading::Sleep(std::chrono::milliseconds(10));
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
case 0x00070029: {
|
||||
// XMPGetPlaybackBehavior
|
||||
|
@ -464,7 +462,7 @@ X_RESULT XmpApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
|||
xe::store_and_swap<uint32_t>(memory_->TranslateVirtual(args->unk3_ptr),
|
||||
unknown_flags_);
|
||||
}
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
case 0x0007002E: {
|
||||
assert_true(!buffer_length || buffer_length == 12);
|
||||
|
@ -482,20 +480,20 @@ X_RESULT XmpApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
|||
// We don't use the storage, so just fudge the number.
|
||||
xe::store_and_swap<uint32_t>(memory_->TranslateVirtual(args->size_ptr),
|
||||
4 + uint32_t(args->song_count) * 128);
|
||||
return X_ERROR_SUCCESS;
|
||||
return X_E_SUCCESS;
|
||||
}
|
||||
case 0x0007003D: {
|
||||
// XMPCaptureOutput - not sure how this works :/
|
||||
XELOGD("XMPCaptureOutput(...)");
|
||||
assert_always("XMP output not unimplemented");
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
return X_E_FAIL;
|
||||
}
|
||||
}
|
||||
XELOGE(
|
||||
"Unimplemented XMP message app={:08X}, msg={:08X}, arg1={:08X}, "
|
||||
"arg2={:08X}",
|
||||
app_id(), message, buffer_ptr, buffer_length);
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
return X_E_FAIL;
|
||||
}
|
||||
|
||||
} // namespace apps
|
||||
|
|
|
@ -68,23 +68,24 @@ class XmpApp : public App {
|
|||
|
||||
explicit XmpApp(KernelState* kernel_state);
|
||||
|
||||
X_RESULT XMPGetStatus(uint32_t status_ptr);
|
||||
X_HRESULT XMPGetStatus(uint32_t status_ptr);
|
||||
|
||||
X_RESULT XMPCreateTitlePlaylist(uint32_t songs_ptr, uint32_t song_count,
|
||||
uint32_t playlist_name_ptr,
|
||||
const std::u16string& playlist_name,
|
||||
uint32_t flags, uint32_t out_song_handles,
|
||||
uint32_t out_playlist_handle);
|
||||
X_RESULT XMPDeleteTitlePlaylist(uint32_t playlist_handle);
|
||||
X_RESULT XMPPlayTitlePlaylist(uint32_t playlist_handle, uint32_t song_handle);
|
||||
X_RESULT XMPContinue();
|
||||
X_RESULT XMPStop(uint32_t unk);
|
||||
X_RESULT XMPPause();
|
||||
X_RESULT XMPNext();
|
||||
X_RESULT XMPPrevious();
|
||||
X_HRESULT XMPCreateTitlePlaylist(uint32_t songs_ptr, uint32_t song_count,
|
||||
uint32_t playlist_name_ptr,
|
||||
const std::u16string& playlist_name,
|
||||
uint32_t flags, uint32_t out_song_handles,
|
||||
uint32_t out_playlist_handle);
|
||||
X_HRESULT XMPDeleteTitlePlaylist(uint32_t playlist_handle);
|
||||
X_HRESULT XMPPlayTitlePlaylist(uint32_t playlist_handle,
|
||||
uint32_t song_handle);
|
||||
X_HRESULT XMPContinue();
|
||||
X_HRESULT XMPStop(uint32_t unk);
|
||||
X_HRESULT XMPPause();
|
||||
X_HRESULT XMPNext();
|
||||
X_HRESULT XMPPrevious();
|
||||
|
||||
X_RESULT DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
||||
uint32_t buffer_length) override;
|
||||
X_HRESULT DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
||||
uint32_t buffer_length) override;
|
||||
|
||||
private:
|
||||
static const uint32_t kMsgStateChanged = 0x0A000001;
|
||||
|
|
|
@ -30,7 +30,7 @@ static int content_device_id_ = 0;
|
|||
|
||||
ContentPackage::ContentPackage(KernelState* kernel_state,
|
||||
const std::string_view root_name,
|
||||
const XCONTENT_DATA& data,
|
||||
const ContentData& data,
|
||||
const std::filesystem::path& package_path)
|
||||
: kernel_state_(kernel_state), root_name_(root_name) {
|
||||
device_path_ = fmt::format("\\Device\\Content\\{0}\\", ++content_device_id_);
|
||||
|
@ -88,16 +88,16 @@ std::filesystem::path ContentManager::ResolvePackageRoot(
|
|||
}
|
||||
|
||||
std::filesystem::path ContentManager::ResolvePackagePath(
|
||||
const XCONTENT_DATA& data) {
|
||||
const ContentData& data) {
|
||||
// Content path:
|
||||
// content_root/title_id/type_name/data_file_name/
|
||||
auto package_root = ResolvePackageRoot(data.content_type);
|
||||
return package_root / xe::to_path(data.file_name);
|
||||
}
|
||||
|
||||
std::vector<XCONTENT_DATA> ContentManager::ListContent(uint32_t device_id,
|
||||
uint32_t content_type) {
|
||||
std::vector<XCONTENT_DATA> result;
|
||||
std::vector<ContentData> ContentManager::ListContent(uint32_t device_id,
|
||||
uint32_t content_type) {
|
||||
std::vector<ContentData> result;
|
||||
|
||||
// Search path:
|
||||
// content_root/title_id/type_name/*
|
||||
|
@ -108,7 +108,7 @@ std::vector<XCONTENT_DATA> ContentManager::ListContent(uint32_t device_id,
|
|||
// Directories only.
|
||||
continue;
|
||||
}
|
||||
XCONTENT_DATA content_data;
|
||||
ContentData content_data;
|
||||
content_data.device_id = device_id;
|
||||
content_data.content_type = content_type;
|
||||
content_data.display_name = xe::path_to_utf16(file_info.name);
|
||||
|
@ -120,7 +120,7 @@ std::vector<XCONTENT_DATA> ContentManager::ListContent(uint32_t device_id,
|
|||
}
|
||||
|
||||
std::unique_ptr<ContentPackage> ContentManager::ResolvePackage(
|
||||
const std::string_view root_name, const XCONTENT_DATA& data) {
|
||||
const std::string_view root_name, const ContentData& data) {
|
||||
auto package_path = ResolvePackagePath(data);
|
||||
if (!std::filesystem::exists(package_path)) {
|
||||
return nullptr;
|
||||
|
@ -133,13 +133,13 @@ std::unique_ptr<ContentPackage> ContentManager::ResolvePackage(
|
|||
return package;
|
||||
}
|
||||
|
||||
bool ContentManager::ContentExists(const XCONTENT_DATA& data) {
|
||||
bool ContentManager::ContentExists(const ContentData& data) {
|
||||
auto path = ResolvePackagePath(data);
|
||||
return std::filesystem::exists(path);
|
||||
}
|
||||
|
||||
X_RESULT ContentManager::CreateContent(const std::string_view root_name,
|
||||
const XCONTENT_DATA& data) {
|
||||
const ContentData& data) {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
|
||||
if (open_packages_.count(string_key(root_name))) {
|
||||
|
@ -166,7 +166,7 @@ X_RESULT ContentManager::CreateContent(const std::string_view root_name,
|
|||
}
|
||||
|
||||
X_RESULT ContentManager::OpenContent(const std::string_view root_name,
|
||||
const XCONTENT_DATA& data) {
|
||||
const ContentData& data) {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
|
||||
if (open_packages_.count(string_key(root_name))) {
|
||||
|
@ -204,7 +204,7 @@ X_RESULT ContentManager::CloseContent(const std::string_view root_name) {
|
|||
return X_ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
X_RESULT ContentManager::GetContentThumbnail(const XCONTENT_DATA& data,
|
||||
X_RESULT ContentManager::GetContentThumbnail(const ContentData& data,
|
||||
std::vector<uint8_t>* buffer) {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
auto package_path = ResolvePackagePath(data);
|
||||
|
@ -223,7 +223,7 @@ X_RESULT ContentManager::GetContentThumbnail(const XCONTENT_DATA& data,
|
|||
}
|
||||
}
|
||||
|
||||
X_RESULT ContentManager::SetContentThumbnail(const XCONTENT_DATA& data,
|
||||
X_RESULT ContentManager::SetContentThumbnail(const ContentData& data,
|
||||
std::vector<uint8_t> buffer) {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
auto package_path = ResolvePackagePath(data);
|
||||
|
@ -239,7 +239,7 @@ X_RESULT ContentManager::SetContentThumbnail(const XCONTENT_DATA& data,
|
|||
}
|
||||
}
|
||||
|
||||
X_RESULT ContentManager::DeleteContent(const XCONTENT_DATA& data) {
|
||||
X_RESULT ContentManager::DeleteContent(const ContentData& data) {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
|
||||
auto package_path = ResolvePackagePath(data);
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "xenia/base/memory.h"
|
||||
#include "xenia/base/mutex.h"
|
||||
#include "xenia/base/string_key.h"
|
||||
#include "xenia/base/string_util.h"
|
||||
#include "xenia/xbox.h"
|
||||
|
||||
namespace xe {
|
||||
|
@ -31,32 +32,91 @@ namespace kernel {
|
|||
namespace xam {
|
||||
|
||||
struct XCONTENT_DATA {
|
||||
static const size_t kSize = 4 + 4 + 128 * 2 + 42 + 2; // = 306 + 2b padding
|
||||
be<uint32_t> device_id;
|
||||
be<uint32_t> content_type;
|
||||
union {
|
||||
be<uint16_t> display_name[128];
|
||||
char16_t display_name_chars[128];
|
||||
};
|
||||
char file_name[42];
|
||||
uint8_t padding[2];
|
||||
};
|
||||
static_assert_size(XCONTENT_DATA, 308);
|
||||
|
||||
struct XCONTENT_AGGREGATE_DATA {
|
||||
be<uint32_t> device_id;
|
||||
be<uint32_t> content_type;
|
||||
union {
|
||||
be<uint16_t> display_name[128];
|
||||
char16_t display_name_chars[128];
|
||||
};
|
||||
char file_name[42];
|
||||
uint8_t padding[2];
|
||||
be<uint32_t> title_id;
|
||||
};
|
||||
static_assert_size(XCONTENT_AGGREGATE_DATA, 312);
|
||||
|
||||
struct ContentData {
|
||||
uint32_t device_id;
|
||||
uint32_t content_type;
|
||||
std::u16string display_name; // 128 chars
|
||||
std::u16string display_name;
|
||||
std::string file_name;
|
||||
|
||||
XCONTENT_DATA() = default;
|
||||
explicit XCONTENT_DATA(const uint8_t* ptr) {
|
||||
device_id = xe::load_and_swap<uint32_t>(ptr + 0);
|
||||
content_type = xe::load_and_swap<uint32_t>(ptr + 4);
|
||||
display_name = xe::load_and_swap<std::u16string>(ptr + 8);
|
||||
file_name = xe::load_and_swap<std::string>(ptr + 8 + 128 * 2);
|
||||
ContentData() = default;
|
||||
|
||||
explicit ContentData(const XCONTENT_DATA& data) {
|
||||
device_id = data.device_id;
|
||||
content_type = data.content_type;
|
||||
display_name = xe::load_and_swap<std::u16string>(data.display_name);
|
||||
file_name = xe::load_and_swap<std::string>(data.file_name);
|
||||
}
|
||||
|
||||
void Write(uint8_t* ptr) {
|
||||
xe::store_and_swap<uint32_t>(ptr + 0, device_id);
|
||||
xe::store_and_swap<uint32_t>(ptr + 4, content_type);
|
||||
xe::store_and_swap<std::u16string>(ptr + 8, display_name);
|
||||
xe::store_and_swap<std::string>(ptr + 8 + 128 * 2, file_name);
|
||||
void Write(XCONTENT_DATA* data) const {
|
||||
data->device_id = device_id;
|
||||
data->content_type = content_type;
|
||||
xe::string_util::copy_and_swap_truncating(
|
||||
data->display_name_chars, display_name,
|
||||
xe::countof(data->display_name_chars));
|
||||
xe::string_util::copy_maybe_truncating<
|
||||
string_util::Safety::IKnowWhatIAmDoing>(data->file_name, file_name,
|
||||
xe::countof(data->file_name));
|
||||
}
|
||||
};
|
||||
|
||||
struct ContentAggregateData {
|
||||
uint32_t device_id;
|
||||
uint32_t content_type;
|
||||
std::u16string display_name;
|
||||
std::string file_name;
|
||||
uint32_t title_id;
|
||||
|
||||
ContentAggregateData() = default;
|
||||
|
||||
explicit ContentAggregateData(const XCONTENT_AGGREGATE_DATA& data) {
|
||||
device_id = data.device_id;
|
||||
content_type = data.content_type;
|
||||
display_name = xe::load_and_swap<std::u16string>(data.display_name);
|
||||
file_name = xe::load_and_swap<std::string>(data.file_name);
|
||||
title_id = data.title_id;
|
||||
}
|
||||
|
||||
void Write(XCONTENT_AGGREGATE_DATA* data) const {
|
||||
data->device_id = device_id;
|
||||
data->content_type = content_type;
|
||||
xe::string_util::copy_and_swap_truncating(
|
||||
data->display_name_chars, display_name,
|
||||
xe::countof(data->display_name_chars));
|
||||
xe::string_util::copy_maybe_truncating<
|
||||
string_util::Safety::IKnowWhatIAmDoing>(data->file_name, file_name,
|
||||
xe::countof(data->file_name));
|
||||
data->title_id = title_id;
|
||||
}
|
||||
};
|
||||
|
||||
class ContentPackage {
|
||||
public:
|
||||
ContentPackage(KernelState* kernel_state, const std::string_view root_name,
|
||||
const XCONTENT_DATA& data,
|
||||
const ContentData& data,
|
||||
const std::filesystem::path& package_path);
|
||||
~ContentPackage();
|
||||
|
||||
|
@ -72,28 +132,28 @@ class ContentManager {
|
|||
const std::filesystem::path& root_path);
|
||||
~ContentManager();
|
||||
|
||||
std::vector<XCONTENT_DATA> ListContent(uint32_t device_id,
|
||||
uint32_t content_type);
|
||||
std::vector<ContentData> ListContent(uint32_t device_id,
|
||||
uint32_t content_type);
|
||||
|
||||
std::unique_ptr<ContentPackage> ResolvePackage(
|
||||
const std::string_view root_name, const XCONTENT_DATA& data);
|
||||
const std::string_view root_name, const ContentData& data);
|
||||
|
||||
bool ContentExists(const XCONTENT_DATA& data);
|
||||
bool ContentExists(const ContentData& data);
|
||||
X_RESULT CreateContent(const std::string_view root_name,
|
||||
const XCONTENT_DATA& data);
|
||||
const ContentData& data);
|
||||
X_RESULT OpenContent(const std::string_view root_name,
|
||||
const XCONTENT_DATA& data);
|
||||
const ContentData& data);
|
||||
X_RESULT CloseContent(const std::string_view root_name);
|
||||
X_RESULT GetContentThumbnail(const XCONTENT_DATA& data,
|
||||
X_RESULT GetContentThumbnail(const ContentData& data,
|
||||
std::vector<uint8_t>* buffer);
|
||||
X_RESULT SetContentThumbnail(const XCONTENT_DATA& data,
|
||||
X_RESULT SetContentThumbnail(const ContentData& data,
|
||||
std::vector<uint8_t> buffer);
|
||||
X_RESULT DeleteContent(const XCONTENT_DATA& data);
|
||||
X_RESULT DeleteContent(const ContentData& data);
|
||||
std::filesystem::path ResolveGameUserContentPath();
|
||||
|
||||
private:
|
||||
std::filesystem::path ResolvePackageRoot(uint32_t content_type);
|
||||
std::filesystem::path ResolvePackagePath(const XCONTENT_DATA& data);
|
||||
std::filesystem::path ResolvePackagePath(const ContentData& data);
|
||||
|
||||
KernelState* kernel_state_;
|
||||
std::filesystem::path root_path_;
|
||||
|
|
|
@ -9,8 +9,10 @@
|
|||
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/base/string_util.h"
|
||||
#include "xenia/kernel/kernel_state.h"
|
||||
#include "xenia/kernel/util/shim_utils.h"
|
||||
#include "xenia/kernel/xam/xam_content_device.h"
|
||||
#include "xenia/kernel/xam/xam_private.h"
|
||||
#include "xenia/kernel/xenumerator.h"
|
||||
#include "xenia/xbox.h"
|
||||
|
@ -29,33 +31,6 @@ namespace xe {
|
|||
namespace kernel {
|
||||
namespace xam {
|
||||
|
||||
struct DeviceInfo {
|
||||
uint32_t device_id;
|
||||
uint32_t device_type;
|
||||
uint64_t total_bytes;
|
||||
uint64_t free_bytes;
|
||||
char16_t name[28];
|
||||
};
|
||||
|
||||
// TODO(gibbed): real information.
|
||||
//
|
||||
// Until we expose real information about a HDD device, we
|
||||
// claim there is 3GB free on a 4GB dummy HDD.
|
||||
//
|
||||
// There is a possibility that certain games are bugged in that
|
||||
// they incorrectly only look at the lower 32-bits of free_bytes,
|
||||
// when it is a 64-bit value. Which means any size above ~4GB
|
||||
// will not be recognized properly.
|
||||
#define ONE_GB (1024ull * 1024ull * 1024ull)
|
||||
static const DeviceInfo dummy_device_info_ = {
|
||||
0x00000001, // id
|
||||
1, // 1=HDD
|
||||
20ull * ONE_GB, // 20GB
|
||||
3ull * ONE_GB, // 3GB, so it looks a little used.
|
||||
u"Dummy HDD",
|
||||
};
|
||||
#undef ONE_GB
|
||||
|
||||
dword_result_t XamContentGetLicenseMask(lpdword_t mask_ptr,
|
||||
lpunknown_t overlapped_ptr) {
|
||||
// Each bit in the mask represents a granted license. Available licenses
|
||||
|
@ -73,78 +48,10 @@ dword_result_t XamContentGetLicenseMask(lpdword_t mask_ptr,
|
|||
}
|
||||
DECLARE_XAM_EXPORT2(XamContentGetLicenseMask, kContent, kStub, kHighFrequency);
|
||||
|
||||
dword_result_t XamContentGetDeviceName(dword_t device_id,
|
||||
lpu16string_t name_buffer,
|
||||
dword_t name_capacity) {
|
||||
if ((device_id & 0x0000000F) != dummy_device_info_.device_id) {
|
||||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
auto name = std::u16string(dummy_device_info_.name);
|
||||
if (name_capacity < name.size() + 1) {
|
||||
return X_ERROR_INSUFFICIENT_BUFFER;
|
||||
}
|
||||
|
||||
xe::store_and_swap<std::u16string>(name_buffer, name);
|
||||
((char16_t*)name_buffer)[name.size()] = 0;
|
||||
return X_ERROR_SUCCESS;
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamContentGetDeviceName, kContent, kImplemented);
|
||||
|
||||
dword_result_t XamContentGetDeviceState(dword_t device_id,
|
||||
lpunknown_t overlapped_ptr) {
|
||||
if ((device_id & 0x0000000F) != dummy_device_info_.device_id) {
|
||||
if (overlapped_ptr) {
|
||||
kernel_state()->CompleteOverlappedImmediateEx(
|
||||
overlapped_ptr, X_ERROR_FUNCTION_FAILED, X_ERROR_DEVICE_NOT_CONNECTED,
|
||||
0);
|
||||
return X_ERROR_IO_PENDING;
|
||||
} else {
|
||||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||
}
|
||||
}
|
||||
|
||||
if (overlapped_ptr) {
|
||||
kernel_state()->CompleteOverlappedImmediate(overlapped_ptr,
|
||||
X_ERROR_SUCCESS);
|
||||
return X_ERROR_IO_PENDING;
|
||||
} else {
|
||||
return X_ERROR_SUCCESS;
|
||||
}
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamContentGetDeviceState, kContent, kStub);
|
||||
|
||||
typedef struct {
|
||||
xe::be<uint32_t> device_id;
|
||||
xe::be<uint32_t> device_type;
|
||||
xe::be<uint64_t> total_bytes;
|
||||
xe::be<uint64_t> free_bytes;
|
||||
xe::be<uint16_t> name[28];
|
||||
} X_CONTENT_DEVICE_DATA;
|
||||
static_assert_size(X_CONTENT_DEVICE_DATA, 0x50);
|
||||
|
||||
dword_result_t XamContentGetDeviceData(
|
||||
dword_t device_id, pointer_t<X_CONTENT_DEVICE_DATA> device_data) {
|
||||
if ((device_id & 0x0000000F) != dummy_device_info_.device_id) {
|
||||
// TODO(benvanik): memset 0 the data?
|
||||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
device_data.Zero();
|
||||
const auto& device_info = dummy_device_info_;
|
||||
device_data->device_id = device_info.device_id;
|
||||
device_data->device_type = device_info.device_type;
|
||||
device_data->total_bytes = device_info.total_bytes;
|
||||
device_data->free_bytes = device_info.free_bytes;
|
||||
xe::store_and_swap<std::u16string>(&device_data->name[0], device_info.name);
|
||||
return X_ERROR_SUCCESS;
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamContentGetDeviceData, kContent, kImplemented);
|
||||
|
||||
dword_result_t XamContentResolve(dword_t user_index, lpvoid_t content_data_ptr,
|
||||
lpunknown_t buffer_ptr, dword_t buffer_size,
|
||||
dword_t unk1, dword_t unk2, dword_t unk3) {
|
||||
auto content_data = XCONTENT_DATA((uint8_t*)content_data_ptr);
|
||||
auto content_data = content_data_ptr.as<XCONTENT_DATA*>();
|
||||
|
||||
// Result of buffer_ptr is sent to RtlInitAnsiString.
|
||||
// buffer_size is usually 260 (max path).
|
||||
|
@ -164,8 +71,9 @@ dword_result_t XamContentCreateEnumerator(dword_t user_index, dword_t device_id,
|
|||
lpdword_t buffer_size_ptr,
|
||||
lpdword_t handle_out) {
|
||||
assert_not_null(handle_out);
|
||||
if ((device_id && (device_id & 0x0000000F) != dummy_device_info_.device_id) ||
|
||||
!handle_out) {
|
||||
|
||||
auto device_info = device_id == 0 ? nullptr : GetDummyDeviceInfo(device_id);
|
||||
if ((device_id && device_info == nullptr) || !handle_out) {
|
||||
if (buffer_size_ptr) {
|
||||
*buffer_size_ptr = 0;
|
||||
}
|
||||
|
@ -175,22 +83,29 @@ dword_result_t XamContentCreateEnumerator(dword_t user_index, dword_t device_id,
|
|||
}
|
||||
|
||||
if (buffer_size_ptr) {
|
||||
*buffer_size_ptr = (uint32_t)XCONTENT_DATA::kSize * items_per_enumerate;
|
||||
*buffer_size_ptr = sizeof(XCONTENT_DATA) * items_per_enumerate;
|
||||
}
|
||||
|
||||
auto e = new XStaticEnumerator(kernel_state(), items_per_enumerate,
|
||||
XCONTENT_DATA::kSize);
|
||||
e->Initialize();
|
||||
auto e = object_ref<XStaticEnumerator>(new XStaticEnumerator(
|
||||
kernel_state(), items_per_enumerate, sizeof(XCONTENT_DATA)));
|
||||
auto result = e->Initialize(0xFF, 0xFE, 0x20005, 0x20007, 0);
|
||||
if (XFAILED(result)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get all content data.
|
||||
auto content_datas = kernel_state()->content_manager()->ListContent(
|
||||
device_id ? static_cast<uint32_t>(device_id)
|
||||
: dummy_device_info_.device_id,
|
||||
content_type);
|
||||
for (auto& content_data : content_datas) {
|
||||
auto ptr = e->AppendItem();
|
||||
assert_not_null(ptr);
|
||||
content_data.Write(ptr);
|
||||
if (!device_info || device_info->device_id == DummyDeviceId::HDD) {
|
||||
// Get all content data.
|
||||
auto content_datas = kernel_state()->content_manager()->ListContent(
|
||||
static_cast<uint32_t>(DummyDeviceId::HDD), content_type);
|
||||
for (const auto& content_data : content_datas) {
|
||||
auto item = reinterpret_cast<XCONTENT_DATA*>(e->AppendItem());
|
||||
assert_not_null(item);
|
||||
content_data.Write(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (!device_info || device_info->device_id == DummyDeviceId::ODD) {
|
||||
// TODO(gibbed): disc drive content
|
||||
}
|
||||
|
||||
XELOGD("XamContentCreateEnumerator: added {} items to enumerator",
|
||||
|
@ -201,36 +116,6 @@ dword_result_t XamContentCreateEnumerator(dword_t user_index, dword_t device_id,
|
|||
}
|
||||
DECLARE_XAM_EXPORT1(XamContentCreateEnumerator, kContent, kImplemented);
|
||||
|
||||
dword_result_t XamContentCreateDeviceEnumerator(dword_t content_type,
|
||||
dword_t content_flags,
|
||||
dword_t max_count,
|
||||
lpdword_t buffer_size_ptr,
|
||||
lpdword_t handle_out) {
|
||||
assert_not_null(handle_out);
|
||||
|
||||
if (buffer_size_ptr) {
|
||||
*buffer_size_ptr = sizeof(DeviceInfo) * max_count;
|
||||
}
|
||||
|
||||
auto e = new XStaticEnumerator(kernel_state(), max_count, sizeof(DeviceInfo));
|
||||
e->Initialize();
|
||||
|
||||
// Copy our dummy device into the enumerator
|
||||
DeviceInfo* dev = (DeviceInfo*)e->AppendItem();
|
||||
if (dev) {
|
||||
xe::store_and_swap(&dev->device_id, dummy_device_info_.device_id);
|
||||
xe::store_and_swap(&dev->device_type, dummy_device_info_.device_type);
|
||||
xe::store_and_swap(&dev->total_bytes, dummy_device_info_.total_bytes);
|
||||
xe::store_and_swap(&dev->free_bytes, dummy_device_info_.free_bytes);
|
||||
xe::copy_and_swap(dev->name, dummy_device_info_.name,
|
||||
xe::countof(dev->name));
|
||||
}
|
||||
|
||||
*handle_out = e->handle();
|
||||
return X_ERROR_SUCCESS;
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamContentCreateDeviceEnumerator, kNone, kImplemented);
|
||||
|
||||
dword_result_t XamContentCreateEx(dword_t user_index, lpstring_t root_name,
|
||||
lpvoid_t content_data_ptr, dword_t flags,
|
||||
lpdword_t disposition_ptr,
|
||||
|
@ -238,7 +123,8 @@ dword_result_t XamContentCreateEx(dword_t user_index, lpstring_t root_name,
|
|||
dword_t cache_size, qword_t content_size,
|
||||
lpvoid_t overlapped_ptr) {
|
||||
X_RESULT result = X_ERROR_INVALID_PARAMETER;
|
||||
auto content_data = XCONTENT_DATA((uint8_t*)content_data_ptr);
|
||||
auto content_data =
|
||||
static_cast<ContentData>(*content_data_ptr.as<XCONTENT_DATA*>());
|
||||
|
||||
auto content_manager = kernel_state()->content_manager();
|
||||
bool create = false;
|
||||
|
@ -390,7 +276,8 @@ dword_result_t XamContentGetCreator(dword_t user_index,
|
|||
lpunknown_t overlapped_ptr) {
|
||||
auto result = X_ERROR_SUCCESS;
|
||||
|
||||
auto content_data = XCONTENT_DATA((uint8_t*)content_data_ptr);
|
||||
auto content_data =
|
||||
static_cast<ContentData>(*content_data_ptr.as<XCONTENT_DATA*>());
|
||||
|
||||
if (content_data.content_type == 1) {
|
||||
// User always creates saves.
|
||||
|
@ -421,7 +308,8 @@ dword_result_t XamContentGetThumbnail(dword_t user_index,
|
|||
lpunknown_t overlapped_ptr) {
|
||||
assert_not_null(buffer_size_ptr);
|
||||
uint32_t buffer_size = *buffer_size_ptr;
|
||||
auto content_data = XCONTENT_DATA((uint8_t*)content_data_ptr);
|
||||
auto content_data =
|
||||
static_cast<ContentData>(*content_data_ptr.as<XCONTENT_DATA*>());
|
||||
|
||||
// Get thumbnail (if it exists).
|
||||
std::vector<uint8_t> buffer;
|
||||
|
@ -457,7 +345,8 @@ dword_result_t XamContentSetThumbnail(dword_t user_index,
|
|||
lpvoid_t content_data_ptr,
|
||||
lpvoid_t buffer_ptr, dword_t buffer_size,
|
||||
lpunknown_t overlapped_ptr) {
|
||||
auto content_data = XCONTENT_DATA((uint8_t*)content_data_ptr);
|
||||
auto content_data =
|
||||
static_cast<ContentData>(*content_data_ptr.as<XCONTENT_DATA*>());
|
||||
|
||||
// Buffer is PNG data.
|
||||
auto buffer = std::vector<uint8_t>((uint8_t*)buffer_ptr,
|
||||
|
@ -476,7 +365,8 @@ DECLARE_XAM_EXPORT1(XamContentSetThumbnail, kContent, kImplemented);
|
|||
|
||||
dword_result_t XamContentDelete(dword_t user_index, lpvoid_t content_data_ptr,
|
||||
lpunknown_t overlapped_ptr) {
|
||||
auto content_data = XCONTENT_DATA((uint8_t*)content_data_ptr);
|
||||
auto content_data =
|
||||
static_cast<ContentData>(*content_data_ptr.as<XCONTENT_DATA*>());
|
||||
|
||||
auto result = kernel_state()->content_manager()->DeleteContent(content_data);
|
||||
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/base/string_util.h"
|
||||
#include "xenia/kernel/kernel_state.h"
|
||||
#include "xenia/kernel/util/shim_utils.h"
|
||||
#include "xenia/kernel/xam/xam_content_device.h"
|
||||
#include "xenia/kernel/xam/xam_private.h"
|
||||
#include "xenia/kernel/xenumerator.h"
|
||||
#include "xenia/vfs/file.h"
|
||||
#include "xenia/xbox.h"
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
|
||||
void AddODDContentTest(object_ref<XStaticEnumerator> e, uint32_t content_type) {
|
||||
auto root_entry = kernel_state()->file_system()->ResolvePath(
|
||||
"game:\\Content\\0000000000000000");
|
||||
if (!root_entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto content_type_path = fmt::format("{:08X}", content_type);
|
||||
|
||||
xe::filesystem::WildcardEngine title_find_engine;
|
||||
title_find_engine.SetRule("????????");
|
||||
|
||||
xe::filesystem::WildcardEngine content_find_engine;
|
||||
content_find_engine.SetRule("????????????????");
|
||||
|
||||
size_t title_find_index = 0;
|
||||
vfs::Entry* title_entry;
|
||||
for (;;) {
|
||||
title_entry =
|
||||
root_entry->IterateChildren(title_find_engine, &title_find_index);
|
||||
if (!title_entry) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto title_id =
|
||||
string_util::from_string<uint32_t>(title_entry->name(), true);
|
||||
|
||||
auto content_root_entry = title_entry->ResolvePath(content_type_path);
|
||||
if (content_root_entry) {
|
||||
size_t content_find_index = 0;
|
||||
vfs::Entry* content_entry;
|
||||
for (;;) {
|
||||
content_entry = content_root_entry->IterateChildren(
|
||||
content_find_engine, &content_find_index);
|
||||
if (!content_entry) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto item = reinterpret_cast<XCONTENT_AGGREGATE_DATA*>(e->AppendItem());
|
||||
assert_not_null(item);
|
||||
ContentAggregateData content_aggregate_data = {};
|
||||
content_aggregate_data.device_id =
|
||||
static_cast<uint32_t>(DummyDeviceId::ODD);
|
||||
content_aggregate_data.content_type = content_type;
|
||||
content_aggregate_data.display_name = to_utf16(content_entry->name());
|
||||
content_aggregate_data.file_name = content_entry->name();
|
||||
content_aggregate_data.title_id = title_id;
|
||||
content_aggregate_data.Write(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dword_result_t XamContentAggregateCreateEnumerator(qword_t xuid,
|
||||
dword_t device_id,
|
||||
dword_t content_type,
|
||||
unknown_t unk3,
|
||||
lpdword_t handle_out) {
|
||||
assert_not_null(handle_out);
|
||||
|
||||
auto device_info = device_id == 0 ? nullptr : GetDummyDeviceInfo(device_id);
|
||||
if ((device_id && device_info == nullptr) || !handle_out) {
|
||||
return X_E_INVALIDARG;
|
||||
}
|
||||
|
||||
auto e = object_ref<XStaticEnumerator>(new XStaticEnumerator(
|
||||
kernel_state(), 1, sizeof(XCONTENT_AGGREGATE_DATA)));
|
||||
X_KENUMERATOR_CONTENT_AGGREGATE* extra;
|
||||
auto result = e->Initialize(0xFF, 0xFE, 0x2000E, 0x20010, 0, &extra);
|
||||
if (XFAILED(result)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
extra->magic = 'XEN\0';
|
||||
extra->handle = e->handle();
|
||||
|
||||
if (!device_info || device_info->device_type == DeviceType::HDD) {
|
||||
// Get all content data.
|
||||
auto content_datas = kernel_state()->content_manager()->ListContent(
|
||||
static_cast<uint32_t>(DummyDeviceId::HDD), content_type);
|
||||
for (const auto& content_data : content_datas) {
|
||||
auto item = reinterpret_cast<XCONTENT_AGGREGATE_DATA*>(e->AppendItem());
|
||||
assert_not_null(item);
|
||||
ContentAggregateData content_aggregate_data = {};
|
||||
content_aggregate_data.device_id = content_data.device_id;
|
||||
content_aggregate_data.content_type = content_data.content_type;
|
||||
content_aggregate_data.display_name = content_data.display_name;
|
||||
content_aggregate_data.file_name = content_data.file_name;
|
||||
content_aggregate_data.title_id = 1u;
|
||||
content_aggregate_data.Write(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (!device_info || device_info->device_type == DeviceType::ODD) {
|
||||
AddODDContentTest(e, content_type);
|
||||
}
|
||||
|
||||
XELOGD("XamContentAggregateCreateEnumerator: added {} items to enumerator",
|
||||
e->item_count());
|
||||
|
||||
*handle_out = e->handle();
|
||||
return X_ERROR_SUCCESS;
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamContentAggregateCreateEnumerator, kContent, kStub);
|
||||
|
||||
void RegisterContentAggregateExports(xe::cpu::ExportResolver* export_resolver,
|
||||
KernelState* kernel_state) {}
|
||||
|
||||
} // namespace xam
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
|
@ -0,0 +1,174 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/kernel/xam/xam_content_device.h"
|
||||
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/kernel/kernel_state.h"
|
||||
#include "xenia/kernel/util/shim_utils.h"
|
||||
#include "xenia/kernel/xam/xam_private.h"
|
||||
#include "xenia/kernel/xenumerator.h"
|
||||
#include "xenia/xbox.h"
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
|
||||
// TODO(gibbed): real information.
|
||||
//
|
||||
// Until we expose real information about a HDD device, we
|
||||
// claim there is 3GB free on a 4GB dummy HDD.
|
||||
//
|
||||
// There is a possibility that certain games are bugged in that
|
||||
// they incorrectly only look at the lower 32-bits of free_bytes,
|
||||
// when it is a 64-bit value. Which means any size above ~4GB
|
||||
// will not be recognized properly.
|
||||
#define ONE_GB (1024ull * 1024ull * 1024ull)
|
||||
|
||||
static const DummyDeviceInfo dummy_hdd_device_info_ = {
|
||||
DummyDeviceId::HDD, DeviceType::HDD,
|
||||
20ull * ONE_GB, // 20GB
|
||||
3ull * ONE_GB, // 3GB, so it looks a little used.
|
||||
u"Dummy HDD",
|
||||
};
|
||||
static const DummyDeviceInfo dummy_odd_device_info_ = {
|
||||
DummyDeviceId::ODD, DeviceType::ODD,
|
||||
7ull * ONE_GB, // 7GB (rough maximum)
|
||||
0ull * ONE_GB, // read-only FS, so no free space
|
||||
u"Dummy ODD",
|
||||
};
|
||||
static const DummyDeviceInfo* dummy_device_infos_[] = {
|
||||
&dummy_hdd_device_info_,
|
||||
&dummy_odd_device_info_,
|
||||
};
|
||||
#undef ONE_GB
|
||||
|
||||
const DummyDeviceInfo* GetDummyDeviceInfo(uint32_t device_id) {
|
||||
const auto& begin = std::begin(dummy_device_infos_);
|
||||
const auto& end = std::end(dummy_device_infos_);
|
||||
auto it = std::find_if(begin, end, [device_id](const auto& item) {
|
||||
return static_cast<uint32_t>(item->device_id) == device_id;
|
||||
});
|
||||
return it == end ? nullptr : *it;
|
||||
}
|
||||
|
||||
dword_result_t XamContentGetDeviceName(dword_t device_id,
|
||||
lpu16string_t name_buffer,
|
||||
dword_t name_capacity) {
|
||||
auto device_info = GetDummyDeviceInfo(device_id);
|
||||
if (device_info == nullptr) {
|
||||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||
}
|
||||
auto name = std::u16string(device_info->name);
|
||||
if (name_capacity < name.size() + 1) {
|
||||
return X_ERROR_INSUFFICIENT_BUFFER;
|
||||
}
|
||||
xe::string_util::copy_and_swap_truncating(name_buffer, name, name_capacity);
|
||||
return X_ERROR_SUCCESS;
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamContentGetDeviceName, kContent, kImplemented);
|
||||
|
||||
dword_result_t XamContentGetDeviceState(dword_t device_id,
|
||||
lpunknown_t overlapped_ptr) {
|
||||
auto device_info = GetDummyDeviceInfo(device_id);
|
||||
if (device_info == nullptr) {
|
||||
if (overlapped_ptr) {
|
||||
kernel_state()->CompleteOverlappedImmediateEx(
|
||||
overlapped_ptr, X_ERROR_FUNCTION_FAILED, X_ERROR_DEVICE_NOT_CONNECTED,
|
||||
0);
|
||||
return X_ERROR_IO_PENDING;
|
||||
} else {
|
||||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||
}
|
||||
}
|
||||
if (overlapped_ptr) {
|
||||
kernel_state()->CompleteOverlappedImmediate(overlapped_ptr,
|
||||
X_ERROR_SUCCESS);
|
||||
return X_ERROR_IO_PENDING;
|
||||
} else {
|
||||
return X_ERROR_SUCCESS;
|
||||
}
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamContentGetDeviceState, kContent, kStub);
|
||||
|
||||
typedef struct {
|
||||
xe::be<uint32_t> device_id;
|
||||
xe::be<uint32_t> device_type;
|
||||
xe::be<uint64_t> total_bytes;
|
||||
xe::be<uint64_t> free_bytes;
|
||||
union {
|
||||
xe::be<uint16_t> name[28];
|
||||
char16_t name_chars[28];
|
||||
};
|
||||
} X_CONTENT_DEVICE_DATA;
|
||||
static_assert_size(X_CONTENT_DEVICE_DATA, 0x50);
|
||||
|
||||
dword_result_t XamContentGetDeviceData(
|
||||
dword_t device_id, pointer_t<X_CONTENT_DEVICE_DATA> device_data) {
|
||||
auto device_info = GetDummyDeviceInfo(device_id);
|
||||
if (device_info == nullptr) {
|
||||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||
}
|
||||
device_data.Zero();
|
||||
device_data->device_id = static_cast<uint32_t>(device_info->device_id);
|
||||
device_data->device_type = static_cast<uint32_t>(device_info->device_type);
|
||||
device_data->total_bytes = device_info->total_bytes;
|
||||
device_data->free_bytes = device_info->free_bytes;
|
||||
xe::string_util::copy_and_swap_truncating(
|
||||
device_data->name_chars, device_info->name,
|
||||
xe::countof(device_data->name_chars));
|
||||
return X_ERROR_SUCCESS;
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamContentGetDeviceData, kContent, kImplemented);
|
||||
|
||||
dword_result_t XamContentCreateDeviceEnumerator(dword_t content_type,
|
||||
dword_t content_flags,
|
||||
dword_t max_count,
|
||||
lpdword_t buffer_size_ptr,
|
||||
lpdword_t handle_out) {
|
||||
assert_not_null(handle_out);
|
||||
|
||||
if (buffer_size_ptr) {
|
||||
*buffer_size_ptr = sizeof(X_CONTENT_DEVICE_DATA) * max_count;
|
||||
}
|
||||
|
||||
auto e = object_ref<XStaticEnumerator>(new XStaticEnumerator(
|
||||
kernel_state(), max_count, sizeof(X_CONTENT_DEVICE_DATA)));
|
||||
auto result = e->Initialize(0xFE, 0xFE, 0x2000A, 0x20009, 0);
|
||||
if (XFAILED(result)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (const auto& device_info : dummy_device_infos_) {
|
||||
// Copy our dummy device into the enumerator
|
||||
auto device_data = (X_CONTENT_DEVICE_DATA*)e->AppendItem();
|
||||
if (device_data) {
|
||||
device_data->device_id = static_cast<uint32_t>(device_info->device_id);
|
||||
device_data->device_type =
|
||||
static_cast<uint32_t>(device_info->device_type);
|
||||
device_data->total_bytes = device_info->total_bytes;
|
||||
device_data->free_bytes = device_info->free_bytes;
|
||||
xe::string_util::copy_and_swap_truncating(
|
||||
device_data->name_chars, device_info->name,
|
||||
xe::countof(device_data->name_chars));
|
||||
}
|
||||
}
|
||||
|
||||
*handle_out = e->handle();
|
||||
return X_ERROR_SUCCESS;
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamContentCreateDeviceEnumerator, kNone, kImplemented);
|
||||
|
||||
void RegisterContentDeviceExports(xe::cpu::ExportResolver* export_resolver,
|
||||
KernelState* kernel_state) {}
|
||||
|
||||
} // namespace xam
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_KERNEL_XAM_XAM_CONTENT_DEVICE_H_
|
||||
#define XENIA_KERNEL_XAM_XAM_CONTENT_DEVICE_H_
|
||||
|
||||
#include "xenia/xbox.h"
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
|
||||
enum class DeviceType : uint32_t {
|
||||
HDD = 1,
|
||||
ODD = 4,
|
||||
};
|
||||
|
||||
enum class DummyDeviceId : uint32_t {
|
||||
HDD = 1,
|
||||
ODD = 2,
|
||||
};
|
||||
|
||||
struct DummyDeviceInfo {
|
||||
DummyDeviceId device_id;
|
||||
DeviceType device_type;
|
||||
uint64_t total_bytes;
|
||||
uint64_t free_bytes;
|
||||
const std::u16string_view name;
|
||||
};
|
||||
|
||||
const DummyDeviceInfo* GetDummyDeviceInfo(uint32_t device_id);
|
||||
|
||||
} // namespace xam
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_KERNEL_XAM_XAM_CONTENT_DEVICE_H_
|
|
@ -0,0 +1,144 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/string_util.h"
|
||||
#include "xenia/kernel/kernel_state.h"
|
||||
#include "xenia/kernel/util/shim_utils.h"
|
||||
#include "xenia/kernel/xam/xam_module.h"
|
||||
#include "xenia/kernel/xam/xam_private.h"
|
||||
#include "xenia/kernel/xenumerator.h"
|
||||
#include "xenia/xbox.h"
|
||||
|
||||
#if XE_PLATFORM_WIN32
|
||||
#include "xenia/base/platform_win.h"
|
||||
#endif
|
||||
|
||||
#include "third_party/fmt/include/fmt/format.h"
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
|
||||
// https://github.com/LestaD/SourceEngine2007/blob/master/se2007/engine/xboxsystem.cpp#L518
|
||||
uint32_t xeXamEnumerate(uint32_t handle, uint32_t flags, void* buffer,
|
||||
uint32_t buffer_length, uint32_t* items_returned,
|
||||
uint32_t overlapped_ptr) {
|
||||
assert_true(flags == 0);
|
||||
|
||||
auto e = kernel_state()->object_table()->LookupObject<XEnumerator>(handle);
|
||||
if (!e) {
|
||||
if (overlapped_ptr) {
|
||||
kernel_state()->CompleteOverlappedImmediateEx(
|
||||
overlapped_ptr, X_ERROR_INVALID_HANDLE, X_ERROR_INVALID_HANDLE, 0);
|
||||
return X_ERROR_IO_PENDING;
|
||||
} else {
|
||||
return X_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
size_t actual_buffer_length = buffer_length;
|
||||
if (buffer_length == e->items_per_enumerate()) {
|
||||
actual_buffer_length = e->item_size() * e->items_per_enumerate();
|
||||
// Known culprits:
|
||||
// Final Fight: Double Impact (saves)
|
||||
XELOGW(
|
||||
"Broken usage of XamEnumerate! buffer length={:X} vs actual "
|
||||
"length={:X} "
|
||||
"(item size={:X}, items per enumerate={})",
|
||||
(uint32_t)buffer_length, actual_buffer_length, e->item_size(),
|
||||
e->items_per_enumerate());
|
||||
}
|
||||
|
||||
std::memset(buffer, 0, actual_buffer_length);
|
||||
|
||||
X_RESULT result;
|
||||
uint32_t item_count = 0;
|
||||
|
||||
if (actual_buffer_length < e->item_size()) {
|
||||
result = X_ERROR_INSUFFICIENT_BUFFER;
|
||||
} else if (e->current_item() >= e->item_count()) {
|
||||
result = X_ERROR_NO_MORE_FILES;
|
||||
} else {
|
||||
auto item_buffer = static_cast<uint8_t*>(buffer);
|
||||
auto max_items = actual_buffer_length / e->item_size();
|
||||
while (max_items--) {
|
||||
if (!e->WriteItem(item_buffer)) {
|
||||
break;
|
||||
}
|
||||
item_buffer += e->item_size();
|
||||
item_count++;
|
||||
}
|
||||
result = X_ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
if (items_returned) {
|
||||
assert_true(!overlapped_ptr);
|
||||
*items_returned = result == X_ERROR_SUCCESS ? item_count : 0;
|
||||
return result;
|
||||
} else if (overlapped_ptr) {
|
||||
assert_true(!items_returned);
|
||||
kernel_state()->CompleteOverlappedImmediateEx(
|
||||
overlapped_ptr,
|
||||
result == X_ERROR_SUCCESS ? X_ERROR_SUCCESS : X_ERROR_FUNCTION_FAILED,
|
||||
X_HRESULT_FROM_WIN32(result),
|
||||
result == X_ERROR_SUCCESS ? item_count : 0);
|
||||
return X_ERROR_IO_PENDING;
|
||||
} else {
|
||||
assert_always();
|
||||
return X_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
dword_result_t XamEnumerate(dword_t handle, dword_t flags, lpvoid_t buffer,
|
||||
dword_t buffer_length, lpdword_t items_returned,
|
||||
pointer_t<XAM_OVERLAPPED> overlapped) {
|
||||
uint32_t dummy;
|
||||
auto result = xeXamEnumerate(handle, flags, buffer, buffer_length,
|
||||
!overlapped ? &dummy : nullptr, overlapped);
|
||||
if (!overlapped && items_returned) {
|
||||
*items_returned = dummy;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamEnumerate, kNone, kImplemented);
|
||||
|
||||
dword_result_t XamCreateEnumeratorHandle(unknown_t unk1, unknown_t unk2,
|
||||
unknown_t unk3, unknown_t unk4,
|
||||
unknown_t unk5, unknown_t unk6,
|
||||
unknown_t unk7, unknown_t unk8) {
|
||||
return X_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamCreateEnumeratorHandle, kNone, kStub);
|
||||
|
||||
dword_result_t XamGetPrivateEnumStructureFromHandle(dword_t handle,
|
||||
lpdword_t out_object_ptr) {
|
||||
auto e = kernel_state()->object_table()->LookupObject<XEnumerator>(handle);
|
||||
if (!e) {
|
||||
return X_STATUS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
// Caller takes the reference.
|
||||
// It's released in ObDereferenceObject.
|
||||
e->RetainHandle();
|
||||
|
||||
if (out_object_ptr.guest_address()) {
|
||||
*out_object_ptr = e->guest_object();
|
||||
}
|
||||
|
||||
return X_STATUS_SUCCESS;
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamGetPrivateEnumStructureFromHandle, kNone, kStub);
|
||||
|
||||
void RegisterEnumExports(xe::cpu::ExportResolver* export_resolver,
|
||||
KernelState* kernel_state) {}
|
||||
|
||||
} // namespace xam
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
|
@ -337,91 +337,6 @@ dword_result_t XamFree(lpdword_t ptr) {
|
|||
}
|
||||
DECLARE_XAM_EXPORT1(XamFree, kMemory, kImplemented);
|
||||
|
||||
// https://github.com/LestaD/SourceEngine2007/blob/master/se2007/engine/xboxsystem.cpp#L518
|
||||
dword_result_t XamEnumerate(dword_t handle, dword_t flags, lpvoid_t buffer,
|
||||
dword_t buffer_length, lpdword_t items_returned,
|
||||
pointer_t<XAM_OVERLAPPED> overlapped) {
|
||||
assert_true(flags == 0);
|
||||
|
||||
auto e = kernel_state()->object_table()->LookupObject<XEnumerator>(handle);
|
||||
if (!e) {
|
||||
if (overlapped) {
|
||||
kernel_state()->CompleteOverlappedImmediateEx(
|
||||
overlapped, X_ERROR_INVALID_HANDLE, X_ERROR_INVALID_HANDLE, 0);
|
||||
return X_ERROR_IO_PENDING;
|
||||
} else {
|
||||
return X_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
size_t actual_buffer_length = (uint32_t)buffer_length;
|
||||
if (buffer_length == e->items_per_enumerate()) {
|
||||
actual_buffer_length = e->item_size() * e->items_per_enumerate();
|
||||
// Known culprits:
|
||||
// Final Fight: Double Impact (saves)
|
||||
XELOGW(
|
||||
"Broken usage of XamEnumerate! buffer length={:X} vs actual "
|
||||
"length={:X} "
|
||||
"(item size={:X}, items per enumerate={})",
|
||||
(uint32_t)buffer_length, actual_buffer_length, e->item_size(),
|
||||
e->items_per_enumerate());
|
||||
}
|
||||
|
||||
buffer.Zero(actual_buffer_length);
|
||||
|
||||
X_RESULT result;
|
||||
uint32_t item_count = 0;
|
||||
|
||||
if (actual_buffer_length < e->item_size()) {
|
||||
result = X_ERROR_INSUFFICIENT_BUFFER;
|
||||
} else if (e->current_item() >= e->item_count()) {
|
||||
result = X_ERROR_NO_MORE_FILES;
|
||||
} else {
|
||||
auto item_buffer = buffer.as<uint8_t*>();
|
||||
auto max_items = actual_buffer_length / e->item_size();
|
||||
while (max_items--) {
|
||||
if (!e->WriteItem(item_buffer)) {
|
||||
break;
|
||||
}
|
||||
item_buffer += e->item_size();
|
||||
item_count++;
|
||||
}
|
||||
result = X_ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
if (items_returned) {
|
||||
assert_true(!overlapped);
|
||||
*items_returned = result == X_ERROR_SUCCESS ? item_count : 0;
|
||||
return result;
|
||||
} else if (overlapped) {
|
||||
assert_true(!items_returned);
|
||||
kernel_state()->CompleteOverlappedImmediateEx(
|
||||
overlapped,
|
||||
result == X_ERROR_SUCCESS ? X_ERROR_SUCCESS : X_ERROR_FUNCTION_FAILED,
|
||||
X_HRESULT_FROM_WIN32(result),
|
||||
result == X_ERROR_SUCCESS ? item_count : 0);
|
||||
return X_ERROR_IO_PENDING;
|
||||
} else {
|
||||
assert_always();
|
||||
return X_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamEnumerate, kNone, kImplemented);
|
||||
|
||||
dword_result_t XamCreateEnumeratorHandle(unknown_t unk1, unknown_t unk2,
|
||||
unknown_t unk3, unknown_t unk4,
|
||||
unknown_t unk5, unknown_t unk6,
|
||||
unknown_t unk7, unknown_t unk8) {
|
||||
return X_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamCreateEnumeratorHandle, kNone, kStub);
|
||||
|
||||
dword_result_t XamGetPrivateEnumStructureFromHandle(unknown_t unk1,
|
||||
unknown_t unk2) {
|
||||
return X_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamGetPrivateEnumStructureFromHandle, kNone, kStub);
|
||||
|
||||
dword_result_t XamQueryLiveHiveW(lpu16string_t name, lpvoid_t out_buf,
|
||||
dword_t out_size, dword_t type /* guess */) {
|
||||
return X_STATUS_INVALID_PARAMETER_1;
|
||||
|
|
|
@ -19,24 +19,18 @@ namespace xe {
|
|||
namespace kernel {
|
||||
namespace xam {
|
||||
|
||||
std::atomic<int> xam_dialogs_shown_ = {0};
|
||||
|
||||
bool xeXamIsUIActive() { return xam_dialogs_shown_ > 0; }
|
||||
|
||||
XamModule::XamModule(Emulator* emulator, KernelState* kernel_state)
|
||||
: KernelModule(kernel_state, "xe:\\xam.xex"), loader_data_() {
|
||||
RegisterExportTable(export_resolver_);
|
||||
|
||||
// Register all exported functions.
|
||||
RegisterAvatarExports(export_resolver_, kernel_state_);
|
||||
RegisterContentExports(export_resolver_, kernel_state_);
|
||||
RegisterInfoExports(export_resolver_, kernel_state_);
|
||||
RegisterInputExports(export_resolver_, kernel_state_);
|
||||
RegisterLocaleExports(export_resolver_, kernel_state_);
|
||||
RegisterMsgExports(export_resolver_, kernel_state_);
|
||||
RegisterNetExports(export_resolver_, kernel_state_);
|
||||
RegisterNotifyExports(export_resolver_, kernel_state_);
|
||||
RegisterNuiExports(export_resolver_, kernel_state_);
|
||||
RegisterUIExports(export_resolver_, kernel_state_);
|
||||
RegisterUserExports(export_resolver_, kernel_state_);
|
||||
RegisterVideoExports(export_resolver_, kernel_state_);
|
||||
RegisterVoiceExports(export_resolver_, kernel_state_);
|
||||
#define XE_MODULE_EXPORT_GROUP(m, n) \
|
||||
Register##n##Exports(export_resolver_, kernel_state_);
|
||||
#include "xam_module_export_groups.inc"
|
||||
#undef XE_MODULE_EXPORT_GROUP
|
||||
}
|
||||
|
||||
std::vector<xe::cpu::Export*> xam_exports(4096);
|
||||
|
|
|
@ -21,6 +21,8 @@ namespace xe {
|
|||
namespace kernel {
|
||||
namespace xam {
|
||||
|
||||
bool xeXamIsUIActive();
|
||||
|
||||
class XamModule : public KernelModule {
|
||||
public:
|
||||
XamModule(Emulator* emulator, KernelState* kernel_state);
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
// This is a partial file designed to be included by other files when
|
||||
// constructing various tables.
|
||||
|
||||
XE_MODULE_EXPORT_GROUP(xam, Avatar)
|
||||
XE_MODULE_EXPORT_GROUP(xam, Content)
|
||||
XE_MODULE_EXPORT_GROUP(xam, ContentAggregate)
|
||||
XE_MODULE_EXPORT_GROUP(xam, ContentDevice)
|
||||
XE_MODULE_EXPORT_GROUP(xam, Enum)
|
||||
XE_MODULE_EXPORT_GROUP(xam, Info)
|
||||
XE_MODULE_EXPORT_GROUP(xam, Input)
|
||||
XE_MODULE_EXPORT_GROUP(xam, Locale)
|
||||
XE_MODULE_EXPORT_GROUP(xam, Msg)
|
||||
XE_MODULE_EXPORT_GROUP(xam, Net)
|
||||
XE_MODULE_EXPORT_GROUP(xam, Notify)
|
||||
XE_MODULE_EXPORT_GROUP(xam, NUI)
|
||||
XE_MODULE_EXPORT_GROUP(xam, Task)
|
||||
XE_MODULE_EXPORT_GROUP(xam, UI)
|
||||
XE_MODULE_EXPORT_GROUP(xam, User)
|
||||
XE_MODULE_EXPORT_GROUP(xam, Video)
|
||||
XE_MODULE_EXPORT_GROUP(xam, Voice)
|
|
@ -11,7 +11,9 @@
|
|||
#include "xenia/kernel/kernel_state.h"
|
||||
#include "xenia/kernel/util/shim_utils.h"
|
||||
#include "xenia/kernel/xam/xam_private.h"
|
||||
#include "xenia/kernel/xboxkrnl/xboxkrnl_error.h"
|
||||
#include "xenia/kernel/xevent.h"
|
||||
#include "xenia/kernel/xthread.h"
|
||||
#include "xenia/xbox.h"
|
||||
|
||||
namespace xe {
|
||||
|
@ -40,39 +42,49 @@ dword_result_t XMsgSystemProcessCall(dword_t app, dword_t message,
|
|||
}
|
||||
DECLARE_XAM_EXPORT1(XMsgSystemProcessCall, kNone, kImplemented);
|
||||
|
||||
dword_result_t XMsgStartIORequest(dword_t app, dword_t message,
|
||||
pointer_t<XAM_OVERLAPPED> overlapped_ptr,
|
||||
dword_t buffer, dword_t buffer_length) {
|
||||
struct XMSGSTARTIOREQUEST_UNKNOWNARG {
|
||||
be<uint32_t> unk_0;
|
||||
be<uint32_t> unk_1;
|
||||
};
|
||||
|
||||
X_HRESULT xeXMsgStartIORequestEx(uint32_t app, uint32_t message,
|
||||
uint32_t overlapped_ptr, uint32_t buffer_ptr,
|
||||
uint32_t buffer_length,
|
||||
XMSGSTARTIOREQUEST_UNKNOWNARG* unknown) {
|
||||
auto result = kernel_state()->app_manager()->DispatchMessageAsync(
|
||||
app, message, buffer, buffer_length);
|
||||
if (result == X_ERROR_NOT_FOUND) {
|
||||
XELOGE("XMsgStartIORequest: app {:08X} undefined", app);
|
||||
app, message, buffer_ptr, buffer_length);
|
||||
if (result == X_E_NOTFOUND) {
|
||||
XELOGE("XMsgStartIORequestEx: app {:08X} undefined", app);
|
||||
result = X_E_INVALIDARG;
|
||||
XThread::SetLastError(X_ERROR_NOT_FOUND);
|
||||
}
|
||||
if (overlapped_ptr) {
|
||||
kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, result);
|
||||
result = X_ERROR_IO_PENDING;
|
||||
}
|
||||
if (result == X_ERROR_SUCCESS || X_ERROR_IO_PENDING) {
|
||||
XThread::SetLastError(0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XMsgStartIORequest, kNone, kImplemented);
|
||||
|
||||
dword_result_t XMsgStartIORequestEx(dword_t app, dword_t message,
|
||||
pointer_t<XAM_OVERLAPPED> overlapped_ptr,
|
||||
dword_t buffer, dword_t buffer_length,
|
||||
lpdword_t unknown_ptr) {
|
||||
auto result = kernel_state()->app_manager()->DispatchMessageAsync(
|
||||
app, message, buffer, buffer_length);
|
||||
if (result == X_ERROR_NOT_FOUND) {
|
||||
XELOGE("XMsgStartIORequestEx: app {:08X} undefined", app);
|
||||
}
|
||||
if (overlapped_ptr) {
|
||||
kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, result);
|
||||
result = X_ERROR_IO_PENDING;
|
||||
}
|
||||
return result;
|
||||
dword_result_t XMsgStartIORequestEx(
|
||||
dword_t app, dword_t message, pointer_t<XAM_OVERLAPPED> overlapped_ptr,
|
||||
dword_t buffer_ptr, dword_t buffer_length,
|
||||
pointer_t<XMSGSTARTIOREQUEST_UNKNOWNARG> unknown_ptr) {
|
||||
return xeXMsgStartIORequestEx(app, message, overlapped_ptr, buffer_ptr,
|
||||
buffer_length, unknown_ptr);
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XMsgStartIORequestEx, kNone, kImplemented);
|
||||
|
||||
dword_result_t XMsgStartIORequest(dword_t app, dword_t message,
|
||||
pointer_t<XAM_OVERLAPPED> overlapped_ptr,
|
||||
dword_t buffer_ptr, dword_t buffer_length) {
|
||||
return xeXMsgStartIORequestEx(app, message, overlapped_ptr, buffer_ptr,
|
||||
buffer_length, nullptr);
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XMsgStartIORequest, kNone, kImplemented);
|
||||
|
||||
dword_result_t XMsgCancelIORequest(pointer_t<XAM_OVERLAPPED> overlapped_ptr,
|
||||
dword_t wait) {
|
||||
X_HANDLE event_handle = XOverlappedGetEvent(overlapped_ptr);
|
||||
|
@ -88,9 +100,42 @@ dword_result_t XMsgCancelIORequest(pointer_t<XAM_OVERLAPPED> overlapped_ptr,
|
|||
}
|
||||
DECLARE_XAM_EXPORT1(XMsgCancelIORequest, kNone, kImplemented);
|
||||
|
||||
dword_result_t XMsgCompleteIORequest(pointer_t<XAM_OVERLAPPED> overlapped_ptr,
|
||||
dword_t result, dword_t extended_error,
|
||||
dword_t length) {
|
||||
kernel_state()->CompleteOverlappedImmediateEx(overlapped_ptr, result,
|
||||
extended_error, length);
|
||||
return X_ERROR_SUCCESS;
|
||||
}
|
||||
DECLARE_XAM_EXPORT2(XMsgCompleteIORequest, kNone, kImplemented, kSketchy);
|
||||
|
||||
dword_result_t XamGetOverlappedResult(pointer_t<XAM_OVERLAPPED> overlapped_ptr,
|
||||
lpdword_t length_ptr, dword_t unknown) {
|
||||
uint32_t result;
|
||||
if (overlapped_ptr->result != X_ERROR_IO_PENDING) {
|
||||
result = overlapped_ptr->result;
|
||||
} else if (!overlapped_ptr->event) {
|
||||
result = X_ERROR_IO_INCOMPLETE;
|
||||
} else {
|
||||
auto ev = kernel_state()->object_table()->LookupObject<XEvent>(
|
||||
overlapped_ptr->event);
|
||||
result = ev->Wait(3, 1, 0, nullptr);
|
||||
if (XSUCCEEDED(result)) {
|
||||
result = overlapped_ptr->result;
|
||||
} else {
|
||||
result = xboxkrnl::xeRtlNtStatusToDosError(result);
|
||||
}
|
||||
}
|
||||
if (XSUCCEEDED(result) && length_ptr) {
|
||||
*length_ptr = overlapped_ptr->length;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
DECLARE_XAM_EXPORT2(XamGetOverlappedResult, kNone, kImplemented, kSketchy);
|
||||
|
||||
void RegisterMsgExports(xe::cpu::ExportResolver* export_resolver,
|
||||
KernelState* kernel_state) {}
|
||||
|
||||
} // namespace xam
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
} // namespace xe
|
||||
|
|
|
@ -18,27 +18,35 @@ namespace xe {
|
|||
namespace kernel {
|
||||
namespace xam {
|
||||
|
||||
dword_result_t XamNotifyCreateListenerInternal(qword_t mask, dword_t unk,
|
||||
dword_t one) {
|
||||
// r4=1 may indicate user process?
|
||||
uint32_t xeXamNotifyCreateListener(uint64_t mask, uint32_t is_system,
|
||||
uint32_t max_version) {
|
||||
assert_true(max_version < 11);
|
||||
|
||||
if (max_version > 10) {
|
||||
max_version = 10;
|
||||
}
|
||||
|
||||
auto listener =
|
||||
object_ref<XNotifyListener>(new XNotifyListener(kernel_state()));
|
||||
listener->Initialize(mask);
|
||||
listener->Initialize(mask, max_version);
|
||||
|
||||
// Handle ref is incremented, so return that.
|
||||
uint32_t handle = listener->handle();
|
||||
|
||||
return handle;
|
||||
}
|
||||
DECLARE_XAM_EXPORT2(XamNotifyCreateListenerInternal, kNone, kImplemented,
|
||||
kSketchy);
|
||||
|
||||
dword_result_t XamNotifyCreateListener(qword_t mask, dword_t one) {
|
||||
return XamNotifyCreateListenerInternal(mask, 0, one);
|
||||
dword_result_t XamNotifyCreateListener(qword_t mask, dword_t max_version) {
|
||||
return xeXamNotifyCreateListener(mask, 0, max_version);
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamNotifyCreateListener, kNone, kImplemented);
|
||||
|
||||
dword_result_t XamNotifyCreateListenerInternal(qword_t mask, dword_t is_system,
|
||||
dword_t max_version) {
|
||||
return xeXamNotifyCreateListener(mask, is_system, max_version);
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamNotifyCreateListenerInternal, kNone, kImplemented);
|
||||
|
||||
// https://github.com/CodeAsm/ffplay360/blob/master/Common/AtgSignIn.cpp
|
||||
dword_result_t XNotifyGetNext(dword_t handle, dword_t match_id,
|
||||
lpdword_t id_ptr, lpdword_t param_ptr) {
|
||||
|
@ -47,14 +55,15 @@ dword_result_t XNotifyGetNext(dword_t handle, dword_t match_id,
|
|||
}
|
||||
|
||||
if (!id_ptr) {
|
||||
return X_ERROR_INVALID_PARAMETER;
|
||||
return 0;
|
||||
}
|
||||
*id_ptr = 0;
|
||||
|
||||
// Grab listener.
|
||||
auto listener =
|
||||
kernel_state()->object_table()->LookupObject<XNotifyListener>(handle);
|
||||
if (!listener) {
|
||||
return X_ERROR_INVALID_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool dequeued = false;
|
||||
|
|
|
@ -64,7 +64,7 @@ dword_result_t XamShowNuiTroubleshooterUI(unknown_t unk1, unknown_t unk2,
|
|||
}
|
||||
DECLARE_XAM_EXPORT1(XamShowNuiTroubleshooterUI, kNone, kStub);
|
||||
|
||||
void RegisterNuiExports(xe::cpu::ExportResolver* export_resolver,
|
||||
void RegisterNUIExports(xe::cpu::ExportResolver* export_resolver,
|
||||
KernelState* kernel_state) {}
|
||||
|
||||
} // namespace xam
|
||||
|
|
|
@ -18,26 +18,16 @@ namespace xe {
|
|||
namespace kernel {
|
||||
namespace xam {
|
||||
|
||||
bool xeXamIsUIActive();
|
||||
|
||||
xe::cpu::Export* RegisterExport_xam(xe::cpu::Export* export_entry);
|
||||
|
||||
// Registration functions, one per file.
|
||||
#define DECLARE_REGISTER_EXPORTS(n) \
|
||||
#define XE_MODULE_EXPORT_GROUP(m, n) \
|
||||
void Register##n##Exports(xe::cpu::ExportResolver* export_resolver, \
|
||||
KernelState* kernel_state)
|
||||
DECLARE_REGISTER_EXPORTS(Avatar);
|
||||
DECLARE_REGISTER_EXPORTS(Content);
|
||||
DECLARE_REGISTER_EXPORTS(Info);
|
||||
DECLARE_REGISTER_EXPORTS(Input);
|
||||
DECLARE_REGISTER_EXPORTS(Locale);
|
||||
DECLARE_REGISTER_EXPORTS(Msg);
|
||||
DECLARE_REGISTER_EXPORTS(Net);
|
||||
DECLARE_REGISTER_EXPORTS(Notify);
|
||||
DECLARE_REGISTER_EXPORTS(Nui);
|
||||
DECLARE_REGISTER_EXPORTS(UI);
|
||||
DECLARE_REGISTER_EXPORTS(User);
|
||||
DECLARE_REGISTER_EXPORTS(Video);
|
||||
DECLARE_REGISTER_EXPORTS(Voice);
|
||||
#undef DECLARE_REGISTER_EXPORTS
|
||||
KernelState* kernel_state);
|
||||
#include "xam_module_export_groups.inc"
|
||||
#undef XE_MODULE_EXPORT_GROUP
|
||||
|
||||
} // namespace xam
|
||||
} // namespace kernel
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/string_util.h"
|
||||
#include "xenia/cpu/processor.h"
|
||||
#include "xenia/kernel/kernel_state.h"
|
||||
#include "xenia/kernel/util/shim_utils.h"
|
||||
#include "xenia/kernel/xam/xam_module.h"
|
||||
#include "xenia/kernel/xam/xam_private.h"
|
||||
#include "xenia/kernel/xthread.h"
|
||||
#include "xenia/xbox.h"
|
||||
|
||||
#if XE_PLATFORM_WIN32
|
||||
#include "xenia/base/platform_win.h"
|
||||
#endif
|
||||
|
||||
#include "third_party/fmt/include/fmt/format.h"
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
|
||||
struct XTASK_MESSAGE {
|
||||
be<uint32_t> unknown_00;
|
||||
be<uint32_t> unknown_04;
|
||||
be<uint32_t> unknown_08;
|
||||
be<uint32_t> callback_arg_ptr;
|
||||
be<uint32_t> event_handle;
|
||||
be<uint32_t> unknown_14;
|
||||
be<uint32_t> task_handle;
|
||||
};
|
||||
static_assert_size(XTASK_MESSAGE, 0x1C);
|
||||
|
||||
dword_result_t XamTaskSchedule(lpvoid_t callback,
|
||||
pointer_t<XTASK_MESSAGE> message,
|
||||
dword_t unknown, lpdword_t handle_ptr) {
|
||||
assert_zero(unknown);
|
||||
|
||||
// TODO(gibbed): figure out what this is for
|
||||
*handle_ptr = 12345;
|
||||
|
||||
XELOGW("!! Executing scheduled task ({:08X}) synchronously, PROBABLY BAD !! ",
|
||||
callback.guest_address());
|
||||
|
||||
// TODO(gibbed): this is supposed to be async... let's cheat.
|
||||
auto thread_state = XThread::GetCurrentThread()->thread_state();
|
||||
uint64_t args[] = {message.guest_address()};
|
||||
auto result = kernel_state()->processor()->Execute(thread_state, callback,
|
||||
args, xe::countof(args));
|
||||
return X_STATUS_SUCCESS;
|
||||
}
|
||||
DECLARE_XAM_EXPORT2(XamTaskSchedule, kNone, kImplemented, kSketchy);
|
||||
|
||||
void RegisterTaskExports(xe::cpu::ExportResolver* export_resolver,
|
||||
KernelState* kernel_state) {}
|
||||
|
||||
} // namespace xam
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
|
@ -23,31 +23,196 @@ namespace xe {
|
|||
namespace kernel {
|
||||
namespace xam {
|
||||
|
||||
std::atomic<int> xam_dialogs_shown_ = {0};
|
||||
// TODO(gibbed): This is all one giant WIP that seems to work better than the
|
||||
// previous immediate synchronous completion of dialogs.
|
||||
//
|
||||
// The deferred execution of dialog handling is done in such a way that there is
|
||||
// a pre-, peri- (completion), and post- callback steps.
|
||||
//
|
||||
// pre();
|
||||
// result = completion();
|
||||
// CompleteOverlapped(result);
|
||||
// post();
|
||||
//
|
||||
// There are games that are batshit insane enough to wait for the X_OVERLAPPED
|
||||
// to be completed (ie not X_ERROR_PENDING) before creating a listener to
|
||||
// receive a notification, which is why we have distinct pre- and post- steps.
|
||||
//
|
||||
// We deliberately delay the XN_SYS_UI = false notification to give games time
|
||||
// to create a listener (if they're insane enough do this).
|
||||
|
||||
dword_result_t XamIsUIActive() { return xam_dialogs_shown_ > 0 ? 1 : 0; }
|
||||
extern std::atomic<int> xam_dialogs_shown_;
|
||||
|
||||
class XamDialog : public xe::ui::ImGuiDialog {
|
||||
public:
|
||||
void set_close_callback(std::function<void()> close_callback) {
|
||||
close_callback_ = close_callback;
|
||||
}
|
||||
|
||||
protected:
|
||||
XamDialog(xe::ui::Window* window) : xe::ui::ImGuiDialog(window) {}
|
||||
|
||||
void OnClose() override {
|
||||
if (close_callback_) {
|
||||
close_callback_();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void()> close_callback_ = nullptr;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
X_RESULT xeXamDispatchDialog(T* dialog,
|
||||
std::function<X_RESULT(T*)> close_callback,
|
||||
uint32_t overlapped) {
|
||||
auto pre = []() {
|
||||
// Broadcast XN_SYS_UI = true
|
||||
kernel_state()->BroadcastNotification(0x9, true);
|
||||
};
|
||||
auto run = [dialog, close_callback]() -> X_RESULT {
|
||||
X_RESULT result;
|
||||
dialog->set_close_callback([&dialog, &result, &close_callback]() {
|
||||
result = close_callback(dialog);
|
||||
});
|
||||
xe::threading::Fence fence;
|
||||
kernel_state()->emulator()->display_window()->loop()->PostSynchronous(
|
||||
[&dialog, &fence]() { dialog->Then(&fence); });
|
||||
++xam_dialogs_shown_;
|
||||
fence.Wait();
|
||||
--xam_dialogs_shown_;
|
||||
// dialog should be deleted at this point!
|
||||
return result;
|
||||
};
|
||||
auto post = []() {
|
||||
xe::threading::Sleep(std::chrono::milliseconds(100));
|
||||
// Broadcast XN_SYS_UI = false
|
||||
kernel_state()->BroadcastNotification(0x9, false);
|
||||
};
|
||||
if (!overlapped) {
|
||||
pre();
|
||||
auto result = run();
|
||||
post();
|
||||
return result;
|
||||
} else {
|
||||
kernel_state()->CompleteOverlappedDeferred(run, overlapped, pre, post);
|
||||
return X_ERROR_IO_PENDING;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
X_RESULT xeXamDispatchDialogEx(
|
||||
T* dialog, std::function<X_RESULT(T*, uint32_t&, uint32_t&)> close_callback,
|
||||
uint32_t overlapped) {
|
||||
auto pre = []() {
|
||||
// Broadcast XN_SYS_UI = true
|
||||
kernel_state()->BroadcastNotification(0x9, true);
|
||||
};
|
||||
auto run = [dialog, close_callback](uint32_t& extended_error,
|
||||
uint32_t& length) -> X_RESULT {
|
||||
auto display_window = kernel_state()->emulator()->display_window();
|
||||
X_RESULT result;
|
||||
dialog->set_close_callback(
|
||||
[&dialog, &result, &extended_error, &length, &close_callback]() {
|
||||
result = close_callback(dialog, extended_error, length);
|
||||
});
|
||||
xe::threading::Fence fence;
|
||||
display_window->loop()->PostSynchronous(
|
||||
[&dialog, &fence]() { dialog->Then(&fence); });
|
||||
++xam_dialogs_shown_;
|
||||
fence.Wait();
|
||||
--xam_dialogs_shown_;
|
||||
// dialog should be deleted at this point!
|
||||
return result;
|
||||
};
|
||||
auto post = []() {
|
||||
xe::threading::Sleep(std::chrono::milliseconds(100));
|
||||
// Broadcast XN_SYS_UI = false
|
||||
kernel_state()->BroadcastNotification(0x9, false);
|
||||
};
|
||||
if (!overlapped) {
|
||||
pre();
|
||||
uint32_t extended_error, length;
|
||||
auto result = run(extended_error, length);
|
||||
post();
|
||||
// TODO(gibbed): do something with extended_error/length?
|
||||
return result;
|
||||
} else {
|
||||
kernel_state()->CompleteOverlappedDeferredEx(run, overlapped, pre, post);
|
||||
return X_ERROR_IO_PENDING;
|
||||
}
|
||||
}
|
||||
|
||||
X_RESULT xeXamDispatchHeadless(std::function<X_RESULT()> run_callback,
|
||||
uint32_t overlapped) {
|
||||
auto pre = []() {
|
||||
// Broadcast XN_SYS_UI = true
|
||||
kernel_state()->BroadcastNotification(0x9, true);
|
||||
};
|
||||
auto post = []() {
|
||||
xe::threading::Sleep(std::chrono::milliseconds(100));
|
||||
// Broadcast XN_SYS_UI = false
|
||||
kernel_state()->BroadcastNotification(0x9, false);
|
||||
};
|
||||
if (!overlapped) {
|
||||
pre();
|
||||
auto result = run_callback();
|
||||
post();
|
||||
return result;
|
||||
} else {
|
||||
kernel_state()->CompleteOverlappedDeferred(run_callback, overlapped, pre,
|
||||
post);
|
||||
return X_ERROR_IO_PENDING;
|
||||
}
|
||||
}
|
||||
|
||||
X_RESULT xeXamDispatchHeadlessEx(
|
||||
std::function<X_RESULT(uint32_t&, uint32_t&)> run_callback,
|
||||
uint32_t overlapped) {
|
||||
auto pre = []() {
|
||||
// Broadcast XN_SYS_UI = true
|
||||
kernel_state()->BroadcastNotification(0x9, true);
|
||||
};
|
||||
auto post = []() {
|
||||
xe::threading::Sleep(std::chrono::milliseconds(100));
|
||||
// Broadcast XN_SYS_UI = false
|
||||
kernel_state()->BroadcastNotification(0x9, false);
|
||||
};
|
||||
if (!overlapped) {
|
||||
pre();
|
||||
uint32_t extended_error, length;
|
||||
auto result = run_callback(extended_error, length);
|
||||
post();
|
||||
// TODO(gibbed): do something with extended_error/length?
|
||||
return result;
|
||||
} else {
|
||||
kernel_state()->CompleteOverlappedDeferredEx(run_callback, overlapped, pre,
|
||||
post);
|
||||
return X_ERROR_IO_PENDING;
|
||||
}
|
||||
}
|
||||
|
||||
dword_result_t XamIsUIActive() { return xeXamIsUIActive(); }
|
||||
DECLARE_XAM_EXPORT2(XamIsUIActive, kUI, kImplemented, kHighFrequency);
|
||||
|
||||
class MessageBoxDialog : public xe::ui::ImGuiDialog {
|
||||
class MessageBoxDialog : public XamDialog {
|
||||
public:
|
||||
MessageBoxDialog(xe::ui::Window* window, std::u16string title,
|
||||
std::u16string description,
|
||||
std::vector<std::u16string> buttons, uint32_t default_button,
|
||||
uint32_t* out_chosen_button)
|
||||
: ImGuiDialog(window),
|
||||
title_(xe::to_utf8(title)),
|
||||
description_(xe::to_utf8(description)),
|
||||
MessageBoxDialog(xe::ui::Window* window, std::string title,
|
||||
std::string description, std::vector<std::string> buttons,
|
||||
uint32_t default_button)
|
||||
: XamDialog(window),
|
||||
title_(title),
|
||||
description_(description),
|
||||
buttons_(std::move(buttons)),
|
||||
default_button_(default_button),
|
||||
out_chosen_button_(out_chosen_button) {
|
||||
chosen_button_(default_button) {
|
||||
if (!title_.size()) {
|
||||
title_ = "Message Box";
|
||||
}
|
||||
if (out_chosen_button) {
|
||||
*out_chosen_button = default_button;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t chosen_button() const { return chosen_button_; }
|
||||
|
||||
void OnDraw(ImGuiIO& io) override {
|
||||
bool first_draw = false;
|
||||
if (!has_opened_) {
|
||||
|
@ -64,11 +229,8 @@ class MessageBoxDialog : public xe::ui::ImGuiDialog {
|
|||
ImGui::SetKeyboardFocusHere();
|
||||
}
|
||||
for (size_t i = 0; i < buttons_.size(); ++i) {
|
||||
auto button_name = xe::to_utf8(buttons_[i]);
|
||||
if (ImGui::Button(button_name.c_str())) {
|
||||
if (out_chosen_button_) {
|
||||
*out_chosen_button_ = static_cast<uint32_t>(i);
|
||||
}
|
||||
if (ImGui::Button(buttons_[i].c_str())) {
|
||||
chosen_button_ = static_cast<uint32_t>(i);
|
||||
ImGui::CloseCurrentPopup();
|
||||
Close();
|
||||
}
|
||||
|
@ -86,9 +248,9 @@ class MessageBoxDialog : public xe::ui::ImGuiDialog {
|
|||
bool has_opened_ = false;
|
||||
std::string title_;
|
||||
std::string description_;
|
||||
std::vector<std::u16string> buttons_;
|
||||
std::vector<std::string> buttons_;
|
||||
uint32_t default_button_ = 0;
|
||||
uint32_t* out_chosen_button_ = nullptr;
|
||||
uint32_t chosen_button_ = 0;
|
||||
};
|
||||
|
||||
// https://www.se7ensins.com/forums/threads/working-xshowmessageboxui.844116/
|
||||
|
@ -97,86 +259,71 @@ dword_result_t XamShowMessageBoxUI(dword_t user_index, lpu16string_t title_ptr,
|
|||
lpdword_t button_ptrs, dword_t active_button,
|
||||
dword_t flags, lpdword_t result_ptr,
|
||||
pointer_t<XAM_OVERLAPPED> overlapped) {
|
||||
std::u16string title;
|
||||
std::string title;
|
||||
if (title_ptr) {
|
||||
title = title_ptr.value();
|
||||
title = xe::to_utf8(title_ptr.value());
|
||||
} else {
|
||||
title = u""; // TODO(gibbed): default title based on flags?
|
||||
title = ""; // TODO(gibbed): default title based on flags?
|
||||
}
|
||||
auto text = text_ptr.value();
|
||||
|
||||
std::vector<std::u16string> buttons;
|
||||
std::u16string all_buttons;
|
||||
for (uint32_t j = 0; j < button_count; ++j) {
|
||||
uint32_t button_ptr = button_ptrs[j];
|
||||
std::vector<std::string> buttons;
|
||||
for (uint32_t i = 0; i < button_count; ++i) {
|
||||
uint32_t button_ptr = button_ptrs[i];
|
||||
auto button = xe::load_and_swap<std::u16string>(
|
||||
kernel_state()->memory()->TranslateVirtual(button_ptr));
|
||||
all_buttons.append(button);
|
||||
if (j + 1 < button_count) {
|
||||
all_buttons.append(u" | ");
|
||||
}
|
||||
buttons.push_back(button);
|
||||
buttons.push_back(xe::to_utf8(button));
|
||||
}
|
||||
|
||||
// Broadcast XN_SYS_UI = true
|
||||
kernel_state()->BroadcastNotification(0x9, true);
|
||||
|
||||
uint32_t chosen_button;
|
||||
X_RESULT result;
|
||||
if (cvars::headless) {
|
||||
// Auto-pick the focused button.
|
||||
chosen_button = active_button;
|
||||
auto run = [result_ptr, active_button]() -> X_RESULT {
|
||||
*result_ptr = static_cast<uint32_t>(active_button);
|
||||
return X_ERROR_SUCCESS;
|
||||
};
|
||||
result = xeXamDispatchHeadless(run, overlapped);
|
||||
} else {
|
||||
// TODO(benvanik): setup icon states.
|
||||
switch (flags & 0xF) {
|
||||
case 0:
|
||||
// config.pszMainIcon = nullptr;
|
||||
break;
|
||||
case 1:
|
||||
// config.pszMainIcon = TD_ERROR_ICON;
|
||||
break;
|
||||
case 2:
|
||||
// config.pszMainIcon = TD_WARNING_ICON;
|
||||
break;
|
||||
case 3:
|
||||
// config.pszMainIcon = TD_INFORMATION_ICON;
|
||||
break;
|
||||
}
|
||||
auto close = [result_ptr](MessageBoxDialog* dialog) -> X_RESULT {
|
||||
*result_ptr = dialog->chosen_button();
|
||||
return X_ERROR_SUCCESS;
|
||||
};
|
||||
auto display_window = kernel_state()->emulator()->display_window();
|
||||
xe::threading::Fence fence;
|
||||
display_window->loop()->PostSynchronous([&]() {
|
||||
// TODO(benvanik): setup icon states.
|
||||
switch (flags & 0xF) {
|
||||
case 0:
|
||||
// config.pszMainIcon = nullptr;
|
||||
break;
|
||||
case 1:
|
||||
// config.pszMainIcon = TD_ERROR_ICON;
|
||||
break;
|
||||
case 2:
|
||||
// config.pszMainIcon = TD_WARNING_ICON;
|
||||
break;
|
||||
case 3:
|
||||
// config.pszMainIcon = TD_INFORMATION_ICON;
|
||||
break;
|
||||
}
|
||||
(new MessageBoxDialog(display_window, title, text, buttons, active_button,
|
||||
&chosen_button))
|
||||
->Then(&fence);
|
||||
});
|
||||
++xam_dialogs_shown_;
|
||||
fence.Wait();
|
||||
--xam_dialogs_shown_;
|
||||
}
|
||||
*result_ptr = chosen_button;
|
||||
|
||||
// Broadcast XN_SYS_UI = false
|
||||
kernel_state()->BroadcastNotification(0x9, false);
|
||||
|
||||
if (overlapped) {
|
||||
kernel_state()->CompleteOverlappedImmediate(overlapped, X_ERROR_SUCCESS);
|
||||
return X_ERROR_IO_PENDING;
|
||||
} else {
|
||||
return X_ERROR_SUCCESS;
|
||||
result = xeXamDispatchDialog<MessageBoxDialog>(
|
||||
new MessageBoxDialog(display_window, title,
|
||||
xe::to_utf8(text_ptr.value()), buttons,
|
||||
active_button),
|
||||
close, overlapped);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamShowMessageBoxUI, kUI, kImplemented);
|
||||
|
||||
class KeyboardInputDialog : public xe::ui::ImGuiDialog {
|
||||
class KeyboardInputDialog : public XamDialog {
|
||||
public:
|
||||
KeyboardInputDialog(xe::ui::Window* window, std::u16string title,
|
||||
std::u16string description, std::u16string default_text,
|
||||
std::u16string* out_text, size_t max_length)
|
||||
: ImGuiDialog(window),
|
||||
title_(xe::to_utf8(title)),
|
||||
description_(xe::to_utf8(description)),
|
||||
default_text_(xe::to_utf8(default_text)),
|
||||
out_text_(out_text),
|
||||
max_length_(max_length) {
|
||||
KeyboardInputDialog(xe::ui::Window* window, std::string title,
|
||||
std::string description, std::string default_text,
|
||||
size_t max_length)
|
||||
: XamDialog(window),
|
||||
title_(title),
|
||||
description_(description),
|
||||
default_text_(default_text),
|
||||
max_length_(max_length),
|
||||
text_buffer_() {
|
||||
if (!title_.size()) {
|
||||
if (!description_.size()) {
|
||||
title_ = "Keyboard Input";
|
||||
|
@ -185,14 +332,15 @@ class KeyboardInputDialog : public xe::ui::ImGuiDialog {
|
|||
description_ = "";
|
||||
}
|
||||
}
|
||||
if (out_text_) {
|
||||
*out_text_ = default_text;
|
||||
}
|
||||
text_ = default_text;
|
||||
text_buffer_.resize(max_length);
|
||||
xe::string_util::copy_truncating(text_buffer_.data(), default_text_,
|
||||
text_buffer_.size());
|
||||
}
|
||||
|
||||
const std::string& text() const { return text_; }
|
||||
bool cancelled() const { return cancelled_; }
|
||||
|
||||
void OnDraw(ImGuiIO& io) override {
|
||||
bool first_draw = false;
|
||||
if (!has_opened_) {
|
||||
|
@ -210,23 +358,21 @@ class KeyboardInputDialog : public xe::ui::ImGuiDialog {
|
|||
}
|
||||
if (ImGui::InputText("##body", text_buffer_.data(), text_buffer_.size(),
|
||||
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||
if (out_text_) {
|
||||
*out_text_ = xe::to_utf16(
|
||||
std::string_view(text_buffer_.data(), text_buffer_.size()));
|
||||
}
|
||||
text_ = std::string(text_buffer_.data(), text_buffer_.size());
|
||||
cancelled_ = false;
|
||||
ImGui::CloseCurrentPopup();
|
||||
Close();
|
||||
}
|
||||
if (ImGui::Button("OK")) {
|
||||
if (out_text_) {
|
||||
*out_text_ = xe::to_utf16(
|
||||
std::string_view(text_buffer_.data(), text_buffer_.size()));
|
||||
}
|
||||
text_ = std::string(text_buffer_.data(), text_buffer_.size());
|
||||
cancelled_ = false;
|
||||
ImGui::CloseCurrentPopup();
|
||||
Close();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel")) {
|
||||
text_ = "";
|
||||
cancelled_ = true;
|
||||
ImGui::CloseCurrentPopup();
|
||||
Close();
|
||||
}
|
||||
|
@ -242,9 +388,10 @@ class KeyboardInputDialog : public xe::ui::ImGuiDialog {
|
|||
std::string title_;
|
||||
std::string description_;
|
||||
std::string default_text_;
|
||||
std::u16string* out_text_ = nullptr;
|
||||
std::vector<char> text_buffer_;
|
||||
size_t max_length_ = 0;
|
||||
std::vector<char> text_buffer_;
|
||||
std::string text_ = "";
|
||||
bool cancelled_ = true;
|
||||
};
|
||||
|
||||
// https://www.se7ensins.com/forums/threads/release-how-to-use-xshowkeyboardui-release.906568/
|
||||
|
@ -257,58 +404,51 @@ dword_result_t XamShowKeyboardUI(dword_t user_index, dword_t flags,
|
|||
return X_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
// Broadcast XN_SYS_UI = true
|
||||
kernel_state()->BroadcastNotification(0x9, true);
|
||||
assert_not_null(overlapped);
|
||||
|
||||
auto buffer_size = static_cast<size_t>(buffer_length) * 2;
|
||||
|
||||
X_RESULT result;
|
||||
if (cvars::headless) {
|
||||
// Redirect default_text back into the buffer.
|
||||
std::memset(buffer, 0, buffer_length * 2);
|
||||
if (default_text) {
|
||||
xe::store_and_swap<std::u16string>(buffer, default_text.value());
|
||||
}
|
||||
|
||||
// Broadcast XN_SYS_UI = false
|
||||
kernel_state()->BroadcastNotification(0x9, false);
|
||||
|
||||
if (overlapped) {
|
||||
kernel_state()->CompleteOverlappedImmediate(overlapped, X_ERROR_SUCCESS);
|
||||
return X_ERROR_IO_PENDING;
|
||||
} else {
|
||||
auto run = [default_text, buffer, buffer_length,
|
||||
buffer_size]() -> X_RESULT {
|
||||
// Redirect default_text back into the buffer.
|
||||
if (!default_text) {
|
||||
std::memset(buffer, 0, buffer_size);
|
||||
} else {
|
||||
string_util::copy_and_swap_truncating(buffer, default_text.value(),
|
||||
buffer_length);
|
||||
}
|
||||
return X_ERROR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
std::u16string out_text;
|
||||
|
||||
auto display_window = kernel_state()->emulator()->display_window();
|
||||
xe::threading::Fence fence;
|
||||
display_window->loop()->PostSynchronous([&]() {
|
||||
(new KeyboardInputDialog(display_window, title ? title.value() : u"",
|
||||
description ? description.value() : u"",
|
||||
default_text ? default_text.value() : u"",
|
||||
&out_text, buffer_length))
|
||||
->Then(&fence);
|
||||
});
|
||||
++xam_dialogs_shown_;
|
||||
fence.Wait();
|
||||
--xam_dialogs_shown_;
|
||||
|
||||
// Zero the output buffer.
|
||||
std::memset(buffer, 0, buffer_length * 2);
|
||||
|
||||
// Truncate the string.
|
||||
out_text = out_text.substr(0, buffer_length - 1);
|
||||
xe::store_and_swap<std::u16string>(buffer, out_text);
|
||||
|
||||
// Broadcast XN_SYS_UI = false
|
||||
kernel_state()->BroadcastNotification(0x9, false);
|
||||
|
||||
if (overlapped) {
|
||||
kernel_state()->CompleteOverlappedImmediate(overlapped, X_ERROR_SUCCESS);
|
||||
return X_ERROR_IO_PENDING;
|
||||
};
|
||||
result = xeXamDispatchHeadless(run, overlapped);
|
||||
} else {
|
||||
return X_ERROR_SUCCESS;
|
||||
auto close = [buffer, buffer_length](KeyboardInputDialog* dialog,
|
||||
uint32_t& extended_error,
|
||||
uint32_t& length) -> X_RESULT {
|
||||
if (dialog->cancelled()) {
|
||||
extended_error = X_ERROR_CANCELLED;
|
||||
length = 0;
|
||||
return X_ERROR_SUCCESS;
|
||||
} else {
|
||||
// Zero the output buffer.
|
||||
auto text = xe::to_utf16(dialog->text());
|
||||
string_util::copy_and_swap_truncating(buffer, text, buffer_length);
|
||||
extended_error = X_ERROR_SUCCESS;
|
||||
length = 0;
|
||||
return X_ERROR_SUCCESS;
|
||||
}
|
||||
};
|
||||
auto display_window = kernel_state()->emulator()->display_window();
|
||||
result = xeXamDispatchDialogEx<KeyboardInputDialog>(
|
||||
new KeyboardInputDialog(
|
||||
display_window, title ? xe::to_utf8(title.value()) : "",
|
||||
description ? xe::to_utf8(description.value()) : "",
|
||||
default_text ? xe::to_utf8(default_text.value()) : "",
|
||||
buffer_length),
|
||||
close, overlapped);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamShowKeyboardUI, kUI, kImplemented);
|
||||
|
||||
|
@ -317,19 +457,13 @@ dword_result_t XamShowDeviceSelectorUI(dword_t user_index, dword_t content_type,
|
|||
qword_t total_requested,
|
||||
lpdword_t device_id_ptr,
|
||||
pointer_t<XAM_OVERLAPPED> overlapped) {
|
||||
// NOTE: 0x00000001 is our dummy device ID from xam_content.cc
|
||||
*device_id_ptr = 0x00000001;
|
||||
|
||||
// Broadcast XN_SYS_UI = true followed by XN_SYS_UI = false
|
||||
kernel_state()->BroadcastNotification(0x9, true);
|
||||
kernel_state()->BroadcastNotification(0x9, false);
|
||||
|
||||
if (overlapped) {
|
||||
kernel_state()->CompleteOverlappedImmediate(overlapped, X_ERROR_SUCCESS);
|
||||
return X_ERROR_IO_PENDING;
|
||||
} else {
|
||||
return X_ERROR_SUCCESS;
|
||||
}
|
||||
return xeXamDispatchHeadless(
|
||||
[device_id_ptr]() -> X_RESULT {
|
||||
// NOTE: 0x00000001 is our dummy device ID from xam_content.cc
|
||||
*device_id_ptr = 0x00000001;
|
||||
return X_ERROR_SUCCESS;
|
||||
},
|
||||
overlapped);
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamShowDeviceSelectorUI, kUI, kImplemented);
|
||||
|
||||
|
@ -339,20 +473,14 @@ void XamShowDirtyDiscErrorUI(dword_t user_index) {
|
|||
exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
auto display_window = kernel_state()->emulator()->display_window();
|
||||
xe::threading::Fence fence;
|
||||
display_window->loop()->PostSynchronous([&]() {
|
||||
xe::ui::ImGuiDialog::ShowMessageBox(
|
||||
display_window, "Disc Read Error",
|
||||
"There's been an issue reading content from the game disc.\nThis is "
|
||||
"likely caused by bad or unimplemented file IO calls.")
|
||||
->Then(&fence);
|
||||
});
|
||||
++xam_dialogs_shown_;
|
||||
fence.Wait();
|
||||
--xam_dialogs_shown_;
|
||||
|
||||
xeXamDispatchDialog<MessageBoxDialog>(
|
||||
new MessageBoxDialog(
|
||||
display_window, "Disc Read Error",
|
||||
"There's been an issue reading content from the game disc.\nThis is "
|
||||
"likely caused by bad or unimplemented file IO calls.",
|
||||
{"OK"}, 0),
|
||||
[](MessageBoxDialog*) -> X_RESULT { return X_ERROR_SUCCESS; }, 0);
|
||||
// This is death, and should never return.
|
||||
// TODO(benvanik): cleaner exit.
|
||||
exit(1);
|
||||
|
|
|
@ -562,11 +562,14 @@ dword_result_t XamUserCreateAchievementEnumerator(dword_t title_id,
|
|||
*buffer_size_ptr = 500 * count;
|
||||
}
|
||||
|
||||
auto e = new XStaticEnumerator(kernel_state(), count, 500);
|
||||
e->Initialize();
|
||||
auto e = object_ref<XStaticEnumerator>(
|
||||
new XStaticEnumerator(kernel_state(), count, 500));
|
||||
auto result = e->Initialize(user_index, 0xFB, 0xB000A, 0xB000B, 0);
|
||||
if (XFAILED(result)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
*handle_ptr = e->handle();
|
||||
|
||||
return X_ERROR_SUCCESS;
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamUserCreateAchievementEnumerator, kUserProfiles,
|
||||
|
|
|
@ -20,7 +20,34 @@ XEnumerator::XEnumerator(KernelState* kernel_state, size_t items_per_enumerate,
|
|||
|
||||
XEnumerator::~XEnumerator() = default;
|
||||
|
||||
void XEnumerator::Initialize() {}
|
||||
X_STATUS XEnumerator::Initialize(uint32_t user_index, uint32_t app_id,
|
||||
uint32_t message, uint32_t message2,
|
||||
uint32_t flags, uint32_t extra_size,
|
||||
void** extra_buffer) {
|
||||
auto native_object = CreateNative(sizeof(X_KENUMERATOR) + extra_size);
|
||||
if (!native_object) {
|
||||
return X_STATUS_NO_MEMORY;
|
||||
}
|
||||
auto guest_object = reinterpret_cast<X_KENUMERATOR*>(native_object);
|
||||
guest_object->app_id = app_id;
|
||||
guest_object->message = message;
|
||||
guest_object->message2 = message2;
|
||||
guest_object->user_index = user_index;
|
||||
guest_object->items_per_enumerate =
|
||||
static_cast<uint32_t>(items_per_enumerate_);
|
||||
guest_object->flags = flags;
|
||||
if (extra_buffer) {
|
||||
*extra_buffer =
|
||||
!extra_buffer ? nullptr : &native_object[sizeof(X_KENUMERATOR)];
|
||||
}
|
||||
return X_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
X_STATUS XEnumerator::Initialize(uint32_t user_index, uint32_t app_id,
|
||||
uint32_t message, uint32_t message2,
|
||||
uint32_t flags) {
|
||||
return Initialize(user_index, app_id, message, message2, flags, 0, nullptr);
|
||||
}
|
||||
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
|
|
|
@ -19,6 +19,21 @@
|
|||
namespace xe {
|
||||
namespace kernel {
|
||||
|
||||
struct X_KENUMERATOR {
|
||||
be<uint32_t> app_id;
|
||||
be<uint32_t> message;
|
||||
be<uint32_t> message2;
|
||||
be<uint32_t> user_index;
|
||||
be<uint32_t> items_per_enumerate;
|
||||
be<uint32_t> flags;
|
||||
};
|
||||
static_assert_size(X_KENUMERATOR, 0x18);
|
||||
|
||||
struct X_KENUMERATOR_CONTENT_AGGREGATE {
|
||||
be<uint32_t> magic;
|
||||
be<uint32_t> handle;
|
||||
};
|
||||
|
||||
class XEnumerator : public XObject {
|
||||
public:
|
||||
static const XObject::Type kObjectType = XObject::Type::Enumerator;
|
||||
|
@ -27,7 +42,24 @@ class XEnumerator : public XObject {
|
|||
size_t item_size);
|
||||
virtual ~XEnumerator();
|
||||
|
||||
void Initialize();
|
||||
X_STATUS Initialize(uint32_t user_index, uint32_t app_id, uint32_t message,
|
||||
uint32_t message2, uint32_t flags, uint32_t extra_size,
|
||||
void** extra_buffer);
|
||||
|
||||
X_STATUS Initialize(uint32_t user_index, uint32_t app_id, uint32_t message,
|
||||
uint32_t message2, uint32_t flags);
|
||||
|
||||
template <typename T>
|
||||
X_STATUS Initialize(uint32_t user_index, uint32_t app_id, uint32_t message,
|
||||
uint32_t message2, uint32_t flags, T** extra) {
|
||||
void* dummy;
|
||||
auto result = Initialize(user_index, app_id, message, message2, flags,
|
||||
static_cast<uint32_t>(sizeof(T)), &dummy);
|
||||
if (extra) {
|
||||
*extra = XFAILED(result) ? nullptr : static_cast<T*>(dummy);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
virtual uint32_t item_count() const = 0;
|
||||
virtual void WriteItems(uint8_t* buffer) = 0;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "xenia/kernel/xnotifylistener.h"
|
||||
|
||||
#include "xenia/base/byte_stream.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/kernel/kernel_state.h"
|
||||
|
||||
namespace xe {
|
||||
|
@ -20,21 +21,26 @@ XNotifyListener::XNotifyListener(KernelState* kernel_state)
|
|||
|
||||
XNotifyListener::~XNotifyListener() {}
|
||||
|
||||
void XNotifyListener::Initialize(uint64_t mask) {
|
||||
void XNotifyListener::Initialize(uint64_t mask, uint32_t max_version) {
|
||||
assert_false(wait_handle_);
|
||||
|
||||
wait_handle_ = xe::threading::Event::CreateManualResetEvent(false);
|
||||
mask_ = mask;
|
||||
max_version_ = max_version;
|
||||
|
||||
kernel_state_->RegisterNotifyListener(this);
|
||||
}
|
||||
|
||||
void XNotifyListener::EnqueueNotification(XNotificationID id, uint32_t data) {
|
||||
auto key = XNotificationKey{id};
|
||||
// Ignore if the notification doesn't match our mask.
|
||||
if ((mask_ & uint64_t(1ULL << (id >> 25))) == 0) {
|
||||
if ((mask_ & uint64_t(1ULL << key.mask_index)) == 0) {
|
||||
return;
|
||||
}
|
||||
// Ignore if the notification is too new.
|
||||
if (key.version > max_version_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
notifications_.push_back(std::pair<XNotificationID, uint32_t>(id, data));
|
||||
wait_handle_->Set();
|
||||
|
@ -60,16 +66,14 @@ bool XNotifyListener::DequeueNotification(XNotificationID* out_id,
|
|||
bool XNotifyListener::DequeueNotification(XNotificationID id,
|
||||
uint32_t* out_data) {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
bool dequeued = false;
|
||||
if (!notifications_.size()) {
|
||||
return dequeued;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool dequeued = false;
|
||||
for (auto it = notifications_.begin(); it != notifications_.end(); ++it) {
|
||||
if (it->first != id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
dequeued = true;
|
||||
*out_data = it->second;
|
||||
notifications_.erase(it);
|
||||
|
@ -85,12 +89,11 @@ bool XNotifyListener::Save(ByteStream* stream) {
|
|||
SaveObject(stream);
|
||||
|
||||
stream->Write(mask_);
|
||||
stream->Write(max_version_);
|
||||
stream->Write(notifications_.size());
|
||||
if (notifications_.size()) {
|
||||
for (auto pair : notifications_) {
|
||||
stream->Write<uint32_t>(pair.first);
|
||||
stream->Write<uint32_t>(pair.second);
|
||||
}
|
||||
for (auto pair : notifications_) {
|
||||
stream->Write<uint32_t>(pair.first);
|
||||
stream->Write<uint32_t>(pair.second);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -102,7 +105,10 @@ object_ref<XNotifyListener> XNotifyListener::Restore(KernelState* kernel_state,
|
|||
notify->kernel_state_ = kernel_state;
|
||||
|
||||
notify->RestoreObject(stream);
|
||||
notify->Initialize(stream->Read<uint64_t>());
|
||||
|
||||
auto mask = stream->Read<uint64_t>();
|
||||
auto max_version = stream->Read<uint32_t>();
|
||||
notify->Initialize(mask, max_version);
|
||||
|
||||
auto notification_count_ = stream->Read<size_t>();
|
||||
for (size_t i = 0; i < notification_count_; i++) {
|
||||
|
|
|
@ -21,6 +21,24 @@
|
|||
namespace xe {
|
||||
namespace kernel {
|
||||
|
||||
union XNotificationKey {
|
||||
struct {
|
||||
uint32_t local_id : 16;
|
||||
uint32_t version : 9;
|
||||
uint32_t mask_index : 6;
|
||||
uint32_t : 1;
|
||||
};
|
||||
XNotificationID id;
|
||||
|
||||
static constexpr XNotificationID get_id(uint8_t mask_index,
|
||||
uint16_t local_id) {
|
||||
XNotificationKey key = {};
|
||||
key.mask_index = mask_index;
|
||||
key.local_id = local_id;
|
||||
return key.id;
|
||||
}
|
||||
};
|
||||
|
||||
class XNotifyListener : public XObject {
|
||||
public:
|
||||
static const XObject::Type kObjectType = XObject::Type::NotifyListener;
|
||||
|
@ -29,8 +47,9 @@ class XNotifyListener : public XObject {
|
|||
~XNotifyListener() override;
|
||||
|
||||
uint64_t mask() const { return mask_; }
|
||||
uint32_t max_version() const { return max_version_; }
|
||||
|
||||
void Initialize(uint64_t mask);
|
||||
void Initialize(uint64_t mask, uint32_t max_version);
|
||||
|
||||
void EnqueueNotification(XNotificationID id, uint32_t data);
|
||||
bool DequeueNotification(XNotificationID* out_id, uint32_t* out_data);
|
||||
|
@ -50,6 +69,7 @@ class XNotifyListener : public XObject {
|
|||
xe::global_critical_region global_critical_region_;
|
||||
std::vector<std::pair<XNotificationID, uint32_t>> notifications_;
|
||||
uint64_t mask_ = 0;
|
||||
uint32_t max_version_ = 0;
|
||||
};
|
||||
|
||||
} // namespace kernel
|
||||
|
|
|
@ -94,6 +94,7 @@ typedef uint32_t X_RESULT;
|
|||
#define X_ERROR_BAD_ARGUMENTS X_RESULT_FROM_WIN32(0x000000A0L)
|
||||
#define X_ERROR_BUSY X_RESULT_FROM_WIN32(0x000000AAL)
|
||||
#define X_ERROR_ALREADY_EXISTS X_RESULT_FROM_WIN32(0x000000B7L)
|
||||
#define X_ERROR_IO_INCOMPLETE X_RESULT_FROM_WIN32(0x000003E4L)
|
||||
#define X_ERROR_IO_PENDING X_RESULT_FROM_WIN32(0x000003E5L)
|
||||
#define X_ERROR_DEVICE_NOT_CONNECTED X_RESULT_FROM_WIN32(0x0000048FL)
|
||||
#define X_ERROR_NOT_FOUND X_RESULT_FROM_WIN32(0x00000490L)
|
||||
|
@ -112,6 +113,8 @@ typedef uint32_t X_HRESULT;
|
|||
|
||||
#define X_E_FALSE static_cast<X_HRESULT>(0x80000000L)
|
||||
#define X_E_SUCCESS X_HRESULT_FROM_WIN32(X_ERROR_SUCCESS)
|
||||
#define X_E_FAIL static_cast<X_HRESULT>(0x80004005L)
|
||||
#define X_E_NO_MORE_FILES X_HRESULT_FROM_WIN32(X_ERROR_NO_MORE_FILES)
|
||||
#define X_E_INVALIDARG X_HRESULT_FROM_WIN32(X_ERROR_INVALID_PARAMETER)
|
||||
#define X_E_DEVICE_NOT_CONNECTED X_HRESULT_FROM_WIN32(X_ERROR_DEVICE_NOT_CONNECTED)
|
||||
#define X_E_NOTFOUND X_HRESULT_FROM_WIN32(X_ERROR_NOT_FOUND)
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit df609672110ac07ff7ea6597911575c4365c2928
|
||||
Subproject commit 7eba2825887e49d3a72b30e0a7480bd427a5bab0
|
|
@ -0,0 +1,24 @@
|
|||
function Write-FatalError($message) {
|
||||
[Console]::ForegroundColor = 'red'
|
||||
[Console]::Error.WriteLine($message)
|
||||
[Console]::ResetColor()
|
||||
Exit 1
|
||||
}
|
||||
|
||||
$pythonPath = Get-Command python | Select-Object -ExpandProperty Definition
|
||||
|
||||
# Check for 'python3' if 'python' isn't found
|
||||
if ([string]::IsNullOrEmpty($pythonPath)) {
|
||||
$pythonPath = Get-Command python3 | Select-Object -ExpandProperty Definition
|
||||
}
|
||||
# Neither found, error and exit
|
||||
if ([string]::IsNullOrEmpty($pythonPath)) {
|
||||
Write-FatalError "ERROR: no Python executable found on PATH.`nMake sure you can run 'python' or 'python3' in a Command Prompt."
|
||||
}
|
||||
|
||||
python -c "import sys; sys.exit(1 if not sys.version_info[:2] >= (3, 4) else 0)"
|
||||
if ($LASTEXITCODE -gt 0) {
|
||||
Write-FatalError "ERROR: Python version mismatch, not at least 3.4.`nFound Python executable was $($pythonPath)"
|
||||
}
|
||||
|
||||
python "$($PSScriptRoot)/xenia-build" $args
|
Loading…
Reference in New Issue