mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 10:19:47 -04:00
multitap enhancements
This commit is contained in:
+20
-3
@@ -103,9 +103,26 @@ Guests rearrange among seats **1..N−1**.
|
||||
| Library / lobby / UI ceiling | **8** seats (`RNET_MAX_SLOTS`, dual SCPH-1070) |
|
||||
| Per-title cap | `game.toml` `players` / `PSX_MAX_PLAYERS` (e.g. MotK=2, Bomberman Party=5) |
|
||||
|
||||
Rollback and delay-sync both carry multitap pad bytes. Offline and netplay
|
||||
share the same multitap arming rules (game-started gate, `multitap_port` in
|
||||
`game.toml`). Empty tap seats are fine — not every slot needs a device.
|
||||
**Offline:** recomp-ui exposes a Multitap checkbox on PSX titles with
|
||||
`num_players > 4`. Off hides controller seats beyond Player 4 and caps
|
||||
`g_offline_pad_count` at 4 (`settings.toml` `[controller] multitap`,
|
||||
default on). Multitap still arms at game-start when three or more offline
|
||||
seats are live (`multitap_port` from `game.toml`).
|
||||
|
||||
**Multitap analog (hack):** tap seats are plain digital by default. Opt in
|
||||
with `game.toml` / `settings.toml` `[controller] multitap_analog = true`
|
||||
(or the Controllers / Lobby Settings checkbox). When on, tap seats may
|
||||
report DualShock (`0x73` + sticks) in multitap bulk status. Hosts publish
|
||||
`match_caps.multitap_analog` so every peer applies the same setting at
|
||||
launch. Not reliable across titles — leave off unless the game needs it.
|
||||
|
||||
**Netplay (psxrecomp only):** lobbies with more than **2** seats always
|
||||
force SCPH-1070 multitap on (`force_session_pads_connected` /
|
||||
session start when `slot_count >= 3`). The offline Multitap toggle does
|
||||
not opt out of that. Empty tap seats are fine — not every slot needs a
|
||||
device.
|
||||
|
||||
Rollback and delay-sync both carry multitap pad bytes.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "bios_rom_alias.h"
|
||||
#include "fmt/format.h"
|
||||
@@ -696,6 +698,10 @@ static RuntimeConfig parse_runtime_block(const toml::value& cfg, const fs::path&
|
||||
rt.multitap_port = static_cast<int>(n);
|
||||
rt.has_multitap_port = true;
|
||||
}
|
||||
if (ct.contains("multitap_analog")) {
|
||||
rt.multitap_analog = toml::find<bool>(ct, "multitap_analog");
|
||||
rt.has_multitap_analog = true;
|
||||
}
|
||||
// Prefer string key; accept legacy bool alias.
|
||||
if (ct.contains("legacy_pad_config")) {
|
||||
rt.legacy_pad_config = toml::find<bool>(ct, "legacy_pad_config");
|
||||
@@ -2314,6 +2320,14 @@ UserSettings load_user_settings(const fs::path& path) {
|
||||
}
|
||||
});
|
||||
}
|
||||
if (ct.contains("multitap")) try_get([&]{
|
||||
s.multitap_enabled = toml::find<bool>(ct, "multitap");
|
||||
s.has_multitap_enabled = true;
|
||||
});
|
||||
if (ct.contains("multitap_analog")) try_get([&]{
|
||||
s.multitap_analog = toml::find<bool>(ct, "multitap_analog");
|
||||
s.has_multitap_analog = true;
|
||||
});
|
||||
if (ct.contains("deadzone")) try_get([&]{
|
||||
const auto n = toml::find<int64_t>(ct, "deadzone");
|
||||
if (n >= 0 && n <= 32767) {
|
||||
@@ -2429,7 +2443,8 @@ bool save_user_settings(const fs::path& path, const UserSettings& s) {
|
||||
}
|
||||
|
||||
{
|
||||
bool any_ctrl = s.has_deadzone;
|
||||
bool any_ctrl = s.has_deadzone || s.has_multitap_enabled ||
|
||||
s.has_multitap_analog;
|
||||
for (int i = 0; i < UserSettings::kMaxControllerPlayers; ++i) {
|
||||
if (s.has_p_device[i] || s.has_p_mode[i] || s.has_p_deadzone[i])
|
||||
any_ctrl = true;
|
||||
@@ -2452,6 +2467,12 @@ bool save_user_settings(const fs::path& path, const UserSettings& s) {
|
||||
if (s.has_p_deadzone[i])
|
||||
f << kDzKeys[i] << " = " << s.p_deadzone[i] << "\n";
|
||||
}
|
||||
if (s.has_multitap_enabled)
|
||||
f << "multitap = " << (s.multitap_enabled ? "true" : "false")
|
||||
<< "\n";
|
||||
if (s.has_multitap_analog)
|
||||
f << "multitap_analog = "
|
||||
<< (s.multitap_analog ? "true" : "false") << "\n";
|
||||
/* Keep a global deadzone= for older readers (mirrors P1). */
|
||||
if (s.has_deadzone || s.has_p_deadzone[0])
|
||||
f << "deadzone = "
|
||||
@@ -2478,4 +2499,102 @@ bool save_user_settings(const fs::path& path, const UserSettings& s) {
|
||||
return f.good();
|
||||
}
|
||||
|
||||
bool upsert_game_toml_controller_bool(const std::filesystem::path& path,
|
||||
const char* key, bool value)
|
||||
{
|
||||
if (!key || !key[0]) return false;
|
||||
std::string text;
|
||||
{
|
||||
std::ifstream in(path);
|
||||
if (in) {
|
||||
std::ostringstream ss;
|
||||
ss << in.rdbuf();
|
||||
text = ss.str();
|
||||
}
|
||||
}
|
||||
const std::string val = value ? "true" : "false";
|
||||
const std::string assign = std::string(key) + " = " + val;
|
||||
|
||||
auto is_section = [](const std::string& line) {
|
||||
size_t i = 0;
|
||||
while (i < line.size() && (line[i] == ' ' || line[i] == '\t')) ++i;
|
||||
return i < line.size() && line[i] == '[';
|
||||
};
|
||||
auto section_name = [](const std::string& line) -> std::string {
|
||||
size_t a = line.find('[');
|
||||
size_t b = line.find(']');
|
||||
if (a == std::string::npos || b == std::string::npos || b <= a + 1)
|
||||
return {};
|
||||
return line.substr(a + 1, b - a - 1);
|
||||
};
|
||||
auto key_prefix = [&](const std::string& line) {
|
||||
size_t i = 0;
|
||||
while (i < line.size() && (line[i] == ' ' || line[i] == '\t')) ++i;
|
||||
if (i < line.size() && line[i] == '#') return false;
|
||||
const std::string rest = line.substr(i);
|
||||
return rest.rfind(std::string(key) + " =", 0) == 0 ||
|
||||
rest.rfind(std::string(key) + "=", 0) == 0;
|
||||
};
|
||||
|
||||
std::istringstream ls(text);
|
||||
std::vector<std::string> lines;
|
||||
std::string line;
|
||||
while (std::getline(ls, line)) {
|
||||
if (!line.empty() && line.back() == '\r') line.pop_back();
|
||||
lines.push_back(line);
|
||||
}
|
||||
|
||||
int ctrl_start = -1, ctrl_end = -1;
|
||||
for (int i = 0; i < (int)lines.size(); ++i) {
|
||||
if (is_section(lines[i]) && section_name(lines[i]) == "controller") {
|
||||
ctrl_start = i;
|
||||
ctrl_end = (int)lines.size();
|
||||
for (int j = i + 1; j < (int)lines.size(); ++j) {
|
||||
if (is_section(lines[j])) {
|
||||
ctrl_end = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctrl_start < 0) {
|
||||
if (!text.empty() && text.back() != '\n') text.push_back('\n');
|
||||
text += "\n[controller]\n";
|
||||
text += assign;
|
||||
text += "\n";
|
||||
} else {
|
||||
int replace_at = -1;
|
||||
for (int i = ctrl_start + 1; i < ctrl_end; ++i) {
|
||||
if (key_prefix(lines[i])) {
|
||||
replace_at = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (replace_at >= 0) {
|
||||
lines[replace_at] = assign;
|
||||
} else {
|
||||
int insert_at = ctrl_end;
|
||||
while (insert_at > ctrl_start + 1 &&
|
||||
lines[insert_at - 1].find_first_not_of(" \t") ==
|
||||
std::string::npos)
|
||||
--insert_at;
|
||||
lines.insert(lines.begin() + insert_at, assign);
|
||||
}
|
||||
std::ostringstream out;
|
||||
for (size_t i = 0; i < lines.size(); ++i) {
|
||||
out << lines[i];
|
||||
if (i + 1 < lines.size() || (!text.empty() && text.back() == '\n'))
|
||||
out << '\n';
|
||||
}
|
||||
text = out.str();
|
||||
}
|
||||
|
||||
std::ofstream f(path, std::ios::binary | std::ios::trunc);
|
||||
if (!f) return false;
|
||||
f << text;
|
||||
return f.good();
|
||||
}
|
||||
|
||||
} // namespace PSXRecompV4
|
||||
|
||||
@@ -511,6 +511,12 @@ struct RuntimeConfig {
|
||||
bool has_multitap_port = false;
|
||||
int multitap_port = 1;
|
||||
|
||||
// multitap_analog: opt-in DualShock-on-tap hack (default false). When true,
|
||||
// multitap bulk seats may report 0x73 + stick bytes; when false (faithful),
|
||||
// tap seats stay plain digital. Overridable by settings.toml / match_caps.
|
||||
bool has_multitap_analog = false;
|
||||
bool multitap_analog = false;
|
||||
|
||||
// legacy_pad_config: per-game pad-protocol compatibility opt-in. false (default)
|
||||
// = the modern DualShock config state machine (proper 0x43 enter/exit, config id
|
||||
// 0xF3 only while in config) — required by MMX6 and the correct default for every
|
||||
@@ -1115,6 +1121,13 @@ struct UserSettings {
|
||||
// Legacy global deadzone (settings.toml `deadzone=`). Applied to slots
|
||||
// that do not have an explicit pN_deadzone. Default 10%.
|
||||
bool has_deadzone = false; int deadzone = 3277;
|
||||
// SCPH-1070 multitap for offline seats beyond Player 4 (settings.toml
|
||||
// [controller] multitap). Default ON when unset. Netplay lobbies with
|
||||
// more than 2 seats always arm multitap in the runtime regardless.
|
||||
bool has_multitap_enabled = false; bool multitap_enabled = true;
|
||||
// DualShock-on-tap hack (settings.toml [controller] multitap_analog).
|
||||
// Default off when unset; game.toml [controller] multitap_analog seeds it.
|
||||
bool has_multitap_analog = false; bool multitap_analog = false;
|
||||
// Localization: the launcher's chosen language code (feeds RuntimeConfig
|
||||
// .language / g_lang). "off"/"jp"/"" = untranslated native game. Persisted to
|
||||
// settings.toml [localization].language.
|
||||
@@ -1167,6 +1180,12 @@ UserSettings load_user_settings(const std::filesystem::path& path);
|
||||
// Write settings.toml deterministically. Returns false on I/O failure.
|
||||
bool save_user_settings(const std::filesystem::path& path, const UserSettings& s);
|
||||
|
||||
// Surgical upsert of `key = true|false` under [controller] in game.toml.
|
||||
// Preserves comments and unrelated keys. Creates [controller] if missing.
|
||||
// Used to persist launcher multitap_analog into the title game config.
|
||||
bool upsert_game_toml_controller_bool(const std::filesystem::path& path,
|
||||
const char* key, bool value);
|
||||
|
||||
// Locate the project root by walking upward from `config_path` until a
|
||||
// directory containing `.gitignore`, `.git`, or `CMakeLists.txt` is found.
|
||||
// Throws std::runtime_error if not found within 8 levels.
|
||||
|
||||
@@ -62,6 +62,8 @@ typedef struct PsxLobbyMatchCaps {
|
||||
int force_input_relay; /* 0/1 — server input relay (vs P2P) */
|
||||
int force_turn; /* 0/1 — ICE relay-only (Force TURN for UDP) */
|
||||
int rollback; /* 0/1 — invent/rollback netplay (default on) */
|
||||
/* DualShock-on-multitap-tap hack (0/1). Host-authoritative for the match. */
|
||||
int multitap_analog;
|
||||
char language[PSX_LOBBY_LANG_LEN];
|
||||
} PsxLobbyMatchCaps;
|
||||
|
||||
|
||||
@@ -84,9 +84,15 @@ void sio_set_legacy_cfg(int enabled);
|
||||
int sio_get_legacy_cfg(void);
|
||||
/* 1 when multitap is armed and `logical_slot` is a tap pad (not the lone
|
||||
* pad on the opposite console port). SCPH-1070 taps are treated as plain
|
||||
* digital controllers (0x41) — DualShock/analog on a tap is not reliable
|
||||
* across titles, so host input and SIO type requests are forced digital. */
|
||||
* digital controllers (0x41) by default — DualShock/analog on a tap is not
|
||||
* reliable across titles. Opt in with sio_set_multitap_analog(1) (hack). */
|
||||
int sio_pad_on_multitap(int logical_slot);
|
||||
/* Opt-in DualShock-on-tap hack. Off by default. When on, tap seats may
|
||||
* report 0x73 + stick bytes in multitap bulk status (and host/netplay
|
||||
* sampling stops forcing digital). game.toml [controller] multitap_analog /
|
||||
* settings.toml / lobby match_caps may enable this. */
|
||||
void sio_set_multitap_analog(int enabled);
|
||||
int sio_get_multitap_analog(void);
|
||||
|
||||
/* Update pad button state. Buttons use PS1 convention: 0=pressed, 1=released.
|
||||
Bit layout: SELECT, L3, R3, START, UP, RIGHT, DOWN, LEFT,
|
||||
|
||||
+134
-16
@@ -329,6 +329,17 @@ struct PlayerInput {
|
||||
static PlayerInput g_players[PSX_MAX_PLAYERS];
|
||||
/* Offline SIO sample loop bound (from game.toml players; clamped). */
|
||||
static int g_offline_pad_count = 2;
|
||||
/* Offline seat ceiling from game.toml players, optionally capped at 4 when
|
||||
* the launcher Multitap toggle is off (seats 5+). Netplay ignores this and
|
||||
* arms multitap whenever session slot_count > 2. */
|
||||
static void apply_offline_pad_count(int game_players, bool multitap_enabled)
|
||||
{
|
||||
int n = game_players > 0 ? game_players : 1;
|
||||
if (n > PSX_MAX_PLAYERS) n = PSX_MAX_PLAYERS;
|
||||
if (game_players > 4 && !multitap_enabled && n > 4)
|
||||
n = 4;
|
||||
g_offline_pad_count = n;
|
||||
}
|
||||
/* ARGB8888 staging buffer. Sized for the active internal resolution:
|
||||
* 640*scale x 512*scale. Allocated once the supersampling scale is known
|
||||
* (sized for the native 640x512 when supersampling is off). */
|
||||
@@ -3416,10 +3427,10 @@ static int effective_player_mode(const PlayerInput& p) {
|
||||
return p.mode;
|
||||
}
|
||||
|
||||
/* Pad mode for the SIO seat this sample will drive. Multitap taps are always
|
||||
* plain digital (0x41) — DualShock on a SCPH-1070 tap is not reliable. */
|
||||
/* Pad mode for the SIO seat this sample will drive. Multitap taps stay digital
|
||||
* unless the DualShock-on-tap hack (sio_get_multitap_analog) is armed. */
|
||||
static int effective_player_mode_for_sio(const PlayerInput& p, int sio_slot) {
|
||||
if (sio_pad_on_multitap(sio_slot))
|
||||
if (sio_pad_on_multitap(sio_slot) && !sio_get_multitap_analog())
|
||||
return (int)PSXRecompV4::PAD_MODE_DIGITAL;
|
||||
return effective_player_mode(p);
|
||||
}
|
||||
@@ -3869,10 +3880,10 @@ static int capture_pad_slot(int s, PsxNetPad* out) {
|
||||
* analog axes below. An assigned device keeps its configured mode (a
|
||||
* launcher-selected analog DualShock stays analog, so its input path / SIO
|
||||
* handshake cadence is preserved exactly). Keyboard is always digital.
|
||||
* Multitap taps are forced digital. A P1 with no assigned device but
|
||||
* dev-any-input on presents as HYBRID. */
|
||||
* Multitap taps are forced digital unless multitap_analog hack is on.
|
||||
* A P1 with no assigned device but dev-any-input on presents as HYBRID. */
|
||||
int mode;
|
||||
if (sio_pad_on_multitap(s))
|
||||
if (sio_pad_on_multitap(s) && !sio_get_multitap_analog())
|
||||
mode = (int)PSXRecompV4::PAD_MODE_DIGITAL;
|
||||
else if (p.kind != 0) mode = effective_player_mode(p);
|
||||
else if (dev_here) mode = (int)PSXRecompV4::PAD_MODE_HYBRID;
|
||||
@@ -3978,7 +3989,7 @@ static int capture_pad_slot_exclusive(int s, PsxNetPad* out, int present_sio_slo
|
||||
}
|
||||
|
||||
static void apply_pad_slot_to_sio(int s, const PsxNetPad& pad) {
|
||||
if (sio_pad_on_multitap(s))
|
||||
if (sio_pad_on_multitap(s) && !sio_get_multitap_analog())
|
||||
sio_set_pad_config_capable(s, 0);
|
||||
sio_set_pad_state_slot(s, pad.buttons);
|
||||
sio_set_pad_sticks(s, pad.lx, pad.ly, pad.rx, pad.ry);
|
||||
@@ -4974,16 +4985,19 @@ static NetplayVblankEpilogue sdl_vblank_present_body(void) {
|
||||
if (g_offline_pad_count >= 3 && fntrace_is_game_started() &&
|
||||
!sio_get_multitap()) {
|
||||
sio_set_multitap(1);
|
||||
/* Tap seats drop to plain digital as soon as the tap is live. */
|
||||
/* Tap seats drop to plain digital unless multitap_analog is on. */
|
||||
for (int s = 0; s < PSX_MAX_PLAYERS; ++s) {
|
||||
if (!sio_pad_on_multitap(s)) continue;
|
||||
if (sio_get_multitap_analog()) continue;
|
||||
sio_set_pad_config_capable(s, 0);
|
||||
sio_set_pad_analog(s, 0, 0x80, 0x80, 0x80, 0x80);
|
||||
}
|
||||
std::fprintf(stdout,
|
||||
"psxrecomp: multitap armed (console Port %d; "
|
||||
"tap pads forced digital)\n",
|
||||
sio_get_multitap_port() + 1);
|
||||
"tap pads %s)\n",
|
||||
sio_get_multitap_port() + 1,
|
||||
sio_get_multitap_analog() ? "analog hack on"
|
||||
: "forced digital");
|
||||
}
|
||||
/* Solo resim self-check replay republishes the recorded rows itself —
|
||||
* live sampling must not touch SIO during the replay window. */
|
||||
@@ -5958,6 +5972,7 @@ namespace {
|
||||
int g_lnch_force_turn = 0;
|
||||
/* Lobby default on; host “Disable Rollback” clears this → delay_sync. */
|
||||
int g_lnch_rollback = 1;
|
||||
int g_lnch_multitap_analog = 0;
|
||||
int g_lnch_host_max_slots = 2;
|
||||
|
||||
/* Delay-sync READY/START waits for every seat in slot_count. Use seated
|
||||
@@ -7090,6 +7105,8 @@ namespace {
|
||||
caps.force_input_relay = g_lnch_force_input_relay != 0;
|
||||
caps.force_turn = g_lnch_force_turn != 0;
|
||||
caps.rollback = g_lnch_rollback != 0;
|
||||
caps.multitap_analog = g_lnch_multitap_analog != 0;
|
||||
if (s) caps.multitap_analog = s->multitap_analog != 0;
|
||||
return caps;
|
||||
}
|
||||
|
||||
@@ -7109,6 +7126,8 @@ namespace {
|
||||
caps.force_input_relay = g_lnch_force_input_relay != 0;
|
||||
caps.force_turn = g_lnch_force_turn != 0;
|
||||
caps.rollback = g_lnch_rollback != 0;
|
||||
caps.multitap_analog = g_lnch_multitap_analog != 0;
|
||||
if (settings) caps.multitap_analog = settings->multitap_analog != 0;
|
||||
(void)psx_lobby_set_match_caps(&caps);
|
||||
}
|
||||
|
||||
@@ -7174,6 +7193,19 @@ namespace {
|
||||
ae_np_push_match_caps(nullptr);
|
||||
return 0;
|
||||
}
|
||||
int ae_np_multitap_analog_get(void*) {
|
||||
if (!g_lnch_hosting_lan && !g_lnch_joined_lan) {
|
||||
const PsxLobbyMatchCaps* caps = psx_lobby_match_caps();
|
||||
if (caps && caps->valid)
|
||||
return caps->multitap_analog ? 1 : 0;
|
||||
}
|
||||
return g_lnch_multitap_analog;
|
||||
}
|
||||
int ae_np_multitap_analog_set(void*, int enable) {
|
||||
g_lnch_multitap_analog = enable ? 1 : 0;
|
||||
ae_np_push_match_caps(nullptr);
|
||||
return 0;
|
||||
}
|
||||
int ae_np_input_prediction_get(void*) {
|
||||
if (!g_lnch_hosting_lan && !g_lnch_joined_lan) {
|
||||
const PsxLobbyMatchCaps* caps = psx_lobby_match_caps();
|
||||
@@ -8473,6 +8505,8 @@ namespace {
|
||||
ae_np_rollback_set,
|
||||
ae_np_input_prediction_get,
|
||||
ae_np_input_prediction_set,
|
||||
ae_np_multitap_analog_get,
|
||||
ae_np_multitap_analog_set,
|
||||
};
|
||||
} // namespace
|
||||
#endif
|
||||
@@ -8637,6 +8671,8 @@ int main(int argc, char** argv) {
|
||||
std::filesystem::path memcard2_path; /* explicit slot-2 .mcd (empty => dir/card2.mcd) */
|
||||
bool memcard1_enabled = true;
|
||||
bool memcard2_enabled = true;
|
||||
bool multitap_enabled = true; /* [controller] multitap; seats 5+ offline */
|
||||
bool multitap_analog = false; /* DualShock-on-tap hack; game.toml/settings */
|
||||
/* [controller] device routing (defaults: P1 keyboard/digital, P2 none). */
|
||||
/* Dev builds default Player 1 to the first connected controller ("auto").
|
||||
* PSX_DEV_INPUT=1 additionally merges every other controller and the
|
||||
@@ -8717,9 +8753,7 @@ int main(int argc, char** argv) {
|
||||
game_id = gc.id;
|
||||
game_region = gc.region;
|
||||
game_players = gc.players;
|
||||
g_offline_pad_count = game_players > 0 ? game_players : 1;
|
||||
if (g_offline_pad_count > PSX_MAX_PLAYERS)
|
||||
g_offline_pad_count = PSX_MAX_PLAYERS;
|
||||
apply_offline_pad_count(game_players, multitap_enabled);
|
||||
game_has_disc_crc = gc.has_disc_crc;
|
||||
game_disc_crc = gc.disc_crc;
|
||||
g_netplay_disc_expect.require_cue = gc.netplay_require_cue;
|
||||
@@ -8919,6 +8953,17 @@ int main(int argc, char** argv) {
|
||||
"psxrecomp: multitap on console Port %d\n",
|
||||
gc.runtime.multitap_port);
|
||||
}
|
||||
if (gc.runtime.has_multitap_analog) {
|
||||
multitap_analog = gc.runtime.multitap_analog;
|
||||
sio_set_multitap_analog(multitap_analog ? 1 : 0);
|
||||
#if defined(RECOMP_LAUNCHER) && defined(PSX_HAS_LOBBY_CLIENT)
|
||||
g_lnch_multitap_analog = multitap_analog ? 1 : 0;
|
||||
#endif
|
||||
if (multitap_analog)
|
||||
std::fprintf(stdout,
|
||||
"psxrecomp: multitap_analog hack ON "
|
||||
"(DualShock on tap seats)\n");
|
||||
}
|
||||
/* LEGACY per-game pad-config opt-in (default modern). Only Tomba sets
|
||||
* it, so its launcher Hybrid mode's analog<->digital flip doesn't make
|
||||
* libpad manufacture a 1-frame "pad unplugged". sio_init() does not
|
||||
@@ -9082,6 +9127,14 @@ int main(int argc, char** argv) {
|
||||
if (us.has_memcard2_path) memcard2_path = us.memcard2_path;
|
||||
if (us.has_memcard1_enabled) memcard1_enabled = us.memcard1_enabled;
|
||||
if (us.has_memcard2_enabled) memcard2_enabled = us.memcard2_enabled;
|
||||
if (us.has_multitap_enabled) multitap_enabled = us.multitap_enabled;
|
||||
if (us.has_multitap_analog) {
|
||||
multitap_analog = us.multitap_analog;
|
||||
sio_set_multitap_analog(multitap_analog ? 1 : 0);
|
||||
#if defined(RECOMP_LAUNCHER) && defined(PSX_HAS_LOBBY_CLIENT)
|
||||
g_lnch_multitap_analog = multitap_analog ? 1 : 0;
|
||||
#endif
|
||||
}
|
||||
if (us.has_language) resolved_language = us.language;
|
||||
{
|
||||
const int n = std::min(PSX_MAX_PLAYERS,
|
||||
@@ -9093,6 +9146,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
if (us.has_deadzone) resolved_deadzone = us.deadzone;
|
||||
}
|
||||
apply_offline_pad_count(game_players, multitap_enabled);
|
||||
if (us.has_low_latency_input) g_low_latency_input = us.low_latency_input ? 1 : 0;
|
||||
if (us.has_vsync) g_video_vsync = us.vsync;
|
||||
if (us.has_frame_interpolation)
|
||||
@@ -9489,6 +9543,8 @@ int main(int argc, char** argv) {
|
||||
seed.memcard_dir = memcard_dir; seed.has_memcard_dir = true;
|
||||
seed.memcard1_enabled = memcard1_enabled; seed.has_memcard1_enabled = true;
|
||||
seed.memcard2_enabled = memcard2_enabled; seed.has_memcard2_enabled = true;
|
||||
seed.multitap_enabled = multitap_enabled; seed.has_multitap_enabled = true;
|
||||
seed.multitap_analog = multitap_analog; seed.has_multitap_analog = true;
|
||||
if (!memcard1_path.empty()) { seed.memcard1_path = memcard1_path; seed.has_memcard1_path = true; }
|
||||
if (!memcard2_path.empty()) { seed.memcard2_path = memcard2_path; seed.has_memcard2_path = true; }
|
||||
seed.language = resolved_language; seed.has_language = true;
|
||||
@@ -9613,6 +9669,13 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
ls.memcard_enabled[0] = seed.memcard1_enabled ? 1 : 0;
|
||||
ls.memcard_enabled[1] = seed.memcard2_enabled ? 1 : 0;
|
||||
#if defined(RECOMP_LAUNCHER_HAS_MULTITAP_ENABLED)
|
||||
ls.multitap_enabled = seed.multitap_enabled ? 1 : 0;
|
||||
#endif
|
||||
#if defined(RECOMP_LAUNCHER_HAS_MULTITAP_ANALOG)
|
||||
ls.multitap_analog = seed.multitap_analog ? 1 : 0;
|
||||
g_lnch_multitap_analog = seed.multitap_analog ? 1 : 0;
|
||||
#endif
|
||||
|
||||
/* Region badge: game.toml [game] region wins verbatim; otherwise derive
|
||||
* from the game_id serial prefix (SCUS/SLUS/LSP -> USA, SCES/SLES ->
|
||||
@@ -9749,9 +9812,7 @@ int main(int argc, char** argv) {
|
||||
#if defined(PSX_HAS_RECOMP_NET) && defined(PSX_HAS_LOBBY_CLIENT)
|
||||
g_lnch_netplay_game_name = game_name.empty() ? "PSX" : game_name;
|
||||
g_lnch_game_players = game_players;
|
||||
g_offline_pad_count = game_players > 0 ? game_players : 1;
|
||||
if (g_offline_pad_count > PSX_MAX_PLAYERS)
|
||||
g_offline_pad_count = PSX_MAX_PLAYERS;
|
||||
apply_offline_pad_count(game_players, multitap_enabled);
|
||||
psx_lobby_set_max_slots(game_players);
|
||||
gi.netplay_supported =
|
||||
(game_players >= 2 && game_players <= PSX_MAX_PLAYERS) ? 1 : 0;
|
||||
@@ -9867,6 +9928,22 @@ int main(int argc, char** argv) {
|
||||
/* Memory-card slots: enable flags + any Browse/New paths. */
|
||||
seed.memcard1_enabled = ls.memcard_enabled[0] != 0; seed.has_memcard1_enabled = true;
|
||||
seed.memcard2_enabled = ls.memcard_enabled[1] != 0; seed.has_memcard2_enabled = true;
|
||||
#if defined(RECOMP_LAUNCHER_HAS_MULTITAP_ENABLED)
|
||||
seed.multitap_enabled = ls.multitap_enabled != 0;
|
||||
seed.has_multitap_enabled = true;
|
||||
multitap_enabled = seed.multitap_enabled;
|
||||
apply_offline_pad_count(game_players, multitap_enabled);
|
||||
#endif
|
||||
#if defined(RECOMP_LAUNCHER_HAS_MULTITAP_ANALOG)
|
||||
seed.multitap_analog = ls.multitap_analog != 0;
|
||||
seed.has_multitap_analog = true;
|
||||
multitap_analog = seed.multitap_analog;
|
||||
sio_set_multitap_analog(multitap_analog ? 1 : 0);
|
||||
if (game_config_path && game_config_path[0]) {
|
||||
(void)PSXRecompV4::upsert_game_toml_controller_bool(
|
||||
game_config_path, "multitap_analog", multitap_analog);
|
||||
}
|
||||
#endif
|
||||
if (ls.memcard_path[0][0]) { seed.memcard1_path = ls.memcard_path[0]; seed.has_memcard1_path = true; }
|
||||
if (ls.memcard_path[1][0]) { seed.memcard2_path = ls.memcard_path[1]; seed.has_memcard2_path = true; }
|
||||
|
||||
@@ -9882,6 +9959,11 @@ int main(int argc, char** argv) {
|
||||
if (ls.netplay_launch.enabled) {
|
||||
const PsxLobbyMatchCaps* caps = psx_lobby_match_caps();
|
||||
if (caps && caps->valid) {
|
||||
/* Host-authoritative DualShock-on-tap for the match. */
|
||||
multitap_analog = caps->multitap_analog != 0;
|
||||
seed.multitap_analog = multitap_analog;
|
||||
seed.has_multitap_analog = true;
|
||||
sio_set_multitap_analog(multitap_analog ? 1 : 0);
|
||||
seed.aspect_num = caps->aspect_num ? caps->aspect_num : seed.aspect_num;
|
||||
seed.aspect_den = caps->aspect_den ? caps->aspect_den : seed.aspect_den;
|
||||
seed.has_aspect_ratio = true;
|
||||
@@ -11114,6 +11196,13 @@ soft_return_lobby:
|
||||
std::snprintf(ls.netplay_player_name, sizeof(ls.netplay_player_name), "%s",
|
||||
psx_lobby_display_name());
|
||||
std::snprintf(ls.bios_path, sizeof(ls.bios_path), "%s", bios_path_str.c_str());
|
||||
#if defined(RECOMP_LAUNCHER_HAS_MULTITAP_ENABLED)
|
||||
ls.multitap_enabled = multitap_enabled ? 1 : 0;
|
||||
#endif
|
||||
#if defined(RECOMP_LAUNCHER_HAS_MULTITAP_ANALOG)
|
||||
ls.multitap_analog = multitap_analog ? 1 : 0;
|
||||
g_lnch_multitap_analog = multitap_analog ? 1 : 0;
|
||||
#endif
|
||||
/* Preserve input devices across soft-return. A zeroed ls left every
|
||||
* player_src at None, so the rematch UI (and a subsequent writeback)
|
||||
* looked like netplay had wiped the pad assignment. */
|
||||
@@ -11196,6 +11285,18 @@ soft_return_lobby:
|
||||
disc_path_str = resolved_disc.string();
|
||||
}
|
||||
if (ls.netplay_launch.enabled) {
|
||||
{
|
||||
const PsxLobbyMatchCaps* caps = psx_lobby_match_caps();
|
||||
if (caps && caps->valid) {
|
||||
multitap_analog = caps->multitap_analog != 0;
|
||||
g_lnch_multitap_analog = multitap_analog ? 1 : 0;
|
||||
sio_set_multitap_analog(multitap_analog ? 1 : 0);
|
||||
} else {
|
||||
multitap_analog = ls.multitap_analog != 0;
|
||||
g_lnch_multitap_analog = multitap_analog ? 1 : 0;
|
||||
sio_set_multitap_analog(multitap_analog ? 1 : 0);
|
||||
}
|
||||
}
|
||||
net_cfg = {};
|
||||
net_cfg.enabled = 1;
|
||||
net_cfg.local_slot = ls.netplay_launch.local_slot;
|
||||
@@ -11264,6 +11365,23 @@ soft_return_lobby:
|
||||
}
|
||||
us.deadzone = player_deadzone[0];
|
||||
us.has_deadzone = true;
|
||||
#if defined(RECOMP_LAUNCHER_HAS_MULTITAP_ENABLED)
|
||||
multitap_enabled = ls.multitap_enabled != 0;
|
||||
us.multitap_enabled = multitap_enabled;
|
||||
us.has_multitap_enabled = true;
|
||||
apply_offline_pad_count(game_players, multitap_enabled);
|
||||
#endif
|
||||
#if defined(RECOMP_LAUNCHER_HAS_MULTITAP_ANALOG)
|
||||
multitap_analog = ls.multitap_analog != 0;
|
||||
us.multitap_analog = multitap_analog;
|
||||
us.has_multitap_analog = true;
|
||||
g_lnch_multitap_analog = multitap_analog ? 1 : 0;
|
||||
sio_set_multitap_analog(multitap_analog ? 1 : 0);
|
||||
if (game_config_path && game_config_path[0]) {
|
||||
(void)PSXRecompV4::upsert_game_toml_controller_bool(
|
||||
game_config_path, "multitap_analog", multitap_analog);
|
||||
}
|
||||
#endif
|
||||
us.renderer = ls.renderer;
|
||||
us.has_renderer = true;
|
||||
us.supersampling = ls.supersampling;
|
||||
|
||||
@@ -1276,6 +1276,7 @@ static void parse_match_caps_object(const char *obj, PsxLobbyMatchCaps *out)
|
||||
out->force_turn = json_get_bool(obj, "force_turn", 0);
|
||||
/* Absent field → delay-sync (older hosts). New hosts always publish explicit. */
|
||||
out->rollback = json_get_bool(obj, "rollback", 0);
|
||||
out->multitap_analog = json_get_bool(obj, "multitap_analog", 0);
|
||||
json_get_str(obj, "language", out->language, sizeof(out->language));
|
||||
out->valid = 1;
|
||||
}
|
||||
@@ -1306,7 +1307,7 @@ static int append_match_caps_json(char *dst, size_t dst_cap, const PsxLobbyMatch
|
||||
"\"turbo_loads\":%s,\"bios_hle\":%s,\"fast_boot\":%s,"
|
||||
"\"auto_skip_fmv\":%s,\"input_delay\":%d,\"input_prediction\":%d,"
|
||||
"\"force_input_relay\":%s,\"force_turn\":%s,\"rollback\":%s,"
|
||||
"\"language\":\"%s\"}",
|
||||
"\"multitap_analog\":%s,\"language\":\"%s\"}",
|
||||
caps->aspect_num, caps->aspect_den,
|
||||
caps->turbo_loads ? "true" : "false",
|
||||
caps->bios_hle ? "true" : "false",
|
||||
@@ -1317,6 +1318,7 @@ static int append_match_caps_json(char *dst, size_t dst_cap, const PsxLobbyMatch
|
||||
caps->force_input_relay ? "true" : "false",
|
||||
caps->force_turn ? "true" : "false",
|
||||
caps->rollback ? "true" : "false",
|
||||
caps->multitap_analog ? "true" : "false",
|
||||
lang);
|
||||
}
|
||||
|
||||
|
||||
@@ -164,8 +164,10 @@ static void force_session_pads_connected(int slot_count)
|
||||
sio_set_multitap(0);
|
||||
for (i = 0; i < slot_count; ++i) {
|
||||
sio_connect_pad(i);
|
||||
/* Multitap taps are plain digital (sio clamps); lone port pad may be DS. */
|
||||
sio_set_pad_config_capable(i, sio_pad_on_multitap(i) ? 0 : 1);
|
||||
/* Tap seats are digital unless multitap_analog hack is armed. */
|
||||
const int force_dig =
|
||||
sio_pad_on_multitap(i) && !sio_get_multitap_analog();
|
||||
sio_set_pad_config_capable(i, force_dig ? 0 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2099,14 +2101,15 @@ static void apply_pad_slot(int slot, const PsxNetPad *pad)
|
||||
g_sio_pad_prev[slot] = pad->buttons;
|
||||
g_sio_pad_have[slot] = 1u;
|
||||
}
|
||||
const int force_dig = on_tap && !sio_get_multitap_analog();
|
||||
sio_set_pad_connected(slot, 1);
|
||||
sio_set_pad_config_capable(slot, on_tap ? 0 : 1);
|
||||
sio_set_pad_config_capable(slot, force_dig ? 0 : 1);
|
||||
sio_set_pad_state_slot(slot, pad->buttons);
|
||||
if (on_tap)
|
||||
if (force_dig)
|
||||
sio_set_pad_sticks(slot, 0x80, 0x80, 0x80, 0x80);
|
||||
else
|
||||
sio_set_pad_sticks(slot, pad->lx, pad->ly, pad->rx, pad->ry);
|
||||
sio_request_pad_type(slot, (!on_tap && pad->analog) ? 1 : 0);
|
||||
sio_request_pad_type(slot, (!force_dig && pad->analog) ? 1 : 0);
|
||||
if (psx_start_consumer_enabled()) {
|
||||
uint32_t sim = g_np.session ? rnet_session_sim_tick(g_np.session) : 0u;
|
||||
psx_start_consumer_note(slot, sim, pad->buttons);
|
||||
|
||||
+20
-5
@@ -69,6 +69,8 @@ static uint8_t pad_connected = 0;
|
||||
static int sio_multitap_enabled = 0;
|
||||
/* Physical SIO port hosting the tap: 0 = console Port 1, 1 = Port 2. */
|
||||
static int sio_multitap_port = 0;
|
||||
/* Opt-in DualShock on tap seats (default off — faithful digital taps). */
|
||||
static int sio_multitap_analog_hack = 0;
|
||||
|
||||
/* Pad communication state machine */
|
||||
typedef enum {
|
||||
@@ -794,12 +796,20 @@ int sio_get_multitap_port(void) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void sio_set_multitap_analog(int enabled) {
|
||||
sio_multitap_analog_hack = enabled ? 1 : 0;
|
||||
}
|
||||
|
||||
int sio_get_multitap_analog(void) {
|
||||
return sio_multitap_analog_hack;
|
||||
}
|
||||
|
||||
int sio_pad_on_multitap(int logical_slot) {
|
||||
#if PSX_MAX_PLAYERS >= 5
|
||||
if (!sio_multitap_active()) return 0;
|
||||
if (logical_slot < 0 || logical_slot >= PSX_MAX_PLAYERS) return 0;
|
||||
/* Dual tap: every seat is a tap pad (digital). Single tap: opposite-port
|
||||
* lone pad may stay DualShock; every tap slot is digital. */
|
||||
/* Dual tap: every seat is a tap pad. Single tap: opposite-port lone pad
|
||||
* may stay DualShock; every other slot is on the tap. */
|
||||
if (sio_dual_multitap()) return 1;
|
||||
return (logical_slot == mtap_standalone_logical()) ? 0 : 1;
|
||||
#else
|
||||
@@ -808,6 +818,11 @@ int sio_pad_on_multitap(int logical_slot) {
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Tap seat that must stay digital unless the analog hack is armed. */
|
||||
static int sio_tap_force_digital(int logical_slot) {
|
||||
return sio_pad_on_multitap(logical_slot) && !sio_multitap_analog_hack;
|
||||
}
|
||||
|
||||
void sio_connect_pad(int slot) {
|
||||
if (slot >= 0 && slot < PSX_MAX_PLAYERS)
|
||||
pad_connected |= (uint8_t)(1u << slot);
|
||||
@@ -821,7 +836,7 @@ void sio_set_pad_connected(int slot, int connected) {
|
||||
|
||||
void sio_set_pad_config_capable(int slot, int capable) {
|
||||
if (slot < 0 || slot >= PSX_MAX_PLAYERS) return;
|
||||
if (sio_pad_on_multitap(slot)) capable = 0;
|
||||
if (sio_tap_force_digital(slot)) capable = 0;
|
||||
pad_supports_config[slot] = capable ? 1 : 0;
|
||||
/* A plain digital pad can never be in config mode; clear any stale latch so
|
||||
* the next poll reports the digital id (0x41), not the config id (0xF3). */
|
||||
@@ -844,7 +859,7 @@ void sio_set_pad_state_slot(int slot, uint16_t buttons) {
|
||||
void sio_set_pad_analog(int slot, int enabled,
|
||||
uint8_t lx, uint8_t ly, uint8_t rx, uint8_t ry) {
|
||||
if (slot < 0 || slot >= PSX_MAX_PLAYERS) return;
|
||||
if (sio_pad_on_multitap(slot)) {
|
||||
if (sio_tap_force_digital(slot)) {
|
||||
enabled = 0;
|
||||
lx = ly = rx = ry = 0x80;
|
||||
}
|
||||
@@ -866,7 +881,7 @@ void sio_set_pad_sticks(int slot, uint8_t lx, uint8_t ly, uint8_t rx, uint8_t ry
|
||||
* can never split a poll or a config handshake. A no-op if already that type. */
|
||||
void sio_request_pad_type(int slot, int analog) {
|
||||
if (slot < 0 || slot >= PSX_MAX_PLAYERS) return;
|
||||
if (sio_pad_on_multitap(slot)) analog = 0;
|
||||
if (sio_tap_force_digital(slot)) analog = 0;
|
||||
int want = analog ? 1 : 0;
|
||||
pad_type_req[slot] = (pad_analog[slot] == want) ? -1 : (int8_t)want;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user