12 #define ADS ":::AGSinject " VERSION " by rofl0r:::"
16 static char *tempnam(const char *dir
, const char *pfx
) {
18 char buf
[L_tmpnam
+ 64];
21 int i
; char *p
= buf
+ strlen(buf
);
22 for(i
= 0; pfx
[i
] && i
< 8; ++i
, ++p
)
25 sprintf(p
, "%016llx", (unsigned long long) _rdtsc());
28 static int RENAME(const char *OLD
, const char *NEW
) {
30 return rename(OLD
, NEW
);
33 #define RENAME(OLD, NEW) rename(OLD, NEW)
36 int usage(char *argv0
) {
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"
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"
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
);
61 /* inj = filename of file to inject in */
62 static int inject(const char *o
, const char *inj
, unsigned which
) {
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;
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
);
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");
88 AF_dump_chunk_stream(f
, 0, isroom
? start
-room_length_bytes
: start
, out
);
91 /* open replacement object file */
94 ByteArray_open_file(&b
, o
);
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
);
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
);
115 /* 2b) dump object file */
116 ByteArray_dump_to_stream(&b
, out
);
117 ByteArray_close_file(&b
);
120 if(!ASI_read_script(f
, &s
)) {
121 fprintf(stderr
, "trouble finding script in %s\n", inj
);
124 /* 3) dump rest of file */
125 AF_dump_chunk_stream(f
, start
+ s
.len
, ByteArray_get_length(f
->b
) - (start
+ s
.len
), 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
);
139 static int check_objname(const char* o
) {
141 if(!(p
= strrchr(o
, '.')) || strcmp(p
, ".o")) {
142 fprintf(stderr
, "error: object file has no .o extension\n");
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");
155 fprintf(stderr
, "invalid index %d for roomfile, only 0 possible\n", which
);
157 } else if (ret
== -1) perror("error");
163 static int getstamp(const char* fn
, time_t *stamp
) {
165 if(stat(fn
, &st
) == -1) {
169 *stamp
= st
.st_mtime
;
173 static int ts_is_newer(const time_t *t1
, const time_t *t2
)
178 int main(int argc
, char**argv
) {
181 if(argc
== 4 && isdigit(*argv
[1])) {
184 which
= atoi(argv
[1]);
185 if(!check_objname(obj
)) return 1;
186 if(!injectpr(obj
, out
, which
)) return 1;
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]);
201 if(usestamps
&& !getstamp(out
, &stamp
)) return 1;
203 while(argv
[++optind
]) {
205 char *p
= strchr(obj
, ':');
206 if(!isdigit(*obj
) || !p
) return usage(argv
[0]);
210 if(!check_objname(obj
)) return 1;
213 if(!getstamp(obj
, &ostamp
)) return 1;
214 if(!ts_is_newer(&stamp
, &ostamp
)) continue;
216 if(!injectpr(obj
, out
, which
)) return 1;