diff --git a/.gitignore b/.gitignore
index 24b64db..61311f6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
/challenge.bin
/dumps
+/tools/ackermann.o
diff --git a/notes.md b/notes.md
index 28d62ba..069e7ec 100644
--- a/notes.md
+++ b/notes.md
@@ -2,13 +2,15 @@
Note: Codes seem to be unique for every user.
-## Code 1
+## The programming codes
+
+### Code 1
Found in `arch-spec`.
== hints ==
- Here's a code for the challenge website: ............
-## Code 2
+### Code 2
Obtained on initial execution of code. (Or just be lazy like me and extract it from the binary.)
Welcome to the Synacor Challenge!
@@ -17,7 +19,7 @@ Obtained on initial execution of code. (Or just be lazy like me and extract it f
Required opcodes: `nop` and `out`
-## Code 3
+### Code 3
Obtained at the completion of the self-test.
Executing self-test...
@@ -27,7 +29,9 @@ Obtained at the completion of the self-test.
Required opcodes: All except `halt` and `in`
-## Code 4
+## The RPG codes
+
+### Code 4 (Tablet)
At the foothills, the first area of the RPG:
== Foothills ==
@@ -41,7 +45,7 @@ At the foothills, the first area of the RPG:
Required opcodes: All except `halt`
-## Code 5 (Twisty passages)
+### Code 5 (Twisty passages)
After falling down the bridge, there is an `empty lantern` to the `east`, which should be `take`n. To the `west`, there is a passage, where one can take the `ladder` down or venture into the `darkness`. Attempting to venture into the `darkness` at this point will result in being eaten by Grues.
Taking the `ladder` down, then traversing `west`, `south`, `north`, a code is obtained:
@@ -54,12 +58,12 @@ Taking the `ladder` down, then traversing `west`, `south`, `north`, a code is ob
There is also a can, which can be `take`n and `use`d to fill the lantern, which can then be `use`d to become lit, and which will keep away Grues.
-## Code 6 (Dark passage, ruins and teleporter)
+### Code 6 (Dark passage, ruins and teleporter)
Returning to the fork at the passage and venturing to the `darkness`, we now `continue` `west`ward to the ruins. The `north` door is locked, but dotted elsewhere around the ruins are a `red coin` (2 dots), `corroded coin` (triangle = 3 sides), `shiny coin` (pentagon = 5 sides), `concave coin` (7 dots) and `blue coin` (9 dots) which should be `take`n and `look`ed at, and the equation:
_ + _ * _^2 + _^3 - _ = 399
-### Mathematical!
+#### Mathematical!
In other words, we seek a solution to the equation *a* + *bc*2 + *d*3 - e = 399, where {*a*, *b*, *c*, *d*, *e*} = {2, 3, 5, 7, 9}.
Synacor being, of course, a programming-orientated exercise, the usual response to this problem is to code up a quick program to loop through all 5! = 120 permutations of the coins to find which one satisfies the equation. Being a mathematician, however – could you tell from the italics? – I will solve this the [*proper* way](https://xkcd.com/435/): with a scientific calculator and some thinking (\*insert insufferably smug expression here\*).
@@ -90,3 +94,142 @@ Proceed to the `north` door and `use` the `teleporter` to obtain the code:
............
After a few moments, you find yourself back on solid ground and a little disoriented.
+
+## The true-believers-only codes
+At this point, you will almost certainly need to delve into the code of the challenge, if you haven't already. The code in `challenge.bin` past the self-test is encrypted, so disassembling and analysing the code is most easily done based off a memory dump from a running copy.
+
+### The guts
+(Note to self: `pop` takes an operand, *duh*. No wonder everything looked funny…)
+
+Note that at `1808` there is the following data:
+
+ 1808 data 00b7
+ 1809 data "You find yourself standing at the base of an enormous mountain. ...
+
+(Accompanied by some other familiar-sounding but somewhat garbled strings.) Searching the code for references to `1809` yields nothing, but searching for `1808` yields the following as the only result:
+
+ 090d data 17fe 1808 6914 6917
+ 0911 halt # (i.e. data 0000)
+
+The first address referred to in the `090d` line has value `0009`, which looks suspiciously like a length. Indeed, this is the exact length of the following string `Foothills`. Continuing in this manner,
+
+ 17fe data 0009 "Foothills"
+ 1808 data 00b7 "You find yourself standing at the base of an enormous mountain. ...
+ 6914 data 0002 18c0 18c8
+ 6917 data 0002 0917 0912
+
+Following the rabbit hole,
+
+ 18c0 data 0007 "doorway"
+ 18c8 data 0005 "south"
+ 0917 data 1929 1933 691e 6921 0000
+ 0912 data 18ce 18d8 691a 691c 0000
+
+And unsurprisingly, the `0912` line leads to the data for the screen reached when venturing `south` from the beginning.
+
+Scrolling through the list of rooms beginning `090d`, we notice a peculiarly long line, `0949`:
+
+ 0949 data
+ 1fe3 # -> string "Twisty passages"
+ 1ff3 # -> string "You are in a maze ...
+ 695b # -> data 0005 206f 2076 207c 2082 2087
+ 6961 # -> data 0005 093f 094e 0953 0958 095d
+
+So far so good, but what's this??
+
+ 0e9e # -> wmem 0e8e 0000; ret
+ 208c # -> string "Twisty passages"
+ 209c # -> string "You are in a twisty maze ...
+ ...
+
+Aah, so it looks like each room is stored as a block of 5 words, each a pointer to a length of words: a string (the title), a string (the text), a list of pointers to strings (the exit names), a list of pointers to more rooms (the exits), and a memory location to `call` (or `0000`).
+
+Further analysis suggests that this particular call relates to the step counter for the Grues in the maze.
+
+We probably could have reached these same conclusions by analysing the suspicious-looking block of code following the room definitions, but assembly makes my head spin so ¯\_(ツ)_/¯
+
+Now what about items? Looking at a more familiar item, the tablet:
+
+ 0a6c data 468e 4695 090d 1270
+ 468e data 0006 "tablet"
+ 4695 data 0088 "The tablet seems appropriate for use as a writing surface but is unfortunately blank. Perhaps you should USE it as a writing surface..."
+ 090d ... # the foothills from earlier
+ 1270 ... # a subroutine that presumably prints code 4
+
+### Code 7 (Synacor Headquarters and Teleporter 2: Electric Boogaloo)
+
+Examining the data for the teleporter:
+
+ 0a94 data 4a55 4a60 099f 1545
+ 4a55 data 000a "teleporter"
+ 4a60 data 0048 "This small device has a button on it and reads \"teleporter\" on the side."
+ 099f ... # the north room in the ruins
+ 1545 ... # aha!
+
+Now, let's see what this `1545` does. C-style, because assembly makes my brain spin.
+
+```c
+1545() {
+ if (R8 == 0)
+ return 15e5(); // Ground state teleport
+ 05b2(70ac, 05fb, 1807 + 585a); // Secure text print
+ for(i = 0; i < 5; i++); // Speed up loop, because why not?
+
+ if (178b(0004, 0001) != 0006) // The check!
+ return 15cb();
+
+ 05b2(7156, 05fb, 1ed6 + 0992);
+
+ 0731(R8, 650a, 7fff, 7239);
+
+ 05b2(723d, 05fb, 7c1f + 0146);
+
+ mem[0aac] = 09c2;
+ mem[0aad] = 0000;
+
+ mem[0a94 + 0002] = 7fff;
+
+ return 1652();
+}
+
+178b(R1, R2) {
+ if (R1 != 0) {
+ return 1793(R1, R2);
+ }
+ return R2 + 0001;
+}
+
+1793(R1, R2) {
+ if (R2 != 0) {
+ return 17a0(R1, R2);
+ }
+ R1 = R1 + 7fff;
+ R2 = R8;
+ R1 = 178b(R1, R2);
+ return R1;
+}
+
+17a0(R1, R2) {
+ R2 = R2 + 7fff;
+ R2 = 178b(R1, R2);
+ R1 = R1 + 7fff;
+ R1 = 178b(R1, R2);
+ return R1;
+}
+```
+
+Phew, so in other words, we seek an `R8` such that `178b(4, 1, R8)` equals 6. Let's see if we can't rewrite that function. Note that adding 0x7fff = 32767 to a number modulo 32768 is equivalent to subtracting 1. Thinking through the code, then,
+
+ A(R1, R2) = R2 + 1 , if R1 = 0
+ A(R1 - 1, R8) , if R1 ≠ 0 and R2 = 0
+ A(R1 - 1, A(R1, R2 - 1)) , if R1 ≠ 0 and R2 ≠ 0
+
+Recognise anything? Well, neither did I the first time, and I'd [already seen the video](https://www.youtube.com/watch?v=i7sm9dzFtEI). It's the [Ackermann function](https://en.wikipedia.org/wiki/Ackermann_function)! With the slight twist that instead of the second line being `A(R1 - 1, 1)`, it's `A(R1 - 1, R8)`.
+
+No mathematical wizardry here, just implementing this and run a brute-force on all possible values of `R8`. And as much as it pains me to admit this, this is a tool for the raw processing efficiency of C, which I am not very proficient in, so I based my solution on [this wonderful code](https://github.com/glguy/synacor-vm/blob/master/teleport.c) by [glguy](https://github.com/glguy). My only contribution is the parallelisation of the computation. (500% speed-up! Whoo!)
+
+ gcc ackermann.c -o ackermann -lpthread -O3 && ./ackermann
+
+Running the algorithm, the correct value is revealed to be `0x6486`.
+
+### Code 8 (Beach and vault)
diff --git a/synacor.py b/synacor.py
index c329c93..50e1112 100755
--- a/synacor.py
+++ b/synacor.py
@@ -16,7 +16,7 @@
# along with this program. If not, see .
import struct # for bytes<-->word handling
-import sys # for stdin
+import sys # for args, stdin
SYN_PTR = 0
SYN_MEM = [0] * 32768
diff --git a/tools/ackermann.c b/tools/ackermann.c
new file mode 100644
index 0000000..e62b732
--- /dev/null
+++ b/tools/ackermann.c
@@ -0,0 +1,74 @@
+/*
+ synacor.py - An implementation of the Synacor Challenge
+ Copyright © 2016 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 .
+*/
+
+#include
+#include
+#include
+#include
+
+#define NUM_THREADS 8
+
+static uint16_t A(uint16_t R1, uint16_t R2, uint16_t R8, uint16_t cache[5][0x8000]) {
+ if (cache[R1][R2] != 0xffff) {
+ return cache[R1][R2];
+ }
+
+ if (R1 == 0)
+ return (R2 + 1) & 0x7fff;
+ if (R2 == 0)
+ return A(R1 - 1, R8, R8, cache);
+
+ return A(R1 - 1, cache[R1][R2 - 1] = A(R1, R2 - 1, R8, cache), R8, cache);
+}
+
+static void* bruteThread(void* rmdrVoid) {
+ uint16_t rmdr;
+ uint16_t cache[5][0x8000];
+ uint16_t R8;
+
+ rmdr = *((int *) rmdrVoid);
+
+ for (R8 = rmdr; R8 <= 0x7fff; R8 += NUM_THREADS) {
+ memset(cache, 0xff, sizeof(cache)); /* Clear the cache */
+
+ if (!(R8 & 0xff))
+ printf("%04x...\n", R8);
+
+ if (A(4, 1, R8, cache) == 6)
+ printf("Found a solution! %04x...\n", R8);
+ }
+}
+
+int main() {
+ pthread_t threads[NUM_THREADS];
+ int thread_args[NUM_THREADS];
+ int i;
+
+ for (i = 0; i < NUM_THREADS; i++) {
+ printf("Spawning thread %d...\n", i);
+ thread_args[i] = i; /* rmdr */
+ pthread_create(&threads[i], NULL, bruteThread, (void*) &thread_args[i]);
+ }
+
+ /* wait */
+ for (i = 0; i < NUM_THREADS; i++) {
+ pthread_join(threads[i], NULL);
+ }
+
+ printf("Search complete\n");
+}
diff --git a/tools/disasm.py b/tools/disasm.py
new file mode 100755
index 0000000..1d5ebc7
--- /dev/null
+++ b/tools/disasm.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python3
+# synacor.py - An implementation of the Synacor Challenge
+# Copyright © 2016 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 .
+
+import struct # for bytes<-->word handling
+import sys # for args
+
+class OpLiteral:
+ def __init__(self, value):
+ self.value = value;
+ def get(self):
+ return '{:04x}'.format(self.value);
+ def set(self):
+ return '{:04x}'.format(self.value);
+
+class OpRegister:
+ def __init__(self, register):
+ self.register = register;
+ def get(self):
+ return 'R{}'.format(self.register + 1);
+ def set(self):
+ return 'R{}'.format(self.register + 1);
+
+def readWord():
+ byteData = data.read(2)
+ if len(byteData) < 2:
+ return None
+ return struct.unpack('