mirror of
https://github.com/radareorg/r2unity.git
synced 2026-11-07 06:36:00 -10:00
Document all resource formats used in Unity
This commit is contained in:
1 parent
540e6ffb16
commit
a50a18325c
1 file changed
+983
@@ -0,0 +1,983 @@
|
||||
# Unity Resources, Assets, Sidecars, and Save Data
|
||||
|
||||
This document describes the resource-like files found in Unity Player and
|
||||
IL2CPP builds, how the formats relate to one another, what radare2 and
|
||||
r2unity support today, and what remains to be implemented.
|
||||
|
||||
The most important point is that similar-looking names refer to several
|
||||
unrelated systems:
|
||||
|
||||
```text
|
||||
Unity content
|
||||
├── data.unity3d / *.bundle Unity archive (usually UnityFS)
|
||||
│ ├── resources.assets Unity SerializedFile object database
|
||||
│ ├── sharedassets*.assets Unity SerializedFile object database
|
||||
│ ├── *.resS streamed texture and mesh bytes
|
||||
│ └── *.resource streamed audio and video bytes
|
||||
│
|
||||
Managed/.NET resources
|
||||
└── Data/Managed/Resources/*.dll-resources.dat
|
||||
├── IL2CPP manifest-resource table
|
||||
└── payloads: XML, icons, binary data, or sometimes *.resources
|
||||
└── *.resources .NET name/value resource dictionary
|
||||
|
||||
Save data
|
||||
└── Application.persistentDataPath / PlayerPrefs
|
||||
└── filenames and formats selected by each game
|
||||
```
|
||||
|
||||
Consequently:
|
||||
|
||||
- `.assets`, `.resS`, and `.resource` normally contain read-only build
|
||||
content, not saved game state.
|
||||
- `.resources`, `.resource`, `.resS`, and `*-resources.dat` are distinct
|
||||
formats despite their similar names.
|
||||
- `.dat` is only a generic suffix. Its meaning must be inferred from its
|
||||
path, filename, magic, structure, and the code that reads it.
|
||||
- A literal `.res` file is not the same as Unity's case-sensitive `.resS`
|
||||
sidecar.
|
||||
|
||||
## 1. Format summary
|
||||
|
||||
| File or suffix | Meaning | Self-contained? | Current support |
|
||||
| --- | --- | --- | --- |
|
||||
| `global-metadata.dat` | IL2CPP managed metadata | Yes, but native address correlation needs the matching IL2CPP binary | r2unity |
|
||||
| `*.dll-resources.dat` | IL2CPP assembly manifest-resource pack | Yes | None |
|
||||
| `*.resources` | Standard binary .NET resource dictionary | Yes | Outer payload only when embedded in a PE; no entry parser |
|
||||
| `resources.assets` | Serialized assets originating in project `Resources` directories | Mostly; large payloads can be in sidecars | None |
|
||||
| `sharedassets*.assets` | Serialized assets shared by scenes | Mostly; large payloads can be in sidecars | None |
|
||||
| `globalgamemanagers.assets` | Assets referenced by global engine/project settings | Mostly; large payloads can be in sidecars | None |
|
||||
| `level*`, `CAB-*`, `*.sharedAssets` | Other Unity SerializedFiles | Mostly; may reference sidecars and other SerializedFiles | None |
|
||||
| `*.resS` | Raw streamed texture and mesh data | No; the owner supplies offsets, sizes, types, and names | None |
|
||||
| `*.resource` | Raw streamed audio and video data | Usually no; the owner supplies offsets, sizes, types, and names | None |
|
||||
| `data.unity3d`, `*.bundle` | Unity archive containing SerializedFiles and sidecars | Yes as a container | None |
|
||||
| Addressables `catalog.bin` / `catalog.json` | Key-to-location and dependency mapping | No; it points at bundles and providers | None |
|
||||
| Addressables `catalog.hash` | Catalog version/update fingerprint | No | None |
|
||||
| literal `*.res` | Usually a compiled Windows resource file, or an application-specific file | Yes for Win32 `.res` | Linked PE resources only; no standalone parser |
|
||||
| arbitrary `*.dat` | Application-specific data, cache, save, or one of the known `.dat` formats above | Unknown | Format-dependent |
|
||||
|
||||
## 2. Unity's build-content graph
|
||||
|
||||
Unity serializes content as a graph rather than as a directory of independent
|
||||
media files. A typical asset is recovered by following several layers:
|
||||
|
||||
```text
|
||||
UnityFS archive member
|
||||
↓
|
||||
SerializedFile object-table entry
|
||||
↓
|
||||
class/type information and object payload
|
||||
↓
|
||||
optional stream reference: path + offset + size
|
||||
↓
|
||||
bytes in a .resS or .resource sidecar
|
||||
```
|
||||
|
||||
Object references can also cross SerializedFiles. A scene object can point to
|
||||
an asset in `sharedassets0.assets`, `resources.assets`, another bundle, or a
|
||||
built-in resource file. Correct extraction therefore needs a deployment-wide
|
||||
view, not merely a parser for one extension.
|
||||
|
||||
Unity documents the high-level organization, but does not publish a complete
|
||||
stable specification for every version of the low-level SerializedFile and
|
||||
UnityFS encodings. It also warns that build layouts and filenames may change
|
||||
between versions. An r2unity implementation must use versioned parsers and a
|
||||
corpus covering old and current Unity releases.
|
||||
|
||||
References:
|
||||
|
||||
- [Unity build content output](https://docs.unity3d.com/ja/current/Manual/build-content-output.html)
|
||||
- [Unity AssetBundle file format](https://docs.unity3d.com/6000.0/Documentation/Manual/assetbundles-file-format.html)
|
||||
- [Unity AssetBundle compression](https://docs.unity3d.com/6000.0/Documentation/Manual/assetbundles-compression-format.html)
|
||||
|
||||
## 3. Unity SerializedFiles: `.assets`, `level*`, and `CAB-*`
|
||||
|
||||
### 3.1 Purpose
|
||||
|
||||
A Unity SerializedFile is an object database. Common filenames include:
|
||||
|
||||
- `resources.assets`
|
||||
- `sharedassets0.assets`, `sharedassets1.assets`, and so on
|
||||
- `globalgamemanagers` and `globalgamemanagers.assets`
|
||||
- `level0`, `level1`, and other scene files
|
||||
- `CAB-<hash>` members inside AssetBundles
|
||||
- `PlayerBuild-<scene>` and accompanying shared-asset members
|
||||
- built-in files such as `unity default resources` and
|
||||
`unity_builtin_extra`
|
||||
|
||||
The extension is not a reliable detector: several SerializedFiles have no
|
||||
extension, while unrelated files can use `.assets`. SerializedFiles normally
|
||||
do not begin with a textual magic string.
|
||||
|
||||
### 3.2 Logical contents
|
||||
|
||||
The versioned header and metadata describe at least:
|
||||
|
||||
- metadata size and total file size
|
||||
- SerializedFile format version
|
||||
- object-data offset
|
||||
- file endianness
|
||||
- Unity engine version, when present
|
||||
- target platform
|
||||
- whether a serialized type tree is present
|
||||
- serialized type/class definitions
|
||||
- object records
|
||||
- script type references
|
||||
- external SerializedFile references
|
||||
- reference types and user information in newer versions
|
||||
|
||||
Each object record associates a path ID with a type and a byte range. The
|
||||
object's stored offset is resolved relative to the file's data area. The
|
||||
exact width, alignment, and interpretation of fields depend on the
|
||||
SerializedFile version.
|
||||
|
||||
The type information may contain a type tree describing serialized fields.
|
||||
When the type tree is stripped, built-in Unity class layouts must be selected
|
||||
from a versioned database. `MonoBehaviour` and `ScriptableObject` payloads
|
||||
also require the corresponding managed script schema. For IL2CPP builds,
|
||||
r2unity can use `global-metadata.dat` to recover script field names and types,
|
||||
although the final Unity serialization layout still has to obey Unity's own
|
||||
serialization rules and version-specific alignment.
|
||||
|
||||
### 3.3 `resources.assets`
|
||||
|
||||
`resources.assets` is not a .NET `.resources` file. It is a Unity
|
||||
SerializedFile containing objects collected from project directories named
|
||||
`Resources`. Unity includes those objects in the build even when no scene
|
||||
directly references them, and `Resources.Load` resolves objects from this
|
||||
content.
|
||||
|
||||
Dependencies pulled in by a resource object may be stored alongside it or in
|
||||
other shared files. The logical name used by `Resources.Load` is not simply a
|
||||
filesystem entry in `resources.assets`; the SerializedFile objects and their
|
||||
container/name metadata must be decoded.
|
||||
|
||||
### 3.4 What should become an `RBinResource`
|
||||
|
||||
Not every serialized Unity object should be presented as an extractable
|
||||
resource. A useful mapping is:
|
||||
|
||||
- sections/fields: file header, metadata, type trees, object table, and data
|
||||
area
|
||||
- symbols: object path IDs and known object names
|
||||
- classes: Unity class IDs and recovered managed script types
|
||||
- resources: payload-bearing objects such as `TextAsset`, `Texture2D`,
|
||||
`Mesh`, `AudioClip`, `VideoClip`, shaders, fonts, and selected serialized
|
||||
script blobs
|
||||
|
||||
An `RBinResource` for a Unity asset should carry:
|
||||
|
||||
- a logical object or asset name when recoverable
|
||||
- the Unity class as its type
|
||||
- the owning SerializedFile as its origin
|
||||
- the inline or external offset and size
|
||||
- a stable index and, where useful, the path ID
|
||||
|
||||
Objects with bytes in a sidecar need special handling described in section 4.
|
||||
|
||||
## 4. Streamed sidecars: `.resS` and `.resource`
|
||||
|
||||
### 4.1 `.resS`
|
||||
|
||||
Unity uses `.resS` sidecars primarily for large texture and mesh data. The
|
||||
capital `S` is significant on case-sensitive filesystems.
|
||||
|
||||
A `.resS` file is generally a byte store, not a self-describing object
|
||||
database. The owning Texture2D, Mesh, or related object supplies a stream
|
||||
reference containing some combination of:
|
||||
|
||||
- sidecar or archive path
|
||||
- byte offset
|
||||
- byte size
|
||||
|
||||
Without the owning SerializedFile, r2unity can identify the file only as a
|
||||
raw data sidecar. It normally cannot assign names, types, or reliable record
|
||||
boundaries.
|
||||
|
||||
### 4.2 `.resource`
|
||||
|
||||
Unity uses `.resource` sidecars for audio and video content. As with `.resS`,
|
||||
the owning SerializedFile identifies the useful ranges.
|
||||
|
||||
The contents are not guaranteed to be one universal codec. For example, the
|
||||
Unity 6 iOS build inspected while preparing this document has a
|
||||
`sharedassets0.resource` beginning with `FSB5`, an FMOD sound-bank signature.
|
||||
Other builds can contain different audio/video encodings or several streamed
|
||||
ranges.
|
||||
|
||||
Opening a `.resource` file independently may reveal a recognizable inner
|
||||
container, but that does not replace the Unity object metadata needed to map
|
||||
the bytes back to `AudioClip` or `VideoClip` objects.
|
||||
|
||||
### 4.3 Why the current r2 resource API is insufficient for sidecars
|
||||
|
||||
`r_bin_file_get_resource_data()` currently obtains resource bytes by slicing
|
||||
`resource->paddr .. resource->paddr + resource->size` from the current
|
||||
`RBinFile` buffer. The `origin` member is descriptive metadata and is not
|
||||
consulted for extraction.
|
||||
|
||||
That model works for inline objects and `*.dll-resources.dat`, but not when a
|
||||
resource listed while `resources.assets` is open resides in
|
||||
`resources.assets.resS`.
|
||||
|
||||
Possible solutions are:
|
||||
|
||||
1. Add an optional `RBinPlugin` callback that returns an `RBuffer` for a
|
||||
resource, falling back to the current-file slice when absent.
|
||||
2. Represent sidecars as separate logical `RBinFile` objects and make the
|
||||
owning object graph point to them.
|
||||
3. Mount the entire Unity deployment or UnityFS archive as a filesystem and
|
||||
resolve stream paths through that mount.
|
||||
|
||||
The first option is the smallest generic radare2 enhancement. The second and
|
||||
third remain useful for navigation and container analysis.
|
||||
|
||||
## 5. UnityFS, `data.unity3d`, and AssetBundles
|
||||
|
||||
### 5.1 Unity archives
|
||||
|
||||
Modern AssetBundles and LZ4-compressed Player content normally use Unity's
|
||||
archive format and begin with the NUL-terminated signature `UnityFS`.
|
||||
`data.unity3d` is commonly such an archive.
|
||||
|
||||
The archive header identifies the archive format and Unity/generator
|
||||
versions, total size, compressed and uncompressed block-information sizes,
|
||||
and flags. The block-information structure describes compressed content
|
||||
blocks and directory nodes. Directory nodes name logical files such as:
|
||||
|
||||
- `CAB-<hash>` SerializedFiles
|
||||
- scene SerializedFiles
|
||||
- `.resS` sidecars
|
||||
- `.resource` sidecars
|
||||
- other build products
|
||||
|
||||
The low-level integers in common UnityFS headers are stored in big-endian
|
||||
form even though most contained SerializedFiles target little-endian
|
||||
platforms. A parser must treat container and member endianness separately.
|
||||
|
||||
### 5.2 Compression
|
||||
|
||||
Unity supports:
|
||||
|
||||
- full-content LZMA compression
|
||||
- chunked LZ4/LZ4HC compression
|
||||
- uncompressed content
|
||||
|
||||
LZ4 bundles divide content into independently decompressible blocks; Unity's
|
||||
documentation describes 128 KiB chunks. A member can span block boundaries,
|
||||
so extraction must reconstruct the logical uncompressed address space rather
|
||||
than treating a directory offset as a raw file offset.
|
||||
|
||||
radare2 already contains LZ4 decompression primitives. Full UnityFS support
|
||||
also needs LZMA support or an optional portable dependency.
|
||||
|
||||
### 5.3 Correct radare2 abstraction
|
||||
|
||||
Archive members are files, not merely `RBinResource` records. UnityFS is best
|
||||
implemented as either:
|
||||
|
||||
- an `RBinXtrPlugin` that returns decompressed member buffers, or
|
||||
- an `RFSPlugin` that mounts and lists archive members
|
||||
|
||||
After extraction, the normal SerializedFile plugin can process `CAB-*`,
|
||||
`.assets`, and scene members recursively.
|
||||
|
||||
Older Unity releases also used `UnityRaw` and `UnityWeb` bundle variants.
|
||||
They can be added after UnityFS support and require separate fixtures.
|
||||
|
||||
## 6. IL2CPP `*.dll-resources.dat`
|
||||
|
||||
### 6.1 Purpose and path
|
||||
|
||||
IL2CPP externalizes assembly manifest resources into files under the Player
|
||||
data directory's `Resources` directory. The generated runtime constructs the
|
||||
name as:
|
||||
|
||||
```text
|
||||
<Runtime data directory>/Resources/
|
||||
<assembly image name>-resources.dat
|
||||
```
|
||||
|
||||
Because an assembly image name normally includes `.dll`, common files are:
|
||||
|
||||
```text
|
||||
mscorlib.dll-resources.dat
|
||||
System.Data.dll-resources.dat
|
||||
System.Drawing.dll-resources.dat
|
||||
```
|
||||
|
||||
This behavior is implemented by generated libil2cpp code in:
|
||||
|
||||
```text
|
||||
Il2CppOutputProject/IL2CPP/libil2cpp/icalls/mscorlib/
|
||||
System.Reflection/RuntimeAssembly.cpp
|
||||
```
|
||||
|
||||
The runtime memory-maps the complete file and returns named resource slices
|
||||
to managed reflection APIs such as `GetManifestResourceStream`.
|
||||
|
||||
### 6.2 On-disk layout
|
||||
|
||||
The observed and generated-runtime layout is:
|
||||
|
||||
```c
|
||||
int32_t resourceRecordsSize;
|
||||
int32_t numberOfResources;
|
||||
|
||||
for (int32_t i = 0; i < numberOfResources; i++) {
|
||||
uint32_t resourceDataSize;
|
||||
int32_t resourceNameSize;
|
||||
uint8_t resourceName[resourceNameSize]; /* not NUL-terminated */
|
||||
}
|
||||
|
||||
/* Resource payloads follow consecutively in record order. */
|
||||
uint8_t resourceData[];
|
||||
```
|
||||
|
||||
The first payload starts at:
|
||||
|
||||
```text
|
||||
4 + resourceRecordsSize
|
||||
```
|
||||
|
||||
The offset of each later payload is the previous payload offset plus its
|
||||
declared `resourceDataSize`.
|
||||
|
||||
Current little-endian fixtures therefore decode as:
|
||||
|
||||
```text
|
||||
offset size meaning
|
||||
0x00 4 resource-record area size, excluding this field
|
||||
0x04 4 number of resources
|
||||
0x08 4 first payload size
|
||||
0x0c 4 first name size
|
||||
0x10 ... first name bytes
|
||||
... following descriptors
|
||||
4+size ... concatenated payload bytes
|
||||
```
|
||||
|
||||
Generated libil2cpp reads integers with native `memcpy` rather than explicit
|
||||
byte swapping. Existing desktop/mobile fixtures are little-endian; a robust
|
||||
implementation should use companion target information or reject ambiguous
|
||||
endianness rather than silently misparsing unusual targets.
|
||||
|
||||
### 6.3 Example payloads
|
||||
|
||||
Fixtures inspected in `test/bins/other/unityapks` contain:
|
||||
|
||||
- `System.Data.dll-resources.dat`
|
||||
- one `System.Data.SqlClient.SqlMetaData.xml` XML document
|
||||
- `System.Drawing.dll-resources.dat`
|
||||
- `Mono.ico`
|
||||
- `Information.ico`
|
||||
- `Error.ico`
|
||||
- `Warning.ico`
|
||||
- `Question.ico`
|
||||
- `Shield.ico`
|
||||
- `mscorlib.dll-resources.dat`
|
||||
- seven `collation.*.bin` payloads
|
||||
|
||||
Manifest-resource payloads are opaque to the outer pack. A payload can be
|
||||
XML, an image, an arbitrary binary, or a standard `.resources` dictionary.
|
||||
|
||||
### 6.4 Detection and validation
|
||||
|
||||
This format has no fixed magic. A plugin should combine a filename/path hint
|
||||
with complete structural validation:
|
||||
|
||||
- require a plausible nonnegative record-area size and resource count
|
||||
- cap the number of entries and name length
|
||||
- keep every descriptor inside the declared record area
|
||||
- reject negative name lengths
|
||||
- check every addition for integer overflow
|
||||
- require the payload area to start inside the file
|
||||
- require every payload to fit inside the remaining bytes
|
||||
- optionally require descriptor consumption to equal the declared record
|
||||
area exactly
|
||||
- reject overlapping or trailing layouts unless a real fixture demonstrates
|
||||
that Unity permits them
|
||||
|
||||
Filename matching alone must not claim arbitrary `.dat` files.
|
||||
|
||||
### 6.5 `RBinResource` mapping
|
||||
|
||||
The format maps directly onto radare2's resource API:
|
||||
|
||||
```text
|
||||
RBinResource.name manifest resource name
|
||||
RBinResource.type detected MIME/format or MANIFESTRESOURCE
|
||||
RBinResource.encoding raw unless the outer data is actually encoded
|
||||
RBinResource.origin assembly name or pack filename
|
||||
RBinResource.paddr payload offset
|
||||
RBinResource.vaddr payload offset for a non-VA file
|
||||
RBinResource.size payload size
|
||||
RBinResource.id UT64_MAX
|
||||
RBinResource.index descriptor index
|
||||
RBinResource.named true
|
||||
```
|
||||
|
||||
This is the highest-value short-term resource implementation for r2unity:
|
||||
the layout is small, deterministic, and immediately enables `iU`, `iUj`, and
|
||||
`iUx`.
|
||||
|
||||
## 7. Standard .NET `.resources`
|
||||
|
||||
### 7.1 Relationship to assemblies and IL2CPP packs
|
||||
|
||||
A `.resources` file is a compiled .NET name/value resource dictionary. It can
|
||||
be standalone or embedded as one manifest resource in an assembly. Under
|
||||
IL2CPP, the same manifest resource can become one payload in an assembly's
|
||||
`*.dll-resources.dat` file.
|
||||
|
||||
The layers must not be conflated:
|
||||
|
||||
```text
|
||||
System.Example.dll-resources.dat IL2CPP outer pack
|
||||
└── Company.Product.Strings.resources
|
||||
├── MainWindow.Title .NET dictionary entry
|
||||
├── Error.Network .NET dictionary entry
|
||||
└── Logo .NET dictionary entry
|
||||
```
|
||||
|
||||
### 7.2 Header and tables
|
||||
|
||||
Standard binary `.resources` files begin with the little-endian magic value:
|
||||
|
||||
```text
|
||||
0xBEEFCACE
|
||||
```
|
||||
|
||||
The ResourceManager header contains:
|
||||
|
||||
1. magic number
|
||||
2. ResourceManager header version
|
||||
3. number of bytes to skip past the remaining ResourceManager header
|
||||
4. length-prefixed resource-reader type name
|
||||
5. length-prefixed ResourceSet type name
|
||||
|
||||
The ResourceReader data that follows contains versioned information including:
|
||||
|
||||
- resource count
|
||||
- serialized/user type count and names
|
||||
- aligned name-hash table
|
||||
- name-position table
|
||||
- data-section offset
|
||||
- name section
|
||||
- typed value records in the data section
|
||||
|
||||
Resource names are used as dictionary keys. Value records carry a type code
|
||||
and can represent strings, byte arrays, streams, primitive values, or
|
||||
user-defined serialized types. A safe reverse-engineering parser should
|
||||
enumerate and extract raw value data without instantiating arbitrary managed
|
||||
types.
|
||||
|
||||
References:
|
||||
|
||||
- [Microsoft: create resource files](https://learn.microsoft.com/en-us/dotnet/core/extensions/create-resource-files)
|
||||
- [Microsoft: ResourceReader](https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-resources-resourcereader)
|
||||
- [Microsoft reference source: ResourceManager header](https://github.com/microsoft/referencesource/blob/master/mscorlib/system/resources/resourcemanager.cs)
|
||||
|
||||
### 7.3 Current radare2 behavior
|
||||
|
||||
radare2's PE parser handles two resource families:
|
||||
|
||||
- native PE resource-directory entries
|
||||
- embedded CLR `ManifestResource` rows whose implementation field indicates
|
||||
that the payload resides in the current assembly
|
||||
|
||||
For a managed resource, r2 reads the CLR resource-directory length prefix and
|
||||
creates one `RBinResource` for the complete manifest payload. It does not
|
||||
currently inspect the payload for `0xBEEFCACE` or enumerate the inner
|
||||
`.resources` dictionary.
|
||||
|
||||
A standalone `.resources` bin plugin belongs in radare2 rather than r2unity
|
||||
because the format is generic .NET. It should also be usable recursively on
|
||||
payloads obtained from PE or IL2CPP resource containers.
|
||||
|
||||
## 8. Win32 `.res`
|
||||
|
||||
A literal `.res` file usually means the binary output of the Windows resource
|
||||
compiler. It is unrelated to Unity `.resS` and .NET `.resources`.
|
||||
|
||||
A compiled `.res` file contains concatenated, DWORD-aligned entries. Each
|
||||
entry declares its header and data sizes and describes:
|
||||
|
||||
- resource type, either numeric or named
|
||||
- resource name/identifier
|
||||
- data version
|
||||
- memory flags
|
||||
- language
|
||||
- resource version
|
||||
- characteristics
|
||||
- type-specific payload
|
||||
|
||||
The linker transforms these entries into the resource directory embedded in
|
||||
a PE executable or DLL. radare2 already enumerates the linked PE resource
|
||||
tree, but no standalone `.res` parser was found in the current source.
|
||||
|
||||
Reference:
|
||||
|
||||
- [Microsoft: Resource File Formats](https://learn.microsoft.com/en-us/windows/win32/menurc/resource-file-formats)
|
||||
|
||||
If a game ships a file with a lowercase `.res` suffix on a non-Windows
|
||||
platform, the suffix alone is not enough to identify it. Inspect its magic,
|
||||
path, and reader code before treating it as Win32 data.
|
||||
|
||||
## 9. `.dat` files
|
||||
|
||||
`.dat` has no common file format. Several unrelated files in one Unity build
|
||||
can use it.
|
||||
|
||||
### 9.1 `global-metadata.dat`
|
||||
|
||||
`global-metadata.dat` is IL2CPP's managed metadata database. Its sanity value
|
||||
is `0xFAB11BAF` and the next word identifies the metadata wire version.
|
||||
|
||||
It stores such information as:
|
||||
|
||||
- assemblies and images
|
||||
- types, methods, fields, properties, and events
|
||||
- generic metadata
|
||||
- custom attributes and default-value blobs
|
||||
- identifier strings
|
||||
- managed `ldstr` literal data
|
||||
|
||||
It does not contain Unity textures, scenes, prefabs, audio, or AssetBundle
|
||||
object databases. See `doc/datvsbin.md` and `doc/strings.md` for the detailed
|
||||
r2unity coverage.
|
||||
|
||||
### 9.2 `*.dll-resources.dat`
|
||||
|
||||
These files use the IL2CPP manifest-resource pack described in section 6.
|
||||
They are identified by their path, filename, and validated record layout, not
|
||||
by the generic `.dat` suffix.
|
||||
|
||||
### 9.3 Catalogs, caches, and application files
|
||||
|
||||
Packages or games can assign `.dat` to custom catalogs, caches, databases,
|
||||
network content, or obfuscated/encrypted files. The only reliable definition
|
||||
is the producing/consuming code.
|
||||
|
||||
### 9.4 Saved games
|
||||
|
||||
Unity does not define a universal binary save format. A developer can write
|
||||
any bytes under `Application.persistentDataPath`, including:
|
||||
|
||||
- JSON or XML
|
||||
- a custom BinaryReader/BinaryWriter layout
|
||||
- SQLite
|
||||
- protocol buffers or MessagePack
|
||||
- compressed data
|
||||
- encrypted or authenticated data
|
||||
- a mixture of versioned headers and application-specific records
|
||||
|
||||
The `.dat` extension does not distinguish any of these.
|
||||
|
||||
## 10. Saved state, PlayerPrefs, and StreamingAssets
|
||||
|
||||
### 10.1 `Application.persistentDataPath`
|
||||
|
||||
Unity exposes `Application.persistentDataPath` as a per-application directory
|
||||
for data retained between runs. Platform paths differ, but filenames and
|
||||
formats inside the directory are chosen by the game.
|
||||
|
||||
Reference:
|
||||
|
||||
- [Unity: Application.persistentDataPath](https://docs.unity3d.com/kr/current/ScriptReference/Application-persistentDataPath.html)
|
||||
|
||||
An r2unity save-analysis workflow should therefore:
|
||||
|
||||
1. identify the exact file path and platform
|
||||
2. inspect magic, entropy, strings, and compression
|
||||
3. search IL2CPP metadata for likely save/model/serializer types
|
||||
4. locate file and serialization calls in the native IL2CPP binary
|
||||
5. reconstruct a schema specific to that game and version
|
||||
|
||||
r2unity can provide triage and schema-recovery assistance, but cannot provide
|
||||
one parser for every `save.dat`.
|
||||
|
||||
### 10.2 PlayerPrefs
|
||||
|
||||
`PlayerPrefs` stores string, integer, and floating-point preferences through
|
||||
platform-native storage. Current Unity documentation lists, among others:
|
||||
|
||||
- Android SharedPreferences XML
|
||||
- iOS `NSUserDefaults`
|
||||
- macOS plist files
|
||||
- the Windows registry
|
||||
- Linux configuration paths
|
||||
- UWP `playerprefs.dat`
|
||||
|
||||
PlayerPrefs is not encrypted and should not be confused with arbitrary files
|
||||
under `persistentDataPath`.
|
||||
|
||||
Reference:
|
||||
|
||||
- [Unity: PlayerPrefs](https://docs.unity3d.com/ja/current/ScriptReference/PlayerPrefs.html)
|
||||
|
||||
### 10.3 StreamingAssets
|
||||
|
||||
Files placed in a project's `StreamingAssets` directory are generally copied
|
||||
into the build as files rather than converted into `resources.assets`.
|
||||
Applications commonly use this area for databases, media, configuration, or
|
||||
custom packs. These are shipped inputs and are often read-only on installed
|
||||
platforms; writable state belongs under `persistentDataPath`.
|
||||
|
||||
A `.dat` found in StreamingAssets is therefore likely application-supplied
|
||||
content, not automatically a saved game.
|
||||
|
||||
## 11. Addressables
|
||||
|
||||
Addressables add a lookup layer above AssetBundles. A content catalog maps
|
||||
keys and labels to physical locations, provider information, dependencies,
|
||||
and bundle entries.
|
||||
|
||||
Common artifacts include:
|
||||
|
||||
- `catalog.json` or a binary/compressed local catalog
|
||||
- `catalog.bin` in some package/build configurations
|
||||
- `catalog.hash`
|
||||
- platform directories containing `*.bundle`
|
||||
|
||||
The hash determines whether a remote catalog differs from the local catalog.
|
||||
The catalog does not replace AssetBundle or SerializedFile parsing; it tells
|
||||
the loader which bundle and provider contain a requested address.
|
||||
|
||||
Reference:
|
||||
|
||||
- [Unity Addressables content catalogs](https://docs.unity3d.com/Packages/com.unity.addressables%402.7/manual/build-content-catalogs.html)
|
||||
|
||||
A complete r2unity implementation should parse catalogs only after UnityFS
|
||||
and SerializedFile support exists, then use them to build a deployment graph:
|
||||
|
||||
```text
|
||||
address / label
|
||||
→ catalog location
|
||||
→ bundle and dependencies
|
||||
→ UnityFS member
|
||||
→ SerializedFile object
|
||||
→ optional .resS/.resource slice
|
||||
```
|
||||
|
||||
## 12. Quick identification guide
|
||||
|
||||
| Leading bytes or evidence | Likely format |
|
||||
| --- | --- |
|
||||
| `UnityFS\0` | Modern Unity archive / AssetBundle |
|
||||
| sanity `0xFAB11BAF` (`af 1b b1 fa` on little-endian disk) plus plausible version | `global-metadata.dat` |
|
||||
| `0xBEEFCACE` (`ce ca ef be` on disk) | Standard .NET `.resources` |
|
||||
| `FSB5` | FMOD sound bank, sometimes inside a Unity `.resource` sidecar |
|
||||
| valid IL2CPP record area plus `*.dll-resources.dat` path/name | IL2CPP manifest-resource pack |
|
||||
| plausible versioned header and Unity version string, but no textual magic | Unity SerializedFile |
|
||||
| `PK\x03\x04`, gzip, SQLite, JSON, XML, etc. | Inner/common format; especially relevant for custom saves and StreamingAssets |
|
||||
| no magic and high entropy | Possibly compressed, encrypted, packed, or simply raw media data |
|
||||
|
||||
Identification must always validate lengths and offsets. Magic matching alone
|
||||
is insufficient for hostile or corrupted input.
|
||||
|
||||
## 13. radare2 resource API
|
||||
|
||||
### 13.1 Data model
|
||||
|
||||
Current radare2 exposes binary resources with `RBinResource`, whose useful
|
||||
members include:
|
||||
|
||||
- `name`
|
||||
- `type`
|
||||
- `encoding`
|
||||
- `language`
|
||||
- `timestamp`
|
||||
- `origin`
|
||||
- `vaddr`, `paddr`, and `size`
|
||||
- numeric and textual identifiers
|
||||
- resource index, type ID, language ID, and code page
|
||||
|
||||
A bin plugin implements `load_resources(RBinFile *bf)` and appends entries to
|
||||
`bf->bo->resources_vec`. Loading is lazy and cached. The generic extractor
|
||||
validates that an inline range fits within the current file.
|
||||
|
||||
Supported generic `encoding` transformations currently include raw data,
|
||||
base64, data URIs, gzip/zlib, LZ4, and UTF-16 variants. `encoding` describes
|
||||
the bytes of one resource; it must not be used as a substitute for mounting a
|
||||
compressed container such as UnityFS.
|
||||
|
||||
### 13.2 Commands
|
||||
|
||||
radare2 commands are:
|
||||
|
||||
```text
|
||||
iU list resources and metadata
|
||||
iU, table output/query
|
||||
iU* emit resource flags as r2 commands
|
||||
iUj JSON output
|
||||
iUq address, size, type, and name
|
||||
iUqq names only
|
||||
iUx [directory] extract resources
|
||||
ixU [directory] extraction alias
|
||||
```
|
||||
|
||||
`rabin2` provides:
|
||||
|
||||
```text
|
||||
rabin2 -U file
|
||||
rabin2 -jU file
|
||||
rabin2 -xU [-o directory] file
|
||||
```
|
||||
|
||||
Extraction sanitizes generated filenames and refuses to overwrite existing
|
||||
files.
|
||||
|
||||
### 13.3 r2hermes precedent
|
||||
|
||||
The r2hermes HBC plugin demonstrates how a non-PE plugin can use the API. It
|
||||
examines HBC string data for complete SVG, HTML, XML, PEM, and data-URI
|
||||
payloads, then records the underlying byte range as an `RBinResource` with a
|
||||
MIME type and origin.
|
||||
|
||||
The same conservative classifier can be reused for complete documents or
|
||||
data URIs in r2unity's managed `ldstr` literals. Ordinary strings should stay
|
||||
in the string API and must not all become resources.
|
||||
|
||||
## 14. Current r2unity and radare2 coverage
|
||||
|
||||
### 14.1 r2unity today
|
||||
|
||||
The current `bin_r2unity` plugin recognizes only the IL2CPP
|
||||
`global-metadata.dat` sanity value and version. It exposes metadata sections,
|
||||
strings, symbols, imports, classes, fields, and header information. It has no
|
||||
`load_resources` callback.
|
||||
|
||||
Companion discovery currently locates:
|
||||
|
||||
- the main executable/input
|
||||
- the IL2CPP native binary
|
||||
- `global-metadata.dat`
|
||||
- the Player Data directory
|
||||
|
||||
It does not enumerate `Managed/Resources`, loose SerializedFiles, UnityFS
|
||||
archives, sidecars, or Addressables content.
|
||||
|
||||
The existing documentation correctly states that Unity asset, scene, prefab,
|
||||
and AssetBundle data is not stored in `global-metadata.dat`.
|
||||
|
||||
### 14.2 radare2 today
|
||||
|
||||
radare2 provides the generic resource API and commands, and currently exposes:
|
||||
|
||||
- native PE resource-directory entries
|
||||
- embedded CLR ManifestResource payloads
|
||||
- resources from plugins that implement `load_resources`, such as the
|
||||
Pebble resource-pack plugin and r2hermes when installed
|
||||
|
||||
No current parser was found for:
|
||||
|
||||
- UnityFS
|
||||
- Unity SerializedFiles
|
||||
- Unity `.resS` or `.resource` sidecars
|
||||
- IL2CPP `*.dll-resources.dat`
|
||||
- nested/standalone .NET `.resources` entries
|
||||
- standalone Windows `.res`
|
||||
|
||||
Running the current `rabin2 -U` on the available
|
||||
`mscorlib.dll-resources.dat` and `resources.assets` fixtures produces no
|
||||
resource entries, confirming that the gap is parser implementation rather
|
||||
than command wiring.
|
||||
|
||||
## 15. Implementation roadmap
|
||||
|
||||
### Stage 0: fixtures and format boundaries
|
||||
|
||||
- preserve small valid fixtures for each format and version
|
||||
- include malformed/truncated cases
|
||||
- document which fixtures are distributable
|
||||
- distinguish container, object database, sidecar, and inner payload APIs
|
||||
- add file-identification tests before enabling weak/no-magic detection
|
||||
|
||||
### Stage 1: IL2CPP manifest-resource packs in r2unity
|
||||
|
||||
Add a dedicated library parser and bin plugin for
|
||||
`*.dll-resources.dat`:
|
||||
|
||||
- parse and validate descriptors
|
||||
- expose records through `RBinResource`
|
||||
- detect common payload types from extension and magic
|
||||
- set the assembly/pack as `origin`
|
||||
- support `iU`, JSON, flags, and safe extraction without new commands
|
||||
- add unit and integration tests for the System.Data, System.Drawing, and
|
||||
mscorlib fixtures
|
||||
|
||||
This should be a separate plugin from `bin_r2unity` because the format has a
|
||||
different detector and object lifetime from `global-metadata.dat`.
|
||||
|
||||
### Stage 2: generic .NET `.resources` in radare2
|
||||
|
||||
Add a standalone parser that:
|
||||
|
||||
- validates `0xBEEFCACE` and all table offsets
|
||||
- enumerates dictionary keys and value records
|
||||
- identifies built-in primitive, string, byte-array, and stream types
|
||||
- exposes raw values safely without deserializing arbitrary objects
|
||||
- works when opened directly
|
||||
- can be invoked recursively on a PE or IL2CPP manifest-resource payload
|
||||
|
||||
This belongs in radare2 because it benefits every managed binary format.
|
||||
|
||||
### Stage 3: conservative resources from metadata literals
|
||||
|
||||
Optionally add `load_resources` to the existing `bin_r2unity` plugin and
|
||||
reuse the r2hermes classifier for:
|
||||
|
||||
- complete XML documents
|
||||
- SVG
|
||||
- HTML
|
||||
- PEM blocks
|
||||
- data URIs
|
||||
|
||||
This is heuristic convenience, not Unity asset parsing. All ordinary `ldstr`
|
||||
literals remain strings.
|
||||
|
||||
### Stage 4: Unity SerializedFile library and bin plugin
|
||||
|
||||
Implement versioned parsing for:
|
||||
|
||||
- headers and endianness
|
||||
- metadata and type trees
|
||||
- serialized types and class IDs
|
||||
- object table and path IDs
|
||||
- script types
|
||||
- external references
|
||||
- reference types and newer metadata additions
|
||||
- object names and common built-in object layouts
|
||||
|
||||
Expose structure through sections, fields, symbols, classes, and resources.
|
||||
Start with loose current-version `.assets` fixtures, then expand across Unity
|
||||
versions.
|
||||
|
||||
### Stage 5: external sidecar resource access in radare2
|
||||
|
||||
Add a plugin-level resource-data resolver or equivalent abstraction so that a
|
||||
resource owned by one `RBinFile` can return bytes from a companion buffer.
|
||||
|
||||
Requirements include:
|
||||
|
||||
- safe path resolution relative to the deployment/archive
|
||||
- retaining or reopening companion buffers safely
|
||||
- offset/size bounds checks against the sidecar
|
||||
- showing the real origin in `iU`/JSON
|
||||
- extraction without changing the current seek/file
|
||||
|
||||
### Stage 6: `.resS` and `.resource` resolution in r2unity
|
||||
|
||||
Decode streaming references for common classes and map them to sidecars:
|
||||
|
||||
- Texture2D
|
||||
- Mesh
|
||||
- AudioClip
|
||||
- VideoClip
|
||||
- other version-specific streamed data objects
|
||||
|
||||
Initially expose raw slices. Codec-specific plugins such as FSB5 can be added
|
||||
independently or invoked on extracted buffers.
|
||||
|
||||
### Stage 7: UnityFS extraction or mounting
|
||||
|
||||
Implement:
|
||||
|
||||
- `UnityFS` validation
|
||||
- block-info placement and flags
|
||||
- uncompressed blocks
|
||||
- LZ4/LZ4HC blocks
|
||||
- LZMA content
|
||||
- directory nodes
|
||||
- members spanning block boundaries
|
||||
- recursive opening of SerializedFiles
|
||||
|
||||
An extractor and a filesystem view are both useful, but one shared archive
|
||||
library should implement parsing and decompression.
|
||||
|
||||
### Stage 8: AssetBundle and Addressables semantics
|
||||
|
||||
- decode AssetBundle container objects
|
||||
- map asset names to path IDs
|
||||
- follow external SerializedFile and bundle dependencies
|
||||
- parse JSON and binary/compressed catalogs
|
||||
- map addresses and labels to bundle members and Unity objects
|
||||
- expose a cross-file object/resource graph
|
||||
|
||||
### Stage 9: standalone Win32 `.res` in radare2
|
||||
|
||||
Reuse PE resource types and naming where possible, but parse the compiler's
|
||||
concatenated `.res` representation directly. This is generic Windows support,
|
||||
not Unity-specific work.
|
||||
|
||||
### Stage 10: save-data triage
|
||||
|
||||
Add discovery and analysis helpers rather than claiming a universal save
|
||||
format:
|
||||
|
||||
- identify PlayerPrefs and persistent-data locations
|
||||
- recognize common inner formats and compression
|
||||
- find likely save and serializer types in IL2CPP metadata
|
||||
- correlate file I/O and serializer calls with native methods
|
||||
- let per-game plugins/scripts describe recovered schemas
|
||||
|
||||
## 16. Parser safety requirements
|
||||
|
||||
All these formats are attacker-controlled inputs from radare2's perspective.
|
||||
Every implementation should:
|
||||
|
||||
- perform checked arithmetic before adding offsets and sizes
|
||||
- cap counts, strings, type-tree depth, and decompressed sizes
|
||||
- validate every range before reading or slicing
|
||||
- reject overlapping or contradictory regions unless specified
|
||||
- avoid native object deserialization for `.resources` and saves
|
||||
- protect recursive container traversal from cycles
|
||||
- protect dependency traversal from path escape
|
||||
- treat compressed-size and uncompressed-size fields as untrusted
|
||||
- include truncated and oversized fuzz cases
|
||||
- avoid detector false positives for no-magic formats
|
||||
|
||||
UnityFS, SerializedFile, and `.resources` parsers should be fuzzed separately
|
||||
from higher-level object decoders so that structural bugs are isolated from
|
||||
class-schema bugs.
|
||||
|
||||
## 17. Recommended ownership split
|
||||
|
||||
The clean project boundary is:
|
||||
|
||||
### r2unity
|
||||
|
||||
- `global-metadata.dat`
|
||||
- `*.dll-resources.dat`
|
||||
- Unity SerializedFile
|
||||
- `.resS` / `.resource` stream resolution
|
||||
- UnityFS and AssetBundle semantics
|
||||
- Addressables lookup
|
||||
- IL2CPP-assisted save-schema recovery
|
||||
|
||||
### radare2
|
||||
|
||||
- generic `RBinResource` commands and extraction
|
||||
- plugin-supplied/external resource-buffer support
|
||||
- standalone and nested .NET `.resources`
|
||||
- standalone Win32 `.res`
|
||||
- reusable LZ4/LZMA primitives and container infrastructure
|
||||
|
||||
Unity-specific parsers can remain out-of-tree in r2unity while maturing. Only
|
||||
generic APIs and formats shared with non-Unity binaries need to enter radare2
|
||||
core.
|
||||
|
||||
## 18. Practical first milestone
|
||||
|
||||
The smallest useful milestone is:
|
||||
|
||||
1. add `bin_r2unity_resources` for `*.dll-resources.dat`
|
||||
2. populate `RBinResource` records with name, origin, offset, size, and type
|
||||
3. test `iU`, `iUj`, and `iUx` on the existing resource-pack fixtures
|
||||
4. add the optional document/data-URI classifier to managed literals
|
||||
5. implement generic `.resources` parsing in radare2 next
|
||||
|
||||
This produces immediate resource visibility without pretending that raw
|
||||
`.resS`, arbitrary `.dat` saves, or full Unity object serialization are the
|
||||
same problem. SerializedFile and UnityFS support can then grow on top of a
|
||||
clear container/resource abstraction.
|
||||
Reference in new issue
Block a user