From: hnsngr@sirius.com (Ron Hunsinger)
Newsgroups: alt.games.marathon
Subject: Re: Pathways into Darkness Encryption?
Message-ID: <hnsngr-ya023180001712000227320001@news.flash.net>
References: <141220001926092035%petrich@netcom.com> <santiago-4558B0.16465215122000@newshost.cc.utexas.edu> <hnsngr-ya023180001612000036060001@news.flash.net> <161220001000247019%petrich@netcom.com>
Organization: ErsteSoft
Date: Sun, 17 Dec 2000 10:27:26 GMT
NNTP-Posting-Date: Sun, 17 Dec 2000 04:27:26 CST

In article <161220001000247019%petrich@netcom.com>, Loren Petrich
<petrich@netcom.com> wrote:

> In article <hnsngr-ya023180001612000036060001@news.flash.net>, Ron
> Hunsinger <hnsngr@sirius.com> wrote:
> > The code to encrypt/decrypt a scri resource is:
> > 
> >     void EncodeScript (Handle h) {
> >         int len = **(short**) h;
> >         Ptr p = *h + 2;
> >         for (int i = 0; i < len; ++i) {
> >             *p++ ^= i; }
> >         }
> > 
> > Of course, there's still a little more to it than that, ...
> 
>    Thanx. I've gotten some intelligible text out of that, and I think I
> have a clue as to those opcodes. My guess at the moment is that they
> are 2-byte instead of 1-byte ones.

I can give you some more information. First off, I want to correct an error
in the above code. The length word at the beginning of the resource counts
itself as part of the length. The above code assumes it doesn't, so it
translates two bytes beyond the end of the resource. To correct it, the
initial value for len should be:

    int len = **(short**) h - 2;
                            ^^^

Oddly, the code I posted is exactly the code I used to decrypt the
resources lo these many moons ago. I guess I was just lucky it didn't
crash.

You can partially DeRez the decrypted resource using this definition:

    type 'scri' {
        unsigned integer = StringEnd / 8;
        unsigned integer = $$Countof (StringArray);
        unsigned integer = StringStart / 8;
        unsigned integer = 1;   /* I don't know what this is */
        unsigned integer = 10;  /* I don't know what this is */
        literal longint;        /* Type of corpse */
    CodeStart:
        hex string [StringStart / 8 - CodeStart / 8];
    StringStart:
        array StringArray {
            cstring; };
    StringEnd:
        };

That hex string starting at CodeStart is the program. The instructions set
is the following. (I've made up the names, and I'm a little unclear on the
finer nuances of some of them. I think I knew the details at one time, but
my notes are incomplete in places.)

    a (byte) is 8 bits
    a (word) is 16 bits
    an (OSType) is 32 bits containing 4 printable characters

    branch instructions contain a 16-bit signed delta which, if added to
        the address of the *beginning* of the current instruction, gives
        the address of the beginning of the instruction being branched to
    a filler is always ignored, and usually contains garbage
    strings are referenced by index, not offset, starting at zero

    Test Environment
        opcode = 0      (byte)
        filler          (byte)
        what to check   (OSType)
        delta           (word)

        Tests the indicated condition in the environment, and branches
        if the condition is true. (For example, 'dark' is true if you
        do not have a turned-on flashlight. See scri#138.)

    Test Variable
        opcode = 1      (byte)
        filler          (byte)
        mask            (word)
        value           (word)
        delta           (word)
   
        There is a word-size variable associated with each corpse. The
        value is retained across uses of the yellow crystal. The condition
        being tested is ((variable & mask) == value).

        An unconditional branch is obtained by setting mask = value = 0.

    Speak
        opcode = 2      (byte)
        variations      (byte)
        first string    (word)

        Speaks a randomly selected string from among the <variations>
        strings starting with indicated <first string>. For example,
        the instruction 02030007 randomly selects one of string #7,
        string #8, or string #9 for output.

    Listen
        opcode = 3      (byte)
        prompted        (byte)
        hidden          (byte)
        filler          (byte)
        first word      (word)
        deltas          (array of words)

        Waits for input from the user, then scans it looking for one
        of the known words. <prompted> is the number of words that are
        given to the user (in a popup menu, perhaps?). This is always
        zero in PiD. <hidden> is the number of words that the user has
        to stumble across by guesswork.

        The words themselves appear as strings, starting with string
        #<first word>. The array of deltas contains one element for
        each word; the instruction does a conditional branch using the
        delta from the best match found. If none of the words are found
        in the input, execution falls through to the next instruction.

    Set Variable
        opcode = 4      (byte)
        filler          (byte)
        mask            (word)
        value           (word)

        Sets the indicated portion of the variable associated with the
        current corpse. I believe the calculation is:

            variable = (variable & ~mask) | (value & mask);

        but that's one of the things I didn't put in my notes.

        The most common use of this is to remember if you've talked to
        this corpse before, so it can say something different when you
        come back. ("Hello again" instead of "Who are you?")

    Callback
        opcode = 5      (byte)
        filler          (byte)
        action          (OSType)

        Performs the indicated action. (Must be one built into the engine.)
        The only action I know of is 'STOP'. The program for each scri
        resource ends with a callback to this action. (That is, the final
        instruction is always 05xx53544F50, where the xx is garbage.)
        That final instruction is never reachable.

As an example, the program contained in scri#138 (for the corpse on "We Can
See In the Dark, Can You?") begins as follows:

000E:   0000 6461 726B 003A     // if 'dark' goto 0048 (000E+003A)
0016:   0201 0000               // speak 0 ("Get that light away from me!
                                             Get it away!  No lights!
                                             They're coming!")

001A:   0300 045F 0001          // listen for 4 words starting at #1
            0016 0016 0022 0022 // goto, respectively, 
                                //      light       -> 0030 (001A+0016)
                                //      flashlight  -> 0030 (001A+0016)
                                //      they        -> 003C (001A+0022)
                                //      who         -> 003C (001A+0022)

0028:   019B 0000 0000 FFEE     // goto 0016 (0028+FFEE)
        
0030:   0201 0005               // speak 5 ("They're attracted to your
                                             light.  Fool!  Get away
                                             from me!")

0034:   019B 0000 0000 FFE6     // goto 001A (0034+FFE6)
        
003C:   0201 0006               // speak 6 ("Those things, those things!
                                             They're all around, they hide
                                             in the corners until they see
                                             light ...")

0040:   019B 0000 0000 FFDA     // goto 001A (0040+FFDA)
    
    
0048:   0201 0007               // speak 7 ("Are they following you?  You
                                             don't have any lights, do
                                             you?  Stay away ...")

004C:   0300 2365 0008          // listen for 35 words starting at #8
            0070 00D0 ...       // goto, respectively, ...


-Ron Hunsinger