hbmap: fix iterator truncation when size_t < 32bit
[rofl0r-agsutils.git] / agscriptxtract.c
blob01f993064e8863661ab566cedbe233c08e64ef36
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>
58 static int is_roomfile(const char *fn) {
59 const char *p = strrchr(fn, '.');
60 if(!p) return 0;
61 ++p;
62 #define LOWER(X) tolower((unsigned) (X))
63 return LOWER(p[0]) == 'c' && LOWER(p[1]) == 'r' && LOWER(p[2]) == 'm' && p[3] == 0;
64 #undef LOWER
67 static int dumprooms(const char* dir, const char* out, int flags) {
68 DIR* d = opendir(dir);
69 if(!d) return 1;
70 int errors = 0;
71 struct dirent* di = 0;
73 while((di = readdir(d))) {
74 size_t l = strlen(di->d_name);
75 if(l > 4 && is_roomfile(di->d_name)) {
76 char fnbuf[512];
77 snprintf(fnbuf, sizeof(fnbuf), "%s%c%s", dir, PSEP, di->d_name);
78 AF f; ssize_t off; ASI s;
79 if(!AF_open(&f, fnbuf)) goto extract_error;
80 struct RoomFile rinfo = {0};
81 if(!RoomFile_read(&f, &rinfo)) goto extract_error;
82 if((off = ARF_find_code_start(&f, 0)) == -1) goto extract_error;
83 assert(off == rinfo.blockpos[BLOCKTYPE_COMPSCRIPT3]);
84 AF_set_pos(&f, off);
85 if(!ASI_read_script(&f, &s)) {
86 fprintf(stderr, "trouble finding script in %s\n", di->d_name);
87 continue;
89 char buf[256];
90 assert(l < sizeof(buf));
91 memcpy(buf, di->d_name, l - 4);
92 buf[l-4] = '.';
93 buf[l-3] = 'o';
94 buf[l-2] = 0;
95 char outbuf[256];
96 AF_dump_chunk(&f, s.start, s.len, filename(out, buf, outbuf, sizeof outbuf));
97 disas(di->d_name, outbuf, flags);
98 size_t sourcelen;
99 char *source = RoomFile_extract_source(&f, &rinfo, &sourcelen);
100 if(source) {
101 buf[l-3] = 'a';
102 buf[l-2] = 's';
103 buf[l-1] = 'c';
104 buf[l] = 0;
105 FILE *f = fopen(filename(out, buf, outbuf, sizeof outbuf), "wb");
106 if(f) {
107 fprintf(stdout, "extracting room source %s -> %s\n", di->d_name, outbuf);
108 fwrite(source, 1, sourcelen, f);
109 fclose(f);
111 free(source);
113 continue;
114 extract_error:
115 fprintf(stderr, "warning: extraction of file %s failed\n", di->d_name);
116 ++errors;
119 closedir(d);
120 return errors;
123 void dump_script(AF* f, ASI* s, char* fn, int flags) {
124 if(!s->len) return;
125 AF_dump_chunk(f, s->start, s->len, fn);
126 disas("game28.dta", fn, flags);
129 static char *pfx_capitalize(char *in, char prefix, char *out) {
130 char *p = out, *n = in;
131 *(p++) = prefix;
132 *(p++) = toupper(*(n++));
133 while(*n) *(p++) = tolower(*(n++));
134 *p = 0;
135 return out;
138 void dump_header(ADF *a, char *fn) {
139 unsigned i;
140 fprintf(stdout, "regenerating script header %s\n", fn);
141 FILE *f = fopen(fn, "w");
142 fprintf(f, "#if SCRIPT_API < 300000 && SCRIPT_API > 262000\n");
143 if(ADF_get_cursorcount(a)) fprintf(f, "enum CursorMode {\n");
144 for(i=0; i<ADF_get_cursorcount(a); ++i) {
145 if(i > 0) fprintf(f, ",\n");
146 char buf[16] = {0}, *p = ADF_get_cursorname(a, i), *q = buf;
147 while(*p) {
148 if(!isspace(*p)) *(q++) = *p;
149 ++p;
151 fprintf(f, " eMode%s = %u", buf, i);
153 if(i) fprintf(f, "};\n");
154 fprintf(f, "import Character character[%zu];\n", ADF_get_charactercount(a));
155 for(i=0; i<ADF_get_charactercount(a); ++i) {
156 char buf[64], *p = buf, *n = ADF_get_characterscriptname(a, i);
157 pfx_capitalize(ADF_get_characterscriptname(a, i), 'c', buf);
158 fprintf(f, "import Character %s;\n", buf);
160 fprintf(f, "import InventoryItem inventory[%zu];\n", ADF_get_inventorycount(a));
161 if(a->inventorynames) for(i=1; i<ADF_get_inventorycount(a); ++i) {
162 fprintf(f, "import InventoryItem %s;\n", ADF_get_inventoryname(a, i));
164 for(i=0; i<ADF_get_guicount(a); ++i) {
165 char buf[64];
166 pfx_capitalize(ADF_get_guiname(a, i), 'g', buf);
167 fprintf(f, "import GUI %s;\n", buf);
169 fprintf(f, "#endif\n");
170 for(i=0; i<ADF_get_charactercount(a); ++i)
171 fprintf(f, "#define %s %zu\n", ADF_get_characterscriptname(a, i), (size_t)i);
172 for(i=0; i<ADF_get_guicount(a); ++i) {
173 char buf[64], *p = ADF_get_guiname(a, i), *q = buf;
174 while(*p) *(q++) = toupper(*(p++));
175 *q = 0;
176 fprintf(f, "#define %s FindGUIID(\"%s\")\n", buf, ADF_get_guiname(a, i));
178 for(i=0; i<ADF_get_viewcount(a); ++i) if(ADF_get_viewname(a, i)[0])
179 fprintf(f, "#define %s %d\n", ADF_get_viewname(a, i), (int) i+1);
181 fclose(f);
184 static void dump_old_dialogscripts(ADF *a, char *dir) {
185 if(!a->old_dialogscripts) return;
186 size_t i, n =a->game.dialogcount;
187 for(i=0; i<n; ++i) {
188 char fnbuf[512];
189 snprintf(fnbuf, sizeof(fnbuf), "%s%cdialogscript%03d.ads", dir, PSEP, (int)i);
190 fprintf(stdout, "extracting dialogscript source %s\n", fnbuf);
191 FILE *f = fopen(fnbuf, "w");
192 if(!f) {
193 perror("fopen");
194 continue;
196 fprintf(f, "%s", a->old_dialogscripts[i]);
197 fclose(f);
201 int main(int argc, char**argv) {
202 int flags = 0, c;
203 while ((c = getopt(argc, argv, "oblf")) != EOF) switch(c) {
204 case 'o': flags |= DISAS_DEBUG_OFFSETS; break;
205 case 'b': flags |= DISAS_DEBUG_BYTECODE; break;
206 case 'l': flags |= DISAS_SKIP_LINENO; break;
207 case 'f': flags |= DISAS_DEBUG_FIXUPS; break;
208 default: return usage(argv[0]);
210 if(!argv[optind]) return usage(argv[0]);
211 char *dir = argv[optind];
212 char *out = argv[optind+1];
213 if(!out) out = ".";
214 else MKDIR(out);
216 int errors = 0;
217 ADF a_b, *a = &a_b;
218 char fnbuf[512];
219 enum ADF_open_error aoe;
220 if(!ADF_find_datafile(dir, fnbuf, sizeof(fnbuf))) {
221 fprintf(stderr, "failed to find datafile\n");
222 return 1;
224 aoe = ADF_open(a, fnbuf);
225 if(aoe != AOE_success && aoe <= AOE_script) {
226 fprintf(stderr, "failed to open/process data file: %s\n", AOE2str(aoe));
227 return 1;
228 } else if (aoe != AOE_success) {
229 fprintf(stderr, "warning: failed to process some non-essential parts (%s) of gamefile, probably from a newer game format\n", AOE2str(aoe));
231 ASI* s;
232 s = ADF_get_global_script(a);
233 char buf[256];
234 dump_script(a->f, s, filename(out, "globalscript.o", buf, sizeof buf), flags);
235 s = ADF_get_dialog_script(a);
236 dump_script(a->f, s, filename(out, "dialogscript.o", buf, sizeof buf), flags);
237 size_t i, l = ADF_get_scriptcount(a);
238 for(i = 0; i < l; i++) {
239 char fnbuf[32];
240 s = ADF_get_script(a, i);
241 snprintf(fnbuf, sizeof(fnbuf), "gamescript%zu.o", i);
242 dump_script(a->f, s, filename(out, fnbuf, buf, sizeof buf), flags);
245 if(aoe == AOE_success) {
246 dump_header(a, filename(out, "builtinscriptheader.ash", buf, sizeof buf));
247 dump_old_dialogscripts(a, out);
248 } else {
249 fprintf(stderr, "skipping scriptheader and dialogscripts due to non-fatal errors\n");
251 ADF_close(a);
252 errors += dumprooms(dir, out, flags);
253 if(errors) fprintf(stderr, "agscriptxtract: got %d errors\n", errors);
255 return !!errors;