User Tools

Site Tools


release:zzt_enhancer:craig_notes_signing

Craig Boston on ZZT Enhancer signing

Converted from .TXT file.

Resource file format

Position Type  Description
-------------------------------------------
0  UINT  NumEntries

Directory Entries:

Position Type  Description
-------------------------------------------
X+0  UINT  Resource ID
X+2  ULONG  File position
X+6  ULONG  File length

(files go here)

Position Type  Description
-------------------------------------------
EOF-4  UBYTE  CRC ZZT
EOF-3  UBYTE  CXR ZZT
EOF-2  UBYTE  CRC CER
EOF-1  UBYTE  CXR CER

Okay, this will need a little explaining. To get CRC ZZT, simply add up all of the bytes of the game's .ZZT file % 256 (or && 255 however you want to do it). To get CXR ZZT, XOR all of the bytes of the .ZZT file together. Then XOR the result of this with CRC ZZT.

For CRC CER, add all of the bytes in the .CER file (including the CRC ZZT and CXR ZZT you just wrote to it) together, etc. CXR CER works the same way – XOR everything in the .CER file (including CRC ZZT and CXR ZZT but *NOT* CRC CER). Then XOR CXR CER with CRC CER just like you did for the other one.

Okay, I realize that if you wrote CRC CER after you calculated it, you could simply XOR everything in the .CER and ingore the filal XOR with the CRC. But this way you can calculate CRC and CXR simultanously out of one file buffer and it's better to be consistent anyway.

How EZZT detects a valid game

First of all, here's how the structure of the CER's numeric ID system works:

  • slot 0 - always nonexistant
  • slot 1 - game's LST file (see below)
  • slot 2 - raw 984-byte screen data containing a 41×12 size text block with the image that's shown as EZZT initializes (I use TheDraw to make them)
  • slots 3-9 Reserved for future use
  • slots 10-64999 For use by game programmer
  • slots 65000-65535 Reserved for future use

Structure of the LST file

The LST file is just a plain ASCII file:

  • Line 1 - Name (in all caps) of the game file without the ZZT extention. This is used both in the superencryption (I'll talk about that later) and for EZZT to verify by reading it off of the ZZT status bar.
  • Line 2 - Encyption Key #1 - A 32-digit hex number. This is made up by the game programmer and should be unique for each game. (this is also described later)
  • Line 3 - Name of the game to be shown under the picture on the init screen.
  • Line 4 - Can be either the number 0, 1, or 2. (possible more values in the future)

If Line 4 is 0, the file ends there. If Line 4 is 1, slot 3 in the CER must contain an ASCII file with the global object program (NOTE: I scrapped the global object idea in version 0.25 and while it still works for now it is not guaranteed to be supported in the future and will remain undocumented). If Line 4 is 2, there will be an extra line in the LST file (Line 5) with the filename of a DLM to be loaded.

SUPERENCRYPTION(R)

SuperEncryption is my method to make absolutely sure that EZZT games can't be created until I'm sure EZZT is ready for it […]. When EZZT first starts, none of the commands are available – it works just like normal ZZT. Only one command can be used and is the first command executed by and EZZT game, ']enhance. The format of the enhance command is this:

']enhance <code>

<code> is Key #2 of the superencrpytion key pair. It is generated by a program (the source to which is included later in this message) and is based on Key #1, the name of the game from Line 1 of the LST file, the phase of the moon, etc. *IF* and only if: 1) The CRC/CXR pairs check out, 2) The game name displayed on the ZZT status bar is correct, and 3) ']enhance is executed with the proper Key #2, then EZZT will spring into action and start processing commands (this sequence is somewhat different when loading saved games, but it is still secure). If you load a different game, even a properly set up EZZT-based game, EZZT will shut down and return to normal ZZT mode. Thus, you can only play the game that you started from the command line.

Here's the superencryption program:

---------Cut Here---------
/* Patented super-encryption technology.  NOBODY can break this code. */

#include <string.h>
#include <stdlib.h>

char *superencrypt(char *code, char *name)
  {
    char *result;
    unsigned char bytes[16];
    unsigned char temp;
    char temp2[2];
    int t;
    int q;
    result = (char*)malloc(33);
    for (t = 0; t < 16; t++)
      {
        temp2[0] = code[t * 2];
        temp2[1] = 0;
        bytes[t] = strtol(temp2, NULL, 16) * 16;
        temp2[0] = code[t * 2 + 1];
        temp2[1] = 0;
        bytes[t] = bytes[t] + strtol(temp2, NULL, 16);
      }
    /* Start out with some boring XORs */
    for (t = 0; t < 16; t++)
      {
        q = t % strlen(name);
        bytes[t] = bytes[t] ^ name[q];
      }
    for (t = 0; t < 16; t++)
      bytes[t] = bytes[t] ^ 0x47;
    for (t = 0; t < 16; t++)
      bytes[t] = bytes[t] ^ 0x9F;
    srandom(0xE3);
    for (t = 0; t < 16; t++)
      bytes[t] = bytes[t] ^ (random() % 0x100);
    srandom(name[0]);
    for (t = 0; t < 16; t++)
      bytes[t] = bytes[t] ^ (random() % 0x100);
    /* Now comes the fun stuff :) */
    for (t = 0; t < 16; t++)
      {
        q = (t * 17 + 3) % strlen(name);
        bytes[t] = (bytes[t] + name[q]) % 0x100;
      }
    srandom(bytes[0]);
    for (t = 1; t < 16; t++)
      bytes[t] = ((bytes[t] + (random() % 0x100)) % 0x100) ^ (random() % 0x100);
    srandom(bytes[15]);
    temp = 0;
    for (t = 0; t < 15; t++)
      {
        if (temp == 2)
          {
            srandom(random());
            temp = 0;
          }
        else temp++;
        bytes[t] = bytes[t] ^ (random() % 0x100);
      }
    /* Heh, heh, heh */
    /* Now change it back into text (in reverse order to throw off idiots) */
    for (t = 0; t < 16; t++)
      {
        itoa(bytes[15 - t] / 16, temp2, 16);
        result[t * 2] = temp2[0];
        itoa(bytes[15 - t] % 16, temp2, 16);
        result[t * 2 + 1] = temp2[0];
      }
    result[32] = 0;
    return result;
  }

char *superdecrypt(char *code, char *name)
  {
    char *result;
    unsigned char bytes[16];
    unsigned char temp;
    char temp2[2];
    int t;
    int q;
    result = (char*)malloc(33);
    for (t = 0; t < 16; t++)
      {
        temp2[0] = code[t * 2];
        temp2[1] = 0;
        bytes[15 - t] = strtol(temp2, NULL, 16) * 16;
        temp2[0] = code[t * 2 + 1];
        temp2[1] = 0;
        bytes[15 - t] = bytes[15 - t] + strtol(temp2, NULL, 16);
      }
    srandom(bytes[15]);
    temp = 0;
    for (t = 0; t < 15; t++)
      {
        if (temp == 2)
          {
            srandom(random());
            temp = 0;
          }
        else temp++;
      bytes[t] = bytes[t] ^ (random() % 0x100);
    }
    srandom(bytes[0]);
    for (t = 1; t < 16; t++)
      {
        temp2[0] = random() % 0x100;
        temp2[1] = random() % 0x100;
        q = (bytes[t] ^ temp2[1]) - temp2[0];
        if (q < 0) q += 0x100;
        bytes[t] = q;
      }
    for (t = 0; t < 16; t++)
      {
        q = (t * 17 + 3) % strlen(name);
        q = bytes[t] - name[q];
        if (q < 0) q = q + 0x100;
        bytes[t] = q;
      }
    for (t = 0; t < 16; t++)
      {
        q = t % strlen(name);
        bytes[t] = bytes[t] ^ name[q];
      }
    for (t = 0; t < 16; t++)
      bytes[t] = bytes[t] ^ 0x47;
    for (t = 0; t < 16; t++)
      bytes[t] = bytes[t] ^ 0x9F;
    srandom(0xE3);
    for (t = 0; t < 16; t++)
      bytes[t] = bytes[t] ^ (random() % 0x100);
    srandom(name[0]);
    for (t = 0; t < 16; t++)
      bytes[t] = bytes[t] ^ (random() % 0x100);
    for (t = 0; t < 16; t++)
      {
        itoa(bytes[t] / 16, temp2, 16);
        result[t * 2] = temp2[0];
        itoa(bytes[t] % 16, temp2, 16);
        result[t * 2 + 1] = temp2[0];
      }
    result[32] = 0;
    return result;
  }
---------Cut Here---------

You really don't need superdecrypt, but I included it for completeness. superencrypt() takes two arguments: code is a pointer to the 32-digit (and null terminated) Key #1 from the LST file, and name is a pointer to the null-terminated name of the game (minus the .ZZT extention). Make sure that code is in lower-case and name is in all capitals. It returns a pointer to another 32-digit hex number, Key #2 (the one that should be used in the ']enhance command). If you use this directly, you should free() the returned pointer yourself when you're done with it.

I think that's about enough for now. I'll let you chew on this for a while so I can write some more specs for you to read.

–Craig

release/zzt_enhancer/craig_notes_signing.txt · Last modified: 2021/06/26 22:00 by asie