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
34 changes: 34 additions & 0 deletions dylib/feats/compat_run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1025,6 +1025,31 @@ if [ "$verb" = waitforexitandrun ] && [ -f "$np_defaults_file" ] && [ -r "$np_de
[ "${wine_unix##*/}" = aarch64-unix ] || continue ;;
MTL_HUD_ENABLED=[01] | DXMT_ENABLE_NVEXT=[01] | DXMT_METALFX_SPATIAL_SWAPCHAIN=[01] | \
WINEMSYNC=[01] | NOTPROTON_RETINA=[01] | NOTPROTON_RAW_CONTROLLERS=[01] | NOTPROTON_DISABLE_HIDRAW=[01]) ;;
DXMT_CONFIG=*\;*)
dxmt_added=""
old_ifs=$IFS; IFS=";"
set -f
for clause in $value; do
case "$clause" in d3d11.metalSpatialUpscaleFactor=*)
case "${clause#*=}" in 1.5 | 1.72 | 2.0 | 3.0) ;; *) continue ;; esac
if ! printf '%s' "${DXMT_CONFIG-}" | tr ';' '\n' \
| grep -q '^[[:space:]]*d3d11\.metalSpatialUpscaleFactor[[:space:]]*='; then
export DXMT_CONFIG="${DXMT_CONFIG:+$DXMT_CONFIG;}$clause"
dxmt_added="${dxmt_added:+$dxmt_added;}$clause"
fi ;;
d3d11.preferredMaxFrameRate=*)
case "${clause#*=}" in 30 | 40 | 60 | 120 | 144) ;; *) continue ;; esac
if ! printf '%s' "${DXMT_CONFIG-}" | tr ';' '\n' \
| grep -q '^[[:space:]]*d3d11\.preferredMaxFrameRate[[:space:]]*='; then
export DXMT_CONFIG="${DXMT_CONFIG:+$DXMT_CONFIG;}$clause"
dxmt_added="${dxmt_added:+$dxmt_added;}$clause"
fi ;; esac
done
set +f; IFS=$old_ifs
if [ -n "$dxmt_added" ]; then
np_defaulted="$np_defaulted${np_defaulted:+ }DXMT_CONFIG=$dxmt_added"
fi
continue ;;
DXMT_CONFIG=d3d11.metalSpatialUpscaleFactor=*)
case "${value#*=}" in 1.5 | 1.72 | 2.0 | 3.0) ;; *) continue ;; esac
if printf '%s' "${DXMT_CONFIG-}" | tr ';' '\n' \
Expand All @@ -1034,6 +1059,15 @@ if [ "$verb" = waitforexitandrun ] && [ -f "$np_defaults_file" ] && [ -r "$np_de
export DXMT_CONFIG="${DXMT_CONFIG:+$DXMT_CONFIG;}$value"
np_defaulted="$np_defaulted${np_defaulted:+ }$line"
continue ;;
DXMT_CONFIG=d3d11.preferredMaxFrameRate=*)
case "${value#*d3d11.preferredMaxFrameRate=}" in 30 | 40 | 60 | 120 | 144) ;; *) continue ;; esac
if printf '%s' "${DXMT_CONFIG-}" | tr ';' '\n' \
| grep -q '^[[:space:]]*d3d11\.preferredMaxFrameRate[[:space:]]*='; then
continue
fi
export DXMT_CONFIG="${DXMT_CONFIG:+$DXMT_CONFIG;}$value"
np_defaulted="$np_defaulted${np_defaulted:+ }$line"
continue ;;
*) continue ;;
esac
eval "[ -z \"\${$key+x}\" ]" || continue
Expand Down
32 changes: 32 additions & 0 deletions dylib/feats/defaults.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ static const char *const g_bools[] = { "0", "1", NULL };
static const char *const g_backends[] = { "dxmt", "dxvk", "wined3d", NULL };
static const char *const g_d3dmetal[] = { "d3dmetal", NULL };
static const char *const g_factors[] = { "1.5", "1.72", "2.0", "3.0", NULL };
static const char *const g_fps[] = { "30", "40", "60", "120", "144", NULL };

static const struct {
const char *key;
Expand Down Expand Up @@ -56,7 +57,38 @@ static int is_one_of(const char *const *list, const char *v, size_t len) {
return 0;
}

#define FACTOR_PREFIX "d3d11.metalSpatialUpscaleFactor="
#define FPS_PREFIX "d3d11.preferredMaxFrameRate="

static int valid_dxmt_config(const char *v, size_t len) {
if (len == 0 || v[0] == ';' || v[len - 1] == ';') return 0;
const size_t factor_pre = sizeof(FACTOR_PREFIX) - 1;
const size_t fps_pre = sizeof(FPS_PREFIX) - 1;
int seen_factor = 0, seen_fps = 0;
size_t at = 0;
while (at < len) {
const char *semi = memchr(v + at, ';', len - at);
size_t n = semi ? (size_t)(semi - (v + at)) : len - at;
if (n == 0) return 0;
const char *clause = v + at;

if (n > factor_pre && memcmp(clause, FACTOR_PREFIX, factor_pre) == 0) {
if (seen_factor || !is_one_of(g_factors, clause + factor_pre, n - factor_pre)) return 0;
seen_factor = 1;
} else if (n > fps_pre && memcmp(clause, FPS_PREFIX, fps_pre) == 0) {
if (seen_fps || !is_one_of(g_fps, clause + fps_pre, n - fps_pre)) return 0;
seen_fps = 1;
} else {
return 0;
}
at += n + (semi ? 1 : 0);
}
return seen_factor || seen_fps;
}

static int valid_value(size_t i, uint32_t runner, const char *v, size_t len) {
if (is(g_settings[i].key, strlen(g_settings[i].key), "DXMT_CONFIG"))
return valid_dxmt_config(v, len);
size_t pre = strlen(g_settings[i].prefix);
if (len <= pre || memcmp(v, g_settings[i].prefix, pre) != 0) return 0;
return is_one_of(g_settings[i].values, v + pre, len - pre)
Expand Down
29 changes: 26 additions & 3 deletions dylib/feats/webpatch.c
Original file line number Diff line number Diff line change
Expand Up @@ -1219,6 +1219,7 @@ static int out_expand(np_out_t *o, const char *replace, const np_cap_t *caps) {
#define NP_CX_BARREL_READY(B) "[" NP_CX_BARREL(NP_CX_BARREL_JS) "].every(n=>" B "[n])"

#define NP_CX_FACTOR_ENTRY "e=>/^[ \\t\\r]*d3d11\\.metalSpatialUpscaleFactor[ \\t\\r]*=/.test(e)"
#define NP_CX_FPS_ENTRY "e=>/^[ \\t\\r]*d3d11\\.preferredMaxFrameRate[ \\t\\r]*=/.test(e)"

#define NP_CX_DEFAULTS "(globalThis.MSCXDefaultsData||(globalThis.MSCXDefaultsData=" NP_DEFAULTS "))"
#define NP_CX_BUILDS "(globalThis.MSCXBuilds||(globalThis.MSCXBuilds=" NP_BUILDS "))"
Expand Down Expand Up @@ -1343,23 +1344,35 @@ static int out_expand(np_out_t *o, const char *replace, const np_cap_t *caps) {
"dx=\"\"===b||\"dxmt\"===b," \
"sw=\"1\"===G(\"DXMT_METALFX_SPATIAL_SWAPCHAIN\")," \
"F=\"d3d11.metalSpatialUpscaleFactor=\",fk=" NP_CX_FACTOR_ENTRY "," \
"FP=\"d3d11.preferredMaxFrameRate=\",fpk=" NP_CX_FPS_ENTRY "," \
"fe=readSaved(\"DXMT_CONFIG\").split(\";\").filter(e=>e.trim())," \
"fx=v=>fe.filter(e=>!fk(e)).concat(v?[F+v]:[]).join(\";\")," \
"fxFps=v=>fe.filter(e=>!fpk(e)).concat(v?[FP+v]:[]).join(\";\")," \
"pf=c=>{const v=(c.split(\";\").filter(fk).pop()||\"=2\").replace(/^[^=]*=/,\"\").replace(/\"/g,\"\").trim().split(/\\s/)[0];" \
"return Math.max(isNaN(+v)?2:+v,1)}," \
"pfp=c=>{const v=(c.split(\";\").filter(fpk).pop()||\"\").replace(/^[^=]*=/,\"\").replace(/\"/g,\"\").trim().split(/\\s/)[0];" \
"return 0===+v?\"\":v}," \
"fn=pf(G(\"DXMT_CONFIG\"))," \
"fnFps=pfp(G(\"DXMT_CONFIG\"))," \
"U=[{data:\"\",label:npL(\"Off\",\"关闭\",\"Disattivato\")}," \
"{data:\"1.5\",label:\"1.5x\"}," \
"{data:\"1.72\",label:\"1.72x\"}," \
"{data:\"2.0\",label:\"2x\"}," \
"{data:\"3.0\",label:\"3x\"}]," \
"FPS=[{data:\"\",label:npL(\"Off\",\"关闭\",\"Disattivato\")}," \
"{data:\"30\",label:\"30 FPS\"}," \
"{data:\"40\",label:\"40 FPS\"}," \
"{data:\"60\",label:\"60 FPS\"}," \
"{data:\"120\",label:\"120 FPS\"}," \
"{data:\"144\",label:\"144 FPS\"}]," \
"B=[{data:\"\",label:npL(\"Automatic\",\"自动\",\"Automatico\")}," \
"{data:\"d3dmetal\",label:\"D3DMetal\"}," \
"{data:\"dxmt\",label:\"DXMT\"}," \
"{data:\"dxvk\",label:\"DXVK\"}," \
"{data:\"wined3d\",label:\"WineD3D\"}].filter(x=>!fex||x.data!==\"d3dmetal\")," \
"pick=f=>\"1\"===f(\"DXMT_METALFX_SPATIAL_SWAPCHAIN\")?(U.find(u=>u.data&&+u.data===pf(f(\"DXMT_CONFIG\")||\"\"))" \
"||{data:String(pf(f(\"DXMT_CONFIG\")||\"\"))}).data:\"\";" \
"||{data:String(pf(f(\"DXMT_CONFIG\")||\"\"))}).data:\"\"," \
"pickFps=f=>pfp(f(\"DXMT_CONFIG\")||\"\");" \
/* Without our run script behind it a row can only be what a launch option can carry,
so one asking for a staged DLL or an override is not offered, and neither is one
with nothing to deliver. */ \
Expand Down Expand Up @@ -1392,6 +1405,14 @@ static int out_expand(np_out_t *o, const char *replace, const np_cap_t *caps) {
"onChange:v=>s(dflt&&v.data===pick(k=>dflt(k)||\"\")" \
"?[[\"DXMT_METALFX_SPATIAL_SWAPCHAIN\",\"\"],[\"DXMT_CONFIG\",fx(\"\")]]" \
":[[\"DXMT_METALFX_SPATIAL_SWAPCHAIN\",v.data?\"1\":\"0\"],[\"DXMT_CONFIG\",fx(v.data)]])})},\"usf\")," \
"ours&&dx&&(0," RT ".jsx)(" BARREL ".XY," \
"{label:npL(\"DXMT Frame Rate Limit\"," \
"\"DXMT Frame Rate Limit\",\"DXMT Frame Rate Limit\")," \
"children:(0," RT ".jsx)(" BARREL ".m,{rgOptions:FPS," \
"selectedOption:(FPS.find(u=>u.data===fnFps)||{data:fnFps}).data," \
"onChange:v=>s(dflt&&v.data===pickFps(k=>dflt(k)||\"\")" \
"?[[\"DXMT_CONFIG\",fxFps(\"\")]]" \
":[[\"DXMT_CONFIG\",fxFps(v.data||(dflt?\"0\":\"\"))]])})},\"fps\")," \
"ours&&(0," RT ".jsxs)(" BARREL ".XY,{label:npL(\"Controllers\",\"控制器\",\"Controller\"),children:[" \
"T(\"NOTPROTON_RAW_CONTROLLERS\",npL(\"Let Games Read Controllers Directly (May break Steam Input. Not recommended)\"," \
"\"允许游戏直接读取控制器(可能导致 Steam Input 失效,不推荐)\"," \
Expand Down Expand Up @@ -1428,9 +1449,11 @@ static int out_expand(np_out_t *o, const char *replace, const np_cap_t *caps) {
"fex=" NP_FEX_TOOLS ".indexOf(tn)>=0," \
"L=MSCXLaunch(o,t.unAppID),g=L.g,has=k=>L.k.includes(k)," \
"lost=globalThis.MSCXDefaultsLost,DF=lost?{}:(" NP_CX_BUILDS ".find(x=>x.id===tn)||{}).override||" NP_CX_DEFAULTS "[fex?\"fex\":\"rosetta\"]||{}," \
"fk=" NP_CX_FACTOR_ENTRY "," \
"fk=" NP_CX_FACTOR_ENTRY ",fpk=" NP_CX_FPS_ENTRY "," \
"own=g(\"DXMT_CONFIG\").split(\";\").filter(e=>e.trim())," \
"G=k=>\"DXMT_CONFIG\"===k?(own.some(fk)?g(k):DF[k]||\"\"):has(k)?g(k):DF[k]||\"\";" \
"dfe=(DF[\"DXMT_CONFIG\"]||\"\").split(\";\").filter(e=>e.trim())," \
"mergedDxmt=[own.filter(fk).pop()||dfe.filter(fk).pop(),own.filter(fpk).pop()||dfe.filter(fpk).pop()].concat(own.filter(e=>!fk(e)&&!fpk(e))).filter(Boolean).join(\";\")," \
"G=k=>\"DXMT_CONFIG\"===k?mergedDxmt:has(k)?g(k):DF[k]||\"\";" \
"if(t.unAppID<2147483648&&(t.vecPlatforms||[]).indexOf(\"osx\")>=0" \
"&&!t.strCompatToolName)return null;" \
"return MSCXPanel({fex,get:G,saved:g,set:L.s,clearUnused:1,dev:1,tool:tn," \
Expand Down
14 changes: 13 additions & 1 deletion dylib/tests/defaults-check.c
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,23 @@ static void valid_cases(void) {
"a row takes only 0 or 1");
check(valid(NP_DEFAULTS_ROSETTA, "DXMT_CONFIG=d3d11.metalSpatialUpscaleFactor=1.72"),
"DXMT_CONFIG takes a factor");
check(valid(NP_DEFAULTS_ROSETTA, "DXMT_CONFIG=d3d11.preferredMaxFrameRate=60") &&
valid(NP_DEFAULTS_ROSETTA, "DXMT_CONFIG=d3d11.preferredMaxFrameRate=144"),
"DXMT_CONFIG takes a frame rate");
check(valid(NP_DEFAULTS_ROSETTA, "DXMT_CONFIG=d3d11.metalSpatialUpscaleFactor=1.5;d3d11.preferredMaxFrameRate=60") &&
valid(NP_DEFAULTS_ROSETTA, "DXMT_CONFIG=d3d11.preferredMaxFrameRate=120;d3d11.metalSpatialUpscaleFactor=2.0"),
"DXMT_CONFIG takes both factor and frame rate in either order");
check(!valid(NP_DEFAULTS_ROSETTA, "DXMT_CONFIG=d3d11.metalSpatialUpscaleFactor=") &&
!valid(NP_DEFAULTS_ROSETTA, "DXMT_CONFIG=d3d11.metalSpatialUpscaleFactor=.") &&
!valid(NP_DEFAULTS_ROSETTA, "DXMT_CONFIG=d3d11.metalSpatialUpscaleFactor=1;x=1") &&
!valid(NP_DEFAULTS_ROSETTA, "DXMT_CONFIG=d3d11.metalSpatialUpscaleFactor=1234567") &&
!valid(NP_DEFAULTS_ROSETTA, "DXMT_CONFIG=d3d11.preferredMaxFrameRate=60"),
!valid(NP_DEFAULTS_ROSETTA, "DXMT_CONFIG=d3d11.preferredMaxFrameRate=") &&
!valid(NP_DEFAULTS_ROSETTA, "DXMT_CONFIG=d3d11.preferredMaxFrameRate=0") &&
!valid(NP_DEFAULTS_ROSETTA, "DXMT_CONFIG=d3d11.preferredMaxFrameRate=15") &&
!valid(NP_DEFAULTS_ROSETTA, "DXMT_CONFIG=d3d11.preferredMaxFrameRate=60;d3d11.preferredMaxFrameRate=30") &&
!valid(NP_DEFAULTS_ROSETTA, "DXMT_CONFIG=d3d11.metalSpatialUpscaleFactor=1.5;d3d11.metalSpatialUpscaleFactor=2.0") &&
!valid(NP_DEFAULTS_ROSETTA, "DXMT_CONFIG=;d3d11.preferredMaxFrameRate=60") &&
!valid(NP_DEFAULTS_ROSETTA, "DXMT_CONFIG=d3d11.preferredMaxFrameRate=60;"),
"DXMT_CONFIG takes nothing else");
check(!valid(NP_DEFAULTS_ROSETTA, "WINEDEBUG=+all") && !valid(2, "WINEMSYNC=1"),
"other keys and runners are refused");
Expand Down
26 changes: 24 additions & 2 deletions dylib/tests/envcheck.sh
Original file line number Diff line number Diff line change
Expand Up @@ -232,10 +232,32 @@ eval "$defaults"
is "the game's factor wins" "a=1; d3d11.metalSpatialUpscaleFactor = 3.0" "$DXMT_CONFIG"
clear
cp "$np_support/defaults-rosetta" "$work/rosetta.keep"
printf 'DXMT_CONFIG=d3d11.metalSpatialUpscaleFactor=.\nDXMT_CONFIG=d3d11.metalSpatialUpscaleFactor=1234567\nDXMT_CONFIG=d3d11.metalSpatialUpscaleFactor=\n' \
printf 'DXMT_CONFIG=d3d11.preferredMaxFrameRate=60\n' > "$np_support/defaults-rosetta"
eval "$defaults"
is "a defaulted frame rate is the DXMT_CONFIG" "d3d11.preferredMaxFrameRate=60" "${DXMT_CONFIG-}"
clear
export DXMT_CONFIG="d3d11.preferredMaxFrameRate=120"
eval "$defaults"
is "the game's frame rate wins over the default" "d3d11.preferredMaxFrameRate=120" "$DXMT_CONFIG"
clear
export DXMT_CONFIG="d3d11.preferredMaxFrameRate=0"
eval "$defaults"
is "a game that turned the limit off keeps it off" "d3d11.preferredMaxFrameRate=0" "$DXMT_CONFIG"
clear
printf 'DXMT_CONFIG=d3d11.metalSpatialUpscaleFactor=2.0;d3d11.preferredMaxFrameRate=60\n' > "$np_support/defaults-rosetta"
eval "$defaults"
is "both factor and frame rate are applied from combined defaults" \
"d3d11.metalSpatialUpscaleFactor=2.0;d3d11.preferredMaxFrameRate=60" "$DXMT_CONFIG"
clear
export DXMT_CONFIG="d3d11.preferredMaxFrameRate=30"
eval "$defaults"
is "the game's frame rate wins and default factor joins" \
"d3d11.preferredMaxFrameRate=30;d3d11.metalSpatialUpscaleFactor=2.0" "$DXMT_CONFIG"
clear
printf 'DXMT_CONFIG=d3d11.metalSpatialUpscaleFactor=.\nDXMT_CONFIG=d3d11.metalSpatialUpscaleFactor=1234567\nDXMT_CONFIG=d3d11.metalSpatialUpscaleFactor=\nDXMT_CONFIG=d3d11.preferredMaxFrameRate=abc\nDXMT_CONFIG=d3d11.preferredMaxFrameRate=0\n' \
> "$np_support/defaults-rosetta"
eval "$defaults"
is "factors the Settings page would refuse are skipped" "unset" "${DXMT_CONFIG-unset}"
is "factors and frame rates the Settings page would refuse are skipped" "unset" "${DXMT_CONFIG-unset}"
mv "$work/rosetta.keep" "$np_support/defaults-rosetta"

clear
Expand Down
42 changes: 36 additions & 6 deletions dylib/tests/panel-behavior/behavior.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,16 +128,16 @@ for (const form of Object.keys(FORMS)) {
}

let r = nodes('');
t.ok(toggles(r).length === 7 && sections(r).length === 4, 'automatic shows seven toggles in four sections');
t.ok(r.filter(x => x.type === 'Dropdown').length === 2, 'two dropdowns');
t.ok(toggles(r).length === 7 && sections(r).length === 5, 'automatic shows seven toggles in five sections');
t.ok(r.filter(x => x.type === 'Dropdown').length === 3, 'three dropdowns');
t.ok(!('label' in r.find(x => x.type === 'Dropdown').props), 'the backend dropdown carries no label column');

r = nodes('CX_GRAPHICS_BACKEND=dxmt');
t.ok(toggles(r).filter(l => l === 'DLSS').length === 1 && sections(r).includes('MetalFX'),
'dxmt keeps one DLSS and keeps MetalFX');
t.ok(toggles(r).filter(l => l === 'DLSS').length === 1 && sections(r).includes('MetalFX') && sections(r).includes('DXMT'),
'dxmt keeps one DLSS and keeps MetalFX and DXMT sections');
r = nodes('CX_GRAPHICS_BACKEND=d3dmetal');
t.ok(toggles(r).filter(l => l === 'DLSS').length === 1 && !sections(r).includes('MetalFX'),
'd3dmetal keeps one DLSS and hides MetalFX');
t.ok(toggles(r).filter(l => l === 'DLSS').length === 1 && !sections(r).includes('MetalFX') && !sections(r).includes('DXMT'),
'd3dmetal keeps one DLSS and hides MetalFX and DXMT sections');
// Both flags are labeled DLSS, so a backend showing its own and its neighbor's
// would put the same word on two rows.
for (const b of ['', 'd3dmetal', 'dxmt', 'dxvk', 'wined3d']) {
Expand Down Expand Up @@ -165,6 +165,36 @@ for (const form of Object.keys(FORMS)) {
nodes('CX_GRAPHICS_BACKEND=dxmt').filter(x => x.type === 'Dropdown')[1].props.onChange({ data: '2.0' });
t.ok(last().includes('metalSpatialUpscaleFactor=2.0'), 'the upscaler writes its factor');

nodes('CX_GRAPHICS_BACKEND=dxmt').filter(x => x.type === 'Dropdown')[2].props.onChange({ data: '60' });
t.ok(last().includes('preferredMaxFrameRate=60'), 'the frame rate limiter writes its FPS');

nodes('CX_GRAPHICS_BACKEND=dxmt DXMT_CONFIG="d3d11.metalSpatialUpscaleFactor=2.0"').filter(x => x.type === 'Dropdown')[2].props.onChange({ data: '120' });
t.ok(last().includes('metalSpatialUpscaleFactor=2.0') && last().includes('preferredMaxFrameRate=120'),
'the frame rate limiter preserves upscale factor');

nodes('CX_GRAPHICS_BACKEND=dxmt DXMT_CONFIG="d3d11.metalSpatialUpscaleFactor=2.0;d3d11.preferredMaxFrameRate=60"').filter(x => x.type === 'Dropdown')[2].props.onChange({ data: '' });
t.ok(last().includes('metalSpatialUpscaleFactor=2.0') && !last().includes('preferredMaxFrameRate'),
'turning off frame rate limiter keeps upscale factor');

{
const capped = { document: { documentElement: { lang: 'en' } },
MSCXDefaultsData: { rosetta: { DXMT_CONFIG: 'd3d11.preferredMaxFrameRate=60' }, fex: {} } };
const { render: PC, written: cw } = panel(emit, form, capped);
const fps = opts => walk(PC({ details: details(opts + ' %command%') }))
.filter(x => x.type === 'Dropdown')[2];
t.ok(fps('CX_GRAPHICS_BACKEND=dxmt').props.selectedOption === '60', 'a game shows the default frame rate');
fps('CX_GRAPHICS_BACKEND=dxmt').props.onChange({ data: '' });
const off = cw[cw.length - 1].opts;
t.ok(off.includes('preferredMaxFrameRate=0'), `turning the limit off writes 0 over the default (${off})`);
t.ok(fps(off.replace(/ ?%command%$/, '')).props.selectedOption === '', 'and reads back as off');
fps('CX_GRAPHICS_BACKEND=dxmt DXMT_CONFIG="d3d11.preferredMaxFrameRate=0"').props.onChange({ data: '60' });
t.ok(!cw[cw.length - 1].opts.includes('DXMT_CONFIG'), 'picking the default again drops the clause');
}

const fpsOptions = nodes('CX_GRAPHICS_BACKEND=dxmt').filter(x => x.type === 'Dropdown')[2]
.props.rgOptions.map(o => o.data);
t.ok(fpsOptions.join(',') === ',30,40,60,120,144', `the fps options are available (${fpsOptions.join(',')})`);

// The section names the factor, so a label promising a resolution to go and set in
// the game is the wording this replaced.
const upscaler = nodes('CX_GRAPHICS_BACKEND=dxmt').find(x => x.type === 'Section' &&
Expand Down
Loading