-
-
Save SChinchi/7dd6b321807e9166b65705e6aec96634 to your computer and use it in GitHub Desktop.
Include asset extension in the filename to prevent conflicts and number prefab child objects with 1-based indexing
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
// Add this script to your mod and in the Awake of your plugin and do | |
// RoR2Application.onLoad += delegate() { RoR2AssetDump.RoR2AssetDump.Execute(Logger); }; | |
using BepInEx.Logging; | |
using RoR2; | |
using RoR2.ConVar; | |
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Reflection; | |
using System.Text; | |
using UnityEngine; | |
using UnityEngine.AddressableAssets; | |
namespace RoR2AssetDump | |
{ | |
internal static class RoR2AssetDump | |
{ | |
// Relative to the main Risk of Rain 2 folder | |
private const string DUMP_FOLDER = @"prefabs"; | |
private static ManualLogSource Logger; | |
private static bool IsNetworkBaseType(Type type) | |
{ | |
return type.BaseType != null && type.BaseType == typeof(UnityEngine.Networking.NetworkBehaviour); | |
} | |
private static bool HasValidBaseType(Type type) | |
{ | |
return type.BaseType != null | |
&& type.BaseType != typeof(UnityEngine.Object) | |
&& type.BaseType != typeof(UnityEngine.Component); | |
} | |
private static List<FieldInfo> GetOrderedFields(Type type) | |
{ | |
var results = type.GetFields((BindingFlags)(-1)).ToList(); | |
if (IsNetworkBaseType(type)) | |
{ | |
// Skip NetworkBehaviour because it's mostly noise | |
results.InsertRange(0, GetOrderedFields(type.BaseType.BaseType)); | |
} | |
else if (HasValidBaseType(type)) | |
{ | |
results.InsertRange(0, GetOrderedFields(type.BaseType)); | |
} | |
return results; | |
} | |
private static List<PropertyInfo> GetOrderedProperties(Type type) | |
{ | |
var results = type.GetProperties((BindingFlags)(-1)).ToList(); | |
if (IsNetworkBaseType(type)) | |
{ | |
// Skip NetworkBehaviour because it's mostly noise | |
results.InsertRange(0, GetOrderedProperties(type.BaseType.BaseType)); | |
} | |
else if (HasValidBaseType(type)) | |
{ | |
results.InsertRange(0, GetOrderedProperties(type.BaseType)); | |
} | |
return results; | |
} | |
private static void OutputComponents(StringBuilder sb, Component[] components, string delimi) | |
{ | |
foreach (Component component in components) | |
{ | |
OutputUnityObject(sb, component, delimi); | |
} | |
} | |
private static void OutputUnityObject(StringBuilder sb, UnityEngine.Object unityObject, string delimi) | |
{ | |
string ParsePoolEntries(DccsPool.PoolEntry[] entries) | |
{ | |
return "[" + string.Join(", ", entries.Select(x => $"({x.dccs.name}, {x.weight})")) + "]"; | |
} | |
string ParseConditionalPoolEntries(DccsPool.ConditionalPoolEntry[] entries) | |
{ | |
return "[" + string.Join(", ", entries.Select(x => $"({x.dccs.name}, {x.weight}, [{string.Join(", ", x.requiredExpansions.Select(x => x.name))}])")) + "]"; | |
} | |
sb.Append($"\n{delimi} "); | |
if (unityObject is RectTransform rectTransform) | |
{ | |
var transformOutput = $"RectTransform = p: {rectTransform.localPosition} r: {rectTransform.eulerAngles} s: {rectTransform.localScale}"; | |
transformOutput += $"\n{delimi}v anchored3D: {rectTransform.anchoredPosition3D} pivot: {rectTransform.pivot} offsetMin: {rectTransform.offsetMin} offsetMax: {rectTransform.offsetMax}"; | |
sb.AppendLine(transformOutput); | |
} | |
else if (unityObject is Transform transform) | |
{ | |
var transformOutput = $"Transform = p: {transform.localPosition} r: {transform.eulerAngles} s: {transform.localScale}"; | |
sb.AppendLine(transformOutput); | |
} | |
else if (unityObject is EntityStateConfiguration esc) | |
{ | |
sb.AppendLine("EntityStateConfiguration: " + esc.targetType.LookupType().Name); | |
if (!esc.serializedFieldsCollection.serializedFields.Any()) | |
{ | |
sb.AppendLine($"{delimi} <Empty>"); | |
} | |
else | |
{ | |
foreach (var field in esc.serializedFieldsCollection.serializedFields) | |
{ | |
try | |
{ | |
var fieldOutput = $"{delimi}v {field.fieldName} = {(field.fieldValue.objectValue ? field.fieldValue.objectValue : field.fieldValue.stringValue)}"; | |
sb.AppendLine(fieldOutput); | |
} | |
catch (Exception e) | |
{ | |
Logger.LogError(e); | |
} | |
} | |
} | |
} | |
else | |
{ | |
Type type = unityObject.GetType(); | |
sb.AppendLine(type.FullName); | |
foreach (FieldInfo fieldInfo in GetOrderedFields(type)) | |
{ | |
if (fieldInfo.Name.Contains("k__BackingField")) | |
{ | |
continue; | |
} | |
try | |
{ | |
string fieldOutput; | |
if (unityObject is not RoR2.Navigation.NodeGraph && typeof(System.Collections.ICollection).IsAssignableFrom(fieldInfo.FieldType)) | |
{ //Nodegraphs are too meaninglessly large to expand,consider finding some method of visualisation if you wish to analyze them. | |
fieldOutput = $"{delimi}v {fieldInfo.Name} = "; | |
var collection = fieldInfo.GetValue(unityObject); | |
if (collection != null) | |
{ | |
fieldOutput = $"{delimi}v {fieldInfo.Name} = {{ "; | |
foreach (var value in collection as System.Collections.IEnumerable) | |
{ | |
if (value is DccsPool.Category dpCategory) | |
{ | |
fieldOutput += $"{{name: {dpCategory.name}, weight: {dpCategory.categoryWeight}, always: {ParsePoolEntries(dpCategory.alwaysIncluded)}, if: {ParseConditionalPoolEntries(dpCategory.includedIfConditionsMet)}, ifno: {ParsePoolEntries(dpCategory.includedIfNoConditionsMet)}}}"; | |
fieldOutput += "\n"; | |
} | |
else if (value is DirectorCardCategorySelection.Category category) | |
{ | |
fieldOutput += $"{{name: {category.name}, weight: {category.selectionWeight}, cards: [{string.Join(", ", category.cards.Select(card => $"({card.spawnCard.name}, {card.selectionWeight})"))}]}}"; | |
fieldOutput += "\n"; | |
} | |
else if (value is RangeFloat range) | |
{ | |
fieldOutput += $"{{min: {range.min}, max: {range.max}}}"; | |
fieldOutput += ", "; | |
} | |
else if (value is RoR2.Skills.SkillFamily.Variant variant) | |
{ | |
fieldOutput += $"{variant.skillDef}"; | |
fieldOutput += ", "; | |
} | |
else | |
{ | |
fieldOutput += value; | |
fieldOutput += ", "; | |
} | |
} | |
fieldOutput = fieldOutput.TrimEnd(' ', ',', '\n'); | |
fieldOutput += " }"; | |
} | |
} | |
else if (fieldInfo.FieldType == typeof(EntityStates.SerializableEntityStateType)) | |
{ | |
var stateType = (EntityStates.SerializableEntityStateType)fieldInfo.GetValue(unityObject); | |
fieldOutput = $"{delimi}v {fieldInfo.Name} = {stateType.stateType}"; | |
} | |
else if (fieldInfo.FieldType.IsSubclassOf(typeof(BaseConVar))) | |
{ | |
fieldOutput = $"{delimi}v {fieldInfo.Name} = {((BaseConVar)fieldInfo.GetValue(unityObject)).GetString()}"; | |
} | |
else | |
{ | |
fieldOutput = $"{delimi}v {fieldInfo.Name} = {fieldInfo.GetValue(unityObject)}"; | |
} | |
sb.AppendLine(fieldOutput); | |
} | |
catch (Exception e) | |
{ | |
Logger.LogError(e); | |
} | |
} | |
foreach (PropertyInfo propertyInfo in GetOrderedProperties(type)) | |
{ | |
if (propertyInfo.Name == "renderingDisplaySize" && unityObject is Canvas) | |
{ //Crash-preventation | |
continue; | |
} | |
try | |
{ | |
if (propertyInfo.GetAccessors() != null && propertyInfo.GetAccessors().Length > 0) | |
{ | |
var fieldOutput = $"{delimi}v {propertyInfo.Name} = {propertyInfo.GetValue(unityObject)}"; | |
sb.AppendLine(fieldOutput); | |
} | |
} | |
catch (Exception e) | |
{ | |
if (e.InnerException != null) | |
{ | |
if (e.InnerException is NullReferenceException || e.InnerException is ArgumentNullException) | |
{ | |
// Lots of properties internally refer to a field that is instantiated on Awake | |
sb.AppendLine($"{delimi}v {propertyInfo.Name} = "); | |
continue; | |
} | |
else if (e.InnerException is NotImplementedException) | |
{ | |
// Ignore the error for some ScriptableObjects' name property | |
continue; | |
} | |
} | |
Logger.LogError(e); | |
} | |
} | |
} | |
} | |
private static void GetChildren(StringBuilder sb, Transform transform, string delimi) | |
{ | |
for (var i = 0; i < transform.childCount; i++) | |
{ | |
GameObject gameObject = transform.GetChild(i).gameObject; | |
var childGameObjectHeader = $"\n{delimi}GameObject Child {i+1}/{transform.childCount}: {gameObject.name}"; | |
sb.AppendLine(childGameObjectHeader); | |
Component[] components = gameObject.GetComponents<Component>(); | |
OutputComponents(sb, components, delimi + ">"); | |
GetChildren(sb, transform.GetChild(i), delimi + ">"); | |
} | |
} | |
internal static void Execute(ManualLogSource logger) | |
{ | |
Logger = logger; | |
UnityEngine.Object assetlazy = Addressables.LoadAssetAsync<UnityEngine.Object>("RoR2/Base/Huntress/Skins.Huntress.Alt1.asset").WaitForCompletion(); | |
Logger.LogWarning(assetlazy); | |
var i = 0; | |
foreach (UnityEngine.AddressableAssets.ResourceLocators.IResourceLocator item in Addressables.ResourceLocators) | |
{ | |
foreach (var key in item.Keys) | |
{ | |
try | |
{ | |
i++; | |
if (key.ToString().Contains("bundle")) | |
{ | |
continue; | |
} | |
if (!key.ToString().Contains(".")) | |
{ | |
} | |
else | |
{ | |
var digitCount = 0; | |
foreach (var c in key.ToString()) | |
{ | |
if (char.IsDigit(c)) | |
{ | |
digitCount++; | |
if (digitCount > 5) | |
{ | |
break; | |
} | |
} | |
} | |
if (digitCount > 5) | |
{ | |
continue; | |
} | |
Logger.LogWarning(i + " | " + key.ToString()); | |
UnityEngine.Object asset = Addressables.LoadAssetAsync<UnityEngine.Object>(key).WaitForCompletion(); | |
if (asset) | |
{ | |
var sb = new StringBuilder(); | |
sb.AppendLine("Key Path : " + key.ToString() + " | UnityObject Type : " + asset.GetType() + " | UnityObject Name : " + asset.name); | |
if (asset is GameObject go) | |
{ | |
Component[] components = go.GetComponents<Component>(); | |
OutputComponents(sb, components, ">"); | |
GetChildren(sb, go.transform, ">"); | |
DumpToFile(key, sb); | |
} | |
else if (asset is UnityEngine.Object unityObject) | |
{ | |
OutputUnityObject(sb, unityObject, ">"); | |
DumpToFile(key, sb); | |
} | |
} | |
} | |
} | |
catch (Exception e) | |
{ | |
Logger.LogError(e); | |
} | |
} | |
} | |
} | |
private static void DumpToFile(object key, StringBuilder sb) | |
{ | |
var relativeFilePathTxt = key.ToString() + ".txt"; | |
var fullFilePath = System.IO.Path.Combine(DUMP_FOLDER, relativeFilePathTxt); | |
var directoryPath = System.IO.Path.GetDirectoryName(fullFilePath); | |
Directory.CreateDirectory(directoryPath); | |
File.WriteAllText(fullFilePath, sb.ToString()); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment