├── bmltool ├── Makefile ├── Makefile.win32 ├── bmltool.c ├── prs-comp.c ├── prs-decomp.c ├── prs.h └── windows_compat.c ├── prstool ├── Makefile ├── prs-comp.c ├── prs-decomp.c ├── prs.h └── prstool.c ├── pso_artool ├── Makefile ├── Makefile.mingw ├── afs.c ├── artool.c ├── gsl.c ├── prs.c ├── prsd.c ├── windows_compat.c └── windows_compat.h ├── qst_tool └── qst_tool.c ├── quest_enemies ├── quest_enemies.c ├── quest_enemies.h └── quests.c └── xboxdlqconv └── xboxdlqconv.c /bmltool/Makefile: -------------------------------------------------------------------------------- 1 | # *nix Makefile. 2 | # Should build with any standardish C99-supporting compiler. 3 | 4 | all: bmltool 5 | 6 | bmltool: bmltool.c prs-comp.c prs-decomp.c 7 | $(CC) -o bmltool bmltool.c prs-comp.c prs-decomp.c 8 | 9 | .PHONY: clean 10 | 11 | clean: 12 | -rm -fr bmltool *.o *.dSYM 13 | -------------------------------------------------------------------------------- /bmltool/Makefile.win32: -------------------------------------------------------------------------------- 1 | # Windows Makefile. 2 | # Build with nmake from a VS command prompt: 3 | # nmake /f Makefile.win32 nodebug=1 4 | 5 | !include 6 | 7 | all: bmltool.exe 8 | 9 | OBJS = bmltool.obj prs-comp.obj prs-decomp.obj windows_compat.obj 10 | 11 | .c.obj: 12 | $(cc) $(cdebug) $(cflags) $(cvars) $*.c /D_CRT_SECURE_NO_WARNINGS 13 | 14 | bmltool.exe: $(OBJS) 15 | $(link) $(ldebug) $(conflags) -out:bmltool.exe $(OBJS) $(conlibs) 16 | -------------------------------------------------------------------------------- /bmltool/bmltool.c: -------------------------------------------------------------------------------- 1 | /* 2 | Sylverant PSO Tools 3 | BML Tool 4 | Copyright (C) 2014 Lawrence Sebald 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License version 3 8 | as published by the Free Software Foundation. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | #ifndef _WIN32 28 | #include 29 | #include 30 | #endif 31 | 32 | #include "prs.h" 33 | 34 | #if defined(__BIG_ENDIAN__) || defined(WORDS_BIGENDIAN) 35 | #define LE32(x) (((x >> 24) & 0x00FF) | \ 36 | ((x >> 8) & 0xFF00) | \ 37 | ((x & 0xFF00) << 8) | \ 38 | ((x & 0x00FF) << 24)) 39 | #else 40 | #define LE32(x) (x) 41 | #endif 42 | 43 | typedef struct bml_entry { 44 | char filename[32]; 45 | uint32_t csize; 46 | uint32_t unk; 47 | uint32_t usize; 48 | uint32_t pvm_csize; 49 | uint32_t pvm_usize; 50 | uint32_t padding[3]; 51 | } bml_entry_t; 52 | 53 | struct update_cxt { 54 | FILE *fp; 55 | const char *fn; 56 | const char *path; 57 | long fpos; 58 | long wpos; 59 | int is_pvm; 60 | }; 61 | 62 | static uint8_t xbuf[512]; 63 | 64 | #ifdef _WIN32 65 | /* In windows_compat.c */ 66 | char *basename(char *input); 67 | int my_rename(const char *old, const char *new); 68 | #define rename my_rename 69 | #endif 70 | 71 | static int copy_file(FILE *dst, FILE *src, uint32_t size) { 72 | /* Read in the file in 512-byte chunks, writing each one out to the 73 | output file (incuding the last chunk, which may be less than 512 74 | bytes in length). */ 75 | while(size > 512) { 76 | if(fread(xbuf, 1, 512, src) != 512) { 77 | printf("Error reading file: %s\n", strerror(errno)); 78 | return -1; 79 | } 80 | 81 | if(fwrite(xbuf, 1, 512, dst) != 512) { 82 | printf("Error writing file: %s\n", strerror(errno)); 83 | return -2; 84 | } 85 | 86 | size -= 512; 87 | } 88 | 89 | if(size) { 90 | if(fread(xbuf, 1, size, src) != size) { 91 | printf("Error reading file: %s\n", strerror(errno)); 92 | return -3; 93 | } 94 | 95 | if(fwrite(xbuf, 1, size, dst) != size) { 96 | printf("Error writing file: %s\n", strerror(errno)); 97 | return -4; 98 | } 99 | } 100 | 101 | return 0; 102 | } 103 | 104 | static long pad_file(FILE *fp, int boundary) { 105 | long pos = ftell(fp); 106 | uint8_t tmp = 0; 107 | 108 | /* If we aren't actually padding, don't do anything. */ 109 | if(boundary <= 0) 110 | return pos; 111 | 112 | pos = (pos & ~(boundary - 1)) + boundary; 113 | 114 | if(fseek(fp, pos - 1, SEEK_SET)) { 115 | printf("Seek error: %s\n", strerror(errno)); 116 | return -1; 117 | } 118 | 119 | if(fwrite(&tmp, 1, 1, fp) != 1) { 120 | printf("Cannot write to archive: %s\n", strerror(errno)); 121 | return -1; 122 | } 123 | 124 | return pos; 125 | } 126 | 127 | static FILE *open_bml(const char *fn, uint32_t *entries) { 128 | FILE *fp; 129 | uint8_t buf[16]; 130 | 131 | /* Open up the file */ 132 | if(!(fp = fopen(fn, "rb"))) { 133 | printf("Cannot open %s: %s\n", fn, strerror(errno)); 134 | return NULL; 135 | } 136 | 137 | /* Make sure that it looks like a sane BML file. */ 138 | if(fread(buf, 1, 12, fp) != 12) { 139 | printf("Error reading file %s: %s\n", fn, strerror(errno)); 140 | fclose(fp); 141 | return NULL; 142 | } 143 | 144 | if(buf[0] != 0 || buf[1] != 0 || buf[2] != 0 || buf[3] != 0 || 145 | buf[8] != 0x50 || buf[9] != 0x01 || buf[10] != 0 || buf[11] != 0) { 146 | printf("%s is not an BML archive!\n", fn); 147 | fclose(fp); 148 | return NULL; 149 | } 150 | 151 | *entries = (buf[4]) | (buf[5] << 8) | (buf[6] << 16) | (buf[7] << 24); 152 | 153 | /* Seek to the first file. */ 154 | if(fseek(fp, 64, SEEK_SET)) { 155 | printf("Error seeking file: %s\n", strerror(errno)); 156 | fclose(fp); 157 | return NULL; 158 | } 159 | 160 | return fp; 161 | } 162 | 163 | static int scan_bml(const char *fn, int (*p)(FILE *, bml_entry_t *, uint32_t, 164 | uint32_t, uint32_t, void *), 165 | void *userdata) { 166 | FILE *fp; 167 | int rv = 0; 168 | uint32_t entries, i, offset, poffset, eoffset; 169 | long next; 170 | bml_entry_t ent; 171 | 172 | /* Open up the file */ 173 | if(!(fp = open_bml(fn, &entries))) 174 | return -1; 175 | 176 | /* Gonna guess that this will never be a problem, considering we shouldn't 177 | have anywhere near a 2GiB BML file. */ 178 | rv = (int)entries; 179 | 180 | /* Figure out the length of the header based on the number of entries. */ 181 | offset = (entries + 1) * 64; 182 | 183 | if((offset & 0x7FF)) 184 | offset = (offset + 0x800) & 0xFFFFF800; 185 | 186 | /* Read in each file in the archive, writing each one out. */ 187 | for(i = 0; i < entries; ++i) { 188 | /* Read in the header of the file. */ 189 | if(fread(&ent, 1, sizeof(ent), fp) != sizeof(ent)) { 190 | printf("Error reading file %s: %s\n", fn, strerror(errno)); 191 | rv = -2; 192 | goto out; 193 | } 194 | 195 | /* Swap the endianness, if needed. */ 196 | ent.csize = LE32(ent.csize); 197 | ent.unk = LE32(ent.unk); 198 | ent.usize = LE32(ent.usize); 199 | ent.pvm_csize = LE32(ent.pvm_csize); 200 | ent.pvm_usize = LE32(ent.pvm_usize); 201 | 202 | poffset = offset + ent.csize; 203 | if((poffset & 0x1F)) 204 | poffset = (poffset + 0x20) & 0xFFFFFFE0; 205 | 206 | eoffset = poffset; 207 | next = ftell(fp); 208 | 209 | /* Adjust the next offset if there's a PVM attached. */ 210 | if(ent.pvm_csize) { 211 | eoffset = poffset + ent.pvm_csize; 212 | if((eoffset & 0x1F)) 213 | eoffset = (eoffset + 0x20) & 0xFFFFFFE0; 214 | } 215 | 216 | if((rv = p(fp, &ent, i, offset, poffset, userdata))) 217 | goto out; 218 | 219 | /* Adjust things for the next iteration... */ 220 | offset = eoffset; 221 | 222 | if(fseek(fp, next, SEEK_SET) < 0) { 223 | printf("Seek error: %s\n", strerror(errno)); 224 | rv = -3; 225 | goto out; 226 | } 227 | } 228 | 229 | out: 230 | fclose(fp); 231 | return rv; 232 | } 233 | 234 | static int print_file_info(FILE *fp, bml_entry_t *ent, uint32_t i, 235 | uint32_t offset, uint32_t poffset, void *d) { 236 | #ifndef _WIN32 237 | printf("File %4" PRIu32 " '%s'\n compressed size: %" PRIu32 " " 238 | "uncompressed size: %" PRIu32 " Unknown: %#010" PRIx32 "\n " 239 | "offset: %#010" PRIx32 "\n", i, ent->filename, ent->csize, 240 | ent->usize, ent->unk, offset); 241 | 242 | if(ent->pvm_csize) 243 | printf(" PVM size: %" PRIu32 " PVM uncompressed size: %" PRIu32 244 | "\n PVM offset: %#010" PRIx32 "\n", ent->pvm_csize, 245 | ent->pvm_usize, poffset); 246 | #else 247 | printf("File %4u '%s'\n compressed size: %u uncompressed size: %u" 248 | " Unknown: %#010x\n offset: %#010x\n", i, ent->filename, 249 | ent->csize, ent->usize, ent->unk, offset); 250 | 251 | if(ent->pvm_csize) 252 | printf(" PVM size: %u PVM uncompressed size: %u\n" 253 | " PVM offset: %#010x\n", ent->pvm_csize, ent->pvm_usize, 254 | poffset); 255 | #endif 256 | 257 | return 0; 258 | } 259 | 260 | static int extract_file(FILE *fp, bml_entry_t *ent, uint32_t i, 261 | uint32_t offset, uint32_t poffset, void *d) { 262 | FILE *ofp; 263 | char fn[50]; 264 | 265 | /* If we're only extracting one file, then make sure we have the right one 266 | before we extract it. */ 267 | if(d && strcmp((const char *)d, ent->filename)) 268 | return 0; 269 | 270 | sprintf(fn, "%s.prs", ent->filename); 271 | 272 | /* Open the output file. */ 273 | if(!(ofp = fopen(fn, "wb"))) { 274 | printf("Cannot open file '%s' for write: %s\n", fn, strerror(errno)); 275 | return -1; 276 | } 277 | 278 | if(fseek(fp, offset, SEEK_SET) < 0) { 279 | printf("Seek error: %s\n", strerror(errno)); 280 | fclose(ofp); 281 | return -2; 282 | } 283 | 284 | /* Copy the data out into its new file. */ 285 | if(copy_file(ofp, fp, ent->csize)) { 286 | fclose(ofp); 287 | return -3; 288 | } 289 | 290 | /* We're done with this file, close it and check if we have a PVM to deal 291 | with still. */ 292 | fclose(ofp); 293 | 294 | if(ent->pvm_csize) { 295 | sprintf(fn, "%s.pvm.prs", ent->filename); 296 | 297 | /* Open the output file. */ 298 | if(!(ofp = fopen(fn, "wb"))) { 299 | printf("Cannot open file '%s' for write: %s\n", fn, 300 | strerror(errno)); 301 | return -4; 302 | } 303 | 304 | if(fseek(fp, poffset, SEEK_SET) < 0) { 305 | printf("Seek error: %s\n", strerror(errno)); 306 | fclose(ofp); 307 | return -5; 308 | } 309 | 310 | /* Copy the data out into its new file. */ 311 | if(copy_file(ofp, fp, ent->csize)) { 312 | fclose(ofp); 313 | return -6; 314 | } 315 | 316 | /* We're done with this file, close it. */ 317 | fclose(ofp); 318 | } 319 | 320 | return 0; 321 | } 322 | 323 | static int read_and_dec(FILE *fp, uint32_t offset, uint32_t cs, uint32_t ds, 324 | const char *fn) { 325 | FILE *ofp; 326 | uint8_t *comp, *decomp; 327 | int rv; 328 | 329 | if(fseek(fp, offset, SEEK_SET) < 0) { 330 | printf("Seek error: %s\n", strerror(errno)); 331 | return -1; 332 | } 333 | 334 | /* Allocate both of the buffers we'll need. */ 335 | if(!(comp = (uint8_t *)malloc(cs))) { 336 | printf("Cannot allocate memory: %s\n", strerror(errno)); 337 | return -2; 338 | } 339 | 340 | if(!(decomp = (uint8_t *)malloc(ds))) { 341 | printf("Cannot allocate memory: %s\n", strerror(errno)); 342 | free(comp); 343 | return -3; 344 | } 345 | 346 | /* Read into the compressed buffer. */ 347 | if(fread(comp, 1, cs, fp) != cs) { 348 | printf("File read error: %s\n", strerror(errno)); 349 | free(decomp); 350 | free(comp); 351 | return -4; 352 | } 353 | 354 | /* Decompress it. */ 355 | if((rv = prs_decompress_buf2(comp, decomp, cs, ds)) != ds) { 356 | printf("Error decompressing file %s: ", fn); 357 | 358 | if(rv >= 0) 359 | printf("Size mismatch!\n"); 360 | else 361 | printf("%s\n", strerror(-rv)); 362 | 363 | free(decomp); 364 | free(comp); 365 | return -5; 366 | } 367 | 368 | /* Free the compressed buffer, since we're done with it. */ 369 | free(comp); 370 | 371 | /* Open the output file. */ 372 | if(!(ofp = fopen(fn, "wb"))) { 373 | printf("Cannot open file '%s' for write: %s\n", fn, strerror(errno)); 374 | free(decomp); 375 | return -6; 376 | } 377 | 378 | /* Write it out. */ 379 | if(fwrite(decomp, 1, ds, ofp) != ds) { 380 | printf("File write error '%s': %s\n", fn, strerror(errno)); 381 | free(decomp); 382 | fclose(ofp); 383 | return -7; 384 | } 385 | 386 | /* We're done, so clean up. */ 387 | fclose(ofp); 388 | free(decomp); 389 | 390 | return 0; 391 | } 392 | 393 | static int decompress_file(FILE *fp, bml_entry_t *ent, uint32_t i, 394 | uint32_t offset, uint32_t poffset, void *d) { 395 | char fn[50]; 396 | 397 | /* If we're only extracting one file, then make sure we have the right one 398 | before we extract it. */ 399 | if(d && strcmp((const char *)d, ent->filename)) 400 | return 0; 401 | 402 | if(read_and_dec(fp, offset, ent->csize, ent->usize, ent->filename)) 403 | return -1; 404 | 405 | if(ent->pvm_csize) { 406 | sprintf(fn, "%s.pvm", ent->filename); 407 | 408 | if(read_and_dec(fp, poffset, ent->pvm_csize, ent->pvm_usize, fn)) 409 | return -2; 410 | } 411 | 412 | return 0; 413 | } 414 | 415 | static uint8_t *read_and_cmp(const char *fn, uint32_t *cs, uint32_t *ds) { 416 | FILE *fp; 417 | uint8_t *comp, *decomp; 418 | int rv; 419 | long len; 420 | 421 | /* Open the file and figure out how long it is. */ 422 | if(!(fp = fopen(fn, "rb"))) { 423 | printf("Cannot open '%s': %s\n", fn, strerror(errno)); 424 | return NULL; 425 | } 426 | 427 | if(fseek(fp, 0, SEEK_END) < 0) { 428 | printf("Seek error: %s\n", strerror(errno)); 429 | fclose(fp); 430 | return NULL; 431 | } 432 | 433 | if((len = ftell(fp)) < 0) { 434 | printf("Cannot read file position: %s\n", strerror(errno)); 435 | fclose(fp); 436 | return NULL; 437 | } 438 | 439 | if(fseek(fp, 0, SEEK_SET) < 0) { 440 | printf("Seek error: %s\n", strerror(errno)); 441 | fclose(fp); 442 | return NULL; 443 | } 444 | 445 | /* Allocate both of the buffers we'll need. */ 446 | if(!(decomp = (uint8_t *)malloc(len))) { 447 | printf("Cannot allocate memory: %s\n", strerror(errno)); 448 | fclose(fp); 449 | return NULL; 450 | } 451 | 452 | /* Read into the compressed buffer. */ 453 | if(fread(decomp, 1, len, fp) != len) { 454 | printf("File read error: %s\n", strerror(errno)); 455 | free(decomp); 456 | fclose(fp); 457 | return NULL; 458 | } 459 | 460 | fclose(fp); 461 | 462 | /* Compress it. */ 463 | if((rv = prs_compress(decomp, &comp, len)) < 0) { 464 | printf("Error compressing file %s: %s", fn, strerror(-rv)); 465 | free(decomp); 466 | return NULL; 467 | } 468 | 469 | /* Clean up. */ 470 | free(decomp); 471 | *cs = (uint32_t)rv; 472 | *ds = (uint32_t)len; 473 | 474 | return comp; 475 | } 476 | 477 | static int copy_update(FILE *fp, bml_entry_t *ent, uint32_t i, uint32_t offset, 478 | uint32_t poffset, void *d) { 479 | struct update_cxt *cxt = (struct update_cxt *)d; 480 | uint32_t cs = ent->csize, pcs = ent->pvm_csize, ncs, nus; 481 | uint8_t *buf; 482 | 483 | /* Look if we're supposed to update this one. */ 484 | if(!strcmp(cxt->fn, ent->filename)) { 485 | /* Read in the file we're replacing this one with, compressing it as we 486 | do so. */ 487 | if(!(buf = read_and_cmp(cxt->path, &ncs, &nus))) 488 | return -10; 489 | 490 | /* Write the header out. */ 491 | if(fseek(cxt->fp, cxt->fpos, SEEK_SET)) { 492 | printf("Seek error: %s\n", strerror(errno)); 493 | return -11; 494 | } 495 | 496 | ent->unk = LE32(ent->unk); 497 | 498 | if(!cxt->is_pvm) { 499 | ent->csize = LE32(ncs); 500 | ent->usize = LE32(nus); 501 | ent->pvm_csize = LE32(ent->pvm_csize); 502 | ent->pvm_usize = LE32(ent->pvm_usize); 503 | } 504 | else { 505 | ent->csize = LE32(ent->csize); 506 | ent->usize = LE32(ent->usize); 507 | ent->pvm_csize = LE32(ncs); 508 | ent->pvm_usize = LE32(nus); 509 | } 510 | 511 | if(fwrite(ent, 1, 64, cxt->fp) != 64) { 512 | printf("Cannot write to file: %s\n", strerror(errno)); 513 | return -12; 514 | } 515 | 516 | cxt->fpos += 64; 517 | 518 | if(fseek(cxt->fp, cxt->wpos, SEEK_SET)) { 519 | printf("Seek error: %s\n", strerror(errno)); 520 | return -13; 521 | } 522 | 523 | if(!cxt->is_pvm) { 524 | /* Write out the compressed data. */ 525 | if(fwrite(buf, 1, ncs, cxt->fp) != ncs) { 526 | printf("Write error: %s\n", strerror(errno)); 527 | return -15; 528 | } 529 | } 530 | else { 531 | if(fseek(fp, offset, SEEK_SET)) { 532 | printf("Seek error: %s\n", strerror(errno)); 533 | return -17; 534 | } 535 | 536 | if(copy_file(cxt->fp, fp, cs)) 537 | return -18; 538 | } 539 | 540 | /* Add padding, as needed. */ 541 | if((cxt->wpos = pad_file(cxt->fp, 32)) < 0) 542 | return -16; 543 | 544 | /* Deal with the PVM, if there is one. */ 545 | if(pcs || cxt->is_pvm) { 546 | if(!cxt->is_pvm) { 547 | if(fseek(fp, poffset, SEEK_SET)) { 548 | printf("Seek error: %s\n", strerror(errno)); 549 | return -17; 550 | } 551 | 552 | if(copy_file(cxt->fp, fp, pcs)) 553 | return -18; 554 | } 555 | else { 556 | if(fwrite(buf, 1, ncs, cxt->fp) != ncs) { 557 | printf("Write error: %s\n", strerror(errno)); 558 | return -15; 559 | } 560 | } 561 | 562 | /* Add padding, as needed. */ 563 | if((cxt->wpos = pad_file(cxt->fp, 32)) < 0) 564 | return -19; 565 | } 566 | 567 | free(buf); 568 | return 0; 569 | } 570 | 571 | /* Write the header back out. */ 572 | if(fseek(cxt->fp, cxt->fpos, SEEK_SET)) { 573 | printf("Seek error: %s\n", strerror(errno)); 574 | return -1; 575 | } 576 | 577 | /* Swap the endianness, if needed. */ 578 | ent->csize = LE32(ent->csize); 579 | ent->unk = LE32(ent->unk); 580 | ent->usize = LE32(ent->usize); 581 | ent->pvm_csize = LE32(ent->pvm_csize); 582 | ent->pvm_usize = LE32(ent->pvm_usize); 583 | 584 | if(fwrite(ent, 1, 64, cxt->fp) != 64) { 585 | printf("Cannot write to file: %s\n", strerror(errno)); 586 | return -2; 587 | } 588 | 589 | cxt->fpos += 64; 590 | 591 | if(fseek(cxt->fp, cxt->wpos, SEEK_SET)) { 592 | printf("Seek error: %s\n", strerror(errno)); 593 | return -3; 594 | } 595 | 596 | if(fseek(fp, offset, SEEK_SET)) { 597 | printf("Seek error: %s\n", strerror(errno)); 598 | return -4; 599 | } 600 | 601 | /* Copy the file over from the old archive to the new one. */ 602 | if(copy_file(cxt->fp, fp, cs)) 603 | return -5; 604 | 605 | /* Add padding, as needed. */ 606 | if((cxt->wpos = pad_file(cxt->fp, 32)) < 0) 607 | return -6; 608 | 609 | /* Deal with the PVM, if there is one. */ 610 | if(pcs) { 611 | if(fseek(fp, poffset, SEEK_SET)) { 612 | printf("Seek error: %s\n", strerror(errno)); 613 | return -7; 614 | } 615 | 616 | if(copy_file(cxt->fp, fp, pcs)) 617 | return -8; 618 | 619 | /* Add padding, as needed. */ 620 | if((cxt->wpos = pad_file(cxt->fp, 32)) < 0) 621 | return -9; 622 | } 623 | 624 | return 0; 625 | } 626 | 627 | int update_bml(const char *fn, const char *file, const char *path, int pvm) { 628 | int fd; 629 | char tmpfn[16]; 630 | uint32_t entries, hdrlen; 631 | struct update_cxt cxt; 632 | FILE *fp; 633 | uint8_t hdrbuf[64] = { 0 }; 634 | 635 | #ifndef _WIN32 636 | mode_t mask; 637 | #endif 638 | 639 | /* Parse out all the entries for the context first. */ 640 | memset(&cxt, 0, sizeof(cxt)); 641 | cxt.fn = file; 642 | cxt.path = path; 643 | cxt.is_pvm = pvm; 644 | 645 | /* Figure out how many entries are in the existing file. */ 646 | if(!(fp = open_bml(fn, &entries))) 647 | return -1; 648 | 649 | fclose(fp); 650 | 651 | /* Figure out the size of the header. */ 652 | /* Figure out the length of the header based on the number of entries. */ 653 | hdrlen = (entries + 1) * 64; 654 | 655 | if((hdrlen & 0x7FF)) 656 | hdrlen = (hdrlen + 0x800) & 0xFFFFF800; 657 | 658 | /* Open up a temporary file for writing. */ 659 | strcpy(tmpfn, "bmltoolXXXXXX"); 660 | if((fd = mkstemp(tmpfn)) < 0) { 661 | printf("Cannot create temporary file: %s\n", strerror(errno)); 662 | return -2; 663 | } 664 | 665 | if(!(cxt.fp = fdopen(fd, "wb"))) { 666 | printf("Cannot open temporary file: %s\n", strerror(errno)); 667 | close(fd); 668 | unlink(tmpfn); 669 | return -3; 670 | } 671 | 672 | if(fseek(cxt.fp, hdrlen, SEEK_SET)) { 673 | printf("Cannot create blank file table: %s\n", strerror(errno)); 674 | fclose(cxt.fp); 675 | unlink(tmpfn); 676 | return -4; 677 | } 678 | 679 | /* Save where we'll write the first file and move back to the file table. */ 680 | cxt.wpos = ftell(cxt.fp); 681 | if(fseek(cxt.fp, 0, SEEK_SET)) { 682 | printf("Seek error: %s\n", strerror(errno)); 683 | fclose(cxt.fp); 684 | unlink(tmpfn); 685 | return -5; 686 | } 687 | 688 | /* Write out the first header entry. */ 689 | hdrbuf[4] = (uint8_t)(entries); 690 | hdrbuf[5] = (uint8_t)(entries >> 8); 691 | hdrbuf[6] = (uint8_t)(entries >> 16); 692 | hdrbuf[7] = (uint8_t)(entries >> 24); 693 | hdrbuf[8] = 0x50; 694 | hdrbuf[9] = 0x01; 695 | 696 | if(fwrite(hdrbuf, 1, 64, cxt.fp) != 64) { 697 | printf("Write error: %s\n", strerror(errno)); 698 | fclose(cxt.fp); 699 | unlink(tmpfn); 700 | return -6; 701 | } 702 | 703 | cxt.fpos = 64; 704 | 705 | if(scan_bml(fn, ©_update, &cxt) < 0) { 706 | fclose(cxt.fp); 707 | unlink(tmpfn); 708 | return -7; 709 | } 710 | 711 | /* All the files are copied, so move the archive into its place. */ 712 | #ifndef _WIN32 713 | mask = umask(0); 714 | umask(mask); 715 | fchmod(fileno(cxt.fp), (~mask) & 0666); 716 | #endif 717 | 718 | fclose(cxt.fp); 719 | rename(tmpfn, fn); 720 | 721 | return 0; 722 | } 723 | 724 | /* Print information about this program to stdout. */ 725 | static void print_program_info(void) { 726 | #if defined(VERSION) 727 | printf("Sylverant BML Tool version %s\n", VERSION); 728 | #elif defined(SVN_REVISION) 729 | printf("Sylverant BML Tool SVN revision: %s\n", SVN_REVISION); 730 | #else 731 | printf("Sylverant BML Tool\n"); 732 | #endif 733 | printf("Copyright (C) 2014 Lawrence Sebald\n\n"); 734 | printf("This program is free software: you can redistribute it and/or\n" 735 | "modify it under the terms of the GNU Affero General Public\n" 736 | "License version 3 as published by the Free Software Foundation.\n\n" 737 | "This program is distributed in the hope that it will be useful,\n" 738 | "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" 739 | "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" 740 | "GNU General Public License for more details.\n\n" 741 | "You should have received a copy of the GNU Affero General Public\n" 742 | "License along with this program. If not, see " 743 | ".\n"); 744 | } 745 | 746 | /* Print help to the user to stdout. */ 747 | static void print_help(const char *bin) { 748 | printf("Usage:\n" 749 | "To list the files in an archive:\n" 750 | " %s -t bml_archive\n" 751 | "To extract all files from an archive:\n" 752 | " %s -x bml_archive\n" 753 | "To extract and decompress all files from an archive:\n" 754 | " %s -xd bml_archive\n" 755 | "To extract a single file from an archive:\n" 756 | " %s -xs bml_archive file_in_archive\n" 757 | "To extract and decompress a single file from an archive:\n" 758 | " %s -xsd bml_archive file_in_archive\n" 759 | "To update a file in an archive (or replace it with another file):\n" 760 | " %s -u bml_archive file_in_archive filename\n" 761 | "To update a PVM file (attached to a file in the archive):\n" 762 | " %s -up bml_archive parent_file_in_archive filename\n" 763 | "To print this help message:\n" 764 | " %s --help\n" 765 | "To print version information:\n" 766 | " %s --version\n\n" 767 | "Note that when extracting a single file, if there is an attached\n" 768 | "PVM file to the specified file, it will also be extracted.\n\n" 769 | "Also, for updating a file, you must provide the uncompressed file\n" 770 | "to be added. This program will compress it as appropriate.\n", 771 | bin, bin, bin, bin, bin, bin, bin, bin, bin); 772 | } 773 | 774 | /* Parse any command-line arguments passed in. */ 775 | static void parse_command_line(int argc, const char *argv[]) { 776 | if(argc < 2) { 777 | print_help(argv[0]); 778 | exit(EXIT_FAILURE); 779 | } 780 | 781 | if(!strcmp(argv[1], "--version")) { 782 | print_program_info(); 783 | } 784 | else if(!strcmp(argv[1], "--help")) { 785 | print_help(argv[0]); 786 | } 787 | else if(!strcmp(argv[1], "-t")) { 788 | if(argc != 3) { 789 | print_help(argv[0]); 790 | exit(EXIT_FAILURE); 791 | } 792 | 793 | if(scan_bml(argv[2], &print_file_info, NULL) < 0) 794 | exit(EXIT_FAILURE); 795 | } 796 | else if(!strcmp(argv[1], "-x")) { 797 | if(argc != 3) { 798 | print_help(argv[0]); 799 | exit(EXIT_FAILURE); 800 | } 801 | 802 | if(scan_bml(argv[2], &extract_file, NULL) < 0) 803 | exit(EXIT_FAILURE); 804 | } 805 | else if(!strcmp(argv[1], "-xd")) { 806 | if(argc != 3) { 807 | print_help(argv[0]); 808 | exit(EXIT_FAILURE); 809 | } 810 | 811 | if(scan_bml(argv[2], &decompress_file, NULL) < 0) 812 | exit(EXIT_FAILURE); 813 | } 814 | else if(!strcmp(argv[1], "-xs")) { 815 | if(argc != 4) { 816 | print_help(argv[0]); 817 | exit(EXIT_FAILURE); 818 | } 819 | 820 | if(scan_bml(argv[2], &extract_file, (void *)argv[3]) < 0) 821 | exit(EXIT_FAILURE); 822 | } 823 | else if(!strcmp(argv[1], "-xsd")) { 824 | if(argc != 4) { 825 | print_help(argv[0]); 826 | exit(EXIT_FAILURE); 827 | } 828 | 829 | if(scan_bml(argv[2], &decompress_file, (void *)argv[3]) < 0) 830 | exit(EXIT_FAILURE); 831 | } 832 | else if(!strcmp(argv[1], "-u")) { 833 | if(argc != 5) { 834 | print_help(argv[0]); 835 | exit(EXIT_FAILURE); 836 | } 837 | 838 | if(update_bml(argv[2], argv[3], argv[4], 0)) 839 | exit(EXIT_FAILURE); 840 | } 841 | else if(!strcmp(argv[1], "-up")) { 842 | if(argc != 5) { 843 | print_help(argv[0]); 844 | exit(EXIT_FAILURE); 845 | } 846 | 847 | if(update_bml(argv[2], argv[3], argv[4], 1)) 848 | exit(EXIT_FAILURE); 849 | } 850 | else { 851 | printf("Illegal command line argument: %s\n", argv[1]); 852 | print_help(argv[0]); 853 | exit(EXIT_FAILURE); 854 | } 855 | 856 | exit(EXIT_SUCCESS); 857 | } 858 | 859 | int main(int argc, const char *argv[]) { 860 | /* Parse the command line... */ 861 | parse_command_line(argc, argv); 862 | 863 | return 0; 864 | } 865 | -------------------------------------------------------------------------------- /bmltool/prs-comp.c: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Sylverant PSO Server. 3 | 4 | Copyright (C) 2014 Lawrence Sebald 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License version 3 8 | as published by the Free Software Foundation. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | /****************************************************************************** 20 | PRS Compression Library 21 | 22 | PRS Compression, as used by Sega in many games (including all of the PSO 23 | games), is basically just a normal implementation of the Lempel-Ziv '77 24 | (LZ77) sliding-window compression algorithm. LZ77 is the basis of many other 25 | compression algorithms, including the very popular DEFLATE algorithm (from 26 | gzip and zlib). The code in here actually takes a number of cues from 27 | DEFLATE and specifically from zlib. 28 | ******************************************************************************/ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #define MAX_WINDOW 0x2000 38 | #define WINDOW_MASK (MAX_WINDOW - 1) 39 | #define HASH_SIZE (1 << 8) 40 | #define HASH_MASK (HASH_SIZE - 1) 41 | #define HASH(c1, c2) (c1 ^ c2) 42 | #define ENT(h) hash[h] 43 | #define PREV(s) h_prev[((uintptr_t)(s)) & WINDOW_MASK] 44 | 45 | #define ADD_TO_HASH(htab, s, h) { \ 46 | htab->PREV((s)) = htab->ENT(h); \ 47 | htab->ENT(h) = s; \ 48 | } 49 | 50 | #define INIT_ADD_HASH(htab, s, h) { \ 51 | h = HASH(*(s), *(s + 1)); \ 52 | ADD_TO_HASH(htab, s, h); \ 53 | } 54 | 55 | #define HASH_STR(s) HASH(*(s), *((s) + 1)) 56 | 57 | struct prs_comp_cxt { 58 | uint8_t flags; 59 | 60 | int bits_left; 61 | const uint8_t *src; 62 | uint8_t *dst; 63 | uint8_t *flag_ptr; 64 | 65 | size_t src_len; 66 | size_t dst_len; 67 | size_t src_pos; 68 | size_t dst_pos; 69 | }; 70 | 71 | struct prs_hash_cxt { 72 | const uint8_t *hash[HASH_SIZE]; 73 | const uint8_t *h_prev[MAX_WINDOW]; 74 | }; 75 | 76 | /****************************************************************************** 77 | Determine the max size of a "compressed" version of a piece of data. 78 | 79 | This function determines the maximum size that a file will be "compressed" 80 | to. Note that this value will *always* be greater than the original size of 81 | the file. This is used internally to allocate a buffer to start compression 82 | into, where the final buffer is resized accordingly. 83 | 84 | The formula for this size is as follows: 85 | len + ceil((len + 2) / 8) + 2 86 | 87 | This is derived from the fact that if a file is completely incompressible 88 | (no patterns repeat within the window), it will be spat out as a bunch of 89 | literals. For every literal spat out by the compressor, one bit of a flag 90 | byte is used. At the end of the file, a final two bit pattern is output into 91 | the flag byte, as well as two NUL bytes to symbolize the end of the data. 92 | Thus, the number of bits in the various flag bytes will be capped at a max 93 | of len + 2, leading to ceil((len + 2) / 8) bytes used for flags. The 94 | individual data bytes make up the len part of the formula and the two final 95 | NUL bytes make up the rest. 96 | 97 | Most of the time, the compressed output will be quite a bit smaller than the 98 | value returned here. We just use the maximum value so that we never have to 99 | worry about reallocating buffers as we go along. 100 | ******************************************************************************/ 101 | size_t prs_max_compressed_size(size_t len) { 102 | len += 2; 103 | return len + (len >> 3) + ((len & 0x07) ? 1 : 0); 104 | } 105 | 106 | static int set_bit(struct prs_comp_cxt *cxt, int value) { 107 | if(!cxt->bits_left--) { 108 | if(cxt->dst_pos >= cxt->dst_len) 109 | return -ENOSPC; 110 | 111 | /* Write out the flags to their position in the file, and set up the 112 | next flag position. */ 113 | *cxt->flag_ptr = cxt->flags; 114 | cxt->flag_ptr = cxt->dst + cxt->dst_pos++; 115 | cxt->bits_left = 7; 116 | } 117 | 118 | cxt->flags >>= 1; 119 | if(value) 120 | cxt->flags |= 0x80; 121 | 122 | return 0; 123 | } 124 | 125 | static int write_final_flags(struct prs_comp_cxt *cxt) { 126 | cxt->flags >>= cxt->bits_left; 127 | *cxt->flag_ptr = cxt->flags; 128 | 129 | return 0; 130 | } 131 | 132 | static int copy_literal(struct prs_comp_cxt *cxt) { 133 | if(cxt->dst_pos >= cxt->dst_len) 134 | return -ENOSPC; 135 | 136 | if(cxt->src_pos >= cxt->src_len) 137 | return -EPERM; 138 | 139 | *(cxt->dst + cxt->dst_pos++) = *(cxt->src + cxt->src_pos++); 140 | 141 | return 0; 142 | } 143 | 144 | static int write_literal(struct prs_comp_cxt *cxt, uint8_t val) { 145 | if(cxt->dst_pos >= cxt->dst_len) 146 | return -ENOSPC; 147 | 148 | *(cxt->dst + cxt->dst_pos++) = val; 149 | 150 | return 0; 151 | } 152 | 153 | static int write_eof(struct prs_comp_cxt *cxt) { 154 | int rv; 155 | 156 | /* Set the last two bits (01) and write the flags to the buffer. */ 157 | if((rv = set_bit(cxt, 0))) 158 | return rv; 159 | 160 | if((rv = set_bit(cxt, 1))) 161 | return rv; 162 | 163 | if((rv = write_final_flags(cxt))) 164 | return rv; 165 | 166 | /* Write the two NUL bytes that the file must end with. */ 167 | if((rv = write_literal(cxt, 0))) 168 | return rv; 169 | 170 | if((rv = write_literal(cxt, 0))) 171 | return rv; 172 | 173 | return 0; 174 | } 175 | 176 | static int match_length(struct prs_comp_cxt *cxt, const uint8_t *s2) { 177 | int len = 0; 178 | const uint8_t *s1 = cxt->src + cxt->src_pos, *end = cxt->src + cxt->src_len; 179 | 180 | while(s1 < end && *s1 == *s2) { 181 | ++len; 182 | ++s1; 183 | ++s2; 184 | } 185 | 186 | return len; 187 | } 188 | 189 | static int find_longest_match(struct prs_comp_cxt *cxt, struct prs_hash_cxt *hc, 190 | int *pos, int lazy) { 191 | uint8_t hash; 192 | const uint8_t *ent, *ent2; 193 | int mlen; 194 | int longest = 0; 195 | const uint8_t *longest_match = NULL; 196 | uintptr_t diff; 197 | 198 | if(cxt->src_pos >= cxt->src_len) 199 | return 0; 200 | 201 | /* Figure out where we're looking. */ 202 | hash = HASH_STR(cxt->src + cxt->src_pos); 203 | 204 | /* Is there anything in the table at that point? If not, we obviously don't 205 | have a match, so bail out now. */ 206 | if(!(ent = hc->ENT(hash))) { 207 | if(!lazy) 208 | ADD_TO_HASH(hc, cxt->src + cxt->src_pos, hash); 209 | return 0; 210 | } 211 | 212 | /* Make sure our initial match isn't outside the window. */ 213 | diff = (uintptr_t)ent - (uintptr_t)cxt->src; 214 | 215 | /* If we'd go outside the window, truncate the hash chain now. */ 216 | if(cxt->src_pos - diff > MAX_WINDOW) { 217 | hc->ENT(hash) = NULL; 218 | if(!lazy) 219 | ADD_TO_HASH(hc, cxt->src + cxt->src_pos, hash); 220 | return 0; 221 | } 222 | 223 | /* Ok, we have something in the hash table that matches the hash value. That 224 | doesn't necessarily mean we have a matching string though, of course. 225 | Follow the chain to see if we do, and find the longest match. */ 226 | while(ent) { 227 | if((mlen = match_length(cxt, ent))) { 228 | if(mlen > longest || mlen >= 256) { 229 | longest = mlen; 230 | longest_match = ent; 231 | } 232 | } 233 | 234 | /* Follow the chain, making sure not to exceed a difference of 8KiB. */ 235 | if((ent2 = hc->PREV(ent))) { 236 | diff = (uintptr_t)ent2 - (uintptr_t)cxt->src; 237 | 238 | /* If we'd go outside the window, truncate the hash chain now. */ 239 | if(cxt->src_pos - diff > MAX_WINDOW) { 240 | hc->PREV(ent) = NULL; 241 | ent2 = NULL; 242 | } 243 | } 244 | 245 | /* Follow the chain for the next pass. */ 246 | ent = ent2; 247 | } 248 | 249 | /* Did we find a match? */ 250 | if(longest) { 251 | diff = (uintptr_t)longest_match - (uintptr_t)cxt->src; 252 | *pos = ((int)diff - (int)cxt->src_pos); 253 | } 254 | 255 | /* Add our current string to the hash. */ 256 | if(!lazy) 257 | ADD_TO_HASH(hc, cxt->src + cxt->src_pos, hash); 258 | 259 | return longest; 260 | } 261 | 262 | static void add_intermediates(struct prs_comp_cxt *cxt, struct prs_hash_cxt *hc, 263 | int len) { 264 | int i; 265 | uint8_t hash; 266 | 267 | for(i = 1; i < len; ++i) { 268 | hash = HASH_STR(cxt->src + cxt->src_pos + i); 269 | ADD_TO_HASH(hc, cxt->src + cxt->src_pos + i, hash); 270 | } 271 | } 272 | 273 | /****************************************************************************** 274 | Archive a buffer of data into PRS format. 275 | 276 | This function archives a buffer of data into a format that can be 277 | "decompressed" by a program that handles PRS files. By archive, I mean that 278 | the file is not actually compressed -- in fact, the output will be larger 279 | than the input data (see prs_max_compressed_size for how big it'll actually 280 | be). 281 | 282 | There's really very little reason to ever use this, but it is here for you 283 | if you really want it. 284 | ******************************************************************************/ 285 | int prs_archive(const uint8_t *src, uint8_t **dst, size_t src_len) { 286 | struct prs_comp_cxt cxt; 287 | int rv; 288 | 289 | /* Check the input to make sure we've got valid source/destination pointers 290 | and something to do. */ 291 | if(!src || !dst) 292 | return -EFAULT; 293 | 294 | if(!src_len) 295 | return -EINVAL; 296 | 297 | /* Clear the context and fill in what we need to do our job. */ 298 | memset(&cxt, 0, sizeof(cxt)); 299 | cxt.src = src; 300 | cxt.src_len = src_len; 301 | cxt.dst_len = prs_max_compressed_size(src_len); 302 | 303 | /* Allocate our "compressed" buffer. */ 304 | if(!(cxt.dst = (uint8_t *)malloc(cxt.dst_len))) 305 | return -errno; 306 | 307 | cxt.flag_ptr = cxt.dst; 308 | 309 | /* Copy each byte, filling in the flags as we go along. */ 310 | while(src_len--) { 311 | /* Set the bit in the flag since we're just putting a literal in the 312 | output. */ 313 | if((rv = set_bit(&cxt, 1))) 314 | return rv; 315 | 316 | /* Copy the byte over. */ 317 | if((rv = copy_literal(&cxt))) 318 | return rv; 319 | } 320 | 321 | if((rv = write_eof(&cxt))) 322 | return rv; 323 | 324 | *dst = cxt.dst; 325 | return (int)cxt.dst_pos; 326 | } 327 | 328 | /****************************************************************************** 329 | Compress a buffer of data into PRS format. 330 | 331 | This function compresses a buffer of data with PRS compression. This 332 | function will never produce output larger than that of the prs_archive 333 | function, and will usually produce output that is significantly smaller. 334 | ******************************************************************************/ 335 | int prs_compress(const uint8_t *src, uint8_t **dst, size_t src_len) { 336 | struct prs_comp_cxt cxt; 337 | struct prs_hash_cxt *hcxt; 338 | int rv, mlen, mlen2; 339 | uint8_t tmp; 340 | int offset, offset2; 341 | int lazy_offs = 0; 342 | 343 | /* Check the input to make sure we've got valid source/destination pointers 344 | and something to do. */ 345 | if(!src || !dst) 346 | return -EFAULT; 347 | 348 | if(!src_len) 349 | return -EINVAL; 350 | 351 | /* Meh. Don't feel like dealing with it here, since it's not compressible 352 | at all anyway. */ 353 | if(src_len <= 3) 354 | return prs_archive(src, dst, src_len); 355 | 356 | /* Allocate the hash context. */ 357 | if(!(hcxt = (struct prs_hash_cxt *)malloc(sizeof(struct prs_hash_cxt)))) 358 | return -errno; 359 | 360 | /* Clear the contexts and fill in what we need to do our job. */ 361 | memset(&cxt, 0, sizeof(cxt)); 362 | memset(hcxt, 0, sizeof(struct prs_hash_cxt)); 363 | cxt.src = src; 364 | cxt.src_len = src_len; 365 | cxt.dst_len = prs_max_compressed_size(src_len); 366 | 367 | /* Allocate our "compressed" buffer. */ 368 | if(!(cxt.dst = (uint8_t *)malloc(cxt.dst_len))) { 369 | free(hcxt); 370 | return -errno; 371 | } 372 | 373 | cxt.flag_ptr = cxt.dst; 374 | 375 | /* Add the first two "strings" to the hash table. */ 376 | INIT_ADD_HASH(hcxt, src, tmp); 377 | INIT_ADD_HASH(hcxt, src + 1, tmp); 378 | 379 | /* Copy the first two bytes as literals... */ 380 | if((rv = set_bit(&cxt, 1))) 381 | goto out; 382 | 383 | if((rv = copy_literal(&cxt))) 384 | goto out; 385 | 386 | if((rv = set_bit(&cxt, 1))) 387 | goto out; 388 | 389 | if((rv = copy_literal(&cxt))) 390 | goto out; 391 | 392 | /* Process each byte. */ 393 | while(cxt.src_pos < cxt.src_len - 1) { 394 | /* Is there a match? */ 395 | if((mlen = find_longest_match(&cxt, hcxt, &offset, 0))) { 396 | cxt.src_pos++; 397 | mlen2 = find_longest_match(&cxt, hcxt, &offset2, 1); 398 | cxt.src_pos--; 399 | 400 | /* Did the "lazy match" produce something more compressed? */ 401 | if(mlen2 > mlen) { 402 | /* Check if it is a good idea to switch from a short match to a 403 | long one, if we would do that. */ 404 | if(mlen >= 2 && mlen <= 5 && offset2 < offset) { 405 | if(offset >= -256 && offset2 < -256) { 406 | if(mlen2 - mlen < 3) { 407 | goto blergh; 408 | } 409 | } 410 | } 411 | 412 | if((rv = set_bit(&cxt, 1))) 413 | goto out; 414 | 415 | if((rv = copy_literal(&cxt))) 416 | goto out; 417 | 418 | continue; 419 | } 420 | 421 | blergh: 422 | /* What kind of match did we find? */ 423 | if(mlen >= 2 && mlen <= 5 && offset >= -256) { 424 | /* Short match. */ 425 | if((rv = set_bit(&cxt, 0))) 426 | goto out; 427 | 428 | if((rv = set_bit(&cxt, 0))) 429 | goto out; 430 | 431 | if((rv = set_bit(&cxt, (mlen - 2) & 0x02))) 432 | goto out; 433 | 434 | if((rv = set_bit(&cxt, (mlen - 2) & 0x01))) 435 | goto out; 436 | 437 | if((rv = write_literal(&cxt, offset & 0xFF))) 438 | goto out; 439 | 440 | add_intermediates(&cxt, hcxt, mlen); 441 | cxt.src_pos += mlen; 442 | continue; 443 | } 444 | else if(mlen >= 3 && mlen <= 9) { 445 | /* Long match, short length. */ 446 | if((rv = set_bit(&cxt, 0))) 447 | goto out; 448 | 449 | if((rv = set_bit(&cxt, 1))) 450 | goto out; 451 | 452 | tmp = ((offset & 0x1f) << 3) | ((mlen - 2) & 0x07); 453 | if((rv = write_literal(&cxt, tmp))) 454 | goto out; 455 | 456 | tmp = offset >> 5; 457 | if((rv = write_literal(&cxt, tmp))) 458 | goto out; 459 | 460 | add_intermediates(&cxt, hcxt, mlen); 461 | cxt.src_pos += mlen; 462 | continue; 463 | } 464 | else if(mlen > 9) { 465 | /* Long match, long length. */ 466 | if(mlen > 256) 467 | mlen = 256; 468 | 469 | if((rv = set_bit(&cxt, 0))) 470 | goto out; 471 | 472 | if((rv = set_bit(&cxt, 1))) 473 | goto out; 474 | 475 | tmp = ((offset & 0x1f) << 3); 476 | if((rv = write_literal(&cxt, tmp))) 477 | goto out; 478 | 479 | tmp = offset >> 5; 480 | if((rv = write_literal(&cxt, tmp))) 481 | goto out; 482 | 483 | if((rv = write_literal(&cxt, mlen - 1))) 484 | goto out; 485 | 486 | add_intermediates(&cxt, hcxt, mlen); 487 | cxt.src_pos += mlen; 488 | continue; 489 | } 490 | } 491 | 492 | /* If we get here, we didn't find a suitable match, so just write the 493 | byte as a literal in the output. */ 494 | if((rv = set_bit(&cxt, 1))) 495 | goto out; 496 | 497 | /* Copy the byte over. */ 498 | if((rv = copy_literal(&cxt))) 499 | goto out; 500 | } 501 | 502 | /* If we still have a left over byte at the end, put it in as a literal. */ 503 | if(cxt.src_pos < cxt.src_len) { 504 | /* Set the bit in the flag since we're just putting a literal in the 505 | output. */ 506 | if((rv = set_bit(&cxt, 1))) 507 | goto out; 508 | 509 | /* Copy the byte over. */ 510 | if((rv = copy_literal(&cxt))) 511 | goto out; 512 | } 513 | 514 | if((rv = write_eof(&cxt))) 515 | goto out; 516 | 517 | free(hcxt); 518 | 519 | /* Resize the output (if realloc fails to resize it, then just use the 520 | unshortened buffer). */ 521 | if(!(*dst = realloc(cxt.dst, cxt.dst_pos))) 522 | *dst = cxt.dst; 523 | 524 | return (int)cxt.dst_pos; 525 | 526 | out: 527 | free(cxt.dst); 528 | free(hcxt); 529 | return rv; 530 | } 531 | -------------------------------------------------------------------------------- /bmltool/prs-decomp.c: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Sylverant PSO Server. 3 | 4 | Copyright (C) 2014 Lawrence Sebald 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License version 3 8 | as published by the Free Software Foundation. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | struct prs_dec_cxt { 26 | uint8_t flags; 27 | 28 | int bit_pos; 29 | const uint8_t *src; 30 | uint8_t *dst; 31 | void *udata; 32 | 33 | size_t src_len; 34 | size_t dst_len; 35 | size_t src_pos; 36 | size_t dst_pos; 37 | 38 | int (*copy_byte)(struct prs_dec_cxt *cxt); 39 | int (*offset_copy)(struct prs_dec_cxt *cxt, int offset); 40 | int (*fetch_bit)(struct prs_dec_cxt *cxt); 41 | int (*fetch_byte)(struct prs_dec_cxt *cxt); 42 | int (*fetch_short)(struct prs_dec_cxt *cxt); 43 | }; 44 | 45 | /****************************************************************************** 46 | PRS Decompression Function 47 | 48 | This function does the real work of decompressing whatever you throw at it. 49 | It uses a bunch of callbacks in the context provided to read the compressed 50 | data and do whatever is needed with it. 51 | ******************************************************************************/ 52 | static int do_decompress(struct prs_dec_cxt *cxt) { 53 | int flag, size; 54 | int32_t offset; 55 | 56 | for(;;) { 57 | /* Read the flag bit for this pass. */ 58 | if((flag = cxt->fetch_bit(cxt)) < 0) 59 | return flag; 60 | 61 | /* Flag bit = 1 -> Simple byte copy from src to dst. */ 62 | if(flag) { 63 | if((flag = cxt->copy_byte(cxt)) < 0) 64 | return flag; 65 | 66 | continue; 67 | } 68 | 69 | /* The flag starts with a zero, so it isn't just a simple byte copy. 70 | Read the next bit to see what we have left to do. */ 71 | if((flag = cxt->fetch_bit(cxt)) < 0) 72 | return flag; 73 | 74 | /* Flag bit = 1 -> Either long copy or end of file. */ 75 | if(flag) { 76 | if((offset = cxt->fetch_short(cxt)) < 0) 77 | return offset; 78 | 79 | /* Two zero bytes implies that this is the end of the file. Return 80 | the length of the file. */ 81 | if(!offset) 82 | return (int)cxt->dst_pos; 83 | 84 | /* Do we need to read a size byte, or is it encoded in what we 85 | already got? */ 86 | size = offset & 0x0007; 87 | offset >>= 3; 88 | 89 | if(!size) { 90 | if((size = cxt->fetch_byte(cxt)) < 0) 91 | return size; 92 | 93 | ++size; 94 | } 95 | else { 96 | size += 2; 97 | } 98 | 99 | offset |= 0xFFFFE000; 100 | } 101 | /* Flag bit = 0 -> short copy. */ 102 | else { 103 | /* Fetch the two bits needed to determine the size. */ 104 | if((flag = cxt->fetch_bit(cxt)) < 0) 105 | return flag; 106 | 107 | if((size = cxt->fetch_bit(cxt)) < 0) 108 | return size; 109 | 110 | size = (size | (flag << 1)) + 2; 111 | 112 | /* Fetch the offset byte. */ 113 | if((offset = cxt->fetch_byte(cxt)) < 0) 114 | return offset; 115 | 116 | offset |= 0xFFFFFF00; 117 | } 118 | 119 | /* Copy the data. */ 120 | while(size--) { 121 | if((flag = cxt->offset_copy(cxt, offset)) < 0) 122 | return flag; 123 | } 124 | } 125 | } 126 | 127 | /****************************************************************************** 128 | Internal utility functions. 129 | 130 | Depending on how the compressed data is to be obtained, different sets of 131 | these functions will be used. 132 | ******************************************************************************/ 133 | static int fetch_bit(struct prs_dec_cxt *cxt) { 134 | int rv; 135 | 136 | /* Did we finish with a full byte last time we were in here? */ 137 | if(!cxt->bit_pos) { 138 | /* Make sure we won't fall off the end of the file by reading the byte 139 | from it. */ 140 | if(cxt->src_pos >= cxt->src_len) 141 | return -EBADMSG; 142 | 143 | cxt->flags = *cxt->src++; 144 | ++cxt->src_pos; 145 | cxt->bit_pos = 8; 146 | } 147 | 148 | /* Fetch the bit and shift it off the end of the byte. */ 149 | rv = cxt->flags & 1; 150 | cxt->flags >>= 1; 151 | --cxt->bit_pos; 152 | 153 | return rv; 154 | } 155 | 156 | static int copy_byte(struct prs_dec_cxt *cxt) { 157 | /* Make sure we still have data left in the input buffer. */ 158 | if(cxt->src_pos >= cxt->src_len) 159 | return -EBADMSG; 160 | 161 | /* Make sure we have space left in the destination buffer. */ 162 | if(cxt->dst_pos >= cxt->dst_len) 163 | return -ENOSPC; 164 | 165 | /* Copy the byte and increment all the counters/pointers. */ 166 | *cxt->dst++ = *cxt->src++; 167 | ++cxt->src_pos; 168 | ++cxt->dst_pos; 169 | 170 | return 0; 171 | } 172 | 173 | static int fetch_byte(struct prs_dec_cxt *cxt) { 174 | uint8_t rv; 175 | 176 | /* Make sure we still have data left in the input buffer. */ 177 | if(cxt->src_pos >= cxt->src_len) 178 | return -EBADMSG; 179 | 180 | /* Read the byte from the buffer. */ 181 | rv = *cxt->src++; 182 | ++cxt->src_pos; 183 | 184 | return (int)rv; 185 | } 186 | 187 | static int fetch_short(struct prs_dec_cxt *cxt) { 188 | uint16_t rv; 189 | 190 | /* Make sure we still have data left in the input buffer. */ 191 | if(cxt->src_pos + 1 >= cxt->src_len) 192 | return -EBADMSG; 193 | 194 | /* Read the two bytes from the buffer. */ 195 | rv = *cxt->src++; 196 | ++cxt->src_pos; 197 | rv |= *cxt->src++ << 8; 198 | ++cxt->src_pos; 199 | 200 | return (int)rv; 201 | } 202 | 203 | static int offset_copy(struct prs_dec_cxt *cxt, int offset) { 204 | int tmp = (int)cxt->dst_pos + offset; 205 | 206 | /* Make sure the offset is valid. */ 207 | if(tmp < 0) 208 | return -EBADMSG; 209 | 210 | /* Make sure we have space left in the destination buffer. */ 211 | if(cxt->dst_pos >= cxt->dst_len) 212 | return -ENOSPC; 213 | 214 | /* Copy the byte and increment all the counters/pointers. */ 215 | *cxt->dst++ = *(cxt->dst + offset); 216 | ++cxt->dst_pos; 217 | 218 | return 0; 219 | } 220 | 221 | static int nocopy_byte(struct prs_dec_cxt *cxt) { 222 | /* Make sure we still have data left in the input buffer. */ 223 | if(cxt->src_pos >= cxt->src_len) 224 | return -EBADMSG; 225 | 226 | /* Increment the counters/pointers. */ 227 | ++cxt->src; 228 | ++cxt->src_pos; 229 | ++cxt->dst_pos; 230 | 231 | return 0; 232 | } 233 | 234 | static int offset_nocopy(struct prs_dec_cxt *cxt, int offset) { 235 | int tmp = (int)cxt->dst_pos + offset; 236 | 237 | /* Make sure the offset is valid. */ 238 | if(tmp < 0) 239 | return -EBADMSG; 240 | 241 | /* Increment the counter... */ 242 | ++cxt->dst_pos; 243 | 244 | return 0; 245 | } 246 | 247 | static int file_bit(struct prs_dec_cxt *cxt) { 248 | int rv; 249 | 250 | /* Did we finish with a full byte last time we were in here? */ 251 | if(!cxt->bit_pos) { 252 | /* Make sure we won't fall off the end of the file by reading the byte 253 | from it. */ 254 | if(cxt->src_pos >= cxt->src_len) 255 | return -EBADMSG; 256 | 257 | /* Read the next byte from the file. */ 258 | if((rv = fgetc((FILE *)cxt->udata)) == EOF) { 259 | if(ferror((FILE *)cxt->udata)) 260 | return -errno; 261 | return -EBADMSG; 262 | } 263 | 264 | cxt->flags = (uint8_t)rv; 265 | ++cxt->src_pos; 266 | cxt->bit_pos = 8; 267 | } 268 | 269 | /* Fetch the bit and shift it off the end of the byte. */ 270 | rv = cxt->flags & 1; 271 | cxt->flags >>= 1; 272 | --cxt->bit_pos; 273 | 274 | return rv; 275 | } 276 | 277 | static int copy_fbyte(struct prs_dec_cxt *cxt) { 278 | int b; 279 | void *tmp; 280 | 281 | /* Make sure we still have data left in the input file. */ 282 | if(cxt->src_pos >= cxt->src_len) 283 | return -EBADMSG; 284 | 285 | /* Make sure we have space left in the destination buffer. */ 286 | if(cxt->dst_pos >= cxt->dst_len) { 287 | if(!(tmp = realloc(cxt->dst, cxt->dst_len * 2))) 288 | return -errno; 289 | 290 | cxt->dst = (uint8_t *)tmp; 291 | cxt->dst_len *= 2; 292 | } 293 | 294 | /* Read the next byte from the file. */ 295 | if((b = fgetc((FILE *)cxt->udata)) == EOF) { 296 | if(ferror((FILE *)cxt->udata)) 297 | return -errno; 298 | return -EBADMSG; 299 | } 300 | 301 | /* Copy the byte and increment all the counters/pointers. */ 302 | *(cxt->dst + cxt->dst_pos) = (uint8_t)b; 303 | ++cxt->src_pos; 304 | ++cxt->dst_pos; 305 | 306 | return 0; 307 | } 308 | 309 | static int file_byte(struct prs_dec_cxt *cxt) { 310 | int rv; 311 | 312 | /* Make sure we still have data left in the input file. */ 313 | if(cxt->src_pos >= cxt->src_len) 314 | return -EBADMSG; 315 | 316 | /* Read the next byte from the file. */ 317 | if((rv = fgetc((FILE *)cxt->udata)) == EOF) { 318 | if(ferror((FILE *)cxt->udata)) 319 | return -errno; 320 | return -EBADMSG; 321 | } 322 | 323 | ++cxt->src_pos; 324 | 325 | return (int)rv; 326 | } 327 | 328 | static int file_short(struct prs_dec_cxt *cxt) { 329 | uint16_t rv; 330 | uint8_t b[2]; 331 | 332 | /* Make sure we still have data left in the input file. */ 333 | if(cxt->src_pos + 1 >= cxt->src_len) 334 | return -EBADMSG; 335 | 336 | /* Read the next two bytes from the file. */ 337 | if(fread(b, 1, 2, (FILE *)cxt->udata) != 2) 338 | return -errno; 339 | 340 | /* Combine the bytes into the 16-bit value we're looking for. */ 341 | rv = b[0] | (b[1] << 8); 342 | cxt->src_pos += 2; 343 | 344 | return (int)rv; 345 | } 346 | 347 | static int offset_copy_alloc(struct prs_dec_cxt *cxt, int offset) { 348 | int tmp = (int)cxt->dst_pos + offset; 349 | void *tmp2; 350 | 351 | /* Make sure the offset is valid. */ 352 | if(tmp < 0) 353 | return -EBADMSG; 354 | 355 | /* Make sure we have space left in the destination buffer. */ 356 | if(cxt->dst_pos >= cxt->dst_len) { 357 | if(!(tmp2 = realloc(cxt->dst, cxt->dst_len * 2))) 358 | return -errno; 359 | 360 | cxt->dst = (uint8_t *)tmp2; 361 | cxt->dst_len *= 2; 362 | } 363 | 364 | /* Copy the byte and increment all the counters/pointers. */ 365 | *(cxt->dst + cxt->dst_pos) = *(cxt->dst + cxt->dst_pos + offset); 366 | ++cxt->dst_pos; 367 | 368 | return 0; 369 | } 370 | 371 | static int copy_abyte(struct prs_dec_cxt *cxt) { 372 | void *tmp; 373 | 374 | /* Make sure we still have data left in the input file. */ 375 | if(cxt->src_pos >= cxt->src_len) 376 | return -EBADMSG; 377 | 378 | /* Make sure we have space left in the destination buffer. */ 379 | if(cxt->dst_pos >= cxt->dst_len) { 380 | if(!(tmp = realloc(cxt->dst, cxt->dst_len * 2))) 381 | return -errno; 382 | 383 | cxt->dst = (uint8_t *)tmp; 384 | cxt->dst_len *= 2; 385 | } 386 | 387 | /* Copy the byte and increment all the counters/pointers. */ 388 | *(cxt->dst + cxt->dst_pos) = *cxt->src++; 389 | ++cxt->src_pos; 390 | ++cxt->dst_pos; 391 | 392 | return 0; 393 | } 394 | 395 | /****************************************************************************** 396 | Public interface functions 397 | 398 | These functions are the public functions used to decompress PRS-compressed 399 | data. There are a variety of functions provided here for different purposes. 400 | 401 | prs_decompress_buf: 402 | Decompress data from a memory buffer into another memory buffer, 403 | allocating space as needed for the destination buffer. It is the 404 | caller's responsibility to free the decompressed memory buffer when it 405 | is no longer needed. 406 | 407 | prs_decompress_buf2: 408 | Decompress data from a memory buffer into another (pre-allocated) memory 409 | buffer. If the buffer is not large enough, an error (-ENOSPC) will be 410 | returned. 411 | 412 | prs_decompress_size: 413 | Determine the decompressed size of a block of memory containing PRS- 414 | compressed data. 415 | 416 | prs_decompress_file: 417 | Open the specified PRS-compressed file and decompress it into a new 418 | memory buffer. It is the caller's responsibility to free the 419 | decompressed memory buffer when it is no longer needed. 420 | 421 | All of these functions will return the size of the decompressed data on 422 | success, or a negative error code (from errno) on error. Common error codes 423 | include the following: 424 | -EBADMSG: Invalid compressed data encountered while decoding. 425 | -EINVAL: Invalid source length (0) given. 426 | -EFAULT: NULL pointer passed in. 427 | 428 | In addition, prs_decompress_file may return many other error codes related 429 | to reading from a file. prs_decompress_file and prs_decompress_buf may also 430 | return errors related to memory allocation. 431 | ******************************************************************************/ 432 | int prs_decompress_buf(const uint8_t *src, uint8_t **dst, size_t src_len) { 433 | struct prs_dec_cxt cxt = 434 | { 0, 0, src, NULL, NULL, src_len, src_len * 2, 0, 0, ©_abyte, 435 | &offset_copy_alloc, &fetch_bit, &fetch_byte, &fetch_short }; 436 | int rv; 437 | 438 | if(!src || !dst) 439 | return -EFAULT; 440 | 441 | if(!src_len) 442 | return -EINVAL; 443 | 444 | /* The minimum length of a PRS compressed file (if you were to "compress" a 445 | zero-byte file) is 3 bytes. If we don't have that, then bail out now. */ 446 | if(cxt.src_len < 3) 447 | return -EBADMSG; 448 | 449 | /* Allocate some space for the output. Start with two times the length of 450 | the input (we will resize this later, as needed). */ 451 | if(!(cxt.dst = (uint8_t *)malloc(cxt.dst_len))) 452 | return -errno; 453 | 454 | /* Do the decompression. */ 455 | if((rv = do_decompress(&cxt)) < 0) { 456 | free(cxt.dst); 457 | return rv; 458 | } 459 | 460 | /* Resize the output (if realloc fails to resize it, then just use the 461 | unshortened buffer). */ 462 | if(!(*dst = realloc(cxt.dst, rv))) 463 | *dst = cxt.dst; 464 | 465 | return rv; 466 | } 467 | 468 | int prs_decompress_buf2(const uint8_t *src, uint8_t *dst, size_t src_len, 469 | size_t dst_len) { 470 | struct prs_dec_cxt cxt = 471 | { 0, 0, src, dst, NULL, src_len, dst_len, 0, 0, ©_byte, 472 | &offset_copy, &fetch_bit, &fetch_byte, &fetch_short }; 473 | 474 | if(!src || !dst) 475 | return -EFAULT; 476 | 477 | if(!src_len || !dst_len) 478 | return -EINVAL; 479 | 480 | /* The minimum length of a PRS compressed file (if you were to "compress" a 481 | zero-byte file) is 3 bytes. If we don't have that, then bail out now. */ 482 | if(cxt.src_len < 3) 483 | return -EBADMSG; 484 | 485 | return do_decompress(&cxt); 486 | } 487 | 488 | int prs_decompress_size(const uint8_t *src, size_t src_len) { 489 | struct prs_dec_cxt cxt = 490 | { 0, 0, src, NULL, NULL, src_len, SIZE_MAX, 0, 0, &nocopy_byte, 491 | &offset_nocopy, &fetch_bit, &fetch_byte, &fetch_short }; 492 | 493 | if(!src) 494 | return -EFAULT; 495 | 496 | if(!src_len) 497 | return -EINVAL; 498 | 499 | /* The minimum length of a PRS compressed file (if you were to "compress" a 500 | zero-byte file) is 3 bytes. If we don't have that, then bail out now. */ 501 | if(cxt.src_len < 3) 502 | return -EBADMSG; 503 | 504 | return do_decompress(&cxt); 505 | } 506 | 507 | int prs_decompress_file(const char *fn, uint8_t **dst) { 508 | struct prs_dec_cxt cxt = 509 | { 0, 0, NULL, NULL, NULL, 0, 0, 0, 0, 510 | ©_fbyte, &offset_copy_alloc, &file_bit, &file_byte, &file_short }; 511 | long len; 512 | int rv; 513 | FILE *fp; 514 | 515 | if(!fn || !dst) 516 | return -EFAULT; 517 | 518 | if(!(fp = fopen(fn, "rb"))) 519 | return -errno; 520 | 521 | cxt.udata = fp; 522 | 523 | /* Figure out the length of the file. */ 524 | if(fseek(fp, 0, SEEK_END)) { 525 | fclose(fp); 526 | return -errno; 527 | } 528 | 529 | if((len = ftell(fp)) < 0) { 530 | fclose(fp); 531 | return -errno; 532 | } 533 | 534 | if(fseek(fp, 0, SEEK_SET)) { 535 | fclose(fp); 536 | return -errno; 537 | } 538 | 539 | cxt.src_len = (size_t)len; 540 | cxt.dst_len = cxt.src_len * 2; 541 | 542 | /* The minimum length of a PRS compressed file (if you were to "compress" a 543 | zero-byte file) is 3 bytes. If we don't have that, then bail out now. */ 544 | if(cxt.src_len < 3) { 545 | fclose(fp); 546 | return -EBADMSG; 547 | } 548 | 549 | /* Allocate some space for the output. Start with two times the length of 550 | the input (we will resize this later, as needed). */ 551 | if(!(cxt.dst = (uint8_t *)malloc(cxt.dst_len))) { 552 | fclose(fp); 553 | return -errno; 554 | } 555 | 556 | /* Do the decompression. */ 557 | if((rv = do_decompress(&cxt)) < 0) { 558 | free(cxt.dst); 559 | fclose(fp); 560 | return rv; 561 | } 562 | 563 | fclose(fp); 564 | 565 | /* Resize the output (if realloc fails to resize it, then just use the 566 | unshortened buffer). */ 567 | if(!(*dst = realloc(cxt.dst, rv))) 568 | *dst = cxt.dst; 569 | 570 | return rv; 571 | } 572 | -------------------------------------------------------------------------------- /bmltool/prs.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Sylverant PSO Server. 3 | 4 | Copyright (C) 2014 Lawrence Sebald 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License version 3 8 | as published by the Free Software Foundation. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #ifndef SYLVERANT__PRS_H 20 | #define SYLVERANT__PRS_H 21 | 22 | #include 23 | #include 24 | 25 | /* Compress a buffer with PRS compression. 26 | 27 | This function compresses the data in the src buffer into a new buffer. This 28 | function will never produce output larger than that of the prs_archive 29 | function, and will usually beat that function rather significantly. 30 | 31 | In testing, the compressed output of this function actually beats Sega's own 32 | compression slightly (by 100 bytes or so on an uncompressed version of the 33 | ItemPMT.prs from PSO Episode I & II Plus). 34 | 35 | It is the caller's responsibility to free *dst when it is no longer in use. 36 | 37 | Returns a negative value on failure (specifically something from . 38 | Returns the size of the compressed output on success. 39 | */ 40 | extern int prs_compress(const uint8_t *src, uint8_t **dst, size_t src_len); 41 | 42 | /* Archive a buffer in PRS format. 43 | 44 | This function archives the data in the src buffer into a new buffer. This 45 | function will always produce output that is larger in size than the input 46 | data (it does not actually compress the output. There's probably no good 47 | reason to ever use this, but it is here if you want it for some reason. 48 | 49 | All the notes about parameters and return values from prs_compress also apply 50 | to this function. The size of the output from this function will be equal to 51 | the return value of prs_max_compressed_size when called on the same length. 52 | */ 53 | extern int prs_archive(const uint8_t *src, uint8_t **dst, size_t src_len); 54 | 55 | /* Return the maximum size of archiving a buffer in PRS format. 56 | 57 | This function returns the size that prs_archive will spit out. This is used 58 | internally to allocate memory for prs_archive and prs_compress and probably 59 | has little utility outside of that. 60 | */ 61 | extern size_t prs_max_compressed_size(size_t len); 62 | 63 | /* Decompress a PRS archive from a file. 64 | 65 | This function opens the file specified and decompresses the data from the 66 | file into a newly allocated memory buffer. 67 | 68 | It is the caller's responsibility to free *dst when it is no longer in use. 69 | 70 | Returns a negative value on failure (specifically something from ). 71 | Returns the size of the decompressed output on success. 72 | */ 73 | extern int prs_decompress_file(const char *fn, uint8_t **dst); 74 | 75 | /* Decompress PRS-compressed data from a memory buffer. 76 | 77 | This function decompresses PRS-compressed data from the src buffer into a 78 | newly allocated memory buffer. 79 | 80 | It is the caller's responsibility to free *dst when it is no longer in use. 81 | 82 | Returns a negative value on failure (specifically something from ). 83 | Returns the size of the decompressed output on success. 84 | */ 85 | extern int prs_decompress_buf(const uint8_t *src, uint8_t **dst, 86 | size_t src_len); 87 | 88 | /* Decompress PRS-compressed data from a memory buffer into a previously 89 | allocated memory buffer. 90 | 91 | This function decompresses PRS-compressed data from the src buffer into the 92 | previously allocated allocated memory buffer dst. You must have already 93 | allocated the buffer at dst, and it should be at least the size returned by 94 | prs_decompress_size on the compressed input (otherwise, you will get an error 95 | back from the function). 96 | 97 | Returns a negative value on failure (specifically something from ). 98 | Returns the size of the decompressed output on success. 99 | */ 100 | extern int prs_decompress_buf2(const uint8_t *src, uint8_t *dst, size_t src_len, 101 | size_t dst_len); 102 | 103 | /* Determine the size that the PRS-compressed data in a buffer will expand to. 104 | 105 | This function essentially decompresses the PRS-compressed data from the src 106 | buffer without writing any output. By doing this, it determines the actual 107 | size of the decompressed output and returns it. 108 | 109 | Returns a negative value on failure (specifically something from ). 110 | Returns the size of the decompressed output on success. 111 | */ 112 | extern int prs_decompress_size(const uint8_t *src, size_t src_len); 113 | 114 | #endif /* !SYLVERANT__PRS_H */ 115 | -------------------------------------------------------------------------------- /bmltool/windows_compat.c: -------------------------------------------------------------------------------- 1 | /* 2 | Sylverant PSO Tools 3 | PSO Archive Tool 4 | Copyright (C) 2014 Lawrence Sebald 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License version 3 8 | as published by the Free Software Foundation. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | /* Have I ever mentioned how much I dislike the fact that Microsoft doesn't 20 | implement the standard functions that developers might expect in their C 21 | libraries? No, well, consider this me mentioning it. */ 22 | 23 | #ifdef _WIN32 24 | 25 | #define _WIN32_LEAN_AND_MEAN 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | /* mkstemp extracted from libc/sysdeps/posix/tempname.c. Copyright 33 | (C) 1991-1999, 2000, 2001, 2006 Free Software Foundation, Inc. 34 | 35 | The GNU C Library is free software; you can redistribute it and/or 36 | modify it under the terms of the GNU Lesser General Public 37 | License as published by the Free Software Foundation; either 38 | version 2.1 of the License, or (at your option) any later version. */ 39 | 40 | static const char letters[] = 41 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 42 | 43 | /* Generate a temporary file name based on TMPL. TMPL must match the 44 | rules for mk[s]temp (i.e. end in "XXXXXX"). The name constructed 45 | does not exist at the time of the call to mkstemp. TMPL is 46 | overwritten with the result. */ 47 | int 48 | mkstemp (char *tmpl) 49 | { 50 | int len; 51 | char *XXXXXX; 52 | static unsigned long long value; 53 | unsigned long long random_time_bits; 54 | unsigned int count; 55 | int fd = -1; 56 | int save_errno = errno; 57 | 58 | /* A lower bound on the number of temporary files to attempt to 59 | generate. The maximum total number of temporary file names that 60 | can exist for a given template is 62**6. It should never be 61 | necessary to try all these combinations. Instead if a reasonable 62 | number of names is tried (we define reasonable as 62**3) fail to 63 | give the system administrator the chance to remove the problems. */ 64 | #define ATTEMPTS_MIN (62 * 62 * 62) 65 | 66 | /* The number of times to attempt to generate a temporary file. To 67 | conform to POSIX, this must be no smaller than TMP_MAX. */ 68 | #if ATTEMPTS_MIN < TMP_MAX 69 | unsigned int attempts = TMP_MAX; 70 | #else 71 | unsigned int attempts = ATTEMPTS_MIN; 72 | #endif 73 | 74 | len = strlen (tmpl); 75 | if (len < 6 || strcmp (&tmpl[len - 6], "XXXXXX")) 76 | { 77 | errno = EINVAL; 78 | return -1; 79 | } 80 | 81 | /* This is where the Xs start. */ 82 | XXXXXX = &tmpl[len - 6]; 83 | 84 | /* Get some more or less random data. */ 85 | { 86 | SYSTEMTIME stNow; 87 | FILETIME ftNow; 88 | 89 | // get system time 90 | GetSystemTime(&stNow); 91 | stNow.wMilliseconds = 500; 92 | if (!SystemTimeToFileTime(&stNow, &ftNow)) 93 | { 94 | errno = -1; 95 | return -1; 96 | } 97 | 98 | random_time_bits = (((unsigned long long)ftNow.dwHighDateTime << 32) 99 | | (unsigned long long)ftNow.dwLowDateTime); 100 | } 101 | value += random_time_bits ^ (unsigned long long)GetCurrentThreadId (); 102 | 103 | for (count = 0; count < attempts; value += 7777, ++count) 104 | { 105 | unsigned long long v = value; 106 | 107 | /* Fill in the random bits. */ 108 | XXXXXX[0] = letters[v % 62]; 109 | v /= 62; 110 | XXXXXX[1] = letters[v % 62]; 111 | v /= 62; 112 | XXXXXX[2] = letters[v % 62]; 113 | v /= 62; 114 | XXXXXX[3] = letters[v % 62]; 115 | v /= 62; 116 | XXXXXX[4] = letters[v % 62]; 117 | v /= 62; 118 | XXXXXX[5] = letters[v % 62]; 119 | 120 | fd = open (tmpl, O_RDWR | O_CREAT | O_EXCL | O_BINARY, 0600); 121 | if (fd >= 0) 122 | { 123 | errno = save_errno; 124 | return fd; 125 | } 126 | else if (errno != EEXIST) 127 | return -1; 128 | } 129 | 130 | /* We got out of the loop because we ran out of combinations to try. */ 131 | errno = EEXIST; 132 | return -1; 133 | } 134 | 135 | /* Not thread safe, but I don't care. */ 136 | char *basename(char *input) { 137 | static char output[512]; 138 | char ext[256]; 139 | 140 | _splitpath(input, NULL, NULL, output, ext); 141 | strcat(output, ext); 142 | return output; 143 | } 144 | 145 | /* Really? rename() won't overwrite existing files on Windows? */ 146 | int my_rename(const char *old, const char *new) { 147 | if(!MoveFileEx(old, new, MOVEFILE_REPLACE_EXISTING | 148 | MOVEFILE_WRITE_THROUGH | MOVEFILE_COPY_ALLOWED)) 149 | return -1; 150 | 151 | return 0; 152 | } 153 | 154 | #endif 155 | -------------------------------------------------------------------------------- /prstool/Makefile: -------------------------------------------------------------------------------- 1 | # *nix Makefile. 2 | # Should build with any standardish C99-supporting compiler. 3 | 4 | all: prstool 5 | 6 | prstool: prstool.c prs-comp.c prs-decomp.c 7 | $(CC) -o prstool prstool.c prs-comp.c prs-decomp.c 8 | 9 | .PHONY: clean 10 | 11 | clean: 12 | -rm -fr prstool *.o *.dSYM 13 | -------------------------------------------------------------------------------- /prstool/prs-comp.c: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Sylverant PSO Server. 3 | 4 | Copyright (C) 2014 Lawrence Sebald 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License version 3 8 | as published by the Free Software Foundation. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | /****************************************************************************** 20 | PRS Compression Library 21 | 22 | PRS Compression, as used by Sega in many games (including all of the PSO 23 | games), is basically just a normal implementation of the Lempel-Ziv '77 24 | (LZ77) sliding-window compression algorithm. LZ77 is the basis of many other 25 | compression algorithms, including the very popular DEFLATE algorithm (from 26 | gzip and zlib). The code in here actually takes a number of cues from 27 | DEFLATE and specifically from zlib. 28 | ******************************************************************************/ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #define MAX_WINDOW 0x2000 38 | #define WINDOW_MASK (MAX_WINDOW - 1) 39 | #define HASH_SIZE (1 << 8) 40 | #define HASH_MASK (HASH_SIZE - 1) 41 | #define HASH(c1, c2) (c1 ^ c2) 42 | #define ENT(h) hash[h] 43 | #define PREV(s) h_prev[((uintptr_t)(s)) & WINDOW_MASK] 44 | 45 | #define ADD_TO_HASH(htab, s, h) { \ 46 | htab->PREV((s)) = htab->ENT(h); \ 47 | htab->ENT(h) = s; \ 48 | } 49 | 50 | #define INIT_ADD_HASH(htab, s, h) { \ 51 | h = HASH(*(s), *(s + 1)); \ 52 | ADD_TO_HASH(htab, s, h); \ 53 | } 54 | 55 | #define HASH_STR(s) HASH(*(s), *((s) + 1)) 56 | 57 | struct prs_comp_cxt { 58 | uint8_t flags; 59 | 60 | int bits_left; 61 | const uint8_t *src; 62 | uint8_t *dst; 63 | uint8_t *flag_ptr; 64 | 65 | size_t src_len; 66 | size_t dst_len; 67 | size_t src_pos; 68 | size_t dst_pos; 69 | }; 70 | 71 | struct prs_hash_cxt { 72 | const uint8_t *hash[HASH_SIZE]; 73 | const uint8_t *h_prev[MAX_WINDOW]; 74 | }; 75 | 76 | /****************************************************************************** 77 | Determine the max size of a "compressed" version of a piece of data. 78 | 79 | This function determines the maximum size that a file will be "compressed" 80 | to. Note that this value will *always* be greater than the original size of 81 | the file. This is used internally to allocate a buffer to start compression 82 | into, where the final buffer is resized accordingly. 83 | 84 | The formula for this size is as follows: 85 | len + ceil((len + 2) / 8) + 2 86 | 87 | This is derived from the fact that if a file is completely incompressible 88 | (no patterns repeat within the window), it will be spat out as a bunch of 89 | literals. For every literal spat out by the compressor, one bit of a flag 90 | byte is used. At the end of the file, a final two bit pattern is output into 91 | the flag byte, as well as two NUL bytes to symbolize the end of the data. 92 | Thus, the number of bits in the various flag bytes will be capped at a max 93 | of len + 2, leading to ceil((len + 2) / 8) bytes used for flags. The 94 | individual data bytes make up the len part of the formula and the two final 95 | NUL bytes make up the rest. 96 | 97 | Most of the time, the compressed output will be quite a bit smaller than the 98 | value returned here. We just use the maximum value so that we never have to 99 | worry about reallocating buffers as we go along. 100 | ******************************************************************************/ 101 | size_t prs_max_compressed_size(size_t len) { 102 | len += 2; 103 | return len + (len >> 3) + ((len & 0x07) ? 1 : 0); 104 | } 105 | 106 | static int set_bit(struct prs_comp_cxt *cxt, int value) { 107 | if(!cxt->bits_left--) { 108 | if(cxt->dst_pos >= cxt->dst_len) 109 | return -ENOSPC; 110 | 111 | /* Write out the flags to their position in the file, and set up the 112 | next flag position. */ 113 | *cxt->flag_ptr = cxt->flags; 114 | cxt->flag_ptr = cxt->dst + cxt->dst_pos++; 115 | cxt->bits_left = 7; 116 | } 117 | 118 | cxt->flags >>= 1; 119 | if(value) 120 | cxt->flags |= 0x80; 121 | 122 | return 0; 123 | } 124 | 125 | static int write_final_flags(struct prs_comp_cxt *cxt) { 126 | cxt->flags >>= cxt->bits_left; 127 | *cxt->flag_ptr = cxt->flags; 128 | 129 | return 0; 130 | } 131 | 132 | static int copy_literal(struct prs_comp_cxt *cxt) { 133 | if(cxt->dst_pos >= cxt->dst_len) 134 | return -ENOSPC; 135 | 136 | if(cxt->src_pos >= cxt->src_len) 137 | return -EPERM; 138 | 139 | *(cxt->dst + cxt->dst_pos++) = *(cxt->src + cxt->src_pos++); 140 | 141 | return 0; 142 | } 143 | 144 | static int write_literal(struct prs_comp_cxt *cxt, uint8_t val) { 145 | if(cxt->dst_pos >= cxt->dst_len) 146 | return -ENOSPC; 147 | 148 | *(cxt->dst + cxt->dst_pos++) = val; 149 | 150 | return 0; 151 | } 152 | 153 | static int write_eof(struct prs_comp_cxt *cxt) { 154 | int rv; 155 | 156 | /* Set the last two bits (01) and write the flags to the buffer. */ 157 | if((rv = set_bit(cxt, 0))) 158 | return rv; 159 | 160 | if((rv = set_bit(cxt, 1))) 161 | return rv; 162 | 163 | if((rv = write_final_flags(cxt))) 164 | return rv; 165 | 166 | /* Write the two NUL bytes that the file must end with. */ 167 | if((rv = write_literal(cxt, 0))) 168 | return rv; 169 | 170 | if((rv = write_literal(cxt, 0))) 171 | return rv; 172 | 173 | return 0; 174 | } 175 | 176 | static int match_length(struct prs_comp_cxt *cxt, const uint8_t *s2) { 177 | int len = 0; 178 | const uint8_t *s1 = cxt->src + cxt->src_pos, *end = cxt->src + cxt->src_len; 179 | 180 | while(s1 < end && *s1 == *s2) { 181 | ++len; 182 | ++s1; 183 | ++s2; 184 | } 185 | 186 | return len; 187 | } 188 | 189 | static int find_longest_match(struct prs_comp_cxt *cxt, struct prs_hash_cxt *hc, 190 | int *pos, int lazy) { 191 | uint8_t hash; 192 | const uint8_t *ent, *ent2; 193 | int mlen; 194 | int longest = 0; 195 | const uint8_t *longest_match = NULL; 196 | uintptr_t diff; 197 | 198 | if(cxt->src_pos >= cxt->src_len) 199 | return 0; 200 | 201 | /* Figure out where we're looking. */ 202 | hash = HASH_STR(cxt->src + cxt->src_pos); 203 | 204 | /* Is there anything in the table at that point? If not, we obviously don't 205 | have a match, so bail out now. */ 206 | if(!(ent = hc->ENT(hash))) { 207 | if(!lazy) 208 | ADD_TO_HASH(hc, cxt->src + cxt->src_pos, hash); 209 | return 0; 210 | } 211 | 212 | /* Make sure our initial match isn't outside the window. */ 213 | diff = (uintptr_t)ent - (uintptr_t)cxt->src; 214 | 215 | /* If we'd go outside the window, truncate the hash chain now. */ 216 | if(cxt->src_pos - diff > MAX_WINDOW) { 217 | hc->ENT(hash) = NULL; 218 | if(!lazy) 219 | ADD_TO_HASH(hc, cxt->src + cxt->src_pos, hash); 220 | return 0; 221 | } 222 | 223 | /* Ok, we have something in the hash table that matches the hash value. That 224 | doesn't necessarily mean we have a matching string though, of course. 225 | Follow the chain to see if we do, and find the longest match. */ 226 | while(ent) { 227 | if((mlen = match_length(cxt, ent))) { 228 | if(mlen > longest || mlen >= 256) { 229 | longest = mlen; 230 | longest_match = ent; 231 | } 232 | } 233 | 234 | /* Follow the chain, making sure not to exceed a difference of 8KiB. */ 235 | if((ent2 = hc->PREV(ent))) { 236 | diff = (uintptr_t)ent2 - (uintptr_t)cxt->src; 237 | 238 | /* If we'd go outside the window, truncate the hash chain now. */ 239 | if(cxt->src_pos - diff > MAX_WINDOW) { 240 | hc->PREV(ent) = NULL; 241 | ent2 = NULL; 242 | } 243 | } 244 | 245 | /* Follow the chain for the next pass. */ 246 | ent = ent2; 247 | } 248 | 249 | /* Did we find a match? */ 250 | if(longest) { 251 | diff = (uintptr_t)longest_match - (uintptr_t)cxt->src; 252 | *pos = ((int)diff - (int)cxt->src_pos); 253 | } 254 | 255 | /* Add our current string to the hash. */ 256 | if(!lazy) 257 | ADD_TO_HASH(hc, cxt->src + cxt->src_pos, hash); 258 | 259 | return longest; 260 | } 261 | 262 | static void add_intermediates(struct prs_comp_cxt *cxt, struct prs_hash_cxt *hc, 263 | int len) { 264 | int i; 265 | uint8_t hash; 266 | 267 | for(i = 1; i < len; ++i) { 268 | hash = HASH_STR(cxt->src + cxt->src_pos + i); 269 | ADD_TO_HASH(hc, cxt->src + cxt->src_pos + i, hash); 270 | } 271 | } 272 | 273 | /****************************************************************************** 274 | Archive a buffer of data into PRS format. 275 | 276 | This function archives a buffer of data into a format that can be 277 | "decompressed" by a program that handles PRS files. By archive, I mean that 278 | the file is not actually compressed -- in fact, the output will be larger 279 | than the input data (see prs_max_compressed_size for how big it'll actually 280 | be). 281 | 282 | There's really very little reason to ever use this, but it is here for you 283 | if you really want it. 284 | ******************************************************************************/ 285 | int prs_archive(const uint8_t *src, uint8_t **dst, size_t src_len) { 286 | struct prs_comp_cxt cxt; 287 | int rv; 288 | 289 | /* Check the input to make sure we've got valid source/destination pointers 290 | and something to do. */ 291 | if(!src || !dst) 292 | return -EFAULT; 293 | 294 | if(!src_len) 295 | return -EINVAL; 296 | 297 | /* Clear the context and fill in what we need to do our job. */ 298 | memset(&cxt, 0, sizeof(cxt)); 299 | cxt.src = src; 300 | cxt.src_len = src_len; 301 | cxt.dst_len = prs_max_compressed_size(src_len); 302 | 303 | /* Allocate our "compressed" buffer. */ 304 | if(!(cxt.dst = (uint8_t *)malloc(cxt.dst_len))) 305 | return -errno; 306 | 307 | cxt.flag_ptr = cxt.dst; 308 | 309 | /* Copy each byte, filling in the flags as we go along. */ 310 | while(src_len--) { 311 | /* Set the bit in the flag since we're just putting a literal in the 312 | output. */ 313 | if((rv = set_bit(&cxt, 1))) 314 | return rv; 315 | 316 | /* Copy the byte over. */ 317 | if((rv = copy_literal(&cxt))) 318 | return rv; 319 | } 320 | 321 | if((rv = write_eof(&cxt))) 322 | return rv; 323 | 324 | *dst = cxt.dst; 325 | return (int)cxt.dst_pos; 326 | } 327 | 328 | /****************************************************************************** 329 | Compress a buffer of data into PRS format. 330 | 331 | This function compresses a buffer of data with PRS compression. This 332 | function will never produce output larger than that of the prs_archive 333 | function, and will usually produce output that is significantly smaller. 334 | ******************************************************************************/ 335 | int prs_compress(const uint8_t *src, uint8_t **dst, size_t src_len) { 336 | struct prs_comp_cxt cxt; 337 | struct prs_hash_cxt *hcxt; 338 | int rv, mlen, mlen2; 339 | uint8_t tmp; 340 | int offset, offset2; 341 | int lazy_offs = 0; 342 | 343 | /* Check the input to make sure we've got valid source/destination pointers 344 | and something to do. */ 345 | if(!src || !dst) 346 | return -EFAULT; 347 | 348 | if(!src_len) 349 | return -EINVAL; 350 | 351 | /* Meh. Don't feel like dealing with it here, since it's not compressible 352 | at all anyway. */ 353 | if(src_len <= 3) 354 | return prs_archive(src, dst, src_len); 355 | 356 | /* Allocate the hash context. */ 357 | if(!(hcxt = (struct prs_hash_cxt *)malloc(sizeof(struct prs_hash_cxt)))) 358 | return -errno; 359 | 360 | /* Clear the contexts and fill in what we need to do our job. */ 361 | memset(&cxt, 0, sizeof(cxt)); 362 | memset(hcxt, 0, sizeof(struct prs_hash_cxt)); 363 | cxt.src = src; 364 | cxt.src_len = src_len; 365 | cxt.dst_len = prs_max_compressed_size(src_len); 366 | 367 | /* Allocate our "compressed" buffer. */ 368 | if(!(cxt.dst = (uint8_t *)malloc(cxt.dst_len))) { 369 | free(hcxt); 370 | return -errno; 371 | } 372 | 373 | cxt.flag_ptr = cxt.dst; 374 | 375 | /* Add the first two "strings" to the hash table. */ 376 | INIT_ADD_HASH(hcxt, src, tmp); 377 | INIT_ADD_HASH(hcxt, src + 1, tmp); 378 | 379 | /* Copy the first two bytes as literals... */ 380 | if((rv = set_bit(&cxt, 1))) 381 | goto out; 382 | 383 | if((rv = copy_literal(&cxt))) 384 | goto out; 385 | 386 | if((rv = set_bit(&cxt, 1))) 387 | goto out; 388 | 389 | if((rv = copy_literal(&cxt))) 390 | goto out; 391 | 392 | /* Process each byte. */ 393 | while(cxt.src_pos < cxt.src_len - 1) { 394 | /* Is there a match? */ 395 | if((mlen = find_longest_match(&cxt, hcxt, &offset, 0))) { 396 | cxt.src_pos++; 397 | mlen2 = find_longest_match(&cxt, hcxt, &offset2, 1); 398 | cxt.src_pos--; 399 | 400 | /* Did the "lazy match" produce something more compressed? */ 401 | if(mlen2 > mlen) { 402 | /* Check if it is a good idea to switch from a short match to a 403 | long one, if we would do that. */ 404 | if(mlen >= 2 && mlen <= 5 && offset2 < offset) { 405 | if(offset >= -256 && offset2 < -256) { 406 | if(mlen2 - mlen < 3) { 407 | goto blergh; 408 | } 409 | } 410 | } 411 | 412 | if((rv = set_bit(&cxt, 1))) 413 | goto out; 414 | 415 | if((rv = copy_literal(&cxt))) 416 | goto out; 417 | 418 | continue; 419 | } 420 | 421 | blergh: 422 | /* What kind of match did we find? */ 423 | if(mlen >= 2 && mlen <= 5 && offset >= -256) { 424 | /* Short match. */ 425 | if((rv = set_bit(&cxt, 0))) 426 | goto out; 427 | 428 | if((rv = set_bit(&cxt, 0))) 429 | goto out; 430 | 431 | if((rv = set_bit(&cxt, (mlen - 2) & 0x02))) 432 | goto out; 433 | 434 | if((rv = set_bit(&cxt, (mlen - 2) & 0x01))) 435 | goto out; 436 | 437 | if((rv = write_literal(&cxt, offset & 0xFF))) 438 | goto out; 439 | 440 | add_intermediates(&cxt, hcxt, mlen); 441 | cxt.src_pos += mlen; 442 | continue; 443 | } 444 | else if(mlen >= 3 && mlen <= 9) { 445 | /* Long match, short length. */ 446 | if((rv = set_bit(&cxt, 0))) 447 | goto out; 448 | 449 | if((rv = set_bit(&cxt, 1))) 450 | goto out; 451 | 452 | tmp = ((offset & 0x1f) << 3) | ((mlen - 2) & 0x07); 453 | if((rv = write_literal(&cxt, tmp))) 454 | goto out; 455 | 456 | tmp = offset >> 5; 457 | if((rv = write_literal(&cxt, tmp))) 458 | goto out; 459 | 460 | add_intermediates(&cxt, hcxt, mlen); 461 | cxt.src_pos += mlen; 462 | continue; 463 | } 464 | else if(mlen > 9) { 465 | /* Long match, long length. */ 466 | if(mlen > 256) 467 | mlen = 256; 468 | 469 | if((rv = set_bit(&cxt, 0))) 470 | goto out; 471 | 472 | if((rv = set_bit(&cxt, 1))) 473 | goto out; 474 | 475 | tmp = ((offset & 0x1f) << 3); 476 | if((rv = write_literal(&cxt, tmp))) 477 | goto out; 478 | 479 | tmp = offset >> 5; 480 | if((rv = write_literal(&cxt, tmp))) 481 | goto out; 482 | 483 | if((rv = write_literal(&cxt, mlen - 1))) 484 | goto out; 485 | 486 | add_intermediates(&cxt, hcxt, mlen); 487 | cxt.src_pos += mlen; 488 | continue; 489 | } 490 | } 491 | 492 | /* If we get here, we didn't find a suitable match, so just write the 493 | byte as a literal in the output. */ 494 | if((rv = set_bit(&cxt, 1))) 495 | goto out; 496 | 497 | /* Copy the byte over. */ 498 | if((rv = copy_literal(&cxt))) 499 | goto out; 500 | } 501 | 502 | /* If we still have a left over byte at the end, put it in as a literal. */ 503 | if(cxt.src_pos < cxt.src_len) { 504 | /* Set the bit in the flag since we're just putting a literal in the 505 | output. */ 506 | if((rv = set_bit(&cxt, 1))) 507 | goto out; 508 | 509 | /* Copy the byte over. */ 510 | if((rv = copy_literal(&cxt))) 511 | goto out; 512 | } 513 | 514 | if((rv = write_eof(&cxt))) 515 | goto out; 516 | 517 | free(hcxt); 518 | 519 | /* Resize the output (if realloc fails to resize it, then just use the 520 | unshortened buffer). */ 521 | if(!(*dst = realloc(cxt.dst, cxt.dst_pos))) 522 | *dst = cxt.dst; 523 | 524 | return (int)cxt.dst_pos; 525 | 526 | out: 527 | free(cxt.dst); 528 | free(hcxt); 529 | return rv; 530 | } 531 | -------------------------------------------------------------------------------- /prstool/prs-decomp.c: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Sylverant PSO Server. 3 | 4 | Copyright (C) 2014 Lawrence Sebald 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License version 3 8 | as published by the Free Software Foundation. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | struct prs_dec_cxt { 26 | uint8_t flags; 27 | 28 | int bit_pos; 29 | const uint8_t *src; 30 | uint8_t *dst; 31 | void *udata; 32 | 33 | size_t src_len; 34 | size_t dst_len; 35 | size_t src_pos; 36 | size_t dst_pos; 37 | 38 | int (*copy_byte)(struct prs_dec_cxt *cxt); 39 | int (*offset_copy)(struct prs_dec_cxt *cxt, int offset); 40 | int (*fetch_bit)(struct prs_dec_cxt *cxt); 41 | int (*fetch_byte)(struct prs_dec_cxt *cxt); 42 | int (*fetch_short)(struct prs_dec_cxt *cxt); 43 | }; 44 | 45 | /****************************************************************************** 46 | PRS Decompression Function 47 | 48 | This function does the real work of decompressing whatever you throw at it. 49 | It uses a bunch of callbacks in the context provided to read the compressed 50 | data and do whatever is needed with it. 51 | ******************************************************************************/ 52 | static int do_decompress(struct prs_dec_cxt *cxt) { 53 | int flag, size; 54 | int32_t offset; 55 | 56 | for(;;) { 57 | /* Read the flag bit for this pass. */ 58 | if((flag = cxt->fetch_bit(cxt)) < 0) 59 | return flag; 60 | 61 | /* Flag bit = 1 -> Simple byte copy from src to dst. */ 62 | if(flag) { 63 | if((flag = cxt->copy_byte(cxt)) < 0) 64 | return flag; 65 | 66 | continue; 67 | } 68 | 69 | /* The flag starts with a zero, so it isn't just a simple byte copy. 70 | Read the next bit to see what we have left to do. */ 71 | if((flag = cxt->fetch_bit(cxt)) < 0) 72 | return flag; 73 | 74 | /* Flag bit = 1 -> Either long copy or end of file. */ 75 | if(flag) { 76 | if((offset = cxt->fetch_short(cxt)) < 0) 77 | return offset; 78 | 79 | /* Two zero bytes implies that this is the end of the file. Return 80 | the length of the file. */ 81 | if(!offset) 82 | return (int)cxt->dst_pos; 83 | 84 | /* Do we need to read a size byte, or is it encoded in what we 85 | already got? */ 86 | size = offset & 0x0007; 87 | offset >>= 3; 88 | 89 | if(!size) { 90 | if((size = cxt->fetch_byte(cxt)) < 0) 91 | return size; 92 | 93 | ++size; 94 | } 95 | else { 96 | size += 2; 97 | } 98 | 99 | offset |= 0xFFFFE000; 100 | } 101 | /* Flag bit = 0 -> short copy. */ 102 | else { 103 | /* Fetch the two bits needed to determine the size. */ 104 | if((flag = cxt->fetch_bit(cxt)) < 0) 105 | return flag; 106 | 107 | if((size = cxt->fetch_bit(cxt)) < 0) 108 | return size; 109 | 110 | size = (size | (flag << 1)) + 2; 111 | 112 | /* Fetch the offset byte. */ 113 | if((offset = cxt->fetch_byte(cxt)) < 0) 114 | return offset; 115 | 116 | offset |= 0xFFFFFF00; 117 | } 118 | 119 | /* Copy the data. */ 120 | while(size--) { 121 | if((flag = cxt->offset_copy(cxt, offset)) < 0) 122 | return flag; 123 | } 124 | } 125 | } 126 | 127 | /****************************************************************************** 128 | Internal utility functions. 129 | 130 | Depending on how the compressed data is to be obtained, different sets of 131 | these functions will be used. 132 | ******************************************************************************/ 133 | static int fetch_bit(struct prs_dec_cxt *cxt) { 134 | int rv; 135 | 136 | /* Did we finish with a full byte last time we were in here? */ 137 | if(!cxt->bit_pos) { 138 | /* Make sure we won't fall off the end of the file by reading the byte 139 | from it. */ 140 | if(cxt->src_pos >= cxt->src_len) 141 | return -EBADMSG; 142 | 143 | cxt->flags = *cxt->src++; 144 | ++cxt->src_pos; 145 | cxt->bit_pos = 8; 146 | } 147 | 148 | /* Fetch the bit and shift it off the end of the byte. */ 149 | rv = cxt->flags & 1; 150 | cxt->flags >>= 1; 151 | --cxt->bit_pos; 152 | 153 | return rv; 154 | } 155 | 156 | static int copy_byte(struct prs_dec_cxt *cxt) { 157 | /* Make sure we still have data left in the input buffer. */ 158 | if(cxt->src_pos >= cxt->src_len) 159 | return -EBADMSG; 160 | 161 | /* Make sure we have space left in the destination buffer. */ 162 | if(cxt->dst_pos >= cxt->dst_len) 163 | return -ENOSPC; 164 | 165 | /* Copy the byte and increment all the counters/pointers. */ 166 | *cxt->dst++ = *cxt->src++; 167 | ++cxt->src_pos; 168 | ++cxt->dst_pos; 169 | 170 | return 0; 171 | } 172 | 173 | static int fetch_byte(struct prs_dec_cxt *cxt) { 174 | uint8_t rv; 175 | 176 | /* Make sure we still have data left in the input buffer. */ 177 | if(cxt->src_pos >= cxt->src_len) 178 | return -EBADMSG; 179 | 180 | /* Read the byte from the buffer. */ 181 | rv = *cxt->src++; 182 | ++cxt->src_pos; 183 | 184 | return (int)rv; 185 | } 186 | 187 | static int fetch_short(struct prs_dec_cxt *cxt) { 188 | uint16_t rv; 189 | 190 | /* Make sure we still have data left in the input buffer. */ 191 | if(cxt->src_pos + 1 >= cxt->src_len) 192 | return -EBADMSG; 193 | 194 | /* Read the two bytes from the buffer. */ 195 | rv = *cxt->src++; 196 | ++cxt->src_pos; 197 | rv |= *cxt->src++ << 8; 198 | ++cxt->src_pos; 199 | 200 | return (int)rv; 201 | } 202 | 203 | static int offset_copy(struct prs_dec_cxt *cxt, int offset) { 204 | int tmp = (int)cxt->dst_pos + offset; 205 | 206 | /* Make sure the offset is valid. */ 207 | if(tmp < 0) 208 | return -EBADMSG; 209 | 210 | /* Make sure we have space left in the destination buffer. */ 211 | if(cxt->dst_pos >= cxt->dst_len) 212 | return -ENOSPC; 213 | 214 | /* Copy the byte and increment all the counters/pointers. */ 215 | *cxt->dst++ = *(cxt->dst + offset); 216 | ++cxt->dst_pos; 217 | 218 | return 0; 219 | } 220 | 221 | static int nocopy_byte(struct prs_dec_cxt *cxt) { 222 | /* Make sure we still have data left in the input buffer. */ 223 | if(cxt->src_pos >= cxt->src_len) 224 | return -EBADMSG; 225 | 226 | /* Increment the counters/pointers. */ 227 | ++cxt->src; 228 | ++cxt->src_pos; 229 | ++cxt->dst_pos; 230 | 231 | return 0; 232 | } 233 | 234 | static int offset_nocopy(struct prs_dec_cxt *cxt, int offset) { 235 | int tmp = (int)cxt->dst_pos + offset; 236 | 237 | /* Make sure the offset is valid. */ 238 | if(tmp < 0) 239 | return -EBADMSG; 240 | 241 | /* Increment the counter... */ 242 | ++cxt->dst_pos; 243 | 244 | return 0; 245 | } 246 | 247 | static int file_bit(struct prs_dec_cxt *cxt) { 248 | int rv; 249 | 250 | /* Did we finish with a full byte last time we were in here? */ 251 | if(!cxt->bit_pos) { 252 | /* Make sure we won't fall off the end of the file by reading the byte 253 | from it. */ 254 | if(cxt->src_pos >= cxt->src_len) 255 | return -EBADMSG; 256 | 257 | /* Read the next byte from the file. */ 258 | if((rv = fgetc((FILE *)cxt->udata)) == EOF) { 259 | if(ferror((FILE *)cxt->udata)) 260 | return -errno; 261 | return -EBADMSG; 262 | } 263 | 264 | cxt->flags = (uint8_t)rv; 265 | ++cxt->src_pos; 266 | cxt->bit_pos = 8; 267 | } 268 | 269 | /* Fetch the bit and shift it off the end of the byte. */ 270 | rv = cxt->flags & 1; 271 | cxt->flags >>= 1; 272 | --cxt->bit_pos; 273 | 274 | return rv; 275 | } 276 | 277 | static int copy_fbyte(struct prs_dec_cxt *cxt) { 278 | int b; 279 | void *tmp; 280 | 281 | /* Make sure we still have data left in the input file. */ 282 | if(cxt->src_pos >= cxt->src_len) 283 | return -EBADMSG; 284 | 285 | /* Make sure we have space left in the destination buffer. */ 286 | if(cxt->dst_pos >= cxt->dst_len) { 287 | if(!(tmp = realloc(cxt->dst, cxt->dst_len * 2))) 288 | return -errno; 289 | 290 | cxt->dst = (uint8_t *)tmp; 291 | cxt->dst_len *= 2; 292 | } 293 | 294 | /* Read the next byte from the file. */ 295 | if((b = fgetc((FILE *)cxt->udata)) == EOF) { 296 | if(ferror((FILE *)cxt->udata)) 297 | return -errno; 298 | return -EBADMSG; 299 | } 300 | 301 | /* Copy the byte and increment all the counters/pointers. */ 302 | *(cxt->dst + cxt->dst_pos) = (uint8_t)b; 303 | ++cxt->src_pos; 304 | ++cxt->dst_pos; 305 | 306 | return 0; 307 | } 308 | 309 | static int file_byte(struct prs_dec_cxt *cxt) { 310 | int rv; 311 | 312 | /* Make sure we still have data left in the input file. */ 313 | if(cxt->src_pos >= cxt->src_len) 314 | return -EBADMSG; 315 | 316 | /* Read the next byte from the file. */ 317 | if((rv = fgetc((FILE *)cxt->udata)) == EOF) { 318 | if(ferror((FILE *)cxt->udata)) 319 | return -errno; 320 | return -EBADMSG; 321 | } 322 | 323 | ++cxt->src_pos; 324 | 325 | return (int)rv; 326 | } 327 | 328 | static int file_short(struct prs_dec_cxt *cxt) { 329 | uint16_t rv; 330 | uint8_t b[2]; 331 | 332 | /* Make sure we still have data left in the input file. */ 333 | if(cxt->src_pos + 1 >= cxt->src_len) 334 | return -EBADMSG; 335 | 336 | /* Read the next two bytes from the file. */ 337 | if(fread(b, 1, 2, (FILE *)cxt->udata) != 2) 338 | return -errno; 339 | 340 | /* Combine the bytes into the 16-bit value we're looking for. */ 341 | rv = b[0] | (b[1] << 8); 342 | cxt->src_pos += 2; 343 | 344 | return (int)rv; 345 | } 346 | 347 | static int offset_copy_alloc(struct prs_dec_cxt *cxt, int offset) { 348 | int tmp = (int)cxt->dst_pos + offset; 349 | void *tmp2; 350 | 351 | /* Make sure the offset is valid. */ 352 | if(tmp < 0) 353 | return -EBADMSG; 354 | 355 | /* Make sure we have space left in the destination buffer. */ 356 | if(cxt->dst_pos >= cxt->dst_len) { 357 | if(!(tmp2 = realloc(cxt->dst, cxt->dst_len * 2))) 358 | return -errno; 359 | 360 | cxt->dst = (uint8_t *)tmp2; 361 | cxt->dst_len *= 2; 362 | } 363 | 364 | /* Copy the byte and increment all the counters/pointers. */ 365 | *(cxt->dst + cxt->dst_pos) = *(cxt->dst + cxt->dst_pos + offset); 366 | ++cxt->dst_pos; 367 | 368 | return 0; 369 | } 370 | 371 | static int copy_abyte(struct prs_dec_cxt *cxt) { 372 | void *tmp; 373 | 374 | /* Make sure we still have data left in the input file. */ 375 | if(cxt->src_pos >= cxt->src_len) 376 | return -EBADMSG; 377 | 378 | /* Make sure we have space left in the destination buffer. */ 379 | if(cxt->dst_pos >= cxt->dst_len) { 380 | if(!(tmp = realloc(cxt->dst, cxt->dst_len * 2))) 381 | return -errno; 382 | 383 | cxt->dst = (uint8_t *)tmp; 384 | cxt->dst_len *= 2; 385 | } 386 | 387 | /* Copy the byte and increment all the counters/pointers. */ 388 | *(cxt->dst + cxt->dst_pos) = *cxt->src++; 389 | ++cxt->src_pos; 390 | ++cxt->dst_pos; 391 | 392 | return 0; 393 | } 394 | 395 | /****************************************************************************** 396 | Public interface functions 397 | 398 | These functions are the public functions used to decompress PRS-compressed 399 | data. There are a variety of functions provided here for different purposes. 400 | 401 | prs_decompress_buf: 402 | Decompress data from a memory buffer into another memory buffer, 403 | allocating space as needed for the destination buffer. It is the 404 | caller's responsibility to free the decompressed memory buffer when it 405 | is no longer needed. 406 | 407 | prs_decompress_buf2: 408 | Decompress data from a memory buffer into another (pre-allocated) memory 409 | buffer. If the buffer is not large enough, an error (-ENOSPC) will be 410 | returned. 411 | 412 | prs_decompress_size: 413 | Determine the decompressed size of a block of memory containing PRS- 414 | compressed data. 415 | 416 | prs_decompress_file: 417 | Open the specified PRS-compressed file and decompress it into a new 418 | memory buffer. It is the caller's responsibility to free the 419 | decompressed memory buffer when it is no longer needed. 420 | 421 | All of these functions will return the size of the decompressed data on 422 | success, or a negative error code (from errno) on error. Common error codes 423 | include the following: 424 | -EBADMSG: Invalid compressed data encountered while decoding. 425 | -EINVAL: Invalid source length (0) given. 426 | -EFAULT: NULL pointer passed in. 427 | 428 | In addition, prs_decompress_file may return many other error codes related 429 | to reading from a file. prs_decompress_file and prs_decompress_buf may also 430 | return errors related to memory allocation. 431 | ******************************************************************************/ 432 | int prs_decompress_buf(const uint8_t *src, uint8_t **dst, size_t src_len) { 433 | struct prs_dec_cxt cxt = 434 | { 0, 0, src, NULL, NULL, src_len, src_len * 2, 0, 0, ©_abyte, 435 | &offset_copy_alloc, &fetch_bit, &fetch_byte, &fetch_short }; 436 | int rv; 437 | 438 | if(!src || !dst) 439 | return -EFAULT; 440 | 441 | if(!src_len) 442 | return -EINVAL; 443 | 444 | /* The minimum length of a PRS compressed file (if you were to "compress" a 445 | zero-byte file) is 3 bytes. If we don't have that, then bail out now. */ 446 | if(cxt.src_len < 3) 447 | return -EBADMSG; 448 | 449 | /* Allocate some space for the output. Start with two times the length of 450 | the input (we will resize this later, as needed). */ 451 | if(!(cxt.dst = (uint8_t *)malloc(cxt.dst_len))) 452 | return -errno; 453 | 454 | /* Do the decompression. */ 455 | if((rv = do_decompress(&cxt)) < 0) { 456 | free(cxt.dst); 457 | return rv; 458 | } 459 | 460 | /* Resize the output (if realloc fails to resize it, then just use the 461 | unshortened buffer). */ 462 | if(!(*dst = realloc(cxt.dst, rv))) 463 | *dst = cxt.dst; 464 | 465 | return rv; 466 | } 467 | 468 | int prs_decompress_buf2(const uint8_t *src, uint8_t *dst, size_t src_len, 469 | size_t dst_len) { 470 | struct prs_dec_cxt cxt = 471 | { 0, 0, src, dst, NULL, src_len, dst_len, 0, 0, ©_byte, 472 | &offset_copy, &fetch_bit, &fetch_byte, &fetch_short }; 473 | 474 | if(!src || !dst) 475 | return -EFAULT; 476 | 477 | if(!src_len || !dst_len) 478 | return -EINVAL; 479 | 480 | /* The minimum length of a PRS compressed file (if you were to "compress" a 481 | zero-byte file) is 3 bytes. If we don't have that, then bail out now. */ 482 | if(cxt.src_len < 3) 483 | return -EBADMSG; 484 | 485 | return do_decompress(&cxt); 486 | } 487 | 488 | int prs_decompress_size(const uint8_t *src, size_t src_len) { 489 | struct prs_dec_cxt cxt = 490 | { 0, 0, src, NULL, NULL, src_len, SIZE_MAX, 0, 0, &nocopy_byte, 491 | &offset_nocopy, &fetch_bit, &fetch_byte, &fetch_short }; 492 | 493 | if(!src) 494 | return -EFAULT; 495 | 496 | if(!src_len) 497 | return -EINVAL; 498 | 499 | /* The minimum length of a PRS compressed file (if you were to "compress" a 500 | zero-byte file) is 3 bytes. If we don't have that, then bail out now. */ 501 | if(cxt.src_len < 3) 502 | return -EBADMSG; 503 | 504 | return do_decompress(&cxt); 505 | } 506 | 507 | int prs_decompress_file(const char *fn, uint8_t **dst) { 508 | struct prs_dec_cxt cxt = 509 | { 0, 0, NULL, NULL, NULL, 0, 0, 0, 0, 510 | ©_fbyte, &offset_copy_alloc, &file_bit, &file_byte, &file_short }; 511 | long len; 512 | int rv; 513 | FILE *fp; 514 | 515 | if(!fn || !dst) 516 | return -EFAULT; 517 | 518 | if(!(fp = fopen(fn, "rb"))) 519 | return -errno; 520 | 521 | cxt.udata = fp; 522 | 523 | /* Figure out the length of the file. */ 524 | if(fseek(fp, 0, SEEK_END)) { 525 | fclose(fp); 526 | return -errno; 527 | } 528 | 529 | if((len = ftell(fp)) < 0) { 530 | fclose(fp); 531 | return -errno; 532 | } 533 | 534 | if(fseek(fp, 0, SEEK_SET)) { 535 | fclose(fp); 536 | return -errno; 537 | } 538 | 539 | cxt.src_len = (size_t)len; 540 | cxt.dst_len = cxt.src_len * 2; 541 | 542 | /* The minimum length of a PRS compressed file (if you were to "compress" a 543 | zero-byte file) is 3 bytes. If we don't have that, then bail out now. */ 544 | if(cxt.src_len < 3) { 545 | fclose(fp); 546 | return -EBADMSG; 547 | } 548 | 549 | /* Allocate some space for the output. Start with two times the length of 550 | the input (we will resize this later, as needed). */ 551 | if(!(cxt.dst = (uint8_t *)malloc(cxt.dst_len))) { 552 | fclose(fp); 553 | return -errno; 554 | } 555 | 556 | /* Do the decompression. */ 557 | if((rv = do_decompress(&cxt)) < 0) { 558 | free(cxt.dst); 559 | fclose(fp); 560 | return rv; 561 | } 562 | 563 | fclose(fp); 564 | 565 | /* Resize the output (if realloc fails to resize it, then just use the 566 | unshortened buffer). */ 567 | if(!(*dst = realloc(cxt.dst, rv))) 568 | *dst = cxt.dst; 569 | 570 | return rv; 571 | } 572 | -------------------------------------------------------------------------------- /prstool/prs.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Sylverant PSO Server. 3 | 4 | Copyright (C) 2014 Lawrence Sebald 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License version 3 8 | as published by the Free Software Foundation. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #ifndef SYLVERANT__PRS_H 20 | #define SYLVERANT__PRS_H 21 | 22 | #include 23 | #include 24 | 25 | /* Compress a buffer with PRS compression. 26 | 27 | This function compresses the data in the src buffer into a new buffer. This 28 | function will never produce output larger than that of the prs_archive 29 | function, and will usually beat that function rather significantly. 30 | 31 | In testing, the compressed output of this function actually beats Sega's own 32 | compression slightly (by 100 bytes or so on an uncompressed version of the 33 | ItemPMT.prs from PSO Episode I & II Plus). 34 | 35 | It is the caller's responsibility to free *dst when it is no longer in use. 36 | 37 | Returns a negative value on failure (specifically something from . 38 | Returns the size of the compressed output on success. 39 | */ 40 | extern int prs_compress(const uint8_t *src, uint8_t **dst, size_t src_len); 41 | 42 | /* Archive a buffer in PRS format. 43 | 44 | This function archives the data in the src buffer into a new buffer. This 45 | function will always produce output that is larger in size than the input 46 | data (it does not actually compress the output. There's probably no good 47 | reason to ever use this, but it is here if you want it for some reason. 48 | 49 | All the notes about parameters and return values from prs_compress also apply 50 | to this function. The size of the output from this function will be equal to 51 | the return value of prs_max_compressed_size when called on the same length. 52 | */ 53 | extern int prs_archive(const uint8_t *src, uint8_t **dst, size_t src_len); 54 | 55 | /* Return the maximum size of archiving a buffer in PRS format. 56 | 57 | This function returns the size that prs_archive will spit out. This is used 58 | internally to allocate memory for prs_archive and prs_compress and probably 59 | has little utility outside of that. 60 | */ 61 | extern size_t prs_max_compressed_size(size_t len); 62 | 63 | /* Decompress a PRS archive from a file. 64 | 65 | This function opens the file specified and decompresses the data from the 66 | file into a newly allocated memory buffer. 67 | 68 | It is the caller's responsibility to free *dst when it is no longer in use. 69 | 70 | Returns a negative value on failure (specifically something from ). 71 | Returns the size of the decompressed output on success. 72 | */ 73 | extern int prs_decompress_file(const char *fn, uint8_t **dst); 74 | 75 | /* Decompress PRS-compressed data from a memory buffer. 76 | 77 | This function decompresses PRS-compressed data from the src buffer into a 78 | newly allocated memory buffer. 79 | 80 | It is the caller's responsibility to free *dst when it is no longer in use. 81 | 82 | Returns a negative value on failure (specifically something from ). 83 | Returns the size of the decompressed output on success. 84 | */ 85 | extern int prs_decompress_buf(const uint8_t *src, uint8_t **dst, 86 | size_t src_len); 87 | 88 | /* Decompress PRS-compressed data from a memory buffer into a previously 89 | allocated memory buffer. 90 | 91 | This function decompresses PRS-compressed data from the src buffer into the 92 | previously allocated allocated memory buffer dst. You must have already 93 | allocated the buffer at dst, and it should be at least the size returned by 94 | prs_decompress_size on the compressed input (otherwise, you will get an error 95 | back from the function). 96 | 97 | Returns a negative value on failure (specifically something from ). 98 | Returns the size of the decompressed output on success. 99 | */ 100 | extern int prs_decompress_buf2(const uint8_t *src, uint8_t *dst, size_t src_len, 101 | size_t dst_len); 102 | 103 | /* Determine the size that the PRS-compressed data in a buffer will expand to. 104 | 105 | This function essentially decompresses the PRS-compressed data from the src 106 | buffer without writing any output. By doing this, it determines the actual 107 | size of the decompressed output and returns it. 108 | 109 | Returns a negative value on failure (specifically something from ). 110 | Returns the size of the decompressed output on success. 111 | */ 112 | extern int prs_decompress_size(const uint8_t *src, size_t src_len); 113 | 114 | #endif /* !SYLVERANT__PRS_H */ 115 | -------------------------------------------------------------------------------- /prstool/prstool.c: -------------------------------------------------------------------------------- 1 | /* 2 | Sylverant PSO Tools 3 | PRS Archive Compression/Decompression Tool 4 | Copyright (C) 2014 Lawrence Sebald 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License version 3 8 | as published by the Free Software Foundation. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "prs.h" 25 | 26 | static const char *in_file, *out_file; 27 | static int operation = 0; 28 | 29 | /* Print information about this program to stdout. */ 30 | static void print_program_info(void) { 31 | #if defined(VERSION) 32 | printf("Sylverant PRS Tool version %s\n", VERSION); 33 | #elif defined(SVN_REVISION) 34 | printf("Sylverant PRS Tool SVN revision: %s\n", SVN_REVISION); 35 | #else 36 | printf("Sylverant PRS Tool\n"); 37 | #endif 38 | printf("Copyright (C) 2014 Lawrence Sebald\n\n"); 39 | printf("This program is free software: you can redistribute it and/or\n" 40 | "modify it under the terms of the GNU Affero General Public\n" 41 | "License version 3 as published by the Free Software Foundation.\n\n" 42 | "This program is distributed in the hope that it will be useful,\n" 43 | "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" 44 | "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" 45 | "GNU General Public License for more details.\n\n" 46 | "You should have received a copy of the GNU Affero General Public\n" 47 | "License along with this program. If not, see " 48 | ".\n"); 49 | } 50 | 51 | /* Print help to the user to stdout. */ 52 | static void print_help(const char *bin) { 53 | printf("Usage: %s arguments [input_file] [output_file]\n" 54 | "-----------------------------------------------------------------\n" 55 | "--help Print this help and exit\n" 56 | "--version Print version info and exit\n" 57 | "-x Decompress input_file into output_file\n" 58 | "-c Compress input_file into output_file\n", bin); 59 | } 60 | 61 | /* Parse any command-line arguments passed in. */ 62 | static void parse_command_line(int argc, char *argv[]) { 63 | int i; 64 | 65 | /* See if we have any of the boring options... */ 66 | if(argc == 2) { 67 | if(!strcmp(argv[1], "--version")) { 68 | print_program_info(); 69 | exit(EXIT_SUCCESS); 70 | } 71 | else if(!strcmp(argv[1], "--help")) { 72 | print_help(argv[0]); 73 | exit(EXIT_SUCCESS); 74 | } 75 | } 76 | 77 | /* Otherwise, we need exactly 4 arguments, so bail if we have more. */ 78 | if(argc != 4) { 79 | print_help(argv[0]); 80 | exit(EXIT_FAILURE); 81 | } 82 | 83 | /* Figure out what they're asking us to do. */ 84 | if(!strcmp(argv[1], "-x")) { 85 | operation = 2; 86 | } 87 | else if(!strcmp(argv[1], "-c")) { 88 | operation = 1; 89 | } 90 | else { 91 | printf("Illegal command line argument: %s\n", argv[i]); 92 | print_help(argv[0]); 93 | exit(EXIT_FAILURE); 94 | } 95 | 96 | /* Save the files we'll be working with. */ 97 | in_file = argv[2]; 98 | out_file = argv[3]; 99 | } 100 | 101 | static uint8_t *read_input(long *len) { 102 | FILE *ifp; 103 | uint8_t *rv; 104 | 105 | if(!(ifp = fopen(in_file, "rb"))) { 106 | perror(""); 107 | exit(EXIT_FAILURE); 108 | } 109 | 110 | /* Figure out the length of the file. */ 111 | if(fseek(ifp, 0, SEEK_END)) { 112 | perror(""); 113 | exit(EXIT_FAILURE); 114 | } 115 | 116 | if((*len = ftell(ifp)) < 0) { 117 | perror(""); 118 | exit(EXIT_FAILURE); 119 | } 120 | 121 | if(fseek(ifp, 0, SEEK_SET)) { 122 | perror(""); 123 | exit(EXIT_FAILURE); 124 | } 125 | 126 | /* Allocate a buffer. */ 127 | if(!(rv = malloc(*len))) { 128 | perror(""); 129 | exit(EXIT_FAILURE); 130 | } 131 | 132 | /* Read in the file. */ 133 | if(fread(rv, 1, *len, ifp) != *len) { 134 | perror(""); 135 | exit(EXIT_FAILURE); 136 | } 137 | 138 | /* Clean up. */ 139 | fclose(ifp); 140 | return rv; 141 | } 142 | 143 | static void write_output(int len, const uint8_t *buf) { 144 | FILE *ofp; 145 | 146 | if(!(ofp = fopen(out_file, "wb"))) { 147 | perror(""); 148 | exit(EXIT_FAILURE); 149 | } 150 | 151 | if(fwrite(buf, 1, len, ofp) != len) { 152 | perror(""); 153 | exit(EXIT_FAILURE); 154 | } 155 | 156 | fclose(ofp); 157 | } 158 | 159 | static void decompress(void) { 160 | uint8_t *buf; 161 | int len; 162 | 163 | /* Decompress the file. */ 164 | if((len = prs_decompress_file(in_file, &buf)) < 0) { 165 | fprintf(stderr, "decompress: %s\n", strerror(-len)); 166 | exit(EXIT_FAILURE); 167 | } 168 | 169 | /* Write it out. */ 170 | write_output(len, buf); 171 | 172 | /* Clean up. */ 173 | free(buf); 174 | } 175 | 176 | static void compress(void) { 177 | long unc_len; 178 | uint8_t *unc, *cmp; 179 | int cmp_len; 180 | 181 | /* Read the file in */ 182 | unc = read_input(&unc_len); 183 | 184 | /* Compress it. */ 185 | if((cmp_len = prs_compress(unc, &cmp, (size_t)unc_len)) < 0) { 186 | fprintf(stderr, "compress: %s\n", strerror(-cmp_len)); 187 | exit(EXIT_FAILURE); 188 | } 189 | 190 | /* Write it out to the output file. */ 191 | write_output(cmp_len, cmp); 192 | 193 | /* Clean up. */ 194 | free(cmp); 195 | free(unc); 196 | } 197 | 198 | int main(int argc, char *argv[]) { 199 | /* Parse the command line... */ 200 | parse_command_line(argc, argv); 201 | 202 | if(operation == 1) 203 | compress(); 204 | else 205 | decompress(); 206 | 207 | return 0; 208 | } 209 | -------------------------------------------------------------------------------- /pso_artool/Makefile: -------------------------------------------------------------------------------- 1 | # *nix Makefile. 2 | # Should build with any standardish C99-supporting compiler. 3 | 4 | SRCS = artool.c prs.c prsd.c afs.c gsl.c 5 | LIBS = -lpsoarchive 6 | TARGET = pso_artool 7 | INSTDIR ?= /usr/local 8 | CFLAGS ?= -Wall -Wextra -I/usr/local/include 9 | LDFLAGS ?= -Wall -Wextra -L/usr/local/lib 10 | 11 | # Nothing should have to change below here... 12 | 13 | OBJS = $(patsubst %.c,%.o,$(SRCS)) 14 | 15 | all: $(TARGET) 16 | 17 | %.o: %.c 18 | $(CC) $(CFLAGS) -c $< -o $@ 19 | 20 | $(TARGET): $(OBJS) 21 | $(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS) $(LIBS) 22 | 23 | .PHONY: clean install 24 | 25 | clean: 26 | -rm -fr $(TARGET) $(OBJS) *.dSYM 27 | 28 | install: $(TARGET) 29 | install -d '$(INSTDIR)/bin' 30 | install -m 755 $(TARGET) '$(INSTDIR)/bin' 31 | -------------------------------------------------------------------------------- /pso_artool/Makefile.mingw: -------------------------------------------------------------------------------- 1 | # MinGW-w64 Makefile. 2 | # Would probably work with plain ol' MinGW too, but I haven't tried. 3 | 4 | CC = i686-w64-mingw32-gcc 5 | SRCS = artool.c prs.c prsd.c afs.c gsl.c windows_compat.c 6 | LIBS = -lpsoarchive 7 | TARGET = pso_artool.exe 8 | CFLAGS ?= -Wall -Wextra 9 | LDFLAGS ?= -Wall -Wextra 10 | 11 | # Nothing should have to change below here... 12 | 13 | OBJS = $(patsubst %.c,%.o,$(SRCS)) 14 | 15 | all: $(TARGET) 16 | 17 | %.o: %.c 18 | $(CC) $(CFLAGS) -c $< -o $@ 19 | 20 | $(TARGET): $(OBJS) 21 | $(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS) $(LIBS) 22 | 23 | .PHONY: clean install 24 | 25 | clean: 26 | -rm -fr $(TARGET) $(OBJS) *.dSYM 27 | -------------------------------------------------------------------------------- /pso_artool/afs.c: -------------------------------------------------------------------------------- 1 | /* 2 | Sylverant PSO Tools 3 | PSO Archive Tool 4 | Copyright (C) 2014, 2016 Lawrence Sebald 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License version 3 8 | as published by the Free Software Foundation. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | 27 | #ifndef _WIN32 28 | #include 29 | #include 30 | #include 31 | #endif 32 | 33 | #include 34 | 35 | static int make_fntab = 0; 36 | 37 | #ifdef _WIN32 38 | #include "windows_compat.h" 39 | #endif 40 | 41 | static int digits(uint32_t n) { 42 | int r = 1; 43 | while(n /= 10) ++r; 44 | return r; 45 | } 46 | 47 | static int afs_list(const char *fn) { 48 | pso_afs_read_t *cxt; 49 | pso_error_t err; 50 | uint32_t cnt, i; 51 | int dg; 52 | ssize_t sz; 53 | char afn[64]; 54 | 55 | if(!(cxt = pso_afs_read_open(fn, make_fntab, &err))) { 56 | fprintf(stderr, "Cannot open archive %s: %s\n", fn, pso_strerror(err)); 57 | return EXIT_FAILURE; 58 | } 59 | 60 | /* Loop through each file... */ 61 | cnt = pso_afs_file_count(cxt); 62 | dg = digits(cnt); 63 | 64 | for(i = 0; i < cnt; ++i) { 65 | sz = pso_afs_file_size(cxt, i); 66 | pso_afs_file_name(cxt, i, afn, 64); 67 | 68 | #ifndef _WIN32 69 | printf("File %*" PRIu32 ": '%s' size: %" PRIu32 "\n", dg, i, afn, 70 | (uint32_t)sz); 71 | #else 72 | printf("File %*I32u: '%s' size: %I32u\n", dg, i, afn, (uint32_t)sz); 73 | #endif 74 | } 75 | 76 | pso_afs_read_close(cxt); 77 | return 0; 78 | } 79 | 80 | static int afs_extract(const char *fn) { 81 | pso_afs_read_t *cxt; 82 | pso_error_t err; 83 | uint32_t cnt, i; 84 | ssize_t sz; 85 | char afn[64]; 86 | FILE *fp; 87 | uint8_t *buf; 88 | struct stat st; 89 | struct timeval tms[2]; 90 | 91 | if(!(cxt = pso_afs_read_open(fn, make_fntab, &err))) { 92 | fprintf(stderr, "Cannot open archive %s: %s\n", fn, pso_strerror(err)); 93 | return EXIT_FAILURE; 94 | } 95 | 96 | /* Loop through each file... */ 97 | cnt = pso_afs_file_count(cxt); 98 | 99 | for(i = 0; i < cnt; ++i) { 100 | if((sz = pso_afs_file_size(cxt, i)) < 0) { 101 | fprintf(stderr, "Cannot extract file: %s\n", pso_strerror(sz)); 102 | pso_afs_read_close(cxt); 103 | return EXIT_FAILURE; 104 | } 105 | 106 | if(pso_afs_file_name(cxt, i, afn, 64) < 0) { 107 | fprintf(stderr, "Cannot extract file: %s\n", pso_strerror(sz)); 108 | pso_afs_read_close(cxt); 109 | return EXIT_FAILURE; 110 | } 111 | 112 | if(!(buf = malloc(sz))) { 113 | perror("Cannot extract file"); 114 | pso_afs_read_close(cxt); 115 | return EXIT_FAILURE; 116 | } 117 | 118 | if((err = pso_afs_file_read(cxt, i, buf, (size_t)sz)) < 0) { 119 | fprintf(stderr, "Cannot extract file: %s\n", pso_strerror(sz)); 120 | free(buf); 121 | pso_afs_read_close(cxt); 122 | return EXIT_FAILURE; 123 | } 124 | 125 | if(!(fp = fopen(afn, "wb"))) { 126 | perror("Cannot extract file"); 127 | free(buf); 128 | pso_afs_read_close(cxt); 129 | return EXIT_FAILURE; 130 | } 131 | 132 | if(fwrite(buf, 1, sz, fp) != (size_t)sz) { 133 | perror("Cannot extract file"); 134 | fclose(fp); 135 | free(buf); 136 | pso_afs_read_close(cxt); 137 | return EXIT_FAILURE; 138 | } 139 | 140 | /* Clean up, we're done with this file. */ 141 | fclose(fp); 142 | free(buf); 143 | 144 | /* If we have a filename table, fix the timestamp on the file... */ 145 | if(make_fntab) { 146 | if(pso_afs_file_stat(cxt, i, &st) == PSOARCHIVE_OK) { 147 | tms[0].tv_sec = time(NULL); 148 | tms[0].tv_usec = 0; 149 | tms[1].tv_sec = st.st_mtime; 150 | tms[1].tv_usec = 0; 151 | utimes(afn, tms); 152 | } 153 | } 154 | } 155 | 156 | pso_afs_read_close(cxt); 157 | return 0; 158 | } 159 | 160 | static int afs_create(const char *fn, int file_cnt, const char *files[]) { 161 | pso_afs_write_t *cxt; 162 | pso_error_t err; 163 | int i; 164 | char *tmp, *bn; 165 | 166 | if(!(cxt = pso_afs_new(fn, make_fntab, &err))) { 167 | fprintf(stderr, "Cannot create archive %s: %s\n", fn, 168 | pso_strerror(err)); 169 | return EXIT_FAILURE; 170 | } 171 | 172 | for(i = 0; i < file_cnt; ++i) { 173 | if(!(tmp = strdup(files[i]))) { 174 | perror("Cannot create archive"); 175 | pso_afs_write_close(cxt); 176 | return EXIT_FAILURE; 177 | } 178 | 179 | /* Figure out the basename of the file. Not really relevant for AFS 180 | files without a filename table, but whatever... */ 181 | bn = basename(tmp); 182 | 183 | /* Add the file to the archive. */ 184 | if((err = pso_afs_write_add_file(cxt, bn, files[i]))) { 185 | fprintf(stderr, "Cannot add file '%s' to archive: %s\n", files[i], 186 | pso_strerror(err)); 187 | free(tmp); 188 | pso_afs_write_close(cxt); 189 | return EXIT_FAILURE; 190 | } 191 | 192 | free(tmp); 193 | } 194 | 195 | pso_afs_write_close(cxt); 196 | return 0; 197 | } 198 | 199 | static int afs_append(const char *fn, int file_cnt, const char *files[]) { 200 | pso_afs_read_t *rcxt; 201 | pso_afs_write_t *wcxt; 202 | pso_error_t err; 203 | uint32_t cnt, i; 204 | int fd, j; 205 | ssize_t sz; 206 | uint8_t *buf; 207 | char afn[64], tmpfn[16]; 208 | char *bn, *tmp; 209 | 210 | #ifndef _WIN32 211 | mode_t mask; 212 | #endif 213 | 214 | /* Create a temporary file for the new archive... */ 215 | strcpy(tmpfn, "artoolXXXXXX"); 216 | if((fd = mkstemp(tmpfn)) < 0) { 217 | perror("Cannot create temporary file"); 218 | return EXIT_FAILURE; 219 | } 220 | 221 | /* Open the source archive. */ 222 | if(!(rcxt = pso_afs_read_open(fn, make_fntab, &err))) { 223 | fprintf(stderr, "Cannot open archive %s: %s\n", fn, pso_strerror(err)); 224 | close(fd); 225 | unlink(tmpfn); 226 | return EXIT_FAILURE; 227 | } 228 | 229 | /* Create a context to write to the temporary file. */ 230 | if(!(wcxt = pso_afs_new_fd(fd, make_fntab, &err))) { 231 | fprintf(stderr, "Cannot create archive: %s\n", pso_strerror(err)); 232 | pso_afs_read_close(rcxt); 233 | close(fd); 234 | unlink(tmpfn); 235 | return EXIT_FAILURE; 236 | } 237 | 238 | /* Loop through each file that's already in the archive... */ 239 | cnt = pso_afs_file_count(rcxt); 240 | 241 | for(i = 0; i < cnt; ++i) { 242 | if((sz = pso_afs_file_size(rcxt, i)) < 0) { 243 | fprintf(stderr, "Cannot extract file: %s\n", pso_strerror(sz)); 244 | goto err_out; 245 | } 246 | 247 | if(pso_afs_file_name(rcxt, i, afn, 64) < 0) { 248 | fprintf(stderr, "Cannot extract file: %s\n", pso_strerror(sz)); 249 | goto err_out; 250 | } 251 | 252 | if(!(buf = malloc(sz))) { 253 | perror("Cannot extract file"); 254 | goto err_out; 255 | } 256 | 257 | if((err = pso_afs_file_read(rcxt, i, buf, (size_t)sz)) < 0) { 258 | fprintf(stderr, "Cannot extract file: %s\n", pso_strerror(sz)); 259 | free(buf); 260 | goto err_out; 261 | } 262 | 263 | if((err = pso_afs_write_add(wcxt, afn, buf, (size_t)sz)) < 0) { 264 | fprintf(stderr, "Cannot add file to archive: %s\n", 265 | pso_strerror(sz)); 266 | free(buf); 267 | goto err_out; 268 | } 269 | 270 | /* Clean up, we're done with this file. */ 271 | free(buf); 272 | } 273 | 274 | /* We're done with the old archive... */ 275 | pso_afs_read_close(rcxt); 276 | 277 | /* Loop through all the new files. */ 278 | for(j = 0; j < file_cnt; ++j) { 279 | if(!(tmp = strdup(files[j]))) { 280 | perror("Cannot add file to archive"); 281 | goto err_out2; 282 | } 283 | 284 | /* Figure out the basename of the file. Not really relevant for AFS 285 | files without a filename table, but whatever... */ 286 | bn = basename(tmp); 287 | 288 | /* Add the file to the archive. */ 289 | if((err = pso_afs_write_add_file(wcxt, bn, files[j]))) { 290 | fprintf(stderr, "Cannot add file '%s' to archive: %s\n", files[j], 291 | pso_strerror(err)); 292 | free(tmp); 293 | goto err_out2; 294 | } 295 | 296 | free(tmp); 297 | } 298 | 299 | /* Done writing to the temporary file, time to overwrite the archive... */ 300 | pso_afs_write_close(wcxt); 301 | 302 | #ifndef _WIN32 303 | mask = umask(0); 304 | umask(mask); 305 | fchmod(fd, (~mask) & 0666); 306 | #endif 307 | 308 | rename(tmpfn, fn); 309 | 310 | return 0; 311 | 312 | err_out: 313 | pso_afs_read_close(rcxt); 314 | 315 | err_out2: 316 | pso_afs_write_close(wcxt); 317 | close(fd); 318 | unlink(tmpfn); 319 | return EXIT_FAILURE; 320 | } 321 | 322 | static int afs_update(const char *fn, const char *oldfn, const char *newfn) { 323 | pso_afs_read_t *rcxt; 324 | pso_afs_write_t *wcxt; 325 | pso_error_t err; 326 | uint32_t cnt, i, fnum = 0; 327 | int fd, replace = 0; 328 | ssize_t sz; 329 | uint8_t *buf; 330 | char afn[64], tmpfn[16]; 331 | struct stat st; 332 | time_t ts = time(NULL); 333 | 334 | #ifndef _WIN32 335 | mode_t mask; 336 | #endif 337 | 338 | if(!make_fntab) { 339 | errno = 0; 340 | fnum = strtoul(oldfn, NULL, 0); 341 | 342 | if(errno) { 343 | fprintf(stderr, "Cannot update archive: Invalid file number.\n"); 344 | return EXIT_FAILURE; 345 | } 346 | } 347 | 348 | /* Create a temporary file for the new archive... */ 349 | strcpy(tmpfn, "artoolXXXXXX"); 350 | if((fd = mkstemp(tmpfn)) < 0) { 351 | perror("Cannot create temporary file"); 352 | return EXIT_FAILURE; 353 | } 354 | 355 | /* Open the source archive. */ 356 | if(!(rcxt = pso_afs_read_open(fn, make_fntab, &err))) { 357 | fprintf(stderr, "Cannot open archive %s: %s\n", fn, pso_strerror(err)); 358 | close(fd); 359 | unlink(tmpfn); 360 | return EXIT_FAILURE; 361 | } 362 | 363 | /* Create a context to write to the temporary file. */ 364 | if(!(wcxt = pso_afs_new_fd(fd, make_fntab, &err))) { 365 | fprintf(stderr, "Cannot create archive: %s\n", pso_strerror(err)); 366 | pso_afs_read_close(rcxt); 367 | close(fd); 368 | unlink(tmpfn); 369 | return EXIT_FAILURE; 370 | } 371 | 372 | /* Loop through each file that's already in the archive... */ 373 | cnt = pso_afs_file_count(rcxt); 374 | 375 | for(i = 0; i < cnt; ++i) { 376 | if(pso_afs_file_name(rcxt, i, afn, 64) < 0) { 377 | fprintf(stderr, "Cannot extract file: %s\n", pso_strerror(sz)); 378 | goto err_out; 379 | } 380 | 381 | /* See if this file is the one we're updating... */ 382 | if(!replace && !make_fntab) { 383 | if(fnum == i) 384 | replace = 1; 385 | } 386 | else { 387 | if(!strcmp(afn, oldfn)) 388 | replace = 1; 389 | } 390 | 391 | /* Was this the one to replace? */ 392 | if(replace == 1) { 393 | if((err = pso_afs_write_add_file(wcxt, afn, newfn))) { 394 | fprintf(stderr, "Cannot add file to archive: %s\n", 395 | pso_strerror(sz)); 396 | goto err_out; 397 | } 398 | 399 | replace = 2; 400 | continue; 401 | } 402 | 403 | if((sz = pso_afs_file_size(rcxt, i)) < 0) { 404 | fprintf(stderr, "Cannot extract file: %s\n", pso_strerror(sz)); 405 | goto err_out; 406 | } 407 | 408 | if(!(buf = malloc(sz))) { 409 | perror("Cannot extract file"); 410 | goto err_out; 411 | } 412 | 413 | if((err = pso_afs_file_read(rcxt, i, buf, (size_t)sz)) < 0) { 414 | fprintf(stderr, "Cannot extract file: %s\n", pso_strerror(sz)); 415 | free(buf); 416 | goto err_out; 417 | } 418 | 419 | if(make_fntab) { 420 | if((err = pso_afs_file_stat(rcxt, i, &st)) == PSOARCHIVE_OK) 421 | ts = st.st_mtime; 422 | } 423 | 424 | if((err = pso_afs_write_add_ex(wcxt, afn, buf, (size_t)sz, ts)) < 0) { 425 | fprintf(stderr, "Cannot add file to archive: %s\n", 426 | pso_strerror(sz)); 427 | free(buf); 428 | goto err_out; 429 | } 430 | 431 | /* Clean up, we're done with this file. */ 432 | free(buf); 433 | } 434 | 435 | /* We're done with both of the archives... */ 436 | pso_afs_read_close(rcxt); 437 | pso_afs_write_close(wcxt); 438 | 439 | #ifndef _WIN32 440 | mask = umask(0); 441 | umask(mask); 442 | fchmod(fd, (~mask) & 0666); 443 | #endif 444 | 445 | rename(tmpfn, fn); 446 | 447 | return 0; 448 | 449 | err_out: 450 | pso_afs_read_close(rcxt); 451 | pso_afs_write_close(wcxt); 452 | close(fd); 453 | unlink(tmpfn); 454 | return EXIT_FAILURE; 455 | } 456 | 457 | static int afs_delete(const char *fn, int file_cnt, const char *files[]) { 458 | pso_afs_read_t *rcxt; 459 | pso_afs_write_t *wcxt; 460 | pso_error_t err; 461 | uint32_t cnt, i; 462 | int fd, j, skip = 0; 463 | ssize_t sz; 464 | uint8_t *buf; 465 | char afn[64], tmpfn[16]; 466 | struct stat st; 467 | time_t ts = time(NULL); 468 | 469 | #ifndef _WIN32 470 | mode_t mask; 471 | #endif 472 | 473 | /* Create a temporary file for the new archive... */ 474 | strcpy(tmpfn, "artoolXXXXXX"); 475 | if((fd = mkstemp(tmpfn)) < 0) { 476 | perror("Cannot create temporary file"); 477 | return EXIT_FAILURE; 478 | } 479 | 480 | /* Open the source archive. */ 481 | if(!(rcxt = pso_afs_read_open(fn, make_fntab, &err))) { 482 | fprintf(stderr, "Cannot open archive %s: %s\n", fn, pso_strerror(err)); 483 | close(fd); 484 | unlink(tmpfn); 485 | return EXIT_FAILURE; 486 | } 487 | 488 | /* Create a context to write to the temporary file. */ 489 | if(!(wcxt = pso_afs_new_fd(fd, make_fntab, &err))) { 490 | fprintf(stderr, "Cannot create archive: %s\n", pso_strerror(err)); 491 | pso_afs_read_close(rcxt); 492 | close(fd); 493 | unlink(tmpfn); 494 | return EXIT_FAILURE; 495 | } 496 | 497 | /* Loop through each file that's already in the archive... */ 498 | cnt = pso_afs_file_count(rcxt); 499 | 500 | for(i = 0; i < cnt; ++i) { 501 | if(pso_afs_file_name(rcxt, i, afn, 64) < 0) { 502 | fprintf(stderr, "Cannot extract file: %s\n", pso_strerror(sz)); 503 | goto err_out; 504 | } 505 | 506 | /* See if this file is in the list to delete. */ 507 | if(!make_fntab) { 508 | for(j = 0; j < file_cnt; ++j) { 509 | if(strtoul(files[j], NULL, 0) == i) { 510 | skip = 1; 511 | break; 512 | } 513 | } 514 | } 515 | else { 516 | for(j = 0; j < file_cnt; ++j) { 517 | if(!strcmp(afn, files[j])) { 518 | skip = 1; 519 | break; 520 | } 521 | } 522 | } 523 | 524 | if(skip) 525 | continue; 526 | 527 | if((sz = pso_afs_file_size(rcxt, i)) < 0) { 528 | fprintf(stderr, "Cannot extract file: %s\n", pso_strerror(sz)); 529 | goto err_out; 530 | } 531 | 532 | if(!(buf = malloc(sz))) { 533 | perror("Cannot extract file"); 534 | goto err_out; 535 | } 536 | 537 | if((err = pso_afs_file_read(rcxt, i, buf, (size_t)sz)) < 0) { 538 | fprintf(stderr, "Cannot extract file: %s\n", pso_strerror(sz)); 539 | free(buf); 540 | goto err_out; 541 | } 542 | 543 | if(make_fntab) { 544 | if((err = pso_afs_file_stat(rcxt, i, &st)) == PSOARCHIVE_OK) 545 | ts = st.st_mtime; 546 | } 547 | 548 | if((err = pso_afs_write_add_ex(wcxt, afn, buf, (size_t)sz, ts)) < 0) { 549 | fprintf(stderr, "Cannot add file to archive: %s\n", 550 | pso_strerror(sz)); 551 | free(buf); 552 | goto err_out; 553 | } 554 | 555 | /* Clean up, we're done with this file. */ 556 | free(buf); 557 | } 558 | 559 | /* We're done with both of the archives... */ 560 | pso_afs_read_close(rcxt); 561 | pso_afs_write_close(wcxt); 562 | 563 | #ifndef _WIN32 564 | mask = umask(0); 565 | umask(mask); 566 | fchmod(fd, (~mask) & 0666); 567 | #endif 568 | 569 | rename(tmpfn, fn); 570 | 571 | return 0; 572 | 573 | err_out: 574 | pso_afs_read_close(rcxt); 575 | pso_afs_write_close(wcxt); 576 | close(fd); 577 | unlink(tmpfn); 578 | return EXIT_FAILURE; 579 | } 580 | 581 | int afs(int argc, const char *argv[]) { 582 | if(argc < 4) 583 | return -1; 584 | 585 | /* Which style of AFS archive are we making? */ 586 | if(!strcmp(argv[1], "--afs")) { 587 | /* No filenames. */ 588 | make_fntab = 0; 589 | } 590 | else if(!strcmp(argv[1], "--afs2")) { 591 | /* AFS with filename table. */ 592 | make_fntab = PSO_AFS_FN_TABLE; 593 | } 594 | 595 | if(!strcmp(argv[2], "-t")) { 596 | /* List archive. */ 597 | if(argc != 4) 598 | return -1; 599 | 600 | return afs_list(argv[3]); 601 | } 602 | else if(!strcmp(argv[2], "-x")) { 603 | /* Extract. */ 604 | if(argc != 4) 605 | return -1; 606 | 607 | return afs_extract(argv[3]); 608 | } 609 | else if(!strcmp(argv[2], "-c")) { 610 | /* Create archive. */ 611 | if(argc < 5) 612 | return -1; 613 | 614 | return afs_create(argv[3], argc - 4, argv + 4); 615 | } 616 | else if(!strcmp(argv[2], "-r")) { 617 | /* Append file(s). */ 618 | if(argc < 5) 619 | return -1; 620 | 621 | return afs_append(argv[3], argc - 4, argv + 4); 622 | } 623 | else if(!strcmp(argv[2], "-u")) { 624 | /* Update archived file. */ 625 | if(argc != 6) 626 | return -1; 627 | 628 | return afs_update(argv[3], argv[4], argv[5]); 629 | } 630 | else if(!strcmp(argv[2], "--delete")) { 631 | /* Delete file from archive. */ 632 | if(argc < 5) 633 | return -1; 634 | 635 | return afs_delete(argv[3], argc - 4, argv + 4); 636 | } 637 | 638 | return -1; 639 | } 640 | -------------------------------------------------------------------------------- /pso_artool/artool.c: -------------------------------------------------------------------------------- 1 | /* 2 | Sylverant PSO Tools 3 | PSO Archive Tool 4 | Copyright (C) 2014, 2016 Lawrence Sebald 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License version 3 8 | as published by the Free Software Foundation. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | /* Available archive types. */ 25 | #define ARCHIVE_TYPE_NONE -1 26 | #define ARCHIVE_TYPE_AFS 0 27 | #define ARCHIVE_TYPE_GSL 1 28 | #define ARCHIVE_TYPE_PRS 2 29 | #define ARCHIVE_TYPE_PRSD 3 30 | 31 | #define ARCHIVE_TYPE_COUNT 4 32 | 33 | extern int afs(int argc, const char *argv[]); 34 | extern int gsl(int argc, const char *argv[]); 35 | extern int prs(int argc, const char *argv[]); 36 | extern int prsd(int argc, const char *argv[]); 37 | 38 | int (*archive_funcs[ARCHIVE_TYPE_COUNT])(int argc, const char *argv[]) = { 39 | &afs, &gsl, &prs, &prsd 40 | }; 41 | 42 | /* Utility functions... */ 43 | int write_file(const char *fn, const uint8_t *buf, size_t sz) { 44 | FILE *fp; 45 | 46 | if(!(fp = fopen(fn, "wb"))) { 47 | perror("Cannot write file"); 48 | return EXIT_FAILURE; 49 | } 50 | 51 | if(fwrite(buf, 1, sz, fp) != sz) { 52 | perror("Cannot write file"); 53 | fclose(fp); 54 | return EXIT_FAILURE; 55 | } 56 | 57 | fclose(fp); 58 | return 0; 59 | } 60 | 61 | int read_file(const char *fn, uint8_t **buf) { 62 | FILE *fp; 63 | uint8_t *out; 64 | size_t len; 65 | 66 | if(!(fp = fopen(fn, "rb"))) { 67 | perror("Cannot read file"); 68 | return -1; 69 | } 70 | 71 | if(fseek(fp, 0, SEEK_END)) { 72 | perror("Cannot read file"); 73 | fclose(fp); 74 | return -1; 75 | } 76 | 77 | len = (size_t)ftell(fp); 78 | 79 | if(fseek(fp, 0, SEEK_SET)) { 80 | perror("Cannot read file"); 81 | fclose(fp); 82 | return -1; 83 | } 84 | 85 | if(!(out = malloc(len))) { 86 | perror("Cannot read file"); 87 | fclose(fp); 88 | return -1; 89 | } 90 | 91 | if(fread(out, 1, len, fp) != len) { 92 | perror("Cannot read file"); 93 | free(out); 94 | fclose(fp); 95 | return -1; 96 | } 97 | 98 | fclose(fp); 99 | *buf = out; 100 | return (int)len; 101 | } 102 | 103 | /* Print information about this program to stdout. */ 104 | static void print_program_info(void) { 105 | #if defined(VERSION) 106 | printf("Sylverant PSO Archive Tool version %s\n", VERSION); 107 | #elif defined(GIT_REVISION) 108 | printf("Sylverant PSO Archive Tool Git revision: %s\n", GIT_REVISION); 109 | #else 110 | printf("Sylverant PSO Archive Tool\n"); 111 | #endif 112 | printf("Copyright (C) 2014, 2016 Lawrence Sebald\n\n"); 113 | printf("This program is free software: you can redistribute it and/or\n" 114 | "modify it under the terms of the GNU Affero General Public\n" 115 | "License version 3 as published by the Free Software Foundation.\n\n" 116 | "This program is distributed in the hope that it will be useful,\n" 117 | "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" 118 | "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" 119 | "GNU General Public License for more details.\n\n" 120 | "You should have received a copy of the GNU Affero General Public\n" 121 | "License along with this program. If not, see " 122 | ".\n"); 123 | } 124 | 125 | /* Print help to the user to stdout. */ 126 | static void print_help(const char *bin) { 127 | printf("General usage:\n" 128 | " %s type operation [operation aguments]\n" 129 | " %s --help\n" 130 | " %s --version\n" 131 | "Where type is one of the following:\n" 132 | " --afs, --afs2, --gsl, --gsl-little, --gsl-big, --prs, --prsd,\n" 133 | " --prsd-little, --prsd-big, --prc, --prc-little, or --prc-big\n" 134 | " (the prc options are aliases of the prsd ones)\n\n" 135 | "Available operations per archive type are shown below:\n\n" 136 | "For AFS (--afs, --afs2) and GSL (--gsl, --gsl-little, --gsl-big)\n" 137 | "files:\n" 138 | " -t archive\n" 139 | " List all files in the archive.\n" 140 | " -x archive\n" 141 | " Extract all files from the archive.\n" 142 | " -c archive file1 [file2 ...]\n" 143 | " Create a new archive containing the files specified.\n" 144 | " -r archive file1 [file2 ...]\n" 145 | " Append the files specified to an existing archive.\n" 146 | " -u archive file_in_archive file_on_disk\n" 147 | " Update an archive, replacing the file contained in it with\n" 148 | " the file on the disk.\n" 149 | " --delete archive file1 [file2 ...]\n" 150 | " Delete the specified files from the archive.\n\n" 151 | "For PRS (--prs) files:\n" 152 | " -x archive [to]\n" 153 | " Extract the archive to the specified filename. If to is not\n" 154 | " specified, the default output filename shall have the same\n" 155 | " basename as the archive with the extension .bin appended.\n" 156 | " -c archive file\n" 157 | " Compress the specified file and store it as archive.\n\n" 158 | "For PRSD/PRC (--prsd, --prsd-little, --prsd-big, --prc, \n" 159 | " --prc-little, --prc-big) files:\n" 160 | " -x archive [to]\n" 161 | " Extract the archive to the specified filename. If to is not\n" 162 | " specified, the default output filename shall have the same\n" 163 | " basename as the archive with the extension .bin appended.\n" 164 | " -c archive file [key]\n" 165 | " Compress the specified file and store it as archive. If\n" 166 | " specified, key will be used as the encryption key for the\n" 167 | " archive, otherwise a random key will be generated.\n\n", 168 | bin, bin, bin); 169 | printf("Many AFS files do not store filenames at all. Files created by\n" 170 | "this tool with the --afs type will not contain filenames, whereas\n" 171 | "those created with --afs2 will. If using the --afs type, any\n" 172 | "files that are specified in an archive (for the -u and --delete\n" 173 | "operations) must be specified by index, not by name.\n\n"); 174 | printf("GSL and PRSD/PRC archives are supported in both big and\n" 175 | "little-endian forms. If the endianness is not specified, then it\n" 176 | "will be auto-detected for operations other than archive creation.\n" 177 | "For archive creation, little-endian mode is assumed if the\n" 178 | "endianness is not specified.\n" 179 | "Big-endian archives are used in PSO for Gamecube, whereas all\n" 180 | "other versions of the game use little-endian archives.\n\n"); 181 | } 182 | 183 | /* Parse any command-line arguments passed in. */ 184 | static void parse_command_line(int argc, const char *argv[]) { 185 | int i = 2; 186 | int t = ARCHIVE_TYPE_NONE; 187 | 188 | if(argc < 2) { 189 | print_help(argv[0]); 190 | exit(EXIT_FAILURE); 191 | } 192 | 193 | if(!strcmp(argv[1], "--afs") || !strcmp(argv[1], "--afs2")) { 194 | t = ARCHIVE_TYPE_AFS; 195 | } 196 | else if(!strcmp(argv[1], "--gsl") || !strcmp(argv[1], "--gsl-little") || 197 | !strcmp(argv[1], "--gsl-big")) { 198 | t = ARCHIVE_TYPE_GSL; 199 | } 200 | else if(!strcmp(argv[1], "--prs")) { 201 | t = ARCHIVE_TYPE_PRS; 202 | } 203 | else if(!strcmp(argv[1], "--prsd") || !strcmp(argv[1], "--prsd-little") || 204 | !strcmp(argv[1], "--prsd-big") || !strcmp(argv[1], "--prc") || 205 | !strcmp(argv[1], "--prc-little") || !strcmp(argv[1], "--prc-big")) { 206 | t = ARCHIVE_TYPE_PRSD; 207 | } 208 | else { 209 | i = 1; 210 | } 211 | 212 | if(!strcmp(argv[i], "--version")) { 213 | print_program_info(); 214 | exit(EXIT_SUCCESS); 215 | } 216 | else if(!strcmp(argv[i], "--help")) { 217 | print_help(argv[0]); 218 | exit(EXIT_SUCCESS); 219 | } 220 | 221 | /* All other operations require an archive type, so if we didn't get one, 222 | then bail out. */ 223 | if(i == 1) { 224 | print_help(argv[0]); 225 | exit(EXIT_FAILURE); 226 | } 227 | 228 | /* Leave it up to the handler to do the rest of the work. */ 229 | i = archive_funcs[t](argc, argv); 230 | if(i == -1) { 231 | print_help(argv[0]); 232 | exit(EXIT_FAILURE); 233 | } 234 | 235 | exit(i); 236 | } 237 | 238 | int main(int argc, const char *argv[]) { 239 | /* Parse the command line... */ 240 | parse_command_line(argc, argv); 241 | 242 | return 0; 243 | } 244 | -------------------------------------------------------------------------------- /pso_artool/gsl.c: -------------------------------------------------------------------------------- 1 | /* 2 | Sylverant PSO Tools 3 | PSO Archive Tool 4 | Copyright (C) 2014, 2016 Lawrence Sebald 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License version 3 8 | as published by the Free Software Foundation. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | 26 | #ifndef _WIN32 27 | #include 28 | #include 29 | #include 30 | #endif 31 | 32 | #include 33 | 34 | #ifdef _WIN32 35 | #include "windows_compat.h" 36 | #endif 37 | 38 | static uint32_t endian = 0; 39 | 40 | static int digits(uint32_t n) { 41 | int r = 1; 42 | while(n /= 10) ++r; 43 | return r; 44 | } 45 | 46 | static int gsl_list(const char *fn) { 47 | pso_gsl_read_t *cxt; 48 | pso_error_t err; 49 | uint32_t cnt, i; 50 | int dg; 51 | ssize_t sz; 52 | char afn[64]; 53 | 54 | if(!(cxt = pso_gsl_read_open(fn, endian, &err))) { 55 | fprintf(stderr, "Cannot open archive %s: %s\n", fn, pso_strerror(err)); 56 | return EXIT_FAILURE; 57 | } 58 | 59 | /* Loop through each file... */ 60 | cnt = pso_gsl_file_count(cxt); 61 | dg = digits(cnt); 62 | 63 | for(i = 0; i < cnt; ++i) { 64 | sz = pso_gsl_file_size(cxt, i); 65 | pso_gsl_file_name(cxt, i, afn, 64); 66 | 67 | #ifndef _WIN32 68 | printf("File %*" PRIu32 ": '%s' size: %" PRIu32 "\n", dg, i, afn, 69 | (uint32_t)sz); 70 | #else 71 | printf("File %*I32u: '%s' size: %I32u\n", dg, i, afn, (uint32_t)sz); 72 | #endif 73 | } 74 | 75 | pso_gsl_read_close(cxt); 76 | return 0; 77 | } 78 | 79 | static int gsl_extract(const char *fn) { 80 | pso_gsl_read_t *cxt; 81 | pso_error_t err; 82 | uint32_t cnt, i; 83 | ssize_t sz; 84 | char afn[64]; 85 | FILE *fp; 86 | uint8_t *buf; 87 | 88 | if(!(cxt = pso_gsl_read_open(fn, endian, &err))) { 89 | fprintf(stderr, "Cannot open archive %s: %s\n", fn, pso_strerror(err)); 90 | return EXIT_FAILURE; 91 | } 92 | 93 | /* Loop through each file... */ 94 | cnt = pso_gsl_file_count(cxt); 95 | 96 | for(i = 0; i < cnt; ++i) { 97 | if((sz = pso_gsl_file_size(cxt, i)) < 0) { 98 | fprintf(stderr, "Cannot extract file: %s\n", pso_strerror(sz)); 99 | pso_gsl_read_close(cxt); 100 | return EXIT_FAILURE; 101 | } 102 | 103 | if(pso_gsl_file_name(cxt, i, afn, 64) < 0) { 104 | fprintf(stderr, "Cannot extract file: %s\n", pso_strerror(sz)); 105 | pso_gsl_read_close(cxt); 106 | return EXIT_FAILURE; 107 | } 108 | 109 | if(!(buf = malloc(sz))) { 110 | perror("Cannot extract file"); 111 | pso_gsl_read_close(cxt); 112 | return EXIT_FAILURE; 113 | } 114 | 115 | if((err = pso_gsl_file_read(cxt, i, buf, (size_t)sz)) < 0) { 116 | fprintf(stderr, "Cannot extract file: %s\n", pso_strerror(sz)); 117 | free(buf); 118 | pso_gsl_read_close(cxt); 119 | return EXIT_FAILURE; 120 | } 121 | 122 | if(!(fp = fopen(afn, "wb"))) { 123 | perror("Cannot extract file"); 124 | free(buf); 125 | pso_gsl_read_close(cxt); 126 | return EXIT_FAILURE; 127 | } 128 | 129 | if(fwrite(buf, 1, sz, fp) != (size_t)sz) { 130 | perror("Cannot extract file"); 131 | fclose(fp); 132 | free(buf); 133 | pso_gsl_read_close(cxt); 134 | return EXIT_FAILURE; 135 | } 136 | 137 | /* Clean up, we're done with this file. */ 138 | fclose(fp); 139 | free(buf); 140 | } 141 | 142 | pso_gsl_read_close(cxt); 143 | return 0; 144 | } 145 | 146 | static int gsl_create(const char *fn, int file_cnt, const char *files[]) { 147 | pso_gsl_write_t *cxt; 148 | pso_error_t err; 149 | int i; 150 | char *tmp, *bn; 151 | 152 | if(!(cxt = pso_gsl_new(fn, endian, &err))) { 153 | fprintf(stderr, "Cannot create archive %s: %s\n", fn, 154 | pso_strerror(err)); 155 | return EXIT_FAILURE; 156 | } 157 | 158 | if((err = pso_gsl_write_set_ftab_size(cxt, file_cnt)) < 0) { 159 | fprintf(stderr, "Cannot create archive %s: %s\n", fn, 160 | pso_strerror(err)); 161 | pso_gsl_write_close(cxt); 162 | return EXIT_FAILURE; 163 | } 164 | 165 | for(i = 0; i < file_cnt; ++i) { 166 | if(!(tmp = strdup(files[i]))) { 167 | perror("Cannot create archive"); 168 | pso_gsl_write_close(cxt); 169 | return EXIT_FAILURE; 170 | } 171 | 172 | /* Figure out the basename of the file... */ 173 | bn = basename(tmp); 174 | 175 | /* Add the file to the archive. */ 176 | if((err = pso_gsl_write_add_file(cxt, bn, files[i]))) { 177 | fprintf(stderr, "Cannot add file '%s' to archive: %s\n", files[i], 178 | pso_strerror(err)); 179 | free(tmp); 180 | pso_gsl_write_close(cxt); 181 | return EXIT_FAILURE; 182 | } 183 | 184 | free(tmp); 185 | } 186 | 187 | pso_gsl_write_close(cxt); 188 | return 0; 189 | } 190 | 191 | static int gsl_append(const char *fn, int file_cnt, const char *files[]) { 192 | pso_gsl_read_t *rcxt; 193 | pso_gsl_write_t *wcxt; 194 | pso_error_t err; 195 | uint32_t cnt, i; 196 | int fd, j; 197 | ssize_t sz; 198 | uint8_t *buf; 199 | char afn[64], tmpfn[16]; 200 | char *bn, *tmp; 201 | 202 | #ifndef _WIN32 203 | mode_t mask; 204 | #endif 205 | 206 | /* Create a temporary file for the new archive... */ 207 | strcpy(tmpfn, "artoolXXXXXX"); 208 | if((fd = mkstemp(tmpfn)) < 0) { 209 | perror("Cannot create temporary file"); 210 | return EXIT_FAILURE; 211 | } 212 | 213 | /* Open the source archive. */ 214 | if(!(rcxt = pso_gsl_read_open(fn, endian, &err))) { 215 | fprintf(stderr, "Cannot open archive %s: %s\n", fn, pso_strerror(err)); 216 | close(fd); 217 | unlink(tmpfn); 218 | return EXIT_FAILURE; 219 | } 220 | 221 | /* Create a context to write to the temporary file. */ 222 | if(!(wcxt = pso_gsl_new_fd(fd, endian, &err))) { 223 | fprintf(stderr, "Cannot create archive: %s\n", pso_strerror(err)); 224 | pso_gsl_read_close(rcxt); 225 | close(fd); 226 | unlink(tmpfn); 227 | return EXIT_FAILURE; 228 | } 229 | 230 | /* Loop through each file that's already in the archive... */ 231 | cnt = pso_gsl_file_count(rcxt); 232 | 233 | if((err = pso_gsl_write_set_ftab_size(wcxt, file_cnt + cnt)) < 0) { 234 | fprintf(stderr, "Cannot create archive %s: %s\n", fn, 235 | pso_strerror(err)); 236 | goto err_out; 237 | } 238 | 239 | for(i = 0; i < cnt; ++i) { 240 | if((sz = pso_gsl_file_size(rcxt, i)) < 0) { 241 | fprintf(stderr, "Cannot extract file: %s\n", pso_strerror(sz)); 242 | goto err_out; 243 | } 244 | 245 | if(pso_gsl_file_name(rcxt, i, afn, 64) < 0) { 246 | fprintf(stderr, "Cannot extract file: %s\n", pso_strerror(sz)); 247 | goto err_out; 248 | } 249 | 250 | if(!(buf = malloc(sz))) { 251 | perror("Cannot extract file"); 252 | goto err_out; 253 | } 254 | 255 | if((err = pso_gsl_file_read(rcxt, i, buf, (size_t)sz)) < 0) { 256 | fprintf(stderr, "Cannot extract file: %s\n", pso_strerror(sz)); 257 | free(buf); 258 | goto err_out; 259 | } 260 | 261 | if((err = pso_gsl_write_add(wcxt, afn, buf, (size_t)sz)) < 0) { 262 | fprintf(stderr, "Cannot add file to archive: %s\n", 263 | pso_strerror(sz)); 264 | free(buf); 265 | goto err_out; 266 | } 267 | 268 | /* Clean up, we're done with this file. */ 269 | free(buf); 270 | } 271 | 272 | /* We're done with the old archive... */ 273 | pso_gsl_read_close(rcxt); 274 | 275 | /* Loop through all the new files. */ 276 | for(j = 0; j < file_cnt; ++j) { 277 | if(!(tmp = strdup(files[j]))) { 278 | perror("Cannot add file to archive"); 279 | goto err_out2; 280 | } 281 | 282 | /* Figure out the basename of the file. */ 283 | bn = basename(tmp); 284 | 285 | /* Add the file to the archive. */ 286 | if((err = pso_gsl_write_add_file(wcxt, bn, files[j]))) { 287 | fprintf(stderr, "Cannot add file '%s' to archive: %s\n", files[j], 288 | pso_strerror(err)); 289 | free(tmp); 290 | goto err_out2; 291 | } 292 | 293 | free(tmp); 294 | } 295 | 296 | /* Done writing to the temporary file, time to overwrite the archive... */ 297 | pso_gsl_write_close(wcxt); 298 | 299 | #ifndef _WIN32 300 | mask = umask(0); 301 | umask(mask); 302 | fchmod(fd, (~mask) & 0666); 303 | #endif 304 | 305 | rename(tmpfn, fn); 306 | 307 | return 0; 308 | 309 | err_out: 310 | pso_gsl_read_close(rcxt); 311 | 312 | err_out2: 313 | pso_gsl_write_close(wcxt); 314 | close(fd); 315 | unlink(tmpfn); 316 | return EXIT_FAILURE; 317 | } 318 | 319 | static int gsl_update(const char *fn, const char *oldfn, const char *newfn) { 320 | pso_gsl_read_t *rcxt; 321 | pso_gsl_write_t *wcxt; 322 | pso_error_t err; 323 | uint32_t cnt, i; 324 | int fd, replace = 0; 325 | ssize_t sz; 326 | uint8_t *buf; 327 | char afn[64], tmpfn[16]; 328 | 329 | #ifndef _WIN32 330 | mode_t mask; 331 | #endif 332 | 333 | /* Create a temporary file for the new archive... */ 334 | strcpy(tmpfn, "artoolXXXXXX"); 335 | if((fd = mkstemp(tmpfn)) < 0) { 336 | perror("Cannot create temporary file"); 337 | return EXIT_FAILURE; 338 | } 339 | 340 | /* Open the source archive. */ 341 | if(!(rcxt = pso_gsl_read_open(fn, endian, &err))) { 342 | fprintf(stderr, "Cannot open archive %s: %s\n", fn, pso_strerror(err)); 343 | close(fd); 344 | unlink(tmpfn); 345 | return EXIT_FAILURE; 346 | } 347 | 348 | /* Create a context to write to the temporary file. */ 349 | if(!(wcxt = pso_gsl_new_fd(fd, endian, &err))) { 350 | fprintf(stderr, "Cannot create archive: %s\n", pso_strerror(err)); 351 | pso_gsl_read_close(rcxt); 352 | close(fd); 353 | unlink(tmpfn); 354 | return EXIT_FAILURE; 355 | } 356 | 357 | /* Loop through each file that's already in the archive... */ 358 | cnt = pso_gsl_file_count(rcxt); 359 | 360 | if((err = pso_gsl_write_set_ftab_size(wcxt, cnt)) < 0) { 361 | fprintf(stderr, "Cannot create archive %s: %s\n", fn, 362 | pso_strerror(err)); 363 | goto err_out; 364 | } 365 | 366 | for(i = 0; i < cnt; ++i) { 367 | if(pso_gsl_file_name(rcxt, i, afn, 64) < 0) { 368 | fprintf(stderr, "Cannot extract file: %s\n", pso_strerror(sz)); 369 | goto err_out; 370 | } 371 | 372 | /* See if this file is the one we're updating... */ 373 | if(!replace) { 374 | if(!strcmp(afn, oldfn)) 375 | replace = 1; 376 | } 377 | 378 | /* Was this the one to replace? */ 379 | if(replace == 1) { 380 | if((err = pso_gsl_write_add_file(wcxt, afn, newfn))) { 381 | fprintf(stderr, "Cannot add file to archive: %s\n", 382 | pso_strerror(sz)); 383 | goto err_out; 384 | } 385 | 386 | replace = 2; 387 | continue; 388 | } 389 | 390 | if((sz = pso_gsl_file_size(rcxt, i)) < 0) { 391 | fprintf(stderr, "Cannot extract file: %s\n", pso_strerror(sz)); 392 | goto err_out; 393 | } 394 | 395 | if(!(buf = malloc(sz))) { 396 | perror("Cannot extract file"); 397 | goto err_out; 398 | } 399 | 400 | if((err = pso_gsl_file_read(rcxt, i, buf, (size_t)sz)) < 0) { 401 | fprintf(stderr, "Cannot extract file: %s\n", pso_strerror(sz)); 402 | free(buf); 403 | goto err_out; 404 | } 405 | 406 | if((err = pso_gsl_write_add(wcxt, afn, buf, (size_t)sz)) < 0) { 407 | fprintf(stderr, "Cannot add file to archive: %s\n", 408 | pso_strerror(sz)); 409 | free(buf); 410 | goto err_out; 411 | } 412 | 413 | /* Clean up, we're done with this file. */ 414 | free(buf); 415 | } 416 | 417 | /* We're done with both of the archives... */ 418 | pso_gsl_read_close(rcxt); 419 | pso_gsl_write_close(wcxt); 420 | 421 | #ifndef _WIN32 422 | mask = umask(0); 423 | umask(mask); 424 | fchmod(fd, (~mask) & 0666); 425 | #endif 426 | 427 | rename(tmpfn, fn); 428 | 429 | return 0; 430 | 431 | err_out: 432 | pso_gsl_read_close(rcxt); 433 | pso_gsl_write_close(wcxt); 434 | close(fd); 435 | unlink(tmpfn); 436 | return EXIT_FAILURE; 437 | } 438 | 439 | static int gsl_delete(const char *fn, int file_cnt, const char *files[]) { 440 | pso_gsl_read_t *rcxt; 441 | pso_gsl_write_t *wcxt; 442 | pso_error_t err; 443 | uint32_t cnt, i; 444 | int fd, j, skip = 0; 445 | ssize_t sz; 446 | uint8_t *buf; 447 | char afn[64], tmpfn[16]; 448 | 449 | #ifndef _WIN32 450 | mode_t mask; 451 | #endif 452 | 453 | /* Create a temporary file for the new archive... */ 454 | strcpy(tmpfn, "artoolXXXXXX"); 455 | if((fd = mkstemp(tmpfn)) < 0) { 456 | perror("Cannot create temporary file"); 457 | return EXIT_FAILURE; 458 | } 459 | 460 | /* Open the source archive. */ 461 | if(!(rcxt = pso_gsl_read_open(fn, endian, &err))) { 462 | fprintf(stderr, "Cannot open archive %s: %s\n", fn, pso_strerror(err)); 463 | close(fd); 464 | unlink(tmpfn); 465 | return EXIT_FAILURE; 466 | } 467 | 468 | /* Create a context to write to the temporary file. */ 469 | if(!(wcxt = pso_gsl_new_fd(fd, endian, &err))) { 470 | fprintf(stderr, "Cannot create archive: %s\n", pso_strerror(err)); 471 | pso_gsl_read_close(rcxt); 472 | close(fd); 473 | unlink(tmpfn); 474 | return EXIT_FAILURE; 475 | } 476 | 477 | /* Loop through each file that's already in the archive... */ 478 | cnt = pso_gsl_file_count(rcxt); 479 | 480 | /* Play it safe here, in case we don't actually end up deleting anything. */ 481 | if((err = pso_gsl_write_set_ftab_size(wcxt, cnt)) < 0) { 482 | fprintf(stderr, "Cannot create archive %s: %s\n", fn, 483 | pso_strerror(err)); 484 | goto err_out; 485 | } 486 | 487 | for(i = 0; i < cnt; ++i) { 488 | if(pso_gsl_file_name(rcxt, i, afn, 64) < 0) { 489 | fprintf(stderr, "Cannot extract file: %s\n", pso_strerror(sz)); 490 | goto err_out; 491 | } 492 | 493 | /* See if this file is in the list to delete. */ 494 | for(j = 0; j < file_cnt; ++j) { 495 | if(!strcmp(afn, files[j])) { 496 | skip = 1; 497 | break; 498 | } 499 | } 500 | 501 | if(skip) 502 | continue; 503 | 504 | if((sz = pso_gsl_file_size(rcxt, i)) < 0) { 505 | fprintf(stderr, "Cannot extract file: %s\n", pso_strerror(sz)); 506 | goto err_out; 507 | } 508 | 509 | if(!(buf = malloc(sz))) { 510 | perror("Cannot extract file"); 511 | goto err_out; 512 | } 513 | 514 | if((err = pso_gsl_file_read(rcxt, i, buf, (size_t)sz)) < 0) { 515 | fprintf(stderr, "Cannot extract file: %s\n", pso_strerror(sz)); 516 | free(buf); 517 | goto err_out; 518 | } 519 | 520 | if((err = pso_gsl_write_add(wcxt, afn, buf, (size_t)sz)) < 0) { 521 | fprintf(stderr, "Cannot add file to archive: %s\n", 522 | pso_strerror(sz)); 523 | free(buf); 524 | goto err_out; 525 | } 526 | 527 | /* Clean up, we're done with this file. */ 528 | free(buf); 529 | } 530 | 531 | /* We're done with both of the archives... */ 532 | pso_gsl_read_close(rcxt); 533 | pso_gsl_write_close(wcxt); 534 | 535 | #ifndef _WIN32 536 | mask = umask(0); 537 | umask(mask); 538 | fchmod(fd, (~mask) & 0666); 539 | #endif 540 | 541 | rename(tmpfn, fn); 542 | 543 | return 0; 544 | 545 | err_out: 546 | pso_gsl_read_close(rcxt); 547 | pso_gsl_write_close(wcxt); 548 | close(fd); 549 | unlink(tmpfn); 550 | return EXIT_FAILURE; 551 | } 552 | 553 | int gsl(int argc, const char *argv[]) { 554 | if(argc < 4) 555 | return -1; 556 | 557 | /* Which style of GSL archive are we making? */ 558 | if(!strcmp(argv[1], "--gsl-little")) { 559 | /* Little endian. */ 560 | endian = PSO_GSL_LITTLE_ENDIAN; 561 | } 562 | else if(!strcmp(argv[1], "--gsl-big")) { 563 | /* Big endian. */ 564 | endian = PSO_GSL_BIG_ENDIAN; 565 | } 566 | 567 | if(!strcmp(argv[2], "-t")) { 568 | /* List archive. */ 569 | if(argc != 4) 570 | return -1; 571 | 572 | return gsl_list(argv[3]); 573 | } 574 | else if(!strcmp(argv[2], "-x")) { 575 | /* Extract. */ 576 | if(argc != 4) 577 | return -1; 578 | 579 | return gsl_extract(argv[3]); 580 | } 581 | else if(!strcmp(argv[2], "-c")) { 582 | /* Create archive. */ 583 | if(argc < 5) 584 | return -1; 585 | 586 | return gsl_create(argv[3], argc - 4, argv + 4); 587 | } 588 | else if(!strcmp(argv[2], "-r")) { 589 | /* Append file(s). */ 590 | if(argc < 5) 591 | return -1; 592 | 593 | return gsl_append(argv[3], argc - 4, argv + 4); 594 | } 595 | else if(!strcmp(argv[2], "-u")) { 596 | /* Update archived file. */ 597 | if(argc != 6) 598 | return -1; 599 | 600 | return gsl_update(argv[3], argv[4], argv[5]); 601 | } 602 | else if(!strcmp(argv[2], "--delete")) { 603 | /* Delete file from archive. */ 604 | if(argc < 5) 605 | return -1; 606 | 607 | return gsl_delete(argv[3], argc - 4, argv + 4); 608 | } 609 | 610 | return -1; 611 | } 612 | -------------------------------------------------------------------------------- /pso_artool/prs.c: -------------------------------------------------------------------------------- 1 | /* 2 | Sylverant PSO Tools 3 | PSO Archive Tool 4 | Copyright (C) 2016 Lawrence Sebald 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License version 3 8 | as published by the Free Software Foundation. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #ifndef _WIN32 25 | #include 26 | #else 27 | #include "windows_compat.h" 28 | #endif 29 | 30 | extern int write_file(const char *fn, const uint8_t *buf, size_t sz); 31 | extern int read_file(const char *fn, uint8_t **buf); 32 | 33 | int prs(int argc, const char *argv[]) { 34 | uint8_t *dst, *src; 35 | char *fn, *tmp; 36 | int sz; 37 | 38 | /* Make sure it's sane... */ 39 | if(argc < 4 || argc > 5) 40 | return -1; 41 | 42 | /* Parse out the operation requested. */ 43 | if(!strcmp(argv[2], "-x")) { 44 | /* Extract. */ 45 | if((sz = pso_prs_decompress_file(argv[3], &dst)) < 0) { 46 | fprintf(stderr, "Cannot extract %s: %s\n", argv[3], 47 | pso_strerror(sz)); 48 | return EXIT_FAILURE; 49 | } 50 | 51 | if(argc == 5) { 52 | return write_file(argv[4], dst, sz); 53 | } 54 | else { 55 | if(!(fn = (char *)malloc(strlen(argv[3]) + 5))) { 56 | perror("Cannot write file"); 57 | return EXIT_FAILURE; 58 | } 59 | 60 | tmp = strdup(argv[3]); 61 | strcpy(fn, basename(tmp)); 62 | strcat(fn, ".bin"); 63 | sz = write_file(fn, dst, sz); 64 | free(tmp); 65 | free(fn); 66 | return sz; 67 | } 68 | } 69 | else if(!strcmp(argv[2], "-c")) { 70 | /* Compress. */ 71 | if(argc != 5) 72 | return -1; 73 | 74 | if((sz = read_file(argv[4], &src)) < 0) 75 | return EXIT_FAILURE; 76 | 77 | if((sz = pso_prs_compress(src, &dst, sz)) < 0) { 78 | fprintf(stderr, "Cannot compress %s: %s\n", argv[4], 79 | pso_strerror(sz)); 80 | return EXIT_FAILURE; 81 | } 82 | 83 | free(src); 84 | sz = write_file(argv[3], dst, sz); 85 | free(dst); 86 | return sz; 87 | } 88 | 89 | return -1; 90 | } 91 | -------------------------------------------------------------------------------- /pso_artool/prsd.c: -------------------------------------------------------------------------------- 1 | /* 2 | Sylverant PSO Tools 3 | PSO Archive Tool 4 | Copyright (C) 2016 Lawrence Sebald 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License version 3 8 | as published by the Free Software Foundation. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #ifndef _WIN32 27 | #include 28 | #else 29 | #include "windows_compat.h" 30 | #endif 31 | 32 | extern int write_file(const char *fn, const uint8_t *buf, size_t sz); 33 | extern int read_file(const char *fn, uint8_t **buf); 34 | 35 | int prsd(int argc, const char *argv[]) { 36 | uint8_t *dst, *src; 37 | char *fn, *tmp; 38 | int sz; 39 | int endian = PSO_PRSD_AUTO_ENDIAN; 40 | uint32_t key; 41 | 42 | /* Make sure it's sane... */ 43 | if(argc < 4 || argc > 6) 44 | return -1; 45 | 46 | /* Figure out the endianness the user specified. */ 47 | if(!strcmp(argv[1], "--prsd-little") || !strcmp(argv[1], "--prc-little")) 48 | endian = PSO_PRSD_LITTLE_ENDIAN; 49 | else if(!strcmp(argv[1], "--prsd-big") || !strcmp(argv[1], "--prc-big")) 50 | endian = PSO_PRSD_BIG_ENDIAN; 51 | 52 | /* Parse out the operation requested. */ 53 | if(!strcmp(argv[2], "-x")) { 54 | /* Extract. */ 55 | if(argc == 6) 56 | return -1; 57 | 58 | if((sz = pso_prsd_decompress_file(argv[3], &dst, endian)) < 0) { 59 | fprintf(stderr, "Cannot extract %s: %s\n", argv[3], 60 | pso_strerror(sz)); 61 | return EXIT_FAILURE; 62 | } 63 | 64 | if(argc == 5) { 65 | return write_file(argv[4], dst, sz); 66 | } 67 | else { 68 | if(!(fn = (char *)malloc(strlen(argv[3]) + 5))) { 69 | perror("Cannot write file"); 70 | return EXIT_FAILURE; 71 | } 72 | 73 | tmp = strdup(argv[3]); 74 | strcpy(fn, basename(tmp)); 75 | strcat(fn, ".bin"); 76 | sz = write_file(fn, dst, sz); 77 | free(tmp); 78 | free(fn); 79 | return sz; 80 | } 81 | } 82 | else if(!strcmp(argv[2], "-c")) { 83 | /* Compress. */ 84 | if(argc < 5) 85 | return -1; 86 | 87 | if(endian == PSO_PRSD_AUTO_ENDIAN) 88 | endian = PSO_PRSD_LITTLE_ENDIAN; 89 | 90 | if(argc == 6) { 91 | errno = 0; 92 | key = (uint32_t)strtoul(argv[5], NULL, 0); 93 | if(errno) { 94 | perror("Invalid key"); 95 | return EXIT_FAILURE; 96 | } 97 | } 98 | else { 99 | srand(time(NULL)); 100 | key = rand(); 101 | } 102 | 103 | if((sz = read_file(argv[4], &src)) < 0) 104 | return EXIT_FAILURE; 105 | 106 | if((sz = pso_prsd_compress(src, &dst, sz, key, endian)) < 0) { 107 | fprintf(stderr, "Cannot compress %s: %s\n", argv[4], 108 | pso_strerror(sz)); 109 | return EXIT_FAILURE; 110 | } 111 | 112 | free(src); 113 | sz = write_file(argv[3], dst, sz); 114 | free(dst); 115 | return sz; 116 | } 117 | 118 | return -1; 119 | } 120 | -------------------------------------------------------------------------------- /pso_artool/windows_compat.c: -------------------------------------------------------------------------------- 1 | /* 2 | Sylverant PSO Tools 3 | PSO Archive Tool 4 | Copyright (C) 2014, 2016 Lawrence Sebald 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License version 3 8 | as published by the Free Software Foundation. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | /* Have I ever mentioned how much I dislike the fact that Microsoft doesn't 20 | implement the standard functions that developers might expect in their C 21 | libraries? No, well, consider this me mentioning it. */ 22 | 23 | #ifdef _WIN32 24 | 25 | #define _WIN32_LEAN_AND_MEAN 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | 34 | /* mkstemp extracted from libc/sysdeps/posix/tempname.c. Copyright 35 | (C) 1991-1999, 2000, 2001, 2006 Free Software Foundation, Inc. 36 | 37 | The GNU C Library is free software; you can redistribute it and/or 38 | modify it under the terms of the GNU Lesser General Public 39 | License as published by the Free Software Foundation; either 40 | version 2.1 of the License, or (at your option) any later version. */ 41 | 42 | static const char letters[] = 43 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 44 | 45 | /* Generate a temporary file name based on TMPL. TMPL must match the 46 | rules for mk[s]temp (i.e. end in "XXXXXX"). The name constructed 47 | does not exist at the time of the call to mkstemp. TMPL is 48 | overwritten with the result. */ 49 | int 50 | mkstemp (char *tmpl) 51 | { 52 | int len; 53 | char *XXXXXX; 54 | static unsigned long long value; 55 | unsigned long long random_time_bits; 56 | unsigned int count; 57 | int fd = -1; 58 | int save_errno = errno; 59 | 60 | /* A lower bound on the number of temporary files to attempt to 61 | generate. The maximum total number of temporary file names that 62 | can exist for a given template is 62**6. It should never be 63 | necessary to try all these combinations. Instead if a reasonable 64 | number of names is tried (we define reasonable as 62**3) fail to 65 | give the system administrator the chance to remove the problems. */ 66 | #define ATTEMPTS_MIN (62 * 62 * 62) 67 | 68 | /* The number of times to attempt to generate a temporary file. To 69 | conform to POSIX, this must be no smaller than TMP_MAX. */ 70 | #if ATTEMPTS_MIN < TMP_MAX 71 | unsigned int attempts = TMP_MAX; 72 | #else 73 | unsigned int attempts = ATTEMPTS_MIN; 74 | #endif 75 | 76 | len = strlen (tmpl); 77 | if (len < 6 || strcmp (&tmpl[len - 6], "XXXXXX")) 78 | { 79 | errno = EINVAL; 80 | return -1; 81 | } 82 | 83 | /* This is where the Xs start. */ 84 | XXXXXX = &tmpl[len - 6]; 85 | 86 | /* Get some more or less random data. */ 87 | { 88 | SYSTEMTIME stNow; 89 | FILETIME ftNow; 90 | 91 | // get system time 92 | GetSystemTime(&stNow); 93 | stNow.wMilliseconds = 500; 94 | if (!SystemTimeToFileTime(&stNow, &ftNow)) 95 | { 96 | errno = -1; 97 | return -1; 98 | } 99 | 100 | random_time_bits = (((unsigned long long)ftNow.dwHighDateTime << 32) 101 | | (unsigned long long)ftNow.dwLowDateTime); 102 | } 103 | value += random_time_bits ^ (unsigned long long)GetCurrentThreadId (); 104 | 105 | for (count = 0; count < attempts; value += 7777, ++count) 106 | { 107 | unsigned long long v = value; 108 | 109 | /* Fill in the random bits. */ 110 | XXXXXX[0] = letters[v % 62]; 111 | v /= 62; 112 | XXXXXX[1] = letters[v % 62]; 113 | v /= 62; 114 | XXXXXX[2] = letters[v % 62]; 115 | v /= 62; 116 | XXXXXX[3] = letters[v % 62]; 117 | v /= 62; 118 | XXXXXX[4] = letters[v % 62]; 119 | v /= 62; 120 | XXXXXX[5] = letters[v % 62]; 121 | 122 | fd = open (tmpl, O_RDWR | O_CREAT | O_EXCL | O_BINARY, 0600); 123 | if (fd >= 0) 124 | { 125 | errno = save_errno; 126 | return fd; 127 | } 128 | else if (errno != EEXIST) 129 | return -1; 130 | } 131 | 132 | /* We got out of the loop because we ran out of combinations to try. */ 133 | errno = EEXIST; 134 | return -1; 135 | } 136 | 137 | /* Not thread safe, but I don't care. */ 138 | char *basename(char *input) { 139 | static char output[512]; 140 | char ext[256]; 141 | 142 | _splitpath(input, NULL, NULL, output, ext); 143 | strcat(output, ext); 144 | return output; 145 | } 146 | 147 | /* Really? rename() won't overwrite existing files on Windows? */ 148 | int my_rename(const char *old, const char *new) { 149 | if(!MoveFileEx(old, new, MOVEFILE_REPLACE_EXISTING | 150 | MOVEFILE_WRITE_THROUGH | MOVEFILE_COPY_ALLOWED)) 151 | return -1; 152 | 153 | return 0; 154 | } 155 | 156 | int utimes(const char *path, const struct timeval tv[2]) { 157 | struct _utimbuf times; 158 | 159 | times.actime = tv[0].tv_sec; 160 | times.modtime = tv[1].tv_sec; 161 | 162 | return _utime(path, ×); 163 | } 164 | 165 | #endif 166 | -------------------------------------------------------------------------------- /pso_artool/windows_compat.h: -------------------------------------------------------------------------------- 1 | /* 2 | Sylverant PSO Tools 3 | PSO Archive Tool 4 | Copyright (C) 2016 Lawrence Sebald 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License version 3 8 | as published by the Free Software Foundation. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #ifndef WINDOWS_COMPAT_H 20 | 21 | #include 22 | 23 | int mkstemp(char *tmpl); 24 | char *basename(char *input); 25 | int my_rename(const char *old, const char *new); 26 | int utimes(const char *path, const struct timeval tv[2]); 27 | 28 | #endif /* !WINDOWS_COMPAT_H */ 29 | -------------------------------------------------------------------------------- /quest_enemies/quest_enemies.c: -------------------------------------------------------------------------------- 1 | /* 2 | Sylverant PSO Tools 3 | Quest Enemy Parser 4 | Copyright (C) 2012, 2013, 2014 Lawrence Sebald 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License version 3 8 | as published by the Free Software Foundation. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "quest_enemies.h" 25 | #include "packets.h" 26 | 27 | static int version = CLIENT_VERSION_DC; 28 | static int episode = 1; 29 | static int compressed = 1; 30 | static const char *filename; 31 | 32 | /* Print information about this program to stdout. */ 33 | static void print_program_info(void) { 34 | #if defined(VERSION) 35 | printf("Sylverant Quest Enemy Parser version %s\n", VERSION); 36 | #elif defined(SVN_REVISION) 37 | printf("Sylverant Quest Enemy Parser SVN revision: %s\n", SVN_REVISION); 38 | #else 39 | printf("Sylverant Quest Enemy Parser\n"); 40 | #endif 41 | printf("Copyright (C) 2012, 2013, 2014 Lawrence Sebald\n\n"); 42 | printf("This program is free software: you can redistribute it and/or\n" 43 | "modify it under the terms of the GNU Affero General Public\n" 44 | "License version 3 as published by the Free Software Foundation.\n\n" 45 | "This program is distributed in the hope that it will be useful,\n" 46 | "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" 47 | "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" 48 | "GNU General Public License for more details.\n\n" 49 | "You should have received a copy of the GNU Affero General Public\n" 50 | "License along with this program. If not, see " 51 | ".\n"); 52 | } 53 | 54 | /* Print help to the user to stdout. */ 55 | static void print_help(const char *bin) { 56 | printf("Usage: %s [arguments] quest_file\n" 57 | "-----------------------------------------------------------------\n" 58 | "--help Print this help and exit\n" 59 | "--version Print version info and exit\n" 60 | "--uncompressed The .dat file specified is uncompressed. This\n" 61 | " option is ignored when parsing a .qst file.\n" 62 | "--dc Quest specified is for Dreamcast\n" 63 | "--pc Quest specified is for PSO for PC\n" 64 | "--gc Quest specified is for Gamecube\n" 65 | "--bb Quest specified is for PSO Blue Burst\n" 66 | "--ep1 Quest specified is for Episode I\n" 67 | "--ep2 Quest specified is for Episode II\n\n" 68 | "If an episode is not specified, the quest is assumed to be for\n" 69 | "Episode I.\n" 70 | "If a version of the game is not specified, the quest is assumed\n" 71 | "to be for the Dreamcast version of the game.\n\n" 72 | "The quest file can be a Schtserv-style .qst file, a PRS\n" 73 | "compressed .dat file from the quest, or an uncompressed .dat\n" 74 | "file. If using an uncompressed .dat file, make sure to specify\n" 75 | "the relevant command line option to ensure the file is parsed\n" 76 | "correctly.\n", bin); 77 | } 78 | 79 | /* Parse any command-line arguments passed in. */ 80 | static void parse_command_line(int argc, char *argv[]) { 81 | int i; 82 | 83 | if(argc < 2) { 84 | print_help(argv[0]); 85 | exit(EXIT_FAILURE); 86 | } 87 | 88 | for(i = 1; i < argc - 1; ++i) { 89 | if(!strcmp(argv[i], "--version")) { 90 | print_program_info(); 91 | exit(EXIT_SUCCESS); 92 | } 93 | else if(!strcmp(argv[i], "--help")) { 94 | print_help(argv[0]); 95 | exit(EXIT_SUCCESS); 96 | } 97 | else if(!strcmp(argv[i], "--dc")) { 98 | version = CLIENT_VERSION_DC; 99 | } 100 | else if(!strcmp(argv[i], "--pc")) { 101 | version = CLIENT_VERSION_PC; 102 | } 103 | else if(!strcmp(argv[i], "--gc")) { 104 | version = CLIENT_VERSION_GC; 105 | } 106 | else if(!strcmp(argv[i], "--bb")) { 107 | version = CLIENT_VERSION_BB; 108 | } 109 | else if(!strcmp(argv[i], "--ep1")) { 110 | episode = 1; 111 | } 112 | else if(!strcmp(argv[i], "--ep2")) { 113 | episode = 2; 114 | } 115 | else if(!strcmp(argv[i], "--uncompressed")) { 116 | compressed = 0; 117 | } 118 | else { 119 | printf("Illegal command line argument: %s\n", argv[i]); 120 | print_help(argv[0]); 121 | exit(EXIT_FAILURE); 122 | } 123 | } 124 | 125 | /* Save the file we'll be working with. */ 126 | filename = argv[argc - 1]; 127 | } 128 | 129 | int main(int argc, char *argv[]) { 130 | uint8_t *dat = NULL; 131 | uint32_t sz, ocnt, area; 132 | int alt, idx = 0, i, type = 0; 133 | const quest_dat_hdr_t *ptrs[2][18] = { { 0 } }; 134 | const quest_dat_hdr_t *hdr; 135 | 136 | /* Parse the command line... */ 137 | parse_command_line(argc, argv); 138 | 139 | /* See if we got a .qst file a .dat file. */ 140 | type = is_qst(filename, version); 141 | 142 | if(type == 1) { 143 | dat = read_qst(filename, &sz, version); 144 | } 145 | else if(!type) { 146 | dat = read_dat(filename, &sz, compressed); 147 | } 148 | 149 | if(!dat) { 150 | printf("Confused by earlier errors, bailing out.\n"); 151 | return -1; 152 | } 153 | 154 | parse_quest_objects(dat, sz, &ocnt, ptrs); 155 | printf("Found %d objects\n", (int)ocnt); 156 | 157 | for(i = 0; i < 18; ++i) { 158 | if((hdr = ptrs[1][i])) { 159 | /* XXXX: Ugly! */ 160 | sz = LE32(hdr->size); 161 | area = LE32(hdr->area); 162 | alt = 0; 163 | 164 | if((episode == 3 && area > 5) || (episode == 2 && area > 15)) 165 | alt = 1; 166 | 167 | if(parse_map((map_enemy_t *)(hdr->data), sz / sizeof(map_enemy_t), 168 | episode, alt, &idx, (int)area)) { 169 | printf("Cannot parse map!\n"); 170 | return -4; 171 | } 172 | } 173 | } 174 | 175 | return 0; 176 | } 177 | -------------------------------------------------------------------------------- /quest_enemies/quest_enemies.h: -------------------------------------------------------------------------------- 1 | /* 2 | Sylverant PSO Tools 3 | Quest Enemy Parser 4 | Copyright (C) 2014 Lawrence Sebald 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License version 3 8 | as published by the Free Software Foundation. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #ifndef QUEST_ENEMIES_H 20 | #define QUEST_ENEMIES_H 21 | 22 | #include 23 | 24 | #define CLIENT_VERSION_DC 0 25 | #define CLIENT_VERSION_PC 1 26 | #define CLIENT_VERSION_GC 2 27 | #define CLIENT_VERSION_BB 3 28 | 29 | #ifdef PACKED 30 | #undef PACKED 31 | #endif 32 | 33 | #define PACKED __attribute__((packed)) 34 | 35 | /* Header for sections of the .dat files for quests. */ 36 | typedef struct quest_dat_hdr { 37 | uint32_t obj_type; 38 | uint32_t next_hdr; 39 | uint32_t area; 40 | uint32_t size; 41 | uint8_t data[]; 42 | } PACKED quest_dat_hdr_t; 43 | 44 | /* Enemy data in the map files. This the same as the ENEMY_ENTRY struct from 45 | newserv. */ 46 | typedef struct map_enemy { 47 | uint32_t base; 48 | uint16_t reserved0; 49 | uint16_t num_clones; 50 | uint32_t reserved[11]; 51 | uint32_t reserved12; 52 | uint32_t reserved13; 53 | uint32_t reserved14; 54 | uint32_t skin; 55 | uint32_t reserved15; 56 | } PACKED map_enemy_t; 57 | 58 | /* Object data in the map object files. */ 59 | typedef struct map_object { 60 | uint32_t skin; 61 | uint32_t unk1; 62 | uint32_t unk2; 63 | uint32_t obj_id; 64 | float x; 65 | float y; 66 | float z; 67 | uint32_t rpl; 68 | uint32_t rotation; 69 | uint32_t unk3; 70 | uint32_t unk4; 71 | /* Everything beyond this point depends on the object type. */ 72 | union { 73 | float sp[6]; 74 | uint32_t dword[6]; 75 | }; 76 | } PACKED map_object_t; 77 | 78 | #undef PACKED 79 | 80 | /* In quests.c */ 81 | int is_qst(const char *fn, int ver); 82 | uint8_t *read_dat(const char *fn, uint32_t *osz, int comp); 83 | uint8_t *read_qst(const char *fn, uint32_t *osz, int ver); 84 | 85 | int parse_map(map_enemy_t *en, int en_ct, int ep, int alt, int *idx, int map); 86 | void parse_quest_objects(const uint8_t *data, uint32_t len, uint32_t *obj_cnt, 87 | const quest_dat_hdr_t *ptrs[2][18]); 88 | 89 | #endif /* !QUEST_ENEMIES_H */ 90 | -------------------------------------------------------------------------------- /xboxdlqconv/xboxdlqconv.c: -------------------------------------------------------------------------------- 1 | /* 2 | Sylverant PSO Tools 3 | Xbox Download Quest Converter 4 | Copyright (C) 2021, 2023 Lawrence Sebald 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License version 3 8 | as published by the Free Software Foundation. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | /* This program will convert a QEdit .qst file in Gamecube downloadable quest 20 | format for use on the Xbox version of the game. */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__) 29 | #define LE16(x) (((x >> 8) & 0xFF) | ((x & 0xFF) << 8)) 30 | #define LE32(x) (((x >> 24) & 0x00FF) | \ 31 | ((x >> 8) & 0xFF00) | \ 32 | ((x & 0xFF00) << 8) | \ 33 | ((x & 0x00FF) << 24)) 34 | #define LE64(x) (((x >> 56) & 0x000000FF) | \ 35 | ((x >> 40) & 0x0000FF00) | \ 36 | ((x >> 24) & 0x00FF0000) | \ 37 | ((x >> 8) & 0xFF000000) | \ 38 | ((x & 0xFF000000) << 8) | \ 39 | ((x & 0x00FF0000) << 24) | \ 40 | ((x & 0x0000FF00) << 40) | \ 41 | ((x & 0x000000FF) << 56)) 42 | #else 43 | #define LE16(x) x 44 | #define LE32(x) x 45 | #define LE64(x) x 46 | #endif 47 | 48 | #ifdef PACKED 49 | #undef PACKED 50 | #endif 51 | 52 | #define PACKED __attribute__((packed)) 53 | 54 | typedef struct dc_pkt_hdr { 55 | uint8_t pkt_type; 56 | uint8_t flags; 57 | uint16_t pkt_len; 58 | } PACKED dc_pkt_hdr_t; 59 | 60 | typedef struct gc_quest_file { 61 | dc_pkt_hdr_t hdr; 62 | char name[32]; 63 | uint16_t unused; 64 | uint16_t flags; 65 | char filename[16]; 66 | uint32_t length; 67 | } PACKED gc_quest_file_pkt; 68 | 69 | typedef struct xbox_quest_file { 70 | dc_pkt_hdr_t hdr; 71 | char name[32]; 72 | uint16_t quest_id; 73 | uint16_t flags; 74 | char filename[16]; 75 | uint32_t length; 76 | char xbox_filename[16]; 77 | uint16_t quest_id2; 78 | uint16_t flags2; /* ??? 0x00 0x30 */ 79 | uint32_t unused2; 80 | } PACKED xbox_quest_file_pkt; 81 | 82 | typedef struct dc_quest_chunk { 83 | dc_pkt_hdr_t hdr; 84 | char filename[16]; 85 | char data[1024]; 86 | uint32_t length; 87 | } PACKED dc_quest_chunk_pkt; 88 | 89 | static int write_xbox_hdr(FILE *fp, gc_quest_file_pkt *gc, 90 | unsigned long quest_id, unsigned long ep, 91 | char lang) { 92 | xbox_quest_file_pkt xb; 93 | int isbin = 0; 94 | 95 | if(ep == 2) 96 | ep = 256; 97 | else 98 | ep = 0; 99 | 100 | if(strstr(gc->filename, ".bin")) 101 | isbin = 1; 102 | 103 | memset(&xb, 0, sizeof(xbox_quest_file_pkt)); 104 | xb.hdr.pkt_type = 0xA6; 105 | xb.hdr.flags = (uint8_t)quest_id; 106 | xb.hdr.pkt_len = LE16(0x0054); 107 | 108 | memcpy(xb.name, gc->name, 32); 109 | xb.quest_id = LE16(quest_id | ep); 110 | if(isbin) 111 | sprintf(xb.filename, "quest%lu.bin", quest_id | ep); 112 | else 113 | sprintf(xb.filename, "quest%lu.dat", quest_id | ep); 114 | xb.length = gc->length; 115 | 116 | sprintf(xb.xbox_filename, "quest%lu_%c.dat", quest_id | ep, lang); 117 | xb.quest_id2 = LE16(quest_id | ep); 118 | xb.flags2 = LE16(0x3000); 119 | 120 | return fwrite(&xb, sizeof(xbox_quest_file_pkt), 1, fp); 121 | } 122 | 123 | static int copy_chunks(FILE *in, FILE *out, unsigned long qid, 124 | unsigned long ep) { 125 | dc_quest_chunk_pkt pkt; 126 | char fn[17]; 127 | uint16_t len; 128 | 129 | if(ep == 2) 130 | ep = 256; 131 | else 132 | ep = 0; 133 | 134 | memset(fn, 0, sizeof(fn)); 135 | 136 | while(fread(&pkt.hdr, 1, sizeof(dc_pkt_hdr_t), in) == sizeof(dc_pkt_hdr_t)) { 137 | len = LE16(pkt.hdr.pkt_len) - sizeof(dc_pkt_hdr_t); 138 | if(fread(pkt.filename, 1, len, in) != (ssize_t)len) { 139 | perror("Error reading file data"); 140 | return -1; 141 | } 142 | 143 | memcpy(fn, pkt.filename, 16); 144 | if(strstr(fn, ".bin")) 145 | sprintf(pkt.filename, "quest%lu.bin", qid | ep); 146 | else 147 | sprintf(pkt.filename, "quest%lu.dat", qid | ep); 148 | 149 | len += sizeof(dc_pkt_hdr_t); 150 | if(fwrite(&pkt, 1, len, out) != (ssize_t)len) { 151 | perror("Error copying file data"); 152 | return -1; 153 | } 154 | } 155 | 156 | return 0; 157 | } 158 | 159 | int main(int argc, char *argv[]) { 160 | FILE *in, *out; 161 | gc_quest_file_pkt gc; 162 | char lang; 163 | unsigned long qid, ep; 164 | int rv; 165 | 166 | if(argc != 6) { 167 | printf("Usage: %s input output quest_id episode l\n", argv[0]); 168 | printf("Where l is letter representing a language (j, e, f, s, g)\n"); 169 | return 1; 170 | } 171 | 172 | errno = 0; 173 | qid = strtoul(argv[3], NULL, 0); 174 | if(errno) { 175 | perror("Cannot read quest id"); 176 | return 1; 177 | } 178 | 179 | if(qid > 255) { 180 | printf("Quest ID '%s' is invalid, must be less than 255.", argv[3]); 181 | return 1; 182 | } 183 | 184 | errno = 0; 185 | ep = strtoul(argv[4], NULL, 0); 186 | if(errno) { 187 | perror("Cannot read episode"); 188 | return 1; 189 | } 190 | 191 | if(ep != 1 && ep != 2) { 192 | printf("Episode '%s' is invalid, must be 1 or 2.", argv[4]); 193 | return 1; 194 | } 195 | 196 | if(strlen(argv[5]) != 1) { 197 | printf("Language code '%s' is invalid\n", argv[4]); 198 | return 1; 199 | } 200 | 201 | if(argv[5][0] != 'j' && argv[5][0] != 'e' && argv[5][0] != 'f' && 202 | argv[5][0] != 's' && argv[5][0] != 'g') { 203 | printf("Language code '%s' is invalid\n", argv[5]); 204 | return 1; 205 | } 206 | 207 | lang = argv[5][0]; 208 | 209 | if(!(in = fopen(argv[1], "rb"))) { 210 | perror("Cannot open input file"); 211 | return 1; 212 | } 213 | 214 | if(!(out = fopen(argv[2], "wb"))) { 215 | perror("Cannot open output file"); 216 | fclose(in); 217 | return 1; 218 | } 219 | 220 | if(fread(&gc, sizeof(gc_quest_file_pkt), 1, in) != 1) { 221 | printf("Cannot read from input file\n"); 222 | fclose(out); 223 | fclose(in); 224 | return 1; 225 | } 226 | 227 | if(write_xbox_hdr(out, &gc, qid, ep, lang) != 1) { 228 | printf("Cannot write to output file\n"); 229 | fclose(out); 230 | fclose(in); 231 | return 1; 232 | } 233 | 234 | if(fread(&gc, sizeof(gc_quest_file_pkt), 1, in) != 1) { 235 | printf("Cannot read from input file\n"); 236 | fclose(out); 237 | fclose(in); 238 | return 1; 239 | } 240 | 241 | if(write_xbox_hdr(out, &gc, qid, ep, lang) != 1) { 242 | printf("Cannot write to output file\n"); 243 | fclose(out); 244 | fclose(in); 245 | return 1; 246 | } 247 | 248 | rv = copy_chunks(in, out, qid, ep); 249 | fclose(out); 250 | fclose(in); 251 | 252 | return rv; 253 | } 254 | --------------------------------------------------------------------------------