agssim: implement zeromemory insn
[rofl0r-agsutils.git] / agsinject.c
blob56a618df2cc7cb71d9f0512437a1704782d7a4a4
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 <sys/stat.h>
10 #include "version.h"
11 #define ADS ":::AGSinject " VERSION " by rofl0r:::"
13 int usage(char *argv0) {
14 dprintf(2,
15 ADS "\n"
16 "usage (simple):\n"
17 "---------------\n"
18 "%s index input.o inject_to.crm\n"
19 "index is the number of script to replace, i.e. 0 for first script\n"
20 "only relevant if the output file is a gamefile which contains multiple scripts\n"
21 "for example gamescript is 0, dialogscript is 1 (if existing), etc\n"
22 "a room file (.crm) only has one script so you must pass 0.\n\n"
24 "usage (extended):\n"
25 "-----------------\n"
26 "%s -e [OPTIONS] target index1:input1.o [index2:input2.o...indexN:inputN.o]\n"
27 "in extended mode, indicated by -e switch, target denotes destination file\n"
28 "(e.g. game28.dta, *.crm...), and file(s) to inject are passed as\n"
29 "index:filename tuples.\n"
30 "this allows to inject several compiled scripts at once.\n"
31 "OPTIONS:\n"
32 "-t : only inject obj files whose timestamps are newer than the one of target.\n"
33 "example: %s -e game28.dta 0:globalscript.o 1:dialogscript.o\n"
34 , argv0, argv0, argv0);
35 return 1;
38 /* inj = filename of file to inject in */
39 static int inject(const char *o, const char *inj, unsigned which) {
40 //ARF_find_code_start
41 AF f_b, *f = &f_b;
42 size_t index, found;
43 int isroom = !strcmp(".crm", inj + strlen(inj) - 4);
44 if(isroom && which != 0) return -1;
45 if(!AF_open(f, inj)) return 0;
46 ssize_t start;
47 for(index = found = 0; 1 ; found++, index = start + 4) {
48 if(!isroom && (start = ARF_find_code_start(f, index)) == -1) {
49 dprintf(2, "error, only %zu scripts found\n", found);
50 return 0;
51 } else if(isroom) {
52 /* use roomfile specific script lookup, as it's faster */
53 struct RoomFile rinfo = {0};
54 if(!RoomFile_read(f, &rinfo)) return 0;
55 start = rinfo.blockpos[BLOCKTYPE_COMPSCRIPT3];
57 if(found != which) continue;
58 char *tmp = tempnam(".", "agsinject.tmp");
59 FILE *out = fopen(tmp, "w");
60 if(!out) return 0;
62 /* 1) dump header */
63 AF_dump_chunk_stream(f, 0, isroom ? start -4 : start, out);
64 AF_set_pos(f, start);
66 /* open replacement object file */
67 struct ByteArray b;
68 ByteArray_ctor(&b);
69 ByteArray_open_file(&b, o);
71 if(isroom) {
72 /* 2a) if room, write length */
73 /* room files, unlike game files, have a length field of size 4 before
74 * the compiled script starts. */
75 unsigned l = ByteArray_get_length(&b);
76 struct ByteArray c;
77 ByteArray_ctor(&c);
78 ByteArray_open_mem(&c, 0, 0);
79 ByteArray_set_flags(&c, BAF_CANGROW);
80 ByteArray_set_endian(&c, BAE_LITTLE);
81 ByteArray_writeInt(&c, l);
82 ByteArray_dump_to_stream(&c, out);
83 ByteArray_close(&c);
85 /* 2b) dump object file */
86 ByteArray_dump_to_stream(&b, out);
87 ByteArray_close_file(&b);
89 ASI s;
90 if(!ASI_read_script(f, &s)) {
91 dprintf(2, "trouble finding script in %s\n", inj);
92 return 0;
94 /* 3) dump rest of file */
95 AF_dump_chunk_stream(f, start + s.len, ByteArray_get_length(f->b) - (start + s.len), out);
96 AF_close(f);
97 fclose(out);
98 return !rename(tmp, inj);
100 return 0;
103 static int check_objname(const char* o) {
104 const char* p;
105 if(!(p = strrchr(o, '.')) || strcmp(p, ".o")) {
106 dprintf(2, "error: object file has no .o extension\n");
107 return 0;
109 return 1;
112 static int injectpr(const char *obj, const char *out, unsigned which) {
113 printf("injecting %s into %s as %d'th script ...", obj, out, which);
114 int ret = inject(obj, out, which);
115 if(ret >= 0) printf("OK\n");
116 else {
117 printf("FAIL\n");
118 if(ret == -1) {
119 dprintf(2, "invalid index %d for roomfile, only 0 possible\n", which);
120 ret = 0;
121 } else perror("error:");
123 return ret;
126 static int getstamp(const char* fn, struct timespec *stamp) {
127 struct stat st;
128 if(stat(fn, &st) == -1) {
129 perror("stat");
130 return 0;
132 *stamp = st.st_mtim;
133 return 1;
136 static int ts_is_newer(const struct timespec *t1, const struct timespec *t2)
138 return t2->tv_sec > t1->tv_sec ||
139 (t2->tv_sec == t1->tv_sec && t2->tv_nsec > t1->tv_nsec);
142 int main(int argc, char**argv) {
143 char *out, *obj;
144 int which;
145 if(argc == 4 && isdigit(*argv[1])) {
146 obj = argv[2];
147 out = argv[3];
148 which = atoi(argv[1]);
149 if(!check_objname(obj)) return 1;
150 if(!injectpr(obj, out, which)) return 1;
151 return 0;
153 int c, extended = 0, usestamps = 0;
154 while ((c = getopt(argc, argv, "et")) != EOF) switch(c) {
155 case 'e': extended = 1; break;
156 case 't': usestamps = 1; break;
157 default: return usage(argv[0]);
159 if(!extended || !argv[optind] || !argv[optind+1])
160 return usage(argv[0]);
162 out = argv[optind];
163 struct timespec stamp;
165 if(usestamps && !getstamp(out, &stamp)) return 1;
167 while(argv[++optind]) {
168 obj = argv[optind];
169 char *p = strchr(obj, ':');
170 if(!isdigit(*obj) || !p) return usage(argv[0]);
171 *p = 0;
172 which = atoi(obj);
173 obj = ++p;
174 if(!check_objname(obj)) return 1;
175 if(usestamps) {
176 struct timespec ostamp;
177 if(!getstamp(obj, &ostamp)) return 1;
178 if(!ts_is_newer(&stamp, &ostamp)) continue;
180 if(!injectpr(obj, out, which)) return 1;
182 return 0;