diff --git a/tools/dos-str/Makefile b/tools/dos-str/Makefile new file mode 100644 index 000000000..a2811878c --- /dev/null +++ b/tools/dos-str/Makefile @@ -0,0 +1,9 @@ +unstr: unstr.c + gcc -W -Wall -g -O0 unstr.c -o unstr + +clean: + rm unstr unstr.tgz + +tgz: unstr.c Makefile + tar -cvzf unstr.tgz unstr.c Makefile + diff --git a/tools/dos-str/unstr b/tools/dos-str/unstr new file mode 100755 index 000000000..c443c5711 Binary files /dev/null and b/tools/dos-str/unstr differ diff --git a/tools/dos-str/unstr.c b/tools/dos-str/unstr.c new file mode 100644 index 000000000..6e3f02e4e --- /dev/null +++ b/tools/dos-str/unstr.c @@ -0,0 +1,311 @@ +/* + * DOS SC2 string extractor. + * + * By Serge van den Boom (svdb@stack.nl), 20021027 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// The DOS strings are contained within a dynamically loaded piece of 16 +// bits x86 code. These code files also contain a table with starting +// offsets of the various strings. +// This program is very simple; it takes the filename of the resource, +// and will then search backwards from the end of the file to the start +// off the string table. It will then print the strings in a human-readable +// format. +// +// To use this program, you need to take the following steps: +// - use unpkg to extract the various conversation packages, given +// starcon.pkg, con1.pkg, and con2.pkg. (starcon.pkg contains the index; +// the actual data comes from con1.pkg and con2.pkg) +// - use unpkg on the extracted con packages. The con packages all have +// resource type 6 (the last two numbers of the resource id presented as +// a string are "06"). This produces some files; the files with resource +// id 00200007 and 00400007 are the ones we're interested in. +// - on these files, run "lz" from the "dos-ani" directory, to decompress +// them. +// - run this program, unstr, on the decompressed files. + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +#include + + +typedef uint8_t uint8; +typedef uint16_t uint16; +typedef int32_t int32; + +#define MAKE_WORD(x1, x2) (((x2) << 8) | (x1)) + + + +struct options { + char *infile; + off_t startOffset; + off_t endOffset; + char verbose; +}; + + +bool testStringTable(const uint8 *data, const uint8 *start, + const uint8 *end); +void findStringTable(const uint8 *data, size_t len, off_t *tableStart, + off_t *tableEnd); +void parse_arguments(int argc, char *argv[], struct options *opts); +void dumpStrings(const uint8 *data, off_t startOff, off_t endOff); + + +int +main(int argc, char *argv[]) { + int in; + struct stat sb; + uint8 *buf; + struct options opts; + + parse_arguments(argc, argv, &opts); + + in = open(opts.infile, O_RDONLY); + if (in == -1) { + fprintf(stderr, "Error: Could not open file %s: %s\n", opts.infile, + strerror(errno)); + return EXIT_FAILURE; + } + + if (fstat(in, &sb) == -1) { + perror("stat() failed"); + return EXIT_FAILURE; + } + + buf = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, in, 0); + if (buf == MAP_FAILED) { + perror("mmap() failed"); + return EXIT_FAILURE; + } + close(in); + + if (opts.startOffset == (off_t) -1) { + findStringTable(buf, sb.st_size, &opts.startOffset, + &opts.endOffset); + if (opts.verbose) { + fprintf(stderr, "String table found at offset %u " + "(0x%08x) to %u (0x%08x).\n", + (unsigned int) opts.startOffset, + (unsigned int) opts.startOffset, + (unsigned int) opts.endOffset, + (unsigned int) opts.endOffset); + } + } else { + if (opts.endOffset == (off_t) -1) + opts.endOffset = sb.st_size; + + if (opts.startOffset > opts.endOffset || + opts.endOffset > sb.st_size) { + fprintf(stderr, "Invalid offset.\n"); + exit(EXIT_FAILURE); + } + } + + dumpStrings(buf, opts.startOffset, opts.endOffset); + + munmap(buf, sb.st_size); + return EXIT_SUCCESS; +} + +void +usage() { + fprintf(stderr, "unstr [-s [-e ]] \n" + "\t-s start of the string table. If not specified, unstr tries\n" + "\t to locate it itself.\n"); +} + +void +parse_arguments(int argc, char *argv[], struct options *opts) { + char ch; + + memset(opts, '\0', sizeof (struct options)); + opts->startOffset = (off_t) -1; + opts->endOffset = (off_t) -1; + while (1) { + ch = getopt(argc, argv, "s:v"); + if (ch == -1) + break; + switch(ch) { + case 'v': + opts->verbose = 1; + break; + case 'e': { + char *strEnd; + long value; + + errno = 0; + value = strtoul(optarg, &strEnd, 0); + if (errno != 0 || *optarg == '\0' || *strEnd != '\0') { + fprintf(stderr, "Invalid argument to '-e'.\n"); + exit(EXIT_FAILURE); + } + opts->endOffset = (off_t) value; + break; + } + case 's': { + char *strEnd; + long value; + + errno = 0; + value = strtoul(optarg, &strEnd, 0); + if (errno != 0 || *optarg == '\0' || *strEnd != '\0') { + fprintf(stderr, "Invalid argument to '-s'.\n"); + exit(EXIT_FAILURE); + } + opts->startOffset = (off_t) value; + break; + } + case '?': + case 'h': + default: + usage(); + exit(EXIT_SUCCESS); + } + } + + if (opts->startOffset == (off_t) -1 && opts->endOffset != (off_t) -1) { + fprintf(stderr, "'-e' is only valid in combination with '-s'.\n"); + exit(EXIT_FAILURE); + } + + argc -= optind; + argv += optind; + if (argc != 1) { + usage(); + exit(EXIT_FAILURE); + } + opts->infile = argv[0]; +} + +// Pre: start and end are aligned on 2 bytes. +bool +testStringTable(const uint8 *data, const uint8 *start, const uint8 *end) { + const uint8 *ptr; + off_t tableStartOff = start - data; + int32 lastWord; + + lastWord = -1; + for (ptr = start; ptr < end; ptr += 2) { + uint16 word = MAKE_WORD(ptr[0], ptr[1]); + + // Check the range. + if (word >= tableStartOff) + return false; + + // Offsets must be increasing. + if (word <= lastWord) + return false; + + // XXX: I could whether the strings end where the next one starts, + // but this scanning is good enough. + + lastWord = word; + } + return true; +} + +void +findStringTable(const uint8 *data, size_t len, off_t *tableStart, + off_t *tableEnd) { + off_t i = (off_t) len; + + // Table is aligned on 2 bytes. + if (i % 2 == 1) + i--; + + while (i >= 2) { + uint16 word; + const uint8 *ptr; + i -= 2; + + // Check whether the word points to someplace before &data[i]. + word = MAKE_WORD(data[i], data[i + 1]); + if (word >= i) + continue; + + // There should be one string starting at &data[word], and ending + // just before the start of the string table. + ptr = memchr(&data[word], '\0', i - word); + if (ptr == NULL) + continue; + // If the pointer to the end of the string is an even position, + // the next byte must be '\0' too. + if (((uintptr_t) ptr % 2) == 0) { + ptr++; + if (*ptr != '\0') + continue; + } + ptr++; + // Ptr now points past the end of the string. The start of the + // string table could be here. + + if (testStringTable(data, ptr, &data[i + 2])) { + *tableStart = ptr - data; + *tableEnd = i + 2; + return; + } + } + + fprintf(stderr, "Could not find the start of the string table.\n"); + exit(EXIT_FAILURE); +} + +// Pre: startOff and endOff fall within the range of data. +void +dumpStrings(const uint8 *data, off_t startOff, off_t endOff) { + const uint8 *start = data + startOff; + const uint8 *end = data + endOff; + const uint8 *maxStr = start; + // The strings come directly before the string table. + size_t i; + + for (i = 0; start < end; start += 2, i++) { + uint16 strOff = MAKE_WORD(start[0], start[1]); + size_t maxLen; + const char *strEnd; + + if (&data[strOff] >= maxStr) { + fprintf(stderr, "Invalid string table entry on offset %u.\n", + (uint16) (start - data)); + continue; + } + + maxLen = (size_t) (maxStr - &data[strOff]); + strEnd = memchr(&data[strOff], '\0', maxLen); + if (strEnd == NULL) { + fprintf(stderr, "Invalid string table entry on offset %u.\n", + (uint16) (start - data)); + continue; + } + + printf("#%d\n%s\n", i, (const char *) &data[strOff]); + } +} + + diff --git a/tools/dos-str/unstrall.sh b/tools/dos-str/unstrall.sh new file mode 100755 index 000000000..3cfeca416 --- /dev/null +++ b/tools/dos-str/unstrall.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +UNSTR="./unstr" +INPREFIX="./" +OUTPREFIX="./dialog/" +INFORMAT='${INPREFIX}${RACE1}.con/$ID.out' +OUTFORMAT='${OUTPREFIX}${RACE2}.txt' + + +while read RACE1 ID RACE2; do + eval INFILE="$INFORMAT" + eval OUTFILE="$OUTFORMAT" + echo "Processing... $INFILE ==> $OUTFILE" + $UNSTR -v "$INFILE" > "$OUTFILE" +done << EOF + arilou 00200007 arilou + blackur 00200007 blackur + chmmr 00200007 chmmr + comandr 00200007 comandr + comandr 00400107 starbas + druuge 00200007 druuge + ilwrath 00200007 ilwrath + melnorm 00200007 melnorm + mycon 00200007 mycon + orz 00200007 orz + pkunk 00200007 pkunk + shofixt 00200007 shofixt + slyhome 00200007 slyhome + slyland 00200007 slyland + spathi 00200007 spathi + spathi 00400107 spahome + supox 00200007 supox + syreen 00200007 syreen + talkpet 00200007 talkpet + thradd 00200007 thradd + umgah 00200007 umgah + urquan 00200007 urquan + utwig 00200007 utwig + vux 00200007 vux + yehat 00200007 yehat + yehat 00400107 rebel + zoqfot 00200007 zoqfot +EOF +