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
3 changes: 3 additions & 0 deletions .github/workflows/app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ jobs:
make -s compatcheck
make -s compatsvc-check
make -s prefixstate-check
make -s defaults-check
make -s compatpage-check
make -s defaults-drift
make -s shortcutcheck
make -s launch-shell
make -s envcheck
Expand Down
26 changes: 25 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ SRCS := \
dylib/feats/compat.c \
dylib/feats/webui.c \
dylib/feats/compatsvc.c \
dylib/feats/defaults.c \
dylib/feats/compatpage.c \
dylib/feats/prefixstate.c \
dylib/feats/usershortcuts.c \
dylib/feats/webpatch.c \
Expand Down Expand Up @@ -71,7 +73,7 @@ DEPS := $(OBJS:.o=.d)
tests-list overlay-shim overlay-shim-install overlay-shim-tests \
overlay-shim-bench iconmaker icon \
appinfo helpers-install ntdll-resolve bridge runcheck compatcheck \
compatsvc-check prefixstate-check shortcutcheck scriptcheck envcheck buildcheck settingscheck routecheck bridgecheck gamedrivecheck launchercheck launch-shell activatecheck FORCE
compatsvc-check prefixstate-check defaults-check compatpage-check defaults-drift shortcutcheck scriptcheck envcheck buildcheck settingscheck routecheck bridgecheck gamedrivecheck launchercheck launch-shell activatecheck FORCE

APP_PAYLOAD := app/Sources/NotProtonApp/Resources/payload

Expand Down Expand Up @@ -180,6 +182,27 @@ compatsvc-check:
$(TEST_CC) -o $(COMPATSVC_CHECK) dylib/tests/compatsvc-check.c && \
$(COMPATSVC_CHECK)

DEFAULTS_CHECK := $(OUT_DIR)/defaults-check

$(DEFAULTS_CHECK): dylib/tests/defaults-check.c dylib/feats/defaults.c dylib/feats/defaults.h \
dylib/util/buildid.h dylib/util/file.c dylib/util/file.h
@mkdir -p $(dir $@)
$(TEST_CC) -o $@ dylib/tests/defaults-check.c dylib/util/file.c

defaults-check: $(DEFAULTS_CHECK)
@$(DEFAULTS_CHECK)

COMPATPAGE_CHECK := $(OUT_DIR)/compatpage-check

compatpage-check:
@if [ ! -f dylib/tests/compatpage-check.c ]; then $(call SKIP,compatpage-check,dylib/tests/compatpage-check.c); exit 0; fi; \
mkdir -p $(OUT_DIR) && \
$(TEST_CC) -o $(COMPATPAGE_CHECK) dylib/tests/compatpage-check.c dylib/feats/defaults.c dylib/util/file.c && \
$(COMPATPAGE_CHECK)

defaults-drift: $(DEFAULTS_CHECK)
@sh dylib/tests/defaults-drift.sh $(DEFAULTS_CHECK)

SHORTCUT_BUNDLE_CHECK := $(OUT_DIR)/shortcut-bundle-check
SHORTCUTCHECK := $(OUT_DIR)/shortcutcheck

Expand Down Expand Up @@ -387,6 +410,7 @@ panel-behavior:
node $(PANEL_TESTS)/localization.js $(OUT_DIR)/panel-emit && \
node $(PANEL_TESTS)/fex.js $(OUT_DIR)/panel-emit && \
node $(PANEL_TESTS)/prefix-row.js $(OUT_DIR)/panel-emit && \
node $(PANEL_TESTS)/defaults.js $(OUT_DIR)/panel-emit && \
echo "==> panel behavior: renders as expected, no stale arguments"

CX_ROOT ?= /Applications/CrossOver Preview.app
Expand Down
4 changes: 2 additions & 2 deletions app/Tests/NotProtonAppTests/ToggleAgreementTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ struct ToggleAgreementTests {
static func toggleNames(in webpatch: String) throws -> Set<String> {
var names: Set<String> = []
for pattern in [
#"g\(\\"([A-Z0-9_]+)\\"\)"#,
#"T\(\[\\"([A-Z0-9_]+)\\""#,
#"[gG]\(\\"([A-Z0-9_]+)\\"\)"#,
#"[TD]\(\[?\\"([A-Z0-9_]+)\\""#,
#"\[\\"([A-Z0-9_]+)\\","#,
] {
for match in webpatch.matches(of: try Regex(pattern)) {
Expand Down
30 changes: 28 additions & 2 deletions dylib/feats/compat.c
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ void np_compat_export_tools_path(void) {

typedef struct {
char name[64];
char build[64];
char build[NP_BUILD_ID_MAX + 1];
char flavor[16];
char display[96];
char dir[640];
Expand Down Expand Up @@ -383,7 +383,7 @@ static int parse_tool_line(char *line, tool_entry_t *out) {
field[n++] = tok;
if (n != 4) return 0;

if (!is_tool_name(field[0]) || !is_token(field[1], ".-")
if (!is_tool_name(field[0]) || !np_is_build_id(field[1])
|| (strcmp(field[2], "fex") != 0 && strcmp(field[2], "rosetta") != 0)
|| !is_display(field[3]))
return 0;
Expand Down Expand Up @@ -896,6 +896,32 @@ size_t np_compat_fex_tools(const char **names, size_t max) {
return n;
}

static np_compat_tool_info_t tool_info(int i) {
return (np_compat_tool_info_t){ g_tools[i].name, g_tools[i].build, g_tools[i].display,
strcmp(g_tools[i].flavor, "fex") == 0 };
}

size_t np_compat_tools(np_compat_tool_info_t *out, size_t max) {
load_tool_list_once();
size_t n = 0;
for (int i = 0; i < g_tool_count && n < max; i++, n++)
out[n] = tool_info(i);
if ((size_t)g_tool_count > max)
NP_WARN("np_compat_tools: more than %zu tools, %s and later are left out", max,
g_tools[max].name);
return n;
}

bool np_compat_tool_by_name(const char *name, size_t n, np_compat_tool_info_t *out) {
load_tool_list_once();
for (int i = 0; name && i < g_tool_count; i++) {
if (strlen(g_tools[i].name) != n || memcmp(g_tools[i].name, name, n) != 0) continue;
*out = tool_info(i);
return true;
}
return false;
}

void *np_compat_registered_tool(void *compat_mgr) {
if (!compat_mgr)
return NULL;
Expand Down
13 changes: 13 additions & 0 deletions dylib/feats/compat.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#include <stddef.h>
#include <stdint.h>

#include "../util/buildid.h"

// Manager tool array: base pointer at TOOL_ARRAY_OFF, count at TOOL_COUNT_OFF,
// fixed per-entry stride from np_compat_tool_stride.
#define COMPAT_MANAGER_TOOL_ARRAY_OFF 0x320
Expand Down Expand Up @@ -130,6 +132,17 @@ const char *np_compat_fallback_tool_name(void);
// Names of the listed tools whose flavor is fex, up to max. Returns how many it wrote.
size_t np_compat_fex_tools(const char **names, size_t max);

typedef struct {
const char *name;
const char *build;
const char *display;
bool fex;
} np_compat_tool_info_t;

size_t np_compat_tools(np_compat_tool_info_t *out, size_t max);

bool np_compat_tool_by_name(const char *name, size_t n, np_compat_tool_info_t *out);

uint32_t np_compat_manager_tools_max(void);

// Absolute path to the directory of the first tool in the app's list, or NULL.
Expand Down
51 changes: 50 additions & 1 deletion dylib/feats/compat_run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,50 @@ if [ "$np_flavor" = rosetta ] || [ ! -x "$WINELOADER" ] || [ ! -x "$WINESERVER"
fi
export WINELOADER WINESERVER

np_from_defaults() {
case " $np_defaulted " in *" $1="*) return 0 ;; esac
return 1
}
np_defaulted=""
case "${np_flavor-}" in
fex) np_defaults_runner=fex ;;
rosetta) np_defaults_runner=rosetta ;;
*) np_defaults_runner=rosetta; [ "${wine_unix##*/}" != aarch64-unix ] || np_defaults_runner=fex ;;
esac
np_defaults_file="$np_support/defaults-$np_defaults_runner"
if [ -n "${np_build-}" ] && [ -f "$np_support/defaults-override-$np_build-$np_defaults_runner" ]; then
np_defaults_file="$np_support/defaults-override-$np_build-$np_defaults_runner"
fi
if [ "$verb" = waitforexitandrun ] && [ -f "$np_defaults_file" ] && [ -r "$np_defaults_file" ]; then
while IFS= read -r line || [ -n "$line" ]; do
key=${line%%=*}
value=${line#*=}
case "$line" in
CX_GRAPHICS_BACKEND=dxmt | CX_GRAPHICS_BACKEND=dxvk | CX_GRAPHICS_BACKEND=wined3d) ;;
CX_GRAPHICS_BACKEND=d3dmetal | D3DM_ENABLE_METALFX=[01] | ROSETTA_ADVERTISE_AVX=[01])
[ "${wine_unix##*/}" != aarch64-unix ] || continue ;;
FEX_X87REDUCEDPRECISION=[01])
[ "${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=d3d11.metalSpatialUpscaleFactor=*)
case "${value#*=}" 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
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
export "${line?}"
np_defaulted="$np_defaulted${np_defaulted:+ }$line"
done < "$np_defaults_file"
fi
[ "${CX_GRAPHICS_BACKEND-}" != auto ] || unset -v CX_GRAPHICS_BACKEND

np_dropped=""
if [ "${wine_unix##*/}" = aarch64-unix ]; then
if [ "${CX_GRAPHICS_BACKEND:-}" = d3dmetal ]; then
Expand All @@ -184,10 +228,12 @@ if [ "${wine_unix##*/}" = aarch64-unix ]; then
done
fex_from=environment
[ -n "${FEX_X87REDUCEDPRECISION+x}" ] || fex_from=default
if np_from_defaults FEX_X87REDUCEDPRECISION; then fex_from=${np_defaults_file##*/}; fi
export FEX_X87REDUCEDPRECISION="${FEX_X87REDUCEDPRECISION-1}"
else
avx_from=environment
[ -n "$ROSETTA_ADVERTISE_AVX" ] || avx_from=default
if np_from_defaults ROSETTA_ADVERTISE_AVX; then avx_from=${np_defaults_file##*/}; fi
export ROSETTA_ADVERTISE_AVX="${ROSETTA_ADVERTISE_AVX:-1}"
fi

Expand Down Expand Up @@ -217,6 +263,7 @@ fi
echo "STEAM_COMPAT_APP_ID=$STEAM_COMPAT_APP_ID"
echo "-- steam env passed through --"
env | grep -iE '^(Steam|SDL_)' | sort
[ -z "$np_defaulted" ] || echo "from ${np_defaults_file##*/}: $np_defaulted"
[ -z "$np_dropped" ] || echo "ignored on arm64 Wine: $np_dropped"
} >> "$log" 2>&1 || true

Expand Down Expand Up @@ -1370,7 +1417,9 @@ if [ -n "$STEAM_COMPAT_DATA_PATH" ]; then
stage_step="prefix lock"
prepare_prefix_directory
msync_from=environment
if [ -z "$WINEMSYNC" ] && [ -r "$STEAM_COMPAT_DATA_PATH/notproton-msync" ]; then
if np_from_defaults WINEMSYNC; then msync_from=${np_defaults_file##*/}; fi
if [ -z "$WINEMSYNC" ] && [ "$verb" != waitforexitandrun ] \
&& [ -r "$STEAM_COMPAT_DATA_PATH/notproton-msync" ]; then
WINEMSYNC=$(tr -d ' \t\n' \
< "$STEAM_COMPAT_DATA_PATH/notproton-msync" 2>/dev/null || true)
msync_from=carried-over
Expand Down
41 changes: 41 additions & 0 deletions dylib/feats/compatpage.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include "compatpage.h"
#include "compat.h"
#include "defaults.h"
#include "webpatch.h"
#include "../util/log.h"

#include <stdlib.h>

static void publish_defaults(void) {
char rosetta[NP_DEFAULTS_TEXT_MAX + 1], fex[NP_DEFAULTS_TEXT_MAX + 1];
int read = np_defaults_read(NP_DEFAULTS_ROSETTA, rosetta, sizeof rosetta);
read &= np_defaults_read(NP_DEFAULTS_FEX, fex, sizeof fex);

np_compat_tool_info_t tools[NP_WEBPATCH_BUILDS_MAX];
size_t count = np_compat_tools(tools, NP_WEBPATCH_BUILDS_MAX);
char (*own)[NP_DEFAULTS_TEXT_MAX + 1] = count ? malloc(count * sizeof *own) : NULL;
np_webpatch_build_t builds[NP_WEBPATCH_BUILDS_MAX];
size_t n = 0;
if (count && !own) read = 0;
for (size_t i = 0; own && i < count; i++) {
uint32_t runner = tools[i].fex ? NP_DEFAULTS_FEX : NP_DEFAULTS_ROSETTA;
int has = np_defaults_read_build(tools[i].build, runner, own[i], sizeof own[i]);
if (has < 0) {
NP_WARN("webpatch: %s's own settings cannot be read, so the Settings page leaves it out", tools[i].name);
continue;
}
builds[n++] = (np_webpatch_build_t){ tools[i].name, tools[i].build, tools[i].display,
tools[i].fex, has > 0 ? own[i] : NULL };
}
if (read) np_webpatch_set_defaults(rosetta, fex);
else np_webpatch_set_defaults(NULL, NULL);
np_webpatch_set_builds(builds, n);
free(own);
}

void np_compatpage_publish(const uint8_t *src, size_t len) {
np_webpatch_set_fallback_tool(np_compat_fallback_tool_name());
const char *fex[NP_WEBPATCH_FEX_TOOLS_MAX];
np_webpatch_set_fex_tools(fex, np_compat_fex_tools(fex, NP_WEBPATCH_FEX_TOOLS_MAX));
if (np_webpatch_has_shape(src, len)) publish_defaults();
}
10 changes: 10 additions & 0 deletions dylib/feats/compatpage.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Compatibility page inputs
#ifndef NOTPROTON_FEATS_COMPATPAGE_H
#define NOTPROTON_FEATS_COMPATPAGE_H

#include <stddef.h>
#include <stdint.h>

void np_compatpage_publish(const uint8_t *src, size_t len);

#endif // NOTPROTON_FEATS_COMPATPAGE_H
50 changes: 50 additions & 0 deletions dylib/feats/compatsvc.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include "compatsvc.h"
#include "compat.h"
#include "defaults.h"
#include "prefixstate.h"
#include "webui.h"
#include "../resolver/anchor.h"
Expand All @@ -30,6 +31,9 @@
#define ROUTE_REBUILD "NotProton.RebuildPrefix#1"
#define ROUTE_BACKUP_REBUILD "NotProton.BackupAndRebuildPrefix#1"
#define ROUTE_OPEN_LOG "NotProton.OpenPrefixLog#1"
#define ROUTE_SET_DEFAULTS "NotProton.SetDefaults#1"
#define ROUTE_SET_TOOL_DEFAULTS "NotProton.SetToolDefaults#1"
#define ROUTE_APPLY_TOOL_DEFAULTS "NotProton.ApplyToolDefaults#1"

// Mangled RTTI names of the message types. A client that builds the CompatManager
// page compiles these without registering the service, so the vtables are there to
Expand Down Expand Up @@ -481,6 +485,46 @@ static int open_prefix_log(uintptr_t request, uintptr_t response) {
return answer_json(response, appid, opened ? "{\"opened\":true}" : "{\"error\":\"log\"}");
}

static int set_defaults(uintptr_t request, uintptr_t response) {
uint32_t has_bits = *(const uint32_t *)(request + REQ_HAS_BITS_OFF);
uint32_t runner = (has_bits & SPECIFY_HAS_APPID)
? *(const uint32_t *)(request + SPECIFY_APPID_OFF) : 0;
(void)response;
return np_defaults_save(runner, string_field(request + SPECIFY_TOOL_NAME_OFF))
? RESULT_OK : RESULT_FAIL;
}

static int tool_build(const char *tool, size_t n, char *build, size_t size, uint32_t *runner) {
np_compat_tool_info_t info;
if (np_compat_tool_by_name(tool, n, &info) && info.build && strlen(info.build) < size) {
snprintf(build, size, "%s", info.build);
*runner = info.fex ? NP_DEFAULTS_FEX : NP_DEFAULTS_ROSETTA;
return 1;
}
NP_ERR("compatsvc: %.*s is not an installed CrossOver tool", (int)(n > 128 ? 128 : n), tool);
return 0;
}

static int set_tool_defaults(uintptr_t request, uintptr_t response) {
const char *text = string_field(request + SPECIFY_TOOL_NAME_OFF), *nl = strchr(text, '\n');
char build[NP_BUILD_ID_MAX + 1];
uint32_t runner;
(void)response;
if (!tool_build(text, nl ? (size_t)(nl - text) : strlen(text), build, sizeof build, &runner))
return RESULT_FAIL;
return np_defaults_save_build(build, runner, nl ? nl + 1 : "") ? RESULT_OK : RESULT_FAIL;
}

static int apply_tool_defaults(uintptr_t request, uintptr_t response) {
const char *tool = string_field(request + SPECIFY_TOOL_NAME_OFF);
char build[NP_BUILD_ID_MAX + 1];
uint32_t runner;
(void)response;
if (!tool_build(tool, strlen(tool), build, sizeof build, &runner)) return RESULT_FAIL;
int done = np_defaults_apply_build(build, runner);
return done > 0 ? RESULT_OK : done < 0 ? NP_COMPATSVC_PARTLY_APPLIED : RESULT_FAIL;
}

static uintptr_t g_state_changed;
static int g_routes = NP_COMPATSVC_UNTRIED;

Expand Down Expand Up @@ -523,6 +567,12 @@ void np_compatsvc_register(const struct mach_header_64 *mh, intptr_t slide,

np_webui_register_route(ROUTE_SPECIFY_COMPAT_TOOL, specify_request,
specify_response, specify_compat_tool);
if (!np_webui_register_route(ROUTE_SET_DEFAULTS, specify_request, specify_response, set_defaults)
|| !np_webui_register_route(ROUTE_SET_TOOL_DEFAULTS, specify_request, specify_response,
set_tool_defaults)
|| !np_webui_register_route(ROUTE_APPLY_TOOL_DEFAULTS, specify_request, specify_response,
apply_tool_defaults))
NP_WARN("compatsvc: the Settings page cannot save default compatibility settings");

g_routes = NP_COMPATSVC_READY;

Expand Down
2 changes: 2 additions & 0 deletions dylib/feats/compatsvc.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ int np_compatsvc_routes(void);
// Sentinel name for "no tool".
#define NP_COMPAT_TOOL_NONE "notproton.none"

#define NP_COMPATSVC_PARTLY_APPLIED 129

// Tells the page the mapping changed.
void np_compatsvc_state_changed(void);

Expand Down
Loading
Loading