If you appreciate the work done within the wiki, please consider supporting The Cutting Room Floor on Patreon. Thanks for all your support!

Notes:Teardown

From The Cutting Room Floor
Jump to navigation Jump to search

This page contains notes for the game Teardown.

.tde file format

Some files are encrypted and have ".tde" tacked onto the end of their name, usually because they contain paid stock assets that the developers are legally obligated to protect.[1] An exception is data/script/achievements.lua.tde, which is encrypted for a different reason: to not spoil hidden achievements[2] (note that it isn't otherwise obfuscated in any way). Every version from launch day forward encrypts some of its assets (the performance test didn't use or support it).

The encryption is symmetric and done by XORing against an array of keys. There are two encryption keys and they can be found in the executable shortly after the string "F4 - Back to editor". Files can be decrypted/reencrypted with the following Lua code. The keys have never changed, so keys for latest will also work on files from launch day. Note that it isn't necessary to reencrypt a modified decrypted file; just put your modified version at the same path without ".tde" and the game will try to load it first and only load the encrypted version if the decrypted one was not found.

local length = infile:seek("end", 0)
infile:seek("set", 0)
for i=0, length-1 do
	local byte = infile:read(1):byte()
	for j=1, #keys do
		local key = keys[j]
		local k = i%#key+1
		byte = bit.bxor(byte, key:byte(k, k))
	end
	outfile:write(string.char(byte))
end

.bin file format (TDBIN)

Base game maps (officially "levels") were originally in XML format just like modded levels, but only on the developers' computers. In all known builds, they (except for the built-in example mods) are converted into a custom proprietary binary format identified with the magic bytes "TDBIN" (except the performance test which doesn't have this header), compressed with DEFLATE, and put into data/bin/ and given .bin extensions. This is the same format %LOCALAPPDATA%/Teardown/quicksave.bin is in, except quicksaves aren't compressed. It is a common misconception in the Teardown community that these files are encrypted. This has never been the case.

They can be decompressed/recompressed with zlib-flate if you're on a Unix-like operating system.

Tools for analysing binary levels:

Tools for decompiling binary levels (turning them into XML and .vox files):

Binary levels include the near complete contents of the registry (excluding savegame). This allows one to gain some information about the machine that baked the levels by looking at certain interesting nodes like game.path, game.savegamepath, spawn, etc. We can determine that the lead developer (Dennis Gustafsson) specifically baked the levels on their personal machine for most builds (with Marcus Dawson taking over for newer builds), and know at least some of the Workshop mods they are subscribed to. (This can also be the cause of it taking an unreasonably long time to save/load: being subscribed to a large number of mods and thus having a large number of items in your spawn menu which all get saved.)

Binary levels also include the environments for every loaded Lua script (except metatables and non-simple types like functions).

Scripting

The game uses Lua 5.1.4 (with support for an #include preprocessor directive shoddily patched in, and loading of precompiled bytecode patched out). Considerable portions of the game logic are implemented in Lua scripts. These scripts are not minified, obfuscated, or encrypted (with the sole exception of data/script/achievements.lua.tde).

Every script runs in its own isolated environment, unless a script #includes another script, in which case they share an environment and the included file will run first. They still keep their names in error messages and the like; it's not literally like C #include. If the include is invalid (e.g. file doesn't exist), it will silently fail and cause the script that made the include to not be run.

Registry

The game has a "registry" (not to be confused with the Windows registry), which is a tree of "nodes", which are key and value pairs. All keys and all values are strings. Nodes can have children. A period (".") acts as a delimiter. Values may not contain null bytes. There is a distinction between a node being nonexistent and a node existing with a value of an empty string. Creating a node also automatically creates all of its ascendants and gives them a value of an empty string if they don't already exist.

States

SPLASH
Runs data/ui/splash.lua (Tuxedo Labs logo, disclaimer, copyright).
MENU
Runs data/ui/menu.lua (main menu).
UI
Used when you click "Options" on a mod. Pressing Escape will cause you to return to MENU.
PLAY
Used when in a level. Runs data/ui/hud.lua (HUD, pause menu).
EDIT
Used when in level editor. Entirely in-engine; the editor does not use Lua (except for voxscript elements in an opened map).
QUIT
Closes the game. No Lua.

.pth file format (TDPTH)

In Teardown, your route through a mission is saved and able to drawn in-game as a red line. These paths are saved to files in the format %LOCALAPPDATA%/Teardown/path-id.pth, where id is the mission ID followed by either -best or -last. These files are in a custom proprietary binary format identified with the magic bytes "TDPTH". Here's an ImHex pattern to parse it:

#pragma endian little
#include <std/io.pat>
#include <std/sys.pat>
struct vec_t {
    float x;
    float y;
    float z;
} [[static, format("vec_str")]];
fn vec_str(vec_t vec) {
    return std::format("Vec({:.3g}, {:.3g}, {:.3g})", vec.x, vec.y, vec.z);
};
struct node_t {
    float time;
    u32 flags; // purpose unknown
    if (flags != 0 && flags != 1) {
        std::warning(std::format("unexpected node flags {:d}", flags));
    }
    vec_t pos;
} [[static, format("node_str")]];
fn node_str(node_t node) {
    return std::format("{:.3g}, {:d}, {:s}", node.time, node.flags, vec_str(node.pos));
};
struct tdpth_t {
    char magic[5];
    std::assert(magic == "TDPTH", "bad magic");
    be u24 version;
    std::print(
        "TDPTH {:d}.{:d}.{:d}",
        (version & 0xff0000) >> 16,
        (version & 0xff00) >> 8,
        version & 0xff
    );
    float time;
    u32 length;
    node_t nodes[length];
};
tdpth_t tdpth @ 0;

Featured mods

When entering the main menu, an unencrypted HTTP request is made to hxxp://promo.teardowngame.com/promo.php?version=1.3.0. It will return a URL like hxxp://promo.teardowngame.com/2023-03-31.zip. The response is saved to %LOCALAPPDATA%/Teardown/promo.txt, and then the URL is followed a ZIP file is downloaded and extracted to %LOCALAPPDATA%/Teardown/promo/. %LOCALAPPDATA%/Teardown/promo/promo.xml is then read and parsed. The file will probably not be redownloaded if the filename is the same as last time.

References

  1. https://discord.com/channels/760105076755922996/768940642767208468/798644224625999903 Decrypting .tde's is against the TOS, so please don't do that or post links to such tools. We'd love to distribute all materials in readable form, but since they include sub-licensed material we're legally bound to prevent reading them... —Voxagon, the official Teardown Discord server
  2. https://discord.com/channels/760105076755922996/841535153805459456/1031515432999452693 To not reveal the logic behind hidden achievements But at this point it can easily be googled anyway, so maybe we should remove that —Voxagon, the official Teardown Discord server