add ByteArray_get_mem(), resolve FIXME
[rofl0r-agsutils.git] / agscriptxtract.c
blob710a5f20d53b607fcdf413de5f749f348bda1288
1 #define _GNU_SOURCE
2 #include "DataFile.h"
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <unistd.h>
6 #include <ctype.h>
7 #include "version.h"
9 #ifdef _WIN32
10 #include <direct.h>
11 #define MKDIR(D) mkdir(D)
12 #define PSEP '\\'
13 #else
14 #define MKDIR(D) mkdir(D, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)
15 #define PSEP '/'
16 #endif
18 #define ADS ":::AGStract " VERSION " by rofl0r:::"
20 static int usage(char *argv0) {
21 fprintf(stderr, ADS "\nusage:\n%s [-oblf] dir [outdir]\n"
22 "extract all scripts from game files in dir\n"
23 "pass a directory with extracted game files.\n"
24 "options:\n"
25 "-o : dump offset comments in disassembly\n"
26 "-b : dump hexadecimal bytecode comments in disassembly\n"
27 "-f : dump informative original fixups section\n"
28 "-l : remove linenumber debug assembly directives [produces smaller files]\n"
29 , argv0);
30 return 1;
33 static void disas(const char*inp, char *o, int flags) {
34 //ARF_find_code_start
35 AF f_b, *f = &f_b;
36 ASI sc;
37 if(AF_open(f, o)) {
38 char s[256];
39 size_t l = strlen(o);
40 memcpy(s, o, l + 1);
41 s[l-1] = 's';
42 ASI *i = ASI_read_script(f, &sc) ? &sc : 0;
43 fprintf(stdout, "disassembling [%s] %s -> %s", inp, o, s);
44 if(!i || !ASI_disassemble(f, i, s, flags)) fprintf(stdout, " FAIL");
45 fprintf(stdout, "\n");
46 AF_close(f);
50 static char *filename(const char *dir, const char *fn, char *buf, size_t bsize) {
51 snprintf(buf, bsize, "%s%c%s", dir, PSEP, fn);
52 return buf;
55 #include "RoomFile.h"
56 #include <dirent.h>
57 static int dumprooms(const char* dir, const char* out, int flags) {
58 DIR* d = opendir(dir);
59 if(!d) return 1;
60 int errors = 0;
61 struct dirent* di = 0;
63 while((di = readdir(d))) {
64 size_t l = strlen(di->d_name);
65 if(l > 4 + 4 && !memcmp(di->d_name, "room", 4) && !memcmp(di->d_name + l - 4, ".crm", 4)) {
66 char fnbuf[512];
67 snprintf(fnbuf, sizeof(fnbuf), "%s%c%s", dir, PSEP, di->d_name);
68 AF f; ssize_t off; ASI s;
69 if(!AF_open(&f, fnbuf)) goto extract_error;
70 struct RoomFile rinfo = {0};
71 if(!RoomFile_read(&f, &rinfo)) goto extract_error;
72 if((off = ARF_find_code_start(&f, 0)) == -1) goto extract_error;
73 assert(off == rinfo.blockpos[BLOCKTYPE_COMPSCRIPT3]);
74 AF_set_pos(&f, off);
75 if(!ASI_read_script(&f, &s)) {
76 fprintf(stderr, "trouble finding script in %s\n", di->d_name);
77 continue;
79 char buf[256];
80 assert(l < sizeof(buf));
81 memcpy(buf, di->d_name, l - 4);
82 buf[l-4] = '.';
83 buf[l-3] = 'o';
84 buf[l-2] = 0;
85 char outbuf[256];
86 AF_dump_chunk(&f, s.start, s.len, filename(out, buf, outbuf, sizeof outbuf));
87 disas(di->d_name, outbuf, flags);
88 size_t sourcelen;
89 char *source = RoomFile_extract_source(&f, &rinfo, &sourcelen);
90 if(source) {
91 buf[l-3] = 'a';
92 buf[l-2] = 's';
93 buf[l-1] = 'c';
94 buf[l] = 0;
95 FILE *f = fopen(filename(out, buf, outbuf, sizeof outbuf), "wb");
96 if(f) {
97 fprintf(stdout, "extracting room source %s -> %s\n", di->d_name, outbuf);
98 fwrite(source, 1, sourcelen, f);
99 fclose(f);
101 free(source);
103 continue;
104 extract_error:
105 fprintf(stderr, "warning: extraction of file %s failed\n", di->d_name);
106 ++errors;
109 closedir(d);
110 return errors;
113 void dump_script(AF* f, ASI* s, char* fn, int flags) {
114 if(!s->len) return;
115 AF_dump_chunk(f, s->start, s->len, fn);
116 disas("game28.dta", fn, flags);
119 static char *pfx_capitalize(char *in, char prefix, char *out) {
120 char *p = out, *n = in;
121 *(p++) = prefix;
122 *(p++) = toupper(*(n++));
123 while(*n) *(p++) = tolower(*(n++));
124 *p = 0;
125 return out;
128 void dump_header(ADF *a, char *fn) {
129 unsigned i;
130 fprintf(stdout, "regenerating script header %s\n", fn);
131 FILE *f = fopen(fn, "w");
132 fprintf(f, "#if SCRIPT_API < 300000 && SCRIPT_API > 262000\n");
133 if(ADF_get_cursorcount(a)) fprintf(f, "enum CursorMode {\n");
134 for(i=0; i<ADF_get_cursorcount(a); ++i) {
135 if(i > 0) fprintf(f, ",\n");
136 char buf[16] = {0}, *p = ADF_get_cursorname(a, i), *q = buf;
137 while(*p) {
138 if(!isspace(*p)) *(q++) = *p;
139 ++p;
141 fprintf(f, " eMode%s = %u", buf, i);
143 if(i) fprintf(f, "};\n");
144 fprintf(f, "import Character character[%zu];\n", ADF_get_charactercount(a));
145 for(i=0; i<ADF_get_charactercount(a); ++i) {
146 char buf[64], *p = buf, *n = ADF_get_characterscriptname(a, i);
147 pfx_capitalize(ADF_get_characterscriptname(a, i), 'c', buf);
148 fprintf(f, "import Character %s;\n", buf);
150 fprintf(f, "import InventoryItem inventory[%zu];\n", ADF_get_inventorycount(a));
151 if(a->inventorynames) for(i=1; i<ADF_get_inventorycount(a); ++i) {
152 fprintf(f, "import InventoryItem %s;\n", ADF_get_inventoryname(a, i));
154 for(i=0; i<ADF_get_guicount(a); ++i) {
155 char buf[64];
156 pfx_capitalize(ADF_get_guiname(a, i), 'g', buf);
157 fprintf(f, "import GUI %s;\n", buf);
159 fprintf(f, "#endif\n");
160 for(i=0; i<ADF_get_charactercount(a); ++i)
161 fprintf(f, "#define %s %zu\n", ADF_get_characterscriptname(a, i), (size_t)i);
162 for(i=0; i<ADF_get_guicount(a); ++i) {
163 char buf[64], *p = ADF_get_guiname(a, i), *q = buf;
164 while(*p) *(q++) = toupper(*(p++));
165 *q = 0;
166 fprintf(f, "#define %s FindGUIID(\"%s\")\n", buf, ADF_get_guiname(a, i));
168 for(i=0; i<ADF_get_viewcount(a); ++i) if(ADF_get_viewname(a, i)[0])
169 fprintf(f, "#define %s %d\n", ADF_get_viewname(a, i), (int) i+1);
171 fclose(f);
174 static void dump_old_dialogscripts(ADF *a, char *dir) {
175 if(!a->old_dialogscripts) return;
176 size_t i, n =a->game.dialogcount;
177 for(i=0; i<n; ++i) {
178 char fnbuf[512];
179 snprintf(fnbuf, sizeof(fnbuf), "%s%cdialogscript%03d.ads", dir, PSEP, (int)i);
180 fprintf(stdout, "extracting dialogscript source %s\n", fnbuf);
181 FILE *f = fopen(fnbuf, "w");
182 if(!f) {
183 perror("fopen");
184 continue;
186 fprintf(f, "%s", a->old_dialogscripts[i]);
187 fclose(f);
191 int main(int argc, char**argv) {
192 int flags = 0, c;
193 while ((c = getopt(argc, argv, "oblf")) != EOF) switch(c) {
194 case 'o': flags |= DISAS_DEBUG_OFFSETS; break;
195 case 'b': flags |= DISAS_DEBUG_BYTECODE; break;
196 case 'l': flags |= DISAS_SKIP_LINENO; break;
197 case 'f': flags |= DISAS_DEBUG_FIXUPS; break;
198 default: return usage(argv[0]);
200 if(!argv[optind]) return usage(argv[0]);
201 char *dir = argv[optind];
202 char *out = argv[optind+1];
203 if(!out) out = ".";
204 else MKDIR(out);
206 int errors = 0;
207 ADF a_b, *a = &a_b;
208 char fnbuf[512];
209 enum ADF_open_error aoe;
210 if(!ADF_find_datafile(dir, fnbuf, sizeof(fnbuf))) {
211 fprintf(stderr, "failed to find datafile\n");
212 return 1;
214 aoe = ADF_open(a, fnbuf);
215 if(aoe != AOE_success && aoe <= AOE_script) {
216 fprintf(stderr, "failed to open/process data file: %s\n", AOE2str(aoe));
217 return 1;
218 } else if (aoe != AOE_success) {
219 fprintf(stderr, "warning: failed to process some non-essential parts (%s) of gamefile, probably from a newer game format\n", AOE2str(aoe));
221 ASI* s;
222 s = ADF_get_global_script(a);
223 char buf[256];
224 dump_script(a->f, s, filename(out, "globalscript.o", buf, sizeof buf), flags);
225 s = ADF_get_dialog_script(a);
226 dump_script(a->f, s, filename(out, "dialogscript.o", buf, sizeof buf), flags);
227 size_t i, l = ADF_get_scriptcount(a);
228 for(i = 0; i < l; i++) {
229 char fnbuf[32];
230 s = ADF_get_script(a, i);
231 snprintf(fnbuf, sizeof(fnbuf), "gamescript%zu.o", i);
232 dump_script(a->f, s, filename(out, fnbuf, buf, sizeof buf), flags);
235 if(aoe == AOE_success) {
236 dump_header(a, filename(out, "builtinscriptheader.ash", buf, sizeof buf));
237 dump_old_dialogscripts(a, out);
238 } else {
239 fprintf(stderr, "skipping scriptheader and dialogscripts due to non-fatal errors\n");
241 ADF_close(a);
242 errors += dumprooms(dir, out, flags);
243 if(errors) fprintf(stderr, "agscriptxtract: got %d errors\n", errors);
245 return !!errors;