Stefan Wolff's UHS (Universal Hint System) page

Grokking the UHS

The UHS (Universal Hint System) is a system designed to help players who get stuck while playing (computer) role-playing or adventure games. The UHS philosophy is to show only the hints necessary for the player to become unstuck, without spoiling the fun of solving the rest of the game's puzzles. There is a wealth of UHS hint files at http://www.uhs-hints.com/ containing hints for games old and new.

This document describes, to some detail, the results of my digging into the UHS file format. I dug for several reasons: Apart from being a resource for people curious of the inner workings of the UHS format, this document is also supposed to be the documentation for my UHS-to-HTML converter, written in May 2005, avaiable elsewhere on this page.

By no means is this meant to annoy the creator of UHS, Jason Strautman, by publishing things intended to be secrets. Contact me if you feel this is a violation.

UHS2HTML

UHS2HTML is not currently online.

UHS2HTML.pl is a Perl script, written by myself in May 2005, that converts a UHS file into a truckload of HTML files, such that the hints (and embedded images, etc) may be viewed using a vanilla web browser, one hint at a time, true to the spirit of the UHS. It supports every UHS format known to me (UHS88a, UHS91a, UHS95a, UHS96a), yet some (unimportant) features are not yet implemented. Since I've yet to encounter a UHS file containing sound, this is not yet implemented.

The syntax is:

perl UHS2HTML.pl uhsfile.uhs

This will convert the file uhsfile.uhs into a squillion HTML and PNG files in the current directory, overwriting files without warning. Use with caution!

After converting, just load in the generated file index.html into your web browser. Beware, however, that this code is not ready for prime-time. It's merely a hack, done for my own convenience. Although standard disclaimers apply, you shouldn't worry too much: UHS2HTML won't do any harm if you know what you're (and it's) doing.

Bugs

Yes, probably plenty. Contact me (see below) if you encounter one or (even better) if you fix one. Note, however, that this is the first larger program I've written in Perl, and it shows!

UHS88a

The original UHS format (UHS88a) was developed in 1988 by Jason Strautman. UHS88a is pretty simple, and doesn't support all the bells, whistles and gongs of its younger siblings. Like these, it is based on lines (MS-DOS style, terminated by Carriage Return (ASCII 0x0d), followed by a Newline/Linefeed (ASCII 0x0a)). Strings are encrypted using a fairly simple arithmetic substitution cipher (this probably isn't the correct name, though).

UHS88a decryption

The decryption of each character of an encrypted string may be described by the following pseudocode function, which returns the ASCII value of the decrypted character when i is the ASCII value of the character in question:

if i<32
  return i
endif
if i<80
  return 2*i-32
else
  return 2*i-127
endif
An example: Say we want to decrypt the string "gHrGtGs iqrGr".

The character "g" has the ASCII value 103, which is greater than or equal to 80, so our function above would return 2*103-127=79, which is the ASCII code for "O".

The character "H" has the ASCII value 72, which is less than 80, so our function above would return 2*72-32=112, which is the ASCII code for "p".

So far, we've got "Op", and continuing like this, we'll get the decrypted string "Opening Scene". Tada!

UHS88a file components

A UHS88a-style file has two type of components. I'll call them links and hints.

The link

A link points to either a hint or another link. A link is defined by two pieces of information (each on its own line in the file): A label and the line number of the component this link points to. The label is encrypted using the cipher described above.

The hint

A hint points to the next hint or nowhere. A hint just consists of a single line, namely the hint itself, which is encrypted using the cipher described above.

The file structure

A UHS88a-file consists of a header, then a tree-like structure of links, followed by a list of hints. When interpreting line numbers, the four-line header is ignored, so all line numbers given in the file should be increased by four to get the actual line number within the file -- assuming that the first line of the file is line one (and not line zero).

I'll demonstrate the format using the UHS hints for the Interplay adventure game oldie "Borrowed Time" (UHS hint file available at http://www.uhs-hints.com/):
Line  Content
1    "UHS", a magic marker indicating a UHS file.
2    "Borrowed Time", the main title
3    "73" the line number of the first hint (meaning line 77)
4    "143" the line number of the last hint (meaning line 147)
[end of header]

[directory tree]
5    "gHrGtGs iqrGr"="Opening Scene"
6    "23" the line number jumped to when selecting this link (meaning line 27)
[...]
25   "kIpHHtGs jH JDr apyr"="Wrapping Up the Case"
26   "69" -- this is the last entry of the main directory, since a link above linked to line 27.
27   "kDpJ Bw d Bw tG JDr wCCtqr"="What do I do in the office" (note: no question mark)
28   "73" link destination line no (meaning line 77)
29   "4w{ Bw d ryqpHr CIwv JDr JDzsy"="How do I escape from the thugs" (note: no question mark)
30   "76" link destination line no (meaning line 80)
[...]
75   "4w{ Bw d pIIryJ 3pIGDpv"="How do I arrest Farnham" (note: no question mark)
76   "141" link destination line no (meaning line 145)
[end of directory tree]

[hints]
77   This is the first hint line, as indicated in the header.
[...]
147  This is the last hint line, as indicated in the header.
From this disection, we can see that, from the main list of links (lines 5 to 26, inclusive: "Opening Scene" to "Wrapping Up the case"), we may jump to the "Opening Scene" list of links, where we may jump to the "What do I do in the office" list of hints (lines 77 to 79, inclusive).

Note the missing question marks. UHS2HTML inserts question marks whenever a link points to a list of hints, and not to a list of links. From the UHS88a files I've seen, this seems to be the convention. This may not be the canonical way of doing it, however, so you may experience missing and erroneously added question marks here and there.

New-style UHS formats (UHS91a, UHS95a, UHS96a)

For backwards compatibility (of sorts), the younger generation of UHS files has a header consisting of a UHS88a file telling users using an old UHS reader to upgrader to a newer one. This header takes different forms, but is always terminated by the line: "** END OF 88A FORMAT **".

Starting with UHS91a (I think), the format changed drastically. It was (perhaps inspired by the IFF file format) turned into a format consisting of "hunks", albeit still line-based. A hunk is defined using a line as

1234 foo

meaning "Here is a hunk of type foo, taking up a total of 1234 lines, including this one". This is nice, since software may choose to skip hunks it doesn't know how to handle or doesn't care about.

New-style UHS line numbers

Line numbers in UHS91a files and newer count such that the first line following the "** END OF 88A FORMAT **" line is line number 1. This will typically be the main subject hunk.

A new encryption format

When the UHS95a format came around (I believe), the UHS developers introduced a new form of encryption that's based on a key generated from the label of the main subject hunk. The main subject hunk typically (always?) follows immediately after the "** END OF 88A FORMAT **" marker. This label is typically the name of the game. The following snippet of pseudo code describes the generation of the key, based on the label of the main subject hunk and the three-character string k containing the word "key". I'll be using x[n] to denote the ASCII value of the n'th character of the string x, with indices starting at n=0.
for i=0 to length(mainlabel)-1
  key[i] = mainlabel[i] + (k[i mod 3] xor (i + 40))
  while (key[i]>127)
    key[i]=key[i]-96
  endwhile
endfor
Using this key, encrypted strings may be decrypted in two very similar ways, depending on the hunk type:

Variant 1: nesthint hunks, ...
for i=0 to length(message)-1
  codeoffset = i % length(key)
  key[i] = message[i] - (key[codeoffset] xor (i + 40))
  while (key[i]<32)
    key[i]=key[i]+96
  endwhile
endfor
Variant 2: text hunks, ...
for i=0 to length(message)-1
  codeoffset = i % length(key)
  key[i] = message[i] - (key[codeoffset] xor (codeoffset + 40))
  while (key[i]<32)
    key[i]=key[i]+96
  endwhile
endfor

Hunk types

The following is a description of the most interesting hunk types. There are a several other hunk types most of which have a straight-forward format.

subject hunks

Each subject hunk consists (apart from the hunk header) of the label of the hunk, followed by its sub-hunks. Example:
6 subject                   [this subject hunk consists, in total, of 6 lines]
Frotzing the baz	    [subject label]
[4 lines of sub-hunks]	    [4 lines of subhunks + 2 header lines equals 6 lines]

link hunks

Each link hunk consists (apart from the hunk header) of the hunk label, followed by the link destination line number.

Example:
3 link                      [this link hunk consists, in total, of 3 lines]
How can I foo the baz?	    [link label]
203                         [link destination line number]

text hunks

A text hunk points to some string of encrypted data beyond the main subject hunk. After the hunk header line and the hunk label line, a text hunk has a third line describing the location and length of this encrypted data string. This third line consists of four numbers: The text hunks are encrypted using the new-style UHS95a encryption scheme, variant 2.

Example:
3 text                      [this text hunk consists, in total, of 3 lines]
List of insults		    [text label]
000000 0 0761355 008986	    [unknown, unknown, file offset, length]

hint hunks

Each hint hunk consists (apart from the hunk header) of the hunk label, followed by a sequence of encrypted hints separated by lines containing a single hyphen.

The hints are encrypted using the old-style UHS88a encryption scheme.

Example:
5 hint                      [hunk header]
How do I moon the glutzpah? [hunk label]
slej n34sad lfn             [first hint, encrypted]
-                           [hint separator]
4ijf 3l5jh l34nndfcm        [second (and last, since this is the fifth line) hint, also encrypted]

nesthint hunks

A nesthint hunk is must like the hint hunk except that it may contain nested (hence the name) hunks.

The nesthint hunks are encrypted using the new-style UHS95a encryption scheme, variant 1.

Example:
12 nesthint                 [hunk header]
Why can't I frob Wally?     [hunk label]
slej n34sad lfn             [first hint, encrypted]
-                           [hint separator]
4ijf 3l5jh l34nndfcm        [second hint, also encrypted]
=                           [nested hunk indicator]
3 link			    [nested hunk, belonging to the second hint]
Where's Wally?		    [nested hunk, belonging to the second hint]
228                         [nested hunk, belonging to the second hint]
nbrmr 3 r4njkn		    [remainder of second hint, also encrypted]
-                           [hint separator]
dn 45vn., mn4 56mgflm       [third (and last, since this is the twelfth line) hint, also encrypted]

hyperpng hunks

Each hyperpng hunk consists of the hunk header line, followed by a line containing three decimal numbers and then zero or more clickable "hotspot" definitions. The three numbers in the line following the hunk header line are: Each hotspot definition consists of a line containing four numbers defining the clickable rectangle, followed by a hunk.

Example:
11 hyperpng                 [hunk header line]
Map of Fooville             [hunk label]
000000 0096351 002051	    [unknown, map file offset, map file length]
0107 0000 0230 0149         [hotspot 1 rectangle definition]
3 link                      [hotspot 1 hunk -- a link]
Fooville, Old Town	    [hotspot 1 link label]
15208                       [hotspot 1 link destination line number]
0230 0000 0348 0149	    [hotspot 2 rectangle definition]
3 link                      [hotspot 2 hunk -- a link]
Fooville, New Town	    [hotspot 2 link label]
16370                       [hotspot 2 link destination line number]
I believe the hyperpng type hunk was introduced with the UHS96a format.

Escape sequences

The hash (ASCII 0x23) is used as an escape code to change output style within UHS strings.

Examples:
#p+ means "start using proportional (normal) font"
#p- means "start using non-proportional font"
##  generates a single hash (#)

CRC checksum

The last two bytes of each UHS file comprise a 16-bit CRC checksum, used to protect users from reading corrupted hint files and to prevent third parties from tampering. UHS2HTML calculates the correct checksum and displays the stored one, but otherwise ignores them completely.

Access control

This resides in the aptly named incentive hunk, which is apparently an encrypted (using the "new-style" encryption described above) list of file offsets to hints restricted to registered users of the UHS hint readers. The web interface at http://www.uhs-hints.com/ is apparently not hampered by any access restrictions. Access control is not implemented in UHS2HTML.

Important

You are not allowed to publish or broadcast the converted UHS files in any way or form. Besides, the conversion of some UHS files may be prohibited. Check the file before converting.

Danger, Will Robinson! Danger!

Contact

Look here: http://www.swolff.dk/

Last updated on 5th of June 2005