diff --git a/AssetHelper.slnx b/AssetHelper.slnx
index 8291a26..83e59ae 100644
--- a/AssetHelper.slnx
+++ b/AssetHelper.slnx
@@ -1,4 +1,5 @@
+
diff --git a/AssetHelper/AssetHelper.csproj b/AssetHelper/AssetHelper.csproj
index 0c0e6af..d2e1cac 100644
--- a/AssetHelper/AssetHelper.csproj
+++ b/AssetHelper/AssetHelper.csproj
@@ -14,9 +14,6 @@
Silksong.AssetHelper
$(NoWarn);MSB3270
- True
-
- True
true
@@ -41,7 +38,7 @@
all
-
+
all
runtime; build; native; contentfiles; analyzers
@@ -72,9 +69,32 @@
-
+
+
+
+ false
+ ../../AssetHelperLib/AssetHelperLib/bin/Debug/netstandard2.1
+
+
+
+
+
+
+
+
+ $(AssetHelperLibFolder)/AssetHelperLib.dll
+
+
+
+
+ $(PkgAssetHelperLib)/lib/netstandard2.1
+
+
@@ -101,8 +121,8 @@
-
-
+
+
@@ -110,24 +130,13 @@
-
+
-
+
-
+
diff --git a/AssetHelper/CatalogTools/CatalogEntryUtils.cs b/AssetHelper/CatalogTools/CatalogEntryUtils.cs
index 0ef10a0..04cba80 100644
--- a/AssetHelper/CatalogTools/CatalogEntryUtils.cs
+++ b/AssetHelper/CatalogTools/CatalogEntryUtils.cs
@@ -56,7 +56,7 @@ public static ContentCatalogDataEntry CreateBundleEntry(
return bundleEntry;
}
- ///
+ ///
public static ContentCatalogDataEntry CreateAssetEntry(
string internalId,
Type assetType,
@@ -66,7 +66,7 @@ out string primaryKey
{
primaryKey = internalId;
- return CreateAssetEntry(internalId, assetType, dependencyKeys, primaryKey);
+ return CreateAssetEntry(internalId, assetType, dependencyKeys, [primaryKey]);
}
///
@@ -75,33 +75,28 @@ out string primaryKey
/// The internal ID of the asset. This is the name of the asset within the bundle.
/// Unity type of the asset. Eg: GameObject
/// Primary keys of the bundle dependencies. These should be in the catalog.
- /// The primary key used to access the asset with Addressables.
+ /// The primary keys used to access the asset with Addressables.
public static ContentCatalogDataEntry CreateAssetEntry(
string internalId,
Type assetType,
List dependencyKeys,
- string primaryKey
+ List primaryKeys
)
{
object[] deps = dependencyKeys.Cast
/// The type of the asset to load.
/// The Addressables Key used to load the asset.
-public class ManagedAsset(string key) : IManagedAsset
+public class ManagedAsset(string key) : ManagedAssetBase
{
///
/// The Addressables Key used to load the asset.
///
public string Key { get; } = key;
- private AsyncOperationHandle? _handle;
-
- ///
- /// The operation handle containing the asset. This will be null if the asset has not been loaded.
- ///
- /// This handle should not be unloaded manually; instead, the method
- /// on this instance should be used.
- ///
- /// Exception thrown if this instance has not been loaded when accessing the handle.
- public AsyncOperationHandle Handle =>
- _handle.HasValue
- ? _handle.Value
- : throw new InvalidOperationException(
- $"Addressable asset with key {Key} must be loaded before accessing the handle!"
- );
+ ///
+ protected internal override string Identifier => Key;
///
/// Construct an instance for the given scene asset.
@@ -97,54 +83,13 @@ public static ManagedAsset FromNonSceneAsset(string assetName, string? bundle
return new(key);
}
- ///
- /// Load the underlying asset. This operation is idempotent.
- ///
- /// This should be called prior to using the asset.
- ///
- /// The handle used to load the asset.
- public AsyncOperationHandle Load()
- {
- if (_handle == null)
- {
- _handle = Addressables.LoadAssetAsync(Key);
- }
- return Handle;
- }
-
- object? IManagedAsset.Load() => Load();
-
- ///
- /// Unload the underlying asset. This operation is idempotent.
- ///
- /// This should not be called if the asset is still in use.
- ///
- public void Unload()
- {
- if (_handle.HasValue)
- {
- Addressables.Release(_handle.Value);
- _handle = null;
- }
- }
-
- ///
- /// Whether or not the asset has finished loading.
- ///
- public bool IsLoaded => HasBeenLoaded && Handle.IsDone;
-
- ///
- /// Whether or not the asset load request has been made.
- ///
- public bool HasBeenLoaded => _handle.HasValue;
+ ///
+ protected override AsyncOperationHandle DoLoad()
+ => Addressables.LoadAssetAsync(Key);
///
/// Create a new instance that wraps the same underlying asset.
/// The new instance starts out unloaded.
- ///
- /// If a library has defined a instance but it is not
- /// guaranteed to be loaded, then you should clone the instance and load the clone
- /// rather than loading the instance owned by the library.
///
public ManagedAsset Clone()
{
diff --git a/AssetHelper/ManagedAssets/ManagedAssetBase.cs b/AssetHelper/ManagedAssets/ManagedAssetBase.cs
new file mode 100644
index 0000000..d770d26
--- /dev/null
+++ b/AssetHelper/ManagedAssets/ManagedAssetBase.cs
@@ -0,0 +1,79 @@
+using System;
+using UnityEngine.AddressableAssets;
+using UnityEngine.ResourceManagement.AsyncOperations;
+
+namespace Silksong.AssetHelper.ManagedAssets;
+
+///
+/// Base class for objects that wrap an Addressables key and load the underlying asset.
+///
+/// The type parameter of the AsyncOperationHandle loaded by Addressables.
+public abstract class ManagedAssetBase : IManagedAsset
+{
+ ///
+ /// An identifier associated with this asset.
+ ///
+ protected internal abstract string Identifier { get; }
+
+ private AsyncOperationHandle? _handle;
+
+ ///
+ /// The operation handle containing the asset. This will be null if the asset has not been loaded.
+ ///
+ /// This handle should not be unloaded manually; instead, the method
+ /// on this instance should be used.
+ ///
+ /// Exception thrown if this instance has not been loaded when accessing the handle.
+ public AsyncOperationHandle Handle =>
+ _handle.HasValue
+ ? _handle.Value
+ : throw new InvalidOperationException(
+ $"Addressable asset with identifier {Identifier} must be loaded before accessing the handle!"
+ );
+
+ ///
+ /// Load the underlying asset. This operation is idempotent.
+ ///
+ /// This should be called prior to using the asset.
+ ///
+ /// The handle used to load the asset.
+ public AsyncOperationHandle Load()
+ {
+ if (_handle == null)
+ {
+ _handle = DoLoad();
+ }
+ return Handle;
+ }
+
+ ///
+ /// Function to load the underlying asset.
+ ///
+ protected abstract AsyncOperationHandle DoLoad();
+
+ object? IManagedAsset.Load() => Load();
+
+ ///
+ /// Unload the underlying asset. This operation is idempotent.
+ ///
+ /// This should not be called if the asset is still in use.
+ ///
+ public void Unload()
+ {
+ if (_handle.HasValue)
+ {
+ Addressables.Release(_handle.Value);
+ _handle = null;
+ }
+ }
+
+ ///
+ /// Whether or not the asset has finished loading.
+ ///
+ public bool IsLoaded => HasBeenLoaded && Handle.IsDone;
+
+ ///
+ /// Whether or not the asset load request has been made.
+ ///
+ public bool HasBeenLoaded => _handle.HasValue;
+}
diff --git a/AssetHelper/ManagedAssets/ManagedAssetBundle.cs b/AssetHelper/ManagedAssets/ManagedAssetBundle.cs
new file mode 100644
index 0000000..c091a67
--- /dev/null
+++ b/AssetHelper/ManagedAssets/ManagedAssetBundle.cs
@@ -0,0 +1,41 @@
+using Silksong.AssetHelper.Core;
+using UnityEngine.AddressableAssets;
+using UnityEngine.ResourceManagement.AsyncOperations;
+using UnityEngine.ResourceManagement.ResourceProviders;
+
+namespace Silksong.AssetHelper.ManagedAssets;
+
+///
+/// Class wrapping an asset bundle that can be loaded by Addressables.
+///
+/// Typically assets should be loaded directly (e.g. via a )
+/// because loading bundles in this way may not load dependencies.
+///
+/// The name of the bundle, given as a path relative to
+/// the StandaloneX folder.
+public class ManagedAssetBundle(string bundleName) : ManagedAssetBase
+{
+ ///
+ /// The name of the bundle, given as a path relative to
+ /// the StandaloneX folder.
+ ///
+ public string BundleName { get; } = bundleName;
+
+ ///
+ protected internal override string Identifier => BundleName;
+
+ ///
+ protected override AsyncOperationHandle DoLoad()
+ => Addressables.LoadAssetAsync(
+ AddressablesData.ToBundleKey(BundleName));
+
+ ///
+ /// Create a new instance that wraps the same underlying asset.
+ /// The new instance starts out unloaded.
+ ///
+ public ManagedAssetBundle Clone()
+ {
+ return new(BundleName);
+ }
+
+}
diff --git a/AssetHelper/ManagedAssets/ManagedAssetExtensions.cs b/AssetHelper/ManagedAssets/ManagedAssetExtensions.cs
index b16601f..262d5e2 100644
--- a/AssetHelper/ManagedAssets/ManagedAssetExtensions.cs
+++ b/AssetHelper/ManagedAssets/ManagedAssetExtensions.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel;
namespace Silksong.AssetHelper.ManagedAssets;
@@ -10,6 +11,7 @@ public static class ManagedAssetExtensions
///
/// Instantiate the asset managed by this instance.
///
+ /// If the asset has not finished loading.
public static T InstantiateAsset(this ManagedAsset asset)
where T : UObject
{
@@ -35,12 +37,12 @@ public static T InstantiateAsset(this ManagedAsset asset)
/// This method will write an error message to the log if there was an exception during loading,
/// but this method will not throw.
///
- public static void EnsureLoaded(this ManagedAsset asset)
+ public static void EnsureLoaded(this ManagedAssetBase asset)
{
if (!asset.HasBeenLoaded)
{
AssetHelperPlugin.InstanceLogger.LogWarning(
- $"{nameof(EnsureLoaded)} has been called on {asset.Key} before loading the asset!");
+ $"{nameof(EnsureLoaded)} has been called on {asset.Identifier} before loading the asset!");
asset.Load();
}
if (!asset.IsLoaded)
@@ -50,10 +52,17 @@ public static void EnsureLoaded(this ManagedAsset asset)
if (asset.Handle.OperationException != null)
{
- AssetHelperPlugin.InstanceLogger.LogError($"Operation exception when loading asset with key {asset.Key}\n" + asset.Handle.OperationException);
+ AssetHelperPlugin.InstanceLogger.LogError(
+ $"Operation exception when loading asset {asset.Identifier}\n" + asset.Handle.OperationException);
}
}
+ // Kept for backward compatibility
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void EnsureLoaded(this ManagedAsset asset)
+ => EnsureLoaded((ManagedAssetBase)asset);
+
///
/// Instantiate an asset in this group accessed by key.
///
@@ -67,4 +76,35 @@ public static T InstantiateAsset(this ManagedAssetGroup group, string key)
return UObject.Instantiate(group[key].Result);
}
+
+ ///
+ /// Instantiate an asset from a .
+ /// If multiple assets match the predicate then the first will be instantiated;
+ /// which one this is will be arbitrary.
+ ///
+ ///
+ ///
+ /// Function used to check if a given asset is the one being looked for. Commonly
+ /// this will inspect the children or components of the given asset.
+ /// This function should not mutate the argument.
+ ///
+ /// If the asset has not finished loading.
+ /// If none of the assets match the predicate.
+ public static T InstantiateAsset(this ManagedAssetList asset, Func predicate) where T : UObject
+ {
+ if (!asset.IsLoaded)
+ {
+ throw new InvalidOperationException($"The asset has not finished loading!");
+ }
+
+ foreach (T t in asset.Handle.Result)
+ {
+ if (predicate(t))
+ {
+ return UObject.Instantiate(t);
+ }
+ }
+
+ throw new ArgumentException($"No matching asset for managed asset list with key {asset.Key} was found!");
+ }
}
diff --git a/AssetHelper/ManagedAssets/ManagedAssetList.cs b/AssetHelper/ManagedAssets/ManagedAssetList.cs
new file mode 100644
index 0000000..1ee82e1
--- /dev/null
+++ b/AssetHelper/ManagedAssets/ManagedAssetList.cs
@@ -0,0 +1,75 @@
+using Silksong.AssetHelper.Plugin;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.AddressableAssets;
+using UnityEngine.ResourceManagement.AsyncOperations;
+
+namespace Silksong.AssetHelper.ManagedAssets;
+
+///
+/// Class that wraps the load of a group of addressable assets with the same primary key.
+///
+/// This is commonly used when multiple game objects in a (repacked) scene have the same name.
+///
+///
+///
+public class ManagedAssetList(string key) : ManagedAssetBase>
+{
+ ///
+ /// The Addressables Key used to load the asset.
+ ///
+ public string Key { get; } = key;
+
+ ///
+ protected internal override string Identifier => Key;
+
+
+ ///
+ protected override AsyncOperationHandle> DoLoad()
+ => Addressables.LoadAssetsAsync(Key);
+
+ ///
+ /// Create a new instance that wraps the same underlying asset.
+ /// The new instance starts out unloaded.
+ ///
+ public ManagedAssetList Clone()
+ {
+ return new(Key);
+ }
+
+ ///
+ /// Construct an instance for the given scene asset.
+ ///
+ /// Doing this during your plugin's Awake method will cause it to be requested automatically.
+ /// via the API.
+ ///
+ /// The name of the scene.
+ /// The hierarchical path to the game object.
+ ///
+ public static ManagedAssetList FromSceneAsset(string sceneName, string objPath)
+ {
+ if (typeof(T) != typeof(GameObject))
+ {
+ AssetHelperPlugin.InstanceLogger.LogWarning(
+ $"{nameof(ManagedAssetList<>)} instances for scene assets should have GameObject as the type argument!"
+ );
+ }
+
+ if (AssetRequestAPI.RequestApiAvailable)
+ {
+ AssetRequestAPI.RequestSceneAsset(sceneName, objPath);
+ }
+ else
+ {
+ if (!AssetRequestAPI.Request.SceneAssets.TryGetValue(sceneName.ToLowerInvariant(), out HashSet objNames)
+ || !objNames.Contains(objPath))
+ {
+ AssetHelperPlugin.InstanceLogger.LogWarning(
+ $"Constructing managed asset list from scene {sceneName}, {objPath} after Awake may not work unless the asset has been requested first!");
+ }
+ }
+
+ string key = CatalogKeys.GetKeyForSceneAsset(sceneName, objPath);
+ return new(key);
+ }
+}
diff --git a/AssetHelper/Plugin/AssetRequest.cs b/AssetHelper/Plugin/AssetRequest.cs
index 23f095e..c6015f6 100644
--- a/AssetHelper/Plugin/AssetRequest.cs
+++ b/AssetHelper/Plugin/AssetRequest.cs
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
-using System.Text;
using Newtonsoft.Json;
+using Silksong.AssetHelper.Internal;
namespace Silksong.AssetHelper.Plugin;
@@ -9,6 +9,7 @@ internal class AssetRequest
{
public Dictionary> SceneAssets { get; set; } = [];
+ [JsonConverter(typeof(DictListConverter<(string bundleName, string assetName), Type>))]
public Dictionary<(string bundleName, string assetName), Type> NonSceneAssets { get; set; } = [];
[JsonIgnore]
diff --git a/AssetHelper/Plugin/BaseStartupTask.cs b/AssetHelper/Plugin/BaseStartupTask.cs
new file mode 100644
index 0000000..f95d030
--- /dev/null
+++ b/AssetHelper/Plugin/BaseStartupTask.cs
@@ -0,0 +1,24 @@
+using Silksong.AssetHelper.Plugin.LoadingPage;
+using System.Collections;
+
+namespace Silksong.AssetHelper.Plugin;
+
+///
+/// Base class for tasks that happen at startup.
+///
+public abstract class BaseStartupTask
+{
+ // Note - for tasks defined by AssetHelper the enumerator will be executed safely
+ // and the objects yielded will be passed to unity.
+ ///
+ /// Run the startup task. The enumerator will be executed by unity.
+ ///
+ /// A loading screen.
+ ///
+ /// Tasks executed this way will usually be fairly slow and cpu-intensive tasks.
+ /// There is not a good way to decide when to yield, except that:
+ /// - After updating the loading screen, you should yield or the change may not be visible
+ /// - You should yield after every chunk of work, so that Unity knows your program hasn't hung
+ ///
+ public abstract IEnumerator Run(ILoadingScreen loadingScreen);
+}
diff --git a/AssetHelper/Plugin/CatalogKeys.cs b/AssetHelper/Plugin/CatalogKeys.cs
index 33bb968..2ede473 100644
--- a/AssetHelper/Plugin/CatalogKeys.cs
+++ b/AssetHelper/Plugin/CatalogKeys.cs
@@ -40,4 +40,16 @@ public static string GetKeyForNonSceneAsset(string assetName)
{
return $"{NonSceneCatalogId}/{assetName}";
}
+
+ ///
+ /// Get the primary key for a scene bundle asset with a particular transform path ID.
+ ///
+ ///
+ /// This is essentially a secondary primary key for the asset, guaranteed to be unique,
+ /// but not intended to be used by clients.
+ ///
+ internal static string GetKeyForAssetAtTransform(string sceneName, long tPathId)
+ {
+ return $"{SceneCatalogId}/RepackedTransforms/{sceneName}/t{tPathId}.prefab";
+ }
}
diff --git a/AssetHelper/Plugin/CatalogMetadata.cs b/AssetHelper/Plugin/CatalogMetadata.cs
index c3e342a..a24f9eb 100644
--- a/AssetHelper/Plugin/CatalogMetadata.cs
+++ b/AssetHelper/Plugin/CatalogMetadata.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using Newtonsoft.Json;
using Silksong.AssetHelper.Internal;
@@ -7,9 +8,9 @@ namespace Silksong.AssetHelper.Plugin;
internal class CatalogMetadata
{
- public string SilksongVersion { get; set; } = VersionData.SilksongVersion;
-
- public string PluginVersion { get; set; } = AssetHelperPlugin.Version;
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
+ [DefaultValue(null)]
+ public CachedFileMetadata Metadata { get; set; } = CachedFileMetadata.CreateNew();
}
///
diff --git a/AssetHelper/Plugin/CustomCatalogBuilder.cs b/AssetHelper/Plugin/CustomCatalogBuilder.cs
index 08a3031..e24682b 100644
--- a/AssetHelper/Plugin/CustomCatalogBuilder.cs
+++ b/AssetHelper/Plugin/CustomCatalogBuilder.cs
@@ -5,6 +5,7 @@
using AssetHelperLib.Repacking;
using Silksong.AssetHelper.CatalogTools;
using Silksong.AssetHelper.Core;
+using Silksong.AssetHelper.Plugin.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets.ResourceLocators;
using UnityEngine.ResourceManagement.ResourceLocations;
@@ -55,7 +56,7 @@ public CustomCatalogBuilder(string primaryKeyPrefix = "AssetHelper")
string primaryKey = $"{_primaryKeyPrefix}/DependencyBundles/{bundleName}";
ContentCatalogDataEntry entry = CatalogEntryUtils.CreateEntryFromLocation(
location,
- primaryKey
+ [primaryKey]
);
_baseBundleEntries.Add(bundleName, entry);
@@ -82,7 +83,8 @@ public bool TryDeclareBundleDep(string bundleName, [NotNullWhen(true)] out strin
return true;
}
- public void AddRepackedSceneData(string sceneName, RepackedBundleData data, string bundlePath, string? serializedBundlePath = null)
+ public void AddRepackedSceneData(
+ string sceneName, RepackedBundleData data, SceneCatalogInfo info, string bundlePath, string? serializedBundlePath = null)
{
// Create an entry for the bundle
string repackedSceneBundleKey = $"{_primaryKeyPrefix}/SceneBundles/{sceneName}";
@@ -116,13 +118,29 @@ string dep in BundleMetadata.DetermineCatalogDeps(
}
// Create entries for the assets
- foreach ((string containerPath, string objPath) in data.GameObjectAssets ?? [])
+ foreach (var rootGoInfo in info.RootGameObjects)
{
ContentCatalogDataEntry entry = CatalogEntryUtils.CreateAssetEntry(
- containerPath,
+ rootGoInfo.ContainerPath,
typeof(GameObject),
dependencyKeys,
- $"{_primaryKeyPrefix}/Assets/{sceneName}/{objPath}"
+ [
+ $"{_primaryKeyPrefix}/Assets/{sceneName}/{rootGoInfo.ObjPath}",
+ CatalogKeys.GetKeyForAssetAtTransform(sceneName, rootGoInfo.TransformPathId)
+ ]
+ );
+ _addedEntries.Add(entry);
+ }
+
+ foreach (var childGoInfo in info.ChildGameObjects)
+ {
+ ContentCatalogDataEntry entry = CatalogEntryUtils.CreateChildGameObjectEntry(
+ CatalogKeys.GetKeyForAssetAtTransform(sceneName, childGoInfo.AncestorTransformPathId),
+ childGoInfo.RelativePath,
+ [
+ $"{_primaryKeyPrefix}/Assets/{sceneName}/{childGoInfo.ObjPath}",
+ CatalogKeys.GetKeyForAssetAtTransform(sceneName, childGoInfo.TransformPathId)
+ ]
);
_addedEntries.Add(entry);
}
@@ -157,7 +175,7 @@ public void AddAssets(string bundle, List<(string asset, Type assetType)> data)
asset,
assetType,
dependencyKeys,
- $"{_primaryKeyPrefix}/{asset}"
+ [$"{_primaryKeyPrefix}/{asset}"]
);
_addedEntries.Add(entry);
}
diff --git a/AssetHelper/Plugin/LoadingPage/ILoadingScreen.cs b/AssetHelper/Plugin/LoadingPage/ILoadingScreen.cs
index 0603187..2146ca3 100644
--- a/AssetHelper/Plugin/LoadingPage/ILoadingScreen.cs
+++ b/AssetHelper/Plugin/LoadingPage/ILoadingScreen.cs
@@ -5,14 +5,32 @@ namespace Silksong.AssetHelper.Plugin.LoadingPage;
///
/// Interface defining the contract for a loading screen.
///
-internal interface ILoadingScreen
+public interface ILoadingScreen
{
+ ///
+ /// Set the large text above the progress bar.
+ /// Typically used to describe the operation that is going on (e.g. "Repacking Scenes").
+ ///
public void SetText(string text);
+ ///
+ /// Set the small text below the progress bar.
+ /// Typically used for more detailed updates (e.g. a particular scene name).
+ /// This will often be left blank.
+ ///
+ ///
public void SetSubtext(string text);
+ ///
+ /// Set the progress of the progress bar.
+ ///
+ /// A float between 0 and 1 indicating the progress.
public void SetProgress(float progress);
+ ///
+ /// Set whether the screen should be visible.
+ /// This method should rarely be used.
+ ///
public void SetVisible(bool visible);
}
diff --git a/AssetHelper/Plugin/RepackedBundleMetadata.cs b/AssetHelper/Plugin/RepackedBundleMetadata.cs
index 25ba018..585dc4a 100644
--- a/AssetHelper/Plugin/RepackedBundleMetadata.cs
+++ b/AssetHelper/Plugin/RepackedBundleMetadata.cs
@@ -1,23 +1,23 @@
using AssetHelperLib.Repacking;
+using Newtonsoft.Json;
using Silksong.AssetHelper.Internal;
+using Silksong.AssetHelper.Plugin.Tasks;
+using System.ComponentModel;
namespace Silksong.AssetHelper.Plugin;
///
/// Data about a repacked scene bundle used by AssetHelper.
///
-public sealed class RepackedSceneBundleData
+internal sealed class RepackedSceneBundleData
{
///
- /// The Silksong version used to create the bundle.
+ /// The metadata when creating the bundle.
///
- public string SilksongVersion { get; init; } = VersionData.SilksongVersion;
-
- ///
- /// The Asset Helper version used to create the bundle.
- ///
- public string PluginVersion { get; init; } = AssetHelperPlugin.Version;
-
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
+ [DefaultValue(null)]
+ public CachedFileMetadata Metadata { get; init; } = CachedFileMetadata.CreateNew();
+
///
/// The name of the scene used to generate the bundle.
///
@@ -32,4 +32,9 @@ public sealed class RepackedSceneBundleData
/// The data generated for the repacked bundle.
///
public RepackedBundleData? Data { get; set; } = null;
+
+ ///
+ /// Info used to build the catalog entries for the repacked bundle.
+ ///
+ public SceneCatalogInfo? CatalogInfo { get; set; } = null;
}
diff --git a/AssetHelper/Plugin/Tasks/BaseStartupTask.cs b/AssetHelper/Plugin/Tasks/BaseStartupTask.cs
deleted file mode 100644
index cbdc84e..0000000
--- a/AssetHelper/Plugin/Tasks/BaseStartupTask.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using Silksong.AssetHelper.Plugin.LoadingPage;
-using System.Collections;
-
-namespace Silksong.AssetHelper.Plugin.Tasks;
-
-///
-/// Base class for tasks that happen at startup.
-///
-internal abstract class BaseStartupTask
-{
- ///
- /// Run the startup task. The objects yielded by this enumerator will
- /// be passed through to Unity.
- ///
- /// A loading screen.
- public abstract IEnumerator Run(ILoadingScreen loadingScreen);
-}
diff --git a/AssetHelper/Plugin/Tasks/NonSceneCatalog.cs b/AssetHelper/Plugin/Tasks/NonSceneCatalog.cs
index cdc3aab..4ebfe47 100644
--- a/AssetHelper/Plugin/Tasks/NonSceneCatalog.cs
+++ b/AssetHelper/Plugin/Tasks/NonSceneCatalog.cs
@@ -59,8 +59,10 @@ private IEnumerator CreateNonSceneAssetCatalog(ILoadingScreen screen)
bool shouldWriteCatalog = false;
if (JsonExtensions.TryLoadFromFile(catalogMetadataPath, out NonSceneCatalogMetadata? existingCatalogData)
- && existingCatalogData.SilksongVersion == VersionData.SilksongVersion
- && VersionData.EarliestAcceptableNonSceneCatalogVersion.AllowCachedData(existingCatalogData.PluginVersion))
+ && existingCatalogData.Metadata != null
+ && existingCatalogData.Metadata.SilksongVersion == VersionData.SilksongVersion
+ && VersionData.EarliestAcceptableNonSceneCatalogVersion.AllowCachedData(existingCatalogData.Metadata.PluginVersion)
+ && existingCatalogData.Metadata.OSFolderName == AssetPaths.OSFolderName)
{
toCatalog = existingCatalogData.CatalogAssets;
}
diff --git a/AssetHelper/Plugin/Tasks/SceneCatalogInfo.cs b/AssetHelper/Plugin/Tasks/SceneCatalogInfo.cs
new file mode 100644
index 0000000..d695ffb
--- /dev/null
+++ b/AssetHelper/Plugin/Tasks/SceneCatalogInfo.cs
@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Silksong.AssetHelper.Plugin.Tasks;
+
+///
+/// Class containing the data used to construct the catalog entries for a single repacked scene.
+///
+internal class SceneCatalogInfo
+{
+ public record GameObjectInfo(string ObjPath, string ContainerPath, long TransformPathId);
+ public record ChildGameObjectInfo(
+ string ObjPath,
+ long TransformPathId,
+ string AncestorObjPath,
+ string RelativePath,
+ long AncestorTransformPathId);
+
+ ///
+ /// Game objects which are at the root of the repacked bundle (not necessarily a root in the original scene).
+ ///
+ public List RootGameObjects { get; init; } = [];
+
+ ///
+ /// Game objects which are not at the root in the repacked bundle, but should still be made addressable.
+ ///
+ public List ChildGameObjects { get; init; } = [];
+
+ public IEnumerable LoadableAssets => [
+ .. RootGameObjects.Select(x => x.ObjPath),
+ .. ChildGameObjects.Select(x => x.ObjPath)
+ ];
+}
diff --git a/AssetHelper/Plugin/Tasks/SceneRepacking.cs b/AssetHelper/Plugin/Tasks/SceneRepacking.cs
index 86f1fa6..d3a8beb 100644
--- a/AssetHelper/Plugin/Tasks/SceneRepacking.cs
+++ b/AssetHelper/Plugin/Tasks/SceneRepacking.cs
@@ -1,161 +1,360 @@
-using AssetHelperLib.Repacking;
-using Silksong.AssetHelper.CatalogTools;
-using Silksong.AssetHelper.Internal;
-using AssetHelperLib.Util;
+using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
+using AssetHelperLib.BundleTools;
+using AssetHelperLib.PreloadTable;
+using AssetHelperLib.Repacking;
using UnityEngine.AddressableAssets;
using UnityEngine.AddressableAssets.ResourceLocators;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceLocations;
using UnityEngine.ResourceManagement.ResourceProviders;
-using RepackDataCollection = System.Collections.Generic.Dictionary;
using Silksong.AssetHelper.Core;
-using AssetHelperLib.PreloadTable;
-using CPPCache = System.Collections.Generic.Dictionary;
-using System;
+using Silksong.AssetHelper.Internal;
using Silksong.AssetHelper.Plugin.LoadingPage;
+using CPPCache = System.Collections.Generic.Dictionary;
+using GoInfo = AssetHelperLib.BundleTools.GameObjectLookup.GameObjectInfo;
+using RepackDataCollection = System.Collections.Generic.Dictionary;
namespace Silksong.AssetHelper.Plugin.Tasks;
-///
-/// Run the routine to repack scene assets, create a catalog and load it.
-///
internal class SceneRepacking : BaseStartupTask
{
- // Path to the scene catalog .bin file
private static string SceneCatalogPath => Path.Combine(AssetPaths.CatalogFolder, $"{CatalogKeys.SceneCatalogId}.bin");
- // (scene, gameObjs) that need to be repacked
- private Dictionary> _toRepack = [];
+ private static string CatalogMetadataPath => Path.ChangeExtension(SceneCatalogPath, ".json");
// Data about the repacked assets in the bundles on disk
private RepackDataCollection _repackData = [];
- private bool _didRepack = false;
-
public override IEnumerator Run(ILoadingScreen loadingScreen)
{
- return RepackAndCatalogScenes(loadingScreen);
- }
+ if (AssetRequestAPI.Request.SceneAssets.Count == 0)
+ {
+ AssetHelperPlugin.InstanceLogger.LogInfo("Not running scene repack operation: no scenes in request");
+ }
- private IEnumerator RepackAndCatalogScenes(ILoadingScreen bar)
- {
- IEnumerator repack = PrepareAndRun(bar);
+ // Prepare operation
+ if (JsonExtensions.TryLoadFromFile(AssetPaths.RepackedSceneBundleMetadataPath, out RepackDataCollection? repackData))
+ {
+ _repackData = repackData;
+ }
+ else
+ {
+ _repackData = [];
+ }
- bar.SetText(LanguageKeys.REPACKING_SCENE.GetLocalized());
- yield return null;
+ // This block sets _repackData to include only the data for scenes that do not need to be repacked
+ _repackData = _repackData
+ // If some data is missing, we must repack
+ .Where(kvp => kvp.Value.CatalogInfo is not null && kvp.Value.Data is not null)
+ // If the metadata (plugin version, bundle hash) changes, we must repack
+ .Where(kvp => !MetadataMismatch(kvp.Key, kvp.Value))
+ // If the bundle does not exist, we must repack
+ .Where(kvp => File.Exists(GetBundlePathForScene(kvp.Key)))
+ // If the existing data does not support everything in the request, we repack
+ .Where(kvp => CanLoadAll(kvp.Value.CatalogInfo!, kvp.Key))
+ .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
- while (repack.MoveNext())
+ List scenesToRepack;
+
+ // Repack scenes that need to be repacked
{
- // Yield after each repack op is done
+ loadingScreen.Reset();
+ loadingScreen.SetText(LanguageKeys.REPACKING_SCENE.GetLocalized());
yield return null;
- }
- bar.Reset();
- bar.SetText(LanguageKeys.BULDING_SCENE.GetLocalized());
- yield return null;
+ CachedObject SyncedCppCache = CachedObject.CreateSynced(
+ "container_pointer_preloads_cache.json", () => new(), mutable: true, out IDisposable? cppSyncHandle);
- IEnumerator catalogCreate = CreateSceneAssetCatalog(_repackData, bar);
- while (catalogCreate.MoveNext())
- {
+ ContainerPointerPreloads cpp = new(ResolveCab) { Cache = SyncedCppCache.Value };
+ PreloadTableResolver resolver = new([new DefaultPreloadTableResolver(), cpp]);
+ SceneRepacker repacker = new StrippedSceneRepacker(resolver);
+
+ scenesToRepack = AssetRequestAPI.Request.SceneAssets.Keys
+ .Where(x => !_repackData.ContainsKey(x))
+ .ToList();
+
+ Stopwatch mainSw = Stopwatch.StartNew();
+
+ int total = scenesToRepack.Count;
+ int count = 0;
+
+ AssetHelperPlugin.InstanceLogger.LogInfo($"Repacking {total} scenes");
+
+ foreach (string scene in scenesToRepack)
+ {
+ HashSet request = AssetRequestAPI.Request.SceneAssets[scene];
+ Stopwatch sw = Stopwatch.StartNew();
+ AssetHelperPlugin.InstanceLogger.LogInfo($"Repacking {request.Count} objects in scene {scene}");
+ loadingScreen.SetSubtext(scene);
+
+ RepackedSceneBundleData sceneRepackData = DoRepack(scene, request, repacker);
+
+ sw.Stop();
+ _repackData[scene] = sceneRepackData;
+ // Serialize after each step so that interrupted operations can continue
+ _repackData.SerializeToFile(AssetPaths.RepackedSceneBundleMetadataPath);
+ AssetHelperPlugin.InstanceLogger.LogInfo($"Repacked {scene} in {sw.ElapsedMilliseconds} ms");
+
+ count += 1;
+ loadingScreen.SetProgress((float)count / (float)total);
+
+ yield return null;
+ }
+
+ mainSw.Stop();
+ AssetHelperPlugin.InstanceLogger.LogInfo($"Finished scene repacking after {mainSw.ElapsedMilliseconds} ms");
+
+ loadingScreen.Reset();
+
+ // Save and dispose the cpp cache
+ cppSyncHandle?.Dispose();
yield return null;
}
- yield return null;
-
- // Only load the catalog if anyone's requested scene assets
- if (_repackData.Count > 0)
+ // Write catalog
+ // We can skip the catalog writing if nothing was freshly repacked
+ // and the catalog exists with the correct metadata.
{
- bar.SetText(LanguageKeys.LOADING_SCENE.GetLocalized());
- yield return null;
+ bool shouldWriteCatalog = (scenesToRepack.Count > 0) || MustWriteCatalog();
+
+ if (shouldWriteCatalog)
+ {
+ loadingScreen.Reset();
+ loadingScreen.SetText(LanguageKeys.BULDING_SCENE.GetLocalized());
+ yield return null;
+
+ AssetHelperPlugin.InstanceLogger.LogInfo($"Creating catalog");
+ Stopwatch sw = Stopwatch.StartNew();
+
+ CustomCatalogBuilder cbr = new(CatalogKeys.SceneCatalogId);
+
+ foreach ((string scene, RepackedSceneBundleData data) in _repackData)
+ {
+ if (data.Data == null || data.CatalogInfo == null) continue;
+ string bundlePath = GetBundlePathForScene(scene);
+ string bundleFileName = Path.GetFileName(bundlePath);
+ string serializedBundlePath = $"{GetSerializedBundleDirPrefix()}/{bundleFileName}";
- AssetHelperPlugin.InstanceLogger.LogInfo($"Loading scene catalog");
- AsyncOperationHandle catalogLoadOp = Addressables.LoadContentCatalogAsync(SceneCatalogPath);
- yield return catalogLoadOp;
- AssetRequestAPI.SceneAssetLocator = catalogLoadOp.Result;
+ cbr.AddRepackedSceneData(scene, data.Data, data.CatalogInfo, bundlePath, serializedBundlePath);
+ }
+ sw.Stop();
+ AssetHelperPlugin.InstanceLogger.LogInfo($"Prepared catalog in {sw.ElapsedMilliseconds} ms");
+
+ loadingScreen.SetText(LanguageKeys.WRITING_SCENE.GetLocalized());
+ loadingScreen.SetProgress(0);
+ yield return null;
+
+ sw = Stopwatch.StartNew();
+
+ int catCount = 0;
+ using IEnumerator serializationRoutine = cbr.BuildRoutine();
+ while (serializationRoutine.MoveNext())
+ {
+ float progress = serializationRoutine.Current;
+ catCount++;
+ if (catCount % 10 == 0)
+ {
+ loadingScreen.SetProgress(progress);
+ yield return null;
+ }
+ }
+
+ sw.Stop();
+ AssetHelperPlugin.InstanceLogger.LogInfo($"Finished writing catalog in {sw.ElapsedMilliseconds} ms");
+
+ SceneCatalogMetadata metadata = new();
+ metadata.SerializeToFile(CatalogMetadataPath);
+ }
+ else
+ {
+ AssetHelperPlugin.InstanceLogger.LogInfo($"Not creating catalog");
+ }
}
+
+ yield return null;
+
+ // Load catalog
+ loadingScreen.SetText(LanguageKeys.LOADING_SCENE.GetLocalized());
+ yield return null;
+
+ AssetHelperPlugin.InstanceLogger.LogInfo($"Loading scene catalog");
+ AsyncOperationHandle catalogLoadOp = Addressables.LoadContentCatalogAsync(SceneCatalogPath);
+ yield return catalogLoadOp;
+ AssetRequestAPI.SceneAssetLocator = catalogLoadOp.Result;
+
yield return null;
}
- private IEnumerator PrepareAndRun(ILoadingScreen bar)
+ private RepackedSceneBundleData DoRepack(
+ string scene,
+ HashSet request,
+ SceneRepacker repacker
+ )
{
- Prepare();
+ Dictionary>> transformSeqs = null!;
- if (_toRepack.Count > 0)
+ string containerPrefix = $"{nameof(AssetHelper)}/{scene}";
+
+ RepackingParams rParams = new()
{
- _didRepack = true;
- return RunRepacking(bar);
- }
- else
+ SceneBundlePath = AssetPaths.GetScenePath(scene),
+ ObjectNames = request.ToList(),
+ ContainerPrefix = containerPrefix,
+ OutBundlePath = GetBundlePathForScene(scene),
+ LateCallback = (ctx, data) => transformSeqs = BuildTransformSequences(ctx, data, request),
+ };
+ RepackedBundleData repackData = repacker.Repack(rParams);
+
+ string? hash = null;
+ if (AddressablesData.TryGetLocationForScene(scene, out IResourceLocation? location) && location.Data is AssetBundleRequestOptions opts)
{
- return Enumerable.Empty().GetEnumerator();
+ hash = opts.Hash;
}
+
+ SceneCatalogInfo catInfo = BuildSceneCatalogInfo(repackData, transformSeqs, containerPrefix);
+
+ RepackedSceneBundleData sceneRepackData = new()
+ {
+ SceneName = scene,
+ BundleHash = hash,
+ Data = repackData,
+ CatalogInfo = catInfo,
+ };
+
+ return sceneRepackData;
}
- ///
- /// Prepare the repacking request.
- ///
- /// True if there is any repacking to be done.
- private void Prepare()
+ private SceneCatalogInfo BuildSceneCatalogInfo(
+ RepackedBundleData repackData, Dictionary>> transformSeqs, string containerPrefix)
{
- if (JsonExtensions.TryLoadFromFile(AssetPaths.RepackedSceneBundleMetadataPath, out RepackDataCollection? repackData))
+ SceneCatalogInfo info = new();
+
+ HashSet rootGos = repackData.GameObjectAssets?.Values.Distinct().ToHashSet() ?? [];
+ Dictionary rootTransformPathIds = [];
+ foreach (string rootGo in rootGos)
{
- _repackData = repackData;
+ foreach (List transformSeq in transformSeqs[rootGo])
+ {
+ string containerPath = repackData.GameObjectAssets!
+ .First(kvp => kvp.Key.StartsWith($"{containerPrefix}/{transformSeq[0]}") && kvp.Value == rootGo)
+ .Key;
+ info.RootGameObjects.Add(new(rootGo, containerPath, transformSeq[0]));
+ rootTransformPathIds.Add(transformSeq[0], rootGo);
+ }
}
- else
+
+ foreach (string goPath in transformSeqs.Keys)
{
- _repackData = [];
+ if (rootGos.Contains(goPath)) continue;
+
+ foreach (List transformSeq in transformSeqs[goPath])
+ {
+ long ancestorPathId = transformSeq.FirstOrDefault(x => rootTransformPathIds.ContainsKey(x));
+ if (ancestorPathId == 0)
+ {
+ throw new Exception($"Unexpectedly failed to find ancestor for {goPath} [{transformSeq[0]}]");
+ }
+ string ancestor = rootTransformPathIds[ancestorPathId];
+ if (!goPath.StartsWith(ancestor + "/"))
+ {
+ throw new Exception($"Object {goPath} unexpectedly matched ancestor {ancestor}");
+ }
+ string relativePath = goPath.Substring(ancestor.Length + 1);
+
+ info.ChildGameObjects.Add(new(
+ goPath,
+ transformSeq[0],
+ ancestor,
+ relativePath,
+ ancestorPathId
+ ));
+ }
}
- // Any data with a metadata mismatch should be removed from the dictionary
- // Also remove data if they have manually deleted the file, as a way to individually
- // reset scene data
- _repackData = _repackData
- .Where(kvp => !MetadataMismatch(kvp.Key, kvp.Value) && File.Exists(GetBundlePathForScene(kvp.Key)))
- .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
+ return info;
+ }
- _toRepack = [];
+ ///
+ /// For each game object path, compute all transform path sequences for that game object path.
+ ///
+ /// A transform path sequence is a list of transform path IDs [id0, id1, ..., idn], where id0 is the
+ /// transform path id for the game object, id1 is its parent, ..., idn is the root.
+ ///
+ /// The number of path sequences for each game object path will match the number of game objects with
+ /// that game object path.
+ ///
+ private Dictionary>> BuildTransformSequences(
+ RepackingContext ctx, RepackedBundleData data, HashSet request)
+ {
+ GameObjectLookup goLookup = ctx.GameObjLookup
+ ?? GameObjectLookup.CreateFromFile(ctx.SceneAssetsManager, ctx.MainAssetsFileInstance);
- foreach ((string scene, HashSet request) in AssetRequestAPI.Request.SceneAssets)
+ Dictionary>> transformSeqs = [];
+
+ // We need to find transform sequences for all game objects in the request,
+ // and also all game objects in the bundle. The latter will only matter if there is a root game object
+ // in the repacked bundle that wasn't in the request, but has been included due to being a dependency
+ // for a requested asset.
+ HashSet requiredPaths = [
+ .. request,
+ .. data.GameObjectAssets?.Values ?? Enumerable.Empty()
+ ];
+
+ foreach (string objPath in requiredPaths)
{
- if (!_repackData.TryGetValue(scene, out RepackedSceneBundleData existingBundleData))
+ List> objTransformSeqs = [];
+
+ if (!goLookup.TryLookupName(objPath, out List? infos))
{
- _toRepack[scene] = request;
- continue;
- }
- if (existingBundleData.Data is null)
- {
- _toRepack[scene] = request;
- continue;
+ throw new Exception($"Failed to find {objPath} in bundle");
}
- if (request.All(x => existingBundleData.Data.TriedToRepack(x)))
+ foreach (GoInfo info in infos)
{
- // No need to re-repack as there's nothing new to try
- continue;
+ GoInfo currentInfo = info;
+ List tPathSeq = [];
+ long tPathId = currentInfo.TransformPathId;
+
+ tPathSeq.Add(tPathId);
+
+ while (currentInfo.ParentPathId != 0)
+ {
+ currentInfo = goLookup.LookupTransform(currentInfo.ParentPathId);
+ tPathSeq.Add(currentInfo.TransformPathId);
+ }
+
+ objTransformSeqs.Add(tPathSeq);
}
- // Include everything from the old bundle - perhaps this should be a config option?
- _toRepack[scene] = new(request
- .Union(existingBundleData.Data.GameObjectAssets?.Values ?? Enumerable.Empty())
- // I don't think we should try to repack failed assets unless they're re-requested
- // .Union(existingBundleData.Data.NonRepackedAssets ?? Enumerable.Empty())
- );
+ transformSeqs[objPath] = objTransformSeqs;
}
- }
- private static string GetSerializedBundleDirPrefix()
- {
- return $$"""{Silksong.{{nameof(AssetHelper)}}.Core.{{nameof(AssetPaths)}}.{{nameof(AssetPaths.RepackedSceneBundleDir)}}}""";
+ return transformSeqs;
}
- private static string GetBundlePathForScene(string sceneName)
+ private bool MustWriteCatalog()
{
- return Path.Combine(AssetPaths.RepackedSceneBundleDir, $"repacked_{sceneName}.bundle");
+ if (!JsonExtensions.TryLoadFromFile(CatalogMetadataPath, out SceneCatalogMetadata? oldMeta)
+ || oldMeta.Metadata == null
+ || oldMeta.Metadata.SilksongVersion != VersionData.SilksongVersion
+ || !VersionData.EarliestAcceptableSceneRepackVersion.AllowCachedData(oldMeta.Metadata.PluginVersion)
+ || oldMeta.Metadata.OSFolderName != AssetPaths.OSFolderName
+ )
+ {
+ return true;
+ }
+
+ if (!File.Exists(SceneCatalogPath))
+ {
+ return true;
+ }
+
+ return false;
}
///
@@ -163,14 +362,25 @@ private static string GetBundlePathForScene(string sceneName)
///
private static bool MetadataMismatch(string scene, RepackedSceneBundleData existingData)
{
- if (!VersionData.EarliestAcceptableSceneRepackVersion.AllowCachedData(existingData.PluginVersion))
+ if (existingData.Metadata == null)
+ {
+ return true;
+ }
+
+ if (!VersionData.EarliestAcceptableSceneRepackVersion.AllowCachedData(existingData.Metadata.PluginVersion))
{
// Mismatch: the version of the plugin used to repack needs to be after the last acceptable version.
// We do not accept versions from the future.
return true;
}
- if (existingData.SilksongVersion == VersionData.SilksongVersion)
+ if (existingData.Metadata.OSFolderName != AssetPaths.OSFolderName)
+ {
+ // Different OS strings mean the base game bundles may be different
+ return true;
+ }
+
+ if (existingData.Metadata.SilksongVersion == VersionData.SilksongVersion)
{
// If the Silksong version matches, then we're definitely fine.
return false;
@@ -189,178 +399,59 @@ private static bool MetadataMismatch(string scene, RepackedSceneBundleData exist
return true;
}
- private static bool ResolveCab(string cabName, out string? bundlePath)
+ ///
+ /// Return true if the provided catalog info is capable of loading all assets requested
+ /// for the given scene.
+ ///
+ ///
+ ///
+ ///
+ private bool CanLoadAll(SceneCatalogInfo catalogInfo, string sceneName)
{
- bundlePath = null;
-
- if (cabName.Contains("unity"))
- {
- // this isn't a game bundle (not a cab name) so we should skip
- return true;
- }
-
- if (!BundleMetadata.CabLookup.TryGetValue(cabName.ToLowerInvariant(), out string? bundleName))
- {
- return false;
- }
+ HashSet existingAssets = new(catalogInfo.LoadableAssets);
- if (bundleName.Contains("monoscripts") || bundleName.Contains("builtinassets"))
+ if (!AssetRequestAPI.Request.SceneAssets.TryGetValue(sceneName, out HashSet requested))
{
- // Skip these because we shouldn't follow any deps there
+ // If nothing was requested, then certainly everything requested can be loaded.
return true;
}
- bundlePath = Path.Combine(AssetPaths.BundleFolder, bundleName);
- return true;
+ return requested.IsSubsetOf(existingAssets);
}
- ///
- /// Run the repacking procedure so that by the end, anything in the request which could be repacked has been.
- ///
- private IEnumerator RunRepacking(ILoadingScreen bar)
+ private static string GetSerializedBundleDirPrefix()
{
- CachedObject SyncedCppCache = CachedObject.CreateSynced(
- "container_pointer_preloads_cache.json", () => new(), mutable: true, out IDisposable? cppSyncHandle);
-
- ContainerPointerPreloads cpp = new(ResolveCab) { Cache = SyncedCppCache.Value };
- PreloadTableResolver resolver = new([new DefaultPreloadTableResolver(), cpp]);
-
- SceneRepacker repacker = new StrippedSceneRepacker(resolver);
-
- Stopwatch mainSw = Stopwatch.StartNew();
-
- int total = _toRepack.Count;
- int count = 0;
-
- AssetHelperPlugin.InstanceLogger.LogInfo($"Repacking {_toRepack.Count} scenes");
- foreach ((string scene, HashSet request) in _toRepack)
- {
- Stopwatch sw = Stopwatch.StartNew();
- AssetHelperPlugin.InstanceLogger.LogInfo($"Repacking {request.Count} objects in scene {scene}");
- bar.SetSubtext(scene);
-
- RepackingParams rParams = new()
- {
- SceneBundlePath = AssetPaths.GetScenePath(scene),
- ObjectNames = request.ToList(),
- ContainerPrefix = $"{nameof(AssetHelper)}/{scene}",
- OutBundlePath = GetBundlePathForScene(scene),
- };
- RepackedBundleData repackData = repacker.Repack(rParams);
-
- string? hash = null;
- if (AddressablesData.TryGetLocationForScene(scene, out IResourceLocation? location) && location.Data is AssetBundleRequestOptions opts)
- {
- hash = opts.Hash;
- }
-
- RepackedSceneBundleData sceneRepackData = new()
- {
- SceneName = scene,
- BundleHash = hash,
- Data = repackData
- };
-
- _repackData[scene] = sceneRepackData;
- _repackData.SerializeToFile(AssetPaths.RepackedSceneBundleMetadataPath);
- AssetHelperPlugin.InstanceLogger.LogInfo($"Repacked {scene} in {sw.ElapsedMilliseconds} ms");
-
- count++;
- bar.SetProgress((float)count / (float)total);
-
- yield return null;
- }
-
- mainSw.Stop();
- AssetHelperPlugin.InstanceLogger.LogInfo($"Finished scene repacking after {mainSw.ElapsedMilliseconds} ms");
+ return $$"""{Silksong.{{nameof(AssetHelper)}}.Core.{{nameof(AssetPaths)}}.{{nameof(AssetPaths.RepackedSceneBundleDir)}}}""";
+ }
- bar.Reset();
- cppSyncHandle?.Dispose();
+ private static string GetBundlePathForScene(string sceneName)
+ {
+ return Path.Combine(AssetPaths.RepackedSceneBundleDir, $"repacked_{sceneName}.bundle");
}
- private IEnumerator CreateSceneAssetCatalog(RepackDataCollection data, ILoadingScreen screen)
+ private static bool ResolveCab(string cabName, out string? bundlePath)
{
- string catalogMetadataPath = Path.ChangeExtension(SceneCatalogPath, ".json");
+ bundlePath = null;
- if (!_didRepack
- && JsonExtensions.TryLoadFromFile(catalogMetadataPath, out SceneCatalogMetadata? oldMeta)
- && oldMeta.SilksongVersion == VersionData.SilksongVersion
- && VersionData.EarliestAcceptableSceneRepackVersion.AllowCachedData(oldMeta.PluginVersion)
- )
+ if (cabName.Contains("unity"))
{
- // We can skip only if there's no change in the repacked bundles and no change to the version metadata
- yield break;
+ // This isn't a game bundle (not a cab name) so we should silently skip
+ return true;
}
- AssetHelperPlugin.InstanceLogger.LogInfo($"Creating catalog");
-
- Stopwatch sw = Stopwatch.StartNew();
-
- CustomCatalogBuilder cbr = new(CatalogKeys.SceneCatalogId);
- foreach ((string sceneName, RepackedSceneBundleData repackBunData) in data)
+ if (!BundleMetadata.CabLookup.TryGetValue(cabName.ToLowerInvariant(), out string? bundleName))
{
- if (repackBunData.Data == null) continue;
- string bundlePath = GetBundlePathForScene(sceneName);
- string bundleFileName = Path.GetFileName(bundlePath);
- string serializedBundlePath = $"{GetSerializedBundleDirPrefix()}/{bundleFileName}";
- cbr.AddRepackedSceneData(sceneName, repackBunData.Data, bundlePath, serializedBundlePath);
-
- // Add in requested child paths
- if (AssetRequestAPI.Request.SceneAssets.TryGetValue(sceneName, out HashSet requested))
- {
- foreach (string child in requested)
- {
- if (!ObjPathUtil.TryFindAncestor(
- repackBunData.Data.GameObjectAssets?.Values.ToList(),
- child,
- out string? ancestorPath,
- out string? relativePath
- ))
- {
- AssetHelperPlugin.InstanceLogger.LogWarning($"Failed to find {child} in bundle for {sceneName} as loadable");
- continue;
- }
- if (string.IsNullOrEmpty(relativePath))
- {
- // Directly in bundle so no need to include as child
- continue;
- }
-
- string parentKey = CatalogKeys.GetKeyForSceneAsset(sceneName, ancestorPath);
- string childKey = CatalogKeys.GetKeyForSceneAsset(sceneName, child);
- ContentCatalogDataEntry entry = CatalogEntryUtils.CreateChildGameObjectEntry(parentKey, relativePath, childKey);
- cbr.AddCatalogEntry(entry);
- }
- }
+ // Surprisingly failed to resolve a cab, so we should ensure a warning is emitted
+ return false;
}
- sw.Stop();
- AssetHelperPlugin.InstanceLogger.LogInfo($"Prepared catalog in {sw.ElapsedMilliseconds} ms");
-
- screen.SetText(LanguageKeys.WRITING_SCENE.GetLocalized());
- yield return null;
-
- sw = Stopwatch.StartNew();
-
- int count = 0;
- using IEnumerator serializationRoutine = cbr.BuildRoutine();
- while (serializationRoutine.MoveNext())
+ if (bundleName.Contains("monoscripts") || bundleName.Contains("builtinassets"))
{
- float progress = serializationRoutine.Current;
- count++;
- if (count%10 == 0)
- {
- screen.SetProgress(progress);
- yield return null;
- }
+ // Silently skip these because we shouldn't follow any deps there
+ return true;
}
- sw.Stop();
- AssetHelperPlugin.InstanceLogger.LogInfo($"Finished writing catalog in {sw.ElapsedMilliseconds} ms");
-
- SceneCatalogMetadata metadata = new();
- metadata.SerializeToFile(catalogMetadataPath);
-
- yield return null;
+ bundlePath = Path.Combine(AssetPaths.BundleFolder, bundleName);
+ return true;
}
}
diff --git a/AssetHelper/packages.lock.json b/AssetHelper/packages.lock.json
deleted file mode 100644
index 93650cc..0000000
--- a/AssetHelper/packages.lock.json
+++ /dev/null
@@ -1,432 +0,0 @@
-{
- "version": 1,
- "dependencies": {
- ".NETStandard,Version=v2.1": {
- "AssetHelperLib": {
- "type": "Direct",
- "requested": "[0.11.0, )",
- "resolved": "0.11.0",
- "contentHash": "1YIHnp3S+gBLa1NzFTtSmMFA+zT7D0mCgCPqPhVa4pv5CKpJQgSiZPeiRxzMDvDMK0sdGa6O6/Uuh1pPIrBYlw=="
- },
- "AssetsTools.NET": {
- "type": "Direct",
- "requested": "[3.0.4, )",
- "resolved": "3.0.4",
- "contentHash": "NqYy5UIucwKwdkpamFTi8lr53hD+V8qQmbHzztQj1v4Pq1G4TjNlpkUWOhktTLBqBm4aPRaAkpFgB+3TDEuomw=="
- },
- "BepInEx.Analyzers": {
- "type": "Direct",
- "requested": "[1.0.8, )",
- "resolved": "1.0.8",
- "contentHash": "xrfNmunsPhBx+vStTxLonq/aHkRrDH77c9tG/x3m5eejrKe5B0nf7cJPRRt6x330sGI0bLaPTtygdeHUgvI3wQ=="
- },
- "BepInEx.Core": {
- "type": "Direct",
- "requested": "[5.4.21, )",
- "resolved": "5.4.21",
- "contentHash": "NMUPlbHTTfJ+qIQCI90uIvjuUQ4wnwt4cpRsK3ItBh1DhsWFzAHXNiZjBxZkPysljEKQ2iu89sxMTga4bxBXVQ==",
- "dependencies": {
- "BepInEx.BaseLib": "5.4.20",
- "HarmonyX": "2.7.0"
- }
- },
- "DataDrivenConstants": {
- "type": "Direct",
- "requested": "[1.1.0, )",
- "resolved": "1.1.0",
- "contentHash": "HwaWo9+GHQjS8DQnV9L+sPyXfgpkBItQCRbOZ4jaDmYxsJnF2JJWS+wCFFMx5tKDupaaxK8y4m0CkwWRPX3tHA=="
- },
- "Hamunii.BepInEx.AutoPlugin": {
- "type": "Direct",
- "requested": "[2.1.0, )",
- "resolved": "2.1.0",
- "contentHash": "EuH0DoeBkpEiLYwCVZrlrQiNoqYi7U42+MCGpVim22erghvYYjPosEQTCHMsmyyBjLO/0NuLCu4I9aO/EkjVRQ=="
- },
- "HarmonyX": {
- "type": "Direct",
- "requested": "[2.9.0, )",
- "resolved": "2.9.0",
- "contentHash": "vB03a0lJJefzSnPP8VJSzyTtgSx81dnxU2olfirHTBnbXY8Uc2BMoxCODPK1nzg4C53lQR0NOi+bp9170sYVFA==",
- "dependencies": {
- "MonoMod.RuntimeDetour": "22.1.29.1",
- "System.Reflection.Emit": "4.7.0"
- }
- },
- "Krafs.Publicizer": {
- "type": "Direct",
- "requested": "[2.3.0, )",
- "resolved": "2.3.0",
- "contentHash": "DjktTgctwxUMhMkWKrRECer3LR1lHzanCOlE4mpinAiY8SfWJq4DG/QitP5h1A+WBjyWHzQSOG+204i3VpO1FA=="
- },
- "Microsoft.Unity.Analyzers": {
- "type": "Direct",
- "requested": "[1.26.0, )",
- "resolved": "1.26.0",
- "contentHash": "lU4QRpGxzwBGkDJs86Q18CwhiqigssQrItJFr+c1Ijra35Y/uTwV/bRiSIYcXSaWl9RsXCi6dqG4xVAXb9sCtQ=="
- },
- "MonoDetour": {
- "type": "Direct",
- "requested": "[*, )",
- "resolved": "0.7.10",
- "contentHash": "st/61d+jjzIN/TfxU8gwTiD9NWhG36yiIRdo3ANcV1bpQhOr/i2WYUJ3j/Omi00+yX+kfwzMu43ygDufELWw/g==",
- "dependencies": {
- "MonoMod.RuntimeDetour": "21.12.13.1"
- }
- },
- "MonoDetour.HookGen": {
- "type": "Direct",
- "requested": "[0.7.*, )",
- "resolved": "0.7.12",
- "contentHash": "h9UhcqMDiXqlO5omQ8J3n17n/3vVT3IzxiL14HQ9QxYnGVX8kBiSkIV0mT+sZ2yccmgEu3daJwHQxUFphVrwpA==",
- "dependencies": {
- "MonoDetour": "0.5.0"
- }
- },
- "PolySharp": {
- "type": "Direct",
- "requested": "[1.15.0, )",
- "resolved": "1.15.0",
- "contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g=="
- },
- "Silksong.GameLibs": {
- "type": "Direct",
- "requested": "[*-*, )",
- "resolved": "1.2.0-silksong1.0.29315",
- "contentHash": "MwcUC0FMUn7aCwYHWgQegOBxNGgNBDie9nfg0GeodqbnfrHOjUZ06sZjq+0Iiz9/WBL8yd511YZujFS91FgVMw=="
- },
- "UnityEngine.Modules": {
- "type": "Direct",
- "requested": "[6000.0.50, )",
- "resolved": "6000.0.50",
- "contentHash": "hEFz1r4NGzeYXPY2yPh+/btcnwVB6EnEeRjDMCVNi19PD7VvRMvJeu46x1dNH9jYkT0B/ZXuyMBYiuI7YlRnKw=="
- },
- "BepInEx.BaseLib": {
- "type": "Transitive",
- "resolved": "5.4.20",
- "contentHash": "0bXgYxbCEN2Ixp3kiFEhyw+RASeFQeg/ww+lbMt7if6XMeVS60eg6epNsMA8Jbx57dmNOzNevkKKw8mP8SUMqw=="
- },
- "Microsoft.NETCore.Platforms": {
- "type": "Transitive",
- "resolved": "1.1.0",
- "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A=="
- },
- "Microsoft.NETCore.Targets": {
- "type": "Transitive",
- "resolved": "1.1.0",
- "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg=="
- },
- "Mono.Cecil": {
- "type": "Transitive",
- "resolved": "0.11.4",
- "contentHash": "IC1h5g0NeJGHIUgzM1P82ld57knhP0IcQfrYITDPXlNpMYGUrsG5TxuaWTjaeqDNQMBDNZkB8L0rBnwsY6JHuQ=="
- },
- "MonoMod.RuntimeDetour": {
- "type": "Transitive",
- "resolved": "22.1.29.1",
- "contentHash": "CksRFEGCPs8VGKe6pc3zsOPd2Jik+FMlmx5HDxWVTxz9JlAmWDk1+0PCwAVw4iGDuDZmIUjZDjhSACasyw9y/Q==",
- "dependencies": {
- "Mono.Cecil": "0.11.4",
- "MonoMod.Utils": "22.1.29.1",
- "System.Collections.NonGeneric": "4.3.0",
- "System.ComponentModel.TypeConverter": "4.3.0",
- "System.IO.FileSystem.Primitives": "4.3.0",
- "System.Reflection.Emit.ILGeneration": "4.7.0",
- "System.Reflection.Emit.Lightweight": "4.7.0",
- "System.Reflection.TypeExtensions": "4.7.0"
- }
- },
- "MonoMod.Utils": {
- "type": "Transitive",
- "resolved": "22.1.29.1",
- "contentHash": "qKyM/JXIA3hr2BpE5LTtYfZvIFlcLB62pkORARRv0bXKSk1n23Nl3oAdYymQMjNfrhn/jCPZRqKDFftrYa1K2Q==",
- "dependencies": {
- "Mono.Cecil": "0.11.4",
- "System.Collections.NonGeneric": "4.3.0",
- "System.ComponentModel.TypeConverter": "4.3.0",
- "System.IO.FileSystem.Primitives": "4.3.0",
- "System.Reflection.Emit.ILGeneration": "4.7.0",
- "System.Reflection.Emit.Lightweight": "4.7.0",
- "System.Reflection.TypeExtensions": "4.7.0"
- }
- },
- "System.Collections": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.Runtime": "4.3.0"
- }
- },
- "System.Collections.NonGeneric": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "prtjIEMhGUnQq6RnPEYLpFt8AtLbp9yq2zxOSrY7KJJZrw25Fi97IzBqY7iqssbM61Ek5b8f3MG/sG1N2sN5KA==",
- "dependencies": {
- "System.Diagnostics.Debug": "4.3.0",
- "System.Globalization": "4.3.0",
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0",
- "System.Runtime.Extensions": "4.3.0",
- "System.Threading": "4.3.0"
- }
- },
- "System.Collections.Specialized": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "Epx8PoVZR0iuOnJJDzp7pWvdfMMOAvpUo95pC4ScH2mJuXkKA2Y4aR3cG9qt2klHgSons1WFh4kcGW7cSXvrxg==",
- "dependencies": {
- "System.Collections.NonGeneric": "4.3.0",
- "System.Globalization": "4.3.0",
- "System.Globalization.Extensions": "4.3.0",
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0",
- "System.Runtime.Extensions": "4.3.0",
- "System.Threading": "4.3.0"
- }
- },
- "System.ComponentModel": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "VyGn1jGRZVfxnh8EdvDCi71v3bMXrsu8aYJOwoV7SNDLVhiEqwP86pPMyRGsDsxhXAm2b3o9OIqeETfN5qfezw==",
- "dependencies": {
- "System.Runtime": "4.3.0"
- }
- },
- "System.ComponentModel.Primitives": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "j8GUkCpM8V4d4vhLIIoBLGey2Z5bCkMVNjEZseyAlm4n5arcsJOeI3zkUP+zvZgzsbLTYh4lYeP/ZD/gdIAPrw==",
- "dependencies": {
- "System.ComponentModel": "4.3.0",
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0"
- }
- },
- "System.ComponentModel.TypeConverter": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "16pQ6P+EdhcXzPiEK4kbA953Fu0MNG2ovxTZU81/qsCd1zPRsKc3uif5NgvllCY598k6bI0KUyKW8fanlfaDQg==",
- "dependencies": {
- "System.Collections": "4.3.0",
- "System.Collections.NonGeneric": "4.3.0",
- "System.Collections.Specialized": "4.3.0",
- "System.ComponentModel": "4.3.0",
- "System.ComponentModel.Primitives": "4.3.0",
- "System.Globalization": "4.3.0",
- "System.Linq": "4.3.0",
- "System.Reflection": "4.3.0",
- "System.Reflection.Extensions": "4.3.0",
- "System.Reflection.Primitives": "4.3.0",
- "System.Reflection.TypeExtensions": "4.3.0",
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0",
- "System.Runtime.Extensions": "4.3.0",
- "System.Threading": "4.3.0"
- }
- },
- "System.Diagnostics.Debug": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.Runtime": "4.3.0"
- }
- },
- "System.Globalization": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.Runtime": "4.3.0"
- }
- },
- "System.Globalization.Extensions": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "System.Globalization": "4.3.0",
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0",
- "System.Runtime.Extensions": "4.3.0",
- "System.Runtime.InteropServices": "4.3.0"
- }
- },
- "System.IO": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.Runtime": "4.3.0",
- "System.Text.Encoding": "4.3.0",
- "System.Threading.Tasks": "4.3.0"
- }
- },
- "System.IO.FileSystem.Primitives": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==",
- "dependencies": {
- "System.Runtime": "4.3.0"
- }
- },
- "System.Linq": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==",
- "dependencies": {
- "System.Collections": "4.3.0",
- "System.Diagnostics.Debug": "4.3.0",
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0",
- "System.Runtime.Extensions": "4.3.0"
- }
- },
- "System.Reflection": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.IO": "4.3.0",
- "System.Reflection.Primitives": "4.3.0",
- "System.Runtime": "4.3.0"
- }
- },
- "System.Reflection.Emit": {
- "type": "Transitive",
- "resolved": "4.7.0",
- "contentHash": "VR4kk8XLKebQ4MZuKuIni/7oh+QGFmZW3qORd1GvBq/8026OpW501SzT/oypwiQl4TvT8ErnReh/NzY9u+C6wQ=="
- },
- "System.Reflection.Emit.ILGeneration": {
- "type": "Transitive",
- "resolved": "4.7.0",
- "contentHash": "AucBYo3DSI0IDxdUjKksBcQJXPHyoPyrCXYURW1WDsLI4M65Ar/goSHjdnHOAY9MiYDNKqDlIgaYm+zL2hA1KA=="
- },
- "System.Reflection.Emit.Lightweight": {
- "type": "Transitive",
- "resolved": "4.7.0",
- "contentHash": "a4OLB4IITxAXJeV74MDx49Oq2+PsF6Sml54XAFv+2RyWwtDBcabzoxiiJRhdhx+gaohLh4hEGCLQyBozXoQPqA=="
- },
- "System.Reflection.Extensions": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.Reflection": "4.3.0",
- "System.Runtime": "4.3.0"
- }
- },
- "System.Reflection.Primitives": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.Runtime": "4.3.0"
- }
- },
- "System.Reflection.TypeExtensions": {
- "type": "Transitive",
- "resolved": "4.7.0",
- "contentHash": "VybpaOQQhqE6siHppMktjfGBw1GCwvCqiufqmP8F1nj7fTUNtW35LOEt3UZTEsECfo+ELAl/9o9nJx3U91i7vA=="
- },
- "System.Resources.ResourceManager": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.Globalization": "4.3.0",
- "System.Reflection": "4.3.0",
- "System.Runtime": "4.3.0"
- }
- },
- "System.Runtime": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0"
- }
- },
- "System.Runtime.Extensions": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.Runtime": "4.3.0"
- }
- },
- "System.Runtime.Handles": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.Runtime": "4.3.0"
- }
- },
- "System.Runtime.InteropServices": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.Reflection": "4.3.0",
- "System.Reflection.Primitives": "4.3.0",
- "System.Runtime": "4.3.0",
- "System.Runtime.Handles": "4.3.0"
- }
- },
- "System.Text.Encoding": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.Runtime": "4.3.0"
- }
- },
- "System.Threading": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==",
- "dependencies": {
- "System.Runtime": "4.3.0",
- "System.Threading.Tasks": "4.3.0"
- }
- },
- "System.Threading.Tasks": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.Runtime": "4.3.0"
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/AssetHelperMenu/AssetHelperMenu.csproj b/AssetHelperMenu/AssetHelperMenu.csproj
index 79b58fb..faee3c0 100644
--- a/AssetHelperMenu/AssetHelperMenu.csproj
+++ b/AssetHelperMenu/AssetHelperMenu.csproj
@@ -14,10 +14,6 @@
$(NoWarn);MSB3270
- True
-
- True
-
$(MSBuildProjectDirectory)=/
@@ -33,7 +29,7 @@
-
+
diff --git a/AssetHelperMenu/AssetHelperMenuPlugin.cs b/AssetHelperMenu/AssetHelperMenuPlugin.cs
index c4e2738..bb768a2 100644
--- a/AssetHelperMenu/AssetHelperMenuPlugin.cs
+++ b/AssetHelperMenu/AssetHelperMenuPlugin.cs
@@ -8,6 +8,7 @@
using BepInEx.Configuration;
using USceneManager = UnityEngine.SceneManagement.SceneManager;
using Silksong.AssetHelper.Core;
+using Silksong.ModMenu.Models;
namespace AssetHelperMenu;
@@ -64,8 +65,13 @@ public AbstractMenuScreen BuildCustomMenu()
DumpGameObjectPaths(USceneManager.GetSceneAt(i).name);
}
});
-
- // TODO - add a button to dump a scene by name - blocked by ModMenu needing a text entry field
+
+ TextInput sceneEntryField = new TextInput("Scene", TextModels.ForStrings(), "Choose a scene to dump");
+ sceneSubpageBuilder.Add(sceneEntryField);
+ sceneSubpageBuilder.AddButton("Dump selected scene", () =>
+ {
+ DumpGameObjectPaths(sceneEntryField.Model.Value);
+ });
PaginatedMenuScreen sceneSubpage = sceneSubpageBuilder.Build();
diff --git a/AssetHelperMenu/packages.lock.json b/AssetHelperMenu/packages.lock.json
deleted file mode 100644
index bb1b2a9..0000000
--- a/AssetHelperMenu/packages.lock.json
+++ /dev/null
@@ -1,445 +0,0 @@
-{
- "version": 1,
- "dependencies": {
- ".NETStandard,Version=v2.1": {
- "BepInEx.Analyzers": {
- "type": "Direct",
- "requested": "[1.0.8, )",
- "resolved": "1.0.8",
- "contentHash": "xrfNmunsPhBx+vStTxLonq/aHkRrDH77c9tG/x3m5eejrKe5B0nf7cJPRRt6x330sGI0bLaPTtygdeHUgvI3wQ=="
- },
- "BepInEx.Core": {
- "type": "Direct",
- "requested": "[5.4.21, )",
- "resolved": "5.4.21",
- "contentHash": "NMUPlbHTTfJ+qIQCI90uIvjuUQ4wnwt4cpRsK3ItBh1DhsWFzAHXNiZjBxZkPysljEKQ2iu89sxMTga4bxBXVQ==",
- "dependencies": {
- "BepInEx.BaseLib": "5.4.20",
- "HarmonyX": "2.7.0"
- }
- },
- "Hamunii.BepInEx.AutoPlugin": {
- "type": "Direct",
- "requested": "[2.1.0, )",
- "resolved": "2.1.0",
- "contentHash": "EuH0DoeBkpEiLYwCVZrlrQiNoqYi7U42+MCGpVim22erghvYYjPosEQTCHMsmyyBjLO/0NuLCu4I9aO/EkjVRQ=="
- },
- "HarmonyX": {
- "type": "Direct",
- "requested": "[2.9.0, )",
- "resolved": "2.9.0",
- "contentHash": "vB03a0lJJefzSnPP8VJSzyTtgSx81dnxU2olfirHTBnbXY8Uc2BMoxCODPK1nzg4C53lQR0NOi+bp9170sYVFA==",
- "dependencies": {
- "MonoMod.RuntimeDetour": "22.1.29.1",
- "System.Reflection.Emit": "4.7.0"
- }
- },
- "Microsoft.Unity.Analyzers": {
- "type": "Direct",
- "requested": "[1.26.0, )",
- "resolved": "1.26.0",
- "contentHash": "lU4QRpGxzwBGkDJs86Q18CwhiqigssQrItJFr+c1Ijra35Y/uTwV/bRiSIYcXSaWl9RsXCi6dqG4xVAXb9sCtQ=="
- },
- "Silksong.GameLibs": {
- "type": "Direct",
- "requested": "[*-*, )",
- "resolved": "1.2.0-silksong1.0.29315",
- "contentHash": "MwcUC0FMUn7aCwYHWgQegOBxNGgNBDie9nfg0GeodqbnfrHOjUZ06sZjq+0Iiz9/WBL8yd511YZujFS91FgVMw=="
- },
- "Silksong.ModMenu": {
- "type": "Direct",
- "requested": "[0.4.3, )",
- "resolved": "0.4.3",
- "contentHash": "uRI5SS6NIX3ivv/eLFLD9elYdLEfg6s6yHX/SY2N7ps4Ijdt28KMMlRyNI09Z0oXRULRIUSl02nasFUxEB+RPg==",
- "dependencies": {
- "BepInEx.Core": "5.4.21",
- "HarmonyX": "2.9.0",
- "MonoDetour": "0.7.5",
- "MonoDetour.HookGen": "0.7.3",
- "Silksong.UnityHelper": "1.0.1",
- "UnityEngine.Modules": "6000.0.50"
- }
- },
- "UnityEngine.Modules": {
- "type": "Direct",
- "requested": "[6000.0.50, )",
- "resolved": "6000.0.50",
- "contentHash": "hEFz1r4NGzeYXPY2yPh+/btcnwVB6EnEeRjDMCVNi19PD7VvRMvJeu46x1dNH9jYkT0B/ZXuyMBYiuI7YlRnKw=="
- },
- "AssetHelperLib": {
- "type": "Transitive",
- "resolved": "0.11.0",
- "contentHash": "1YIHnp3S+gBLa1NzFTtSmMFA+zT7D0mCgCPqPhVa4pv5CKpJQgSiZPeiRxzMDvDMK0sdGa6O6/Uuh1pPIrBYlw=="
- },
- "AssetsTools.NET": {
- "type": "Transitive",
- "resolved": "3.0.4",
- "contentHash": "NqYy5UIucwKwdkpamFTi8lr53hD+V8qQmbHzztQj1v4Pq1G4TjNlpkUWOhktTLBqBm4aPRaAkpFgB+3TDEuomw=="
- },
- "BepInEx.BaseLib": {
- "type": "Transitive",
- "resolved": "5.4.20",
- "contentHash": "0bXgYxbCEN2Ixp3kiFEhyw+RASeFQeg/ww+lbMt7if6XMeVS60eg6epNsMA8Jbx57dmNOzNevkKKw8mP8SUMqw=="
- },
- "Microsoft.NETCore.Platforms": {
- "type": "Transitive",
- "resolved": "1.1.0",
- "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A=="
- },
- "Microsoft.NETCore.Targets": {
- "type": "Transitive",
- "resolved": "1.1.0",
- "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg=="
- },
- "Mono.Cecil": {
- "type": "Transitive",
- "resolved": "0.11.4",
- "contentHash": "IC1h5g0NeJGHIUgzM1P82ld57knhP0IcQfrYITDPXlNpMYGUrsG5TxuaWTjaeqDNQMBDNZkB8L0rBnwsY6JHuQ=="
- },
- "MonoDetour": {
- "type": "Transitive",
- "resolved": "0.7.10",
- "contentHash": "st/61d+jjzIN/TfxU8gwTiD9NWhG36yiIRdo3ANcV1bpQhOr/i2WYUJ3j/Omi00+yX+kfwzMu43ygDufELWw/g==",
- "dependencies": {
- "MonoMod.RuntimeDetour": "21.12.13.1"
- }
- },
- "MonoDetour.HookGen": {
- "type": "Transitive",
- "resolved": "0.7.3",
- "contentHash": "82nNksQf2HHpoTt0QhWY+uWCkA9gYE4H+7fbBI2IOa+TVe4In838/XogX/5Lx6EhXXF58qV4XWvwFxmmdg/YQw==",
- "dependencies": {
- "MonoDetour": "0.5.0"
- }
- },
- "MonoMod.RuntimeDetour": {
- "type": "Transitive",
- "resolved": "22.1.29.1",
- "contentHash": "CksRFEGCPs8VGKe6pc3zsOPd2Jik+FMlmx5HDxWVTxz9JlAmWDk1+0PCwAVw4iGDuDZmIUjZDjhSACasyw9y/Q==",
- "dependencies": {
- "Mono.Cecil": "0.11.4",
- "MonoMod.Utils": "22.1.29.1",
- "System.Collections.NonGeneric": "4.3.0",
- "System.ComponentModel.TypeConverter": "4.3.0",
- "System.IO.FileSystem.Primitives": "4.3.0",
- "System.Reflection.Emit.ILGeneration": "4.7.0",
- "System.Reflection.Emit.Lightweight": "4.7.0",
- "System.Reflection.TypeExtensions": "4.7.0"
- }
- },
- "MonoMod.Utils": {
- "type": "Transitive",
- "resolved": "22.1.29.1",
- "contentHash": "qKyM/JXIA3hr2BpE5LTtYfZvIFlcLB62pkORARRv0bXKSk1n23Nl3oAdYymQMjNfrhn/jCPZRqKDFftrYa1K2Q==",
- "dependencies": {
- "Mono.Cecil": "0.11.4",
- "System.Collections.NonGeneric": "4.3.0",
- "System.ComponentModel.TypeConverter": "4.3.0",
- "System.IO.FileSystem.Primitives": "4.3.0",
- "System.Reflection.Emit.ILGeneration": "4.7.0",
- "System.Reflection.Emit.Lightweight": "4.7.0",
- "System.Reflection.TypeExtensions": "4.7.0"
- }
- },
- "Silksong.UnityHelper": {
- "type": "Transitive",
- "resolved": "1.0.1",
- "contentHash": "e9pIwaIKjEjtV+gzvTXKiI3i/0qxSdCgOV6sMGlm6U4f2HeSSIgmhi0DVLJ7qU2LaAUTpLqQEt9q5duPUouK0g==",
- "dependencies": {
- "BepInEx.Core": "5.4.21",
- "HarmonyX": "2.9.0",
- "UnityEngine.Modules": "6000.0.50"
- }
- },
- "System.Collections": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.Runtime": "4.3.0"
- }
- },
- "System.Collections.NonGeneric": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "prtjIEMhGUnQq6RnPEYLpFt8AtLbp9yq2zxOSrY7KJJZrw25Fi97IzBqY7iqssbM61Ek5b8f3MG/sG1N2sN5KA==",
- "dependencies": {
- "System.Diagnostics.Debug": "4.3.0",
- "System.Globalization": "4.3.0",
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0",
- "System.Runtime.Extensions": "4.3.0",
- "System.Threading": "4.3.0"
- }
- },
- "System.Collections.Specialized": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "Epx8PoVZR0iuOnJJDzp7pWvdfMMOAvpUo95pC4ScH2mJuXkKA2Y4aR3cG9qt2klHgSons1WFh4kcGW7cSXvrxg==",
- "dependencies": {
- "System.Collections.NonGeneric": "4.3.0",
- "System.Globalization": "4.3.0",
- "System.Globalization.Extensions": "4.3.0",
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0",
- "System.Runtime.Extensions": "4.3.0",
- "System.Threading": "4.3.0"
- }
- },
- "System.ComponentModel": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "VyGn1jGRZVfxnh8EdvDCi71v3bMXrsu8aYJOwoV7SNDLVhiEqwP86pPMyRGsDsxhXAm2b3o9OIqeETfN5qfezw==",
- "dependencies": {
- "System.Runtime": "4.3.0"
- }
- },
- "System.ComponentModel.Primitives": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "j8GUkCpM8V4d4vhLIIoBLGey2Z5bCkMVNjEZseyAlm4n5arcsJOeI3zkUP+zvZgzsbLTYh4lYeP/ZD/gdIAPrw==",
- "dependencies": {
- "System.ComponentModel": "4.3.0",
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0"
- }
- },
- "System.ComponentModel.TypeConverter": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "16pQ6P+EdhcXzPiEK4kbA953Fu0MNG2ovxTZU81/qsCd1zPRsKc3uif5NgvllCY598k6bI0KUyKW8fanlfaDQg==",
- "dependencies": {
- "System.Collections": "4.3.0",
- "System.Collections.NonGeneric": "4.3.0",
- "System.Collections.Specialized": "4.3.0",
- "System.ComponentModel": "4.3.0",
- "System.ComponentModel.Primitives": "4.3.0",
- "System.Globalization": "4.3.0",
- "System.Linq": "4.3.0",
- "System.Reflection": "4.3.0",
- "System.Reflection.Extensions": "4.3.0",
- "System.Reflection.Primitives": "4.3.0",
- "System.Reflection.TypeExtensions": "4.3.0",
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0",
- "System.Runtime.Extensions": "4.3.0",
- "System.Threading": "4.3.0"
- }
- },
- "System.Diagnostics.Debug": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.Runtime": "4.3.0"
- }
- },
- "System.Globalization": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.Runtime": "4.3.0"
- }
- },
- "System.Globalization.Extensions": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "System.Globalization": "4.3.0",
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0",
- "System.Runtime.Extensions": "4.3.0",
- "System.Runtime.InteropServices": "4.3.0"
- }
- },
- "System.IO": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.Runtime": "4.3.0",
- "System.Text.Encoding": "4.3.0",
- "System.Threading.Tasks": "4.3.0"
- }
- },
- "System.IO.FileSystem.Primitives": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==",
- "dependencies": {
- "System.Runtime": "4.3.0"
- }
- },
- "System.Linq": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==",
- "dependencies": {
- "System.Collections": "4.3.0",
- "System.Diagnostics.Debug": "4.3.0",
- "System.Resources.ResourceManager": "4.3.0",
- "System.Runtime": "4.3.0",
- "System.Runtime.Extensions": "4.3.0"
- }
- },
- "System.Reflection": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.IO": "4.3.0",
- "System.Reflection.Primitives": "4.3.0",
- "System.Runtime": "4.3.0"
- }
- },
- "System.Reflection.Emit": {
- "type": "Transitive",
- "resolved": "4.7.0",
- "contentHash": "VR4kk8XLKebQ4MZuKuIni/7oh+QGFmZW3qORd1GvBq/8026OpW501SzT/oypwiQl4TvT8ErnReh/NzY9u+C6wQ=="
- },
- "System.Reflection.Emit.ILGeneration": {
- "type": "Transitive",
- "resolved": "4.7.0",
- "contentHash": "AucBYo3DSI0IDxdUjKksBcQJXPHyoPyrCXYURW1WDsLI4M65Ar/goSHjdnHOAY9MiYDNKqDlIgaYm+zL2hA1KA=="
- },
- "System.Reflection.Emit.Lightweight": {
- "type": "Transitive",
- "resolved": "4.7.0",
- "contentHash": "a4OLB4IITxAXJeV74MDx49Oq2+PsF6Sml54XAFv+2RyWwtDBcabzoxiiJRhdhx+gaohLh4hEGCLQyBozXoQPqA=="
- },
- "System.Reflection.Extensions": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.Reflection": "4.3.0",
- "System.Runtime": "4.3.0"
- }
- },
- "System.Reflection.Primitives": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.Runtime": "4.3.0"
- }
- },
- "System.Reflection.TypeExtensions": {
- "type": "Transitive",
- "resolved": "4.7.0",
- "contentHash": "VybpaOQQhqE6siHppMktjfGBw1GCwvCqiufqmP8F1nj7fTUNtW35LOEt3UZTEsECfo+ELAl/9o9nJx3U91i7vA=="
- },
- "System.Resources.ResourceManager": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.Globalization": "4.3.0",
- "System.Reflection": "4.3.0",
- "System.Runtime": "4.3.0"
- }
- },
- "System.Runtime": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0"
- }
- },
- "System.Runtime.Extensions": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.Runtime": "4.3.0"
- }
- },
- "System.Runtime.Handles": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.Runtime": "4.3.0"
- }
- },
- "System.Runtime.InteropServices": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.Reflection": "4.3.0",
- "System.Reflection.Primitives": "4.3.0",
- "System.Runtime": "4.3.0",
- "System.Runtime.Handles": "4.3.0"
- }
- },
- "System.Text.Encoding": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.Runtime": "4.3.0"
- }
- },
- "System.Threading": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==",
- "dependencies": {
- "System.Runtime": "4.3.0",
- "System.Threading.Tasks": "4.3.0"
- }
- },
- "System.Threading.Tasks": {
- "type": "Transitive",
- "resolved": "4.3.0",
- "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==",
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0",
- "Microsoft.NETCore.Targets": "1.1.0",
- "System.Runtime": "4.3.0"
- }
- },
- "Silksong.AssetHelper": {
- "type": "Project",
- "dependencies": {
- "AssetHelperLib": "[0.11.0, )",
- "AssetsTools.NET": "[3.0.4, )",
- "BepInEx.Core": "[5.4.21, )",
- "HarmonyX": "[2.9.0, )",
- "MonoDetour": "[*, )",
- "UnityEngine.Modules": "[6000.0.50, )"
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/AssetHelperTesting/AssetHelperTesting.csproj b/AssetHelperTesting/AssetHelperTesting.csproj
new file mode 100644
index 0000000..5a420d1
--- /dev/null
+++ b/AssetHelperTesting/AssetHelperTesting.csproj
@@ -0,0 +1,76 @@
+
+
+
+
+ AssetHelperTesting
+ netstandard2.1
+ latest
+ enable
+ True
+ recommended
+
+ $(NoWarn);MSB3270
+
+
+ $(MSBuildProjectDirectory)=/
+
+ false
+
+
+
+ $(SilksongFolder)\Hollow Knight Silksong_Data\Managed
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AssetHelperTesting/AssetHelperTestingPlugin.cs b/AssetHelperTesting/AssetHelperTestingPlugin.cs
new file mode 100644
index 0000000..71b4a89
--- /dev/null
+++ b/AssetHelperTesting/AssetHelperTestingPlugin.cs
@@ -0,0 +1,35 @@
+using AssetHelperTesting.Tests;
+using BepInEx;
+using BepInEx.Logging;
+using Silksong.AssetHelper.Dev;
+using Silksong.AssetHelper.Plugin;
+using UnityEngine;
+
+namespace AssetHelperTesting
+{
+ // TODO - adjust the plugin guid as needed
+ [BepInAutoPlugin(id: "org.silksong-modding.assethelpertesting")]
+ public partial class AssetHelperTestingPlugin : BaseUnityPlugin
+ {
+ internal static ManualLogSource InstanceLogger { get; private set; }
+
+ private void Awake()
+ {
+ InstanceLogger = Logger;
+
+ PrepareTests();
+
+ AssetRequestAPI.InvokeAfterBundleCreation(
+ () => DebugTools.DumpAllAddressableAssets(AssetRequestAPI.SceneAssetLocator!, "scene_locator.json")
+ );
+
+ Logger.LogInfo($"Plugin {Name} ({Id}) has loaded!");
+ }
+
+ // Contributors should freely modify this method
+ private void PrepareTests()
+ {
+ SquirrmTest.Prepare();
+ }
+ }
+}
diff --git a/AssetHelperTesting/Events.cs b/AssetHelperTesting/Events.cs
new file mode 100644
index 0000000..b2f59b4
--- /dev/null
+++ b/AssetHelperTesting/Events.cs
@@ -0,0 +1,35 @@
+using System;
+
+namespace AssetHelperTesting;
+
+internal static class Events
+{
+ private static Action? _onHeroStart;
+
+ public static event Action? OnHeroStart
+ {
+ add
+ {
+ EnsureHooked();
+ _onHeroStart += value;
+ }
+ remove
+ {
+ _onHeroStart -= value;
+ }
+ }
+
+ private static bool _hooked;
+
+ private static void EnsureHooked()
+ {
+ if (_hooked) return;
+ Md.HeroController.Start.Postfix(InvokeSubscribers);
+ _hooked = true;
+ }
+
+ private static void InvokeSubscribers(HeroController self)
+ {
+ _onHeroStart?.Invoke();
+ }
+}
diff --git a/AssetHelperTesting/JsonHelper.cs b/AssetHelperTesting/JsonHelper.cs
new file mode 100644
index 0000000..20d2225
--- /dev/null
+++ b/AssetHelperTesting/JsonHelper.cs
@@ -0,0 +1,32 @@
+using Newtonsoft.Json;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Reflection;
+
+namespace AssetHelperTesting;
+
+internal static class JsonHelper
+{
+ public static bool TryLoadEmbeddedJson(string filename, [NotNullWhen(true)] out T? parsed)
+ {
+ parsed = default;
+ Assembly assembly = typeof(JsonHelper).Assembly;
+
+ string resourceName = $"AssetHelperTesting.Resources.{filename}.json";
+
+ try
+ {
+ using Stream? stream = assembly.GetManifestResourceStream(resourceName);
+ if (stream == null) return false;
+
+ using StreamReader reader = new(stream);
+ string jsonText = reader.ReadToEnd();
+ parsed = JsonConvert.DeserializeObject(jsonText);
+ return parsed != null;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+}
diff --git a/AssetHelperTesting/MonoDetourImports.cs b/AssetHelperTesting/MonoDetourImports.cs
new file mode 100644
index 0000000..017f834
--- /dev/null
+++ b/AssetHelperTesting/MonoDetourImports.cs
@@ -0,0 +1,3 @@
+using MonoDetour.HookGen;
+
+[assembly: MonoDetourTargets(typeof(HeroController))]
diff --git a/AssetHelperTesting/README.md b/AssetHelperTesting/README.md
new file mode 100644
index 0000000..add95dc
--- /dev/null
+++ b/AssetHelperTesting/README.md
@@ -0,0 +1,10 @@
+# AssetHelperTesting
+
+Testing code for AssetHelper.
+
+## Contributing
+
+Any tests that should be persisted should be created as a separate class in the tests folder.
+
+Contributors should feel free to modify the code in AssetHelperTestingPlugin.PrepareTests as they
+see fit. Any code specific to a test should persist in a file in the Tests subfolder.
diff --git a/AssetHelperTesting/Resources/nonscenegroup.json b/AssetHelperTesting/Resources/nonscenegroup.json
new file mode 100644
index 0000000..dace21b
--- /dev/null
+++ b/AssetHelperTesting/Resources/nonscenegroup.json
@@ -0,0 +1,922 @@
+[
+ {
+ "BundleName": "audiocuesdynamic_assets_areaabyss",
+ "AssetName": "Assets/Audio/AtmosCues/Abyss Cocoon.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaabyss",
+ "AssetName": "Assets/Audio/MusicCues/Abyss Ascent.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaabyss",
+ "AssetName": "Assets/Audio/MusicCues/Abyss.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaabyss",
+ "AssetName": "Assets/Audio/MusicCues/Enemy Battle Abyss.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaabyss",
+ "AssetName": "Assets/Audio/MusicCues/LastDivePrologue.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaabyss",
+ "AssetName": "Assets/Audio/MusicCues/LostLace.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaabyss",
+ "AssetName": "Assets/Audio/MusicCues/LostLace2.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaabyssareadocks",
+ "AssetName": "Assets/Audio/AtmosCues/Abyss.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaant",
+ "AssetName": "Assets/Audio/AtmosCues/Hunters March.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaant",
+ "AssetName": "Assets/Audio/MusicCues/Hunter Queen Carmelita.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaant",
+ "AssetName": "Assets/Audio/MusicCues/Hunters Trail.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaant",
+ "AssetName": "Assets/Audio/Voices/Enemies_Silksong/bone hunter child/ant_child_death.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaant",
+ "AssetName": "Assets/Audio/Voices/Enemies_Silksong/bone hunter child/ant_child_sing.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaant",
+ "AssetName": "Assets/Audio/Voices/Enemies_Silksong/bone hunter child/ant_child_yelp.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaantareaboneareacoralareamossareawilds",
+ "AssetName": "Assets/Audio/MusicCues/RipAndShred.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaaqueduct",
+ "AssetName": "Assets/Audio/AtmosCues/Pharloom Chasm.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaaqueduct",
+ "AssetName": "Assets/Audio/MusicCues/Aqueducts.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaaqueduct",
+ "AssetName": "Assets/Audio/MusicCues/Pinstress.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaarborium",
+ "AssetName": "Assets/Audio/AtmosCues/Arborium.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaarborium",
+ "AssetName": "Assets/Audio/MusicCues/Memorium.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaarboriumareahangareaunderstoreareaward",
+ "AssetName": "Assets/Audio/AtmosCues/Halls PreWake.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaarboriumareahangareaunderstoreareaward",
+ "AssetName": "Assets/Audio/AtmosCues/Halls.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areabell",
+ "AssetName": "Assets/Audio/MusicCues/BellBattle.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areabell",
+ "AssetName": "Assets/Audio/MusicCues/Bell_Surrounds.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areabell",
+ "AssetName": "Assets/Audio/MusicCues/Belltown Act3.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areabell",
+ "AssetName": "Assets/Audio/MusicCues/Belltown.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areabell",
+ "AssetName": "Assets/Audio/MusicCues/Belltown_Cursed.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areabellareabellway",
+ "AssetName": "Assets/Audio/AtmosCues/BellArea.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areabellareabone",
+ "AssetName": "Assets/Audio/MusicCues/Shrine.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areabellareacoralareashellwoodareaward",
+ "AssetName": "Assets/Audio/MusicCues/Creepy Boss Main.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areabellareashellwoodareaward",
+ "AssetName": "Assets/Audio/MusicCues/Creepy Boss Ambient.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areabellareaslab",
+ "AssetName": "Assets/Audio/MusicCues/Spinner.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areabellareaslab",
+ "AssetName": "Assets/Audio/MusicCues/SpinnerRage.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areabellshrineareaboneareawilds",
+ "AssetName": "Assets/Audio/AtmosCues/Boneforest.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areabellwayareadocksareasongareawilds",
+ "AssetName": "Assets/Audio/AtmosCues/Deep Docks.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areabellwayareapeak",
+ "AssetName": "Assets/Audio/MusicCues/Peak.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areabellwayareapeakareaslab",
+ "AssetName": "Assets/Audio/AtmosCues/Mountain.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areabone",
+ "AssetName": "Assets/Audio/MusicCues/Boneforest.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areabone",
+ "AssetName": "Assets/Audio/MusicCues/Bonetown.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaboneareadocks",
+ "AssetName": "Assets/Audio/MusicCues/SongGolem.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areabonearealibrary",
+ "AssetName": "Assets/Audio/MusicCues/Vaults.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaboneareashellwoodareaslabareaward",
+ "AssetName": "Assets/Audio/MusicCues/Enemy Battle Small.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaclover",
+ "AssetName": "Assets/Audio/AtmosCues/Cloverland.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaclover",
+ "AssetName": "Assets/Audio/MusicCues/CloverDancers.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaclover",
+ "AssetName": "Assets/Audio/MusicCues/Cloverland.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacloverareacoralareamemoryareaslab",
+ "AssetName": "Assets/Audio/MusicCues/Memory.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacloverareatutorialareaweaver",
+ "AssetName": "Assets/Audio/AtmosCues/Moss Cave.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacog",
+ "AssetName": "Assets/Audio/AtmosCues/Cogwork Core.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacog",
+ "AssetName": "Assets/Audio/MusicCues/Cogwork Core.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacogareacradleareasong",
+ "AssetName": "Assets/Audio/AtmosCues/Citadel Ruined Pipes.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacoral",
+ "AssetName": "Assets/Audio/AtmosCues/Blasted Steps.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacoral",
+ "AssetName": "Assets/Audio/MusicCues/Coral King.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacoral",
+ "AssetName": "Assets/Audio/MusicCues/Coral Ruins.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacoral",
+ "AssetName": "Assets/Audio/MusicCues/Coral_Gorge.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacoral",
+ "AssetName": "Assets/Audio/MusicCues/Coral_River.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacoral",
+ "AssetName": "Assets/Audio/MusicCues/FinalJudge.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacoralarealibraryareasong",
+ "AssetName": "Assets/Audio/MusicCues/Ambience Citadel Surrounds.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacoralareamemory",
+ "AssetName": "Assets/Audio/AtmosCues/Coral Tower.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacoralareamemory",
+ "AssetName": "Assets/Audio/MusicCues/Coral Tower Ambient.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacoralareamemory",
+ "AssetName": "Assets/Audio/MusicCues/Coral Tower Battle.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacoralareamemoryareaslabareatutorial",
+ "AssetName": "Assets/Audio/AtmosCues/Memory.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacradle",
+ "AssetName": "Assets/Audio/MusicCues/Cradle.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacradle",
+ "AssetName": "Assets/Audio/MusicCues/Silk Boss A.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacradle",
+ "AssetName": "Assets/Audio/MusicCues/Silk Boss Ambient A.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacradle",
+ "AssetName": "Assets/Audio/MusicCues/Silk Boss Ambient B.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacradle",
+ "AssetName": "Assets/Audio/MusicCues/Silk Boss B.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacradle",
+ "AssetName": "Assets/Audio/MusicCues/SurfaceAscent.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacradleareasong",
+ "AssetName": "Assets/Audio/AtmosCues/Cradle.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacradleareasurface",
+ "AssetName": "Assets/Audio/AtmosCues/Surface.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacrawl",
+ "AssetName": "Assets/Audio/AtmosCues/Crawl.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areacrawl",
+ "AssetName": "Assets/Audio/MusicCues/Crawl.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areadocks",
+ "AssetName": "Assets/Audio/AtmosCues/SurfaceInterior.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areadocks",
+ "AssetName": "Assets/Audio/MusicCues/Deep Docks.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areadocksareawilds",
+ "AssetName": "Assets/Audio/MusicCues/Deep Deep Docks.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areadust",
+ "AssetName": "Assets/Audio/AtmosCues/Dustpens.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areadust",
+ "AssetName": "Assets/Audio/MusicCues/Dustpens.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areadust",
+ "AssetName": "Assets/Audio/MusicCues/MistMaze.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areadustareaorgan",
+ "AssetName": "Assets/Audio/AtmosCues/Organ.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areagreymoor",
+ "AssetName": "Assets/Audio/AtmosCues/Greymoor.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areagreymoor",
+ "AssetName": "Assets/Audio/MusicCues/Greymoor.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areahang",
+ "AssetName": "Assets/Audio/AtmosCues/Hanging Garden.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areahang",
+ "AssetName": "Assets/Audio/MusicCues/CitadelHang.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areahangarealibrary",
+ "AssetName": "Assets/Audio/MusicCues/Grand Forum Battle.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_arealibrary",
+ "AssetName": "Assets/Audio/AtmosCues/Citadel Undercave.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_arealibrary",
+ "AssetName": "Assets/Audio/AtmosCues/Library Act3.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_arealibrary",
+ "AssetName": "Assets/Audio/AtmosCues/Library.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areamemoryareatutorial",
+ "AssetName": "Assets/Audio/MusicCues/Red Memory.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areamoss",
+ "AssetName": "Assets/Audio/AtmosCues/Bonebottom Chasm.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areamoss",
+ "AssetName": "Assets/Audio/MusicCues/MossCave Act3.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaorgan",
+ "AssetName": "Assets/Audio/MusicCues/MistMaze_Organ.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaorgan",
+ "AssetName": "Assets/Audio/MusicCues/Phantom.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areashellwood",
+ "AssetName": "Assets/Audio/AtmosCues/Shellwood Underlake.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areashellwood",
+ "AssetName": "Assets/Audio/AtmosCues/Shellwood.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areashellwood",
+ "AssetName": "Assets/Audio/MusicCues/FlowerBattle.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areashellwood",
+ "AssetName": "Assets/Audio/MusicCues/Seth Battle.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areashellwood",
+ "AssetName": "Assets/Audio/MusicCues/Shellwood.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaslab",
+ "AssetName": "Assets/Audio/AtmosCues/Slab.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaslab",
+ "AssetName": "Assets/Audio/MusicCues/Cloak Battle.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaslab",
+ "AssetName": "Assets/Audio/MusicCues/FirstWeaver.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaslab",
+ "AssetName": "Assets/Audio/MusicCues/Slab.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areasong",
+ "AssetName": "Assets/Audio/AtmosCues/Citadel Main Act 3.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areasong",
+ "AssetName": "Assets/Audio/AtmosCues/Citadel Main Unwoken.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areasong",
+ "AssetName": "Assets/Audio/AtmosCues/Citadel Main.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areasong",
+ "AssetName": "Assets/Audio/MusicCues/CitadelHalls Act3.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areasong",
+ "AssetName": "Assets/Audio/MusicCues/CitadelHalls.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areasong",
+ "AssetName": "Assets/Audio/MusicCues/Enclave.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areasong",
+ "AssetName": "Assets/Audio/MusicCues/LaceDefeated.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaswamp",
+ "AssetName": "Assets/Audio/MusicCues/Shadow.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaswampareaweaverareawilds",
+ "AssetName": "Assets/Audio/MusicCues/Weaverlands.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areatutorial",
+ "AssetName": "Assets/Audio/AtmosCues/None.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areatutorial",
+ "AssetName": "Assets/Audio/MusicCues/MossCave.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areatutorial",
+ "AssetName": "Assets/Audio/MusicCues/Mosstown.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaunderstore",
+ "AssetName": "Assets/Audio/AtmosCues/Understore.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaunderstore",
+ "AssetName": "Assets/Audio/MusicCues/Understore.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areaward",
+ "AssetName": "Assets/Audio/MusicCues/Ward.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areawilds",
+ "AssetName": "Assets/Audio/AtmosCues/Wilds.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areawilds",
+ "AssetName": "Assets/Audio/MusicCues/Wilds.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_areawisp",
+ "AssetName": "Assets/Audio/MusicCues/Wisp.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_battle",
+ "AssetName": "Assets/Audio/MusicCues/Enemy Battle Grind.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_battle",
+ "AssetName": "Assets/Audio/MusicCues/Enemy Battle Mid.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_blackthread",
+ "AssetName": "Assets/Audio/MusicCues/Abyss Tension.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_boss",
+ "AssetName": "Assets/Audio/MusicCues/Boss Strive.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_boss",
+ "AssetName": "Assets/Audio/MusicCues/SmallBattle.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_candle",
+ "AssetName": "Assets/Audio/MusicCues/Chapel.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_laceboss",
+ "AssetName": "Assets/Audio/MusicCues/LaceBattle.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_menu",
+ "AssetName": "Assets/Audio/AtmosCues/MiscWind.asset",
+ "Type": "AtmosCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_menu",
+ "AssetName": "Assets/Audio/MusicCues/Title.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_shared",
+ "AssetName": "Assets/Audio/MusicCues/None.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_trobbio",
+ "AssetName": "Assets/Audio/MusicCues/TormentedTrobbio.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "audiocuesdynamic_assets_trobbio",
+ "AssetName": "Assets/Audio/MusicCues/Trobbio.asset",
+ "Type": "MusicCue, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "maps_assets_all",
+ "AssetName": "Assets/Prefabs/UI/Map/Game_Map_Hornet.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "prompts_assets_all",
+ "AssetName": "Assets/Prefabs/UI/Hornet UI/Ancestral_Art_Get_Prompt.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areabellway",
+ "AssetName": "Assets/Prefabs/UI/Fast Travel Map.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areabellway",
+ "AssetName": "Assets/Prefabs/Enemies/Projectiles/Shot Giant Centipede.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areabellway",
+ "AssetName": "Assets/Prefabs/Hornet Bosses/Giant Centipede Bomb.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "shopui_assets_all",
+ "AssetName": "Assets/Prefabs/UI/Shop/Shop Menu.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areaclover",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Aspid Hatchling.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areadust",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Grove Pilgrim Fly.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areadust",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Caltrop.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areadust",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Caltrop Ball.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areadust",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Chef Maggot Blob.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areacloverareamoss",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/SilkAcid BurstCloud.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areamoss",
+ "AssetName": "Assets/Prefabs/Enemies/Fungus 1 + 2/Grass Ball.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areamoss",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Aspid Collector Glob.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areacrawl",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Aspid Bullet.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areabone",
+ "AssetName": "Assets/Prefabs/Enemies/Projectiles/Rock Roller Bomb.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_shared",
+ "AssetName": "Assets/Prefabs/Hornet NPCs/Bellbeast Child.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areadocks",
+ "AssetName": "Assets/Prefabs/Enemies/Projectiles/DF Bomb Rock.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areawilds",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Spine Floater Spine.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_lifeblood",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Lifeblood Projectile.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_lifeblood",
+ "AssetName": "Assets/Prefabs/Items/Health Flyer.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areaant",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Hunter Child Sickle.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areaant",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Bone Hunter Javelin.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areaantareamemory",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Bone Hunter Chief Javelin.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areagreymoor",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Centipede Farmer Projectile.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areagreymoor",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Crow.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areawisp",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Wisp Fireball.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areasong",
+ "AssetName": "Assets/Prefabs/Enemies/Fungus 1 + 2/Throwing Bell.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areashellwood",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Shellwood Gnat.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areaarborium",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Lightning Bola Ball Enemy.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areacoralareamemory",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Coral Bubble.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_arealibrary",
+ "AssetName": "Assets/Prefabs/Enemies/Fungus 1 + 2/Lightbearer Globe Projectile.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_arealibrary",
+ "AssetName": "Assets/Prefabs/Hornet Bosses/Tormented Trobbio Tornado.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_arealibrary",
+ "AssetName": "Assets/Prefabs/Hornet Bosses/Trobbio Cross Bomb.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areasong",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Song Pilgrim 03.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_trobbio",
+ "AssetName": "Assets/Prefabs/Hornet Bosses/Trobbio Tornado.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_trobbio",
+ "AssetName": "Assets/Prefabs/Hornet Bosses/Trobbio Bomb.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areahang",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/rune bomb small.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areahang",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/rune slam.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areahangareasong",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Song Handmaiden.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areaslab",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Slab Fly Glob.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areaslab",
+ "AssetName": "Assets/Prefabs/Heroes/Tools/First Weaver Bomb Blast.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_shared",
+ "AssetName": "Assets/Prefabs/Effects/hero_maggoted_effect.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_shared",
+ "AssetName": "Assets/Prefabs/Enemies/Generic Attacks/Gas Explosion Recycle M.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_shared",
+ "AssetName": "Assets/Prefabs/Effects/Particle System/Knight Particles_follow.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areaswamp",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Swamp Shaman Fireball.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areadustmaze",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Silkfly Mistmaze.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areaaqueduct",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Skinny Mosquito Bullet.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areaabyss",
+ "AssetName": "Assets/Prefabs/Hornet Enemies/Gloomfly.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areaabyss",
+ "AssetName": "Assets/Prefabs/Enemies/Abyss Attacks/Abyss Bullet.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_areaabyss",
+ "AssetName": "Assets/Prefabs/Enemies/Abyss Attacks/Abyss Vomit Glob.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "localpoolprefabs_assets_laceboss",
+ "AssetName": "Assets/Prefabs/Hornet Bosses/Lost Lace/Lost Lace Summon Bullet.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "globalpoolprefabs_assets_all",
+ "AssetName": "Assets/Prefabs/Silk Possession Obj.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ },
+ {
+ "BundleName": "herodynamic_assets_all",
+ "AssetName": "Assets/Prefabs/Heroes/Hornet Cocoon Corpse.prefab",
+ "Type": "UnityEngine.GameObject, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
+ }
+]
\ No newline at end of file
diff --git a/AssetHelperTesting/Resources/scenegroup1.json b/AssetHelperTesting/Resources/scenegroup1.json
new file mode 100644
index 0000000..ce8ebd7
--- /dev/null
+++ b/AssetHelperTesting/Resources/scenegroup1.json
@@ -0,0 +1,882 @@
+{
+ "arborium_09": [
+ "_Managers",
+ "_SceneManager",
+ "TileMap",
+ "MossBone Crawler (1)",
+ "MossBone Crawler Fat"
+ ],
+ "tut_02": [
+ "bone_plat_01",
+ "bone_plat_02",
+ "Tiny Dragonfly (3)",
+ "green_grass_tri (6)/Green Grass A"
+ ],
+ "bonetown_boss": [
+ "Boss Scene/Boulders Battle",
+ "Boss Scene/Skull King"
+ ],
+ "slab_16b": [
+ "Broodmother Scene Control/Broodmother Scene/Battle Scene Broodmother/Spawner Flies",
+ "Broodmother Scene Control/Broodmother Scene/Battle Scene Broodmother/Spawner Flies/Slab Fly Small Fresh",
+ "Broodmother Scene Control/Broodmother Scene/Battle Scene Broodmother/Wave 4/Slab Fly Broodmother"
+ ],
+ "cog_dancers_boss": [
+ "Dancer Control/Death Chunks 1"
+ ],
+ "slab_10b": [
+ "Boss Scene/Pin Projectiles",
+ "Boss Scene/Loose Pins",
+ "Boss Scene/Blasts",
+ "Boss Scene/First Weaver"
+ ],
+ "shellwood_22": [
+ "Boss Scene/Pt Shield Trail"
+ ],
+ "library_13": [
+ "Grand Stage Scene/Boss Scene Trobbio/Flare Glitter",
+ "Grand Stage Scene/Boss Scene Trobbio/Floor Tiles",
+ "Grand Stage Scene/Boss Scene Trobbio/Trapdoor Bursts",
+ "Grand Stage Scene/Boss Scene TormentedTrobbio/Flare Glitter",
+ "Grand Stage Scene/Boss Scene TormentedTrobbio/Floor Tiles",
+ "Grand Stage Scene/Boss Scene TormentedTrobbio/Trapdoor Bursts",
+ "Grand Stage Scene/Boss Scene Trobbio/Trobbio",
+ "Grand Stage Scene/Boss Scene TormentedTrobbio/Tormented Trobbio"
+ ],
+ "coral_36": [
+ "Judge Children Sing Trigger",
+ "Judge Children Trigger",
+ "Judge Child (1)"
+ ],
+ "shellwood_18": [
+ "Boss Scene Parent/Boss Scene/Spikes"
+ ],
+ "memory_coral_tower": [
+ "Boss Scene/Long Spear",
+ "Boss Scene/Air Spear",
+ "Boss Scene/Uppercut Spear",
+ "Boss Scene/Roar Spikes",
+ "Boss Scene/Cross Spears",
+ "Boss Scene/Cross Followup Spears",
+ "Boss Scene/Shoot Spikes",
+ "Battle Scenes/Battle Scene Chamber 2/Wave 9/Coral Conch Driller",
+ "Enemy Activator Groups/Enemy Activator Low/Enemy Folder/Coral Goomba M (2)",
+ "Enemy Activator Groups/Enemy Activator Low/Enemy Folder/Coral Goomba L",
+ "Battle Scenes/Battle Scene Chamber 3/Wave 5 - fish1/Coral Swimmer Fat (1)",
+ "Battle Scenes/Battle Scene Chamber 3/Wave 5 - fish1/Coral Poke Swimmer",
+ "Battle Scenes/Battle Scene Chamber 2/Wave 7b - Fish/Coral Spike Swimmer (1)",
+ "Battle Scenes/Battle Scene Chamber 2/Wave 2/Coral Warrior (1)",
+ "Battle Scenes/Battle Scene Chamber 4/Wave 3/Coral Bubble Brute",
+ "Battle Scenes/Battle Scene Chamber 2/Wave 10/Coral Brawler (1)",
+ "Battle Scenes/Battle Scene Chamber 2/Wave 1/Coral Hunter",
+ "Enemy Activator Groups/Enemy Activator Low/Enemy Folder/Coral Swimmer Small",
+ "Battle Scenes/Battle Scene Chamber 3/Wave 15b - double jellyfish/Coral Big Jellyfish",
+ "Battle Scenes/Battle Scene Chamber 1/Wave 5/Coral Flyer",
+ "Battle Scenes/Battle Scene Chamber 3/Wave 2b/Coral Flyer Throw",
+ "Battle Scenes/Battle Scene Chamber 2/Wave 10/Coral Brawler (1)/Stomp Spire L",
+ "Boss Scene/Roar Spikes/Spike Holder 1/Coral Spike",
+ "Boss Scene/Coral King",
+ "Fish/Pt Exit"
+ ],
+ "memory_ant_queen": [
+ "Boss Scene/Grind Spikes R",
+ "Boss Scene/Grind Spikes L",
+ "Boss Scene/Battle Scene/Wave 4/Bone Hunter Fly Chief",
+ "Boss Scene/Hunter Queen Boss"
+ ],
+ "bone_east_08": [
+ "Boss Scene/Lava Plats",
+ "Boss Scene/Lava Rocks",
+ "Boss Scene/Pre Activation Floor/Song Golem Floor (8)"
+ ],
+ "cog_04": [
+ "Spike Cog collider Round/Cog Damager",
+ "cog_rail_hazard (9)/cog",
+ "Spike_cog_set_core (5)/Spike Cog 2",
+ "Spike_cog_set_core (5)/Spike Cog 3 (1)",
+ "cog_lever",
+ "Black Thread States Thread Only Variant/Normal World/Song Automaton 01",
+ "Black Thread States Thread Only Variant/Black Thread World/Group (1)/Song Automaton 02",
+ "Song Automaton Goomba",
+ "cog_plat_float_tiny (1)",
+ "cog_plat_float (2)"
+ ],
+ "under_06": [
+ "junk_chute_ manual (1)",
+ "Grind Plat Control/Understore Grind Plat (1)",
+ "understore_junk_pipe"
+ ],
+ "bone_east_09": [
+ "Lava Box",
+ "lava_set/LavaBase (1)"
+ ],
+ "abyss_07": [
+ "Abyss Tendril Hero Damager",
+ "Abyss Tendrils (16)"
+ ],
+ "coral_11": [
+ "Sand Centipede Hero Damager",
+ "Sand Centipede Attacker (10)"
+ ],
+ "bone_east_03": [
+ "Coal Region",
+ "Black Thread States Thread Only Variant/Normal World/Bone Goomba",
+ "Black Thread States Thread Only Variant/Normal World/Bone Goomba Large",
+ "bone_goomba_skull_break",
+ "bone_goomba_skull_break_large)",
+ "Black Thread States Thread Only Variant/Normal World/Hunting PreScene/Bone Circler",
+ "Black Thread States Thread Only Variant/Black Thread World/Bone Circler Vicious (1)",
+ "Black Thread States Thread Only Variant/Normal World/Bone_Boulder",
+ "bone_plat_02 (2)",
+ "bone_plat_03 (2)",
+ "bone_plat_03 (6)",
+ "Dock Worker",
+ "lava_rocks_top_glower"
+ ],
+ "cradle_03": [
+ "cradle_spike_plat (10)",
+ "Boss Scene/Rubble Fields/Rubble Field M",
+ "cradle_plat (7)",
+ "cradle_spike_plat (10)/art/Cradle__0004_moving_plat (9)"
+ ],
+ "coral_29": [
+ "Zap Hazard Parent",
+ "Zap Worm Lightning (2)",
+ "Boss Scene/Zap Clusters/Cluster 1/Mega Jelly Zap",
+ "coral_zap_mounds_shortest (10)"
+ ],
+ "belltown": [
+ "Town States/Spinner Defeated/Quest Board Pivot/Quest_Board",
+ "hanging_bell_house 2/plat_float_07",
+ "Hornet House States/Full/plat_float_07 (1)",
+ "Black Thread States Thread Only Variant/Normal World/shop_sign",
+ "Pin Pilgrim",
+ "Town States/Spinner Defeated/Bagpipers Not Here/Belltown Greeter Act3",
+ "BlurPlane"
+ ],
+ "arborium_04": [
+ "MossBone Fly (1)",
+ "Enemy Respawner/Source Folder/Bone Thumper"
+ ],
+ "tut_03": [
+ "Black Thread States/Normal World/Battle Scene/Wave 1/Mossbone Mother"
+ ],
+ "mosstown_01": [
+ "Black Thread States Thread Only Variant/Black Thread World/Aspid Collector",
+ "Black Thread States Thread Only Variant/Normal World/Pilgrim Moss Spitter",
+ "Pilgrim 03 (1)",
+ "Pilgrim 01",
+ "Bone Lever"
+ ],
+ "greymoor_13": [
+ "Pilgrim 03/Drop Dust",
+ "Black Thread States Thread Only Variant/Normal World/Pilgrim BellThrower",
+ "Black Thread States Thread Only Variant/Black Thread World/Pilgrim Bellthrower Fly"
+ ],
+ "bone_east_14b": [
+ "Pilgrim 04",
+ "Pilgrim 02 (1)"
+ ],
+ "coral_32": [
+ "Black Thread States/Black Thread World/Black_Thread_Core/Enemy Group/Pilgrim Fly",
+ "Black Thread States/Black Thread World/Black_Thread_Core/Enemy Group/Pilgrim Hiker",
+ "shell_plat_hang_bell (4)",
+ "Black Thread States/Normal World/Coral Judge (3)",
+ "fossil_judge_break_leanRight",
+ "blown_sand_tiled_set",
+ "Coral Conch Shooter (1)"
+ ],
+ "bonegrave": [
+ "Pilgrim Groups/Group 1/Act3 Pilgrim 05",
+ "Pilgrim Groups/Group 2/Pilgrim StaffWielder",
+ "Pilgrim Groups/Rosary Pilgrim Scene/Rosary Pilgrim",
+ "Pilgrim Groups/Rosary Pilgrim Scene/Geo Small Persistent (4)"
+ ],
+ "mosstown_02": [
+ "Pilgrim Trap Wire",
+ "traps_left/Pilgrim Trap Spike",
+ "Black Thread States Thread Only Variant/Normal World/Thick Silk Vines"
+ ],
+ "bonetown": [
+ "Black Thread States/Normal World/Bonetown Resident",
+ "Black Thread States/Normal World/RestBench Control/Bench Getting Repaired/Fixer Pilgrim Bench Repair",
+ "Black Thread States/Normal World/fixer_constructs/fixer_statue/Shell Shard Fossil Big",
+ "rosary_shrine_small",
+ "Black Thread States/Black Thread World/Thief Scene/Rosary Thief Group/Rosary Thief"
+ ],
+ "weave_05b": [
+ "Bone Goomba Bounce Fly (11)"
+ ],
+ "ant_19": [
+ "Bone Crawler (2)",
+ "Enemy Break Cage (9)/Enemy/Bone Flyer",
+ "Boss Control/Boss Scene/Bone Flyer Giant",
+ "Updraft Region (1)"
+ ],
+ "arborium_03": [
+ "Bone Roller",
+ "Flower Drifter (3)",
+ "Flower Drifter (4)/pollen_flare_attack",
+ "Bloom Shooter",
+ "hanging_gardens_plat_float_metal_small (3)",
+ "Arborium Plat Mid",
+ "Arborium Keeper"
+ ],
+ "bone_east_lavachallenge": [
+ "Bone Spitter",
+ "bone_plat_01_crumble_small (2)",
+ "bone_plat_01_crumble (2)",
+ "bone_plat_02_crumble (1)",
+ "bone_plat_crumble_tall (4)",
+ "Heart Piece (1)"
+ ],
+ "bone_06": [
+ "Rock Roller Scene/Rock Roller",
+ "Shell Fossil Mimic AppearVariant"
+ ],
+ "chapel_wanderer": [
+ "Battle Scene/Gates/Battle Gate Bone"
+ ],
+ "bone_east_01": [
+ "Black Thread States Thread Only Variant/Normal World/Dock Flyer",
+ "dock_plat_float_01 (1)",
+ "dock_plat_float_01 (9)"
+ ],
+ "dock_02": [
+ "Tar Slug",
+ "Dock Bomber",
+ "Shield Dockworker Spawn/Shield Dockworker (2)"
+ ],
+ "dock_11": [
+ "Tar Slug Huge (1)"
+ ],
+ "dock_02b": [
+ "Dock Charger",
+ "lava_crumble_plat (5)"
+ ],
+ "bone_east_15": [
+ "Song_Gate_small (3)",
+ "Song_lever_side",
+ "fung_plat_float_06",
+ "Fields Flock Flyer",
+ "Fields Goomba",
+ "Fields Flyer",
+ "bell_bench",
+ "bell_bench/RestBench",
+ "bell_bench/Enviro Region Simple"
+ ],
+ "under_19": [
+ "Lava_Waterfall Set (4)",
+ "lava_crumble_plat",
+ "Understore Automaton",
+ "Understore Automaton EX (9)",
+ "Pilgrim Staff Understore",
+ "Pilgrim 03 Understore (1)"
+ ],
+ "dock_09": [
+ "Boss Scene/Dock Guard Slasher",
+ "Boss Scene/Dock Guard Thrower"
+ ],
+ "room_forge": [
+ "_NPCs/Forge Daughter"
+ ],
+ "bone_east_14": [
+ "bone_plat_03",
+ "Explode Floor Scene/DropBomb Rock (2)",
+ "Spine Floater (9)",
+ "explode_wall (4)"
+ ],
+ "bone_east_24": [
+ "Bone Hopper Group/Bone Hopper Simple",
+ "Bone Hopper Group/Bone Hopper Giant",
+ "Ant Trapper Quest Scene (3)/Tracking Scene/Trapper Barb Trap Landmine"
+ ],
+ "bone_east_10_church": [
+ "Rhino Scene/Rhino",
+ "Black Thread States Thread Only Variant/Normal World/Flea Rescue Sleeping"
+ ],
+ "bone_east_08_boss_golem": [
+ "Boss Scene"
+ ],
+ "crawl_04": [
+ "Little Crabs/Crabs/Small Crab",
+ "Roof Crab"
+ ],
+ "crawl_01": [
+ "Crypt Worms/GameObject/Crypt Worm (9)",
+ "Bone Worm Nests/Worm Pool/Bone Worm (2)"
+ ],
+ "crawl_03": [
+ "Area_States/Infected/Bone Worm BlueBlood (1)",
+ "Area_States/Infected/Bone Worm BlueTurret",
+ "PUSTULE_States/Active/pustule_set_small (1)/PUSTULE",
+ "bone_plat_02 (4)"
+ ],
+ "crawl_10": [
+ "Area_States/Infected/Blue Assistant"
+ ],
+ "crawl_09": [
+ "Area_States/Infected/Health Cocoon"
+ ],
+ "crawl_05": [
+ "Group/bone_plat_01 (3)",
+ "Crest_shrine_corner_plat (2)"
+ ],
+ "ant_04": [
+ "Black Thread States Thread Only Variant/Normal World/Bone Hunter Tiny",
+ "Black Thread States Thread Only Variant/Normal World/Bone Hunter Buzzer",
+ "Black Thread States Thread Only Variant/Normal World/Bone Hunter Child",
+ "Black Thread States Thread Only Variant/Normal World/Bone Hunter",
+ "White Palace Fly",
+ "Hunter Sickle Trap",
+ "Hunter Trap Plate",
+ "Silkcatcher Plant"
+ ],
+ "ant_21": [
+ "Enemy Control/Ant Merchant Killed/Big Guard Dead/Bone Hunter Fly",
+ "Enemy Control/Normal/Bone Hunter Throw",
+ "ant_rosary_string",
+ "ant_rosary_string_medium (1)",
+ "ant_rosary_string_large"
+ ],
+ "ant_09": [
+ "Fields Harpoon Ring Pole"
+ ],
+ "greymoor_06": [
+ "Farmer Scissors",
+ "Mite",
+ "Greymoor_windmill_cog (1)/GameObject/dustpen_trap_shine0000"
+ ],
+ "greymoor_05": [
+ "Scene Control/Farmer Enemies/Roosting Enemies/Farmer Catcher (2)",
+ "Scene Control/Farmer Enemies/Farmer Centipede (1)"
+ ],
+ "greymoor_16": [
+ "Gnat Giant"
+ ],
+ "greymoor_03": [
+ "Mitefly (1)",
+ "Weathervane States/Post Quest/Scarecraw",
+ "greymoor_balloon_small (2)",
+ "greymoor_balloon_mid (1)",
+ "greymoor_balloon_large",
+ "Black Thread States Thread Only Variant/Normal World/Strut Structure/Tilt Plat",
+ "break_grey_lamp_dual_twist (1)"
+ ],
+ "greymoor_15b": [
+ "Crowman",
+ "Crowman Dagger (1)"
+ ],
+ "room_crowcourt_02": [
+ "Battle Scene/Wave 2/Crowman Juror Tiny",
+ "Battle Scene/Wave 1/Crowman Juror",
+ "Battle Scene/Wave 1/Crowman Dagger Juror",
+ "Battle Scene/Wave 6/Crawfather",
+ "Battle Scene/Wave 6/Crawfather/Chains/Crawfather Attack Chain"
+ ],
+ "greymoor_05_boss": [
+ "Vampire Gnat Boss Scene/Vampire Gnat"
+ ],
+ "greymoor_07": [
+ "grey_lever_gate (1)",
+ "Greymoor_Rain_Tiled_Set"
+ ],
+ "dust_05": [
+ "Dustroach",
+ "Roachkeeper"
+ ],
+ "dust_02": [
+ "Roachfeeder Short",
+ "Black Thread States Thread Only Variant/Normal World/Roachfeeder Tall",
+ "greymoor_flip_bridge (1)"
+ ],
+ "dust_chef": [
+ "Battle Parent/Kitchen Pipe Gong/kitchen_string_offset/kitchen_string",
+ "Battle Parent/Battle Scene/Wave 1/Roachkeeper Chef Tiny",
+ "Battle Parent/Battle Scene/Wave 2/Roachkeeper Chef (1)"
+ ],
+ "dust_11": [
+ "Steel Soul States/Regular/NPC Control/Large Cocoon 1"
+ ],
+ "sprintmaster_cave": [
+ "Race Group/Tracks/Track 2/Sprintmaster Bounce Pod (4)"
+ ],
+ "wisp_02": [
+ "Wisp Bounce Pod",
+ "Wisp Farmers/Wisp Flame Lantern",
+ "Wisp Farmers/Farmer Wisp"
+ ],
+ "belltown_basement_03": [
+ "Bell Goomba",
+ "Bell Fly",
+ "rosary_cache_bell_ground"
+ ],
+ "belltown_04": [
+ "Drop Bell (1)"
+ ],
+ "shellwood_01": [
+ "shellwood_plat_float_thin",
+ "shellwood_plat_float_wide",
+ "Black Thread States/Normal World/Pilgrim Fisher Enemy (1)",
+ "Shellwood Goomba",
+ "Shellwood Goomba Flyer (1)",
+ "Black Thread States/Black Thread World/Shakra Guard Scene/Scene Folder/Mapper StandGuard NPC"
+ ],
+ "arborium_05": [
+ "Shellwood Bounce Bloom",
+ "Pond Skater",
+ "Bloom Puncher"
+ ],
+ "shellwood_10": [
+ "weaver_harp_sign",
+ "Flower Drifter",
+ "pollen_particles (1)",
+ "Ability Scene (1)/Shrine Weaver Ability"
+ ],
+ "shellwood_02": [
+ "Shellwood Wasp",
+ "Stick Insect",
+ "Stick Insect Charger"
+ ],
+ "shellwood_26": [
+ "Black Thread States/Normal World/Stick Insect Flyer (1)"
+ ],
+ "belltown_room_shellwood": [
+ "shell_hang_rope"
+ ],
+ "coral_34": [
+ "Harpoon Ring Pinstress Rope (4)"
+ ],
+ "song_20b": [
+ "sc_plat_hang_bell (5)",
+ "Dial Door Bridge"
+ ],
+ "coral_judge_arena": [
+ "Boss Scene/Last Judge"
+ ],
+ "coral_37": [
+ "Room_States/Steel/Steel Sentinel"
+ ],
+ "coral_24": [
+ "Coral Spike Goomba",
+ "Coral Conch Shooter Heavy (1)",
+ "Coral Conch Stabber (1)",
+ "coral_crust_tree (5)/Coral Crust Tree Activator",
+ "coral_crust_tree (5)/Interactive Activate Parent/Branch 1/Coral Crust Tree Spike Red",
+ "coral_crust_tree (10)/Interactive Activate Parent/Branch 1/Coral Crust Tree Spike Grey",
+ "coral_crust_tree (5)/Interactive Activate Parent/Branch 1/Coral Crust Tree Plat Small Grey",
+ "coral_crust_tree (7)/Interactive Activate Parent/Branch 1/Coral Crust Tree Plat Small Red",
+ "coral_crust_tree (5)/Interactive Activate Parent/Branch 1/Coral Crust Tree Plat Mid Red",
+ "Coral_plat_float_green_medium (1)"
+ ],
+ "arborium_06": [
+ "Coral Goomba Large (1)",
+ "Coral Crust Wall Small",
+ "Coral Crust Wall Mid (1)",
+ "Coral Crust Wall Tall (3)"
+ ],
+ "coral_39": [
+ "Coral Warrior Grey"
+ ],
+ "coral_tower_01": [
+ "Coral_Warrior_break"
+ ],
+ "arborium_08": [
+ "Giant Flea Scene/Giant Flea"
+ ],
+ "arborium_02": [
+ "ant_tiny_white_bug_swarm"
+ ],
+ "under_03": [
+ "fan_hazard"
+ ],
+ "cog_09": [
+ "puzzle cylinders/Cog_Choir_Cylinder/Anim Offset/Cog 1/puzzle cog lever"
+ ],
+ "cog_06": [
+ "Song Automaton Shield"
+ ],
+ "cog_05": [
+ "Battle Scene/Wave 2/Song Automaton Fly Spike"
+ ],
+ "cog_07": [
+ "Black Thread States/Normal World/Song Automaton Fly (3)",
+ "Black Thread States/Normal World/Repairable Scene/Song Automaton Ball (1)"
+ ],
+ "under_17": [
+ "Architect Scene/Chair/pillar E/pillar D/pillar C/pillar B/pillar A/seat/Architect NPC"
+ ],
+ "under_19b": [
+ "Understore Thrower"
+ ],
+ "under_10": [
+ "Battle Scene/Wave 1/Understore Small",
+ "Battle Scene/Wave 2/Understore Poker",
+ "Battle Scene/Wave 6/Understore Heavy (1)"
+ ],
+ "slab_15": [
+ "Mite Heavy (1)",
+ "Slab Prisoner Leaper New",
+ "Slab Prisoner Fly New"
+ ],
+ "hang_10": [
+ "Understore Mite Giant",
+ "Citadel Bat"
+ ],
+ "arborium_11": [
+ "Merchant Quest Parent/Quest Active/Battle Scene/Wave 1/Citadel Bat Large"
+ ],
+ "under_07": [
+ "steam_vent_short (3)",
+ "Battle Scene/Gates/steam_vent_short (2)"
+ ],
+ "under_05": [
+ "cog_05_shortcut/before/blocking cogs/Spike Cog 3",
+ "cog_05_shortcut/before/blocking cogs/Spike Cog 2",
+ "dock_metal_grate_floor_set (1)"
+ ],
+ "under_03d": [
+ "Black Thread States/Normal World/Understore Large Worker"
+ ],
+ "library_04": [
+ "Acolyte Control/Song Scholar Acolyte",
+ "Black Thread States/Normal World/Lightbearer (3)",
+ "Black Thread States/Normal World/Scrollkeeper",
+ "Scholar",
+ "library_lamp_stand (1)",
+ "library_lamp_wall (2)"
+ ],
+ "song_11": [
+ "Black Thread States Thread Only Variant/Normal World/Pilgrim 01 Song",
+ "Pilgrim 02 Song",
+ "Pilgrim 04 Song (2)",
+ "Pilgrim Stomper Song",
+ "metronome_plat (11)",
+ "sc_plat_float_mid (5)"
+ ],
+ "song_17": [
+ "March Group Control/March Group R/Song Pilgrim 01",
+ "Garmond Fight Scene/Garmond Fighter"
+ ],
+ "hang_04_boss": [
+ "Battle Scene/Wave 3/Pilgrim 03 Song",
+ "Battle Scene/Wave 8 - Heavy Sentry/Song Heavy Sentry",
+ "Battle Scene/Wave 5 - Song Admins/Song Administrator",
+ "Battle Scene/Wave 13 - Maestro/Song Pilgrim Maestro",
+ "Battle Scene/Wave 1/Song Reed"
+ ],
+ "hang_07": [
+ "Black Thread States/Normal World/Unscaler/Song Reed Grand (1)"
+ ],
+ "song_25": [
+ "Black Thread States/Normal World/Song Reed Grand (2)/grand_reed_spell_sphere",
+ "Song Knight Control/Song Knight Present/Song Knight BattleEncounter"
+ ],
+ "song_09": [
+ "Hornet_pressure_plate_small_persistent",
+ "Citadel Switch Gate"
+ ],
+ "coral_10": [
+ "Hornet_pressure_plate/Plate",
+ "Song Gate Entrance Right",
+ "Seth Stand NPC",
+ "Black Thread States/Normal World/Hornet_way_pole_harp (9)"
+ ],
+ "song_enclave": [
+ "sc_plat_float_fat",
+ "Black Thread States/Normal World/Enclave States/States/Level 1/Enclave Simple NPC Tall",
+ "Black Thread States/Normal World/Enclave States/States/Level 1/Enclave Caretaker",
+ "Black Thread States/Normal World/Enclave States/States/Level 2/Sherma Enclave NPC",
+ "Black Thread States/Black Thread World/Enclave Act 3/Sherma Caretaker"
+ ],
+ "song_12": [
+ "Black Thread States/Normal World/sc_plat_float_fat (1)"
+ ],
+ "song_01": [
+ "sc_plat_float_tall"
+ ],
+ "cog_dancers": [
+ "Black Thread States/Normal World/harpoon_ring_gate"
+ ],
+ "ward_09": [
+ "Sherma Rescue Scene/Activation Folder/Battle Scene/Wave 1/Song Pilgrim 02"
+ ],
+ "ward_03": [
+ "Song Creeper (2)",
+ "brk_barrel_03_opencoal"
+ ],
+ "ward_02": [
+ "Boss Scene Parent/Respawn Scene/Husks/Slasher 1",
+ "Boss Scene Parent/Respawn Scene/Husks/Slammer 1",
+ "Boss Scene Parent/Silk Heart"
+ ],
+ "bone_11b": [
+ "Silk Spool"
+ ],
+ "slab_05": [
+ "Slab Fly Small",
+ "spike_trap_slab_jail/pressure_plate",
+ "Jail Gate Door (2)"
+ ],
+ "slab_04": [
+ "Slab Fly Mid (2)"
+ ],
+ "slab_22": [
+ "Slab Fly Large",
+ "slab_jail_lever"
+ ],
+ "bone_east_04c": [
+ "Scene Control/Slab Jailer Scene/Slab Fly Large Cage"
+ ],
+ "slab_21": [
+ "slab_spike_ball",
+ "trap_spinning_blade_S_bend_slab_jail (4)/prop_blade"
+ ],
+ "peak_06": [
+ "Crystal Drifter",
+ "Crystal Drifter Giant",
+ "Float Crystal (13)"
+ ],
+ "peak_05": [
+ "Peaks Drifter",
+ "chair_lift_ring/Harpoon Ring Citadel",
+ "weaver_heat_lamp (2)/Lamp",
+ "coal_lantern_jail_wall_mount/string_cap",
+ "Slide Surface (2)",
+ "peak_storm_set_mid_strength"
+ ],
+ "hang_08": [
+ "Harpoon Ring VerticalRide"
+ ],
+ "cog_08": [
+ "Harpoon Ring Rail Slider"
+ ],
+ "bellway_peak_02": [
+ "Snowflake Chunk (82)",
+ "weaver lamp_roof heat large"
+ ],
+ "peak_mask_maker": [
+ "Peak Mask Maker"
+ ],
+ "peak_08b": [
+ "DJ Get Sequence/Fayforn Ground Sit NPC"
+ ],
+ "shadow_02": [
+ "Bloat Roach",
+ "Black Thread States Thread Only Variant/Normal World/Swamp Goomba",
+ "plank_plat (4)",
+ "Swamp Bounce Pod",
+ "moss_crumble_plat",
+ "Shakra Trail Quest Parent/Active/Tracking Trail (2)/Silhouettes Parent/Silhoutte",
+ "Mapper/Mapper_ambient_rings/rings/mapper extra rings/mapper rings/Mapper_Ring_world (3)"
+ ],
+ "shadow_04": [
+ "Swamp Mosquito (3)",
+ "GameObject/Surface Water Region"
+ ],
+ "hang_09": [
+ "Soft Waterfall Region",
+ "coral_river_chunk/particle_barrel_splash",
+ "coral_river_chunk/river_top/Base",
+ "coral_river_chunk/waterfall/Base"
+ ],
+ "shadow_12": [
+ "Swamp Muckman All Control/Swamp Muckman (4)",
+ "Swamp Muckman All Control/Swamp Muckman Tall Control/Activation Folder/Swamp Muckman Tall"
+ ],
+ "shadow_26": [
+ "Swamp Drifter",
+ "gloom_lift_destroy/gloom_lift_set/gloom_plat_lift destroy"
+ ],
+ "shadow_18": [
+ "Battle Scene/Wave 6 - Boss/Swamp Shaman",
+ "maggot_sack_break (1)",
+ "Battle Scene/stake_trap_swing_repeater",
+ "Battle Scene/Gates/Battle Gate Swamp"
+ ],
+ "shadow_10": [
+ "Swamp Stake Shooter Folder (1)/Swamp Stake Shooter",
+ "Spike Ball Folder/stake_trap_swing"
+ ],
+ "shadow_27": [
+ "Breakable Hang Sack 2"
+ ],
+ "dust_maze_01": [
+ "Wraith",
+ "Mist Maze Controller/Trap Sets/Trap Set/Dust Trap Spike Plate",
+ "Mist Maze Controller/Trap Sets/Trap Set/Dust Trap Spike Dropper",
+ "Mist Maze Controller/Trap Sets/Trap Set/Mite Trap"
+ ],
+ "organ_01": [
+ "Boss Scene/Phantom",
+ "Spike (7)",
+ "Organ_outer__0012_balcony_side_plat",
+ "GameObject (58)/metal_bridge (2)",
+ "organ_lift_broken_drop/lift_bottom_broken"
+ ],
+ "aqueduct_03": [
+ "Swamp Mosquito Skinny",
+ "Swamp Barnacle (1)",
+ "Swamp Ductsucker",
+ "waterways_particles (1)",
+ "Breakable Wall"
+ ],
+ "aqueduct_05_caravan": [
+ "Caravan_States/Fleatopia/Caravan Lech/Caravan Lech Wounded"
+ ],
+ "aqueduct_04": [
+ "Swamp Barnacle Slab Fly"
+ ],
+ "abyss_05": [
+ "Abyss Crawler (2)",
+ "Abyss Crawler Large (1)",
+ "abyss_plat_mid",
+ "abyss_plat_wide",
+ "Abyss Bounce Pod"
+ ],
+ "abyss_02b": [
+ "Gloom Beast"
+ ],
+ "memory_red": [
+ "Scenery Groups/End Scenery/White Palace Fly Red Memory (1)",
+ "Scenery Groups/Deepnest Scenery/Control Lever",
+ "Scenery Groups/Entry Scenery/memory_ground_plat (6)",
+ "thread_memory_region/web_particles (1)",
+ "Scenery Groups/Entry Scenery/red_memory_silk_pod0007 (15)",
+ "Scenery Groups/Hive Scenery/Hive_Break_01",
+ "Scenery Groups/Deepnest Scenery/plat_float_07",
+ "Scenery Groups/Deepnest Scenery/deepnest_platform_05",
+ "Scenery Groups/Deepnest Scenery/deepnest_platform_03",
+ "Scenery Groups/Deepnest Scenery/deepnest_platform_04",
+ "Scenery Groups/Deepnest Scenery/deepnest_platform_01",
+ "Scenery Groups/Hive Scenery/hive_plat_04 (1)",
+ "Scenery Groups/Hive Scenery/hive_plat_02",
+ "Scenery Groups/Hive Scenery/hive_plat_01"
+ ],
+ "clover_02c": [
+ "water_components_moss_short 1/caustic_small_000 (13)",
+ "grove_pod (1)/Clover Bounce Pod Activator",
+ "grove_pod (1)/Interactive Parent/Clover Bounce Pod (3)",
+ "Memory Orb Group/Clover_Statue_Break Orb",
+ "Grass Goomba",
+ "Lilypad Plat/Lilypad Fly"
+ ],
+ "tut_04": [
+ "States/Outro Scene/Snail_Shell_Small",
+ "States/Outro Scene/Snail_Shell_Mid",
+ "States/Outro Scene/Snail_Shell_Large"
+ ],
+ "song_04": [
+ "Black Thread States/Normal World/Scene States/Green Prince Stand Song_04"
+ ],
+ "clover_04b": [
+ "Lilypad Trap Setter/Lilypad Plat (1)",
+ "Battle Scene/Wave 3/Grasshopper Child (1)",
+ "Battle Scene/Return Scene/Grasshopper Slasher",
+ "Grasshopper Fly",
+ "break_grey_lamp_harp"
+ ],
+ "clover_06": [
+ "Cloverstag (2)",
+ "Group/Clover_Silk_Pod"
+ ],
+ "clover_21": [
+ "Group/clover_gate_outer_0000_1 (53)",
+ "Group/clover_gate_outer_0000_1 (54)",
+ "clover___0019_roof2_plat (5)"
+ ],
+ "clover_05c": [
+ "Hornet_pressure_plate_small_persistent",
+ "Clover Gate (1)",
+ "Hornet Dragonfly"
+ ],
+ "clover_18": [
+ "Dragonfly Large"
+ ],
+ "aqueduct_05_festival": [
+ "Caravan_States/Flea_Games_Start_effect/confetti_burst (1)",
+ "Flea Games Counter",
+ "Caravan_States/Flea Festival/Flea Game - Juggling/Flea Games Host NPC"
+ ],
+ "cradle_destroyed_challenge_01": [
+ "Cradle Challenge Pea Break",
+ "Centipede Trap Control/Centipede Trap",
+ "Spike Lazy Flyer"
+ ],
+ "abandoned_town": [
+ "plat_float_06",
+ "Surface Scuttler",
+ "collid"
+ ],
+ "cradle_destroyed_challenge_02": [
+ "Blade Spider",
+ "Blade Spider Hang"
+ ],
+ "bone_19": [
+ "Bone Chest",
+ "Breakable Wall"
+ ],
+ "song_03": [
+ "Chest Scene/Chest"
+ ],
+ "dock_06_church": [
+ "Black Thread States Thread Only Variant/Normal World/City Shard Chest"
+ ],
+ "under_08": [
+ "Understore Toll Bench (2)"
+ ],
+ "belltown_room_spare": [
+ "furnishings/bed/RestBench"
+ ],
+ "hang_06_bank": [
+ "rosary_cannon/Art/Rosary Cannon Scene/rosary_string_machine"
+ ],
+ "hang_06b": [
+ "new_scene/Reflection_surface"
+ ],
+ "ant_17": [
+ "Gilly"
+ ],
+ "library_09": [
+ "Black Thread States/Normal World/Scene Control/Garmond Scene/Garmond Fighter"
+ ],
+ "coral_33": [
+ "Black Thread States/Black Thread World/Garmond Scenes/Garmond Black Threaded Scene/Garmond Black Threaded Fighter"
+ ],
+ "hang_01": [
+ "Thread Spinner"
+ ],
+ "weave_04": [
+ "top set/Song_Gate_set (2)",
+ "Weaver Servitor (2)"
+ ],
+ "peak_04d": [
+ "Weaver Servitor Large"
+ ],
+ "weave_12": [
+ "weaver_lift_power_chamber/switches/Lever_Left"
+ ],
+ "greymoor_08_mapper": [
+ "Mapper Call Pole",
+ "Mapper Spar NPC"
+ ],
+ "hang_17b": [
+ "Boss Scene - To Additive Load/Song Knight"
+ ],
+ "bone_steel_servant": [
+ "Steel Servant Scene/Battle Scene/Wave 1/Abyss Mass"
+ ],
+ "song_19_entrance": [
+ "Black Thread States/Black Thread World/black_thread_strand"
+ ],
+ "greymoor_20c": [
+ "Crest Get Shrine"
+ ],
+ "song_15": [
+ "Black Thread States/Black Thread World/Black_Thread_Core_Citadel"
+ ],
+ "bone_01c": [
+ "bell_bench/frame/small_bell Parent",
+ "bell_bench/frame/Bell Main Parent"
+ ]
+}
\ No newline at end of file
diff --git a/AssetHelperTesting/Resources/scenegroup2.json b/AssetHelperTesting/Resources/scenegroup2.json
new file mode 100644
index 0000000..2e3903f
--- /dev/null
+++ b/AssetHelperTesting/Resources/scenegroup2.json
@@ -0,0 +1,188 @@
+{
+ "memory_coral_tower": [
+ "Group (4)/Pt Fish Shiny (11)",
+ "Group (4)/Pt Fish Fat (12)",
+ "Group (4)/Pt Fish Finny (12)",
+ "Group (4)/Pt Fish Shiny (13)",
+ "Group (4)/Pt Fish Finny (13)",
+ "Group (4)/Pt Fish Fat (10)",
+ "Fish/Pt Main/BG (2)/Pt Fish Shiny (3)",
+ "Fish/Pt Main/BG (2)/Pt Fish Finny (3)",
+ "Fish/Pt Main/BG (2)/Pt Fish Fat (3)",
+ "Fish/Pt Main/BG (2)/Pt Fish Shiny (1)",
+ "Fish/Pt Main/BG (2)/Pt Fish Finny (1)",
+ "Fish/Pt Main/BG (2)/Pt Fish Fat (1)",
+ "Fish/Pt Main/BG (2)/Pt Fish Fat",
+ "Fish/Pt Main/BG (2)/Pt Fish Finny",
+ "Fish/Pt Main/BG (2)/Pt Fish Shiny",
+ "Fish/Pt Main/BG (2)/Pt Fish Finny (2)",
+ "Fish/Pt Main/BG (2)/Pt Fish Shiny (2)",
+ "Fish/Pt Main/BG (2)/Pt Fish Fat (4)",
+ "Fish/Pt Exit/BG (2)/Pt Fish Finny",
+ "Fish/Pt Exit/BG (2)/Pt Fish Finny (1)",
+ "Fish/Pt Exit/BG (2)/Pt Fish Shiny",
+ "Fish/Pt Exit/BG (2)/Pt Fish Shiny (1)",
+ "Fish/Pt Exit/BG (2)/Pt Fish Shiny (2)",
+ "Fish/Pt Exit/BG (2)/Pt Fish Shiny (3)",
+ "Fish/Pt Exit/BG (2)/Pt Fish Finny (2)",
+ "Fish/Pt Exit/BG (2)/Pt Fish Finny (5)",
+ "Fish/Pt Exit/BG (2)/Pt Fish Shiny (5)",
+ "Fish/Pt Exit/FG (1)/Pt Fish Fat",
+ "Fish/Pt Exit/FG (1)/Pt Fish Fat (1)",
+ "Fish/Pt Exit/FG (1)/Pt Fish Finny",
+ "Fish/Pt Exit/FG (1)/Pt Fish Finny (1)",
+ "Fish/Pt Exit/FG (1)/Pt Fish Shiny",
+ "Fish/Pt Exit/FG (1)/Pt Fish Shiny (1)",
+ "Fish/Pt Exit/FG (1)/Pt Fish Finny (2)",
+ "Fish/Pt Exit/FG (1)/Pt Fish Fat (3)",
+ "Fish/Pt Exit/FG (1)/Pt Fish Shiny (2)",
+ "Fish/Pt Entry/FG/Pt Fish Fat",
+ "Fish/Pt Entry/FG/Pt Fish Fat (1)",
+ "Fish/Pt Entry/FG/Pt Fish Finny",
+ "Fish/Pt Entry/FG/Pt Fish Finny (1)",
+ "Fish/Pt Entry/FG/Pt Fish Shiny",
+ "Fish/Pt Entry/FG/Pt Fish Shiny (1)",
+ "Fish/Pt Entry/FG/Pt Fish Finny (2)",
+ "Fish/Pt Entry/FG/Pt Fish Fat (3)",
+ "Fish/Pt Entry/FG/Pt Fish Shiny (2)",
+ "Fish/Pt Entry/BG/Pt Fish Fat",
+ "Fish/Pt Entry/BG/Pt Fish Fat (1)",
+ "Fish/Pt Entry/BG/Pt Fish Finny",
+ "Fish/Pt Entry/BG/Pt Fish Finny (1)",
+ "Fish/Pt Entry/BG/Pt Fish Shiny",
+ "Fish/Pt Entry/BG/Pt Fish Shiny (1)",
+ "Fish/Pt Entry/BG/Pt Fish Fat (3)",
+ "Fish/Pt Entry/BG/Pt Fish Finny (2)",
+ "Fish/Pt Entry/BG/Pt Fish Finny (3)",
+ "Fish/Pt Entry/BG/Pt Fish Shiny (2)",
+ "Fish/Pt Entry/BG/Pt Fish Shiny (3)",
+ "Fish/Pt Entry/BG (1)/Pt Fish Finny",
+ "Fish/Pt Entry/BG (1)/Pt Fish Finny (1)",
+ "Fish/Pt Entry/BG (1)/Pt Fish Shiny",
+ "Fish/Pt Entry/BG (1)/Pt Fish Shiny (1)",
+ "Fish/Pt Entry/BG (1)/Pt Fish Shiny (2)",
+ "Fish/Pt Entry/BG (1)/Pt Fish Shiny (3)",
+ "Fish/Pt Entry/BG (1)/Pt Fish Finny (2)",
+ "Fish/Pt Entry/BG (1)/Pt Fish Finny (5)",
+ "Fish/Pt Entry/BG (1)/Pt Fish Shiny (5)",
+ "Group (38)/Coral_lamp_hang_single (1)",
+ "Group (38)/Coral_lamp_hang_double (1)",
+ "Group (38)/Coral_lamp_hang_double",
+ "Group (38)/Coral_lamp_hang_single",
+ "Group (41)/Coral_lamp_hang_single (4)",
+ "Group (17)/Coral_lamp_hang_double",
+ "Group (8)/white_palace_light_beam (25)",
+ "Group (8)/white_palace_light_beam (26)",
+ "Group (8)/white_palace_light_beam (28)",
+ "Coral_BG_set (1)/white_palace_light_beam",
+ "Coral_BG_set (1)/white_palace_light_beam (22)",
+ "Coral_BG_set (1)/white_palace_light_beam (4)",
+ "Coral_BG_set (1)/white_palace_light_beam (1)",
+ "Coral_BG_set (1)/white_palace_light_beam (6)",
+ "Coral_BG_set (1)/white_palace_light_beam (9)",
+ "Coral_BG_set (1)/white_palace_light_beam (10)",
+ "Coral_BG_set (1)/white_palace_light_beam (11)",
+ "Coral_BG_set (1)/white_palace_light_beam (12)",
+ "Coral_BG_set (1)/white_palace_light_beam (15)",
+ "Coral_BG_set (1)/white_palace_light_beam (20)",
+ "Coral_BG_set (1)/white_palace_light_beam (23)",
+ "Coral_BG_set (1)/white_palace_light_beam (24)",
+ "Coral_BG_set (1)/white_palace_light_beam (14)",
+ "Group (38)/pillar_piece (2)",
+ "Group (38)/pillar_piece",
+ "Group (17)/pillar_piece (3)",
+ "Group (8)/temp fog (32)",
+ "Coral_BG_set (1)/temp fog (1)",
+ "Coral_BG_set (1)/temp fog (2)",
+ "Coral_BG_set (1)/temp fog (3)",
+ "Coral_BG_set (1)/temp fog (5)",
+ "Coral_BG_set (1)/temp fog (59)",
+ "Coral_BG_set (1)/temp fog (7)",
+ "Coral_BG_set (1)/temp fog (8)",
+ "Coral_BG_set (1)/temp fog (9)",
+ "Coral_BG_set (1)/temp fog (10)",
+ "Coral_BG_set (1)/temp fog (11)",
+ "Coral_BG_set (1)/temp fog (12)",
+ "Coral_BG_set (1)/temp fog (13)",
+ "Coral_BG_set (1)/temp fog (14)",
+ "Coral_BG_set (1)/temp fog (60)",
+ "Coral_BG_set (1)/temp fog (61)",
+ "Coral_BG_set (1)/temp fog (15)",
+ "Coral_BG_set (1)/temp fog (16)",
+ "Coral_BG_set (1)/temp fog (17)",
+ "Coral_BG_set (1)/temp fog (18)",
+ "Coral_BG_set (1)/temp fog (21)",
+ "Coral_BG_set (1)/temp fog (22)",
+ "Coral_BG_set (1)/temp fog (23)",
+ "Coral_BG_set (1)/temp fog (24)",
+ "Coral_BG_set (1)/temp fog (26)",
+ "Coral_BG_set (1)/temp fog (64)",
+ "Coral_BG_set (1)/temp fog (70)",
+ "Coral_BG_set (1)/temp fog (65)",
+ "Coral_BG_set (1)/temp fog (27)",
+ "Coral_BG_set (1)/temp fog (28)",
+ "Coral_BG_set (1)/temp fog (30)",
+ "Coral_BG_set (1)/temp fog (63)",
+ "Coral_BG_set (1)/temp fog (25)",
+ "Coral_BG_set (1)/temp fog (29)",
+ "Coral_BG_set (1)/temp fog (52)",
+ "Coral_BG_set (1)/temp fog (32)",
+ "Coral_BG_set (1)/temp fog (66)",
+ "Coral_BG_set (1)/temp fog (67)",
+ "Coral_BG_set (1)/temp fog (33)",
+ "Coral_BG_set (1)/temp fog (34)",
+ "Coral_BG_set (1)/temp fog (35)",
+ "Coral_BG_set (1)/temp fog (36)",
+ "Coral_BG_set (1)/temp fog (37)",
+ "Coral_BG_set (1)/temp fog (38)",
+ "Coral_BG_set (1)/temp fog (39)",
+ "Coral_BG_set (1)/temp fog (40)",
+ "Coral_BG_set (1)/temp fog (41)",
+ "Coral_BG_set (1)/temp fog (42)",
+ "Coral_BG_set (1)/temp fog (43)",
+ "Coral_BG_set (1)/temp fog (44)",
+ "Coral_BG_set (1)/temp fog (45)",
+ "Coral_BG_set (1)/temp fog (46)",
+ "Coral_BG_set (1)/temp fog (62)",
+ "Coral_BG_set (1)/temp fog (49)",
+ "Coral_BG_set (1)/temp fog (50)",
+ "Coral_BG_set (1)/temp fog (51)",
+ "Coral_BG_set (1)/temp fog (31)",
+ "Coral_BG_set (1)/temp fog (68)",
+ "Coral_BG_set (1)/temp fog (69)",
+ "Coral_BG_set (1)/temp fog (53)",
+ "Coral_BG_set (1)/temp fog (54)",
+ "Coral_BG_set (1)/temp fog (55)",
+ "Coral_BG_set (1)/temp fog (56)",
+ "Coral_BG_set (1)/temp fog (57)",
+ "Coral_BG_set (1)/temp fog (58)",
+ "Boss Scene/coral_king_final_throne/initial_darkeners/temp fog (69)",
+ "Boss Scene/coral_king_final_throne/fogger/temp fog (52)",
+ "Group (4)/temp fog (63)",
+ "Group/waterfall caustic",
+ "waterfall caustic (1)/waterfall caustic (3)",
+ "Top (33)",
+ "Top (29)",
+ "Top (32)",
+ "waterfall_base_large (1)/Fungus_Steam (2)",
+ "waterfall_base_large (1)/particle_barrel_splash (4)",
+ "waterfall_base_large (1)/particle_barrel_splash (5)",
+ "Group (6)/Audio Waterfall Small"
+ ],
+ "hang_02": [
+ "coral_river_chunk/river_top",
+ "Surface Water Region",
+ "water_components_short_simple_white/StillWater",
+ "water_components_short_simple_white/waterways_water_components/acid_water_top",
+ "Surface Water Region (1)",
+ "coral_river_chunk/corner"
+ ],
+ "hang_03": [
+ "coral_river_chunk/waterfall"
+ ],
+ "coral_26": [
+ "Coral_Spikes_pin (10)/Coral_extras_01_0005_s3",
+ "Coral_Spikes_pin (12)/Coral_extras_01_0004_s4 (3)",
+ "Spike Collider Barb (2)"
+ ]
+}
\ No newline at end of file
diff --git a/AssetHelperTesting/Tests/DependentParentTest.cs b/AssetHelperTesting/Tests/DependentParentTest.cs
new file mode 100644
index 0000000..fb9da00
--- /dev/null
+++ b/AssetHelperTesting/Tests/DependentParentTest.cs
@@ -0,0 +1,113 @@
+using HutongGames.PlayMaker;
+using HutongGames.PlayMaker.Actions;
+using Silksong.AssetHelper.ManagedAssets;
+using Silksong.FsmUtil;
+using UnityEngine;
+
+namespace AssetHelperTesting.Tests;
+
+///
+/// Test spawning an asset which depends on an ancestor.
+///
+public class DependentParentTest : MonoBehaviour
+{
+ public KeyCode SpawnHotkey { get; set; }
+
+ public static void Prepare(KeyCode spawnHotkey = KeyCode.H)
+ {
+ GameObject go = new("MossMother Spawner");
+ DontDestroyOnLoad(go);
+ DependentParentTest component = go.AddComponent();
+ component.SpawnHotkey = spawnHotkey;
+ }
+
+ private ManagedAsset _asset;
+
+ void Awake()
+ {
+ _asset = ManagedAsset.FromSceneAsset(
+ sceneName: "Tut_03",
+ objPath: "Black Thread States/Normal World/Battle Scene/Wave 1/Mossbone Mother");
+
+ Md.HeroController.Start.Postfix(DoLoad);
+ }
+
+ private void DoLoad(HeroController self)
+ {
+ _asset.Load();
+ }
+
+ // Code lifted from https://github.com/cometcake575/Architect-Silksong/blob/main/Behaviour/Fixers/EnemyFixers.cs
+ public static void FixMossMother(GameObject obj)
+ {
+ PlayMakerFSM fsm = obj.LocateMyFSM("Control");
+
+ // Variables to align
+ FsmVariables vars = fsm.FsmVariables;
+ FsmFloat centreX = vars.FindFsmFloat("Centre X");
+ FsmFloat leftX = vars.FindFsmFloat("Left X");
+ FsmFloat rightX = vars.FindFsmFloat("Right X");
+ FsmFloat swoopY = vars.FindFsmFloat("Swoop Height");
+ FsmFloat maxY = vars.FindFsmFloat("Max Height");
+
+ // Align based on self
+ fsm.GetState("Init")!.InsertMethod(() => Realign(obj.transform.position), 0);
+
+ // Wake
+ obj.GetComponent().enabled = true;
+ obj.transform.GetChild(0).gameObject.SetActive(false);
+ fsm.GetState("Dormant")!.InsertMethod(() =>
+ {
+ if (obj.GetComponent().isDead) return;
+ fsm.SetState("Roar");
+ }, 0);
+
+ // Disable stun
+ fsm.GetState("Roar")!.GetAction(9)!.stunHero = false;
+
+ // Align based on player
+ fsm.GetState("Idle")!.InsertMethod(() => Realign(HeroController.instance.transform.position), 0);
+
+ // Fix stuck
+ fsm.GetState("Slam RePos")!.InsertMethod(() => fsm.SendEvent("FINISHED"), 0);
+
+ // Swoop follow alignment
+ FsmState swoop = fsm.GetState("Swoop")!;
+ swoop.DisableAction(1);
+ swoop.DisableAction(2);
+ swoop.DisableAction(3);
+
+ // Disable music
+ FsmState roarEnd = fsm.GetState("Roar End")!;
+ roarEnd.DisableAction(3);
+ roarEnd.DisableAction(4);
+
+ // Disable death bool
+ fsm.GetState("End")!.DisableAction(4);
+
+ return;
+
+ void Realign(Vector2 source)
+ {
+ centreX.value = source.x;
+ leftX.value = source.x - 10.5f;
+ rightX.value = source.x + 10.5f;
+ swoopY.value = source.y;
+ maxY.value = source.y + 6;
+ }
+ }
+
+ void Update()
+ {
+ if (Input.GetKeyDown(SpawnHotkey))
+ {
+ _asset.EnsureLoaded();
+ GameObject mossMom = _asset.InstantiateAsset();
+
+ mossMom.transform.position = HeroController.instance.transform.position + new Vector3(3, 3, 0);
+ FixMossMother(mossMom);
+
+ mossMom.SetActive(true);
+ }
+ }
+}
diff --git a/AssetHelperTesting/Tests/EnemySpawn.cs b/AssetHelperTesting/Tests/EnemySpawn.cs
new file mode 100644
index 0000000..7b62e26
--- /dev/null
+++ b/AssetHelperTesting/Tests/EnemySpawn.cs
@@ -0,0 +1,62 @@
+using AssetHelperTesting;
+using Silksong.AssetHelper.ManagedAssets;
+using System;
+using UnityEngine;
+
+///
+/// Test that requests an enemy asset, loads when entering game, spawns when hotkey pressed
+///
+public class EnemySpawn : MonoBehaviour
+{
+ public KeyCode SpawnHotkey { get; set; }
+
+ public static void Prepare(KeyCode spawnHotkey = KeyCode.H)
+ {
+ GameObject go = new("Alita Spawner");
+ DontDestroyOnLoad(go);
+ EnemySpawn component = go.AddComponent();
+ component.SpawnHotkey = spawnHotkey;
+ }
+
+ private ManagedAsset _asset;
+
+ void Awake()
+ {
+ _asset = ManagedAsset.FromSceneAsset(
+ sceneName: "Memory_Coral_Tower",
+ objPath: "Battle Scenes/Battle Scene Chamber 2/Wave 1/Coral Hunter");
+
+ Events.OnHeroStart += () => _asset.Load();
+ }
+
+ // Code lifted from https://github.com/cometcake575/Architect-Silksong/blob/main/Behaviour/Fixers/EnemyFixers.cs
+ public static void FixAlita(GameObject obj)
+ {
+ PlayMakerFSM fsm = obj.LocateMyFSM("Control");
+ fsm.FsmVariables.FindFsmBool("Spear Spawner").Value = false;
+
+ float ground = obj.transform.GetPositionY();
+ fsm.FsmVariables.FindFsmFloat("Tele Air Y Max").Value = ground + 8;
+ fsm.FsmVariables.FindFsmFloat("Tele Air Y Min").Value = ground + 2;
+ fsm.FsmVariables.FindFsmFloat("Tele Ground Y").Value = ground;
+
+ fsm.FsmVariables.FindFsmFloat("Tele X Max").Value = obj.transform.GetPositionX() + 11;
+ fsm.FsmVariables.FindFsmFloat("Tele X Min").Value = obj.transform.GetPositionX() - 11;
+
+ fsm.FsmVariables.FindFsmGameObject("Aiming Cursor").Value = new GameObject(obj.name + " Aim Cursor");
+ }
+
+ void Update()
+ {
+ if (Input.GetKeyDown(SpawnHotkey))
+ {
+ _asset.EnsureLoaded();
+ GameObject alita = _asset.InstantiateAsset();
+
+ alita.transform.position = HeroController.instance.transform.position + new Vector3(3, 0, 0);
+ FixAlita(alita);
+
+ alita.SetActive(true);
+ }
+ }
+}
diff --git a/AssetHelperTesting/Tests/LargeRequest.cs b/AssetHelperTesting/Tests/LargeRequest.cs
new file mode 100644
index 0000000..532f902
--- /dev/null
+++ b/AssetHelperTesting/Tests/LargeRequest.cs
@@ -0,0 +1,74 @@
+using Silksong.AssetHelper.Dev;
+using Silksong.AssetHelper.Plugin;
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.Windows.Speech;
+
+namespace AssetHelperTesting.Tests;
+
+///
+/// Test that makes a large request (taken from existing AssetHelper clients)
+/// but does nothing with the assets.
+///
+public class LargeRequest : MonoBehaviour
+{
+ // SceneReq1, NonSceneReq are Architect; SceneReq2 is Pharlooms Glory
+ public static void Prepare(bool sceneReq1 = true, bool sceneReq2 = true, bool nonSceneReq = true)
+ {
+ GameObject go = new("Large Request Owner");
+ DontDestroyOnLoad(go);
+ LargeRequest component = go.AddComponent();
+
+ MakeRequests(sceneReq1, sceneReq2, nonSceneReq);
+ AssetRequestAPI.InvokeAfterBundleCreation(() => DebugTools.SerializeAssetRequest());
+ }
+
+ private static void MakeRequests(bool sceneReq1, bool sceneReq2, bool nonSceneReq)
+ {
+ if (sceneReq1)
+ {
+ RequestSceneAssets("scenegroup1");
+ }
+ if (sceneReq2)
+ {
+ RequestSceneAssets("scenegroup2");
+ }
+ if (nonSceneReq)
+ {
+ RequestNonSceneAssets("nonscenegroup");
+ }
+ }
+
+ private static void RequestSceneAssets(string filename)
+ {
+ if (!JsonHelper.TryLoadEmbeddedJson(filename, out Dictionary>? parsed))
+ {
+ AssetHelperTestingPlugin.InstanceLogger.LogWarning($"Failed to parse scene assets for {filename}");
+ return;
+ }
+
+ foreach ((string scene, List assets) in parsed)
+ {
+ AssetRequestAPI.RequestSceneAssets(scene, assets);
+ }
+ }
+
+ private record RequestedNonSceneAsset(string BundleName, string AssetName, Type Type);
+
+ private static void RequestNonSceneAssets(string filename)
+ {
+ if (!JsonHelper.TryLoadEmbeddedJson(
+ filename,
+ out List? parsed))
+ {
+ AssetHelperTestingPlugin.InstanceLogger.LogWarning($"Failed to parse non-scene assets for {filename}");
+ return;
+ }
+
+ foreach ((string bundleName, string assetName, Type assetType) in parsed)
+ {
+ AssetRequestAPI.RequestNonSceneAsset(bundleName, assetName, assetType);
+ }
+ }
+}
diff --git a/AssetHelperTesting/Tests/MultiGoSpawn.cs b/AssetHelperTesting/Tests/MultiGoSpawn.cs
new file mode 100644
index 0000000..1d16c62
--- /dev/null
+++ b/AssetHelperTesting/Tests/MultiGoSpawn.cs
@@ -0,0 +1,45 @@
+using Silksong.AssetHelper.ManagedAssets;
+using Silksong.AssetHelper.Plugin;
+using Silksong.UnityHelper.Extensions;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using UnityEngine;
+using UnityEngine.AddressableAssets;
+
+namespace AssetHelperTesting.Tests;
+
+///
+/// Test associated with multiple game objects having the same name.
+///
+public class MultiGoSpawn : MonoBehaviour
+{
+ public KeyCode RootHotkey { get; set; }
+
+ private static ManagedAssetList _groupAsset;
+
+ public static void Prepare(KeyCode rootHotkey = KeyCode.H)
+ {
+ GameObject go = new("Group Spawner");
+ DontDestroyOnLoad(go);
+ MultiGoSpawn component = go.AddComponent();
+ component.RootHotkey = rootHotkey;
+ }
+
+ void Awake()
+ {
+ _groupAsset = ManagedAssetList.FromSceneAsset(sceneName: "Weave_08", objPath: "Group");
+ Events.OnHeroStart += () => _groupAsset.Load();
+ }
+
+ void Update()
+ {
+ if (!Input.GetKeyDown(RootHotkey)) return;
+
+ _groupAsset.EnsureLoaded();
+
+ GameObject spawnedGroup = _groupAsset.InstantiateAsset(go => go.FindChild("Inspect Region (1)") != null);
+ spawnedGroup.transform.position = HeroController.instance.transform.position + new Vector3(5, 0, 0);
+ spawnedGroup.SetActive(true);
+ }
+}
diff --git a/AssetHelperTesting/Tests/SpawnRequestedChild.cs b/AssetHelperTesting/Tests/SpawnRequestedChild.cs
new file mode 100644
index 0000000..ad03f92
--- /dev/null
+++ b/AssetHelperTesting/Tests/SpawnRequestedChild.cs
@@ -0,0 +1,41 @@
+using Silksong.AssetHelper.ManagedAssets;
+using Silksong.AssetHelper.Plugin;
+using UnityEngine;
+
+namespace AssetHelperTesting.Tests;
+
+///
+/// Test spawning a child game object whose ancestor was requested.
+///
+public class SpawnRequestedChild : MonoBehaviour
+{
+ public KeyCode SpawnHotkey { get; set; }
+
+ private static ManagedAsset _asset;
+
+ public static void Prepare(KeyCode spawnHotkey = KeyCode.H)
+ {
+ GameObject go = new("Group Spawner");
+ DontDestroyOnLoad(go);
+ SpawnRequestedChild component = go.AddComponent();
+ component.SpawnHotkey = spawnHotkey;
+ }
+
+ void Awake()
+ {
+ _asset = ManagedAsset.FromSceneAsset(sceneName: "Memory_Coral_Tower", objPath: "Fish/Pt Exit/BG (2)/Pt Fish Shiny (5)");
+ AssetRequestAPI.RequestSceneAsset(sceneName: "Memory_Coral_Tower", assetPath: "Fish/Pt Exit");
+ Events.OnHeroStart += () => _asset.Load();
+ }
+
+ void Update()
+ {
+ if (!Input.GetKeyDown(SpawnHotkey)) return;
+
+ _asset.EnsureLoaded();
+
+ GameObject spawnedAsset = _asset.InstantiateAsset();
+ spawnedAsset.transform.position = HeroController.instance.transform.position + new Vector3(5, 5, 0);
+ spawnedAsset.SetActive(true);
+ }
+}
diff --git a/AssetHelperTesting/Tests/SquirrmTest.cs b/AssetHelperTesting/Tests/SquirrmTest.cs
new file mode 100644
index 0000000..5f4fea4
--- /dev/null
+++ b/AssetHelperTesting/Tests/SquirrmTest.cs
@@ -0,0 +1,46 @@
+using Silksong.AssetHelper.ManagedAssets;
+using UnityEngine;
+
+namespace AssetHelperTesting.Tests;
+
+///
+/// Loading and instantiating this asset causes concerning warning logs to be emitted - this test
+/// is here to support investigation into this issue.
+///
+internal class SquirrmTest : MonoBehaviour
+{
+ public KeyCode LoadHotkey { get; set; }
+ public KeyCode InstantiateHotkey { get; set; }
+
+ public static void Prepare(KeyCode loadHotkey = KeyCode.G, KeyCode instantiateHotkey = KeyCode.H)
+ {
+ GameObject go = new("Squirrm loader");
+ DontDestroyOnLoad(go);
+ SquirrmTest component = go.AddComponent();
+ component.LoadHotkey = loadHotkey;
+ component.InstantiateHotkey = instantiateHotkey;
+ }
+
+ private ManagedAsset _asset;
+
+ void Awake()
+ {
+ _asset = ManagedAsset.FromSceneAsset(
+ sceneName: "Coral_36",
+ objPath: "Judge Child (1)");
+ }
+
+ void Update()
+ {
+ if (Input.GetKeyDown(LoadHotkey))
+ {
+ _asset.Load();
+ _asset.Handle.Completed += _ => AssetHelperTestingPlugin.InstanceLogger.LogInfo($"Squirrm loaded");
+ }
+
+ if (Input.GetKeyDown(InstantiateHotkey))
+ {
+ GameObject go = _asset.InstantiateAsset();
+ }
+ }
+}
diff --git a/docs/articles/load.md b/docs/articles/load.md
index ef49dc9..4f2625e 100644
--- a/docs/articles/load.md
+++ b/docs/articles/load.md
@@ -26,7 +26,7 @@ with `yield return managedAsset.Load();` executed before using the asset.
If the function loading the asset is not the one using the asset but the asset
is expected to be loaded , for instance
if the asset is being loaded on enter game, then it may be sensible to use the
-@"Silksong.AssetHelper.ManagedAssets.ManagedAssetExtensions.EnsureLoaded``1(Silksong.AssetHelper.ManagedAssets.ManagedAsset{``0})"
+@"Silksong.AssetHelper.ManagedAssets.ManagedAssetExtensions.EnsureLoaded``1(Silksong.AssetHelper.ManagedAssets.ManagedAssetBase{``0})"
method to ensure the asset is loaded before using it.
## Unloading assets