Libraries for Fun: Terraria World Library
Mon 07 March 2022Taking a Step Back
I guess I haven't made it enough of my personality that I'm the crazy computer guy. I say this because I've never been asked a question like: "Where did you learn to program?" or "What's the first line of code you wrote?" If I were to answer this, I'd probably bring up either one of two things. A program that allows a user to perform perfect bunny hops in Garry's Mod, or a Terraria world parser. Both of these programs I've since recreated with newer programming knowledge. However, I'm going to examine the world parser today. In the future, I'll talk about my adventures in making the bunny hopper much stronger than the first iteration.
The First Iteration
I'm actually surprised I got as far as I did back in 2020. I had so little knowledge of binary file formats that I thought I could search on StackOverflow for "how to parse binary file". After a bit of gathering resources, I set off on trying to make the parser. Unfortunately during this time, I didn't make frequent backups of my computer's storage, so a failed SSD in June of 2020 rendered any chance of finding an archive of this parser down to zero. However, I conveniently was somewhat proud as this was one of the first C++ projects I made, so I happily made a short video scrolling through the parser! (it was not indeed something to be proud of.)
Children: Please Cover Your Eyes
Oh my, profane comments, and the use of __uint8_t
?
How in the world did I even find that type? I've never seen it used anywhere but
this one parser.
2 Years Later...
Alright, here we go. 2 years of experience consumed, let's start. I planned to make this more of a library rather than a script to dump some random information about a Terraria world. So I started with a few files, one of which you'd include like a typical library:
#include <wldlib.h>
With the files created, I got the boring stuff out of the way (writing Makefiles, commenting, contemplating project setup.) I then started with actual file operations. I made a quick struct to pass around some information about the file buffer that I'd be using in various functions.
Parsing the info header parser was pretty easy. It gives information you'd expect a typical file header to give, such as a signature, file version, and other things. The only thing odd about it is that it stores a table of bools for looking up whether or not a tile's texture has specific UV coordinates.
The World Header
This part of the file is more exciting, and it certainly has excited me back in 2020. When reading this part of the file, it makes you think: "Dang, That actually works?" Anyways, this section has a lot of bools that tell you if various events have occurred in the world (bosses defeated, is there going to be a blood moon, etc.) Apart from the bools, there are also values such as world size, seed, texture styles, among others. This section is easy to parse, granted you have the time to name a variable for every single value you will be parsing.
Here's a big struct.
No, I don't expect you to decipher this struct. This is how many variables are stored in a Terraria world. I initially tried to include this as an image, but it's so horridly long, it doesn't format properly.
The Fun Part: Tile Data
Well, it's fun when you aren't trying to fix minor bugs. Anyways, the tile compression for Terraria world files is pretty easy to parse. (Yay!) Starting from the top left corner of the world, read a byte. We'll call this byte the active flags. If these flags have the first bit active, we'll read another byte. If this byte has it's first bit active, then we read a third byte. It helps to think of these two bytes after the active flags as a combined flags variable called the tile flags. Starting from the bottom bit and working our way up, If the second bit in the active flags is set, a tile is present, so read the next byte for the id. Same for the third bit, but it's a wall instead. Bits 4 and 5 tell us the type of liquid there is. If it is not 0, read the next byte for the liquid amount. If bit 6 is set, read the upper byte of the tile id (the tile id is greater than 255.) Bits 7 and 8 tell us if we need to read the number of tile copies as a a byte, or a short.
Onto the tile flags, if bits 2, 3, or 4 are set, there is a red, blue, or green wire in that order. Bits 5, 6, and 7 tell us the block's orientation (sloped, half block, etc.) Bits 10 and 11 tell us if there is an actuator, and if it's actively actuated. Bits 12 and 13 tell us if there is a tile color, or wall color, and to read the next byte for the color id. Finally, bit 14 tells us that a yellow wire is present, and bit 15 tells us to read the high byte of the wall id. Bonus: If the tile is important (checking back to the file's info header,) you read two shorts, the first one being the tile's U texture coordinate, and the second being the V.
If the recent Steam Deck updates to Terraria had you convinced there might be more content updates to come, This should hopefully reassure you that there will not be. The tile compression is very compact in the way it is stored now, so if worlds wanted to store more tile information, there would be some drastic changes.
Conclusion
The current functionality of the library took about a good 12 hours of continuous work, whereas I was probably cluelessly hoping something would piece together for days back in 2020. I haven't gotten around to parsing the rest of the file such as chest items, npcs, and signs, but I have managed to do some cool stuff with the current state of this library. For example, look at this:
I'd like to thank whoever went through the painful process to document the world format so I could write this parser. Round of applause for this article being very well written.