Wir haben uns mit Ben, einem unserer Programmierer, zusammengesetzt, der den ”kryptischen Text”-Fehler behoben hat, der Path of Exile lange Zeit heimgesucht hat. Weil der Fehler selbst einiges Interesse hervorgerufen hat, war Ben so freundlich, für uns eine Post-Mortem-Analyse zu verfassen. Seht sie euch unten an!



Nachdem wir angekündigt hatten, dass wir endlich einen Fix für den berüchtigten “kryptischen Text”-Fehler gefunden hatten, der Spielern in den letzten sechs Jahren immer wieder begegnet ist, hatten einige Spieler Interesse an einer Post-Mortem-Analyse für dieses uralte Problem. Ich wollte schon immer mal einen News-Beitrag über technische Details schreiben, also habe ich diese Gelegenheit genutzt! Der Fehler war unter verschiedenen Namen bekannt und wurde als “durcheinandergewürfelter”, “verderbter” oder “gekrangelter” Text bezeichnet. Intern haben wir ihn meistens “kryptisch” genannt, also werde ich dabei bleiben.

Ich weiß, dass sich der Fehler am 25. April 2016 in die Datenbank geschlichen und es mit der Erweiterung 2.3.0, der Prophecy-Liga, ins Spiel geschafft hat. Die Ursache war eine Neugliederung (Refactoring) der Text-Engine, um die damals bevorstehende Veröffentlichung der Xbox-Version von Path of Exile zu unterstützen.

Die Symptome

Ich bin mir ziemlich sicher, dass die Mehrheit der Spieler diesen Fehler über die Jahre einmal gesehen hat, der meistens nach längerer Spieldauer auftrat, aber einigen Verbannten ist er viel häufiger begegnet als anderen. Er konnte jeden Text im Spiel betreffen und hatte zwei verschiedene Effekte: Zum einen konnte das Kerning, oder der Abstand, zwischen einzelnen Glyphen zu groß oder zu klein sein:



Und zum anderen konnten die einzelnen Buchstaben grafisch durch die falsche Glyphe dargestellt werden:


Einigen scharfäugigen Spielern fiel auf, dass man im zweiten Fall durch die Anwendung einer Substitutionschiffre den ursprünglichen Text wiederherstellen konnte.

Beispiel: Bei “Physischer Sch^den” → “Physischer Schaden”, entspricht ^ in der Zeichentabelle einem “a”.

Eines der Dinge, die uns immer komisch vorkamen war, dass Großbuchstaben einen anderen (oder manchmal gar keinen) Abstand als Kleinbuchstaben hatten.

Die Jagd

Das früheste interne Ticket für diesen Fehler stammt vom 4. Juni 2016 und basierte auf den Fehlermeldungen in den Foren kurz nach der Veröffentlichung von Prophecy. Die größte Hürde war, dass wir nie einen Weg finden konnten, den Fehler zuverlässig auf unseren Rechnern zu reproduzieren und er nur zufällig und selten auftrat. Soweit ich weiß, sind wir ihm auf dem Computer eines Programmierers nur ein- oder zweimal begegnet, was aber der Schlüssel ist, um uns den Speicher anzusehen und Hinweise darauf zu sammeln, was schiefgegangen ist. Bis wir Schritte zur Reproduktion gefunden hatten, waren spekulative Behebungen das Beste, das wir tun konnten (in der Hoffnung, dass die Meldungen danach aufhörten). Da wir nichts finden konnten und es auch kein großartig das Spiel beeinflussender Fehler war, wurde er in der Dringlichkeit heruntergestuft, damit mehr Zeit für neue Inhalte und andere Problembehebungen zur Verfügung stand.

Viele Entwickler (inklusive mir) haben über die Jahre selbst versucht, das Problem zu finden, während jeden Monat mehr und mehr Links zu Fehlerberichten von Spielern dazu kamen, um uns an diesen rätselhaften Fehler zu erinnern. Aufgrund der Berichte, der Screenshots und meiner eigenen Erfahrung wusste ich die folgenden Dinge:

  • Er betraf individuelle Schrift-Stile (eine Kombination aus Schriftart, Größe, und ob sie kursiv/fett war) anstatt bestimmter Text-Darstellungen oder Zeichenfolgen (Strings).
  • Es schien kein Problem der Generierung von Texturen, der Beschädigung von Daten oder des Atlasings zu sein, da keine der Glyphen jemals abgeschnitten oder halbiert dargestellt wurde. Dies konnten wir bei einem seltenen Auftreten des Fehlers auf dem Computer eines Programmierers bestätigen.
  • In den meisten Fällen konnte der Fehler nicht durch Ausloggen, sondern nur durch einen Neustart des Spielclients behoben werden.
  • Mir fiel auf, dass wir nie einen Fehlerbericht für Xbox, PlayStation oder MacOS erhielten, was mir im Endeffekt geholfen hat, das Problem auf einen bestimmten Bereich der Text-Engine einzugrenzen.

Um die Veröffentlichung von Scourge herum bemerkte ich, dass der Fehler häufiger gemeldet wurde und ich begegnete dem Problem selbst öfter während ich spielte. Ich hielt die meisten dieser Vorkommnisse fest, sammelte Bilder von anderen Spielern und begann, ein paar Verdachtsmomente aufzuschreiben, konnte aber immer noch keinen anderen Weg als “spiele eine Weile” finden, um den Fehler zu reproduzieren. Vor einigen Wochen hatte ich zwischendurch ein bisschen Zeit und beschloss, einen weiteren ernsthaften Versuch zu unternehmen und verbrachte ein paar Tage damit, mich in die Text-Engine zu vertiefen, um ihre ganze Komplexität zu verstehen.

Die Behebung

Tief verborgen im Code der Text-Engine fand ich endlich diese Funktion:

SCRIPT_CACHE* ShapingEngineUniscribe::GetFontScriptCache( const Resources::Font& font )
{
    const auto font_resource = font.GetResource()->GetPointer();
    // `font_script_caches` here is a map of `const FontResource*` to `SCRIPT_CACHE` values
    auto it = font_script_caches.find( font_resource );
    if( it == std::end( font_script_caches ) )
        it = font_script_caches.emplace( std::make_pair( font_resource, nullptr ) ).first;
    return &it->second;
}

Für Nicht-Programmierer: Diese Funktion nimmt einen Verweis auf einen bestimmte Schriftart-Ressource und verwendet ihren Speicherort als Schlüssel (Lookup-Wert) für ein SCRIPT_CACHE-Datenobjekt oder erstellt einen neuen Eintrag, falls es noch nicht existiert. Die Funktion liefert dann einen Adressenverweis auf das SCRIPT_CACHE-Objekt, der zulässt, dass der Funktionsaufruf das gespeicherte SCRIPT_CACHE modifizieren kann, anstelle einer Kopie, deren Änderungen nicht in der ‘font_script_caches’-Zeichentabelle bestehen geblieben wären.
In diesem Fall ist das SCRIPT_CACHE-Objekt ein von der Windows Uniscribe-Bibliothek (die wir nur für die Windows-Version des Clients benutzen) verwendeter opaker Datentyp. Die Uniscribe-Dokumentation macht keine Angaben darüber, was dort eigentlich gespeichert wird, nur, dass die Anwendung eines davon für jeden verwendeten “Buchstaben-Stil” behalten muss. Aus den Effekten des “kryptischen Text”-Fehlers konnten wir ableiten, dass es zumindest für den Abstand und die Zuweisung von Buchstaben zu Glyphentexturen verwendet wird.

Auf den ersten Blick scheint diese Funktion etwas völlig sinnvolles zu tun, was wahrscheinlich der Grund dafür ist, dass das Problem jahrelang nicht aufgefallen ist. Das Problem fällt nur auf, wenn man realisiert, dass Ressourcen für Schriftarten von unserem Ressourcen-Manager entfernt werden, wenn sie nicht mehr verwendet werden. Der Fehler tritt auf, wenn eine andere Schrift (andere Schriftart, Stil und/oder Größe) von dem Ressourcen-Manager dem exakt gleichen Speicherort zugewiesen wird, wodurch die neue Schrift den SCRIPT_CACHE der alten wiederverwendet.
Nachdem ich darauf gestoßen war, führte ich eine Reihe von Tests durch, um meine Theorie, dass das der Fehler war, zu bestätigen.

Zu Erzwingen, dass jede Schriftart denselben Script Cache verwendet, führte beim Start des Spiels hierzu:


Hurra! Beide Arten von Symptomen auf einem Bildschirm, was auch bestätigte, dass diese Effekte auf dem gleichen Problem basierten und nicht zwei verschiedene Ursachen hatten. Ausgehend davon konnte ich den Fehler einfach dadurch reproduzieren, indem ich absichtlich so viele Schriften wie möglich lud und löschte, bis eine neue Schrift den Speicherplatz einer alten erhielt:


Da wir das Problem jetzt kannten, gab es mehrere Möglichkeiten es zu beheben: Man könnte das SCRIPT_CACHE-Objekt dem Resource::Font-Objekt zuweisen, den alten SCRIPT_CACHE löschen, wenn die Schrift aus dem Speicher entfernt wird, oder den Lookup-Wert dahingehend ändern, dass er nicht mehr auf der Speicheradresse, sondern auf der Schriftart, der Größe und dem Stil der Schrift basiert, was schließlich das ist, das eine Schrift einzigartig macht. Alle diese Optionen funktionieren, aber jede hat ihre Vor- und Nachteile und sollten danach beurteilt werden, wie sie in das gesamte System passen.

Zusammenfassung

Die Fehlerursache selbst ist gar nicht mal so interessant, aber dafür die Erkenntnis, dass Speicheradressen erneut verwendet werden können und werden, daher muss man vorsichtig sein, falls/wenn man Verweise als Schlüssel benutzt. Dieser Fehler wird mir im Gedächtnis bleiben wegen der besonders merkwürdigen Symptome, der nervenden Suche nach den Ursachen und zu guter Letzt, weil er so lange im Spiel war. Fast fehlt er mir ein bisschen, weil es jetzt ein “großes Rätsel” weniger gibt, mit dem mein Gehirn sich beschäftigen kann. Sieht so aus, als müsste ich einfach ein neues faszinierendes Mysterium finden!

Vielen Dank an alle, die diesen und andere Fehler über die Jahre hinweg gemeldet haben! Das Entwickeln und insbesondere das Debuggen von Software kann manchmal seltsam sein und die kleinsten Dinge können wirklich merkwürdige Fehler produzieren. Detaillierte Fehlermeldungen sind immer sehr wertvoll, um sich ein Bild davon machen zu können, was wohl passiert ist und helfen uns, das Problem zu reproduzieren und Behebungen zu entwickeln und zu testen anstatt im Nebel herumzustochern.


R.I.P T l e e v
Beitrag von 
am
Grinding Gear Games

Beitrag melden

Konto melden:

Meldegrund

Weitere Informationen: