A Guide To ZZT Lock Picking

ZZT DRM and how to defeat it

Authored By: Dr. Dos
Published: Feb 19, 2019
Revisions (as of Oct. 8, 2023, 8:43 p.m.):
2023-10-08: Updated the Omega Lock section to link the one game that appears to have actually used it.
RSS icon

Page #2/2
< 1 2

So that's really it as far as locks go. Ultimately locking ZZT worlds was futile, with every one being easily circumvented as soon as the knowledge of how to do so got around. By the time external editors like KevEdit and ZZTAE rolled around, not only were people not bothering with locks, but they were truly dead as these editors paid no mind to any locks. But just because KevEdit doesn't respect ZZT's locks, there's always an opportunity to write locks for KevEdit!

NedLock

nedlock

No game purposely used what I'm calling a NedLock, named for the game where it was discovered, and in fact, KevEdit still slowly but surely receives patches and bug fixes which mean that this little exploit has actually been patched out fairly recently (December 2017). Prior to then however, KevEdit did have a bug which would allow you to prevent it from accessing certain boards. Ironically, these boards could be opened in ZZT's own editor just fine.

While playing Jeremy LaMarr's classic Ned The Knight, I managed to get stuck and had to edit the game to figure out where to go next. Alas, both KevEdit and the Museum's file viewer at the time were locked out of certain boards.

How It Works

The issue has to do with ZZT's file format and corrupt boards. Ned The Knight's second file has a corrupt board in the middle of the world, and KevEdit/the file viewer couldn't access anything after it. ZZT's file format is structured as an opening world header, followed by the data for each board. When one board ends, the very next byte is the start of the following board. It is very easy when coding something that works with ZZT files to just run through the file linearly, processing each board as they show up in the file.

Corrupt boards, throw a wrench into this plan.

When a board is corrupt, ZZT's constants don't really hold true. A board itself is split into three portions, the board header information (its title, time limit, etc.); a compressed format that details the 80x25 elements that make up the board; and lastly all the stats that contain things like ZZT-OOP or what a lion's intelligence value is. Board parsing involves running through that compressed element data, unpacking it until all 1500 tiles are accounted for, but a corrupt board is kind of a garbage-filled mess and might have more or fewer than 1500 elements. This causes things to desync as board element data starts getting parsed as stat data instead, and basically everything snowballs from there where random bytes suddenly say "Ok this stat has 10,395 bytes of ZZT-OOP" and the march through the file continues, breaking into the next non-corrupt board's legit data as if it were still part of the previous one's.

We're incredibly fortunate that ZZT stores the size of the board in the board header which does correctly track how big all the garbage data is and retains an accurate value even on corrupt boards. If it didn't, a corrupt board would have meant that data after a corrupt board was also corrupt, and I imagine far fewer games would recover from having a board get corrupted in the early days without any tools to find and extract the rest of the safe data.

Circumvention

As stated before, this is fixed in recent releases of KevEdit (and the file viewer). Instead of traveling through the board's data until it ends, the program just needs to use that board size value and jump to the right address in the file to the start of the next board.

RLE Lock

But even the latest version of KevEdit (phew, and not the file viewer) falls victim to this one. This time we get into an odd quirk of ZZT's board decoder to engineer a board that KevEdit doesn't understand how to parse, but ZZT does.

When discussing NedLock, I mentioned boards having a component that was compressed. ZZT uses what's known as Run-length Encoding (RLE) as a way to simplify storage for a board. The tiles that make up a ZZT board are encoded as three bytes: the quantity, the element, and the color. So 04 16 2A translates to four normal walls that are green-on-dark-green. These patterns repeat until all 1500 tiles are decompressed. Since the quantity is restricted to a single byte, the maximum value is FF, 255 instances of the same element and color in a row. Where ZZT and KevEdit diverge however, is what happens if the quantity byte is set to 00. KevEdit takes the completely reasonable approach of "Zero means zero". ZZT... treats it as 256!

I have to imagine this is deliberate, and was something Tim cooked up to shave off file sizes even further, but ZZT will treat 256 identical tiles in a row as 255 + 1, leaving the memory saving technique unused. The important part however, is that ZZT will still decompress a 00 to 256, while KevEdit will not.

How It Works

Make a board with 256 of something in a row and hex editing it to use 00 in its compressed state instead. (It is far easier to make 255 in a row, change it to 256, and then shave 1 off of another set of repeating elements so as not to throw off the board size as well.) ZZT will know how to decode this, but KevEdit will essentially skip the 256 tiles, turn up drastically short and begin parsing stat data as board data and once again things kind of explode from there. (KevEdit is nice enough to just say it can't load the board rather than crash.)

With earlier version of KevEdit, you could use this effect on an early board and prevent access to anything below via the NedLock effect. On the latest version, you're going to need a stretch of 256 of something on every board you want to keep KevEdit out of. That's 17% of the board that needs to be identical. Every time. So maybe it's not very practical. It's the only lock that can't be foiled. Err, except by using ZZT that is.

There's also a chance that Kev or somebody will see this article and submit a patch and fix it.

Circumvention

Open it in ZZT and save. The 256 will be broken back up into a 255 and a 1.

Medusa Lock

During a livestream of Rabbitboots's fantastic Faux Amie, there's a board with a lot of transporters set to cycle 255 to drastically slow down their animation speed. I thought it was a weird design decision, saying that if the animation is tied to the transporters' cycles, then why not just use cycle 0 and freeze them in place?

Well it turns out cycle 0 transporters crash ZZT.

This is pretty weird. No other element does this. GreaseMonkey, a regular over on the Worlds of ZZT Discord server decided to look into what exactly was going on. On Oct. 10, 2018 they discovered while poking at a disassembly of ZZT that when determining which character to display when drawing a transporter, the cycle is used as the divisor.

So a cycle 0 transporter crashes because ZZT tries to divide by zero.

How It Works

Okay, but how do you make a lock with it?

Well, the transporter isn't crashing ZZT by existing, or even by usage, but by being drawn. Put a cycle 0 transporter in a dark room and it works exactly the same as a standard transporter! But attempt to gaze upon the gorgon and be annihilated.

You'll have to make some small changes to your world to take advantage of this, but the same can be said for a Super Lock. Add a board, make it dark, surround the player with passages to the original starting board, and put a cycle 0 transporter somewhere. The dark board can be played safely, but the editor doesn't render dark rooms as dark, which causes the transporter to attempt to be drawn and crashes ZZT.

Interestingly, this one can kind of be ported over to Super ZZT which doesn't have dark boards, but it does have a camera system as boards are too large to fit on the screen in their entirety. It just so happens that the editor uses the camera coordinates stored in the world for where to position the camera, while playing instead tries to center the camera on the player. Edit the stored coordinates, and you can point the editor to a medusa transporter while the game stays with the player instead essentially creating the same effect.

Circumvention

And yet even Medusa can be slain with just ZZT if you're clever enough.

Debug mode won't help here. ZZT is happy to try and open the Medusa Locked file, and will promptly crash. What's instead needed is a way to force ZZT's editor to render a dark board as dark, and sure enough there's a way.

Open ZZT's editor, and load a file like TOWN.ZZT that's locked. You'll get your message about the game being locked an uneditable. For some weird reason, that message puts ZZT in a slightly confused state. The editor remains open on a new world, and everything seems normal, but in fact the graphics are being rendered as if ZZT was in its gameplay mode. This means that invisible walls are drawn as " " rather than "░", and that dark rooms, are rendered in darkness.

medusa

From there, opening the Medusa Locked world will put you on the medusa locked board drawn in darkness and you can delete the board, blindly try and erase the transporter, or just save on a different board. The Medusa Lock is defeated by showing it another lock.


That's all of them! Not one of these things is capable of any more than minor annoyance. Ultimately the ZZT community rejected locking and Medusa/Ned/RLE locks are less intended to "protect" a ZZT world and more intended as thought exercises continuing the fruitless quest started by Sweeney himself. Still, they're a lot of fun to pick apart.

Page #2/2
< 1 2

====== A Worlds of ZZT Production ======

The Worlds of ZZT project is committed to the preservation of ZZT and its history.

This article was produced thanks to supporters on Patreon.

Support Worlds of ZZT on Patreon!
Top of Page
Article directory
Main page