/* * jzexe.c version 1.1 * * This program creates standalone MS-DOS game executables from Infocom * format story files. It does so by concatenating the JZip executable * with the story file and then patching the excutable to start reading * the story from the correct file position. * * Written by Magnus Olsson (mol@df.lth.se), 20 November, 1995, * as part of the JZip distribution. * You may use this code in any way you like as long as it is * attributed to its author. * */ #include #include #include #include "jzexe.h" #ifndef TRUE #define TRUE 1 #define FALSE 0 #endif #ifndef SEEK_SET #define SEEK_SET 0 #endif /* The size of the buffer used when copying files */ #define BUFSIZE 16384 static unsigned char *buf; static unsigned char *jzipmagic = (unsigned char *) MAGIC_STRING; #if defined(MSDOS) || defined(__MSDOS__) #define READ_MODE "rb" #define WRITE_MODE "wb" #else #define READ_MODE "r" #define WRITE_MODE "w" #endif #define NAME_LENGTH 200 static char in_name[NAME_LENGTH + 1]; static char out_name[NAME_LENGTH + 1]; /* The name of the JZip executable */ #define JZIP_NAME "jzip.exe" #ifdef __STDC__ void abort_program(int exit_code) #else void abort_program(exit_code) #endif { fprintf(stderr, "\nProgram aborted.\n"); /* Remove output file; it's probably corrupted. */ remove(out_name); exit(exit_code); } #ifdef __STDC__ FILE *open_file(char *name, int input) #else FILE *open_file(name, input) char *name; int input; #endif { char *mode; FILE *f; if (input) { strncpy(in_name, name, NAME_LENGTH); /* If name is too long, in_name won't be null terminated: fix this, just in case */ in_name[NAME_LENGTH] = 0; mode = READ_MODE; } else { strncpy(out_name, name, NAME_LENGTH); out_name[NAME_LENGTH] = 0; mode = WRITE_MODE; } f = fopen(name, mode); if (!f) { fprintf(stderr, "Error opening file %s\n", name); abort_program(1); } return f; } /* * Search through the file f for the magic string. If found, return * the offset stored in the patch area, otherwise abort the program. */ #ifdef __STDC__ long search(FILE *f) #else long search(f) FILE *f; #endif { int c, i; i = 0; while ((c = getc(f)) > -1) { if (c != jzipmagic[i]) { if (c == jzipmagic[0]) i = 1; else i = 0; } else if (++i == MAGIC_END) { /* Found magic string. */ long offset; /* Next byte must be zero. */ if (getc(f) != 0) { fprintf(stderr, "Error: standalone flag != 0\n"); abort_program(1); } offset = getc(f); offset += 256L * getc(f); offset += 65536L * getc(f); return offset; } } fprintf(stderr, "Couldn't find magic string."); abort_program(1); } /* * Copy the file in to the file out. Set length to the length in bytes * of the input file, and set magic_pos to the offset of the first * occurrence of MAGIC_STRING. */ #ifdef __STDC__ void copy_and_search(FILE *in, FILE *out, long *length, long *magic_pos) #else void copy_and_search(in, out, length, magic_pos) FILE *in, *out; long *length, *magic_pos; #endif { int c, buf_idx, magic_idx; unsigned buf_end; long pos; pos = 0; buf_idx = buf_end = 0; magic_idx = 0; *magic_pos = 0; while (TRUE) { if (buf_idx >= buf_end) { /* Reached end of input buffer; read in a new chunk */ buf_end = fread(buf, 1, BUFSIZE, in); if (buf_end < 1) { /* Reached end of input file. pos is now the number of bytes read. */ *length = pos; return; } if (fwrite(buf, 1, buf_end, out) != buf_end) { fprintf(stderr, "Write error on file %s\n", out_name); fclose(in); fclose(out); abort_program(1); } buf_idx = 0; } c = buf[buf_idx++]; /* Get current character */ ++pos; /* Offset of next byte */ /* The following search algorithm utilizes the fact that the first character of the magic string is unique. */ if (c != jzipmagic[magic_idx]) if (c == jzipmagic[0]) magic_idx = 1; else magic_idx = 0; else if (++magic_idx == MAGIC_END) { /* Matched the entire magic string */ if (*magic_pos > 0) { /* If this condition occurs, the magic string isn't unique, with potentially bad consequences. Re-compile with a different magic string. */ fprintf(stderr, "Found more than one instance of the magic string.\n"); fclose(in); fclose(out); abort_program(1); } *magic_pos = pos; } } } /* * Copy the input file to the output file. */ #ifdef __STDC__ void copy(FILE *in, FILE *out) #else void copy(in, out) FILE *in, *out; #endif { unsigned bytes_read; long total = 0; do { bytes_read = fread(buf, 1, BUFSIZE, in); if (bytes_read > 0) if (fwrite(buf, 1, bytes_read, out) != bytes_read) { fprintf(stderr, "Write error on file %s\n", out_name); fclose(in); fclose(out); abort_program(1); } total += bytes_read; } while (bytes_read > 0); if (total == 0) fprintf(stderr, "Nothing read from file %s - probably an error.\n", in_name); } /* * Patch the output file so it will read Z code from the correct position. */ #ifdef __STDC__ void patch(FILE *f, long pos, long value) #else void patch(f, pos, value) FILE *f; long pos, value; #endif { int i; fflush(f); if (fseek(f, pos, SEEK_SET)) { fprintf(stderr, "Seek error on file %s\n", out_name); fclose(f); abort_program(1); } /* Set STANDALONE_FLAG to FALSE */ fputc(FALSE, f); /* Write the length of the JZip interpreter to the file as a little-endian, 24-bit number. */ for (i = 0; i < 3; ++i) { fputc((int) (value & 0xff), f); value >>= 8; } } #ifdef __STDC__ void create_executable(char *story, char *jzip) #else void create_executable(story, jzip) char *story, *jzip; #endif { FILE *in, *out; long p, length, pos; char fn[NAME_LENGTH + 4]; char *ext; int ok; strcpy(in_name, ""); strcpy(out_name, ""); if (strlen(story) > NAME_LENGTH || strlen(jzip) > NAME_LENGTH) { fprintf(stderr, "Filename too long.\n"); abort_program(1); } strcpy(fn, story); ext = strrchr(fn, '.'); if (ext) *ext = '\0'; strcat(fn, ".exe"); printf("Creating standalone story %s\n", fn); out = open_file(fn, FALSE); printf("Copying JZip interpreter (%s).\n", jzip); in = open_file(jzip, TRUE); copy_and_search(in, out, &length, &pos); fclose(in); if (pos == 0) { fprintf(stderr, "Error: couldn't find magic string.\n"); fprintf(stderr, "Check that you have the right version of JZip.\n"); abort_program(1); } printf("Copying story file %s.\n", story); in = open_file(story, TRUE); copy(in, out); fclose(in); printf("Patching interpreter.\n"); patch(out, pos, length); fclose(out); printf("Done!\n"); } #ifdef __STDC__ void extract_zcode(char *filename) #else void extract_zcode(filename) char *filename; #endif { FILE *in, *out; long offset; char fn[NAME_LENGTH + 4]; char *ext; int z_version; strcpy(in_name, ""); strcpy(out_name, ""); if (strlen(filename) > NAME_LENGTH) { fprintf(stderr, "Filename too long.\n"); abort_program(1); } in = open_file(filename, TRUE); offset = search(in); fseek(in, offset, SEEK_SET); z_version = fgetc(in); if (z_version < 1 || z_version > 8) { fprintf(stderr, "Unsupported Z code version: %d\n", z_version); abort_program(1); } strcpy(fn, filename); ext = strrchr(fn, '.'); if (ext == 0) ext = fn + strlen(fn); sprintf(ext, ".z%d", z_version); printf("Extracting Z code to story file %s\n", fn); out = open_file(fn, FALSE); fputc(z_version, out); copy(in, out); fclose(in); fclose(out); } #define USAGE "JZexe Ver. 1.1 - creates standalone Infocom-format games for MS-DOS.\n\ (c) 1995 by Magnus Olsson (mol@df.lth.se)\n\n\ Usage: jzexe story_file\n\ jzexe story_file jzip_file\n\n\ These two commands create a standalone game from an Infocom story file.\n\ In the first case, it is assumed that the JZip interpreter is called jzip.exe\n\ and resides in the current directory.\n\n\ jzexe -x game.exe\n\n\ This command extracts the Z code from a standalone executable and creates\n\ a story file that can be played with JZip or any other interpreter.\n\ Note that the extension '.exe' of the game file must be given.\n" #ifdef __STDC__ void main(int argc, char **argv) #else void main(argc, argv) int argc; char **argv; #endif { buf=(unsigned char *)malloc(BUFSIZE*sizeof(unsigned char)); if (argc == 3 && strcmp(argv[1], "-x") == 0) extract_zcode(argv[2]); else if (argc == 2) create_executable(argv[1], JZIP_NAME); else if (argc == 3) create_executable(argv[1], argv[2]); else fprintf(stderr, USAGE); exit(0); }