This is a C++17 library for reading data from Warframe's files stored in the Cache.Windows folder. Data stored in these cache files contain compressed data in a directory-like structure. See below for an example to retrieve a file's data.
This will allow you to access files stored within the .toc and .cache files. That's it. It doesn't have any knowledge of the file types. It cannot distinguish between 3D Models, Textures, and Audio clips. But this library will retrieve and decompress the raw data for these files.
- Adding this project as a Git submodule is the easiest integration method for a project.
git submodule add https://github.com/Puxtril/LotusLib.git
- Initilize all required submodules with
git submodule update --init --recursive LotusLib
. - This builds using CMake. Ensure CMake is installed on your platform, then add this to your project's CMakeLists.txt:
add_subdirectory(path/to/LotusLib)
. When you create your executable, link withtarget_link_libraries(<YOUR_EXE> LotusLib)
. - This library uses Oodle for decompression. To avoid issues, the static library is not included in this repository. You will need to obtain this yourself first.
- Download the files.
- Easy: Download from here.
- Hard: Download from the engine
- Download Unreal Engine from the official website (You will need an account and the Epic launcher)
- Once downloaded find the SDK folder
Engine/Source/Runtime/OodleDataCompression/Sdks/<version>/lib
- Create a folder in the root of this repository (or top-most project) named
bin
- Copy folders
Linux
andWin64
intobin
. We want the static libraries here.
- Download the files.
- Once you have everything installed, you need to create a PackageCollection to start reading files. You currently have 2 options for the CachePairs: CachePairReader and CachePairMeta.
- If you want to read file data, use CachePairReader. This requires cache files to be present.
- If you only have the .toc files, use CachePairMeta.
- If you have other needs, subclass CachePair and create your own interface.
- Determine the filepath of your Cache.Windows folder. If you installed through Steam, it's most likely in "C:\Steam\steamapps\common\Warframe\Cache.Windows".
- Determine if your installation is pre/post Ensmallening. This is pretty easy - if the installation was updated after September 9th, 2020; it's post.
Now you can create a collection like so:
#include "LotusLib.h"
int main()
{
// "Cache.Windows" is located in the installation directory
LotusLib::PackagesReader dir("C:\\Steam\\steamapps\\common\\Warframe\\Cache.Windows");
// This loads the package "Misc" from the "Cache.Windows" directory
LotusLib::PackageReader pkg = dir.getPackage("Misc");
/*
These are defined in the project
Putting their definition here for documentation purposes.
struct FileEntry
{
CommonHeader commonHeader;
LotusPath internalPath;
BinaryReader headerData;
BinaryReader bData;
BinaryReader fData;
};
struct CommonHeader
{
std::array<uint8_t, 16> hash;
std::vector<std::string> paths;
std::string attributes;
uint32_t type;
};
The CommonHeader gives you the Type (Model/Texture/Material/etc...)
This is an int enumeration that must be known ahead of time.
Currently known formats are used in Warframe Exporter
The contents of `bData` and fData` may not always be populated.
Some types only store binary data in B, others only if F, some a mix of both.
*/
LotusLib::FileEntry entry = pkg.getFile("/Lotus/Characters/Tenno/Excalibur/Excalibur_skel.fbx");
}
Each interface has a linear iterator, making iteration very easy.
Here's an example that counts every file in the archives:
#include "LotusLib.h"
using namespace LotusLib;
int main()
{
LotusLib::PackagesReader dir("C:\\Steam\\steamapps\\common\\Warframe\\Cache.Windows");
for (auto& x : dir)
{
LotusLib::PackageReader easyReader(x);
for (auto& file : easyReader)
{
LotusLib::FileEntry entry = easyReader.getFile(file);
}
}
}
That's up to you! Within the cache files are 3D models, Textures, Audio files, Maps, AI scripts, Shaders, Animations, and much more. While you may see common file extensions like .fbx, .png, and .wav, they are certianly not stored in that format. Likely they are stored on the original servers in this format, but once packaged into the cache files, they are converted to something else. Most files are stored in a custom format, so no existing program (like Blender) can read the data. You will need to write a converter to a standard format. This is not an easy task, and falls under the general term of "Reverse Engineering". Crack open your hex editor and have fun!
The folks on Xentax have already done some of this work. Personally I have written an extractor for 3D Models (some errors) and Textures (fully functional). I have not released this code, but plan on releasing in another open-source project. If you would like to assist, or would simply like the file definitions for these, please contact me on Discord or Email. I also have partial definitions for Animations and Maps.
Other than Reverse Engineering, you can simply collect metadata on the current cache. You can take snapshots, look at historical versions of Warframe and see how the data has changed. Warframe has gone undergone many revisions, so plenty to analyze there!
So you're not left wondering "Why the hell was it designed like this"
Looking at the Warframe cache folder, we see a bunch of .toc and .cache files. Toc files only contain a tree-heirarchy of files/folders. They contain file sizes and offsets of the binary data in the matching cache file. These files should always be paired together, so they are abstracted into the CachePair class. Since there are multiple ways to interface with these files, CachePair is a base class. For reading data in the cache file, use CachePairReader and if you only have the toc file, use CachePairMeta. CachePairMeta is slightly misleading because it doesn't require cache files.
_CachePair_s are the lowest interface the user should interact with, but to make accessing easier, containers are built on top of the CachePair for easy, logical access. Package represent the triple pairs of H, F, and B. Within a package, the triples share a name. PackageCollection is a collection of _Package_s, indexed by their names.
Let's take an example file name. This is straight from the Cache.Windows folder:
H.Texture.toc
- ------- ---
| | |
| | |-Table of Contents file. There will always be a matching .cache file.
| |
| |-Package name. There is no defined limit of these.
|
|-PackageTrioType. Always an H, usually a matching B and F, but both not required.
Most files in the H PackagePair will start with a CommonHeader struct. Within this struct is an enumeration that defines the content of the files definition in H, F, and B.
Warframe uses 2 types of decompression: LZ and Oodle. Before their Ensmallening update, LZ was used as the main compression method. There is a custom LZ decompressor implementation (taken from a Xentax user). Post-ensmallening, everything (including non-textures), started using Oodle compression. Well, mostly. Files are not compressed in one block, the files can have multiple compressed blocks, and each of those blocks may use either compression method. 99% of the time it's oodle, but there is the rare occurrance of LZ compression.
Many extractors supporting Oodle still use the dynamic Oodle library to link against (oodlecore_9.dll). I find dynamic libraries a pain in the ass, especially for end-users if they need to source the file. Thus, the developer should be responsible for sourcing the static library.