There is a fun tradition from back in Trails in the Sky for Trails in the Chest that involves hiding fun messages inside of the treasure chests in some of the Kiskei games. You can find some of these chest messages here, such as the Trails in the Chest series but no comprehensive list seems to exist anywhere. It turns out they are not too hard to extract from the game, so I thought it would be a fun evening project to extra the ones from Trails to Azure since I just finished playing it and it was fantastic! I’ll write about that later. For now, let’s move on to getting those chest messages!
First, let’s begin with just finding the Trails in the Chest messages since they’re easier to find as they have well known bounds and text.
Let’s start with the following:
strings
on the game scene files to create a dump from all the strings I could find. This is pretty easy to do since the files are inside of a single data
directory and they’re not compressed. Something like find . | xargs strings > ~/workspace/dump
less ~/workspace/dump
shows us the following (mild spoilers from Azure):#5CCongratulations on reaching the end.
You've got a long road ahead of you still,
but I know you can do it.#0C
This is one of the messages from the game. We can see that it has some codes between the text. These are probably font colour op-codes since it is shared with some things that aren’t chest messages as well and they appear in a different font in game. For example:
Skimming through the file reveals that some other non-chest messages inside of the file. However, for now there is no need to worry about that since we’re just looking for the “Trails in the Chest” messages. They have some characteristics that make them easy to find:
With this, we can just use awk
to find them in the dump: *cat ~/workspace/dump | awk '/#5C\[.\*\/.\*\]/,/#0C/'*
. This gives us all the messages of which you can find a sample of them below:
#5C[55/61] In Bose, cramped between two of the most popular
shops on the block, was a tiny, tiny building. Last year,
it had been in a terrible state. Cobwebs once adorned the
windows, and insect droppings were commonplace.#0C
#5C[1/61] You remember reading the teaser in Trails in the Sky the 3rd
for 'Trails in the Chest, Pt. II,' but you didn't think it'd ever come.
Yet suddenly, you have a craving for more Trails...in the Chest.#0C
#5C[6/61] The day of their wedding, she, beautifully pristine
to a distressing degree in his eyes, had marched down
the aisle at a stiff pace. When he lifted her veil, he'd
seen no joy in her expression.#0C
#5C[44/61] 'Oh, SHUT UP!' Genevieve hopped just enough to
grip it and pulled it downward. Both had their hands on
the chest now, and neither one appeared to have any
intention of letting go. And then, to Genevieve's shock...#0C
#5C[11/61] True love! Something out of fairy tales, yes, but
Cyril's heart always did pang with longing while reading
them. He was a romantic at heart, and now here he sat,
We could do some cleanup to make this easier to read but it’s some simple regular expressions that we don’t need to cover here.
Now, how do we find all the chest messages? This is a bit trickier and we need to observe the following things about the string dump:
With this, we can write a short program to try and get the data out of our strings file:
const fs = require("fs");
const b = fs.readFileSync("/home/touma/Downloads/ao/dump");
const msgs = b.toString();
let pageBuffer = undefined;
let pageCount = 0;
const pages = [];
const lines = msgs.split("\n");
for (const line of lines) {
if (line.startsWith("#5C")) {
pageCount = 0;
pageBuffer = {
msg: "",
};
pageCount++;
}
if (pageCount > 4) {
// Give up on this message, lost cause and probably "garbage"
pageCount = 0;
pageBuffer = undefined;
}
if (line.includes("#0C")) {
// End of the line, we can include this line and then start over
if (pageBuffer) {
pageBuffer.msg += line.replace("#0C", "").replace("#5C", "") + "\n";
pages.push(pageBuffer);
}
pageCount = 0;
pageBuffer = undefined;
}
pageCount++;
if (pageBuffer) {
pageBuffer.msg += line.replace("#5C", "") + "\n";
}
}
for (const page of pages) {
const { msg } = page;
console.log(msg);
}
console.log(pages.length);
This gives us pretty much a perfect listing with some manual cleaning needed to remove some false positives.