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:

image-20240103172522712

Location Layout

The port has a very Misfit Toys vibe:

image-20240102065607156Click for full size image

The Goose greets me with:

Good of the Island of Misfit Toys

Good of the Island of Misfit Toys

Beep beep

Luggage Lock Decode

Challenge

This area adds the Luggage Lock objective to the badge:

image-20240103172938115

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

Garland Candlesticks

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:

image-20240102070300043

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

image-20240102080130932

The help button shows up to interact:

image-20240102080154273

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:

img

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:

  1. Looking at the setup message to figure out the combo.
  2. Sending 10,000 “Open” messages with each combination possibility from the dev tools console.

Full details in this video:

Epilogue

Garland Candlesticks

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

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:

image-20240103184100202

The button turns to “Reel it in”:

image-20240103184125287

After some amount of time, it will turn red:

image-20240103184155623

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

image-20240103184338054

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

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):

image-20240103185134865 image-20240103185142176

Find Heat Maps

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

image-20240103184922120

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:

image-20240103185015170

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:

image-20240103185505419

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:

image-20240103185903516

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

image-20240103190044508

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:

image-20240103190513964

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:

image-20240103190640942

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:

image-20240103190819024

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

image-20240103190913322

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:

image-20240103191629624

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

image-20240103191712373

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

image-20240103191834704

The map in the bottom right updates:

image-20240103191852779

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:

image-20240103191948669

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

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.