Lucasartsifier: Statische analyse voor Sierra-avonturenspellen

Er hoeft niets over de specifieke titels te worden gedeclareerd: de startkamer, de overwinningskamer, het doodsignaal en de debug-vlaggen worden allemaal ontdekt uit de eigen code van het spel.

In tegenstelling tot LucasArts-spellen, laten Sierra-spellen het toe dat je vast komt te zitten. Als je bijvoorbeeld de zonnebrandcrème in Los Angeles vergeet en aan boord van het cruiseschip gaat, ga je dagen later dood op een vlot zonder weg terug. Deze tool vindt dergelijke vallen automatisch en blokkeert de overgang die ze veroorzaakt, op het laatste moment dat de speler nog kan voldoen aan de vereisten.

Vier spellen zijn geanalyseerd en play-tested — Leisure Suit Larry 2 (SCI0, 1988), King's Quest IV (SCI0, 1988), King's Quest VI (SCI1.1, 1992), en Laura Bow 2 (SCI1.1, 1992). Deze maken gebruik van dezelfde engine, waardoor er geen spelspecifieke analysecode nodig is.

Demo

Voorbeeld van King's Quest IV (gepatched): de walvis, de nachtklok en zeven gestrande items zijn nu allemaal beveiligd.

Een verkorte weergave van een run op Leisure Suit Larry 2 (python3 -m pipeline <game>):

[2] ANALYZE
anchors: start rm11, victory [86]  (discovered)
death signal: global101 == 1001, debug globals: [14, 100]  (derived)
101 rooms, 27 strongly-connected components, 40 gating registers
softlocks: 15 items + 1 disjunctive group(s)
- Sunscreen
...
[3] DERIVE
rm38 -> rm131: (and (gEgo has: 11) (gEgo has: 12) (gEgo has: 14) (gEgo has: 15))
rm57 -> rm58: (and (gEgo has: 21) (gEgo has: 24) (gEgo has: 25) (gEgo has: 26))
rm79 -> rm80: (or (gEgo has: 30) (gEgo has: 31))
rm131 -> rm138: (not (gEgo has: 13))
rm63: delete `(gEgo put: 21 -1)` (Hair_Rejuvenator)
verifying against the guarded model...
fixed 15 + 1 group(s); NEW softlocks introduced: none
[4] PATCH
compiled 117/118 scripts
script.000  Main  10790 bytes
script.057  rm57  2938 bytes
...
Done. 10 patch files in build/patch

De analyzer identificeerde het instappen op het schip als een eenrichtingsverkeer-overgang, leidde af welke items mee moeten, verifieerde het beveiligde model om te bewijzen dat de guards geen nieuwe softlocks introduceren, en compileerde de aangepaste scripts opnieuw naar het eigen loose-patch formaat van Sierra.

Opmerking over rm131 -> rm138: (not (gEgo has: 13)): Guards kunnen ook negatieve literalen bevatten. De Spinach Dip is fataal als je deze in rm138 bezit; de oplossing is dus om de overgang te weigeren zolang je het item nog hebt. Dit is geplaatst op een punt waar je het item nog overboord kunt gooien, omdat het eisen dat je iets weggooit wat je niet meer kunt weggooien een 'muur' (hardlock) zou creëren, wat dit project als erger beschouwt dan de bug. De pipeline weigert iets te produceren als de guards de verificatie niet passeren of als een bewerkt script niet compileert.

Een gepatcht spel speelt normaal. Het patch-mechanisme is de methode die Sierra zelf gebruikte voor bugfixes, waardoor de originelen nooit worden gewijzigd (verwijder de patchbestanden om terug te draaien). Het gedrag van de guards kan in-game worden ingesteld:

  • Full: voorkomt elke gevaarlijke actie.
  • Lite: voorkomt het één keer, en staat het daarna toe met een waarschuwing.
  • Off: zet de guards uit.

Wat telt als een softlock? (Caveat Player)

Sommige doodsituaties zijn bewust gelaten — zij die nog vermeden kunnen worden vanaf de huidige positie. De analyse maakt onderscheid tussen onwinbare toestanden en vermijdbare sterfgevallen op basis van bereikbaarheid, niet op basis van de doodsvoorwaarden.

In Leisure Suit Larry 2 sterf je als je het KGB-strand op loopt zonder volledige vermomming. Sommige delen van de vermomming zijn alleen op het cruiseschip te vinden; de analyzer weigert daarom het schip te laten verlaten zonder deze items. Echter, de rest is verkrijgbaar op het eiland — precies op de plek waar de dood optreedt — dus die doodssituatie blijft bestaan. Dit is de manier waarop Sierra-spellen hints geven over wat je moet doen. Zoals Al Lowe zegt: "Save Early, Save Often!"

Status

Vier spellen zijn voltooid, verspreid over de twee belangrijkste tijdperken van de engine (SCI0 1988 → SCI1.1 1992), zonder dat er per titel iets is gedeclareerd.

SpelEngineStatusOpmerkingen
Leisure Suit Larry 2 (1988)SCI0Gereed & getestSpinach Dip: fataal om te dragen, dus guard is een negatieve literaal.
King's Quest IV (1988)SCI0Gereed & getestReal-time nachtklok; de walvis — willekeurige gebeurtenissen beveiligd zodat ze alleen optreden als ze overleefbaar zijn.
King's Quest VI (1992)SCI1.1Gereed & getestDe twee einde-paden compliceren de analyse; de start van de bruiloft (een timer) is beveiligd tot de nodige items in bezit zijn.
Laura Bow 2 (1992)SCI1.1Gereed & getestDe act-structuur: de plot-klok is een register, act-overgangen zijn eenrichtingsverkeer.
King's Quest V (1990)SCI1-middleIn progress (kq5 branch)De dorpsmarkt: betalingen aan kooplieden matchen zodat iedereen betaald kan worden.

Hoe het werkt (beknopt)

  1. Decompileer het game-binary naar een typed control-flow AST (JSON IR).
  2. Abstract-interpreteer deze AST en stel padvoorwaarden samen in een game-graaf: beveiligde bewegingsranden, itemverwervingen, itemverliezen en register-schrijfacties. Ook kamerkunst (PIC/VIEW) en obstakel-polygonen worden gelezen, aangezien sommige poorten geometrisch zijn en niet in het script voorkomen.
  3. Condenseer de graaf naar strongly-connected components — regio's waarin je vrij kunt rondwandelen. Alleen de eenrichtingsranden tussen deze componenten kunnen je stranden, wat het probleem eindig maakt.
  4. Vind strandingen: een item dat verkrijgbaar is vóór een overgang, onbeschikbaar is daarna, maar nog steeds nodig is verderop.
  5. Leid een guard af vanuit de winnende regio — de voorwaarde waaronder het doel nog bereikbaar is — en plaats deze op het laatste punt waar de speler nog kan voldoen aan de voorwaarde. Dead-ends waarbij items worden verspild, worden apart geneutraliseerd met een "Just kidding!"-bericht, zonder scorestraf.
  6. Recompileer en emit. Het gepatchte spel is nu normaal speelbaar (bijv. in ScummVM of DOSBox).

De Toolchain

Stap 1 en 6 maken gebruik van twee bestaande projecten:

  • Decompilatie gebeurt via sci-tools (sluicebox, MIT). Er wordt een fork gebruikt waarvan de json-ir branch een tweede emitter toevoegt naast de .sc bronuitvoer: de typed control-flow AST als JSON. De decompilatie-logica zelf is ongewijzigd.
  • Compilatie gebeurt via de scriptcompiler van SCICompanion (Philip Fortier, GPL-2.0+). Deze is geport om headless op Linux te draaien. De port bevindt zich in tools/scicompile/ en vervangt de MFC/Windows-interface. De vendor tree wordt tijdens het build-proces gekloond en nooit bewerkt; enkele bestanden worden gepatcht als build-stap voor MSVC-specifieke constructies.

Installatie

De analyse is geschreven in Python 3 zonder externe packages. De twee externe toolchains (decompiler in C# en compiler in C++) moeten vanuit broncode worden gebouwd.

Vereisten

ComponentRedenGeverifieerd tegen
Python 3.12Analyse en tests (src/)3.12.3
.NET SDK 8Bouwt sci-tools voor decompilatie8.0.129
cmake $\ge$ 3.16, C++14 compiler, makeBouwt scicompile voor recompilatiecmake 3.28.3, g++ 13.3
gitVoor het klonen van vendor trees2.43

Eenmalige build

git clone https://github.com/katiahayati/lucasartsifier && cd lucasartsifier

# 1. De decompiler. Kloont de sci-tools fork, bouwt deze en decompileert het spel naar build/ir.
tools/sci-tools-fork/build.sh /pad/naar/spel

# 2. De compiler: SCICompanion, geport naar headless.
git clone --depth 1 https://github.com/icefallgames/SCICompanion vendor/SCICompanion
cmake -S tools/scicompile -B tools/scicompile/build
cmake --build tools/scicompile/build -j

Uitvoering

De gebruiker dient zelf een kopie van het spel te leveren. De commando's worden uitgevoerd vanuit src/:

cd src
python3 -m pipeline /pad/naar/spel            # decompile -> analyse -> derive -> patch
python3 -m pipeline /pad/naar/spel --report   # alleen analyse, schrijft niets
python3 -m pipeline /pad/naar/spel --skip-decompile    # hergebruikt IR onder build/ir

De output komt in build/patch/ terecht als loose patch bestanden:

cp build/patch/script.* /kopie/van/spel/    # installeren
rm  /kopie/van/spel/script.0*               # terugdraaien

Projectstructuur

  • src/: De analyse (Python 3, alleen standaardbibliotheek).
  • src/testdata/: Bevroren surfaces voor tests.
  • tools/run_tests.py: De test-runner.
  • tools/drive_scummvm.py: Play-testen van een gepatchte build zonder menselijke interactie.
  • tools/kq6panelprobe.py: Driver script voor KQ6's guard control.
  • tools/sci-tools-fork/: Build-script voor de JSON-IR fork van sci-tools [C#].
  • tools/scicompile/: Headless Linux port van de SCICompanion compiler [C++, GPL-2.0+].
  • docs/: Documentatie over werking, architectuur, testen, status per spel en licenties.
  • vendor/: Gekloond tijdens build-tijd, niet opgenomen in repository.

Toekomstige plannen

  • Vereiste acties: Momenteel worden acties die later in het spel nodig zijn (maar nu genegeerd kunnen worden) niet gemodelleerd.
  • State explosion in Quest For Glory: Deze spellen zijn te complex voor de huidige analyzer. Dit moet mogelijk worden opgelost door spelersstatistieken en combat-elementen te abstraheren.
  • SCI1.0 / SCI1-middle: SCI0 en SCI1.1 zijn gemodelleerd; King's Quest V (een hybride) is in progress op de kq5 branch.
  • Volledige playtesting: De spellen zijn uitgebreid getest waar gepatcht, maar nog niet van begin tot eind gespeeld.
  • Meer spellen: Hoewel de engine generiek is, vereist elk nieuw spel soms uitbreiding van de analyse door spelspecifieke code.
  • AGI: Spellen voor de AGI-engine moeten nog worden toegevoegd.

Licenties

MIT, behalve tools/scicompile/ welke GPL-2.0-or-later is, aangezien dit een afgeleid werk is van SCICompanion. Zie LICENSE, NOTICE en docs/LICENSING.md.