We just reached 25,000 articles on this wiki! 🥳
If you appreciate the work done within the wiki, please consider supporting The Cutting Room Floor on Patreon. Thanks for all your support!

Talk:King's Quest: Quest for the Crown

From The Cutting Room Floor
Jump to navigation Jump to search

IBM PCJr.

The IBM PCJr. version is rather odd compared to later releases, such as a different opening jingle and some odd changes here and there. It's not all that well-documented here, but I'm curious if anyone else can knows of something about this first version. --From: divingkataetheweirdo (talk) 16:15, 25 November 2015 (EST)

I took notes on the PCJr. version about three years ago. I didn't add them to the page because I was either told not to since it's basically a port comparison, or decided not to on my own, I can't remember. Here are the notes.
About the formatting: The control codes for the PCJr. version and the AGI engine are different, which is why the PCJr. version is properly formatted in the document and the AGI text isn't.--GoldS (talk) 00:21, 28 November 2015 (EST)

Version differences

One example of several text revisions here: https://twitter.com/dosnostalgic/status/1260013345794486272 Not sure if anyone knows a good way to do a full text dump and diff for changes, but could be interesting to add. --RavenWorks (talk) 20:44, 5 February 2022 (UTC)

Unused duplicate resources in VOL files

The VOL.0, VOL.1, and VOL.2 files contain most of the game data in the form of "resources". The directory files LOGDIR, PICDIR, VIEWDIR, and SNDDIR contain indexes into the VOL files. This Python program searches for unused bytes between resources:

#!/usr/bin/env python3

# Looks for unused bytes in AGIv2 VOL files -- bytes that are not referred to by
# any of the directory files.
#
# Usage: python3 volunused /path/to/kq1
#   where /path/to/kq1 is a directory containing the game files
#   LOGDIR, PICDIR, VIEWDIR, SNDDIR, and VOL.*.

import collections
import getopt
import hashlib
import os
import struct
import sys

_, (game_path,) = getopt.gnu_getopt(sys.argv[1:], "")

INTERVALS = {}
Interval = collections.namedtuple("Interval", ("label", "begin", "end"))

# Truncated hash, intended to make it easy to see when two resources are
# identical in the output.
def digest(data):
    return hashlib.sha256(data).digest()[:16].hex()

for dirname in ("LOGDIR", "PICDIR", "VIEWDIR", "SNDDIR"):
    with open(os.path.join(game_path, dirname), "rb") as dir:
        i = 0
        while True:
            label = f"{dirname}.{i}"
            # http://agiwiki.sierrahelp.com/index.php?title=AGI_Specifications:_Chapter_5_-_Resource_Formats#Version_2_directories
            entry = dir.read(3)
            if not entry:
                break
            elif entry == b"\xff\xff\xff":
                pass
            else:
                assert len(entry) == 3, entry
                vol_no = (entry[0] & 0xf0) >> 4
                pos = ((entry[0] & 0x0f) << 16) | (entry[1] << 8) | (entry[2] << 0)
                vol_filename = f"VOL.{vol_no}"
                with open(os.path.join(game_path, vol_filename), "rb") as vol:
                    vol.seek(pos)
                    # http://agiwiki.sierrahelp.com/index.php?title=AGI_Specifications:_Chapter_5_-_Resource_Formats#5.2_Format_of_Vol_files_.28version_2.29
                    header = vol.read(5)
                    if header:
                        assert len(header) == 5, header
                        sig, header_vol_no, size = struct.unpack("<HBH", header)
                        assert sig == 0x3412, sig
                        assert header_vol_no == vol_no, header_vol_no
                        int = Interval(label, pos, pos + len(header) + size)
                        INTERVALS.setdefault(vol_filename, [])
                        INTERVALS[vol_filename].append(int)
                        data = vol.read(size)
                        assert len(data) == size, (len(data), size)
                        print(f"{vol_filename} used {label:11} {pos:6}--{int.end:<6} {int.end - pos:4} {digest(data)}")
                    else:
                        print(f"{vol_filename} out-of-bounds {label:11} {pos:6}")
            i += 1

for intervals in INTERVALS.values():
    intervals.sort(key = lambda int: int.begin)

for vol_filename in sorted(INTERVALS.keys()):
    intervals = INTERVALS[vol_filename]

    vol_size = os.stat(vol_filename).st_size
    with open(os.path.join(game_path, vol_filename), "rb") as vol:
        def unused(begin, end):
            vol.seek(begin)
            data = vol.read(end - begin)
            # If the beginning of the unused data looks like a resource header,
            # show the digest of what follows the header. Otherwise show "-".
            dig = "-"
            if len(data) >= 5:
                sig, header_vol_no, size = struct.unpack("<HBH", data[:5])
                if sig == 0x3412 and 5 + size <= len(data):
                    dig = digest(data[5:5 + size])
            print(f"{vol_filename} unused {begin:6}--{end:<6} {end - begin:4} {dig} {data}")

        prev = None
        for int in intervals:
            if prev is None and int.begin != 0:
                # Unused bytes at the beginning.
                unused(0, int.begin)
            elif prev is not None and prev.end < int.begin:
                # Unused bytes between two intervals.
                unused(prev.end, int.begin)
            elif prev is not None and prev.end > int.begin:
                print(f"{vol_filename} overlap {prev} {int}")

            if int.end > vol_size:
                print(f"{vol_filename} overrun {int} {vol_size}")

            if prev is None or int.end > prev.end:
                prev = int

        if prev is not None and prev.end < vol_size:
            # Unused bytes at the end.
            unused(prev.end, vol_size)

Running the program on the game files of https://archive.org/details/msdos_Kings_Quest_I_-_Quest_for_the_Crown_1987 finds 6216 bytes that are never referred to, but they are all either duplicates of other resources that are used, or truncated resource headers.

VOL.2 out-of-bounds SNDDIR.34   134703
VOL.2 out-of-bounds SNDDIR.35   134799
VOL.2 out-of-bounds SNDDIR.36   134893
VOL.2 out-of-bounds SNDDIR.37   135787
VOL.0 unused  24488--26319  1831 665eec864221d42b018a050e44a8365d b'\x124\x00"\x07\x01\x01...'
VOL.0 unused  42422--43628  1206 43f1789de4e413024199c323b59ffb6f b'\x124\x00\xb1\x04\x08\x00\x9f...'
VOL.0 unused  44797--44800     3 - b'\x124\x00'
VOL.1 unused  23296--24086   790 5e53845a0eb120cb2a578f77165a2c02 b'\x124\x01\x11\x03\xf0\x00\xf2...'
VOL.1 unused  90010--90800   790 5e53845a0eb120cb2a578f77165a2c02 b'\x124\x01\x11\x03\xf0\x00\xf2...'
VOL.1 unused 105213--105216    3 - b'\x124\x01'
VOL.1 unused 120233--121023  790 5e53845a0eb120cb2a578f77165a2c02 b'\x124\x01\x11\x03\xf0\x00\xf2...'
VOL.1 unused 131717--132507  790 5e53845a0eb120cb2a578f77165a2c02 b'\x124\x01\x11\x03\xf0\x00\xf2...'
VOL.1 unused 145406--145408    2 - b'\x124'
VOL.1 unused 162301--162304    3 - b'\x124\x01'
VOL.1 unused 199421--199424    3 - b'\x124\x01'
VOL.2 unused  34559--34560     1 - b'\x12'
VOL.2 unused  90108--90112     4 - b'\x124\x02\xb7'
  • 665eec864221d42b018a050e44a8365d is a copy of VIEWDIR.14 in VOL.1.
  • 43f1789de4e413024199c323b59ffb6f is a copy of SNDDIR.0 in VOL.1.
  • 5e53845a0eb120cb2a578f77165a2c02 are copies of PICDIR.84 (the picture overlay for the base of the beanstalk) in VOL.1.

Mouser (talk) 17:40, 2 June 2022 (UTC)