Asset Base Class and the Asset Registry
The Asset abstract base class is the root of Unturned's entire content system. Every gameplay-defining object — items, vehicles, effects, objects, NPCs, spawn tables, road definitions, weather profiles, and animations — is represented by a class that inherits from Asset. The Assets singleton manages the master registry that maps GUIDs and legacy IDs to their corresponding asset instances, orchestrates the multi-threaded loading pipeline, resolves master bundle references, and provides the lookup API used by every subsystem at runtime.
This article documents the Asset base class fields and methods, the Assets singleton's architecture including the dual mapping system (GUID → asset and legacy ID → asset), the AssetsWorker multi-threaded loading pipeline, the PopulateAsset lifecycle method, the TypeRegistryDictionary that maps string type names to asset classes, the master bundle resolution system (MasterBundleConfig), and the asset error reporting infrastructure.
Source code location: Unturned/Bundles/Asset.cs, Unturned/Bundles/Assets.cs, Unturned/Bundles/AssetsWorker.cs, Unturned/Bundles/AssetReference.cs
The Asset Base Class
Asset is defined in SDG.Unturned and implements IAssetErrorContext. Every asset in the game — regardless of type — carries these fields:
| Field | Type | Set by | Purpose |
|---|---|---|---|
name | string | Constructor / PopulateAsset | Internal asset name |
id | ushort | PopulateAsset | Legacy 16-bit numeric ID |
GUID | Guid | PopulateAsset | 128-bit globally unique identifier |
origin | AssetOrigin | PopulateAsset | Source of the asset (core, workshop, map) |
hash | byte[] | PopulateAsset | SHA1 hash of the asset's input file |
originMasterBundle | MasterBundleConfig | PopulateAsset | Master bundle this asset was loaded from |
absoluteOriginFilePath | string | LoadFile | Full path to the asset's source file |
hasBeenReplaced | bool | AddToMapping | True if another asset with same ID/GUID replaced this one |
hasErrors | bool | PopulateAsset | True if any errors were reported during loading |
requiredShaderUpgrade | bool | LoadFile | True if shaders were converted to Standard |
ignoreNPOT | bool | PopulateAsset | Skip non-power-of-two texture warnings |
ignoreTextureReadable | bool | PopulateAsset | Skip read/write texture warnings |
OriginParsedData | IDatDictionary | When resaving enabled | Original file contents for re-serialization |
Localization | Local | When Keep_Localization_Loaded is true | Translation data for the asset |
Constructor
The base constructor sets name = GetType().Name — meaning if no name override is provided, the asset's name defaults to its C# class name (e.g., "ItemGunAsset"). Subclass constructors can override this.
PopulateAsset
PopulateAsset is the core initialization method. It is called by the Assets loading pipeline after the asset class is instantiated but before it is registered in the mapping. The method receives a PopulateAssetParameters struct:
csharp
public struct PopulateAssetParameters
{
public Bundle bundle; // The bundle containing this asset's prefab
public IDatDictionary data; // Parsed key-value data from the .dat file
public Local localization; // Translation data
public bool CanPerformDataConversions; // True if re-saving is allowed
}The base Asset.PopulateAsset implementation:
- Sets
namefrom the bundle name, or falls back to"Asset_" + id - Reads the
originMasterBundlefrom the bundle if it is aMasterBundle - Reads
Ignore_NPOTandIgnore_TexRWflags from the data dictionary
Subclasses override PopulateAsset to read their type-specific fields. The call chain is:
Asset.PopulateAsset (base)
└─ ItemAsset.PopulateAsset (adds item-specific fields)
└─ ItemWeaponAsset.PopulateAsset (adds weapon fields)
└─ ItemGunAsset.PopulateAsset (adds gun-specific fields)Error Reporting
The IAssetErrorContext interface provides the error reporting contract:
csharp
public interface IAssetErrorContext
{
string AssetErrorPrefix { get; } // Formatted string: "Origin FriendlyName (Type) [GUID]"
void ReportAssetError(string message);
}The AssetErrorPrefix implementation produces strings like: "Vanilla Eaglefire (Gun) [b03d581a5c1a490f995f8deba57b0f17]"
When an error is reported, asset.HasErrors is set to true and the error is logged via UnturnedLog.warn.
AssetReference<T>
The AssetReference<T> struct is a type-safe GUID reference to any asset. It wraps a single Guid and provides Find() which delegates to Assets.find(this).
csharp
public struct AssetReference<T> where T : Asset
{
public Guid GUID;
public bool isValid => GUID != Guid.Empty;
public T Find(); // Resolves via Assets.find
}The struct uses the GUID-based lookup exclusively. It does not store a cached reference, so every Find() call performs a dictionary lookup. When newer code needs to cache the result, CachingAssetRef<T> is recommended instead.
Parse support is provided for both inline GUID values and nested dictionary GUIDs:
In .dat: "MyAsset b03d581a5c1a490f995f8deba57b0f17"
In .dat: "MyAsset {GUID b03d581a5c1a490f995f8deba57b0f17}"The Assets Singleton
Assets is a MonoBehaviour that serves as the global asset manager. It owns the asset mapping dictionaries, manages the loading pipeline, provides lookup methods, and tracks loading progress.
Dual Mapping Architecture
Assets are stored in two parallel data structures within AssetMapping:
csharp
internal class AssetMapping
{
public Dictionary<EAssetType, Dictionary<ushort, Asset>> legacyAssetsTable;
public Dictionary<Guid, Asset> assetDictionary;
public List<Asset> assetList;
}- GUID dictionary (
assetDictionary) — the primary lookup by 128-bit GUID. Every registered asset is stored here. - Legacy ID table (
legacyAssetsTable) — a tiered lookup byEAssetType→ushort. Only assets with a non-zeroidare stored here. Used for backward compatibility with code that references assets by 16-bit numeric ID. - Flat list (
assetList) — an ordered list of all assets, used for iteration.
There are two AssetMapping instances:
| Mapping | Purpose |
|---|---|
defaultAssetMapping | Contains all assets loaded from disk. Used in singleplayer and the level editor. |
currentAssetMapping | In singleplayer, same as defaultAssetMapping. When playing on a server, a subset of assets based on server workshop files. |
The ApplyServerAssetMapping method constructs currentAssetMapping from origins in a specific order: core → level → server workshop files → remaining origins. On dedicated servers, remaining non-core origins are inserted at the front to reduce ID conflict chances.
Type Registry Dictionaries
The Assets.assetTypes and Assets.useableTypes are TypeRegistryDictionary instances that map string names to System.Type:
csharp
Assets.assetTypes.addType("Gun", typeof(ItemGunAsset));
Assets.assetTypes.getType("Gun"); // Returns typeof(ItemGunAsset)The type registry is populated by UnturnedNexus.initialize() and is used by the asset parser (LoadFile method) to determine which class to instantiate when parsing a .dat file:
csharp
string legacyType = data.GetString("Type");
assetType = assetTypes.getType(legacyType);Find Methods
The lookup methods support RedirectorAsset chaining — when a lookup hits a RedirectorAsset, it follows the redirect chain up to 32 hops before giving up:
csharp
public static Asset find(Guid GUID) // Primary GUID lookup
public static T find<T>(Guid guid) // Type-safe GUID lookup
public static T find<T>(AssetReference<T> reference) // Reference wrapper
public static Asset find(EAssetType type, ushort id) // Legacy ID lookup
public static void find<T>(List<T> results) // Filter all assets by typeThe redirectCount limiter at 32 prevents infinite redirect loops from corrupted or malicious asset files.
Master Bundle Resolution
The findMasterBundleByPath method finds the most specific master bundle for a given asset file path:
csharp
public static MasterBundleConfig findMasterBundleByPath(string path)It checks all loaded master bundles and selects the one with the longest matching directory prefix. For example, if two bundles are registered at "Items" and "Items/Guns", an asset at "Items/Guns/Eaglefire/Eaglefire.asset" matches "Items/Guns" because it has a longer path.
Asset files can also override their master bundle with the Master_Bundle_Override field in the .dat, or exclude themselves from master bundle usage with Exclude_From_Master_Bundle.
The AssetsWorker — Multi-Threaded Loading
The AssetsWorker class handles file discovery and parsing on background threads, while the main thread handles type instantiation and asset population.
Worker Architecture
AssetsWorker
│
├── Main thread calls RequestSearch(path, origin)
│ └── Queues a worker thread state
│
├── SearcherThreadMain (ThreadPool thread)
│ └── Recursively scans directories:
│ ├── Looks for MasterBundle.dat
│ ├── Looks for *.asset or *.dat files
│ └── Enqueues ReaderWorkItem for each found file
│
├── ReaderThreadMain (ThreadPool thread, async)
│ └── Dequeues ReaderWorkItem:
│ ├── MasterBundle item:
│ │ ├── Read MasterBundle.dat → parse config
│ │ ├── Read .unity3d bytes + compute SHA1 hash
│ │ └── Enqueue MasterBundle result
│ │
│ └── Asset item:
│ ├── Read .asset/.dat file
│ ├── Parse via DatParser
│ ├── Read localization files (English.dat / language.dat)
│ └── Enqueue AssetDefinition result
│
└── Main thread Update() drains results:
├── MasterBundle result → register bundle, load asynchronously
└── AssetDefinition result → instantiate, PopulateAsset, registerSeacher Thread File Discovery
The searcher thread enumerates directories recursively. For each directory, it calls FindAssets() which checks for asset files in priority order:
<FolderName>.asset— highest priority, single asset per folder<FolderName>.dat— old format, same folder structureAsset.dat— default asset name*.asset— folder with multiple assets
The search is breadth-first (using a Queue<string>). The thread does not sleep while work is available; it loops at maximum speed until the directory tree is fully enumerated.
Reader Thread
The reader thread processes ReaderWorkItem objects asynchronously using await. For each file:
- Master bundles: Reads
MasterBundle.dat(JSON configuration), then reads the.unity3dfile bytes into memory with SHA1 hashing viaSHA1Stream. - Assets: Reads the
.assetor.datfile, parses it withDatParser, reads localization files (Language.dat+ optionallyEnglish.datas fallback), computes the SHA1 hash, and enqueues anAssetDefinition.
Both results are enqueued to the ConcurrentQueue<ResultItem>. The main thread drains this queue in its Update loop.
Progress Tracking
The worker tracks progress through Interlocked counters:
csharp
internal int totalMasterBundlesFound;
internal int totalMasterBundlesRead;
internal int totalAssetDefinitionsFound;
internal int totalAssetDefinitionsRead;
internal int totalSearchLocationRequests;
internal int totalSearchLocationsFinishedSearching;
internal int totalSearchLocationsFinishedReading;These counters feed into AssetLoadingStats which the LoadingUI reads to update progress bars.
The LoadFile Pipeline
The LoadFile static method on Assets processes each AssetDefinition on the main thread. The sequence is:
LoadFile(AssetDefinition file)
│
├─ 1. Validate path length (warn if > 260 chars for Windows MAX_PATH)
├─ 2. Check for parser errors from worker thread
│
├─ 3. Extract GUID and Type from Metadata dictionary
│ (or assign a new GUID if none is present)
│
├─ 4. Fall back to legacy Type field if no metadata
│ (look up in assetTypes registry)
│
├─ 5. Verify type derives from Asset
│
├─ 6. Resolve master bundle by path or override
│
├─ 7. Create Bundle or MasterBundle wrapper
│ (determines shader conversion flags from version)
│
├─ 8. Parse legacy ID and GUID from data
│
├─ 9. Instantiate asset via Activator.CreateInstance(assetType)
│
├─ 10. Set base fields: id, GUID, hash, origin, error flags
│
├─ 11. Call asset.PopulateAsset(parameters)
│
├─ 12. Add to origin's asset list
│
├─ 13. Call AddToMapping(asset, ...) to register in dictionaries
│
└─ 14. Unload the bundleGUID Auto-Assignment
If a .dat file is missing a GUID entirely (not mis-formatted, but absent), the loader generates one:
csharp
if (!rootData.ContainsKey("GUID"))
{
assetGuid = Guid.NewGuid();
// Write GUID back to file for persistence
string text = "GUID " + assetGuid.ToString("N") + Environment.NewLine + text;
File.WriteAllText(assetPath, text);
UnturnedLog.info($"Assigned GUID {assetGuid:N} to asset \"{assetPath}\"");
}This auto-assignment is a convenience for mod authors. Once assigned, the GUID is written back to the file so subsequent loads use the same value.
AssetBundleVersion Constants
The AssetBundleVersion class defines constants for each Unity version's asset bundle format:
| Constant | Value | Unity Version |
|---|---|---|
UNITY_5 | 1 | Unity 5.5 and earlier |
UNITY_2017_LTS | 2 | Unity 2017 LTS |
UNITY_2018_AND_2019_LTS | 3 | Unity 2018/2019 LTS |
UNITY_2020_LTS | 4 | Unity 2020 LTS |
UNITY_2021_LTS | 5 | Unity 2021 LTS |
NEWEST | 6 | Unity 2022 LTS+ |
The version determines shader conversion behavior:
< UNITY_2017_LTS: Convert shaders to Standard< UNITY_2018_AND_2019_LTS: Consolidate shaders (convert to a shared Standard variant)>= UNITY_2018_AND_2019_LTS: No conversion needed
The effective version is the maximum of the master bundle version and the per-asset Asset_Bundle_Version field.
Asset Recording
The asset system can be started with the -ExportAssetsReport flag, which generates a JSON report of all loaded assets. Each asset contributes to Cargo data tables through BuildCargoData. These tables are used by the wiki's Cargo system to auto-generate asset reference pages:
| Table | Purpose |
|---|---|
Asset | Base asset metadata: GUID, ID, filename, master bundle, origin, type |
Clothing | Clothing-specific fields (armor, proof flags, movement speed) |
Bag | Bag-specific fields (width, height for storage) |
Gear | Gear-specific fields (hair, beard visibility) |
Glasses | Glasses-specific fields (vision mode, nightvision color) |
Asset Class Registration Lifecycle
The complete lifecycle of an asset from disk to registry:
1. Worker thread: File discovery
└─ FindAssets() scans directory for .asset/.dat files
2. Worker thread: File parsing
└─ DatParser.Parse() reads key-value pairs from file
3. Worker thread: Localization
└─ Read English.dat and language-specific .dat files
4. Worker thread: Hash computation
└─ SHA1 hash of file content via SHA1Stream
5. Worker thread: Enqueue AssetDefinition
└─ Add to ConcurrentQueue<ResultItem>
6. Main thread: TryDequeueResult
└─ Drain completed results each Update() frame
7. Main thread: LoadFile()
├─ Extract GUID and Type
├─ Resolve master bundle
├─ Activator.CreateInstance(assetType)
├─ Set base fields (id, GUID, hash, origin)
└─ Call asset.PopulateAsset(parameters)
8. Main thread: AddToMapping()
├─ Check for duplicate GUID → error if exists
├─ Check for duplicate legacy ID → error if exists
├─ Register in assetDictionary by GUID
├─ Register in legacyAssetsTable by type + ID
└─ Add to assetList
9. Main thread: Bundle unload
└─ bundle.unload() releases loaded resourcesAssetOrigin System
Each asset has an AssetOrigin that tracks its source:
csharp
internal class AssetOrigin
{
public string name; // Display name for logging
public ulong workshopFileId; // Non-zero for workshop content
public bool canResave; // Allow re-saving for assets in Maps folder
public bool shouldAssetsOverrideExistingIds; // Workshop files override existing
public List<Asset> assets; // Assets loaded from this origin
}Origins are tracked in Assets.assetOrigins (a List<AssetOrigin>). The built-in origins are:
| Origin | Name | workshopFileId | canResave |
|---|---|---|---|
coreOrigin | Core | 0 | false |
reloadOrigin | Reload | 0 | false |
legacyServerSharedOrigin | Legacy Shared | 0 | false |
legacyPerServerOrigin | Legacy Server | 0 | false |
| Workshop origins | Workshop File ({id}) | PublishedFileId | true |
| Map origins | Map "{name}" | 0 (or workshop ID) | true |
The shouldAssetsOverrideExistingIds flag is set to true for workshop origins, which means workshop content replaces existing assets with the same ID. Core origins have this as false, so duplicate IDs in vanilla assets produce an error rather than a silent replacement.
Asset Lookup Resolution with Redirectors
When Assets.find(GUID) is called, the resolver follows RedirectorAsset chains:
csharp
public static Asset find(Guid GUID)
{
Asset resultAsset;
currentAssetMapping.assetDictionary.TryGetValue(GUID, out resultAsset);
int redirectCount = 0;
do
{
if (resultAsset is RedirectorAsset redirectorAsset)
{
currentAssetMapping.assetDictionary.TryGetValue(
redirectorAsset.TargetGuid, out resultAsset);
++redirectCount;
if (redirectCount > 32)
{
resultAsset = null;
break;
}
}
else
{
break;
}
}
while (true);
return resultAsset;
}The 32-hop limit prevents infinite redirect loops from malicious or corrupted asset files. Each hop resolves the current asset's TargetGuid from the dictionary. If a RedirectorAsset points to a GUID that does not exist in the dictionary, resultAsset becomes null and the loop exits.
The find(EAssetType, ushort) overload performs the same redirect chain but starts from the legacy ID table.
MasterBundleConfig Cache
Master bundle configurations are stored in two lists:
csharp
private static List<MasterBundleConfig> allMasterBundles; // Fully loaded
private static List<MasterBundleConfig> pendingMasterBundles; // In loading queueThe allMasterBundles list is used for path-based lookups via findMasterBundleByPath. Bundles are added to this list after their AssetBundle is fully loaded from disk. The pendingMasterBundles list tracks bundles that have been discovered but not yet loaded into memory.
The findMasterBundleByPath method uses the longest-matching-prefix algorithm rather than an exact match, because an asset file at Items/Guns/Eaglefire/Eaglefire.asset should match the bundle configured at Items/Guns even though the asset is two subdirectories deep. The method checks that the character immediately after the bundle directory path is a path separator, preventing false matches on same-prefix directory names (e.g., Items_Guns should not match Items).
Re-Saving and Data Conversions
The asset system supports automatic re-saving of .dat files when started with the appropriate command-line flags:
| Flag | Effect | Required for |
|---|---|---|
-ParseAssetMetadata | Enable metadata parsing (line numbers, comments) | Re-saving, error context |
-ResaveAssets | Re-save all assets after loading | Automatic field upgrades |
When re-saving is enabled, asset.OriginParsedData is populated with the original IDatDictionary, allowing BuildCargoData and PreResaveAsset to compare original vs. modified values. Modifications are only saved if the asset has no errors — the check asset.HasErrors == false prevents data loss from partially-loaded assets.
The CanPerformDataConversions field in PopulateAssetParameters is set to true only when all three conditions are met: -ResaveAssets flag is set, the asset's origin allows re-saving (origin.canResave == true), and -ParseAssetMetadata is set.
Appendix A: Asset System Command-Line Flags
| Flag | Type | Default | Purpose |
|---|---|---|---|
-SkipAssets | bool flag | Not set | Skip all asset loading (quick dev launches) |
-NoDeferAssets | bool flag | Not set | Disable deferred asset loading |
-ValidateAssets | bool flag | Not set | Enable extra validation during loading |
-ParseAssetMetadata | bool flag | Not set | Parse line numbers and comments in .dat files |
-ResaveAssets | bool flag | Not set | Re-save all .dat files after loading |
-AggressiveGC | bool flag | Not set | GC and clean unused assets every loading frame |
-LogWorkshopAssets | bool flag | Not set | Log workshop asset names and IDs during loading |
-LogSpawnInsertions | bool flag | Not set | Log spawn table root insertions |
-ExportAssetsReport | bool flag | Not set | Export JSON report of all assets (editor only) |
-AlwaysLoadItemPrefab | bool flag | Not set | Load item prefabs on dedicated server |
-LoadCoreAssetBundleFromSteamInstall | bool flag | Not set | Load core bundle from Steam install (dev) |
Appendix B: AssetMapping Structure
AssetMapping
├── legacyAssetsTable: Dictionary<EAssetType, Dictionary<ushort, Asset>>
│ ├── EAssetType.ITEM → Dictionary<ushort, ItemAsset>
│ ├── EAssetType.EFFECT → Dictionary<ushort, EffectAsset>
│ ├── EAssetType.OBJECT → Dictionary<ushort, ObjectAsset>
│ ├── EAssetType.RESOURCE → Dictionary<ushort, ResourceAsset>
│ ├── EAssetType.VEHICLE → Dictionary<ushort, VehicleAsset>
│ ├── EAssetType.ANIMAL → Dictionary<ushort, AnimalAsset>
│ ├── EAssetType.MYTHIC → Dictionary<ushort, MythicAsset>
│ ├── EAssetType.SKIN → Dictionary<ushort, SkinAsset>
│ ├── EAssetType.SPAWN → Dictionary<ushort, SpawnAsset>
│ └── EAssetType.NPC → Dictionary<ushort, NPCAsset>
│
├── assetDictionary: Dictionary<Guid, Asset>
│ └── All assets of all types, keyed by GUID
│
├── assetList: List<Asset>
│ └── All assets in registration order
│
└── modificationCounter: int
└── Incremented on every add/removeAppendix C: Error Message Format
Error messages reported by Assets.ReportError use the format:
{OriginName} {FriendlyName} ({TypeFriendlyName}) [{GUID:N}]: {message}Example outputs:
"Vanilla Eaglefire (Gun) [b03d581a5c1a490f995f8deba57b0f17]: missing 'Barrel' GameObject""Workshop File (12345) MyCustomItem (Hat) [c4d5e6f7081a4b9c2d3e4f5a6b7c8d9e]: needs a non-zero ID""Map 'PEI' Item_3001 (Supply) [a1b2c3d4e5f64a7b8c9d0e1f2a3b4c5d]: legacy ID 3001 already taken by Eaglefire (Gun) in Vanilla!"
The AssetErrorPrefix property constructs this string from the asset's origin name, friendly name, type name (with "Asset" suffix removed and in title case), and GUID in hex format.
Asset Origin Resolution During Level Loading
When a level is loaded, Assets.ApplyServerAssetMapping constructs the per-server currentAssetMapping. The mapping includes origins for the specific level being loaded:
csharp
AssetOrigin levelAssetOrigin = null;
if (pendingLevel != null)
{
levelAssetOrigin = FindLevelOrigin(pendingLevel);
if (levelAssetOrigin != null)
{
originsToAdd.Add(levelAssetOrigin);
}
}The FindLevelOrigin method checks whether the level is a workshop item (by checking publishedFileId) or a local map. Workshop levels use FindWorkshopFileOrigin; local maps create a new origin with name Map "{name}" and canResave = true.
If the level asset origin matches a workshop file origin that was already added, it is skipped to prevent duplication. This happens when a workshop file contains both assets and a map — the same origin handles both.
Asset Hot-Reloading
The Assets.reload(string absolutePath) method triggers a partial reload of a specific directory:
csharp
public static void reload(string absolutePath)
{
if (hasFinishedInitialStartupLoading && !isLoading)
{
loadingStats.Reset();
RequestAddSearchLocation(absolutePath, reloadOrigin);
}
}The reloadOrigin is a special origin with shouldAssetsOverrideExistingIds = true. When assets are loaded from this origin, they replace any existing asset with the same GUID or legacy ID in the mapping. This allows workshop content updates to replace assets without restarting the game.
The reloadOrigin is cleared after each reload operation — it is a transient origin that does not persist across reloads.
Asset Error Reporting During Loading
Errors encountered during asset loading are accumulated in a global error list:
csharp
private static List<string> errors;
public static void reportError(string error)
{
errors.Add(error);
UnturnedLog.warn(error);
}
public static List<string> getReportedErrorsList()
{
return errors;
}The error list is consumed by the loading UI and server startup scripts. Each error is formatted with the asset's error prefix (origin name, friendly name, type, GUID) for easy identification.
The ReportError overloads on Assets accept an IAssetErrorContext (typically the Asset itself):
csharp
public static void ReportError(IAssetErrorContext context, string error)
{
if (context is Asset asset)
{
asset.HasErrors = true;
}
reportError($"{context.AssetErrorPrefix}: {error}");
}Setting asset.HasErrors = true prevents the asset from being re-saved (avoiding data loss). Modders can check this flag at runtime to determine whether an asset loaded correctly.
TypeRegistryDictionary Implementation Details
The TypeRegistryDictionary class provides the string-to-type mapping used by the asset parser:
csharp
public class TypeRegistryDictionary
{
private Dictionary<string, Type> typeDict;
public void addType(string name, Type type);
public Type getType(string name);
public void removeType(string name);
}The dictionary is populated in UnturnedNexus.initialize() with entries like:
csharp
Assets.assetTypes.addType("Gun", typeof(ItemGunAsset));The addType method stores the mapping in an internal Dictionary<string, Type>. The getType method performs a case-sensitive lookup. If the string is not found, it attempts to parse the string as a full C# type name (for the "v2" metadata format). The removeType method removes the entry (used during module shutdown).
Spawn Table Linking
The linkSpawns() method processes SpawnAsset root insertions after all assets are loaded:
csharp
public static void linkSpawns()
{
if (hasUnlinkedSpawns)
hasUnlinkedSpawns = false;
else
return;
List<SpawnAsset> spawnAssetList = new List<SpawnAsset>();
FindAssetsByType_UseDefaultAssetMapping(spawnAssetList);
// Process insertRoots for each SpawnAsset
}The spawn linking process:
- Collects all
SpawnAssetinstances - For each asset's
insertRoots, finds the parent spawn table by GUID or legacy ID - Inserts the asset as a child of the parent table
- Sets
isLink = trueto mark the inserted entry - Marks the parent's tables as dirty for re-evaluation
The hasUnlinkedSpawns flag is set to true when a new SpawnAsset with EAssetType.SPAWN is added to the mapping. This triggers the linking process on the next call to linkSpawns().
Asset Integrity Verification
The ClientAssetIntegrity class provides a hash-based verification system for multiplayer. When a client connects to a server, the server can compare asset hashes against the client's hashes to detect modified assets.
Each asset stores a SHA1 hash of its input file:
csharp
public byte[] hash;
public void appendHash(byte[] otherHash)
{
hash = Hash.combineSHA1Hashes(hash, otherHash);
}The hash is computed from the source file content (including metadata if present) and incorporates the bundle-relative path to prevent path-swapping attacks (public issue #4279).
Document history
| Version | Date | Author | Notes |
|---|---|---|---|
| 1.0 | 2026-07-27 | 57 Studios | Initial publication. Asset base class, dual mapping, worker pipeline, type registry, master bundle resolution. |
