Resource system: Difference between revisions
imported>Leoetlino |
imported>Leoetlino No edit summary |
||
(17 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
In ''Breath of the Wild'', the '''resource system''' is a subsystem that is responsible for managing resources, i.e. content files. It is composed of [[ResourceMgrTask]], [[ResourceSystem]] and a few ancillary classes (such as "resource binders" and "handles"). | In ''Breath of the Wild'', the '''resource system''' is a subsystem that is responsible for managing resources, i.e. content files. It is composed of [[ResourceMgrTask]], [[ResourceSystem]] and a few ancillary classes (such as "resource binders" and "handles"). | ||
BotW's resource system is also used in ''[[wikipedia:Animal Crossing: New Horizons|Animal Crossing: New Horizons]]'' and called "ares" or "ARes". | |||
== Concepts == | == Concepts == | ||
=== Resource === | === Resource === | ||
Resources (in the context of the codebase) are C++ classes that are responsible for parsing raw data from content files and storing them in a more convenient data structure. | Resources (in the context of the codebase) are C++ classes that are responsible for parsing raw data from content files and storing them in a more convenient data structure. | ||
Line 7: | Line 10: | ||
In general, there is one resource class per file type, not format. As an example, [[bgparamlist]] and [[bxml]] do not share the same class even though both are [[AAMP]] files. | In general, there is one resource class per file type, not format. As an example, [[bgparamlist]] and [[bxml]] do not share the same class even though both are [[AAMP]] files. | ||
In some cases, the resource is nothing more than a thin wrapper over the underlying file bytes. This is the case for [[BYML]] | In some cases, the resource is nothing more than a thin wrapper over the underlying file bytes. This is the case for [[BYML|BYMLs]] and other simple resource types that don't require any additional memory allocation. Such a wrapper is (unofficially) called ResourceBase. | ||
=== Resource factory === | === Resource factory === | ||
Line 20: | Line 23: | ||
Note that: | Note that: | ||
* For ResourceLoadArg3 (used in model/bfres related code), the factory is hardcoded to be the ResourceBase factory. | * For ResourceLoadArg3 (used in model/bfres related code), the factory is hardcoded to be the ResourceBase factory. | ||
* For ResourceLoadArg2 (used for actor resources and physics stuff) and ResourceLoadArg (everything else), the factory is determined from the file extension. | * For ResourceLoadArg2 (used for actor resources and physics stuff) and ResourceLoadArg (everything else), the factory is determined from the file extension. | ||
Line 25: | Line 29: | ||
== Special cases == | == Special cases == | ||
=== Compressed files === | === Compressed files === | ||
Compressed files can be automatically decompressed and loaded if their extension starts with an 's'. The 's' prefix indicates that a file is [[Yaz0]] compressed. | Compressed files can be automatically decompressed and loaded if their extension starts with an 's'. The 's' prefix indicates that a file is [[Yaz0]] compressed. | ||
Line 30: | Line 35: | ||
When a resource load is requested, the resource system will in most cases automatically prepend 's' to the extension and use <code>sead::ResourceMgr::tryLoadWithDecomp</code> to try loading a compressed version first. This means that any code that interacts with the resource loading functions '''must''' not include the prefix in resource paths. | When a resource load is requested, the resource system will in most cases automatically prepend 's' to the extension and use <code>sead::ResourceMgr::tryLoadWithDecomp</code> to try loading a compressed version first. This means that any code that interacts with the resource loading functions '''must''' not include the prefix in resource paths. | ||
Exceptions: bfevfl, bcamanim and barslist are always loaded uncompressed | Exceptions: bfevfl, bcamanim and barslist are always loaded uncompressed<ref>See 0x710120A9F4 in Switch 1.5.0</ref>. | ||
'''Warning''': This behaviour can be overridden by passing some flags to the resource load functions. | |||
=== Archives === | === Archives === | ||
The resource system can be configured to try loading from an [[SARC]] archive first. | The resource system can be configured to try loading from an [[SARC]] archive first. | ||
To load from an archive, the global resource pack pointer (<code>res::ResourceMgrTask::sInstance->packRes</code> @ this+0x9c06f0 in Switch 1.5.0) | To load from an archive, a pointer to the archive resource handle should be set in <code>ResourceLoadArg::packRes</code> @ 0x58. If it is null, and if ResourceMgrTask's flag 2 is not set<ref>0x71012087EC</ref>, packRes will automatically be set to the global resource pack pointer (<code>res::ResourceMgrTask::sInstance->packRes</code> @ this+0x9c06f0 in Switch 1.5.0). Files in the archive can then be accessed as if they were at the root of the romfs/content partition. For example, if <code>EventFlow/Test.bfevfl</code> is a file in <code>Pack/TitleBG.pack</code>, the path that is for loading is <code>EventFlow/Test.bfevfl</code>, not <code>Pack/TitleBG.pack/EventFlow/Test.bfevfl</code>. | ||
Calls to any of the resource loading functions will automatically check whether the specified resource exists in the archive. If it does exist, it will be loaded from the archive. If the file cannot be found in the archive, the game will ignore the active resource pack and load from the usual file device. | Calls to any of the resource loading functions will automatically check whether the specified resource exists in the archive. If it does exist, it will be loaded from the archive. If the file cannot be found in the archive, the game will ignore the active resource pack and load from the usual file device. | ||
Line 42: | Line 49: | ||
In order to load add-on content, the file device '''must''' be set to the AoC file device when calling a resource loading function. | In order to load add-on content, the file device '''must''' be set to the AoC file device when calling a resource loading function. | ||
This can be done by calling various <code>aocManager</code> that return the AoC file device if the resource path matches specific AoC path patterns and assigning the result to field 0x48 in the ResourceLoadArg class. | This can be done by calling various <code>aocManager</code> functions that return the AoC file device if the resource path matches specific AoC path patterns and assigning the result to field 0x48 in the ResourceLoadArg class. | ||
If no file device is explicitly set, the resource system will use the default file device (romfs on Switch or content directory on Wii U). | If no file device is explicitly set, the resource system will use the default file device (romfs on Switch or content directory on Wii U). | ||
Line 50: | Line 57: | ||
==== Cases where the AoC file device is used ==== | ==== Cases where the AoC file device is used ==== | ||
If the AoC version is >= 0x200 (in most cases) or >= 0x300 (for DLC Pack 2 content), and if the canonical resource name (without the <code>Aoc/0010/</code> prefix) matches any of the below patterns: | If the AoC version is >= 0x200 (in most cases) or >= 0x300 (for DLC Pack 2 content), and if the canonical resource name (without the <code>Aoc/0010/</code> prefix) matches any of the below patterns: | ||
* <code>Terrain/A/AocField*</code> | * <code>Terrain/A/AocField*</code> | ||
* Any dungeon pack (<code>Pack/%s.pack</code>): shrines, divine beasts, Final Trial (for shrines, the dungeon number must be > 119) | * Any dungeon pack (<code>Pack/%s.pack</code>): shrines, divine beasts, Final Trial (for shrines, the dungeon number must be > 119) | ||
Line 67: | Line 75: | ||
* <code>Physics/TeraMeshRigidBody/AocField/*</code> | * <code>Physics/TeraMeshRigidBody/AocField/*</code> | ||
* <code>Voice/*/Stream_Demo6*/*.bfstm</code> | * <code>Voice/*/Stream_Demo6*/*.bfstm</code> | ||
* <code>System/AocVersion.txt</code> | |||
There are two other situations where the <code>Aoc/0010/</code> prefix is supposed to be prepended: | There are two other situations where the <code>Aoc/0010/</code> prefix is supposed to be prepended: | ||
* If the load file device is set to the <code>Pack/AocMainField.pack</code> archive file device explicitly | * If the load file device is set to the <code>Pack/AocMainField.pack</code> archive file device explicitly | ||
* If the global resource pack is an AoC dungeon pack | * If the global resource pack is an AoC dungeon pack | ||
However, the first case is irrelevant because that archive only contains Map/MainField/ (a case that is already handled by the path checks) and the second check appears to never pass. | However, the first case is irrelevant because that archive only contains Map/MainField/ (a case that is already handled by the path checks) and the second check appears to never pass. | ||
=== Failed resource load === | |||
If a resource loading failure is detected by the resource system, it will try again up to 30 times.<ref>0x710121087C in Switch 1.5.0 (ResourceBinder::retryLoading -- member function name is unofficial)</ref> | |||
== Memory allocation == | == Memory allocation == | ||
=== Heap size === | === Heap size === | ||
The size of the resource loading heap the system allocates every time a resource is loaded depends on the value that is listed in the [[#Resource size table]] (RSTB). If lookup fails, the game will fall back to the following formula (Switch on 1.5.0): | The size of the resource loading heap the system allocates every time a resource is loaded depends on the value that is listed in the [[#Resource size table]] (RSTB). If lookup fails, the game will fall back to the following formula (Switch on 1.5.0): | ||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
alignedFileSize = (actualFileSize + 31) & -32; | alignedFileSize = (actualFileSize + 31) & -32; | ||
Line 86: | Line 101: | ||
+ 0x750 | + 0x750 | ||
</syntaxhighlight> | </syntaxhighlight> | ||
'''Warning''': Failure to add entries to the RSTB for a large number of resources may result in system instability. The purpose of this fallback appears to be to allow loading resources during development even without an entry in the resource size table. As such, the system will often allocate way more memory than needed to account for any dynamic allocation that the resource class may do. | '''Warning''': Failure to add entries to the RSTB for a large number of resources may result in system instability. The purpose of this fallback appears to be to allow loading resources during development even without an entry in the resource size table. As such, the system will often allocate way more memory than needed to account for any dynamic allocation that the resource class may do. | ||
Line 97: | Line 111: | ||
The ''Resource Size Table'' (RSTB) contains a list of canonical resource names and their sizes. It is loaded from [[ResourceSizeTable.product.rsizetable]] and used to determine how much memory should be allocated when loading a resource. | The ''Resource Size Table'' (RSTB) contains a list of canonical resource names and their sizes. It is loaded from [[ResourceSizeTable.product.rsizetable]] and used to determine how much memory should be allocated when loading a resource. | ||
==== Resource size calculation ==== | |||
According to an internal source, Nintendo automatically generates the Resource Size Table using development builds of the game that are believed to contain instrumentation code (perhaps in the [[Patrol]] component) This is consistent with the fact that the RSTB asset has the "product" suffix in its name, which is used for machine-generated files, and also with the fact that a similar solution is used for actor instance heap size measurement. | |||
The resource loading heap is, for most factories, only used to allocate the file loading buffer (which is as large as the file to load), the C++ resource class, extra allocations depending on the resource class and some extra bytes to ensure data is aligned correctly in memory. | |||
Therefore, the following formula should give a correct size value for all resource types: | Therefore, the following formula should give a correct size value for all resource types: | ||
(size rounded up to multiple of 32) + CONSTANT + sizeof(ResourceClass) + PARSE_SIZE | (size rounded up to multiple of 32) + CONSTANT + sizeof(ResourceClass) + PARSE_SIZE | ||
CONSTANT is 0x168 in the Switch version and 0xe4 on Wii U. | CONSTANT is 0x168 in the Switch version and 0xe4 on Wii U. | ||
Line 108: | Line 123: | ||
[[Category:Internals]] | [[Category:Internals]] | ||
<references /> |
Latest revision as of 14:49, 20 March 2020
In Breath of the Wild, the resource system is a subsystem that is responsible for managing resources, i.e. content files. It is composed of ResourceMgrTask, ResourceSystem and a few ancillary classes (such as "resource binders" and "handles").
BotW's resource system is also used in Animal Crossing: New Horizons and called "ares" or "ARes".
Concepts
Resource
Resources (in the context of the codebase) are C++ classes that are responsible for parsing raw data from content files and storing them in a more convenient data structure.
In general, there is one resource class per file type, not format. As an example, bgparamlist and bxml do not share the same class even though both are AAMP files.
In some cases, the resource is nothing more than a thin wrapper over the underlying file bytes. This is the case for BYMLs and other simple resource types that don't require any additional memory allocation. Such a wrapper is (unofficially) called ResourceBase.
Resource factory
Factories are C++ classes that create resource objects.
During application init, factory instances are created and registered with the resource system. When a resource is loaded, the system will look up the corresponding factory based on the file extension, load the entire file into memory, and pass the data to the factory, which then returns a Resource object on which parse()
must be called.
List of resource factories
A complete list of resource factories can be found in the rstb
project.
These values were extracted from the Switch and Wii U 1.5.0 executables.
Note that:
- For ResourceLoadArg3 (used in model/bfres related code), the factory is hardcoded to be the ResourceBase factory.
- For ResourceLoadArg2 (used for actor resources and physics stuff) and ResourceLoadArg (everything else), the factory is determined from the file extension.
- Any file for which there isn't any specific factory will use the ResourceBase factory.
Special cases
Compressed files
Compressed files can be automatically decompressed and loaded if their extension starts with an 's'. The 's' prefix indicates that a file is Yaz0 compressed.
When a resource load is requested, the resource system will in most cases automatically prepend 's' to the extension and use sead::ResourceMgr::tryLoadWithDecomp
to try loading a compressed version first. This means that any code that interacts with the resource loading functions must not include the prefix in resource paths.
Exceptions: bfevfl, bcamanim and barslist are always loaded uncompressed[1].
Warning: This behaviour can be overridden by passing some flags to the resource load functions.
Archives
The resource system can be configured to try loading from an SARC archive first.
To load from an archive, a pointer to the archive resource handle should be set in ResourceLoadArg::packRes
@ 0x58. If it is null, and if ResourceMgrTask's flag 2 is not set[2], packRes will automatically be set to the global resource pack pointer (res::ResourceMgrTask::sInstance->packRes
@ this+0x9c06f0 in Switch 1.5.0). Files in the archive can then be accessed as if they were at the root of the romfs/content partition. For example, if EventFlow/Test.bfevfl
is a file in Pack/TitleBG.pack
, the path that is for loading is EventFlow/Test.bfevfl
, not Pack/TitleBG.pack/EventFlow/Test.bfevfl
.
Calls to any of the resource loading functions will automatically check whether the specified resource exists in the archive. If it does exist, it will be loaded from the archive. If the file cannot be found in the archive, the game will ignore the active resource pack and load from the usual file device.
Add-on content
In order to load add-on content, the file device must be set to the AoC file device when calling a resource loading function.
This can be done by calling various aocManager
functions that return the AoC file device if the resource path matches specific AoC path patterns and assigning the result to field 0x48 in the ResourceLoadArg class.
If no file device is explicitly set, the resource system will use the default file device (romfs on Switch or content directory on Wii U).
If the file device is set to the AoC file device, Aoc/0010/
will be prepended to the canonical resource path. This is done to avoid conflicts between the base and AoC versions of a resource. Warning: the AoC file device is not always set when loading resources from AoC archives. This is the case for the model and ActorParam code. Conversely, the AoC device is sometimes set even when it is unnecessary.
Cases where the AoC file device is used
If the AoC version is >= 0x200 (in most cases) or >= 0x300 (for DLC Pack 2 content), and if the canonical resource name (without the Aoc/0010/
prefix) matches any of the below patterns:
Terrain/A/AocField*
- Any dungeon pack (
Pack/%s.pack
): shrines, divine beasts, Final Trial (for shrines, the dungeon number must be > 119) UI/StaffRollDLC/*
Map/MainField/*
(if Pack/AocMainField.pack was loaded successfully)Map/MainFieldDungeon/*
Map/AocField/*
Map/CDungeon/*
(for DungeonNNN with NNN > 119)Physics/StaticCompound/AocField/*
Physics/StaticCompound/MainFieldDungeon/*
Physics/StaticCompound/CDungeon/*
(for DungeonNNN with NNN > 119)Movie/Demo6*
Game/AocField/*
NavMesh/AocField/*
NavMesh/MainFieldDungeon/*
NavMesh/CDungeon/*
(for DungeonNNN with NNN > 119)Physics/TeraMeshRigidBody/AocField/*
Voice/*/Stream_Demo6*/*.bfstm
System/AocVersion.txt
There are two other situations where the Aoc/0010/
prefix is supposed to be prepended:
- If the load file device is set to the
Pack/AocMainField.pack
archive file device explicitly - If the global resource pack is an AoC dungeon pack
However, the first case is irrelevant because that archive only contains Map/MainField/ (a case that is already handled by the path checks) and the second check appears to never pass.
Failed resource load
If a resource loading failure is detected by the resource system, it will try again up to 30 times.[3]
Memory allocation
Heap size
The size of the resource loading heap the system allocates every time a resource is loaded depends on the value that is listed in the #Resource size table (RSTB). If lookup fails, the game will fall back to the following formula (Switch on 1.5.0):
alignedFileSize = (actualFileSize + 31) & -32;
factory->getResourceSize()
+ factory->constant
+ factory->getLoadDataAlignment()
+ (signed int)(float)(factory->sizeMultiplier * alignedFileSize)
+ (factory->sizeMultiplier * alignedFileSize >= 0.0 &&
(float)(signed int)(float)(factory->sizeMultiplier * alignedFileSize) != (float)(sizeMultiplier * alignedFileSize))
+ 0x750
Warning: Failure to add entries to the RSTB for a large number of resources may result in system instability. The purpose of this fallback appears to be to allow loading resources during development even without an entry in the resource size table. As such, the system will often allocate way more memory than needed to account for any dynamic allocation that the resource class may do.
Parent heap
If the heap size is smaller than 0x80000 bytes, the heap is created with the "arena for resource S" (small) heap as parent.
Otherwise, the parent heap is the "arena for resource L" (large).
Resource size table
The Resource Size Table (RSTB) contains a list of canonical resource names and their sizes. It is loaded from ResourceSizeTable.product.rsizetable and used to determine how much memory should be allocated when loading a resource.
Resource size calculation
According to an internal source, Nintendo automatically generates the Resource Size Table using development builds of the game that are believed to contain instrumentation code (perhaps in the Patrol component) This is consistent with the fact that the RSTB asset has the "product" suffix in its name, which is used for machine-generated files, and also with the fact that a similar solution is used for actor instance heap size measurement.
The resource loading heap is, for most factories, only used to allocate the file loading buffer (which is as large as the file to load), the C++ resource class, extra allocations depending on the resource class and some extra bytes to ensure data is aligned correctly in memory.
Therefore, the following formula should give a correct size value for all resource types:
(size rounded up to multiple of 32) + CONSTANT + sizeof(ResourceClass) + PARSE_SIZE
CONSTANT is 0x168 in the Switch version and 0xe4 on Wii U.
PARSE_SIZE is the amount of memory allocated from the resource heap in the Resource::parse
function. Determining the exact value of PARSE_SIZE requires reversing that function and tracking calls to operator new()
because the amount of dynamically allocated memory depends on the resource.