summaryrefslogtreecommitdiff
path: root/lib/EventFlow
diff options
context:
space:
mode:
authorLéo Lam <leo@leolam.fr>2022-03-21 19:25:20 +0100
committerLéo Lam <leo@leolam.fr>2022-03-21 21:31:42 +0100
commit18c60323a9bbc7594c287d18d6e87a1ae5d60006 (patch)
tree387c2c412b5ea13493c5d942c85b8b046255431b /lib/EventFlow
parentffcc7f659ebc9bc9d149e52bec553b906bb47369 (diff)
Switch to subrepos
git subrepo clone https://github.com/open-ead/sead lib/sead subrepo: subdir: "lib/sead" merged: "1b66e825d" upstream: origin: "https://github.com/open-ead/sead" branch: "master" commit: "1b66e825d" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo" commit: "2f68596" git subrepo clone (merge) https://github.com/open-ead/nnheaders lib/NintendoSDK subrepo: subdir: "lib/NintendoSDK" merged: "9ee21399f" upstream: origin: "https://github.com/open-ead/nnheaders" branch: "master" commit: "9ee21399f" git-subrepo: version: "0.4.3" origin: "ssh://git@github.com/ingydotnet/git-subrepo" commit: "2f68596" git subrepo clone https://github.com/open-ead/agl lib/agl subrepo: subdir: "lib/agl" merged: "7c063271b" upstream: origin: "https://github.com/open-ead/agl" branch: "master" commit: "7c063271b" git-subrepo: version: "0.4.3" origin: "ssh://git@github.com/ingydotnet/git-subrepo" commit: "2f68596" git subrepo clone https://github.com/open-ead/EventFlow lib/EventFlow subrepo: subdir: "lib/EventFlow" merged: "c35d21b34" upstream: origin: "https://github.com/open-ead/EventFlow" branch: "master" commit: "c35d21b34" git-subrepo: version: "0.4.3" origin: "ssh://git@github.com/ingydotnet/git-subrepo" commit: "2f68596"
Diffstat (limited to 'lib/EventFlow')
m---------lib/EventFlow0
-rw-r--r--lib/EventFlow/.clang-format75
-rw-r--r--lib/EventFlow/.gitignore31
-rw-r--r--lib/EventFlow/.gitrepo12
-rw-r--r--lib/EventFlow/CMakeLists.txt56
-rw-r--r--lib/EventFlow/include/evfl/Action.h125
-rw-r--r--lib/EventFlow/include/evfl/EvflAllocator.h31
-rw-r--r--lib/EventFlow/include/evfl/Flowchart.h208
-rw-r--r--lib/EventFlow/include/evfl/Param.h208
-rw-r--r--lib/EventFlow/include/evfl/Query.h29
-rw-r--r--lib/EventFlow/include/evfl/ResActor.h187
-rw-r--r--lib/EventFlow/include/evfl/ResEventFlowFile.h36
-rw-r--r--lib/EventFlow/include/evfl/ResFlowchart.h136
-rw-r--r--lib/EventFlow/include/evfl/ResTimeline.h80
-rw-r--r--lib/EventFlow/include/evfl/TimelineObj.h78
-rw-r--r--lib/EventFlow/include/ore/Allocator.h45
-rw-r--r--lib/EventFlow/include/ore/Array.h287
-rw-r--r--lib/EventFlow/include/ore/BinaryFile.h153
-rw-r--r--lib/EventFlow/include/ore/BitUtils.h149
-rw-r--r--lib/EventFlow/include/ore/Buffer.h25
-rw-r--r--lib/EventFlow/include/ore/EnumUtil.h115
-rw-r--r--lib/EventFlow/include/ore/IntrusiveList.h100
-rw-r--r--lib/EventFlow/include/ore/IterRange.h28
-rw-r--r--lib/EventFlow/include/ore/RelocationTable.h50
-rw-r--r--lib/EventFlow/include/ore/ResDic.h62
-rw-r--r--lib/EventFlow/include/ore/ResEndian.h75
-rw-r--r--lib/EventFlow/include/ore/ResMetaData.h58
-rw-r--r--lib/EventFlow/include/ore/StringPool.h20
-rw-r--r--lib/EventFlow/include/ore/StringView.h79
-rw-r--r--lib/EventFlow/include/ore/Types.h20
-rw-r--r--lib/EventFlow/src/evfl/Action.cpp56
-rw-r--r--lib/EventFlow/src/evfl/Flowchart.cpp694
-rw-r--r--lib/EventFlow/src/evfl/FlowchartObj.cpp243
-rw-r--r--lib/EventFlow/src/evfl/Param.cpp714
-rw-r--r--lib/EventFlow/src/evfl/ResActor.cpp89
-rw-r--r--lib/EventFlow/src/evfl/ResEventFlowFile.cpp112
-rw-r--r--lib/EventFlow/src/evfl/ResFlowchart.cpp191
-rw-r--r--lib/EventFlow/src/evfl/ResTimeline.cpp118
-rw-r--r--lib/EventFlow/src/evfl/TimelineObj.cpp235
-rw-r--r--lib/EventFlow/src/ore/BinaryFile.cpp182
-rw-r--r--lib/EventFlow/src/ore/BitUtils.cpp115
-rw-r--r--lib/EventFlow/src/ore/EnumUtil.cpp13
-rw-r--r--lib/EventFlow/src/ore/RelocationTable.cpp106
-rw-r--r--lib/EventFlow/src/ore/ResDic.cpp50
-rw-r--r--lib/EventFlow/src/ore/ResMetaData.cpp89
-rw-r--r--lib/EventFlow/src/ore/StringPool.cpp13
46 files changed, 5578 insertions, 0 deletions
diff --git a/lib/EventFlow b/lib/EventFlow
deleted file mode 160000
-Subproject c35d21b34397bec6dd3c67a649fc66b68839341
diff --git a/lib/EventFlow/.clang-format b/lib/EventFlow/.clang-format
new file mode 100644
index 00000000..cea20307
--- /dev/null
+++ b/lib/EventFlow/.clang-format
@@ -0,0 +1,75 @@
+---
+Language: Cpp
+AccessModifierOffset: -4
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignOperands: true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: Never
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: Inline
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: Yes
+BinPackArguments: true
+BinPackParameters: true
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Attach
+BreakBeforeTernaryOperators: false
+BreakConstructorInitializersBeforeComma: false
+ColumnLimit: 100
+CommentPragmas: '^ (IWYU pragma:|NOLINT)'
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+DisableFormat: false
+ForEachMacros: []
+IncludeCategories:
+ - Regex: '^<[Ww]indows\.h>$'
+ Priority: 1
+ - Regex: '^<'
+ Priority: 2
+ - Regex: '^"'
+ Priority: 3
+IndentCaseLabels: false
+IndentWidth: 4
+IndentWrappedFunctionNames: false
+KeepEmptyLinesAtTheStartOfBlocks: false
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBlockIndentWidth: 4
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+PointerAlignment: Left
+ReflowComments: true
+SortIncludes: true
+SpaceAfterCStyleCast: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 2
+SpacesInAngles: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: c++17
+TabWidth: 4
+UseTab: Never
+WhitespaceSensitiveMacros: ["ORE_ENUM"]
+...
diff --git a/lib/EventFlow/.gitignore b/lib/EventFlow/.gitignore
new file mode 100644
index 00000000..343c32e3
--- /dev/null
+++ b/lib/EventFlow/.gitignore
@@ -0,0 +1,31 @@
+__pycache__/
+*.pyc
+*.egg-info/
+*.dist-info/
+*.so
+*.dll
+dist/
+build/
+bin/
+.mypy_cache/
+.benchmarks/
+
+.idea/
+.vscode/
+
+*.id0
+*.id1
+*.id2
+*.idb
+*.i64
+*.nam
+*.til
+
+main.elf
+
+perf.mData
+perf.mData.old
+.gdb_history
+
+.DS_Store
+tools/aarch64-none-elf-objdump \ No newline at end of file
diff --git a/lib/EventFlow/.gitrepo b/lib/EventFlow/.gitrepo
new file mode 100644
index 00000000..b5109f06
--- /dev/null
+++ b/lib/EventFlow/.gitrepo
@@ -0,0 +1,12 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://github.com/open-ead/EventFlow
+ branch = master
+ commit = c35d21b34397bec6dd3c67a649fc66b68839341e
+ parent = ffcc7f659ebc9bc9d149e52bec553b906bb47369
+ method = merge
+ cmdver = 0.4.3
diff --git a/lib/EventFlow/CMakeLists.txt b/lib/EventFlow/CMakeLists.txt
new file mode 100644
index 00000000..394c6ffa
--- /dev/null
+++ b/lib/EventFlow/CMakeLists.txt
@@ -0,0 +1,56 @@
+project(EventFlow CXX ASM)
+
+option(EVFL_VER_LABO "Build Labo's version of this library" OFF)
+
+add_library(evfl OBJECT
+ include/evfl/Action.h
+ include/evfl/EvflAllocator.h
+ include/evfl/Flowchart.h
+ include/evfl/Param.h
+ include/evfl/Query.h
+ include/evfl/ResActor.h
+ include/evfl/ResEventFlowFile.h
+ include/evfl/ResFlowchart.h
+ include/evfl/ResTimeline.h
+ include/evfl/TimelineObj.h
+ include/ore/Allocator.h
+ include/ore/Array.h
+ include/ore/BinaryFile.h
+ include/ore/BitUtils.h
+ include/ore/Buffer.h
+ include/ore/EnumUtil.h
+ include/ore/IntrusiveList.h
+ include/ore/IterRange.h
+ include/ore/RelocationTable.h
+ include/ore/ResDic.h
+ include/ore/ResEndian.h
+ include/ore/ResMetaData.h
+ include/ore/StringPool.h
+ include/ore/StringView.h
+ include/ore/Types.h
+ src/evfl/Action.cpp
+ src/evfl/Param.cpp
+ src/evfl/Flowchart.cpp
+ src/evfl/FlowchartObj.cpp
+ src/evfl/TimelineObj.cpp
+ src/evfl/ResEventFlowFile.cpp
+ src/evfl/ResTimeline.cpp
+ src/evfl/ResActor.cpp
+ src/evfl/ResFlowchart.cpp
+ src/ore/BitUtils.cpp
+ src/ore/EnumUtil.cpp
+ src/ore/BinaryFile.cpp
+ src/ore/RelocationTable.cpp
+ src/ore/StringPool.cpp
+ src/ore/ResDic.cpp
+ src/ore/ResMetaData.cpp
+)
+
+target_compile_options(evfl PRIVATE -fno-exceptions)
+target_compile_options(evfl PRIVATE -fno-strict-aliasing)
+target_compile_options(evfl PRIVATE -Wno-invalid-offsetof)
+target_include_directories(evfl PUBLIC include/)
+
+if(EVFL_VER_LABO)
+ target_compile_definitions(evfl PUBLIC EVFL_VER_LABO=1)
+endif()
diff --git a/lib/EventFlow/include/evfl/Action.h b/lib/EventFlow/include/evfl/Action.h
new file mode 100644
index 00000000..88dea6dd
--- /dev/null
+++ b/lib/EventFlow/include/evfl/Action.h
@@ -0,0 +1,125 @@
+#pragma once
+
+#include <evfl/Param.h>
+#include <ore/EnumUtil.h>
+#include <ore/IntrusiveList.h>
+#include <utility>
+
+namespace evfl {
+
+class FlowchartContext;
+class FlowchartContextNode;
+class FlowchartObj;
+struct ResClip;
+struct ResEvent;
+struct ResOneshot;
+class TimelineObj;
+class VariablePack;
+
+ORE_VALUED_ENUM(TriggerType, kFlowchart = 0, kClipEnter = 1, kClipLeave = 2, kOneshot = 3,
+ kNormal = 0, kEnter = 1, kLeave = 2)
+
+class ActionDoneHandler {
+public:
+ ActionDoneHandler() = default;
+ ActionDoneHandler(FlowchartObj* obj, FlowchartContext* context, int node_idx);
+ explicit ActionDoneHandler(TimelineObj* obj) : m_is_flowchart(false) { m_timeline_obj = obj; }
+ ~ActionDoneHandler() { m_list_node.Erase(); }
+
+ ActionDoneHandler(const ActionDoneHandler&) = delete;
+ auto operator=(const ActionDoneHandler&) = delete;
+
+ ActionDoneHandler(ActionDoneHandler&& other) noexcept { *this = std::move(other); }
+
+ ActionDoneHandler& operator=(ActionDoneHandler&& other) noexcept {
+ m_context = other.m_context;
+ m_node_idx = other.m_node_idx;
+ m_obj = other.m_obj;
+ m_node_counter = other.m_node_counter;
+ m_handled = other.m_handled;
+ m_is_flowchart = other.m_is_flowchart;
+ m_list_node = std::move(other.m_list_node);
+
+ other.m_context = nullptr;
+ other.m_node_idx = -1;
+ other.m_node_counter = -1;
+ other.m_obj = nullptr;
+ other.m_handled = false;
+ other.m_is_flowchart = true;
+
+ return *this;
+ }
+
+ FlowchartContextNode* GetContextNode();
+ void InvokeFromFlowchartImpl();
+ void InvokeFromTimelineImpl();
+ bool IsWaitingJoin();
+ bool CancelWaiting();
+
+ void Reset() {
+ m_context = nullptr;
+ m_obj = nullptr;
+ m_node_idx = -1;
+ m_node_counter = -1;
+ m_handled = false;
+ m_is_flowchart = true;
+ }
+
+ static constexpr size_t GetListNodeOffset() { return offsetof(ActionDoneHandler, m_list_node); }
+
+private:
+ friend class FlowchartContext;
+
+ ore::IntrusiveListNode m_list_node;
+ FlowchartContext* m_context = nullptr;
+ int m_node_idx = -1;
+ int m_node_counter = -1;
+ union {
+ FlowchartObj* m_obj = nullptr;
+ TimelineObj* m_timeline_obj;
+ };
+ bool m_handled = false;
+ bool m_is_flowchart = true;
+};
+
+struct ActionArg {
+ ActionArg(FlowchartContext* context, int node_idx, void* actor_user_data_,
+ void* action_user_data_, VariablePack* variable_pack_, const ResEvent* event_)
+ : param_accessor(context, node_idx), actor_user_data(actor_user_data_),
+ action_user_data(action_user_data_), flowchart_ctx(context),
+ variable_pack(variable_pack_), res(event_), timeline_time_delta(0.0),
+ trigger_type(TriggerType::kFlowchart) {}
+
+ ActionArg(const ResClip* clip, void* actor_user_data_, void* action_user_data_,
+ float timeline_time_delta_, TriggerType::Type type, const ore::ResMetaData* params)
+ : param_accessor(params), actor_user_data(actor_user_data_),
+ action_user_data(action_user_data_), res(clip), timeline_time_delta(timeline_time_delta_),
+ trigger_type(type) {}
+
+ ActionArg(const ResOneshot* oneshot, void* actor_user_data_, void* action_user_data_,
+ float timeline_time_delta_, const ore::ResMetaData* params)
+ : param_accessor(params), actor_user_data(actor_user_data_),
+ action_user_data(action_user_data_), res(oneshot),
+ timeline_time_delta(timeline_time_delta_), trigger_type(TriggerType::kOneshot) {
+ res.oneshot = oneshot;
+ }
+
+ ParamAccessor param_accessor;
+ void* actor_user_data = nullptr;
+ void* action_user_data = nullptr;
+ FlowchartContext* flowchart_ctx = nullptr;
+ VariablePack* variable_pack = nullptr;
+ union Res {
+ explicit Res(const ResEvent* e) : event(e) {}
+ explicit Res(const ResClip* c) : clip(c) {}
+ explicit Res(const ResOneshot* o) : oneshot(o) {}
+
+ const ResEvent* event;
+ const ResClip* clip;
+ const ResOneshot* oneshot;
+ } res;
+ float timeline_time_delta{};
+ TriggerType::Type trigger_type{};
+};
+
+} // namespace evfl
diff --git a/lib/EventFlow/include/evfl/EvflAllocator.h b/lib/EventFlow/include/evfl/EvflAllocator.h
new file mode 100644
index 00000000..e81d4441
--- /dev/null
+++ b/lib/EventFlow/include/evfl/EvflAllocator.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <ore/Allocator.h>
+
+namespace evfl {
+
+struct AllocateArg {
+ void* (*alloc)(size_t size, size_t alignment, void* userdata);
+ void (*free)(void* ptr, void* userdata);
+ void* alloc_userdata;
+ void* free_userdata;
+};
+
+class EvflAllocator : public ore::Allocator {
+public:
+ EvflAllocator() = default;
+ explicit EvflAllocator(AllocateArg arg) : m_arg(arg) {}
+
+ void* AllocImpl(size_t size, size_t alignment) override {
+ return m_arg.alloc(size, alignment, m_arg.alloc_userdata);
+ }
+
+ void FreeImpl(void* ptr) override { m_arg.free(ptr, m_arg.free_userdata); }
+
+ AllocateArg GetArg() const { return m_arg; }
+
+private:
+ AllocateArg m_arg;
+};
+
+} // namespace evfl
diff --git a/lib/EventFlow/include/evfl/Flowchart.h b/lib/EventFlow/include/evfl/Flowchart.h
new file mode 100644
index 00000000..4bc40a17
--- /dev/null
+++ b/lib/EventFlow/include/evfl/Flowchart.h
@@ -0,0 +1,208 @@
+#pragma once
+
+#include <evfl/EvflAllocator.h>
+#include <evfl/ResActor.h>
+#include <ore/Array.h>
+#include <ore/Buffer.h>
+#include <ore/EnumUtil.h>
+#include <ore/IntrusiveList.h>
+#include <ore/IterRange.h>
+#include <ore/StringView.h>
+#include <ore/Types.h>
+
+namespace ore {
+class Allocator;
+class BitArray;
+} // namespace ore
+
+namespace evfl {
+
+class MetaDataPack;
+struct ResEntryPoint;
+struct ResEvent;
+struct ResFlowchart;
+class VariablePack;
+
+ORE_ENUM(SubFlowCallbackType, kEnter, kLeave)
+
+class FlowchartObj {
+public:
+ class Builder {
+ public:
+ Builder(const ResFlowchart* flowchart, ore::BitArray* visited_entry_points)
+ : m_flowchart(flowchart), m_entry_points_mask(visited_entry_points) {}
+
+ bool Build(FlowchartObj* obj, ore::Allocator* allocator,
+ ore::IterRange<const ResFlowchart* const*> flowcharts);
+
+ private:
+ const ResFlowchart* m_flowchart{};
+ ore::BitArray* m_entry_points_mask{};
+ ActBinder::Builder m_act_binder_builder{};
+ };
+
+#ifdef MATCHING_HACK_NX_CLANG
+ [[gnu::always_inline]]
+#endif
+ ~FlowchartObj() = default;
+
+ const ResFlowchart* GetFlowchart() const { return m_flowchart; }
+ const ActBinder& GetActBinder() const { return m_act_binder; }
+ ActBinder& GetActBinder() { return m_act_binder; }
+
+private:
+ const ResFlowchart* m_flowchart{};
+ ActBinder m_act_binder;
+};
+
+class FlowchartContextNode {
+public:
+ ORE_ENUM(State, kInvalid, kFree, kNotInvoked, kInvoked, kDone, kWaiting)
+
+ FlowchartContextNode() { Reset(); }
+
+ bool IsInvalidOrFree() const { return m_state == State::kInvalid || m_state == State::kFree; }
+ FlowchartObj* GetObj() const { return m_obj; }
+ VariablePack* GetVariablePack() const { return m_variable_pack; }
+ int GetNodeCounter() const { return m_node_counter; }
+ u16 GetEventIdx() const { return m_event_idx; }
+ u16 GetNextNodeIdx() const { return m_next_node_idx; }
+ State::Type GetState() const { return m_state; }
+
+ void Reset() {
+ m_node_counter = -1;
+ m_obj = nullptr;
+ m_event_idx = -1;
+ m_next_node_idx = -1;
+ m_idx = -1;
+ m_state = State::kInvalid;
+ m_variable_pack = nullptr;
+ m_owns_variable_pack = false;
+ }
+
+private:
+ friend class ActionDoneHandler;
+ friend class FlowchartContext;
+
+ FlowchartObj* m_obj;
+ VariablePack* m_variable_pack;
+ int m_node_counter;
+ u16 m_event_idx;
+ u16 m_next_node_idx;
+ u16 m_idx;
+ ore::SizedEnum<State::Type, u8> m_state;
+ bool m_owns_variable_pack;
+};
+
+class FlowchartContext {
+public:
+ class Builder {
+ public:
+ using FlowchartRange = ore::IterRange<const ResFlowchart* const*>;
+ ORE_ENUM(BuildResultType, kSuccess, kInvalidOperation, kResFlowchartNotFound, kEntryPointNotFound)
+
+ struct BuildResult {
+ BuildResultType::Type result;
+ /// Indicates which flowchart was required yet couldn't be found.
+ ore::StringView missing_flowchart_name{};
+ /// Indicates which entry point was required yet couldn't be found.
+ ore::StringView missing_entry_point_name{};
+ };
+
+ Builder() = default;
+ explicit Builder(FlowchartRange flowcharts, int flowchart_idx = 0)
+ : m_flowcharts(flowcharts), m_flowchart_idx(flowchart_idx) {}
+
+ bool SetEntryPoint(const ore::StringView& flowchart_name,
+ const ore::StringView& entry_point_name);
+ bool SetEntryPoint(BuildResult* result, const ore::StringView& flowchart_name,
+ const ore::StringView& entry_point_name);
+
+ bool Build(FlowchartContext* context, AllocateArg allocate_arg);
+ bool Build(BuildResult* result, FlowchartContext* context, AllocateArg allocate_arg);
+
+ private:
+ bool BuildImpl(BuildResult* result, FlowchartRange flowcharts, FlowchartContext* context,
+ AllocateArg allocate_arg, ore::Buffer flowchart_obj_buffer);
+
+ FlowchartRange m_flowcharts{};
+ int m_flowchart_idx = 0;
+ int m_entry_point_idx = -1;
+ };
+
+ FlowchartContext();
+ ~FlowchartContext() {
+ Dispose();
+ m_objs.DestructElements();
+ }
+
+ FlowchartContext(const FlowchartContext&) = delete;
+ auto operator=(const FlowchartContext&) = delete;
+
+ void Start(MetaDataPack* pack);
+ int AllocNode();
+ void AllocVariablePack(FlowchartContextNode& node, const ResEntryPoint& entry_point);
+ void ProcessContext();
+ FlowchartObj* FindFlowchartObj(ore::StringView name);
+ const FlowchartObj* FindFlowchartObj(ore::StringView name) const;
+ void UnbindAll();
+ void Clear();
+ void FreeVariablePack(FlowchartContextNode& node);
+ void CopyVariablePack(FlowchartContextNode& src, FlowchartContextNode& dst);
+ bool ProcessContextNode(int node_idx);
+ ActorBinding* TrackBackArgumentActor(int node_idx, const ore::StringView& name);
+ bool IsUsing(const ResFlowchart* flowchart) const;
+ bool IsPlaying(const ResFlowchart* flowchart) const;
+ const ore::Array<ActorBinding>* GetUsedResActors(ore::StringView flowchart_name) const;
+
+ FlowchartContextNode& GetNode(int idx) { return m_nodes[idx]; }
+ const FlowchartContextNode& GetNode(int idx) const { return m_nodes[idx]; }
+ MetaDataPack* GetMetaDataPack() const { return m_metadata_pack; }
+ ore::Array<FlowchartObj>& GetObjs() { return m_objs; }
+ const ore::Array<FlowchartObj>& GetObjs() const { return m_objs; }
+
+private:
+ void Dispose() {
+ m_obj_idx = -1;
+ m_active_entry_point_idx = -1;
+ m_metadata_pack = nullptr;
+ m_objs.Clear(&m_allocator);
+ for (auto& node : m_nodes)
+ FreeVariablePack(node);
+ m_nodes.Reset();
+ }
+
+ void UpdateNodeCounter(int node_idx) {
+ auto& node = GetNode(node_idx);
+ node.m_node_counter = ++s_GlobalCounter;
+ }
+
+ void CallSubFlowCallback(const ResFlowchart* flowchart, const ResEvent* event,
+ SubFlowCallbackType::Type type) {
+#ifdef EVFL_VER_LABO
+ if (m_on_sub_flow_callback)
+ m_on_sub_flow_callback(this, flowchart, event, type);
+#endif
+ }
+
+ static int s_GlobalCounter;
+
+ EvflAllocator m_allocator;
+ ore::Array<FlowchartObj> m_objs;
+ ore::DynArrayList<FlowchartContextNode> m_nodes;
+ ore::IntrusiveList<ActionDoneHandler> m_handlers;
+#ifdef EVFL_VER_LABO
+ void (*m_on_sub_flow_callback)(FlowchartContext* context, const ResFlowchart* flowchart,
+ const ResEvent* event, SubFlowCallbackType::Type type) = nullptr;
+#endif
+ MetaDataPack* m_metadata_pack = nullptr;
+ void* _78 = nullptr;
+ int m_next_node_idx = 0;
+ int m_num_allocated_nodes = 0;
+ int m_obj_idx = -1;
+ int m_active_entry_point_idx = -1;
+ bool m_is_processing = false;
+ u8 _91 = 0;
+};
+
+} // namespace evfl
diff --git a/lib/EventFlow/include/evfl/Param.h b/lib/EventFlow/include/evfl/Param.h
new file mode 100644
index 00000000..439aba28
--- /dev/null
+++ b/lib/EventFlow/include/evfl/Param.h
@@ -0,0 +1,208 @@
+#pragma once
+
+#include <evfl/EvflAllocator.h>
+#include <ore/Array.h>
+#include <ore/BinaryFile.h>
+#include <ore/Buffer.h>
+#include <ore/EnumUtil.h>
+#include <ore/IterRange.h>
+#include <ore/ResMetaData.h>
+#include <ore/StringView.h>
+#include <ore/Types.h>
+
+namespace ore {
+struct ResDic;
+struct ResMetaData;
+} // namespace ore
+
+namespace evfl {
+
+class FlowchartContext;
+class FlowchartObj;
+struct ResEntryPoint;
+
+class MetaDataPack {
+public:
+ ORE_ENUM(DataType, kInt, kBool, kFloat, kString, kWString)
+
+ struct Entry {
+ union Value {
+ bool b;
+ int i;
+ float f;
+ const char* str;
+ const wchar_t* wstr;
+ };
+
+ bool IsKey(const ore::StringView& other) const { return ore::StringView(key) == other; }
+
+ const char* key;
+ Value value;
+ DataType::Type type;
+ };
+
+ class Builder {
+ public:
+ void CalcMemSize();
+ bool Build(MetaDataPack* pack, ore::Buffer buffer);
+ void SetNumEntries(int num) { m_num_entries = num; }
+ int GetRequiredSize() const { return m_required_size; }
+
+ private:
+ int m_num_entries = 16;
+ int m_required_size = 0;
+ int m_alignment_real = 16;
+ int m_entries_byte_size = 0;
+ int m_alignment = 16;
+ int _14 = 0;
+ int m_buffer_offset = -1;
+ };
+
+ void AddInt(const char* key, int value);
+ void AddBool(const char* key, bool value);
+ void AddFloat(const char* key, float value);
+ void AddStringPtr(const char* key, const char* value);
+ void AddWStringPtr(const char* key, const wchar_t* value);
+
+ bool FindInt(int* value, const ore::StringView& key) const;
+ bool FindBool(bool* value, const ore::StringView& key) const;
+ bool FindFloat(float* value, const ore::StringView& key) const;
+ bool FindString(ore::StringView* value, const ore::StringView& key) const;
+ bool FindWString(ore::WStringView* value, const ore::StringView& key) const;
+
+ ore::ResMetaData::DataType::Type GetType(const ore::StringView& key) const;
+
+private:
+ Entry* Find(const ore::StringView& key) const;
+ Entry& AddEntry() { return m_entries[m_entries_num++]; }
+
+ ore::Array<Entry> GetEntries() const { return {m_entries, m_entries_num}; }
+
+ ore::Buffer m_buffer{};
+ Entry* m_entries{};
+ int m_entries_num{};
+ int m_entries_capacity{};
+};
+
+class VariablePack {
+public:
+ using VariableType = ore::ResMetaData::DataType::Type;
+
+ struct Entry {
+ union Value {
+ void* dummy;
+ int i;
+ float f;
+ ore::DynArrayList<int>* int_array;
+ ore::DynArrayList<float>* float_array;
+ };
+
+ Value value;
+ VariableType type;
+ };
+
+ VariablePack();
+ ~VariablePack();
+ VariablePack(const VariablePack&) = delete;
+ auto operator=(const VariablePack&) = delete;
+
+ void Init(AllocateArg arg, const ResEntryPoint* entry_point);
+
+ Entry* GetVariableEntry(const ore::StringView& name);
+ const Entry* GetVariableEntry(const ore::StringView& name) const;
+ VariableType GetVariableType(const ore::StringView& name) const;
+ ore::StringView GetVariableName(int idx) const;
+ int GetVariableCount() const;
+
+ bool Contains(const ore::StringView& name) const;
+
+ bool FindInt(int* value, const ore::StringView& name) const;
+ bool FindBool(bool* value, const ore::StringView& name) const;
+ bool FindFloat(float* value, const ore::StringView& name) const;
+ bool FindIntList(ore::DynArrayList<int>** value, const ore::StringView& name) const;
+ bool FindFloatList(ore::DynArrayList<float>** value, const ore::StringView& name) const;
+
+ int GetInt(const ore::StringView& name) const;
+ bool GetBool(const ore::StringView& name) const;
+ float GetFloat(const ore::StringView& name) const;
+ ore::DynArrayList<int>* GetIntList(const ore::StringView& name) const;
+ ore::DynArrayList<float>* GetFloatList(const ore::StringView& name) const;
+
+ void SetInt(const ore::StringView& name, int value);
+ void SetFloat(const ore::StringView& name, float value);
+ void SetBool(const ore::StringView& name, bool value);
+
+private:
+ void Dispose();
+ ore::Allocator* GetAllocator() { return &m_allocator; }
+
+ const ore::ResDic* m_names = nullptr;
+ ore::Array<Entry> m_variables;
+ EvflAllocator m_allocator;
+};
+
+class ParamAccessor {
+public:
+ using Type = ore::ResMetaData::DataType::Type;
+ using IntRange = ore::IterRange<const int*>;
+ using FloatRange = ore::IterRange<const float*>;
+ using StringRange = ore::IterRange<const ore::BinTPtr<ore::BinString>*>;
+ using WStringRange = ore::IterRange<const ore::BinTPtr<ore::BinWString>*>;
+
+ explicit ParamAccessor(const ore::ResMetaData* metadata);
+ explicit ParamAccessor(const FlowchartContext* context, int node_idx);
+
+ const ore::ResMetaData* GetFrontResMetaData() const;
+ int GetParamCount() const;
+ ore::StringView GetParamName(int idx) const;
+ Type GetParamType(int idx) const;
+
+ /// @param out_metadata Must be nonnull.
+ /// @param out_metadata_pack Must be nonnull.
+ /// @param out_variable_pack Must be nonnull.
+ /// @param out_obj May be null.
+ /// @param argument Name of the argument to search for.
+ ore::StringView TrackBackArgument(const ore::ResMetaData** out_metadata,
+ const MetaDataPack** out_metadata_pack,
+ const VariablePack** out_variable_pack,
+ FlowchartObj** out_obj,
+ const ore::StringView& argument) const;
+
+ int GetInt(int idx) const;
+ bool FindInt(int* value, const ore::StringView& name) const;
+
+ bool GetBool(int idx) const;
+ bool FindBool(bool* value, const ore::StringView& name) const;
+
+ float GetFloat(int idx) const;
+ bool FindFloat(float* value, const ore::StringView& name) const;
+
+ ore::StringView GetString(int idx) const;
+ bool FindString(ore::StringView* value, const ore::StringView& name) const;
+
+ ore::WStringView GetWString(int idx) const;
+ bool FindWString(ore::WStringView* value, const ore::StringView& name) const;
+
+ IntRange GetIntArray(int idx) const;
+ bool FindIntArray(IntRange* value, const ore::StringView& name) const;
+
+ FloatRange GetFloatArray(int idx) const;
+ bool FindFloatArray(FloatRange* value, const ore::StringView& name) const;
+
+ StringRange GetStringArray(int idx) const;
+ bool FindStringArray(StringRange* value, const ore::StringView& name) const;
+
+ WStringRange GetWStringArray(int idx) const;
+ bool FindWStringArray(WStringRange* value, const ore::StringView& name) const;
+
+ bool FindActorIdentifier(ore::StringView* actor_name, ore::StringView* actor_sub_name,
+ const ore::StringView& name) const;
+
+private:
+ const ore::ResMetaData* m_metadata = nullptr;
+ const FlowchartContext* m_context = nullptr;
+ int m_node_idx = -1;
+ u32 m_node_counter = -1;
+};
+
+} // namespace evfl
diff --git a/lib/EventFlow/include/evfl/Query.h b/lib/EventFlow/include/evfl/Query.h
new file mode 100644
index 00000000..979907c7
--- /dev/null
+++ b/lib/EventFlow/include/evfl/Query.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <evfl/Param.h>
+#include <ore/EnumUtil.h>
+
+namespace evfl {
+
+class FlowchartContext;
+struct ResEvent;
+class VariablePack;
+
+ORE_ENUM(QueryValueType, kBool, kInt, kFloat, kString, kConst)
+
+struct QueryArg {
+ QueryArg(FlowchartContext* context, int node_idx, void* actor_user_data_,
+ void* query_user_data_, const ResEvent* event_, VariablePack* variable_pack_)
+ : actor_user_data(actor_user_data_), query_user_data(query_user_data_), main_event(event_),
+ flowchart_ctx(context), variable_pack(variable_pack_), param_accessor(context, node_idx) {
+ }
+
+ void* actor_user_data;
+ void* query_user_data;
+ const ResEvent* main_event;
+ FlowchartContext* flowchart_ctx;
+ VariablePack* variable_pack;
+ ParamAccessor param_accessor;
+};
+
+} // namespace evfl
diff --git a/lib/EventFlow/include/evfl/ResActor.h b/lib/EventFlow/include/evfl/ResActor.h
new file mode 100644
index 00000000..5a43e1a4
--- /dev/null
+++ b/lib/EventFlow/include/evfl/ResActor.h
@@ -0,0 +1,187 @@
+#pragma once
+
+#include <cstddef>
+#include <ore/Array.h>
+#include <ore/BinaryFile.h>
+#include <ore/IterRange.h>
+#include <utility>
+
+namespace ore {
+struct ResEndian;
+struct ResMetaData;
+} // namespace ore
+
+namespace evfl {
+
+struct ActionArg;
+class ActionDoneHandler;
+class FlowchartContext;
+class FlowchartContextNode;
+class FlowchartObj;
+struct QueryArg;
+
+struct ResAction {
+ ore::BinTPtr<ore::BinString> name;
+};
+
+struct ResQuery {
+ ore::BinTPtr<ore::BinString> name;
+};
+
+struct ResActor {
+ bool HasArgumentName() const { return !argument_name.Get()->empty(); }
+
+ ore::BinTPtr<ore::BinString> name;
+ ore::BinTPtr<ore::BinString> secondary_name;
+ ore::BinTPtr<ore::BinString> argument_name;
+ ore::BinTPtr<ResAction> actions;
+ ore::BinTPtr<ResQuery> queries;
+ ore::BinTPtr<ore::ResMetaData> params;
+ u16 num_actions;
+ u16 num_queries;
+ /// Entry point index for assicated entry point (0xffff if none)
+ u16 entry_point_idx;
+ // TODO: Cut number? This is set to 1 for flowcharts but other values have been seen
+ // for timeline actors.
+ u8 cut_number;
+};
+
+using ActionHandler = void (*)(const ActionArg& arg, ActionDoneHandler done_handler);
+using QueryHandler = int (*)(const QueryArg& arg);
+
+class ActorBinding {
+public:
+ struct Action {
+ ActionHandler handler{};
+ void* user_data{};
+ const ResAction* res_action{};
+ };
+
+ struct Query {
+ QueryHandler handler{};
+ void* user_data{};
+ const ResQuery* res_query{};
+ };
+
+ ActorBinding() = default;
+
+ void Register(const ResAction* action);
+ void Register(const ResQuery* query);
+
+ // Similar to std::find_if. Returns m_actions.end() if the specified action is not found.
+ Action* GetAction(const ore::StringView& name);
+ const Action* GetAction(const ore::StringView& name) const;
+ Query* GetQuery(const ore::StringView& name);
+ const Query* GetQuery(const ore::StringView& name) const;
+
+ ore::DynArrayList<Action>& GetActions() { return m_actions; }
+ ore::DynArrayList<Query>& GetQueries() { return m_queries; }
+ const ore::DynArrayList<Action>& GetActions() const { return m_actions; }
+ const ore::DynArrayList<Query>& GetQueries() const { return m_queries; }
+ auto ActionsEnd() const { return m_actions.end(); }
+ auto QueriesEnd() const { return m_queries.end(); }
+
+ auto GetActor() const { return m_actor; }
+
+ void* GetUserData() const { return m_user_data; }
+ void SetUserData(void* user_data) { m_user_data = user_data; }
+
+ bool IsInitialized() const { return m_initialized; }
+ bool IsUsed() const { return m_is_used; }
+ void SetInitialized(bool initialized) { m_initialized = initialized; }
+ void SetIsUsed(bool used) { m_is_used = used; }
+
+ void UnbindAll() {
+ m_user_data = nullptr;
+ m_initialized = false;
+ for (auto it = m_actions.begin(); it != m_actions.end(); ++it) {
+ it->handler = nullptr;
+ it->user_data = nullptr;
+ }
+ for (auto it = m_queries.begin(); it != m_queries.end(); ++it) {
+ it->handler = nullptr;
+ it->user_data = nullptr;
+ }
+ }
+
+private:
+ friend class ActBinder;
+
+ ore::DynArrayList<Action> m_actions{};
+ ore::DynArrayList<Query> m_queries{};
+ void* m_user_data{};
+ const ResActor* m_actor{};
+ bool m_initialized{};
+ bool m_is_used{};
+};
+
+class ActBinder {
+public:
+ class Builder {
+ public:
+ bool Build(evfl::ActBinder* binder, ore::Allocator* allocator,
+ ore::IterRange<const ResActor*> actors);
+ };
+
+ ActBinder() = default;
+ ActBinder(const ActBinder&) = delete;
+ auto operator=(const ActBinder&) = delete;
+
+#ifdef MATCHING_HACK_NX_CLANG
+ [[gnu::always_inline]]
+#endif
+ ~ActBinder() {
+ Reset();
+ }
+
+ u32 GetEventUsedActorCount() const { return m_event_used_actor_count; }
+ const ore::Array<ActorBinding>* GetUsedResActors() const;
+
+ ore::Array<ActorBinding>& GetBindings() { return m_bindings; }
+ const ore::Array<ActorBinding>& GetBindings() const { return m_bindings; }
+ void IncrementNumActors() { ++m_event_used_actor_count; }
+ void SetIsUsed() { m_is_used = true; }
+ bool IsUsed() const { return m_is_used; }
+
+ void RegisterAction(int actor_idx, const evfl::ResAction* action) {
+ auto& binding = GetBindings()[actor_idx];
+ if (!binding.IsUsed() && binding.GetActor()->argument_name.Get()->empty()) {
+ IncrementNumActors();
+ binding.SetIsUsed(true);
+ }
+ binding.Register(action);
+ }
+
+ void RegisterQuery(int actor_idx, const evfl::ResQuery* query) {
+ auto& binding = GetBindings()[actor_idx];
+ if (!binding.IsUsed() && binding.GetActor()->argument_name.Get()->empty()) {
+ IncrementNumActors();
+ binding.SetIsUsed(true);
+ }
+ binding.Register(query);
+ }
+
+ void UnbindAll() {
+ for (auto it = m_bindings.begin(); it != m_bindings.end(); ++it)
+ it->UnbindAll();
+ }
+
+ void Reset() {
+ m_event_used_actor_count = 0;
+ if (auto* data = m_bindings.data()) {
+ m_bindings.ClearWithoutFreeing();
+ m_allocator->Free(data);
+ }
+ m_allocator = nullptr;
+ }
+
+private:
+ u32 m_event_used_actor_count{};
+ ore::Allocator* m_allocator{};
+ ore::SelfDestructingArray<ActorBinding> m_bindings{};
+ bool m_is_used{};
+};
+
+void SwapEndian(ore::ResEndian* endian, ResActor* actor);
+
+} // namespace evfl
diff --git a/lib/EventFlow/include/evfl/ResEventFlowFile.h b/lib/EventFlow/include/evfl/ResEventFlowFile.h
new file mode 100644
index 00000000..30d8217c
--- /dev/null
+++ b/lib/EventFlow/include/evfl/ResEventFlowFile.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include <ore/BinaryFile.h>
+#include <ore/Types.h>
+
+namespace ore {
+struct ResDic;
+struct ResEndian;
+} // namespace ore
+
+namespace evfl {
+
+struct ResFlowchart;
+struct ResTimeline;
+
+struct ResEventFlowFile {
+ /// data must be a pointer to a buffer of size >= 0x20.
+ static bool IsValid(void* data);
+ /// data must be a valid ResEventFlowFile.
+ static ResEventFlowFile* ResCast(void* data);
+
+ void Relocate();
+ void Unrelocate();
+
+ ore::BinaryFileHeader header;
+ u16 num_flowcharts;
+ u16 num_timelines;
+ ore::BinTPtr<ore::BinTPtr<ResFlowchart>> flowcharts;
+ ore::BinTPtr<ore::ResDic> flowchart_names;
+ ore::BinTPtr<ore::BinTPtr<ResTimeline>> timelines;
+ ore::BinTPtr<ore::ResDic> timeline_names;
+};
+
+void SwapEndian(ore::ResEndian* endian, ResEventFlowFile* file);
+
+} // namespace evfl
diff --git a/lib/EventFlow/include/evfl/ResFlowchart.h b/lib/EventFlow/include/evfl/ResFlowchart.h
new file mode 100644
index 00000000..99517bd2
--- /dev/null
+++ b/lib/EventFlow/include/evfl/ResFlowchart.h
@@ -0,0 +1,136 @@
+#pragma once
+
+#include <ore/BinaryFile.h>
+#include <ore/EnumUtil.h>
+#include <ore/ResDic.h>
+#include <ore/ResMetaData.h>
+#include <ore/StringView.h>
+
+namespace ore {
+struct ResEndian;
+}
+
+namespace evfl {
+
+struct ResActor;
+
+struct ResCase {
+ u32 value;
+ u16 event_idx;
+};
+
+struct ResEvent {
+ ORE_ENUM(EventType, kAction, kSwitch, kFork, kJoin, kSubFlow)
+
+ ore::BinTPtr<ore::BinString> name;
+ ore::SizedEnum<EventType::Type, u8> type;
+
+ union {
+ // Action, Join, Sub flow
+ u16 next_event_idx;
+ // Switch
+ u16 num_cases;
+ // Fork
+ u16 num_forks;
+ };
+
+ union {
+ // Action, Switch
+ u16 actor_idx;
+ // Fork
+ u16 join_event_idx;
+ };
+
+ union {
+ // Action
+ u16 actor_action_idx;
+ // Switch
+ u16 actor_query_idx;
+ };
+
+ union {
+ // Action, Switch, Sub flow
+ ore::BinTPtr<ore::ResMetaData> params;
+ // Fork
+ ore::BinTPtr<u16> fork_event_indices;
+ };
+
+ union {
+ // Switch
+ ore::BinTPtr<ResCase> cases;
+ // Sub flow
+ ore::BinTPtr<ore::BinString> sub_flow_flowchart;
+ };
+
+ union {
+ // Sub flow
+ ore::BinTPtr<ore::BinString> sub_flow_entry_point;
+ };
+};
+
+struct ResVariableDef {
+ union Value {
+ // Also used for booleans. Anything that is != 0 is treated as true.
+ int i;
+ float f;
+ ore::BinTPtr<int> int_array;
+ ore::BinTPtr<float> float_array;
+ };
+
+ Value value;
+ u16 num;
+ ore::SizedEnum<ore::ResMetaData::DataType::Type, u8> type;
+};
+
+struct ResEntryPoint {
+ ore::BinTPtr<u16> sub_flow_event_indices;
+ ore::BinTPtr<ore::ResDic> variable_defs_names;
+ ore::BinTPtr<ResVariableDef> variable_defs;
+ u16 num_sub_flow_event_indices;
+ u16 num_variable_defs;
+ u16 main_event_idx;
+};
+
+struct ResFlowchart {
+ int CountEvent(ResEvent::EventType::Type type) const;
+
+ const ResEntryPoint* GetEntryPoint(const ore::StringView& entry_point_name) const {
+ const int idx = entry_point_names.Get()->FindIndex(entry_point_name);
+ if (idx == -1)
+ return nullptr;
+
+ return entry_points.Get() + idx;
+ }
+
+ ore::StringView GetEntryPointName(int idx) const {
+ return entry_point_names.Get()->GetEntries()[1 + idx].GetKey();
+ }
+
+ /// 'EVFL'
+ u32 magic;
+ /// String pool offset (relative to this structure)
+ u32 string_pool_offset;
+ u32 reserved_8;
+ u32 reserved_c;
+ u16 num_actors;
+ u16 num_actions;
+ u16 num_queries;
+ u16 num_events;
+ u16 num_entry_points;
+ u16 reserved_1a;
+ u16 reserved_1c;
+ u16 reserved_1e;
+ ore::BinTPtr<ore::BinString> name;
+ ore::BinTPtr<ResActor> actors;
+ ore::BinTPtr<ResEvent> events;
+ ore::BinTPtr<ore::ResDic> entry_point_names;
+ ore::BinTPtr<ResEntryPoint> entry_points;
+};
+
+void SwapEndian(ore::ResEndian* endian, ResCase* case_);
+void SwapEndian(ore::ResEndian* endian, ResEvent* event);
+void SwapEndian(ore::ResEndian* endian, ResEntryPoint* entry);
+void SwapEndian(ore::ResEndian* endian, ResVariableDef* def);
+void SwapEndian(ore::ResEndian* endian, ResFlowchart* flowchart);
+
+} // namespace evfl
diff --git a/lib/EventFlow/include/evfl/ResTimeline.h b/lib/EventFlow/include/evfl/ResTimeline.h
new file mode 100644
index 00000000..f6a45ff8
--- /dev/null
+++ b/lib/EventFlow/include/evfl/ResTimeline.h
@@ -0,0 +1,80 @@
+#pragma once
+
+#include <ore/BinaryFile.h>
+#include <ore/EnumUtil.h>
+#include <ore/Types.h>
+
+namespace ore {
+struct ResEndian;
+struct ResMetaData;
+} // namespace ore
+
+namespace evfl {
+
+struct ResActor;
+
+struct ResTrigger {
+ u16 clip_index;
+ u8 trigger_type;
+};
+
+struct ResCut {
+ float start_time;
+ ore::BinTPtr<ore::BinString> name;
+ ore::BinTPtr<ore::ResMetaData> params;
+};
+
+struct ResClip {
+ float start_time;
+ float duration;
+ u16 actor_index;
+ u16 actor_action_index;
+ u8 _c;
+ ore::BinTPtr<ore::ResMetaData> params;
+};
+
+struct ResOneshot {
+ float time;
+ u16 actor_index;
+ u16 actor_action_index;
+ u32 _8;
+ u32 _c;
+ ore::BinTPtr<ore::ResMetaData> params;
+};
+
+struct ResSubtimeline {
+ ore::BinTPtr<ore::BinString> name;
+};
+
+struct ResTimeline {
+ /// 'TLIN'
+ u32 magic;
+ /// String pool offset (relative to this structure)
+ int string_pool_offset;
+ u32 reserved_8;
+ u32 reserved_c;
+ float duration;
+ u16 num_actors;
+ u16 num_actions;
+ u16 num_clips;
+ u16 num_oneshots;
+ u16 num_subtimelines;
+ u16 num_cuts;
+ ore::BinTPtr<ore::BinString> name;
+ ore::BinTPtr<ResActor> actors;
+ ore::BinTPtr<ResClip> clips;
+ ore::BinTPtr<ResOneshot> oneshots;
+ ore::BinTPtr<ResTrigger> triggers;
+ ore::BinTPtr<ResSubtimeline> subtimelines;
+ ore::BinTPtr<ResCut> cuts;
+ ore::BinTPtr<ore::ResMetaData> params;
+};
+
+void SwapEndian(ore::ResEndian* endian, ResTrigger* trigger);
+void SwapEndian(ore::ResEndian* endian, ResCut* cut);
+void SwapEndian(ore::ResEndian* endian, ResClip* clip);
+void SwapEndian(ore::ResEndian* endian, ResOneshot* oneshot);
+void SwapEndian(ore::ResEndian* endian, ResSubtimeline* subtimeline);
+void SwapEndian(ore::ResEndian* endian, ResTimeline* timeline);
+
+} // namespace evfl
diff --git a/lib/EventFlow/include/evfl/TimelineObj.h b/lib/EventFlow/include/evfl/TimelineObj.h
new file mode 100644
index 00000000..5d1ab38a
--- /dev/null
+++ b/lib/EventFlow/include/evfl/TimelineObj.h
@@ -0,0 +1,78 @@
+#pragma once
+
+#include <evfl/EvflAllocator.h>
+#include <evfl/ResActor.h>
+#include <ore/Array.h>
+#include <ore/EnumUtil.h>
+#include <ore/Types.h>
+
+namespace evfl {
+
+struct ResTimeline;
+
+ORE_ENUM(TimelineState, kNotStarted, kPlaying, kStop, kPause)
+
+class TimelineObj {
+public:
+ class Builder {
+ public:
+ explicit Builder(const ResTimeline* timeline) : m_timeline(timeline) {}
+
+ bool Build(TimelineObj* obj, AllocateArg allocate_arg);
+
+ private:
+ const ResTimeline* m_timeline = nullptr;
+ ActBinder::Builder m_act_binder_builder;
+ };
+
+ TimelineObj();
+
+ void Calc();
+ void Reset();
+ void SetState(TimelineState::Type state);
+ void Start(float start_time);
+ void JumpTimeTo(float time);
+ void AdvanceTimeTo(float time);
+ bool RegisterSubtimeline(TimelineObj* obj);
+
+ ActBinder& GetActBinder() { return m_act_binder; }
+ const ActBinder& GetActBinder() const { return m_act_binder; }
+ const ore::Array<TimelineObj*>& GetSubTimelines() const { return m_sub_timelines; }
+ const ResTimeline* GetTimeline() const { return m_timeline; }
+ float GetTime() const { return m_time; }
+ float GetNewTime() const { return m_new_time; }
+ int GetLastTriggerIdx() const { return m_last_trigger_idx; }
+ int GetLastOneshotIdx() const { return m_last_oneshot_idx; }
+ int GetPlayCounter() const { return m_play_counter; }
+ bool IsStarted() const { return m_started; }
+ bool IsJumpedTime() const { return m_jumped_time; }
+ TimelineState::Type GetState() const { return m_state; }
+
+private:
+ void CalcImpl();
+ void JumpTimeToImpl(float time);
+ void AdvanceTimeToImpl(float time);
+
+ static int s_GlobalPlayCounter;
+
+ void Finalize() {
+ m_timeline = nullptr;
+ m_act_binder.Reset();
+ m_sub_timelines.Clear(&m_allocator);
+ }
+
+ EvflAllocator m_allocator;
+ ore::Array<TimelineObj*> m_sub_timelines;
+ const ResTimeline* m_timeline{};
+ ActBinder m_act_binder{};
+ float m_time{};
+ float m_new_time{};
+ int m_last_trigger_idx = -1;
+ int m_last_oneshot_idx = -1;
+ int m_play_counter = 0;
+ bool m_started = false;
+ bool m_jumped_time = false;
+ ore::SizedEnum<TimelineState::Type, u8> m_state = TimelineState::kNotStarted;
+};
+
+} // namespace evfl
diff --git a/lib/EventFlow/include/ore/Allocator.h b/lib/EventFlow/include/ore/Allocator.h
new file mode 100644
index 00000000..23f6cd81
--- /dev/null
+++ b/lib/EventFlow/include/ore/Allocator.h
@@ -0,0 +1,45 @@
+#pragma once
+
+#include <cstddef>
+#include <memory>
+#include <ore/Types.h>
+
+namespace ore {
+
+class Allocator {
+public:
+ Allocator() = default;
+ virtual ~Allocator() = default;
+
+ void* New(size_t size, size_t alignment = alignof(std::max_align_t)) {
+ return AllocImpl(size, alignment);
+ }
+
+ template <typename T>
+ T* New(size_t alignment = alignof(std::max_align_t)) {
+ auto* buffer = AllocImpl(sizeof(T), alignment);
+ if (buffer)
+ return new (buffer) T;
+ return static_cast<T*>(buffer);
+ }
+
+ template <typename T>
+ void Delete(T* ptr) {
+ std::destroy_at(ptr);
+ Free(ptr);
+ }
+
+ template <typename T>
+ void DeleteAndNull(T*& ptr) {
+ std::destroy_at(ptr);
+ Free(ptr);
+ ptr = nullptr;
+ }
+
+ void Free(void* ptr) { FreeImpl(ptr); }
+
+ virtual void* AllocImpl(size_t size, size_t alignment) = 0;
+ virtual void FreeImpl(void* ptr) = 0;
+};
+
+} // namespace ore
diff --git a/lib/EventFlow/include/ore/Array.h b/lib/EventFlow/include/ore/Array.h
new file mode 100644
index 00000000..342ce62d
--- /dev/null
+++ b/lib/EventFlow/include/ore/Array.h
@@ -0,0 +1,287 @@
+#pragma once
+
+#include <algorithm>
+#include <iterator>
+#include <memory>
+#include <ore/Allocator.h>
+#include <ore/Buffer.h>
+#include <ore/IterRange.h>
+#include <type_traits>
+
+namespace ore {
+
+// This is like a std::span, not a fixed-size array like std::array.
+// Elements will NOT be automatically freed.
+template <typename T>
+class Array {
+public:
+ Array() = default;
+ Array(T* data, int size) : m_data(data), m_size(size) {}
+
+ T* data() const { return m_data; }
+ int size() const { return m_size; }
+
+ auto begin() const { return data(); }
+ auto end() const { return data() + size(); }
+
+ T& operator[](int idx) { return m_data[idx]; }
+ const T& operator[](int idx) const { return m_data[idx]; }
+
+ T& front() { return m_data[0]; }
+ const T& front() const { return m_data[0]; }
+
+ T& back() { return m_data[m_size - 1]; }
+ const T& back() const { return m_data[m_size - 1]; }
+
+ void SetBuffer(void* new_buffer, int num) {
+ DestructElements();
+ m_data = static_cast<T*>(new_buffer);
+ m_size = num;
+ }
+
+ void SetBuffer(int num, Allocator* allocator) {
+ auto* new_buffer = allocator->AllocImpl(num * int(sizeof(T)), alignof(std::max_align_t));
+ SetBuffer(new_buffer, num);
+ }
+
+ void ConstructElements(int num, Allocator* allocator) {
+ SetBuffer(num, allocator);
+ DefaultConstructElements();
+ }
+
+ void ConstructElements(void* new_buffer, int num) {
+ SetBuffer(new_buffer, num);
+ DefaultConstructElements();
+ }
+
+ void ConstructElements(Buffer buffer) {
+ DestructElements();
+ m_data = reinterpret_cast<T*>(buffer.data);
+ m_size = buffer.size / int(sizeof(T));
+ DefaultConstructElements();
+ }
+
+ void DestructElements() { std::destroy(begin(), end()); }
+
+ void ClearWithoutFreeing() {
+ DestructElements();
+ m_data = nullptr;
+ m_size = 0;
+ }
+
+ void Clear(Allocator* allocator) {
+ if (!m_data)
+ return;
+ auto* data = m_data;
+ ClearWithoutFreeing();
+ allocator->Free(data);
+ }
+
+ void DefaultConstructElements() {
+ for (auto it = begin(), e = end(); it != e;)
+ new (it++) T;
+ }
+
+ void UninitializedDefaultConstructElements() {
+ std::uninitialized_default_construct(begin(), end());
+ }
+
+private:
+ T* m_data{};
+ int m_size{};
+};
+
+template <typename T>
+class SelfDestructingArray : public Array<T> {
+public:
+ ~SelfDestructingArray() { this->DestructElements(); }
+};
+
+template <typename T>
+class ArrayListBase {
+public:
+ ArrayListBase() : m_data(), m_size(), m_capacity() {}
+ ArrayListBase(T* data, int capacity) {
+ m_size = 0;
+ m_data = data;
+ m_capacity = capacity;
+ }
+ ~ArrayListBase() { clear(); }
+
+ ArrayListBase(const ArrayListBase&) = delete;
+ auto operator=(const ArrayListBase&) = delete;
+
+ T* begin() { return m_data; }
+ const T* begin() const { return m_data; }
+
+ T* end() { return m_data + m_size; }
+ const T* end() const { return m_data + m_size; }
+
+ T* data() { return m_data; }
+ const T* data() const { return m_data; }
+
+ int size() const { return m_size; }
+ int capacity() const { return m_capacity; }
+
+ T& operator[](int idx) { return m_data[idx]; }
+ const T& operator[](int idx) const { return m_data[idx]; }
+
+ T& front() { return m_data[0]; }
+ const T& front() const { return m_data[0]; }
+
+ T& back() { return m_data[m_size - 1]; }
+ const T& back() const { return m_data[m_size - 1]; }
+
+ template <typename... Args>
+ T& emplace_back(Args&&... args) {
+ auto* item = new (&m_data[m_size++]) T(std::forward<Args>(args)...);
+ return *item;
+ }
+
+ void push_back(const T& item) { new (&m_data[m_size++]) T(item); }
+
+ void pop_back() {
+ std::destroy_at(&back());
+ --m_size;
+ }
+
+ void clear() {
+ std::destroy(begin(), end());
+ m_size = 0;
+ }
+
+ T* m_data;
+ int m_size;
+ int m_capacity;
+};
+
+template <typename T, int N>
+class FixedArrayList : public ArrayListBase<T> {
+public:
+ FixedArrayList() : ArrayListBase<T>(reinterpret_cast<T*>(m_storage), N) {}
+
+private:
+ std::aligned_storage_t<sizeof(T), alignof(T)> m_storage[N];
+};
+
+// This is like a std::vector.
+template <typename T>
+class DynArrayList : public ArrayListBase<T> {
+public:
+ DynArrayList() = default;
+ explicit DynArrayList(Allocator* allocator) : m_allocator(allocator) {}
+
+ ~DynArrayList() {
+ clear();
+ m_allocator = nullptr;
+ this->m_size = 0;
+ }
+
+ void Reset() {
+ clear();
+ m_allocator = nullptr;
+ }
+
+ DynArrayList(const DynArrayList&) = delete;
+ auto operator=(const DynArrayList&) = delete;
+
+ void Init(Allocator* allocator, int initial_capacity = 1) {
+ clear();
+ m_allocator = allocator;
+ Reallocate(initial_capacity);
+ }
+
+ template <typename... Args>
+ T& emplace_back(Args&&... args) {
+ GrowIfNeeded();
+ return ArrayListBase<T>::emplace_back(std::forward<Args>(args)...);
+ }
+
+ void push_back(const T& item) {
+ GrowIfNeeded();
+ return ArrayListBase<T>::push_back(item);
+ }
+
+ void clear() {
+ std::destroy(this->begin(), this->end());
+ auto* data = this->m_data;
+ this->m_data = nullptr;
+ this->m_size = 0;
+ this->m_capacity = 0;
+ Free(data);
+ }
+
+ template <typename InputIterator>
+ void OverwriteWith(InputIterator src_begin, InputIterator src_end) {
+ const int src_size = std::distance(src_begin, src_end);
+ if (src_size > this->m_capacity) {
+ this->m_size = 0;
+ Reallocate(2 * src_size);
+ }
+ this->m_size = src_size;
+ std::uninitialized_copy(src_begin, src_end, this->begin());
+ }
+
+ /// Quadratic complexity; only use this for small copies.
+ template <typename Range>
+ void DeduplicateCopy(const Range& range) {
+ for (auto it = range.begin(), end = range.end(); it != end; ++it) {
+ auto value = *it;
+ if (std::find_if(range.begin(), it, [&](const auto& v) { return value == v; }) == it)
+ this->emplace_back(value);
+ }
+ }
+
+ /// Resize the array so that it contains `new_size` elements.
+ ///
+ /// - If the new size is greater than the current size, new elements are added and
+ /// default initialized. Iterators may be invalidated.
+ /// - If the new size is less than the current size, excess elements are destroyed.
+ ///
+ /// @param new_size The new size of the array.
+ void Resize(int new_size) {
+ if (this->m_capacity < new_size)
+ Reallocate(new_size);
+
+ if (this->m_size < new_size) {
+ std::uninitialized_default_construct(this->m_data + this->m_size,
+ this->m_data + new_size);
+ } else {
+ std::destroy(this->m_data + new_size, this->m_data + this->m_size);
+ }
+
+ this->m_size = new_size;
+ }
+
+private:
+ void GrowIfNeeded() {
+ if (this->m_size < this->m_capacity)
+ return;
+ Reallocate(2 * this->m_size + 2);
+ }
+
+ void Reallocate(int new_capacity) {
+ const int num_bytes = sizeof(T) * new_capacity;
+ auto* new_buffer =
+ static_cast<T*>(m_allocator->AllocImpl(num_bytes, alignof(std::max_align_t)));
+ auto* old_buffer = this->m_data;
+ auto* capacity = &this->m_capacity;
+ UninitializedCopyTo(new_buffer);
+ this->m_data = new_buffer;
+ *capacity = new_capacity;
+ Free(old_buffer);
+ }
+
+ void UninitializedCopyTo(T* destination) const {
+ std::uninitialized_copy(this->begin(), this->end(), destination);
+ }
+
+ void Free(void* ptr) {
+ if (ptr)
+ m_allocator->Free(ptr);
+ }
+
+ Allocator* m_allocator{};
+};
+
+} // namespace ore
diff --git a/lib/EventFlow/include/ore/BinaryFile.h b/lib/EventFlow/include/ore/BinaryFile.h
new file mode 100644
index 00000000..c2d62587
--- /dev/null
+++ b/lib/EventFlow/include/ore/BinaryFile.h
@@ -0,0 +1,153 @@
+#pragma once
+
+#include <ore/StringView.h>
+#include <ore/Types.h>
+#include <type_traits>
+#include <utility>
+
+namespace ore {
+
+template <typename T>
+constexpr T AlignUpToPowerOf2(T val, int base) {
+ return val + base - 1 & static_cast<unsigned int>(-base);
+}
+
+struct RelocationTable;
+
+struct BinaryBlockHeader {
+ BinaryBlockHeader* FindNextBlock(int type);
+ const BinaryBlockHeader* FindNextBlock(int type) const;
+ BinaryBlockHeader* GetNextBlock();
+ const BinaryBlockHeader* GetNextBlock() const;
+ void SetNextBlock(BinaryBlockHeader* block);
+
+ u32 magic;
+ int next_block_offset;
+};
+
+struct BinaryFileHeader {
+ bool IsValid(s64 magic_, int ver_major_, int ver_minor_, int ver_patch_, int ver_sub_) const;
+ bool IsSignatureValid(s64 magic_) const;
+ bool IsVersionValid(int major, int minor, int patch, int sub) const;
+ bool IsEndianReverse() const;
+ bool IsEndianValid() const;
+
+ bool IsAlignmentValid() const;
+ int GetAlignment() const;
+ void SetAlignment(int alignment_);
+
+ bool IsRelocated() const;
+ void SetRelocated(bool relocated);
+
+ void SetByteOrderMark();
+
+ int GetFileSize() const;
+ void SetFileSize(int size);
+
+ StringView GetFileName() const;
+ void SetFileName(const StringView& name);
+
+ RelocationTable* GetRelocationTable();
+ void SetRelocationTable(RelocationTable* table);
+
+ BinaryBlockHeader* GetFirstBlock();
+ const BinaryBlockHeader* GetFirstBlock() const;
+ void SetFirstBlock(BinaryBlockHeader* block);
+
+ BinaryBlockHeader* FindFirstBlock(int type);
+ const BinaryBlockHeader* FindFirstBlock(int type) const;
+
+ u64 magic;
+ u8 ver_major;
+ u8 ver_minor;
+ u8 ver_patch;
+ u8 ver_sub;
+ s16 bom;
+ u8 alignment;
+ u8 _f;
+ int file_name_offset;
+ u16 relocation_flags;
+ u16 first_block_offset;
+ int relocation_table_offset;
+ int file_size;
+};
+
+template <typename T>
+struct BinTString {
+ // Make it impossible to accidentally construct a (partial, broken) copy.
+ BinTString(const BinTString&) = delete;
+ auto operator=(const BinTString&) = delete;
+
+ T* data() { return chars; }
+ const T* data() const { return chars; }
+
+ T& operator[](size_t idx) { return data()[idx]; }
+ const T& operator[](size_t idx) const { return data()[idx]; }
+
+ auto begin() { return data(); }
+ auto begin() const { return data(); }
+
+ auto end() { return data() + length; }
+ auto end() const { return data() + length; }
+
+ bool empty() const { return length == 0; }
+
+ // NOLINTNEXTLINE(google-explicit-constructor)
+ operator TStringView<T>() const { return {data(), length}; }
+
+ BinTString* NextString() { return const_cast<BinTString*>(std::as_const(*this).NextString()); }
+
+ const BinTString* NextString() const {
+ // XXX: this shouldn't have to be a separate case.
+ if constexpr (std::is_same_v<T, wchar_t>) {
+ const auto offset = ((2 + (4 * (length + 1) - 1)) & -4) + 2;
+ return reinterpret_cast<const BinTString*>(reinterpret_cast<const char*>(this) +
+ offset);
+
+ } else {
+ // + 1 for the null terminator
+ const auto offset = offsetof(BinTString, chars) + sizeof(T) * (length + 1);
+ return reinterpret_cast<const BinTString*>(
+ reinterpret_cast<const char*>(this) +
+ AlignUpToPowerOf2(offset, alignof(BinTString)));
+ }
+ }
+
+ u16 length;
+ T chars[1];
+};
+
+using BinString = BinTString<char>;
+using BinWString = BinTString<wchar_t>;
+
+template <typename T>
+struct BinTPtr {
+ void Clear() { offset_or_ptr = 0; }
+ void Set(T* ptr) { offset_or_ptr = reinterpret_cast<u64>(ptr); }
+
+ // Only use this after relocation.
+ T* Get() { return reinterpret_cast<T*>(offset_or_ptr); }
+ const T* Get() const { return reinterpret_cast<const T*>(offset_or_ptr); }
+
+ void SetOffset(void* base, void* ptr) {
+ offset_or_ptr = static_cast<int>(ptr ? uintptr_t(ptr) - uintptr_t(base) : 0);
+ }
+
+ u64 GetOffset() const { return offset_or_ptr; }
+
+ T* ToPtr(void* base) const {
+ const auto offset = static_cast<int>(offset_or_ptr);
+ if (offset == 0)
+ return nullptr;
+ return reinterpret_cast<T*>(reinterpret_cast<char*>(base) + offset);
+ }
+
+ void Relocate(void* base) { Set(ToPtr(base)); }
+ void Unrelocate(void* base) { SetOffset(base, Get()); }
+
+ u64 offset_or_ptr;
+};
+
+static_assert(sizeof(u64) >= sizeof(void*));
+
+} // namespace ore
diff --git a/lib/EventFlow/include/ore/BitUtils.h b/lib/EventFlow/include/ore/BitUtils.h
new file mode 100644
index 00000000..fd907f9c
--- /dev/null
+++ b/lib/EventFlow/include/ore/BitUtils.h
@@ -0,0 +1,149 @@
+#pragma once
+
+#include <ore/Allocator.h>
+#include <ore/Types.h>
+
+namespace ore {
+
+constexpr int PopCount(u32 x) {
+ x = x - ((x >> 1) & 0x55555555);
+ x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
+ x = (x + (x >> 4)) & 0x0F0F0F0F;
+ x += (x >> 8);
+ x += (x >> 16);
+ return int(x & 0x3f);
+}
+
+constexpr int PopCount(u64 x) {
+ x = x - ((x >> 1) & 0x5555555555555555);
+ x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333);
+ x = (x + (x >> 4)) & 0x0F0F0F0F0F0F0F0F;
+ x += (x >> 8);
+ x += (x >> 16);
+ x += (x >> 32);
+ return int(x & 0x7f);
+}
+
+constexpr int CountTrailingZeros(u32 x) {
+ return PopCount((x & -x) - 1);
+}
+
+constexpr int CountTrailingZeros(u64 x) {
+ return PopCount((x & -x) - 1);
+}
+
+namespace detail {
+template <typename T>
+constexpr T AlignUpToPowerOf2(T val, int base) {
+ return val + base - 1 & static_cast<unsigned int>(-base);
+}
+} // namespace detail
+
+class BitArray {
+public:
+ using Word = size_t;
+ static constexpr int NumBitsPerWord = sizeof(Word) * 8;
+ static constexpr int ClearMask = ~(NumBitsPerWord - 1);
+ static constexpr int ShiftAmount = CountTrailingZeros(u32(NumBitsPerWord));
+
+ class TestIter {
+ public:
+ TestIter(const Word* start, const Word* end);
+ TestIter& operator++();
+
+ int operator*() const { return m_bit; }
+ bool operator==(const TestIter& other) const { return m_bit == other.m_bit; }
+ bool operator!=(const TestIter& other) const { return !operator==(other); }
+
+ private:
+ void SetInvalid() {
+ m_bit = -1;
+ m_current_word = nullptr;
+ m_last_word = nullptr;
+ m_next = 0;
+ }
+
+ int m_bit;
+ const Word* m_current_word;
+ const Word* m_last_word;
+ Word m_next;
+ };
+
+ /// Same as TestIter but clears bits after iterating over them.
+ class TestClearIter {
+ public:
+ TestClearIter(Word* start, Word* end);
+ TestClearIter& operator++();
+ int operator*() const { return m_bit; }
+ bool operator==(const TestClearIter& other) const { return m_bit == other.m_bit; }
+ bool operator!=(const TestClearIter& other) const { return !operator==(other); }
+
+ private:
+ void SetInvalid() {
+ m_bit = -1;
+ m_current_word = nullptr;
+ m_last_word = nullptr;
+ m_next = 0;
+ }
+
+ int m_bit;
+ Word* m_current_word;
+ Word* m_last_word;
+ Word m_next;
+ };
+
+ constexpr BitArray() = default;
+ constexpr BitArray(void* buffer, int num_bits) { SetData(buffer, num_bits); }
+ constexpr BitArray(ore::Allocator* allocator, int num_bits) {
+ AllocateBuffer(allocator, num_bits);
+ }
+
+ void SetData(void* buffer, int num_bits) {
+ m_words = reinterpret_cast<Word*>(buffer);
+ m_num_bits = num_bits;
+ }
+
+ void AllocateBuffer(ore::Allocator* allocator, int num_bits) {
+ SetData(allocator->New(GetRequiredBufferSize(num_bits)), num_bits);
+ SetAllOff();
+ }
+
+ void FreeBufferIfNeeded(ore::Allocator* allocator) {
+ if (m_words)
+ allocator->Delete(m_words);
+ }
+
+ void FreeBuffer(ore::Allocator* allocator) { allocator->Delete(m_words); }
+
+ bool Test(int bit) const {
+ return (GetWord(bit) & (Word(1) << (Word(bit) % NumBitsPerWord))) != 0;
+ }
+ void Set(int bit) { GetWord(bit) |= Word(1) << (Word(bit) % NumBitsPerWord); }
+ void Clear(int bit) { GetWord(bit) &= ~(Word(1) << (Word(bit) % NumBitsPerWord)); }
+
+ void SetAllOn();
+ void SetAllOff();
+ TestIter BeginTest() const;
+ TestIter EndTest() const;
+ TestClearIter BeginTestClear();
+ TestClearIter EndTestClear();
+
+ static int GetRequiredBufferSize(int num_bits) {
+ return sizeof(Word) * (detail::AlignUpToPowerOf2(num_bits, NumBitsPerWord) >> ShiftAmount);
+ }
+
+private:
+ Word& GetWord(int bit) const { return m_words[bit >> ShiftAmount]; }
+ int GetNumWords() const { return int((m_num_bits + NumBitsPerWord - 1) >> ShiftAmount); }
+
+ void Fill(int num, Word value) {
+ auto* it = m_words;
+ for (int i = num - 1; i >= 0; --i)
+ *it++ = value;
+ }
+
+ Word* m_words{};
+ int m_num_bits{};
+};
+
+} // namespace ore
diff --git a/lib/EventFlow/include/ore/Buffer.h b/lib/EventFlow/include/ore/Buffer.h
new file mode 100644
index 00000000..0952ab6c
--- /dev/null
+++ b/lib/EventFlow/include/ore/Buffer.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include <ore/Allocator.h>
+#include <ore/Types.h>
+
+namespace ore {
+
+struct Buffer {
+ template <typename T>
+ void Allocate(Allocator* allocator, int num) {
+ size = sizeof(T) * num;
+ data = static_cast<char*>(allocator->New(size));
+ }
+
+ void Free(Allocator* allocator) {
+ allocator->Free(data);
+ data = nullptr;
+ size = 0;
+ }
+
+ char* data;
+ int size;
+};
+
+} // namespace ore
diff --git a/lib/EventFlow/include/ore/EnumUtil.h b/lib/EventFlow/include/ore/EnumUtil.h
new file mode 100644
index 00000000..e0c2d953
--- /dev/null
+++ b/lib/EventFlow/include/ore/EnumUtil.h
@@ -0,0 +1,115 @@
+#pragma once
+
+#include <iterator>
+#include <ore/IterRange.h>
+#include <ore/StringView.h>
+#include <type_traits>
+
+namespace ore {
+
+namespace detail::EnumUtil {
+
+int FindIndex(int value, const IterRange<const int*>& values);
+void Parse(const IterRange<StringView*>& out, StringView definition);
+
+constexpr int CountValues(const char* text_all, size_t text_all_len) {
+ int count = 1;
+ for (size_t i = 0; i < text_all_len; ++i) {
+ if (text_all[i] == ',')
+ ++count;
+ }
+ return count;
+}
+
+} // namespace detail::EnumUtil
+
+template <class T>
+struct Enum {
+public:
+ Enum() { T::Init(); }
+
+ static Enum<T>& Info() { return s_Info; }
+
+ StringView name{};
+ IterRange<StringView*> members{};
+
+private:
+ static inline Enum<T> s_Info{};
+};
+
+#define ORE_ENUM(NAME, ...) \
+ class NAME { \
+ public: \
+ enum Type { __VA_ARGS__ }; \
+ \
+ static void Init() { \
+ static ore::StringView names[cCount]; \
+ ore::detail::EnumUtil::Parse(ore::IterRange<ore::StringView*>(names), cTextAll); \
+ ore::Enum<NAME>::Info().name = #NAME; \
+ ore::Enum<NAME>::Info().members = ore::IterRange<ore::StringView*>(names); \
+ } \
+ \
+ static constexpr int Size() { return cCount; } \
+ static constexpr Type Invalid() { return Type(Size()); } \
+ \
+ private: \
+ static constexpr const char* cTextAll = #__VA_ARGS__; \
+ static constexpr size_t cTextAllLen = sizeof(#__VA_ARGS__); \
+ static constexpr int cCount = ore::detail::EnumUtil::CountValues(cTextAll, cTextAllLen); \
+ };
+
+// FIXME
+template <class T>
+class ValuedEnum {
+public:
+ ValuedEnum() { T::Init(); }
+
+ static Enum<T>& Info() { return s_Info; }
+
+ StringView name{};
+ IterRange<StringView*> members{};
+
+private:
+ static inline Enum<T> s_Info{};
+};
+
+#define ORE_VALUED_ENUM(NAME, ...) \
+ class NAME { \
+ public: \
+ enum Type { __VA_ARGS__ }; \
+ \
+ static void Init() { \
+ static ore::StringView names[cCount]; \
+ ore::detail::EnumUtil::Parse(ore::IterRange<ore::StringView*>(names), cTextAll); \
+ ore::ValuedEnum<NAME>::Info().name = #NAME; \
+ ore::ValuedEnum<NAME>::Info().members = ore::IterRange<ore::StringView*>(names); \
+ } \
+ \
+ static constexpr int Size() { return cCount; } \
+ static constexpr Type Invalid() { return Type(Size()); } \
+ \
+ private: \
+ static constexpr const char* cTextAll = #__VA_ARGS__; \
+ static constexpr size_t cTextAllLen = sizeof(#__VA_ARGS__); \
+ static constexpr int cCount = ore::detail::EnumUtil::CountValues(cTextAll, cTextAllLen); \
+ };
+
+/// For storing an enum with a particular storage size when specifying the underlying type of the
+/// enum is not an option.
+template <typename Enum, typename Storage>
+struct SizedEnum {
+ static_assert(std::is_enum<Enum>());
+ static_assert(!std::is_enum<Storage>());
+
+ constexpr SizedEnum() = default;
+ constexpr SizedEnum(Enum value) { *this = value; }
+ constexpr operator Enum() const { return static_cast<Enum>(mValue); }
+ constexpr SizedEnum& operator=(Enum value) {
+ mValue = static_cast<Storage>(value);
+ return *this;
+ }
+
+ Storage mValue;
+};
+
+} // namespace ore
diff --git a/lib/EventFlow/include/ore/IntrusiveList.h b/lib/EventFlow/include/ore/IntrusiveList.h
new file mode 100644
index 00000000..33f4e257
--- /dev/null
+++ b/lib/EventFlow/include/ore/IntrusiveList.h
@@ -0,0 +1,100 @@
+#pragma once
+
+#include <utility>
+
+namespace ore {
+
+class IntrusiveListNode {
+public:
+ constexpr explicit IntrusiveListNode() { m_prev = m_next = this; }
+
+ IntrusiveListNode(const IntrusiveListNode&) = delete;
+ auto operator=(const IntrusiveListNode&) = delete;
+
+ IntrusiveListNode(IntrusiveListNode&& other) noexcept { *this = std::move(other); }
+ IntrusiveListNode& operator=(IntrusiveListNode&& other) noexcept {
+ auto* prev = other.m_prev;
+ other.m_prev = this;
+ prev->m_next = this;
+ m_prev = prev;
+ m_next = &other;
+ other.Erase();
+ return *this;
+ }
+
+ IntrusiveListNode* Prev() const { return m_prev; }
+ IntrusiveListNode* Next() const { return m_next; }
+ bool IsLinked() const { return Prev() || Next(); }
+
+ void Erase() {
+ auto* next = m_next;
+ auto* next_prev = next->m_prev;
+ m_prev->m_next = next;
+ next->m_prev = m_prev;
+ // This is a circular list.
+ next_prev->m_next = this;
+ m_prev = next_prev;
+ }
+
+ void InsertFront(IntrusiveListNode* node) {
+ auto* prev = node->m_prev;
+ node->m_prev = m_prev;
+ prev->m_next = this;
+ m_prev->m_next = node;
+ m_prev = prev;
+ }
+
+private:
+ template <typename T>
+ friend class IntrusiveList;
+
+ IntrusiveListNode* m_prev{};
+ IntrusiveListNode* m_next{};
+};
+
+template <typename T>
+class IntrusiveList {
+public:
+ void SetOffset(int offset) { m_offset = offset; }
+
+ bool Empty() const { return m_node.m_next == &m_node; }
+ T* Front() { return NodeToItemWithNullCheck(m_node.m_next); }
+ T* Back() { return NodeToItemWithNullCheck(m_node.m_prev); }
+ const T* Front() const { return NodeToItemWithNullCheck(m_node.m_next); }
+ const T* Back() const { return NodeToItemWithNullCheck(m_node.m_prev); }
+
+ void Erase(T* item) { ItemToNode(item)->Erase(); }
+
+ void InsertFront(T* item) { m_node.InsertFront(ItemToNode(item)); }
+
+private:
+ IntrusiveListNode* ItemToNode(T* item) const {
+ return reinterpret_cast<IntrusiveListNode*>(reinterpret_cast<char*>(item) + m_offset);
+ }
+
+ const IntrusiveListNode* ItemToNode(const T* item) const {
+ return reinterpret_cast<const IntrusiveListNode*>(reinterpret_cast<const char*>(item) +
+ m_offset);
+ }
+
+ T* NodeToItem(IntrusiveListNode* node) const {
+ return reinterpret_cast<T*>(reinterpret_cast<char*>(node) - m_offset);
+ }
+
+ const T* NodeToItem(const IntrusiveListNode* node) const {
+ return reinterpret_cast<const T*>(reinterpret_cast<const char*>(node) - m_offset);
+ }
+
+ T* NodeToItemWithNullCheck(IntrusiveListNode* node) const {
+ return node == &m_node ? nullptr : NodeToItem(node);
+ }
+
+ const T* NodeToItemWithNullCheck(const IntrusiveListNode* node) const {
+ return node == &m_node ? nullptr : NodeToItem(node);
+ }
+
+ IntrusiveListNode m_node;
+ int m_offset = -1;
+};
+
+} // namespace ore
diff --git a/lib/EventFlow/include/ore/IterRange.h b/lib/EventFlow/include/ore/IterRange.h
new file mode 100644
index 00000000..338628ca
--- /dev/null
+++ b/lib/EventFlow/include/ore/IterRange.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include <iterator>
+
+namespace ore {
+
+template <typename T>
+class IterRange {
+public:
+ constexpr IterRange() = default;
+ constexpr IterRange(const T& begin_, const T& end_) : m_begin(begin_), m_end(end_) {}
+ template <typename Other>
+ // NOLINTNEXTLINE(google-explicit-constructor)
+ constexpr IterRange(Other& x) : IterRange(std::begin(x), std::end(x)) {}
+
+ constexpr IterRange(const T& begin, int size) : m_begin(begin), m_end(begin + size) {}
+
+ const auto& begin() const { return m_begin; }
+ const auto& end() const { return m_end; }
+
+ int size() const { return end() - begin(); }
+
+private:
+ T m_begin{};
+ T m_end{};
+};
+
+} // namespace ore
diff --git a/lib/EventFlow/include/ore/RelocationTable.h b/lib/EventFlow/include/ore/RelocationTable.h
new file mode 100644
index 00000000..4593abb3
--- /dev/null
+++ b/lib/EventFlow/include/ore/RelocationTable.h
@@ -0,0 +1,50 @@
+#pragma once
+
+#include <ore/Types.h>
+
+namespace ore {
+
+struct RelocationTable {
+ struct Section {
+ struct Entry {
+ /// Offset to pointers to relocate
+ int pointers_offset;
+ /// Bit field that determines which pointers need to be relocated
+ /// (next to 32 contiguous pointers starting from the listed offset)
+ u32 mask;
+ };
+
+ void SetPtr(void* ptr_);
+ void* GetPtr() const;
+ void* GetPtrInFile(void* base) const;
+ void* GetBasePtr(void* base) const;
+ u32 GetSize() const;
+
+ u64 ptr;
+ int offset;
+ int size;
+ int first_entry_idx;
+ int num_entries;
+ };
+
+ u32 magic;
+ int table_start_offset;
+ int num_sections;
+ Section sections[1];
+
+ Section* GetSections() { return sections; }
+ const Section* GetSections() const { return sections; }
+
+ Section::Entry* GetEntries() {
+ return reinterpret_cast<Section::Entry*>(GetSections() + num_sections);
+ }
+ const Section::Entry* GetEntries() const {
+ return reinterpret_cast<const Section::Entry*>(GetSections() + num_sections);
+ }
+
+ void Relocate();
+ void Unrelocate();
+ static int CalcSize(int num_sections, int num_entries);
+};
+
+} // namespace ore
diff --git a/lib/EventFlow/include/ore/ResDic.h b/lib/EventFlow/include/ore/ResDic.h
new file mode 100644
index 00000000..d9a686f6
--- /dev/null
+++ b/lib/EventFlow/include/ore/ResDic.h
@@ -0,0 +1,62 @@
+#pragma once
+
+#include <ore/BinaryFile.h>
+#include <ore/StringView.h>
+#include <ore/Types.h>
+
+namespace ore {
+
+struct ResEndian;
+
+struct ResDicEntry {
+ StringView GetKey() const { return *name.Get(); }
+
+ // Bits 3-7: index of the byte that should be checked
+ // Bits 0-2: index of the bit in that byte
+ int compact_bit_idx;
+ u16 next_indices[2];
+ BinTPtr<BinString> name;
+};
+
+struct ResDic {
+ static int FindRefBit(const StringView& str1, const StringView& str2);
+
+ const ResDicEntry* FindEntry(const StringView& key) const {
+ auto* prev = &entries[0];
+ auto* entry = &entries[prev->next_indices[0]];
+ while (prev->compact_bit_idx < entry->compact_bit_idx) {
+ const int bit_idx = entry->compact_bit_idx;
+ long bit = 0;
+ if (u32(key.length()) > u32(bit_idx >> 3))
+ bit = ((key[key.length() + -((bit_idx >> 3) + 1)] >> (bit_idx & 7))) & 1;
+
+ prev = entry;
+ entry = &entries[prev->next_indices[bit]];
+ }
+ return entry;
+ }
+
+ /// Returns the index for the specified key or -1 if it cannot be found.
+ int FindIndex(const StringView& key) const {
+ const auto* entry = FindEntry(key);
+ const auto entry_name = entry->GetKey();
+ bool ok = [&] { return StringView(key.data(), key.length()) == entry_name; }();
+ if (!ok)
+ return -1;
+ return static_cast<int>(entry - &GetEntries()[1]);
+ }
+
+ /// Entry 0 is the root entry.
+ ResDicEntry* GetEntries() { return entries; }
+ /// Entry 0 is the root entry.
+ const ResDicEntry* GetEntries() const { return entries; }
+
+ u32 magic;
+ int num_entries;
+ ResDicEntry entries[1];
+ // Followed by ResDicEntry[num_entries].
+};
+
+void SwapEndian(ResEndian* endian, ResDic* dic);
+
+} // namespace ore
diff --git a/lib/EventFlow/include/ore/ResEndian.h b/lib/EventFlow/include/ore/ResEndian.h
new file mode 100644
index 00000000..dc38e111
--- /dev/null
+++ b/lib/EventFlow/include/ore/ResEndian.h
@@ -0,0 +1,75 @@
+#pragma once
+
+#ifdef _MSC_VER
+#include <stdlib.h>
+#endif
+
+#include <cstring>
+#include <ore/Types.h>
+
+namespace ore {
+
+[[nodiscard]] inline u8 SwapEndian(u8 x) {
+ return x;
+}
+
+[[nodiscard]] inline u16 SwapEndian(u16 x) {
+#ifdef _MSC_VER
+ return _byteswap_ushort(x);
+#else
+ return __builtin_bswap16(x);
+#endif
+}
+
+[[nodiscard]] inline u32 SwapEndian(u32 x) {
+#ifdef _MSC_VER
+ return _byteswap_ulong(x);
+#else
+ return __builtin_bswap32(x);
+#endif
+}
+
+[[nodiscard]] inline u64 SwapEndian(u64 x) {
+#ifdef _MSC_VER
+ return _byteswap_uint64(x);
+#else
+ return __builtin_bswap64(x);
+#endif
+}
+
+[[nodiscard]] inline s8 SwapEndian(s8 x) {
+ return SwapEndian(u8(x));
+}
+
+[[nodiscard]] inline s16 SwapEndian(s16 x) {
+ return SwapEndian(u16(x));
+}
+
+[[nodiscard]] inline s32 SwapEndian(s32 x) {
+ return SwapEndian(u32(x));
+}
+
+[[nodiscard]] inline s64 SwapEndian(s64 x) {
+ return SwapEndian(u64(x));
+}
+
+[[nodiscard]] inline f32 SwapEndian(f32 x) {
+ static_assert(sizeof(u32) == sizeof(f32));
+ u32 i;
+ std::memcpy(&i, &x, sizeof(i));
+ i = SwapEndian(i);
+ std::memcpy(&x, &i, sizeof(i));
+ return x;
+}
+
+template <typename T>
+inline void SwapEndian(T* value) {
+ *value = SwapEndian(*value);
+}
+
+struct ResEndian {
+ char* base;
+ bool is_serializing;
+};
+
+} // namespace ore
diff --git a/lib/EventFlow/include/ore/ResMetaData.h b/lib/EventFlow/include/ore/ResMetaData.h
new file mode 100644
index 00000000..60e84cd0
--- /dev/null
+++ b/lib/EventFlow/include/ore/ResMetaData.h
@@ -0,0 +1,58 @@
+#pragma once
+
+#include <ore/BinaryFile.h>
+#include <ore/EnumUtil.h>
+#include <ore/ResDic.h>
+#include <ore/StringView.h>
+#include <ore/Types.h>
+
+namespace ore {
+
+struct ResDic;
+struct ResEndian;
+
+struct ResMetaData {
+ struct ActorIdentifier {
+ BinTPtr<BinString> name;
+ BinTPtr<BinString> sub_name;
+ };
+
+ union Value {
+ BinTPtr<ResMetaData> container;
+ // Also used for booleans. Anything that is != 0 is treated as true.
+ int i;
+ float f;
+ BinTPtr<BinString> str;
+ BinTPtr<BinWString> wstr;
+ ActorIdentifier actor;
+ };
+
+ ORE_ENUM(DataType, kArgument, kContainer, kInt, kBool, kFloat, kString, kWString, kIntArray, kBoolArray, kFloatArray, kStringArray, kWStringArray, kActorIdentifier)
+
+ /// @warning Only usable if type == kContainer.
+ const ResMetaData* Get(const StringView& key, DataType::Type expected_type) const {
+ const int idx = dictionary.Get()->FindIndex(key);
+ if (idx == -1)
+ return nullptr;
+
+ const auto* meta = (&value.container + idx)->Get();
+ if (meta->type != expected_type)
+ return nullptr;
+
+ return meta;
+ }
+
+ SizedEnum<DataType::Type, u8> type;
+ u16 num_items;
+ BinTPtr<ResDic> dictionary;
+ Value value;
+};
+
+// XXX: is this unused?
+struct ResUserData {
+ ORE_ENUM(DataType, kInt, kFloat, kString, kWString, kStream)
+};
+
+void SwapEndian(ResEndian* endian, ResMetaData* res);
+
+} // namespace ore
diff --git a/lib/EventFlow/include/ore/StringPool.h b/lib/EventFlow/include/ore/StringPool.h
new file mode 100644
index 00000000..2777ce2b
--- /dev/null
+++ b/lib/EventFlow/include/ore/StringPool.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <ore/BinaryFile.h>
+#include <ore/Types.h>
+
+namespace ore {
+
+struct StringPool : BinaryBlockHeader {
+ int GetLength() const;
+ void SetLength(int len);
+
+ BinString* GetFirstString() { return dummy_string.NextString(); }
+
+ u32 reserved_8;
+ u32 reserved_c;
+ int length;
+ BinString dummy_string;
+};
+
+} // namespace ore
diff --git a/lib/EventFlow/include/ore/StringView.h b/lib/EventFlow/include/ore/StringView.h
new file mode 100644
index 00000000..f23d47f5
--- /dev/null
+++ b/lib/EventFlow/include/ore/StringView.h
@@ -0,0 +1,79 @@
+#pragma once
+
+#include <algorithm>
+#include <ore/Types.h>
+#include <string>
+
+namespace ore {
+
+template <typename T>
+constexpr size_t StringLength(const T* str) {
+ if (str == nullptr || str[0] == 0)
+ return 0;
+
+ size_t len = 0;
+ while (*str++ != 0)
+ ++len;
+
+#ifdef MATCHING_HACK_NX_CLANG
+ __builtin_assume(len <= 0xffffffff);
+#endif
+ return len;
+}
+
+template <typename T>
+class TStringView {
+public:
+ // Annoyingly enough, this cannot be defaulted (otherwise Clang will not dynamically
+ // initialize static StringView variables).
+ TStringView() {}
+
+ constexpr TStringView(const T* data, size_t len) : m_data(data), m_len(len) {}
+
+ /// @param data A null-terminated string. Must not be nullptr.
+ // NOLINTNEXTLINE(google-explicit-constructor)
+ TStringView(const T* data) : m_data(data), m_len(StringLength(data)) {}
+
+ constexpr const T* data() const { return m_data; }
+ constexpr int size() const { return m_len; }
+ constexpr int length() const { return m_len; }
+ constexpr bool empty() const { return size() == 0; }
+
+ constexpr auto begin() const { return m_data; }
+ constexpr auto cbegin() const { return m_data; }
+ constexpr auto end() const { return m_data + m_len; }
+ constexpr auto cend() const { return m_data + m_len; }
+
+ const T& operator[](size_t idx) const { return m_data[idx]; }
+
+ static int Compare(TStringView lhs, TStringView rhs) {
+ const T* s1 = lhs.data();
+ const T* s2 = rhs.data();
+ int len = std::min(lhs.size(), rhs.size());
+ if (len < 1)
+ return lhs.size() - rhs.size();
+ while (len-- > 0) {
+ if (*s1 == 0 || *s1 != *s2)
+ return *s1 - *s2;
+ ++s1, ++s2;
+ }
+ return lhs.size() - rhs.size();
+ }
+
+ int Compare(TStringView rhs) const { return Compare(*this, rhs); }
+
+ friend bool operator==(TStringView lhs, TStringView rhs) {
+ return lhs.size() == rhs.size() && Compare(lhs, rhs) == 0;
+ }
+
+ friend bool operator!=(TStringView lhs, TStringView rhs) { return !operator==(lhs, rhs); }
+
+private:
+ const T* m_data{};
+ u32 m_len{};
+};
+
+using StringView = TStringView<char>;
+using WStringView = TStringView<wchar_t>;
+
+} // namespace ore
diff --git a/lib/EventFlow/include/ore/Types.h b/lib/EventFlow/include/ore/Types.h
new file mode 100644
index 00000000..601d9dd6
--- /dev/null
+++ b/lib/EventFlow/include/ore/Types.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <cstddef>
+#include <cstdint>
+
+using u8 = std::uint8_t;
+using u16 = std::uint16_t;
+using u32 = std::uint32_t;
+using u64 = std::uint64_t;
+
+using s8 = std::int8_t;
+using s16 = std::int16_t;
+using s32 = std::int32_t;
+using s64 = std::int64_t;
+
+using f32 = float;
+using f64 = double;
+
+using char16 = char16_t;
+using size_t = std::size_t;
diff --git a/lib/EventFlow/src/evfl/Action.cpp b/lib/EventFlow/src/evfl/Action.cpp
new file mode 100644
index 00000000..5fe7a490
--- /dev/null
+++ b/lib/EventFlow/src/evfl/Action.cpp
@@ -0,0 +1,56 @@
+#include <evfl/Action.h>
+#include <evfl/Flowchart.h>
+
+namespace evfl {
+
+ActionDoneHandler::ActionDoneHandler(FlowchartObj* obj, FlowchartContext* context, int node_idx)
+ : m_context(context) {
+ m_node_idx = node_idx;
+ m_obj = obj;
+ m_node_counter = context->GetNode(node_idx).GetNodeCounter();
+}
+
+FlowchartContextNode* ActionDoneHandler::GetContextNode() {
+ if (!m_context)
+ return nullptr;
+
+ auto& node = m_context->GetNode(m_node_idx);
+ if (node.GetNodeCounter() != m_node_counter)
+ return nullptr;
+
+ return &node;
+}
+
+void ActionDoneHandler::InvokeFromFlowchartImpl() {
+ auto* node = GetContextNode();
+ if (!node)
+ return;
+
+ node->m_state = FlowchartContextNode::State::kDone;
+ m_context->ProcessContext();
+}
+
+void ActionDoneHandler::InvokeFromTimelineImpl() {}
+
+bool ActionDoneHandler::IsWaitingJoin() {
+ if (!m_context)
+ return false;
+
+ const auto& node = m_context->GetNode(m_node_idx);
+ if (node.GetNodeCounter() != m_node_counter)
+ return false;
+
+ return node.GetState() == FlowchartContextNode::State::kWaiting;
+}
+
+bool ActionDoneHandler::CancelWaiting() {
+ auto* node = GetContextNode();
+ if (!node)
+ return false;
+
+ node->m_state = FlowchartContextNode::State::kInvoked;
+ m_handled = false;
+ return true;
+}
+
+} // namespace evfl
diff --git a/lib/EventFlow/src/evfl/Flowchart.cpp b/lib/EventFlow/src/evfl/Flowchart.cpp
new file mode 100644
index 00000000..84c0af05
--- /dev/null
+++ b/lib/EventFlow/src/evfl/Flowchart.cpp
@@ -0,0 +1,694 @@
+#include <algorithm>
+#include <evfl/Action.h>
+#include <evfl/Flowchart.h>
+#include <evfl/Param.h>
+#include <evfl/Query.h>
+#include <evfl/ResFlowchart.h>
+#include <iterator>
+#include <ore/Array.h>
+#include <ore/BitUtils.h>
+#include <ore/IterRange.h>
+#include <ore/ResDic.h>
+#include <ore/StringView.h>
+
+namespace evfl {
+
+int FlowchartContext::s_GlobalCounter{};
+
+FlowchartContext::FlowchartContext() {
+ m_handlers.SetOffset(ActionDoneHandler::GetListNodeOffset());
+}
+
+void FlowchartContext::Start(MetaDataPack* pack) {
+ auto& obj = m_objs[m_obj_idx];
+ const auto& entry_point = obj.GetFlowchart()->entry_points.Get()[m_active_entry_point_idx];
+
+ const auto main_event_idx = entry_point.main_event_idx;
+ if (main_event_idx == 0xffff)
+ return;
+
+ m_metadata_pack = pack;
+
+ const int node_idx = AllocNode();
+ auto& node = m_nodes[node_idx];
+ node.m_obj = &obj;
+ node.m_event_idx = main_event_idx;
+ node.m_next_node_idx = -1;
+ node.m_idx = node_idx;
+ node.m_state = FlowchartContextNode::State::kNotInvoked;
+ AllocVariablePack(node, entry_point);
+
+ ProcessContext();
+}
+
+int FlowchartContext::AllocNode() {
+ int idx = m_next_node_idx;
+
+ if (idx == 0xffff) {
+ idx = m_nodes.size();
+ m_nodes.Resize(2 * m_nodes.size());
+ for (int i = idx, n = m_nodes.size(); i < n - 1; ++i) {
+ m_nodes[i].m_next_node_idx = i + 1;
+ m_nodes[i].m_state = FlowchartContextNode::State::kFree;
+ }
+ }
+
+ auto& node = m_nodes[idx];
+ m_next_node_idx = node.m_next_node_idx;
+ node.m_event_idx = -1;
+ node.m_next_node_idx = -1;
+ node.m_idx = -1;
+ node.m_owns_variable_pack = false;
+ node.m_obj = nullptr;
+ node.m_variable_pack = nullptr;
+ node.m_node_counter = ++s_GlobalCounter;
+ node.m_state = FlowchartContextNode::State::kInvalid;
+ ++m_num_allocated_nodes;
+ return idx;
+}
+
+void FlowchartContext::AllocVariablePack(FlowchartContextNode& node,
+ const ResEntryPoint& entry_point) {
+ FreeVariablePack(node);
+
+ if (entry_point.num_variable_defs != 0) {
+ auto* pack = m_allocator.New<VariablePack>();
+ pack->Init(m_allocator.GetArg(), &entry_point);
+ node.m_variable_pack = pack;
+ node.m_owns_variable_pack = true;
+ }
+}
+
+void FlowchartContext::ProcessContext() {
+ if (m_is_processing) {
+ _91 = 1;
+ } else {
+ m_is_processing = true;
+ do {
+ _91 = 0;
+ for (int i = 0, n = m_nodes.size(); i < n; ++i) {
+ if (!m_nodes[i].IsInvalidOrFree())
+ _91 = (ProcessContextNode(i) | (_91 != 0)) & 1;
+ }
+ } while (_91);
+ m_is_processing = false;
+ }
+}
+
+FlowchartObj* FlowchartContext::FindFlowchartObj(ore::StringView name) {
+ auto it = std::find_if(m_objs.begin(), m_objs.end(), [&](const FlowchartObj& obj) {
+ return name == *obj.GetFlowchart()->name.Get();
+ });
+ return it == m_objs.end() ? nullptr : it;
+}
+
+const FlowchartObj* FlowchartContext::FindFlowchartObj(ore::StringView name) const {
+ auto it = std::find_if(m_objs.begin(), m_objs.end(), [&](const FlowchartObj& obj) {
+ return name == *obj.GetFlowchart()->name.Get();
+ });
+ return it == m_objs.end() ? nullptr : it;
+}
+
+void FlowchartContext::UnbindAll() {
+ Clear();
+ for (auto it = m_objs.begin(); it != m_objs.end(); ++it)
+ it->GetActBinder().UnbindAll();
+}
+
+void FlowchartContext::Clear() {
+ for (int i = 0, n = m_nodes.size(); i < n; ++i) {
+ FreeVariablePack(m_nodes[i]);
+ m_nodes[i].Reset();
+ if (i < n - 1) {
+ m_nodes[i].m_next_node_idx = i + 1;
+ m_nodes[i].m_state = FlowchartContextNode::State::kFree;
+ }
+ }
+
+ m_next_node_idx = 0;
+ m_num_allocated_nodes = 0;
+ m_metadata_pack = nullptr;
+
+ while (!m_handlers.Empty()) {
+ auto* handler = m_handlers.Front();
+ handler->m_list_node.Erase();
+ handler->Reset();
+ }
+}
+
+void FlowchartContext::FreeVariablePack(FlowchartContextNode& node) {
+ if (node.m_variable_pack && node.m_owns_variable_pack)
+ m_allocator.DeleteAndNull(node.m_variable_pack);
+
+ node.m_variable_pack = nullptr;
+ node.m_owns_variable_pack = false;
+}
+
+void FlowchartContext::CopyVariablePack(FlowchartContextNode& src, FlowchartContextNode& dst) {
+ FreeVariablePack(dst);
+ dst.m_variable_pack = src.m_variable_pack;
+ dst.m_owns_variable_pack = false;
+}
+
+bool FlowchartContext::ProcessContextNode(int node_idx) {
+ using State = FlowchartContextNode::State;
+ auto& node = GetNode(node_idx);
+ while (true) {
+ auto* obj = node.m_obj;
+ const auto* flowchart = obj->GetFlowchart();
+ const auto& event = flowchart->events.Get()[node.m_event_idx];
+
+ int next_event_idx = -1;
+
+ switch (event.type) {
+ case ResEvent::EventType::kAction: {
+ switch (node.m_state) {
+ case State::kNotInvoked: {
+ node.m_state = State::kInvoked;
+
+ const auto actor_idx = event.actor_idx;
+#ifdef EVFL_VER_LABO
+ const auto& actor = flowchart->actors.Get()[actor_idx];
+#else
+ const auto& actor = obj->GetFlowchart()->actors.Get()[actor_idx];
+#endif
+ const auto* actions = actor.actions.Get();
+ const auto* arg_name = actor.argument_name.Get();
+ const auto action_idx = event.actor_action_idx;
+
+ const auto* binding = &obj->GetActBinder().GetBindings()[actor_idx];
+ if (!arg_name->empty()) {
+ binding = TrackBackArgumentActor(node_idx, *arg_name);
+ if (!binding)
+ return false;
+ }
+
+ const auto* action = binding->GetAction(*actions[action_idx].name.Get());
+ const ActionArg arg(this, node_idx, binding->GetUserData(), action->user_data,
+ node.m_variable_pack, &event);
+ ActionDoneHandler done_handler(obj, this, node_idx);
+ m_handlers.InsertFront(&done_handler);
+ action->handler(arg, std::move(done_handler));
+ return false;
+ } // action case State::kNotInvoked
+ case State::kDone:
+ next_event_idx = event.next_event_idx;
+ break;
+ default:
+ return false;
+ }
+ break;
+ } // case ResEvent::EventType::kAction
+
+ case ResEvent::EventType::kSwitch: {
+ if (node.m_state != State::kNotInvoked)
+ return false;
+
+ const auto actor_idx = event.actor_idx;
+ const auto& actor = flowchart->actors.Get()[actor_idx];
+ const auto* queries = actor.queries.Get();
+ const auto* arg_name = actor.argument_name.Get();
+ const auto query_idx = event.actor_query_idx;
+
+ const auto* binding = &obj->GetActBinder().GetBindings()[actor_idx];
+ if (!arg_name->empty()) {
+ binding = TrackBackArgumentActor(node_idx, *arg_name);
+ if (!binding)
+ return false;
+ }
+
+ const auto* query = binding->GetQuery(*queries[query_idx].name.Get());
+ const QueryArg arg(this, node_idx, binding->GetUserData(), query->user_data, &event,
+ node.m_variable_pack);
+ const int result = query->handler(arg);
+
+ next_event_idx = 0xffff;
+ ore::Array<const ResCase> cases{event.cases.Get(), event.num_cases};
+ for (const auto& case_ : cases) {
+ if (case_.value == result) {
+ next_event_idx = case_.event_idx;
+ break;
+ }
+ }
+ break;
+ } // case ResEvent::EventType::kSwitch
+
+ case ResEvent::EventType::kFork: {
+ if (node.m_state != State::kNotInvoked)
+ return false;
+
+ node.m_event_idx = event.join_event_idx;
+ node.m_state = State::kInvoked;
+ UpdateNodeCounter(node_idx);
+
+ int prev_fork_node_idx = -1;
+ int first_fork_node_idx = -1;
+ int last_fork_node_idx = -1;
+
+ const ore::Array<const u16> forks{event.fork_event_indices.Get(), event.num_forks};
+ for (const auto& fork : forks) {
+ last_fork_node_idx = AllocNode();
+ auto& fork_node = GetNode(last_fork_node_idx);
+ fork_node.m_obj = obj;
+ fork_node.m_event_idx = fork;
+ fork_node.m_next_node_idx = node_idx;
+ fork_node.m_idx = prev_fork_node_idx;
+ fork_node.m_state = State::kNotInvoked;
+ if (first_fork_node_idx == -1)
+ first_fork_node_idx = last_fork_node_idx;
+ CopyVariablePack(node, fork_node);
+ prev_fork_node_idx = last_fork_node_idx;
+ }
+
+ GetNode(first_fork_node_idx).m_idx = u16(last_fork_node_idx);
+ return true;
+ } // case ResEvent::EventType::kFork
+
+ case ResEvent::EventType::kJoin: {
+ if (node.m_state != State::kDone)
+ return false;
+ next_event_idx = event.next_event_idx;
+ break;
+ } // case ResEvent::EventType::kJoin
+
+ case ResEvent::EventType::kSubFlow: {
+ bool called;
+ bool valid_parameters;
+ bool tried_invoking = false;
+ switch (node.m_state) {
+ case State::kNotInvoked: {
+ tried_invoking = true;
+ node.m_state = State::kInvoked;
+
+ const ore::StringView sub_flow_flowchart = *event.sub_flow_flowchart.Get();
+ if (!sub_flow_flowchart.empty()) {
+ obj = FindFlowchartObj(sub_flow_flowchart);
+ if (obj == nullptr) {
+ called = false;
+ valid_parameters = false;
+ break;
+ }
+ }
+
+ const int entry_point_idx = obj->GetFlowchart()->entry_point_names.Get()->FindIndex(
+ *event.sub_flow_entry_point.Get());
+ if (entry_point_idx == -1) {
+ called = false;
+ valid_parameters = false;
+ break;
+ }
+
+ const auto& entry_point = obj->GetFlowchart()->entry_points.Get()[entry_point_idx];
+
+ const u16 main_event_idx = entry_point.main_event_idx;
+ if (main_event_idx == 0xffff) {
+ node.m_state = State::kDone;
+ called = false;
+ valid_parameters = true;
+ break;
+ }
+
+ // Optimization: if this is a tail call, we don't need to allocate a new node.
+ if (event.next_event_idx == 0xffff && event.params.Get() == nullptr) {
+ node.m_obj = obj;
+ node.m_event_idx = main_event_idx;
+ node.m_state = State::kNotInvoked;
+ AllocVariablePack(node, entry_point);
+ UpdateNodeCounter(node_idx);
+ } else {
+ const auto sub_flow_node_idx = AllocNode();
+ auto& sub_flow_node = GetNode(sub_flow_node_idx);
+ sub_flow_node.m_obj = obj;
+ sub_flow_node.m_event_idx = main_event_idx;
+ sub_flow_node.m_next_node_idx = node_idx;
+ sub_flow_node.m_idx = sub_flow_node_idx;
+ sub_flow_node.m_state = State::kNotInvoked;
+ AllocVariablePack(sub_flow_node, entry_point);
+ }
+ CallSubFlowCallback(flowchart, &event, SubFlowCallbackType::kEnter);
+ called = true;
+ /// @bug valid_parameters should have been initialized to true here.
+ /// This bug causes LLVM to generate dumb code like
+ /// mov w8, wzr; mov w9, wzr; orr w8, w8, w9 (for the failure cases above)
+ /// or more worryingly:
+ /// mov w8, #1; orr w8, w8, w9
+ /// where w9 is actually undefined!
+#ifdef AVOID_UB
+ valid_parameters = true;
+#endif
+ break;
+ } // subflow case State::kNotInvoked
+ case State::kInvoked:
+ return false;
+ case State::kDone:
+ next_event_idx = event.next_event_idx;
+ CallSubFlowCallback(flowchart, &event, SubFlowCallbackType::kLeave);
+ break;
+ default:
+ break;
+ }
+ if (tried_invoking)
+ return valid_parameters || called;
+ break;
+ } // case ResEvent::EventType::kSubFlow
+ }
+
+ // We are checking for 0xffff (a 0xffff that comes from the ResEvent data), *not* -1.
+ if (next_event_idx == 0xffff) {
+ bool ready = false;
+ auto* node_2 = &node;
+ if (node.m_idx != node_idx) {
+ do {
+ node_2 = &GetNode(node_2->m_idx);
+ ready |= node_2->m_state != State::kWaiting;
+ } while (node_2->m_idx != node_idx);
+ }
+
+ if (!ready) {
+ if (node.m_next_node_idx != 0xffff)
+ GetNode(node.m_next_node_idx).m_state = State::kDone;
+
+ int next_node_idx = node_idx;
+ int next;
+ do {
+ auto& node_to_free = GetNode(next_node_idx);
+ next = node_to_free.m_idx;
+ FreeVariablePack(node_to_free);
+ node_to_free.Reset();
+ node_to_free.m_next_node_idx = m_next_node_idx;
+ node_to_free.m_state = State::kFree;
+ m_next_node_idx = next_node_idx;
+ --m_num_allocated_nodes;
+ next_node_idx = next;
+ } while (next != node_idx);
+ return true;
+ }
+
+ if (event.type == ResEvent::EventType::kAction) {
+ node.m_state = State::kWaiting;
+ return true;
+ }
+
+ node_2->m_idx = node.m_idx;
+ auto& node_to_free = GetNode(node_idx);
+ FreeVariablePack(node_to_free);
+ node_to_free.Reset();
+ node_to_free.m_next_node_idx = m_next_node_idx;
+ node_to_free.m_state = State::kFree;
+ m_next_node_idx = node_idx;
+ --m_num_allocated_nodes;
+ return true;
+ }
+
+ node.m_event_idx = next_event_idx;
+ node.m_state = State::kNotInvoked;
+ UpdateNodeCounter(node_idx);
+ }
+}
+
+ActorBinding* FlowchartContext::TrackBackArgumentActor(int node_idx, const ore::StringView& name) {
+ if (node_idx == -1)
+ return nullptr;
+
+ const ore::ResMetaData* metadata;
+ const MetaDataPack* metadata_pack;
+ const VariablePack* variable_pack;
+ FlowchartObj* obj;
+ const ParamAccessor accessor{this, GetNode(node_idx).GetNextNodeIdx()};
+ const auto real_name =
+ accessor.TrackBackArgument(&metadata, &metadata_pack, &variable_pack, &obj, name);
+
+ if (real_name.empty() || !metadata || !obj)
+ return nullptr;
+
+ const auto* param = metadata->Get(real_name, ore::ResMetaData::DataType::kActorIdentifier);
+ if (!param)
+ return nullptr;
+
+ const ore::StringView actor_name = *param->value.actor.name.Get();
+ const ore::StringView actor_sub_name = *param->value.actor.sub_name.Get();
+
+ auto actor = obj->GetFlowchart()->actors.Get();
+ for (int i = 0; i < int(obj->GetFlowchart()->num_actors); ++i, ++actor) {
+ if (*actor->name.Get() == actor_name && *actor->secondary_name.Get() == actor_sub_name) {
+ if (!actor->argument_name.Get()->empty())
+ return nullptr;
+ return &obj->GetActBinder().GetBindings()[i];
+ }
+ }
+
+ return nullptr;
+}
+
+bool FlowchartContext::IsUsing(const ResFlowchart* flowchart) const {
+ auto* obj = FindFlowchartObj(*flowchart->name.Get());
+ return obj && obj->GetActBinder().IsUsed();
+}
+
+// NON_MATCHING: if (((state | 4) & 7) != 4) -- extremely weird check
+bool FlowchartContext::IsPlaying(const ResFlowchart* flowchart) const {
+ int state = 2;
+ for (int i = 0, n = m_nodes.size(); i < n; ++i) {
+ if (m_nodes[i].IsInvalidOrFree()) {
+ state = 4;
+ } else {
+ const auto flowchart_name = ore::StringView(*flowchart->name.Get());
+ const auto node_flowchart_name =
+ ore::StringView(*m_nodes[i].m_obj->GetFlowchart()->name.Get());
+ if (node_flowchart_name == flowchart_name) {
+ state = 1;
+ } else {
+ state = 0;
+ }
+ }
+
+ if (((state | 4) & 7) != 4)
+ break;
+ }
+ return state != 2;
+}
+
+const ore::Array<ActorBinding>*
+FlowchartContext::GetUsedResActors(ore::StringView flowchart_name) const {
+ auto* obj = FindFlowchartObj(flowchart_name);
+ if (!obj)
+ return nullptr;
+ return obj->GetActBinder().GetUsedResActors();
+}
+
+/// Recursively checks subflow calls in the specified entry point for missing flowcharts
+/// or entry points.
+/// @param result Optional.
+/// @param visited Visited entry points (one BitArray per flowchart)
+/// @param flowchart_idx Index of the flowchart to which the entry point belongs.
+/// @param entry_point_idx Index of the entry point to be checked.
+/// @returns true on success, false on failure
+bool CheckSubFlowCalls(FlowchartContext::Builder::BuildResult* result,
+ const ore::IterRange<const ResFlowchart* const*>& flowcharts,
+ ore::Array<ore::BitArray>& visited, int flowchart_idx, int entry_point_idx) {
+ if (visited[flowchart_idx].Test(entry_point_idx))
+ return true;
+ visited[flowchart_idx].Set(entry_point_idx);
+
+ const auto* flowchart = *std::next(flowcharts.begin(), flowchart_idx);
+ const auto entry_point_name =
+ flowchart->entry_point_names.Get()->GetEntries()[1 + entry_point_idx].GetKey();
+ const auto* entry_point = flowchart->GetEntryPoint(entry_point_name);
+
+ for (u16 i = 0; i != entry_point->num_sub_flow_event_indices; ++i) {
+ const auto& event = flowchart->events.Get()[entry_point->sub_flow_event_indices.Get()[i]];
+ ore::StringView sub_flow_flowchart = *event.sub_flow_flowchart.Get();
+ const ore::StringView sub_flow_entry_point = *event.sub_flow_entry_point.Get();
+
+ auto sub_flowchart_idx = flowchart_idx;
+ auto* sub_flowchart_res = flowchart;
+
+ if (!sub_flow_flowchart.empty()) {
+ const auto it =
+ std::find_if(flowcharts.begin(), flowcharts.end(), [=](const ResFlowchart* f) {
+ return sub_flow_flowchart == *f->name.Get();
+ });
+
+ if (it == flowcharts.end()) {
+ if (result) {
+ result->result =
+ FlowchartContext::Builder::BuildResultType::kResFlowchartNotFound;
+ result->missing_flowchart_name = sub_flow_flowchart;
+ result->missing_entry_point_name = {};
+ }
+ return false;
+ }
+
+ sub_flowchart_idx = std::distance(flowcharts.begin(), it);
+ sub_flowchart_res = *it;
+
+ } else {
+ sub_flow_flowchart = *flowchart->name.Get();
+ }
+
+ const int sub_entry_point_idx =
+ sub_flowchart_res->entry_point_names.Get()->FindIndex(sub_flow_entry_point);
+
+ if (sub_entry_point_idx == -1) {
+ if (result) {
+ result->result = FlowchartContext::Builder::BuildResultType::kEntryPointNotFound;
+ result->missing_flowchart_name = sub_flow_flowchart;
+ result->missing_entry_point_name = sub_flow_entry_point;
+ }
+ return false;
+ }
+
+ if (!CheckSubFlowCalls(result, flowcharts, visited, sub_flowchart_idx, sub_entry_point_idx))
+ return false;
+ }
+
+ if (result) {
+ result->result = FlowchartContext::Builder::BuildResultType::kSuccess;
+ result->missing_flowchart_name = {};
+ result->missing_entry_point_name = {};
+ }
+ return true;
+}
+
+bool FlowchartContext::Builder::SetEntryPoint(const ore::StringView& flowchart_name,
+ const ore::StringView& entry_point_name) {
+ return SetEntryPoint(nullptr, flowchart_name, entry_point_name);
+}
+
+bool FlowchartContext::Builder::SetEntryPoint(BuildResult* result,
+ const ore::StringView& flowchart_name,
+ const ore::StringView& entry_point_name) {
+ const auto* flowchart_it =
+ std::find_if(m_flowcharts.begin(), m_flowcharts.end(), [=](const ResFlowchart* flowchart) {
+ return flowchart_name == *flowchart->name.Get();
+ });
+
+ if (flowchart_it == m_flowcharts.end()) {
+ if (result) {
+ result->result = BuildResultType::kResFlowchartNotFound;
+ result->missing_flowchart_name = flowchart_name;
+ result->missing_entry_point_name = {};
+ }
+ return false;
+ }
+
+ const auto entry_point_idx =
+ (*flowchart_it)->entry_point_names.Get()->FindIndex(entry_point_name);
+
+ if (entry_point_idx == -1) {
+ if (result) {
+ result->result = BuildResultType::kEntryPointNotFound;
+ result->missing_flowchart_name = flowchart_name;
+ result->missing_entry_point_name = entry_point_name;
+ }
+ return false;
+ }
+
+ m_flowchart_idx = std::distance(m_flowcharts.begin(), flowchart_it);
+ m_entry_point_idx = entry_point_idx;
+ if (result) {
+ result->result = BuildResultType::kSuccess;
+ result->missing_flowchart_name = {};
+ result->missing_entry_point_name = {};
+ }
+ return true;
+}
+
+bool FlowchartContext::Builder::BuildImpl(BuildResult* result, FlowchartRange flowcharts,
+ FlowchartContext* context, AllocateArg allocate_arg,
+ ore::Buffer flowchart_obj_buffer) {
+ context->m_nodes.Reset();
+ EvflAllocator allocator{allocate_arg};
+ context->m_allocator = allocator;
+
+ const int num_flowcharts = flowcharts.size();
+
+ ore::Array<ore::BitArray> visited_entry_points;
+ visited_entry_points.SetBuffer(num_flowcharts, &allocator);
+ visited_entry_points.UninitializedDefaultConstructElements();
+
+ const auto clean_up_visited_entry_points = [&] {
+ if (auto* data = visited_entry_points.data()) {
+ for (auto it = data; it != data + visited_entry_points.size(); ++it)
+ it->FreeBufferIfNeeded(&allocator);
+
+ visited_entry_points.DestructElements();
+ allocator.Free(data);
+ }
+ };
+
+ for (int i = 0; i < num_flowcharts; ++i) {
+ visited_entry_points[i].AllocateBuffer(&allocator,
+ (flowcharts.begin()[i])->num_entry_points);
+ }
+
+ if (!CheckSubFlowCalls(result, flowcharts, visited_entry_points, m_flowchart_idx,
+ m_entry_point_idx)) {
+ clean_up_visited_entry_points();
+ return false;
+ }
+
+ context->m_objs.ConstructElements(flowchart_obj_buffer);
+
+ for (int i = 0; i < num_flowcharts; ++i) {
+ auto* obj = &context->m_objs[i];
+ FlowchartObj::Builder obj_builder{flowcharts.begin()[i], &visited_entry_points[i]};
+
+ if (!obj_builder.Build(obj, &context->m_allocator, flowcharts)) {
+ clean_up_visited_entry_points();
+ context->m_objs.ClearWithoutFreeing();
+
+ if (result) {
+ const auto* flowchart = flowcharts.begin()[m_flowchart_idx];
+ const ore::StringView name = *flowchart->name.Get();
+ const ore::StringView ep_name = flowchart->GetEntryPointName(m_entry_point_idx);
+ result->result = BuildResultType::kInvalidOperation;
+ result->missing_flowchart_name = name;
+ result->missing_entry_point_name = ep_name;
+ }
+ return false;
+ }
+ }
+
+ context->m_obj_idx = m_flowchart_idx;
+ context->m_active_entry_point_idx = m_entry_point_idx;
+ context->m_nodes.Init(&context->m_allocator, 16);
+ context->m_nodes.Resize(16);
+ context->Clear();
+ clean_up_visited_entry_points();
+
+ if (result) {
+ result->result = BuildResultType::kSuccess;
+ result->missing_flowchart_name = {};
+ result->missing_entry_point_name = {};
+ }
+ return true;
+}
+
+bool FlowchartContext::Builder::Build(FlowchartContext* context, AllocateArg allocate_arg) {
+ return Build(nullptr, context, allocate_arg);
+}
+
+bool FlowchartContext::Builder::Build(BuildResult* result, FlowchartContext* context,
+ AllocateArg allocate_arg) {
+ context->Dispose();
+
+ EvflAllocator allocator{allocate_arg};
+
+ ore::DynArrayList<const ResFlowchart*> flowcharts{&allocator};
+ flowcharts.Init(&allocator);
+ flowcharts.DeduplicateCopy(m_flowcharts);
+
+ FlowchartRange range{flowcharts};
+ ore::Buffer obj_buffer{};
+ obj_buffer.Allocate<FlowchartObj>(&allocator, flowcharts.size());
+
+ if (!BuildImpl(result, range, context, allocate_arg, obj_buffer)) {
+ obj_buffer.Free(&allocator);
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace evfl
diff --git a/lib/EventFlow/src/evfl/FlowchartObj.cpp b/lib/EventFlow/src/evfl/FlowchartObj.cpp
new file mode 100644
index 00000000..b0306b59
--- /dev/null
+++ b/lib/EventFlow/src/evfl/FlowchartObj.cpp
@@ -0,0 +1,243 @@
+#include <algorithm>
+#include <evfl/Flowchart.h>
+#include <evfl/ResActor.h>
+#include <evfl/ResFlowchart.h>
+#include <ore/Allocator.h>
+#include <ore/Array.h>
+#include <ore/BitUtils.h>
+#include <ore/IterRange.h>
+#include <ore/ResMetaData.h>
+#include <ore/Types.h>
+
+namespace evfl {
+
+namespace {
+
+void RegisterBindings(FlowchartObj* obj, ore::BitArray* visited_events, int event_idx) {
+ const ResActor* actors = obj->GetFlowchart()->actors.Get();
+ const ResEvent* events = obj->GetFlowchart()->events.Get();
+
+ while (event_idx != 0xffff && !visited_events->Test(event_idx)) {
+ visited_events->Set(event_idx);
+ const ResEvent& event = events[event_idx];
+ const auto event_type = event.type;
+
+ switch (event_type.mValue) {
+ case ResEvent::EventType::kAction:
+ if (actors[event.actor_idx].argument_name.Get()->empty()) {
+ auto* action = actors[event.actor_idx].actions.Get() + event.actor_action_idx;
+ obj->GetActBinder().RegisterAction(event.actor_idx, action);
+ }
+ break;
+ case ResEvent::EventType::kSwitch:
+ if (actors[event.actor_idx].argument_name.Get()->empty()) {
+ auto* query = actors[event.actor_idx].queries.Get() + event.actor_query_idx;
+ obj->GetActBinder().RegisterQuery(event.actor_idx, query);
+ }
+ break;
+ default:
+ break;
+ }
+
+ // Process the next event.
+
+ switch (event_type) {
+ case ResEvent::EventType::kAction:
+ case ResEvent::EventType::kJoin:
+ case ResEvent::EventType::kSubFlow:
+ event_idx = event.next_event_idx;
+ break;
+ case ResEvent::EventType::kSwitch: {
+ ore::Array<const ResCase> cases{event.cases.Get(), event.num_cases};
+ std::for_each(cases.begin(), cases.end(), [&](const ResCase& case_) {
+ RegisterBindings(obj, visited_events, case_.event_idx);
+ });
+ return;
+ }
+ case ResEvent::EventType::kFork: {
+ ore::Array<const u16> forks{event.fork_event_indices.Get(), event.num_forks};
+ std::for_each(forks.begin(), forks.end(),
+ [&](u16 fork) { RegisterBindings(obj, visited_events, fork); });
+ event_idx = event.join_event_idx;
+ break;
+ }
+ }
+ }
+}
+
+struct ActorArgumentInfo {
+ bool operator==(const ActorArgumentInfo& rhs) const {
+ return entry_point_idx == rhs.entry_point_idx && flowchart == rhs.flowchart &&
+ rhs.GetActorArgumentName() == GetActorArgumentName();
+ }
+ bool operator!=(const ActorArgumentInfo& rhs) const { return !(*this == rhs); }
+
+ ore::StringView GetActorArgumentName() const {
+ return ore::StringView(actor_argument_name, actor_argument_name_len);
+ }
+
+ const ResFlowchart* flowchart;
+ int entry_point_idx;
+ const char* actor_argument_name;
+ size_t actor_argument_name_len;
+};
+
+// NON_MATCHING: the if checks are reordered to hell for some reason
+const ResActor* FindActor(const ActorArgumentInfo& entry) {
+ ore::Array<const ResActor> actors{entry.flowchart->actors.Get(), entry.flowchart->num_actors};
+ for (const auto& actor : actors) {
+ if (actor.entry_point_idx != entry.entry_point_idx)
+ continue;
+ if (actor.argument_name.Get()->empty())
+ continue;
+ if (*actor.argument_name.Get() != entry.GetActorArgumentName())
+ continue;
+ return &actor;
+ }
+ return nullptr;
+}
+
+void RegisterBindingsForArguments(ActBinder& binder, int actor_idx,
+ const ore::IterRange<const ResFlowchart* const*>& flowcharts,
+ ore::ArrayListBase<ActorArgumentInfo>& processed,
+ const ActorArgumentInfo& entry) {
+ if (std::find(processed.begin(), processed.end(), entry) != processed.end())
+ return;
+
+ processed.emplace_back(entry);
+
+ if (auto* actor = FindActor(entry)) {
+ ore::Array<const ResAction> actions{actor->actions.Get(), actor->num_actions};
+ for (const auto& action : actions)
+ binder.RegisterAction(actor_idx, &action);
+
+ ore::Array<const ResQuery> queries{actor->queries.Get(), actor->num_queries};
+ for (const auto& query : queries)
+ binder.RegisterQuery(actor_idx, &query);
+ }
+
+ const auto& entry_point = entry.flowchart->entry_points.Get()[entry.entry_point_idx];
+ ore::Array<const u16> sub_flow_event_indices{entry_point.sub_flow_event_indices.Get(),
+ entry_point.num_sub_flow_event_indices};
+ for (auto sub_flow_event_idx : sub_flow_event_indices) {
+ const auto& event = entry.flowchart->events.Get()[sub_flow_event_idx];
+ const ore::StringView sub_flow_flowchart = *event.sub_flow_flowchart.Get();
+ const ore::StringView sub_flow_entry_point = *event.sub_flow_entry_point.Get();
+
+ auto* params = event.params.Get();
+ if (!params)
+ continue;
+
+ for (int i = 0; i < params->num_items; ++i) {
+ auto* param = (&params->value.container + i)->Get();
+ if (param->type != ore::ResMetaData::DataType::kArgument)
+ continue;
+ if (entry.GetActorArgumentName() != *param->value.str.Get())
+ continue;
+
+ const ResFlowchart* flowchart = entry.flowchart;
+ if (!sub_flow_flowchart.empty()) {
+ flowchart =
+ *std::find_if(flowcharts.begin(), flowcharts.end(), [=](const ResFlowchart* f) {
+ return sub_flow_flowchart == *f->name.Get();
+ });
+ }
+
+ const auto entry_point_idx =
+ flowchart->entry_point_names.Get()->FindIndex(sub_flow_entry_point);
+ const auto actor_argument_name = params->dictionary.Get()->GetEntries()[1 + i].GetKey();
+
+ ActorArgumentInfo arg;
+ arg.flowchart = flowchart;
+ arg.entry_point_idx = entry_point_idx;
+ arg.actor_argument_name = actor_argument_name.data();
+ arg.actor_argument_name_len = actor_argument_name.size();
+ RegisterBindingsForArguments(binder, actor_idx, flowcharts, processed, arg);
+ }
+ }
+
+ processed.pop_back();
+}
+
+void RegisterBindingsForActorIdentifiers(FlowchartObj* obj,
+ ore::IterRange<const ResFlowchart* const*> flowcharts,
+ int entry_point_idx) {
+ auto* flowchart = obj->GetFlowchart();
+ const auto& entry_point = flowchart->entry_points.Get()[entry_point_idx];
+
+ ore::IterRange<const u16*> sub_flow_event_indices{entry_point.sub_flow_event_indices.Get(),
+ entry_point.num_sub_flow_event_indices};
+ const ResEvent* events = flowchart->events.Get();
+ ore::IterRange<const ResActor*> actors{flowchart->actors.Get(), flowchart->num_actors};
+
+ for (auto sub_flow_event_idx : sub_flow_event_indices) {
+ const auto& event = events[sub_flow_event_idx];
+
+ auto* params = event.params.Get();
+ if (!params)
+ continue;
+
+ const ore::StringView sub_flow_flowchart = *event.sub_flow_flowchart.Get();
+ const ore::StringView sub_flow_entry_point = *event.sub_flow_entry_point.Get();
+ const ResFlowchart* arg_flowchart = flowchart;
+ if (!sub_flow_flowchart.empty()) {
+ arg_flowchart =
+ *std::find_if(flowcharts.begin(), flowcharts.end(), [=](const ResFlowchart* f) {
+ return sub_flow_flowchart == *f->name.Get();
+ });
+ }
+
+ for (int i = 0; i < params->num_items; ++i) {
+ auto* param = (&params->value.container + i)->Get();
+ if (param->type != ore::ResMetaData::DataType::kActorIdentifier)
+ continue;
+
+ const ore::StringView param_name =
+ params->dictionary.Get()->GetEntries()[1 + i].GetKey();
+ const ore::StringView name = *param->value.actor.name.Get();
+ const ore::StringView sub_name = *param->value.actor.sub_name.Get();
+ auto* actor = std::find_if(actors.begin(), actors.end(), [&](const ResActor& a) {
+ return name == *a.name.Get() && sub_name == *a.secondary_name.Get();
+ });
+ const int actor_idx = std::distance(actors.begin(), actor);
+ const int arg_entry_point_idx =
+ arg_flowchart->entry_point_names.Get()->FindIndex(sub_flow_entry_point);
+
+ ore::FixedArrayList<ActorArgumentInfo, 64> processed;
+ ActorArgumentInfo arg;
+ arg.flowchart = arg_flowchart;
+ arg.entry_point_idx = arg_entry_point_idx;
+ arg.actor_argument_name = param_name.data();
+ arg.actor_argument_name_len = param_name.size();
+ RegisterBindingsForArguments(obj->GetActBinder(), actor_idx, flowcharts, processed,
+ arg);
+ }
+ }
+}
+
+} // namespace
+
+bool FlowchartObj::Builder::Build(FlowchartObj* obj, ore::Allocator* allocator,
+ ore::IterRange<const ResFlowchart* const*> flowcharts) {
+ obj->m_flowchart = m_flowchart;
+ m_act_binder_builder.Build(&obj->GetActBinder(), allocator,
+ {m_flowchart->actors.Get(), m_flowchart->num_actors});
+
+ const int num_events = m_flowchart->num_events;
+ ore::BitArray visited_events{allocator, num_events};
+
+ auto* entry_points = obj->m_flowchart->entry_points.Get();
+ auto entry_point_it = m_entry_points_mask->BeginTest();
+ const auto entry_point_end = m_entry_points_mask->EndTest();
+ while (entry_point_it != entry_point_end) {
+ RegisterBindings(obj, &visited_events, entry_points[*entry_point_it].main_event_idx);
+ RegisterBindingsForActorIdentifiers(obj, flowcharts, *entry_point_it);
+ obj->GetActBinder().SetIsUsed();
+ ++entry_point_it;
+ }
+
+ visited_events.FreeBuffer(allocator);
+ return true;
+}
+
+} // namespace evfl
diff --git a/lib/EventFlow/src/evfl/Param.cpp b/lib/EventFlow/src/evfl/Param.cpp
new file mode 100644
index 00000000..596ca7ee
--- /dev/null
+++ b/lib/EventFlow/src/evfl/Param.cpp
@@ -0,0 +1,714 @@
+#include <algorithm>
+#include <evfl/Flowchart.h>
+#include <evfl/Param.h>
+#include <evfl/ResFlowchart.h>
+#include <ore/BinaryFile.h>
+#include <ore/ResDic.h>
+
+namespace evfl {
+
+namespace {
+
+constexpr ore::ResMetaData::DataType::Type
+ConvertMetaDataPackTypeToMDType(MetaDataPack::DataType::Type type) {
+ if (u32(type) < u32(MetaDataPack::DataType::Invalid()))
+ return ore::ResMetaData::DataType::Type(ore::ResMetaData::DataType::kInt + type);
+
+#ifdef MATCHING_HACK_NX_CLANG
+ // Force a branch to be generated (instead of CSEL)
+ __builtin_assume(type >= 0);
+#endif
+ return ore::ResMetaData::DataType::Invalid();
+}
+
+} // namespace
+
+void MetaDataPack::AddInt(const char* key, int value) {
+ auto& entry = AddEntry();
+ entry.key = key;
+ entry.value.i = value;
+ entry.type = DataType::kInt;
+}
+
+// NON_MATCHING: ???
+void MetaDataPack::AddBool(const char* key, bool value) {
+ auto& entry = AddEntry();
+ entry.key = key;
+ entry.value.i = value;
+ entry.type = DataType::kBool;
+}
+
+void MetaDataPack::AddFloat(const char* key, float value) {
+ auto& entry = AddEntry();
+ entry.key = key;
+ entry.value.f = value;
+ entry.type = DataType::kFloat;
+}
+
+void MetaDataPack::AddStringPtr(const char* key, const char* value) {
+ auto& entry = AddEntry();
+ entry.key = key;
+ entry.value.str = value;
+ entry.type = DataType::kString;
+}
+
+void MetaDataPack::AddWStringPtr(const char* key, const wchar_t* value) {
+ auto& entry = AddEntry();
+ entry.key = key;
+ entry.value.wstr = value;
+ entry.type = DataType::kWString;
+}
+
+MetaDataPack::Entry* MetaDataPack::Find(const ore::StringView& key) const {
+ for (auto& entry : GetEntries()) {
+ if (entry.IsKey(key))
+ return &entry;
+ }
+ return nullptr;
+}
+
+bool MetaDataPack::FindInt(int* value, const ore::StringView& key) const {
+ auto* entry = Find(key);
+ if (!entry || entry->type != DataType::kInt)
+ return false;
+ *value = entry->value.i;
+ return true;
+}
+
+bool MetaDataPack::FindBool(bool* value, const ore::StringView& key) const {
+ auto* entry = Find(key);
+ if (!entry || entry->type != DataType::kBool)
+ return false;
+ *value = entry->value.i == 1;
+ return true;
+}
+
+bool MetaDataPack::FindFloat(float* value, const ore::StringView& key) const {
+ auto* entry = Find(key);
+ if (!entry || entry->type != DataType::kFloat)
+ return false;
+ *value = entry->value.f;
+ return true;
+}
+
+bool MetaDataPack::FindString(ore::StringView* value, const ore::StringView& key) const {
+ auto* entry = Find(key);
+ if (!entry || entry->type != DataType::kString)
+ return false;
+ *value = entry->value.str;
+ return true;
+}
+
+bool MetaDataPack::FindWString(ore::WStringView* value, const ore::StringView& key) const {
+ auto* entry = Find(key);
+ if (!entry || entry->type != DataType::kWString)
+ return false;
+ *value = entry->value.wstr;
+ return true;
+}
+
+ore::ResMetaData::DataType::Type MetaDataPack::GetType(const ore::StringView& key) const {
+ auto* entry = Find(key);
+ return ConvertMetaDataPackTypeToMDType(entry ? entry->type : DataType::Invalid());
+}
+
+// NON_MATCHING: two add operands swapped
+void MetaDataPack::Builder::CalcMemSize() {
+ m_entries_byte_size = sizeof(Entry) * m_num_entries;
+ m_required_size = 0;
+ m_alignment_real = 16;
+ if (m_num_entries != 0) {
+ m_alignment_real = std::max(16, m_alignment);
+ m_buffer_offset = ore::AlignUpToPowerOf2(_14, m_alignment) - _14;
+ m_required_size = m_buffer_offset + m_entries_byte_size;
+ }
+}
+
+bool MetaDataPack::Builder::Build(MetaDataPack* pack, ore::Buffer buffer) {
+ if (m_alignment_real <= 0)
+ return false;
+ if (m_required_size > buffer.size)
+ return false;
+ pack->m_buffer.data = buffer.data;
+ pack->m_buffer.size = buffer.size;
+ const int byte_size = m_entries_byte_size;
+ pack->m_entries = reinterpret_cast<Entry*>(buffer.data + m_buffer_offset);
+ pack->m_entries_capacity = byte_size / int(sizeof(Entry));
+ pack->m_entries_num = 0;
+ return true;
+}
+
+ParamAccessor::ParamAccessor(const ore::ResMetaData* metadata) : m_metadata(metadata) {}
+
+ParamAccessor::ParamAccessor(const FlowchartContext* context, int node_idx)
+ : m_context(context), m_node_idx(node_idx) {
+ m_node_counter = context->GetNode(node_idx).GetNodeCounter();
+}
+
+const ore::ResMetaData* ParamAccessor::GetFrontResMetaData() const {
+ if (m_node_idx == -1)
+ return m_metadata;
+
+ const auto& node = m_context->GetNode(m_node_idx);
+ const auto& event = node.GetObj()->GetFlowchart()->events.Get()[node.GetEventIdx()];
+ return event.params.Get();
+}
+
+int ParamAccessor::GetParamCount() const {
+ auto* meta = GetFrontResMetaData();
+ return meta ? meta->num_items : 0;
+}
+
+ore::StringView ParamAccessor::GetParamName(int idx) const {
+ auto* meta = GetFrontResMetaData();
+ return meta->dictionary.Get()->GetEntries()[1 + idx].GetKey();
+}
+
+ParamAccessor::Type ParamAccessor::GetParamType(int idx) const {
+ Type type = ore::ResMetaData::DataType::Invalid();
+
+ const ore::ResMetaData* metadata;
+ const MetaDataPack* metadata_pack;
+ const VariablePack* variable_pack;
+ const auto param_name = GetParamName(idx);
+ const auto real_name =
+ TrackBackArgument(&metadata, &metadata_pack, &variable_pack, nullptr, param_name);
+
+ if (metadata) {
+ const int entry_idx = metadata->dictionary.Get()->FindIndex(real_name);
+ if (entry_idx == -1)
+ return ore::ResMetaData::DataType::Invalid();
+ type = (&metadata->value.container + entry_idx)->Get()->type;
+ } else if (variable_pack) {
+ type = variable_pack->GetVariableType(real_name);
+ } else if (metadata_pack) {
+ type = metadata_pack->GetType(real_name);
+ }
+
+ if (type == ore::ResMetaData::DataType::Invalid())
+ return ore::ResMetaData::DataType::Invalid();
+
+ return type;
+}
+
+ore::StringView ParamAccessor::TrackBackArgument(const ore::ResMetaData** out_metadata,
+ const MetaDataPack** out_metadata_pack,
+ const VariablePack** out_variable_pack,
+ FlowchartObj** out_obj,
+ const ore::StringView& argument) const {
+ *out_metadata = nullptr;
+ *out_metadata_pack = nullptr;
+ *out_variable_pack = nullptr;
+ if (out_obj)
+ *out_obj = nullptr;
+
+ if (m_node_idx == -1) {
+ *out_metadata = m_metadata;
+ return argument;
+ }
+
+ auto ret = argument;
+
+ for (int i = m_node_idx; i != 0xffff; i = m_context->GetNode(i).GetNextNodeIdx()) {
+ const auto& node = m_context->GetNode(i);
+ const auto* variable_pack = node.GetVariablePack();
+ const auto* events = node.GetObj()->GetFlowchart()->events.Get();
+ const auto& event = events[node.GetEventIdx()];
+
+ if (!((event.type == ResEvent::EventType::kAction && i == m_node_idx) ||
+ (event.type == ResEvent::EventType::kSwitch && i == m_node_idx) ||
+ event.type == ResEvent::EventType::kSubFlow)) {
+ continue;
+ }
+
+ const auto* params = event.params.Get();
+ if (!params)
+ return {};
+
+ const auto* param = params->Get(ret, Type::kArgument);
+ if (param) {
+ ret = *param->value.str.Get();
+ if (variable_pack && variable_pack->Contains(ret)) {
+ *out_variable_pack = variable_pack;
+ return ret;
+ }
+ continue;
+ }
+
+ *out_metadata = params;
+ if (out_obj)
+ *out_obj = node.GetObj();
+ return ret;
+ }
+
+ *out_metadata_pack = m_context->GetMetaDataPack();
+ if (*out_metadata_pack == nullptr)
+ return {};
+ return ret;
+}
+
+int ParamAccessor::GetInt(int idx) const {
+ int value;
+ FindInt(&value, GetParamName(idx));
+ return value;
+}
+
+bool ParamAccessor::FindInt(int* value, const ore::StringView& name) const {
+ const ore::ResMetaData* metadata;
+ const MetaDataPack* metadata_pack;
+ const VariablePack* variable_pack;
+ const auto real_name =
+ TrackBackArgument(&metadata, &metadata_pack, &variable_pack, nullptr, name);
+
+ if (metadata) {
+ const auto* entry = metadata->Get(real_name, Type::kInt);
+ if (entry != nullptr) {
+ *value = entry->value.i;
+ return true;
+ }
+ }
+
+ if (variable_pack && variable_pack->FindInt(value, real_name))
+ return true;
+
+ if (metadata_pack && metadata_pack->FindInt(value, real_name))
+ return true;
+
+ return false;
+}
+
+bool ParamAccessor::GetBool(int idx) const {
+ bool value;
+ FindBool(&value, GetParamName(idx));
+ return value;
+}
+
+bool ParamAccessor::FindBool(bool* value, const ore::StringView& name) const {
+ const ore::ResMetaData* metadata;
+ const MetaDataPack* metadata_pack;
+ const VariablePack* variable_pack;
+ const auto real_name =
+ TrackBackArgument(&metadata, &metadata_pack, &variable_pack, nullptr, name);
+
+ if (metadata) {
+ const auto* entry = metadata->Get(real_name, Type::kBool);
+ if (entry != nullptr) {
+ *value = entry->value.i != 0;
+ return true;
+ }
+ }
+
+ if (variable_pack && variable_pack->FindBool(value, real_name))
+ return true;
+
+ if (metadata_pack && metadata_pack->FindBool(value, real_name))
+ return true;
+
+ return false;
+}
+
+float ParamAccessor::GetFloat(int idx) const {
+ float value;
+ FindFloat(&value, GetParamName(idx));
+ return value;
+}
+
+bool ParamAccessor::FindFloat(float* value, const ore::StringView& name) const {
+ const ore::ResMetaData* metadata;
+ const MetaDataPack* metadata_pack;
+ const VariablePack* variable_pack;
+ const auto real_name =
+ TrackBackArgument(&metadata, &metadata_pack, &variable_pack, nullptr, name);
+
+ if (metadata) {
+ const auto* entry = metadata->Get(real_name, Type::kFloat);
+ if (entry != nullptr) {
+ *value = entry->value.f;
+ return true;
+ }
+ }
+
+ if (variable_pack && variable_pack->FindFloat(value, real_name))
+ return true;
+
+ if (metadata_pack && metadata_pack->FindFloat(value, real_name))
+ return true;
+
+ return false;
+}
+
+ore::StringView ParamAccessor::GetString(int idx) const {
+ ore::StringView value;
+ FindString(&value, GetParamName(idx));
+ return value;
+}
+
+bool ParamAccessor::FindString(ore::StringView* value, const ore::StringView& name) const {
+ const ore::ResMetaData* metadata;
+ const MetaDataPack* metadata_pack;
+ const VariablePack* variable_pack;
+ const auto real_name =
+ TrackBackArgument(&metadata, &metadata_pack, &variable_pack, nullptr, name);
+
+ if (metadata) {
+ const auto* entry = metadata->Get(real_name, Type::kString);
+ if (entry) {
+ *value = *entry->value.str.Get();
+ return true;
+ }
+ return false;
+ }
+
+ if (metadata_pack)
+ return metadata_pack->FindString(value, real_name);
+
+ return false;
+}
+
+ore::WStringView ParamAccessor::GetWString(int idx) const {
+ ore::WStringView value;
+ FindWString(&value, GetParamName(idx));
+ return value;
+}
+
+bool ParamAccessor::FindWString(ore::WStringView* value, const ore::StringView& name) const {
+ const ore::ResMetaData* metadata;
+ const MetaDataPack* metadata_pack;
+ const VariablePack* variable_pack;
+ const auto real_name =
+ TrackBackArgument(&metadata, &metadata_pack, &variable_pack, nullptr, name);
+
+ if (metadata) {
+ const auto* entry = metadata->Get(real_name, Type::kWString);
+ if (entry) {
+ *value = *entry->value.wstr.Get();
+ return true;
+ }
+ return false;
+ }
+
+ if (metadata_pack)
+ return metadata_pack->FindWString(value, real_name);
+
+ return false;
+}
+
+ParamAccessor::IntRange ParamAccessor::GetIntArray(int idx) const {
+ IntRange value;
+ FindIntArray(&value, GetParamName(idx));
+ return value;
+}
+
+bool ParamAccessor::FindIntArray(ParamAccessor::IntRange* value,
+ const ore::StringView& name) const {
+ const ore::ResMetaData* metadata;
+ const MetaDataPack* metadata_pack;
+ const VariablePack* variable_pack;
+ const auto real_name =
+ TrackBackArgument(&metadata, &metadata_pack, &variable_pack, nullptr, name);
+
+ if (metadata) {
+ const auto* entry = metadata->Get(real_name, Type::kIntArray);
+ if (entry) {
+ *value = IntRange{&entry->value.i, entry->num_items};
+ return true;
+ }
+ }
+
+ if (variable_pack) {
+ ore::DynArrayList<int>* list;
+ if (variable_pack->FindIntList(&list, real_name)) {
+ *value = IntRange{*list};
+ return true;
+ }
+ }
+
+ return false;
+}
+
+ParamAccessor::FloatRange ParamAccessor::GetFloatArray(int idx) const {
+ FloatRange value;
+ FindFloatArray(&value, GetParamName(idx));
+ return value;
+}
+
+bool ParamAccessor::FindFloatArray(ParamAccessor::FloatRange* value,
+ const ore::StringView& name) const {
+ const ore::ResMetaData* metadata;
+ const MetaDataPack* metadata_pack;
+ const VariablePack* variable_pack;
+ const auto real_name =
+ TrackBackArgument(&metadata, &metadata_pack, &variable_pack, nullptr, name);
+
+ if (metadata) {
+ const auto* entry = metadata->Get(real_name, Type::kFloatArray);
+ if (entry) {
+ *value = FloatRange{&entry->value.f, entry->num_items};
+ return true;
+ }
+ }
+
+ if (variable_pack) {
+ ore::DynArrayList<float>* list;
+ if (variable_pack->FindFloatList(&list, real_name)) {
+ *value = FloatRange{*list};
+ return true;
+ }
+ }
+
+ return false;
+}
+
+ParamAccessor::StringRange ParamAccessor::GetStringArray(int idx) const {
+ StringRange value;
+ FindStringArray(&value, GetParamName(idx));
+ return value;
+}
+
+bool ParamAccessor::FindStringArray(ParamAccessor::StringRange* value,
+ const ore::StringView& name) const {
+ const ore::ResMetaData* metadata;
+ const MetaDataPack* metadata_pack;
+ const VariablePack* variable_pack;
+ const auto real_name =
+ TrackBackArgument(&metadata, &metadata_pack, &variable_pack, nullptr, name);
+
+ if (metadata) {
+ const auto* entry = metadata->Get(real_name, Type::kStringArray);
+ if (entry) {
+ *value = StringRange{&entry->value.str, entry->num_items};
+ return true;
+ }
+ }
+
+ return false;
+}
+
+ParamAccessor::WStringRange ParamAccessor::GetWStringArray(int idx) const {
+ WStringRange value;
+ FindWStringArray(&value, GetParamName(idx));
+ return value;
+}
+
+bool ParamAccessor::FindWStringArray(ParamAccessor::WStringRange* value,
+ const ore::StringView& name) const {
+ const ore::ResMetaData* metadata;
+ const MetaDataPack* metadata_pack;
+ const VariablePack* variable_pack;
+ const auto real_name =
+ TrackBackArgument(&metadata, &metadata_pack, &variable_pack, nullptr, name);
+
+ if (metadata) {
+ const auto* entry = metadata->Get(real_name, Type::kWStringArray);
+ if (entry) {
+ *value = WStringRange{&entry->value.wstr, entry->num_items};
+ return true;
+ }
+ }
+
+ return false;
+}
+
+VariablePack::VariableType VariablePack::GetVariableType(const ore::StringView& name) const {
+ if (!Contains(name))
+ return ore::ResMetaData::DataType::Invalid();
+ return GetVariableEntry(name)->type;
+}
+
+bool VariablePack::FindInt(int* value, const ore::StringView& name) const {
+ if (GetVariableType(name) != ore::ResMetaData::DataType::kInt)
+ return false;
+ *value = GetVariableEntry(name)->value.i;
+ return true;
+}
+
+bool VariablePack::FindBool(bool* value, const ore::StringView& name) const {
+ if (GetVariableType(name) != ore::ResMetaData::DataType::kBool)
+ return false;
+ *value = GetVariableEntry(name)->value.i;
+ return true;
+}
+
+bool VariablePack::FindFloat(float* value, const ore::StringView& name) const {
+ if (GetVariableType(name) != ore::ResMetaData::DataType::kFloat)
+ return false;
+ *value = GetVariableEntry(name)->value.f;
+ return true;
+}
+
+bool VariablePack::FindIntList(ore::DynArrayList<int>** value, const ore::StringView& name) const {
+ if (GetVariableType(name) != ore::ResMetaData::DataType::kIntArray)
+ return false;
+ *value = GetVariableEntry(name)->value.int_array;
+ return true;
+}
+
+bool VariablePack::FindFloatList(ore::DynArrayList<float>** value,
+ const ore::StringView& name) const {
+ if (GetVariableType(name) != ore::ResMetaData::DataType::kFloatArray)
+ return false;
+ *value = GetVariableEntry(name)->value.float_array;
+ return true;
+}
+
+bool ParamAccessor::FindActorIdentifier(ore::StringView* actor_name,
+ ore::StringView* actor_sub_name,
+ const ore::StringView& name) const {
+ const ore::ResMetaData* metadata;
+ const MetaDataPack* metadata_pack;
+ const VariablePack* variable_pack;
+ const auto real_name =
+ TrackBackArgument(&metadata, &metadata_pack, &variable_pack, nullptr, name);
+
+ if (metadata) {
+ const auto* entry = metadata->Get(real_name, Type::kActorIdentifier);
+ if (entry) {
+ *actor_name = *entry->value.actor.name.Get();
+ *actor_sub_name = *entry->value.actor.sub_name.Get();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool VariablePack::Contains(const ore::StringView& name) const {
+ return m_names->FindIndex(name) != -1;
+}
+
+VariablePack::VariablePack() = default;
+
+VariablePack::~VariablePack() {
+ Dispose();
+}
+
+void VariablePack::Dispose() {
+ auto* variables = m_variables.data();
+ if (!variables)
+ return;
+
+ for (auto& variable : m_variables) {
+ switch (variable.type) {
+ case ore::ResMetaData::DataType::kIntArray:
+ if (variable.value.int_array)
+ GetAllocator()->DeleteAndNull(variable.value.int_array);
+ break;
+ case ore::ResMetaData::DataType::kFloatArray:
+ if (variable.value.float_array)
+ GetAllocator()->DeleteAndNull(variable.value.float_array);
+ break;
+ default:
+ break;
+ }
+ }
+ GetAllocator()->FreeImpl(variables);
+}
+
+void VariablePack::Init(AllocateArg arg, const ResEntryPoint* entry_point) {
+ Dispose();
+ m_names = entry_point->variable_defs_names.Get();
+ m_allocator = evfl::EvflAllocator{arg};
+ m_variables.ConstructElements(m_names->num_entries, GetAllocator());
+
+ const ResVariableDef* def = entry_point->variable_defs.Get();
+ for (auto& variable : m_variables) {
+ variable.type = def->type;
+ variable.value = {};
+ switch (variable.type) {
+ case ore::ResMetaData::DataType::kArgument:
+ case ore::ResMetaData::DataType::kContainer:
+ break;
+ case ore::ResMetaData::DataType::kInt:
+ case ore::ResMetaData::DataType::kBool:
+ variable.value.i = def->value.i;
+ break;
+ case ore::ResMetaData::DataType::kFloat:
+ variable.value.f = def->value.f;
+ break;
+ case ore::ResMetaData::DataType::kString:
+ case ore::ResMetaData::DataType::kWString:
+ break;
+ case ore::ResMetaData::DataType::kIntArray: {
+ variable.value.int_array = GetAllocator()->New<ore::DynArrayList<int>>();
+ variable.value.int_array->Init(GetAllocator(), 2);
+ ore::Array<const int> values{def->value.int_array.Get(), def->num};
+ variable.value.int_array->OverwriteWith(values.begin(), values.end());
+ break;
+ }
+ case ore::ResMetaData::DataType::kBoolArray:
+ break;
+ case ore::ResMetaData::DataType::kFloatArray: {
+ variable.value.float_array = GetAllocator()->New<ore::DynArrayList<float>>();
+ variable.value.float_array->Init(GetAllocator(), 2);
+ ore::Array<const float> values{def->value.float_array.Get(), def->num};
+ variable.value.float_array->OverwriteWith(values.begin(), values.end());
+ break;
+ }
+ case ore::ResMetaData::DataType::kStringArray:
+ case ore::ResMetaData::DataType::kWStringArray:
+ case ore::ResMetaData::DataType::kActorIdentifier:
+ break;
+ }
+ ++def;
+ }
+}
+
+const VariablePack::Entry* VariablePack::GetVariableEntry(const ore::StringView& name) const {
+ return &m_variables[m_names->FindIndex(name)];
+}
+
+ore::StringView VariablePack::GetVariableName(int idx) const {
+ return *m_names->GetEntries()[1 + idx].name.Get();
+}
+
+int VariablePack::GetVariableCount() const {
+ return m_names->num_entries;
+}
+
+VariablePack::Entry* VariablePack::GetVariableEntry(const ore::StringView& name) {
+ return &m_variables[m_names->FindIndex(name)];
+}
+
+void VariablePack::SetInt(const ore::StringView& name, int value) {
+ m_variables[m_names->FindIndex(name)].value.i = value;
+}
+
+void VariablePack::SetFloat(const ore::StringView& name, float value) {
+ m_variables[m_names->FindIndex(name)].value.f = value;
+}
+
+void VariablePack::SetBool(const ore::StringView& name, bool value) {
+ m_variables[m_names->FindIndex(name)].value.i = value;
+}
+
+int VariablePack::GetInt(const ore::StringView& name) const {
+ int value;
+ FindInt(&value, name);
+ return value;
+}
+
+bool VariablePack::GetBool(const ore::StringView& name) const {
+ bool value;
+ FindBool(&value, name);
+ return value;
+}
+
+float VariablePack::GetFloat(const ore::StringView& name) const {
+ float value;
+ FindFloat(&value, name);
+ return value;
+}
+
+ore::DynArrayList<int>* VariablePack::GetIntList(const ore::StringView& name) const {
+ ore::DynArrayList<int>* value;
+ FindIntList(&value, name);
+ return value;
+}
+
+ore::DynArrayList<float>* VariablePack::GetFloatList(const ore::StringView& name) const {
+ ore::DynArrayList<float>* value;
+ FindFloatList(&value, name);
+ return value;
+}
+
+} // namespace evfl
diff --git a/lib/EventFlow/src/evfl/ResActor.cpp b/lib/EventFlow/src/evfl/ResActor.cpp
new file mode 100644
index 00000000..053ec21e
--- /dev/null
+++ b/lib/EventFlow/src/evfl/ResActor.cpp
@@ -0,0 +1,89 @@
+#include <algorithm>
+#include <evfl/ResActor.h>
+#include <ore/ResEndian.h>
+#include <ore/ResMetaData.h>
+
+namespace evfl {
+
+void ActorBinding::Register(const ResAction* action) {
+ if (GetAction(*action->name.Get()) != m_actions.end())
+ return;
+
+ Action entry;
+ entry.res_action = action;
+ m_actions.emplace_back(entry);
+}
+
+void ActorBinding::Register(const ResQuery* query) {
+ if (GetQuery(*query->name.Get()) != m_queries.end())
+ return;
+
+ Query entry;
+ entry.res_query = query;
+ m_queries.emplace_back(entry);
+}
+
+ActorBinding::Action* ActorBinding::GetAction(const ore::StringView& name) {
+ return std::find_if(m_actions.begin(), m_actions.end(), [name](const Action& action) {
+ return name == *action.res_action->name.Get();
+ });
+}
+
+const ActorBinding::Action* ActorBinding::GetAction(const ore::StringView& name) const {
+ return std::find_if(m_actions.begin(), m_actions.end(), [name](const Action& action) {
+ return name == *action.res_action->name.Get();
+ });
+}
+
+ActorBinding::Query* ActorBinding::GetQuery(const ore::StringView& name) {
+ return std::find_if(m_queries.begin(), m_queries.end(), [name](const Query& query) {
+ return name == *query.res_query->name.Get();
+ });
+}
+
+const ActorBinding::Query* ActorBinding::GetQuery(const ore::StringView& name) const {
+ return std::find_if(m_queries.begin(), m_queries.end(), [name](const Query& query) {
+ return name == *query.res_query->name.Get();
+ });
+}
+
+bool ActBinder::Builder::Build(evfl::ActBinder* binder, ore::Allocator* allocator,
+ ore::IterRange<const ResActor*> actors) {
+ binder->m_event_used_actor_count = 0;
+ binder->m_allocator = allocator;
+ binder->m_bindings.ConstructElements(actors.size(), allocator);
+
+ auto it = actors.begin();
+ for (int i = 0; i < actors.size(); ++i) {
+ auto& binding = binder->m_bindings[i];
+ binding.m_actor = it;
+ binding.m_actions.Init(binder->m_allocator);
+ binding.m_queries.Init(binder->m_allocator);
+ ++it;
+ }
+
+ return true;
+}
+
+const ore::Array<ActorBinding>* ActBinder::GetUsedResActors() const {
+ return &m_bindings;
+}
+
+void SwapEndian(ore::ResEndian* endian, ResActor* actor) {
+ using ore::SwapEndian;
+ if (endian->is_serializing) {
+ if (auto* params = actor->params.ToPtr(endian->base))
+ SwapEndian(endian, params);
+ SwapEndian(&actor->num_actions);
+ SwapEndian(&actor->num_queries);
+ SwapEndian(&actor->entry_point_idx);
+ } else {
+ SwapEndian(&actor->num_actions);
+ SwapEndian(&actor->num_queries);
+ SwapEndian(&actor->entry_point_idx);
+ if (auto* params = actor->params.ToPtr(endian->base))
+ SwapEndian(endian, params);
+ }
+}
+
+} // namespace evfl
diff --git a/lib/EventFlow/src/evfl/ResEventFlowFile.cpp b/lib/EventFlow/src/evfl/ResEventFlowFile.cpp
new file mode 100644
index 00000000..4fac653c
--- /dev/null
+++ b/lib/EventFlow/src/evfl/ResEventFlowFile.cpp
@@ -0,0 +1,112 @@
+#include <evfl/ResEventFlowFile.h>
+#include <evfl/ResFlowchart.h>
+#include <evfl/ResTimeline.h>
+#include <ore/Array.h>
+#include <ore/RelocationTable.h>
+#include <ore/ResDic.h>
+#include <ore/ResEndian.h>
+#include <ore/StringPool.h>
+#include <string_view>
+
+namespace evfl {
+
+using ore::SwapEndian;
+
+template <typename T>
+constexpr T MakeMagic(std::string_view magic) {
+ T result = 0;
+ for (size_t i = 0; i < magic.length(); ++i)
+ result |= T(magic[i]) << (8 * i);
+ return result;
+}
+
+bool ResEventFlowFile::IsValid(void* data) {
+ return static_cast<ore::BinaryFileHeader*>(data)->IsValid(MakeMagic<u64>("BFEVFL"), 0, 3, 0, 0);
+}
+
+ResEventFlowFile* ResEventFlowFile::ResCast(void* data) {
+ auto* file = static_cast<ResEventFlowFile*>(data);
+ file->Relocate();
+ return file;
+}
+
+void ResEventFlowFile::Relocate() {
+ if (header.IsRelocated())
+ return;
+
+ auto* table = header.GetRelocationTable();
+ table->Relocate();
+ header.SetRelocated(true);
+}
+
+void ResEventFlowFile::Unrelocate() {
+ if (!header.IsRelocated())
+ return;
+
+ auto* table = header.GetRelocationTable();
+ table->Unrelocate();
+ header.SetRelocated(false);
+}
+
+static void SwapEndian(ore::ResEndian* endian, ore::StringPool* pool) {
+ ore::BinString* str = pool->GetFirstString();
+ const int num_strings = pool->GetLength();
+ if (endian->is_serializing) {
+ for (int i = 0; i < num_strings; ++i) {
+ auto* next = str->NextString();
+ SwapEndian(&str->length);
+ str = next;
+ }
+ } else {
+ for (int i = 0; i < num_strings; ++i) {
+ SwapEndian(&str->length);
+ str = str->NextString();
+ }
+ }
+}
+
+template <typename T>
+static void SwapEndian(ore::ResEndian* endian, ore::BinTPtr<T>* ptr) {
+ if (auto* value = ptr->ToPtr(endian->base))
+ SwapEndian(endian, value);
+}
+
+static void SwapEndianForFileData(ore::ResEndian* endian, ResEventFlowFile* file) {
+ ore::Array<ore::BinTPtr<ResFlowchart>> flowcharts{file->flowcharts.ToPtr(endian->base),
+ file->num_flowcharts};
+ for (auto& flowchart : flowcharts)
+ SwapEndian(endian, &flowchart);
+
+ if (auto* flowchart_names = file->flowchart_names.ToPtr(endian->base))
+ SwapEndian(endian, flowchart_names);
+
+ ore::Array<ore::BinTPtr<ResTimeline>> timelines{file->timelines.ToPtr(endian->base),
+ file->num_timelines};
+ for (auto& timeline : timelines)
+ SwapEndian(endian, &timeline);
+
+ if (auto* timeline_names = file->timeline_names.ToPtr(endian->base))
+ SwapEndian(endian, timeline_names);
+
+ auto* string_pool =
+ static_cast<ore::StringPool*>(file->header.FindFirstBlock(MakeMagic<u32>("STR ")));
+ SwapEndian(endian, string_pool);
+}
+
+void SwapEndian(ore::ResEndian* endian, ResEventFlowFile* file) {
+ const auto swap_fields = [&] {
+ SwapEndian(&file->header.bom);
+ SwapEndian(&file->num_flowcharts);
+ SwapEndian(&file->num_timelines);
+ };
+
+ if (endian->is_serializing) {
+ SwapEndianForFileData(endian, file);
+ swap_fields();
+ } else {
+ swap_fields();
+ SwapEndianForFileData(endian, file);
+ }
+}
+
+} // namespace evfl
diff --git a/lib/EventFlow/src/evfl/ResFlowchart.cpp b/lib/EventFlow/src/evfl/ResFlowchart.cpp
new file mode 100644
index 00000000..da23e457
--- /dev/null
+++ b/lib/EventFlow/src/evfl/ResFlowchart.cpp
@@ -0,0 +1,191 @@
+#include <algorithm>
+#include <evfl/ResActor.h>
+#include <evfl/ResFlowchart.h>
+#include <ore/Array.h>
+#include <ore/ResDic.h>
+#include <ore/ResEndian.h>
+
+namespace evfl {
+
+using ore::SwapEndian;
+
+static void SwapEndianForFlowchartData(ore::ResEndian* endian, ResFlowchart* flowchart) {
+ ore::Array<ResActor> actors{flowchart->actors.ToPtr(endian->base), flowchart->num_actors};
+ for (auto& actor : actors)
+ SwapEndian(endian, &actor);
+
+ ore::Array<ResEvent> events{flowchart->events.ToPtr(endian->base), flowchart->num_events};
+ for (auto& event : events)
+ SwapEndian(endian, &event);
+
+ if (auto* names = flowchart->entry_point_names.ToPtr(endian->base))
+ SwapEndian(endian, names);
+
+ ore::Array<ResEntryPoint> entry_points{flowchart->entry_points.ToPtr(endian->base),
+ flowchart->num_entry_points};
+ for (auto& entry : entry_points)
+ SwapEndian(endian, &entry);
+}
+
+static void SwapEndianForFlowchartFields(ore::ResEndian* endian, ResFlowchart* flowchart) {
+ SwapEndian(&flowchart->num_actors);
+ SwapEndian(&flowchart->num_actions);
+ SwapEndian(&flowchart->num_queries);
+ SwapEndian(&flowchart->num_events);
+ SwapEndian(&flowchart->num_entry_points);
+}
+
+void SwapEndian(ore::ResEndian* endian, ResFlowchart* flowchart) {
+ if (endian->is_serializing) {
+ SwapEndianForFlowchartData(endian, flowchart);
+ SwapEndianForFlowchartFields(endian, flowchart);
+ } else {
+ SwapEndianForFlowchartFields(endian, flowchart);
+ SwapEndianForFlowchartData(endian, flowchart);
+ }
+}
+
+int ResFlowchart::CountEvent(ResEvent::EventType::Type type) const {
+ ore::Array<const ResEvent> array{events.Get(), num_events};
+ return std::count_if(array.begin(), array.end(),
+ [type](const ResEvent& event) { return event.type == type; });
+}
+
+void SwapEndian(ore::ResEndian* endian, ResCase* case_) {
+ SwapEndian(&case_->event_idx);
+ SwapEndian(&case_->value);
+}
+
+static void SwapEndianForEventData(ore::ResEndian* endian, ResEvent* event) {
+ switch (event->type) {
+ case ResEvent::EventType::kAction:
+ if (auto* params = event->params.ToPtr(endian->base))
+ SwapEndian(endian, params);
+ break;
+ case ResEvent::EventType::kSwitch: {
+ ore::Array<ResCase> cases{event->cases.ToPtr(endian->base), event->num_cases};
+ for (auto& case_ : cases)
+ SwapEndian(endian, &case_);
+ if (auto* params = event->params.ToPtr(endian->base))
+ SwapEndian(endian, params);
+ break;
+ }
+ case ResEvent::EventType::kFork: {
+ ore::Array<u16> forks{event->fork_event_indices.ToPtr(endian->base), event->num_forks};
+ for (auto& fork : forks)
+ SwapEndian(&fork);
+ break;
+ }
+ case ResEvent::EventType::kJoin:
+ break;
+ case ResEvent::EventType::kSubFlow:
+ if (auto* params = event->params.ToPtr(endian->base))
+ SwapEndian(endian, params);
+ break;
+ }
+}
+
+static void SwapEndianForEventFields(ore::ResEndian* endian, ResEvent* event) {
+ switch (event->type) {
+ case ResEvent::EventType::kAction:
+ SwapEndian(&event->next_event_idx);
+ SwapEndian(&event->actor_idx);
+ SwapEndian(&event->actor_action_idx);
+ break;
+ case ResEvent::EventType::kSwitch:
+ SwapEndian(&event->next_event_idx);
+ SwapEndian(&event->actor_idx);
+ SwapEndian(&event->actor_query_idx);
+ break;
+ case ResEvent::EventType::kFork:
+ SwapEndian(&event->num_forks);
+ SwapEndian(&event->join_event_idx);
+ break;
+ case ResEvent::EventType::kJoin:
+ case ResEvent::EventType::kSubFlow:
+ SwapEndian(&event->next_event_idx);
+ break;
+ }
+}
+
+void SwapEndian(ore::ResEndian* endian, ResEvent* event) {
+ if (endian->is_serializing) {
+ SwapEndianForEventData(endian, event);
+ SwapEndianForEventFields(endian, event);
+ } else {
+ SwapEndianForEventFields(endian, event);
+ SwapEndianForEventData(endian, event);
+ }
+}
+
+static void SwapEndianImpl(ore::ResEndian* endian, ResEntryPoint* entry) {
+ ore::Array<u16> sub_flow_event_indices{entry->sub_flow_event_indices.ToPtr(endian->base),
+ entry->num_sub_flow_event_indices};
+ for (auto& x : sub_flow_event_indices)
+ SwapEndian(&x);
+
+ auto* variable_def_names = entry->variable_defs_names.ToPtr(endian->base);
+ if (variable_def_names)
+ SwapEndian(endian, variable_def_names);
+
+ ore::Array<ResVariableDef> variable_defs{entry->variable_defs.ToPtr(endian->base),
+ entry->num_variable_defs};
+ for (auto& def : variable_defs)
+ SwapEndian(endian, &def);
+}
+
+void SwapEndian(ore::ResEndian* endian, ResEntryPoint* entry) {
+ if (endian->is_serializing) {
+ SwapEndianImpl(endian, entry);
+ SwapEndian(&entry->main_event_idx);
+ SwapEndian(&entry->num_sub_flow_event_indices);
+ SwapEndian(&entry->num_variable_defs);
+ } else {
+ SwapEndian(&entry->main_event_idx);
+ SwapEndian(&entry->num_sub_flow_event_indices);
+ SwapEndian(&entry->num_variable_defs);
+ SwapEndianImpl(endian, entry);
+ }
+}
+
+static void SwapEndianImpl(ore::ResEndian* endian, ResVariableDef* def) {
+ switch (def->type) {
+ case ore::ResMetaData::DataType::kIntArray: {
+ const auto num = def->num;
+ ore::Array<int> array{def->value.int_array.ToPtr(endian->base), num};
+ for (auto& x : array)
+ SwapEndian(&x);
+ break;
+ }
+ case ore::ResMetaData::DataType::kFloatArray: {
+ const auto num = def->num;
+ ore::Array<float> array{def->value.float_array.ToPtr(endian->base), num};
+ for (auto& x : array)
+ SwapEndian(reinterpret_cast<u32*>(&x));
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+static bool IsScalarVariableDef(ResVariableDef* def) {
+ using Type = ore::ResMetaData::DataType::Type;
+ return def->type == Type::kInt || def->type == Type::kBool || def->type == Type::kFloat;
+}
+
+void SwapEndian(ore::ResEndian* endian, ResVariableDef* def) {
+ if (endian->is_serializing) {
+ SwapEndianImpl(endian, def);
+ SwapEndian(&def->num);
+ if (IsScalarVariableDef(def))
+ SwapEndian(&def->value.i);
+ } else {
+ SwapEndian(&def->num);
+ if (IsScalarVariableDef(def))
+ SwapEndian(&def->value.i);
+ SwapEndianImpl(endian, def);
+ }
+}
+
+} // namespace evfl
diff --git a/lib/EventFlow/src/evfl/ResTimeline.cpp b/lib/EventFlow/src/evfl/ResTimeline.cpp
new file mode 100644
index 00000000..a81c9bf1
--- /dev/null
+++ b/lib/EventFlow/src/evfl/ResTimeline.cpp
@@ -0,0 +1,118 @@
+#include <evfl/ResActor.h>
+#include <evfl/ResTimeline.h>
+#include <ore/Array.h>
+#include <ore/ResEndian.h>
+#include <ore/ResMetaData.h>
+
+namespace evfl {
+
+using ore::SwapEndian;
+
+void SwapEndian(ore::ResEndian* endian, ResTrigger* trigger) {
+ SwapEndian(&trigger->clip_index);
+}
+
+void SwapEndian(ore::ResEndian* endian, ResCut* cut) {
+ if (endian->is_serializing) {
+ if (auto* params = cut->params.ToPtr(endian->base))
+ SwapEndian(endian, params);
+ SwapEndian(&cut->start_time);
+ } else {
+ SwapEndian(&cut->start_time);
+ if (auto* params = cut->params.ToPtr(endian->base))
+ SwapEndian(endian, params);
+ }
+}
+
+void SwapEndian(ore::ResEndian* endian, ResClip* clip) {
+ const auto swap_fields = [&] {
+ SwapEndian(&clip->start_time);
+ SwapEndian(&clip->duration);
+ SwapEndian(&clip->actor_index);
+ SwapEndian(&clip->actor_action_index);
+ };
+
+ const auto swap_params = [&] {
+ if (auto* params = clip->params.ToPtr(endian->base))
+ SwapEndian(endian, params);
+ };
+
+ if (endian->is_serializing) {
+ swap_params();
+ swap_fields();
+ } else {
+ swap_fields();
+ swap_params();
+ }
+}
+
+void SwapEndian(ore::ResEndian* endian, ResOneshot* oneshot) {
+ const auto swap_fields = [&] {
+ SwapEndian(&oneshot->time);
+ SwapEndian(&oneshot->actor_index);
+ SwapEndian(&oneshot->actor_action_index);
+ };
+
+ const auto swap_params = [&] {
+ if (auto* params = oneshot->params.ToPtr(endian->base))
+ SwapEndian(endian, params);
+ };
+
+ if (endian->is_serializing) {
+ swap_params();
+ swap_fields();
+ } else {
+ swap_fields();
+ swap_params();
+ }
+}
+
+void SwapEndian(ore::ResEndian* endian, ResSubtimeline* subtimeline) {}
+
+static void SwapEndianForTimelineData(ore::ResEndian* endian, ResTimeline* timeline) {
+ ore::Array<ResClip> clips{timeline->clips.ToPtr(endian->base), timeline->num_clips};
+ for (auto& clip : clips)
+ SwapEndian(endian, &clip);
+
+ ore::Array<ResOneshot> oneshots{timeline->oneshots.ToPtr(endian->base), timeline->num_oneshots};
+ for (auto& oneshot : oneshots)
+ SwapEndian(endian, &oneshot);
+
+ ore::Array<ResActor> actors{timeline->actors.ToPtr(endian->base), timeline->num_actors};
+ for (auto& actor : actors)
+ SwapEndian(endian, &actor);
+
+ const int num_triggers = timeline->num_clips * 2;
+ ore::Array<ResTrigger> triggers{timeline->triggers.ToPtr(endian->base), num_triggers};
+ for (auto& trigger : triggers)
+ SwapEndian(endian, &trigger);
+
+ ore::Array<ResCut> cuts{timeline->cuts.ToPtr(endian->base), timeline->num_cuts};
+ for (auto& cut : cuts)
+ SwapEndian(endian, &cut);
+
+ if (auto* params = timeline->params.ToPtr(endian->base))
+ SwapEndian(endian, params);
+}
+
+void SwapEndian(ore::ResEndian* endian, ResTimeline* timeline) {
+ const auto swap_fields = [&] {
+ SwapEndian(&timeline->duration);
+ SwapEndian(&timeline->num_actors);
+ SwapEndian(&timeline->num_actions);
+ SwapEndian(&timeline->num_clips);
+ SwapEndian(&timeline->num_oneshots);
+ SwapEndian(&timeline->num_subtimelines);
+ SwapEndian(&timeline->num_cuts);
+ };
+
+ if (endian->is_serializing) {
+ SwapEndianForTimelineData(endian, timeline);
+ swap_fields();
+ } else {
+ swap_fields();
+ SwapEndianForTimelineData(endian, timeline);
+ }
+}
+
+} // namespace evfl
diff --git a/lib/EventFlow/src/evfl/TimelineObj.cpp b/lib/EventFlow/src/evfl/TimelineObj.cpp
new file mode 100644
index 00000000..14e89d8b
--- /dev/null
+++ b/lib/EventFlow/src/evfl/TimelineObj.cpp
@@ -0,0 +1,235 @@
+#include <evfl/Action.h>
+#include <evfl/ResActor.h>
+#include <evfl/ResTimeline.h>
+#include <evfl/TimelineObj.h>
+#include <utility>
+
+namespace evfl {
+
+int TimelineObj::s_GlobalPlayCounter{};
+
+// NON_MATCHING: didn't bother matching, clearly equivalent
+TimelineObj::TimelineObj() = default;
+
+void TimelineObj::Calc() {
+ CalcImpl();
+ for (auto* timeline : m_sub_timelines) {
+ if (timeline)
+ timeline->Calc();
+ }
+}
+
+void TimelineObj::Reset() {
+ m_play_counter = ++s_GlobalPlayCounter;
+ m_started = false;
+ m_state = TimelineState::kNotStarted;
+ for (auto* timeline : m_sub_timelines) {
+ if (timeline)
+ timeline->Reset();
+ }
+}
+
+void TimelineObj::SetState(TimelineState::Type state) {
+ m_state = state;
+}
+
+void TimelineObj::Start(float start_time) {
+ if (m_started)
+ return;
+
+ m_started = true;
+ m_state = TimelineState::kPlaying;
+ m_time = -1.0;
+ m_last_trigger_idx = -1;
+ m_last_oneshot_idx = -1;
+ JumpTimeTo(start_time);
+ Calc();
+
+ for (auto* timeline : m_sub_timelines) {
+ if (timeline && !timeline->m_started)
+ timeline->Start(start_time);
+ }
+}
+
+void TimelineObj::JumpTimeTo(float time) {
+ JumpTimeToImpl(time);
+}
+
+void TimelineObj::AdvanceTimeTo(float time) {
+ AdvanceTimeToImpl(time);
+}
+
+namespace {
+
+TriggerType::Type ReverseTriggerType(TriggerType::Type clip_trigger_type) {
+ switch (clip_trigger_type) {
+ case TriggerType::kClipEnter:
+ return TriggerType::kClipLeave;
+ case TriggerType::kClipLeave:
+ return TriggerType::kClipEnter;
+ case TriggerType::kOneshot:
+ return TriggerType::kOneshot;
+ default:
+ return {};
+ }
+}
+
+float GetTriggerTime(TriggerType::Type type, const ResClip& clip) {
+ switch (type) {
+ case TriggerType::kEnter:
+ return clip.start_time;
+ case TriggerType::kLeave:
+ return clip.start_time + clip.duration;
+ default:
+ return 0.0;
+ }
+}
+
+float GetTriggerTimeReverse(TriggerType::Type type, const ResClip& clip) {
+ switch (type) {
+ case TriggerType::kEnter:
+ return clip.start_time + clip.duration;
+ case TriggerType::kLeave:
+ return clip.start_time;
+ default:
+ return 0.0;
+ }
+}
+
+} // namespace
+
+// NON_MATCHING: reorderings for the action binding stuff
+void TimelineObj::CalcImpl() {
+ if (m_time == m_new_time)
+ return;
+
+ const int direction = m_time < m_new_time ? 1 : -1;
+ const auto time_max = m_time < m_new_time ? m_new_time : m_time;
+ const auto time_min = m_time < m_new_time ? m_time : m_new_time;
+
+ ore::Array<const ResTrigger> triggers{m_timeline->triggers.Get(), 2 * m_timeline->num_clips};
+ ore::Array<const ResClip> clips{m_timeline->clips.Get(), m_timeline->num_clips};
+
+ for (int trigger_idx = (m_time < m_new_time) + m_last_trigger_idx;
+ u32(trigger_idx) < u32(triggers.size()); trigger_idx += direction) {
+ const auto& trigger = triggers[trigger_idx];
+ const auto& clip = clips[trigger.clip_index];
+ const auto trigger_type = TriggerType::Type(trigger.trigger_type);
+
+ const float trigger_time = GetTriggerTime(trigger_type, clip);
+ if (trigger_time <= time_min || time_max < trigger_time)
+ break;
+
+ m_last_trigger_idx = trigger_idx;
+
+ if (m_jumped_time) {
+ const float rev_trigger_time = GetTriggerTimeReverse(trigger_type, clip);
+ if (time_min < rev_trigger_time && rev_trigger_time <= time_max)
+ continue;
+ }
+
+ const auto actor_idx = clip.actor_index;
+ const auto& bindings = m_act_binder.GetBindings();
+ const auto* actions = m_timeline->actors.Get()[actor_idx].actions.Get();
+ const auto& binding = bindings[actor_idx];
+ const ore::StringView name = *actions[clip.actor_action_index].name.Get();
+ const auto* action = binding.GetAction(name);
+
+ auto real_trigger_type = trigger_type;
+ if (m_jumped_time && direction < 0)
+ real_trigger_type = ReverseTriggerType(real_trigger_type);
+
+ const ActionArg arg(&clip, binding.GetUserData(), action->user_data,
+ m_new_time - trigger_time, real_trigger_type, clip.params.Get());
+ ActionDoneHandler handler{this};
+ action->handler(arg, std::move(handler));
+ }
+
+ ore::Array<const ResOneshot> oneshots{m_timeline->oneshots.Get(), m_timeline->num_oneshots};
+ for (int oneshot_idx = (m_time < m_new_time) + m_last_oneshot_idx;
+ u32(oneshot_idx) < u32(oneshots.size()); oneshot_idx += direction) {
+ const auto& oneshot = oneshots[oneshot_idx];
+ const auto trigger_time = oneshot.time;
+ if (trigger_time < time_min || time_max < trigger_time)
+ break;
+
+ m_last_oneshot_idx = oneshot_idx;
+
+ if (m_jumped_time && trigger_time != m_new_time)
+ continue;
+
+ const auto actor_idx = oneshot.actor_index;
+ const auto& bindings = m_act_binder.GetBindings();
+ const auto& binding = bindings[actor_idx];
+ const auto* actions = m_timeline->actors.Get()[actor_idx].actions.Get();
+ const ore::StringView name = *actions[oneshot.actor_action_index].name.Get();
+ const auto* action = binding.GetAction(name);
+
+ const ActionArg arg(&oneshot, binding.GetUserData(), action->user_data,
+ m_new_time - trigger_time, oneshot.params.Get());
+ ActionDoneHandler handler{this};
+ action->handler(arg, std::move(handler));
+ }
+
+ m_time = m_new_time;
+}
+
+void TimelineObj::AdvanceTimeToImpl(float time) {
+ m_new_time = time;
+ m_jumped_time = false;
+ for (auto* timeline : m_sub_timelines) {
+ if (timeline)
+ timeline->AdvanceTimeToImpl(time);
+ }
+}
+
+void TimelineObj::JumpTimeToImpl(float time) {
+ m_new_time = time;
+ m_jumped_time = true;
+ for (auto* timeline : m_sub_timelines) {
+ if (timeline)
+ timeline->JumpTimeToImpl(time);
+ }
+}
+
+bool TimelineObj::RegisterSubtimeline(TimelineObj* obj) {
+ ore::Array<const ResSubtimeline> subtimelines{m_timeline->subtimelines.Get(),
+ m_timeline->num_subtimelines};
+ for (int i = 0; i < subtimelines.size(); ++i) {
+ if (ore::StringView(*obj->m_timeline->name.Get()) == *subtimelines[i].name.Get()) {
+ m_sub_timelines[i] = obj;
+ return true;
+ }
+ }
+ return false;
+}
+
+// NON_MATCHING: std::fill using obj->m_sub_timelines.size() rather than the byte size
+bool TimelineObj::Builder::Build(TimelineObj* obj, AllocateArg allocate_arg) {
+ if (!obj)
+ return false;
+
+ if (!allocate_arg.alloc || !allocate_arg.free)
+ return false;
+
+ obj->Finalize();
+
+ EvflAllocator allocator{allocate_arg};
+ obj->m_allocator = allocator;
+
+ m_act_binder_builder.Build(&obj->m_act_binder, &obj->m_allocator,
+ {m_timeline->actors.Get(), m_timeline->num_actors});
+
+ auto& bindings = obj->m_act_binder.GetBindings();
+ for (auto it = bindings.begin(); it != bindings.end(); ++it)
+ it->SetIsUsed(true);
+
+ obj->m_timeline = m_timeline;
+ obj->m_allocator = allocator;
+ obj->m_sub_timelines.ConstructElements(m_timeline->num_subtimelines, &allocator);
+ std::fill(obj->m_sub_timelines.begin(), obj->m_sub_timelines.end(), nullptr);
+
+ return true;
+}
+
+} // namespace evfl
diff --git a/lib/EventFlow/src/ore/BinaryFile.cpp b/lib/EventFlow/src/ore/BinaryFile.cpp
new file mode 100644
index 00000000..f705306b
--- /dev/null
+++ b/lib/EventFlow/src/ore/BinaryFile.cpp
@@ -0,0 +1,182 @@
+#include <cstdint>
+#include <ore/BinaryFile.h>
+#include <ore/BitUtils.h>
+
+namespace ore {
+
+bool BinaryFileHeader::IsValid(s64 magic_, int ver_major_, int ver_minor_, int ver_patch_,
+ int ver_sub_) const {
+ bool valid = true;
+ valid &= int(ver_major) == ver_major_ && int(ver_minor) == ver_minor_ &&
+ magic == magic_ & int(ver_patch) <= ver_patch_;
+ valid &= IsEndianReverse() || IsEndianValid();
+ valid &= IsAlignmentValid();
+ return valid;
+}
+
+bool BinaryFileHeader::IsSignatureValid(s64 magic_) const {
+ return magic == magic_;
+}
+
+bool BinaryFileHeader::IsVersionValid(int major, int minor, int patch, int sub) const {
+ if (int(ver_major) != major)
+ return false;
+ if (int(ver_minor) != minor)
+ return false;
+ if (int(ver_patch) > patch)
+ return false;
+ return true;
+}
+
+bool BinaryFileHeader::IsEndianReverse() const {
+ return bom == s16(0xFFFE);
+}
+
+bool BinaryFileHeader::IsEndianValid() const {
+ return bom == s16(0xFEFF);
+}
+
+bool BinaryFileHeader::IsAlignmentValid() const {
+ return (std::uintptr_t(this) & (GetAlignment() - 1)) == 0;
+}
+
+int BinaryFileHeader::GetAlignment() const {
+ return 1 << alignment;
+}
+
+static constexpr u32 FlagRelocated = 1 << 0;
+
+bool BinaryFileHeader::IsRelocated() const {
+ return relocation_flags & FlagRelocated;
+}
+
+void BinaryFileHeader::SetRelocated(bool relocated) {
+ if (relocated)
+ relocation_flags |= FlagRelocated;
+ else
+ relocation_flags &= ~FlagRelocated;
+}
+
+void BinaryFileHeader::SetByteOrderMark() {
+ bom = s16(0xFEFF);
+}
+
+int BinaryFileHeader::GetFileSize() const {
+ return file_size;
+}
+
+void BinaryFileHeader::SetFileSize(int size) {
+ file_size = size;
+}
+
+void BinaryFileHeader::SetAlignment(int alignment_) {
+ alignment = CountTrailingZeros(u32(alignment_));
+}
+
+StringView BinaryFileHeader::GetFileName() const {
+ StringView name;
+ if (file_name_offset != 0)
+ name = reinterpret_cast<const char*>(this) + file_name_offset;
+ return name;
+}
+
+void BinaryFileHeader::SetFileName(const StringView& name) {
+ if (name.empty()) {
+ file_name_offset = 0;
+ } else {
+ file_name_offset = int(intptr_t(name.data()) - intptr_t(this));
+#ifdef MATCHING_HACK_NX_CLANG
+ asm("");
+#endif
+ }
+}
+
+RelocationTable* BinaryFileHeader::GetRelocationTable() {
+ if (relocation_table_offset == 0)
+ return nullptr;
+ return reinterpret_cast<RelocationTable*>(reinterpret_cast<char*>(this) +
+ relocation_table_offset);
+}
+
+void BinaryFileHeader::SetRelocationTable(RelocationTable* table) {
+ if (table == nullptr) {
+ relocation_table_offset = 0;
+ } else {
+ relocation_table_offset = int(intptr_t(table) - intptr_t(this));
+#ifdef MATCHING_HACK_NX_CLANG
+ asm("");
+#endif
+ }
+}
+
+BinaryBlockHeader* BinaryFileHeader::GetFirstBlock() {
+ if (first_block_offset == 0)
+ return nullptr;
+ return reinterpret_cast<BinaryBlockHeader*>(reinterpret_cast<char*>(this) + first_block_offset);
+}
+
+const BinaryBlockHeader* BinaryFileHeader::GetFirstBlock() const {
+ if (first_block_offset == 0)
+ return nullptr;
+ return reinterpret_cast<const BinaryBlockHeader*>(reinterpret_cast<const char*>(this) +
+ first_block_offset);
+}
+
+BinaryBlockHeader* BinaryFileHeader::FindFirstBlock(int type) {
+ auto* block = GetFirstBlock();
+ if (!block || block->magic == type)
+ return block;
+ return block->FindNextBlock(type);
+}
+
+const BinaryBlockHeader* BinaryFileHeader::FindFirstBlock(int type) const {
+ auto* block = GetFirstBlock();
+ if (!block || block->magic == type)
+ return block;
+ return block->FindNextBlock(type);
+}
+
+void BinaryFileHeader::SetFirstBlock(BinaryBlockHeader* block) {
+ if (block == nullptr)
+ first_block_offset = 0;
+ else
+ first_block_offset = int(intptr_t(block) - intptr_t(this));
+}
+
+BinaryBlockHeader* BinaryBlockHeader::FindNextBlock(int type) {
+ auto* block = this;
+ do
+ block = block->GetNextBlock();
+ while (block && block->magic != type);
+ return block;
+}
+
+const BinaryBlockHeader* BinaryBlockHeader::FindNextBlock(int type) const {
+ auto* block = this;
+ do
+ block = block->GetNextBlock();
+ while (block && block->magic != type);
+ return block;
+}
+
+BinaryBlockHeader* BinaryBlockHeader::GetNextBlock() {
+ if (next_block_offset == 0)
+ return nullptr;
+ return reinterpret_cast<BinaryBlockHeader*>(reinterpret_cast<char*>(this) + next_block_offset);
+}
+
+const BinaryBlockHeader* BinaryBlockHeader::GetNextBlock() const {
+ if (next_block_offset == 0)
+ return nullptr;
+ return reinterpret_cast<const BinaryBlockHeader*>(reinterpret_cast<const char*>(this) +
+ next_block_offset);
+}
+
+void BinaryBlockHeader::SetNextBlock(BinaryBlockHeader* block) {
+ if (block == nullptr)
+ next_block_offset = 0;
+ else
+ next_block_offset = int(intptr_t(block) - intptr_t(this));
+}
+
+} // namespace ore
diff --git a/lib/EventFlow/src/ore/BitUtils.cpp b/lib/EventFlow/src/ore/BitUtils.cpp
new file mode 100644
index 00000000..e392bd13
--- /dev/null
+++ b/lib/EventFlow/src/ore/BitUtils.cpp
@@ -0,0 +1,115 @@
+#include <ore/BitUtils.h>
+
+namespace ore {
+
+void BitArray::SetAllOn() {
+ const int num = m_num_bits >> ShiftAmount;
+ Fill(num, Word(-1));
+
+ u32 remainder = u32(m_num_bits) % NumBitsPerWord;
+ if (remainder != 0)
+ m_words[num] = (1ul << remainder) - 1;
+}
+
+void BitArray::SetAllOff() {
+ Fill(GetNumWords(), Word(0));
+}
+
+BitArray::TestIter BitArray::BeginTest() const {
+ return TestIter(m_words, m_words + GetNumWords());
+}
+
+BitArray::TestIter BitArray::EndTest() const {
+ return TestIter(nullptr, nullptr);
+}
+
+BitArray::TestClearIter BitArray::BeginTestClear() {
+ return TestClearIter(m_words, m_words + GetNumWords());
+}
+
+BitArray::TestClearIter BitArray::EndTestClear() {
+ return TestClearIter(nullptr, nullptr);
+}
+
+BitArray::TestIter::TestIter(const BitArray::Word* start, const BitArray::Word* end) {
+ for (auto* it = start; it != end; ++it) {
+ if (*it != 0) {
+ auto idx = CountTrailingZeros(*it);
+ idx += 8 * int(intptr_t(it) - intptr_t(start)) & ClearMask;
+ m_bit = idx;
+ m_current_word = it;
+ m_last_word = end;
+ m_next = *it & (*it - 1);
+ return;
+ }
+ }
+
+ SetInvalid();
+}
+
+BitArray::TestIter& BitArray::TestIter::operator++() {
+ m_bit &= ClearMask;
+
+ // Fast path: we still have bits in the current word
+ if (m_next != 0) {
+ m_bit += CountTrailingZeros(m_next);
+ m_next &= m_next - 1;
+ return *this;
+ }
+
+ // Find the next nonzero word and the first set bit in it
+ ++m_current_word;
+ for (; m_current_word != m_last_word; ++m_current_word) {
+ m_bit += NumBitsPerWord;
+ if (*m_current_word == 0)
+ continue;
+ m_bit += CountTrailingZeros(*m_current_word);
+ m_next = *m_current_word & (*m_current_word - 1);
+ return *this;
+ }
+
+ SetInvalid();
+ return *this;
+}
+
+BitArray::TestClearIter::TestClearIter(BitArray::Word* start, BitArray::Word* end) {
+ for (auto* it = start; it != end; ++it) {
+ if (*it != 0) {
+ auto idx = CountTrailingZeros(*it);
+ idx += 8 * int(intptr_t(it) - intptr_t(start)) & ClearMask;
+ m_bit = idx;
+ m_current_word = it;
+ m_last_word = end;
+ m_next = *it & (*it - 1);
+ return;
+ }
+ }
+
+ SetInvalid();
+}
+
+BitArray::TestClearIter& BitArray::TestClearIter::operator++() {
+ m_bit &= ClearMask;
+
+ if (m_next != 0) {
+ m_bit += CountTrailingZeros(m_next);
+ m_next &= m_next - 1;
+ return *this;
+ }
+
+ *m_current_word = 0;
+ ++m_current_word;
+ for (; m_current_word != m_last_word; *m_current_word = 0, ++m_current_word) {
+ m_bit += NumBitsPerWord;
+ if (*m_current_word == 0)
+ continue;
+ m_bit += CountTrailingZeros(*m_current_word);
+ m_next = *m_current_word & (*m_current_word - 1);
+ return *this;
+ }
+
+ SetInvalid();
+ return *this;
+}
+
+} // namespace ore
diff --git a/lib/EventFlow/src/ore/EnumUtil.cpp b/lib/EventFlow/src/ore/EnumUtil.cpp
new file mode 100644
index 00000000..71c1cc95
--- /dev/null
+++ b/lib/EventFlow/src/ore/EnumUtil.cpp
@@ -0,0 +1,13 @@
+#include <algorithm>
+#include <ore/EnumUtil.h>
+
+namespace ore {
+
+int detail::EnumUtil::FindIndex(int value, const IterRange<const int*>& values) {
+ auto it = std::find_if(values.begin(), values.end(), [value](int x) { return value == x; });
+ if (it == values.end())
+ return -1;
+ return static_cast<int>(it - values.begin());
+}
+
+} // namespace ore
diff --git a/lib/EventFlow/src/ore/RelocationTable.cpp b/lib/EventFlow/src/ore/RelocationTable.cpp
new file mode 100644
index 00000000..b58625fb
--- /dev/null
+++ b/lib/EventFlow/src/ore/RelocationTable.cpp
@@ -0,0 +1,106 @@
+#include <cstring>
+#include <ore/RelocationTable.h>
+
+namespace ore {
+
+namespace {
+
+struct BitFlag32 {
+ explicit BitFlag32(u32 flags) : m_flags(flags) {}
+ bool operator[](int idx) const { return m_flags & (1 << idx); }
+
+ u32 m_flags{};
+};
+
+} // namespace
+
+void RelocationTable::Section::SetPtr(void* ptr_) {
+ ptr = reinterpret_cast<u64>(ptr_);
+}
+
+void* RelocationTable::Section::GetPtr() const {
+ return reinterpret_cast<void*>(ptr);
+}
+
+void* RelocationTable::Section::GetPtrInFile(void* base) const {
+ return static_cast<char*>(base) + offset;
+}
+
+void* RelocationTable::Section::GetBasePtr(void* base) const {
+ if (ptr)
+ base = reinterpret_cast<void*>(ptr - offset);
+ return base;
+}
+
+u32 RelocationTable::Section::GetSize() const {
+ return size;
+}
+
+void RelocationTable::Relocate() {
+ char* const table_base = reinterpret_cast<char*>(this) - table_start_offset;
+ const auto* entries = GetEntries();
+ const int num = num_sections;
+
+ for (int section_idx = 0; section_idx < num; ++section_idx) {
+ const auto& section = GetSections()[section_idx];
+
+ auto* base = static_cast<char*>(section.GetBasePtr(table_base));
+ const int idx0 = section.first_entry_idx;
+ const int end = idx0 + section.num_entries;
+
+ for (int idx = idx0; idx < end; ++idx) {
+ const auto& entry = entries[idx];
+ const auto pointers_offset = entry.pointers_offset;
+ const BitFlag32 mask{entry.mask};
+
+ auto* pointer_ptr = reinterpret_cast<u64*>(table_base + pointers_offset);
+ for (int i = 0; i < 32; ++i, ++pointer_ptr) {
+ if (!mask[i])
+ continue;
+ const auto offset = static_cast<int>(*pointer_ptr);
+ void* ptr = offset == 0 ? nullptr : reinterpret_cast<void*>(base + offset);
+ std::memcpy(pointer_ptr, &ptr, sizeof(ptr));
+ }
+ }
+ }
+}
+
+void RelocationTable::Unrelocate() {
+ char* const table_base = reinterpret_cast<char*>(this) - table_start_offset;
+ const auto* entries = GetEntries();
+ const int num = num_sections;
+
+ for (int section_idx = 0; section_idx < num; ++section_idx) {
+ auto& section = GetSections()[section_idx];
+
+ auto* base = static_cast<char*>(section.GetBasePtr(table_base));
+ section.SetPtr(nullptr);
+ const int idx0 = section.first_entry_idx;
+ const int end = idx0 + section.num_entries;
+
+ for (int idx = idx0; idx < end; ++idx) {
+ const auto& entry = entries[idx];
+ const auto pointers_offset = entry.pointers_offset;
+ const BitFlag32 mask{entry.mask};
+
+ auto* pointer_ptr = reinterpret_cast<void**>(table_base + pointers_offset);
+ for (int i = 0; i < 32; ++i, ++pointer_ptr) {
+ if (!mask[i])
+ continue;
+ void* ptr = *pointer_ptr;
+ u64 offset = static_cast<int>(ptr == nullptr ? 0 : intptr_t(ptr) - intptr_t(base));
+ std::memcpy(pointer_ptr, &offset, sizeof(offset));
+ }
+ }
+ }
+}
+
+int RelocationTable::CalcSize(int num_sections, int num_entries) {
+ int size = 0;
+ size += offsetof(RelocationTable, sections);
+ size += sizeof(Section) * num_sections;
+ size += sizeof(Section::Entry) * num_entries;
+ return size;
+}
+
+} // namespace ore
diff --git a/lib/EventFlow/src/ore/ResDic.cpp b/lib/EventFlow/src/ore/ResDic.cpp
new file mode 100644
index 00000000..019ffe61
--- /dev/null
+++ b/lib/EventFlow/src/ore/ResDic.cpp
@@ -0,0 +1,50 @@
+#include <algorithm>
+#include <ore/ResDic.h>
+#include <ore/ResEndian.h>
+
+namespace ore {
+
+int ResDic::FindRefBit(const StringView& str1, const StringView& str2) {
+ const auto len1 = str1.size();
+ const auto len2 = str2.size();
+ const auto len = std::max(len1, len2);
+
+ for (int bit_idx = 0; bit_idx < 8 * len; ++bit_idx) {
+ const int idx = bit_idx >> 3;
+
+ int bit1 = 0;
+ if (len1 > idx)
+ bit1 = str1[len1 + -(idx + 1)] >> (bit_idx % 8) & 1;
+
+ int bit2 = 0;
+ if (len2 > idx)
+ bit2 = str2[len2 + -(idx + 1)] >> (bit_idx % 8) & 1;
+
+ if (bit1 != bit2)
+ return bit_idx;
+ }
+
+ return -1;
+}
+
+void SwapEndian(ResEndian* endian, ResDic* dic) {
+ const auto swap_entries = [&] {
+ const int num_entries = dic->num_entries + 1;
+ for (int i = 0; i < num_entries; ++i) {
+ ResDicEntry& entry = dic->GetEntries()[i];
+ SwapEndian(&entry.compact_bit_idx);
+ SwapEndian(&entry.next_indices[0]);
+ SwapEndian(&entry.next_indices[1]);
+ }
+ };
+
+ if (endian->is_serializing) {
+ swap_entries();
+ SwapEndian(&dic->num_entries);
+ } else {
+ SwapEndian(&dic->num_entries);
+ swap_entries();
+ }
+}
+
+} // namespace ore
diff --git a/lib/EventFlow/src/ore/ResMetaData.cpp b/lib/EventFlow/src/ore/ResMetaData.cpp
new file mode 100644
index 00000000..2587f3d6
--- /dev/null
+++ b/lib/EventFlow/src/ore/ResMetaData.cpp
@@ -0,0 +1,89 @@
+#include <ore/ResDic.h>
+#include <ore/ResEndian.h>
+#include <ore/ResMetaData.h>
+
+namespace ore {
+
+static void SwapEndianImpl(ResEndian* endian, ResMetaData* res) {
+ auto* dictionary = res->dictionary.ToPtr(endian->base);
+ if (dictionary)
+ SwapEndian(endian, dictionary);
+
+ switch (res->type) {
+ case ResMetaData::DataType::kArgument:
+ case ResMetaData::DataType::kString:
+ case ResMetaData::DataType::kStringArray:
+ case ResMetaData::DataType::kActorIdentifier: {
+ if (res->num_items == 0)
+ break;
+ BinString* str = res->value.str.ToPtr(endian->base);
+ if (endian->is_serializing) {
+ for (int i = 0, n = res->num_items; i < n; ++i) {
+ auto* next = str->NextString();
+ SwapEndian(&str->length);
+ str = next;
+ }
+ } else {
+ for (int i = 0, n = res->num_items; i < n; ++i) {
+ SwapEndian(&str->length);
+ str = str->NextString();
+ }
+ }
+ break;
+ }
+ case ResMetaData::DataType::kContainer: {
+ for (int i = 0, n = res->num_items; i < n; ++i) {
+ ResMetaData* ptr = (&res->value.container + i)->ToPtr(endian->base);
+ if (ptr)
+ SwapEndian(endian, ptr);
+ }
+ break;
+ }
+ case ResMetaData::DataType::kInt:
+ case ResMetaData::DataType::kBool:
+ case ResMetaData::DataType::kFloat:
+ case ResMetaData::DataType::kIntArray:
+ case ResMetaData::DataType::kFloatArray:
+ for (int i = 0, n = res->num_items; i < n; ++i) {
+ SwapEndian(&res->value.i + i);
+ }
+ break;
+ case ResMetaData::DataType::kWString:
+ case ResMetaData::DataType::kWStringArray: {
+ if (res->num_items == 0)
+ break;
+ BinWString* str = res->value.wstr.ToPtr(endian->base);
+ if (endian->is_serializing) {
+ for (int i = 0, n = res->num_items; i < n; ++i) {
+ for (auto& c : *str)
+ c = static_cast<wchar_t>(SwapEndian(static_cast<u32>(c)));
+ auto* next = str->NextString();
+ SwapEndian(&str->length);
+ str = next;
+ }
+ } else {
+ for (int i = 0, n = res->num_items; i < n; ++i) {
+ SwapEndian(&str->length);
+ for (auto& c : *str)
+ c = static_cast<wchar_t>(SwapEndian(static_cast<u32>(c)));
+ str = str->NextString();
+ }
+ }
+ break;
+ }
+ case ResMetaData::DataType::kBoolArray:
+ break;
+ }
+}
+
+void SwapEndian(ResEndian* endian, ResMetaData* res) {
+ if (endian->is_serializing) {
+ SwapEndianImpl(endian, res);
+ SwapEndian(&res->num_items);
+ } else {
+ SwapEndian(&res->num_items);
+ SwapEndianImpl(endian, res);
+ }
+}
+
+} // namespace ore
diff --git a/lib/EventFlow/src/ore/StringPool.cpp b/lib/EventFlow/src/ore/StringPool.cpp
new file mode 100644
index 00000000..6b45338a
--- /dev/null
+++ b/lib/EventFlow/src/ore/StringPool.cpp
@@ -0,0 +1,13 @@
+#include <ore/StringPool.h>
+
+namespace ore {
+
+int StringPool::GetLength() const {
+ return length;
+}
+
+void StringPool::SetLength(int len) {
+ length = len;
+}
+
+} // namespace ore