Es gibt viele Artikel über die Entwicklung von Spielen auf Habré, aber unter ihnen gibt es nur sehr wenige Artikel, die sich mit Themen „hinter den Kulissen“ befassen. Eines dieser Themen ist die Organisation der Bereitstellung des Spiels für eine große Anzahl von Benutzern über einen langen Zeitraum (eins, zwei, drei). Trotz der Tatsache, dass die Aufgabe für einige trivial erscheinen mag, habe ich mich entschlossen, meine Erfahrungen mit dem Harken in dieser Angelegenheit für ein bestimmtes Projekt zu teilen. Interessierte - bitte.Ein kleiner Exkurs über die Offenlegung von Informationen. Die meisten Unternehmen sind sehr eifersüchtig, dass die „innere Küche“ der Öffentlichkeit nicht zugänglich wird. Warum - ich weiß nicht, aber was ist - das ist. In diesem speziellen Projekt - The Universim - hatte ich Glück und war der CEO von Crytivo Inc. (ehemals Crytivo Games Inc.) Alex Wallet erwies sich in einer solchen Angelegenheit als absolut vernünftig, sodass ich die Möglichkeit habe, Erfahrungen mit anderen zu teilen.Ein wenig über Patcher an sich
Ich bin schon lange in die Spieleentwicklung involviert. In einigen - als Spieledesigner und Programmierer, in anderen - als Fusion eines Systemadministrators und eines Programmierers (ich mag den Begriff „Devops“ nicht, da er die Essenz der Aufgaben, die ich in solchen Projekten ausführe, nicht genau widerspiegelt).
Ende 2013 (der Schrecken der Zeit) dachte ich darüber nach, den Benutzern neue Versionen (Builds) bereitzustellen. Zu dieser Zeit gab es natürlich viele Lösungen für eine solche Aufgabe, aber der Wunsch, ein Produkt herzustellen, und der Wunsch nach „Fahrradbau“ gewannen. Außerdem wollte ich C # genauer lernen - also habe ich beschlossen, meinen eigenen Patcher zu erstellen. Mit Blick auf die Zukunft werde ich sagen, dass das Projekt ein Erfolg war. Mehr als ein Dutzend Unternehmen haben es genutzt und verwenden es in ihren Projekten. Einige haben darum gebeten, eine Version zu erstellen, die genau ihren Wünschen entspricht.
Die klassische Lösung besteht darin, Delta-Pakete (oder Diffs) von Version zu Version zu erstellen. Dieser Ansatz ist jedoch sowohl für Tester als auch für Entwickler unpraktisch. In einem Fall müssen Sie die gesamte Aktualisierungskette durchlaufen, um die neueste Version des Spiels zu erhalten. Das heißt, Der Spieler muss nacheinander eine bestimmte Datenmenge zusammenführen, die er (a) niemals verwenden wird, und der Entwickler muss auf seinem Server (oder seinen Servern) eine Reihe veralteter Daten speichern, die einige Spieler möglicherweise einmal benötigen.
In einem anderen Fall müssen Sie den Patch für Ihre Version auf den neuesten Stand bringen, aber der Entwickler muss den gesamten Patch-Zoo zu Hause lassen. Einige Implementierungen von Patch-Systemen erfordern bestimmte Software und eine gewisse Logik auf den Servern - was Entwicklern zusätzliche Kopfschmerzen bereitet. Darüber hinaus möchten Spieleentwickler häufig nichts tun, was nicht direkt mit der Entwicklung des Spiels selbst zusammenhängt. Ich werde noch mehr sagen - die meisten sind keine Spezialisten, die Server für die Verteilung von Inhalten konfigurieren können - dies ist einfach nicht ihr Tätigkeitsbereich.
Vor diesem Hintergrund wollte ich eine Lösung finden, die für Benutzer (die schneller spielen und nicht mit Patches verschiedener Versionen tanzen möchten) sowie für Entwickler, die ein Spiel schreiben müssen und nicht herausfinden, was und warum, so einfach wie möglich ist vom nächsten Benutzer nicht aktualisiert.
Da ich wusste, wie einige Datensynchronisationsprotokolle funktionieren - wenn Daten auf dem Client analysiert werden und nur Änderungen vom Server übertragen werden -, habe ich mich für denselben Ansatz entschieden.
Darüber hinaus ändern sich in der Praxis während des gesamten Entwicklungszeitraums von Version zu Version viele Spieledateien geringfügig - die Textur ist vorhanden, das Modell selbst, einige Geräusche.
Infolgedessen schien es logisch, jede Datei im Spielverzeichnis als eine Reihe von Datenblöcken zu betrachten. Wenn die nächste Version veröffentlicht wird, wird der Spielaufbau analysiert, eine Blockkarte erstellt und die Spieledateien selbst werden Block für Block komprimiert. Der Client analysiert vorhandene Blöcke und nur die Differenz wird heruntergeladen.
Ursprünglich war der Patcher als Modul in Unity3D geplant, es trat jedoch ein unangenehmes Detail auf, das mich dazu veranlasste, dies zu überdenken. Tatsache ist, dass Unity3D eine Anwendung (Engine) ist, die völlig unabhängig von Ihrem Code ist. Und während die Engine läuft, sind eine ganze Reihe von Dateien geöffnet, was zu Problemen führt, wenn Sie sie aktualisieren möchten.
In Unix-ähnlichen Systemen stellt das Überschreiben einer geöffneten Datei (sofern sie nicht ausdrücklich blockiert ist) keine Probleme dar. Unter Windows funktioniert eine solche „Finte mit Ohren“ jedoch nicht, ohne mit einem Tamburin zu tanzen. Aus diesem Grund habe ich den Patcher als separate Anwendung erstellt, die nur Systembibliotheken lädt. De facto stellte sich heraus, dass der Patcher selbst ein von der Unity3D-Engine völlig unabhängiges Dienstprogramm war, das jedoch nicht verhinderte, ihn dem Unity3D-Speicher hinzuzufügen.
Patcher-Algorithmus
Daher veröffentlichen Entwickler neue Versionen mit einer bestimmten Häufigkeit. Spieler möchten, dass diese Versionen erhalten. Das Ziel des Entwicklers ist es, diesen Prozess mit minimalen Kosten und minimalen Kopfschmerzen für die Spieler bereitzustellen.
Vom Entwickler
Bei der Vorbereitung eines Patches sieht der Algorithmus für die Aktionen des Patchers folgendermaßen aus:
○ Erstellen Sie einen Spieledateibaum mit ihren Attributen und SHA512-Prüfsummen
○ Für jede Datei:
► Teilen Sie den Inhalt in Blöcke.
► Speichern Sie die SHA256-Prüfsumme.
► Komprimieren Sie einen Block und fügen Sie ihn der Dateiblockzuordnung hinzu.
► Speichern Sie die Blockadresse im Index.
○ Speichern Sie den Dateibaum mit ihren Prüfsummen.
○ Speichern Sie die Versionsdatendatei.
Der Entwickler muss die empfangenen Dateien auf den Server hochladen.
Spielerseite
Auf dem Client führt der Patcher Folgendes aus:
○ Kopiert sich in eine Datei mit einem anderen Namen. Dadurch wird die ausführbare Datei des Patchers bei Bedarf aktualisiert. Dann wird die Kontrolle auf die Kopie übertragen und das Original fertiggestellt.
○ Laden Sie die Versionsdatei herunter und vergleichen Sie sie mit der lokalen Versionsdatei.
○ Wenn der Vergleich keinen Unterschied ergab - Sie können spielen, wir haben die neueste Version. Wenn es einen Unterschied gibt, fahren Sie mit dem nächsten Punkt fort.
○ Laden Sie einen Dateibaum mit ihren Prüfsummen herunter.
○ Für jede Datei im Baum vom Server:
► Wenn eine Datei vorhanden ist, wird deren Prüfsumme (SHA512) berücksichtigt. Wenn nicht, betrachtet es es als leer, d. H. Besteht aus durchgezogenen Nullen und betrachtet auch seine Prüfsumme.
► Wenn die Summe der lokalen Datei nicht mit der Prüfsumme der Datei aus der neuesten Version übereinstimmt:
► Erstellt eine lokale Blockzuordnung und vergleicht sie mit der Blockzuordnung vom Server.
► Für jeden lokalen Block, der sich vom Remote-Block unterscheidet, wird ein komprimierter Block vom Server heruntergeladen und lokal überschrieben.
○ Wenn keine Fehler vorliegen, wird die Versionsdatei aktualisiert.
Ich habe die Datenblockgröße auf ein Vielfaches von 1024 Bytes festgelegt. Nach einer bestimmten Anzahl von Tests entschied ich, dass es einfacher ist, mit 64-KB-Blöcken zu arbeiten. Obwohl die Universalität im Code erhalten bleibt:
#region DQPatcher class public class DQPatcher {
Wenn Sie die Blöcke klein machen, benötigt der Client weniger Änderungen, wenn die Änderungen selbst gering sind. Es tritt jedoch ein anderes Problem auf - die Größe der Indexdatei nimmt umgekehrt mit der Abnahme der Blockgröße zu - d.h. Wenn wir mit Blöcken von 8 KB arbeiten, ist die Indexdatei achtmal größer als bei Blöcken von 64 KB.
Ich habe SHA256 / 512 für Dateien und Blöcke aus folgenden Gründen ausgewählt: Die Geschwindigkeit unterscheidet sich geringfügig von der (veralteten) MD5 / SHA128, aber Sie müssen immer noch Blöcke und Dateien lesen. Die Wahrscheinlichkeit von Kollisionen mit SHA256 / 512 ist erheblich geringer als mit MD5 / SHA128. Ganz langweilig zu sein - es ist in diesem Fall, aber es ist so klein, dass diese Wahrscheinlichkeit vernachlässigt werden kann.
Zusätzlich berücksichtigt der Kunde die folgenden Punkte:
► Datenblöcke können in verschiedenen Versionen verschoben werden, d. H. lokal haben wir Blocknummer 10 und auf dem Server haben wir Blocknummer 12 oder umgekehrt. Dies wird berücksichtigt, um keine zusätzlichen Daten herunterzuladen.
► Blöcke werden nicht einzeln, sondern in Gruppen angefordert. Der Client versucht, die Bereiche der erforderlichen Blöcke zu kombinieren, und fordert sie mithilfe des Bereichskopfs vom Server an. Dies minimiert auch die Serverlast:
Es stellte sich natürlich heraus, dass der Client jederzeit unterbrochen werden kann und beim anschließenden Start de facto seine Arbeit fortsetzt und nicht alles von Grund auf neu herunterlädt.
Hier können Sie sich ein Video ansehen, das die Arbeit des Patchers am Beispielprojekt Angry Bots veranschaulicht:
Informationen zur Organisation des Patches für das Spieluniversum
Im September 2015 kontaktierte mich Alex Koshelkov und bot mir an, dem Projekt beizutreten. Sie brauchten eine Lösung, die 30.000 (mit einem Schwanz) Spielern monatliche Updates bietet. Die anfängliche Größe des Spiels im Archiv beträgt 600 Megabyte. Bevor ich mich kontaktierte, gab es Versuche, mit Electron eine eigene Version zu erstellen, aber alles stieß auf das gleiche Problem mit geöffneten Dateien (die aktuelle Version von Electron kann dies übrigens) und einige andere. Außerdem verstand keiner der Entwickler, wie das alles funktionieren würde - sie stellten mir mehrere Fahrraddesigns zur Verfügung, der Serverteil fehlte insgesamt - sie wollten es tun, nachdem alle anderen Aufgaben gelöst waren.
Außerdem musste das Problem der Verhinderung des Verlusts von Spielerschlüsseln gelöst werden. Tatsache ist, dass die Schlüssel für die Steam-Plattform bestimmt waren, obwohl das Spiel selbst auf Steam noch nicht öffentlich verfügbar war. Das Verteilen des Spiels war streng nach dem Schlüssel erforderlich - obwohl die Möglichkeit bestand, dass die Spieler den Spielschlüssel mit Freunden teilen konnten, konnte dies vernachlässigt werden, da der Schlüssel nur einmal aktiviert werden konnte, wenn das Spiel auf Steam erschien.
In der normalen Version des Patchers sieht der Datenbaum für den Patch folgendermaßen aus:
./
| - Linux
| | - 1.0.0
| `- version.txt
| - macosx
| | - 1.0.0
| `- version.txt
`- Fenster
| - 1.0.0
`- version.txt
Ich musste sicherstellen, dass nur diejenigen mit dem richtigen Schlüssel Zugriff hatten.
Ich habe die folgende Lösung gefunden - für jeden Schlüssel erhalten wir seinen Hash (SHA1), dann verwenden wir ihn als Pfad, um auf die Patch-Daten auf dem Server zuzugreifen. Auf dem Server übertragen wir die Patch-Daten auf eine höhere Ebene als die Docroot und fügen dem Verzeichnis Symlinks mit den Patch-Daten in der Docroot selbst hinzu. Symbolische Links haben dieselben Namen wie Schlüssel-Hashes, die nur in mehrere Ebenen unterteilt sind (um den Betrieb des Dateisystems zu erleichtern), d. H. Der Hash 0f99e50314d63c30271 ... ... ade71963e7ff wird als dargestellt
./0f/99/e5/0314d63c30271.....ade71963e7ff -----> / full / path / to / patch-data /
Daher ist es nicht erforderlich, die Schlüssel selbst an jemanden zu verteilen, der die Update-Server unterstützt - es reicht aus, ihre Hashes zu übertragen, die für die Spieler selbst absolut nutzlos sind.
Um neue Schlüssel hinzuzufügen (oder alte zu löschen), fügen Sie einfach den entsprechenden symbolischen Link hinzu / entfernen Sie ihn.
Bei dieser Implementierung wird die Überprüfung des Schlüssels selbst offensichtlich nirgendwo durchgeführt. Der Empfang von 404-Fehlern auf dem Client zeigt an, dass der Schlüssel falsch ist (oder deaktiviert wurde).
Es ist zu beachten, dass der Schlüsselzugriff kein vollwertiger DRM-Schutz ist - dies sind lediglich Einschränkungen in der Phase des (geschlossenen) Alpha- und Betatests. Und die Suche kann leicht über den Webserver selbst unterbrochen werden (zumindest in Nginx, das ich verwende).
Im Startmonat wurden allein am ersten Tag nur 2,5 TB Verkehr geliefert, und an den folgenden Tagen wird durchschnittlich ungefähr die gleiche Menge pro Monat verteilt:

Wenn Sie vorhaben, viele Inhalte zu verbreiten, sollten Sie daher im Voraus berechnen, wie viel es Sie kosten wird. Nach persönlichen Beobachtungen - der billigste Verkehr von europäischen Hostern, der teuerste (ich würde sagen "Gold") von Amazon und Google.
In der Praxis sind die durchschnittlichen Verkehrseinsparungen pro Jahr bei The Universim enorm - vergleichen Sie die obigen Zahlen. Wenn der Benutzer überhaupt kein Spiel hat oder es sehr veraltet ist, geschieht natürlich kein Wunder und er muss viele Daten vom Server herunterladen - wenn von Grund auf neu, dann etwas mehr, als das Spiel im Archiv benötigt. Mit monatlichen Updates wird es jedoch richtig gut. In weniger als 6 Monaten gab der amerikanische Spiegel etwas mehr als 10 TB Verkehr, ohne die Verwendung eines Patchers wäre dieser Wert erheblich gestiegen.
So sieht der jährliche Verkehr des Projekts aus:

Ein paar Worte zu dem denkwürdigsten "Rechen", den wir bei der Arbeit an einem benutzerdefinierten Patcher für das Spiel "The Universim" benötigen:
● Das größte Problem war, mich von den Antivirenprogrammen zu erwarten. Nun, sie mögen keine Anwendungen, die dort etwas aus dem Internet herunterladen, Dateien (einschließlich ausführbarer Dateien) ändern und dann auch versuchen, die heruntergeladenen auszuführen. Einige Virenschutzprogramme blockierten nicht nur den Zugriff auf lokale Dateien, sondern blockierten auch die Aufrufe des Update-Servers selbst und gelangten direkt in die vom Client heruntergeladenen Daten. Die Lösung bestand darin, eine gültige digitale Signatur für den Patcher zu verwenden - dies reduziert die Paranoia von Antivirenprogrammen drastisch und die Verwendung des HTTPS-Protokolls anstelle von HTTP -, um einige der Fehler, die mit der Neugier von Antivirenprogrammen verbunden sind, schnell zu beseitigen.
● Fortschrittsaktualisierung. Viele Benutzer (und Kunden) möchten den Aktualisierungsfortschritt sehen. Man muss improvisieren, da es nicht immer möglich ist, zuverlässig Fortschritte zu zeigen, ohne zusätzliche Arbeit leisten zu müssen. Ja, und der genaue Zeitpunkt des Endes des Patch-Vorgangs kann ebenfalls nicht angezeigt werden, da der Patcher selbst keine Daten darüber hat, welche Dateien im Voraus aktualisiert werden müssen.
● Eine große Anzahl von Benutzern aus den USA hat eine nicht sehr hohe Verbindungsgeschwindigkeit zu Servern aus Europa. Die Migration des Update-Servers in die USA hat dieses Problem behoben. Für Benutzer anderer Kontinente haben wir den Server in Deutschland verlassen. Übrigens ist der Verkehr in den USA in einigen Fällen viel teurer als in Europa - mehrere Dutzend Mal.
● Apple ist mit dieser Methode zur Installation von Anwendungen nicht sehr vertraut. Offizielle Richtlinie - Anwendungen sollten nur in ihrem Geschäft installiert werden. Das Problem ist jedoch, dass Anwendungen in der Alpha- und Betatestphase im Store nicht zulässig sind. Darüber hinaus gibt es nichts zu sagen über den Verkauf von Rohanwendungen aus dem frühen Zugriff. Daher müssen Sie Anweisungen schreiben, wie man auf Mohnblumen tanzt. Die Option mit AppAnnie (damals waren sie noch unabhängig) wurde aufgrund der Begrenzung der Anzahl der Tester nicht berücksichtigt.
● Die Vernetzung ist ziemlich unvorhersehbar. Damit die Bewerbung nicht sofort aufgab, musste ich einen Fehlerzähler eingeben. Mit 9 abgefangenen Ausnahmen können Sie dem Benutzer klar mitteilen, dass er Probleme mit dem Netzwerk hat.
● 32-Bit-Betriebssysteme haben Einschränkungen hinsichtlich der Größe der Dateien, die im Speicher (Memory Mapped Files - MMF) für jeden Ausführungsthread und für den gesamten Prozess angezeigt werden. Die ersten Versionen des Patchers verwendeten MMF, um die Arbeit zu beschleunigen, aber da die Dateien mit Spielressourcen sehr groß sein können, musste ich diesen Ansatz aufgeben und normale Dateistreams verwenden. Ein besonderer Leistungsverlust wurde übrigens nicht beobachtet - höchstwahrscheinlich aufgrund des proaktiven Lesens des Betriebssystems.
● Sie müssen darauf vorbereitet sein, dass Benutzer sich beschweren. Egal wie gut Ihr Produkt sein mag, es wird immer diejenigen geben, die nicht zufrieden sind. Und je mehr Benutzer Ihres Produkts (im Fall von The Universim gibt es derzeit mehr als 50.000), desto quantitativer werden Sie beschwert. In Prozent ausgedrückt ist dies eine sehr kleine Zahl, aber in quantitativer Hinsicht ...
Trotz der Tatsache, dass das Projekt insgesamt ein Erfolg war, hat es einige Nachteile:
● Obwohl ich anfangs die gesamte Hauptlogik separat herausgenommen habe, unterscheidet sich der GUI-Teil in der Implementierung für MAC und Windows. Die Linux-Version verursachte keine Probleme - alle Probleme traten hauptsächlich bei Verwendung eines monolithischen Builds auf, für den die Mono Runtime Environment - MRE nicht erforderlich war. Da Sie jedoch eine zusätzliche Lizenz benötigen, um solche ausführbaren Dateien zu verteilen, wurde beschlossen, monolithische Builds aufzugeben und einfach MRE zu benötigen. Die Linux-Version unterscheidet sich von der Windows-Version nur durch die Unterstützung von Dateiattributen, die für * nix-Systeme spezifisch sind. Für mein zweites Projekt, das mehr als nur ein Patcher sein wird, plane ich einen modularen Ansatz in Form eines Kernel-Prozesses, der im Hintergrund ausgeführt wird und die Verwaltung aller Elemente auf der lokalen Schnittstelle ermöglicht. Und die Steuerung selbst kann von einer auf Electron und dergleichen basierenden Anwendung (oder einfach von einem Browser) ausgeführt werden. Mit jedem kleinen Ding. Bevor Sie über die Größe der Verteilung solcher Anwendungen sprechen, schauen Sie sich die Größe der Spiele an. Demoversionen (!!!) von einigen belegen 5 oder mehr Gigabyte im Archiv (!!!).
● Die jetzt verwendeten Strukturen sparen keinen Platz, wenn das Spiel für 3 Plattformen veröffentlicht wird. De facto müssen Sie 3 Kopien nahezu identischer Daten aufbewahren, wenn auch komprimiert.
● Die aktuelle Version des Patchers speichert seine Arbeit nicht zwischen - jedes Mal, wenn alle Prüfsummen aller Dateien neu berechnet werden. Es wäre möglich, die Zeit erheblich zu verkürzen, wenn der Patcher die Ergebnisse für die Dateien zwischenspeichert, die sich bereits auf dem Client befinden. Es gibt jedoch ein Dilemma: Wenn die Datei beschädigt ist (oder fehlt), der Cache-Eintrag für diese Datei jedoch gespeichert wird, überspringt der Patcher ihn, was zu Problemen führt.
● Die aktuelle Version kann nicht gleichzeitig mit mehreren Servern arbeiten (es sei denn, Sie machen Round-Robin mit DNS). Ich möchte auf eine "Torrent-ähnliche" Technologie umsteigen, damit Sie mehrere Server gleichzeitig verwenden können. Es kommt nicht in Frage, Clients als Datenquelle zu verwenden, da dies viele rechtliche Probleme aufwirft und es einfacher ist, dies von Anfang an abzulehnen.
● Wenn Sie den Zugriff auf Updates einschränken möchten, muss diese Logik unabhängig implementiert werden. Eigentlich kann dies kaum als Nachteil bezeichnet werden, da jeder seine eigenen Wünsche bezüglich der Einschränkungen haben kann. Die einfachste Schlüsselbeschränkung - ohne Serverteil - ist ziemlich einfach, wie ich oben gezeigt habe.
● Ein Patcher wird jeweils nur für ein Projekt erstellt. Wenn Sie etwas Ähnliches wie Steam erstellen möchten, ist bereits ein vollständiges System zur Bereitstellung von Inhalten erforderlich. Und das ist ein Projekt auf einer ganz anderen Ebene.
Ich plane, den Patcher nach der Implementierung der „zweiten Generation“ selbst öffentlich zugänglich zu machen - ein System zur Bereitstellung von Spielinhalten, das nicht nur den weiterentwickelten Patcher, sondern auch ein Telemetriemodul enthält (da Entwickler wissen müssen, was die Spieler tun). Cloud speichert das Modul und einige andere Module.
Wenn Sie ein gemeinnütziges Projekt haben und einen Patcher benötigen, schreiben Sie mir die Details zu Ihrem Projekt und ich werde Ihnen eine kostenlose Kopie geben. Hier gibt es keine Links, da dies nicht der "I PR" -Hub ist.
Gerne beantworte ich Ihre Fragen.