mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
Keep the state file's line ahead of RivaTuner Statistics Server's profile, and keep what a run owes the profile after a put back that did not land.
The review found three ways rtss-profile.txt could fall behind the profile. Every write after a run's first one saved the profile before the line, so a kill between the two left a line whose value the profile no longer held, and the next start took the game's limit for the player's (rtss_restore_plan LEAVE). A put back whose write did not land kept the line, but rtss_put_back_done cleared S.changed, so a later limit took the run's first-write path again and read the game's own limit from the profile as the value to put back, overwriting the true one in the line. And put_back removed the line after DeleteProfile without checking that the profile went away. The line now carries the value the run wrote before the newest one (rtss_decide.h RtssSaved prev_limit and prev_den, rtss1 with two more fields; the file came with this branch and was never released). rtss_limit.c apply writes the new value into the line before the profile (record_before) and drops the previous one once the profile holds the new one, or returns to it when RTSS refused it (record_after), and rtss_restore_plan takes a profile holding either value for the game's. A new owed flag keeps had_profile, old_limit and old_denominator from the run's first write until a put back lands: a later first write keeps them and the line's values, a failed first write leaves the line alone, and rtss_limit_restore tries the put back again at the exit. put_back and restore_left check that a deleted profile is gone before they remove the line, and restore_left's lines name the value the profile held. tests/rtss_limit_check.c covers the new fields and the plan for either value; tests/rtss_worker_check.c checks the line before and after a write (state_record), a run killed inside the save of its second limit and the next start removing the profile (kill_restart_mid_write), a put back that did not land followed by a limit and the exit (put_back_owed), and a removal that did not land (put_back_undeleted). Lines over 120 columns from the earlier fixes are wrapped. Not reproduced before the change. Checks that passed: tests/rtss_limit_check.py (44 of 44), tests/exit_order_check.py, tests/fatal_exit_check.py, pass_cc_check.py --wall on rtss_limit.c, tools/check_content.py --staged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -469,8 +469,8 @@ void rtss_limit_want(int64_t mhz); // the pacer's limit i
|
||||
void rtss_limit_movie(int hz); // the rate of a movie that plays, 0 none: the pacer holds it
|
||||
int rtss_limit_active(void); // RTSS limits to the wanted rate: the pacer keeps the ceiling
|
||||
int rtss_limit_state(void); // menu: 1 RTSS limits, 2 its profile is read-only, 3 it let the
|
||||
// game run past its limit or never showed it holds it, 4 kept
|
||||
// out while a limit set in RTSS itself holds the game too
|
||||
// game run past its limit or never showed it holds it, 4
|
||||
// kept out while a limit set in RTSS holds the game too
|
||||
void rtss_limit_restore(void); // before the process ends: the profile's old limit back
|
||||
void rtss_limit_crash(void); // a crash or fatal error: no call into RTSS, the next start
|
||||
// puts the profile back (rtss-profile.txt)
|
||||
|
||||
@@ -341,25 +341,29 @@ static inline uint64_t rtss_pace_period(int holds, int movie, uint64_t now_ns, u
|
||||
// ---- the state file (rtss_limit.c, rtss-profile.txt next to the executable): each RTSS profile a run changed, as it
|
||||
// was before, so that a start after a run that did not close (a kill, a hang, a crash) puts it back. One line per
|
||||
// profile, so executables that share the folder keep their own:
|
||||
// rtss1 <existed 0|1> <old limit> <old denominator> <written limit> <written denominator> <profile name>
|
||||
// The name comes last and may hold spaces; lines that start with # are comments.
|
||||
// rtss1 <existed 0|1> <old limit> <old denominator> <written limit> <written denominator> <previous limit>
|
||||
// <previous denominator> <profile name>
|
||||
// on one line. The name comes last and may hold spaces; lines that start with # are comments. The line takes a new
|
||||
// value before the profile does, and the value written before it stays as the previous one until the profile holds the
|
||||
// new one: a run that ends between the two leaves either value in the profile, and both are the game's.
|
||||
#define RTSS_SAVED_HEADER \
|
||||
"# RivaTuner Statistics Server's profiles as they were before the game changed them (rtss_limit.c): existed, old\n" \
|
||||
"# limit and denominator, the game's limit and denominator, profile. The game puts a profile back and removes its\n" \
|
||||
"# line when it closes; a start that finds a line puts that profile back first.\n"
|
||||
"# limit and denominator, the game's limit and denominator, the game's previous ones, profile. The game puts a\n" \
|
||||
"# profile back and removes its line when it closes; a start that finds a line puts that profile back first.\n"
|
||||
enum { RTSS_NAME_MAX = 260 };
|
||||
|
||||
typedef struct {
|
||||
int existed; // the profile existed before the run changed it
|
||||
uint32_t old_limit, old_den; // what it held then
|
||||
uint32_t limit, den; // what the run wrote last
|
||||
uint32_t prev_limit, prev_den; // what it wrote before, while the profile may still hold it; else limit and den
|
||||
char name[RTSS_NAME_MAX]; // the profile's name, the executable's (ocarina.exe)
|
||||
} RtssSaved;
|
||||
|
||||
// The line of r, with its newline: its length, 0 when it does not fit n.
|
||||
static inline size_t rtss_saved_format(char *out, size_t n, const RtssSaved *r) {
|
||||
int k = snprintf(out, n, "rtss1 %d %u %u %u %u %s\n", r->existed ? 1 : 0, r->old_limit, r->old_den, r->limit,
|
||||
r->den, r->name);
|
||||
int k = snprintf(out, n, "rtss1 %d %u %u %u %u %u %u %s\n", r->existed ? 1 : 0, r->old_limit, r->old_den,
|
||||
r->limit, r->den, r->prev_limit, r->prev_den, r->name);
|
||||
return k > 0 && (size_t)k < n ? (size_t)k : 0;
|
||||
}
|
||||
|
||||
@@ -371,11 +375,14 @@ static inline int rtss_saved_parse(const char *line, size_t len, RtssSaved *r) {
|
||||
memcpy(buf, line, len);
|
||||
buf[len] = 0;
|
||||
int existed = -1, at = 0;
|
||||
unsigned v[4];
|
||||
if (sscanf(buf, "rtss1 %d %u %u %u %u %n", &existed, &v[0], &v[1], &v[2], &v[3], &at) != 5 || !at) return 0;
|
||||
unsigned v[6];
|
||||
if (sscanf(buf, "rtss1 %d %u %u %u %u %u %u %n", &existed, &v[0], &v[1], &v[2], &v[3], &v[4], &v[5], &at) != 7 ||
|
||||
!at)
|
||||
return 0;
|
||||
size_t name = len - (size_t)at;
|
||||
if ((existed != 0 && existed != 1) || !v[1] || !v[3] || !name || name >= RTSS_NAME_MAX) return 0;
|
||||
if ((existed != 0 && existed != 1) || !v[1] || !v[3] || !v[5] || !name || name >= RTSS_NAME_MAX) return 0;
|
||||
r->existed = existed, r->old_limit = v[0], r->old_den = v[1], r->limit = v[2], r->den = v[3];
|
||||
r->prev_limit = v[4], r->prev_den = v[5];
|
||||
memcpy(r->name, buf + at, name + 1);
|
||||
return 1;
|
||||
}
|
||||
@@ -430,7 +437,7 @@ static inline size_t rtss_saved_update(const char *text, const char *name, const
|
||||
}
|
||||
|
||||
// What a start does with a line a run that did not close left (r), given the profile as it is now: whether it exists
|
||||
// and the limit and denominator it holds.
|
||||
// and the limit and denominator it holds. Either value of the line is the game's (RtssSaved.prev_limit).
|
||||
enum {
|
||||
RTSS_PLAN_NONE, // no profile, and there was none before that run: as it was
|
||||
RTSS_PLAN_LEAVE, // the player changed or removed the profile since: theirs as it is
|
||||
@@ -439,7 +446,8 @@ enum {
|
||||
};
|
||||
static inline int rtss_restore_plan(const RtssSaved *r, int exists, uint32_t limit, uint32_t den) {
|
||||
if (!exists) return r->existed ? RTSS_PLAN_LEAVE : RTSS_PLAN_NONE;
|
||||
if (limit != r->limit || (den ? den : 1) != r->den) return RTSS_PLAN_LEAVE;
|
||||
den = den ? den : 1;
|
||||
if ((limit != r->limit || den != r->den) && (limit != r->prev_limit || den != r->prev_den)) return RTSS_PLAN_LEAVE;
|
||||
return r->existed ? RTSS_PLAN_WRITE : RTSS_PLAN_DELETE;
|
||||
}
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ static RtssState S = RTSS_STATE_INIT; // the worker's decisions (rtss_decide.h
|
||||
static int has_denominator; // this RTSS knows FramerateLimitDenominator
|
||||
static int had_profile; // the profile existed before this run changed it
|
||||
static DWORD old_limit, old_denominator = 1;
|
||||
static int owed; // this run changed the profile and has not put it back: the three above stay as found
|
||||
static int creations, removals; // the profile's "created" and "removed" lines this run, numbered (under lock)
|
||||
static char state_path[MAX_PATH + 32]; // rtss-profile.txt next to the executable; "" when its path is unknown
|
||||
static RtssSaved rec; // this run's line in it, while the profile holds a value of this run
|
||||
@@ -197,20 +198,30 @@ static void unrecorded(void) {
|
||||
"Server's profile %s", *state_path ? state_path : "rtss-profile.txt", profile);
|
||||
}
|
||||
|
||||
// rec's written value is limit/denominator now: the line follows it, so that the next start tells the game's value from
|
||||
// a value the player set since.
|
||||
static void record(DWORD limit, DWORD denominator) {
|
||||
// Before a write of limit/denominator into the profile: rec's written value is that one, and the one written before it
|
||||
// stays as the previous one (rtss_decide.h RtssSaved), so that a run that ends before the profile changed, or after,
|
||||
// leaves a profile the next start tells from a value the player set since.
|
||||
static void record_before(DWORD limit, DWORD denominator) {
|
||||
if (!atomic_load(&recorded) || (rec.limit == limit && rec.den == denominator)) return;
|
||||
rec.prev_limit = rec.limit, rec.prev_den = rec.den;
|
||||
rec.limit = limit, rec.den = denominator;
|
||||
if (!state_put(&rec)) unrecorded();
|
||||
}
|
||||
|
||||
// After that write: the profile holds the written value alone (landed), or kept the previous one.
|
||||
static void record_after(int landed) {
|
||||
if (!atomic_load(&recorded) || (rec.limit == rec.prev_limit && rec.den == rec.prev_den)) return;
|
||||
if (landed) rec.prev_limit = rec.limit, rec.prev_den = rec.den;
|
||||
else rec.limit = rec.prev_limit, rec.den = rec.prev_den;
|
||||
if (!state_put(&rec)) unrecorded();
|
||||
}
|
||||
|
||||
// Writes mhz into the profile (rtss_poll's RtssOps.apply): RTSS_WROTE_*. first: the run's first write, which records
|
||||
// what to put back on exit, in the state file too before anything changes; clear: on a refusal, clear the limit of this
|
||||
// run the profile holds.
|
||||
static int apply(void *ctx, int64_t mhz, int first, int clear) {
|
||||
(void)ctx;
|
||||
if (first) { // what to put back on exit
|
||||
if (first && !owed) { // what to put back on exit; after a put back that did not land, the profile still owes it
|
||||
had_profile = profile_exists();
|
||||
load_profile(profile);
|
||||
old_limit = 0, old_denominator = 1;
|
||||
@@ -222,18 +233,24 @@ static int apply(void *ctx, int64_t mhz, int first, int clear) {
|
||||
rtss_limit_value(mhz, has_denominator, &l, &d);
|
||||
DWORD limit = l, denominator = d;
|
||||
if (first) { // the line goes in before the profile changes: a kill between the two finds it as it was
|
||||
rec = (RtssSaved){had_profile, old_limit, old_denominator, limit, denominator, ""};
|
||||
RtssSaved was = rec; // still in the file after a put back that did not land: the profile holds its value
|
||||
int kept = atomic_load(&recorded);
|
||||
rec = (RtssSaved){had_profile, old_limit, old_denominator, limit, denominator, kept ? was.limit : limit,
|
||||
kept ? was.den : denominator, ""};
|
||||
snprintf(rec.name, sizeof rec.name, "%s", profile);
|
||||
if (state_put(&rec)) atomic_store(&recorded, 1);
|
||||
else unrecorded();
|
||||
} else {
|
||||
record_before(limit, denominator);
|
||||
}
|
||||
int r = write_limit(limit, denominator);
|
||||
if (r < 0) {
|
||||
if (r < 0) { // the line keeps both values: the profile may hold either
|
||||
rt_log("frame rate: RivaTuner Statistics Server's profile %s did not keep the limit: its Profiles folder needs the "
|
||||
"game run as administrator", profile);
|
||||
if (first && atomic_exchange(&recorded, 0)) state_put(NULL); // the profile is as it was
|
||||
if (first && !owed && atomic_exchange(&recorded, 0)) state_put(NULL); // the profile is as it was
|
||||
return RTSS_WROTE_READONLY;
|
||||
}
|
||||
owed = 1;
|
||||
if (first) {
|
||||
if (!had_profile) // numbered: a want that flips between a limit and kept out creates it again at each flip
|
||||
rt_log("frame rate: RivaTuner Statistics Server profile %s created; it goes away when the game closes "
|
||||
@@ -244,14 +261,16 @@ static int apply(void *ctx, int64_t mhz, int first, int clear) {
|
||||
}
|
||||
if (r == 0) { // RTSS must not keep an earlier limit of this run under the game's own one
|
||||
rt_log("frame rate: RivaTuner Statistics Server refused a limit of %.3f fps", mhz / 1000.0);
|
||||
if (clear && write_limit(0, 1) > 0) {
|
||||
record(0, 1);
|
||||
update_profiles();
|
||||
return RTSS_WROTE_REFUSED_CLEARED;
|
||||
}
|
||||
return RTSS_WROTE_REFUSED;
|
||||
record_after(0);
|
||||
if (!clear) return RTSS_WROTE_REFUSED;
|
||||
record_before(0, 1);
|
||||
int cleared = write_limit(0, 1) > 0;
|
||||
record_after(cleared);
|
||||
if (!cleared) return RTSS_WROTE_REFUSED;
|
||||
update_profiles();
|
||||
return RTSS_WROTE_REFUSED_CLEARED;
|
||||
}
|
||||
record(limit, denominator);
|
||||
record_after(1);
|
||||
update_profiles();
|
||||
if (!mhz) rt_log("frame rate: RivaTuner Statistics Server's profile %s has no limit", profile);
|
||||
else if ((int64_t)limit * 1000 == mhz * denominator)
|
||||
@@ -262,14 +281,19 @@ static int apply(void *ctx, int64_t mhz, int first, int clear) {
|
||||
return RTSS_WROTE_HELD;
|
||||
}
|
||||
|
||||
// The profile as it was before this run changed it (the caller checked S.changed, and marks it done). Its line in the
|
||||
// state file goes once it is back; one that did not take its old limit keeps it for the next start.
|
||||
// The profile as it was before this run changed it (the caller checked S.changed or owed, and marks S done). Its line
|
||||
// in the state file goes once it is back; one that did not go back keeps the line, and owed, for the exit and the next
|
||||
// start.
|
||||
static void put_back(void) {
|
||||
int back = 1;
|
||||
if (!had_profile) {
|
||||
delete_profile(profile);
|
||||
rt_log("frame rate: RivaTuner Statistics Server profile %s removed, as before the game started "
|
||||
"(removal %d this run)", profile, ++removals);
|
||||
back = !profile_exists();
|
||||
if (back)
|
||||
rt_log("frame rate: RivaTuner Statistics Server profile %s removed, as before the game started "
|
||||
"(removal %d this run)", profile, ++removals);
|
||||
else
|
||||
rt_log("frame rate: RivaTuner Statistics Server profile %s did not go away", profile);
|
||||
} else if (write_limit(old_limit, old_denominator) > 0) {
|
||||
rt_log("frame rate: RivaTuner Statistics Server profile %s has its limit of %lu/%lu back", profile, old_limit,
|
||||
old_denominator);
|
||||
@@ -278,6 +302,7 @@ static void put_back(void) {
|
||||
back = 0;
|
||||
}
|
||||
update_profiles();
|
||||
if (back) owed = 0;
|
||||
if (back && atomic_exchange(&recorded, 0)) state_put(NULL);
|
||||
}
|
||||
|
||||
@@ -292,30 +317,30 @@ static int left_line(RtssSaved *r) {
|
||||
|
||||
// The line r of this profile, left in the state file by a run that did not close (left_start): before this run changes
|
||||
// anything, the profile gets back what it held before that run (rtss_restore_plan), unless the player changed it since,
|
||||
// and the line goes. A profile that does not take its old limit keeps the line, and this run leaves the file alone.
|
||||
// and the line goes. A profile that does not go back keeps the line, and this run leaves the file alone.
|
||||
static void restore_left(const RtssSaved *r) {
|
||||
int exists = profile_exists();
|
||||
load_profile(profile);
|
||||
DWORD limit = 0, den = 1;
|
||||
get_property("FramerateLimit", (LPBYTE)&limit, sizeof limit);
|
||||
has_denominator = get_property("FramerateLimitDenominator", (LPBYTE)&den, sizeof den);
|
||||
if (!den) den = 1;
|
||||
int plan = rtss_restore_plan(r, exists, limit, den);
|
||||
if (plan == RTSS_PLAN_WRITE && write_limit(r->old_limit, r->old_den) <= 0) {
|
||||
rt_log("frame rate: RivaTuner Statistics Server profile %s still has the game's limit of %u/%u from a run that "
|
||||
"did not close, and did not take its old limit back; %s keeps its line", profile, r->limit, r->den,
|
||||
state_path);
|
||||
if (plan == RTSS_PLAN_DELETE) delete_profile(profile);
|
||||
if ((plan == RTSS_PLAN_WRITE && write_limit(r->old_limit, r->old_den) <= 0) ||
|
||||
(plan == RTSS_PLAN_DELETE && profile_exists())) {
|
||||
rt_log("frame rate: RivaTuner Statistics Server profile %s still has the game's limit of %lu/%lu from a run "
|
||||
"that did not close, and did not go back as it was; %s keeps its line", profile, limit, den, state_path);
|
||||
state_off = 1;
|
||||
return;
|
||||
}
|
||||
if (plan == RTSS_PLAN_DELETE) delete_profile(profile);
|
||||
if (plan == RTSS_PLAN_WRITE || plan == RTSS_PLAN_DELETE) update_profiles();
|
||||
if (plan == RTSS_PLAN_WRITE)
|
||||
rt_log("frame rate: RivaTuner Statistics Server profile %s had the game's limit of %u/%u from a run that did not "
|
||||
"close (%s); its limit of %u/%u is back", profile, r->limit, r->den, state_path, r->old_limit,
|
||||
r->old_den);
|
||||
rt_log("frame rate: RivaTuner Statistics Server profile %s had the game's limit of %lu/%lu from a run that did "
|
||||
"not close (%s); its limit of %u/%u is back", profile, limit, den, state_path, r->old_limit, r->old_den);
|
||||
else if (plan == RTSS_PLAN_DELETE)
|
||||
rt_log("frame rate: RivaTuner Statistics Server profile %s had the game's limit of %u/%u from a run that did not "
|
||||
"close (%s); removed, as before that run", profile, r->limit, r->den, state_path);
|
||||
rt_log("frame rate: RivaTuner Statistics Server profile %s had the game's limit of %lu/%lu from a run that did "
|
||||
"not close (%s); removed, as before that run", profile, limit, den, state_path);
|
||||
else if (plan == RTSS_PLAN_LEAVE)
|
||||
rt_log("frame rate: RivaTuner Statistics Server profile %s changed since a run that did not close (%s); left as "
|
||||
"it is", profile, state_path);
|
||||
@@ -579,7 +604,7 @@ void rtss_limit_restore(void) {
|
||||
// the exit may come from a crash on any thread: give up after a second rather than wait on a lock held there
|
||||
for (int i = 0; i < 100; i++) {
|
||||
if (plat_mutex_trylock(&lock)) {
|
||||
if (dll && S.changed) put_back(), rtss_put_back_done(&S);
|
||||
if (dll && (S.changed || owed)) put_back(), rtss_put_back_done(&S);
|
||||
plat_mutex_unlock(&lock);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -353,7 +353,8 @@ static void unproven(void) {
|
||||
{0, 1, "no API seen", "RivaTuner Statistics Server lists the game but has not seen its Vulkan presents yet"},
|
||||
{0x7, 1, "another API's presents",
|
||||
"RivaTuner Statistics Server lists the game but has not seen its Vulkan presents yet"},
|
||||
{RTSS_API_VULKAN, 0, "no period measured", "RivaTuner Statistics Server has measured no frames of the game lately"},
|
||||
{RTSS_API_VULKAN, 0, "no period measured",
|
||||
"RivaTuner Statistics Server has measured no frames of the game lately"},
|
||||
};
|
||||
Run r;
|
||||
RtssOut o;
|
||||
@@ -598,26 +599,30 @@ static void pace_period(void) {
|
||||
// The state file (rtss-profile.txt): its lines, the file's text as the worker rewrites it, and what a start does with a
|
||||
// line that a run which did not close left behind.
|
||||
static void saved(void) {
|
||||
RtssSaved a = {0, 0, 1, 60, 1, "ocarina.exe"}, r;
|
||||
RtssSaved a = {0, 0, 1, 60, 1, 60, 1, "ocarina.exe"}, r;
|
||||
char line[400], out[2048];
|
||||
size_t n = rtss_saved_format(line, sizeof line, &a);
|
||||
CHECK(n == strlen(line) && !strcmp(line, "rtss1 0 0 1 60 1 ocarina.exe\n"), "the line: %s", line);
|
||||
CHECK(n == strlen(line) && !strcmp(line, "rtss1 0 0 1 60 1 60 1 ocarina.exe\n"), "the line: %s", line);
|
||||
CHECK(rtss_saved_parse(line, n - 1, &r) && !r.existed && !r.old_limit && r.old_den == 1 && r.limit == 60 &&
|
||||
r.den == 1 && !strcmp(r.name, "ocarina.exe"),
|
||||
r.den == 1 && r.prev_limit == 60 && r.prev_den == 1 && !strcmp(r.name, "ocarina.exe"),
|
||||
"read back");
|
||||
const char *spaced = "rtss1 1 59940 1000 144 1 My Game.exe\r";
|
||||
const char *spaced = "rtss1 1 59940 1000 144 1 60 1 My Game.exe\r";
|
||||
CHECK(rtss_saved_parse(spaced, strlen(spaced), &r) && r.existed && r.old_limit == 59940 && r.old_den == 1000 &&
|
||||
!strcmp(r.name, "My Game.exe"),
|
||||
r.limit == 144 && r.prev_limit == 60 && !strcmp(r.name, "My Game.exe"),
|
||||
"a name with a space, a line ending in CR");
|
||||
static const char *bad[] = {"rtss1 2 0 1 60 1 x.exe", "rtss1 0 0 0 60 1 x.exe", "rtss1 0 0 1 60 0 x.exe",
|
||||
"rtss1 0 0 1 60 1", "rtss1 0 0 1 60 1 ", "rtss2 0 0 1 60 1 x.exe", "rtss1 0 0 1 x 1 x.exe",
|
||||
"", "# rtss1 0 0 1 60 1 x.exe"};
|
||||
static const char *bad[] = {"rtss1 2 0 1 60 1 60 1 x.exe", "rtss1 0 0 0 60 1 60 1 x.exe",
|
||||
"rtss1 0 0 1 60 0 60 1 x.exe", "rtss1 0 0 1 60 1 60 0 x.exe",
|
||||
"rtss1 0 0 1 60 1 60 1", "rtss1 0 0 1 60 1 60 1 ",
|
||||
"rtss2 0 0 1 60 1 60 1 x.exe", "rtss1 0 0 1 x 1 60 1 x.exe",
|
||||
"rtss1 0 0 1 60 1 x.exe", "",
|
||||
"# rtss1 0 0 1 60 1 60 1 x.exe"};
|
||||
for (unsigned i = 0; i < sizeof bad / sizeof *bad; i++)
|
||||
CHECK(!rtss_saved_parse(bad[i], strlen(bad[i]), &r), "not a line: '%s'", bad[i]);
|
||||
CHECK(!rtss_saved_format(line, 20, &a), "a line that does not fit");
|
||||
|
||||
// found past a comment, a malformed line and another profile's line, whatever the case of its letters
|
||||
const char *text = RTSS_SAVED_HEADER "rtss1 9 garbage\nrtss1 1 30 1 144 1 My Game.exe\nrtss1 0 0 1 60 1 OCARINA.EXE";
|
||||
const char *text = RTSS_SAVED_HEADER
|
||||
"rtss1 9 garbage\nrtss1 1 30 1 144 1 144 1 My Game.exe\nrtss1 0 0 1 60 1 60 1 OCARINA.EXE";
|
||||
CHECK(rtss_saved_find(text, "ocarina.exe", &r) && r.limit == 60 && !r.existed, "ours, the last line, no newline");
|
||||
CHECK(rtss_saved_find(text, "my game.exe", &r) && r.limit == 144 && r.old_limit == 30, "the other one");
|
||||
CHECK(!rtss_saved_find(text, "ocarina_dev.exe", &r) && !rtss_saved_find("", "ocarina.exe", &r), "none");
|
||||
@@ -628,20 +633,23 @@ static void saved(void) {
|
||||
a2.limit = 144;
|
||||
n = rtss_saved_update(text, "ocarina.exe", &a2, out, sizeof out, &records);
|
||||
CHECK(n == strlen(out) && records == 2 &&
|
||||
!strcmp(out, RTSS_SAVED_HEADER "rtss1 1 30 1 144 1 My Game.exe\nrtss1 0 0 1 144 1 ocarina.exe\n"),
|
||||
!strcmp(out, RTSS_SAVED_HEADER
|
||||
"rtss1 1 30 1 144 1 144 1 My Game.exe\nrtss1 0 0 1 144 1 60 1 ocarina.exe\n"),
|
||||
"replaced:\n%s", out);
|
||||
char again[2048];
|
||||
n = rtss_saved_update(out, "OCARINA.exe", NULL, again, sizeof again, &records);
|
||||
CHECK(records == 1 && !strcmp(again, RTSS_SAVED_HEADER "rtss1 1 30 1 144 1 My Game.exe\n"), "removed:\n%s", again);
|
||||
CHECK(records == 1 && !strcmp(again, RTSS_SAVED_HEADER "rtss1 1 30 1 144 1 144 1 My Game.exe\n"), "removed:\n%s",
|
||||
again);
|
||||
n = rtss_saved_update(again, "My Game.exe", NULL, out, sizeof out, &records);
|
||||
CHECK(n && !records, "the last line removed: no record left, the file goes");
|
||||
n = rtss_saved_update("", "ocarina.exe", &a, out, sizeof out, &records);
|
||||
CHECK(records == 1 && !strcmp(out, RTSS_SAVED_HEADER "rtss1 0 0 1 60 1 ocarina.exe\n"), "a new file:\n%s", out);
|
||||
CHECK(records == 1 && !strcmp(out, RTSS_SAVED_HEADER "rtss1 0 0 1 60 1 60 1 ocarina.exe\n"), "a new file:\n%s",
|
||||
out);
|
||||
CHECK(!rtss_saved_update(text, "ocarina.exe", &a, out, sizeof RTSS_SAVED_HEADER + 10, &records),
|
||||
"a text that does not fit");
|
||||
|
||||
// What a start does. Run A made the profile and wrote 60/1, and was killed: B finds 60/1 and removes the profile.
|
||||
RtssSaved made = {0, 0, 1, 60, 1, "ocarina.exe"}, had = {1, 30, 1, 60, 1, "ocarina.exe"};
|
||||
RtssSaved made = {0, 0, 1, 60, 1, 60, 1, "ocarina.exe"}, had = {1, 30, 1, 60, 1, 60, 1, "ocarina.exe"};
|
||||
CHECK(rtss_restore_plan(&made, 1, 60, 1) == RTSS_PLAN_DELETE, "the run's own profile: removed");
|
||||
CHECK(rtss_restore_plan(&made, 1, 75, 1) == RTSS_PLAN_LEAVE, "the player set 75 since: theirs");
|
||||
CHECK(rtss_restore_plan(&made, 0, 0, 1) == RTSS_PLAN_NONE, "the player removed it, or the run was killed before "
|
||||
@@ -650,10 +658,22 @@ static void saved(void) {
|
||||
CHECK(rtss_restore_plan(&had, 1, 30, 1) == RTSS_PLAN_LEAVE, "killed before the write landed: the player's 30/1");
|
||||
CHECK(rtss_restore_plan(&had, 0, 0, 1) == RTSS_PLAN_LEAVE, "the player removed their profile since: left so");
|
||||
CHECK(rtss_restore_plan(&had, 1, 60, 0) == RTSS_PLAN_WRITE, "an RTSS without the denominator reads none: 1");
|
||||
RtssSaved frac = {1, 0, 1, 59940, 1000, "ocarina.exe"};
|
||||
RtssSaved frac = {1, 0, 1, 59940, 1000, 59940, 1000, "ocarina.exe"};
|
||||
CHECK(rtss_restore_plan(&frac, 1, 59940, 1) == RTSS_PLAN_LEAVE && rtss_restore_plan(&frac, 1, 59940, 1000) ==
|
||||
RTSS_PLAN_WRITE,
|
||||
"the denominator is compared too");
|
||||
// A run that wrote 60/1 and then 144/1 ended between the line and the profile's change, or after it: the profile
|
||||
// holds 60/1 or 144/1, both the game's; anything else is the player's.
|
||||
RtssSaved moving = {1, 30, 1, 144, 1, 60, 1, "ocarina.exe"}, moving_made = {0, 0, 1, 144, 1, 60, 1, "ocarina.exe"};
|
||||
CHECK(rtss_restore_plan(&moving, 1, 60, 1) == RTSS_PLAN_WRITE && rtss_restore_plan(&moving, 1, 144, 1) ==
|
||||
RTSS_PLAN_WRITE,
|
||||
"either value of the game's: 30/1 back");
|
||||
CHECK(rtss_restore_plan(&moving_made, 1, 60, 1) == RTSS_PLAN_DELETE &&
|
||||
rtss_restore_plan(&moving_made, 1, 144, 1) == RTSS_PLAN_DELETE,
|
||||
"either value of the game's, in a profile the run made: removed");
|
||||
CHECK(rtss_restore_plan(&moving, 1, 75, 1) == RTSS_PLAN_LEAVE && rtss_restore_plan(&moving, 1, 60, 1000) ==
|
||||
RTSS_PLAN_LEAVE,
|
||||
"another value: the player's");
|
||||
}
|
||||
|
||||
// The profile's values for a limit (T15): whole rates as they are, a fraction over 1000 where RTSS knows the
|
||||
|
||||
@@ -905,15 +905,21 @@ static void churn_run(void) {
|
||||
|
||||
// The state file: the run's first write records the profile as it was, each later one the value written, and the exit's
|
||||
// restore removes the line and the file with it.
|
||||
static char line_at_save[4096]; // the state file as SaveProfile found it (keep_line)
|
||||
static void keep_line(void) { snprintf(line_at_save, sizeof line_at_save, "%s", state()); }
|
||||
static void state_record(void) {
|
||||
app_set(1, 0xA, 1000, 2000, 60);
|
||||
ask(60000);
|
||||
poll();
|
||||
show("playing at 60");
|
||||
CHECK(own.exists && own.limit == 60 && state_has("\nrtss1 0 0 1 60 1 ocarina.exe\n"), "recorded:\n%s", state());
|
||||
CHECK(own.exists && own.limit == 60 && state_has("\nrtss1 0 0 1 60 1 60 1 ocarina.exe\n"), "recorded:\n%s",
|
||||
state());
|
||||
CHECK(!strncmp(state(), "# RivaTuner Statistics Server's profiles", 40), "the header first");
|
||||
during_save = keep_line;
|
||||
settle(144000);
|
||||
CHECK(own.limit == 144 && state_has("\nrtss1 0 0 1 144 1 ocarina.exe\n"), "the line follows:\n%s", state());
|
||||
CHECK(strstr(line_at_save, "\nrtss1 0 0 1 144 1 60 1 ocarina.exe\n"),
|
||||
"before the profile changes, the line holds 144 and the 60 before it:\n%s", line_at_save);
|
||||
CHECK(own.limit == 144 && state_has("\nrtss1 0 0 1 144 1 144 1 ocarina.exe\n"), "then 144 alone:\n%s", state());
|
||||
rtss_limit_restore();
|
||||
show("after the restore");
|
||||
CHECK(!own.exists && !*state(), "the profile and the state file gone:\n%s", state());
|
||||
@@ -923,11 +929,11 @@ static void state_player(void) {
|
||||
app_set(1, 0xA, 1000, 2000, 60);
|
||||
ask(60000);
|
||||
poll();
|
||||
CHECK(own.limit == 60 && state_has("\nrtss1 1 30 1 60 1 ocarina.exe\n"), "recorded:\n%s", state());
|
||||
CHECK(own.limit == 60 && state_has("\nrtss1 1 30 1 60 1 60 1 ocarina.exe\n"), "recorded:\n%s", state());
|
||||
settle(RTSS_OUT_LOWLAT); // kept out: put back during the run, and the line goes with it
|
||||
CHECK(own.limit == 30 && !*state(), "put back, no line:\n%s", state());
|
||||
settle(60000);
|
||||
CHECK(own.limit == 60 && state_has("\nrtss1 1 30 1 60 1 ocarina.exe\n"), "recorded again:\n%s", state());
|
||||
CHECK(own.limit == 60 && state_has("\nrtss1 1 30 1 60 1 60 1 ocarina.exe\n"), "recorded again:\n%s", state());
|
||||
rtss_limit_restore();
|
||||
CHECK(own.exists && own.limit == 30 && !*state(), "the player's 30 back, no line");
|
||||
}
|
||||
@@ -950,6 +956,48 @@ static void state_unwritable(void) {
|
||||
CHECK(!own.exists && !rtss_limit_active(), "the crash put the profile back");
|
||||
}
|
||||
|
||||
// A put back during the run that does not land (the profile went read-only): the line and the values found at the
|
||||
// run's first write stay. A later limit is the run's first write again and keeps the player's 30 to put back, not the
|
||||
// game's 60 the profile still holds, and the exit puts 30 back.
|
||||
static void put_back_owed(void) {
|
||||
own = (Prof){1, 30, 1, 0, 0, 0, 0, 0, 0, 0, 1};
|
||||
app_set(1, 0xA, 1000, 2000, 60);
|
||||
ask(60000);
|
||||
poll();
|
||||
readonly = 1;
|
||||
settle(RTSS_OUT_LOWLAT);
|
||||
show("kept out, the put back did not land");
|
||||
CHECK(own.limit == 60 && logged("did not take its old limit back") &&
|
||||
state_has("\nrtss1 1 30 1 60 1 60 1 ocarina.exe\n"),
|
||||
"the line stays:\n%s", state());
|
||||
readonly = 0;
|
||||
settle(144000);
|
||||
show("a limit again");
|
||||
CHECK(own.limit == 144 && state_has("\nrtss1 1 30 1 144 1 144 1 ocarina.exe\n"), "the player's 30 still owed:\n%s",
|
||||
state());
|
||||
rtss_limit_restore();
|
||||
show("closed");
|
||||
CHECK(own.exists && own.limit == 30 && !*state(), "the exit puts the player's 30 back:\n%s", state());
|
||||
}
|
||||
|
||||
// The same where the run made the profile: a removal that does not land is no removal; the line stays, and the exit
|
||||
// removes the profile.
|
||||
static void put_back_undeleted(void) {
|
||||
app_set(1, 0xA, 1000, 2000, 60);
|
||||
ask(60000);
|
||||
poll();
|
||||
readonly = 1;
|
||||
settle(RTSS_OUT_LOWLAT);
|
||||
show("kept out, the removal did not land");
|
||||
CHECK(own.exists && logged("profile ocarina.exe did not go away") && !removed &&
|
||||
state_has("\nrtss1 0 0 1 60 1 60 1 ocarina.exe\n"),
|
||||
"the line stays:\n%s", state());
|
||||
readonly = 0;
|
||||
rtss_limit_restore();
|
||||
show("closed");
|
||||
CHECK(!own.exists && removed == 1 && !*state(), "the exit removes it:\n%s", state());
|
||||
}
|
||||
|
||||
// A crash with the line in the state file: no call into RTSS's library, the profile and the line stay for the next
|
||||
// start, and nothing after it reaches the profile.
|
||||
static void crash_recorded(void) {
|
||||
@@ -960,7 +1008,8 @@ static void crash_recorded(void) {
|
||||
rtss_limit_crash();
|
||||
show("a crash");
|
||||
CHECK(saves == s0 && deletes == d0 && updates == u0, "no profile call");
|
||||
CHECK(own.exists && own.limit == 60 && state_has("\nrtss1 0 0 1 60 1 ocarina.exe\n"), "left for the next start");
|
||||
CHECK(own.exists && own.limit == 60 && state_has("\nrtss1 0 0 1 60 1 60 1 ocarina.exe\n"),
|
||||
"left for the next start");
|
||||
CHECK(!rtss_limit_active() && !rtss_limit_state(), "the game's limiter");
|
||||
ask(144000);
|
||||
poll();
|
||||
@@ -972,23 +1021,36 @@ static void crash_recorded(void) {
|
||||
// variant 2: the player set 75 in RTSS between the two runs; variant 3: the next start has frame generation on, which
|
||||
// keeps RTSS out, and still puts the profile back (the hooks library loaded for that alone) with no write of its own;
|
||||
// variant 4: the next start has NX2NAVI_RTSS=0, which keeps the thread read-only: the log says what the file holds,
|
||||
// and the profile and the line stay for a start with RTSS let in.
|
||||
// and the profile and the line stay for a start with RTSS let in; variant 5: the run is killed inside the save of its
|
||||
// second limit, after the line took 144 and before the profile did: the next start finds the 60 before it and removes
|
||||
// the profile.
|
||||
static int variant;
|
||||
static void kill_in_save(void) {
|
||||
CHECK(state_has("\nrtss1 0 0 1 144 1 60 1 ocarina.exe\n") && own.limit == 60, "the line before the profile:\n%s",
|
||||
state());
|
||||
prof_save();
|
||||
ExitProcess(fails != 0);
|
||||
}
|
||||
static void kill_first(void) {
|
||||
prof_load();
|
||||
app_set(1, 0xA, 1000, 2000, 60);
|
||||
ask(60000);
|
||||
poll();
|
||||
show("the run before the kill");
|
||||
CHECK(own.exists && own.limit == 60 && state_has(variant == 1 ? "\nrtss1 1 30 1 60 1 ocarina.exe\n"
|
||||
: "\nrtss1 0 0 1 60 1 ocarina.exe\n"),
|
||||
CHECK(own.exists && own.limit == 60 && state_has(variant == 1 ? "\nrtss1 1 30 1 60 1 60 1 ocarina.exe\n"
|
||||
: "\nrtss1 0 0 1 60 1 60 1 ocarina.exe\n"),
|
||||
"recorded:\n%s", state());
|
||||
if (variant == 5) { // killed inside the save of 144: the line has 144, the profile still 60
|
||||
during_save = kill_in_save;
|
||||
settle(144000);
|
||||
CHECK(0, "the run went on past the kill");
|
||||
}
|
||||
prof_save(); // killed: no restore
|
||||
}
|
||||
static void kill_next(void) {
|
||||
prof_load();
|
||||
app_set(1, 0xA, 1000, 2000, 144);
|
||||
if (variant >= 3) {
|
||||
if (variant == 3 || variant == 4) {
|
||||
ask(variant == 3 ? RTSS_OUT_FRAMEGEN : RTSS_OUT_SETTING);
|
||||
poll();
|
||||
poll();
|
||||
@@ -1005,7 +1067,8 @@ static void kill_next(void) {
|
||||
"profile ocarina.exe from a run that did not close; NX2NAVI_RTSS=0 leaves the "
|
||||
"profile as it is"),
|
||||
"read only: logged");
|
||||
CHECK(own.exists && own.limit == 60 && state_has("\nrtss1 0 0 1 60 1 ocarina.exe\n"), "left:\n%s", state());
|
||||
CHECK(own.exists && own.limit == 60 && state_has("\nrtss1 0 0 1 60 1 60 1 ocarina.exe\n"), "left:\n%s",
|
||||
state());
|
||||
}
|
||||
rtss_limit_restore();
|
||||
show("closed");
|
||||
@@ -1015,22 +1078,22 @@ static void kill_next(void) {
|
||||
ask(144000);
|
||||
poll();
|
||||
show("the next start");
|
||||
if (variant == 0) {
|
||||
if (variant == 0 || variant == 5) {
|
||||
CHECK(logged("profile ocarina.exe had the game's limit of 60/1 from a run that did not close") &&
|
||||
logged("; removed, as before that run"),
|
||||
"the killed run's profile removed");
|
||||
CHECK(logged("created; it goes away when the game closes (creation 1 this run)"), "made again by this run");
|
||||
CHECK(state_has("\nrtss1 0 0 1 144 1 ocarina.exe\n"), "this run's line:\n%s", state());
|
||||
CHECK(state_has("\nrtss1 0 0 1 144 1 144 1 ocarina.exe\n"), "this run's line:\n%s", state());
|
||||
} else if (variant == 1) {
|
||||
CHECK(logged("profile ocarina.exe had the game's limit of 60/1 from a run that did not close") &&
|
||||
logged("; its limit of 30/1 is back"),
|
||||
"the player's 30 back");
|
||||
CHECK(logged("held a limit of 30/1; it gets that back"), "this run takes 30 for the player's");
|
||||
CHECK(state_has("\nrtss1 1 30 1 144 1 ocarina.exe\n"), "this run's line:\n%s", state());
|
||||
CHECK(state_has("\nrtss1 1 30 1 144 1 144 1 ocarina.exe\n"), "this run's line:\n%s", state());
|
||||
} else {
|
||||
CHECK(logged("profile ocarina.exe changed since a run that did not close") && logged("; left as it is"),
|
||||
"the player's 75 left");
|
||||
CHECK(state_has("\nrtss1 1 75 1 144 1 ocarina.exe\n"), "this run's line:\n%s", state());
|
||||
CHECK(state_has("\nrtss1 1 75 1 144 1 144 1 ocarina.exe\n"), "this run's line:\n%s", state());
|
||||
}
|
||||
CHECK(own.exists && own.limit == 144, "this run's limit");
|
||||
rtss_limit_restore();
|
||||
@@ -1060,18 +1123,19 @@ static void kill_restart_as(int v) {
|
||||
CHECK(run_phase("kill_next") == 0, "the next start");
|
||||
prof_load();
|
||||
show("after the next start closed");
|
||||
if (v == 0) CHECK(!own.exists, "the profile is gone, as before the killed run");
|
||||
if (v == 0 || v == 5) CHECK(!own.exists, "the profile is gone, as before the killed run");
|
||||
if (v == 1) CHECK(own.exists && own.limit == 30, "the player's 30");
|
||||
if (v == 2) CHECK(own.exists && own.limit == 75, "the player's 75");
|
||||
if (v == 3) CHECK(!own.exists, "the profile is gone, as before the killed run");
|
||||
if (v == 4) CHECK(own.exists && own.limit == 60, "the killed run's 60 stays for a start with RTSS let in");
|
||||
CHECK(v == 4 ? state_has("\nrtss1 0 0 1 60 1 ocarina.exe\n") : !*state(), "the state file:\n%s", state());
|
||||
CHECK(v == 4 ? state_has("\nrtss1 0 0 1 60 1 60 1 ocarina.exe\n") : !*state(), "the state file:\n%s", state());
|
||||
}
|
||||
static void kill_restart(void) { kill_restart_as(0); }
|
||||
static void kill_restart_player(void) { kill_restart_as(1); }
|
||||
static void kill_restart_edited(void) { kill_restart_as(2); }
|
||||
static void kill_restart_framegen(void) { kill_restart_as(3); }
|
||||
static void kill_restart_setting(void) { kill_restart_as(4); }
|
||||
static void kill_restart_mid_write(void) { kill_restart_as(5); }
|
||||
|
||||
// A movie at 60 under a limit of 144: the want stays 144 (frame_rate.c frame_step_cap_ns) and RTSS's profile keeps it
|
||||
// with no write, while the game's pacer holds the movie at its rate; RTSS holds the game again at its end.
|
||||
@@ -1151,6 +1215,9 @@ static const struct { const char *name; void (*run)(void); } scenarios[] = {
|
||||
{"kill_restart_edited", kill_restart_edited},
|
||||
{"kill_restart_framegen", kill_restart_framegen},
|
||||
{"kill_restart_setting", kill_restart_setting},
|
||||
{"kill_restart_mid_write", kill_restart_mid_write},
|
||||
{"put_back_owed", put_back_owed},
|
||||
{"put_back_undeleted", put_back_undeleted},
|
||||
};
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
Reference in New Issue
Block a user