Browse Source

Some random stuff?

dhasenan 3 years ago
parent
commit
1ecbdac7b2
6 changed files with 481 additions and 0 deletions
  1. 44
    0
      GOALS.md
  2. 6
    0
      retk/dub.sdl
  3. 44
    0
      retk/source/retk/app.d
  4. 193
    0
      retk/source/retk/ffstrings.d
  5. 74
    0
      retk/source/retk/findpointer.d
  6. 120
    0
      retk/source/retk/offsetscan.d

+ 44
- 0
GOALS.md View File

@@ -0,0 +1,44 @@
1
+# FFIX Romhack Suite
2
+
3
+## Basics
4
+Replace cutscenes with something very short and vaguely amusing.
5
+Remove FMVs.
6
+Replace title screen with something indicating the randomizer config.
7
+Maybe add a config screen? ...I doubt I can make that happen.
8
+
9
+
10
+## Customization
11
+* Window colors
12
+* Pointer icon? Could add Sims style, question mark, Navi...
13
+
14
+
15
+## Postmaster General
16
+Work your way up the ranks of the Moogle Post Office by delivering letters across the world.
17
+
18
+
19
+## Randomized characters
20
+We shuffle around characters. We shuffle around:
21
+* default names
22
+* ability sets
23
+* graphics
24
+
25
+
26
+## Buffet
27
+All your characters have Eat. They learn all abilities by eating.
28
+
29
+
30
+## Gizamaluke's Treasure Hunt
31
+Start with the open world (somehow). You need to find all the bells required to get through
32
+Gizamaluke's Grotto.
33
+
34
+
35
+## Demystifying
36
+Your objective is to get to the root of the Iifa Tree and defeat the monster there, removing Mist
37
+from the world.
38
+
39
+
40
+## Heart of the Cards
41
+You must assemble the Ultimate Card Decks to defeat the Four Masters of Tetra.
42
+
43
+Each Tetra Master has a random weakness. You need to discover their weaknesses, then farm the
44
+appropriate creatures to get their cards.

+ 6
- 0
retk/dub.sdl View File

@@ -0,0 +1,6 @@
1
+name "retk"
2
+description "FF9 reverse engineering swiss army knife"
3
+authors "Neia Neutuladh"
4
+copyright "Copyright © 2018, Neia Neutuladh"
5
+license "MS-PL"
6
+dependency "scriptlike" version="0.10.2"

+ 44
- 0
retk/source/retk/app.d View File

@@ -0,0 +1,44 @@
1
+module retk.app;
2
+
3
+import std.stdio;
4
+import std.path;
5
+
6
+int main(string[] args)
7
+{
8
+    auto n = baseName(args[0]);
9
+    int retVal = 0;
10
+    if (tryExec(n, args, retVal))
11
+    {
12
+        return retVal;
13
+    }
14
+    if (args.length > 1 && tryExec(args[1], args[1..$], retVal))
15
+    {
16
+        return retVal;
17
+    }
18
+
19
+    writefln("unknown tool invoked");
20
+    return 1;
21
+}
22
+
23
+bool tryExec(string tool, string[] args, out int retVal)
24
+{
25
+    switch (tool)
26
+    {
27
+        case "ffstrings":
28
+            import retk.ffstrings;
29
+            retVal = ffstrings(args);
30
+            return true;
31
+        case "findpointer":
32
+            import retk.findpointer;
33
+            retVal = findpointer(args);
34
+            return true;
35
+        case "offsetscan":
36
+            import retk.offsetscan;
37
+            retVal = offsetscan(args);
38
+            return true;
39
+        default:
40
+            break;
41
+    }
42
+    retVal = 0;
43
+    return false;
44
+}

+ 193
- 0
retk/source/retk/ffstrings.d View File

@@ -0,0 +1,193 @@
1
+module retk.ffstrings;
2
+
3
+import scriptlike;
4
+import std.experimental.logger;
5
+
6
+string[256] characters = [
7
+    "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "-", "=", "*", "%", " ",
8
+    "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P",
9
+    "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "(", "!", "?", "“", ":", ".",
10
+    "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p",
11
+    "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", ")", ",", "/", "+", "_", "_",
12
+    "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
13
+    "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
14
+    "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "\'", "_",
15
+    "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
16
+    "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
17
+    "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
18
+    "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
19
+    "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
20
+    "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
21
+    "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
22
+    "_", "_", "_", "_", "_", "_", "_", "\n", "\t", "_", "_", "_", "_", "_", "_", "\0" ];
23
+
24
+
25
+void xxd(string file, string outputPath)
26
+{
27
+    File o;
28
+    if (outputPath == null)
29
+    {
30
+        o = stdout;
31
+    }
32
+    else
33
+    {
34
+        o = File(outputPath, "w");
35
+    }
36
+    scope (exit) o.close;
37
+
38
+    File input = File(file, "rb");
39
+    scope (exit) input.close;
40
+
41
+    static import core.bitop;
42
+    auto numDigits = (core.bitop.bsr(input.size)) / 4 + 1;
43
+    auto offsetFormatString = "%0" ~ numDigits.to!string ~ "x: ";
44
+    auto formatString = offsetFormatString ~ "%02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x  %s";
45
+
46
+    enum chunkSize = 16;
47
+    size_t offset = 0;
48
+    foreach (line; input.byChunk(chunkSize))
49
+    {
50
+        Appender!string ap;
51
+        foreach (b; line)
52
+        {
53
+            switch (b)
54
+            {
55
+                case 0x0: .. case 0x4d:
56
+                    ap ~= characters[b];
57
+                    break;
58
+                case 0x7e:
59
+                    ap ~= "'";
60
+                    break;
61
+                case 0xf7:
62
+                    ap ~= "¶";
63
+                    break;
64
+                case 0xf8:
65
+                    ap ~= "↦";
66
+                    break;
67
+                default:
68
+                    ap ~= "_";
69
+                    break;
70
+            }
71
+        }
72
+        if (line.length == chunkSize)
73
+        {
74
+            o.writefln(
75
+                    formatString,
76
+                    offset,
77
+                    line[0x0],
78
+                    line[0x1],
79
+                    line[0x2],
80
+                    line[0x3],
81
+                    line[0x4],
82
+                    line[0x5],
83
+                    line[0x6],
84
+                    line[0x7],
85
+                    line[0x8],
86
+                    line[0x9],
87
+                    line[0xA],
88
+                    line[0xB],
89
+                    line[0xC],
90
+                    line[0xD],
91
+                    line[0xE],
92
+                    line[0xF],
93
+                    ap.data);
94
+        }
95
+        else
96
+        {
97
+            // We're on the last line. Handle it specially.
98
+            o.writef(offsetFormatString, offset);
99
+            foreach (i, b; line)
100
+            {
101
+                o.writef("%02x", b);
102
+                if (i % 2 == 1) o.write(" ");
103
+            }
104
+            foreach (i; line.length .. chunkSize)
105
+            {
106
+                o.write("  ");
107
+                if (i % 2 == 1) o.write(" ");
108
+            }
109
+            o.write(" ");
110
+            o.writeln(ap.data);
111
+        }
112
+        offset += line.length;
113
+        if (offset % 0x100 == 0)
114
+        {
115
+            o.flush;
116
+        }
117
+    }
118
+}
119
+
120
+int ffstrings(string[] args)
121
+{
122
+    // add other options here
123
+    uint minLength = 4;
124
+    bool showOffsets;
125
+    bool xxdMode;
126
+    string output;
127
+    auto info = getopt(args,
128
+            "v|verbose", "verbose logging", &scriptlikeEcho,
129
+            "dry-run", "dry run", &scriptlikeDryRun,
130
+            "x|xxd", "emulate xxd", &xxdMode,
131
+            "m|min-length", "minimum string length to print", &minLength,
132
+            "o|output", "output file", &output,
133
+            "s|show-offset", "show where in the document the string occurs", &showOffsets);
134
+
135
+    if (info.helpWanted)
136
+    {
137
+        defaultGetoptPrinter("script description here!", info.options);
138
+        return 1;
139
+    }
140
+    globalLogLevel = scriptlikeEcho ? LogLevel.info : LogLevel.warning;
141
+    if (args.length != 2)
142
+    {
143
+        writeln("You must specify exactly one input file.");
144
+        return 1;
145
+    }
146
+    if (xxdMode)
147
+    {
148
+        xxd(args[1], output);
149
+        return 0;
150
+    }
151
+
152
+    foreach (arg; args[1..$])
153
+    {
154
+        Appender!string ap;
155
+        uint i;
156
+        foreach (chunk; File(arg).byChunk(4096))
157
+        {
158
+            foreach (b; chunk)
159
+            {
160
+                switch (b)
161
+                {
162
+                    case 0x09: .. case 0x4d:
163
+                        ap ~= characters[b];
164
+                        break;
165
+                    case 0x7e:
166
+                        ap ~= "'"d;
167
+                        break;
168
+                    case 0xf7:
169
+                        ap ~= "¶"d;
170
+                        break;
171
+                    case 0xf8:
172
+                        ap ~= "↦"d;
173
+                        break;
174
+                    default:
175
+                        if (ap.data.length >= minLength)
176
+                        {
177
+                            if (showOffsets)
178
+                            {
179
+                                writef("0x%x: ", i - ap.data.length);
180
+                            }
181
+                            writeln(ap.data);
182
+                        }
183
+                        ap = Appender!string();
184
+                        break;
185
+                }
186
+                i++;
187
+            }
188
+        }
189
+    }
190
+
191
+    return 0;
192
+}
193
+

+ 74
- 0
retk/source/retk/findpointer.d View File

@@ -0,0 +1,74 @@
1
+module retk.findpointer;
2
+
3
+import scriptlike;
4
+import std.experimental.logger;
5
+
6
+ubyte[4] hexToLittleEndian(string s)
7
+{
8
+    auto u = parse!uint(s, 16);
9
+    ubyte[4] b;
10
+    b[0] = cast(ubyte)(u & 0xFF);
11
+    b[1] = cast(ubyte)((u >> 8) & 0xFF);
12
+    b[2] = cast(ubyte)((u >> 16) & 0xFF);
13
+    b[3] = cast(ubyte)((u >> 24) & 0xFF);
14
+    return b;
15
+}
16
+
17
+ubyte[4] hexToBigEndian(string s)
18
+{
19
+    auto u = parse!uint(s, 16);
20
+    ubyte[4] b;
21
+    b[3] = cast(ubyte)(u & 0xFF);
22
+    b[2] = cast(ubyte)((u >> 8) & 0xFF);
23
+    b[1] = cast(ubyte)((u >> 16) & 0xFF);
24
+    b[0] = cast(ubyte)((u >> 24) & 0xFF);
25
+    return b;
26
+}
27
+
28
+
29
+int findpointer(string[] args)
30
+{
31
+    bool littleEndian = true;
32
+    string[] pointerStrings;
33
+    // add other options here
34
+    auto info = getopt(args,
35
+            "v|verbose", "verbose logging", &scriptlikeEcho,
36
+            "dry-run", "dry run", &scriptlikeDryRun,
37
+            "l|littleendian", "pointers are little-endian (default true)", &littleEndian,
38
+            "p|pointer", &pointerStrings);
39
+    ubyte[4][] pointers = littleEndian
40
+        ? pointerStrings.map!hexToLittleEndian.array
41
+        : pointerStrings.map!hexToBigEndian.array;
42
+
43
+    if (info.helpWanted)
44
+    {
45
+        defaultGetoptPrinter("script description here!", info.options);
46
+        return 1;
47
+    }
48
+    globalLogLevel = scriptlikeEcho ? LogLevel.info : LogLevel.warning;
49
+
50
+    foreach (arg; args[1..$])
51
+    {
52
+        auto offset = 0;
53
+        auto states = new ubyte[pointers.length];
54
+        foreach (chunk; File(arg, "rb").byChunk(4096))
55
+        {
56
+            foreach (b; chunk)
57
+            {
58
+                offset++;
59
+                // This doesn't correctly handle pointers with repeated octets.
60
+                // Oh well?
61
+                foreach (i, p; pointers)
62
+                {
63
+                    if (states[i] >= p.length) states[i] = 0;
64
+                    if (b == p[states[i]]) states[i]++;
65
+                    if (states[i] == p.length)
66
+                        writefln("%x: %s", offset - p.length, pointerStrings[i]);
67
+                }
68
+            }
69
+        }
70
+    }
71
+
72
+
73
+    return 0;
74
+}

+ 120
- 0
retk/source/retk/offsetscan.d View File

@@ -0,0 +1,120 @@
1
+module retk.offsetscan;
2
+
3
+import scriptlike;
4
+import std.experimental.logger;
5
+
6
+size_t minLength = 32;
7
+size_t maxLength = 256;
8
+size_t increment = 4;
9
+size_t start = 0, end = 0;
10
+size_t minCount = 10;
11
+
12
+void parseRange(string s, ref size_t low, ref size_t high)
13
+{
14
+    auto p = s.split('-');
15
+    enforce(p.length == 2, "expected range like 0-4, got " ~ s);
16
+    low = p[0].parse!size_t(16);
17
+    high = p[1].parse!size_t(16);
18
+}
19
+
20
+int offsetscan(string[] args)
21
+{
22
+    string searchRangeStr, stringTableStr;
23
+    auto info = getopt(args,
24
+            config.caseSensitive | config.bundling,
25
+            "v|verbose", "verbose logging", &scriptlikeEcho,
26
+            "dry-run", "dry run", &scriptlikeDryRun,
27
+            config.required, "s|string-table", "string table address range", &stringTableStr,
28
+            "S|search", "where to search", &searchRangeStr,
29
+            "m|min-length", "minimum length of a unit to look for", &minLength,
30
+            "M|max-length", "maximum length of a unit to look for", &maxLength,
31
+            "c|count", "minimum count of units to show", &minCount,
32
+            "i|increment", "increment for unit size", &increment
33
+            );
34
+
35
+    if (info.helpWanted || args.length != 2)
36
+    {
37
+        defaultGetoptPrinter("usage: " ~ args[0] ~ " -s start -e end infile", info.options);
38
+        return 1;
39
+    }
40
+    globalLogLevel = scriptlikeEcho ? LogLevel.info : LogLevel.warning;
41
+    parseRange(stringTableStr, start, end);
42
+
43
+    auto infile = args[1];
44
+
45
+    auto f = File(infile, "r");
46
+    auto buf = new ubyte[1 << 14];  // 16kb
47
+    auto last = new ubyte[buf.length * 2];
48
+    for (auto size = minLength; size <= maxLength; size += increment)
49
+    {
50
+        buf[] = 0;
51
+        last[] = 0;
52
+        f.seek(0);
53
+        auto baseOffset = 0;
54
+        foreach (chunk; f.byChunk(buf))
55
+        {
56
+            last[0 .. $ / 2] = last[$ / 2 .. $];
57
+            // should only have less than a full buffer if we're at the end of the file
58
+            auto lastEnd = last.length / 2 + buf.length;
59
+            last[$ / 2 .. lastEnd] = buf[];
60
+            foreach (mod; 0..size)
61
+            {
62
+                search(last, baseOffset + mod, mod, size);
63
+            }
64
+            baseOffset += chunk.length;
65
+            //return 0;
66
+        }
67
+    }
68
+
69
+    return 0;
70
+}
71
+
72
+void search(ubyte[] buf, size_t baseOffset, size_t mod, size_t size)
73
+{
74
+    size_t s = mod;
75
+    size_t runStart = size_t.max;
76
+    bool haveNonZero = false;
77
+    void maybeReport()
78
+    {
79
+        if (!haveNonZero) return;
80
+        if (runStart == size_t.max) return;
81
+        auto runCount = (s - runStart) / size;
82
+        if (runCount >= minCount)
83
+        {
84
+            writefln("size: %s start: %x end: %x entries: %s",
85
+                    size,
86
+                    runStart + baseOffset,
87
+                    s + baseOffset,
88
+                    runCount);
89
+            stdout.flush;
90
+        }
91
+    }
92
+    for (; s < buf.length - pointerSize; s += size)
93
+    {
94
+        auto ptr = buf.readPointer(s);
95
+        //writefln("ptr: %x start: %x end: %x", ptr, start, end);
96
+        if (start <= ptr && ptr <= end)
97
+        {
98
+            if (ptr != 0) haveNonZero = true;
99
+            if (runStart == size_t.max) runStart = s;
100
+        }
101
+        else if (runStart != size_t.max)
102
+        {
103
+            maybeReport;
104
+            runStart = size_t.max;
105
+            haveNonZero = false;
106
+        }
107
+    }
108
+    maybeReport;
109
+}
110
+
111
+enum pointerSize = 4;
112
+uint readPointer(ubyte[] buf, size_t s)
113
+{
114
+    if (buf.length <= s + pointerSize) return uint.max;
115
+    return
116
+        (buf[s + 3] << 24) |
117
+        (buf[s + 2] << 16) |
118
+        (buf[s + 1] <<  8) |
119
+        (buf[s + 0] <<  0);
120
+}