#include "stdafx.h" #include "Emu/System.h" #include "Emu/Memory/vm.h" #include "Emu/IdManager.h" #include "Emu/Cell/lv2/sys_event.h" #include "Emu/Cell/ErrorCodes.h" #include "sys_config.h" LOG_CHANNEL(sys_config); // Enums template<> void fmt_class_string::format(std::string& out, u64 id) { const s64 s_id = static_cast(id); switch (s_id) { case SYS_CONFIG_SERVICE_PADMANAGER : out += "SYS_CONFIG_SERVICE_PADMANAGER"; return; case SYS_CONFIG_SERVICE_PADMANAGER2 : out += "SYS_CONFIG_SERVICE_PADMANAGER2"; return; case SYS_CONFIG_SERVICE_USER_LIBPAD : out += "SYS_CONFIG_SERVICE_USER_LIBPAD"; return; case SYS_CONFIG_SERVICE_USER_LIBKB : out += "SYS_CONFIG_SERVICE_USER_LIBKB"; return; case SYS_CONFIG_SERVICE_USER_LIBMOUSE: out += "SYS_CONFIG_SERVICE_USER_LIBMOUSE"; return; } if (s_id < 0) { fmt::append(out, "SYS_CONFIG_SERVICE_USER_%llx", id & ~(1ull << 63)); } else { fmt::append(out, "SYS_CONFIG_SERVICE_%llx", id); } } template<> void fmt_class_string::format(std::string& out, u64 arg) { format_enum(out, arg, [](auto value) { switch (value) { STR_CASE(SYS_CONFIG_SERVICE_LISTENER_ONCE); STR_CASE(SYS_CONFIG_SERVICE_LISTENER_REPEATING); } return unknown; }); } // Utilities void dump_buffer(std::string& out, const std::vector& buffer) { if (!buffer.empty()) { out.reserve(out.size() + buffer.size() * 2 + 1); fmt::append(out, "0x"); for (u8 x : buffer) { fmt::append(out, "%02x", x); } } else { fmt::append(out, "EMPTY"); } } // LV2 Config void lv2_config::initialize() { if (m_state || !m_state.compare_and_swap_test(0, 1)) { return; } // Register padmanager service, notifying vsh that a controller is connected static const u8 hid_info[0x1a] = { 0x01, 0x01, // 2 unk 0x02, 0x02, // 4 0x00, 0x00, // 6 0x00, 0x00, // 8 0x00, 0x00, // 10 0x05, 0x4c, // 12 vid 0x02, 0x68, // 14 pid 0x00, 0x10, // 16 unk2 0x91, 0x88, // 18 0x04, 0x00, // 20 0x00, 0x07, // 22 0x00, 0x00, // 24 0x00, 0x00 // 26 }; // user_id for the padmanager seems to signify the controller port number, and the buffer contains some sort of HID descriptor lv2_config_service::create(SYS_CONFIG_SERVICE_PADMANAGER , 0, 1, 0, hid_info, 0x1a)->notify(); lv2_config_service::create(SYS_CONFIG_SERVICE_PADMANAGER2, 0, 1, 0, hid_info, 0x1a)->notify(); } void lv2_config::add_service_event(const std::shared_ptr& event) { std::lock_guard lock(m_mutex); events.emplace(event->id, event); } void lv2_config::remove_service_event(u32 id) { std::lock_guard lock(m_mutex); events.erase(id); } // LV2 Config Service Listener bool lv2_config_service_listener::check_service(const lv2_config_service& service) { // Filter by type if (type == SYS_CONFIG_SERVICE_LISTENER_ONCE && !service_events.empty()) { return false; } // Filter by service ID or verbosity if (service_id != service.id || min_verbosity > service.verbosity) { return false; } // realhw only seems to send the pad connected events to the listeners that provided 0x01 as the first byte of their data buffer // TODO: Figure out how this filter works more properly if (service_id == SYS_CONFIG_SERVICE_PADMANAGER && (data.empty() || data[0] != 0x01)) { return false; } // Event applies to this listener! return true; } bool lv2_config_service_listener::notify(const std::shared_ptr& event) { service_events.emplace_back(event); return event->notify(); } bool lv2_config_service_listener::notify(const std::shared_ptr& service) { if (!check_service(*service)) return false; // Create service event and notify queue! auto event = lv2_config_service_event::create(handle, service, *this); return notify(event); } void lv2_config_service_listener::notify_all() { std::vector> services; // Grab all events idm::select([&](u32 id, lv2_config_service& service) -> void { if (check_service(service)) { services.push_back(service.get_shared_ptr()); } }, 0); // Sort services by timestamp sort(services.begin(), services.end(), [](const std::shared_ptr& s1, const std::shared_ptr& s2) -> bool { return s1->timestamp < s2->timestamp; }); // Notify listener (now with services in sorted order) for (auto& service : services) { this->notify(service); } } // LV2 Config Service void lv2_config_service::unregister() { registered = false; // Notify listeners notify(); // Allow this object to be destroyed by withdrawing it from the IDM // Note that it won't be destroyed while there are service events that hold a reference to it idm::remove(idm_id); } void lv2_config_service::notify() const { std::vector> listeners; auto sptr = wkptr.lock(); idm::select([&](u32 id, lv2_config_service_listener& listener) -> void { if (listener.check_service(*sptr)) listeners.push_back(listener.get_shared_ptr()); }); for (auto& listener : listeners) { listener->notify(this->get_shared_ptr()); } } bool lv2_config_service_event::notify() const { auto _handle = handle.lock(); if (!_handle) { return false; } // Send event return _handle->notify(SYS_CONFIG_EVENT_SOURCE_SERVICE, (static_cast(service->is_registered()) << 32) | id, service->get_size()); } // LV2 Config Service Event void lv2_config_service_event::write(sys_config_service_event_t *dst) { auto registered = service->is_registered(); dst->service_listener_handle = listener.get_id(); dst->registered = registered; dst->service_id = service->id; dst->user_id = service->user_id; if (registered) { dst->verbosity = service->verbosity; dst->padding = service->padding; auto size = service->data.size(); dst->data_size = static_cast(size); memcpy(dst->data, service->data.data(), size); } } /* * Syscalls */ error_code sys_config_open(u32 equeue_hdl, vm::ptr out_config_hdl) { sys_config.trace("sys_config_open(equeue_hdl=0x%x, out_config_hdl=*0x%x)", equeue_hdl, out_config_hdl); // Find queue with the given ID const auto queue = idm::get(equeue_hdl); if (!queue) { return CELL_ESRCH; } // Initialize lv2_config global state const auto global = g_fxo->get(); if (true) { global->initialize(); } // Create a lv2_config_handle object const auto config = lv2_config_handle::create(std::move(queue)); if (config) { *out_config_hdl = idm::last_id(); return CELL_OK; } // Failed to allocate sys_config object return CELL_EAGAIN; } error_code sys_config_close(u32 config_hdl) { sys_config.trace("sys_config_close(config_hdl=0x%x)", config_hdl); if (!idm::remove(config_hdl)) { return CELL_ESRCH; } return CELL_OK; } error_code sys_config_get_service_event(u32 config_hdl, u32 event_id, vm::ptr dst, u64 size) { sys_config.trace("sys_config_get_service_event(config_hdl=0x%x, event_id=0x%llx, dst=*0x%llx, size=0x%llx)", config_hdl, event_id, dst, size); // Find sys_config handle object with the given ID const auto cfg = idm::get(config_hdl); if (!cfg) { return CELL_ESRCH; } // Find service_event object const auto event = g_fxo->get()->find_event(event_id); if (!event) { return CELL_ESRCH; } // Check buffer fits if (!event->check_buffer_size(size)) { return CELL_EAGAIN; } // Write event to buffer event->write(dst.get_ptr()); return CELL_OK; } error_code sys_config_add_service_listener(u32 config_hdl, sys_config_service_id service_id, u64 min_verbosity, vm::ptr in, u64 size, sys_config_service_listener_type type, vm::ptr out_listener_hdl) { sys_config.trace("sys_config_add_service_listener(config_hdl=0x%x, service_id=0x%llx, min_verbosity=0x%llx, in=*0x%x, size=%lld, type=0x%llx, out_listener_hdl=*0x%x)", config_hdl, service_id, min_verbosity, in, size, type, out_listener_hdl); // Find sys_config handle object with the given ID auto cfg = idm::get(config_hdl); if (!cfg) { return CELL_ESRCH; } // Create service listener const auto listener = lv2_config_service_listener::create(cfg, service_id, min_verbosity, type, static_cast(in.get_ptr()), size); if (!listener) { return CELL_EAGAIN; } if (size > 0) { std::string buf_str; dump_buffer(buf_str, listener->data); sys_config.todo("Registered service listener for service %llx with non-zero buffer: %s", service_id, buf_str.c_str()); } // Notify listener with all past events listener->notify_all(); // Done! *out_listener_hdl = listener->get_id(); return CELL_OK; } error_code sys_config_remove_service_listener(u32 config_hdl, u32 listener_hdl) { sys_config.trace("sys_config_remove_service_listener(config_hdl=0x%x, listener_hdl=0x%x)", config_hdl, listener_hdl); // Remove listener from IDM if (!idm::remove(listener_hdl)) { return CELL_ESRCH; } return CELL_OK; } error_code sys_config_register_service(u32 config_hdl, sys_config_service_id service_id, u64 user_id, u64 verbosity, vm::ptr data_buf, u64 size, vm::ptr out_service_hdl) { sys_config.trace("sys_config_register_service(config_hdl=0x%x, service_id=0x%llx, user_id=0x%llx, verbosity=0x%llx, data_but=*0x%llx, size=%lld, out_service_hdl=*0x%llx)", config_hdl, service_id, user_id, verbosity, data_buf, size, out_service_hdl); // Find sys_config handle object with the given ID const auto cfg = idm::get(config_hdl); if (!cfg) { return CELL_ESRCH; } // Create service auto service = lv2_config_service::create(service_id, user_id, verbosity, 0, data_buf.get_ptr(), size); if (!service) { return CELL_EAGAIN; } // Notify all listeners service->notify(); // Done! *out_service_hdl = service->get_id(); return CELL_OK; } error_code sys_config_unregister_service(u32 config_hdl, u32 service_hdl) { sys_config.trace("sys_config_unregister_service(config_hdl=0x%x, service_hdl=0x%x)", config_hdl, service_hdl); // Remove listener from IDM auto service = idm::withdraw(service_hdl); if (!service) { return CELL_ESRCH; } // Unregister service service->unregister(); // Done! return CELL_OK; } /* * IO Events - TODO */ error_code sys_config_get_io_event(u32 config_hdl, u32 event_id /*?*/, vm::ptr out_buf /*?*/, u64 size /*?*/) { sys_config.todo("sys_config_get_io_event(config_hdl=0x%x, event_id=0x%x, out_buf=*0x%x, size=%lld)", config_hdl, event_id, out_buf, size); return CELL_OK; } error_code sys_config_register_io_error_listener(u32 config_hdl) { sys_config.todo("sys_config_register_io_error_listener(config_hdl=0x%x)", config_hdl); return CELL_OK; } error_code sys_config_unregister_io_error_listener(u32 config_hdl) { sys_config.todo("sys_config_unregister_io_error_listener(config_hdl=0x%x)", config_hdl); return CELL_OK; }