[sylverant] r757 committed - Add a small afs/gsl archive program.

5 views
Skip to first unread message

sylv...@googlecode.com

unread,
Nov 19, 2014, 2:44:14 PM11/19/14
to sylverant-...@googlegroups.com
Revision: 757
Author: blue...@gmail.com
Date: Wed Nov 19 19:43:59 2014 UTC
Log: Add a small afs/gsl archive program.

https://code.google.com/p/sylverant/source/detail?r=757

Added:
/trunk/pso_tools/pso_artool
/trunk/pso_tools/pso_artool/Makefile
/trunk/pso_tools/pso_artool/Makefile.win32
/trunk/pso_tools/pso_artool/afs.c
/trunk/pso_tools/pso_artool/afs.txt
/trunk/pso_tools/pso_artool/artool.c
/trunk/pso_tools/pso_artool/gsl.c
/trunk/pso_tools/pso_artool/gsl.txt
/trunk/pso_tools/pso_artool/windows_compat.c

=======================================
--- /dev/null
+++ /trunk/pso_tools/pso_artool/Makefile Wed Nov 19 19:43:59 2014 UTC
@@ -0,0 +1,12 @@
+# *nix Makefile.
+# Should build with any standardish C99-supporting compiler.
+
+all: pso_artool
+
+pso_artool:
+ $(CC) -o pso_artool artool.c afs.c gsl.c
+
+.PHONY: clean
+
+clean:
+ -rm -fr pso_artool *.o *.dSYM
=======================================
--- /dev/null
+++ /trunk/pso_tools/pso_artool/Makefile.win32 Wed Nov 19 19:43:59 2014 UTC
@@ -0,0 +1,15 @@
+# Windows Makefile.
+# Build with nmake from a VS command prompt:
+# nmake /f Makefile.win32 nodebug=1
+
+!include <win32.mak>
+
+all: pso_artool.exe
+
+OBJS = artool.obj afs.obj gsl.obj windows_compat.obj
+
+.c.obj:
+ $(cc) $(cdebug) $(cflags) $(cvars) $*.c /D_CRT_SECURE_NO_WARNINGS
+
+pso_dns.exe: $(OBJS)
+ $(link) $(ldebug) $(conflags) -out:pso_dns.exe $(OBJS) $(conlibs)
=======================================
--- /dev/null
+++ /trunk/pso_tools/pso_artool/afs.c Wed Nov 19 19:43:59 2014 UTC
@@ -0,0 +1,953 @@
+/*
+ Sylverant PSO Tools
+ PSO Archive Tool
+ Copyright (C) 2014 Lawrence Sebald
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License version 3
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public
License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* This code extracts AFS archives and performs various other tasks on AFS
+ files. Note that the AFS archive format doesn't actually save the names
of
+ the files stored in it, so there's no way to know what the names of the
files
+ should be on extraction (so we punt by just naming them the name of the
input
+ file, with an extension for the index within the file). */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <sys/stat.h>
+
+#ifndef _WIN32
+#include <unistd.h>
+#include <inttypes.h>
+#endif
+
+static uint8_t xbuf[512];
+
+struct delete_cxt {
+ FILE *fp;
+ uint32_t item_count;
+ uint32_t *items;
+ long fpos;
+ long wpos;
+ uint32_t copied_files;
+};
+
+struct update_cxt {
+ FILE *fp;
+ uint32_t item;
+ const char *fn;
+ long fpos;
+ long wpos;
+};
+
+static int digits(uint32_t n) {
+ int r = 1;
+ while(n /= 10) ++r;
+ return r;
+}
+
+static int copy_file(FILE *dst, FILE *src, uint32_t size) {
+ /* Read in the file in 512-byte chunks, writing each one out to the
+ output file (incuding the last chunk, which may be less than 512
+ bytes in length). */
+ while(size > 512) {
+ if(fread(xbuf, 1, 512, src) != 512) {
+ printf("Error reading file: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if(fwrite(xbuf, 1, 512, dst) != 512) {
+ printf("Error writing file: %s\n", strerror(errno));
+ return -2;
+ }
+
+ size -= 512;
+ }
+
+ if(size) {
+ if(fread(xbuf, 1, size, src) != size) {
+ printf("Error reading file: %s\n", strerror(errno));
+ return -3;
+ }
+
+ if(fwrite(xbuf, 1, size, dst) != size) {
+ printf("Error writing file: %s\n", strerror(errno));
+ return -4;
+ }
+ }
+
+ return 0;
+}
+
+static long pad_file(FILE *fp, int boundary) {
+ long pos = ftell(fp);
+ uint8_t tmp = 0;
+
+ /* If we aren't actually padding, don't do anything. */
+ if(boundary <= 0)
+ return pos;
+
+ pos = (pos & ~(boundary - 1)) + boundary;
+
+ if(fseek(fp, pos - 1, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if(fwrite(&tmp, 1, 1, fp) != 1) {
+ printf("Cannot write to archive: %s\n", strerror(errno));
+ return -1;
+ }
+
+ return pos;
+}
+
+static FILE *open_afs(const char *fn, uint32_t *entries) {
+ FILE *fp;
+ uint8_t buf[4];
+
+ /* Open up the file */
+ if(!(fp = fopen(fn, "rb"))) {
+ printf("Cannot open %s: %s\n", fn, strerror(errno));
+ return NULL;
+ }
+
+ /* Make sure that it looks like a sane AFS file. */
+ if(fread(buf, 1, 4, fp) != 4) {
+ printf("Error reading file %s: %s\n", fn, strerror(errno));
+ fclose(fp);
+ return NULL;
+ }
+
+ if(buf[0] != 0x41 || buf[1] != 0x46 || buf[2] != 0x53 || buf[3] !=
0x00) {
+ printf("%s is not an AFS archive!\n", fn);
+ fclose(fp);
+ return NULL;
+ }
+
+ /* Read the number of entries. */
+ if(fread(buf, 1, 4, fp) != 4) {
+ printf("Error reading file %s: %s\n", fn, strerror(errno));
+ fclose(fp);
+ return NULL;
+ }
+
+ *entries = (buf[0]) | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
+ return fp;
+}
+
+static int scan_afs(const char *fn, int (*p)(FILE *fp, uint32_t i,
uint32_t cnt,
+ uint32_t sz, void *d),
+ void *userdata) {
+ FILE *fp;
+ uint8_t buf[4];
+ int rv = 0;
+ uint32_t entries, offset, size, i;
+ long next;
+
+ /* Open up the file */
+ if(!(fp = open_afs(fn, &entries)))
+ return -1;
+
+ /* Since AFS files should have a 16-bit entry count (I think), this
should
+ work fine. */
+ rv = (int)entries;
+
+ /* Read in each file in the archive, writing each one out. */
+ for(i = 0; i < entries; ++i) {
+ /* Figure out where the next file starts in the archive. */
+ if(fread(buf, 1, 4, fp) != 4) {
+ printf("Error reading file %s: %s\n", fn, strerror(errno));
+ rv = -2;
+ goto out;
+ }
+
+ offset = (buf[0]) | (buf[1] << 8) | (buf[2] << 16) | (buf[3] <<
24);
+
+ /* Figure out the size of the next file. */
+ if(fread(buf, 1, 4, fp) != 4) {
+ printf("Error reading file %s: %s\n", fn, strerror(errno));
+ rv = -3;
+ goto out;
+ }
+
+ size = (buf[0]) | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
+ next = ftell(fp);
+
+ if(fseek(fp, (long)offset, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ rv = -4;
+ goto out;
+ }
+
+ /* Call the callback function. */
+ if(p(fp, i, entries, size, userdata)) {
+ rv = -5;
+ goto out;
+ }
+
+ /* Move back to the file table to go onto the next file. */
+ if(fseek(fp, next, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ rv = -6;
+ goto out;
+ }
+ }
+
+out:
+ fclose(fp);
+ return rv;
+}
+
+static int add_files_to_afs(FILE *ofp, long fpos, long wpos,
+ const char *files[], uint32_t count,
+ long *rfpos, long *rwpos) {
+ uint32_t i;
+ FILE *ifp;
+ uint32_t size;
+ uint8_t buf[8];
+
+ /* Scan through each entry, writing the file to the archive. */
+ for(i = 0; i < count; ++i) {
+ /* Open the input file. */
+ if(!(ifp = fopen(files[i], "rb"))) {
+ printf("Cannot open file '%s': %s\n", files[i],
strerror(errno));
+ return -1;
+ }
+
+ /* Figure out its size. */
+ if(fseek(ifp, 0, SEEK_END)) {
+ printf("Seek error: %s\n", strerror(errno));
+ fclose(ifp);
+ return -2;
+ }
+
+ size = (uint32_t)ftell(ifp);
+
+ if(fseek(ifp, 0, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ fclose(ifp);
+ return -3;
+ }
+
+ /* Write the file's information into the file table. */
+ buf[0] = (uint8_t)(wpos);
+ buf[1] = (uint8_t)(wpos >> 8);
+ buf[2] = (uint8_t)(wpos >> 16);
+ buf[3] = (uint8_t)(wpos >> 24);
+ buf[4] = (uint8_t)(size);
+ buf[5] = (uint8_t)(size >> 8);
+ buf[6] = (uint8_t)(size >> 16);
+ buf[7] = (uint8_t)(size >> 24);
+
+ if(fwrite(buf, 1, 8, ofp) != 8) {
+ printf("Cannot write to archive: %s\n", strerror(errno));
+ fclose(ifp);
+ return -4;
+ }
+
+ fpos = ftell(ofp);
+
+ /* Write the file itself to the archive. */
+ if(fseek(ofp, wpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ fclose(ifp);
+ return -5;
+ }
+
+ if(copy_file(ofp, ifp, size)) {
+ fclose(ifp);
+ return -6;
+ }
+
+ /* Add padding, as needed. */
+ if((wpos = pad_file(ofp, 2048)) == -1) {
+ fclose(ifp);
+ return -7;
+ }
+
+ /* Rewind back to the file table for the next entry. */
+ if(fseek(ofp, fpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ fclose(ifp);
+ return -8;
+ }
+
+ /* Close the input file. */
+ fclose(ifp);
+ }
+
+ if(rfpos)
+ *rfpos = fpos;
+
+ if(rwpos)
+ *rwpos = wpos;
+
+ return 0;
+}
+
+static int print_file_info(FILE *fp, uint32_t i, uint32_t cnt, uint32_t sz,
+ void *d) {
+ int dg = digits(cnt);
+ uint32_t offset = (uint32_t)ftell(fp);
+
+#ifndef _WIN32
+ printf("File %*" PRIu32 " @ offset %#010" PRIx32 " size: %"
PRIu32 "\n",
+ dg, i, offset, sz);
+#else
+ printf("File %*d @ offset %#010x size: %d\n", dg, i, offset, sz);
+#endif
+
+ return 0;
+}
+
+static int extract_file(FILE *fp, uint32_t i, uint32_t cnt, uint32_t sz,
+ void *d) {
+ int dg = digits(cnt);
+ const char *fn = (const char *)d;
+ size_t len = strlen(fn);
+ FILE *ofp;
+#ifndef _WIN32
+ char ofn[len + 12];
+#else
+ char ofn[256];
+#endif
+
+ /* Open the output file. */
+#ifndef _WIN32
+ snprintf(ofn, len + 12, "%s.%0*" PRIu32, fn, dg, i);
+#else
+ sprintf_s(ofn, 256, "%s.%0*d", fn, dg, i);
+#endif
+
+ if(!(ofp = fopen(ofn, "wb"))) {
+ printf("Cannot open file %s for write: %s\n", ofn,
strerror(errno));
+ return -1;
+ }
+
+ /* Copy the data out into its new file. */
+ if(copy_file(ofp, fp, sz)) {
+ fclose(ofp);
+ return -2;
+ }
+
+ /* We're done with this file, return to the scan function. */
+ fclose(ofp);
+ return 0;
+}
+
+static int copy_file_cb(FILE *fp, uint32_t i, uint32_t cnt, uint32_t sz,
+ void *d) {
+ FILE *ofp = (FILE *)d;
+ long fpos = (uint32_t)(i << 3) + 8;
+ uint32_t wpos = (uint32_t)ftell(ofp);
+ uint8_t buf[8];
+
+ if(!i)
+ wpos = 0x80000;
+
+ /* Fill in the header data for the file. */
+ buf[0] = (uint8_t)(wpos);
+ buf[1] = (uint8_t)(wpos >> 8);
+ buf[2] = (uint8_t)(wpos >> 16);
+ buf[3] = (uint8_t)(wpos >> 24);
+ buf[4] = (uint8_t)(sz);
+ buf[5] = (uint8_t)(sz >> 8);
+ buf[6] = (uint8_t)(sz >> 16);
+ buf[7] = (uint8_t)(sz >> 24);
+
+ if(fseek(ofp, fpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if(fwrite(buf, 1, 8, ofp) != 8) {
+ printf("Cannot write to file: %s\n", strerror(errno));
+ return -2;
+ }
+
+ if(fseek(ofp, wpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ return -3;
+ }
+
+ /* Copy the file over from the old archive to the new one. */
+ if(copy_file(ofp, fp, sz))
+ return -4;
+
+ /* Add padding, as needed. */
+ if(pad_file(ofp, 2048) < 0)
+ return -5;
+
+ return 0;
+}
+
+static int copy_filtered(FILE *fp, uint32_t i, uint32_t cnt, uint32_t sz,
+ void *d) {
+ struct delete_cxt *cxt = (struct delete_cxt *)d;
+ uint8_t buf[8];
+ uint32_t j;
+
+ /* Look if we're supposed to leave this one off. */
+ for(j = 0; j < cxt->item_count; ++j) {
+ if(cxt->items[j] == i)
+ return 0;
+ }
+
+ /* Fill in the header data for the file. */
+ buf[0] = (uint8_t)(cxt->wpos);
+ buf[1] = (uint8_t)(cxt->wpos >> 8);
+ buf[2] = (uint8_t)(cxt->wpos >> 16);
+ buf[3] = (uint8_t)(cxt->wpos >> 24);
+ buf[4] = (uint8_t)(sz);
+ buf[5] = (uint8_t)(sz >> 8);
+ buf[6] = (uint8_t)(sz >> 16);
+ buf[7] = (uint8_t)(sz >> 24);
+
+ if(fseek(cxt->fp, cxt->fpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if(fwrite(buf, 1, 8, cxt->fp) != 8) {
+ printf("Cannot write to file: %s\n", strerror(errno));
+ return -2;
+ }
+
+ cxt->fpos += 8;
+
+ if(fseek(cxt->fp, cxt->wpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ return -3;
+ }
+
+ /* Copy the file over from the old archive to the new one. */
+ if(copy_file(cxt->fp, fp, sz))
+ return -4;
+
+ /* Add padding, as needed. */
+ if((cxt->wpos = pad_file(cxt->fp, 2048)) < 0)
+ return -5;
+
+ ++cxt->copied_files;
+
+ return 0;
+}
+
+static int copy_update(FILE *fp, uint32_t i, uint32_t cnt, uint32_t sz,
+ void *d) {
+ struct update_cxt *cxt = (struct update_cxt *)d;
+ uint8_t buf[8];
+
+ /* Look if we're supposed to update this one. */
+ if(cxt->item == i) {
+ if(fseek(cxt->fp, cxt->fpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ return -7;
+ }
+
+ if(add_files_to_afs(cxt->fp, cxt->fpos, cxt->wpos, &cxt->fn, 1,
+ &cxt->fpos, &cxt->wpos))
+ return -6;
+
+ return 0;
+ }
+
+ /* Fill in the header data for the file. */
+ buf[0] = (uint8_t)(cxt->wpos);
+ buf[1] = (uint8_t)(cxt->wpos >> 8);
+ buf[2] = (uint8_t)(cxt->wpos >> 16);
+ buf[3] = (uint8_t)(cxt->wpos >> 24);
+ buf[4] = (uint8_t)(sz);
+ buf[5] = (uint8_t)(sz >> 8);
+ buf[6] = (uint8_t)(sz >> 16);
+ buf[7] = (uint8_t)(sz >> 24);
+
+ if(fseek(cxt->fp, cxt->fpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if(fwrite(buf, 1, 8, cxt->fp) != 8) {
+ printf("Cannot write to file: %s\n", strerror(errno));
+ return -2;
+ }
+
+ cxt->fpos += 8;
+
+ if(fseek(cxt->fp, cxt->wpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ return -3;
+ }
+
+ /* Copy the file over from the old archive to the new one. */
+ if(copy_file(cxt->fp, fp, sz))
+ return -4;
+
+ /* Add padding, as needed. */
+ if((cxt->wpos = pad_file(cxt->fp, 2048)) < 0)
+ return -5;
+
+ return 0;
+}
+
+int create_afs(const char *fn, const char *files[], uint32_t count) {
+ FILE *ofp;
+ int fd;
+ char tmpfn[16];
+ uint8_t buf[8];
+ long fpos, wpos;
+
+#ifndef _WIN32
+ mode_t mask;
+#endif
+
+ /* Make sure we won't overflow the file table. */
+ if(count > 65535) {
+ printf("Cowardly refusing to make an archive with > 65535
files.\n");
+ return -14;
+ }
+
+ /* Open up a temporary file for writing. */
+ strcpy(tmpfn, "afstoolXXXXXX");
+ if((fd = mkstemp(tmpfn)) < 0) {
+ printf("Cannot create temporary file: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if(!(ofp = fdopen(fd, "wb"))) {
+ printf("Cannot open temporary file: %s\n", strerror(errno));
+ close(fd);
+ unlink(tmpfn);
+ return -2;
+ }
+
+ /* Write the header to the file. */
+ buf[0] = 0x41; /* 'A' */
+ buf[1] = 0x46; /* 'F' */
+ buf[2] = 0x53; /* 'S' */
+ buf[3] = 0x00; /* '\0' */
+ buf[4] = (uint8_t)(count);
+ buf[5] = (uint8_t)(count >> 8);
+ buf[6] = (uint8_t)(count >> 16);
+ buf[7] = (uint8_t)(count >> 24);
+
+ if(fwrite(buf, 1, 8, ofp) != 8) {
+ printf("Cannot write to temporary file: %s\n", strerror(errno));
+ fclose(ofp);
+ unlink(tmpfn);
+ return -3;
+ }
+
+ /* Make space for the file table. */
+ fpos = ftell(ofp);
+
+ if(fseek(ofp, 0x80000, SEEK_SET)) {
+ printf("Cannot create blank file table: %s\n", strerror(errno));
+ fclose(ofp);
+ unlink(tmpfn);
+ return -4;
+ }
+
+ /* Save where we'll write the first file and move back to the file
table. */
+ wpos = ftell(ofp);
+ if(fseek(ofp, fpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ fclose(ofp);
+ unlink(tmpfn);
+ return -5;
+ }
+
+ /* Add all the files to the archive. */
+ if(add_files_to_afs(ofp, fpos, wpos, files, count, NULL, NULL)) {
+ fclose(ofp);
+ unlink(tmpfn);
+ return -6;
+ }
+
+ /* All the files are copied, so move the archive into its place. */
+#ifndef _WIN32
+ mask = umask(0);
+ umask(mask);
+ fchmod(fileno(ofp), (~mask) & 0666);
+#endif
+
+ fclose(ofp);
+ rename(tmpfn, fn);
+
+ return 0;
+}
+
+int add_to_afs(const char *fn, const char *files[], uint32_t count) {
+ FILE *ofp;
+ int fd;
+ char tmpfn[16];
+ uint8_t buf[8];
+ int entries;
+ long fpos, wpos;
+
+#ifndef _WIN32
+ mode_t mask;
+#endif
+
+ /* Open up a temporary file for writing. */
+ strcpy(tmpfn, "afstoolXXXXXX");
+ if((fd = mkstemp(tmpfn)) < 0) {
+ printf("Cannot create temporary file: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if(!(ofp = fdopen(fd, "wb"))) {
+ printf("Cannot open temporary file: %s\n", strerror(errno));
+ close(fd);
+ unlink(tmpfn);
+ return -2;
+ }
+
+ /* Make space for the file table. */
+ fpos = 8;
+
+ if(fseek(ofp, 0x80000, SEEK_SET)) {
+ printf("Cannot create blank file table: %s\n", strerror(errno));
+ fclose(ofp);
+ unlink(tmpfn);
+ return -3;
+ }
+
+ /* Save where we'll write the first file and move back to the file
table. */
+ wpos = ftell(ofp);
+ if(fseek(ofp, fpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ fclose(ofp);
+ unlink(tmpfn);
+ return -4;
+ }
+
+ if((entries = scan_afs(fn, &copy_file_cb, ofp)) < 0) {
+ fclose(ofp);
+ unlink(tmpfn);
+ return -5;
+ }
+
+ /* Kind of a bit late to be checking this, but oh well. */
+ if(entries + count > 65535) {
+ printf("Cowardly refusing to make an archive with > 65535
files.\n");
+ fclose(ofp);
+ unlink(tmpfn);
+ return -6;
+ }
+
+ wpos = ftell(ofp);
+ fpos = (entries << 3) + 8;
+
+ /* Write the header to the file. */
+ buf[0] = 0x41; /* 'A' */
+ buf[1] = 0x46; /* 'F' */
+ buf[2] = 0x53; /* 'S' */
+ buf[3] = 0x00; /* '\0' */
+ buf[4] = (uint8_t)((entries + count));
+ buf[5] = (uint8_t)((entries + count) >> 8);
+ buf[6] = (uint8_t)((entries + count) >> 16);
+ buf[7] = (uint8_t)((entries + count) >> 24);
+
+ if(fseek(ofp, 0, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ fclose(ofp);
+ unlink(tmpfn);
+ return -7;
+ }
+
+ if(fwrite(buf, 1, 8, ofp) != 8) {
+ printf("Cannot write to file: %s\n", strerror(errno));
+ fclose(ofp);
+ unlink(tmpfn);
+ return -8;
+ }
+
+ if(fseek(ofp, fpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ fclose(ofp);
+ unlink(tmpfn);
+ return -9;
+ }
+
+ /* Add all the new files to the archive. */
+ if(add_files_to_afs(ofp, fpos, wpos, files, count, NULL, NULL)) {
+ fclose(ofp);
+ unlink(tmpfn);
+ return -10;
+ }
+
+ /* All the files are copied, so move the archive into its place. */
+#ifndef _WIN32
+ mask = umask(0);
+ umask(mask);
+ fchmod(fileno(ofp), (~mask) & 0666);
+#endif
+
+ fclose(ofp);
+ rename(tmpfn, fn);
+
+ return 0;
+}
+
+int update_afs(const char *fn, const char *fno, const char *file) {
+ int fd;
+ char tmpfn[16];
+ uint8_t buf[8];
+ int entries;
+ struct update_cxt cxt;
+
+#ifndef _WIN32
+ mode_t mask;
+#endif
+
+ /* Parse out all the entries for the context first. */
+ memset(&cxt, 0, sizeof(cxt));
+
+ errno = 0;
+ cxt.item = (uint32_t)strtoul(fno, NULL, 0);
+ cxt.fn = file;
+ if(errno) {
+ printf("%s is not a valid file number.\n", fno);
+ return -2;
+ }
+
+ /* Open up a temporary file for writing. */
+ strcpy(tmpfn, "afstoolXXXXXX");
+ if((fd = mkstemp(tmpfn)) < 0) {
+ printf("Cannot create temporary file: %s\n", strerror(errno));
+ return -3;
+ }
+
+ if(!(cxt.fp = fdopen(fd, "wb"))) {
+ printf("Cannot open temporary file: %s\n", strerror(errno));
+ close(fd);
+ unlink(tmpfn);
+ return -4;
+ }
+
+ /* Make space for the file table. */
+ cxt.fpos = 8;
+
+ if(fseek(cxt.fp, 0x80000, SEEK_SET)) {
+ printf("Cannot create blank file table: %s\n", strerror(errno));
+ fclose(cxt.fp);
+ unlink(tmpfn);
+ return -5;
+ }
+
+ /* Save where we'll write the first file and move back to the file
table. */
+ cxt.wpos = ftell(cxt.fp);
+ if(fseek(cxt.fp, cxt.fpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ fclose(cxt.fp);
+ unlink(tmpfn);
+ return -6;
+ }
+
+ if((entries = scan_afs(fn, &copy_update, &cxt)) < 0) {
+ fclose(cxt.fp);
+ unlink(tmpfn);
+ return -7;
+ }
+
+ if(cxt.item >= entries) {
+#ifndef _WIN32
+ printf("Item out of range: %" PRIu32 "\n", cxt.item);
+#else
+ printf("Item out of range: %d\n", cxt.item);
+#endif
+
+ fclose(cxt.fp);
+ unlink(tmpfn);
+ return -8;
+ }
+
+ /* Write the header to the file. */
+ buf[0] = 0x41; /* 'A' */
+ buf[1] = 0x46; /* 'F' */
+ buf[2] = 0x53; /* 'S' */
+ buf[3] = 0x00; /* '\0' */
+ buf[4] = (uint8_t)(entries);
+ buf[5] = (uint8_t)(entries >> 8);
+ buf[6] = (uint8_t)(entries >> 16);
+ buf[7] = (uint8_t)(entries >> 24);
+
+ if(fseek(cxt.fp, 0, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ fclose(cxt.fp);
+ unlink(tmpfn);
+ return -7;
+ }
+
+ if(fwrite(buf, 1, 8, cxt.fp) != 8) {
+ printf("Cannot write to file: %s\n", strerror(errno));
+ fclose(cxt.fp);
+ unlink(tmpfn);
+ return -8;
+ }
+
+ /* All the files are copied, so move the archive into its place. */
+#ifndef _WIN32
+ mask = umask(0);
+ umask(mask);
+ fchmod(fileno(cxt.fp), (~mask) & 0666);
+#endif
+
+ fclose(cxt.fp);
+ rename(tmpfn, fn);
+
+ return 0;
+}
+
+int delete_from_afs(const char *fn, const char *files[], uint32_t cnt) {
+ int fd;
+ char tmpfn[16];
+ uint8_t buf[8];
+ int entries;
+ struct delete_cxt cxt;
+ uint32_t i;
+
+#ifndef _WIN32
+ mode_t mask;
+#endif
+
+ /* Parse out all the entries for the context first. */
+ memset(&cxt, 0, sizeof(cxt));
+ cxt.item_count = cnt;
+
+ if(!(cxt.items = (uint32_t *)malloc(sizeof(uint32_t) * cnt))) {
+ printf("Cannot allocate memory: %s\n", strerror(errno));
+ return -1;
+ }
+
+ errno = 0;
+ for(i = 0; i < cnt; ++i) {
+ cxt.items[i] = (uint32_t)strtoul(files[i], NULL, 0);
+ if(errno) {
+ printf("%s is not a valid file number.\n", files[i]);
+ free(cxt.items);
+ return -2;
+ }
+ }
+
+ /* Open up a temporary file for writing. */
+ strcpy(tmpfn, "afstoolXXXXXX");
+ if((fd = mkstemp(tmpfn)) < 0) {
+ printf("Cannot create temporary file: %s\n", strerror(errno));
+ free(cxt.items);
+ return -3;
+ }
+
+ if(!(cxt.fp = fdopen(fd, "wb"))) {
+ printf("Cannot open temporary file: %s\n", strerror(errno));
+ close(fd);
+ unlink(tmpfn);
+ free(cxt.items);
+ return -4;
+ }
+
+ /* Make space for the file table. */
+ cxt.fpos = 8;
+
+ if(fseek(cxt.fp, 0x80000, SEEK_SET)) {
+ printf("Cannot create blank file table: %s\n", strerror(errno));
+ fclose(cxt.fp);
+ unlink(tmpfn);
+ free(cxt.items);
+ return -5;
+ }
+
+ /* Save where we'll write the first file and move back to the file
table. */
+ cxt.wpos = ftell(cxt.fp);
+ if(fseek(cxt.fp, cxt.fpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ fclose(cxt.fp);
+ unlink(tmpfn);
+ free(cxt.items);
+ return -6;
+ }
+
+ if((entries = scan_afs(fn, &copy_filtered, &cxt)) < 0) {
+ fclose(cxt.fp);
+ unlink(tmpfn);
+ free(cxt.items);
+ return -7;
+ }
+
+ for(i = 0; i < cnt; ++i) {
+ if(cxt.items[i] >= entries) {
+#ifndef _WIN32
+ printf("Item out of range: %" PRIu32 "\n", cxt.items[i]);
+#else
+ printf("Item out of range: %d\n", cxt.items[i]);
+#endif
+
+ fclose(cxt.fp);
+ unlink(tmpfn);
+ free(cxt.items);
+ return -8;
+ }
+ }
+
+ free(cxt.items);
+
+ /* Write the header to the file. */
+ buf[0] = 0x41; /* 'A' */
+ buf[1] = 0x46; /* 'F' */
+ buf[2] = 0x53; /* 'S' */
+ buf[3] = 0x00; /* '\0' */
+ buf[4] = (uint8_t)(cxt.copied_files);
+ buf[5] = (uint8_t)(cxt.copied_files >> 8);
+ buf[6] = (uint8_t)(cxt.copied_files >> 16);
+ buf[7] = (uint8_t)(cxt.copied_files >> 24);
+
+ if(fseek(cxt.fp, 0, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ fclose(cxt.fp);
+ unlink(tmpfn);
+ return -7;
+ }
+
+ if(fwrite(buf, 1, 8, cxt.fp) != 8) {
+ printf("Cannot write to file: %s\n", strerror(errno));
+ fclose(cxt.fp);
+ unlink(tmpfn);
+ return -8;
+ }
+
+ /* All the files are copied, so move the archive into its place. */
+#ifndef _WIN32
+ mask = umask(0);
+ umask(mask);
+ fchmod(fileno(cxt.fp), (~mask) & 0666);
+#endif
+
+ fclose(cxt.fp);
+ rename(tmpfn, fn);
+
+ return 0;
+}
+
+int print_afs_files(const char *fn) {
+ return scan_afs(fn, &print_file_info, NULL);
+}
+
+int extract_afs(const char *fn) {
+ return scan_afs(fn, &extract_file, (void *)fn);
+}
=======================================
--- /dev/null
+++ /trunk/pso_tools/pso_artool/afs.txt Wed Nov 19 19:43:59 2014 UTC
@@ -0,0 +1,62 @@
+Quick and Dirty AFS Archive Documentation
+-----------------------------------------
+
+--------------------------------------------------------------------------------
+ Introduction
+--------------------------------------------------------------------------------
+
+This text file explains the format of AFS archive files, as used in
Phantasy
+Star Online. These are probably the same as those used by other games as a
+container for multiple ADX files, but I've never looked at any of those
other
+games to say for sure.
+
+The AFS archive file format is exceedingly simplistic and, in many ways,
+inadequate for an archive file format. There is no compression in the file
+format itself, although you can obviously compress AFS archives with
whatever
+compression tool you want if you use them for your own purposes (why you
would
+is not covered here, since there is no good reason to ever use them). The
table
+of files in the archive does not store filenames, so you're on your own to
+figure out what each file is within the archive. The file table is padded
out to
+the (what I assume to be) maximum number of entries within the table.
Based on
+the size of the table, the maximum number of files within one AFS archive
is
+65535, although PSO doesn't tend to store all that many files in one
(ItemPT.afs
+and ItemRT.afs store MANY fewer files than that in the archive, leading to
a lot
+of wasted space for the header -- about 500KB of wasted space, to be
exact). In
+addition, files within the archive are padded out to 2KiB boundaries,
possibly
+to aid in alignment on sector boundaries on a GD-ROM.
+
+Now that that is out of the way, onto the specifics of the file format...
+
+--------------------------------------------------------------------------------
+ File Format
+--------------------------------------------------------------------------------
+
+All multi-byte numeric types used in an AFS archive are stored in
little-endian
+(Intel) byte ordering.
+
+All AFS archives consist of 3 parts, which always appear in the same
order. That
+is a File header, a File table, and the individual files stored in the
archive.
+Of these, the file header and file table are of fixed-length (although, it
seems
+that PSO does handle variable-length file tables just fine). Files are
stored
+one after the other, with padding between as necessary to pad each file to
a
+2KiB boundary.
+
+File Header (8 bytes):
+ 4 bytes - Magic Number to identify the file as an AFS archive
+ 0x00534641 - or 'AFS\0' in ASCII
+ 4 bytes - Number of files stored in the archive
+ I'm assuming the max is 65535 here, so this could be a 2 byte file
count
+ and two bytes of padding, but it's just as easy to assume it's a 4
byte
+ file count
+
+File Table (524280 bytes):
+Each 8 bytes represents a single file in the archive. Unused entries should
+simply contain zeroes. Entries are arranged as follows:
+ 4 bytes - Byte offset within the archive where the file data starts
+ In normal archives, this will always be >= 0x80000, as the file
table
+ and file header will take up to that point in the file.
+ 4 bytes - Byte length of the data of the file.
+
+Archived Files (variable length):
+Each file follows at the positions specified in the file table above.
Files are
+padded to 2KiB boundaries, probably for alignment purposes on a GD-ROM.
=======================================
--- /dev/null
+++ /trunk/pso_tools/pso_artool/artool.c Wed Nov 19 19:43:59 2014 UTC
@@ -0,0 +1,215 @@
+/*
+ Sylverant PSO Tools
+ PSO Archive Tool
+ Copyright (C) 2014 Lawrence Sebald
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License version 3
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public
License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+
+/* Available archive types. */
+#define ARCHIVE_TYPE_NONE -1
+#define ARCHIVE_TYPE_AFS 0
+#define ARCHIVE_TYPE_GSLBE 1
+
+#define ARCHIVE_TYPE_COUNT 2
+
+/* Archive manipulation functions. These should probably go in a header,
but I'm
+ feeling lazy at the moment. */
+int print_afs_files(const char *fn);
+int extract_afs(const char *fn);
+int create_afs(const char *fn, const char *files[], uint32_t count);
+int add_to_afs(const char *fn, const char *files[], uint32_t count);
+int update_afs(const char *fn, const char *fno, const char *path);
+int delete_from_afs(const char *fn, const char *files[], uint32_t cnt);
+
+int print_gsl_files(const char *fn);
+int extract_gsl(const char *fn);
+int create_gsl(const char *fn, const char *files[], uint32_t count);
+int add_to_gsl(const char *fn, const char *files[], uint32_t count);
+int update_gsl(const char *fn, const char *file, const char *path);
+int delete_from_gsl(const char *fn, const char *files[], uint32_t cnt);
+
+/* This looks ugly, but whatever. */
+struct {
+ int (*print)(const char *);
+ int (*extract)(const char *);
+ int (*create)(const char *, const char *[], uint32_t);
+ int (*add)(const char *, const char *[], uint32_t);
+ int (*update)(const char *, const char *, const char *);
+ int (*delete)(const char *, const char *[], uint32_t);
+} archive_funcs[ARCHIVE_TYPE_COUNT] = {
+ { &print_afs_files, &extract_afs, &create_afs, &add_to_afs,
&update_afs,
+ &delete_from_afs },
+ { &print_gsl_files, &extract_gsl, &create_gsl, &add_to_gsl,
&update_gsl,
+ &delete_from_gsl }
+};
+
+/* Print information about this program to stdout. */
+static void print_program_info(void) {
+#if defined(VERSION)
+ printf("Sylverant PSO Archive Tool version %s\n", VERSION);
+#elif defined(SVN_REVISION)
+ printf("Sylverant PSO Archive Tool SVN revision: %s\n", SVN_REVISION);
+#else
+ printf("Sylverant PSO Archive Tool\n");
+#endif
+ printf("Copyright (C) 2014 Lawrence Sebald\n\n");
+ printf("This program is free software: you can redistribute it
and/or\n"
+ "modify it under the terms of the GNU Affero General Public\n"
+ "License version 3 as published by the Free Software
Foundation.\n\n"
+ "This program is distributed in the hope that it will be
useful,\n"
+ "but WITHOUT ANY WARRANTY; without even the implied warranty
of\n"
+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
the\n"
+ "GNU General Public License for more details.\n\n"
+ "You should have received a copy of the GNU Affero General
Public\n"
+ "License along with this program. If not, see "
+ "<http://www.gnu.org/licenses/>.\n");
+}
+
+/* Print help to the user to stdout. */
+static void print_help(const char *bin) {
+ printf("Usage:\n"
+ " %s type operation [operation aguments]\n"
+ "Where type is one of --afs or --gsl. Available operations
and\n"
+ "their arguments are shown below:\n"
+ "To list the files in an archive:\n"
+ " -t archive\n"
+ "To extract an archive:\n"
+ " -x archive\n"
+ "To create an archive:\n"
+ " -c archive file1 [file2 ...]\n"
+ "To add files to an archive:\n"
+ " -r archive file1 [file2 ...]\n"
+ "To update a file in an archive (or replace it with another
file):\n"
+ " -u archive file_in_archive filename\n"
+ "To remove a file from an archive:\n"
+ " --delete archive file1 [file2 ...]\n\n"
+ "Other general operations (which don't require a type):\n"
+ "To print this help message:\n"
+ " --help\n"
+ "To print version information:\n"
+ " --version\n\n", bin);
+ printf("As AFS archives do not store filenames, any files specified in
an\n"
+ "AFS archive are specified by index within the archive (for the
-u\n"
+ "and --delete operations). For other archive formats, you
should\n"
+ "specify the file names within the archive for these
operations.\n");
+}
+
+/* Parse any command-line arguments passed in. */
+static void parse_command_line(int argc, const char *argv[]) {
+ int i = 2;
+ int t = ARCHIVE_TYPE_NONE;
+
+ if(argc < 2) {
+ print_help(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ if(!strcmp(argv[1], "--afs"))
+ t = ARCHIVE_TYPE_AFS;
+ else if(!strcmp(argv[1], "--gsl"))
+ t = ARCHIVE_TYPE_GSLBE;
+ else
+ i = 1;
+
+ if(!strcmp(argv[i], "--version")) {
+ print_program_info();
+ exit(EXIT_SUCCESS);
+ }
+ else if(!strcmp(argv[i], "--help")) {
+ print_help(argv[0]);
+ exit(EXIT_SUCCESS);
+ }
+
+ /* All other operations require an archive type, so if we didn't get
one,
+ then bail out. */
+ if(i == 1) {
+ print_help(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ /* See what they asked us to do. */
+ if(!strcmp(argv[2], "-t")) {
+ if(argc != 4) {
+ print_help(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ if(archive_funcs[t].print(argv[3]) < 0)
+ exit(EXIT_FAILURE);
+ }
+ else if(!strcmp(argv[2], "-x")) {
+ if(argc != 4) {
+ print_help(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ if(archive_funcs[t].extract(argv[3]) < 0)
+ exit(EXIT_FAILURE);
+ }
+ else if(!strcmp(argv[2], "-c")) {
+ if(argc < 5) {
+ print_help(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ if(archive_funcs[t].create(argv[3], argv + 4, argc - 4))
+ exit(EXIT_FAILURE);
+ }
+ else if(!strcmp(argv[2], "-r")) {
+ if(argc < 5) {
+ print_help(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ if(archive_funcs[t].add(argv[3], argv + 4, argc - 4))
+ exit(EXIT_FAILURE);
+ }
+ else if(!strcmp(argv[2], "-u")) {
+ if(argc != 6) {
+ print_help(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ if(archive_funcs[t].update(argv[3], argv[4], argv[5]))
+ exit(EXIT_FAILURE);
+ }
+ else if(!strcmp(argv[2], "--delete")) {
+ if(argc < 5) {
+ print_help(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ if(archive_funcs[t].delete(argv[3], argv + 4, argc - 4))
+ exit(EXIT_FAILURE);
+ }
+ else {
+ printf("Illegal archive operation argument: %s\n", argv[2]);
+ print_help(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, const char *argv[]) {
+ /* Parse the command line... */
+ parse_command_line(argc, argv);
+
+ return 0;
+}
=======================================
--- /dev/null
+++ /trunk/pso_tools/pso_artool/gsl.c Wed Nov 19 19:43:59 2014 UTC
@@ -0,0 +1,896 @@
+/*
+ Sylverant PSO Tools
+ PSO Archive Tool
+ Copyright (C) 2014 Lawrence Sebald
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License version 3
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public
License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* This code extracts GSL archives and performs various other tasks on GSL
+ archive files. */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <sys/stat.h>
+
+#ifndef _WIN32
+#include <inttypes.h>
+#include <unistd.h>
+#include <libgen.h>
+#endif
+
+static uint8_t xbuf[512];
+
+struct delete_cxt {
+ FILE *fp;
+ uint32_t item_count;
+ const char **items;
+ long fpos;
+ long wpos;
+ uint32_t copied_files;
+};
+
+struct update_cxt {
+ FILE *fp;
+ const char *fn;
+ const char *path;
+ long fpos;
+ long wpos;
+};
+
+#ifdef _WIN32
+/* In windows_compat.c */
+char *basename(char *input);
+#endif
+
+static int copy_file(FILE *dst, FILE *src, uint32_t size) {
+ /* Read in the file in 512-byte chunks, writing each one out to the
+ output file (incuding the last chunk, which may be less than 512
+ bytes in length). */
+ while(size > 512) {
+ if(fread(xbuf, 1, 512, src) != 512) {
+ printf("Error reading file: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if(fwrite(xbuf, 1, 512, dst) != 512) {
+ printf("Error writing file: %s\n", strerror(errno));
+ return -2;
+ }
+
+ size -= 512;
+ }
+
+ if(size) {
+ if(fread(xbuf, 1, size, src) != size) {
+ printf("Error reading file: %s\n", strerror(errno));
+ return -3;
+ }
+
+ if(fwrite(xbuf, 1, size, dst) != size) {
+ printf("Error writing file: %s\n", strerror(errno));
+ return -4;
+ }
+ }
+
+ return 0;
+}
+
+static long pad_file(FILE *fp, int boundary) {
+ long pos = ftell(fp);
+ uint8_t tmp = 0;
+
+ /* If we aren't actually padding, don't do anything. */
+ if(boundary <= 0)
+ return pos;
+
+ pos = (pos & ~(boundary - 1)) + boundary;
+
+ if(fseek(fp, pos - 1, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if(fwrite(&tmp, 1, 1, fp) != 1) {
+ printf("Cannot write to archive: %s\n", strerror(errno));
+ return -1;
+ }
+
+ return pos;
+}
+
+static FILE *open_gsl(const char *fn) {
+ FILE *fp;
+
+ /* Open up the file */
+ if(!(fp = fopen(fn, "rb"))) {
+ printf("Cannot open %s: %s\n", fn, strerror(errno));
+ return NULL;
+ }
+
+ /* TODO: Perhaps we should sanity check the file here a bit? Doing so
is a
+ bit more difficult than with an AFS file, since there is no magic
number
+ or anything like that at the top of the file. */
+
+ return fp;
+}
+
+static int scan_gsl(const char *fn, int (*p)(FILE *fp, uint32_t i,
uint32_t sz,
+ const char *fn, void *d),
+ void *userdata) {
+ FILE *fp;
+ uint8_t buf[4];
+ char filename[33];
+ int rv = 0;
+ uint32_t offset, size, i;
+ long next;
+
+ /* Open up the file */
+ if(!(fp = open_gsl(fn)))
+ return -1;
+
+ /* Read in each file in the archive, writing each one out. */
+ for(i = 0; ; ++i) {
+ /* Read the filename. */
+ if(fread(filename, 1, 32, fp) != 32) {
+ printf("Error reading file %s: %s\n", fn, strerror(errno));
+ rv = -2;
+ goto out;
+ }
+
+ /* If we have an empty filename, we've hit the end of the list. */
+ if(filename[0] == '\0') {
+ rv = (int)i;
+ break;
+ }
+
+ filename[32] = '\0';
+
+ /* Figure out where the next file starts in the archive. */
+ if(fread(buf, 1, 4, fp) != 4) {
+ printf("Error reading file %s: %s\n", fn, strerror(errno));
+ rv = -3;
+ goto out;
+ }
+
+ offset = (buf[3]) | (buf[2] << 8) | (buf[1] << 16) | (buf[0] <<
24);
+
+ /* Figure out the size of the next file. */
+ if(fread(buf, 1, 4, fp) != 4) {
+ printf("Error reading file5 %s: %s\n", fn, strerror(errno));
+ rv = -4;
+ goto out;
+ }
+
+ size = (buf[3]) | (buf[2] << 8) | (buf[1] << 16) | (buf[0] << 24);
+
+ /* Seek over the blank padding space. */
+ if(fseek(fp, 8, SEEK_CUR)) {
+ printf("Seek error: %s\n", strerror(errno));
+ rv = -5;
+ goto out;
+ }
+
+ next = ftell(fp);
+
+ if(fseek(fp, (long)offset * 2048, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ rv = -6;
+ goto out;
+ }
+
+ /* Call the callback function. */
+ if(p && p(fp, i, size, filename, userdata)) {
+ rv = -7;
+ goto out;
+ }
+
+ /* Move back to the file table to go onto the next file. */
+ if(fseek(fp, next, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ rv = -8;
+ goto out;
+ }
+ }
+
+out:
+ fclose(fp);
+ return rv;
+}
+
+static int add_files_to_gsl(FILE *ofp, long fpos, long wpos,
+ const char *files[], uint32_t count,
+ long *rfpos, long *rwpos) {
+ uint32_t i;
+ FILE *ifp;
+ uint32_t size;
+ uint8_t buf[32];
+ char *tmp, *filename;
+ size_t fnlen;
+ long wposp;
+
+ /* Scan through each entry, writing the file to the archive. */
+ for(i = 0; i < count; ++i) {
+ /* Open the input file. */
+ if(!(ifp = fopen(files[i], "rb"))) {
+ printf("Cannot open file '%s': %s\n", files[i],
strerror(errno));
+ return -1;
+ }
+
+ /* Figure out its size. */
+ if(fseek(ifp, 0, SEEK_END)) {
+ printf("Seek error: %s\n", strerror(errno));
+ fclose(ifp);
+ return -2;
+ }
+
+ size = (uint32_t)ftell(ifp);
+
+ if(fseek(ifp, 0, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ fclose(ifp);
+ return -3;
+ }
+
+ /* Get the filename from the path. */
+ if(!(tmp = strdup(files[i]))) {
+ printf("Cannot copy filename string: %s\n", strerror(errno));
+ fclose(ifp);
+ return -9;
+ }
+
+ if(!(filename = basename(tmp))) {
+ printf("Cannot find basename of path: %s\n", strerror(errno));
+ free(tmp);
+ fclose(ifp);
+ return -10;
+ }
+
+ if((fnlen = strlen(filename)) >= 32) {
+ printf("File name \"%s\" too long (must be 31 or less
chars)\n",
+ filename);
+ free(tmp);
+ fclose(ifp);
+ return -11;
+ }
+
+ /* Write the filename to the file. */
+ if(fwrite(filename, 1, fnlen, ofp) != fnlen) {
+ printf("Cannot write to archive: %s\n", strerror(errno));
+ free(tmp);
+ fclose(ifp);
+ return -11;
+ }
+
+ memset(buf, 0, 32);
+
+ if(fwrite(buf, 1, 32 - fnlen, ofp) != 32 - fnlen) {
+ printf("Cannot write to archive: %s\n", strerror(errno));
+ free(tmp);
+ fclose(ifp);
+ return -12;
+ }
+
+ /* Free the copied filename. */
+ free(tmp);
+
+ /* Write the file's information into the file table. */
+ wposp = wpos >> 11;
+ buf[0] = (uint8_t)(wposp >> 24);
+ buf[1] = (uint8_t)(wposp >> 16);
+ buf[2] = (uint8_t)(wposp >> 8);
+ buf[3] = (uint8_t)(wposp);
+ buf[4] = (uint8_t)(size >> 24);
+ buf[5] = (uint8_t)(size >> 16);
+ buf[6] = (uint8_t)(size >> 8);
+ buf[7] = (uint8_t)(size);
+ buf[8] = 0;
+ buf[9] = 0;
+ buf[10] = 0;
+ buf[11] = 0;
+ buf[12] = 0;
+ buf[13] = 0;
+ buf[14] = 0;
+ buf[15] = 0;
+
+ if(fwrite(buf, 1, 16, ofp) != 16) {
+ printf("Cannot write to archive: %s\n", strerror(errno));
+ fclose(ifp);
+ return -4;
+ }
+
+ fpos = ftell(ofp);
+
+ /* Write the file itself to the archive. */
+ if(fseek(ofp, wpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ fclose(ifp);
+ return -5;
+ }
+
+ if(copy_file(ofp, ifp, size)) {
+ fclose(ifp);
+ return -6;
+ }
+
+ /* Add padding, as needed. */
+ if((wpos = pad_file(ofp, 2048)) == -1) {
+ fclose(ifp);
+ return -7;
+ }
+
+ /* Rewind back to the file table for the next entry. */
+ if(fseek(ofp, fpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ fclose(ifp);
+ return -8;
+ }
+
+ /* Close the input file. */
+ fclose(ifp);
+ }
+
+ if(rfpos)
+ *rfpos = fpos;
+
+ if(rwpos)
+ *rwpos = wpos;
+
+ return 0;
+}
+
+static int print_file_info(FILE *fp, uint32_t i, uint32_t sz, const char
*fn,
+ void *d) {
+ uint32_t offset = (uint32_t)ftell(fp);
+
+#ifndef _WIN32
+ printf("File %4" PRIu32 " '%s' @ offset %#010" PRIx32 " size: %" PRIu32
+ "\n", i, fn, offset, sz);
+#else
+ printf("File %4d '%s' @ offset %#010x size %d\n", i, fn, offset, sz);
+#endif
+ return 0;
+}
+
+static int extract_file(FILE *fp, uint32_t i, uint32_t sz, const char *fn,
+ void *d) {
+ FILE *ofp;
+
+ /* Open the output file. */
+ if(!(ofp = fopen(fn, "wb"))) {
+ printf("Cannot open file '%s' for write: %s\n", fn,
strerror(errno));
+ return -1;
+ }
+
+ /* Copy the data out into its new file. */
+ if(copy_file(ofp, fp, sz)) {
+ fclose(ofp);
+ return -2;
+ }
+
+ /* We're done with this file, return to the scan function. */
+ fclose(ofp);
+ return 0;
+}
+
+static int copy_file_cb(FILE *fp, uint32_t i, uint32_t sz,
+ const char *fn, void *d) {
+ FILE *ofp = (FILE *)d;
+ long fpos = (uint32_t)(i * 48);
+ uint32_t wpos = (uint32_t)ftell(ofp), wposp = wpos >> 11;
+ uint8_t buf[48];
+
+ /* Copy the filename, filling in zero bytes as needed. */
+ strncpy((char *)buf, fn, 32);
+
+ /* Fill in the header data for the file. */
+ buf[32] = (uint8_t)(wposp >> 24);
+ buf[33] = (uint8_t)(wposp >> 16);
+ buf[34] = (uint8_t)(wposp >> 8);
+ buf[35] = (uint8_t)(wposp);
+ buf[36] = (uint8_t)(sz >> 24);
+ buf[37] = (uint8_t)(sz >> 16);
+ buf[38] = (uint8_t)(sz >> 8);
+ buf[39] = (uint8_t)(sz);
+ buf[40] = 0;
+ buf[41] = 0;
+ buf[42] = 0;
+ buf[43] = 0;
+ buf[44] = 0;
+ buf[45] = 0;
+ buf[46] = 0;
+ buf[47] = 0;
+
+ if(fseek(ofp, fpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if(fwrite(buf, 1, 48, ofp) != 48) {
+ printf("Cannot write to file: %s\n", strerror(errno));
+ return -2;
+ }
+
+ if(fseek(ofp, wpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ return -3;
+ }
+
+ /* Copy the file over from the old archive to the new one. */
+ if(copy_file(ofp, fp, sz))
+ return -4;
+
+ /* Add padding, as needed. */
+ if(pad_file(ofp, 2048) < 0)
+ return -5;
+
+ return 0;
+}
+
+static int copy_filtered(FILE *fp, uint32_t i, uint32_t sz, const char *fn,
+ void *d) {
+ struct delete_cxt *cxt = (struct delete_cxt *)d;
+ uint32_t j;
+ uint32_t wposp = cxt->wpos >> 11;
+ uint8_t buf[48];
+
+ /* Look if we're supposed to leave this one off. */
+ for(j = 0; j < cxt->item_count; ++j) {
+ if(!strcmp(cxt->items[j], fn))
+ return 0;
+ }
+
+ /* Copy the filename, filling in zero bytes as needed. */
+ strncpy((char *)buf, fn, 32);
+
+ /* Fill in the header data for the file. */
+ buf[32] = (uint8_t)(wposp >> 24);
+ buf[33] = (uint8_t)(wposp >> 16);
+ buf[34] = (uint8_t)(wposp >> 8);
+ buf[35] = (uint8_t)(wposp);
+ buf[36] = (uint8_t)(sz >> 24);
+ buf[37] = (uint8_t)(sz >> 16);
+ buf[38] = (uint8_t)(sz >> 8);
+ buf[39] = (uint8_t)(sz);
+ buf[40] = 0;
+ buf[41] = 0;
+ buf[42] = 0;
+ buf[43] = 0;
+ buf[44] = 0;
+ buf[45] = 0;
+ buf[46] = 0;
+ buf[47] = 0;
+
+ if(fseek(cxt->fp, cxt->fpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if(fwrite(buf, 1, 48, cxt->fp) != 48) {
+ printf("Cannot write to file: %s\n", strerror(errno));
+ return -2;
+ }
+
+ cxt->fpos += 48;
+
+ if(fseek(cxt->fp, cxt->wpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ return -3;
+ }
+
+ /* Copy the file over from the old archive to the new one. */
+ if(copy_file(cxt->fp, fp, sz))
+ return -4;
+
+ /* Add padding, as needed. */
+ if((cxt->wpos = pad_file(cxt->fp, 2048)) < 0)
+ return -5;
+
+ ++cxt->copied_files;
+
+ return 0;
+}
+
+static int copy_update(FILE *fp, uint32_t i, uint32_t sz, const char *fn,
+ void *d) {
+ struct update_cxt *cxt = (struct update_cxt *)d;
+ uint8_t buf[48];
+ uint32_t wposp = cxt->wpos >> 11;
+
+ /* Look if we're supposed to update this one. */
+ if(!strcmp(cxt->fn, fn)) {
+ if(fseek(cxt->fp, cxt->fpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ return -7;
+ }
+
+ if(add_files_to_gsl(cxt->fp, cxt->fpos, cxt->wpos, &cxt->path, 1,
+ &cxt->fpos, &cxt->wpos))
+ return -6;
+
+ return 0;
+ }
+
+ /* Copy the filename, filling in zero bytes as needed. */
+ strncpy((char *)buf, fn, 32);
+
+ /* Fill in the header data for the file. */
+ buf[32] = (uint8_t)(wposp >> 24);
+ buf[33] = (uint8_t)(wposp >> 16);
+ buf[34] = (uint8_t)(wposp >> 8);
+ buf[35] = (uint8_t)(wposp);
+ buf[36] = (uint8_t)(sz >> 24);
+ buf[37] = (uint8_t)(sz >> 16);
+ buf[38] = (uint8_t)(sz >> 8);
+ buf[39] = (uint8_t)(sz);
+ buf[40] = 0;
+ buf[41] = 0;
+ buf[42] = 0;
+ buf[43] = 0;
+ buf[44] = 0;
+ buf[45] = 0;
+ buf[46] = 0;
+ buf[47] = 0;
+
+ if(fseek(cxt->fp, cxt->fpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if(fwrite(buf, 1, 48, cxt->fp) != 48) {
+ printf("Cannot write to file: %s\n", strerror(errno));
+ return -2;
+ }
+
+ cxt->fpos += 48;
+
+ if(fseek(cxt->fp, cxt->wpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ return -3;
+ }
+
+ /* Copy the file over from the old archive to the new one. */
+ if(copy_file(cxt->fp, fp, sz))
+ return -4;
+
+ /* Add padding, as needed. */
+ if((cxt->wpos = pad_file(cxt->fp, 2048)) < 0)
+ return -5;
+
+ return 0;
+}
+
+int create_gsl(const char *fn, const char *files[], uint32_t count) {
+ FILE *ofp;
+ int fd;
+ char tmpfn[16];
+ long fpos, wpos, hdrlen;
+
+#ifndef _WIN32
+ mode_t mask;
+#endif
+
+ /* Figure out the size of the header. Sega's files seem (to me) to
have no
+ rhyme or reason to how long the header is. I just round it up to
the next
+ multiple of 2048. */
+ hdrlen = ((count * 48) + 2048) & 0xFFFFF000;
+
+ /* Open up a temporary file for writing. */
+ strcpy(tmpfn, "gsltoolXXXXXX");
+ if((fd = mkstemp(tmpfn)) < 0) {
+ printf("Cannot create temporary file: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if(!(ofp = fdopen(fd, "wb"))) {
+ printf("Cannot open temporary file: %s\n", strerror(errno));
+ close(fd);
+ unlink(tmpfn);
+ return -2;
+ }
+
+ /* Make space for the file table. */
+ fpos = 0;
+
+ if(fseek(ofp, hdrlen, SEEK_SET)) {
+ printf("Cannot create blank file table: %s\n", strerror(errno));
+ fclose(ofp);
+ unlink(tmpfn);
+ return -4;
+ }
+
+ /* Save where we'll write the first file and move back to the file
table. */
+ wpos = ftell(ofp);
+ if(fseek(ofp, fpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ fclose(ofp);
+ unlink(tmpfn);
+ return -5;
+ }
+
+ /* Add all the files to the archive. */
+ if(add_files_to_gsl(ofp, fpos, wpos, files, count, NULL, NULL)) {
+ fclose(ofp);
+ unlink(tmpfn);
+ return -6;
+ }
+
+ /* All the files are copied, so move the archive into its place. */
+#ifndef _WIN32
+ mask = umask(0);
+ umask(mask);
+ fchmod(fileno(ofp), (~mask) & 0666);
+#endif
+ fclose(ofp);
+ rename(tmpfn, fn);
+
+ return 0;
+}
+
+int add_to_gsl(const char *fn, const char *files[], uint32_t count) {
+ FILE *ofp;
+ int fd;
+ char tmpfn[16];
+ int entries;
+ long fpos, wpos, hdrlen;
+
+#ifndef _WIN32
+ mode_t mask;
+#endif
+
+ /* Figure out how many files are already in the file... */
+ if((entries = scan_gsl(fn, NULL, NULL)) < 0) {
+ return -11;
+ }
+
+ /* Figure out the size of the header. Sega's files seem (to me) to
have no
+ rhyme or reason to how long the header is. I just round it up to
the next
+ multiple of 2048. */
+ hdrlen = (((count + entries) * 48) + 2048) & 0xFFFFF000;
+
+ /* Open up a temporary file for writing. */
+ strcpy(tmpfn, "gsltoolXXXXXX");
+ if((fd = mkstemp(tmpfn)) < 0) {
+ printf("Cannot create temporary file: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if(!(ofp = fdopen(fd, "wb"))) {
+ printf("Cannot open temporary file: %s\n", strerror(errno));
+ close(fd);
+ unlink(tmpfn);
+ return -2;
+ }
+
+ /* Make space for the file table. */
+ if(fseek(ofp, hdrlen, SEEK_SET)) {
+ printf("Cannot create blank file table: %s\n", strerror(errno));
+ fclose(ofp);
+ unlink(tmpfn);
+ return -3;
+ }
+
+ /* Copy the data from the existing file to the new one. */
+ if(scan_gsl(fn, &copy_file_cb, ofp) < 0) {
+ fclose(ofp);
+ unlink(tmpfn);
+ return -5;
+ }
+
+ wpos = ftell(ofp);
+ fpos = entries * 48;
+
+ if(fseek(ofp, fpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ fclose(ofp);
+ unlink(tmpfn);
+ return -9;
+ }
+
+ /* Add all the new files to the archive. */
+ if(add_files_to_gsl(ofp, fpos, wpos, files, count, NULL, NULL)) {
+ fclose(ofp);
+ unlink(tmpfn);
+ return -10;
+ }
+
+ /* All the files are copied, so move the archive into its place. */
+#ifndef _WIN32
+ mask = umask(0);
+ umask(mask);
+ fchmod(fileno(ofp), (~mask) & 0666);
+#endif
+ fclose(ofp);
+ rename(tmpfn, fn);
+
+ return 0;
+}
+
+int update_gsl(const char *fn, const char *file, const char *path) {
+ int fd;
+ char tmpfn[16];
+ int entries;
+ struct update_cxt cxt;
+ long hdrlen;
+
+#ifndef _WIN32
+ mode_t mask;
+#endif
+
+ /* Parse out all the entries for the context first. */
+ memset(&cxt, 0, sizeof(cxt));
+ cxt.fn = file;
+ cxt.path = path;
+
+ /* Figure out how many files are already in the file... */
+ if((entries = scan_gsl(fn, NULL, NULL)) < 0) {
+ return -11;
+ }
+
+ /* Figure out the size of the header. Sega's files seem (to me) to
have no
+ rhyme or reason to how long the header is. I just round it up to
the next
+ multiple of 2048. */
+ hdrlen = ((entries * 48) + 2048) & 0xFFFFF000;
+
+ /* Open up a temporary file for writing. */
+ strcpy(tmpfn, "gsltoolXXXXXX");
+ if((fd = mkstemp(tmpfn)) < 0) {
+ printf("Cannot create temporary file: %s\n", strerror(errno));
+ return -3;
+ }
+
+ if(!(cxt.fp = fdopen(fd, "wb"))) {
+ printf("Cannot open temporary file: %s\n", strerror(errno));
+ close(fd);
+ unlink(tmpfn);
+ return -4;
+ }
+
+ if(fseek(cxt.fp, hdrlen, SEEK_SET)) {
+ printf("Cannot create blank file table: %s\n", strerror(errno));
+ fclose(cxt.fp);
+ unlink(tmpfn);
+ return -5;
+ }
+
+ /* Save where we'll write the first file and move back to the file
table. */
+ cxt.wpos = ftell(cxt.fp);
+ if(fseek(cxt.fp, cxt.fpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ fclose(cxt.fp);
+ unlink(tmpfn);
+ return -6;
+ }
+
+ if(scan_gsl(fn, &copy_update, &cxt) < 0) {
+ fclose(cxt.fp);
+ unlink(tmpfn);
+ return -7;
+ }
+
+ /* All the files are copied, so move the archive into its place. */
+#ifndef _WIN32
+ mask = umask(0);
+ umask(mask);
+ fchmod(fileno(cxt.fp), (~mask) & 0666);
+#endif
+ fclose(cxt.fp);
+ rename(tmpfn, fn);
+
+ return 0;
+}
+
+int delete_from_gsl(const char *fn, const char *files[], uint32_t cnt) {
+ int fd;
+ char tmpfn[16];
+ int entries;
+ struct delete_cxt cxt;
+ uint32_t i;
+
+#ifndef _WIN32
+ mode_t mask;
+#endif
+
+ /* Parse out all the entries for the context first. */
+ memset(&cxt, 0, sizeof(cxt));
+ cxt.item_count = cnt;
+
+ if(!(cxt.items = (const char **)malloc(sizeof(const char *) * cnt))) {
+ printf("Cannot allocate memory: %s\n", strerror(errno));
+ return -1;
+ }
+
+ errno = 0;
+ for(i = 0; i < cnt; ++i) {
+ cxt.items[i] = files[i];
+ }
+
+ /* Figure out how many files are already in the file... */
+ if((entries = scan_gsl(fn, NULL, NULL)) < 0) {
+ free((void *)cxt.items);
+ return -11;
+ }
+
+ /* Figure out the size of the header. Sega's files seem (to me) to
have no
+ rhyme or reason to how long the header is. I just round it up to
the next
+ multiple of 2048. */
+ cxt.wpos = ((entries * 48) + 2048) & 0xFFFFF000;
+
+ /* Open up a temporary file for writing. */
+ strcpy(tmpfn, "gsltoolXXXXXX");
+ if((fd = mkstemp(tmpfn)) < 0) {
+ printf("Cannot create temporary file: %s\n", strerror(errno));
+ free((void *)cxt.items);
+ return -3;
+ }
+
+ if(!(cxt.fp = fdopen(fd, "wb"))) {
+ printf("Cannot open temporary file: %s\n", strerror(errno));
+ close(fd);
+ unlink(tmpfn);
+ free((void *)cxt.items);
+ return -4;
+ }
+
+ /* Make space for the file table. */
+ cxt.fpos = 0;
+
+ if(fseek(cxt.fp, cxt.wpos, SEEK_SET)) {
+ printf("Cannot create blank file table: %s\n", strerror(errno));
+ fclose(cxt.fp);
+ unlink(tmpfn);
+ free((void *)cxt.items);
+ return -5;
+ }
+
+ /* Save where we'll write the first file and move back to the file
table. */
+ if(fseek(cxt.fp, cxt.fpos, SEEK_SET)) {
+ printf("Seek error: %s\n", strerror(errno));
+ fclose(cxt.fp);
+ unlink(tmpfn);
+ free((void *)cxt.items);
+ return -6;
+ }
+
+ if(scan_gsl(fn, &copy_filtered, &cxt) < 0) {
+ fclose(cxt.fp);
+ unlink(tmpfn);
+ free((void *)cxt.items);
+ return -7;
+ }
+
+ free((void *)cxt.items);
+
+ /* All the files are copied, so move the archive into its place. */
+#ifndef _WIN32
+ mask = umask(0);
+ umask(mask);
+ fchmod(fileno(cxt.fp), (~mask) & 0666);
+#endif
+ fclose(cxt.fp);
+ rename(tmpfn, fn);
+
+ return 0;
+}
+
+int print_gsl_files(const char *fn) {
+ return scan_gsl(fn, &print_file_info, NULL);
+}
+
+int extract_gsl(const char *fn) {
+ return scan_gsl(fn, &extract_file, (void *)fn);
+}
=======================================
--- /dev/null
+++ /trunk/pso_tools/pso_artool/gsl.txt Wed Nov 19 19:43:59 2014 UTC
@@ -0,0 +1,58 @@
+Quick and Dirty GSL Archive Documentation
+-----------------------------------------
+
+--------------------------------------------------------------------------------
+ Introduction
+--------------------------------------------------------------------------------
+
+This text file explains the format of GSL archive files, as used in
Phantasy
+Star Online Episodes I & II for the Gamecube (as well as other later
versions of
+PSO). I'm not sure of any other places where GSL archives are used, but if
you
+know of any, I'd be interested to know.
+
+The GSL archive file format is a bit of an improvement over the AFS archive
+format used by earlier versions of PSO, but still isn't really all that
great of
+an archive file format. Unlike AFS, GSL archives do store the names of the
files
+that are contained in the archive. The file table at the beginning of the
+archive is of variable length, but doesn't store the number of files
contained
+within the archive (although, a blank entry in the table signals the end
of the
+list of files, so this isn't too much of a problem). There is no magic
number at
+the beginning of the file to identify the archive as a GSL archive. Files
are
+still padded to 2KiB boundaries, which is enforced by fact that offsets
within
+the archive are notated in 2KiB chunks in the file table.
+
+Now that the intro is out of the way, onto the file format...
+
+--------------------------------------------------------------------------------
+ File Format
+--------------------------------------------------------------------------------
+
+All multi-byte numeric types used in a GSL archive dependent on what
version of
+the game the archive was created for. Archives created for Gamecube
versions of
+the game store multi-byte numeric types in Big Endian (PowerPC) byte order,
+whereas those made for PSO Blue Burst are in Little Endian (Intel) byte
order.
+This only applies to the archive container file itself -- individual files
may
+differ in their byte ordering.
+
+All GSL archives consist of a file table, and the individual files that are
+stored in the archive. There is no global file header as there is in an AFS
+archive. Files are stored after the file table, one after the other,
padded to
+2KiB boundaries.
+
+File Table (variable length -- always seems to have a lot of padding,
though):
+Each entry in the file table is 48 bytes long and is of the following
format:
+ 32 bytes - File Name
+ Stored as plain ASCII characters
+ 4 bytes - Offset within the archive, in 2KiB blocks
+ 4 bytes - Length of the file, in bytes
+ 8 bytes - Unused? Always zeroes.
+
+There must be at least one blank entry at the end of the file table to
signify
+the end of the list. In addition, like the files themselves, this area
must be
+padded to a 2KiB boundary. PSO seems to have files with much larger tables
here
+than would otherwise be needed.
+
+Archived Files (variable length):
+Each file follows at the positions specified in the file table above.
Files are
+padded to 2KiB boundaries, as is required by the offset values in the file
table
+section above.
=======================================
--- /dev/null
+++ /trunk/pso_tools/pso_artool/windows_compat.c Wed Nov 19 19:43:59 2014
UTC
@@ -0,0 +1,144 @@
+/*
+ Sylverant PSO Tools
+ PSO Archive Tool
+ Copyright (C) 2014 Lawrence Sebald
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License version 3
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public
License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* Have I ever mentioned how much I dislike the fact that Microsoft doesn't
+ implement the standard functions that developers might expect in their C
+ libraries? No, well, consider this me mentioning it. */
+
+#ifdef _WIN32
+
+#define _WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+
+/* mkstemp extracted from libc/sysdeps/posix/tempname.c. Copyright
+ (C) 1991-1999, 2000, 2001, 2006 Free Software Foundation, Inc.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version. */
+
+static const char letters[] =
+"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+
+/* Generate a temporary file name based on TMPL. TMPL must match the
+ rules for mk[s]temp (i.e. end in "XXXXXX"). The name constructed
+ does not exist at the time of the call to mkstemp. TMPL is
+ overwritten with the result. */
+int
+mkstemp (char *tmpl)
+{
+ int len;
+ char *XXXXXX;
+ static unsigned long long value;
+ unsigned long long random_time_bits;
+ unsigned int count;
+ int fd = -1;
+ int save_errno = errno;
+
+ /* A lower bound on the number of temporary files to attempt to
+ generate. The maximum total number of temporary file names that
+ can exist for a given template is 62**6. It should never be
+ necessary to try all these combinations. Instead if a reasonable
+ number of names is tried (we define reasonable as 62**3) fail to
+ give the system administrator the chance to remove the problems. */
+#define ATTEMPTS_MIN (62 * 62 * 62)
+
+ /* The number of times to attempt to generate a temporary file. To
+ conform to POSIX, this must be no smaller than TMP_MAX. */
+#if ATTEMPTS_MIN < TMP_MAX
+ unsigned int attempts = TMP_MAX;
+#else
+ unsigned int attempts = ATTEMPTS_MIN;
+#endif
+
+ len = strlen (tmpl);
+ if (len < 6 || strcmp (&tmpl[len - 6], "XXXXXX"))
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+/* This is where the Xs start. */
+ XXXXXX = &tmpl[len - 6];
+
+ /* Get some more or less random data. */
+ {
+ SYSTEMTIME stNow;
+ FILETIME ftNow;
+
+ // get system time
+ GetSystemTime(&stNow);
+ stNow.wMilliseconds = 500;
+ if (!SystemTimeToFileTime(&stNow, &ftNow))
+ {
+ errno = -1;
+ return -1;
+ }
+
+ random_time_bits = (((unsigned long long)ftNow.dwHighDateTime << 32)
+ | (unsigned long long)ftNow.dwLowDateTime);
+ }
+ value += random_time_bits ^ (unsigned long long)GetCurrentThreadId ();
+
+ for (count = 0; count < attempts; value += 7777, ++count)
+ {
+ unsigned long long v = value;
+
+ /* Fill in the random bits. */
+ XXXXXX[0] = letters[v % 62];
+ v /= 62;
+ XXXXXX[1] = letters[v % 62];
+ v /= 62;
+ XXXXXX[2] = letters[v % 62];
+ v /= 62;
+ XXXXXX[3] = letters[v % 62];
+ v /= 62;
+ XXXXXX[4] = letters[v % 62];
+ v /= 62;
+ XXXXXX[5] = letters[v % 62];
+
+ fd = open (tmpl, O_RDWR | O_CREAT | O_EXCL, 0600);
+ if (fd >= 0)
+ {
+ errno = save_errno;
+ return fd;
+ }
+ else if (errno != EEXIST)
+ return -1;
+ }
+
+ /* We got out of the loop because we ran out of combinations to try. */
+ errno = EEXIST;
+ return -1;
+}
+
+/* Not thread safe, but I don't care. */
+char *basename(char *input) {
+ static char output[512];
+ char ext[256];
+
+ _splitpath(input, NULL, NULL, output, ext);
+ strcat(output, ext);
+}
+
+#endif
Reply all
Reply to author
Forward
0 new messages