Following the excellent suggestion of a TIGSource user, I decided to add a save slot to Netpack’s final version. This single-use slot auto-saves on any non-death exit. It’s destroyed when it’s loaded or when a new game is started. I’m sure there are plenty of options for me in terms of libraries so I wouldn’t have to code the encryption myself, but I figured, “Oh well, what the hell,” and decided to do it anyway, for fun.
The data that need to be written and read for a Netpack save game are bare-bones (player stats, game mode, and dungeon level, basically), making a complex system unnecessary. This was further motivation to do it on my own as an exercise, as well as further evidence that I didn’t need a huge, professional encryption module to accomplish my goal.
I don’t care if anyone messes with their save files to achieve fake high scores or to see the ending or whatever. That’s not the point. I did this for the “hack” of it, so go nuts. Once you read these articles, you’ll be able to cheat Nethack pretty easily.
Note to gamers/fans: I’ll be releasing this version of Netpack in about a week, along with the second part of the article.
Note to experienced programmers: My ideas and code will likely be laughable and redundant. Move along.
My first idea was to write a function that constructs an obscure data string representing the game data. I started by figuring out all the game data I’d need for saving and loading: inventory items, player level, dungeon level, score, game mode, and ghosts killed.
Usually, I work in a test file when I’m trying out a new idea, simulating the relevant parts of the game environment so I don’t have to open the actual game 50 times during testing. (Is this a common? It seems natural to me.) Two runs using my test file follow, one asking for input and creating a data string and one loading and unpacking the string (I hadn’t added the number of ghosts killed yet):
12345678910111213141516171819202122232425262728293031323334353637 >> save or load: s>> plvl: 14>> dlvl: 9>> score: 120450>> game mode: normalhow many ->> bananas: 32>> pretzels: 0>> oranges: 7>> pears: 18>> strawberries: 32>> cherries: 4>> apples: 10data string: '2000071220040a0e09440353202'>> save or load: q------------------------------------->> save or load: lplvl: 14dlvl: 9score: 120450game mode: normalbananas: 32pretzels: 0oranges: 7pears: 18strawberries: 32cherries: 4apples: 10>> save or load: q
At this point, I started implementing in-game. After incorporating the number of ghosts killed, a sample data string would look something like this: ‘2000071220040a0e09Re03532022a’.
For a final obfuscation, I decided to generate 19 other similar yet totally random and inconsequential data strings and hide the real one somewhere in the block. It would be positioned between the third and last lines, the line chosen at random on creation. The third line of the block would always contain the key – a hex telling how many lines past the third the real line is. So the load function would read the block, look at  to find the key, and skip that many lines to find and deconstruct the real data string.
12345678910111213141516171819202122 <strong>sample block (with added color and line counting):</strong>8a76f6577fc5b6d99af3c91fwip00303511092609dc932467a6a79969f9c6w44010424650f6463<strong><span style="color: #ff0000;">9</span></strong>dcdea4bbc9913ae4607wEN001477632de6677cc58cd4047182c82762w5S015071232a<span style="color: #ff0000;"><strong>1</strong></span>8527df416e819c46df04d454wEN015746363e<span style="color: #ff0000;"><strong>2</strong></span>fd9ce75942828973ec13211ctEN015212633e<span style="color: #ff0000;"><strong>3</strong></span>f4ac7b3f33051f689a253a92wEN0143622024<span style="color: #ff0000;"><strong>4</strong></span>bf2fed5718d0137ea212ae7dtRe0144015711<span style="color: #ff0000;"><strong>5</strong></span>386995f5082e60afcfebd77dwRe0107341709<span style="color: #ff0000;"><strong>6</strong></span>f35f42f445983c33f4029330w44011614140d<span style="color: #ff0000;"><strong>7</strong></span>128e70303b0f5cf545ebb262w5S000626663d<span style="color: #ff0000;"><strong>8</strong></span>00000000000003010f00a000o440000000000<span style="color: #ff0000;"><strong>9!</strong></span>ed35d6ee6331a2fecda7094etRe0104136013364ff579398c69c15d600ab3t44002317501be8ac7d1b48e1a9b20d456d36tEN00706450284c56fb8abd05301651f71001tEN012475162f1a214e845efe57b83720f2d5tEN01656773332ece100aeb2fdfdd40dea3d2o44005361711a95be71704e094f570733d53bozP003222511cb75359c947118bc3b74e422cwip0020227436
Of course, the real line sticks out like a sore thumb even without the key since I generated this block by quitting at the beginning of the game with no items or score. I planned to improve the algorithm to generate more realistic fake lines, thus camouflaging the real one a bit better, but then I stumbled upon a much simpler and more effective idea, which I’ll cover in my next post!