mirror of
https://github.com/RPCS3/rpcs3.git
synced 2026-05-12 16:19:44 -06:00
Merge branch 'master' into fix_iso_read
This commit is contained in:
commit
4f1ca637cd
10
README.md
10
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.
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user