Merge branch 'master' into fix_iso_read

This commit is contained in:
Elad 2026-05-10 19:26:59 +03:00 committed by GitHub
commit 4f1ca637cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 294 additions and 9 deletions

View File

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

View File

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

View File

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

View File

@ -32,6 +32,7 @@ void fmt_class_string<usio_btn>::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<usio_btn>::format(std::string& out, u64 arg)
struct usio_memory
{
std::vector<u8> backup_memory;
std::array<std::array<u8, 0x40>, 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<u8>& buf, u16 reg)
{
static std::array<std::vector<u8>, 2> pending_response = {};
usz reader_index = 0;
const auto calculate_checksum = [](bool check, std::vector<u8>& 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<le_t<u16>*>(&data[data_end]) == sum;
*reinterpret_cast<le_t<u16>*>(&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<le_t<u16>*>(buf.data() + 2) = ::narrow<u16>(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<const le_t<u16>*>(&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<usio_memory>().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<usio_memory>().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<u8>& 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<u8>& 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:

View File

@ -20,6 +20,8 @@ private:
void save_backup();
void translate_input_taiko();
void translate_input_tekken();
void emulate_card_reader(std::vector<u8>& buf, u16 reg);
void tap_card(usz player);
void usio_write(u8 channel, u16 reg, std::vector<u8>& 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<u16> coin_counter = 0;
usz card_index = 0;
};
std::array<io_status, 2> m_io_status;

View File

@ -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<usio_btn>
cfg_pad_btn<usio_btn> tekken_button3{this, "Tekken Button 3", usio_btn::tekken_button3, pad_button::cross};
cfg_pad_btn<usio_btn> tekken_button4{this, "Tekken Button 4", usio_btn::tekken_button4, pad_button::circle};
cfg_pad_btn<usio_btn> tekken_button5{this, "Tekken Button 5", usio_btn::tekken_button5, pad_button::R1};
cfg_pad_btn<usio_btn> card_tapping{this, "Card Tapping", usio_btn::card_tapping, pad_button::L1};
};
struct cfg_usios final : public emulated_pads_config<cfg_usio, 4>

View File

@ -1835,6 +1835,24 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
g_fxo->init<named_thread>("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<ppu_module<lv2_obj>*> mod_list;
if (auto& _main = *ensure(g_fxo->try_get<main_ppu_module<lv2_obj>>()); !_main.path.empty())

View File

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