1 /* CLIB32 - DJGPP implemention of the CLIB reader.
2 (c) 1998-99 Chris Jones, (c) 2012 rofl0r
4 22/12/02 - Shawn's Linux changes approved and integrated - CJ
6 v1.2 (Apr'01) added support for new multi-file CLIB version 10 files
7 v1.1 (Jul'99) added support for appended-to-exe data files
9 Adventure Game Studio source code Copyright 1999-2011 Chris Jones.
12 The AGS Editor Source Code is provided under the Artistic License 2.0
13 http://www.opensource.org/licenses/artistic-license-2.0.php
15 You MAY NOT compile your own builds of the engine without making it EXPLICITLY
16 CLEAR that the code has been altered from the Standard Version.
18 v1.3 rofl0r: code cleaned up for usage in agsutils.
19 v1.4 rofl0r: added writer and reader interfaces, as well as the entire writer code.
28 #include "ByteArray.h"
30 #include <sys/types.h>
36 #include "endianness.h"
38 #define RAND_SEED_SALT 9338638
40 static char *clibendfilesig
= "CLIB\x1\x2\x3\x4SIGE";
41 static char *clibpasswencstring
= "My\x1\xde\x4Jibzle";
42 static int _last_rand
;
44 static void init_pseudo_rand_gen(int seed
) {
48 static int get_pseudo_rand() {
49 return( ((_last_rand
= _last_rand
* 214013L
50 + 2531011L) >> 16) & 0x7fff );
53 static void clib_encrypt_text(unsigned char *clear
, unsigned char *encbuf
) {
56 *encbuf
= *clear
+ clibpasswencstring
[adx
];
58 adx
++; encbuf
++; clear
++;
59 if (adx
> 10) adx
= 0;
63 static void clib_decrypt_text(char *toenc
) {
66 *toenc
-= clibpasswencstring
[adx
];
69 if (adx
> 10) adx
= 0;
73 static void fgetnulltermstring(char *sss
, struct ByteArray
*ddd
, int bufsize
) {
75 off_t l
= ByteArray_get_length(ddd
);
77 if (b
< bufsize
- 1) b
++;
78 if(ByteArray_get_position(ddd
) >= l
)
80 sss
[b
] = ByteArray_readByte(ddd
);
81 } while (sss
[b
] != 0);
84 long last_opened_size
;
86 static int fread_data_enc_byte(struct ByteArray
*ba
) {
87 return ByteArray_readUnsignedByte(ba
) - get_pseudo_rand();
90 static uint32_t fread_data_enc_int(struct ByteArray
*ba
) {
95 res
.i
= ByteArray_readUnsignedInt(ba
);
98 res
.c
[i
] -= get_pseudo_rand();
102 static void fread_data_intarray_enc(struct ByteArray
*ba
, unsigned* dest
, size_t count
) {
104 for(; i
< count
; i
++)
105 dest
[i
] = fread_data_enc_int(ba
);
108 static void fread_data_intarray(struct ByteArray
*ba
, unsigned* dest
, size_t count
) {
110 for(; i
< count
; i
++)
111 dest
[i
] = ByteArray_readInt(ba
);
115 static void fread_data_enc(void *data
, size_t dataSize
, size_t dataCount
, struct ByteArray
*ooo
) {
116 ByteArray_readMultiByte(ooo
, (char*)data
, dataSize
* dataCount
);
117 unsigned char *dataChar
= (unsigned char*)data
;
119 for (; i
< dataSize
* dataCount
; i
++)
120 dataChar
[i
] -= get_pseudo_rand();
124 static void fgetstring_enc(char *sss
, struct ByteArray
*ooo
, int maxLength
) {
126 while ((i
== 0) || (sss
[i
- 1] != 0)) {
127 sss
[i
] = ByteArray_readByte(ooo
) - get_pseudo_rand();
128 if (i
< maxLength
- 1) i
++;
132 static int getw_enc(struct ByteArray
*ooo
) {
133 return fread_data_enc_int(ooo
);
136 static int read_new_new_enc_format_clib(struct MultiFileLibNew
* mfl
, struct ByteArray
* wout
) {
138 int randSeed
= ByteArray_readInt(wout
);
139 init_pseudo_rand_gen(randSeed
+ RAND_SEED_SALT
);
140 mfl
->num_data_files
= getw_enc(wout
);
141 for (aa
= 0; aa
< mfl
->num_data_files
; aa
++)
142 fgetstring_enc(mfl
->data_filenames
[aa
], wout
, 50);
143 mfl
->num_files
= getw_enc(wout
);
145 if (mfl
->num_files
> MAX_FILES
)
148 for (aa
= 0; aa
< mfl
->num_files
; aa
++)
149 fgetstring_enc(mfl
->filenames
[aa
], wout
, 100);
151 fread_data_intarray_enc(wout
, mfl
->offset
, mfl
->num_files
);
152 fread_data_intarray_enc(wout
, mfl
->length
, mfl
->num_files
);
153 for(aa
= 0; aa
< mfl
->num_files
; aa
++)
154 mfl
->file_datafile
[aa
] = fread_data_enc_byte(wout
);
158 static int read_new_new_format_clib(struct MultiFileLibNew
* mfl
, struct ByteArray
* wout
) {
160 mfl
->num_data_files
= ByteArray_readInt(wout
);
161 for (aa
= 0; aa
< mfl
->num_data_files
; aa
++)
162 fgetnulltermstring(mfl
->data_filenames
[aa
], wout
, 50);
163 mfl
->num_files
= ByteArray_readInt(wout
);
165 if (mfl
->num_files
> MAX_FILES
) return -1;
167 for (aa
= 0; aa
< mfl
->num_files
; aa
++) {
168 short nameLength
= ByteArray_readShort(wout
);
170 ByteArray_readMultiByte(wout
, mfl
->filenames
[aa
], nameLength
);
171 clib_decrypt_text(mfl
->filenames
[aa
]);
173 fread_data_intarray(wout
, mfl
->offset
, mfl
->num_files
);
174 fread_data_intarray(wout
, mfl
->length
, mfl
->num_files
);
175 ByteArray_readMultiByte(wout
, mfl
->file_datafile
, mfl
->num_files
);
179 static int read_new_format_clib(struct MultiFileLib
* mfl
, struct ByteArray
* wout
, int libver
) {
180 mfl
->num_data_files
= ByteArray_readInt(wout
);
181 ByteArray_readMultiByte(wout
, (char*) mfl
->data_filenames
, 20U * mfl
->num_data_files
);
182 mfl
->num_files
= ByteArray_readInt(wout
);
184 if (mfl
->num_files
> MAX_FILES
) return -1;
186 ByteArray_readMultiByte(wout
, (char*) mfl
->filenames
, 25U * mfl
->num_files
);
188 fread_data_intarray(wout
, mfl
->offset
, mfl
->num_files
);
189 fread_data_intarray(wout
, mfl
->length
, mfl
->num_files
);
190 ByteArray_readMultiByte(wout
, mfl
->file_datafile
, mfl
->num_files
);
194 for (aa
= 0; aa
< mfl
->num_files
; aa
++)
195 clib_decrypt_text(mfl
->filenames
[aa
]);
200 void AgsFile_close(struct AgsFile
*f
) {
202 for (i
=0; i
< AgsFile_getDataFileCount(f
); i
++)
203 ByteArray_close_file(&f
->f
[i
]);
206 int AgsFile_getVersion(struct AgsFile
*f
) {
207 return f
->libversion
;
210 void AgsFile_setVersion(struct AgsFile
*f
, int version
) {
211 f
->libversion
= version
;
214 void AgsFile_setSourceDir(struct AgsFile
*f
, char* sourcedir
) {
218 void AgsFile_setFileCount(struct AgsFile
*f
, size_t count
) {
219 f
->mflib
.num_files
= count
;
222 static off_t
filelength(int fd
) {
228 int AgsFile_setFile(struct AgsFile
*f
, size_t index
, char* fn
) {
229 strncpy(f
->mflib
.filenames
[index
], fn
, 100);
231 snprintf(fnbuf
, sizeof(fnbuf
), "%s/%s", f
->dir
, f
->mflib
.filenames
[index
]);
232 int fd
= open(fnbuf
, O_RDONLY
);
233 if(fd
== -1) return 0;
234 off_t fl
= filelength(fd
);
236 f
->mflib
.length
[index
] = fl
;
240 int AgsFile_setDataFile(struct AgsFile
*f
, size_t index
, char* fn
) {
241 size_t l
= strlen(fn
);
242 if(l
>= 20) return 0;
243 strncpy(f
->mflib
.data_filenames
[index
], fn
, 20);
247 void AgsFile_setDataFileCount(struct AgsFile
*f
, size_t count
) {
248 f
->mflib
.num_data_files
= count
;
252 static int get_int_le(int val
) {
253 #ifdef IS_LITTLE_ENDIAN
256 return byteswap32(val
);
260 static void write_int(int fd
, int val
) {
261 int le
= get_int_le(val
);
262 write(fd
, &le
, sizeof(le
));
265 static short get_short_le(short val
) {
266 #ifdef IS_LITTLE_ENDIAN
269 return byteswap16(val
);
273 static void write_short(int fd
, short val
) {
274 short le
= get_short_le(val
);
275 write(fd
, &le
, sizeof(le
));
278 static void write_int_array(int fd
, int* arr
, size_t len
) {
281 write_int(fd
, arr
[i
]);
284 static int copy_into_file(int fd
, char *dir
, char *fn
, size_t filesize
) {
286 snprintf(fnbuf
, sizeof(fnbuf
), "%s/%s", dir
, fn
);
287 int f
= open(fnbuf
, O_RDONLY
);
288 if(f
== -1) return 0;
292 size_t togo
= filesize
> sizeof(readbuf
) ? sizeof(readbuf
) : filesize
;
293 if((size_t) read(f
, readbuf
, togo
) != togo
) { ret
= 0; goto end
; }
298 wret
= write(fd
, readbuf
+ nwritten
, togo
);
299 if(wret
<= 0) { ret
= 0; goto end
; }
309 #define WH_EWRITE(X,Y,Z) do {if(Z != write(X, Y, Z)) return 0;} while(0)
310 static size_t write_header(struct AgsFile
*f
, int fd
) {
311 int myversion
= 20; //f->libversion;
312 unsigned char version
= myversion
;
313 WH_EWRITE(fd
, "CLIB\x1a", 5);
314 WH_EWRITE(fd
, &version
, 1);
316 if(myversion
>= 10) WH_EWRITE(fd
, &version
, 1);
319 write_int(fd
, f
->mflib
.num_data_files
);
320 written
+= sizeof(int);
321 for(i
= 0; i
< f
->mflib
.num_data_files
; i
++) {
322 l
= strlen(f
->mflib
.data_filenames
[i
]) + 1;
324 if(l
!= (size_t) write(fd
, f
->mflib
.data_filenames
[i
], l
))
328 write_int(fd
, f
->mflib
.num_files
);
329 written
+= sizeof(int);
330 unsigned char encbuf
[100];
331 for(i
= 0; i
< f
->mflib
.num_files
; i
++) {
332 l
= strlen(f
->mflib
.filenames
[i
]) + 1;
333 write_short(fd
, l
* 5);
334 clib_encrypt_text((unsigned char*)f
->mflib
.filenames
[i
], encbuf
);
335 if(l
!= (size_t) write(fd
, encbuf
, l
)) return 0;
336 written
+= sizeof(short) + l
;
338 l
= f
->mflib
.num_files
;
339 write_int_array(fd
, (int*) f
->mflib
.offset
, l
);
340 written
+= sizeof(int) * l
;
341 write_int_array(fd
, (int*) f
->mflib
.length
, l
);
342 written
+= sizeof(int) * l
;
343 if(l
!= (size_t) write(fd
, f
->mflib
.file_datafile
, l
))
349 static void write_footer(int fd
) {
351 write(fd
, clibendfilesig
, 12);
355 int AgsFile_write(struct AgsFile
*f
) {
356 int fd
= open(f
->fn
, O_CREAT
| O_WRONLY
| O_TRUNC
, 0660);
357 if(fd
== -1) return 0;
359 if(!(off
= write_header(f
, fd
))) return 0;
360 lseek(fd
, 0, SEEK_SET
);
361 for(i
= 0; i
< f
->mflib
.num_files
; i
++) {
362 f
->mflib
.offset
[i
] = off
;
363 off
+= f
->mflib
.length
[i
];
366 for(i
= 0; i
< f
->mflib
.num_files
; i
++) {
367 if(!copy_into_file(fd
, f
->dir
, f
->mflib
.filenames
[i
], f
->mflib
.length
[i
]))
375 static int prep_multifiles(struct AgsFile
*f
) {
377 struct ByteArray
*ba
;
378 /* open each datafile as a byte array */
379 assert(MAXMULTIFILES
>= f
->mflib
.num_data_files
);
380 for (aa
= 1; aa
< f
->mflib
.num_data_files
; aa
++) {
383 if(!ByteArray_open_file(ba
, f
->mflib
.data_filenames
[aa
])) return -1;
384 ByteArray_set_endian(ba
, BAE_LITTLE
); // all ints etc are saved in little endian.
389 static int csetlib(struct AgsFile
* f
, char *filename
) {
394 int passwmodifier
= 0;
398 struct ByteArray
*ba
= &f
->f
[0];
400 if(!ByteArray_open_file(ba
, filename
)) return -1;
401 ByteArray_set_endian(ba
, BAE_LITTLE
); // all ints etc are saved in little endian.
402 off_t ba_len
= ByteArray_get_length(ba
);
403 ByteArray_readMultiByte(ba
, clbuff
, 5);
405 uint32_t absoffs
= 0; /* we read 4 bytes- so our datatype
406 must be 4 bytes as well, since we use a pointer to it */
409 if (strncmp(clbuff
, "CLIB", 4) != 0) {
410 ByteArray_set_position(ba
, ba_len
- 12);
411 ByteArray_readMultiByte(ba
, clbuff
, 12);
413 if (strncmp(clbuff
, clibendfilesig
, 12) != 0)
415 // it's an appended-to-end-of-exe thing
416 ByteArray_set_position(ba
, ba_len
- 16);
417 absoffs
= ByteArray_readUnsignedInt(ba
);
418 ByteArray_set_position(ba
, absoffs
+ 5);
419 f
->pack_off
= absoffs
;
422 f
->libversion
= ByteArray_readUnsignedByte(ba
);
423 switch (f
->libversion
) {
424 /* enum MFLVersion (kMFLVersion_MultiV21 ...) in newer AGS */
425 case 6: case 10: case 11: case 15: case 20: case 21:
428 // unsupported version
432 // remove slashes so that the lib name fits in the buffer
433 while (filename
[0] == '\\' || filename
[0] == '/') filename
++;
435 if (f
->libversion
>= 10) {
436 if (ByteArray_readUnsignedByte(ba
) != 0)
437 return -4; // not first datafile in chain
439 if (f
->libversion
>= 21) {
440 if (read_new_new_enc_format_clib(&f
->mflib
, ba
))
443 else if (f
->libversion
== 20) {
444 if (read_new_new_format_clib(&f
->mflib
, ba
))
447 struct MultiFileLib mflibOld_b
, *mflibOld
= &mflibOld_b
;
449 if (read_new_format_clib(mflibOld
, ba
, f
->libversion
))
451 // convert to newer format
452 f
->mflib
.num_files
= mflibOld
->num_files
;
453 f
->mflib
.num_data_files
= mflibOld
->num_data_files
;
454 memcpy(f
->mflib
.offset
, mflibOld
->offset
, sizeof(int) * f
->mflib
.num_files
);
455 memcpy(f
->mflib
.length
, mflibOld
->length
, sizeof(int) * f
->mflib
.num_files
);
456 memcpy(f
->mflib
.file_datafile
, mflibOld
->file_datafile
, sizeof(char) * f
->mflib
.num_files
);
457 assert(MAXMULTIFILES
>= f
->mflib
.num_data_files
);
458 for (aa
= 0; aa
< f
->mflib
.num_data_files
; aa
++)
459 strcpy(f
->mflib
.data_filenames
[aa
], mflibOld
->data_filenames
[aa
]);
460 for (aa
= 0; aa
< f
->mflib
.num_files
; aa
++)
461 strcpy(f
->mflib
.filenames
[aa
], mflibOld
->filenames
[aa
]);
464 strcpy(f
->mflib
.data_filenames
[0], filename
);
465 for (aa
= 0; aa
< f
->mflib
.num_files
; aa
++) {
466 // correct offsetes for EXE file
467 if (f
->mflib
.file_datafile
[aa
] == 0)
468 f
->mflib
.offset
[aa
] += absoffs
;
470 return prep_multifiles(f
);
473 passwmodifier
= ByteArray_readUnsignedByte(ba
);
474 ByteArray_readUnsignedByte(ba
); // unused byte
475 f
->mflib
.num_data_files
= 1;
476 strcpy(f
->mflib
.data_filenames
[0], filename
);
478 short tempshort
= ByteArray_readShort(ba
);
479 f
->mflib
.num_files
= tempshort
;
481 if (f
->mflib
.num_files
> MAX_FILES
) return -4;
483 ByteArray_readMultiByte(ba
, clbuff
, 13); // skip password dooberry
484 for (aa
= 0; aa
< f
->mflib
.num_files
; aa
++) {
485 ByteArray_readMultiByte(ba
, f
->mflib
.filenames
[aa
], 13);
486 l
= strlen(f
->mflib
.filenames
[aa
]);
487 for (cc
= 0; cc
< l
; cc
++)
488 f
->mflib
.filenames
[aa
][cc
] -= passwmodifier
;
490 for(cc
= 0; cc
< f
->mflib
.num_files
; cc
++)
491 f
->mflib
.length
[cc
] = ByteArray_readUnsignedInt(ba
);
493 ByteArray_set_position_rel(ba
, 2 * f
->mflib
.num_files
); // skip flags & ratio
495 f
->mflib
.offset
[0] = ByteArray_get_position(ba
);
497 for (aa
= 1; aa
< f
->mflib
.num_files
; aa
++) {
498 f
->mflib
.offset
[aa
] = f
->mflib
.offset
[aa
- 1] + f
->mflib
.length
[aa
- 1];
499 f
->mflib
.file_datafile
[aa
] = 0;
501 f
->mflib
.file_datafile
[0] = 0;
503 return prep_multifiles(f
);
506 static int checkIndex(struct AgsFile
*f
, size_t index
) {
507 if (index
>= AgsFile_getFileCount(f
)) return 0;
511 static int checkDataIndex(struct AgsFile
*f
, size_t index
) {
512 if (index
>= AgsFile_getDataFileCount(f
)) return 0;
516 size_t AgsFile_getFileCount(struct AgsFile
*f
) {
517 return f
->mflib
.num_files
;
520 size_t AgsFile_getDataFileCount(struct AgsFile
*f
) {
521 return f
->mflib
.num_data_files
;
524 int AgsFile_getFileNumber(struct AgsFile
*f
, size_t index
) {
525 if (!checkIndex(f
, index
)) return -1;
526 return f
->mflib
.file_datafile
[index
];
529 void AgsFile_setFileNumber(struct AgsFile
*f
, size_t index
, int number
) {
530 *(unsigned char*)(&f
->mflib
.file_datafile
[index
]) = number
;
533 char *AgsFile_getFileName(struct AgsFile
*f
, size_t index
) {
534 if (!checkIndex(f
, index
)) return 0;
535 return f
->mflib
.filenames
[index
];
538 char *AgsFile_getDataFileName(struct AgsFile
*f
, size_t index
) {
539 if (!checkDataIndex(f
, index
)) return 0;
540 return f
->mflib
.data_filenames
[index
];
543 size_t AgsFile_getFileSize(struct AgsFile
*f
, size_t index
) {
544 if (!checkIndex(f
, index
)) return 0;
545 return f
->mflib
.length
[index
];
548 size_t AgsFile_getOffset(struct AgsFile
*f
, size_t index
) {
549 if (!checkIndex(f
, index
)) return 0;
550 return f
->mflib
.offset
[index
];
553 static int AgsFile_seek(struct AgsFile
*f
, int multifileno
, off_t pos
) {
554 return ByteArray_set_position(&f
->f
[multifileno
], pos
);
557 static ssize_t
AgsFile_read(struct AgsFile
*f
, int multifileno
, void* buf
, size_t count
) {
558 return ByteArray_readMultiByte(&f
->f
[multifileno
], buf
, count
);
561 int AgsFile_extract(struct AgsFile
* f
, int multifileno
, off_t start
, size_t len
, const char* outfn
) {
562 int fd
= open(outfn
, O_WRONLY
| O_CREAT
| O_TRUNC
, 0660);
563 if(fd
== -1) return 0;
565 size_t written
= 0, l
= len
;
566 off_t save_pos
= ByteArray_get_position(&f
->f
[multifileno
]);
567 AgsFile_seek(f
, multifileno
, start
);
569 size_t togo
= l
- written
;
570 if(togo
> sizeof(buf
)) togo
= sizeof(buf
);
572 ssize_t ret
= AgsFile_read(f
, multifileno
, buf
, togo
);
574 ret
= write(fd
, buf
, togo
);
578 } else if(ret
!= togo
) {
579 dprintf(2, "short write\n");
585 AgsFile_seek(f
, multifileno
, save_pos
);
589 int AgsFile_dump(struct AgsFile
* f
, size_t index
, const char* outfn
) {
590 if (!checkIndex(f
, index
)) return 0;
591 return AgsFile_extract(f
, AgsFile_getFileNumber(f
, index
), AgsFile_getOffset(f
, index
), AgsFile_getFileSize(f
, index
), outfn
);
594 void AgsFile_init(struct AgsFile
*buf
, char* filename
) {
598 int AgsFile_open(struct AgsFile
*buf
) {
599 int ret
= csetlib(buf
, buf
->fn
);