|
| 1 | +package app.gamenative.gamefixes |
| 2 | + |
| 3 | +import android.content.Context |
| 4 | +import app.gamenative.data.GameSource |
| 5 | +import com.winlator.container.Container |
| 6 | +import com.winlator.core.WineRegistryEditor |
| 7 | +import java.io.File |
| 8 | +import timber.log.Timber |
| 9 | + |
| 10 | +private val EA_SOFTWARE_RENDERER_EXES = listOf( |
| 11 | + "EACefSubProcess.exe", |
| 12 | +) |
| 13 | +private val EA_QT_HOST_EXES = listOf("EAappInstaller.exe", "EADesktop.exe", "EALaunchHelper.exe") |
| 14 | +private val EA_SOFTWARE_RENDERER_DLLS = listOf("dxgi", "d3d11", "d3d9") |
| 15 | + |
| 16 | +private const val EA_DESKTOP_INSTALL_ROOT = |
| 17 | + ".wine/drive_c/Program Files/Electronic Arts/EA Desktop" |
| 18 | +private const val EA_INSTALL_SUCCESS_KEY_UPPERCASE = |
| 19 | + "HKEY_LOCAL_MACHINE\\\\SOFTWARE\\\\Electronic Arts\\\\EA Desktop\\\\InstallSuccessful" |
| 20 | +private const val EA_INSTALL_SUCCESS_KEY_WINE_CASE = |
| 21 | + "HKEY_LOCAL_MACHINE\\\\Software\\\\Electronic Arts\\\\EA Desktop\\\\InstallSuccessful" |
| 22 | +private const val EA_INSTALLER_PROCESS = |
| 23 | + "%INSTALLDIR%\\\\__Installer\\\\Origin\\\\redist\\\\internal\\\\EAappInstaller.exe" |
| 24 | +private const val EA_HEADLESS_INSTALLER_PROCESS = |
| 25 | + "%INSTALLDIR%\\\\__Installer\\\\Origin\\\\redist\\\\internal\\\\EAapp-wine-install.cmd" |
| 26 | +private const val EA_HEADLESS_INSTALLER_RELATIVE_PATH = |
| 27 | + "__Installer/Origin/redist/internal/EAapp-wine-install.cmd" |
| 28 | +private const val EA_HEADLESS_MSI_RELATIVE_PATH = |
| 29 | + "__Installer/Origin/redist/internal/EAapp-wine-no-start.msi" |
| 30 | + |
| 31 | +private fun configureEaInstallScript(installPath: String) { |
| 32 | + val installScript = File(installPath, "installscript.vdf") |
| 33 | + if (!installScript.isFile) return |
| 34 | + |
| 35 | + val contents = installScript.readText() |
| 36 | + var configured = contents.replace( |
| 37 | + EA_INSTALL_SUCCESS_KEY_UPPERCASE, |
| 38 | + EA_INSTALL_SUCCESS_KEY_WINE_CASE, |
| 39 | + ) |
| 40 | + if ( |
| 41 | + File(installPath, EA_HEADLESS_INSTALLER_RELATIVE_PATH).isFile && |
| 42 | + File(installPath, EA_HEADLESS_MSI_RELATIVE_PATH).isFile |
| 43 | + ) { |
| 44 | + configured = configured.replace(EA_INSTALLER_PROCESS, EA_HEADLESS_INSTALLER_PROCESS) |
| 45 | + } |
| 46 | + if (configured != contents) installScript.writeText(configured) |
| 47 | +} |
| 48 | + |
| 49 | +private fun findInstalledEaDesktop(container: Container): Pair<String, String>? { |
| 50 | + val installRoot = File(container.rootDir, EA_DESKTOP_INSTALL_ROOT) |
| 51 | + val versionDir = installRoot.listFiles() |
| 52 | + ?.filter { it.isDirectory && File(it, "EA Desktop/EADesktop.exe").isFile } |
| 53 | + ?.maxByOrNull { dir -> |
| 54 | + dir.name.split('.').fold(0L) { value, component -> |
| 55 | + value * 10_000L + (component.toLongOrNull() ?: 0L) |
| 56 | + } |
| 57 | + } |
| 58 | + ?: return null |
| 59 | + |
| 60 | + val version = versionDir.name |
| 61 | + val basePath = "C:\\Program Files\\Electronic Arts\\EA Desktop\\$version" |
| 62 | + return version to "$basePath\\EA Desktop" |
| 63 | +} |
| 64 | + |
| 65 | +internal fun applyEaCompatibilityRegistry(container: Container, gameExeWindowsPath: String?) { |
| 66 | + val (version, eaDesktopDir) = findInstalledEaDesktop(container) ?: return |
| 67 | + val desktopPath = "$eaDesktopDir\\EADesktop.exe" |
| 68 | + val launcherPath = "$eaDesktopDir\\EALauncher.exe" |
| 69 | + val link2EaPath = "$eaDesktopDir\\Link2EA.exe" |
| 70 | + val steamPath = "C:\\Program Files (x86)\\Steam\\steam.exe" |
| 71 | + val useLegacySteamApi = container.isUseLegacyDRM && |
| 72 | + !container.isLaunchRealSteam && |
| 73 | + !container.isLaunchBionicSteam |
| 74 | + val systemRegFile = File(container.rootDir, ".wine/system.reg") |
| 75 | + if (!systemRegFile.isFile) return |
| 76 | + |
| 77 | + WineRegistryEditor(systemRegFile).use { editor -> |
| 78 | + editor.setCreateKeyIfNotExist(true) |
| 79 | + |
| 80 | + // Burn extracts the versioned EA files before its MSI/custom actions |
| 81 | + // register the service. Do not mistake that intermediate state for a |
| 82 | + // completed install or Steam will skip a grey/interrupted installer. |
| 83 | + // system.reg stores the concrete control set. CurrentControlSet is a |
| 84 | + // runtime registry alias and is not guaranteed to exist as a literal |
| 85 | + // key while we edit the prefix offline. |
| 86 | + val serviceKey = listOf( |
| 87 | + "System\\ControlSet001\\Services\\EABackgroundService", |
| 88 | + "System\\CurrentControlSet\\Services\\EABackgroundService", |
| 89 | + ).firstOrNull { key -> |
| 90 | + !editor.getStringValue(key, "ImagePath", null).isNullOrEmpty() |
| 91 | + } |
| 92 | + if (serviceKey == null) { |
| 93 | + for (viewPrefix in listOf("Software", "Software\\Wow6432Node")) { |
| 94 | + editor.setStringValue( |
| 95 | + "$viewPrefix\\Electronic Arts\\EA Desktop", |
| 96 | + "InstallSuccessful", |
| 97 | + "false", |
| 98 | + ) |
| 99 | + } |
| 100 | + return |
| 101 | + } |
| 102 | + editor.setStringValue( |
| 103 | + serviceKey, |
| 104 | + "ImagePath", |
| 105 | + "$eaDesktopDir\\EABackgroundService.exe -start", |
| 106 | + ) |
| 107 | + |
| 108 | + for (protocol in listOf("origin", "origin2")) { |
| 109 | + val protocolKey = "Software\\Classes\\$protocol" |
| 110 | + editor.setStringValue(protocolKey, null, "URL:Origin Protocol") |
| 111 | + editor.setStringValue(protocolKey, "URL Protocol", "") |
| 112 | + editor.setStringValue("$protocolKey\\DefaultIcon", null, "\"$launcherPath\",0") |
| 113 | + editor.setStringValue( |
| 114 | + "$protocolKey\\shell\\open\\command", |
| 115 | + null, |
| 116 | + "\"$launcherPath\" \"%1\"", |
| 117 | + ) |
| 118 | + } |
| 119 | + |
| 120 | + // Steam launches The Sims 4 through EA's link2ea:// URL. Some Wine |
| 121 | + // installs are missing this association even though Link2EA.exe is |
| 122 | + // installed, which makes start.exe return immediately without opening |
| 123 | + // EA or the game. |
| 124 | + val link2EaProtocolKey = "Software\\Classes\\link2ea" |
| 125 | + editor.setStringValue(link2EaProtocolKey, null, "URL:EA Link Protocol") |
| 126 | + editor.setStringValue(link2EaProtocolKey, "URL Protocol", "") |
| 127 | + editor.setStringValue("$link2EaProtocolKey\\DefaultIcon", null, "\"$link2EaPath\",0") |
| 128 | + editor.setStringValue( |
| 129 | + "$link2EaProtocolKey\\shell\\open\\command", |
| 130 | + null, |
| 131 | + // Link2EA starts the background service itself, but immediately |
| 132 | + // sends its launch request. On a clean prefix the service also |
| 133 | + // installs its VC runtimes before EADesktop can start; its IPC |
| 134 | + // listener becomes available before that bootstrap is finished. |
| 135 | + // Keep the service and protocol handler in the same Wine session, |
| 136 | + // but give the complete first-run bootstrap time to settle. |
| 137 | + "\"C:\\windows\\system32\\cmd.exe\" /d /s /c " + |
| 138 | + "\"\"C:\\windows\\system32\\sc.exe\" start EABackgroundService >nul 2>&1 & " + |
| 139 | + "\"C:\\windows\\system32\\timeout.exe\" /t 30 /nobreak >nul & " + |
| 140 | + "\"$link2EaPath\" \"%1\"\"", |
| 141 | + ) |
| 142 | + |
| 143 | + // Steam-owned EA entitlements are deliberately redirected back through |
| 144 | + // steam://run/<appid>. With a real/Bionic Steam client, hand that URL to |
| 145 | + // Proton's steam.exe. Legacy DRM replaces the game's steam_api64.dll |
| 146 | + // directly, so avoid the cold-client/Proton Steam bridge and start the |
| 147 | + // existing game executable after EA has authenticated the request. |
| 148 | + val steamProtocolKey = "Software\\Classes\\steam" |
| 149 | + val legacyGameExe = gameExeWindowsPath.takeIf { useLegacySteamApi && it != null } |
| 150 | + val steamProtocolTarget = legacyGameExe ?: steamPath |
| 151 | + val steamProtocolCommand = if (legacyGameExe != null) { |
| 152 | + "\"$legacyGameExe\"" |
| 153 | + } else { |
| 154 | + "\"$steamPath\" \"%1\"" |
| 155 | + } |
| 156 | + editor.setStringValue(steamProtocolKey, null, "URL:Steam Protocol") |
| 157 | + editor.setStringValue(steamProtocolKey, "URL Protocol", "") |
| 158 | + editor.setStringValue("$steamProtocolKey\\DefaultIcon", null, "\"$steamProtocolTarget\",0") |
| 159 | + editor.setStringValue( |
| 160 | + "$steamProtocolKey\\shell\\open\\command", |
| 161 | + null, |
| 162 | + steamProtocolCommand, |
| 163 | + ) |
| 164 | + |
| 165 | + val desktopValues = mapOf( |
| 166 | + "ClientPath" to desktopPath, |
| 167 | + "ClientVersion" to version, |
| 168 | + "CommonAppPathCreated" to "1", |
| 169 | + "DesktopAppPath" to desktopPath, |
| 170 | + "EaConnectLink2EAAppPath" to link2EaPath, |
| 171 | + "EaSteam2EAAppPath" to "$eaDesktopDir\\EASteamLauncher.exe", |
| 172 | + "ErrorReporterPath" to "$eaDesktopDir\\ErrorReporter.exe", |
| 173 | + "InstallLocation" to "C:\\Program Files\\Electronic Arts\\EA Desktop\\$version", |
| 174 | + "IsUnavailable" to "0", |
| 175 | + "LauncherAppPath" to launcherPath, |
| 176 | + "RazorMode" to "0", |
| 177 | + ) |
| 178 | + |
| 179 | + for (viewPrefix in listOf("Software", "Software\\Wow6432Node")) { |
| 180 | + editor.setStringValue("$viewPrefix\\Origin", "ClientPath", desktopPath) |
| 181 | + editor.setStringValue("$viewPrefix\\Electronic Arts\\EADM", "ClientPath", desktopPath) |
| 182 | + |
| 183 | + val desktopKey = "$viewPrefix\\Electronic Arts\\EA Desktop" |
| 184 | + for ((name, value) in desktopValues) { |
| 185 | + if (name in setOf("CommonAppPathCreated", "IsUnavailable", "RazorMode")) { |
| 186 | + editor.setDwordValue(desktopKey, name, value.toInt()) |
| 187 | + } else { |
| 188 | + editor.setStringValue(desktopKey, name, value) |
| 189 | + } |
| 190 | + } |
| 191 | + } |
| 192 | + } |
| 193 | +} |
| 194 | + |
| 195 | + |
| 196 | +/** |
| 197 | + * A staged self-update makes EA Desktop demand a client restart mid-session, |
| 198 | + * which tears down any running game with it. Drop staged payloads, clear the |
| 199 | + * pending flag, and keep the version directory unwritable so an update can't |
| 200 | + * re-stage. (Remove the write protection deliberately when an EA update is |
| 201 | + * actually wanted.) |
| 202 | + */ |
| 203 | +private fun suppressEaSelfUpdate(container: Container) { |
| 204 | + val (version, _) = findInstalledEaDesktop(container) ?: return |
| 205 | + val versionDir = File(container.rootDir, "$EA_DESKTOP_INSTALL_ROOT/$version") |
| 206 | + |
| 207 | + versionDir.setWritable(true, false) |
| 208 | + versionDir.listFiles()?.forEach { entry -> |
| 209 | + val staged = entry.name != "EA Desktop" && entry.name != "VC" && |
| 210 | + (entry.isDirectory || entry.name.endsWith(".zip") || entry.name.endsWith(".zip.sig")) |
| 211 | + if (staged) { |
| 212 | + Timber.tag("GameFixes").i("Removing staged EA update: %s", entry.name) |
| 213 | + entry.deleteRecursively() |
| 214 | + } |
| 215 | + } |
| 216 | + versionDir.setWritable(false, false) |
| 217 | + |
| 218 | + val machineIni = File(container.rootDir, ".wine/drive_c/ProgramData/EA Desktop/machine.ini") |
| 219 | + if (machineIni.isFile) { |
| 220 | + val lines = machineIni.readLines().map { line -> |
| 221 | + when { |
| 222 | + line.startsWith("machine.updatepending=") -> "machine.updatepending=0" |
| 223 | + line.startsWith("machine.updateinfo=") -> "machine.updateinfo=" |
| 224 | + else -> line |
| 225 | + } |
| 226 | + } |
| 227 | + machineIni.writeText(lines.joinToString("\n")) |
| 228 | + } |
| 229 | +} |
| 230 | + |
| 231 | +const val EA_LINK2EA_LAUNCH_SCRIPT_WINDOWS_PATH = |
| 232 | + "C:\\\\ProgramData\\\\GameNative\\\\ea-link2ea-launch.cmd" |
| 233 | + |
| 234 | +/** |
| 235 | + * Link2EA races EABackgroundService on cold boots: it starts the service and |
| 236 | + * immediately sends its launch request, so the first EA Desktop of a session |
| 237 | + * connects before the IPC listener exists and shows "disconnected, restart". |
| 238 | + * Gate the protocol handoff on the service actually reporting RUNNING. This |
| 239 | + * cannot live in the link2ea registry handler because EA rewrites its protocol |
| 240 | + * keys on every service start. |
| 241 | + */ |
| 242 | +private fun writeLink2EaLaunchScript(container: Container) { |
| 243 | + val link2EaPath = findInstalledEaDesktop(container) |
| 244 | + ?.let { (_, eaDesktopDir) -> "$eaDesktopDir\\Link2EA.exe" } |
| 245 | + ?: return |
| 246 | + val script = File(container.rootDir, ".wine/drive_c/ProgramData/GameNative/ea-link2ea-launch.cmd") |
| 247 | + script.parentFile?.mkdirs() |
| 248 | + script.writeText( |
| 249 | + """ |
| 250 | + @echo off |
| 251 | + "C:\windows\system32\sc.exe" start EABackgroundService >nul 2>&1 |
| 252 | + for /L %%i in (1,1,30) do ( |
| 253 | + "C:\windows\system32\sc.exe" query EABackgroundService | "C:\windows\system32\findstr.exe" /C:"RUNNING" >nul 2>&1 && goto launch |
| 254 | + "C:\windows\system32\timeout.exe" /t 1 /nobreak >nul 2>&1 |
| 255 | + ) |
| 256 | + :launch |
| 257 | + "C:\windows\system32\timeout.exe" /t 2 /nobreak >nul 2>&1 |
| 258 | + "$link2EaPath" %1 |
| 259 | + """.trimIndent().replace("\n", "\r\n"), |
| 260 | + ) |
| 261 | +} |
| 262 | + |
| 263 | +private const val EA_INSTALLER_RELATIVE_DIR = "__Installer/Origin/redist/internal" |
| 264 | + |
| 265 | +/** An EA App title ships EA's installer bundle inside its game directory. */ |
| 266 | +fun isEaAppGame(installPath: String): Boolean = |
| 267 | + File(installPath, "$EA_INSTALLER_RELATIVE_DIR/EAappInstaller.exe").isFile |
| 268 | + |
| 269 | +/** |
| 270 | + * The headless-install pieces are EA-App-generic, but the MSI is ~245MB and |
| 271 | + * cannot ship inside the APK. Generate the cmd from code, and source the MSI |
| 272 | + * from any sibling EA game under the same steamapps/common root that already |
| 273 | + * has one. |
| 274 | + */ |
| 275 | +private fun ensureHeadlessInstallerFiles(installPath: String) { |
| 276 | + val internalDir = File(installPath, EA_INSTALLER_RELATIVE_DIR) |
| 277 | + if (!internalDir.isDirectory) return |
| 278 | + |
| 279 | + val msi = File(internalDir, "EAapp-wine-no-start.msi") |
| 280 | + if (!msi.isFile) { |
| 281 | + val commonRoot = File(installPath).parentFile |
| 282 | + val donor = commonRoot?.listFiles() |
| 283 | + ?.asSequence() |
| 284 | + ?.filter { it.isDirectory } |
| 285 | + ?.map { File(it, "$EA_INSTALLER_RELATIVE_DIR/EAapp-wine-no-start.msi") } |
| 286 | + ?.firstOrNull { it.isFile } |
| 287 | + if (donor == null) { |
| 288 | + Timber.tag("GameFixes").w( |
| 289 | + "No EAapp-wine-no-start.msi found under %s; EA headless install unavailable", |
| 290 | + commonRoot?.absolutePath, |
| 291 | + ) |
| 292 | + return |
| 293 | + } |
| 294 | + Timber.tag("GameFixes").i("Copying EA headless MSI from %s", donor.absolutePath) |
| 295 | + donor.copyTo(msi) |
| 296 | + } |
| 297 | + |
| 298 | + val cmd = File(internalDir, "EAapp-wine-install.cmd") |
| 299 | + if (!cmd.isFile) { |
| 300 | + cmd.writeText( |
| 301 | + """ |
| 302 | + @echo off |
| 303 | + set EA_BUNDLE_KEY={0843d159-4e03-4026-8fa0-32432514dba6} |
| 304 | + set "EA_BUNDLE_CACHE=C:\ProgramData\Package Cache\%EA_BUNDLE_KEY%" |
| 305 | + if not exist "%EA_BUNDLE_CACHE%" mkdir "%EA_BUNDLE_CACHE%" |
| 306 | + copy /Y "%~dp0EAappInstaller.exe" "%EA_BUNDLE_CACHE%\EAappOfflineInstaller.exe" |
| 307 | + reg add "HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\%EA_BUNDLE_KEY%" /f |
| 308 | + reg add "HKLM\SOFTWARE\Electronic Arts\EA Desktop" /v InstallSuccessful /t REG_SZ /d installing /f |
| 309 | + msiexec /x {C2622085-ABD2-49E5-8AB9-D3D6A642C091} /qn /norestart |
| 310 | + rem Pre-register the service: on a fresh prefix wine msi's |
| 311 | + rem StartServices cannot see the service its own ServiceInstall |
| 312 | + rem just registered (error 2) and rolls the whole install back. |
| 313 | + sc create EABackgroundService binPath= "\"C:\Program Files\Electronic Arts\EA Desktop\13.768.7.6285\EA Desktop\EABackgroundService.exe\" -start" >nul 2>&1 |
| 314 | + msiexec /i "%~dp0EAapp-wine-no-start.msi" /qn /norestart /L*V "C:\windows\temp\EAapp-wine-install.log" ARPSYSTEMCOMPONENT=1 MSIFASTINSTALL=7 INSTALL_ROOT="C:\Program Files\Electronic Arts\EA Desktop" CLIENT_VERSION="13.768.7.6285" JUNO_CREATE_DESKTOP_SHORTCUT=1 INSTALLER_ERROR_REPORTER_SHORTCUT_TITLE="EA Error Reporter" INSTALLER_UPDATER_SHORTCUT_TITLE="EA app Updater" INSTALLER_RECOVERY_HELPER_SHORTCUT_TITLE="App Recovery" INSTALLER_ERROR_UNKNOWN="EA app encountered an error during the installation. Try again a bit later." BUNDLE_PROVIDERKEY="%EA_BUNDLE_KEY%" PRODUCT_DISPLAY_NAME="EA app" EAX_LAUNCH_CLIENT=0 INTREPID_ENABLE=0 EAX_ALLOW_WINDOWS_7=1 EAX_DISABLE_SYMLINKS=0 EAX_DOWNLOAD_IN_PLACE_DIR="C:\Program Files\EA Games" EAX_BUNDLE_EXECUTABLE_NAME="EAappOfflineInstaller.exe" EAX_RAZOR_MODE_ENABLE=0 |
| 315 | + set EA_INSTALL_RC=%ERRORLEVEL% |
| 316 | + copy /Y "C:\windows\temp\EAapp-wine-install.log" "%~dp0EAapp-wine-install.log" |
| 317 | + if not "%EA_INSTALL_RC%"=="0" goto install_failed |
| 318 | + reg add "HKLM\SOFTWARE\Electronic Arts\EA Desktop" /v InstallSuccessful /t REG_SZ /d true /f |
| 319 | + exit /b 0 |
| 320 | + :install_failed |
| 321 | + reg add "HKLM\SOFTWARE\Electronic Arts\EA Desktop" /v InstallSuccessful /t REG_SZ /d failed-%EA_INSTALL_RC% /f |
| 322 | + exit /b %EA_INSTALL_RC% |
| 323 | + """.trimIndent().replace("\n", "\r\n"), |
| 324 | + ) |
| 325 | + } |
| 326 | +} |
| 327 | + |
| 328 | +/** |
| 329 | + * EA App family fix: applies to any Steam game that ships the EA installer. |
| 330 | + * |
| 331 | + * Keep the game on DXVK, route EA's Qt hosts through builtin WineD3D without |
| 332 | + * disabling OpenGL, and force only the CEF subprocess to software rendering. |
| 333 | + */ |
| 334 | +val EaAppGameFix: GameFix = object : GameFix { |
| 335 | + override fun apply( |
| 336 | + context: Context, |
| 337 | + gameId: String, |
| 338 | + installPath: String, |
| 339 | + installPathWindows: String, |
| 340 | + container: Container, |
| 341 | + ): Boolean = try { |
| 342 | + ensureHeadlessInstallerFiles(installPath) |
| 343 | + configureEaInstallScript(installPath) |
| 344 | + writeLink2EaLaunchScript(container) |
| 345 | + suppressEaSelfUpdate(container) |
| 346 | + val userRegFile = File(container.rootDir, ".wine/user.reg") |
| 347 | + if (!userRegFile.isFile) { |
| 348 | + userRegFile.parentFile?.mkdirs() |
| 349 | + userRegFile.writeText("WINE REGISTRY Version 2\n\n") |
| 350 | + } |
| 351 | + WineRegistryEditor(userRegFile).use { editor -> |
| 352 | + editor.setCreateKeyIfNotExist(true) |
| 353 | + for (exe in EA_QT_HOST_EXES) { |
| 354 | + val dllOverridesKey = "Software\\Wine\\AppDefaults\\$exe\\DllOverrides" |
| 355 | + val direct3dKey = "Software\\Wine\\AppDefaults\\$exe\\Direct3D" |
| 356 | + for (dll in EA_SOFTWARE_RENDERER_DLLS) { |
| 357 | + if (editor.getStringValue(dllOverridesKey, dll, null) != "builtin") { |
| 358 | + editor.setStringValue(dllOverridesKey, dll, "builtin") |
| 359 | + } |
| 360 | + } |
| 361 | + editor.removeValue(direct3dKey, "renderer") |
| 362 | + } |
| 363 | + for (exe in EA_SOFTWARE_RENDERER_EXES) { |
| 364 | + val dllOverridesKey = "Software\\Wine\\AppDefaults\\$exe\\DllOverrides" |
| 365 | + val direct3dKey = "Software\\Wine\\AppDefaults\\$exe\\Direct3D" |
| 366 | + for (dll in EA_SOFTWARE_RENDERER_DLLS) { |
| 367 | + if (editor.getStringValue(dllOverridesKey, dll, null) != "builtin") { |
| 368 | + editor.setStringValue(dllOverridesKey, dll, "builtin") |
| 369 | + } |
| 370 | + } |
| 371 | + if (editor.getStringValue(direct3dKey, "renderer", null) != "no3d") { |
| 372 | + editor.setStringValue(direct3dKey, "renderer", "no3d") |
| 373 | + } |
| 374 | + } |
| 375 | + } |
| 376 | + val gameExeWindowsPath = container.executablePath |
| 377 | + .takeIf { it.isNotBlank() } |
| 378 | + ?.let { exe -> |
| 379 | + "C:\\Program Files (x86)\\Steam\\steamapps\\common\\" + |
| 380 | + "${File(installPath).name}\\${exe.replace('/', '\\')}" |
| 381 | + } |
| 382 | + applyEaCompatibilityRegistry(container, gameExeWindowsPath) |
| 383 | + true |
| 384 | + } catch (e: Exception) { |
| 385 | + Timber.tag("GameFixes").e(e, "Failed to apply EA App software renderer overrides") |
| 386 | + false |
| 387 | + } |
| 388 | +} |
0 commit comments