
Base64, AMF3, and Ice Cream: Decoding Papa's Scooperia Save Files
How did I fall into this rabbit hole?
For the past few weeks, I've spent my Saturday evening revisiting a game series from my childhood: Papa Louie. It's a series of time management games developed by Flipline Studios where you play as a restaurant employee in charge of taking orders and preparing dishes for customers.

Papa Louie Games
There're over ten games in this series, each one focusing on preparing a specific type of food like sushi, burgers, or wings. The particular game I've been visiting was Papa's Scooperia, which was focused on cookies and ice cream.
I was playing the game on coolmathgames.com because it saved my progress without requiring me to log in, in stark contrast with other sites which would usually reset my progress unless I created an account.
I really liked this flow and, after a few weekends, I started to wonder how it actually worked. As a result, I started to do some digging. The first step was clear: Where was my data being saved?
How did I access my save data?
My initial guess was that the site stored the data on the browser. This seemed like the easiest route. This was further supported when I would try to load the game from a different browser and noticed that my progress wasn't stored there.
I then opened up my browser devtools to view the browser's storage. I figured it was in one of three possible areas:
- Local storage - A storage area that allows websites to store data persistently in the form of key-value pairs
- Session storage - Similar to local storage, but is isolated to each tab and persists only until the tab is closed
- Cookies - Small pieces of data that are automatically sent on each request, typically used in authentication
There is another place the data could be stored: IndexedDB, which is an API that lets you store relatively larger amounts of data client-side. Why didn't I consider this?
Frankly, it wasn't really on my radar. It's a low-level API and was notorious for being difficult to use. It was introduced in 2015, so it's definitely an option, but I thought that it wasn't as easy to integrate compared to just shoving the data in one of the other storage options.
I ruled out cookies because I didn't think the data could fit there, as they have a 4kB size limit. I ruled out session storage as well because my saved game would show up even after I exited the browser and fired it back up again. This left only local storage, so I checked and there it was!

Local Storage with Save File
As mentioned above, local storage stores data in a key-value pair format. My saved data is paired with the key //papasscooperiahd1. You can see the value in the bottom right and I pasted a snippet of it down below. The only issue is I have no idea what this means.
1AWAY3,CgsBFXBsYXllcm5hbWUGDUtvaWxlZQlyYW5rBBkHZGF5BCwLc2NvcmUEgpdGCXRpcHME21wPdGlja2V0cwQAD2NvdXBvbnMEARFhbGxtb25leQSSmXYV...
I figured that this may be a base64 encoded string, since that's a common data format for web development (very scientific, I know). To test this, I went to a base64 decoder and pasted the string in there and I got...confused.

Base64 Decoder (First Attempt)
I didn't recognize anything, so I looked at the string again and I realized that it contained a comma near the start, which isn't a valid character in base64. So, I removed the portion of the string up to the comma, or AWAY3,, and tried it again.

Base64 Decode (Second Attempt)
Progress! Some of the characters are recognizable, for example:
playernameandKoilee- The name of my characterday- Signifies the day I'm currently on, though the value is currently unreadablescore- Signifies my total score in the game thus far, though the value is unreadable
However, we still have some characters that aren't legible. I asked ChatGPT to analyze the string and the data format was made clear: AMF3. This was a common data format utilized by flash games for communicating data to remote servers. I then asked it to decode the string to see if that finally reveals the data and it did!
1{2 "playername": "Koilee",3 "rank": 25,4 "day": 44,5 "score": 35782,6 "tips": 11740,7 "tickets": 0,8 "coupons": 1,9 "allmoney": 298230,10 "alltickets": 145,11 "character": "rita",12 "continuedgame": true,13 "visitedshop": true,14 "visitedbadges": false,15 "shownparade": false,16 "floatsparade": 7,17 "clicktwitter": false,18 "clickfacebook": false,19 // ...20}
I'm now able to see what the actual saved data looks like for the game.
What can we do with this data?
Now that I can get the raw data from the saved string, I began to wonder if it's possible to modify the raw data, convert it back into the encoded string, and override local storage with it. If it worked, then I could play the game with any state I want! I thought of starting with something small.
I love Denis Villeneuve's Dune, so I wanted to change my waiter's name from Carlo, his current name, to the name of the actor of Paul Atreides: Toyota Chevrolet.
1{2 "waiterdata": {3 "gender": "male",4 "skinTone": 15511933,5 "hairColor": 5321240,6 "freckles": 0,7 "eyeStyle": 2,8 "eyeDefaultExpression": 1,9 "eyeHeight": -3,10 "eyeSpacing": 1,11 "eyeNudge": -6,12 "mouthStyle": 1,13 "mouthDefaultExpression": 1,14 "mouthHeight": 0,15 "mouthNudge": -3,16 "mouthScale": 1.05,17 "headHeight": -1,18 "headScale": 0.9,19 "headForward": -14,20 "neckScale": 1.1,21 "weightScale": 0.85,22 "bodyStretch": 1.05,23 "neckStretchOffset": -5,24 "isBulky": false,25 "shirt": "tint_tshirt_solid:S",26 "jacket": "tint_camojacket:T-S-W",27 "pants": "tint_pants_2:T-S",28 "hair": "hair_carlo",29 "hat": "luau_hat_1",30 "shoes": "tint_shoes8:T-B-W",31 "facialhair": "",32 "bangs": "",33 "acca": "tint_lei:Y-T",34 "accb": "tint_belt_8:BU-GR",35 "accc": "tint_gloves1:S",36 "accd": "tint_sunglasses02:Y"37 },38 "waiter": "marty",39 "waitername": "Toyota Chevrolet", // <--- I changed this!40 "didcreatewaiter": true,41}

Changing waiter's name
It worked! I decided to have some more fun with it by trying a bunch of different modifications:
- Virtually infinite money
- Unlock every topping
- Make your worker/waiter look wacky

A wacky-looking waiter
You can do anything with this, though the trouble is figuring out what fields map to what outcomes as some of it isn't straightforward. For example:
unlockedis an array of0's and1's, which I believe corresponds to if a customer has been unlocked or not. The data provides no information as to which element in the array maps to which character.sealsandvisitsare similar tounlockedas they're also arrays corresponding to stats of each character.- Clothing fields of
workerdataandwaiterdata. Although it's obvious to see what field maps to which part of the character's appearance, certain fields only accept specific values. Trying to place a value that isn't allowed results in the game failing to load.
I created a utility script, with the help of ChatGPT, that allows you to create an encoded string based on the modifications you want on my Github.
AMF3 has a compact spec, so it's possible to encode/decode it with our own scripts. The libraries that do this for us are also unmaintained, since the format has been effectively dead for years
The base file there is my current saved state, but you can switch it out with your own. Feel free to check it out and use it! It would be great to disambiguate fields so we can know their exact effect in game. Until then, I hope you found this write-up fun.
Happy coding!