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

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.