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 /* all of the below is needed because windows' rename() function
29 doesn't work like described in POSIX, it won't overwrite an
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
37 typedef unsigned DWORD
;
38 typedef const char *LPCSTR
;
39 #define WINAPI __stdcall
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
);
47 DWORD err
= GetLastError();
48 fprintf(stderr
, "GetLastError(): %u\n", (unsigned) err
);
53 #define RENAME(OLD, NEW) rename(OLD, NEW)
56 int usage(char *argv0
) {
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"
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"
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
);
81 /* inj = filename of file to inject in */
82 static int inject(const char *o
, const char *inj
, unsigned which
) {
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;
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
);
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");
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 */
114 ByteArray_open_file(&b
, o
);
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
);
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
);
135 /* 2b) dump object file */
136 ByteArray_dump_to_stream(&b
, out
);
137 ByteArray_close_file(&b
);
140 if(!ASI_read_script(f
, &s
)) {
141 fprintf(stderr
, "trouble finding script in %s\n", inj
);
144 /* 3) dump rest of file */
145 AF_dump_chunk_stream(f
, start
+ s
.len
, ByteArray_get_length(f
->b
) - (start
+ s
.len
), 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
);
159 static int check_objname(const char* o
) {
161 if(!(p
= strrchr(o
, '.')) || strcmp(p
, ".o")) {
162 fprintf(stderr
, "error: object file has no .o extension\n");
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");
175 fprintf(stderr
, "invalid index %d for roomfile, only 0 possible\n", which
);
177 } else if (ret
== -1) perror("error");
183 static int getstamp(const char* fn
, time_t *stamp
) {
185 if(stat(fn
, &st
) == -1) {
189 *stamp
= st
.st_mtime
;
193 static int ts_is_newer(const time_t *t1
, const time_t *t2
)
198 int main(int argc
, char**argv
) {
201 if(argc
== 4 && isdigit(*argv
[1])) {
204 which
= atoi(argv
[1]);
205 if(!check_objname(obj
)) return 1;
206 if(!injectpr(obj
, out
, which
)) return 1;
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]);
221 if(usestamps
&& !getstamp(out
, &stamp
)) return 1;
223 while(argv
[++optind
]) {
225 char *p
= strchr(obj
, ':');
226 if(!isdigit(*obj
) || !p
) return usage(argv
[0]);
230 if(!check_objname(obj
)) return 1;
233 if(!getstamp(obj
, &ostamp
)) return 1;
234 if(!ts_is_newer(&stamp
, &ostamp
)) continue;
236 if(!injectpr(obj
, out
, which
)) return 1;