A few technical questions

I have a few random TADS 3 questions of a technical nature.

If I have a variable that is holding an object or a class, can I get a single-quoted string of the object/class’s name? See below.

class MyClass: object
  getClassName() {
    // how can I make it so this method returns 'MyClass', while
    // subclasses return 'MySubclass', etc?
  }
;

myObject: object
  getObjectName() {
    // how can I make it so this method returns 'myObject'? Is it same as above?
  }
;

Is it possible for an object to have private variables in TADS 3, as in Java or .NET?

// Example:
class IntegerObject: object
  private value = 0
  get() { return value; }
  set(val) { value = val; }
;

I imagine the answer is no, because (1) I’ve never seen this done in TADS 3 code, and (2) if this were possible, it would probably be done for things like Thing.location, which you’re not supposed to modify directly. Still, I just feel like asking just to make sure, since I’ve never actually read that this is impossible.

I’m working on a TADS 3 project that will be writing out to, and reading in from, an external file. If unencrypted, the file would be a tab-delimited text file, but I will eventually want it to be encrypted.

a.
I’m no cryptography expert, but the idea that popped into my head was to bit-shift each byte of text based on a set of crypto keys. For this to work, it would need to be a non-lossy “circular” bit shift (en.wikipedia.org/wiki/Circular_shift), but TADS 3 bit-shifting operators are the lossy kind, so I don’t know if my idea would be easy or would run efficiently. If anyone can offer any suggestions on how (and whether) to do this, I’d be grateful.
b.
Or if you have a better idea on how to to encrypt a text file, I’d be glad to hear any suggestions. I’m most apt to go for whatever idea is easiest but also secure and fairly efficient.

Thanks,

Greg

Regarding 3a:
Right after posting this I realized that the bit-shifting idea might have some flaws, such as possibly leaving 1 out of 8 characters unencrypted. So never mind 3a I guess, but I welcome answers to the other questions.

Yes, but not by default. Since the symbol table is normally not present in running program, you are not allowed to get name of object as a string. There are alternative approaches to identify type of object at runtime, you can use conditions like myObject.ofKind(MyClass) or you can call getSuperclassList() to examine inheritance of your object in detail. That way you can call for example methods of given class so you can do lots of things with the class, but you won’t get a name of class as a string.

But the symbol table is present at preinit phase of any program and also at run time, but only for programs build for debugging (not for release). This is employed by TADS’s extended reflection services, which makes a copy of the symol table at preinit phase and then allows you to query the table at run time even in programs compiled for release.

You can add reflection to your game by adding reflect.t file (part of standard TADS distribution) to your Makefile.t3m. Note this is not a header file, you won’t use #include directive but rather add it as a separate source to your project the same way you add any of yours source code files. You don’t need to copy the file to your project folder, TADS compiler will find it since it is in standard library path. I’m not sure how to do that in Workbench since I’m using frob and I’m writing my own Makefiles (then I just add a line “-source reflect.t”), but sure there is some easy clicky way in Workbench.

Next you can write your method getClassName() { return valToSymbol(); } Or without the method you can directly issue a command "<<myObject.valToSymbol()>> ";

  1. I’m pretty confident there is no built-in notion of private variables in TADS 3. The library occasionally uses the convention of appending an underscore to properties that are meant to be treated as private (or protected), but that’s purely a matter of convention; no access restriction is enforced at compile- or run-time.

You could simulate something quite close to run-time-enforced access restriction to methods at run-time, but it’s a bit of overhead to be added to each private method. The idea would be the following:

class MyClass: object

    myPrivateMethod() {
        checkPrivate();
        /* Do my stuff. */
    }
;

function checkPrivate()
{
    /*
     *   Get the stack infos for the callee (the code that performs this access check) and the caller (the code that called the callee).
     */
    local calleeStackInfo = t3GetStackTrace(2);
    local callerStackInfo = t3GetStackTrace(3);

    /* If both don’t have the same 'self', this is an access violation. */
    if (callerStackInfo.self_ != calleeStackInfo.self_) throw new AccessException();
}

/* Thrown in unauthorized calls to methods with restricted access. */
class AccessException: Exception;

With this, can in theory create private getter and setter methods, which would allow you to simulate private properties. Of course, in practice, the value of the property still has to be stored somewhere non-private.

  1. For what it’s worth, the .t3 files hide their text contents against casual inspection by simply XORing a constant character with each text character. This is non-lossy, since XORing the same mask character with an encrypted character gives back the original one. It’s not a very secure way of encryption, since a determined attacker who knows the encryption scheme simply has to try out the 255 possible XOR masks and see which mask gives a result that makes sense. But for that matter, bit-shifting seems even more straightforward to break as you only have 7 shift-distances to try.

What use are you contemplating for this? If it’s just to guard a player against the temptation to cheat by looking at the contents of the file, the XOR mask may be discouraging enough.

Michel replied in between, so some of my answer is duplicate, but I’ve already written it…

No, not really. TADS has wide open design, but when some property or method is really meant to be used privately by the library only and not by the programmer, then it has underscore at the end of its name. This does not enforce anything, it’s just a warning sign for the programmer.

Standard way would be to XOR each byte of cleartext with each byte of the encryption key to form encrypted text. XOR is an operation which flips every bit of cleartext for which the corresponding bit of encryption key is 1 and leave intact other bits. For example 0110 ^ 1010 = 1100. Decryption is simple, you once again repeat the same operation with the same key (1100 ^ 1010 = 0110).

Security lies in the quality of key. When the key is short and for example ASCII it would be child’s play to crack it, but when truly random and long enough, than it will be more secure even to a point when the key has same length as a message to be encrypted you get mathematically proved uncrackable cipher, see https://en.wikipedia.org/wiki/One-time_pad :slight_smile:

However don’t get be fooled, there are several problems here. Any computer random number generator won’t provide you with truly random numbers. Initialising random number generator to certain seed value and then generating random key sequence of any length (even message length) and then reinitialise generator to same seed and generate same sequence to decrypt is a rookie mistake.

Even if you come with long and truly random key and even with better (even professional) cryptographic algorithm, you still get no real security because the user has full control over the game and he can look into memory space of running game and find the key here no matter how hard you try to avoid this. Only truly secure arrangements is to host the game as a webplay game on a server under your control with your own storage server and don’t allow the player to download your game.

But that may take a little more effort than you are willing to do :slight_smile: So at least generate a long and truly random key and use the XOR method to make it quite laborious to crack.

Great responses, thanks to both of you guys!

Tomas, thanks for the tip. I had looked at the system manual page on reflection before posting, but hadn’t slogged through the entire thing, and hadn’t landed on the part about valToSymbol.

Yeah, that’s what I thought, just wanting to check. Michel, I never would have though of diving into stack trace info to enforce this… very clever, although overkill for what I’m doing.

I don’t need encryption that goes beyond what’s used for TADS 3 save files. My project is aimed at storing certain kinds of meta-information that shouldn’t be forgotten just because the player types UNDO or forgets to save before quitting. So it’s game state information, not nuclear missile launch codes.

Regarding XOR, sounds like just what I wanted. I appreciate the explanation, I had heard that algorithm mentioned when trying to research this on my own, but hadn’t heard it explained so clearly before.

The only thing I still don’t get is what the TADS 3 syntax would be for XOR encryption. For one thing, the system manual mentions a bitwise XOR operator, but not a byte-wise one, which sounds like what you both mean. And for another thing, would you apply the XOR directly to a string, or would you transform the string to a byte array first, or what?

Greg

Edit: Actually I guess it looks like ^ (“bitwise xor”) actually might be the operator for this. If you can tell me whether this should best be performed on a byte array (as I suspect), let me know. Otherwise I may be able to figure this out on my own too.

It’s ^ we were talking about, indeed. This operator actually doesn’t work on characters or on bytes directly, but on integers, which are four bytes long. The good thing about this is that you can therefore use four-byte masks; the bad thing is that you have to handle each group of 4 bytes together, which is slightly more hassle.

Not very difficult though, actually. The following functions will encode strings as masked byte arrays and decode the masked arrays as strings, respectively, which if I understood your requirement properly should be all you need. You can then write the masked byte arrays to your file instead of the original strings. You can pass a mask integer of your choice to those functions to use as the bitwise XOR operand, or just use the default 0xFFFF, which will have the effect of flipping all bits.

/* Converts a string to an obfuscated byte array, using the specified mask. */
function hideString(str, mask = 0xFFFF)
{
    /* Store the string in a new array, as a count prefix followed with UTF-8 data, 0-padded to a multiple of 4 bytes. */
    local array = ByteArray.packBytes('S/ux4!', str);
    local length = array.length();
    /* Iterate over each group of 4 bytes. */
    for (local offset = 1; offset <= length; offset += 4)
    {
        /* Read the 4 bytes as an int, little-endian. */
        local int = array.readInt(offset, FmtInt32LE);
        /* Apply the mask. */
        int ^= mask;
        /* Store the masked int, big-endian (as an added bonus that will reverse each group of 4 bytes). */
        array.writeInt(offset, FmtInt32BE, int);
    }
    /* Return the obfuscated array. */
    return array;
}

/* Converts an obfuscated byte array to a string, using the specified mask. */
function unhideString(array, mask = 0xFFFF)
{
    local length = array.length();
    /* Iterate over each group of 4 bytes. */
    for (local offset = 1; offset <= length; offset += 4)
    {
        /* Read the 4 bytes as an int, big-endian. */
        local int = array.readInt(offset, FmtInt32BE);
        /* Unapply the mask. */
        int ^= mask;
        /* Store the unmasked int, little-endian. */
        array.writeInt(offset, FmtInt32LE, int);
    }
    /* Decode the array as a string, using the same format it was encoded to, and return it. */
    return array.unpackBytes(0, 'S/ux4!').car();
}

Edit: actually the default mask should be 0xFFFFFFFF, not 0xFFFF (otherwise only two bytes every four will be masked).

Some alternative approach would be to autosave game on exit and autoload autosaved game on start and saving the state in transient objects during runtime (and copy transient objects into persistent before each save and restore transient objects from persistent when there are is no transient state after load).

Thanks Michel, I really appreciate this. This is going to give me something really fun to dig into.

Tomas… Hmm. I was all set on figuring out this file i/o stuff, but I guess your approach would mean less reading/writing to disk. I’ll think about it.

Though at the other end of the spectrum, you may want your settings to persist even across abnormal session terminations (such as power outages or interpreter crashes), in which case you’d want to save the persistent state to a file as soon as possible when it changes.

Also, are you aware of the settings.t module in the adv3 library? It provides for the management of general settings shared across games (and game sessions). Depending on whether your persistent settings are meant to apply to just one game or to all games that use them, this may just provide the persistence management that you need out of the box.

The data I want to save is game-specific. Actually, no reason not to just tell you my idea: a way to store information about “achievements” in the style of console games or Steam games. Such games remember what you’ve achieved irrespective of saving, loading, undoing, etc., but the information is game-specific. I’d like to finish an extension for this within a month or so.

I was just thinking something like that. I like Tomas’s idea of storing the data in memory as a transient object, as that saves me from having to read from disk every single time the player types UNDO. But as for writing to disk, I think I’ll just do that automatically every time there is a change of state within the set of data my extension is concerned about.

youtube.com/watch?v=31g0YE61PLQ

Nice idea. Might provide replay value. Or formalize it, rather, since that alone can’t give replay value to a game that doesn’t provide it by different means, but I can see how it would be nice for a game to keep track of and reward, say, different plot branches that have been explored. I can see how a game such as Slouching Towards Bedlam might possibly have used this for good effect, too.

Haha, I want to unleash achievements as a virus upon the IF community! Soon, all IF games will have achievements unless the author figures out how to remove them!

Seriously, though… the impulse to write this was, I’m working on a game that includes a section that can be breezed through very quickly but has lots to explore. I don’t want people to just miss all the cool stuff. It’s not really my plan to start a trend within IF, but I might as well make my code reuseable for any TADS 3 authors who are in the same situation.

Greg

So what you’re saying is that this particular section is not worth exploring, boring, or otherwise tedious. To counteract this you’re adding “achievements.”

“Congrats. It’s an achievement to play through this part because it’s a boring thing to do but you stuck with it, going through the pain.”