Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/modules/backend/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion app/modules/backend/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "daystrom"
version = "0.4.0"
version = "0.4.1"
edition = "2021"

[lib]
Expand Down
4 changes: 2 additions & 2 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@
"@tauri-apps/cli": "^2.10.1",
"@types/node": "^24.12.0",
"@vitejs/plugin-vue": "^6.0.5",
"@vitest/coverage-v8": "^4.1.1",
"@vitest/coverage-v8": "^4.1.2",
"@vue/tsconfig": "^0.9.1",
"typescript": "~5.9.3",
"vite": "^8.0.3",
"vitest": "^4.1.1",
"vitest": "^4.1.2",
"vue-tsc": "^3.2.6"
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "daystrom-workspace",
"type": "module",
"version": "0.4.0",
"version": "0.4.1",
"private": true,
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319",
"engines": {
Expand Down
186 changes: 102 additions & 84 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions rust-mod/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions rust-mod/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "stfc-mod"
version = "0.4.0"
version = "0.4.1"
edition = "2024"

[lib]
Expand All @@ -13,7 +13,7 @@ max_level_trace = [ "log/max_level_trace" ]

[dependencies]
chrono = "0.4"
ctor = "0.6"
ctor = "0.8.0"
dirs = "6"
futures-util = { version = "0.3", default-features = false, features = [ "sink" ] }
libc = "0.2"
Expand Down
38 changes: 28 additions & 10 deletions rust-mod/src/hooks/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use log::debug;
use std::collections::HashSet;
use std::sync::Mutex;

use log::{debug, info};

pub(crate) mod tracker;

Expand All @@ -12,19 +15,34 @@ mod user_profile;

// ---- Trace mode (dev tool) ------------------------------------------------

/// Hardcoded trace pattern. Set to `Some("substring")` to activate observation mode.
/// Set to `true` to activate trace/observation mode.
/// When active, all hooks pass through to the original, no store interaction, no TOML.
/// Only keys containing the pattern are logged. Set to `None` for normal operation.
const TRACE_PATTERN: Option<&str> = None;

/// Whether a key matches the active trace pattern.
pub fn is_trace_match(key: &str) -> bool {
TRACE_PATTERN.is_some_and(|p| key.contains(p))
}
const TRACE_ENABLED: bool = false;

/// Whether the mod is in trace/observation mode (no store, pure passthrough).
pub fn is_trace_only() -> bool {
TRACE_PATTERN.is_some()
TRACE_ENABLED
}

/// Dedup state: each op:key combination is logged only once per session.
static DEDUP: Mutex<Option<HashSet<String>>> = Mutex::new(None);

/// Check if an op+key combination should be logged (dedup).
///
/// Returns `false` for combinations already seen. High-frequency keys
/// (frame polling) are handled generically: logged once, then silent.
pub fn should_log(op: &str, key: &str) -> bool {
let mut guard = DEDUP.lock().unwrap_or_else(|e| e.into_inner());
let seen = guard.get_or_insert_with(HashSet::new);
seen.insert(format!("{op}:{key}"))
}

/// Log a PlayerPrefs operation in trace mode (deduped).
pub fn trace_log(op: &str, key: &str, detail: &str) {
if !should_log(op, key) {
return;
}
info!(target: "Trace", "{op} \"{key}\" {detail}");
}

// ---- Hook installation ----------------------------------------------------
Expand Down
75 changes: 37 additions & 38 deletions rust-mod/src/hooks/player_prefs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,8 @@ extern "C" fn hook_set_string(
if super::is_trace_only() {
unsafe { original(key, value, method_info) };
let k = display_string(key);
if super::is_trace_match(&k) {
let v = display_string(value);
info!(target: "Trace", "SET_STRING \"{k}\" = \"{v}\"");
}
let v = display_string(value);
super::trace_log("SET_STRING", &k, &format!("= \"{v}\""));
return;
}

Expand All @@ -241,8 +239,8 @@ extern "C" fn hook_set_string(

if profile_store::is_routed(&k) {
let known = profile_store::record(&k, &v);
if !known {
debug!(target: "PlayerPrefs", "STORE SET \"{k}\"");
if super::should_log("SET_STRING", &k) {
debug!(target: "PlayerPrefs", "STORE SET \"{k}\" = \"{v}\" (known={known})");
}
return true; // handled, don't call original
}
Expand All @@ -268,10 +266,8 @@ extern "C" fn hook_get_string_2(
unsafe { std::mem::transmute(ORIGINAL_GET_STRING_2.load(Relaxed)) };
let result = unsafe { original(key, default_value, method_info) };
let k = display_string(key);
if super::is_trace_match(&k) {
let v = display_string(result);
info!(target: "Trace", "GET_STRING/2 \"{k}\" -> \"{v}\"");
}
let v = display_string(result);
super::trace_log("GET_STRING/2", &k, &format!("-> \"{v}\""));
return result;
}

Expand All @@ -281,12 +277,20 @@ extern "C" fn hook_get_string_2(

// Try store first
if let Some(stored) = profile_store::get(&k) {
if super::should_log("GET_STRING", &k) {
debug!(target: "PlayerPrefs", "STORE HIT \"{k}\" -> \"{stored}\"");
}
return make_il2cpp_string(&stored);
}

// Block Registry in NewAccount/Known modes
// Block Registry in NewAccount/Known modes.
// Always return "" for blocked strings, matching Unity's
// PlayerPrefs.GetString behaviour (never null).
if registry_blocked() {
return default_value;
if super::should_log("BLOCKED", &k) {
debug!(target: "PlayerPrefs", "BLOCKED \"{k}\"");
}
return make_il2cpp_string("");
}

// Import mode: fall through to Registry
Expand Down Expand Up @@ -325,10 +329,8 @@ extern "C" fn hook_get_string_1(
unsafe { std::mem::transmute(ORIGINAL_GET_STRING_1.load(Relaxed)) };
let result = unsafe { original(key, method_info) };
let k = display_string(key);
if super::is_trace_match(&k) {
let v = display_string(result);
info!(target: "Trace", "GET_STRING/1 \"{k}\" -> \"{v}\"");
}
let v = display_string(result);
super::trace_log("GET_STRING/1", &k, &format!("-> \"{v}\""));
return result;
}

Expand Down Expand Up @@ -376,9 +378,7 @@ extern "C" fn hook_set_int(key: *mut Il2CppString, value: i32, method_info: *con
if super::is_trace_only() {
unsafe { original(key, value, method_info) };
let k = display_string(key);
if super::is_trace_match(&k) {
info!(target: "Trace", "SET_INT \"{k}\" = {value}");
}
super::trace_log("SET_INT", &k, &format!("= {value}"));
return;
}

Expand All @@ -391,8 +391,8 @@ extern "C" fn hook_set_int(key: *mut Il2CppString, value: i32, method_info: *con
let k = display_string(key);
if profile_store::is_routed(&k) {
let known = profile_store::record_int(&k, value);
if !known {
debug!(target: "PlayerPrefs", "STORE SET_INT \"{k}\" = {value}");
if super::should_log("SET_INT", &k) {
debug!(target: "PlayerPrefs", "STORE SET_INT \"{k}\" = {value} (known={known})");
}
return true;
}
Expand All @@ -413,9 +413,7 @@ extern "C" fn hook_get_int(
let original: GetIntFn = unsafe { std::mem::transmute(ORIGINAL_GET_INT.load(Relaxed)) };
let result = unsafe { original(key, default_value, method_info) };
let k = display_string(key);
if super::is_trace_match(&k) {
info!(target: "Trace", "GET_INT \"{k}\" -> {result}");
}
super::trace_log("GET_INT", &k, &format!("-> {result}"));
return result;
}

Expand All @@ -424,10 +422,16 @@ extern "C" fn hook_get_int(
let k = display_string(key);

if let Some(stored) = profile_store::get_int(&k) {
if super::should_log("GET_INT", &k) {
debug!(target: "PlayerPrefs", "STORE HIT_INT \"{k}\" -> {stored}");
}
return stored;
}

if registry_blocked() {
if super::should_log("BLOCKED_INT", &k) {
debug!(target: "PlayerPrefs", "BLOCKED_INT \"{k}\" -> default {default_value}");
}
return default_value;
}

Expand Down Expand Up @@ -458,9 +462,7 @@ extern "C" fn hook_set_float(key: *mut Il2CppString, value: f32, method_info: *c
if super::is_trace_only() {
unsafe { original(key, value, method_info) };
let k = display_string(key);
if super::is_trace_match(&k) {
info!(target: "Trace", "SET_FLOAT \"{k}\" = {value}");
}
super::trace_log("SET_FLOAT", &k, &format!("= {value}"));
return;
}

Expand All @@ -473,8 +475,8 @@ extern "C" fn hook_set_float(key: *mut Il2CppString, value: f32, method_info: *c
let k = display_string(key);
if profile_store::is_routed(&k) {
let known = profile_store::record_float(&k, value);
if !known {
debug!(target: "PlayerPrefs", "STORE SET_FLOAT \"{k}\" = {value}");
if super::should_log("SET_FLOAT", &k) {
debug!(target: "PlayerPrefs", "STORE SET_FLOAT \"{k}\" = {value} (known={known})");
}
return true;
}
Expand All @@ -496,9 +498,7 @@ extern "C" fn hook_get_float(
unsafe { std::mem::transmute(ORIGINAL_GET_FLOAT.load(Relaxed)) };
let result = unsafe { original(key, default_value, method_info) };
let k = display_string(key);
if super::is_trace_match(&k) {
info!(target: "Trace", "GET_FLOAT \"{k}\" -> {result}");
}
super::trace_log("GET_FLOAT", &k, &format!("-> {result}"));
return result;
}

Expand Down Expand Up @@ -544,9 +544,7 @@ extern "C" fn hook_has_key(key: *mut Il2CppString, method_info: *const MethodInf
let original: HasKeyFn = unsafe { std::mem::transmute(ORIGINAL_HAS_KEY.load(Relaxed)) };
let result = unsafe { original(key, method_info) };
let k = display_string(key);
if super::is_trace_match(&k) {
info!(target: "Trace", "HAS_KEY \"{k}\" -> {result}");
}
super::trace_log("HAS_KEY", &k, &format!("-> {result}"));
return result;
}

Expand All @@ -559,6 +557,9 @@ extern "C" fn hook_has_key(key: *mut Il2CppString, method_info: *const MethodInf
|| profile_store::get_int(&k).is_some()
|| profile_store::get_float(&k).is_some();
if exists {
if super::should_log("HAS_KEY", &k) {
debug!(target: "PlayerPrefs", "HASKEY HIT \"{k}\"");
}
return 1;
}
// Key is routed but not in store. For primary profiles
Expand Down Expand Up @@ -607,9 +608,7 @@ extern "C" fn hook_delete_key(key: *mut Il2CppString, method_info: *const Method
// Trace-only: log matched keys, no store interaction
if super::is_trace_only() {
let k = display_string(key);
if super::is_trace_match(&k) {
info!(target: "Trace", "DELETE \"{k}\"");
}
super::trace_log("DELETE", &k, "");
return;
}

Expand Down
Loading