<# .SYNOPSIS Stellt aus den Sicherungen des SC-Deutsch-Launchers den unveraenderten CIG-Originaltext wieder her - als KOPIE. .DESCRIPTION Der Launcher schreibt vor jeder Aenderung den urspruenglichen Wert nach global.ini.bp.bak.json bzw. global.ini.strings.bak.json, als Zuordnung Schluessel -> Originaltext. Daraus laesst sich der Auslieferungszustand zeichengenau zurueckrechnen. Eine Eigenheit: Die Sicherung schreibt Schluessel klein und laesst den Varianten-Zusatz weg - die ini-Zeile Adagio_..._Levski_E_Desc_001,P steht in der Sicherung als adagio_..._levski_e_desc_001 Deshalb wird zuerst der genaue Schluessel gesucht und erst danach der ohne Zusatz. Der zweite Weg wird nur benutzt, wenn genau eine ini-Zeile auf diesen Sicherungseintrag passt; sonst waere die Zuordnung geraten. Der Text wird nirgends zusammengeschnitten oder rekonstruiert. Was nicht aus der Sicherung stammt, bleibt unveraendert - und faellt damit der abschliessenden Pruefung auf. Die Installation wird NICHT veraendert. Geschrieben wird ausschliesslich die Zieldatei; ein Fehlschlag laesst das Spiel unberuehrt. .PARAMETER IniPath Die gepatchte global.ini. Die beiden .bak.json werden daneben erwartet. .PARAMETER OutPath Zieldatei. Vorgabe: global.restored.ini im TEMP-Verzeichnis. .PARAMETER PassThru Auch dann ein Ergebnis liefern, wenn Reste der Injektion uebrig bleiben, statt abzubrechen. Nur zur Fehlersuche - eine solche Datei darf nicht importiert werden. .EXAMPLE .\restore-original.ps1 -IniPath 'C:\...\german_(germany)\global.ini' -Verbose #> [CmdletBinding()] param( [Parameter(Mandatory)][string] $IniPath, [string] $OutPath, [switch] $PassThru ) $ErrorActionPreference = 'Stop' # Kennzeichen der eingefuegten Bloecke. Bleibt nach dem Zuruecksetzen eines # davon stehen, wurde ein Wert veraendert, ohne gesichert zu werden - dann ist # die Datei nicht verwendbar. $MARKER = '# Expected reputation|# Mission Cooldown|# Blueprint Drop Chance|' + '# Can mission be shared|# Min\. Reputation|# Region:|' + 'POSSIBLE BLUEPRINTS FOR THIS MISSION|' + '\[!\] Blueprints only|\[BP\]|' + 'dynamically generated by the game|scmdb\.net' # Varianten-Zusatz am Schluesselende, etwa ",P". $ZUSATZ = ',[A-Za-z]+$' # --------------------------------------------------------------------- Start if (-not (Test-Path -LiteralPath $IniPath)) { throw "Nicht gefunden: $IniPath" } $quelle = (Resolve-Path -LiteralPath $IniPath).Path $ordner = Split-Path -Parent $quelle if (-not $OutPath) { $OutPath = Join-Path ([IO.Path]::GetTempPath()) 'SCQuestGuide\global.restored.ini' } $zielOrdner = Split-Path -Parent $OutPath if ($zielOrdner -and -not (Test-Path -LiteralPath $zielOrdner)) { New-Item -ItemType Directory -Path $zielOrdner -Force | Out-Null } # --- Sicherungen einlesen ------------------------------------------------- $original = [System.Collections.Generic.Dictionary[string,string]]::new( [StringComparer]::OrdinalIgnoreCase) $sicherungen = @() foreach ($name in 'global.ini.bp.bak.json', 'global.ini.strings.bak.json') { $pfad = Join-Path $ordner $name if (-not (Test-Path -LiteralPath $pfad)) { continue } $sicherungen += $name $obj = [System.IO.File]::ReadAllText($pfad, [System.Text.Encoding]::UTF8) | ConvertFrom-Json foreach ($p in $obj.PSObject.Properties) { $original[$p.Name] = [string]$p.Value } } if ($original.Count -eq 0) { throw "Keine Launcher-Sicherung neben '$quelle'. Der Originaltext laesst sich " + "nicht rekonstruieren - diese Installation ist als Quelle nicht verwendbar." } $zeilen = [System.IO.File]::ReadAllLines($quelle, [System.Text.Encoding]::UTF8) # --- Eindeutigkeit der Zuordnung ohne Zusatz pruefen ---------------------- # Ein Sicherungseintrag darf nur benutzt werden, wenn genau eine ini-Zeile # ohne ihren Varianten-Zusatz darauf passt. Passen mehrere, laesst sich nicht # entscheiden, welche gemeint ist - dann bleibt der Weg gesperrt. $anspruch = [System.Collections.Generic.Dictionary[string,int]]::new( [StringComparer]::OrdinalIgnoreCase) foreach ($z in $zeilen) { $t = $z.IndexOf('='); if ($t -lt 1) { continue } $k = $z.Substring(0, $t) if ($original.ContainsKey($k)) { continue } # exakter Treffer, egal $b = $k -replace $ZUSATZ, '' if ($b -ceq $k -or -not $original.ContainsKey($b)) { continue } $n = 0; [void]$anspruch.TryGetValue($b, [ref]$n) $anspruch[$b] = $n + 1 } $mehrdeutig = [System.Collections.Generic.HashSet[string]]::new( [string[]]@($anspruch.GetEnumerator() | Where-Object { $_.Value -gt 1 } | ForEach-Object { $_.Key }), [StringComparer]::OrdinalIgnoreCase) if ($mehrdeutig.Count) { Write-Verbose "Mehrdeutige Zuordnung, uebersprungen: $($mehrdeutig.Count) Eintraege" } # --- Datei zurueckrechnen ------------------------------------------------- $exakt = 0; $ueberZusatz = 0 $aus = [System.Collections.Generic.List[string]]::new($zeilen.Length) foreach ($z in $zeilen) { $t = $z.IndexOf('=') if ($t -lt 1) { $aus.Add($z); continue } $k = $z.Substring(0, $t); $v = $z.Substring($t + 1) $soll = $null if ($original.TryGetValue($k, [ref]$soll)) { if ($v -cne $soll) { $exakt++ } $aus.Add($k + '=' + $soll) continue } $b = $k -replace $ZUSATZ, '' if ($b -cne $k -and -not $mehrdeutig.Contains($b) -and $original.TryGetValue($b, [ref]$soll)) { if ($v -cne $soll) { $ueberZusatz++ } $aus.Add($k + '=' + $soll) continue } $aus.Add($z) } # UTF-8 mit BOM wie die Vorlage - daran erkennt der Extractor die Kodierung. [System.IO.File]::WriteAllLines($OutPath, $aus, [System.Text.UTF8Encoding]::new($true)) Write-Verbose "Zurueckgesetzt: $exakt exakt, $ueberZusatz ueber Schluessel ohne Zusatz" # --- Ergebnis pruefen ----------------------------------------------------- $text = [System.IO.File]::ReadAllText($OutPath, [System.Text.Encoding]::UTF8) $reste = [ordered]@{} foreach ($m in ([ordered]@{ 'Region-Zeilen' = '# Region:' 'Reputationsangaben' = '# Expected reputation' 'Mindestreputation' = '# Min\. Reputation' 'Cooldown-Angaben' = '# Mission Cooldown' 'Teilbarkeit' = '# Can mission be shared' 'Bauplan-Wahrscheinlichk.' = '# Blueprint Drop Chance' 'Bauplan-Liste' = 'POSSIBLE BLUEPRINTS FOR THIS MISSION' 'BP-Marker' = '\[BP\]' 'Fremdverweis scmdb.net' = 'scmdb\.net' }).GetEnumerator()) { $n = ([regex]::Matches($text, $m.Value)).Count if ($n -gt 0) { $reste[$m.Key] = $n } } $ergebnis = [pscustomobject]@{ Quelle = $quelle Ziel = $OutPath Sicherungen = $sicherungen Eintraege = $original.Count Exakt = $exakt UeberZusatz = $ueberZusatz Reste = $reste Sauber = ($reste.Count -eq 0) } if (-not $ergebnis.Sauber -and -not $PassThru) { $liste = ($reste.GetEnumerator() | ForEach-Object { "$($_.Key): $($_.Value)" }) -join ', ' Remove-Item -LiteralPath $OutPath -ErrorAction SilentlyContinue throw "Rekonstruktion unvollstaendig - es bleiben Reste der Injektion: $liste. " + "Die Datei wurde verworfen und darf nicht importiert werden." } $ergebnis