diff --git a/README.md b/README.md index ac6e3cd594..8ccf4f12ce 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,16 @@ If you want to contribute as a developer, please take a look at the following pa You should also contact any of the developers in the forums or in the Discord server to learn more about the current state of the emulator. +### AI Use + +Use of AI tools for research and reverse engineering purposes is permitted. However, contributors are expected to fully own and understand all code they submit. Any communication with the team — including code, code comments, and GitHub comments — must come from the human contributor, not an AI agent acting autonomously. + +We have unfortunately seen a rise in untested and unverified AI-generated slop being submitted to this project. This wastes maintainer time and, in worse cases, such changes get merged and break functionality for all users. Repeated violations will result in a ban from the repository. Please be respectful of everyone's time. + +**Pull requests opened by AI agents or automated tools must include a disclosure in the PR description** stating the scope of AI involvement — which parts were AI-generated and what human testing or review was performed prior to submission. PRs that omit this disclosure may be closed without review. + +If you are unsure about your work, open a discussion issue to talk it through with the team, or reach out to a maintainer on [Discord](https://discord.gg/RPCS3). + ## Building See [BUILDING.md](BUILDING.md) for more information about how to setup an environment to build RPCS3. diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index 8b4bd15e26..dffca21cae 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -857,7 +857,20 @@ void spu_cache::initialize(bool build_existing_cache) named_thread_group workers("SPU Worker ", worker_count, [&]() -> uint { #ifdef __APPLE__ + // Apple Silicon W^X: enable JIT write mode for this worker and + // pair it with an RAII guard so execute mode is restored on + // every exit path (return, exception, etc.). Leaving a worker + // in write mode at teardown can leave per-thread state + // inconsistent on AArch64. pthread_jit_write_protect_np(false); + + struct jit_write_guard + { + ~jit_write_guard() + { + pthread_jit_write_protect_np(true); + } + } _jit_guard; #endif // Set low priority thread_ctrl::scoped_priority low_prio(-1); diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index 5b63ca80cc..927d7ac187 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -3463,7 +3463,19 @@ public: } #if defined(__APPLE__) + // Apple Silicon W^X: enter write mode for JIT memory and pair + // it with an RAII guard so execute mode is restored on every + // exit path (the early "return nullptr" below would otherwise + // leave the thread in write mode permanently). pthread_jit_write_protect_np(false); + + struct jit_write_guard + { + ~jit_write_guard() + { + pthread_jit_write_protect_np(true); + } + } _jit_guard; #endif if (g_cfg.core.spu_debug) diff --git a/rpcs3/Emu/Io/usio.cpp b/rpcs3/Emu/Io/usio.cpp index 21dc241d81..f707ae8e6a 100644 --- a/rpcs3/Emu/Io/usio.cpp +++ b/rpcs3/Emu/Io/usio.cpp @@ -32,6 +32,7 @@ void fmt_class_string::format(std::string& out, u64 arg) case usio_btn::tekken_button3: return "Tekken Button 3"; case usio_btn::tekken_button4: return "Tekken Button 4"; case usio_btn::tekken_button5: return "Tekken Button 5"; + case usio_btn::card_tapping: return "Card Tapping"; case usio_btn::count: return "Count"; } @@ -42,6 +43,7 @@ void fmt_class_string::format(std::string& out, u64 arg) struct usio_memory { std::vector backup_memory; + std::array, g_cfg_usio.players.size()> card_data{}; usio_memory() = default; usio_memory(const usio_memory&) = delete; @@ -175,6 +177,16 @@ void usb_device_usio::load_backup() } usio_backup_file.read(memory.backup_memory.data(), file_size); + + for (usz i = 0; i < memory.card_data.size(); i++) + { + if (fs::file usio_card_file; + usio_card_file.open(fmt::format("%s/caches/usio_card_p%d.bin", rpcs3::utils::get_hdd1_dir(), i + 1), fs::read) && + usio_card_file.size() == memory.card_data[i].size()) + { + usio_card_file.read(memory.card_data[i].data(), memory.card_data[i].size()); + } + } } void usb_device_usio::save_backup() @@ -261,6 +273,10 @@ void usb_device_usio::translate_input_taiko() if (pressed) std::memcpy(input_buf.data() + 34 + offset, &c_hit, sizeof(u16)); break; + case usio_btn::card_tapping: + if (pressed) + tap_card(player); + break; default: break; } @@ -271,6 +287,8 @@ void usb_device_usio::translate_input_taiko() digital_input |= 0x80; }; + for (usz i = 0; i < m_io_status.size(); i++) + m_io_status[i].card_tapped = false; for (usz i = 0; i < g_cfg_usio.players.size(); i++) translate_from_pad(i, i); @@ -384,6 +402,10 @@ void usb_device_usio::translate_input_tekken() if (pressed) input |= 0x80000000ULL << shift; break; + case usio_btn::card_tapping: + if (pressed) + tap_card(player); + break; default: break; } @@ -398,6 +420,8 @@ void usb_device_usio::translate_input_tekken() } }; + for (usz i = 0; i < m_io_status.size(); i++) + m_io_status[i].card_tapped = false; for (usz i = 0; i < g_cfg_usio.players.size(); i++) translate_from_pad(i, i); @@ -414,6 +438,194 @@ void usb_device_usio::translate_input_tekken() response = std::move(input_buf); } +void usb_device_usio::emulate_card_reader(std::vector& buf, u16 reg) +{ + static std::array, 2> pending_response = {}; + usz reader_index = 0; + + const auto calculate_checksum = [](bool check, std::vector& data) -> bool + { + if (data.size() < 0x06) + return false; + + const usz data_end = data.size() - 2; + u8 sum = data[3] + data[4]; + + for (usz i = 5; i < data_end; i++) + sum -= data[i]; + + if (check) + return *reinterpret_cast*>(&data[data_end]) == sum; + + *reinterpret_cast*>(&data[data_end]) = sum; + return true; + }; + + switch (reg) + { + case 0x0080: + case 0x0090: + { + reader_index = reg == 0x0080 ? 0 : 1; + buf = {0x02, 0x03, 0x00, 0x00, 0xFF, 0x0F, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x10, 0x00}; + *reinterpret_cast*>(buf.data() + 2) = ::narrow(pending_response[reader_index].size()); + break; + } + case 0x7000: + case 0x7800: + { + reader_index = reg == 0x7000 ? 0 : 1; + buf = std::move(pending_response[reader_index]); + pending_response[reader_index].clear(); // Ensure its empty state after being moved + break; + } + case 0x7400: + case 0x7C00: + { + if (!calculate_checksum(true, buf)) + break; + reader_index = reg == 0x7400 ? 0 : 1; + const auto& status = ::at32(m_io_status, reader_index); + const usz card_player = reader_index * 2 + status.card_index; + const u8 payload_length = buf[3]; + const u8 command = buf[4]; + const u8* const payload = &buf[6]; + switch (command) + { + case 0xE8: + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x0D, 0xF3, 0xD5, 0x07, 0xDC, 0xF4, 0x3F, 0x11, 0x4D, 0x85, 0x61, 0xF1, 0x26, 0x6A, 0x87, 0xC9, 0x00}; + break; + } + case 0xEE: + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x0A, 0xF6, 0xD5, 0x07, 0xFF, 0x3F, 0x0E, 0xF1, 0xFF, 0x3F, 0x0E, 0xF1, 0xAA, 0x00}; + break; + } + case 0xF1: + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x03, 0xFD, 0xD5, 0x41, 0x00, 0xEA, 0x00}; + break; + } + case 0xF2: + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x02, 0xFE, 0xD5, 0x33, 0xF8, 0x00}; + break; + } + case 0xF7: + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x03, 0xFD, 0xD5, 0x4B, 0x00, 0xE0, 0x00}; + break; + } + case 0xFA: + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x02, 0xFE, 0xD5, 0x33, 0xF8, 0x00}; + break; + } + case 0xFB: + { + if (payload_length >= 5) + { + if (*reinterpret_cast*>(&payload[0]) == 0x0140) + { + if (payload[3] < 4) + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x13, 0xED, 0xD5, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, 0x00}; + std::memcpy(pending_response[reader_index].data() + 8, g_fxo->get().card_data[card_player].data() + payload[3] * 0x10, 0x10); + } + else + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x03, 0xFD, 0xD5, 0x41, 0x13, 0xD7, 0x00}; + } + } + else + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x03, 0xFD, 0xD5, 0x09, 0x00, 0x22, 0x00}; + } + } + break; + } + case 0xFC: + { + if (payload_length >= 2) + { + switch (payload[0]) + { + case 0x52: + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x04, 0xFC, 0xD5, 0x53, 0x01, 0x00, 0xD7, 0x00}; + break; + case 0x0E: + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x02, 0xFE, 0xD5, 0x0F, 0x1C, 0x00}; + break; + case 0x4A: + if (status.card_tapped) + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x0C, 0xF4, 0xD5, 0x4B, 0x01, 0x01, 0x00, 0x04, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0xCE, 0x00}; + std::memcpy(pending_response[reader_index].data() + 0x13, g_fxo->get().card_data[card_player].data(), 4); + } + else + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x03, 0xFD, 0xD5, 0x4B, 0x00, 0xE0, 0x00}; + } + break; + case 0x32: + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x02, 0xFE, 0xD5, 0x33, 0xF8, 0x00}; + break; + default: + break; + } + } + break; + } + case 0xFD: + { + if (payload_length >= 2) + { + switch (payload[0]) + { + case 0x18: + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x02, 0xFE, 0xD5, 0x19, 0x12, 0x00}; + break; + case 0x12: + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x02, 0xFE, 0xD5, 0x13, 0x18, 0x00}; + break; + default: + break; + } + } + break; + } + case 0xFE: + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x05, 0xFB, 0xD5, 0x0D, 0x00, 0x06, 0x00, 0x18, 0x00}; + break; + } + case 0xFF: + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00}; + break; + } + default: + { + usio_log.trace("Unhandled card reader command: 0x%02X", command); + break; + } + } + calculate_checksum(false, pending_response[reader_index]); + break; + } + default: + break; + } +} + +void usb_device_usio::tap_card(usz player) +{ + auto& status = ::at32(m_io_status, player / 2); + status.card_tapped = true; + status.card_index = player % 2; +} + void usb_device_usio::usio_write(u8 channel, u16 reg, std::vector& data) { const auto get_u16 = [&](std::string_view usio_func) -> u16 @@ -461,6 +673,16 @@ void usb_device_usio::usio_write(u8 channel, u16 reg, std::vector& data) usio_log.trace("SetHopperRequest(Hopper: %d, Limit: 0x%04X)", (reg - 0x4A) / 0x10, get_u16("SetHopperLimit")); break; } + case 0x0080: + case 0x008D: + case 0x0090: + case 0x009D: + case 0x7400: + case 0x7C00: + { + emulate_card_reader(data, reg); + break; + } default: { usio_log.trace("Unhandled channel 0 register write(reg: 0x%04X, size: 0x%04X, data: %s)", reg, data.size(), fmt::buf_to_hexstring(data.data(), data.size())); @@ -502,15 +724,11 @@ void usb_device_usio::usio_read(u8 channel, u16 reg, u16 size) break; } case 0x0080: - { - // Card reader check - 1 - response = {0x02, 0x03, 0x06, 0x00, 0xFF, 0x0F, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x10, 0x00}; - break; - } + case 0x0090: case 0x7000: + case 0x7800: { - // Card reader check - 2 - // No data returned + emulate_card_reader(response, reg); break; } case 0x1000: diff --git a/rpcs3/Emu/Io/usio.h b/rpcs3/Emu/Io/usio.h index 7a83e7a8ca..d88032ed5a 100644 --- a/rpcs3/Emu/Io/usio.h +++ b/rpcs3/Emu/Io/usio.h @@ -20,6 +20,8 @@ private: void save_backup(); void translate_input_taiko(); void translate_input_tekken(); + void emulate_card_reader(std::vector& buf, u16 reg); + void tap_card(usz player); void usio_write(u8 channel, u16 reg, std::vector& data); void usio_read(u8 channel, u16 reg, u16 size); void usio_init(u8 channel, u16 reg, u16 size); @@ -34,7 +36,9 @@ private: bool test_on = false; bool test_key_pressed = false; bool coin_key_pressed = false; + bool card_tapped = false; le_t coin_counter = 0; + usz card_index = 0; }; std::array m_io_status; diff --git a/rpcs3/Emu/Io/usio_config.h b/rpcs3/Emu/Io/usio_config.h index 4c5fe6f017..0a0ecaa454 100644 --- a/rpcs3/Emu/Io/usio_config.h +++ b/rpcs3/Emu/Io/usio_config.h @@ -21,6 +21,7 @@ enum class usio_btn tekken_button3, tekken_button4, tekken_button5, + card_tapping, count }; @@ -46,6 +47,7 @@ struct cfg_usio final : public emulated_pad_config cfg_pad_btn tekken_button3{this, "Tekken Button 3", usio_btn::tekken_button3, pad_button::cross}; cfg_pad_btn tekken_button4{this, "Tekken Button 4", usio_btn::tekken_button4, pad_button::circle}; cfg_pad_btn tekken_button5{this, "Tekken Button 5", usio_btn::tekken_button5, pad_button::R1}; + cfg_pad_btn card_tapping{this, "Card Tapping", usio_btn::card_tapping, pad_button::L1}; }; struct cfg_usios final : public emulated_pads_config diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 199aad38ee..29016a408a 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -1835,6 +1835,24 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, g_fxo->init("SPRX Loader"sv, [this, dir_queue, is_fast = m_precompilation_option.is_fast]() mutable { +#ifdef __APPLE__ + // Apple Silicon W^X: this thread invokes ppu_initialize() + // and ppu_precompile(), which write into MAP_JIT pages. + // Without enabling write mode here, these writes segfault + // before the game can boot (reproducible: RDR BLUS30418 + // crashes ~12s into boot at 0x300010000). Pair the enable + // with an RAII guard so execute mode is restored on every + // exit path (return, exception, etc.). + pthread_jit_write_protect_np(false); + + struct jit_write_guard + { + ~jit_write_guard() + { + pthread_jit_write_protect_np(true); + } + } _jit_guard; +#endif std::vector*> mod_list; if (auto& _main = *ensure(g_fxo->try_get>()); !_main.path.empty()) diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 97081aca4a..28f76a63bc 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -2298,11 +2298,19 @@ void main_window::UpdateLanguageActions(const QStringList& language_codes, const { const QLocale locale = QLocale(code); const QString locale_name = QLocale::languageToString(locale.language()); + const QString territory = QLocale::territoryToString(locale.territory()); + + const bool is_unique = std::count_if(language_codes.cbegin(), language_codes.cend(), [&locale_name](const QString& code) + { + return locale_name == QLocale::languageToString(QLocale(code).language()); + }) == 1; + + const QString display_name = (!is_unique && !territory.isEmpty()) ? QString("%1 (%2)").arg(locale_name, territory) : locale_name; // create new action - QAction* act = new QAction(locale_name, this); + QAction* act = new QAction(display_name, this); act->setData(code); - act->setToolTip(locale_name); + act->setToolTip(display_name); act->setCheckable(true); act->setChecked(code == language_code);