add ByteArray_get_mem(), resolve FIXME
[rofl0r-agsutils.git] / agsinject.c
blob0d1362fbec8d890058c90eb5ac2d972520c4f763
1 #define _GNU_SOURCE
2 #include "DataFile.h"
3 #include "RoomFile.h"
4 #include "ByteArray.h"
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <unistd.h>
8 #include <ctype.h>
9 #include <errno.h>
10 #include <sys/stat.h>
11 #include "version.h"
12 #define ADS ":::AGSinject " VERSION " by rofl0r:::"
14 #ifdef __POCC__
15 #include <time.h>
16 static char *tempnam(const char *dir, const char *pfx) {
17 (void) dir;
18 char buf[L_tmpnam + 64];
19 buf[0] = 0;
20 tmpnam(buf);
21 int i; char *p = buf + strlen(buf);
22 for(i = 0; pfx[i] && i < 8; ++i, ++p)
23 *p = pfx[i];
24 *p = 0;
25 sprintf(p, "%016llx", (unsigned long long) _rdtsc());
26 return strdup(buf);
28 static int RENAME(const char *OLD, const char *NEW) {
29 unlink(NEW);
30 return rename(OLD, NEW);
32 #else
33 #define RENAME(OLD, NEW) rename(OLD, NEW)
34 #endif
36 int usage(char *argv0) {
37 fprintf(stderr,
38 ADS "\n"
39 "usage (simple):\n"
40 "---------------\n"
41 "%s index input.o inject_to.crm\n"
42 "index is the number of script to replace, i.e. 0 for first script\n"
43 "only relevant if the output file is a gamefile which contains multiple scripts\n"
44 "for example gamescript is 0, dialogscript is 1 (if existing), etc\n"
45 "a room file (.crm) only has one script so you must pass 0.\n\n"
47 "usage (extended):\n"
48 "-----------------\n"
49 "%s -e [OPTIONS] target index1:input1.o [index2:input2.o...indexN:inputN.o]\n"
50 "in extended mode, indicated by -e switch, target denotes destination file\n"
51 "(e.g. game28.dta, *.crm...), and file(s) to inject are passed as\n"
52 "index:filename tuples.\n"
53 "this allows to inject several compiled scripts at once.\n"
54 "OPTIONS:\n"
55 "-t : only inject obj files whose timestamps are newer than the one of target.\n"
56 "example: %s -e game28.dta 0:globalscript.o 1:dialogscript.o\n"
57 , argv0, argv0, argv0);
58 return 1;
61 /* inj = filename of file to inject in */
62 static int inject(const char *o, const char *inj, unsigned which) {
63 //ARF_find_code_start
64 AF f_b, *f = &f_b;
65 unsigned long long index, found;
66 int isroom = !strcmp(".crm", inj + strlen(inj) - 4);
67 if(isroom && which != 0) return -2;
68 if(!AF_open(f, inj)) return -1;
69 long long start;
70 for(index = found = 0; 1 ; found++, index = start + 4) {
71 int room_length_bytes = 4;
72 if(!isroom && (start = ARF_find_code_start(f, index)) == -1LL) {
73 fprintf(stderr, "error, only %llu scripts found\n", (long long)found);
74 return -3;
75 } else if(isroom) {
76 /* use roomfile specific script lookup, as it's faster */
77 struct RoomFile rinfo = {0};
78 if(!RoomFile_read(f, &rinfo)) return -3;
79 start = rinfo.blockpos[BLOCKTYPE_COMPSCRIPT3];
80 if(rinfo.version >= 32) room_length_bytes = 8;
82 if(found != which) continue;
83 char *tmp = tempnam(".", "agsinject.tmp");
84 FILE *out = fopen(tmp, "wb");
85 if(!out) return -1;
87 /* 1) dump header */
88 AF_dump_chunk_stream(f, 0, isroom ? start -room_length_bytes : start, out);
89 AF_set_pos(f, start);
91 /* open replacement object file */
92 struct ByteArray b;
93 ByteArray_ctor(&b);
94 ByteArray_open_file(&b, o);
96 if(isroom) {
97 /* 2a) if room, write length */
98 /* room files, unlike game files, have a length field of size 4 before
99 * the compiled script starts. */
100 unsigned l = ByteArray_get_length(&b);
101 struct ByteArray c;
102 ByteArray_ctor(&c);
103 ByteArray_open_mem(&c, 0, 0);
104 ByteArray_set_flags(&c, BAF_CANGROW);
105 ByteArray_set_endian(&c, BAE_LITTLE);
106 ByteArray_writeInt(&c, l);
107 if(room_length_bytes == 8)
108 /* we should actually write one long long
109 instead of 2 ints, but we assume that no
110 room script will be bigger than 2 GB. */
111 ByteArray_writeInt(&c, 0);
112 ByteArray_dump_to_stream(&c, out);
113 ByteArray_close(&c);
115 /* 2b) dump object file */
116 ByteArray_dump_to_stream(&b, out);
117 ByteArray_close_file(&b);
119 ASI s;
120 if(!ASI_read_script(f, &s)) {
121 fprintf(stderr, "trouble finding script in %s\n", inj);
122 return -3;
124 /* 3) dump rest of file */
125 AF_dump_chunk_stream(f, start + s.len, ByteArray_get_length(f->b) - (start + s.len), out);
126 AF_close(f);
127 fclose(out);
129 int rnret = RENAME(tmp, inj);
130 if(rnret == -1 && errno == EEXIST) {
131 /* windows is special, as usual */
132 fprintf(stderr, "rename failed from %s to %s\n", tmp, inj);
134 return rnret;
136 return -5;
139 static int check_objname(const char* o) {
140 const char* p;
141 if(!(p = strrchr(o, '.')) || strcmp(p, ".o")) {
142 fprintf(stderr, "error: object file has no .o extension\n");
143 return 0;
145 return 1;
148 static int injectpr(const char *obj, const char *out, unsigned which) {
149 printf("injecting %s into %s as %d'th script ...", obj, out, which);
150 int ret = inject(obj, out, which);
151 if(ret == 0) printf("OK\n");
152 else {
153 printf("FAIL\n");
154 if(ret == -2) {
155 fprintf(stderr, "invalid index %d for roomfile, only 0 possible\n", which);
156 ret = 0;
157 } else if (ret == -1) perror("error");
158 return 0;
160 return 1;
163 static int getstamp(const char* fn, time_t *stamp) {
164 struct stat st;
165 if(stat(fn, &st) == -1) {
166 perror("stat");
167 return 0;
169 *stamp = st.st_mtime;
170 return 1;
173 static int ts_is_newer(const time_t *t1, const time_t *t2)
175 return *t2 > *t1;
178 int main(int argc, char**argv) {
179 char *out, *obj;
180 int which;
181 if(argc == 4 && isdigit(*argv[1])) {
182 obj = argv[2];
183 out = argv[3];
184 which = atoi(argv[1]);
185 if(!check_objname(obj)) return 1;
186 if(!injectpr(obj, out, which)) return 1;
187 return 0;
189 int c, extended = 0, usestamps = 0;
190 while ((c = getopt(argc, argv, "et")) != EOF) switch(c) {
191 case 'e': extended = 1; break;
192 case 't': usestamps = 1; break;
193 default: return usage(argv[0]);
195 if(!extended || !argv[optind] || !argv[optind+1])
196 return usage(argv[0]);
198 out = argv[optind];
199 time_t stamp = {0};
201 if(usestamps && !getstamp(out, &stamp)) return 1;
203 while(argv[++optind]) {
204 obj = argv[optind];
205 char *p = strchr(obj, ':');
206 if(!isdigit(*obj) || !p) return usage(argv[0]);
207 *p = 0;
208 which = atoi(obj);
209 obj = ++p;
210 if(!check_objname(obj)) return 1;
211 if(usestamps) {
212 time_t ostamp;
213 if(!getstamp(obj, &ostamp)) return 1;
214 if(!ts_is_newer(&stamp, &ostamp)) continue;
216 if(!injectpr(obj, out, which)) return 1;
218 return 0;