STFS
STFS (Secure Transacted File System) is the file system used by the Xbox 360 for all packages created and downloaded by the system. It is protected using a series of SHA1 hashes and a RSA signature. STFS is commonly found in Xbox 360 Content Packages (XContent), but is not limited to those only as the PEC (Profile Embedded Content) files employ STFS. The two known categories for STFS are read-only and writeable. Read-only content packages are found with a PIRS/LIVE signed header and writeable content packages are console signed (CON).
The 360 uses packages to transfer saves/content/games/pictures and more. Most packages start with the strings PIRS, LIVE or "CON ", some are even embedded inside gamer profiles (using the PEC format described below). All of these are STFS content packages which hold the real files along with metadata that the dashboard reads like the title, the licenses and the RSA signature which is used to verify the package.
The acronym STFS stands for Secure Transacted File System, which shows how the packages are secure (signature and hashes) and transacted (multiple file / directory revisions)
LIVE and PIRS files come from Xbox Live, these are signed using a private key that only Microsoft has. The console uses a public key which is hardcoded inside it to verify the package and make sure the person is allowed to use it. CON files are created by the console for saves and profiles. The console uses its own private key to sign CON files. Many editors are available for saves and profiles which can be used with no modification to the console.
Throughout an STFS package, there is a series of SHA1 hashes used to verify the package, and help with downloads (if a block isn't valid, it can be redownloaded). The hashes are located at certain parts of the file, a way of calculating where is (will be!) down below.
Volume Descriptor
There are 2 types of volume descriptor a content file may have, one for STFS packages and another for SVOD
STFS
Offset | Length | Type | Information |
---|---|---|---|
0x00 | 0x01 | byte | Volume descriptor size (0x24) |
0x01 | 0x01 | byte | Reserved |
0x02 | 0x01 | byte | Block Seperation |
0x03 | 0x02 | signed short | File Table Block Count |
0x05 | 0x03 | signed int24 | File Table Block Number |
0x08 | 0x14 | byte[] | Top Hash Table Hash |
0x1C | 0x04 | signed int | Total Allocated Block Count |
0x20 | 0x04 | signed int | Total Unallocated Block Count |
SVOD
Offset | Length | Type | Information |
---|---|---|---|
0x00 | 0x01 | byte | Volume descriptor size (0x24) |
0x01 | 0x01 | byte | Block Cache Element Count |
0x02 | 0x01 | byte | Worker Thread Processor |
0x03 | 0x01 | byte | Worker Thread Priority |
0x04 | 0x14 | byte[] | Hash |
0x18 | 0x01 | byte | Device features |
0x19 | 0x03 | uint24 | Data block count |
0x1C | 0x03 | uint24 | Data block offset |
0x1F | 0x05 | byte[] | Padding/reserved |
Content Packages
Header
Offset | Length | Type | Information |
---|---|---|---|
0x0 | 0x4 | ascii string | Magic |
Magic
The magic can be one of the following:
Signature Type | Information |
---|---|
"CON " | Signed by a console. Found on many files such as cache files, profiles, saved games. |
PIRS | Signed by Microsoft. Found on files delivered by Microsoft through non-Xbox Live means such as system updates. |
LIVE | Signed by Microsoft. Found on files delivered over Xbox Live such as items from the Marketplace like themes. |
Signatures
CON
The following is used for Console Signed ("CON ") packages, and is used by the Xbox as the format for signatures generated by it:
Offset | Length | Type | Information |
---|---|---|---|
0x004 | 0x2 | byte[] | Public Key Certificate Size |
0x006 | 0x5 | byte[] | Certificate Owner Console ID |
0x00B | 0x14 | ascii string | Certificate Owner Console Part Number |
0x01F | 0x1 | byte | Certificate Owner Console Type (1 for devkit, 2 for retail) |
0x020 | 0x8 | ascii string | Certificate Date of Generation |
0x028 | 0x4 | byte[] | Public Exponent |
0x02C | 0x80 | byte[] | Public Modulus |
0x0AC | 0x100 | byte[] | Certificate Signature |
0x1AC | 0x80 | byte[] | Signature |
LIVE / PIRS
and for remotely signed (LIVE/PIRS) packages:
Offset | Length | Type | Information |
---|---|---|---|
0x004 | 0x100 | byte[] | Package Signature |
0x104 | 0x128 | byte[] | Padding |
The Package Signature is generated using the value at 0x32C (Content ID/Header SHA1).
Metadata
If the Metadata Version field is set to 2, the format is slightly changed.
Version 1
Offset | Length | Type | Information |
---|---|---|---|
0x022C | 0x100 | license entries (see below) | Licensing Data (used to check package owner) |
0x032C | 0x14 | byte[] | Header SHA1 Hash (from 0x344 to first hash table) |
0x0340 | 0x4 | int | HeaderSize |
0x0344 | 0x4 | signed int | Content Type (see below) |
0x0348 | 0x4 | signed int | Metadata Version (see below) |
0x034C | 0x8 | signed long | Content Size |
0x0354 | 0x4 | int | Media ID |
0x0358 | 0x4 | signed int | Version (system/title updates) |
0x035C | 0x4 | signed int | Base Version (system/title updates) |
0x0360 | 0x4 | int | Title ID |
0x0364 | 0x1 | byte | Platform (Xbox 360 = 2/PC = 4) |
0x0365 | 0x1 | byte | Executable Type |
0x0366 | 0x1 | byte | Disc Number |
0x0367 | 0x1 | byte | Disc In Set |
0x0368 | 0x4 | int | Save Game ID |
0x036C | 0x5 | byte[] | Console ID |
0x0371 | 0x8 | byte[] | Profile ID |
0x0379 | 0x24 | Volume Descriptor | Volume Descriptor |
0x039D | 0x4 | signed int | Data File Count |
0x03A1 | 0x8 | signed long | Data File Combined Size |
0x03A9 | 0x4 | int (STFS = 0, SVOD = 1) | Descriptor type |
0x03AD | 0x4 | int | Reserved |
0x03B1 | 0x4C | byte[] | Padding |
0x03FD | 0x14 | bytes | Device ID |
0x0411 | 0x900 (each 0x80 = different locale) | UTF-8 string | Display Name |
0x0D11 | 0x900 (each 0x80 = different locale) | UTF-8 string | Display Description |
0x1611 | 0x80 | UTF-8 string | Publisher Name |
0x1691 | 0x80 | UTF-8 string | Title Name |
0x1711 | 0x1 | byte | Transfer Flags (see below) |
0x1712 | 0x4 | signed int | Thumbnail Image Size |
0x1716 | 0x4 | signed int | Title Thumbnail Image Size |
0x171A | 0x4000 (thumbnail size) | image | Thumbnail Image |
0x571A | 0x4000 (title thumbnail size) | image | Title Thumbnail Image |
Version 2
Offset | Length | Type | Information |
---|---|---|---|
0x03B1 | 0x10 | byte[] | Series ID |
0x03C1 | 0x10 | byte[] | Season ID |
0x03D1 | 0x2 | signed short | Season Number |
0x03D3 | 0x2 | signed short | Episode Number |
0x03D5 | 0x28 | byte[] | Padding |
0x171A | 0x3D00 (thumbnail size) | image | Thumbnail Image |
0x541A | 0x300 (each 0x80 = different locale) | image | Additional Display Names |
0x571A | 0x3D00 (title thumbnail size) | image | Title Thumbnail Image |
0x941A | 0x300 (each 0x80 = different locale) | image | Additional Display Descriptions |
License Entries
For every entry in the license data field:
Offset | Length | Type | Information |
---|---|---|---|
0x0 | 0x8 | signed long | License ID (XUID / PUID / console id) |
0x8 | 0x4 | signed int | License Bits |
0xC | 0x4 | signed int | License Flags |
Content Types
Value | Description |
---|---|
0x0000001 | Saved Game |
0x0000002 | Marketplace Content |
0x0000003 | Publisher |
0x0001000 | Xbox 360 Title |
0x0002000 | IPTV Pause Buffer |
0x0004000 | Installed Game |
0x0005000 | Xbox Original Game |
0x0005000 | Xbox Title |
0x0007000 | Game on Demand |
0x0009000 | Avatar Item |
0x0010000 | Profile |
0x0020000 | Gamer Picture |
0x0030000 | Theme |
0x0040000 | Cache File |
0x0050000 | Storage Download |
0x0060000 | Xbox Saved Game |
0x0070000 | Xbox Download |
0x0080000 | Game Demo |
0x0090000 | Video |
0x00A0000 | Game Title |
0x00B0000 | Installer |
0x00C0000 | Game Trailer |
0x00D0000 | Arcade Title |
0x00E0000 | XNA |
0x00F0000 | License Store |
0x0100000 | Movie |
0x0200000 | TV |
0x0300000 | Music Video |
0x0400000 | Game Video |
0x0500000 | Podcast Video |
0x0600000 | Viral Video |
0x2000000 | Community Game |
Transfer Flags
These are bit flags, don't treat them as an enum!
Bit # | Flag Value | Description |
---|---|---|
0 | 0b00000001 | None |
1 | 0b00000010 | None |
2 | 0b00000100 | Deep Link Supported |
3 | 0b00001000 | Disable Network Storage |
4 | 0b00010000 | Kinect Enabled |
5 | 0b00100000 | Move-Only Transfer |
6 | 0b01000000 | Device ID Transfer |
7 | 0b10000000 | Profile ID Transfer |
Profile Embedded Content (PEC)
When extra security is needed for content which is already using STFS, PEC files may be used to add an extra layer on top. PEC files use the STFS descriptor and algorithms, but has no similarity with content packages.
PEC files are only currently used for avatar items/clothing.
Block 0 starts at 0x3000, and hash table 0 at 0x1000/0x2000.
Header
Offset | Length | Type | Information |
---|---|---|---|
0x000 | 0x228 | Console_Security_Certificate | Console Security Certificate |
0x228 | 0x14 | bytes | SHA1 hash from 0x23C-0x1000 |
0x23C | 0x8 | signed long | Unknown |
0x244 | 0x24 | Volume Descriptor | Volume Descriptor (STFS) |
0x268 | 0x4 | signed int | Unknown |
0x26C | 0x8 | bytes | Profile ID |
0x274 | 0x1 | byte | Unknown |
0x275 | 0x5 | bytes | Console ID |
File Listing
The value at 0x37E (File Table Block Number on the structure above) determines where the file table begins. As it is a block number, you will have to convert it to an offset. Here is some code in C# for converting:
internal int BlockToOffset(int xBlock)
{
int xReturn = 0;
if (xBlock > 0xFFFFFF)
xReturn = -1;
else
xReturn = (((MetaData.HeaderSize + 0xFFF) & 0xF000) + (xBlock << 12));
return xReturn;
}
internal int ComputeDataBlockNumber(int xBlock)
{
int xBlockShift;
if (((MetaData.HeaderSize + 0xFFF) & 0xF000) == 0xB000)
xBlockShift = 1;
else
if ((MetaData.Descriptor.BlockSeperation & 1) == 1)
xBlockShift = 0;
else
xBlockShift = 1;
int xBase = ((xBlock + 0xAA) / 0xAA);
if (this.Header.Magic == XContent_Header.Header_Magic.CON)
xBase = (xBase << xBlockShift);
int xReturn = (xBase + xBlock);
if (xBlock > 0xAA)
{
xBase = ((xBlock + 0x70E4) / 0x70E4);
if (this.Header.Magic == XContent_Header.Header_Magic.CON)
xBase = (xBase << xBlockShift);
xReturn += xBase;
if (xBlock > 0x70E4)
{
xBase = ((xBlock + 0x4AF768) / 0x4AF768);
if (this.Header.Magic == xBlockShift)
xBase = (xBase << 1);
xReturn = (xReturn + xBase);
}
}
return xReturn;
}
Each embedded file starts at a 4096 byte boundary. The optional space between embedded files is filled with null bytes.
The file listing consists of entries which have the format below. The listing ends with an entry consisting of only null bytes.
Offset | Length | Type | Information |
---|---|---|---|
0x0 | 0x28 | ASCII string | File name, null-padded |
0x28 | 0x1 | byte | Length of file name, plus flags |
0x29 | 0x3 | signed int24 | Number of blocks allocated for file (little endian) |
0x2C | 0x3 | signed int24 | Copy of 0x29 |
0x2F | 0x3 | signed int24 | Starting block number of file (little endian) |
0x32 | 0x2 | signed short | Path indicator (big endian) |
0x34 | 0x4 | int32 | Size of file in bytes (big endian) |
0x38 | 0x4 | signed int32 | Update date/time stamp of file |
0x3C | 0x4 | signed int32 | Access date/time stamp of file |
Byte 0x28 also has two flags: bit 6 and bit 7. If bit 6 is set it means that all of the blocks in the file are consecutive. Bit 7 indicates that the file is a directory. The first 6 bits 0-5, are the length of the filename.
public System.Boolean IsDirectory//bit 7
{
get
{
return (Flags & 128) == 128;
}
set
{
if (value != IsDirectory)
{
Flags ^= 128;
}
}
}
//public System.Boolean IsUnknown//bit 6
//{
// get
// {
// return (Flags & 64) == 64;
// }
// set
// {
// if (value != IsUnknown)
// {
// Flags ^= 64;
// }
// }
//}
public System.Byte NameLength//first 6 bits
{
get
{
return (System.Byte)(Flags & 63);
}
set
{
Flags ^= (System.Byte)(value ^ NameLength);
}
}
The path indicator indicates the path of the file. -1 (0xFFFF) means that the file is in the root directory, any other value V refers to the (sub)directory which is listed as the Vth entry in the listing (counting from 0). Directories can nest.
The FAT format is used for the date/time stamps of the files.
Hash Tables / Block Offsets
A block is 0x1000 (4096) bytes and the first block is located at 0xC000. Every 0xAA (170) blocks there is a block that contains a hash of each block and every 0xAA*0xAA (0x70E4) blocks there is another table (presumably hashing the 0xAA table blocks). This means that when using a block number from a file listing you need to account for the hash tables which are not included in the block numbering system. For example block 171 is actually located where you would expect block 172 to reside ((171 + int(171/170)) * 0x1000 + 0xC000).
Offset | Length | Type | Information |
---|---|---|---|
0x0 | 0x14 | byte | Hash(SHA1) |
0x14 | 0x1 | byte | Status byte |
0x15 | 0x3 | int24 | Next block |
The status byte is very important when injecting files into a package. Here are the meanings of them.
Value | Meaning |
---|---|
0x00 | Unused Block |
0x40 | Free Block (previously used) 0x80 Used Block |
0x80 | Used Block |
0xC0 | Newly Allocated Block |
Files are not always stored in consecutive blocks, you have to find the corresponding hash table record and read the next block number (similar to cluster maps in FAT).
Here is some C# code for converting a block to it's hash table position(it may not work perfectly!):
internal int ComputeLevelNHashBlockNumber(int xBlock, int xLevel)
{
int xBlockShift;
if (((MetaData.HeaderSize + 0xFFF) & 0xF000) == 0xB000)
xBlockShift = 1;
else
if ((MetaData.Descriptor.BlockSeperation & 1) == 1)
xBlockShift = 0;
else
xBlockShift = 1;
int[] xBlockStep;
if (((MetaData.HeaderSize + 0xFFF) & 0xF000) == 0xB000)
xBlockStep = new[] { 0xAC, 0x723A };
else
if ((MetaData.Descriptor.BlockSeperation & 1) == 1)
xBlockStep = new[] { 0xAB, 0x718F };
else
xBlockStep = new[] { 0xAC, 0x723A };
int xReturn;
int xBase;
int xStep = xBlockStep[1];
if (xLevel == 0)
{
xReturn = (xBlock / 0xAA);
xStep = (xReturn * xBlockStep[0]);
if (xReturn != 0)
{
xReturn = (xBlock / 0x70E4);
xBase = (xReturn + 1);
if (this.Header.Magic == XContent_Header.Header_Magic.CON)
xStep += (xBase << xBlockShift);
else
xStep += xBase;
if (xReturn == 0)
return xStep;
else
{
if (this.Header.Magic == XContent_Header.Header_Magic.CON)
return (xStep + (1 << xBlockShift));
else
return (xStep + 1);
}
}
else
return xStep;
}
else if (xLevel == 1)
{
xReturn = (xBlock / 0x70E4);
xBase = (xReturn * xStep);
if (xReturn != 0)
{
if (this.Header.Magic == XContent_Header.Header_Magic.CON)
return (xBase + (1 << xBlockShift));
else
return (xBase + 1);
}
else
return (xBase + xBlockStep[0]);
}
else if (xLevel == 2)
{
return xStep;
}
else
{
throw new Exception("Level Unknown: " + xLevel.ToString());
return 0xFFFFFF;
}
}
Tools
- Velocity can create, open, view and extract most STFS files, and other Xbox and Xbox 360 files.
- extract360.py is an (old) tool (Python 2.5 required) to analyze and extract these archive files.
- py360 can read STFS files
- wxPirs 1.1 can extract from LIVE/PIRS files fine, but as it doesn't use hash tables properly it doesn't work well with CON files.
- A newer tool was released by DJ Shepherd called Le Fluffie, which can create and extract from CON/LIVE/PIRS files (but it has some problems with creation, some prefer to use XLAST)
- XLAST inside the Xbox 360 SDK can create LIVE/PIRS packages, but it is illegal to share it.