mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
Fix transition capture and enforce title-disabled mod packages
This commit is contained in:
@@ -464,6 +464,10 @@ public:
|
||||
developer_channel_ = visible;
|
||||
}
|
||||
bool developer_channel_visible() const { return developer_channel_; }
|
||||
/* Trusted title policy: hide and deactivate these ids even when an old
|
||||
* bundle or an installed copy survives an update. Saved choices stay
|
||||
* dormant and files remain untouched. Set before scan(). */
|
||||
void set_disabled_packages(std::vector<std::string> ids) { disabled_packages_ = std::move(ids); }
|
||||
|
||||
/* Build-owned catalog: wiped and re-staged by every build. */
|
||||
std::filesystem::path bundled_root() const { return root_ / "bundled"; }
|
||||
@@ -588,6 +592,7 @@ private:
|
||||
bool developer_channel_ = kDeveloperChannelDefault;
|
||||
std::vector<std::string> scan_errors_;
|
||||
std::map<std::string, std::map<std::string, ModPackage>> packages_;
|
||||
std::vector<std::string> disabled_packages_;
|
||||
std::map<std::string, ModSelection> selections_;
|
||||
std::map<std::string, std::filesystem::path> sources_;
|
||||
};
|
||||
|
||||
@@ -1023,7 +1023,8 @@ int psx_mod_set_controller_source_connected(uint32_t player, int connected);
|
||||
* host_flags bit 0 = a
|
||||
* gamepad is assigned, bit 1 / bit 2 = it has a left / right trigger axis;
|
||||
* host_lt / host_rt are those triggers, 0 released .. 255 fully pressed
|
||||
* (0 when absent or while the savestate input guard is armed).
|
||||
* (0 when absent or while the savestate input guard is armed). Bit 3 marks
|
||||
* axes/type supplied by an offline input source (PSX_MOD_PAD_HOST_SOURCE).
|
||||
*
|
||||
* The output arrives pre-filled with the stock pad (what the player gets
|
||||
* without a transform, so a digital pad keeps its fold and centred sticks;
|
||||
@@ -1050,7 +1051,10 @@ enum {
|
||||
enum {
|
||||
PSX_MOD_PAD_HOST_GAMEPAD = 1u << 0,
|
||||
PSX_MOD_PAD_HOST_LT = 1u << 1,
|
||||
PSX_MOD_PAD_HOST_RT = 1u << 2
|
||||
PSX_MOD_PAD_HOST_RT = 1u << 2,
|
||||
/* Axes/type came from an offline input source, rather than the host.
|
||||
* Transforms can preserve already transformed diagnostic recordings. */
|
||||
PSX_MOD_PAD_HOST_SOURCE = 1u << 3
|
||||
};
|
||||
typedef struct PSXModPadFrame {
|
||||
uint32_t struct_size, player, buttons, lx, ly, rx, ry, type;
|
||||
@@ -1147,6 +1151,11 @@ int psx_mod_set_local_mouse_policy(const PSXModMousePolicy* policy);
|
||||
/* Optional action-game capture: click once to capture, Escape to release.
|
||||
* Later clicks reach the normal bindings. Existing hold-to-look is default. */
|
||||
int psx_mod_set_local_mouse_persistent_capture(int enabled);
|
||||
/* Optional persistent-capture companion: keep existing capture through title
|
||||
* eligibility changes (menus/cutscenes/loading), Start and host stalls, while
|
||||
* clearing mouse motion and suspending output. Focus loss, host UI, disconnect,
|
||||
* native right-stick takeover and Escape still release. Never auto-acquires. */
|
||||
int psx_mod_set_local_mouse_capture_across_transitions(int enabled);
|
||||
|
||||
/*
|
||||
* Register a C plugin before main() on the compilers supported by the runtime.
|
||||
|
||||
@@ -23,7 +23,7 @@ void LocalMousePolicy::clear() {
|
||||
clock_valid_ = false;
|
||||
clock_ = event_clock_ = release_clock_ = 0;
|
||||
conflict_noticed_ = capture_failure_noticed_ = false;
|
||||
persistent_ = false;
|
||||
persistent_ = transitions_ = suspended_ = false;
|
||||
}
|
||||
bool LocalMousePolicy::persistent_capture(bool enabled) {
|
||||
if (!installed()) return false;
|
||||
@@ -31,6 +31,12 @@ bool LocalMousePolicy::persistent_capture(bool enabled) {
|
||||
persistent_ = enabled;
|
||||
return true;
|
||||
}
|
||||
bool LocalMousePolicy::capture_across_transitions(bool enabled) {
|
||||
if (!installed()) return false;
|
||||
reset();
|
||||
transitions_ = enabled;
|
||||
return true;
|
||||
}
|
||||
void LocalMousePolicy::dispatch(uint32_t type, uint64_t t, double dx, double dy) {
|
||||
if (!installed()) return;
|
||||
const PSXModMouseEvent event{sizeof(PSXModMouseEvent), type, t, dx, dy};
|
||||
@@ -39,6 +45,7 @@ void LocalMousePolicy::dispatch(uint32_t type, uint64_t t, double dx, double dy)
|
||||
void LocalMousePolicy::reset() {
|
||||
if (captured_) backend_.capture(backend_.context, false);
|
||||
captured_ = false;
|
||||
suspended_ = false;
|
||||
release_clock_ = clock_;
|
||||
event_valid_ = false;
|
||||
hold_blocked_ = hold_down_;
|
||||
@@ -54,12 +61,33 @@ bool LocalMousePolicy::allowed() const {
|
||||
return installed() && host_.live && host_.focused && host_.connected &&
|
||||
host_.analog && !host_.native_right && !host_.start && policy_.eligible(host_.buttons);
|
||||
}
|
||||
bool LocalMousePolicy::capture_allowed() const {
|
||||
return installed() && host_.live && host_.focused && host_.connected &&
|
||||
host_.analog && !host_.native_right;
|
||||
}
|
||||
void LocalMousePolicy::suspend() {
|
||||
release_clock_ = clock_;
|
||||
event_valid_ = false;
|
||||
hold_blocked_ = hold_down_;
|
||||
hold_active_ = false;
|
||||
if (!suspended_) dispatch(PSX_MOD_MOUSE_RESET);
|
||||
suspended_ = true;
|
||||
}
|
||||
void LocalMousePolicy::update(uint64_t now, LocalMouseHost host) {
|
||||
const bool stalled = clock_valid_ && (now < clock_ || now - clock_ > StallNs);
|
||||
clock_ = now;
|
||||
clock_valid_ = true;
|
||||
host_ = host;
|
||||
if (stalled || !allowed()) reset();
|
||||
if (transitions_ && persistent_ && captured_ && capture_allowed()) {
|
||||
if (stalled || !allowed()) suspend();
|
||||
else if (suspended_) {
|
||||
// Reject motion queued during the transition; resuming never
|
||||
// replays an old camera target or requires another click.
|
||||
release_clock_ = now;
|
||||
event_valid_ = false;
|
||||
suspended_ = false;
|
||||
}
|
||||
} else if (stalled || !allowed()) reset();
|
||||
if (host.hold_conflict && !conflict_noticed_) {
|
||||
backend_.notice(backend_.context, "Mouse hold disabled: control is bound to P1");
|
||||
conflict_noticed_ = true;
|
||||
@@ -91,12 +119,13 @@ bool LocalMousePolicy::control(uint64_t t, uint64_t now, MouseControl control,
|
||||
const bool fresh = down && !activation_down_ && !repeat;
|
||||
activation_down_ = down;
|
||||
if (!down) {
|
||||
if (persistent_ && captured_ && allowed()) {
|
||||
if (persistent_ && captured_ && (allowed() ||
|
||||
(transitions_ && capture_allowed()))) {
|
||||
const bool owned = activation_owned_;
|
||||
activation_owned_ = false;
|
||||
// t=0 is the backend's release reconciliation, not a queued
|
||||
// event. Malformed real event times still release capture.
|
||||
if (t) (void)valid_time(t, now);
|
||||
if (t && !(transitions_ && t < release_clock_)) (void)valid_time(t, now);
|
||||
return owned;
|
||||
}
|
||||
// Release is unconditional: a future/backwards event time cannot
|
||||
@@ -150,7 +179,7 @@ bool LocalMousePolicy::control(uint64_t t, uint64_t now, MouseControl control,
|
||||
event_clock_ = std::max(event_clock_, release_time);
|
||||
event_valid_ = true;
|
||||
}
|
||||
} else if (captured_ && fresh && !host_.hold_conflict && !hold_blocked_) {
|
||||
} else if (captured_ && allowed() && fresh && !host_.hold_conflict && !hold_blocked_) {
|
||||
if (valid_time(t, now)) {
|
||||
hold_active_ = true;
|
||||
dispatch(PSX_MOD_MOUSE_HOLD_PRESS, t);
|
||||
@@ -170,14 +199,24 @@ void LocalMousePolicy::sync_left(uint64_t now, bool physically_down) {
|
||||
}
|
||||
void LocalMousePolicy::motion(uint64_t t, uint64_t now, double dx, double dy) {
|
||||
if (!captured_) return;
|
||||
if (!allowed() || !std::isfinite(dx) || !std::isfinite(dy) ||
|
||||
if (!allowed()) {
|
||||
if (transitions_ && persistent_ && capture_allowed()) suspend();
|
||||
else reset();
|
||||
return;
|
||||
}
|
||||
if (transitions_ && t < release_clock_) return;
|
||||
if (!std::isfinite(dx) || !std::isfinite(dy) ||
|
||||
std::abs(dx) > 1e9 || std::abs(dy) > 1e9) { reset(); return; }
|
||||
if (!valid_time(t, now) || (dx == 0.0 && dy == 0.0)) return;
|
||||
dispatch(PSX_MOD_MOUSE_MOTION, t, dx, dy);
|
||||
}
|
||||
void LocalMousePolicy::sample(uint64_t now, uint8_t& rx, uint8_t& ry) {
|
||||
if (!captured_) return;
|
||||
if (!allowed()) { reset(); return; }
|
||||
if (!allowed()) {
|
||||
if (transitions_ && persistent_ && capture_allowed()) suspend();
|
||||
else reset();
|
||||
return;
|
||||
}
|
||||
PSXModMouseOutput out{sizeof(PSXModMouseOutput), 0, 128, 128};
|
||||
policy_.sample(now, &out);
|
||||
if (out.struct_size != sizeof(out) || out.override_right > 1 || out.rx > 255 || out.ry > 255) {
|
||||
|
||||
@@ -22,6 +22,7 @@ public:
|
||||
explicit LocalMousePolicy(LocalMouseBackend backend) : backend_(backend) {}
|
||||
bool install(const PSXModMousePolicy* policy);
|
||||
bool persistent_capture(bool enabled);
|
||||
bool capture_across_transitions(bool enabled);
|
||||
void clear();
|
||||
void reset();
|
||||
void update(uint64_t now, LocalMouseHost host);
|
||||
@@ -35,6 +36,8 @@ public:
|
||||
MouseControl hold_control() const;
|
||||
private:
|
||||
bool allowed() const;
|
||||
bool capture_allowed() const;
|
||||
void suspend();
|
||||
bool valid_time(uint64_t t, uint64_t now);
|
||||
void dispatch(uint32_t type, uint64_t t = 0, double dx = 0, double dy = 0);
|
||||
LocalMouseBackend backend_;
|
||||
@@ -47,5 +50,6 @@ private:
|
||||
bool hold_active_ = false; // accepted interpretation, separate from physical state
|
||||
bool conflict_noticed_ = false, capture_failure_noticed_ = false;
|
||||
bool persistent_ = false;
|
||||
bool transitions_ = false, suspended_ = false;
|
||||
};
|
||||
} // namespace psx
|
||||
|
||||
@@ -97,6 +97,9 @@ extern "C" int psx_mod_set_local_mouse_policy(const PSXModMousePolicy* policy) {
|
||||
extern "C" int psx_mod_set_local_mouse_persistent_capture(int enabled) {
|
||||
return (enabled == 0 || enabled == 1) && state().persistent_capture(enabled != 0);
|
||||
}
|
||||
extern "C" int psx_mod_set_local_mouse_capture_across_transitions(int enabled) {
|
||||
return (enabled == 0 || enabled == 1) && state().capture_across_transitions(enabled != 0);
|
||||
}
|
||||
void psx_local_mouse_clear() { state().clear(); host_ = {}; window_ = nullptr; }
|
||||
bool psx_local_mouse_installed() { return state().installed(); }
|
||||
void psx_local_mouse_reset() {
|
||||
|
||||
@@ -2765,6 +2765,8 @@ bool ModPackageManager::scan_root(const fs::path& packages_root,
|
||||
": package path does not match manifest id/version");
|
||||
continue;
|
||||
}
|
||||
if (std::find(disabled_packages_.begin(), disabled_packages_.end(), package.id) != disabled_packages_.end())
|
||||
continue;
|
||||
if (!developer_channel_ && !strip_developer_features(package)) {
|
||||
/* Every feature was developer-channel, so on this build the
|
||||
* package has nothing to offer. Not an error: it is the
|
||||
|
||||
@@ -1524,6 +1524,17 @@ bool mod_runtime_initialize(const std::filesystem::path& root,
|
||||
s.disc_enabled = false;
|
||||
s.disc_guard_failed = false;
|
||||
s.manager.set_root(root);
|
||||
// Optional comma-separated trusted title policy, stronger than omitting
|
||||
// bundled manifests: stale/installed copies cannot reactivate these ids.
|
||||
#ifdef PSX_MOD_DISABLED_PACKAGES
|
||||
std::vector<std::string> disabled_packages;
|
||||
std::istringstream disabled_stream(PSX_MOD_DISABLED_PACKAGES);
|
||||
for (std::string id; std::getline(disabled_stream, id, ','); )
|
||||
if (!id.empty()) disabled_packages.push_back(id);
|
||||
s.manager.set_disabled_packages(std::move(disabled_packages));
|
||||
#else
|
||||
s.manager.set_disabled_packages({});
|
||||
#endif
|
||||
s.game_id = game_id;
|
||||
s.entry_phys = game_entry_pc & 0x1FFFFFFFu;
|
||||
#if defined(PSX_LAUNCHER_MOD_COMMIT_WORKER_SAFE)
|
||||
|
||||
@@ -39,6 +39,7 @@ static void pad_ext_transform(const PadExtHooks *h, int s, int guarded,
|
||||
}
|
||||
}
|
||||
if (h->host_extras) h->host_extras(h->ctx, s, &f.host_flags, &f.host_lt, &f.host_rt);
|
||||
if (from_source) f.host_flags |= PSX_MOD_PAD_HOST_SOURCE;
|
||||
if (guarded) f.host_lt = f.host_rt = 0;
|
||||
if (!h->pad_transform(h->ctx, s, &f, &stock, &o)) return;
|
||||
if (guarded) {
|
||||
|
||||
@@ -278,6 +278,49 @@ int main() {
|
||||
lost.clear();
|
||||
}
|
||||
|
||||
// Action games retain relative capture across native menus, cutscenes,
|
||||
// Start and loading stalls, but discard all motion from those intervals.
|
||||
{
|
||||
psx::LocalMousePolicy game({nullptr,capture,suppress,notice});
|
||||
CHECK(game.install(&callbacks));
|
||||
CHECK(game.persistent_capture(true));
|
||||
CHECK(game.capture_across_transitions(true));
|
||||
uint64_t now=20000*Ms;
|
||||
game.update(now,host);
|
||||
CHECK(game.control(now,now,psx::MouseControl::Left,true,false));
|
||||
CHECK(game.control(now+Ms,now+Ms,psx::MouseControl::Left,false,false));
|
||||
for(int gate=0;gate<3;++gate) {
|
||||
now+=10*Ms;game.update(now,host);game.motion(now,now,30,0);CHECK(active);
|
||||
auto off=host;
|
||||
if(gate==0)guest=false;
|
||||
if(gate==1)off.start=true;
|
||||
if(gate==2)now+=300*Ms;
|
||||
game.update(now+Ms,off);CHECK(game.captured() && !active);
|
||||
game.control(now-Ms,now+Ms,psx::MouseControl::Left,false,false);
|
||||
CHECK(game.captured()); // queued release cannot expose the cursor
|
||||
if(gate!=2)game.motion(now+2*Ms,now+2*Ms,30,0);
|
||||
CHECK(!active && game.captured());
|
||||
game.control(now+2*Ms,now+2*Ms,psx::MouseControl::Left,false,false);
|
||||
CHECK(game.captured());
|
||||
guest=true;game.update(now+3*Ms,host);
|
||||
game.motion(now+2*Ms,now+3*Ms,30,0);CHECK(!active && game.captured());
|
||||
game.sample(now+3*Ms,x,y);CHECK(x==128 && y==128);
|
||||
game.motion(now+4*Ms,now+4*Ms,30,0);CHECK(active);
|
||||
}
|
||||
for(int gate=0;gate<5;++gate) {
|
||||
now+=10*Ms;auto off=host;
|
||||
if(gate==0)off.focused=false;if(gate==1)off.live=false;
|
||||
if(gate==2)off.connected=false;if(gate==3)off.analog=false;
|
||||
if(gate==4)off.native_right=true;
|
||||
game.update(now,off);CHECK(!game.captured() && !active);
|
||||
game.update(now+Ms,host);CHECK(!game.captured());
|
||||
CHECK(game.control(now+2*Ms,now+2*Ms,psx::MouseControl::Left,true,false));
|
||||
game.control(now+3*Ms,now+3*Ms,psx::MouseControl::Left,false,false);
|
||||
}
|
||||
CHECK(game.control(now+4*Ms,now+4*Ms,psx::MouseControl::Escape,true,false));
|
||||
game.update(now+5*Ms,host);CHECK(!game.captured());
|
||||
game.clear();
|
||||
}
|
||||
policy.reset();host.hold_conflict=false;capture_ok=false;CHECK(acquire());CHECK(!policy.captured());
|
||||
const unsigned failure_notices=notices;CHECK(acquire());CHECK(notices==failure_notices);
|
||||
policy.clear(); CHECK(!policy.installed() && !policy.captured());
|
||||
|
||||
@@ -1798,6 +1798,21 @@ int main() {
|
||||
"both absent packages must be reported as dormant selections");
|
||||
check(std::find(dormant.begin(), dormant.end(), "kept.mod") == dormant.end(),
|
||||
"a present package is never dormant");
|
||||
// Strong title policy also suppresses stale bundles and installed
|
||||
// copies. Preserve the user's choice and files for a later re-enable.
|
||||
write_text(stale_root / "installed/kept.mod/1.0.0/manifest.toml", kept);
|
||||
stale.set_disabled_packages({"kept.mod"});
|
||||
check(stale.scan(&error), error.c_str());
|
||||
const auto disabled = stale.resolve("SLUS-TEST");
|
||||
check(disabled.ok && disabled.ordered.empty() && disabled.writes.empty(),
|
||||
"a title-disabled package must not resolve from either catalog");
|
||||
check(stale.packages().count("kept.mod") == 0 && stale.dormant_selections().size() == 3,
|
||||
"title-disabled packages must be hidden and their saved choices dormant");
|
||||
check(fs::exists(stale_root / "bundled/kept.mod/1.0.0/manifest.toml") &&
|
||||
fs::exists(stale_root / "installed/kept.mod/1.0.0/manifest.toml"),
|
||||
"disabling never deletes catalog files");
|
||||
stale.set_disabled_packages({});
|
||||
check(stale.scan(&error), error.c_str());
|
||||
|
||||
/* Saving (every launch commit does) must keep the dormant choices. */
|
||||
check(stale.save_state(&error), error.c_str());
|
||||
|
||||
@@ -186,7 +186,7 @@ int main(void) {
|
||||
CHECK(pad_ext_resolve(&hx, 0, &o) == 1);
|
||||
CHECK(T.xf_seen.struct_size == sizeof T.xf_seen && T.xf_seen.player == 0);
|
||||
CHECK(T.xf_seen.buttons == (0xfeff & 0xfffe) && T.xf_seen.lx == 200 &&
|
||||
T.xf_seen.type == 1 && T.xf_seen.host_flags == 7 &&
|
||||
T.xf_seen.type == 1 && T.xf_seen.host_flags == (7 | PSX_MOD_PAD_HOST_SOURCE) &&
|
||||
T.xf_seen.host_lt == 60 && T.xf_seen.host_rt == 250);
|
||||
CHECK(o.analog == PSX_MOD_PAD_NEGCON && o.buttons == 0xbfff &&
|
||||
o.lx == 0x30 && o.rx == 250 && o.ry == 60 && o.ly == 5);
|
||||
|
||||
Reference in New Issue
Block a user