[Projekt] Extracting Ace Combat

Hier könnt ihr euch selbst, eure Homepage, euren Entwicklerstammtisch, Termine oder eure Projekte vorstellen.
Forumsregeln
Bitte Präfixe benutzen. Das Präfix "[Projekt]" bewirkt die Aufnahme von Bildern aus den Beiträgen des Themenerstellers in den Showroom. Alle Bilder aus dem Thema Showroom erscheinen ebenfalls im Showroom auf der Frontpage. Es werden nur Bilder berücksichtigt, die entweder mit dem attachement- oder dem img-BBCode im Beitrag angezeigt werden.

Die Bildersammelfunktion muss manuell ausgeführt werden, die URL dazu und weitere Details zum Showroom sind hier zu finden.

This forum is primarily intended for German-language video game developers. Please don't post promotional information targeted at end users.
Antworten
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

[Projekt] Extracting Ace Combat

Beitrag von Krishty »

In nostalgischer Verstimmung extrahiere ich manchmal Assets aus alten PlayStation-Spielen. Ich schrieb hier bereits über meine Experimente mit Driver und Driver 2; nun kommt was zur Ace Combat-Reihe.

Ace Combat begann in den 90ern als PlayStation-Portierung eines Arcade-Flugsimulators nach Top Gun-Manier. Kein Realitätsanspruch, hauchdünne Story, technisch dilettantisch. Hat aber mächtig Spaß gemacht, nicht zuletzt dank des großartigen Rock-Soundtracks. Teil 2 wurde flux nachgeschoben – umfangreicher, technisch ausgereift, spielerisch wunderbar abwechslungsreich … und der Soundtrack war sogar noch besser!

Mit Ace Combat 3: Electrosphere haben sie Neuland betreten: Cyberpunk. Dystopie der Konzerne, Gedankenkontrolle, Nano-Viren. Total futuristischer Look und Elektronik statt Rock. Namco investierte Millionen in eine epische Storyline mit hunderten Anime-Sequenzen. Bei der Übersetzung für den US- und EU-Markt wurde all das weggelassen; zurück blieben völlig zusammenhanglose Missionen ohne Sinn und Zweck. Damals enttäuschte das Spiel; rückwirkend nennen es aber viele als das Beste der Reihe.

Ich nun die CDs von Ace Combat 1–3 in verschiedenen Regionalcodes vor mir liegen und will mal sehen, was ich tun kann.
Ace Combat 3 EU disk content.png


Previous Work

Rad neu erfinden ist scheiße. Also gucken wir doch einfach mal, was bisher so getan wurde.

Die PlayStation-Spiele sind fast 20 Jahre alt und nie für den PC erschienen, es gibt also keine Modding Community. Es scheint überhaupt keine Community zu geben.

Aber dann ist da USEA Today – ein kleines Grüppchen (scheinbar Italiener und Portugisen), das sich vorgenommen hat, der japanischen Originalversion von Ace Combat 3 – ich erwähnte, dass die US- und EU-Version stark beschnitten war – übersetzte Untertitel zu verpassen. Und sie haben’s – wie standen die Chancen? – fast geschafft!
[youtube]-IEXFV8EGLs[/youtube]
Möglich war das übrigens nur, weil die Texte als Bitmaps vorliegen (also als Texturen statt in Textform).

Ich stehe nicht in Kontakt mit der Gruppe (unsere Ziele mögen auch unterschiedlich sein), aber vieles hier baut auf ihrer Arbeit auf. Insbesondere muss ich Gipphe danken, dem ehemaligen Coder der Gruppe. Leider ist seine Webseite offline, und damit auch alle seine Beiträge über die Datenstrukturen. Was verblieben ist, muss ich mir mühsam von ROMhacking zusammensuchen.

Außerdem hat jemand Texturen aus Ace Combat 2 extrahiert – auf die Old-School-Art: Emulator starten, Spiel laden, und den VRAM nach Texturen durchsuchen. Wie das besser geht, erzähle ich nächstes Mal. Der Link ist mittlerweile tot, aber hier hat jemand eine Kopie.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Schrompf
Moderator
Beiträge: 4831
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: [Projekt] Extracting Ace Combat

Beitrag von Schrompf »

Cool. Du hast immer so whacky Hobbies. Viel Erfolg.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
joggel

Re: [Projekt] Extracting Ace Combat

Beitrag von joggel »

Keine Ahnung was Du machst, aber wird schon cool sein.
Respekt und so... :)
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [Projekt] Extracting Ace Combat

Beitrag von Krishty »

Danke!


Hier ist der Inhalt der Ace Combat 2-CD:
Ace Combat 2 EU disk content.png

Er ähnelt sehr den AC3-Daten im ersten Beitrag: zwei große Dateien, und ein paar kleine.
  • SCES_009.02 ist die EXE, die das Spiel bootet.
  • SYSTEM.CNF enthält Einstellungen zu Stack-Größe usw., die das BIOS nutzt, um das Spiel zu starten.
  • ACES2.STH enthält eine Handvoll unbekannter Binärdaten.
  • Natürlich sind wir an den beiden großen Dateien – ACE2.DAT und ACE2.STP – interessiert. Die enthalten eigentliche Spieldaten.
Kurze technische Exkursion: Warum liegen die Spieldaten eigentlich in dicken Archiven?

Weil CDs langsam sind und es äußerst wichtig ist, die Daten auf der CD derart zu organisieren, dass der Lesekopf wenig bewegt werden muss. Das gilt natürlich für alle statischen Daten, um die Ladezeiten vor jedem Level kurz zu halten. Das gilt aber noch mehr für Streaming-Daten, die im laufenden Betrieb gelesen werden müssen, während das CD-Laufwerk bereits Hintergrundmusik liest und die CPU mit Spielmechanik ausgelastet ist. Man findet deshalb oft große Archive vor, die Daten nach Spielsituation sortiert bündeln und an Adressen ablegen, die direkt durch den Lesekopf angesprungen werden können. Im Fall von Ace Combat (2 wie auch 3) enthält ein Archiv die Streaming-Daten (Levelgeometrie, Soundeffekte) und ein anderes statische Daten (Schrift-Texturen, Videosequenzen, Musik).


Ich sagte zuvor, dass man nicht zwingend das Spiel in einen Emulator laden und den VRAM durchforsten muss, um an Texturen zu kommen. Wir schaffen das auch offline.

Das Standardtexturformat der PSX ist TIM. Die meisten Tools des PSX-SDKs arbeiten mit diesem Format, und es ist direkt auf die PSX zugeschnitten. Nicht jedes Spiel nutzt dieses Format, aber die Wahrscheinlichkeit ist recht hoch.

Also durchforsten wir einfach alle Archive nach der Magic Number 10 00 00 00. Das bewirkt natürlich viele False Positives, weil die Zahl in beliebigen Binärdaten nicht gerade selten auftaucht. Zum Glück habe ich aber meinen TIM-Loader zur Hand. Wenn 10 00 00 00 auftaucht, versucht der, die folgenden Bytes auf gut Glück als TIM zu lesen. Sobald ein Problem auftaucht, bricht er ab. Sonst kopiert er die Datei. Nach einigen Sekunden erhalten wir 13.141 Texturen:
Ace Combat 2 TIMs.png
(Wer kein eigenes Programm schreiben will: Tim2View kann beliebige Dateien nach TIMs scannen. Allerdings hat es bei mir große Probleme mit den Paletten.)

Sieht vielversprechend aus. Um die auswerten zu können, muss man aber eine PSX-Eigenheit kennen: das ewige Wechseln der Paletten.

Früher war Speicher knapp usw. Die PSX arbeitet optimal mit palettierten Texturen (16 oder 256 Farben). Um die Grafik weiter zu verbessern, und den Platz effizienter auszunutzen, hat man Bildern erlaubt, mehr als eine Palette zu nutzen. TIMs speichern einfach mehrere Paletten und das Programm wählt je nach Situation die beste Palette aus.

Die Textur links oben ist in der Vorschau schwarz. Gleichzeitig ist sie aber die wichtigste Textur des Spiels, weil sie alle Schriftarten des Hauptmenüs enthält. Schalten wir alle ihre Paletten durch:
Bild

Die 6. Textur enthält alle HUD-Symbole in allen möglichen Farben und Helligkeiten, sowie Animationen der Symbole (über 40 Paletten!):
Bild

Flugzeuge werden üblicherweise mit einer Handvoll Texturen versehen:
Bild
Hier sehen wir (doppelte Größe) vier Paletten:
  1. normale Tarnfarben (damit wird der Großteil des Flugzeugs gerendert)
  2. eine Feuertextur für den Triebwerksinnenraum, die Bordkanone, und den Nachbrenner
  3. eine Specular/Reflection Map
  4. eine unfertige Palette, die mal eine zweite Tarnfarbe werden sollte?
Die Information, welcher Bereich zu welcher Palette gehört, ist übrigens nicht in der Datei enthalten. Man kann also nicht automatisiert eine „richtige“ Textur erzeugen. Diese Daten liegen entweder im Modell oder sind fest im Programm verdrahtet, wir werden’s sehen.


Eine noch:

Bild

Dies ist die fünfte Textur im Spiel, und sie wird scheinbar zusammen mit den Menüs geladen. Das könnte das Überbleibsel eines Editors sein, aber für mich sieht es eher nach einem Labyrinth-Mini-Game aus. (Ace Combat 1 enthielt ein Mini-Game, das sich während des Ladebildschirms freischalten lies.) Jedenfalls habe ich in keinen Cheat-Sammlungen Hinweise darauf gefunden, und abgesehen von den Entwicklern sind wir vielleicht die ersten, die auf dieses Easter Egg blicken :)


Leider lassen sich keine unentdeckten Levels oder Flugzeuge finden. Keine großen Überraschungen. Schade!


Das war ein nettes, einfaches Aufwärmen. Nächstes Mal geht’s mit AC3 weiter.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Chromanoid
Moderator
Beiträge: 4252
Registriert: 16.10.2002, 19:39
Echter Name: Christian Kulenkampff
Wohnort: Lüneburg

Re: [Projekt] Extracting Ace Combat

Beitrag von Chromanoid »

Sehr unterhaltsam aufgebaut, dein Videospiel-Archäologie-Bericht :) Ich bin schon gespannt auf die nächsten Einträge :ugeek: :D
GameDevR
Beiträge: 63
Registriert: 27.06.2014, 10:16
Wohnort: Wien

Re: [Projekt] Extracting Ace Combat

Beitrag von GameDevR »

Obwohl ich die Reihe nur vom Hörensagen her kenne und selbst nie aktiv gezockt habe, finde ich deine Beiträge dazu verdammt interessant zu lesen! :)
Auch zu finden auf: Pewn.de | itch.to | Game Jolt | Twitter | YouTube
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [Projekt] Extracting Ace Combat

Beitrag von Krishty »

Freut mich, dass es euch unterhält. Für mich ist das wie ein Puzzle, aber eben mit 1,4 Milliarden Bytes :) Außerdem kann ich schön was über Hardware und Optimierung lernen.


Ace Combat 2s Texturen konnten wir durch einfache Header-Suche in den Spieldateien extrahieren. Bei Ace Combat 3 ist das schwieriger.


Ace Combat 3 wurde 1999 veröffentlicht – fünf Jahre nach Entwicklungsbeginn von Ace Combat und kurz vor Veröffentlichung der PlayStation 2. Spiele, die so spät erschienen, sind naturgemäß besser optimiert (weil bei Entwicklung mehr über die Besonderheiten der Konsole bekannt war). Auf USEA Today kann man einen Eindruck von der Grafik gewinnen, mit Specular Reflections, Wolken, Nebel – in 34 MHz.
Bild
(Alles geklaut von DragonSpikeXIIIs Picasa)

Das hat einige Konsequenzen. Beispielsweise schreibt AC3 bewusst über Textur-Caches hinaus, was Emulatoren Kopfzerbrechen bereitet. Vor allem aber ist die Datenmenge nur in Echtzeit von der CD auf den Bildschirm zu kriegen, indem man Kompression anwendet.


Die Kompression haben glücklicherweise schon andere geknackt. Zunächst hat CUE unpac geschrieben, das die allgemeine Archivstruktur von .BPB und .SPB aufschlüsselt. Ich finde meine Quelle nicht mehr, also poste ich den Quelltext in Gänze.

Code: Alles auswählen

/*----------------------------------------------------------------------------*/
/*-- unpac.c                                                                --*/
/*-- Unpacker for 'Ace Combat 3' - Play Station                             --*/
/*-- Copyright (C) 2012 CUE                                                 --*/
/*--                                                                        --*/
/*-- This program is free software: you can redistribute it and/or modify   --*/
/*-- it under the terms of the GNU General Public License as published by   --*/
/*-- the Free Software Foundation, either version 3 of the License, or      --*/
/*-- (at your option) any later version.                                    --*/
/*--                                                                        --*/
/*-- This program is distributed in the hope that it will be useful,        --*/
/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of         --*/
/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the           --*/
/*-- GNU General Public License for more details.                           --*/
/*--                                                                        --*/
/*-- You should have received a copy of the GNU General Public License      --*/
/*-- along with this program. If not, see <http://www.gnu.org/licenses/>.   --*/
/*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>

/*----------------------------------------------------------------------------*/
#define FILE_HDR "ACE.BPH"
#define FILE_BIN "ACE.BPB"
#define FOLDER   "BPB"

#define MIN_FILESIZE   0x0000001C
#define MAX_FILESIZE   0x08000000

/*----------------------------------------------------------------------------*/
#define EXIT(text) { printf(text); exit(EXIT_FAILURE); }

/*----------------------------------------------------------------------------*/
void  Title(void);
int   FileSize(char *filename);
char *FileLoad(char *filename);
void  FileSave(char *filename, char *buffer, int length);
char *Memory(int length, int size);
void  Folder(char *path);
void  UnPac(char *path, int count, char *buffer, int size);

/*----------------------------------------------------------------------------*/
int main(void) {
  char *buffer_hdr, *buffer_bin;
  int   num_entries, offset, length;
  int   i;

  Title();

  buffer_hdr = FileLoad(FILE_HDR);
  buffer_bin = FileLoad(FILE_BIN);

  num_entries = *(int *)(buffer_hdr + 16);
  for (i = 0; i < num_entries; i++) {
    offset = *(int *)(buffer_hdr + 20 + i * 8 + 4) * 0x800;
    if (i < num_entries - 1)
      length = *(int *)(buffer_hdr + 20 + i * 8 + 12) * 0x800 - offset;
    else
      length = FileSize(FILE_BIN) - offset;

    UnPac(FOLDER, i, buffer_bin + offset, length);
  }    

  free(buffer_bin);
  free(buffer_hdr);

  printf("\nDone\n");

  exit(EXIT_SUCCESS);
}

/*----------------------------------------------------------------------------*/
void Title(void) {
  printf(
    "\n"
    "UNPAC - Copyright (C) 2012 CUE\n"
    "Unpacker for 'Ace Combat 3' - Play Station\n"
    "\n"
  );
}

/*----------------------------------------------------------------------------*/
int FileSize(char *filename) {
  FILE *fp;
  int   fs;

  if ((fp = fopen(filename, "rb")) == NULL) EXIT("File open error\n");
  fs = filelength(fileno(fp));
  if (fclose(fp) == EOF) EXIT("File close error\n");

  return(fs);
}

/*----------------------------------------------------------------------------*/
char *FileLoad(char *filename) {
  FILE *fp;
  int   fs;
  char *fb;

  if ((fp = fopen(filename, "rb")) == NULL) EXIT("File open error\n");
  fs = filelength(fileno(fp));
  if (fs < MIN_FILESIZE) EXIT("File too short\n");
  if (fs > MAX_FILESIZE) EXIT("File too big\n");
  fb = Memory(fs, sizeof(char));
  if (fread(fb, 1, fs, fp) != fs) EXIT("File read error\n");
  if (fclose(fp) == EOF) EXIT("File close error\n");

  return(fb);
}

/*----------------------------------------------------------------------------*/
void FileSave(char *filename, char *buffer, int length) {
  FILE *fp;

  Folder(filename);

  if ((fp = fopen(filename, "wb")) == NULL) EXIT("File create error\n");
  if (fwrite(buffer, 1, length, fp) != length) EXIT("File write error\n");
  if (fclose(fp) == EOF) EXIT("File close error\n");
}

/*----------------------------------------------------------------------------*/
char *Memory(int length, int size) {
  char *fb;

  fb = (char *)calloc(length, size);
  if (fb == NULL) EXIT("Memory error\n");

  return(fb);
}

/*----------------------------------------------------------------------------*/
void Folder(char *path) {
  int i;

  for (i = 0; path[i]; i++) {
    if ((path[i] == '/') || (path[i] == '\\')) {
      path[i] = 0;
      if (mkdir(path) > 0) EXIT("Create folder error\n");
      path[i] = '/';
    }
  }
}

/*----------------------------------------------------------------------------*/
void UnPac(char *path, int count, char *buffer, int size) {
  char name[256];
  int  header0, header1;
  int  num_entries, offset, length;
  int  i;

  header0 = *(int *)(buffer + 0);
  header1 = *(int *)(buffer + 4);

  if ((header0 + 1) << 2 == header1) {
    num_entries = header0;
    for (i = 0; i < num_entries; i++) {
      offset = *(int *)(buffer + (i + 1) * 4);
      if (i < num_entries - 1)
        length = *(int *)(buffer + (i + 2) * 4) - offset;
      else
        length = size - offset;  

      sprintf(name, "%s/%04d", path, count);
      UnPac(name, i, buffer + offset, length);
    }
  } else {
    switch (header0) {
      case 0x00000010: sprintf(name, "%s/%04d.tim", path, count); break;
      case 0x1A7A6C55: sprintf(name, "%s/%04d.ulz", path, count); break;
      default:         sprintf(name, "%s/%04d.dat", path, count); break;
    }
    printf("- '%s'\n", name);
    FileSave(name, buffer, size);
  }
}

/*----------------------------------------------------------------------------*/
/*--  EOF                                           Copyright (C) 2012 CUE  --*/
/*----------------------------------------------------------------------------*/
Die .BPH- und .SPH-Dateien sind also eine Liste von Offsets in die .BPBs und .SPBs, an denen Daten liegen. Diese kann man grob in TIM-Texturen, ULZ-Archive, und unbekannte Daten unterteilen.

ULZ-Dateien sind komprimierte Archive. Weil die PSX-CPU schwach mit variablen Shifts war und Patente eine Rolle spielten, waren heute übliche Kompressionsmethoden damals keine Option. Es lief im Grunde immer auf etwas RLE-artiges oder auf LZ ohne Entropiekodierung hinaus.

Gipphe hatte recht früh die erste Kompressionsmethode als LZSS (Lempel-Ziv-Storer-Szymanski) identifiziert, aber das reichte noch nicht aus, um alle Daten zu extrahieren. esperknight hat später eine zweite Kompressionsmethode identifiziert und Code zum Extrahieren gepostet.

Im Grunde handelt es sich um drei separate Datenströme:
  1. Literals
  2. Referenzen zu bereits geschriebenen Daten
  3. ein Array von Bits, wovon jedes entscheidet, ob die nächsten Daten aus 1. oder 2. kommen
Auf die Daten angewandt erkennt man nun auch in ULZ-Dateien Ordner und Unterordner. Man bekommt eine Liste von fast 50.000 Dateien, darunter über 11.000 TIMs:
3 Ace Combat 3 TIMs.png


In diesen Texturen wird der Trick mit Palettierung auf die Spitze getrieben: Viele Texturen enthalten überlagerte Texte, die nur mit bestimmter Palette sichtbar werden.
Bild
Wie das geht? Um zwei Texte zu überlagern, sammelt man die Palettenindizes in Gruppen:
  • in beiden Texten Hintergrundfarbe
  • in Text A Hintergrundfarbe; in Text B Textfarbe
  • in Text A Textfarbe; in Text B Hintergrundfarbe
  • in beiden Texten Textfarbe
  • … das gleiche für alle Helligkeitsabstufungen
Je nachdem, welchen Indizes man nun eine helle Farbe zuweist, erscheint Text A oder Text B. Ihr könnt in der Animation die Farbpalette am linken unteren Rand beobachten.
(Das wäre ebenso mit zwei 2-Bit-Texturen möglich gewesen, aber die PSX unterstützt mindestens 4-Bit-Texturen.)

Auch die Flugzeugtexturen nutzen wieder mehrere Paletten:
Bild
Richtig ausgewählt ergeben die Abschnitte:
Bild

Aus irgend einem Grund (Werbung?) findet sich auch das Logo des Vorgängers in den Dateien wieder (in anderem Layout als wir es in den Originaldateien im letzten Beitrag gesehen haben):
3 Ace Combat 2 logo.png
3 Ace Combat 2 logo.png (19.14 KiB) 38717 mal betrachtet


Auch hier sind leider keine unbekannten Flugzeuge oder Levels zu finden :( Dafür ein kleines Schmankerl ganz am Anfang der Daten:
3 tittle.png
3 tittle.png (321 Bytes) 38717 mal betrachtet
Immer diese Tittenspiele!


Abgesehen von den TIM-Texturen kommen keine bekannten Dateitypen vor, jedenfalls entdecke ich auf Anhieb keine Magic Numbers. Die Ordnerstruktur und andere Daten knöpfen wir uns nächstes Mal vor.
Zuletzt geändert von Krishty am 29.06.2017, 18:06, insgesamt 1-mal geändert.
Grund: fixed ROMhacking link
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [Projekt] Extracting Ace Combat

Beitrag von Krishty »

Also gut, die Ordnerstruktur von Ace Combat 3. Aua, das ist trocken. Dafür wird nächstes Mal interessanter, versprochen!

Es gibt zwei Hauptarchive:
  • ACE.BPB – 90,5 MiB, indiziert durch ACE.BPH
  • ACE.SPB – 502 MiB, indiziert durch ACE.SPH
Rein intuitiv verspricht uns hier die große Datei mehr. Allerdings haben wir starke Gründe für die kleinere Datei:
  • die Decoding-Tools funktionieren bisher nur mit der kleineren Datei
  • in den entpackten Dateien haben wir so ziemlich jede Textur gefunden, die im Gameplay vorkommt, plus fast 40.000 unbekannte Dateien – unwahrscheinlich, dass die große Datei Gameplay-relevante Daten enthält
  • die Profis sind der Ansicht, dass in der großen Datei nur Anime-Sequenzen und Musik stecken
Ich teile die Intuition der Profis und mache hier mit der kleineren Datei weiter.

Sie entpackt zu 570 Unterordnern und vier Dateien, die sich allerdings einfach gruppieren lassen:
  • Ein Ordner mit Dingen, die während des gesamten Gameplays in Nutzung sind:
    • Texturen mit Menütext
    • Texturen für HUD
    • Texturen für Explosionen
    • Buchstabentabellen
    • eine große unbekannte Datei (nächster Beitrag!)
  • 61 Ordner identischer Struktur enthalten mit an Sicherheit grenzender Wahrscheinlichkeit je ein Level. Diskussion weiter unten.
    (AC3 hat 52 Missionen, aber was die neun zusätzlichen Ordner bedeuten, kann ich erst später sagen.)
  • 37 Ordner identischer Struktur scheinen Flugzeuge zu enthalten, die der Spieler steuern kann (bemerkt man an den deutlich höheren Texturauflösungen gegenüber KI-Flugzeugen).
  • 9 Ordner haben identische, unbekannte Struktur (ohne Texturen).
  • Ein Ordner (0112) enthält das Logo des Vorgängers und so ziemlich jede Textur des Spielmenüs.
  • 14 Ordner mit scheinbaren Menütexturen. (Jedes Menü in einem Ordner? Klar, VRAM ist eben knapp!)
  • 60 Ordner identischer Struktur (0126–0185) enthalten Level-Briefings samt Name und Thumbnail.
  • 62 Ordner identischer Struktur enthalten japanische Walls of Text.
  • 152 Ordner identischer Struktur (0248–0399) mit erneuten Walls of Text (tja, die haben viel in die Story investiert).
  • Ein paar unterschiedliche Ordner.
  • 158 Ordner identischer Struktur (–0573) ohne Texturen.
  • Vier Binärdateien mit Levelnamen, Flugzeugnamen, Pilotennamen, Debug-Ausgaben, und viel unverstandenem Binärkram.
Für Gameplay-Assets sind erstmal am interessantesten: Die Level-Ordner und die Flugzeugordner.

Ein Level-Ordner sieht so aus:
  1. kleine Binärdatei
  2. große Binärdatei
  3. Ordner mit hunderten kleinen Binärdateien
  4. Ordner mit rund hundert winzigen Binärdateien (viele leer – Fehler beim Extrahieren?)
  5. Ordner mit kleinen Binärdateien
  6. Ordner mit Handvoll kleiner Binärdateien
  7. mittlere Binärdatei
  8. Ordner mit Handvoll kleiner Binärdateien
  9. kleine Binärdatei
  10. kleine Binärdatei
  11. kleine Binärdatei
  12. Ordner mit hunderten Texturen von Terrain & Gebäuden
  13. Ordner mit Texturen von KI-Flugzeugen, die im Level auftauchen
  14. Ordner mit Texturen von Raketen, die im Level auftauchen
  15. Ordner mit Wolkentexturen und Sprites für Sonne/Mond und Sterne (Laterne, Laterne)
  16. Ordner mit Textur für Übersichtskarte und japanischen Text
  17. mittlere Binärdatei
Daten mit offensichtlicher Bedeutung habe ich fett markiert. Den Rest müssen wir uns zusammenreimen.

Wo fangen wir da an? Bei 2., der größten Datei. Je größer die Datei, desto höher die Chance, dass wir Muster erkennen :)
4 filesize.png
4 filesize.png (11.01 KiB) 38592 mal betrachtet

Hmmm. 128 KiB. Schöne runde Zahl. Visualisieren wir sie doch mal, indem wir es als 256×512-Pixel Monochrom-Bitmap lesen (ergibt genau 128 KiB):
4 256×512.png
4 256×512.png (5.6 KiB) 38592 mal betrachtet

Da ist ja tatsächlich was Grafisches drin! Nur stören die vertikalen schwarzen Balken. Das geschulte Auge erkennt hier 256×256 16-Bit-Zahlen, wobei die schwarzen Balken die höchstwertigsten Bytes sind (fast immer Null):
4 256×256.png
4 256×256.png (4.47 KiB) 38592 mal betrachtet

Vergleichen wir das mal mit der Übersichtskarte aus Ordner 16:
4 level overview.png
4 level overview.png (4.32 KiB) 38592 mal betrachtet

Perfekt! Wir haben eine Art Karte des Levels gefunden. Eine Heightmap kann’s nicht sein, dafür sind die Konturen zu scharf. Hm.

Ich habe mal schnell ein Programm hingehackt, das die Datei als Array von 16-Bit-Zahlen liest und zählt, wie viele einzigartige Werte darin vorkommen. Das sind … 334. Und der Ordner 3. enthält … *Trommelwirbel* … 332 kleine Binärdateien. Bingo! (Um die kleine Abweichung kümmern wir uns später.)

Wir wissen also nun, dass Datei 2. eine Tile Map des Levels ist, und dass Ordner 3. ungefähr eine kleine Binärdatei pro Kachel enthält. Wie die Daten pro Kachel aussehen, dazu kommen wir später.

Eine Ergänzung von Außerhalb:
DragonSpikeXIII (http://www.romhacking.net/forum/index.php/topic,17658.msg263081.html#msg263081) hat geschrieben:0000 : the 0004.dat contained narrator sounds like engage, bingo, bulls eye, MA, MF, MO, etc

0005 - 0065 : every 0016.dat is sound archive, and no, not all sounds yet.
Die Dateinamen in dem Thread sind etwas anders als hier, aber mit dem ersten Kommentar meint er den Haupt-Ordner. Mit dem zweiten meint er Datei 16. in unserer Liste. Wir aktualisieren also zu:
  1. kleine Binärdatei
  2. Tile Map des Levels
  3. Ordner mit einer Datei pro Level-Kachel
  4. Ordner mit rund hundert winzigen Binärdateien Modellen beweglicher Einheiten (Flugzeuge, Fallschirme) … viele leer – Fehler beim Extrahieren?
  5. Ordner mit kleinen Binärdateien Waffenmodellen (Raketen, Bomben, …)
  6. Ordner mit Handvoll kleiner Binärdateien statischen Modellen (Sky Boxes, Schiffe, große Gebäude)
  7. mittlere Binärdatei
  8. Ordner mit Handvoll kleiner Binärdateien
  9. kleine Binärdatei
  10. kleine Binärdatei
  11. kleine Binärdatei
  12. Ordner mit hunderten Texturen von Terrain & Gebäuden
  13. Ordner mit Texturen von KI-Flugzeugen, die im Level auftauchen
  14. Ordner mit Texturen von Raketen, die im Level auftauchen
  15. Ordner mit Wolkentexturen und Sprites für Sonne/Mond und Sterne (Laterne, Laterne)
  16. Ordner mit Textur für Übersichtskarte und japanischen Text
  17. alle Sounds, die im Level vorkommen
Nicht schlecht für zehn Minuten Arbeit.

Wie DragonSpikeXIII darauf kommt, dass die Dateien Sounds enthalten, wissen wir an dieser Stelle nicht. Aber im nächsten Beitrag schmeißen wir den Hex-Editor an und extrahieren die besagten Klänge in gute alte WAV-Dateien :)
Zuletzt geändert von Krishty am 02.05.2017, 19:24, insgesamt 2-mal geändert.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
xq
Establishment
Beiträge: 1581
Registriert: 07.10.2012, 14:56
Alter Benutzername: MasterQ32
Echter Name: Felix Queißner
Wohnort: Stuttgart & Region
Kontaktdaten:

Re: [Projekt] Extracting Ace Combat

Beitrag von xq »

Das ist echt spannend, hier mitzulesen. Gute Arbeit!
War mal MasterQ32, findet den Namen aber mittlerweile ziemlich albern…

Programmiert viel in ⚡️Zig⚡️ und nervt Leute damit.
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [Projekt] Extracting Ace Combat

Beitrag von Krishty »

Danke! Wird leider bald versiegen – ich habe das alles schon Anfang des Jahres gemacht und mir am Wochenende Zeit genommen, die Arbeitsschritte in Text- und GIF-Form zu pressen. Nach diesem Beitrag kommt vielleicht noch einer, dann ist aber erstmal Schluss bis das Real Life wieder Zeit zur Verfügung stellt :(



Also SFX. Schauen wir uns eine der besagten Dateien an: 292 KiB, viel binäres drin.
raw file.png
Der Header enthält nichts markantes. Die ersten Bytes sind bei jeder Datei, die angeblich Sounds enthält, unterschiedlich. Sehen wir uns die Mitte der Datei an.

Stop! Ich erkenne ein Muster! Meine primitive, aber wirkungsvolle Methode, um Wiederholungen in Binärdaten zu finden:
  • einen Hex-Blob aus der Mitte kopieren (Mitte, damit man keine Header erwischt, denn die sind ungleich schwerer zu verstehen)
  • im Notepad++ einfügen
  • automatische Zeilenumbrüche einschalten
  • einen häufig auftretenden Wert (meist die Null) markieren, Notepad++ markiert dann automatisch alle Vorkommen
  • das Fenster so lange vergrößern, bis man die Wiederholrate erkennt
Bild
Wir sehen, dass jedes 16. Byte Null ist. Wir sehen außerdem, dass das Byte davor immer recht kleine Zahlenwerte annimmt (15, 16, manchmal 45).

Stöbern wir weiter, vielleicht finden wir ja was Markantes …
Bild
Da ist eine ganze Zeile voll kleiner Ws (Zahlenwert 0x77). Wenn das nicht markant ist!

Das Schöne an unserer modernen Zeit ist, dass man jederzeit Zugriff auf das gesammelte Wissen der Menschheit hat. Nutzen wir das doch …
Bild
http://forums.qhimm.com/index.php?topic=8326.msg100499#msg100499 hat geschrieben:It's very easy to detect playstation audio as the actual data is split in block of 16 bytes whereas the last 14 bytes are 28 packed samples, first byte is predictor/shiftfactor and the 2nd byte is flag. The flag byte is usually 0x00 or 0x02, so every 16th byte is either a zero or a two. Also the flag for sample end is 0x07, and often the last block looks like this:

00 07 77 77 77 77 77 77 77 77 77 77 77 77 77 77
Das passt wie gespuckt auf die Hex-Daten, die wir gefunden haben. Es handelt sich also um Audio-Daten im PlayStation-üblichen XA-Format.

XA ist durch die PSX-Community längst dekodiert, aber wie immer ist die Dokumentation
  • schlicht falsch, oder
  • brilliant geschrieben, aber mit Google nicht auffindbar.
Dieser Thread auf PSXDEV war mir eine große Hilfe: Zum einen verlinkt er auf die sehr ausführliche Dokumentation auf problemkaputt.de, zum anderen stellt er C#-Quelltext bereit (der aber kaputt ist [sogar in der korrigierten Version], Anmerkung am Ende dieses Beitrags).

Mit der Dokumentation fügt sich nun das Puzzle zusammen:
  • Die PSX speichert ihre Audio-Daten in Blöcken von 28 Werten. Diese krumme Zahl ergibt sich aus den Eigenschaften des CD-Laufwerks, lest bei Interesse die Dokumentation.
  • Die Daten sind als ADPCM (Adaptive Differential Pulse Code Modulation) kodiert. Das sind im Grunde rohe Zahlenwerte der Klangwelle, jedoch um einen Vorfaktor skaliert und als Delta zu den vorherigen Werten gespeichert. Keine Kosinustransformation oder Wavelets oder schwieriges Zeug. PCM kennt ihr aus WAV-Dateien, das ist die einfachste Art, Klang zu speichern.
  • Wenn man die 28 Samples eines Blocks in 4 Bits kodiert und Zusatzdaten für Vorfaktor und Flags voranstellt, erhält man die 16-Byte-Blöcke, die wir in der Datei identifiziert haben.
  • Die Klänge werden aus dem RAM ohne Längenangabe wiedergegeben. Das setzt voraus, dass das letzte Sample speziell markiert ist. Diese Markierung ist 07 77 77 ….
Die Daten passen perfekt auf die Dokumentation, und die Dokumentation sagt, dass das Datenformat sehr einfach ist (zehn Zeilen Code reichen zum Dekomprimieren!). Also los!
  • Im Hex-Editor den Müll vor und nach den 16-B-Blöcken löschen
  • darauf achten, dass die Datei exakt mit einem Block beginnt
  • ein 30-Zeilen-Programm schreiben, das die Datei liest und alle PSX-XA-ADPCM-XYZ-FUBAR-Daten dekomprimiert und als Array von 16-Bit-Zahlen speichert
Und wie geben wir das jetzt wieder?

Audacity hat die fantastische Funktion, Rohdaten zu importieren. Einfach ein Array von Zahlen reinschmeißen (Größe und Endianness der Werte wird automatisch erkannt), anhören:
Bild
Sieht aus wie Klang. Klingt auch irgendwie so. Nur viel zu schnell!

Die großen Bereiche am Anfang sind Explosionen und Triebwerksgeräusche. Die sind mit einer Frequenz von rund 8 KHz gespeichert (nicht viel) und werden deshalb bei 44 KHz viel zu schnell abgespielt. Die Bereiche weiter rechts sind Sprache (die HUD-Fragmente der Bitching Betty). Ihre Frequenz ist sogar noch niedriger, 6 KHz (verdammt wenig für Sprache).

Okay. Schmeißen wir das Ganze in ein einfacher lesbares Format. Schonmal eine WAV-Datei geschrieben? Falls nicht, nimmt euch Wikipedia an die Hand. Hier ein paar Zeilen, die ein Array von 16-Bit-shorts als WAV speichert:

Code: Alles auswählen

void writeWAV_mono_16bit(
	char const *   outName,
	SInt2B const * samples,
	int            num_samples,
	int            sampleRate
) {
	auto const dataSize       = 2 * num_samples;
	auto const dataSizePadded = (dataSize + 3) / 4 * 4;
	auto const dataChunkSize  = 4 + 4 + dataSizePadded;
	auto const sixteen        = 16;
	auto const fmtChunkSize   = 4 + 4 + sixteen;
	auto const riffSize       = 4 + fmtChunkSize + dataChunkSize;

	auto resFile = fopen(outName, "wb");
	fwrite("RIFF", 1, 4, resFile);
	fwrite(&riffSize, 4, 1, resFile);
	fwrite("WAVE", 1, 4, resFile);

	auto const one            = UInt2B(1);
	auto const frameSize      = UInt2B(one * sizeof *samples);
	auto const bytesPerSecond = sampleRate * frameSize;
	auto const bitDepth       = UInt2B(16);
	fwrite("fmt ", 1, 4, resFile);
	fwrite(&sixteen, 4, 1, resFile);
	fwrite(&one, 2, 1, resFile); // format: PCM
	fwrite(&one, 2, 1, resFile); // mono
	fwrite(&sampleRate, 4, 1, resFile);
	fwrite(&bytesPerSecond, 4, 1, resFile);
	fwrite(&frameSize, 2, 1, resFile);
	fwrite(&bitDepth, 2, 1, resFile);

	fwrite("data", 1, 4, resFile);
	fwrite(&dataSize, 4, 1, resFile);
	fwrite(samples, num_samples, 2, resFile);
	fclose(resFile);
}
Kurz, schmutzig, erledigt die Arbeit. Resultat:
0004.wav
HUD-Effekte beginnen bei 0:48
(1015.54 KiB) 13653-mal heruntergeladen
Jetzt fehlt noch Code, um die Audio-Daten automatisch aus den Dateien zu extrahieren. Den lasse ich aber als Hausaufgabe stehen, denn für heute war’s wirklich genug :)


Lasst mal Revue passieren, wie wenig wir tatsächlich selber programmiert haben. Schultern von Giganten und so :)


Anmerkung zum C#-Quelltext auf PSXDEV:
Error:
sbyte filter = (sbyte)(parameters[4+block*2+nibble] & 0x30 >> 4);

Correction:
sbyte filter = (sbyte)((parameters[4+block*2+nibble] & 0x30) >> 4);
Immernoch falsch. Korrektur:

  sbyte filter = (sbyte)((parameters[4+block*2+nibble] & 0x70) >> 4);

Sonst werden die hochfrequenten Filter weggeschmissen und man bekommt sehr subtiles Rauschen. (Ich hab’s drei Stunden lang gesucht. LOL!)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
marcgfx
Establishment
Beiträge: 2050
Registriert: 18.10.2010, 23:26

Re: [Projekt] Extracting Ace Combat

Beitrag von marcgfx »

hab mir das ganze schon vor einiger zeit durchgelesen und fands richtig interessant was du hier machst. dachte nur ich sollte auch mal bescheid sagen, dass so sachen zumindest bei mir gut ankommen :)
Benutzeravatar
Infrid
Beiträge: 16
Registriert: 04.11.2016, 14:20
Kontaktdaten:

Re: [Projekt] Extracting Ace Combat

Beitrag von Infrid »

Hello,
thanks for allow me to join your community. I came across to this page thanks to my friend, we are trying to translate this game in Italian too from the English script (thanks team nemo). I read (sort of) this page thanks to google translator, alas I don't speak German, so I cannot really understand everything.

Translate the game is not impossible, we need better tools and better documentation. There are various team that try the same challenge (I know a Spanish team and mine of course), so why not join the efforts? Could we write some documentation about our discoveries and open a github repository for the tools.

This page looks incredibly complete and can help other people to translate the game, I was already put my knowledge to hack the game in a wiki, write and compile some tools from the available source code and make the process easier. It would be great to have those information in English for everyone, would you like to join my wiki?

I am a developer and its not hard for me to create new tools from the available code, I tried to hack this game when I was a boy, alas I didn't implemented nothing useful and personal commitments were more important. I always like to see this game translated.

I have also another idea, many translation project are tied to the hacker that changes the game, I'd like to make a small web application for make easier to non tech-savvy translate the text and rise the quality bar for the future projects. Some kind of CMS for game translations. But this projects comes when we have decent tools for the game.

Kind Regards
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [Projekt] Extracting Ace Combat

Beitrag von Krishty »

Infrid hat geschrieben:This page looks incredibly complete and can help other people to translate the game, I was already put my knowledge to hack the game in a wiki, write and compile some tools from the available source code and make the process easier. It would be great to have those information in English for everyone, would you like to join my wiki?
Thanks! I definitely want to, but with spare time being the limiting factor for me, I cannot guarantee for progress in a specific timeframe.

Where is your wiki? I was doing some research on the game before I started reversing, but I didn’t find anything better than the discussions on romhacking.net. I'm glad to find any little piece of information to speed up my reversing :)
Infrid hat geschrieben:I have also another idea, many translation project are tied to the hacker that changes the game, I'd like to make a small web application for make easier to non tech-savvy translate the text and rise the quality bar for the future projects. Some kind of CMS for game translations. But this projects comes when we have decent tools for the game.
I'm not much of a web programmer, so this would be left to you. My next plans involved 1. decoding some 3D models (planes etc.) and 2. decoding the levels to an open mesh format in order to use them in other games …
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Infrid
Beiträge: 16
Registriert: 04.11.2016, 14:20
Kontaktdaten:

Re: [Projekt] Extracting Ace Combat

Beitrag von Infrid »

thanks so much, everybody has real file commitments, I don't want to pressure anyone :D

Sorry for this late reply but I wanted to tidy up the wiki in the spare time, I have cloned some repositories and now I am editing a download section for retrieve the tools.

The wiki anyway is here: http://ac3es.infrid.com/ and I'll send your email/password by email, anyone can ask me for an account by the way!
My next plans involved 1. decoding some 3D models (planes etc.) and 2. decoding the levels to an open mesh format in order to use them in other games …
for who wants to hack the and and maybe create some sort of editor, this would be a great input.

Kind Regards!
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [Projekt] Extracting Ace Combat

Beitrag von Krishty »

I just cracked one of the mesh formats, so I might as well write about it before losing focus again.

Mission subfolder #6 is almost empty for all the “boring” missions, so my initial guess was it containing meshes. Quickly looking at the files, it turned out I was right. Take BPB\0009\0005.ulz\53544\0024.dat, for example:

Code: Alles auswählen

00 00 0D 02 24 00 00 00 00 00 00 00 64 00 00 00 D4 00 00 00 00 00 00 00 50 00 AD FF FA FF F3 FD 62 00 9A FF 08 00 00 00 26 00 F3 FD 50 00 D1 FF F3 FF F3 FD 00 00 00 00 CD FF 00 00 F3 FF FA FF AD FF FA FF F3 FF F3 FD 9A FF E6 FF 33 00 00 00 B0 FF E3 FF F3 FF D1 FF 00 00 00 00 1B 00 62 00 00 00 00 00 03 00 00 00 03 02 00 00 E8 5A F0 5A 14 20 04 0E 18 22 28 34 03 01 04 06 F6 A9 D4 43 FE A1 27 00 F0 A3 F0 9F 11 11 11 2C 03 01 00 00 F8 5A F8 5A 2C 36 28 34 04 0E 04 0E 07 06 01 01 F6 99 D4 43 F0 9F 27 00 FE A1 FE A1 11 11 11 2C 03 01 00 00 40 00 40 00 08 10 1C 24 00 0C 00 0C 02 05 00 00 FF AF D4 43 F0 9F 27 00 F0 AF F0 AF 80 80 80 2C 05 00 00 00 03 01 00 00 E0 5A E0 5A 08 10 14 20 18 22 18 22 02 03 04 04 F5 80 D4 43 F2 9F 27 00 FA 9F FA 9F 88 88 88 3C 22 22 22 3C 22 22 22 3C 22 22 22 3C 03 01 00 00 C8 5A C8 5A 1C 24 28 34 2C 36 2C 36 05 06 07 07 F5 80 D4 43 F9 9E 27 00 F2 9D F2 9D 36 36 36 3C 22 22 22 3C 22 22 22 3C 22 22 22 3C 03 02 00 00 B8 5A C0 5A 08 10 18 22 1C 24 28 34 02 04 05 06 F0 80 D4 43 F7 BF 27 00 FF 80 FF 80 88 88 88 3C 22 22 22 3C 36 36 36 3C 22 22 22 3C 03 02 00 00 A8 5A B0 5A 00 0C 04 0E 08 10 14 20 00 01 02 03 FA 80 D4 43 FC 9D 27 00 F5 80 F2 9F AA AA AA 3C 22 22 22 3C 91 8C 87 3C 22 22 22 3C 03 02 00 00 D0 5A D8 5A 1C 24 2C 36 00 0C 04 0E 05 07 00 01 F5 80 D4 43 F2 9D 27 00 FA 80 FC 9D 36 36 36 3C 22 22 22 3C 36 36 36 3C 22 22 22 3C
The first numbers are 32-bit offsets into the file, obviously. This allows us to group the file into the header and three blocks (it’s five in other files, but let’s keep things simple here):

Code: Alles auswählen

00 00 0D 02
24 00 00 00 ← offset A
00 00 00 00 ← offset B (unused)
64 00 00 00 ← offset C
D4 00 00 00 ← offset D
00 00 00 00 ← offset E (?)

50 00 AD FF FA FF F3 FD 62 00 9A FF ← not sure about that, might be bounding box

block A:
08 00 00 00 26 00 F3 FD 50 00 D1 FF F3 FF F3 FD 00 00 00 00 CD FF 00 00 F3 FF FA FF AD FF FA FF F3 FF F3 FD 9A FF E6 FF 33 00 00 00 B0 FF E3 FF F3 FF D1 FF 00 00 00 00 1B 00 62 00 00 00 00 00

block C:
03 00 00 00 03 02 00 00 E8 5A F0 5A 14 20 04 0E 18 22 28 34 03 01 04 06 F6 A9 D4 43 FE A1 27 00 F0 A3 F0 9F 11 11 11 2C 03 01 00 00 F8 5A F8 5A 2C 36 28 34 04 0E 04 0E 07 06 01 01 F6 99 D4 43 F0 9F 27 00 FE A1 FE A1 11 11 11 2C 03 01 00 00 40 00 40 00 08 10 1C 24 00 0C 00 0C 02 05 00 00 FF AF D4 43 F0 9F 27 00 F0 AF F0 AF 80 80 80 2C

block D:
05 00 00 00 03 01 00 00 E0 5A E0 5A 08 10 14 20 18 22 18 22 02 03 04 04 F5 80 D4 43 F2 9F 27 00 FA 9F FA 9F 88 88 88 3C 22 22 22 3C 22 22 22 3C 22 22 22 3C 03 01 00 00 C8 5A C8 5A 1C 24 28 34 2C 36 2C 36 05 06 07 07 F5 80 D4 43 F9 9E 27 00 F2 9D F2 9D 36 36 36 3C 22 22 22 3C 22 22 22 3C 22 22 22 3C 03 02 00 00 B8 5A C0 5A 08 10 18 22 1C 24 28 34 02 04 05 06 F0 80 D4 43 F7 BF 27 00 FF 80 FF 80 88 88 88 3C 22 22 22 3C 36 36 36 3C 22 22 22 3C 03 02 00 00 A8 5A B0 5A 00 0C 04 0E 08 10 14 20 00 01 02 03 FA 80 D4 43 FC 9D 27 00 F5 80 F2 9F AA AA AA 3C 22 22 22 3C 91 8C 87 3C 22 22 22 3C 03 02 00 00 D0 5A D8 5A 1C 24 2C 36 00 0C 04 0E 05 07 00 01 F5 80 D4 43 F2 9D 27 00 FA 80 FC 9D 36 36 36 3C 22 22 22 3C 36 36 36 3C 22 22 22 3C
Using my usual approach, I quickly grouped blocks C & D into lists of entries. Block A was harder, but the zero words pretty much convinced me it’s a 10-word structure:

Code: Alles auswählen

00000D02
24000000 ← offset A
00000000 ← offset B (unused)
64000000 ← offset C
D4000000 ← offset D
00000000 ← offset E (?)

50 00 AD FF FA FF F3 FD 62 00 9A FF ← not sure about that, might be bounding box

block A:
08000000 ← 8 entries
2600 F3FD 5000 D1FF F3FF F3FD 0000 0000 CDFF 0000 ← always 9 words + terminating zero
F3FF FAFF ADFF FAFF F3FF F3FD 9AFF E6FF 3300 0000
B0FF E3FF F3FF D1FF 0000 0000 1B00 6200 0000 0000

block C:
03000000 ← 3 entries
0302 0000  E8 5A  F0 5A  14 20 04 0E 18 22 28 34  03 01 04 06  F6 A9 D4 43 FE A1 27 00 F0 A3 F0 9F 1111112C
0301 0000  F8 5A  F8 5A  2C 36 28 34 04 0E 04 0E  07 06 01 01  F6 99 D4 43 F0 9F 27 00 FE A1 FE A1 1111112C
0301 0000  40 00  40 00  08 10 1C 24 00 0C 00 0C  02 05 00 00  FF AF D4 43 F0 9F 27 00 F0 AF F0 AF 8080802C

block D:
0500 0000  ← 5 entries
0301 0000  E0 5A  E0 5A  08 10 14 20 18 22 18 22  02 03 04 04  F5 80 D4 43 F2 9F 27 00 FA 9F FA 9F 8888883C 2222223C 2222223C 2222223C
0301 0000  C8 5A  C8 5A  1C 24 28 34 2C 36 2C 36  05 06 07 07  F5 80 D4 43 F9 9E 27 00 F2 9D F2 9D 3636363C 2222223C 2222223C 2222223C
0302 0000  B8 5A  C0 5A  08 10 18 22 1C 24 28 34  02 04 05 06  F0 80 D4 43 F7 BF 27 00 FF 80 FF 80 8888883C 2222223C 3636363C 2222223C
0302 0000  A8 5A  B0 5A  00 0C 04 0E 08 10 14 20  00 01 02 03  FA 80 D4 43 FC 9D 27 00 F5 80 F2 9F AAAAAA3C 2222223C 918C873C 2222223C
0302 0000  D0 5A  D8 5A  1C 24 2C 36 00 0C 04 0E  05 07 00 01  F5 80 D4 43 F2 9D 27 00 FA 80 FC 9D 3636363C 2222223C 3636363C 2222223C
Let’s start with block C, which is the most obvious one:

  0302 0000 E8 5A F0 5A 14 20 04 0E 18 22 28 34 03 01 04 06 F6 A9 D4 43 FE A1 27 00 F0 A3 F0 9F 1111112C

The red numbers are vertex indices:
  • all numbers from 0 to 7 (hint: block A contains 8 entries!)
  • you can easily identify shared edges
The green numbers seem to be triangle colors.

The same logic applies to block D, just with more colors (per-vertex instead of per-triangle):

  0301 0000 E0 5A E0 5A 08 10 14 20 18 22 18 22 02 03 04 04 F5 80 D4 43 F2 9F 27 00 FA 9F FA 9F 8888883C 2222223C 2222223C 2222223C

Notice how there are just three distinct vertex indices in that polygon. From my observation, triangles are always represented as degenerated quads (the last index is always collapsed).

One closer look at the color. These are nice even numbers, but the 3C and 2C constants don’t quite make sense. They can’t be alpha values, as the PSX does not support interpolated alpha blending. Again, Nocash’s PSX Specifications to the rescue:
GPU Render Polygon Commands

[…]

GP0(24h) - Textured three-point polygon, opaque, texture-blending
GP0(25h) - Textured three-point polygon, opaque, raw-texture
GP0(26h) - Textured three-point polygon, semi-transparent, texture-blending
GP0(27h) - Textured three-point polygon, semi-transparent, raw-texture
GP0(2Ch) - Textured four-point polygon, opaque, texture-blending
GP0(2Dh) - Textured four-point polygon, opaque, raw-texture
GP0(2Eh) - Textured four-point polygon, semi-transparent, texture-blending
GP0(2Fh) - Textured four-point polygon, semi-transparent, raw-texture
1st Color+Command (CcBbGgRrh) (color is ignored for raw-textures)
2nd Vertex1 (YyyyXxxxh)
3rd Texcoord1+Palette (ClutYyXxh)
4th Vertex2 (YyyyXxxxh)
5th Texcoord2+Texpage (PageYyXxh)
6th Vertex3 (YyyyXxxxh)
7th Texcoord3 (0000YyXxh)
(8th) Vertex4 (YyyyXxxxh) (if any)
(9th) Texcoord4 (0000YyXxh) (if any)


[…]

GP0(34h) - Shaded Textured three-point polygon, opaque, texture-blending
GP0(36h) - Shaded Textured three-point polygon, semi-transparent, tex-blend
GP0(3Ch) - Shaded Textured four-point polygon, opaque, texture-blending
GP0(3Eh) - Shaded Textured four-point polygon, semi-transparent, tex-blend
1st Color1+Command (CcBbGgRrh)
2nd Vertex1 (YyyyXxxxh)
3rd Texcoord1+Palette (ClutYyXxh)
4th Color2 (00BbGgRrh)
5th Vertex2 (YyyyXxxxh)
6th Texcoord2+Texpage (PageYyXxh)
7th Color3 (00BbGgRrh)
8th Vertex3 (YyyyXxxxh)
9th Texcoord3 (0000YyXxh)
(10th) Color4 (00BbGgRrh) (if any)
(11th) Vertex4 (YyyyXxxxh) (if any)
(12th) Texcoord4 (0000YyXxh) (if any)


[…]

Notes
Polygons are displayed up to <excluding> their lower-right coordinates.
Four-point polygons are internally processed as two Three-point polygons, the first consisting of Vertices 1,2,3, and the second of Vertices 2,3,4.[…]

Caution: For untextured graphics, 8bit RGB values of FFh are brightest. However, for texture blending, 8bit values of 80h are brightest (values 81h..FFh are "brighter than bright" allowing to make textures about twice as bright as than they were originially stored in memory; of course the results can't exceed the maximum brightness, ie. the 5bit values written to the framebuffer are saturated to max 1Fh).
Important insights from the text:
  1. the 1st vertex color passed to the GPU contains the draw command in the most significant bits (i.e. in the last byte)
  2. 2C is the command for a “Textured four-point polygon, opaque, texture-blending”
  3. 3C is the command for a “Shaded Textured four-point polygon, opaque, texture-blending” (matches the color data pretty well, doesn’t it?)
  4. the 808080 isn’t a gray polygon, but rather a white one
  5. the bytes from the file are passed to the GPU without much modification
  6. so texture coordinates and palette indices must be present there, too
At this point I was 99 % sure I had found the models. However, block A (the vertices) are very problematic.

Code: Alles auswählen

08000000 ← 8 entries
2600 F3FD 5000 D1FF F3FF F3FD 0000 0000 CDFF 0000 ← always 9 words + terminating zero
F3FF FAFF ADFF FAFF F3FF F3FD 9AFF E6FF 3300 0000
B0FF E3FF F3FF D1FF 0000 0000 1B00 6200 0000 0000
It’s vertex data, obviously. It’s signed 16-bit numbers (we can tell from the FF bytes). However, it’s stored in some non-linear fashion (zero bytes interleaved after three vertices?). Just reading X-Y-Z, X-Y-Z, X-Y-Z, (skip zero) left me clueless:
2.png

On the other hand, comparing the number of vertices in the block to the number of words (enumerated from all available files) tells us that there *must* be a strict 7-to-1 mapping. With zeroes intermixed, there is no other place for the vertex positions to go.
2016-11-18 bytes per vertex.png
2016-11-18 bytes per vertex.png (4.65 KiB) 38041 mal betrachtet

Now this took me some weeks to figure out. I would have been a nice programming challenge to use a list of individual coordinate components and a list of connections to find a consistent mapping that produces a non-overlapping topology, but this is my hobby and I’m short on time, so screw that.

Came to mind: Like many PSX games, Ace Combat 3 uses a tile-based renderer. Maybe they just decoupled the height information? Turns out, they did. Reading the numbers as X-Y, X-Y, X-Y, Z, Z, Z:
5.png
I have yet to apply vertex colors, textures, winding order etc., but for now it’s a satisfying result:
  • 600 files
  • only static models (buildings etc.), no planes yet … but some four-legged Antlion tanks :)
  • the selected file is a satellite from the space mission
  • the one in the lower right is a ship with wakes
Here’s a ZIP containing the models in STL format, in case anyone bothers:
extracted Ace Combat 3 models.zip
(288.08 KiB) 924-mal heruntergeladen
Infrid, I’ll add all that stuff to your Wiki at some point … just not today; Real Life is calling.
Zuletzt geändert von Krishty am 19.11.2016, 20:56, insgesamt 2-mal geändert.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Schrompf
Moderator
Beiträge: 4831
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: [Projekt] Extracting Ace Combat

Beitrag von Schrompf »

Wow, cool.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
gdsWizard
Establishment
Beiträge: 237
Registriert: 04.02.2005, 09:12
Benutzertext: www.gamedevstudio.com
Echter Name: Thomas Mittelsdorf
Wohnort: Meiningen
Kontaktdaten:

Re: [Projekt] Extracting Ace Combat

Beitrag von gdsWizard »

Das ist wirklich eine echte Leistung. Hut ab !!!
Benutzeravatar
marcgfx
Establishment
Beiträge: 2050
Registriert: 18.10.2010, 23:26

Re: [Projekt] Extracting Ace Combat

Beitrag von marcgfx »

ist noch interessant, ein paar modelle scheinen sich zu wiederholen. so sparsam waren sie wohl doch nicht :)
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [Projekt] Extracting Ace Combat

Beitrag von Krishty »

marcgfx hat geschrieben:ist noch interessant, ein paar modelle scheinen sich zu wiederholen. so sparsam waren sie wohl doch nicht :)
Da geht’s um die Positionierungszeit des CD-Lesekopfes. Die sich wiederholenden Modelle sind je 2–10 KiB groß, aber dafür sind nun alle Modelle eines Levels auf einem Haufen (im selben oder in benachbarten Sektoren).

So wurde wohl bei vielen Spielen verfahren. Die 600 MiB einer CD waren ja gigantisch, verglichen mit den 64 MiB eines N64-Spiels. Dabei hat man gern Zeit gegen Platz getauscht.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [Projekt] Extracting Ace Combat

Beitrag von Krishty »

I was just trying to extract data from the mission’s 0003.ulz folder, which I suspected to contain terrain details.

The structure is pretty much the same as for the meshes in 0005, with just a few differences. The hex formatting is straightforward and boring, so I’ll skip directly to the layouts:

Code: Alles auswählen

struct Mesh_0003_Header {
	UInt4B_LE unknown;
	UInt4B_LE vertexOffsets[3];
	UInt4B_LE zero;
	UInt4B_LE offsetOfTriangles;
	UInt4B_LE offsetOfQuads;
};

// 32-B wide triangle structure.
//  • maps directly to bytes in a file
//  • can be found in mission/0003.ulz files
//  • Gouraud-shaded
struct Mesh_Triangle_32 {
	UInt2B_LE unk[2];
	UInt1B    indices[3];
	UInt1B    zero;
	UInt4B_LE colors[3];
	UInt1B    unk2[6];
	UInt2B_LE unk3;
	UInt1B    unk4[2];
	UInt2B_LE zero2;
};

// 36-B wide quad structure.
//  • maps directly to bytes in a file
//  • can be found in mission/0003.ulz files
//  • Gouraud-shaded
struct Mesh_Quad_36 {
	UInt2B_LE unk[2];
	UInt1B    indices[4];
	UInt4B_LE colors[4];
	UInt1B    unk2[6];
	UInt2B_LE unk3;
	UInt1B    unk4[4];
};
I expected some terrain details when instead I got these beauties:
NPC meshes.png
  • just NPC meshes
  • you can identify some low-poly LODs
  • only triangle colors here, I didn’t implement vertex colors/Gouraud shading yet
  • no textures yet (texture coordinates are present in the files; I’m 95 % sure)
  • there’s actually up to three sets of vertices with identical triangle/quad information in each mesh (may be different damage levels); just showing the first set here
  • some surfaces are still missing – I probably skipped another triangle/quad block that appears in only a few files
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [Projekt] Extracting Ace Combat

Beitrag von Krishty »

Nicht meins, aber für Fans ein Fest: Team NEMO hat die Übersetzung fertig.
  • English subtitles for all 52 missions from both discs;
  • the subs from last year's Disc 1 patch have been revised;
  • localization work by Guest Localizer Agness Kaku on M01;
  • translations for Armory, Search and Archive menus.
Damit kann jeder, der eine ISO der japanischen Originalversion hat, diese nun auch auf Englisch spielen, statt sich mit der stark zusammengekürzten Version abzumühen.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [Projekt] Extracting Ace Combat

Beitrag von Krishty »

Christmas time, spare time, Ace Combat time.

I changed my output format from STL to VRML. Still no texture coordinates, but at least vertex colors work properly.

Furthermore, the tile map in each level’s 0001.ulz can be decoded like:
  1. throw away zeroes (that’s holes in the terrain)
  2. keep only the least significant 9 bits (& 511)
  3. subtract one
  4. get the according file from 0002.ulz
The layout of the maze level stands out in its complexity, and I guess I’ll dedicate an article to cover it. Furthermore, I’m convinced I found two unfinished levels.

Textures an write-ups will take a while (probably months, again). In the meantime, enjoy this shot of Expo City:
p e.png
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [Projekt] Extracting Ace Combat

Beitrag von Krishty »

The format of the playable planes is pretty complicated.
  • There’s three sets of vertices, and the first two sets are distorted. Probably some kind of dynamic LOD.
  • Many objects have no triangles. I suspect there’s multiple vertex sets for the same triangle indices in different situations (e.g. air brake extended/retracted; rudders left/center/right) and the game interpolates the sets dynamically.
  • There’s four different data layouts for triangles/quads with/without lighting.
  • These are the only models without pre-baked lighting and with per-vertex normals instead.
The colors are placeholders.
The colors are placeholders.
All planes are readable, including the hidden Geopelia prototype. There are no other (unknown) planes.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
marcgfx
Establishment
Beiträge: 2050
Registriert: 18.10.2010, 23:26

Re: [Projekt] Extracting Ace Combat

Beitrag von marcgfx »

ist ja wie ein rätsel lösen. ist das mehr meditativ oder doch eher frustrierend? bzw wie lange hast du um die einzelnen schritte zu lösen und wie oft läufst du in die falsche richtung :)
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [Projekt] Extracting Ace Combat

Beitrag von Krishty »

Kommt drauf an – meistens meditativ, ich tu’s ja zur Entspannung. Aber wenn ich keine Texturen extrahieren kann, weil mein Viewer die noch nicht unterstützt und ich das erstmal implementieren muss und noch mehr Drumherum dazukommt, nervt’s auch schonmal. Die vorher erwähnten Vertex-Koordinaten mit XY-XY-XY-Z-Z-Z waren auch grenzwertig.

Dazu muss man aber sagen, dass Ace Combat 3 wirklich einfach ist im Vergleich zu Driver 2 vorher. Die Modelle haben quasi alle die gleiche Struktur. Man kriegt sehr schnell Ergebnisse. Darum schreibe ich auch darüber, und nicht über wirklich harte Brocken.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Infrid
Beiträge: 16
Registriert: 04.11.2016, 14:20
Kontaktdaten:

Re: [Projekt] Extracting Ace Combat

Beitrag von Infrid »

Hello, I wrote a new ulz decompressor, the dashman's code has a bug on ulz v0 flag reading.

I did some checks and for the Japanese version, the first cd contains 2256 ulz files (from ACE.BPB), dashman's decompressor works on 2014 files while mine has no problem at all, all 2256 files decompressed.

Maybe what you are looking for is in those uncompressed files.

You can download the tool here, I plan to write a compressor too and an unpacker for ACE.BPB and ACE.SPB

:D
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [Projekt] Extracting Ace Combat

Beitrag von Krishty »

Can you give an example of a specific file that decompresses fine with your script but was broken before? I can’t see any difference. My code is based on “ULZ update 1.cs” by esperknight.

However, the code is now drastically reduced due to the similarities in both compression methods you found … good work!

Here’s my current code:

Code: Alles auswählen

struct Header {
	// Always “ULZ\x1A”.
	UInt4B_LE id; 
	// • bits  0–23: extracted size
	// • bits 24–31: compression type (“0” or “2”)
	UInt4B_LE typeAndSize;
	// • bits  0–23: offset of symbol table from beginning of archive
	// • bits 24–31: log2 of sliding window
	UInt4B_LE u_pos;
	// Offset of lookups from beginning of archive.
	UInt4B_LE c_pos;
	UInt4B_LE flags[];
};


// Checks whether the given bytes mark the beginning of an ULZ stream.
//  • pass at least 20 bytes
bool isULZ(Byte const * toData, USize const dataSize) {
	return 20 < dataSize && *reinterpret_cast<UInt4B_LE const *>(toData) == bigEndian(0x556C7A1Au);
}

// Returns the size of the extracted data. ULZ supports file sizes up to 24 MiB.
USize extractedSizeOf(Header const & header) {
	return littleEndian(header.typeAndSize) & 0xFFFFFF;
}

// Extracts the given ULZ to the given address.
//  • returns “no” on failure
//  • requires sufficient space – use “extractedSizeOf()”
bool extract(Byte * toOutput, Byte const * const toInput, USize const compressedSize) {
	if(no == isULZ(toInput, compressedSize)) {
		__debugbreak();
		return no;
	}

	auto &     header     = *reinterpret_cast<Header const *>(toInput);
	auto const type       = littleEndian(header.typeAndSize) >> 24;
	auto const offsetBits = littleEndian(header.u_pos) >> 24u;
	auto const offsetMask = (1u << offsetBits) - 1;
	{
		// The sliding window cannot be larger than 2^15 B (because offset-length pairs are 16-bit integers):
		if(offsetBits > 15) {
			__debugbreak(); // TODO: error: bad window
			return no;
		}
		// All tables must be located within the file.
		// TODO
	}

	// There is one stream of new symbols:
	auto toUncompressedData = toInput + (littleEndian(header.u_pos) & 0xFFFFFF);
	// There is one stream of lookups (offset + length) into already-decompressed data:
	auto toCompressedData   = reinterpret_cast<UInt2B const *>(toInput + littleEndian(header.c_pos));
	// There is one stream of 32-bit flags, where each bit determines whether the next symbol is known (offset + length):
	auto toFlags            = header.flags;
	auto bytesLeft          = extractedSizeOf(header);
	if(0 == bytesLeft) {
		return yes; // nothing to do (never happens with real files)
	}

	// Both type 0 and type 2 consume bits (starting at the most significant position), but type 1 skips every 32nd bit:
	UInt4B flagMaskIfConsumed;
	switch(type) {
		case 0: flagMaskIfConsumed = 1; break;
		case 2: flagMaskIfConsumed = 0; break;
		default: __debugbreak(); return no; // TODO: error: bad type
	}
	UInt4B flags;
	UInt4B flagMask = flagMaskIfConsumed; // force refill on first iteration
	do {

		if(flagMaskIfConsumed == flagMask) { // All flags consumed?
			flags    = littleEndian(*toFlags++);
			flagMask = 0x80000000u;
		}

		if(flags & flagMask) {

			// Copy an uncompressed byte:
			*toOutput++ = *toUncompressedData++;
			--bytesLeft;

		} else {

			// Duplicate “length” bytes starting backwards at “offset” in the result.
			auto const offsetAndLength = UInt4B(littleEndian(*toCompressedData++));
			auto const offset          = offsetAndLength & offsetMask;
			auto const length          = 3 + (offsetAndLength >> offsetBits);
			if(length > bytesLeft) {
				__debugbreak(); // TODO: error: overrun
				return no;
			}

			movsb(toOutput, toOutput - 1 - offset, length);
			toOutput += length;
			bytesLeft -= length;

		}

		flagMask >>= 1;
	} while(bytesLeft);

	return yes;
}
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Infrid
Beiträge: 16
Registriert: 04.11.2016, 14:20
Kontaktdaten:

Re: [Projekt] Extracting Ace Combat

Beitrag von Infrid »

thanks!

In the original implementation, the function Uncompress_v0 is running until flags == 0 and doesn't check the final size at all, this brings to the bug that I fixed in my code.

Here a list of file extracted with the bugfix

Code: Alles auswählen

.
├── [       4096]  BPB/
│   ├── [       4096]  0005/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0006/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0007/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0008/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       ├── [     245180]  0011.bin
│   │       ├── [      76100]  0012.bin
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0009/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       ├── [       4828]  0007.bin
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0010/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       ├── [        788]  0013.bin
│   │       └── [      22764]  0014.bin
│   ├── [       4096]  0011/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       ├── [      25168]  0012.bin
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0012/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0013/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0014/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0015/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0016/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       ├── [      18776]  0006.bin
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0017/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       ├── [      25232]  0012.bin
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0018/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       ├── [      16844]  0012.bin
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0019/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0020/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       ├── [        788]  0013.bin
│   │       └── [      17580]  0014.bin
│   ├── [       4096]  0021/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       ├── [       2680]  0007.bin
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0022/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0023/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0024/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       ├── [       2684]  0007.bin
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0025/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       ├── [       6800]  0007.bin
│   │       ├── [        788]  0013.bin
│   │       └── [      22764]  0014.bin
│   ├── [       4096]  0026/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       ├── [       3784]  0007.bin
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0027/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0028/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0029/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0030/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       ├── [       4456]  0007.bin
│   │       ├── [        572]  0010.bin
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0031/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0032/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       ├── [      18776]  0006.bin
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0033/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0034/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0035/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0036/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0037/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0038/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       ├── [      50524]  0012.bin
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0039/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0040/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0041/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0042/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       ├── [       4544]  0007.bin
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0043/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0044/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0045/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0046/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0048/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0049/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       ├── [       5680]  0007.bin
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0050/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       ├── [       4948]  0007.bin
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0051/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0052/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       ├── [      16876]  0012.bin
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0053/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0054/
│   │   └── [       4096]  ulz_data/
│   │       └── [       1016]  0007.bin
│   ├── [       4096]  0055/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0056/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0057/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0058/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       ├── [       1664]  0007.bin
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0059/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0060/
│   │   └── [       4096]  ulz_data/
│   │       ├── [     297748]  0002.bin
│   │       ├── [       3616]  0004.tim
│   │       ├── [       2504]  0007.bin
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0061/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       ├── [      17184]  0006.bin
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0062/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       ├── [        980]  0007.bin
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0063/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       3616]  0004.tim
│   │       ├── [        952]  0007.bin
│   │       └── [        788]  0013.bin
│   ├── [       4096]  0064/
│   │   └── [       4096]  ulz_data/
│   │       ├── [      18872]  0003.bin
│   │       └── [       8360]  0012.bin
│   ├── [       4096]  0065/
│   │   └── [       4096]  ulz_data/
│   │       ├── [      18872]  0003.bin
│   │       ├── [       1016]  0007.bin
│   │       └── [       8360]  0012.bin
│   ├── [       4096]  0071/
│   │   └── [       4096]  ulz_data/
│   │       └── [      33868]  0002.bin
│   ├── [       4096]  0074/
│   │   └── [       4096]  ulz_data/
│   │       └── [      33868]  0002.bin
│   ├── [       4096]  0075/
│   │   └── [       4096]  ulz_data/
│   │       └── [      33868]  0002.bin
│   ├── [       4096]  0089/
│   │   └── [       4096]  ulz_data/
│   │       └── [      33868]  0002.bin
│   ├── [       4096]  0094/
│   │   └── [       4096]  ulz_data/
│   │       └── [      33868]  0002.bin
│   ├── [       4096]  0102/
│   │   └── [       4096]  ulz_data/
│   │       └── [      33868]  0002.bin
│   ├── [       4096]  0112/
│   │   └── [       4096]  ulz_data/
│   │       ├── [     329344]  0004.bin
│   │       └── [       8100]  0007.bin
│   ├── [       4096]  0113/
│   │   └── [       4096]  ulz_data/
│   │       ├── [       8100]  0000.bin
│   │       └── [      16048]  0003.bin
│   ├── [       4096]  0114/
│   │   └── [       4096]  0004/
│   │       └── [       4096]  ulz_data/
│   │           └── [      15940]  0001.tim
│   ├── [       4096]  0116/
│   │   └── [       4096]  0001/
│   │       └── [       4096]  0006/
│   │           └── [       4096]  ulz_data/
│   │               └── [      11208]  0001.bin
│   ├── [       4096]  0118/
│   │   └── [       4096]  0001/
│   │       └── [       4096]  0007/
│   │           └── [       4096]  ulz_data/
│   │               └── [      15772]  0000.bin
│   ├── [       4096]  0119/
│   │   └── [       4096]  0001/
│   │       └── [       4096]  0001/
│   │           └── [       4096]  ulz_data/
│   │               └── [      10920]  0001.bin
│   ├── [       4096]  0121/
│   │   └── [       4096]  0001/
│   │       └── [       4096]  ulz_data/
│   │           ├── [       5248]  0000.tim
│   │           ├── [       5248]  0001.tim
│   │           ├── [       5248]  0002.tim
│   │           ├── [       5248]  0003.tim
│   │           ├── [       5248]  0004.tim
│   │           ├── [       5248]  0008.tim
│   │           ├── [       5248]  0009.tim
│   │           ├── [       5248]  0011.tim
│   │           ├── [       5248]  0013.tim
│   │           ├── [       5248]  0014.tim
│   │           ├── [       5248]  0017.tim
│   │           ├── [       5248]  0019.tim
│   │           ├── [       5248]  0020.tim
│   │           ├── [       5248]  0021.tim
│   │           ├── [       5248]  0022.tim
│   │           ├── [       5248]  0026.tim
│   │           ├── [       5248]  0027.tim
│   │           ├── [       5248]  0028.tim
│   │           ├── [       5248]  0033.tim
│   │           ├── [       5248]  0034.tim
│   │           ├── [       5248]  0035.tim
│   │           ├── [       5248]  0036.tim
│   │           ├── [       5248]  0037.tim
│   │           ├── [       5248]  0038.tim
│   │           ├── [       5248]  0041.tim
│   │           ├── [       5248]  0045.tim
│   │           ├── [       5248]  0057.tim
│   │           └── [       5248]  0059.tim
│   ├── [       4096]  0122/
│   │   └── [       4096]  0002/
│   │       └── [       4096]  ulz_data/
│   │           └── [       5248]  0009.tim
│   ├── [       4096]  0123/
│   │   └── [       4096]  0001/
│   │       └── [       4096]  ulz_data/
│   │           ├── [       5248]  0000.tim
│   │           ├── [       5248]  0001.tim
│   │           ├── [       5248]  0003.tim
│   │           ├── [       5248]  0010.tim
│   │           ├── [       5248]  0011.tim
│   │           ├── [       5248]  0016.tim
│   │           ├── [       5248]  0019.tim
│   │           ├── [       5248]  0021.tim
│   │           ├── [       5248]  0033.tim
│   │           ├── [       5248]  0036.tim
│   │           ├── [       5248]  0038.tim
│   │           ├── [       5248]  0042.tim
│   │           ├── [       5248]  0043.tim
│   │           ├── [       5248]  0044.tim
│   │           ├── [       5248]  0048.tim
│   │           ├── [       5248]  0049.tim
│   │           ├── [       5248]  0052.tim
│   │           ├── [       5248]  0056.tim
│   │           └── [       5248]  0059.tim
│   ├── [       4096]  0124/
│   │   ├── [       4096]  0000/
│   │   │   └── [       4096]  ulz_data/
│   │   │       ├── [       5248]  0001.tim
│   │   │       └── [       5248]  0002.tim
│   │   └── [       4096]  ulz_data/
│   │       └── [      33316]  0001.bin
│   ├── [       4096]  0125/
│   │   └── [       4096]  0001/
│   │       └── [       4096]  ulz_data/
│   │           ├── [       5248]  0000.tim
│   │           └── [       5248]  0013.tim
│   ├── [       4096]  0131/
│   │   └── [       4096]  0003/
│   │       └── [       4096]  ulz_data/
│   │           └── [      32864]  0000.tim
│   ├── [       4096]  0142/
│   │   └── [       4096]  ulz_data/
│   │       └── [      29352]  0007.bin
│   ├── [       4096]  0159/
│   │   └── [       4096]  ulz_data/
│   │       └── [      29352]  0007.bin
│   ├── [       4096]  0160/
│   │   └── [       4096]  ulz_data/
│   │       └── [      39176]  0007.bin
│   ├── [       4096]  0164/
│   │   └── [       4096]  ulz_data/
│   │       └── [      29096]  0007.bin
│   ├── [       4096]  0166/
│   │   └── [       4096]  0003/
│   │       └── [       4096]  ulz_data/
│   │           └── [      32864]  0000.tim
│   ├── [       4096]  0168/
│   │   └── [       4096]  ulz_data/
│   │       └── [      39176]  0007.bin
│   ├── [       4096]  0174/
│   │   └── [       4096]  ulz_data/
│   │       └── [      29352]  0007.bin
│   ├── [       4096]  0175/
│   │   └── [       4096]  ulz_data/
│   │       └── [      29352]  0007.bin
│   ├── [       4096]  0178/
│   │   └── [       4096]  ulz_data/
│   │       └── [      39176]  0007.bin
│   ├── [       4096]  0188/
│   │   └── [       4096]  ulz_data/
│   │       └── [      33968]  0000.bin
│   ├── [       4096]  0215/
│   │   └── [       4096]  ulz_data/
│   │       └── [      19612]  0000.bin
│   ├── [       4096]  0226/
│   │   └── [       4096]  ulz_data/
│   │       └── [      19612]  0000.bin
│   ├── [       4096]  0227/
│   │   └── [       4096]  ulz_data/
│   │       └── [      19612]  0000.bin
│   ├── [       4096]  0252/
│   │   └── [       4096]  0001/
│   │       └── [       4096]  ulz_data/
│   │           └── [      32864]  0000.tim
│   ├── [       4096]  0269/
│   │   └── [       4096]  0001/
│   │       └── [       4096]  ulz_data/
│   │           └── [      32864]  0000.tim
│   ├── [       4096]  0270/
│   │   └── [       4096]  ulz_data/
│   │       └── [       8460]  0002.bin
│   ├── [       4096]  0304/
│   │   └── [       4096]  0001/
│   │       └── [       4096]  ulz_data/
│   │           └── [      32864]  0001.tim
│   ├── [       4096]  0305/
│   │   └── [       4096]  0001/
│   │       └── [       4096]  ulz_data/
│   │           └── [      32864]  0000.tim
│   ├── [       4096]  0331/
│   │   └── [       4096]  ulz_data/
│   │       └── [       8460]  0002.bin
│   ├── [       4096]  0349/
│   │   └── [       4096]  ulz_data/
│   │       └── [       4232]  0002.bin
│   ├── [       4096]  0355/
│   │   └── [       4096]  0001/
│   │       └── [       4096]  ulz_data/
│   │           └── [      32864]  0001.tim
│   ├── [       4096]  0424/
│   │   └── [       4096]  0000/
│   │       └── [       4096]  ulz_data/
│   │           └── [      11208]  0001.bin
│   ├── [       4096]  0429/
│   │   └── [       4096]  0000/
│   │       └── [       4096]  ulz_data/
│   │           └── [      40252]  0000.bin
│   ├── [       4096]  0443/
│   │   └── [       4096]  0000/
│   │       └── [       4096]  ulz_data/
│   │           └── [      40284]  0000.bin
│   ├── [       4096]  0458/
│   │   └── [       4096]  0000/
│   │       └── [       4096]  ulz_data/
│   │           └── [      15772]  0000.bin
│   └── [       4096]  0460/
│       └── [       4096]  0000/
│           └── [       4096]  ulz_data/
│               └── [      10920]  0001.bin
├── [      16947]  report_simple.txt
└── [          0]  report_size.txt

234 directories, 244 files
the same list without the size, easier to read

Code: Alles auswählen

.
├── BPB/
│   ├── 0005/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0006/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0007/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0008/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       ├── 0011.bin
│   │       ├── 0012.bin
│   │       └── 0013.bin
│   ├── 0009/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       ├── 0007.bin
│   │       └── 0013.bin
│   ├── 0010/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       ├── 0013.bin
│   │       └── 0014.bin
│   ├── 0011/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       ├── 0012.bin
│   │       └── 0013.bin
│   ├── 0012/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0013/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0014/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0015/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0016/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       ├── 0006.bin
│   │       └── 0013.bin
│   ├── 0017/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       ├── 0012.bin
│   │       └── 0013.bin
│   ├── 0018/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       ├── 0012.bin
│   │       └── 0013.bin
│   ├── 0019/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0020/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       ├── 0013.bin
│   │       └── 0014.bin
│   ├── 0021/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       ├── 0007.bin
│   │       └── 0013.bin
│   ├── 0022/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0023/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0024/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       ├── 0007.bin
│   │       └── 0013.bin
│   ├── 0025/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       ├── 0007.bin
│   │       ├── 0013.bin
│   │       └── 0014.bin
│   ├── 0026/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       ├── 0007.bin
│   │       └── 0013.bin
│   ├── 0027/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0028/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0029/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0030/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       ├── 0007.bin
│   │       ├── 0010.bin
│   │       └── 0013.bin
│   ├── 0031/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0032/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       ├── 0006.bin
│   │       └── 0013.bin
│   ├── 0033/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0034/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0035/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0036/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0037/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0038/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       ├── 0012.bin
│   │       └── 0013.bin
│   ├── 0039/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0040/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0041/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0042/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       ├── 0007.bin
│   │       └── 0013.bin
│   ├── 0043/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0044/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0045/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0046/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0048/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0049/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       ├── 0007.bin
│   │       └── 0013.bin
│   ├── 0050/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       ├── 0007.bin
│   │       └── 0013.bin
│   ├── 0051/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0052/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       ├── 0012.bin
│   │       └── 0013.bin
│   ├── 0053/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0054/
│   │   └── ulz_data/
│   │       └── 0007.bin
│   ├── 0055/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0056/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0057/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0058/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       ├── 0007.bin
│   │       └── 0013.bin
│   ├── 0059/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       └── 0013.bin
│   ├── 0060/
│   │   └── ulz_data/
│   │       ├── 0002.bin
│   │       ├── 0004.tim
│   │       ├── 0007.bin
│   │       └── 0013.bin
│   ├── 0061/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       ├── 0006.bin
│   │       └── 0013.bin
│   ├── 0062/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       ├── 0007.bin
│   │       └── 0013.bin
│   ├── 0063/
│   │   └── ulz_data/
│   │       ├── 0004.tim
│   │       ├── 0007.bin
│   │       └── 0013.bin
│   ├── 0064/
│   │   └── ulz_data/
│   │       ├── 0003.bin
│   │       └── 0012.bin
│   ├── 0065/
│   │   └── ulz_data/
│   │       ├── 0003.bin
│   │       ├── 0007.bin
│   │       └── 0012.bin
│   ├── 0071/
│   │   └── ulz_data/
│   │       └── 0002.bin
│   ├── 0074/
│   │   └── ulz_data/
│   │       └── 0002.bin
│   ├── 0075/
│   │   └── ulz_data/
│   │       └── 0002.bin
│   ├── 0089/
│   │   └── ulz_data/
│   │       └── 0002.bin
│   ├── 0094/
│   │   └── ulz_data/
│   │       └── 0002.bin
│   ├── 0102/
│   │   └── ulz_data/
│   │       └── 0002.bin
│   ├── 0112/
│   │   └── ulz_data/
│   │       ├── 0004.bin
│   │       └── 0007.bin
│   ├── 0113/
│   │   └── ulz_data/
│   │       ├── 0000.bin
│   │       └── 0003.bin
│   ├── 0114/
│   │   └── 0004/
│   │       └── ulz_data/
│   │           └── 0001.tim
│   ├── 0116/
│   │   └── 0001/
│   │       └── 0006/
│   │           └── ulz_data/
│   │               └── 0001.bin
│   ├── 0118/
│   │   └── 0001/
│   │       └── 0007/
│   │           └── ulz_data/
│   │               └── 0000.bin
│   ├── 0119/
│   │   └── 0001/
│   │       └── 0001/
│   │           └── ulz_data/
│   │               └── 0001.bin
│   ├── 0121/
│   │   └── 0001/
│   │       └── ulz_data/
│   │           ├── 0000.tim
│   │           ├── 0001.tim
│   │           ├── 0002.tim
│   │           ├── 0003.tim
│   │           ├── 0004.tim
│   │           ├── 0008.tim
│   │           ├── 0009.tim
│   │           ├── 0011.tim
│   │           ├── 0013.tim
│   │           ├── 0014.tim
│   │           ├── 0017.tim
│   │           ├── 0019.tim
│   │           ├── 0020.tim
│   │           ├── 0021.tim
│   │           ├── 0022.tim
│   │           ├── 0026.tim
│   │           ├── 0027.tim
│   │           ├── 0028.tim
│   │           ├── 0033.tim
│   │           ├── 0034.tim
│   │           ├── 0035.tim
│   │           ├── 0036.tim
│   │           ├── 0037.tim
│   │           ├── 0038.tim
│   │           ├── 0041.tim
│   │           ├── 0045.tim
│   │           ├── 0057.tim
│   │           └── 0059.tim
│   ├── 0122/
│   │   └── 0002/
│   │       └── ulz_data/
│   │           └── 0009.tim
│   ├── 0123/
│   │   └── 0001/
│   │       └── ulz_data/
│   │           ├── 0000.tim
│   │           ├── 0001.tim
│   │           ├── 0003.tim
│   │           ├── 0010.tim
│   │           ├── 0011.tim
│   │           ├── 0016.tim
│   │           ├── 0019.tim
│   │           ├── 0021.tim
│   │           ├── 0033.tim
│   │           ├── 0036.tim
│   │           ├── 0038.tim
│   │           ├── 0042.tim
│   │           ├── 0043.tim
│   │           ├── 0044.tim
│   │           ├── 0048.tim
│   │           ├── 0049.tim
│   │           ├── 0052.tim
│   │           ├── 0056.tim
│   │           └── 0059.tim
│   ├── 0124/
│   │   ├── 0000/
│   │   │   └── ulz_data/
│   │   │       ├── 0001.tim
│   │   │       └── 0002.tim
│   │   └── ulz_data/
│   │       └── 0001.bin
│   ├── 0125/
│   │   └── 0001/
│   │       └── ulz_data/
│   │           ├── 0000.tim
│   │           └── 0013.tim
│   ├── 0131/
│   │   └── 0003/
│   │       └── ulz_data/
│   │           └── 0000.tim
│   ├── 0142/
│   │   └── ulz_data/
│   │       └── 0007.bin
│   ├── 0159/
│   │   └── ulz_data/
│   │       └── 0007.bin
│   ├── 0160/
│   │   └── ulz_data/
│   │       └── 0007.bin
│   ├── 0164/
│   │   └── ulz_data/
│   │       └── 0007.bin
│   ├── 0166/
│   │   └── 0003/
│   │       └── ulz_data/
│   │           └── 0000.tim
│   ├── 0168/
│   │   └── ulz_data/
│   │       └── 0007.bin
│   ├── 0174/
│   │   └── ulz_data/
│   │       └── 0007.bin
│   ├── 0175/
│   │   └── ulz_data/
│   │       └── 0007.bin
│   ├── 0178/
│   │   └── ulz_data/
│   │       └── 0007.bin
│   ├── 0188/
│   │   └── ulz_data/
│   │       └── 0000.bin
│   ├── 0215/
│   │   └── ulz_data/
│   │       └── 0000.bin
│   ├── 0226/
│   │   └── ulz_data/
│   │       └── 0000.bin
│   ├── 0227/
│   │   └── ulz_data/
│   │       └── 0000.bin
│   ├── 0252/
│   │   └── 0001/
│   │       └── ulz_data/
│   │           └── 0000.tim
│   ├── 0269/
│   │   └── 0001/
│   │       └── ulz_data/
│   │           └── 0000.tim
│   ├── 0270/
│   │   └── ulz_data/
│   │       └── 0002.bin
│   ├── 0304/
│   │   └── 0001/
│   │       └── ulz_data/
│   │           └── 0001.tim
│   ├── 0305/
│   │   └── 0001/
│   │       └── ulz_data/
│   │           └── 0000.tim
│   ├── 0331/
│   │   └── ulz_data/
│   │       └── 0002.bin
│   ├── 0349/
│   │   └── ulz_data/
│   │       └── 0002.bin
│   ├── 0355/
│   │   └── 0001/
│   │       └── ulz_data/
│   │           └── 0001.tim
│   ├── 0424/
│   │   └── 0000/
│   │       └── ulz_data/
│   │           └── 0001.bin
│   ├── 0429/
│   │   └── 0000/
│   │       └── ulz_data/
│   │           └── 0000.bin
│   ├── 0443/
│   │   └── 0000/
│   │       └── ulz_data/
│   │           └── 0000.bin
│   ├── 0458/
│   │   └── 0000/
│   │       └── ulz_data/
│   │           └── 0000.bin
│   └── 0460/
│       └── 0000/
│           └── ulz_data/
│               └── 0001.bin
└── report_simple.txt

234 directories, 243 files
Until my implementation I used the c# code compiled in mono
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [Projekt] Extracting Ace Combat

Beitrag von Krishty »

Okay, then I was already using the correct version. I guess I unknowingly fixed it while translating the code to C++ …
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [Projekt] Extracting Ace Combat

Beitrag von Krishty »

Some notes on the Ace Combat 3 Demo version.

Demos are wonderful to reverse because
  1. they’re heavily reduced (no movies, less UI, …)
  2. you can find some hints on the game’s development because they’re often not as clean as the final versions
E.g. the Driver 2 demo showed us some level features that had been removed for deploy.


Hunting down the demo disks:

The Ace Combat 3 demo was distributed on CD-ROM with various game magazines. I owned one when I was a child, but it’s lost now. It was one of these:
  • Euro Demo Germany 02/00 (SCED-01845)
  • Euro Demo Germany 08/01 (SCED-03471)
  • Euro Demo Germany 05/02 (SCED-03887)
as listed here. I found the three disks here, and the Ace Combat 3 demos are identical. Writes dates are erased on all disks but 02/00, which says 1999-11-24, 16:03:47 UTC.

McDonald’s distributed the same demo (as seen in this video), available here: SCED-03910.

This article mentions an Ace Combat 3 Demo on Disk 2 of the 4.1 issue of the PlayStation Underground magazine. I eventually found the image here: SCUS-94566 (Track 1).

This list includes french demos:
  • Euro Demo 38 France (SCED-01858)
  • Euro Demo 50 France (SCED-02654)
  • Euro Demo 54 France (SCED-02658)
  • Euro Demo 55 France (SCED-03461)
I could not find a single download on them. :(

European demos (UK, Australia, Spain, etc.):
  • Euro Demo 55 (SCED-01829), download
    file date is 1999-11-20, 15:50:20 UTC
  • Official PlayStation Magazine Demo 74 (SCED-03451), broken download
  • Official UK PlayStation Magazine 86 (SCED-03789)
  • Euro Demo (Future) 107 (SCED-04133)

Now for Ace Combat 2:
  • Euro Demo 12 (Germany) (SCED-00677 – vastly different from Demo 12 (Europe), Demo 12-01, Demo 12-99, or any other, for that matter!), download
  • Euro Demo 26 (SCED-00822), download
File structure is also entirely different from the final game, so this will be fun to reverse …


Layout:

The demos are located in the AC3 subfolder. Same BPB/SPB format as the final version. It’s very tiny (2.5 MiB levels + textures + SFX!), and the look & feel is very close to the final version, so I don’t think we’ll find any surprises.

Identical are:
  • the three European demos from German magazines, the European McDonald’s demo, Euro Demo 55
    Let’s call them the European Ace Combat 3 Demo from 1999-11-24, 16:03:47 UTC.
  • the PlayStation Underground demo
    Let’s call it the US Ace Combat 3 Demo.
The difference in size may be due to PAL vs. NTSC. I’ll have a deeper look at the data soon.


Related: There was a great interview with the directors of the Ace Combat games when Ace Combat 6 was released. However, the original article has been trimmed down since the original release (I hate the fucking web so much). A copy can be found here.
  • on Air Combat: “We would have loved to include the replay and landing/take-off features. Actually, we finally got the replay feature working the week after we submitted the game for approval. The landing and take-off feature was up and running just three weeks after. (Even though, we got these features to work, we still needed time to perfect them.) We actually tried to implement both of these features in the game afterwards, but we didn’t make it before the launch. In the end, a copy of Air Combat with the replay and landing/take-off features was something only the development team had.”
  • on Ace Combat 3: “I remember we had the idea that the body of the aircraft would be covered with a non-metallic outer skin layer material known as a "nano-skin". In order to get this feeling of a skin across to the player, we attempted to create aircraft models without joints or seams on the flaps and variable geometry wings. We had a lot of other ideas on how we could differentiate our aircraft from those of the present, but in the end it was just too much for the hardware.”

————

I didn’t know Namco released updated game versions WITH THE SAME FUCKING ID. E.g. Ace Combat 2, Japanese version 1.0 (released in April) vs. 1.1 (released in July), has some amount of changes to game data and EXE, but both versions go by SLPS-00830. Shit. Anyway, let’s see the translation fixes:
Ace Combat 2 1.0 vs. 1.1 changes.png


————

Okay, there are so many versions of the three games that it could be a thread on its own. There even is a re-textured PlayStation 2 version of Ace Combat 2 in the NamCollection
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Antworten