Forum: PC-Programmierung C/C++: Desassemblierten Decomprimierungs-Algorithmus vereinfachen?


von cppbert (Gast)


Lesenswert?

Ich versuche einen funktionierenden aber noch zu komplex 
aussehenden/unverstandenenen Algorithmus zur Dekompression von Daten zu 
simplifizieren

der Algorithmus stammt aus meinem Reverse Engineering Projekt zum DOS 
Spiel Alpha Waves und wird für den Game-Code und die Spieldaten wie 
Grafiken usw. verwendet

Github: https://github.com/LowLevelMahn/alpha_waves_loader

d.h. der Algorithmus ist ehemals DOS 16bit Binärcode den ich mit IDA Pro 
in Assembler-Code umgewandelt haben und dann ueber ein paar Umwege in 
C/C++ Code

der Algorithmus ist vollständig und kann alle Daten die ich habe 
entpacken
es gibt keine Fehler oder Probleme
ich möchte den Algorithmus nur weiter vereinfachen und bräuchte ein paar 
Tips was das für ein Kompressions-Algorithmus sein können und wie ich 
die Iteration+Stack in eine Rekursive Lösung verwandeln könnte

alle Namen von Variablen basieren auf der Position im Original Code und 
sind keine logischen/sinnvollen Namen

also falls jemand Lust hat (es gibt so wenig Entwickler die sich für 
Reverse-Engineering begeistern können) darf hier gerne seinen Senf zu 
abgeben

das self-contained Beispiel sollte mit jedem C++14 Compiler bauen und 
enthält einen einfach Unit-Test

https://github.com/LowLevelMahn/alpha_waves_loader/blob/main/read_some_file_sub_4/example.cpp

hier die Routine die mir noch Kopfschmerzen bereitete - sieht aus wie 
eine Rekursive (wegen dem Stack) RLE Kompression oder sowas
1
void val_3_non_0( uint8_t*& uncompressed_, const tables_t& tables_, const uint8_t val_3_ )
2
{
3
    struct stack_vals_t
4
    {
5
        uint8_t val_0{};
6
        uint8_t val_1{};
7
    };
8
9
    std::stack<stack_vals_t> stack;
10
11
    auto pushing = [&stack, &tables_]( const uint8_t val_7_ ) {
12
        stack.push( { val_7_, tables_.table2[val_7_] } );
13
        return tables_.table1[val_7_];
14
    };
15
16
    auto popping = [&stack]( uint8_t* val_7_, uint8_t* val_4_ ) {
17
        if( stack.empty() )
18
        {
19
            return true;
20
        }
21
22
        const stack_vals_t stack_val = stack.top();
23
        stack.pop();
24
        *val_7_ = stack_val.val_0;
25
        *val_4_ = stack_val.val_1;
26
27
        return false;
28
    };
29
30
    uint8_t val_7 = val_3_;
31
    uint8_t val_4 = pushing( val_7 );
32
33
    while( true )
34
    {
35
        const uint8_t val_5 = val_4;
36
        const uint8_t val_6 = tables_.table3[val_5];
37
38
        if( val_6 == 0 )
39
        {
40
            *uncompressed_++ = val_4;
41
            if( popping( &val_7, &val_4 ) )
42
            {
43
                return;
44
            }
45
        }
46
        else if( val_7 > val_6 )
47
        {
48
            val_7 = val_6;
49
            val_4 = pushing( val_7 );
50
        }
51
        else
52
        {
53
            val_4 = val_7;
54
            val_7 = val_6;
55
56
            assert( stack.size() >= 0 );
57
            while( true )
58
            {
59
                val_7 = tables_.table4[val_7];
60
61
                if( val_7 == 0 )
62
                {
63
                    *uncompressed_++ = val_5;
64
                    if( popping( &val_7, &val_4 ) )
65
                    {
66
                        return;
67
                    }
68
                    break;
69
                }
70
                else if( val_7 < val_4 )
71
                {
72
                    val_4 = pushing( val_7 );
73
                    break;
74
                }
75
76
                // another run
77
            }
78
        }
79
    }
80
}

hier noch der Original-Code der Routine: 
https://github.com/LowLevelMahn/alpha_waves_loader/blob/db422a5475a1939427b6379e688d23844535b7a9/ae.asm#L970

oder meine C++/Assembler-Emulation davon - um die Semantik des 
Algorithmus stubide "fehlerfreier" nach C/C++ Übertragen zu können:

https://github.com/LowLevelMahn/alpha_waves_loader/blob/456b2731b654c3a04c909b44c77a6ab4602e5c21/read_some_file_sub_4/original_port.cpp#L9

Der disassemblierte Assemblercode ist so korrigiert das er ein 
binärgleiche Datei wie der Original Loader erzeugt d.h. es ist ist 100% 
Sicher das dieser Code vollständig und korrekt disassembliert ist

btw: zur Problematik mit Reverse Engineering, das Game ist offiziell 
Freeware und ich habe mit dem Original-Author und dem DOS-Portierer 
Kontakt gehabt, rechtlich alles OK - leider hatten beide keine 
Quelltext-Sicherung :/

von cppbert (Gast)


Lesenswert?

Das ist übrigends das fertige Projekt zu diesem Post:

Beitrag "DOS 16Bit: Interrupt 0xF0, hat jemand eine Idee was das ist?"

ja es ist ein Sound-Plugin das als TSR durch den Loader vor dem 
Spiel-Start installiert wird - mit wilden alten Anti-Crack Techniken um 
das schwer analysierbar zu machen - das TSR und das Spiele kommunizieren 
dann über den Interrupt F0

von Mombert H. (mh_mh)


Lesenswert?

Ich kann nicht wirklich etwas zu dem Algorithmus sagen, habe aber ein 
paar Fragen zu dem Projekt.

Was genau ist das Ziel? Ein Version des Spiels, das auf direkt auf 
aktuellen Systemen lauffähig ist und evtl. Bugfixes und Verbesserungen?

Wenn ja, ist es dann nicht besser die Kompression/Dekompression komplett 
zu ersetzen mit einer gut dokumentiertem und getesteten Bibliothek? Wenn 
ich dich richtig verstehe, kannst du das Verhalten des Orginals bitgenau 
reproduzieren. Lager die Funktionen in ein Tool aus. Nutze dieses Tool 
und konvertiere in ein bekanntes Format. Das Spiel/Plugin selbst hat 
dann nichts mehr mit dieser "Altlast" zu tun. Das macht die 
Dokumentation des Spiels deutlich einfacher und andere Personen, die 
evtl. etwas ändern wollen am Spiel, haben einen deutlich einfacheren 
Einstieg. Das Tool selbst braucht dann nur minimale Dokumentation und 
muss idealerweise nur ein mal genutzt werden, um vorhandene Resourcen zu 
konvertieren.

: Bearbeitet durch User
von Terminator (Gast)


Lesenswert?

Also ich würde mal damit anfangen, die ganzen Variablen die auf den 
künstlichen Stack gepusht werden zu überprüfen, ob die weiter genutzt 
werden. Wenn nicht kann man sie ja weglassen. Dann würde ich die 
Variablen auf den vom künstlichen Stack als Parameter von Funktionen 
aufnehmen.

von Ben B. (Firma: Funkenflug Industries) (stromkraft)


Lesenswert?

Weiß jetzt nicht wie alt das Spiel ist, aber zu DOS-Zeiten konnte es 
mitunter wichtig sein, seinen Code so klein wie möglich zu kriegen. 
Ladezeiten von Diskette z.B. sind deutlich länger als das Entpacken 
eines größeren Programms im Speicher.

Ich hab meine compilierten .EXE damals meistens auch mit einem 
Laufzeitpacker verkleinert. War vielleicht nicht wirklich nötig, aber 
damals war's erstrebenswert, kleine und kompakte Programme zu haben. 
Heute ist das alles Scheißegal, da gönnt sich ein einfacher Browser mit 
dem Fuchs gerne schon mal 20 Gigabyte RAM wenn der Stream lange genug 
gelaufen ist, ein DLC für ein beliebtes eigentlich nicht-nur-Ballerspiel 
zum spontanen Download ist schon mal 10 Gigabyte groß (wenn man das 
ganze Spiel online installiert fallen grob geschätzt 120 Gigabyte an) 
... da lacht man heute drüber. Außer du wohnst in der letzten Straße wo 
das große geldgierige Grauen in Magenta und Konsorten selbst im Jahre 
2022 noch keine Verbindung mit mehr als 3Mbit/s anbieten können. Dann 
kannst du wahrscheinlich weniger darüber lachen und wünschst dir die 
guten alten Zeiten zurück.

von cppbert (Gast)


Lesenswert?

Mombert H. schrieb:
> paar Fragen zu dem Projekt.
> Was genau ist das Ziel? Ein Version des Spiels, das auf direkt auf
> aktuellen Systemen lauffähig ist und evtl. Bugfixes und Verbesserungen?

ja das wäre mein großes Ziel

aber wegen chronischem Zeitmangel habe ich mich erstmal auf die Analyse 
des Spiele-Loaders gestürzt um direkt die original Exe (die vom Loader 
im Speicher zusammengesetzt wird) zu extrahieren und ein paar andere 
Dinge um die Spiel-Analyse zu vereinfachen - das läuft und dafür 
brauchte ich diesen Algorithmus

aber im Grund geht es mir gerade nur darum den Algorithmus genauer zu 
verstehen und ihn zu vereinfachen - am besten sogar noch ein 
pack-Funktion zu schreiben

für eine neue/verbesserte Version würde ich natürlich aktuelle Formate 
und Libs nutzen

von cppbert (Gast)


Lesenswert?

Terminator schrieb:
> Also ich würde mal damit anfangen, die ganzen Variablen die auf
> den
> künstlichen Stack gepusht werden zu überprüfen, ob die weiter genutzt
> werden. Wenn nicht kann man sie ja weglassen.

jede Variable wird gebraucht - man kann nichts mehr weglassen

> Dann würde ich die Variablen auf den vom künstlichen
> Stack als Parameter von Funktionen aufnehmen.

kannst du das genauer erklären? konkret am Code

von cppbert (Gast)


Lesenswert?

Ben B. schrieb:
> Weiß jetzt nicht wie alt das Spiel ist, aber zu DOS-Zeiten konnte
> es
> mitunter wichtig sein, seinen Code so klein wie möglich zu kriegen.
> Ladezeiten von Diskette z.B. sind deutlich länger als das Entpacken
> eines größeren Programms im Speicher.

genau das ist die Aufgabe des analysierten Loaders des Spiels - Platz 
sparen für weniger Disketten

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.