Holiday Hack 2023: Squarewheel Yard
Geography
Getting To
Before I head over to the space Island, I’ll notice three Gameboy objectives in my badge and go exploring. At the backside of the Island of Misfit Toys, I’ll find Squarewheel Yard:

Location Layout
The port has a very Misfit Toys vibe:
The Goose greets me with:

Good of the Island of Misfit Toys
Beep beep
Luggage Lock Decode
Challenge
This area adds the Luggage Lock objective to the badge:

Garland Candlesticks is in the center of the area next to a suitcase:

Garland Candlesticks
Hey there, I’m Garland Candlesticks! I could really use your help with something.
You see, I have this important pamphlet in my luggage, but I just can’t remember the combination to open it!
Chris Elgee gave a talk recently that might help me with this problem. Did you attend that?
I seem to recall Chris mentioning a technique to figure out the combinations…
I have faith in you! We’ll get that luggage open in no time.
This pamphlet is crucial for me, so I can’t thank you enough for your assistance.
Once we retrieve it, I promise to treat you to a frosty snack on me!
Game
The game offers a chance to select a difficulty:

I get luggage with the number of wheels matching what I select:

The help button shows up to interact:

It’s worth noting that clicking on the button (or pushing space) presses in that button a little bit each time, and after 5 or 6 presses, it pops back out. This is meant to represent the amount of pressure being put on the button.
Solve
Chris Elgee’s talk at KringleCon 2023, Lock Talk, talks about how to attack this kind of lock. Basically, I want to put some pressure on the button and look for where there’s resistance to turning the dials. Where it gets stuck, that’s a good sign that it’s the right one, though any position can get resistance with too much pressure and/or bad luck.
On entering the correct code, the bag opens:

Hack
This challenge is a fun one to hack as well. I’ll look at the websocket messages and show two ways to cheat the challenge:
- Looking at the setup message to figure out the combo.
- Sending 10,000 “Open” messages with each combination possibility from the dev tools console.
Full details in this video:
Epilogue

Garland Candlesticks
Wow, you did it! I knew you could crack the code. Thank you so much!
Bonus! Fishing
Challenge Part 1
Poinsettia McMittens (who must be a twin of Garland) is standing by the dock with some information about fishing:

Poinsettia McMittens
Excuse me, but you’re interrupting my fishing serenity. Oh, you’d like to know how to become as good at fishing as I am?
Well, first of all, thank you for noticing my flair for fishing. It’s not just about looking good beside the lake, you know.
The key is in the details, much like crafting the perfect toy. Observe the water, the weather, and the fish’s habits - it’s a science and an art.
Of course, it helps to have a natural charm. Fish seem to find me irresistible. Must be my sparkling personality… or maybe it’s just the glitter of my allure.
Oh, the mysteries of the aquatic life around these islands are as elusive as, well, a clever compliment. But you’ll get one if you probe enough.
Remember, patience is more than a virtue in fishing; it’s a strategy. Like waiting for the right time to use flattery, you wait for the right moment to strike.
Go see if you can catch, say, 20 different types of fish!
Fishing Legit
Fishing is easy enough. I make sure my boat is at a stop in the water, and click the “Cast Line” button:

The button turns to “Reel it in”:

After some amount of time, it will turn red:

Clicking then (and before it goes back to white) will catch a fish, which opens the Pescadex and shows details about it. For example:

With some patience and a few minutes, I’ll catch 20 fish and head back to Poinsettia.
Challenge Part 2
Poinsettia ups the stakes:

Poinsettia McMittens
Hoy small fry, nice work!
Now, just imagine if we had an automatic fish catcher? It would be as ingenious as me on a good day!
I came across this fascinating article about such a device in a magazine during one of my more glamorous fishing sessions.
If only I could get my hands on it, I’d be the undisputed queen of catching them all!
On talking, two objectives show up (one already solved):


Find Heat Maps
At the top of the body of the main sea
page, there’s a commented out HTML link:

This leads to https://2023.holidayhackchallenge.com/sea/fishdensityref.html
, which is a page that has black and white squares for each of a bunch of fish:

Source Analysis
The goal here is to figure out what triggers a fish on the line. At line 327 of client.js
, there’s an event listener created for incoming message:

It does a long if
/ else if
block for the different starting letters of the message. At first I was very intriged by f:
:
} else if (messageType === 'f:') {
const parsed = JSON.parse(payload);
const idx = playerData.fishCaught.findIndex(fish => fish.name === parsed.fish.name);
if (idx === -1) {
playerData.fishCaught.push(parsed.fish);
pescadexIndex = playerData.fishCaught.length - 1;
freshCatch = true;
} else {
pescadexIndex = idx;
}
cotd.classList.add('visible');
updatePescadex();
}
It turns out this message comes from the server after I push “Reel it in”. So that’s not much use. At the end after all the messages are processed, it calls UpdateUI()
regardless which message was called. In that function, this section jumps out:
if (me.fishing) {
reelItInBtn.style.display = 'block';
if (me.onTheLine) {
reelItInBtn.classList.add('gotone');
reelItInBtn.innerText = 'Reel it in!';
} else {
reelItInBtn.classList.remove('gotone');
reelItInBtn.innerText = 'Reel it in';
}
} else {
reelItInBtn.style.display = 'none';
}
me
is defined earlier as Entities[playerData.uid]
. Outside that function, I don’t have access to me
. But Entities
and playerData
do exist:

If I grab the Entities
entry for my uid
, it has fishing
, fishCaught
, and oneTheLine
:

Listening on Socket
POC
I want to try to listen on the socket, so I’ll mimic what’s in the code in my console:

Right away, there are a ton of v:
messages. I’ll re-run to filter those out (refreshing the page to reset the console). At first there’s nothing, but as soon as I start moving, there’s a bunch of e:
messages:

Finding e Messages
I’ll refresh again and add some information about the payload. When I move, e:
messages provide the update as to where my boat is and where it’s going:

But when I fish, must larger e:
messages come through:

These are the updates that set onTheLine
!
Sending on Socket
I want to be able to automatically do things, so I need to know if there’s a message I can send that will “Reel it in”. At line 1007, there’s an event listener defined for when that button is clicked:
const reelItInBtn = document.querySelector('button.reelitin');
reelItInBtn.addEventListener('click', () => {
socket.send(`reel`);
window.top.postMessage({
type: 'sfx',
filename: 'fishing-reel.mp3',
}, '*');
});
It looks like all I have to do is send “reel” over the websocket. I’ll test it, by entering socket.send('reel');
into the console while I’m fishing, and observer that the UI updates to show I’m no longer fishing. I can wait until there’s a fish on the line and then send, and it does catch the fish!
Auto-Fish
I’ll use the following code to automatically fish:
socket.addEventListener("message", event => {
const messageType = event.data.substr(0, 2);
const payload = event.data.substr(2);
if (messageType == "e:") {
const parsed = JSON.parse(payload);
if (playerData.uid in parsed) {
const data = parsed[playerData.uid];
if ('onTheLine' in data && data.onTheLine) {
const idx = playerData.fishCaught.findIndex(fish => fish.name === data.onTheLine);
if (idx !== -1) {
console.log("Already have " + data.onTheLine);
} else {
socket.send('reel');
console.log(data.onTheLine + ' [' + (playerData.fishCaught.length + 1) + ']');
setTimeout(() => {socket.send('cast');}, 500);
}
}
}
}
});
It listens on the websocket, and when there’s an e:
message, it checks for onTheLine
and if it’s true
. If so, it looks in my current caught fish to see if I already have it, and if not, it sends the reel
message over the socket. It then prints a message to the log, and casts again in half a second.
If I want to not fish, all I have to do is manually reel it in, and then it won’t recast until I cast again and it catches something.
Using Heatmaps
Manually
Some fish are tricky to find, and I will need to be in just the right place. I could manually overlay the map with the heatmaps, but that seems like a lot of work.
In the source, there’s a dict ImageAssets
that defines the parts of the map:

Interestingly, by the time they get to my current context, they are not strings, but HTML objects:

These are used each time the UI is updated. I’ll try replacing the minimap.png
with a heatmap:

The map in the bottom right updates:

Now I can move around finding hot spots (where it’s very light) and fish there. This is particularly useful for the Piscis Cyberneticus Skodo, which is only found on one distinct space on the map:

Automate
I don’t want to have to keep finding the links and updating them. I’ll use curl
with some bash
-foo to get a list of the fish:
$ curl https://2023.holidayhackchallenge.com/sea/fishdensityref.html -s | grep h3 | cut -d'>' -f2 | cut -d'<' -f1 > SansHolidayChallenge-2023/fish.txt
$ wc -l fish.txt
171 fish.txt
$ cat fish.txt
Flutterfin Rainbow-Roll
Gelatina Ringletfin
Funfetti Flick-Flick
Fluffle-Muffin Sparklefin
Jovian Jamboree Jellydonut Jellyfish
Gumball Glooperfish
Jester Jellyfin
The Chocolate Star Gingo Guppy
Whirly Snuffleback Trout
Jolly Jellyjam Fish
Lounging Liquorice Crustacean-Nosed Berryfin
Jinglefin Jellyfrizzle
The Polka-Dot-Propeller Puffling Fish
JubiliFLOPinear Snorkeldonut
Glittering Gummy Guppy
Whiskered Whizzler
Flamango-Buzzling Sushi Swimmer
Flutterfin Bubblegum Gumball
ChocoSeahorsefly
Speckled Toastfin Snorkelback
Jamboree Jellywing
Fantabulous Fry-Sherbert Aquapine
Jamboree Jellofish
Bubblegum Blowfish-Bee
Frizzle Fringe Flutterfin
Whiskered Jumblefish
Bumblefin Toffee Torpedo
Jolly Jellypeanut Fish
Gumbubble Guppy
Glittering Gummy Whipray
Flippity Flan Flopper
Biscuit Bugle-Tail Fish
Strudel Scuttle Scalefish
Bubblegum Bumblefin
Flutterfin Falafeluncher
The Splendiferous Spaghetti Starfin
The Rainbow Jelibelly Floatfish
Rhinoceros Beetle Bumble Tuna
Hatwearing Hippofish
Frizzle Fish
Polka-Pop CandyFloss Fish
Bumbleberry Floatfish
Caramelotus Humming Float
Dandy Candy Goby
The Polka-Dot Pudding Puff
Frosted Donut Jellyfluff Puffer
Flamingo Flapjack Finaticus
The Splendiferous Spaghetti Seahorsicle
Flutterfin Scoopscale
Frizzle Frazzle Fly-n-Fish
Frizzle-Frizzled Jambalaya Jellyfish
Sprinkfish
Fantail Flutterfin
JellyChip CuddleSwimmer
Whiskered Lollipop Loonfish
Jester Gumball Pufferfish
The Hummingbrewster BumbleFlish
Jangleroo Snackfin
Blibbering Blubberwing
The Bubblegum Confeetish
Fantastical Fusilloni Flounderfish
Pizzafin Flutterbub
The Whiskered Watermelon Pufferfish
The Bumblebee Doughnut Delphin
Pistachio Pizzafin Puffinfly
Aquatic JellyPuff Doughnut Shark
Gumball Guppygator
The Burgerwing Seahorse
Bellychuckle Balloonfish
FizzleWing PuffleGill
Bumbleberry Rainbow Flicorn Fish
Whistlefin Wafflegill
Pizzadillo Glitter-Guppy
Jamboree Jellydonut Jellyfish Trout
The Bubblegum Bumblefin
Gelatino Floatyfin
The Frambuzzle Flickerfin
The Speckled Pizzafin Fizzflyer
Sparkling Pizzafin Pixie-fish
Bumblecado Finstache Hybridsail
Pizzamarine Popcorn Puffer
Laughter Ligrolomia
Frosted Jelly Doughnut Pegasus Finfish
The Whirling Donut Jellygator
Flutterfin Cupcake Goby
The Gumball Guppy
Bubblegum Blowfish Beetle Bug
Sparkling Gumbubble Piscadot
The Flamboyant Flutter-fish
Twinkling Tortellini Trouterfly
Beatleberry Fluff Guppy.
Glaze Meringuelle
The Whiskered Blubberberry Flapper
Sherbet Swooshfin
Marzipoisson Popsicala
Bubblegum Ballistic Barracuda
Puzzletail Splashcake
Fantasia Fluffernutter Finfish
Rainbow Jelly-Dough Fish
Flutterfin Pizzapuffer
BugBrella Aquacake
Twirly Finny Cakeling
Frizzleberry Flapjack Fish
Whiskered Sprinkle Glider
The Pristimaela Parfait Pengu-Angel
Bubblerooni WhiskerWaffle
The Speckled Whisker-Spoon Puffer
BumbleSquid Donutella
Sparkleberry Gobblefin
Fizzgiggle Frizzlefin
JibberJelly Sundae Swimmer
The Flutterfin Pastry Puffer
Rainbow Gummy Scalefish
Jingle JellyFroth Fish
The Spotted Flutterfin Pastrytetra
Flutterfin Hotcheeto Penguinfish
Piscis Cyberneticus Skodo
Oreo OctoPufferRock
Fluffernutter Pufferpine
Whirlygig Polka-Dotted Jelly-Donut Pufferfish
Bumbleberry Gilled Glider
Polkadot Pancake Puffer
Mermacorn Fish
Sprinkle Starfish Sardine
Choco-Bumblefin Parrot Trout
The Fantabulous Gala Glazed-Guppy
Pudding Puff ParrotMoth Fish
Fantastical Flapjack Flipperfin
TruffleBugle ZephyrFish
Bumbleberry Glitterfin
The Jester Jellycarafe
The Flamingotuna McSprinklefin
Whiskerfroth Flutterfin
Spotted Sprinkledonut Puffer
Stripe-tailed Pepperoni Puffer
Jelly-Feather Macaroon Guppy
Flutterfin Pancake Puffer.
Whiskered Rainbow Glidleberry
Chucklefin Clownfish
Bumbleberry Snorkelsnout
Jolly Jellydozer
The Polka Dotted Jello-fish
The Bumbleberry Guppiesaurus
Flutterfin Pizzacrust Glimmertail
Bumblebee, Pizza-fin Jamboree
Whizzbizzle Poptuckle
Candyfloss Clownphino
Flutterglaze Bumblefin
Bumbleberry Poptarticus
Plaid Zephyr Cuddlefin
Jolly Jambalaya Jubilee Fish
Confetti Clownfrippery Fish
Rainbow Jelly-Bumble Shark
Marshmallow Pogo-Starfish
The Spangled Jelly-Tortle Ripplefin
Fantabulous Rainbow Polka Poptartfish
The ChocoChandelier Goldnipper
Gummybrella Anemofin
Gummy Fizzler
The Bumblebelly Polkadot Glaze-fish
Fantaray Flakefin
Splendiferous Ribbontail
The Butterfleagleberry Seahorse
Sushinano Sweetsquid
The Whiskered Melonfin
The Fantastical Fizzbopper
Splashtastic Bagelback Rainbownose
Pizzafly Rainbowgill
Frizzling Bubblehopper
Cuckoo Bubblegum Unicornfish
The Lucid Lollyscale
I’ll use vim
to quickly create a JavaScript array from this list, and then use that to make the following function:
function update_map() {
var fishlist = ['Flutterfin Rainbow-Roll', 'Gelatina Ringletfin', 'Funfetti Flick-Flick', 'Fluffle-Muffin Sparklefin', 'Jovian Jamboree Jellydonut Jellyfish', 'Gumball Glooperfish', 'Jester Jellyfin', 'The Chocolate Star Gingo Guppy', 'Whirly Snuffleback Trout', 'Jolly Jellyjam Fish', 'Lounging Liquorice Crustacean-Nosed Berryfin', 'Jinglefin Jellyfrizzle', 'The Polka-Dot-Propeller Puffling Fish', 'JubiliFLOPinear Snorkeldonut', 'Glittering Gummy Guppy', 'Whiskered Whizzler', 'Flamango-Buzzling Sushi Swimmer', 'Flutterfin Bubblegum Gumball', 'ChocoSeahorsefly', 'Speckled Toastfin Snorkelback', 'Jamboree Jellywing', 'Fantabulous Fry-Sherbert Aquapine', 'Jamboree Jellofish', 'Bubblegum Blowfish-Bee', 'Frizzle Fringe Flutterfin', 'Whiskered Jumblefish', 'Bumblefin Toffee Torpedo', 'Jolly Jellypeanut Fish', 'Gumbubble Guppy', 'Glittering Gummy Whipray', 'Flippity Flan Flopper', 'Biscuit Bugle-Tail Fish', 'Strudel Scuttle Scalefish', 'Bubblegum Bumblefin', 'Flutterfin Falafeluncher', 'The Splendiferous Spaghetti Starfin', 'The Rainbow Jelibelly Floatfish', 'Rhinoceros Beetle Bumble Tuna', 'Hatwearing Hippofish', 'Frizzle Fish', 'Polka-Pop CandyFloss Fish', 'Bumbleberry Floatfish', 'Caramelotus Humming Float', 'Dandy Candy Goby', 'The Polka-Dot Pudding Puff', 'Frosted Donut Jellyfluff Puffer', 'Flamingo Flapjack Finaticus', 'The Splendiferous Spaghetti Seahorsicle', 'Flutterfin Scoopscale', 'Frizzle Frazzle Fly-n-Fish', 'Frizzle-Frizzled Jambalaya Jellyfish', 'Sprinkfish', 'Fantail Flutterfin', 'JellyChip CuddleSwimmer', 'Whiskered Lollipop Loonfish', 'Jester Gumball Pufferfish', 'The Hummingbrewster BumbleFlish', 'Jangleroo Snackfin', 'Blibbering Blubberwing', 'The Bubblegum Confeetish', 'Fantastical Fusilloni Flounderfish', 'Pizzafin Flutterbub', 'The Whiskered Watermelon Pufferfish', 'The Bumblebee Doughnut Delphin', 'Pistachio Pizzafin Puffinfly', 'Aquatic JellyPuff Doughnut Shark', 'Gumball Guppygator', 'The Burgerwing Seahorse', 'Bellychuckle Balloonfish', 'FizzleWing PuffleGill', 'Bumbleberry Rainbow Flicorn Fish', 'Whistlefin Wafflegill', 'Pizzadillo Glitter-Guppy', 'Jamboree Jellydonut Jellyfish Trout', 'The Bubblegum Bumblefin', 'Gelatino Floatyfin', 'The Frambuzzle Flickerfin', 'The Speckled Pizzafin Fizzflyer', 'Sparkling Pizzafin Pixie-fish', 'Bumblecado Finstache Hybridsail', 'Pizzamarine Popcorn Puffer', 'Laughter Ligrolomia', 'Frosted Jelly Doughnut Pegasus Finfish', 'The Whirling Donut Jellygator', 'Flutterfin Cupcake Goby', 'The Gumball Guppy', 'Bubblegum Blowfish Beetle Bug', 'Sparkling Gumbubble Piscadot', 'The Flamboyant Flutter-fish', 'Twinkling Tortellini Trouterfly', 'Beatleberry Fluff Guppy.', 'Glaze Meringuelle', 'The Whiskered Blubberberry Flapper', 'Sherbet Swooshfin', 'Marzipoisson Popsicala', 'Bubblegum Ballistic Barracuda', 'Puzzletail Splashcake', 'Fantasia Fluffernutter Finfish', 'Rainbow Jelly-Dough Fish', 'Flutterfin Pizzapuffer', 'BugBrella Aquacake', 'Twirly Finny Cakeling', 'Frizzleberry Flapjack Fish', 'Whiskered Sprinkle Glider', 'The Pristimaela Parfait Pengu-Angel', 'Bubblerooni WhiskerWaffle', 'The Speckled Whisker-Spoon Puffer', 'BumbleSquid Donutella', 'Sparkleberry Gobblefin', 'Fizzgiggle Frizzlefin', 'JibberJelly Sundae Swimmer', 'The Flutterfin Pastry Puffer', 'Rainbow Gummy Scalefish', 'Jingle JellyFroth Fish', 'The Spotted Flutterfin Pastrytetra', 'Flutterfin Hotcheeto Penguinfish', 'Piscis Cyberneticus Skodo', 'Oreo OctoPufferRock', 'Fluffernutter Pufferpine', 'Whirlygig Polka-Dotted Jelly-Donut Pufferfish', 'Bumbleberry Gilled Glider', 'Polkadot Pancake Puffer', 'Mermacorn Fish', 'Sprinkle Starfish Sardine', 'Choco-Bumblefin Parrot Trout', 'The Fantabulous Gala Glazed-Guppy', 'Pudding Puff ParrotMoth Fish', 'Fantastical Flapjack Flipperfin', 'TruffleBugle ZephyrFish', 'Bumbleberry Glitterfin', 'The Jester Jellycarafe', 'The Flamingotuna McSprinklefin', 'Whiskerfroth Flutterfin', 'Spotted Sprinkledonut Puffer', 'Stripe-tailed Pepperoni Puffer', 'Jelly-Feather Macaroon Guppy', 'Flutterfin Pancake Puffer.', 'Whiskered Rainbow Glidleberry', 'Chucklefin Clownfish', 'Bumbleberry Snorkelsnout', 'Jolly Jellydozer', 'The Polka Dotted Jello-fish', 'The Bumbleberry Guppiesaurus', 'Flutterfin Pizzacrust Glimmertail', 'Bumblebee, Pizza-fin Jamboree', 'Whizzbizzle Poptuckle', 'Candyfloss Clownphino', 'Flutterglaze Bumblefin', 'Bumbleberry Poptarticus', 'Plaid Zephyr Cuddlefin', 'Jolly Jambalaya Jubilee Fish', 'Confetti Clownfrippery Fish', 'Rainbow Jelly-Bumble Shark', 'Marshmallow Pogo-Starfish', 'The Spangled Jelly-Tortle Ripplefin', 'Fantabulous Rainbow Polka Poptartfish', 'The ChocoChandelier Goldnipper', 'Gummybrella Anemofin', 'Gummy Fizzler', 'The Bumblebelly Polkadot Glaze-fish', 'Fantaray Flakefin', 'Splendiferous Ribbontail', 'The Butterfleagleberry Seahorse', 'Sushinano Sweetsquid', 'The Whiskered Melonfin', 'The Fantastical Fizzbopper', 'Splashtastic Bagelback Rainbownose', 'Pizzafly Rainbowgill', 'Frizzling Bubblehopper', 'Cuckoo Bubblegum Unicornfish', 'The Lucid Lollyscale'];
var i = 0;
var idx = 0
while (idx >= 0 && i < fishlist.length) {
idx = playerData.fishCaught.findIndex(fish => fish.name === fishlist[i]);
if (idx < 0) {
console.log("Looking for: " + fishlist[i] + " [" + i + "]");
ImageAssets['minimap'].src = 'https://2023.holidayhackchallenge.com/sea/assets/noise/' + fishlist[i].replace(/ /g, '%20') + '.png'
}
i = i + 1;
}
}
It will loop through the fish list until it finds one that isn’t in the list, and then update the map. I’ll run setInterval
in the console to run it every 30 seconds:
setInterval(update_map, 30000)
Epilogue
Poinsettia is impressed:

Poinsettia McMittens
You managed to catch every fish? You’re like the fishing version of a Christmas miracle!
Now, if only you could teach me your ways… but then again, I’m already pretty fabulous at everything I do.