| typedef unsigned char byte; | |
| typedef unsigned short word; | |
| typedef enum { APPLE, ATARI, COMMODORE, AMSTRAD, IBM, UNKNOWN } tsystem; | |
| typedef struct { | |
| word offset; | |
| word length; | |
| } tarea; | |
| typedef int bool; | |
| FILE *fpin[MAX_FILES] = { 0, 0, 0, 0, 0 }, *fpout = 0; | |
| struct { | |
| const char *name; | |
| long size; | |
| } file_info[MAX_FILES]; | |
| int area_count = 0; | |
| tarea area[10]; | |
| byte header[64]; | |
| word checksum = 0; | |
| bool checksum_valid = FALSE; | |
| long filesize = 0L; | |
| int hiscore = 0; | |
| void cleanup (void) { | |
| int i; | |
| for (i = 0; i < MAX_FILES; i++) | |
| if (fpin[i] != NULL) | |
| { fclose (fpin[i]); fpin[i] = NULL; } | |
| if (fpout != NULL) | |
| { fclose (fpout); fpout = NULL; } | |
| }/* cleanup */ | |
| void error (const char *message) { | |
| cleanup (); | |
| fprintf (stderr, "Error: %s\n", message); | |
| exit (EXIT_FAILURE); | |
| }/* error */ | |
| void add_area (word offset, word length) { | |
| int i; | |
| for (i = 0; i < area_count; i++) | |
| if (area[i].offset > offset) | |
| break; | |
| memmove (&area[i+1], &area[i], (area_count - i) * sizeof (tarea)); | |
| area[i].offset = offset; | |
| area[i].length = length; | |
| area_count++; | |
| }/* add_area */ | |
| void infile (int disk, long offset, byte *buf, int size) { | |
| fseek (fpin[disk], offset, SEEK_SET); | |
| if (!fread (buf, size, 1, fpin[disk])) | |
| error ("Cannot read input file"); | |
| }/* infile */ | |
| bool check_for_header (int disk, long offset) { | |
| long start_pc, high_mem; | |
| byte buf[64]; | |
| byte version; | |
| int score = 0; | |
| int i, j; | |
| infile (disk, offset, buf, sizeof (buf)); | |
| version = GET_BYTE(buf,0); | |
| if (version >= V1 && version <= V8) | |
| score += 750; | |
| if (GET_BYTE(buf,1) <= 0x07) /* Configuration bits 3 to 7 clear */ | |
| score += 10; | |
| if (GET_WORD(buf,2) < 400) /* Release number < 400 */ | |
| score += 10; | |
| high_mem = GET_WORD(buf,4); | |
| if (version == V6) | |
| start_pc = 4 * (long) GET_WORD(buf,6) + 8 * (long) GET_WORD(buf,40); | |
| else | |
| start_pc = GET_WORD(buf,6); | |
| if (high_mem > start_pc) | |
| high_mem = start_pc; | |
| if (GET_WORD(buf,8) <= high_mem) /* Dictionary addr <= High memory */ | |
| score += 10; | |
| if (GET_WORD(buf,10) <= GET_WORD(buf,14)) /* Object addr <= Dynamic area */ | |
| score += 10; | |
| if (GET_WORD(buf,12) <= GET_WORD(buf,14)) /* Globals <= Dynamic area */ | |
| score += 10; | |
| if (GET_WORD(buf,14) <= high_mem) /* Dynamic area <= High memory */ | |
| score += 10; | |
| if (GET_WORD(buf,16) < 0x0200) /* Flags bits 9 to 15 clear */ | |
| score += 10; | |
| if (version == V1) { | |
| for (i = 0; i < 6; i++) | |
| if (GET_BYTE(buf,18+i) == 0) /* Serial == 0 */ | |
| score += 2; | |
| } else { | |
| for (i = 0; i < 6; i++) | |
| if (GET_BYTE(buf,18+i) >= 32 && GET_BYTE(buf,18+i) < 127) /* Serial in ASCII */ | |
| score += 2; | |
| } | |
| if (version == V1) { | |
| if (GET_WORD(buf,24) == 0) /* Abbreviations == 0 */ | |
| score += 10; | |
| } else { | |
| if (GET_WORD(buf,24) <= GET_WORD(buf,14)) /* Abbreviations <= Dynamic area */ | |
| score += 10; | |
| } | |
| if (version == V1 || version == V2) { | |
| if (GET_WORD(buf,26) == 0) /* File size == 0 */ | |
| score += 10; | |
| if (GET_WORD(buf,28) == 0) /* Checksum == 0 */ | |
| score += 10; | |
| } else if (version == V3) { | |
| if (GET_WORD(buf,26) != 0) { | |
| if (2 * (long) GET_WORD(buf,26) > high_mem) | |
| score += 20; | |
| } else { | |
| if (GET_WORD(buf,28) == 0) /* Checksum == 0 */ | |
| score += 20; | |
| } | |
| } else if (version == V4 || version == V5) { | |
| if (4 * (long) GET_WORD(buf,26) > high_mem) | |
| score += 20; | |
| } else { | |
| if (8 * (long) GET_WORD(buf,26) > high_mem) | |
| score += 20; | |
| } | |
| if (GET_WORD(buf,30) == 0) /* Interpreter == 0 */ | |
| score += 10; | |
| if (GET_WORD(buf,32) == 0) /* Screen format == 0 */ | |
| score += 10; | |
| if (GET_WORD(buf,34) == 0) /* Screen width == 0 */ | |
| score += 10; | |
| if (GET_WORD(buf,36) == 0) /* Screen height == 0 */ | |
| score += 10; | |
| if (GET_WORD(buf,38) == 0) /* Font format == 0 */ | |
| score += 10; | |
| if (version != V6 && version != V7) { | |
| if (GET_WORD(buf,40) == 0) /* Functions offset == 0 */ | |
| score += 10; | |
| if (GET_WORD(buf,42) == 0) /* Strings offset == 0 */ | |
| score += 10; | |
| } else { | |
| if (GET_WORD(buf,40) < GET_WORD(buf,26)) /* Functions offset < File size */ | |
| score += 10; | |
| if (GET_WORD(buf,42) < GET_WORD(buf,26)) /* Strings offset < File size */ | |
| score += 10; | |
| } | |
| if (GET_WORD(buf,44) == 0) /* Default colours == 0 */ | |
| score += 10; | |
| if (version <= V4) { | |
| if (GET_WORD(buf,46) == 0) /* Terminating keys addr == 0 */ | |
| score += 10; | |
| } else { | |
| if (GET_WORD(buf,46) <= GET_WORD(buf,14)) /* Terminating keys addr <= Dynamic size */ | |
| score += 10; | |
| } | |
| if (GET_WORD(buf,48) == 0) /* Line width == 0 */ | |
| score += 10; | |
| if (GET_WORD(buf,50) == 0) /* Unused == 0 */ | |
| score += 10; | |
| if (version <= V4) { | |
| if (GET_WORD(buf,52) == 0) /* Alphabet addr == 0 */ | |
| score += 10; | |
| if (GET_WORD(buf,54) == 0) /* Mouse addr == 0 */ | |
| score += 10; | |
| } else { | |
| if (GET_WORD(buf,52) <= GET_WORD(buf,14)) /* Alphabet addr <= Dynamic size */ | |
| score += 10; | |
| if (GET_WORD(buf,54) <= GET_WORD(buf,14)) /* Mouse addr <= Dynamic size */ | |
| score += 10; | |
| } | |
| for (i = 0; i < 8; i++) | |
| if (GET_BYTE(buf,56+i) == 0 || isprint (GET_BYTE(buf,56+i))) /* User name */ | |
| score += 1; | |
| area_count = 0; | |
| add_area (0, 64); | |
| add_area (GET_WORD(buf,8), 4); | |
| add_area (GET_WORD(buf,10), (version <= 3) ? 62 : 126); | |
| add_area (GET_WORD(buf,12), 480); | |
| add_area (GET_WORD(buf,14), 0); | |
| add_area (high_mem, 0); | |
| if (version != V1) | |
| add_area (GET_WORD(buf,24), (version == 2) ? 64 : 192); | |
| if (version >= V5 && GET_WORD(buf,46) != 0) | |
| add_area (GET_WORD(buf,46), 1); | |
| if (version >= V5 && GET_WORD(buf,52) != 0) | |
| add_area (GET_WORD(buf,52), 78); | |
| if (version >= V5 && GET_WORD(buf,54) != 0) | |
| add_area (GET_WORD(buf,54), 6); | |
| for (i = 0; i < area_count; i++) | |
| for (j = i+1; j < area_count; j++) | |
| if (area[i].offset + area[i].length > area[j].offset) | |
| score -= 10; | |
| if (score < hiscore) | |
| return FALSE; | |
| if (score < 900) | |
| return FALSE; | |
| memcpy (header, buf, 64); | |
| hiscore = score; | |
| return TRUE; | |
| }/* check_for_header */ | |
| void begin_trans (long maxsize) { | |
| static struct { | |
| word release; | |
| byte serial[6]; | |
| long filesize; | |
| word checksum; | |
| } old_games[] = { | |
| { 7, "UG3AU5", 85260L, 0x6fb6 }, /* Zork 2 */ | |
| { 15, "820308", 82110L, 0x7961 }, /* Zork 2 */ | |
| { 17, "820427", 82368L, 0xcf13 }, /* Zork 2 */ | |
| { 18, "820512", 82422L, 0xcf14 }, /* Zork 2 */ | |
| { 18, "820517", 82422L, 0xcf14 }, /* Zork 2 */ | |
| { 5, "000000", 82836L, 0xa8a4 }, /* Zork 1 */ | |
| { 15, "UG3AU5", 78566L, 0xe987 }, /* Zork 1 */ | |
| { 23, "820428", 75780L, 0xe6dc }, /* Zork 1 */ | |
| { 25, "820515", 75808L, 0xdfa0 }, /* Zork 1 */ | |
| { 18, "820311", 111342L, 0x39d5 }, /* Deadline */ | |
| { 19, "820427", 111420L, 0x780e }, /* Deadline */ | |
| { 21, "820512", 111706L, 0xbf83 } /* Deadline */ | |
| }; | |
| int i; | |
| if (hiscore < 900) | |
| error ("Could not find story header"); | |
| printf ("Story file release %d ", (int) GET_WORD(header,2)); | |
| printf ("serial %c%c%c%c%c%c.\n", | |
| isprint (GET_BYTE(header,18)) ? GET_BYTE(header,18) : ' ', | |
| isprint (GET_BYTE(header,19)) ? GET_BYTE(header,19) : ' ', | |
| isprint (GET_BYTE(header,20)) ? GET_BYTE(header,20) : ' ', | |
| isprint (GET_BYTE(header,21)) ? GET_BYTE(header,21) : ' ', | |
| isprint (GET_BYTE(header,22)) ? GET_BYTE(header,22) : ' ', | |
| isprint (GET_BYTE(header,23)) ? GET_BYTE(header,23) : ' '); | |
| filesize = GET_WORD(header,26); | |
| checksum = GET_WORD(header,28); | |
| if (GET_BYTE(header,0) <= V3) { | |
| filesize *= 2; | |
| if (maxsize >= 0x20000L || maxsize == 0) | |
| maxsize = 0x20000L; | |
| } else if (GET_BYTE(header,0) <= V5) { | |
| filesize *= 4; | |
| if (maxsize >= 0x40000L || maxsize == 0) | |
| maxsize = 0x40000L; | |
| } else { | |
| filesize *= 8; | |
| if (maxsize >= 0x80000L || maxsize == 0) | |
| maxsize = 0x80000L; | |
| } | |
| if (filesize == 0L) { | |
| for (i = 0; i < sizeof (old_games) / sizeof (old_games[0]); i++) | |
| if (GET_WORD(header,2) == old_games[i].release) | |
| if (GET_BYTE(header,0) == V1 || !strncmp ((char *) header + 18, (char *) old_games[i].serial, 6)) { | |
| checksum = old_games[i].checksum; | |
| filesize = old_games[i].filesize; | |
| } | |
| } | |
| if (filesize > maxsize) | |
| error ("Disk image has bad format"); | |
| if (filesize != 0L) { | |
| printf ("Story file size is %ld bytes.\n", filesize); | |
| checksum_valid = TRUE; | |
| } else { | |
| printf ("Story file size is unclear -- assuming %ld.\n", filesize = maxsize); | |
| checksum_valid = FALSE; | |
| } | |
| printf ("Writing story file...\n"); | |
| }/* begin_trans */ | |
| void trans (int disk) { | |
| byte buf[256]; | |
| int size = sizeof (buf); | |
| if (filesize < size) | |
| size = (int) filesize; | |
| if (size != 0 && !fread (buf, size, 1, fpin[disk])) | |
| error ("Cannot read input file"); | |
| if (size != 0 && !fwrite (buf, size, 1, fpout)) | |
| error ("Cannot write output file"); | |
| filesize -= size; | |
| while (--size >= 0) checksum -= buf[size]; | |
| }/* trans */ | |
| void end_trans (void) { | |
| int i; | |
| for (i = 0; i < sizeof (header); i++) checksum += header[i]; | |
| if (!checksum_valid) | |
| printf ("Checksum unknown.\n"); | |
| else if (checksum == 0) | |
| printf ("Checksum good.\n"); | |
| else | |
| printf ("Checksum bad.\n"); | |
| }/* end_trans */ | |
| /**************************************************************************** | |
| * | |
| * Apple II specific routines | |
| * | |
| ****************************************************************************/ | |
| const char *interleave; | |
| long apple_sector_offset (int sector) { | |
| char c = interleave[sector & 15]; | |
| if (isalpha (c)) | |
| sector = (sector & ~15) | (c - 'A' + 10); | |
| if (isdigit (c)) | |
| sector = (sector & ~15) | (c - '0'); | |
| return (long) sector * 256; | |
| }/* apple_sector_offset */ | |
| /**************************************************************************** | |
| * | |
| * Apple II V2/V3 specific routines | |
| * | |
| ****************************************************************************/ | |
| void get_small_apple_game (void) { | |
| byte id[18]; | |
| int sector = 48; | |
| infile (0, 0x0cbdL, id, sizeof (id)); | |
| interleave = strncmp ((char *) id, "Apple II Version E", 18) ? | |
| "0DB97531ECA8642F" : "0123456789ABCDEF"; | |
| printf ("Assuming sector interleaving scheme %s.\n", interleave); | |
| check_for_header (0, apple_sector_offset (sector)); | |
| begin_trans (0L); | |
| while (filesize > 0L) { | |
| fseek (fpin[0], apple_sector_offset (sector++), SEEK_SET); | |
| trans (0); | |
| } | |
| end_trans (); | |
| }/* get_small_apple_game */ | |
| /**************************************************************************** | |
| * | |
| * Apple II V4/V5 specific routines | |
| * | |
| ****************************************************************************/ | |
| void convert_disk (void) { | |
| byte buf[258]; | |
| static const byte trans[] = { | |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0x02, 0x03, 0xff, 0x04, 0x05, 0x06, | |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0x08, 0xff, 0xff, 0xff, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, | |
| 0xff, 0xff, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0xff, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, | |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1b, 0xff, 0x1c, 0x1d, 0x1e, | |
| 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0x20, 0x21, 0xff, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, | |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0x29, 0x2a, 0x2b, 0xff, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, | |
| 0xff, 0xff, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0xff, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f }; | |
| static const byte swap_bits[] = { 0, 2, 1, 3 }; | |
| static byte track_data1[0x1a00]; | |
| static byte track_data2[0x1a00]; | |
| int track, sector, i; | |
| if ((fpin[2] = tmpfile ()) == NULL) | |
| error ("Cannot open temporary file"); | |
| for (track = 0; track < 35; track++) { | |
| infile (1, (long) 0x1a00 * track, track_data1, sizeof (track_data1)); | |
| for (i = 0; i < 0x1a00; i++) | |
| if (track_data1[i] == 0xd5) | |
| if (track_data1[(i+1) % 0x1a00] == 0xaa) | |
| if (track_data1[(i+2) % 0x1a00] == 0xad) | |
| goto start_found; | |
| error ("NIB file has bad format"); | |
| start_found: | |
| memcpy (track_data2, track_data1 + i, 0x1a00 - i); | |
| memcpy (track_data2 + 0x1a00 - i, track_data1, i); | |
| for (sector = 0; sector < 18; sector++) { | |
| byte *p = track_data2 + 343 * sector + 5; | |
| byte value = 0; | |
| for (i = 0; i < 343; i++) | |
| if (p[i] & 0x80) | |
| p[i] = value ^= trans[p[i] - 0x80]; | |
| if (value != 0) | |
| fprintf (stderr, "Warning: Bad sector checksum on NIB image\n"); | |
| for (i = 0; i < 86; i++) { | |
| buf[i+0*86] = (p[i+1*86] << 2) | swap_bits[(p[i] >> 0) & 3]; | |
| buf[i+1*86] = (p[i+2*86] << 2) | swap_bits[(p[i] >> 2) & 3]; | |
| buf[i+2*86] = (p[i+3*86] << 2) | swap_bits[(p[i] >> 4) & 3]; | |
| } | |
| if (!fwrite (buf, 256, 1, fpin[2])) | |
| error ("Cannot write temporary file"); | |
| } | |
| } | |
| }/* convert_disk */ | |
| void get_medium_apple_game (void) { | |
| int start = 0; | |
| int sector = 0; | |
| convert_disk (); | |
| interleave = "0DB97531ECA8642F"; | |
| if (check_for_header (0, apple_sector_offset (48))) | |
| start = 48; | |
| if (check_for_header (0, apple_sector_offset (64))) | |
| start = 64; | |
| begin_trans (0L); | |
| for (sector = start; sector < start + 394; sector++) { | |
| fseek (fpin[0], apple_sector_offset (sector), SEEK_SET); | |
| trans (0); | |
| } | |
| fseek (fpin[2], 0L, SEEK_SET); | |
| while (filesize > 0L) | |
| trans (2); | |
| end_trans (); | |
| }/* get_medium_apple_game */ | |
| /**************************************************************************** | |
| * | |
| * Apple II V6 specific routines | |
| * | |
| ****************************************************************************/ | |
| byte table[1024]; | |
| word block_map[MAX_FILES][256]; | |
| word read_table_word (int offs) { | |
| if (offs >= sizeof (table) / sizeof (table[0])) | |
| error ("Disk image has bad format"); | |
| return GET_WORD(table,offs); | |
| }/* read_table_word */ | |
| void find_story_block (word log_block, int *disk, word *phys_block) { | |
| int offs = 20; | |
| for (*disk = 0; *disk < read_table_word (2); *disk += 1) { | |
| word entries = read_table_word (offs + 4); | |
| offs += 8; | |
| while (entries--) { | |
| word b1 = read_table_word (offs + 0); | |
| word b2 = read_table_word (offs + 2); | |
| word at = read_table_word (offs + 4); | |
| offs += 6; | |
| if (b1 <= log_block && log_block <= b2) { | |
| if (log_block + at - b1 >= 256) | |
| error ("Disk image has bad format"); | |
| *phys_block = block_map[*disk][log_block + at - b1]; | |
| return; | |
| } | |
| } | |
| } | |
| error ("Disk image has bad format"); | |
| }/* find_story_block */ | |
| void get_large_apple_game (int n) { | |
| byte lo[256], hi[256]; | |
| byte id[12]; | |
| char fn[12]; | |
| int i, disk; | |
| const char *title[] = { "ZORK0", "SHOGUN", "JOURNEY", "ARTHUR", NULL }; | |
| word log_block = 0; | |
| word phys_block = 0; | |
| int this_side = 0, this_game = 0, prev_game = 0; | |
| interleave = "0EDCBA987654321F"; | |
| for (disk = 0; disk < n; disk++) { | |
| infile (disk, 0x0b05L, id, sizeof (12)); | |
| for (i = 0; title[i]; i++) | |
| if (!strncmp ((char *) id, title[i], strlen (title[i]))) | |
| goto detected; | |
| error ("Cannot identify disk image"); | |
| detected: | |
| this_game = i; | |
| this_side = id[strlen (title[this_game]) + 1] - '0'; | |
| printf ("Image identified as %s side %d.\n", title[this_game], this_side); | |
| if (this_game != prev_game && disk != 0) | |
| error ("Disk images from different games"); | |
| if (this_side != disk + 1) | |
| error ("Wrong ordering of disk images"); | |
| sprintf (fn, "%s.D%d", title[this_game], this_side); | |
| infile (disk, 0x0b2cL, id, sizeof (id)); | |
| if (!strncmp ((char *) id, fn, strlen (fn))) { infile (disk, 0x0b3cL, id, 1); goto found; } | |
| infile (disk, 0x0b53L, id, sizeof (id)); | |
| if (!strncmp ((char *) id, fn, strlen (fn))) { infile (disk, 0x0b63L, id, 1); goto found; } | |
| infile (disk, 0x0b7aL, id, sizeof (id)); | |
| if (!strncmp ((char *) id, fn, strlen (fn))) { infile (disk, 0x0b8aL, id, 1); goto found; } | |
| infile (disk, 0x0ba1L, id, sizeof (id)); | |
| if (!strncmp ((char *) id, fn, strlen (fn))) { infile (disk, 0x0bb1L, id, 1); goto found; } | |
| error ("Disk image has bad format"); | |
| found: | |
| infile (disk, apple_sector_offset (2 * id[0]), lo, sizeof (lo)); | |
| infile (disk, apple_sector_offset (2 * id[0] + 1), hi, sizeof (hi)); | |
| for (i = 0; i < 256; i++) | |
| block_map[disk][i] = hi[i] * 256 + lo[i]; | |
| prev_game = this_game; | |
| } | |
| infile (0, apple_sector_offset (2 * block_map[0][0]), table, 256); | |
| infile (0, apple_sector_offset (2 * block_map[0][0] + 1), table + 256, 256); | |
| if (GET_WORD(table,0) > 256) { | |
| infile (0, apple_sector_offset (2 * block_map[0][1]), table + 512, 256); | |
| infile (0, apple_sector_offset (2 * block_map[0][1] + 1), table + 768, 256); | |
| } | |
| find_story_block (0, &disk, &phys_block); | |
| check_for_header (disk, apple_sector_offset (2 * phys_block)); | |
| begin_trans (0L); | |
| while (filesize > 0L) { | |
| find_story_block (log_block++, &disk, &phys_block); | |
| fseek (fpin[disk], apple_sector_offset (2 * phys_block), SEEK_SET); | |
| trans (disk); | |
| fseek (fpin[disk], apple_sector_offset (2 * phys_block + 1), SEEK_SET); | |
| trans (disk); | |
| } | |
| end_trans (); | |
| }/* get_large_apple_game */ | |
| /**************************************************************************** | |
| * | |
| * Apple II main routine | |
| * | |
| ****************************************************************************/ | |
| void get_apple (int n) { | |
| int disk; | |
| if (n == 1) { /* V2 or V3 game */ | |
| if (file_info[0].size == 232960L) | |
| error ("The disk image should be in DSK format\n"); | |
| get_small_apple_game (); | |
| } | |
| if (n == 2) { /* V4 or V5 game */ | |
| if (file_info[0].size == 232960L) | |
| error ("The first disk image should be in DSK format\n"); | |
| if (file_info[1].size != 232960L) | |
| error ("The second disk image must be in NIB format\n"); | |
| get_medium_apple_game (); | |
| } | |
| if (n == 3) { /* Error */ | |
| error ("Wrong number of disk images (try to omit the third image)"); | |
| } | |
| if (n >= 4) { /* V6 game */ | |
| for (disk = 0; disk < n; disk++) | |
| if (file_info[disk].size == 232960L) | |
| error ("All disk images should be in DSK format"); | |
| get_large_apple_game (n); | |
| } | |
| }/* get_apple */ | |
| /**************************************************************************** | |
| * | |
| * Atari specific routines | |
| * | |
| ****************************************************************************/ | |
| void get_atari (int n) { | |
| long offset = 0L; | |
| long start = 0L; | |
| int sectors = 0; | |
| int disk = 0; | |
| if (n >= 3) | |
| error ("Too many disk image files for an Atari game"); | |
| if (check_for_header (0, 0x01f90L)) | |
| start = 0x01f90L; | |
| if (check_for_header (0, 0x01c10L)) | |
| start = 0x01c10L; | |
| if (check_for_header (0, 0x02410L)) | |
| start = 0x02410L; | |
| if (start == 0L) | |
| for (offset = 16; offset < 0x16810L; offset += 256) | |
| if (check_for_header (0, offset)) | |
| start = offset; | |
| begin_trans (0x16810L - start + ((n == 2) ? 0x16800L : 0)); | |
| if ((GET_BYTE(header,1) & 4) == 4 && n == 1) | |
| error ("This game takes two disk images"); | |
| if ((GET_BYTE(header,1) & 4) == 0 && n == 2) | |
| error ("This game takes only one disk image"); | |
| fseek (fpin[0], start, SEEK_SET); | |
| while (filesize > 0L) { | |
| trans (disk); | |
| if (n == 2 && disk == 0 && (long) ++sectors * 256 >= GET_WORD(header,4)) | |
| fseek (fpin[disk = 1], 16L, SEEK_SET); | |
| } | |
| end_trans (); | |
| }/* get_atari */ | |
| /**************************************************************************** | |
| * | |
| * C=64 specific routines | |
| * | |
| ****************************************************************************/ | |
| void dump_one_side (int track, bool skip17, bool skip18, int sectors_used) { | |
| int sector = 0; | |
| int maxtracks = 35 - track + 1; | |
| if (skip17) maxtracks--; | |
| if (skip18) maxtracks--; | |
| begin_trans ((long) maxtracks * sectors_used * 256); | |
| fseek (fpin[0], (long) (track - 1) * 0x1500, SEEK_SET); | |
| while (filesize > 0L) { | |
| if (track == 17 && skip17) { fseek (fpin[0], (long) 21 * 256, SEEK_CUR); track++; } | |
| if (track == 18 && skip18) { fseek (fpin[0], (long) 19 * 256, SEEK_CUR); track++; } | |
| if (track == 18) | |
| fseek (fpin[0], 512L, SEEK_CUR); | |
| trans (0); | |
| if (track == 18) | |
| fseek (fpin[0], -512L, SEEK_CUR); | |
| if (++sector == sectors_used) { | |
| int total_sectors = | |
| (track <= 17) ? 21 : | |
| (track <= 24) ? 19 : | |
| (track <= 30) ? 18 : | |
| (track <= 35) ? 17 : 0; | |
| fseek (fpin[0], (long) (total_sectors - sectors_used) * 256, SEEK_CUR); | |
| track++; sector = 0; | |
| } | |
| } | |
| end_trans (); | |
| }/* dump one side */ | |
| void dump_two_sides (int track, int last_track, int last_sector, bool skip) { | |
| int sector = 0, disk = 0; | |
| begin_trans (0L); | |
| fseek (fpin[0], (long) (track - 1) * 0x1500, SEEK_SET); | |
| while (filesize > 0L) { | |
| if (sector == 0 && track == 18) | |
| { fseek (fpin[disk], 256L, SEEK_CUR); sector++; } | |
| if (sector == 0 && track == 20 && disk == 1 && skip) | |
| { fseek (fpin[disk], 256L, SEEK_CUR); sector++; } | |
| trans (disk); | |
| if (ftell (fpout) == 0x23900L) | |
| disk = 2 * disk - disk; | |
| if (track != last_track || sector != last_sector || disk != 0) { | |
| int total_sectors = | |
| (track <= 17) ? 21 : | |
| (track <= 24) ? 19 : | |
| (track <= 30) ? 18 : | |
| (track <= 35) ? 17 : 0; | |
| if (++sector == total_sectors) | |
| { sector = 0; track++; } | |
| } else { | |
| disk = 1; | |
| track = 1; | |
| sector = 0; | |
| fseek (fpin[1], 0L, SEEK_SET); | |
| } | |
| } | |
| end_trans (); | |
| }/* dump two sides */ | |
| void get_v3_c64 (void) { | |
| byte id[6]; | |
| int i; | |
| static struct { | |
| char interpreter; | |
| int first_track; | |
| bool skip17, skip18; | |
| int sectors_used; | |
| byte id[6]; | |
| } v3chart[] = { | |
| { '1', 4, FALSE, FALSE, 16, { 0xf5, 0x60, 0x20, 0xf7, 0x1f, 0x20 } }, | |
| { '2', 4, FALSE, FALSE, 16, { 0x00, 0xd0, 0xf5, 0x60, 0x20, 0xf9 } }, | |
| { 'A', 4, FALSE, FALSE, 16, { 0xa0, 0x03, 0x20, 0x7b, 0x27, 0xa9 } }, | |
| { 'B', 5, TRUE, TRUE, 16, { 0x0f, 0xa8, 0xa6, 0x78, 0x20, 0xba } }, | |
| { 'C', 5, TRUE, FALSE, 17, { 0xff, 0xa2, 0x0f, 0x20, 0xc9, 0xff } }, | |
| { 'D', 5, TRUE, FALSE, 17, { 0xdd, 0x27, 0x09, 0x30, 0x99, 0xf0 } }, | |
| { 'E', 5, TRUE, FALSE, 17, { 0xff, 0xa2, 0x00, 0xc9, 0x0a, 0x90 } }, | |
| { 'F', 5, TRUE, FALSE, 17, { 0xa2, 0xef, 0xa0, 0x27, 0xa9, 0x01 } }, | |
| { 'G', 5, TRUE, FALSE, 17, { 0x69, 0x6e, 0x75, 0x65, 0x2e, 0x0d } }, | |
| { 'H', 5, TRUE, FALSE, 17, { 0x45, 0x54, 0x55, 0x52, 0x4e, 0x5d } } | |
| }; | |
| infile (0, 0x2000L, id, sizeof (id)); | |
| for (i = 0; i < sizeof (v3chart) / sizeof (v3chart[0]); i++) | |
| if (!strncmp ((char *) id, (char *) v3chart[i].id, 6)) | |
| goto detected; | |
| error ("Cannot determine interpreter version"); | |
| detected: | |
| printf ("Detected V3 interpreter '%c'.\n", v3chart[i].interpreter); | |
| dump_one_side ( | |
| v3chart[i].first_track, | |
| v3chart[i].skip17, | |
| v3chart[i].skip18, | |
| v3chart[i].sectors_used); | |
| }/* get_v3_c64 */ | |
| void get_v4_c64 (void) { | |
| byte id[6]; | |
| int i; | |
| static struct { | |
| char interpreter; | |
| int last_track, last_sector; | |
| bool skip; /* skip sector 0 track 20 side 1 */ | |
| byte id[6]; | |
| } v4chart[] = { | |
| { 'A', 19, 10, FALSE, { 0xdd, 0x30, 0x2d, 0xf0, 0x05, 0xca } }, /* C128 */ | |
| { 'B', 19, 10, FALSE, { 0x2a, 0x29, 0x7f, 0xc9, 0x0d, 0xf0 } }, /* C128 */ | |
| { 'L', 11, 6, TRUE, { 0x02, 0x69, 0x20, 0xa2, 0x06, 0xdd } } | |
| }; | |
| infile (0, 0x2000L, id, sizeof (id)); | |
| for (i = 0; i < sizeof (v4chart) / sizeof (v4chart[0]); i++) | |
| if (!strncmp ((char *) id, (char *) v4chart[i].id, 6)) | |
| goto detected; | |
| error ("Cannot determine interpreter version"); | |
| detected: | |
| printf ("Detected V4 interpreter '%c'.\n", v4chart[i].interpreter); | |
| dump_two_sides ( | |
| 3, | |
| v4chart[i].last_track, | |
| v4chart[i].last_sector, | |
| v4chart[i].skip); | |
| }/* get_v4_c64 */ | |
| void get_v5_c64 (void) { | |
| byte id[6]; | |
| int i; | |
| static struct { | |
| char interpreter; | |
| int last_track, last_sector; | |
| bool skip; /* skip sector 0 track 20 side 1 */ | |
| byte id[6]; | |
| } v5chart[] = { | |
| { 'A', 21, 14, FALSE, { 0x01, 0xd0, 0x08, 0xa9, 0x12, 0x8d } }, /* C128 */ | |
| { 'C', 13, 6, TRUE, { 0x20, 0x73, 0x2f, 0xa6, 0x7c, 0xca } }, | |
| { 'E', 13, 6, TRUE, { 0xa5, 0x42, 0x88, 0x91, 0x3f, 0xa9 } }, | |
| { 'H', 13, 6, TRUE, { 0x91, 0x3f, 0xa9, 0x00, 0x85, 0x3e } }, | |
| { 'J', 13, 6, TRUE, { 0x38, 0xe9, 0x02, 0x91, 0x3f, 0xb0 } } | |
| }; | |
| infile (0, 0x2300L, id, sizeof (id)); | |
| for (i = 0; i < sizeof (v5chart) / sizeof (v5chart[0]); i++) | |
| if (!strncmp ((char *) id, (char *) v5chart[i].id, 6)) | |
| goto detected; | |
| error ("Cannot determine interpreter version"); | |
| detected: | |
| printf ("Detected V5 interpreter '%c'.", v5chart[i].interpreter); | |
| dump_two_sides ( | |
| 5, | |
| v5chart[i].last_track, | |
| v5chart[i].last_sector, | |
| v5chart[i].skip); | |
| }/* get_v5_c64 */ | |
| void get_commodore (int n) { | |
| if (n >= 3) | |
| error ("Too many disk image files for a C64 game"); | |
| check_for_header (0, 0x2a00L); | |
| check_for_header (0, 0x3f00L); | |
| check_for_header (0, 0x5400L); | |
| if (GET_BYTE(header,0) >= V4 && n == 1) | |
| error ("This game takes two disk images"); | |
| if (GET_BYTE(header,0) <= V3 && n == 2) | |
| error ("This game takes only one disk image"); | |
| switch (GET_BYTE(header,0)) { | |
| case V3: get_v3_c64 (); break; | |
| case V4: get_v4_c64 (); break; | |
| case V5: get_v5_c64 (); break; | |
| default: error ("Couldn't find start of story file"); | |
| } | |
| }/* get_commodore */ | |
| /**************************************************************************** | |
| * | |
| * CPC specific routines | |
| * | |
| ****************************************************************************/ | |
| long amstrad_sector_offset (int sector) { | |
| static int mapper[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 }; | |
| static int previous = -1; | |
| int i; | |
| long offset = 256 + (long) (sector / 9) * 0x1300; | |
| if (sector / 9 != previous) | |
| for (i = 0; i < 9; i++) { | |
| byte c; | |
| infile (0, offset + 26 + 8 * i, &c, 1); | |
| c = (c & 15) - 1; | |
| if (c >= 9) | |
| error ("Disk image has bad format"); | |
| mapper[c] = i; | |
| } | |
| previous = sector / 9; | |
| return offset + (2 * mapper[sector % 9] + 1) * 256; | |
| }/* amstrad_sector_offset */ | |
| int scan_directory (int sector, byte *story_map) { | |
| byte buf[32]; | |
| int part = 0; | |
| int count = 0; | |
| int entry, n; | |
| for (entry = 0; entry < 16; entry++) { | |
| infile (0, amstrad_sector_offset (sector) + 32 * entry, buf, 32); | |
| if (buf[0x00] != 0 || buf[0x0c] != part) | |
| continue; | |
| buf[0x09] &= 0x7f; | |
| buf[0x0a] &= 0x7f; | |
| buf[0x0b] &= 0x7f; | |
| if (strncmp ((char *) buf + 9, "DAT", 3)) | |
| continue; | |
| if (buf[0x0f] > 0x80) | |
| return 0; | |
| for (n = 0; 8 * n < buf[0x0f]; n++) { | |
| if (count == 128 || buf[0x10 + n] + sector / 2 >= 180) | |
| return 0; | |
| story_map[count++] = buf[0x10 + n] + sector / 2; | |
| } | |
| part++; | |
| } | |
| return count; | |
| }/* scan_directory */ | |
| void get_amstrad (int n) { | |
| byte map[128]; | |
| int count; | |
| if (n >= 2) | |
| error ("Too many disk image files for a CPC game"); | |
| count = scan_directory (18, map); | |
| if (count == 0) | |
| count = scan_directory (0, map); | |
| if (count == 0) | |
| error ("Disk image has bad format"); | |
| check_for_header (0, amstrad_sector_offset (2 * map[0])); | |
| begin_trans ((long) count * 1024); | |
| count = 0; | |
| while (filesize > 0L) { | |
| fseek (fpin[0], amstrad_sector_offset (2 * map[count]), SEEK_SET); | |
| trans (0); | |
| trans (0); | |
| fseek (fpin[0], amstrad_sector_offset (2 * map[count] + 1), SEEK_SET); | |
| trans (0); | |
| trans (0); | |
| count++; | |
| } | |
| end_trans (); | |
| }/* get_amstrad */ | |
| /**************************************************************************** | |
| * | |
| * IBM specific routines | |
| * | |
| ****************************************************************************/ | |
| void get_ibm (int drive) { | |
| static byte buf[4096]; | |
| int track; | |
| if ((fpin[0] = tmpfile ()) == NULL) | |
| error ("Cannot open temporary file"); | |
| for (track = 6; track < 38; track++) { | |
| struct diskinfo_t dinfo; | |
| dinfo.drive = drive; | |
| dinfo.head = 0; | |
| dinfo.track = track; | |
| dinfo.sector = 1; | |
| dinfo.nsectors = 8; | |
| dinfo.buffer = buf; | |
| if (_bios_disk (_DISK_READ, &dinfo) != 8) | |
| error ("Cannot read disk"); | |
| if (!fwrite (buf, 4096, 1, fpin[0])) | |
| error ("Cannot write temporary file"); | |
| } | |
| check_for_header (0, 0); | |
| fseek (fpin[0], 0L, SEEK_SET); | |
| begin_trans (0L); | |
| while (filesize > 0L) | |
| trans (0); | |
| end_trans (); | |
| error ("This version does not support IBM bootable disks"); | |
| }/* get_ibm */ | |
| /**************************************************************************** | |
| * | |
| * Generic routines | |
| * | |
| ****************************************************************************/ | |
| void get_generic (int n) { | |
| long i, offset; | |
| if (n >= 2) | |
| error ("Too many disk image files for generic extraction"); | |
| if (!check_for_header (0, 0L)) { | |
| for (i = 0L; i < file_info[0].size - 64; i++) | |
| if (check_for_header (0, i)) | |
| offset = i; | |
| } else offset = 0; | |
| begin_trans (file_info[0].size - offset); | |
| fseek (fpin[0], offset, SEEK_SET); | |
| while (filesize > 0L) | |
| trans (0); | |
| end_trans (); | |
| }/* get_generic */ | |
| /**************************************************************************** | |
| * | |
| * Main routine | |
| * | |
| ****************************************************************************/ | |
| int main (int argc, char *argv[]) | |
| { | |
| int disk, n; | |
| enum tsystem this_type = UNKNOWN; | |
| enum tsystem prev_type = UNKNOWN; | |
| char extension[4], *p; | |
| if (argc < 3) { | |
| puts ( | |
| "\n" | |
| "ZCut V1.1 -- extract Infocom story files\n" | |
| "\n" | |
| "Written by Stefan Jokisch in 1998. Based on work by Matthew T. Russotto,\n" | |
| "Paul D. Doherty, Stephen Tjasink, Mark Howell and Michael Jenkin.\n" | |
| "\n" | |
| "ZCut extracts Infocom story files (aka Z-code) from\n" | |
| "\n" | |
| "- Amstrad CPC disk images [.DSK]\n" | |
| "- Apple ][ disk images [.DSK, .NIB]\n" | |
| "- Atari 800/XL/XE disk images [.ATR]\n" | |
| "- Commodore 64/128 disk images [.D64]\n" | |
| "- IBM PC bootable disks (MS-DOS version only)\n" | |
| "- any file if the Z-code is stored in one piece\n" | |
| "\n" | |
| "Usage: zcut input-file-1 [input-file-2...] output-file\n" | |
| " zcut drive: output-file"); | |
| return EXIT_FAILURE; | |
| } | |
| if ((n = argc - 2) > MAX_FILES) | |
| error ("Too many arguments"); | |
| if (n != 1 || argv[1][1] != ':' || argv[1][2] != 0) | |
| for (disk = 0; disk < n; disk++) { | |
| file_info[disk].name = argv[disk + 1]; | |
| fpin[disk] = fopen (file_info[disk].name, "rb"); | |
| if (fpin[disk] == NULL) | |
| error ("Cannot open disk image"); | |
| this_type = UNKNOWN; | |
| if ((p = strrchr (file_info[disk].name, '.')) != NULL) { | |
| strncpy (extension, p + 1, 3); | |
| extension[0] = toupper (extension[0]); | |
| extension[1] = toupper (extension[1]); | |
| extension[2] = toupper (extension[2]); | |
| extension[3] = 0; | |
| fseek (fpin[disk], 0L, SEEK_END); | |
| file_info[disk].size = ftell (fpin[disk]); | |
| if (!strcmp (extension, "DSK") && file_info[disk].size == 194816L) | |
| this_type = AMSTRAD; | |
| if (!strcmp (extension, "ATR") && file_info[disk].size == 92176L) | |
| this_type = ATARI; | |
| if (!strcmp (extension, "ATR") && file_info[disk].size == 133136L) | |
| this_type = ATARI; | |
| if (!strcmp (extension, "DSK") && file_info[disk].size == 143360L) | |
| this_type = APPLE; | |
| if (!strcmp (extension, "NIB") && file_info[disk].size == 232960L) | |
| this_type = APPLE; | |
| if (!strcmp (extension, "D64") && file_info[disk].size == 174848L) | |
| this_type = COMMODORE; | |
| } | |
| if (disk != 0 && this_type != prev_type) | |
| error ("Disk images have different types"); | |
| prev_type = this_type; | |
| } | |
| else prev_type = IBM; | |
| fpout = fopen (argv[argc - 1], "wb"); | |
| if (fpout == NULL) | |
| error ("Cannot open output file"); | |
| printf ("Input type is "); | |
| switch (prev_type) { | |
| case AMSTRAD: | |
| printf ("Amstrad CPC disk image.\n"); | |
| get_amstrad (n); | |
| break; | |
| case ATARI: | |
| printf ("Atari 800/XL/XE disk image.\n"); | |
| get_atari (n); | |
| break; | |
| case APPLE: | |
| printf ("Apple ][ disk image.\n"); | |
| get_apple (n); | |
| break; | |
| case COMMODORE: | |
| printf ("Commodore 64/128 disk image.\n"); | |
| get_commodore (n); | |
| break; | |
| case IBM: | |
| printf ("IBM bootable disk.\n"); | |
| get_ibm (toupper (argv[1][0]) - 'A'); | |
| break; | |
| default: | |
| printf ("an unknown file format.\n"); | |
| get_generic (n); | |
| break; | |
| } | |
| cleanup (); | |
| printf ("Operation successful.\n"); | |
| return EXIT_SUCCESS; | |
| }/* main */ | |
Xet Storage Details
- Size:
- 31.9 kB
- Xet hash:
- 2959e6c7f717110297a8a619e02b3a222a977f24780b1c05e224ff723756cde0
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.