diff --git a/theme/background.mjs b/theme/background.mjs index 5859fb5..76b975d 100644 --- a/theme/background.mjs +++ b/theme/background.mjs @@ -4,6 +4,7 @@ function randint(max) { const MILLISECOND = 1 const SECOND = MILLISECOND * 1000 +const FRAMERATE = 24 / SECOND // Fast enough for this tomfoolery class Point { constructor(x, y) { @@ -87,7 +88,7 @@ class QixLine { * like the video game "qix" */ class QixBackground { - constructor(ctx, frameInterval = SECOND/6) { + constructor(ctx, frameRate = 6/SECOND) { this.ctx = ctx this.min = new Point(0, 0) this.max = new Point(this.ctx.canvas.width, this.ctx.canvas.height) @@ -95,7 +96,7 @@ class QixBackground { this.lines = [ new QixLine( - 0, + Math.random(), new Point(randint(this.box.x), randint(this.box.y)), new Point(randint(this.box.x), randint(this.box.y)), ) @@ -109,7 +110,7 @@ class QixBackground { new Point(1 + randint(this.box.x / 100), 1 + randint(this.box.y / 100)), ) - this.frameInterval = frameInterval + this.frameInterval = MILLISECOND / frameRate this.nextFrame = 0 } @@ -157,8 +158,8 @@ function init() { let ctx = canvas.getContext("2d") let qix = new QixBackground(ctx) - setInterval(() => qix.Animate(), SECOND/6) - + // window.requestAnimationFrame is overkill for something this silly + setInterval(() => qix.Animate(), MILLISECOND/FRAMERATE) } if (document.readyState === "loading") { diff --git a/theme/basic.css b/theme/basic.css index 995e134..ffc71b9 100644 --- a/theme/basic.css +++ b/theme/basic.css @@ -74,6 +74,7 @@ p { } form, pre { margin: 1em; + overflow-x: auto; } input, select { padding: 0.6em; diff --git a/theme/hash.mjs b/theme/hash.mjs deleted file mode 100644 index 08c8e73..0000000 --- a/theme/hash.mjs +++ /dev/null @@ -1,20 +0,0 @@ -// Dan Bernstein hash v1 -// Used until MOTH v3.5 -function djb2(buf) { - let h = 5381 - for (let c of (new TextEncoder()).encode(buf)) { // Encode as UTF-8 and read in each byte - // JavaScript converts everything to a signed 32-bit integer when you do bitwise operations. - // So we have to do "unsigned right shift" by zero to get it back to unsigned. - h = (((h * 33) + c) & 0xffffffff) >>> 0 - } - return h - } - - // Used until MOTH v4.5 - async function sha256(message) { - const msgUint8 = new TextEncoder().encode(message); // encode as (utf-8) Uint8Array - const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8); // hash the message - const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array - const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // convert bytes to hex string - return hashHex; - } \ No newline at end of file diff --git a/theme/moth.mjs b/theme/moth.mjs index 5bd2911..13acc7d 100644 --- a/theme/moth.mjs +++ b/theme/moth.mjs @@ -15,7 +15,7 @@ class Hash { for (let c of (new TextEncoder()).encode(buf)) { // Encode as UTF-8 and read in each byte // JavaScript converts everything to a signed 32-bit integer when you do bitwise operations. // So we have to do "unsigned right shift" by zero to get it back to unsigned. - h = (((h * 33) + c) & 0xffffffff) >>> 0 + h = ((h * 33) + c) >>> 0 } return h } @@ -29,7 +29,7 @@ class Hash { static djb2xor(buf) { let h = 5381 for (let c of (new TextEncoder()).encode(buf)) { - h = h * 33 ^ c + h = ((h * 33) ^ c) >>> 0 } return h } @@ -40,14 +40,14 @@ class Hash { * Used until MOTH v4.5 * * @param {String} buf Input - * @returns {String} hex-encoded digest + * @returns {Promise.} hex-encoded digest */ - static async sha256(buf) { - const msgUint8 = new TextEncoder().encode(buf) - const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8) - const hashArray = Array.from(new Uint8Array(hashBuffer)) - return this.hexlify(hashArray); - } + static async sha256(buf) { + const msgUint8 = new TextEncoder().encode(buf) + const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8) + const hashArray = Array.from(new Uint8Array(hashBuffer)) + return this.hexlify(hashArray); + } /** * Hex-encode a byte array @@ -55,9 +55,23 @@ class Hash { * @param {Number[]} buf Byte array * @returns {String} */ - static async hexlify(buf) { + static hexlify(buf) { return buf.map(b => b.toString(16).padStart(2, "0")).join("") } + + /** + * Apply every hash to the input buffer. + * + * @param {String} buf Input + * @returns {Promise.} + */ + static async All(buf) { + return [ + String(this.djb2(buf)), + String(this.djb2xor(buf)), + await this.sha256(buf), + ] + } } /** @@ -166,12 +180,24 @@ class Puzzle { return this.server.GetContent(this.Category, this.Points, filename) } + /** + * Check if a string is possibly correct. + * + * The server sends a list of answer hashes with each puzzle: this method + * checks to see if any of those hashes match a hash of the string. + * + * The MOTH development team likes obscure hash functions with a lot of + * collisions, which means that a given input may match another possible + * string's hash. We do this so that if you run a brute force attack against + * the list of hashes, you have to write your own brute force program, and + * you still have to pick through a lot of potentially correct answers when + * it's done. + * + * @param {String} str User-submitted possible answer + * @returns {Promise.} + */ async IsPossiblyCorrect(str) { - let userAnswerHashes = [ - Hash.djb2(str), - Hash.djb2xor(str), - await Hash.sha256(str), - ] + let userAnswerHashes = await Hash.All(str) for (let pah of this.AnswerHashes) { for (let uah of userAnswerHashes) { diff --git a/theme/puzzle.mjs b/theme/puzzle.mjs index ce15f27..5d8d86c 100644 --- a/theme/puzzle.mjs +++ b/theme/puzzle.mjs @@ -95,9 +95,9 @@ async function loadPuzzle(category, points) { let server = new moth.Server() let puzzle = server.GetPuzzle(category, points) - console.time("Puzzle load") + console.time("Populate") await puzzle.Populate() - console.timeEnd("Puzzle load") + console.timeEnd("Populate") let title = `${category} ${points}` document.querySelector("title").textContent = title @@ -122,6 +122,8 @@ async function loadPuzzle(category, points) { document.getElementById("files").appendChild(li) } + let baseElement = document.head.appendChild(document.createElement("base")) + baseElement.href = contentBase window.app.puzzle = puzzle console.info("window.app.puzzle =", window.app.puzzle) @@ -142,6 +144,11 @@ function init() { // There isn't a more graceful way to "unload" scripts attached to the current puzzle window.addEventListener("hashchange", () => location.reload()) + // Make all links absolute, because we're going to be changing the base URL + for (let e of document.querySelectorAll("[href]")) { + e.href = new URL(e.href, location) + } + let hashpart = location.hash.split("#")[1] || "" let catpoints = hashpart.split(":") let category = catpoints[0]