-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6c90353
commit 78685d4
Showing
20 changed files
with
1,081 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Security.Cryptography; | ||
using AssetsTools.NET.Extra; | ||
using BepInEx; | ||
using R2API.AutoVersionGen; | ||
using UnityEngine; | ||
|
||
namespace R2API; | ||
|
||
/// <summary> | ||
/// API for modifying <see cref="RuntimeAnimatorController" /> | ||
/// </summary> | ||
#pragma warning disable CS0436 // Type conflicts with imported type | ||
[AutoVersion] | ||
#pragma warning restore CS0436 // Type conflicts with imported type | ||
public static partial class AnimationsAPI | ||
{ | ||
public const string PluginGUID = R2API.PluginGUID + ".animations"; | ||
public const string PluginName = R2API.PluginName + ".Animations"; | ||
|
||
private const string bundleExtension = ".bundle"; | ||
private const string hashExtension = ".hash"; | ||
private const string dummyBundleName = "dummy_controller_bundle"; | ||
private const long dummyAnimatorControllerPathID = -27250459394986890; | ||
private const string dummyAnimatorControllerPath = "assets/dummycontroller.controller"; | ||
|
||
private static readonly Dictionary<(string, RuntimeAnimatorController), List<AnimatorModifications>> controllerModifications = []; | ||
private static readonly Dictionary<RuntimeAnimatorController, List<Animator>> controllerToAnimators = []; | ||
private static readonly List<UnityEngine.Object> cache = []; | ||
|
||
private static bool _hooksEnabled = false; | ||
private static bool _requestsDone = false; | ||
|
||
internal static void SetHooks() | ||
{ | ||
if (_hooksEnabled) | ||
{ | ||
return; | ||
} | ||
|
||
RoR2.RoR2Application.onLoad += OnLoad; | ||
|
||
_hooksEnabled = true; | ||
} | ||
|
||
internal static void UnsetHooks() | ||
{ | ||
RoR2.RoR2Application.onLoad -= OnLoad; | ||
|
||
_hooksEnabled = false; | ||
} | ||
|
||
private static void OnLoad() | ||
{ | ||
NativeHelpers.Init(); | ||
ApplyModifications(); | ||
} | ||
|
||
|
||
/// <summary> | ||
/// Add <see cref="RuntimeAnimatorController" /> modifications | ||
/// </summary> | ||
/// <param name="sourceBundlePath">Path to a bundle containing <see cref="RuntimeAnimatorController" />. For game bundles you can do System.IO.Path.Combine(Addressables.RuntimePath, "StandaloneWindows64", "bundle_name")</param> | ||
/// <param name="sourceAnimatorController"><see cref="RuntimeAnimatorController" /> to which modifications would be applied</param> | ||
/// <param name="modifications">Modifications for <see cref="RuntimeAnimatorController" /></param> | ||
public static void AddModifications( | ||
string sourceBundlePath, | ||
RuntimeAnimatorController sourceAnimatorController, | ||
AnimatorModifications modifications) | ||
{ | ||
SetHooks(); | ||
|
||
var list = controllerModifications.GetOrAddDefault((sourceBundlePath, sourceAnimatorController), () => []); | ||
list.Add(modifications); | ||
} | ||
|
||
/// <summary> | ||
/// Mapping <see cref="RuntimeAnimatorController" /> to an <see cref="Animator"/>. After modified controller will be created it will be applied to mapped animator. | ||
/// </summary> | ||
/// <param name="animator"><see cref="Animator"/> component from a prefab</param> | ||
/// <param name="controller"><see cref="RuntimeAnimatorController"/> that will have modifications applied</param> | ||
public static void AddAnimatorController(Animator animator, RuntimeAnimatorController controller) | ||
{ | ||
SetHooks(); | ||
|
||
var animators = controllerToAnimators.GetOrAddDefault(controller, () => []); | ||
animators.Add(animator); | ||
} | ||
|
||
internal static void ApplyModifications() | ||
{ | ||
var manager = new AssetsManager(); | ||
|
||
var dummyPath = Path.Combine(Path.GetDirectoryName(AnimationsPlugin.Instance.Info.Location), dummyBundleName); | ||
foreach (var ((sourceBundlePath, sourceAnimatorController), modifications) in controllerModifications) | ||
{ | ||
var sourceAnimatorControllerPathID = NativeHelpers.GetAssetPathID(sourceAnimatorController); | ||
var modifiedBundlePath = Path.Combine( | ||
Paths.CachePath, | ||
"R2API.Animations", | ||
$"{Path.GetFileName(sourceBundlePath)}_{sourceAnimatorControllerPathID}{bundleExtension}"); | ||
var hashPath = Path.Combine( | ||
Paths.CachePath, | ||
"R2API.Animations", | ||
$"{Path.GetFileName(sourceBundlePath)}_{sourceAnimatorControllerPathID}{hashExtension}"); | ||
|
||
var ignoreCache = AnimationsPlugin.IgnoreCache.Value; | ||
string hash = null; | ||
if (ignoreCache || !CachedBundleExists(modifiedBundlePath, hashPath, sourceAnimatorControllerPathID, modifications, out hash)) | ||
{ | ||
var bundleFile = manager.LoadBundleFile(sourceBundlePath); | ||
var assetFile = manager.LoadAssetsFileFromBundle(bundleFile, 0); | ||
|
||
var dummyBundleFile = manager.LoadBundleFile(dummyPath); | ||
var dummyAssetFile = manager.LoadAssetsFileFromBundle(dummyBundleFile, 0); | ||
|
||
var creator = new ModificationsBundleCreator( | ||
manager, | ||
assetFile, | ||
sourceAnimatorControllerPathID, | ||
dummyAssetFile, | ||
dummyBundleFile, | ||
dummyAnimatorControllerPathID, | ||
modifications, | ||
modifiedBundlePath | ||
); | ||
|
||
creator.Run(); | ||
if (!ignoreCache) | ||
{ | ||
File.WriteAllText(hashPath, hash); | ||
} | ||
|
||
manager.UnloadAssetsFile(dummyAssetFile); | ||
manager.UnloadBundleFile(dummyBundleFile); | ||
} | ||
|
||
var modifiedBundle = AssetBundle.LoadFromFile(modifiedBundlePath); | ||
var modifiedAnimatorController = modifiedBundle.LoadAsset<RuntimeAnimatorController>(dummyAnimatorControllerPath); | ||
|
||
if (controllerToAnimators.TryGetValue(sourceAnimatorController, out var animators)) | ||
{ | ||
foreach (var animator in animators) | ||
{ | ||
animator.runtimeAnimatorController = modifiedAnimatorController; | ||
} | ||
} | ||
|
||
cache.Add(sourceAnimatorController); | ||
cache.Add(modifiedAnimatorController); | ||
} | ||
|
||
manager.UnloadAll(true); | ||
controllerModifications.Clear(); | ||
controllerToAnimators.Clear(); | ||
} | ||
|
||
private static bool CachedBundleExists(string modifiedBundlePath, string hashPath, long sourceAnimatorControllerPathID, List<AnimatorModifications> modifications, out string hash) | ||
{ | ||
using (var md5 = MD5.Create()) | ||
using (var stream = new MemoryStream()) | ||
using (var writer = new BinaryWriter(stream)) | ||
{ | ||
writer.Write(RoR2.RoR2Application.buildId); | ||
writer.Write(sourceAnimatorControllerPathID); | ||
foreach (var modification in modifications) | ||
{ | ||
modification.WriteBinary(writer); | ||
} | ||
var hashBytes = md5.ComputeHash(stream); | ||
hash = BitConverter.ToString(hashBytes); | ||
} | ||
|
||
if (!File.Exists(modifiedBundlePath)) | ||
{ | ||
return false; | ||
} | ||
|
||
if (!File.Exists(hashPath)) | ||
{ | ||
return false; | ||
} | ||
|
||
var cachedHash = File.ReadAllText(hashPath); | ||
|
||
return hash == cachedHash; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
using BepInEx; | ||
using BepInEx.Configuration; | ||
using BepInEx.Logging; | ||
|
||
namespace R2API; | ||
|
||
[BepInPlugin(AnimationsAPI.PluginGUID, AnimationsAPI.PluginName, AnimationsAPI.PluginVersion)] | ||
public sealed class AnimationsPlugin : BaseUnityPlugin | ||
{ | ||
internal static AnimationsPlugin Instance { get; private set; } | ||
internal static new ManualLogSource Logger { get; private set; } | ||
internal static ConfigEntry<bool> IgnoreCache { get; private set; } | ||
|
||
private void Awake() | ||
{ | ||
Instance = this; | ||
Logger = base.Logger; | ||
IgnoreCache = Config.Bind("Dev", nameof(IgnoreCache), false, "Always generate new bundles with modifications"); | ||
} | ||
|
||
private void OnDestroy() | ||
{ | ||
AnimationsAPI.UnsetHooks(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using BepInEx; | ||
using R2API.Models; | ||
using UnityEngine; | ||
|
||
namespace R2API; | ||
|
||
/// <summary> | ||
/// Modifications for a <see cref="RuntimeAnimatorController"/> | ||
/// </summary> | ||
public class AnimatorModifications | ||
{ | ||
/// <summary> | ||
/// A key for caching, calculated from plugin info. | ||
/// </summary> | ||
public string Key { get; } | ||
/// <summary> | ||
/// New states to add. The Key is a layer name to which a state will be added. | ||
/// </summary> | ||
public Dictionary<string, State> NewStates { get; } = []; | ||
/// <summary> | ||
/// New transitions to add. The key is a (layer name, state name) | ||
/// </summary> | ||
public Dictionary<(string, string), Transition> NewTransitions { get; } = []; | ||
/// <summary> | ||
/// New parameters to add. | ||
/// </summary> | ||
public List<Parameter> NewParameters { get; } = []; | ||
|
||
/// <summary> | ||
/// Modifications for a <see cref="RuntimeAnimatorController"/> | ||
/// </summary> | ||
/// <param name="plugin">BepInPlugin instance</param> | ||
public AnimatorModifications(BepInPlugin plugin) | ||
{ | ||
Key = $"{plugin.GUID};{plugin.Version}"; | ||
} | ||
|
||
/// <summary> | ||
/// Writing modifications into a binary writer for caching purposes. | ||
/// </summary> | ||
/// <param name="writer"></param> | ||
public void WriteBinary(BinaryWriter writer) | ||
{ | ||
writer.Write(Key); | ||
|
||
foreach (var (layer, state) in NewStates) | ||
{ | ||
writer.Write(layer); | ||
state.WriteBinary(writer); | ||
} | ||
|
||
foreach (var ((layer, state), transition) in NewTransitions) | ||
{ | ||
writer.Write(layer); | ||
writer.Write(state); | ||
transition.WriteBinary(writer); | ||
} | ||
|
||
foreach (var parameter in NewParameters) | ||
{ | ||
parameter.WriteBinary(writer); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Text; | ||
|
||
namespace R2API; | ||
|
||
internal static class Extensions | ||
{ | ||
public static TValue GetOrAddDefault<TKey, TValue>(this Dictionary<TKey, TValue> dict, TKey key, Func<TValue> defaultValueFunc) | ||
{ | ||
if (dict.TryGetValue(key, out var value)) | ||
{ | ||
return value; | ||
} | ||
|
||
return dict[key] = defaultValueFunc(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Globalization; | ||
using System.IO; | ||
using System.Text; | ||
|
||
namespace R2API.Models; | ||
|
||
public class Condition | ||
{ | ||
public ConditionMode ConditionMode { get; set; } | ||
public string ParamName { get; set; } | ||
public float Value { get; set; } | ||
|
||
/// <summary> | ||
/// Writing into a binary writer for caching purposes. | ||
/// </summary> | ||
/// <param name="writer"></param> | ||
public void WriteBinary(BinaryWriter writer) | ||
{ | ||
writer.Write((int)ConditionMode); | ||
writer.Write(ParamName ?? ""); | ||
writer.Write(Value.ToString(CultureInfo.InvariantCulture)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Text; | ||
|
||
namespace R2API.Models; | ||
|
||
public enum ConditionMode | ||
{ | ||
IsTrue = 1, | ||
IsFalse = 2, | ||
IsGreater = 3, | ||
IsLess = 4, | ||
//IsExitTime = 5, | ||
IsEqual = 6, | ||
IsNotEqual = 7, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Text; | ||
|
||
namespace R2API.Models; | ||
|
||
public class Parameter | ||
{ | ||
public string Name { get; set; } | ||
public ParameterType Type { get; set; } | ||
public ParameterValue Value { get; set; } | ||
|
||
/// <summary> | ||
/// Writing into a binary writer for caching purposes. | ||
/// </summary> | ||
/// <param name="writer"></param> | ||
public void WriteBinary(BinaryWriter writer) | ||
{ | ||
writer.Write(Name ?? ""); | ||
writer.Write((int)Type); | ||
Value.WriteBinary(writer); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Text; | ||
|
||
namespace R2API.Models; | ||
|
||
public enum ParameterType | ||
{ | ||
Float = 1, | ||
Int = 3, | ||
Bool = 4, | ||
Trigger = 9 | ||
} |
Oops, something went wrong.