mirror of
https://github.com/RPCS3/rpcs3.git
synced 2026-06-05 14:45:04 -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.
|
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
|
## Building
|
||||||
|
|
||||||
See [BUILDING.md](BUILDING.md) for more information about how to setup an environment to build RPCS3.
|
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
|
named_thread_group workers("SPU Worker ", worker_count, [&]() -> uint
|
||||||
{
|
{
|
||||||
#ifdef __APPLE__
|
#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);
|
pthread_jit_write_protect_np(false);
|
||||||
|
|
||||||
|
struct jit_write_guard
|
||||||
|
{
|
||||||
|
~jit_write_guard()
|
||||||
|
{
|
||||||
|
pthread_jit_write_protect_np(true);
|
||||||
|
}
|
||||||
|
} _jit_guard;
|
||||||
#endif
|
#endif
|
||||||
// Set low priority
|
// Set low priority
|
||||||
thread_ctrl::scoped_priority low_prio(-1);
|
thread_ctrl::scoped_priority low_prio(-1);
|
||||||
|
|||||||
@ -3463,7 +3463,19 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if defined(__APPLE__)
|
#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);
|
pthread_jit_write_protect_np(false);
|
||||||
|
|
||||||
|
struct jit_write_guard
|
||||||
|
{
|
||||||
|
~jit_write_guard()
|
||||||
|
{
|
||||||
|
pthread_jit_write_protect_np(true);
|
||||||
|
}
|
||||||
|
} _jit_guard;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (g_cfg.core.spu_debug)
|
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_button3: return "Tekken Button 3";
|
||||||
case usio_btn::tekken_button4: return "Tekken Button 4";
|
case usio_btn::tekken_button4: return "Tekken Button 4";
|
||||||
case usio_btn::tekken_button5: return "Tekken Button 5";
|
case usio_btn::tekken_button5: return "Tekken Button 5";
|
||||||
|
case usio_btn::card_tapping: return "Card Tapping";
|
||||||
case usio_btn::count: return "Count";
|
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
|
struct usio_memory
|
||||||
{
|
{
|
||||||
std::vector<u8> backup_memory;
|
std::vector<u8> backup_memory;
|
||||||
|
std::array<std::array<u8, 0x40>, g_cfg_usio.players.size()> card_data{};
|
||||||
|
|
||||||
usio_memory() = default;
|
usio_memory() = default;
|
||||||
usio_memory(const usio_memory&) = delete;
|
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);
|
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()
|
void usb_device_usio::save_backup()
|
||||||
@ -261,6 +273,10 @@ void usb_device_usio::translate_input_taiko()
|
|||||||
if (pressed)
|
if (pressed)
|
||||||
std::memcpy(input_buf.data() + 34 + offset, &c_hit, sizeof(u16));
|
std::memcpy(input_buf.data() + 34 + offset, &c_hit, sizeof(u16));
|
||||||
break;
|
break;
|
||||||
|
case usio_btn::card_tapping:
|
||||||
|
if (pressed)
|
||||||
|
tap_card(player);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -271,6 +287,8 @@ void usb_device_usio::translate_input_taiko()
|
|||||||
digital_input |= 0x80;
|
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++)
|
for (usz i = 0; i < g_cfg_usio.players.size(); i++)
|
||||||
translate_from_pad(i, i);
|
translate_from_pad(i, i);
|
||||||
|
|
||||||
@ -384,6 +402,10 @@ void usb_device_usio::translate_input_tekken()
|
|||||||
if (pressed)
|
if (pressed)
|
||||||
input |= 0x80000000ULL << shift;
|
input |= 0x80000000ULL << shift;
|
||||||
break;
|
break;
|
||||||
|
case usio_btn::card_tapping:
|
||||||
|
if (pressed)
|
||||||
|
tap_card(player);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
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++)
|
for (usz i = 0; i < g_cfg_usio.players.size(); i++)
|
||||||
translate_from_pad(i, i);
|
translate_from_pad(i, i);
|
||||||
|
|
||||||
@ -414,6 +438,194 @@ void usb_device_usio::translate_input_tekken()
|
|||||||
response = std::move(input_buf);
|
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)
|
void usb_device_usio::usio_write(u8 channel, u16 reg, std::vector<u8>& data)
|
||||||
{
|
{
|
||||||
const auto get_u16 = [&](std::string_view usio_func) -> u16
|
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"));
|
usio_log.trace("SetHopperRequest(Hopper: %d, Limit: 0x%04X)", (reg - 0x4A) / 0x10, get_u16("SetHopperLimit"));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 0x0080:
|
||||||
|
case 0x008D:
|
||||||
|
case 0x0090:
|
||||||
|
case 0x009D:
|
||||||
|
case 0x7400:
|
||||||
|
case 0x7C00:
|
||||||
|
{
|
||||||
|
emulate_card_reader(data, reg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
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()));
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case 0x0080:
|
case 0x0080:
|
||||||
{
|
case 0x0090:
|
||||||
// Card reader check - 1
|
|
||||||
response = {0x02, 0x03, 0x06, 0x00, 0xFF, 0x0F, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x10, 0x00};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 0x7000:
|
case 0x7000:
|
||||||
|
case 0x7800:
|
||||||
{
|
{
|
||||||
// Card reader check - 2
|
emulate_card_reader(response, reg);
|
||||||
// No data returned
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0x1000:
|
case 0x1000:
|
||||||
|
|||||||
@ -20,6 +20,8 @@ private:
|
|||||||
void save_backup();
|
void save_backup();
|
||||||
void translate_input_taiko();
|
void translate_input_taiko();
|
||||||
void translate_input_tekken();
|
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_write(u8 channel, u16 reg, std::vector<u8>& data);
|
||||||
void usio_read(u8 channel, u16 reg, u16 size);
|
void usio_read(u8 channel, u16 reg, u16 size);
|
||||||
void usio_init(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_on = false;
|
||||||
bool test_key_pressed = false;
|
bool test_key_pressed = false;
|
||||||
bool coin_key_pressed = false;
|
bool coin_key_pressed = false;
|
||||||
|
bool card_tapped = false;
|
||||||
le_t<u16> coin_counter = 0;
|
le_t<u16> coin_counter = 0;
|
||||||
|
usz card_index = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::array<io_status, 2> m_io_status;
|
std::array<io_status, 2> m_io_status;
|
||||||
|
|||||||
@ -21,6 +21,7 @@ enum class usio_btn
|
|||||||
tekken_button3,
|
tekken_button3,
|
||||||
tekken_button4,
|
tekken_button4,
|
||||||
tekken_button5,
|
tekken_button5,
|
||||||
|
card_tapping,
|
||||||
|
|
||||||
count
|
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_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_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> 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>
|
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
|
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;
|
std::vector<ppu_module<lv2_obj>*> mod_list;
|
||||||
|
|
||||||
if (auto& _main = *ensure(g_fxo->try_get<main_ppu_module<lv2_obj>>()); !_main.path.empty())
|
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 QLocale locale = QLocale(code);
|
||||||
const QString locale_name = QLocale::languageToString(locale.language());
|
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
|
// create new action
|
||||||
QAction* act = new QAction(locale_name, this);
|
QAction* act = new QAction(display_name, this);
|
||||||
act->setData(code);
|
act->setData(code);
|
||||||
act->setToolTip(locale_name);
|
act->setToolTip(display_name);
|
||||||
act->setCheckable(true);
|
act->setCheckable(true);
|
||||||
act->setChecked(code == language_code);
|
act->setChecked(code == language_code);
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user