Compare commits

...
Author SHA1 Message Date
xXJsonDeruloXx 67bf8f1568 feat: add opt-in offline launch for Easy Anti-Cheat games 2026-10-17 17:44:36 -04:00
4 changed files with 47 additions and 0 deletions

No files matched your search

@@ -32,6 +32,7 @@ object GameEnvironmentOptions {
Option("MESA_EXTENSION_MAX_YEAR", "", 0, Type.TEXT),
Option("MESA_GL_VERSION_OVERRIDE", "", 0, Type.TEXT),
Option("PULSE_LATENCY_MSEC", "", 0, Type.NUMBER),
Option("DROIDDECK_ANTICHEAT_OFFLINE", "1", R.string.game_env_anticheat_offline, Type.TOGGLE, listOf("0", "1")),
Option("WINE_LARGE_ADDRESS_AWARE", "0", 0, Type.TOGGLE, listOf("0", "1")),
Option("WINEDLLOVERRIDES", "", 0, Type.TEXT),
Option("GALLIUM_HUD", "simple", 0, Type.MULTIPLE, listOf("simple", "fps", "frametime")),
+1
View File
@@ -86,6 +86,7 @@
<string name="game_env_no_fsync">Set to 1 to keep this game off droiddeck-fsync; it uses droiddeck-esync where a pack exists, otherwise droiddeck-ntsync.</string>
<string name="game_env_fsync_first">Set to 1 to run this game on droiddeck-fsync, or 0 to put droiddeck-esync first where a pack fits its Proton.</string>
<string name="game_env_xalia">Set to 0 to disable Proton’s gamepad-navigation helper for this game.</string>
<string name="game_env_anticheat_offline">Set to 1 to start an Easy Anti-Cheat game without its anti-cheat. The game must support offline play; online modes that require Easy Anti-Cheat remain unavailable.</string>
<string name="game_env_hud">DXVK overlay for D3D9–11. fps displays frame rate; unset hides it.</string>
<string name="game_env_dxvk_config">DXVK settings separated by semicolons. Recent builds use dxvk.maxFrameRate for frame limiting; prefer the session frame limiter for ordinary play.</string>
<string name="game_env_vkd3d_limit">D3D12 frame-rate limit. 0 disables the limit.</string>
@@ -119,6 +119,28 @@ def skip_red_launcher(command):
return command
def offline_without_anticheat(command, env):
"""Start the configured game executable when the player opts into launching without EAC."""
if env.get("DROIDDECK_ANTICHEAT_OFFLINE") != "1" or len(command) <= 2:
return command
folder = os.path.dirname(command[2])
try:
with open(os.path.join(folder, "EasyAntiCheat", "settings.json"), encoding="utf-8-sig") as source:
executable = json.load(source).get("executable")
except (OSError, ValueError, AttributeError):
return command
if not isinstance(executable, str) or not executable:
return command
relative = os.path.normpath(executable.replace("\\", "/"))
game = os.path.join(folder, relative)
if os.path.isabs(relative) or relative.startswith("..") or not os.path.isfile(game):
return command
if os.path.normcase(os.path.abspath(game)) == os.path.normcase(os.path.abspath(command[2])):
return command
print("droiddeck-game-env: started without Easy Anti-Cheat (offline only)", file=sys.stderr)
return command[:2] + [game] + command[3:]
def engine_fixes(exe, args):
"""Environment and extra arguments a game needs because of what it is built with."""
game = Game(exe, args)
@@ -307,6 +329,8 @@ def main():
env = apply_config(env, {"version": 1}, app_id, fixes)
except (OSError, ValueError, TypeError, AttributeError):
print("droiddeck-game-env: invalid configuration; using inherited environment", file=sys.stderr)
if app_id and len(command) > 1 and command[1] == "waitforexitandrun":
command = offline_without_anticheat(command, env)
if mic_enabled == "0":
env["BANNER_AUDIO_DIRECT_MIC"] = "0"
env = force_ssbs(command, env)
+21
View File
@@ -179,6 +179,27 @@ class GameEnvironmentTest(unittest.TestCase):
(other / "Other.exe").touch()
self.assertEqual(MODULE["engine_fixes"](str(other / "Other.exe"), []), ({}, [], []))
def test_easy_anti_cheat_games_start_offline_only_when_opted_in(self):
with tempfile.TemporaryDirectory() as tmp:
game = Path(tmp) / "ELDEN RING/Game"
(game / "EasyAntiCheat").mkdir(parents=True)
for name in ("start_protected_game.exe", "eldenring.exe"):
(game / name).touch()
(game / "EasyAntiCheat/settings.json").write_text('\ufeff{ "executable" : "eldenring.exe" }', encoding="utf-8")
command = ["proton", "waitforexitandrun", str(game / "start_protected_game.exe"), "-arg"]
offline = MODULE["offline_without_anticheat"]
self.assertEqual(offline(command, {}), command)
self.assertEqual(offline(command, {"DROIDDECK_ANTICHEAT_OFFLINE": "0"}), command)
self.assertEqual(offline(command, {"DROIDDECK_ANTICHEAT_OFFLINE": "1"}),
["proton", "waitforexitandrun", str(game / "eldenring.exe"), "-arg"])
(game / "EasyAntiCheat/settings.json").write_text('{ "executable" : "../../escape.exe" }')
(Path(tmp) / "escape.exe").touch()
self.assertEqual(offline(command, {"DROIDDECK_ANTICHEAT_OFFLINE": "1"}), command)
(game / "EasyAntiCheat/settings.json").write_text('{ "executable" : "missing.exe" }')
self.assertEqual(offline(command, {"DROIDDECK_ANTICHEAT_OFFLINE": "1"}), command)
plain = ["proton", "waitforexitandrun", str(Path(tmp) / "Other.exe")]
self.assertEqual(offline(plain, {"DROIDDECK_ANTICHEAT_OFFLINE": "1"}), plain)
def test_arkham_city_gets_full_x87_precision_over_the_preset(self):
with tempfile.TemporaryDirectory() as tmp:
city = Path(tmp) / "Batman Arkham City GOTY/Binaries/Win32"