Skip to content

Instantly share code, notes, and snippets.

@Kazyyk
Created March 20, 2026 00:14
Show Gist options
  • Select an option

  • Save Kazyyk/bf03371f75ae457d4e9a96ae63aecee4 to your computer and use it in GitHub Desktop.

Select an option

Save Kazyyk/bf03371f75ae457d4e9a96ae63aecee4 to your computer and use it in GitHub Desktop.
Hide Satisfactory Experimental Watermark Banner by Patching Game Version
# patch_gameversion.ps1
# Patches UFGVersionFunctionLibrary::GetGameVersion() to return GV_Main (0)
# This hides the "EXPERIMENTAL" banner in the UI
param(
[switch]$Restore
)
$gameDll = "C:\Program Files (x86)\Steam\steamapps\common\Satisfactory\FactoryGame\Binaries\Win64\FactoryGameSteam-FactoryGame-Win64-Shipping.dll"
$backupDll = "$gameDll.bak"
$pdbDir = "C:\Program Files (x86)\Steam\steamapps\common\Satisfactory\FactoryGame\Binaries\Win64"
if ($Restore) {
if (Test-Path $backupDll) {
Copy-Item $backupDll $gameDll -Force
Write-Host "Restored original DLL from backup."
} else {
Write-Host "No backup found."
}
exit 0
}
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public class DbgHelp4 {
[DllImport("dbghelp.dll", SetLastError = true)]
public static extern uint SymSetOptions(uint SymOptions);
[DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool SymInitializeW(IntPtr hProcess, string UserSearchPath, bool fInvadeProcess);
[DllImport("dbghelp.dll", SetLastError = true)]
public static extern bool SymCleanup(IntPtr hProcess);
[DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern ulong SymLoadModuleExW(IntPtr hProcess, IntPtr hFile, string ImageName, string ModuleName, ulong BaseOfDll, uint DllSize, IntPtr Data, uint Flags);
[DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool SymFromNameW(IntPtr hProcess, string Name, IntPtr Symbol);
}
"@
[DbgHelp4]::SymSetOptions(0x00000002 -bor 0x00000004) | Out-Null
$hProcess = [IntPtr]::new(0xDEAD)
[DbgHelp4]::SymInitializeW($hProcess, $pdbDir, $false) | Out-Null
$dllSize = [uint32](Get-Item $gameDll).Length
$baseAddr = [DbgHelp4]::SymLoadModuleExW($hProcess, [IntPtr]::Zero, $gameDll, $null, [uint64]0x10000000, $dllSize, [IntPtr]::Zero, 0)
Write-Host "Module loaded at base 0x$($baseAddr.ToString('X'))"
# Look up GetGameVersion
$buf = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(4200)
for ($z = 0; $z -lt 256; $z += 4) {
[System.Runtime.InteropServices.Marshal]::WriteInt32($buf, $z, 0)
}
[System.Runtime.InteropServices.Marshal]::WriteInt32($buf, 0, 88)
[System.Runtime.InteropServices.Marshal]::WriteInt32($buf, 80, 2000)
$found = [DbgHelp4]::SymFromNameW($hProcess, "UFGVersionFunctionLibrary::GetGameVersion", $buf)
if (-not $found) {
Write-Error "Symbol not found!"
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($buf)
[DbgHelp4]::SymCleanup($hProcess) | Out-Null
exit 1
}
$symAddr = [uint64][System.Runtime.InteropServices.Marshal]::ReadInt64($buf, 56)
$symSize = [System.Runtime.InteropServices.Marshal]::ReadInt32($buf, 28)
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($buf)
[DbgHelp4]::SymCleanup($hProcess) | Out-Null
$rva = [uint64]($symAddr - $baseAddr)
Write-Host "GetGameVersion at RVA: 0x$($rva.ToString('X')) ($symSize bytes)"
# Read DLL and convert RVA to file offset
$dllBytes = [System.IO.File]::ReadAllBytes($gameDll)
$peOffset = [BitConverter]::ToInt32($dllBytes, 0x3C)
$numSections = [BitConverter]::ToUInt16($dllBytes, $peOffset + 6)
$optHeaderSize = [BitConverter]::ToUInt16($dllBytes, $peOffset + 20)
$sectionTableOffset = $peOffset + 24 + $optHeaderSize
$fileOffset = -1
for ($i = 0; $i -lt $numSections; $i++) {
$secOff = $sectionTableOffset + ($i * 40)
$vSize = [BitConverter]::ToUInt32($dllBytes, $secOff + 8)
$vAddr = [BitConverter]::ToUInt32($dllBytes, $secOff + 12)
$rOff = [BitConverter]::ToUInt32($dllBytes, $secOff + 20)
if ($rva -ge $vAddr -and $rva -lt ($vAddr + $vSize)) {
$fileOffset = [long]$rOff + ([long]$rva - [long]$vAddr)
break
}
}
if ($fileOffset -lt 0) {
Write-Error "Could not map RVA to file offset"
exit 1
}
Write-Host "File offset: 0x$($fileOffset.ToString('X'))"
$currentBytes = $dllBytes[$fileOffset..($fileOffset + 15)]
$hexCurrent = ($currentBytes | ForEach-Object { '{0:X2}' -f $_ }) -join ' '
Write-Host "Current bytes: $hexCurrent"
# Check if already patched
if ($dllBytes[$fileOffset] -eq 0x31 -and $dllBytes[$fileOffset+1] -eq 0xC0 -and $dllBytes[$fileOffset+2] -eq 0xC3) {
Write-Host "Already patched!"
exit 0
}
# Backup if not exists
if (-not (Test-Path $backupDll)) {
Copy-Item $gameDll $backupDll
Write-Host "Backup saved to: $backupDll"
} else {
Write-Host "Backup already exists."
}
# Patch: xor eax, eax; ret (return 0 = GV_Main)
$dllBytes[$fileOffset] = 0x31 # xor eax, eax
$dllBytes[$fileOffset + 1] = 0xC0
$dllBytes[$fileOffset + 2] = 0xC3 # ret
[System.IO.File]::WriteAllBytes($gameDll, $dllBytes)
Write-Host ""
Write-Host "PATCHED! GetGameVersion() now returns GV_Main (0)."
Write-Host "The experimental banner should be gone."
Write-Host ""
Write-Host "To restore: powershell -ExecutionPolicy Bypass -File $($MyInvocation.MyCommand.Path) -Restore"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment