! --------------------------------------------------------------------
!
! RUNTIME-HINTS-SYSTEM
! for the INFORM 6.x IF-compiler, (c) 1999 Graham Nelson
!
! Provides a Magnetic Scrolls(R)-like hints-system that decodes code-
! sequences into plain text.
!
! Written & copyright (c) 2001, 2006 by Peer Schaefer.
!
! The distribution of this file for free or for profit is allowed, as
! long as this copyright notice and the disclaimer (see below) remain
! intact. You may distribute modified versions of this file under the
! same terms, as long as you place a notice on top of the file that it
! is modified by you, and provide your name and the date of the
! modifications. The unmodified or modified version distributed by you
! must be (re)distributable, modifyable and usable under the same terms
! as this version.
!
! You may use this library unaltered or in a modified version freely
! for your own games, commercial or not. It would be nice if you give
! me some credit or provide the sourcecode, but that is not legally
! required.
!
! NO WARRANTY, NO LIABILITY. PROVIDED FREE OF CHARGE "AS IS". USE ON
! YOUR OWN RISK!
!
! Bug reports, comments and suggestions to:
[email protected]
!
! The latest version should be available at:
!
http://www.wolldingwacht.de/if/hintsms.html
!
! --------------------------------------------------------------------
! DESCRIPTION
! ===========
! Anybody out there remembering the good old hints-system in the
! text-adventures of "Magnetic Scrolls"(R), like The Pawn and The Guild of
! Thieves? You had to type in silly things like <5x rf mz g0 5b rx oo po>
! (that are provided in the manual) on the command-prompt, and the program
! decoded that into a short hint to get you unstuck.
!
! This module provides a very similar hints-system. To encode a hint into
! a code-sequence you can use the new verb "encode", that is only available
! if you compile the game with debug-mode on. In the manual/documentation of
! your game you can provide several code-sequences that you have created in
! this way.
!
! EXAMPLE:
! You can include the following text in the manual of your game:
! "How can I open the small chest in the throne room?
! 42 17 60 49 26 77 6c e1 e2 f7 88 55 24"
!
! To decode such code-sequence your players can use the new verb "hint".
! Typing in
! >HINT 4217604926776ce1e2f7885524
! on the prompt will result in "use a hammer".
!
! The great advantage of such hints-system is that the hints are not
! included in the game itself; the game only contains a decoder for the
! hint-code-sequences. That allows you to publish new hints for your
! games without changing the game itself.
! INSTRUCTIONS
! ============
! Include this file after parser.h
!
! This file creates two new verbs: ENCODE and HINT. The ENCODE verb is
! only available if you compile your game in debug-mode. To encode a hint
! just type ENCODE followed by your hint.
!
! EXAMPLE:
! If you type
! >ENCODE Hello_world!
! on the prompt (please use the underscore _ instead of a space to
! avoid confusing the parser), the computer will output this code-
! sequence:
! fa 7f f0 85 56 77 94 91 1a 77 08 9f 58
!
! To decode such code-sequence use the HINT verb.
!
! EXAMPLE:
! If you type
! >HINT fa7ff085567794911a77089f58
! the result will be "hello world!". Please don't use any spaces
! within the code-sequence to avoid confusing the parser.
!
! That's all.
!
! Bug reports, comments and suggestions to:
[email protected]
! HOW IT WORKS
! ============
! The encoding-engine is very primitive, but hey, we're not encoding state
! secrets here - only hints. In a first step, every character is converted
! into a numerical value. This value is scrambled by calling the ENCODE
! routine. The result is converted into a two-digit-hex-number and printed
! out. When all characters are converted, a checksum is calculated, encoded
! and printed out.
!
! EXAMPLE:
! >ENCODE test
! results in c2 7f 08 9d 70, which means:
! c2 -> a scrambled 't'
! 7f -> a scrambled 'e'
! 08 -> a scrambled 's'
! 9d -> a scrambled 't'
! 70 -> the scrambled checksum
!
! The decoding-engine reverts this process. Each hex-number is converted
! into a numerical value and this value is un-scrambled. A new checksum is
! calculated and compared with the checksum at the end of the code-sequence.
! That's it.
! And here's the code. I apologize for the mess.
! I am neither a good programmer, nor am I very familiar with Inform (nor
! is my english good). If there is a real programmer out there who can
! tidie up a bit here, I would welcome that.
! Please eMail to:
[email protected]
Constant codemask_increment = 19; ! This constant is used during the
! encoding and decoding process as
! a "key". You can change it to
! create your very own hint-codes.
! It must be a constant from 1 to
! 254. I recommend primes.
Global codemask = codemask_increment;
! This is a global counter, used by
! the encoding and decoding routines.
! The following routine converts a two-character-hex-number into a
! numerical value. Syntax is readhex(d16,d1). d16 is the first character
! of the hex-number, d1 is the second character.
[ readhex d16 d1 hi lo;
if ((d16>='a')&&(d16<='f')) hi=d16-'a'+10; else hi=d16-'0';
if ((d1 >='a')&&(d1 <='f')) lo=d1 -'a'+10; else lo=d1 -'0';
return (hi*16)+lo;
];
! Inform provides no xor-operator. I hacked this out, but there is surely
! a more elegant way. Any suggestions?
[ xor n mask a y i;
a = 1;
y = 0;
for (i=1:i<=8:i++)
{
if ((mask&a)==0)
{
if ((n&a)==a) y=y|a;
}
else
{
if ((n&a)==0) y=y|a;
};
a=a*2;
};
return y;
];
! This is the decode-routine. The routine receives one numerical parameter
! and returns a numerical value. Both are in range of 1...255.
[ Decode x y a b i;
x = xor (x, codemask); ! First xor with codemask
a = 1; ! Start with bit no. 1
b = 128; ! Bit no. 8
y = 0;
for (i=1:i<=8:i++) ! This loop mirrors the whole byte,
{ ! making bit 1 to 8, bit 2 to bit 7,
if ((x&b)==b) y=y|a; ! bit 3 to bit 6 etc.
a=a*2; ! Next bit
b=b/2; ! Previous bit
};
codemask = (codemask+codemask_increment)%$ff; ! Increments codemask
return ($ff-y); ! Negate and return
];
[ HintSub hintcode i checksum1 checksum2 c;
if (WordLength(2)<4)
"Too short hint-code."; ! At least four characters
hintcode = WordAddress(2); ! Here it starts
for (i=1 : i<=WordLength(2) : i++) ! Only hex-digits allowed
{
c = hintcode->(i-1);
if (~~( (c>='0') && (c<='9') ||
(c>='a') && (c<='f') ) )
"Illegal character in hint-code (maybe a typo?)";
};
checksum1 = 73; ! Base-value of the checksum
! (This is a arbitrary value.
! You can change it to any
! value from 0 to 255.)
codemask = codemask_increment; ! Reset codemask
for (i=1:i<=(WordLength(2)-2):i++) ! Calculate checksum
if ((i%2)==0)
checksum1 = checksum1
+ Decode (readhex ( hintcode->(i-2),
hintcode->(i-1) ) );
checksum2 = Decode ( readhex ( ! Original checksum
hintcode->(WordLength(2)-2),
hintcode->(WordLength(2)-1)
) );
if ((checksum1%$ff)~=(checksum2%$ff)) ! Checksum OK?
"Sorry, invalid hint-code (maybe a typo?)";
codemask = codemask_increment; ! Reset codemask
for (i=1 : i<=(WordLength(2)-2) : i++) ! Main-decoding-loop
if ((i%2)==0)
{
c = Decode ( readhex (hintcode->(i-2),
hintcode->(i-1) ) );
if (c=='_') c=' '; ! Convert _ to space
print (char) c; ! That's it!
};
print "^"; ! CR
rtrue;
];
! The following parse-routine grabs at much text as possible.
[ ParseAll;
if (NextWordStopped()==-1) ! No text at all.
{
#ifdef DEBUG;
if (action_to_be==##Encode)
print "(Missing text for encoding.)^";
else print "(Missing hint-code.)^";
#endif;
#ifndef DEBUG;
print "(Missing hint-code.)^";
#endif;
return GPR_FAIL;
};
if (NextWordStopped()~=-1) ! More than one word
{
#ifdef DEBUG;
if (action_to_be==##Encode)
print "(Please use no spaces. Use underscores _ instead.)^";
else print "(Please use no spaces. If the code-sequence contains any spaces, just omit them!)^";
#endif;
#ifndef DEBUG;
print "(Please use no spaces. If the code-sequence contains any spaces, just omit them!)^";
#endif;
return GPR_FAIL;
};
return GPR_PREPOSITION; ! O.k.!
];
Verb meta 'hint' * ParseAll -> Hint;
#ifdef DEBUG;
! The following routine prints out a given 1-byte-value as a 2-digit-hex-
! number. Example: "print (hex) 255;" results in "ff".
[ hex n;
if ((n/$10)<10) print (n/$10);
else print (char) 'a'+(n/$10)-10;
if ((n%$10)<10) print (n%$10);
else print (char) 'a'+(n%$10)-10;
];
! This is the encoding-routine. The routine receives one numerical parameter
! and returns a numerical value. Both are in range of 1...255.
[ Encode x y a b i;
a = 1; ! Start with bit no. 1
b = 128; ! Bit no. 8
y = 0;
for (i=1 : i<=8 : i++) ! This loop mirrors the whole byte,
{ ! making bit 1 to 8, bit 2 to bit 7,
if ((x&b)==b) y=y|a; ! bit 3 to bit 6 etc.
a=a*2; ! Next bit
b=b/2; ! Previous bit
};
y = xor($ff-y, codemask); ! xor with codemask
codemask = (codemask+codemask_increment)%$ff; ! Increment codemask
return y; ! That's it!
];
[ EncodeSub hintcode i checksum res;
hintcode = WordAddress(2);
checksum = 73; ! Base-value of the checksum.
! (This is a arbitrary value.
! You can change it to any
! value from 0 to 255.)
codemask = codemask_increment; ! Reset codemask
for (i=1 : i<=WordLength(2) : i++)
{
res = (hintcode->(i-1)); ! Get character
checksum = checksum + res; ! Create checksum
print (hex) Encode(res), " "; ! Encode!
};
print_ret (hex) Encode((checksum%$ff)); ! Encode the checksum
];
Verb meta 'encode' * ParseAll -> Encode;
#endif;
! End.