#include #include #include #include #include #include #include #include #include /* * lump.c: manipulate the lumps in a Quake .bsp file (Currently only Q3/RTCW * are supported * Ben Winslow, Dec 9 2001 */ #define IBSP 0x50534249 /* FIXME: q1 .bsp files don't have an IBSP header */ #define NUM_LUMPS 17 /* FIXME: Check version number for number of lumps */ #define MODE_EXPORT 0 #define MODE_IMPORT 1 static struct lump_t { off_t off; size_t len; } lumps[NUM_LUMPS]; static struct file_t { unsigned char *buf; size_t len; } *bsp; /* Endian swapping functions */ static unsigned long lelong_get(unsigned char *); static void lelong_put(unsigned char *, unsigned long); /* File manipulation functions */ static struct file_t *file_read(char *); static int file_write(struct file_t *, char *); static void file_free(struct file_t *); /* Functions to update the lumps struct and the bsp in memory */ static void lump_import(int, unsigned char *, size_t); static int lump_export(int, char *); /* Prints usage information */ static void usage(char *); int main(int argc, char *argv[]) { char *infile = NULL, *outfile = NULL, *bspfilename; struct file_t *ents = NULL; int x, mode = MODE_EXPORT, lump = 0, opt; while ((opt = getopt(argc, argv, "i:en:o:h")) != -1) { switch (opt) { case 'i': infile = optarg; mode = MODE_IMPORT; break; case 'e': mode = MODE_EXPORT; break; case 'n': lump = atoi(optarg); if (lump > NUM_LUMPS) { printf("lump too high (NUM_LUMPS is %d)\n", NUM_LUMPS); return 1; } break; case 'o': outfile = strdup(optarg); break; case 'h': case '?': case ':': usage(argv[0]); return (int)((char)opt != 'h'); } } if (argc - optind != 1) { usage(argv[0]); return 1; } bspfilename = argv[optind]; /* load the .bsp file into memory */ if (!(bsp = file_read(bspfilename))) { perror("file_read()"); return 1; } if (lelong_get(bsp->buf) != IBSP) printf("warning: file does not have the 'IBSP' magic header\n"); printf("BSP version %lu\n", lelong_get(&bsp->buf[4])); for (x = 0; x < NUM_LUMPS; x++) { lumps[x].off = (off_t)lelong_get(&bsp->buf[(x * 8) + 8]); lumps[x].len = (size_t)lelong_get(&bsp->buf[(x * 8) + 8 + 4]); } switch (mode) { case MODE_EXPORT: if (!outfile) { if (!(outfile = malloc(strlen(bspfilename) + 5))) { perror("malloc()"); return 1; } sprintf(outfile, "%s.%.02d", bspfilename, lump); } fprintf(stderr, "Extracting lump %d to %s...\n", lump, outfile); if (lump_export(lump, outfile) < 0) fprintf(stderr, "lump_export(%d, %s): %s\n", lump, outfile, strerror(errno)); free(outfile); break; case MODE_IMPORT: if (!infile) { fprintf(stderr, "A filename is required for the import operation!\n"); break; } printf("Loading lump %d from %s...\n", lump, infile); if (!(ents = file_read(infile))) { perror("file_read()"); break; } lump_import(lump, ents->buf, ents->len); if (!outfile) outfile = bspfilename; printf("Writing new BSP to %s\n", outfile); if (file_write(bsp, outfile) < 0) fprintf(stderr, "write_file(%s): %s\n", outfile ? outfile : bspfilename, strerror(errno)); file_free(ents); break; } file_free(bsp); return 0; } static unsigned long lelong_get(unsigned char *buf) { return (unsigned long)((buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0]); } static void lelong_put(unsigned char *buf, unsigned long x) { buf[3] = (unsigned char)((x >> 24) & 0xff); buf[2] = (unsigned char)((x >> 16) & 0xff); buf[1] = (unsigned char)((x >> 8) & 0xff); buf[0] = (unsigned char)(x & 0xff); } static struct file_t *file_read(char *filename) { struct stat st; struct file_t *out; unsigned char *buf; int fd; ssize_t res; if (stat(filename, &st) < 0) return NULL; if ((fd = open(filename, O_RDONLY)) < 0) return NULL; if (!(buf = malloc((size_t)st.st_size))) return NULL; if ((res = read(fd, buf, (size_t)st.st_size)) < 0) { free(buf); return NULL; } (void)close(fd); if (!(out = malloc(sizeof(struct file_t)))) { free(buf); return NULL; } out->buf = buf; out->len = (size_t)res; return out; } static int file_write(struct file_t *f, char *filename) { int fd; if ((fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) return -1; if (write(fd, f->buf, f->len) < 0) return -2; if (close(fd) < 0) return -3; return 0; } static void file_free(struct file_t *f) { if (f) { free(f->buf); free(f); } } static void lump_import(int num, unsigned char *buf, size_t len) { size_t newlen; int x; bsp->buf = realloc(bsp->buf, bsp->len + (len - lumps[num].len)); newlen = bsp->len + (len - lumps[num].len); /* * Offset the data after the lump we're changing * (we don't want to clobber it) * FIXME: This is ugly */ memmove(&bsp->buf[lumps[num].off + len], &bsp->buf[lumps[num].off + lumps[num].len], bsp->len - (lumps[num].off + lumps[num].len)); /* Insert the new lump data */ memcpy(&bsp->buf[lumps[num].off], buf, len); /* Fix the header */ for (x = 0; x < 17; x++) { if (lumps[x].off > lumps[num].off) lumps[x].off += (len - lumps[num].len); } lumps[num].len = len; /* And write the header to the buffer again */ for (x = 0; x < NUM_LUMPS; x++) { lelong_put(&bsp->buf[(x * 8) + 8], (unsigned long)lumps[x].off); lelong_put(&bsp->buf[(x * 8) + 8 + 4], (unsigned long)lumps[x].len); } bsp->len = newlen; } static int lump_export(int num, char *filename) { int fd; if ((fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) return -1; if (write(fd, &bsp->buf[lumps[num].off], lumps[num].len) < 0) return -2; if (close(fd) < 0) return -3; return 0; } static void usage(char *name) { fprintf(stderr, "usage: %s [-i |-e] [-n ] [-o ] \n", name); fprintf(stderr, "\t-i \tImport a filename into the specified lump\n"); fprintf(stderr, "\t-e\t\tExport the specified lump (default)\n"); fprintf(stderr, "\t-n \tSpecify the lump number to operate on (default: 0)\n"); fprintf(stderr, "\t-o \tSpecifies the output filename\n"); }