Programmatically generate three of the five in-game codes
This commit is contained in:
parent
80a3dbab99
commit
dbc021967a
@ -111,3 +111,144 @@ In other words, a similar string-handling subroutine is called, but instead of `
|
||||
Now we have everything we need to [extract these encrypted (double-encrypted??) strings](https://github.com/RunasSudo/synacor.py/blob/master/tools/decrypt_strings.py) from the binary!
|
||||
|
||||
Only the self-test completion code appears to be stored there, though, so I'm not sure what the point of encrypting those was…
|
||||
|
||||
## The codes
|
||||
|
||||
We may not have the codes themselves, but we can now easily locate where they are printed. The tablet code, for example, is conveniently sandwiched between strings `6ed1` and `6ef1`. Thus the code we are looking for is:
|
||||
|
||||
1290 set R1 1092
|
||||
1293 set R2 650a
|
||||
1296 set R3 7fff
|
||||
1299 set R4 6eed
|
||||
129c call 0731
|
||||
|
||||
Looking into `0731`:
|
||||
|
||||
0731 push R4
|
||||
0733 push R5
|
||||
0735 push R6
|
||||
0737 push R7
|
||||
0739 set R7 0001
|
||||
073c add R5 R4 R7
|
||||
0740 rmem R5 R5
|
||||
0743 add R6 17ed R7
|
||||
0747 wmem R6 R5
|
||||
074a add R7 R7 0001
|
||||
074e rmem R6 17ed
|
||||
0751 gt R5 R7 R6
|
||||
0755 jf R5 073c
|
||||
0758 set R4 0000
|
||||
075b set R5 0000
|
||||
075e rmem R6 17ed
|
||||
0761 mod R6 R5 R6
|
||||
0765 add R6 R6 17ed
|
||||
0769 add R6 R6 0001
|
||||
076d rmem R7 R6
|
||||
0770 mult R7 R7 1481
|
||||
0774 add R7 R7 3039
|
||||
0778 wmem R6 R7
|
||||
077b push R1
|
||||
077d push R2
|
||||
077f set R2 R7
|
||||
0782 call 084d
|
||||
0784 set R7 R1
|
||||
0787 pop R2
|
||||
0789 pop R1
|
||||
078b rmem R6 R2
|
||||
078e mod R7 R7 R6
|
||||
0792 add R7 R7 0001
|
||||
0796 gt R6 R7 R3
|
||||
079a jt R6 07a0
|
||||
079d set R4 0001
|
||||
07a0 add R7 R7 R2
|
||||
07a4 rmem R7 R7
|
||||
07a7 add R5 R5 0001
|
||||
07ab add R6 R5 17f1
|
||||
07af wmem R6 R7
|
||||
07b2 rmem R6 17f1
|
||||
07b5 eq R6 R5 R6
|
||||
07b9 jf R6 075e
|
||||
07bc jf R4 0758
|
||||
07bf push R1
|
||||
07c1 set R1 17f1
|
||||
07c4 call 05ee
|
||||
07c6 pop R1
|
||||
07c8 pop R7
|
||||
07ca pop R6
|
||||
07cc pop R5
|
||||
07ce pop R4
|
||||
07d0 ret
|
||||
|
||||
Umm… Sorry, could you repeat that?
|
||||
|
||||
Rewriting this again in more friendly terms:
|
||||
|
||||
```c
|
||||
// R1: A seed of sorts - the same for all users
|
||||
// R2: The length and alphabet to use, usually 650a, but 653f for the mirror
|
||||
// R3: Usually 7fff, but 0004 for the mirror
|
||||
// R4: An initialisation vector of sorts - contents different for every user - points to the length, but this is always 3
|
||||
0731(R1, R2, R3, R4) {
|
||||
// copy the string at R4 to 17ed
|
||||
R7 = 0001;
|
||||
do {
|
||||
R5 = R4 + R7;
|
||||
R5 = [R5];
|
||||
R6 = 17ed + R7;
|
||||
[R6] = R5;
|
||||
R7 = R7 + 0001;
|
||||
R6 = [17ed]; // 3, the length - this never seems to change
|
||||
} while (R7 <= R6);
|
||||
|
||||
// the string at 17ed is now what was at R4
|
||||
do {
|
||||
R4 = 0000; // done flag
|
||||
R5 = 0000; // index
|
||||
do {
|
||||
R6 = [17ed]; // 3, the length
|
||||
R6 = R5 % R6;
|
||||
R6 = R6 + 17ed;
|
||||
R6 = R6 + 0001; // will cycle through three addresses of 17ed/R4 string: 17ee, 17ef, 17f0
|
||||
R7 = [R6];
|
||||
R7 = R7 * 1481;
|
||||
R7 = R7 + 3039;
|
||||
[R6] = R7; // mutate that value of the 17ed string
|
||||
R7 = R1 ^ R7; // combine R1 into R7
|
||||
R6 = [R2]; // length of the alphabet
|
||||
R7 = R7 % R6;
|
||||
R7 = R7 + 0001; // calculate index in alphabet
|
||||
if (R7 <= R3) {
|
||||
R4 = 0001; // we are done with the entire code - this returns immediately for all except the mirror
|
||||
}
|
||||
R7 = R7 + R2; // calculate address of letter to use
|
||||
R7 = [R7]; // the letter to use
|
||||
R5 = R5 + 0001; // increment the index
|
||||
R6 = R5 + 17f1; // index of new letter in code
|
||||
[R6] = R7; // set the letter
|
||||
R6 = [17f1]; // length of the code: twelve letters
|
||||
} while (R5 != R6); // loop until we've generated all the letters
|
||||
} while (!R4);
|
||||
print(17f1);
|
||||
}
|
||||
```
|
||||
|
||||
Re-implementing this in Python, we can now extract the code for the tablet directly from the binary!
|
||||
|
||||
Unfortunately, each of the other codes uses an `R1` based on the solution to the puzzle. In the case of the maze code:
|
||||
|
||||
0f03 rmem R1 0e8e
|
||||
|
||||
The value at `0e8e` is derived from which rooms are visited in the maze, as mentioned in the main note file. Armed with our [trusty map](https://github.com/RunasSudo/synacor.py/blob/master/tools/graph.py), and cross-referencing the callbacks with the files, we identify:
|
||||
|
||||
* Twisty passages entrance, `0949`: Calls `0e9e`, resets `0e8e` to `0000`.
|
||||
* West to `095d`: Calls `0ec0`: `OR`s `0e8e` with `0008`.
|
||||
* South to `0926`: Calls `0eca`: `OR`s `0e8e` with `0010`.
|
||||
* North to `096c`: Calls `0ede`: `OR`s `0e8e` with `0040`.
|
||||
|
||||
Putting it all together, the final value at `0e8e` is `0008 | 0010 | 0040` = `0058`.
|
||||
|
||||
Similarly, the `R1` for the teleporter code is the value of `R8` from the Ackermann function, which we determined earlier to be `6486`:
|
||||
|
||||
1592 set R1 R8
|
||||
|
||||
The remaining two codes, for the coins and the vault, are more complicated still, but follow the same pattern of determining `R1` based on the player's history.
|
||||
|
@ -24,6 +24,7 @@ SYN_REG = [0] * 8
|
||||
SYN_STK = []
|
||||
SYN_STDIN_BUF = []
|
||||
|
||||
DBG_FLAG = False
|
||||
DBG_CSTK = []
|
||||
|
||||
class OpLiteral:
|
||||
@ -72,6 +73,9 @@ else:
|
||||
|
||||
# Begin execution
|
||||
while True:
|
||||
if DBG_FLAG:
|
||||
import pdb; pdb.set_trace()
|
||||
|
||||
instruction = swallowOp().get()
|
||||
|
||||
if instruction == 21: #NOP
|
||||
|
76
tools/generate_codes.py
Executable file
76
tools/generate_codes.py
Executable file
@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python3
|
||||
# synacor.py - An implementation of the Synacor Challenge
|
||||
# Copyright © 2017 RunasSudo
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import struct
|
||||
import sys
|
||||
|
||||
IV_LEN = 3
|
||||
CODE_LEN = 12
|
||||
|
||||
def generate_code(R1, R2, R3, R4):
|
||||
R2data = SYN_MEM[R2+1:R2+1+SYN_MEM[R2]]
|
||||
R4data = SYN_MEM[R4+1:R4+1+SYN_MEM[R4]]
|
||||
|
||||
assert len(R4data) == IV_LEN
|
||||
|
||||
done = False
|
||||
while not done:
|
||||
code = ''
|
||||
for i in range(CODE_LEN):
|
||||
R6 = i % IV_LEN
|
||||
R7 = R4data[R6]
|
||||
R7 = (R7 * 0x1481) % 0x8000
|
||||
R7 = (R7 + 0x3039) % 0x8000
|
||||
R4data[R6] = R7
|
||||
R7 = R1 ^ R7
|
||||
R7 = R7 % len(R2data)
|
||||
if R7 + 1 <= R3:
|
||||
done = True
|
||||
R7 = R2data[R7]
|
||||
code += chr(R7)
|
||||
return code
|
||||
|
||||
# Read code into memory
|
||||
SYN_MEM = [0] * 32768
|
||||
|
||||
with open(sys.argv[1], 'rb') as data:
|
||||
i = 0
|
||||
while True:
|
||||
byteData = data.read(2)
|
||||
if len(byteData) < 2:
|
||||
break
|
||||
SYN_MEM[i] = struct.unpack('<H', byteData)[0]
|
||||
i += 1
|
||||
|
||||
# Emulate 06bb
|
||||
for R2 in range(0x17b4, 0x7562):
|
||||
R1 = SYN_MEM[R2]
|
||||
R1 ^= pow(R2, 2, 32768)
|
||||
R1 ^= 0x4154
|
||||
SYN_MEM[R2] = R1
|
||||
|
||||
# Look for calls to 0731
|
||||
CODE_PARAMS = [
|
||||
(0x0058, 0x650a, 0x7fff, 0x6e8b), # R1 from the maze
|
||||
(0x1092, 0x650a, 0x7fff, 0x6eed),
|
||||
(0x6486, 0x650a, 0x7fff, 0x7239), # R1 is R8 from Ackermann
|
||||
# 162e is a bit tricky
|
||||
# 1691 is a bit tricky
|
||||
]
|
||||
|
||||
for cp in CODE_PARAMS:
|
||||
print(generate_code(*cp))
|
Reference in New Issue
Block a user