Anti-external-editor locks
Posted: Sun Apr 15, 2007 8:07 am
First of all, a disclaimer: I strongly advise against locking ZZT games. Although locking a game makes it difficult to cheat by editing, it also makes it difficult to fix bugs. In addition, it's harder for new programmers to study your code and learn from it. The following technique could be used to strengthen a lock, but I'm posting it only because I thought it might interest my fellow ZZT experimenters. Please don't implement it in a real game.
Now that that's over with, here's a little ZZT world that dares you to deface it. The problem is, the file is locked - you can't edit it by conventional means. Most existing locks can be easily bypassed with an external editor. However, the method used to lock this file has an addition that (to my knowledge) has never been used before. This addition prevents the file from opening in both ZZTAE and KevEdit.
How it works (for those who feel so inclined as to figure it out themselves, the following contains spoilers):
ZZT stores terrain data using run-length encoding. This basically means that instead of describing all 1500 tiles on each board, ZZT will describe a tile once and specify how many times it occurs. For example, a line of 10 gems in a row could be described as [10][Tile ID for a Gem][Gem color]. The number of copies is stored in a single byte, which is treated as an 8-bit unsigned integer. This basically means that the number can go from 0 to 255. 1 through 255 make sense, but what does it mean when you state that zero of a tile should appear on the board?
It depends on who you ask. According to ZZTAE and KevEdit, it means the tile does not exist. They just skip that run-length-encoded triplet and move on to the next. However, it means a very different thing to ZZT - both ZZT and ZZT's default editor will interpret the 0 as meaning 256. Because of this difference of interpretation, it is possible to make a board that appears corrupt to an external editor but plays just fine in ZZT.
Why hasn't this been noticed before? Because ZZT will never encode a run of 256 tiles using a zero. If it encounters a run of tiles more than 255 long, it will encode the run as multiple separate, shorter runs. Ditto for all external editors. Run lengths of zero never occur naturally - you have to use a hex editor to get them - and so external editors are never tested for this condition. I'm guessing that ZZT's interpretation of 0 means 256 is more of an exploitable bug than a feature, as it's inconsistent with even its own board encoding algorithm.
Of course, it is important to realize that run lengths of zero do not prevent editing by external editors in general. The only reason they have this effect is because both ZZTAE and KevEdit parse ZZT files in the same way, a way that's slightly different from how ZZT does it. If either CyQ or Kev Vance had reordered the statements in his decoder loop, his editor would be immune to the technique described in this post. In fact, KevEdit is open source, and it would be trivial to build a modified version of KevEdit that could get past the locked file I have posted here. This is why locking is pointless - if you're clever enough to devise a stronger lock, there's always going to be somebody clever enough to crack it.
Now, if you have fun doing this kind of thing, here's a challenge: edit the file I uploaded using only ZZT and ZZTAE - no hex editors and no custom-built programs. It is possible, and it just goes to show that even this lock is far from perfect.
Now that that's over with, here's a little ZZT world that dares you to deface it. The problem is, the file is locked - you can't edit it by conventional means. Most existing locks can be easily bypassed with an external editor. However, the method used to lock this file has an addition that (to my knowledge) has never been used before. This addition prevents the file from opening in both ZZTAE and KevEdit.
How it works (for those who feel so inclined as to figure it out themselves, the following contains spoilers):
ZZT stores terrain data using run-length encoding. This basically means that instead of describing all 1500 tiles on each board, ZZT will describe a tile once and specify how many times it occurs. For example, a line of 10 gems in a row could be described as [10][Tile ID for a Gem][Gem color]. The number of copies is stored in a single byte, which is treated as an 8-bit unsigned integer. This basically means that the number can go from 0 to 255. 1 through 255 make sense, but what does it mean when you state that zero of a tile should appear on the board?
It depends on who you ask. According to ZZTAE and KevEdit, it means the tile does not exist. They just skip that run-length-encoded triplet and move on to the next. However, it means a very different thing to ZZT - both ZZT and ZZT's default editor will interpret the 0 as meaning 256. Because of this difference of interpretation, it is possible to make a board that appears corrupt to an external editor but plays just fine in ZZT.
Why hasn't this been noticed before? Because ZZT will never encode a run of 256 tiles using a zero. If it encounters a run of tiles more than 255 long, it will encode the run as multiple separate, shorter runs. Ditto for all external editors. Run lengths of zero never occur naturally - you have to use a hex editor to get them - and so external editors are never tested for this condition. I'm guessing that ZZT's interpretation of 0 means 256 is more of an exploitable bug than a feature, as it's inconsistent with even its own board encoding algorithm.
Of course, it is important to realize that run lengths of zero do not prevent editing by external editors in general. The only reason they have this effect is because both ZZTAE and KevEdit parse ZZT files in the same way, a way that's slightly different from how ZZT does it. If either CyQ or Kev Vance had reordered the statements in his decoder loop, his editor would be immune to the technique described in this post. In fact, KevEdit is open source, and it would be trivial to build a modified version of KevEdit that could get past the locked file I have posted here. This is why locking is pointless - if you're clever enough to devise a stronger lock, there's always going to be somebody clever enough to crack it.
Now, if you have fun doing this kind of thing, here's a challenge: edit the file I uploaded using only ZZT and ZZTAE - no hex editors and no custom-built programs. It is possible, and it just goes to show that even this lock is far from perfect.