BFEVFL: Difference between revisions
imported>Leoetlino No edit summary |
Tags: Mobile edit Mobile web edit |
||
(31 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
<onlyinclude>''' | <onlyinclude>'''BFEVFL''' is a binary file format for [[Event flow|'''ev'''ent '''fl'''ows]].</onlyinclude> In ''Breath of the Wild'' and other Nintendo games such as ''Splatoon 2'' and ''Link's Awakening'', binary event flows are parsed by Nintendo's EventFlow/evfl library. | ||
== Structures == | == Structures == | ||
BFEVFL is a format that supports both big endian and little endian; however, event flows typically use little endian even on Wii U, as is the case in ''Breath of the Wild''. | |||
BFEVFL uses pointers very heavily to refer to strings, to other sections, etc. All pointers are always 64-bit long -- this is because the same format is used on 64-bit platforms like the Switch. Pointers must be present in the [[#Relocation table]] if the file is to be loaded correctly by the official EventFlow code. | |||
As a direct consequence, most sections don't have any fixed order. Therefore, this article will only document the data structures and mention the order Nintendo places sections in. For more details, it is recommended to look at the [https://github.com/leoetlino/evfl <code>evfl</code> library] directly. | As a direct consequence, most sections don't have any fixed order. Therefore, this article will only document the data structures and mention the order Nintendo places sections in. For more details, it is recommended to look at the [https://github.com/leoetlino/evfl <code>evfl</code> library] directly. | ||
Line 16: | Line 16: | ||
| 0x0 || char[8] || Magic ("BFEVFL\x00\x00") | | 0x0 || char[8] || Magic ("BFEVFL\x00\x00") | ||
|- | |- | ||
| 0x8 || | | 0x8 || u8 || Version (major) | ||
|- | |- | ||
| | | 0x9 || u8 || Version (minor) | ||
|- | |- | ||
| | | 0xa || u8 || Version (patch) | ||
|- | |- | ||
| 0xc || | | 0xb || u8 || Version (sub-patch) | ||
|- | |||
| 0xc || s16 || Byte order mark | |||
|- | |- | ||
| 0xe || u8 || Alignment (to get the actual value: 1 << raw_value) | | 0xe || u8 || Alignment (to get the actual value: 1 << raw_value) | ||
|- | |- | ||
| 0xf || u8 || | | 0xf || u8 || Padding | ||
|- | |- | ||
| 0x10 || | | 0x10 || int || File name offset | ||
|- | |- | ||
| 0x14 || u16 || Is relocated flag (set in memory) | | 0x14 || u16 || Is relocated flag (set in memory) | ||
Line 34: | Line 36: | ||
| 0x16 || u16 || First block offset | | 0x16 || u16 || First block offset | ||
|- | |- | ||
| 0x18 || | | 0x18 || int || Relocation table offset | ||
|- | |- | ||
| 0x1c || | | 0x1c || int || File size | ||
|- | |- | ||
| 0x20 || u16 || Number of [[#Flowchart|flowchart]]s | | 0x20 || u16 || Number of [[#Flowchart|flowchart]]s | ||
Line 44: | Line 46: | ||
| 0x24 || u32 || Padding | | 0x24 || u32 || Padding | ||
|- | |- | ||
| 0x28 || Flowchart* || Flowchart (nullptr if no flowchart) | | 0x28 || Flowchart** || Flowchart (nullptr if no flowchart) | ||
|- | |- | ||
| 0x30 || [[#Dictionary|Dictionary]]* || Flowchart name dictionary | | 0x30 || [[#Dictionary|Dictionary]]* || Flowchart name dictionary | ||
|- | |- | ||
| 0x38 || Timeline* || Timeline (nullptr if no timeline) | | 0x38 || Timeline** || Timeline (nullptr if no timeline) | ||
|- | |- | ||
| 0x40 || Dictionary* || Timeline name dictionary | | 0x40 || Dictionary* || Timeline name dictionary | ||
|} | |} | ||
Note: 0x0-0x20 are just a standard ore::BinaryFileHeader (which is identical to nn::util::BinaryFileHeader). | |||
=== Relocation table === | === Relocation table === | ||
(The official name for this structure in the EventFlow library is ore::RelocationTable.) | |||
{|class="wikitable" | {|class="wikitable" | ||
! Offset !! Type !! Description | ! Offset !! Type !! Description | ||
Line 59: | Line 65: | ||
| 0x0 || char[4] || Magic ("RELT") | | 0x0 || char[4] || Magic ("RELT") | ||
|- | |- | ||
| 0x4 || | | 0x4 || int || Offset to relocation table start (<code>table_start_offset</code>) | ||
|- | |- | ||
| 0x8 || | | 0x8 || int || Number of sections | ||
|- | |- | ||
| 0xc || u32 || Padding | | 0xc || u32 || Padding | ||
|- | |- | ||
| 0x10 || Section[num_sections] || Sections | | 0x10 || Section[num_sections] || Sections | ||
|- | |||
| - || Entry[...] || Section entries (emitted in the same order as each section) | |||
|} | |} | ||
Note: the number of sections is almost always 1 because a single section can already fit 2^32 - 1 entries. If you need more than 4 billion entries, you are probably doing something ''very wrong''. | |||
The table base pointer is calculated as follows: <code>reinterpret_cast<char*>(&table) - table_start_offset</code> | |||
==== Relocation table section ==== | ==== Relocation table section ==== | ||
Line 72: | Line 84: | ||
! Offset !! Type !! Description | ! Offset !! Type !! Description | ||
|- | |- | ||
| 0x0 || void* || | | 0x0 || void* || Optional base pointer (<code>ptr</code>) | ||
|- | |- | ||
| | | 0x8 || int || Optional base pointer offset (<code>offset</code>) | ||
|- | |- | ||
| | | 0xc || int || Size | ||
|- | |- | ||
| | | 0x10 || int || Index of the first entry in this section | ||
|- | |- | ||
| | | 0x14 || int || Number of entries | ||
|} | |} | ||
The base pointer (<code>base</code>) is calculated as follows: | |||
* If the optional base pointer field is nullptr, the base pointer is equal to the table base pointer. | |||
* Otherwise, it is equal to <code>ptr - offset</code>. | |||
==== Relocation table entry ==== | ==== Relocation table entry ==== | ||
Line 89: | Line 103: | ||
! Offset !! Type !! Description | ! Offset !! Type !! Description | ||
|- | |- | ||
| 0x0 || u32 || Offset to pointers to relocate | | 0x0 || u32 || Offset to pointers to relocate, relative to the table base pointer | ||
|- | |- | ||
| 0x4 || u32 || Bit field that determines which pointers need to be relocated (up to 32 contiguous pointers starting from the listed offset) | | 0x4 || u32 || Bit field that determines which pointers need to be relocated (up to 32 contiguous pointers starting from the listed offset) | ||
|} | |} | ||
Offsets are treated as signed 32-bit integers. | |||
Pointers are relocated by overwriting each 64-bit pointer field with <code>base + offset</code>. | |||
=== String pool === | === String pool === | ||
(The official name for this structure in the EventFlow library is ore::StringPool.) | |||
The string pool starts with the <code>STR </code> magic and contains 2-byte aligned strings. The magic is not checked at all since strings are directly referred to using pointers. | The string pool starts with the <code>STR </code> magic and contains 2-byte aligned strings. The magic is not checked at all since strings are directly referred to using pointers. | ||
All strings in the pool are Pascal | All strings in the pool are Pascal strings, i.e. length-prefixed strings. The length is an unsigned 16 bit integer. Strings are stored in reverse order of their binary representation. | ||
=== Dictionary === | === Dictionary === | ||
(The official name for this structure in the EventFlow library is ore::ResDic.) | |||
A dictionary is a data structure that is used to quickly look up the index of an element based on its name. Thus, they are always used in conjunction with an array of elements. However, the way the dictionary and the array are associated depends on the structure. | A dictionary is a data structure that is used to quickly look up the index of an element based on its name. Thus, they are always used in conjunction with an array of elements. However, the way the dictionary and the array are associated depends on the structure. | ||
Line 111: | Line 133: | ||
| 0x4 || u32 || Number of entries (ignoring root entry) | | 0x4 || u32 || Number of entries (ignoring root entry) | ||
|- | |- | ||
| 0x8 || Entry || Root entry | | 0x8 || Entry || Root entry (bit index is <code>0xffffffff</code>) | ||
|- | |- | ||
| 0x18 || Entry[num_entries] || Entries | | 0x18 || Entry[num_entries] || Entries | ||
Line 120: | Line 142: | ||
! Offset !! Type !! Description | ! Offset !! Type !! Description | ||
|- | |- | ||
| 0x0 || u32 || | | 0x0 || u32 || Bit index | ||
|- | |- | ||
| 0x4 || u16 || | | 0x4 || u16 || Index of next node if bit is 0 | ||
|- | |- | ||
| 0x6 || u16 || | | 0x6 || u16 || Index of next node if bit is 1 | ||
|- | |- | ||
| 0x8 || PascalString* || Name | | 0x8 || PascalString* || Name | ||
|} | |} | ||
Example: <code>Hello</code> corresponds to 100100001100101011011000110110001101111. Bits 0-3 are 1, bit 4 is 0, etc. | Example: <code>Hello</code> corresponds to 100100001100101011011000110110001101111. Bits 0-3 are 1, bit 4 is 0, etc. | ||
=== Container === | === Container === | ||
(The official name for this structure in the EventFlow library is ore::ResMetaData.) | |||
Containers are key-value mappings with keys being strings. A [[#Dictionary]] is used to store keys. | Containers are key-value mappings with keys being strings. A [[#Dictionary]] is used to store keys. | ||
{|class="wikitable" | |||
! Offset !! Type !! Description | |||
|- | |||
| 0x0 || ContainerItem || Root container structure (data type is <code>Container</code>) | |||
|} | |||
==== ContainerItem ==== | |||
{|class="wikitable" | |||
! Offset !! Type !! Description | |||
|- | |||
| 0x0 || ContainerDataType (u8) || Data type | |||
|- | |||
| 0x1 || u8 || Padding | |||
|- | |||
| 0x2 || u16 || Number of items | |||
|- | |||
| 0x4 || u32 || Padding | |||
|- | |||
| 0x8 || Dictionary* || Dictionary (only for the Container data type) | |||
|- | |||
| 0x10 || ContainerItemData || Data | |||
|} | |||
==== ContainerItemData ==== | |||
{|class="wikitable" | |||
! Data type !! Data !! Number of items | |||
|- | |||
| Argument || String (not in string pool, follows item structure immediately) || 1 | |||
|- | |||
| Container || ContainerItem*[n] || n | |||
|- | |||
| Int || Signed 32-bit integer || 1 | |||
|- | |||
| Bool || 0x80000001 if true, 0x00000000 otherwise || 1 | |||
|- | |||
| Float || binary32 floating point number || 1 | |||
|- | |||
| String || String (not in string pool, follows item structure immediately) || 1 | |||
|- | |||
| Wide string ("wstring") || Wide string (not in string pool, follows item structure immediately) || 1 | |||
|- | |||
| Int array || Int[n] || n | |||
|- | |||
| Bool array || Bool[n] || n | |||
|- | |||
| Float array || Float[n] || n | |||
|- | |||
| String array || String[n] (aligned to 8-byte boundaries this time) || n | |||
|- | |||
| Wstring array || WString[n] (aligned to 8-byte boundaries this time) || n | |||
|- | |||
| Actor identifier || Two strings: actor name + secondary name || 2 | |||
|} | |||
Wide strings use wchar_t which is 32-bit long on Switch. | |||
==== ContainerDataType enum ==== | ==== ContainerDataType enum ==== | ||
Line 178: | Line 247: | ||
|} | |} | ||
==== | === Actor === | ||
(The official name for this structure in the EventFlow library is evfl::ResActor.) | |||
{|class="wikitable" | |||
! Offset !! Type !! Description | |||
|- | |||
| 0x0 || PascalString* || Name | |||
|- | |||
| 0x8 || PascalString* || Secondary name | |||
|- | |||
| 0x10 || PascalString* || Argument name | |||
|- | |||
| 0x18 || [[#evfl::ResAction]]* || Pointer to array of actions (strings) | |||
|- | |||
| 0x20 || [[#evfl::ResQuery]]* || Pointer to array of queries (strings) | |||
|- | |||
| 0x28 || Container* || Parameters | |||
|- | |||
| 0x30 || u16 || Number of actions | |||
|- | |||
| 0x32 || u16 || Number of queries | |||
|- | |||
| 0x34 || u16 || Entry point index for associated entry point (0xffff if none) | |||
|- | |||
| 0x36 || u8 || Cut number? This is set to 1 for flowcharts. Timeline actors sometimes use a different value here. {{check}} In BotW, this value is passed as the @MA actor parameter<ref>Switch 1.5.0 0x7100DA8628</ref>. | |||
|- | |||
| 0x37 || u8 || Padding | |||
|} | |||
==== evfl::ResAction ==== | |||
{|class="wikitable" | |||
! Offset !! Type !! Description | |||
|- | |||
| 0x0 || PascalString* || Name | |||
|} | |||
==== evfl::ResQuery ==== | |||
{|class="wikitable" | {|class="wikitable" | ||
! Offset !! Type !! Description | ! Offset !! Type !! Description | ||
|- | |- | ||
| 0x0 || | | 0x0 || PascalString* || Name | ||
|} | |||
=== Event === | |||
==== EventType enum ==== | |||
(The official name for this enum in the EventFlow library is evfl::ResEvent::EventType::Type.) | |||
{|class="wikitable" | |||
! Value !! Name !! Description | |||
|- | |||
| 0 || Action || Invokes an actor function ("action") with no return value. | |||
|- | |||
| 1 || Switch || Invokes an actor function ("query") with an int return value and branches execution flow depending on it. | |||
|- | |||
| 2 || Fork || Branches execution flow unconditionally. | |||
|- | |||
| 3 || Join || Used to reunify execution flow after a fork finishes executing. | |||
|- | |||
| 4 || Sub flow || Invokes an entry point in the same or in a different event flow. Similar to a function call. | |||
|} | |||
==== Main event structure ==== | |||
(The official name for this structure in the EventFlow library is evfl::ResEvent.) | |||
{|class="wikitable" | |||
!rowspan=2|Offset | |||
!rowspan=2|Type | |||
!colspan=5|Description | |||
|- | |||
! Action !! Switch !! Fork !! Join !! Sub flow | |||
|- | |||
| 0x0 || PascalString* ||colspan=5| Name | |||
|- | |||
| 0x8 || EventType (u8) ||colspan=5| Type | |||
|- | |||
| 0x9 || u8 ||colspan=5| Padding | |||
|- | |||
| 0xa || u16 || Next event index || Number of cases (>=0) || Number of forks (>0) ||colspan=2| Next event index | |||
|- | |- | ||
| | | 0xc || u16 ||colspan=2| Actor index || Join event index (required) ||colspan=3| Unused | ||
|- | |- | ||
| | | 0xe || u16 || Actor action index || Actor query index ||colspan=3| Unused | ||
|- | |- | ||
| | | 0x10 || void* ||colspan=2| Container* (optional parameters) || u16* (fork event indexes) || Unused || Container* (optional parameters) | ||
|- | |- | ||
| | | 0x18 || void* || Unused || SwitchCases* (0x0: u32 value, 0x4: u16 event index, 0x6: u16 padding) ||colspan=2| Unused || PascalString* (flowchart name) | ||
|- | |- | ||
| | | 0x20 || void* ||colspan=4| Unused || PascalString* (entry point name) | ||
|} | |} | ||
=== | === Entry point === | ||
The | (The official name for this structure in the EventFlow library is evfl::ResEntryPoint.) | ||
{|class="wikitable" | |||
! Offset !! Type !! Description | |||
|- | |||
| 0x0 || u16* || Sub flow event indices | |||
|- | |||
| 0x8 || Dictionary* || VariableDef names (parsed by the evfl lib, but unused by BotW) | |||
|- | |||
| 0x10 || VariableDef* || VariableDefs (parsed by the evfl lib, but unused by BotW) | |||
|- | |||
| 0x18 || u16 || Number of sub flow event indices | |||
|- | |||
| 0x1a || u16 || Number of variable definitions | |||
|- | |||
| 0x1c || u16 || Main event index. 0xffff if the entry point doesn't point to any event. | |||
|- | |||
| 0x1e || u16 || Padding | |||
|} | |||
=== | === Flowchart === | ||
(The official name for this structure in the EventFlow library is evfl::ResFlowchart.) | |||
{|class="wikitable" | |||
! Offset !! Type !! Description | |||
|- | |||
| 0x0 || char[4] || Magic ("EVFL") | |||
|- | |||
| 0x4 || u32 || String pool offset (relative to this structure) | |||
|- | |||
| 0x8 || u32 || Padding | |||
|- | |||
| 0xc || u32 || Padding | |||
|- | |||
| 0x10 || u16 || Number of actors | |||
|- | |||
| 0x12 || u16 || Total number of actions | |||
|- | |||
| 0x14 || u16 || Total number of queries | |||
|- | |||
| 0x16 || u16 || Number of events | |||
|- | |||
| 0x18 || u16 || Number of entry points | |||
|- | |||
| 0x1a || u16 || Padding | |||
|- | |||
| 0x1c || u16 || Padding | |||
|- | |||
| 0x1e || u16 || Padding | |||
|- | |||
| 0x20 || PascalString* || Name | |||
|- | |||
| 0x28 || Actor* || Actors | |||
|- | |||
| 0x30 || Event* || Events | |||
|- | |||
| 0x38 || Dictionary* || Entry point dictionary | |||
|- | |||
| 0x40 || EntryPoint* || Entry points | |||
|} | |||
=== Timeline === | |||
(The official name for this structure in the EventFlow library is evfl::ResTimeline.) | |||
{|class="wikitable" | {|class="wikitable" | ||
! | ! Offset !! Type !! Description | ||
|- | |||
| 0x0 || char[4] || Magic ("TLIN") | |||
|- | |||
| 0x4 || u32 || String pool offset (relative to this structure) | |||
|- | |||
| 0x8 || u32 || Padding | |||
|- | |||
| 0xc || u32 || Padding | |||
|- | |||
| 0x10 || float || Duration | |||
|- | |||
| 0x14 || u16 || Number of actors | |||
|- | |||
| 0x16 || u16 || Total number of actions | |||
|- | |||
| 0x18 || u16 || Number of clips | |||
|- | |||
| 0x1a || u16 || Number of oneshots | |||
|- | |||
| 0x1c || u16 || Number of subtimelines | |||
|- | |||
| 0x1e || u16 || Number of cuts | |||
|- | |||
| 0x20 || PascalString* || Name | |||
|- | |||
| 0x28 || Actor* || Actors | |||
|- | |- | ||
| | | 0x30 || [[#Clip]]* || Clips | ||
|- | |- | ||
| | | 0x38 || [[#Oneshot]]* || Oneshots | ||
|- | |- | ||
| | | 0x40 || [[#Trigger]]* || Triggers (exactly 2 per clip) | ||
|- | |- | ||
| | | 0x48 || [[#Subtimeline]]* || Subtimelines | ||
|- | |- | ||
| | | 0x50 || [[#Cut]]* || Cuts | ||
|- | |- | ||
| | | 0x58 || Container* || Parameters | ||
|} | |||
=== Clip === | |||
(The official name for this structure in the EventFlow library is evfl::ResClip.) | |||
{|class="wikitable" | |||
! Offset !! Type !! Description | |||
|- | |- | ||
| | | 0x0 || float || Start time | ||
|- | |- | ||
| | | 0x4 || float || Duration | ||
|- | |- | ||
| | | 0x8 || u16 || Actor index | ||
|- | |- | ||
| | | 0xa || u16 || Actor action index | ||
|- | |- | ||
| | | 0xc || u8 || ? {{check}} | ||
|- | |- | ||
| | | 0xd || u8[3] || Padding | ||
|- | |- | ||
| | | 0x10 || Container* || Parameters | ||
|} | |} | ||
=== | === Oneshot === | ||
{ | (The official name for this structure in the EventFlow library is evfl::ResOneshot.) | ||
{|class="wikitable" | |||
! Offset !! Type !! Description | |||
|- | |||
| 0x0 || float || Time | |||
|- | |||
| 0x4 || u16 || Actor index | |||
|- | |||
| 0x6 || u16 || Actor action index | |||
|- | |||
| 0x8 || void* || Padding | |||
|- | |||
| 0x10 || Container* || Parameters | |||
|} | |||
=== | === Trigger === | ||
{ | (The official name for this structure in the EventFlow library is evfl::ResTrigger.) | ||
{|class="wikitable" | |||
! Offset !! Type !! Description | |||
|- | |||
| 0x0 || u16 || Clip index | |||
|- | |||
| 0x2 || [[#TriggerType]] || Trigger type | |||
|- | |||
| 0x3 || u8 || Padding | |||
|} | |||
=== | ==== TriggerType ==== | ||
{ | {|class="wikitable" | ||
! Value !! Description | |||
|- | |||
| 1 || Enter - triggered when a clip starts | |||
|- | |||
| 2 || Leave - triggered when a clip ends | |||
|} | |||
=== | === Cut === | ||
{{ | (The official name for this structure in the EventFlow library is evfl::ResCut.) | ||
{|class="wikitable" | |||
! Offset !! Type !! Description | |||
|- | |||
| 0x0 || float || Start time {{check}} | |||
|- | |||
| 0x4 || u32 || ? {{check}} | |||
|- | |||
| 0x8 || PascalString* || Name | |||
|- | |||
| 0x10 || Container* || Parameters | |||
|} | |||
=== | === Subtimeline === | ||
{ | (The official name for this structure in the EventFlow library is evfl::ResSubtimeline.) | ||
{|class="wikitable" | |||
! Offset !! Type !! Description | |||
|- | |||
| 0x0 || PascalString* || Subtimeline name | |||
|} | |||
== Section order == | == Section order == | ||
Line 259: | Line 530: | ||
=== Timeline === | === Timeline === | ||
{{ | * Actor param containers, evfl::ResAction | ||
* Parameter container | |||
* Timeline header | |||
* Actors | |||
* Clips | |||
* Oneshots {{check}} | |||
* Subtimelines | |||
* Triggers | |||
* Cuts | |||
* Clip param containers | |||
* Oneshot param containers {{check}} | |||
* Cut param containers {{check}} | |||
=== Flowchart === | === Flowchart === | ||
Line 279: | Line 561: | ||
* Container dictionary | * Container dictionary | ||
* Container items (+ values) | * Container items (+ values) | ||
== Usage in ''Breath of the Wild'' == | |||
BFEVFL is used as the underlying format for event flows in the release versions. They are always stored under [[Content/EventFlow|EventFlow]] and have "bfevfl" (for flowcharts) and "bfevtm" (for timelines) as their file extensions. | |||
For development versions, it appears that a different, non-binary format was used ("evfl"). | |||
== Tools == | == Tools == | ||
Line 284: | Line 571: | ||
* [https://github.com/leoetlino/event-editor EventEditor]: graphical editor for event flows | * [https://github.com/leoetlino/event-editor EventEditor]: graphical editor for event flows | ||
== References == | |||
<references/> | <references/> | ||
[[Category:File formats]] | [[Category:File formats]] |
Latest revision as of 11:55, 16 April 2023
BFEVFL is a binary file format for event flows. In Breath of the Wild and other Nintendo games such as Splatoon 2 and Link's Awakening, binary event flows are parsed by Nintendo's EventFlow/evfl library.
Structures
BFEVFL is a format that supports both big endian and little endian; however, event flows typically use little endian even on Wii U, as is the case in Breath of the Wild.
BFEVFL uses pointers very heavily to refer to strings, to other sections, etc. All pointers are always 64-bit long -- this is because the same format is used on 64-bit platforms like the Switch. Pointers must be present in the #Relocation table if the file is to be loaded correctly by the official EventFlow code.
As a direct consequence, most sections don't have any fixed order. Therefore, this article will only document the data structures and mention the order Nintendo places sections in. For more details, it is recommended to look at the evfl
library directly.
Another consequence is that sections must be aligned to 8-byte boundaries in most cases to avoid unaligned memory accesses (this is not as important for strings).
Header
Offset | Type | Description |
---|---|---|
0x0 | char[8] | Magic ("BFEVFL\x00\x00") |
0x8 | u8 | Version (major) |
0x9 | u8 | Version (minor) |
0xa | u8 | Version (patch) |
0xb | u8 | Version (sub-patch) |
0xc | s16 | Byte order mark |
0xe | u8 | Alignment (to get the actual value: 1 << raw_value) |
0xf | u8 | Padding |
0x10 | int | File name offset |
0x14 | u16 | Is relocated flag (set in memory) |
0x16 | u16 | First block offset |
0x18 | int | Relocation table offset |
0x1c | int | File size |
0x20 | u16 | Number of flowcharts |
0x22 | u16 | Number of timelines |
0x24 | u32 | Padding |
0x28 | Flowchart** | Flowchart (nullptr if no flowchart) |
0x30 | Dictionary* | Flowchart name dictionary |
0x38 | Timeline** | Timeline (nullptr if no timeline) |
0x40 | Dictionary* | Timeline name dictionary |
Note: 0x0-0x20 are just a standard ore::BinaryFileHeader (which is identical to nn::util::BinaryFileHeader).
Relocation table
(The official name for this structure in the EventFlow library is ore::RelocationTable.)
Offset | Type | Description |
---|---|---|
0x0 | char[4] | Magic ("RELT") |
0x4 | int | Offset to relocation table start (table_start_offset )
|
0x8 | int | Number of sections |
0xc | u32 | Padding |
0x10 | Section[num_sections] | Sections |
- | Entry[...] | Section entries (emitted in the same order as each section) |
Note: the number of sections is almost always 1 because a single section can already fit 2^32 - 1 entries. If you need more than 4 billion entries, you are probably doing something very wrong.
The table base pointer is calculated as follows: reinterpret_cast<char*>(&table) - table_start_offset
Relocation table section
Offset | Type | Description |
---|---|---|
0x0 | void* | Optional base pointer (ptr )
|
0x8 | int | Optional base pointer offset (offset )
|
0xc | int | Size |
0x10 | int | Index of the first entry in this section |
0x14 | int | Number of entries |
The base pointer (base
) is calculated as follows:
- If the optional base pointer field is nullptr, the base pointer is equal to the table base pointer.
- Otherwise, it is equal to
ptr - offset
.
Relocation table entry
Offset | Type | Description |
---|---|---|
0x0 | u32 | Offset to pointers to relocate, relative to the table base pointer |
0x4 | u32 | Bit field that determines which pointers need to be relocated (up to 32 contiguous pointers starting from the listed offset) |
Offsets are treated as signed 32-bit integers.
Pointers are relocated by overwriting each 64-bit pointer field with base + offset
.
String pool
(The official name for this structure in the EventFlow library is ore::StringPool.)
The string pool starts with the STR
magic and contains 2-byte aligned strings. The magic is not checked at all since strings are directly referred to using pointers.
All strings in the pool are Pascal strings, i.e. length-prefixed strings. The length is an unsigned 16 bit integer. Strings are stored in reverse order of their binary representation.
Dictionary
(The official name for this structure in the EventFlow library is ore::ResDic.)
A dictionary is a data structure that is used to quickly look up the index of an element based on its name. Thus, they are always used in conjunction with an array of elements. However, the way the dictionary and the array are associated depends on the structure.
BFEVFL dictionaries are essentially binary radix trees (also called PATRICIA trees or tries). The structure contains a binary search tree and bit-by-bit comparisons of strings are done to navigate through it. It is extremely similar to the Wii U BFRES "index group" structure, but with significant changes to the algorithm. The Switch BFRES format shares the same algorithm.
Offset | Type | Description |
---|---|---|
0x0 | char[4] | Magic ("DIC ") |
0x4 | u32 | Number of entries (ignoring root entry) |
0x8 | Entry | Root entry (bit index is 0xffffffff )
|
0x18 | Entry[num_entries] | Entries |
Dictionary entry
Offset | Type | Description |
---|---|---|
0x0 | u32 | Bit index |
0x4 | u16 | Index of next node if bit is 0 |
0x6 | u16 | Index of next node if bit is 1 |
0x8 | PascalString* | Name |
Example: Hello
corresponds to 100100001100101011011000110110001101111. Bits 0-3 are 1, bit 4 is 0, etc.
Container
(The official name for this structure in the EventFlow library is ore::ResMetaData.)
Containers are key-value mappings with keys being strings. A #Dictionary is used to store keys.
Offset | Type | Description |
---|---|---|
0x0 | ContainerItem | Root container structure (data type is Container )
|
ContainerItem
Offset | Type | Description |
---|---|---|
0x0 | ContainerDataType (u8) | Data type |
0x1 | u8 | Padding |
0x2 | u16 | Number of items |
0x4 | u32 | Padding |
0x8 | Dictionary* | Dictionary (only for the Container data type) |
0x10 | ContainerItemData | Data |
ContainerItemData
Data type | Data | Number of items |
---|---|---|
Argument | String (not in string pool, follows item structure immediately) | 1 |
Container | ContainerItem*[n] | n |
Int | Signed 32-bit integer | 1 |
Bool | 0x80000001 if true, 0x00000000 otherwise | 1 |
Float | binary32 floating point number | 1 |
String | String (not in string pool, follows item structure immediately) | 1 |
Wide string ("wstring") | Wide string (not in string pool, follows item structure immediately) | 1 |
Int array | Int[n] | n |
Bool array | Bool[n] | n |
Float array | Float[n] | n |
String array | String[n] (aligned to 8-byte boundaries this time) | n |
Wstring array | WString[n] (aligned to 8-byte boundaries this time) | n |
Actor identifier | Two strings: actor name + secondary name | 2 |
Wide strings use wchar_t which is 32-bit long on Switch.
ContainerDataType enum
The following enum definition is complete and all names are official[1].
Value | Name |
---|---|
0 | Argument |
1 | Container |
2 | Int |
3 | Bool |
4 | Float |
5 | String |
6 | Wide string ("wstring") |
7 | Int array |
8 | Bool array |
9 | Float array |
10 | String array |
11 | Wstring array |
12 | Actor identifier |
Actor
(The official name for this structure in the EventFlow library is evfl::ResActor.)
Offset | Type | Description |
---|---|---|
0x0 | PascalString* | Name |
0x8 | PascalString* | Secondary name |
0x10 | PascalString* | Argument name |
0x18 | #evfl::ResAction* | Pointer to array of actions (strings) |
0x20 | #evfl::ResQuery* | Pointer to array of queries (strings) |
0x28 | Container* | Parameters |
0x30 | u16 | Number of actions |
0x32 | u16 | Number of queries |
0x34 | u16 | Entry point index for associated entry point (0xffff if none) |
0x36 | u8 | Cut number? This is set to 1 for flowcharts. Timeline actors sometimes use a different value here. [check] In BotW, this value is passed as the @MA actor parameter[2]. |
0x37 | u8 | Padding |
evfl::ResAction
Offset | Type | Description |
---|---|---|
0x0 | PascalString* | Name |
evfl::ResQuery
Offset | Type | Description |
---|---|---|
0x0 | PascalString* | Name |
Event
EventType enum
(The official name for this enum in the EventFlow library is evfl::ResEvent::EventType::Type.)
Value | Name | Description |
---|---|---|
0 | Action | Invokes an actor function ("action") with no return value. |
1 | Switch | Invokes an actor function ("query") with an int return value and branches execution flow depending on it. |
2 | Fork | Branches execution flow unconditionally. |
3 | Join | Used to reunify execution flow after a fork finishes executing. |
4 | Sub flow | Invokes an entry point in the same or in a different event flow. Similar to a function call. |
Main event structure
(The official name for this structure in the EventFlow library is evfl::ResEvent.)
Offset | Type | Description | |||||
---|---|---|---|---|---|---|---|
Action | Switch | Fork | Join | Sub flow | |||
0x0 | PascalString* | Name | |||||
0x8 | EventType (u8) | Type | |||||
0x9 | u8 | Padding | |||||
0xa | u16 | Next event index | Number of cases (>=0) | Number of forks (>0) | Next event index | ||
0xc | u16 | Actor index | Join event index (required) | Unused | |||
0xe | u16 | Actor action index | Actor query index | Unused | |||
0x10 | void* | Container* (optional parameters) | u16* (fork event indexes) | Unused | Container* (optional parameters) | ||
0x18 | void* | Unused | SwitchCases* (0x0: u32 value, 0x4: u16 event index, 0x6: u16 padding) | Unused | PascalString* (flowchart name) | ||
0x20 | void* | Unused | PascalString* (entry point name) |
Entry point
(The official name for this structure in the EventFlow library is evfl::ResEntryPoint.)
Offset | Type | Description |
---|---|---|
0x0 | u16* | Sub flow event indices |
0x8 | Dictionary* | VariableDef names (parsed by the evfl lib, but unused by BotW) |
0x10 | VariableDef* | VariableDefs (parsed by the evfl lib, but unused by BotW) |
0x18 | u16 | Number of sub flow event indices |
0x1a | u16 | Number of variable definitions |
0x1c | u16 | Main event index. 0xffff if the entry point doesn't point to any event. |
0x1e | u16 | Padding |
Flowchart
(The official name for this structure in the EventFlow library is evfl::ResFlowchart.)
Offset | Type | Description |
---|---|---|
0x0 | char[4] | Magic ("EVFL") |
0x4 | u32 | String pool offset (relative to this structure) |
0x8 | u32 | Padding |
0xc | u32 | Padding |
0x10 | u16 | Number of actors |
0x12 | u16 | Total number of actions |
0x14 | u16 | Total number of queries |
0x16 | u16 | Number of events |
0x18 | u16 | Number of entry points |
0x1a | u16 | Padding |
0x1c | u16 | Padding |
0x1e | u16 | Padding |
0x20 | PascalString* | Name |
0x28 | Actor* | Actors |
0x30 | Event* | Events |
0x38 | Dictionary* | Entry point dictionary |
0x40 | EntryPoint* | Entry points |
Timeline
(The official name for this structure in the EventFlow library is evfl::ResTimeline.)
Offset | Type | Description |
---|---|---|
0x0 | char[4] | Magic ("TLIN") |
0x4 | u32 | String pool offset (relative to this structure) |
0x8 | u32 | Padding |
0xc | u32 | Padding |
0x10 | float | Duration |
0x14 | u16 | Number of actors |
0x16 | u16 | Total number of actions |
0x18 | u16 | Number of clips |
0x1a | u16 | Number of oneshots |
0x1c | u16 | Number of subtimelines |
0x1e | u16 | Number of cuts |
0x20 | PascalString* | Name |
0x28 | Actor* | Actors |
0x30 | #Clip* | Clips |
0x38 | #Oneshot* | Oneshots |
0x40 | #Trigger* | Triggers (exactly 2 per clip) |
0x48 | #Subtimeline* | Subtimelines |
0x50 | #Cut* | Cuts |
0x58 | Container* | Parameters |
Clip
(The official name for this structure in the EventFlow library is evfl::ResClip.)
Offset | Type | Description |
---|---|---|
0x0 | float | Start time |
0x4 | float | Duration |
0x8 | u16 | Actor index |
0xa | u16 | Actor action index |
0xc | u8 | ? [check] |
0xd | u8[3] | Padding |
0x10 | Container* | Parameters |
Oneshot
(The official name for this structure in the EventFlow library is evfl::ResOneshot.)
Offset | Type | Description |
---|---|---|
0x0 | float | Time |
0x4 | u16 | Actor index |
0x6 | u16 | Actor action index |
0x8 | void* | Padding |
0x10 | Container* | Parameters |
Trigger
(The official name for this structure in the EventFlow library is evfl::ResTrigger.)
Offset | Type | Description |
---|---|---|
0x0 | u16 | Clip index |
0x2 | #TriggerType | Trigger type |
0x3 | u8 | Padding |
TriggerType
Value | Description |
---|---|
1 | Enter - triggered when a clip starts |
2 | Leave - triggered when a clip ends |
Cut
(The official name for this structure in the EventFlow library is evfl::ResCut.)
Offset | Type | Description |
---|---|---|
0x0 | float | Start time [check] |
0x4 | u32 | ? [check] |
0x8 | PascalString* | Name |
0x10 | Container* | Parameters |
Subtimeline
(The official name for this structure in the EventFlow library is evfl::ResSubtimeline.)
Offset | Type | Description |
---|---|---|
0x0 | PascalString* | Subtimeline name |
Section order
File
- Header (0x48 bytes)
- Flowchart* array if it exists
- Flowchart dictionary (always)
- Timeline* array if it exists
- Timeline dictionary (always)
- Timeline
- Flowchart
- String pool (
STR
) - Relocation table (
RELT
)
Timeline
- Actor param containers, evfl::ResAction
- Parameter container
- Timeline header
- Actors
- Clips
- Oneshots [check]
- Subtimelines
- Triggers
- Cuts
- Clip param containers
- Oneshot param containers [check]
- Cut param containers [check]
Flowchart
- Flowchart header
- Actors
- Argument name is put in the string pool.
- Events
- Entry point dictionary
- Entry points
- Event param containers, fork structs, etc. (in order)
- Actor param containers, string pointer arrays (in order)
- Entry point extra data
- Sub flow event index arrays are written here
- ptr_x10 data may be written here? (ptr_x10 has always been a nullptr in files that have been checked by leoetlino.)
- The size for each entry point is sizeof(event_idx_array) rounded up to the nearest multiple of 8 + 0x18 bytes.
Container
- Container header (variable size)
- Container dictionary
- Container items (+ values)
Usage in Breath of the Wild
BFEVFL is used as the underlying format for event flows in the release versions. They are always stored under EventFlow and have "bfevfl" (for flowcharts) and "bfevtm" (for timelines) as their file extensions.
For development versions, it appears that a different, non-binary format was used ("evfl").
Tools
evfl
: Python library for parsing and writing event flows- EventEditor: graphical editor for event flows