hbmap: fix iterator truncation when size_t < 32bit
[rofl0r-agsutils.git] / agsinject.c
blob2c717cefadf987ec3bbc20b9f52e8104282ed52c
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 /* all of the below is needed because windows' rename() function
29 doesn't work like described in POSIX, it won't overwrite an
30 existing file.
31 rather than including the right header for this winapi function,
32 we define all needed types and prototypes ourselves so pelles C
33 can stay in posix mode. */
34 #define MOVEFILE_REPLACE_EXISTING 0x00000001
35 #define MOVEFILE_COPY_ALLOWED 0x00000002
36 typedef int BOOL;
37 typedef unsigned DWORD;
38 typedef const char *LPCSTR;
39 #define WINAPI __stdcall
40 #define WINBASEAPI
41 #pragma comment(lib, "kernel32.lib")
42 extern WINBASEAPI BOOL WINAPI MoveFileExA(LPCSTR, LPCSTR, DWORD);
43 extern WINBASEAPI DWORD WINAPI GetLastError(void);
44 static int RENAME(const char *OLD, const char *NEW) {
45 BOOL res = MoveFileExA(OLD, NEW, MOVEFILE_REPLACE_EXISTING|MOVEFILE_COPY_ALLOWED);
46 if(res) return 0;
47 DWORD err = GetLastError();
48 fprintf(stderr, "GetLastError(): %u\n", (unsigned) err);
49 errno = EEXIST;
50 return -1;
52 #else
53 #define RENAME(OLD, NEW) rename(OLD, NEW)
54 #endif
56 int usage(char *argv0) {
57 fprintf(stderr,
58 ADS "\n"
59 "usage (simple):\n"
60 "---------------\n"
61 "%s index input.o inject_to.crm\n"
62 "index is the number of script to replace, i.e. 0 for first script\n"
63 "only relevant if the output file is a gamefile which contains multiple scripts\n"
64 "for example gamescript is 0, dialogscript is 1 (if existing), etc\n"
65 "a room file (.crm) only has one script so you must pass 0.\n\n"
67 "usage (extended):\n"
68 "-----------------\n"
69 "%s -e [OPTIONS] target index1:input1.o [index2:input2.o...indexN:inputN.o]\n"
70 "in extended mode, indicated by -e switch, target denotes destination file\n"
71 "(e.g. game28.dta, *.crm...), and file(s) to inject are passed as\n"
72 "index:filename tuples.\n"
73 "this allows to inject several compiled scripts at once.\n"
74 "OPTIONS:\n"
75 "-t : only inject obj files whose timestamps are newer than the one of target.\n"
76 "example: %s -e game28.dta 0:globalscript.o 1:dialogscript.o\n"
77 , argv0, argv0, argv0);
78 return 1;
81 /* inj = filename of file to inject in */
82 static int inject(const char *o, const char *inj, unsigned which) {
83 //ARF_find_code_start
84 AF f_b, *f = &f_b;
85 unsigned long long index, found;
86 int isroom = !strcmp(".crm", inj + strlen(inj) - 4);
87 if(isroom && which != 0) return -2;
88 if(!AF_open(f, inj)) return -1;
89 long long start;
90 for(index = found = 0; 1 ; found++, index = start + 4) {
91 int room_length_bytes = 4;
92 if(!isroom && (start = ARF_find_code_start(f, index)) == -1LL) {
93 fprintf(stderr, "error, only %llu scripts found\n", (long long)found);
94 return -3;
95 } else if(isroom) {
96 /* use roomfile specific script lookup, as it's faster */
97 struct RoomFile rinfo = {0};
98 if(!RoomFile_read(f, &rinfo)) return -3;
99 start = rinfo.blockpos[BLOCKTYPE_COMPSCRIPT3];
100 if(rinfo.version >= 32) room_length_bytes = 8;
102 if(found != which) continue;
103 char *tmp = tempnam(".", "agsinject.tmp");
104 FILE *out = fopen(tmp, "wb");
105 if(!out) return -1;
107 /* 1) dump header */
108 AF_dump_chunk_stream(f, 0, isroom ? start -room_length_bytes : start, out);
109 AF_set_pos(f, start);
111 /* open replacement object file */
112 struct ByteArray b;
113 ByteArray_ctor(&b);
114 ByteArray_open_file(&b, o);
116 if(isroom) {
117 /* 2a) if room, write length */
118 /* room files, unlike game files, have a length field of size 4 before
119 * the compiled script starts. */
120 unsigned l = ByteArray_get_length(&b);
121 struct ByteArray c;
122 ByteArray_ctor(&c);
123 ByteArray_open_mem(&c, 0, 0);
124 ByteArray_set_flags(&c, BAF_CANGROW);
125 ByteArray_set_endian(&c, BAE_LITTLE);
126 ByteArray_writeInt(&c, l);
127 if(room_length_bytes == 8)
128 /* we should actually write one long long
129 instead of 2 ints, but we assume that no
130 room script will be bigger than 2 GB. */
131 ByteArray_writeInt(&c, 0);
132 ByteArray_dump_to_stream(&c, out);
133 ByteArray_close(&c);
135 /* 2b) dump object file */
136 ByteArray_dump_to_stream(&b, out);
137 ByteArray_close_file(&b);
139 ASI s;
140 if(!ASI_read_script(f, &s)) {
141 fprintf(stderr, "trouble finding script in %s\n", inj);
142 return -3;
144 /* 3) dump rest of file */
145 AF_dump_chunk_stream(f, start + s.len, ByteArray_get_length(f->b) - (start + s.len), out);
146 AF_close(f);
147 fclose(out);
149 int rnret = RENAME(tmp, inj);
150 if(rnret == -1 && errno == EEXIST) {
151 /* windows is special, as usual */
152 fprintf(stderr, "rename failed from %s to %s\n", tmp, inj);
154 return rnret;
156 return -5;
159 static int check_objname(const char* o) {
160 const char* p;
161 if(!(p = strrchr(o, '.')) || strcmp(p, ".o")) {
162 fprintf(stderr, "error: object file has no .o extension\n");
163 return 0;
165 return 1;
168 static int injectpr(const char *obj, const char *out, unsigned which) {
169 printf("injecting %s into %s as %d'th script ...", obj, out, which);
170 int ret = inject(obj, out, which);
171 if(ret == 0) printf("OK\n");
172 else {
173 printf("FAIL\n");
174 if(ret == -2) {
175 fprintf(stderr, "invalid index %d for roomfile, only 0 possible\n", which);
176 ret = 0;
177 } else if (ret == -1) perror("error");
178 return 0;
180 return 1;
183 static int getstamp(const char* fn, time_t *stamp) {
184 struct stat st;
185 if(stat(fn, &st) == -1) {
186 perror("stat");
187 return 0;
189 *stamp = st.st_mtime;
190 return 1;
193 static int ts_is_newer(const time_t *t1, const time_t *t2)
195 return *t2 > *t1;
198 int main(int argc, char**argv) {
199 char *out, *obj;
200 int which;
201 if(argc == 4 && isdigit(*argv[1])) {
202 obj = argv[2];
203 out = argv[3];
204 which = atoi(argv[1]);
205 if(!check_objname(obj)) return 1;
206 if(!injectpr(obj, out, which)) return 1;
207 return 0;
209 int c, extended = 0, usestamps = 0;
210 while ((c = getopt(argc, argv, "et")) != EOF) switch(c) {
211 case 'e': extended = 1; break;
212 case 't': usestamps = 1; break;
213 default: return usage(argv[0]);
215 if(!extended || !argv[optind] || !argv[optind+1])
216 return usage(argv[0]);
218 out = argv[optind];
219 time_t stamp = {0};
221 if(usestamps && !getstamp(out, &stamp)) return 1;
223 while(argv[++optind]) {
224 obj = argv[optind];
225 char *p = strchr(obj, ':');
226 if(!isdigit(*obj) || !p) return usage(argv[0]);
227 *p = 0;
228 which = atoi(obj);
229 obj = ++p;
230 if(!check_objname(obj)) return 1;
231 if(usestamps) {
232 time_t ostamp;
233 if(!getstamp(obj, &ostamp)) return 1;
234 if(!ts_is_newer(&stamp, &ostamp)) continue;
236 if(!injectpr(obj, out, which)) return 1;
238 return 0;