Decompiled source of ScallionMiku v1.0.5
plugins/ScallionMiku.dll
Decompiled a month ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using TMPro; using UnityEngine; using UnityEngine.Networking; using UnityEngine.Rendering; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: AssemblyCompany("ScallionMiku")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.3.0")] [assembly: AssemblyInformationalVersion("1.0.3")] [assembly: AssemblyProduct("ScallionMiku")] [assembly: AssemblyTitle("ScallionMiku")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.3.0")] [module: UnverifiableCode] namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] public sealed class IgnoresAccessChecksToAttribute : Attribute { private readonly string _assemblyName; public string AssemblyName => _assemblyName; public IgnoresAccessChecksToAttribute(string assemblyName) { _assemblyName = assemblyName; } } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] public sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte value) { NullableFlags = new byte[1] { value }; } public NullableAttribute(byte[] value) { NullableFlags = value; } } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] public sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte value) { Flag = value; } } [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] public sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int version) { Version = version; } } } namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace BepInEx { [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public sealed class BepInAutoPluginAttribute : Attribute { private string _guid; private string _name; private string _version; public string GUID { get { return _guid; } set { _guid = value; } } public string Name { get { return _name; } set { _name = value; } } public string Version { get { return _version; } set { _version = value; } } public BepInAutoPluginAttribute(string guid = null, string name = null, string version = null) { _guid = guid; _name = name; _version = version; } } } namespace BepInEx.Preloader.Core.Patching { [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public sealed class PatcherAutoPluginAttribute : Attribute { private string _guid; private string _name; private string _version; public string GUID { get { return _guid; } set { _guid = value; } } public string Name { get { return _name; } set { _name = value; } } public string Version { get { return _version; } set { _version = value; } } public PatcherAutoPluginAttribute(string guid = null, string name = null, string version = null) { _guid = guid; _name = name; _version = version; } } } namespace ScallionMiku { [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("com.github.Thanks.ScallionMiku", "ScallionMiku", "1.0.5")] public class Plugin : BaseUnityPlugin { private enum ConfigKey { ModEnabled, WorldScale, BackpackScale, PositionOffsetX, PositionOffsetY, PositionOffsetZ, FaceExpression } internal enum FaceExpressionOption { None, Smile, Blink, Wink, WinkRight, Wink2, Wink2Right, A, I, U, E, O, A2, MouthCornerUp, MouthCornerDown, Hau } private const float LiveConfigRefreshInterval = 0.03f; private const int SimplifiedChineseLanguageIndex = 9; private const string ModConfigPluginGuid = "com.github.PEAKModding.PEAKLib.ModConfig"; private const float ModConfigUiLocalizationIntervalSeconds = 0.25f; private const float LanguageFallbackCheckIntervalSeconds = 1f; private static readonly string[] LegacyPluginIds = new string[2] { "com.github.Thanks.MikuBongFix", "com.github.FelineEntity.MikuBongFix" }; private const string BundleFileName = "ScallionMiku"; private const string ExternalIconFileName = "ScallionMikuUI.png"; private const string PrefabAssetPath = "assets/葱音未来_by_翼月生/scallionmiku.prefab"; private const string MaterialAssetPath = "assets/葱音未来_by_翼月生/materials/scallionmiku-8.shintai.mat"; private const string IconTextureAssetPath = "assets/葱音未来_by_翼月生/textures/身体.png"; private const string MainTextureAssetPath = "assets/葱音未来_by_翼月生/textures/身体.png"; private const string ConfigSection = "Main"; private const string LocalizedConfigSection = "主要"; private const string WorldScaleConfigKey = "Fufu Size In World"; private const string BackpackModelScaleConfigKey = "Backpack Model Scale"; private const string LegacyFufuBackpackScaleConfigKey = "Fufu Size In Backpack"; private const string LegacyMikuBackpackScaleConfigKey = "Miku Size In Backpack"; internal const float WorldMinScaleMultiplier = 0.4f; internal const float WorldMaxScaleMultiplier = 1.1f; internal const float BackpackMinScaleMultiplier = 0.2f; internal const float BackpackMaxScaleMultiplier = 1f; internal const float PositionOffsetMin = -2f; internal const float PositionOffsetMax = 2f; internal const float DefaultWorldScaleMultiplier = 0.9f; internal const float DefaultBackpackScaleMultiplier = 0.4f; private const string ExpressionConfigKey = "Face Expression"; private const string LocalizedExpressionConfigKey = "表情"; private const string DefaultExpressionName = "None"; private const float DefaultExpressionWeight = 1f; private const float RuntimeScaleAdjustment = 0.78f; private const float BaseScaleMultiplier = 1.5f; private const float BundledBaseScaleMin = 0.04f; private const float BundledBaseScaleMax = 1.2f; internal const FaceExpressionOption DefaultExpressionOption = FaceExpressionOption.None; private static readonly Vector3 DefaultBundledReplacementBaseScale = new Vector3(0.52f, 0.52f, 0.52f); private static readonly Vector3 RuntimeReferenceVisibleSizeAtBaseScale = new Vector3(1.5852638f, 1.2168914f, 0.8999324f); private static readonly Vector3 DefaultPositionOffset = Vector3.zero; private static readonly string[] PreferredTextureProps = new string[2] { "_BaseMap", "_MainTex" }; private static readonly string[] PrefabNameHints = new string[3] { "scallionmiku", "miku", "scallion" }; private static readonly string[] MaterialNameHints = new string[3] { "scallionmiku-8.shintai", "shintai", "miku" }; private static readonly string[] IconTextureNameHints = new string[4] { "身体", "shintai", "miku", "scallionmiku" }; private static readonly string[] MainTextureNameHints = new string[5] { "身体", "shintai", "miku", "scallionmiku", "albedo" }; private static readonly Color MikuStyleTint = new Color(0.98f, 1f, 1f, 1f); private static ConfigEntry<bool> _modEnabled; private static ConfigEntry<float> _worldScaleMultiplier; private static ConfigEntry<float> _backpackScaleMultiplier; private static ConfigEntry<float> _positionOffsetX; private static ConfigEntry<float> _positionOffsetY; private static ConfigEntry<float> _positionOffsetZ; private static ConfigEntry<FaceExpressionOption> _expressionOption; private bool _liveConfigStateInitialized; private bool _lastObservedModEnabled; private float _lastObservedWorldScale; private float _lastObservedBackpackScale; private Vector3 _lastObservedPositionOffset; private string _lastObservedExpressionName; private bool _pendingLiveConfigRefresh; private float _nextLiveConfigRefreshTime; private bool _liveConfigEventsBound; private bool _lastDetectedChineseLanguage; private bool _lastNotifiedChineseLanguage; private Coroutine _languageChangeRoutine; private float _nextLanguageFallbackCheckTime; private bool _pendingConfigFileLocalizationRefresh; private bool _pendingConfigFileLocalizationSave; private float _nextModConfigUiLocalizationTime; private static readonly BindingFlags InstanceFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static readonly BindingFlags StaticFlags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private static ManualLogSource _log; private static AssetBundle _bundle; private static GameObject _mochiPrefab; private static Material _mochiMaterial; private static Material _runtimeMikuMaterial; private static Texture2D _mochiTexture; private static Texture2D _mikuMainTexture; private static Vector3 _bundledReplacementBaseScale = DefaultBundledReplacementBaseScale; private static RuntimeReplacementModel _runtimeReplacementModel; private static Dictionary<string, string> _cachedUiLocalizationMapChinese; private static Dictionary<string, string> _cachedUiLocalizationMapEnglish; public const string Name = "ScallionMiku"; public const string Id = "com.github.Thanks.ScallionMiku"; public const string Version = "1.0.5"; internal static Harmony Harmony = new Harmony("com.github.Thanks.ScallionMiku"); internal static ManualLogSource Log { get { return _log; } private set { _log = value; } } internal static AssetBundle Bundle { get { return _bundle; } private set { _bundle = value; } } internal static GameObject MochiPrefab { get { return _mochiPrefab; } private set { _mochiPrefab = value; } } internal static Material MochiMaterial { get { return _mochiMaterial; } private set { _mochiMaterial = value; } } internal static Material RuntimeMikuMaterial { get { return _runtimeMikuMaterial; } private set { _runtimeMikuMaterial = value; } } internal static Texture2D MochiTexture { get { return _mochiTexture; } private set { _mochiTexture = value; } } internal static Texture2D MikuMainTexture { get { return _mikuMainTexture; } private set { _mikuMainTexture = value; } } internal static RuntimeReplacementModel RuntimeReplacementModel { get { return _runtimeReplacementModel; } private set { _runtimeReplacementModel = value; } } internal static bool ModEnabled { get { if (_modEnabled != null) { return _modEnabled.Value; } return true; } } internal static bool KeepOriginalRendererRefs => RuntimeReplacementModel == null; internal static bool EnableVisibilityGuard => true; internal static float WorldScaleMultiplier { get { if (_worldScaleMultiplier != null) { return Mathf.Clamp(_worldScaleMultiplier.Value, 0.4f, 1.1f); } return 0.9f; } } internal static float DefaultWorldScaleSetting => 0.9f; internal static float BackpackScaleMultiplier { get { if (_backpackScaleMultiplier != null) { return Mathf.Clamp(_backpackScaleMultiplier.Value, 0.2f, 1f); } return 0.4f; } } internal static float DefaultBackpackScaleSetting => 0.4f; internal static Vector3 ReplacementBaseScale { get { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) if (RuntimeReplacementModel == null) { return _bundledReplacementBaseScale * 0.78f * 1.5f; } return RuntimeReplacementModel.RecommendedBaseScale * 0.78f * 1.5f; } } internal static Vector3 PositionOffset => new Vector3((_positionOffsetX == null) ? DefaultPositionOffset.x : Mathf.Clamp(_positionOffsetX.Value, -2f, 2f), (_positionOffsetY == null) ? DefaultPositionOffset.y : Mathf.Clamp(_positionOffsetY.Value, -2f, 2f), (_positionOffsetZ == null) ? DefaultPositionOffset.z : Mathf.Clamp(_positionOffsetZ.Value, -2f, 2f)); internal static string ExpressionName { get { string text = ((_expressionOption == null) ? "None" : GetExpressionName(_expressionOption.Value)); if (!string.IsNullOrEmpty(text)) { return text; } return "None"; } } internal static float ExpressionWeight => 1f; internal static string ReplacementDisplayName => "Miku"; internal static int ReplacementRefreshRevision { get; private set; } = 1; internal static string directory => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); private void Awake() { Log = ((BaseUnityPlugin)this).Logger; _lastDetectedChineseLanguage = DetectChineseLanguage(); MigrateLegacyConfigIfNeeded(); InitializeConfig(_lastDetectedChineseLanguage); NormalizeMigratedScaleConfigIfNeeded(); MarkConfigFileLocalizationDirty(saveConfigFile: true); LoadAssets(); try { Harmony.PatchAll(Assembly.GetExecutingAssembly()); } catch (Exception ex) { Log.LogError((object)("Failed to apply Harmony patches: " + ex)); } BindLiveConfigEvents(); CaptureLiveConfigState(); LocalizedText.OnLangugageChanged = (Action)Delegate.Combine(LocalizedText.OnLangugageChanged, new Action(HandleGameLanguageChanged)); Log.LogInfo((object)("[ScallionMiku] Config language: " + BuildLanguageDetectionSummary(_lastDetectedChineseLanguage))); } private void OnDestroy() { LocalizedText.OnLangugageChanged = (Action)Delegate.Remove(LocalizedText.OnLangugageChanged, new Action(HandleGameLanguageChanged)); if (_languageChangeRoutine != null) { ((MonoBehaviour)this).StopCoroutine(_languageChangeRoutine); _languageChangeRoutine = null; } } private void Update() { HandleLanguageChangeFallbackThrottled(); HandlePendingConfigFileLocalizationRefresh(); TryLocalizeVisibleModConfigUiThrottled(); if (HasLiveConfigStateChanged()) { CaptureLiveConfigState(); _pendingLiveConfigRefresh = true; } if (_pendingLiveConfigRefresh && !(Time.realtimeSinceStartup < _nextLiveConfigRefreshTime)) { RefreshReplacementInstancesNow(); } } private bool HasLiveConfigStateChanged() { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) if (!_liveConfigStateInitialized) { return true; } if (_lastObservedModEnabled != ModEnabled) { return true; } if (!Mathf.Approximately(_lastObservedWorldScale, WorldScaleMultiplier)) { return true; } if (!Mathf.Approximately(_lastObservedBackpackScale, BackpackScaleMultiplier)) { return true; } if (_lastObservedPositionOffset != PositionOffset) { return true; } return !string.Equals(_lastObservedExpressionName, ExpressionName, StringComparison.Ordinal); } private void CaptureLiveConfigState() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) _lastObservedModEnabled = ModEnabled; _lastObservedWorldScale = WorldScaleMultiplier; _lastObservedBackpackScale = BackpackScaleMultiplier; _lastObservedPositionOffset = PositionOffset; _lastObservedExpressionName = ExpressionName; _liveConfigStateInitialized = true; } private void BindLiveConfigEvents() { if (!_liveConfigEventsBound) { BindLiveConfigEvent<bool>(_modEnabled); BindLiveConfigEvent<float>(_worldScaleMultiplier); BindLiveConfigEvent<float>(_backpackScaleMultiplier); BindLiveConfigEvent<float>(_positionOffsetX); BindLiveConfigEvent<float>(_positionOffsetY); BindLiveConfigEvent<float>(_positionOffsetZ); BindLiveConfigEvent<FaceExpressionOption>(_expressionOption); _liveConfigEventsBound = true; } } private void BindLiveConfigEvent<T>(ConfigEntry<T> entry) { if (entry != null) { entry.SettingChanged -= OnLiveConfigSettingChanged; entry.SettingChanged += OnLiveConfigSettingChanged; } } private void OnLiveConfigSettingChanged(object sender, EventArgs args) { MarkConfigFileLocalizationDirty(saveConfigFile: false); CaptureLiveConfigState(); if (Time.realtimeSinceStartup >= _nextLiveConfigRefreshTime) { RefreshReplacementInstancesNow(); } else { _pendingLiveConfigRefresh = true; } } private void RefreshReplacementInstancesNow() { _pendingLiveConfigRefresh = false; _nextLiveConfigRefreshTime = Time.realtimeSinceStartup + 0.03f; ReplacementRefreshRevision = ((ReplacementRefreshRevision == int.MaxValue) ? 1 : (ReplacementRefreshRevision + 1)); Patch_AskRoutine.HandleModConfigStateChanged(); ItemPatch.RefreshAllReplacementInstances(); } private void MigrateLegacyConfigIfNeeded() { try { string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath; if (string.IsNullOrEmpty(configFilePath) || File.Exists(configFilePath) || LegacyPluginIds == null) { return; } for (int i = 0; i < LegacyPluginIds.Length; i++) { string text = LegacyPluginIds[i]; if (!string.IsNullOrEmpty(text)) { string text2 = Path.Combine(Paths.ConfigPath, text + ".cfg"); if (File.Exists(text2)) { File.Copy(text2, configFilePath, overwrite: false); ((BaseUnityPlugin)this).Config.Reload(); VerboseLog("Migrated legacy config to: " + configFilePath); break; } } } } catch (Exception ex) { Log.LogWarning((object)("Failed to migrate legacy config file: " + ex.Message)); } } private void InitializeConfig(bool isChineseLanguage) { RemoveConfigVersionSettingIfNeeded(); NormalizeLegacyExpressionConfigIfNeeded(); _modEnabled = BindEntry(ConfigKey.ModEnabled, defaultValue: true, isChineseLanguage); _worldScaleMultiplier = BindEntry(ConfigKey.WorldScale, 0.9f, isChineseLanguage); _backpackScaleMultiplier = BindEntry(ConfigKey.BackpackScale, 0.4f, isChineseLanguage); _positionOffsetX = BindEntry(ConfigKey.PositionOffsetX, DefaultPositionOffset.x, isChineseLanguage); _positionOffsetY = BindEntry(ConfigKey.PositionOffsetY, DefaultPositionOffset.y, isChineseLanguage); _positionOffsetZ = BindEntry(ConfigKey.PositionOffsetZ, DefaultPositionOffset.z, isChineseLanguage); _expressionOption = BindEntry(ConfigKey.FaceExpression, FaceExpressionOption.None, isChineseLanguage); MigrateLocalizedConfigEntries(); } private ConfigEntry<T> BindEntry<T>(ConfigKey configKey, T defaultValue, bool isChineseLanguage) { return ((BaseUnityPlugin)this).Config.Bind<T>(GetSectionName(configKey, isChineseLanguage), GetKeyName(configKey, isChineseLanguage), defaultValue, CreateConfigDescription(configKey, isChineseLanguage)); } private ConfigDescription CreateConfigDescription(ConfigKey configKey, bool isChineseLanguage) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Expected O, but got Unknown //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown string localizedDescription = GetLocalizedDescription(configKey, isChineseLanguage); switch (configKey) { case ConfigKey.WorldScale: return new ConfigDescription(localizedDescription, (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.4f, 1.1f), Array.Empty<object>()); case ConfigKey.BackpackScale: return new ConfigDescription(localizedDescription, (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.2f, 1f), Array.Empty<object>()); case ConfigKey.PositionOffsetX: case ConfigKey.PositionOffsetY: case ConfigKey.PositionOffsetZ: return new ConfigDescription(localizedDescription, (AcceptableValueBase)(object)new AcceptableValueRange<float>(-2f, 2f), Array.Empty<object>()); default: return new ConfigDescription(localizedDescription, (AcceptableValueBase)null, Array.Empty<object>()); } } private void RemoveConfigVersionSettingIfNeeded() { try { string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath; if (string.IsNullOrEmpty(configFilePath) || !File.Exists(configFilePath)) { return; } string[] array = File.ReadAllLines(configFilePath); List<string> list = new List<string>(array.Length); bool flag = false; bool flag2 = false; for (int i = 0; i < array.Length; i++) { string text = array[i] ?? string.Empty; string text2 = text.Trim(); if (text2.StartsWith("[", StringComparison.Ordinal) && text2.EndsWith("]", StringComparison.Ordinal)) { flag2 = string.Equals(text2.Substring(1, text2.Length - 2).Trim(), "Internal", StringComparison.OrdinalIgnoreCase); if (flag2) { flag = true; continue; } } if (flag2) { flag = true; } else { list.Add(text); } } if (flag) { File.WriteAllLines(configFilePath, list.ToArray()); ((BaseUnityPlugin)this).Config.Reload(); VerboseLog("Removed obsolete Internal/Config Version config entry."); } } catch (Exception ex) { Log.LogWarning((object)("Failed to remove obsolete Config Version entry: " + ex.Message)); } } private void NormalizeLegacyExpressionConfigIfNeeded() { try { string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath; if (string.IsNullOrEmpty(configFilePath) || !File.Exists(configFilePath)) { return; } string[] array = File.ReadAllLines(configFilePath); bool flag = false; for (int i = 0; i < array.Length; i++) { if (TrySplitConfigSettingLine(array[i], out var leading, out var keyName, out var separatorAndValue) && IsConfigKeyName(keyName, ConfigKey.FaceExpression)) { int num = separatorAndValue.IndexOf('='); string obj = ((num >= 0) ? separatorAndValue.Substring(num + 1).Trim() : string.Empty); if (!TryParseExpressionOption(obj, out var option)) { option = FaceExpressionOption.None; } string text = option.ToString(); if (!string.Equals(obj, text, StringComparison.Ordinal)) { string keyName2 = GetKeyName(ConfigKey.FaceExpression, _lastDetectedChineseLanguage); array[i] = leading + keyName2 + " = " + text; flag = true; } } } if (flag) { File.WriteAllLines(configFilePath, array); ((BaseUnityPlugin)this).Config.Reload(); VerboseLog("Migrated Face Expression config from PMX morph text to enum value for PEAKLib.ModConfig compatibility."); } } catch (Exception ex) { Log.LogWarning((object)("Failed to normalize Face Expression config value: " + ex.Message)); } } private static bool TryParseExpressionOption(string rawValue, out FaceExpressionOption option) { option = FaceExpressionOption.None; if (string.IsNullOrEmpty(rawValue)) { return false; } string text = rawValue.Trim().Trim('"'); if (Enum.TryParse<FaceExpressionOption>(text, ignoreCase: true, out option) && Enum.IsDefined(typeof(FaceExpressionOption), option)) { return true; } for (int i = 0; i <= 15; i++) { FaceExpressionOption faceExpressionOption = (FaceExpressionOption)i; if (string.Equals(text, GetExpressionName(faceExpressionOption), StringComparison.Ordinal)) { option = faceExpressionOption; return true; } } return false; } private static string GetExpressionName(FaceExpressionOption option) { return option switch { FaceExpressionOption.Smile => "笑い", FaceExpressionOption.Blink => "まばたき", FaceExpressionOption.Wink => "ウィンク", FaceExpressionOption.WinkRight => "ウィンク右", FaceExpressionOption.Wink2 => "ウィンク2", FaceExpressionOption.Wink2Right => "ウィンク2右", FaceExpressionOption.A => "あ", FaceExpressionOption.I => "い", FaceExpressionOption.U => "う", FaceExpressionOption.E => "え", FaceExpressionOption.O => "お", FaceExpressionOption.A2 => "あ2", FaceExpressionOption.MouthCornerUp => "口角上げ", FaceExpressionOption.MouthCornerDown => "口角下げ", FaceExpressionOption.Hau => "はぅ", _ => "None", }; } private void MarkConfigFileLocalizationDirty(bool saveConfigFile) { _pendingConfigFileLocalizationRefresh = true; _pendingConfigFileLocalizationSave |= saveConfigFile; } private void HandlePendingConfigFileLocalizationRefresh() { if (_pendingConfigFileLocalizationRefresh) { bool pendingConfigFileLocalizationSave = _pendingConfigFileLocalizationSave; _pendingConfigFileLocalizationRefresh = false; _pendingConfigFileLocalizationSave = false; TryRefreshLocalizedConfigFile(_lastDetectedChineseLanguage, pendingConfigFileLocalizationSave); } } private void HandleGameLanguageChanged() { if (((Behaviour)this).isActiveAndEnabled) { if (_languageChangeRoutine != null) { ((MonoBehaviour)this).StopCoroutine(_languageChangeRoutine); } _languageChangeRoutine = ((MonoBehaviour)this).StartCoroutine(RefreshLanguageAfterChangeRoutine()); } } private IEnumerator RefreshLanguageAfterChangeRoutine() { yield return null; _languageChangeRoutine = null; HandleLanguageChangeIfNeeded(); } private void HandleLanguageChangeFallbackThrottled() { if (!(Time.unscaledTime < _nextLanguageFallbackCheckTime)) { _nextLanguageFallbackCheckTime = Time.unscaledTime + 1f; HandleLanguageChangeIfNeeded(); } } private void HandleLanguageChangeIfNeeded() { bool flag = DetectChineseLanguage(); if (flag != _lastNotifiedChineseLanguage) { _lastNotifiedChineseLanguage = flag; Log.LogWarning((object)("[ScallionMiku] Game language changed to " + (flag ? "Chinese" : "English") + ". The mod's config language only switches on startup — please restart the game to apply it.")); } } private void TryRefreshLocalizedConfigFile(bool isChineseLanguage, bool saveConfigFile) { try { if (((BaseUnityPlugin)this).Config != null && !string.IsNullOrWhiteSpace(((BaseUnityPlugin)this).Config.ConfigFilePath)) { if (saveConfigFile) { ((BaseUnityPlugin)this).Config.Save(); } RewriteConfigFileLocalization(((BaseUnityPlugin)this).Config.ConfigFilePath, isChineseLanguage); } } catch (Exception ex) { Log.LogWarning((object)("[ScallionMiku] Failed to refresh localized config file: " + ex.Message)); } } private static void RewriteConfigFileLocalization(string configFilePath, bool isChineseLanguage) { if (string.IsNullOrWhiteSpace(configFilePath) || !File.Exists(configFilePath)) { return; } string[] array = File.ReadAllLines(configFilePath); Dictionary<ConfigKey, string> dictionary = new Dictionary<ConfigKey, string>(); for (int i = 0; i < array.Length; i++) { if (TrySplitConfigSettingLine(array[i] ?? string.Empty, out var _, out var keyName, out var separatorAndValue) && TryGetConfigKey(keyName, out var configKey)) { dictionary[configKey] = separatorAndValue; } } List<string> list = new List<string>(array.Length); HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); HashSet<ConfigKey> hashSet2 = new HashSet<ConfigKey>(); bool flag = false; for (int j = 0; j < array.Length; j++) { string text = array[j] ?? string.Empty; string text2 = text.Trim(); string leading2; string keyName2; string separatorAndValue2; ConfigKey configKey2; if (text2.StartsWith("[", StringComparison.Ordinal) && text2.EndsWith("]", StringComparison.Ordinal)) { if (TryGetLocalizedSectionName(text2.Substring(1, text2.Length - 2).Trim(), isChineseLanguage, out var localizedSectionName)) { if (!hashSet.Add(localizedSectionName)) { flag = true; continue; } string text3 = "[" + localizedSectionName + "]"; flag |= !string.Equals(text, text3, StringComparison.Ordinal); list.Add(text3); } else { list.Add(text); } } else if (TrySplitConfigSettingLine(text, out leading2, out keyName2, out separatorAndValue2) && TryGetConfigKey(keyName2, out configKey2)) { if (!hashSet2.Add(configKey2)) { flag = true; continue; } string text4 = leading2 + GetKeyName(configKey2, isChineseLanguage) + dictionary[configKey2]; flag |= !string.Equals(text, text4, StringComparison.Ordinal); list.Add(text4); } else { list.Add(text); } } if (flag) { File.WriteAllLines(configFilePath, list.ToArray()); VerboseLog("[ScallionMiku] Deduplicated and rewrote localized config file: " + configFilePath); } } private static bool TrySplitConfigSettingLine(string line, out string leading, out string keyName, out string separatorAndValue) { leading = string.Empty; keyName = string.Empty; separatorAndValue = string.Empty; if (string.IsNullOrWhiteSpace(line)) { return false; } string text = line.TrimStart(); if (text.StartsWith("#", StringComparison.Ordinal) || text.StartsWith(";", StringComparison.Ordinal)) { return false; } int num = line.IndexOf('='); if (num <= 0) { return false; } int i; for (i = 0; i < num && char.IsWhiteSpace(line[i]); i++) { } int num2 = num - 1; while (num2 >= i && char.IsWhiteSpace(line[num2])) { num2--; } if (num2 < i) { return false; } leading = line.Substring(0, i); keyName = line.Substring(i, num2 - i + 1); separatorAndValue = line.Substring(num2 + 1); return true; } private static bool TryGetLocalizedSectionName(string sectionName, bool isChineseLanguage, out string localizedSectionName) { if (string.Equals(sectionName, "Main", StringComparison.OrdinalIgnoreCase) || string.Equals(sectionName, "主要", StringComparison.Ordinal)) { localizedSectionName = "Main"; return true; } localizedSectionName = string.Empty; return false; } private void MigrateLocalizedConfigEntries() { IDictionary orphanedEntries = GetOrphanedEntries(((BaseUnityPlugin)this).Config); if (orphanedEntries != null && orphanedEntries.Count != 0 && (0u | (TryMigrateLocalizedConfigValue((ConfigEntryBase)(object)_modEnabled, ConfigKey.ModEnabled, orphanedEntries) ? 1u : 0u) | (TryMigrateLocalizedConfigValue((ConfigEntryBase)(object)_worldScaleMultiplier, ConfigKey.WorldScale, orphanedEntries) ? 1u : 0u) | (TryMigrateLocalizedConfigValue((ConfigEntryBase)(object)_backpackScaleMultiplier, ConfigKey.BackpackScale, orphanedEntries) ? 1u : 0u) | (TryMigrateLocalizedConfigValue((ConfigEntryBase)(object)_positionOffsetX, ConfigKey.PositionOffsetX, orphanedEntries) ? 1u : 0u) | (TryMigrateLocalizedConfigValue((ConfigEntryBase)(object)_positionOffsetY, ConfigKey.PositionOffsetY, orphanedEntries) ? 1u : 0u) | (TryMigrateLocalizedConfigValue((ConfigEntryBase)(object)_positionOffsetZ, ConfigKey.PositionOffsetZ, orphanedEntries) ? 1u : 0u) | (TryMigrateLocalizedConfigValue((ConfigEntryBase)(object)_expressionOption, ConfigKey.FaceExpression, orphanedEntries) ? 1u : 0u)) != 0) { ((BaseUnityPlugin)this).Config.Save(); } } private static bool TryMigrateLocalizedConfigValue(ConfigEntryBase entry, ConfigKey configKey, IDictionary orphanedEntries) { if (entry == null || entry.Definition == (ConfigDefinition)null || orphanedEntries == null) { return false; } bool flag = false; foreach (ConfigDefinition aliasDefinition in GetAliasDefinitions(configKey)) { if (DefinitionsEqual(aliasDefinition, entry.Definition) || !orphanedEntries.Contains(aliasDefinition)) { continue; } if (!flag) { object obj = orphanedEntries[aliasDefinition]; if (obj != null) { entry.SetSerializedValue(NormalizeSerializedConfigValue(configKey, obj.ToString())); } flag = true; } orphanedEntries.Remove(aliasDefinition); } return flag; } private static string NormalizeSerializedConfigValue(ConfigKey configKey, string serializedValue) { if (configKey == ConfigKey.FaceExpression && TryParseExpressionOption(serializedValue, out var option)) { return option.ToString(); } return serializedValue; } private static IEnumerable<ConfigDefinition> GetAliasDefinitions(ConfigKey configKey) { string canonicalKey = GetConfigKeyName(configKey); string localizedKey = GetKeyName(configKey, isChineseLanguage: true); yield return new ConfigDefinition("Main", canonicalKey); yield return new ConfigDefinition("Main", localizedKey); yield return new ConfigDefinition("主要", canonicalKey); yield return new ConfigDefinition("主要", localizedKey); } private static IDictionary GetOrphanedEntries(ConfigFile configFile) { return ((object)configFile)?.GetType().GetProperty("OrphanedEntries", InstanceFlags)?.GetValue(configFile) as IDictionary; } private static bool DefinitionsEqual(ConfigDefinition left, ConfigDefinition right) { if (string.Equals((left != null) ? left.Section : null, (right != null) ? right.Section : null, StringComparison.Ordinal)) { return string.Equals((left != null) ? left.Key : null, (right != null) ? right.Key : null, StringComparison.Ordinal); } return false; } private static bool DetectChineseLanguage() { bool isChineseLanguage; bool num = TryGetConfiguredGameLanguage(out isChineseLanguage); string languageName; bool flag = TryGetLocalizedTextLanguageName(out languageName); if (num && isChineseLanguage) { return true; } if (flag && IsChineseLanguageName(languageName)) { return true; } return false; } private static string BuildLanguageDetectionSummary(bool isChineseLanguage) { bool isChineseLanguage2; string languageValueText; string text = (TryGetConfiguredGameLanguage(out isChineseLanguage2, out languageValueText) ? (languageValueText + "/" + (isChineseLanguage2 ? "Chinese" : "English")) : "unknown"); string languageName; string text2 = (TryGetLocalizedTextLanguageName(out languageName) ? languageName : "unknown"); return (isChineseLanguage ? "Chinese" : "English") + " (prefs=" + text + ", runtime=" + text2 + ")"; } private static bool TryGetConfiguredGameLanguage(out bool isChineseLanguage) { string languageValueText; return TryGetConfiguredGameLanguage(out isChineseLanguage, out languageValueText); } private static bool TryGetConfiguredGameLanguage(out bool isChineseLanguage, out string languageValueText) { isChineseLanguage = false; languageValueText = string.Empty; try { if (!PlayerPrefs.HasKey("LanguageSetting")) { return false; } int result = PlayerPrefs.GetInt("LanguageSetting", int.MinValue); if (result != int.MinValue) { languageValueText = result.ToString(CultureInfo.InvariantCulture); isChineseLanguage = IsChineseLanguageIndex(result); return true; } string text = (languageValueText = PlayerPrefs.GetString("LanguageSetting", string.Empty)); if (string.IsNullOrWhiteSpace(text)) { return false; } if (int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out result)) { isChineseLanguage = IsChineseLanguageIndex(result); return true; } isChineseLanguage = IsChineseLanguageName(text); return true; } catch { return false; } } private static bool TryGetLocalizedTextLanguageName(out string languageName) { languageName = string.Empty; try { languageName = ((object)Unsafe.As<Language, Language>(ref LocalizedText.CURRENT_LANGUAGE)/*cast due to .constrained prefix*/).ToString(); return !string.IsNullOrWhiteSpace(languageName); } catch { return false; } } private static bool IsChineseLanguageName(string languageName) { if (string.IsNullOrWhiteSpace(languageName)) { return false; } if (int.TryParse(languageName.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return IsChineseLanguageIndex(result); } if (languageName.IndexOf("Chinese", StringComparison.OrdinalIgnoreCase) < 0 && languageName.IndexOf("中文", StringComparison.OrdinalIgnoreCase) < 0) { return languageName.StartsWith("zh", StringComparison.OrdinalIgnoreCase); } return true; } private static bool IsChineseLanguageIndex(int languageValue) { if (languageValue != 9) { return languageValue == 10; } return true; } private static string GetSectionName(ConfigKey configKey, bool isChineseLanguage) { return "Main"; } private static string GetConfigKeyName(ConfigKey configKey) { return GetKeyName(configKey, isChineseLanguage: false); } private static string GetKeyName(ConfigKey configKey, bool isChineseLanguage) { if (isChineseLanguage) { switch (configKey) { case ConfigKey.ModEnabled: return "启用葱音未来替换"; case ConfigKey.WorldScale: return "世界模型大小"; case ConfigKey.BackpackScale: return "背包模型大小"; case ConfigKey.PositionOffsetX: return "模型位置偏移 X"; case ConfigKey.PositionOffsetY: return "模型位置偏移 Y"; case ConfigKey.PositionOffsetZ: return "模型位置偏移 Z"; case ConfigKey.FaceExpression: return "表情"; } } return configKey switch { ConfigKey.ModEnabled => "Enable Fufu Replacement", ConfigKey.WorldScale => "Fufu Size In World", ConfigKey.BackpackScale => "Backpack Model Scale", ConfigKey.PositionOffsetX => "Model Position Offset X", ConfigKey.PositionOffsetY => "Model Position Offset Y", ConfigKey.PositionOffsetZ => "Model Position Offset Z", ConfigKey.FaceExpression => "Face Expression", _ => string.Empty, }; } private static string GetLocalizedDescription(ConfigKey configKey, bool isChineseLanguage) { if (isChineseLanguage) { switch (configKey) { case ConfigKey.ModEnabled: return "总开关。关闭后会恢复原版 BingBong 的外观、名称、图标和碰撞体。"; case ConfigKey.WorldScale: return "葱音未来模型在手持或放在世界中时的大小倍率。"; case ConfigKey.BackpackScale: return "葱音未来模型显示在背包中时的大小倍率。"; case ConfigKey.PositionOffsetX: return "运行时模型在 X 轴上的额外本地位置偏移。"; case ConfigKey.PositionOffsetY: return "运行时模型在 Y 轴上的额外本地位置偏移。"; case ConfigKey.PositionOffsetZ: return "运行时模型在 Z 轴上的额外本地位置偏移。"; case ConfigKey.FaceExpression: return "基础 PMX 表情。枚举会在运行时映射回实际 morph 名称,例如 Smile=笑い,Blink=まばたき,Wink=ウィンク。"; } } return configKey switch { ConfigKey.ModEnabled => "Master switch for the mod. When disabled, the original BingBong visuals, name, icon, and colliders are restored.", ConfigKey.WorldScale => "Scale multiplier for Fufu while held or lying in the world.", ConfigKey.BackpackScale => "Scale multiplier for the replacement model while the item is shown in the backpack.", ConfigKey.PositionOffsetX => "Extra local position offset on the X axis for the runtime model.", ConfigKey.PositionOffsetY => "Extra local position offset on the Y axis for the runtime model.", ConfigKey.PositionOffsetZ => "Extra local position offset on the Z axis for the runtime model.", ConfigKey.FaceExpression => "Base PMX facial expression. Mapping: Smile=笑い, Blink=まばたき, Wink=ウィンク, WinkRight=ウィンク右, Wink2=ウィンク2, Wink2Right=ウィンク2右, A=あ, I=い, U=う, E=え, O=お, A2=あ2, MouthCornerUp=口角上げ, MouthCornerDown=口角下げ, Hau=はぅ.", _ => string.Empty, }; } private static bool TryGetConfigKey(string keyName, out ConfigKey configKey) { foreach (ConfigKey value in Enum.GetValues(typeof(ConfigKey))) { if (IsConfigKeyName(keyName, value)) { configKey = value; return true; } } configKey = ConfigKey.ModEnabled; return false; } private static bool IsConfigKeyName(string keyName, ConfigKey configKey) { if (!string.Equals(keyName, GetConfigKeyName(configKey), StringComparison.OrdinalIgnoreCase)) { return string.Equals(keyName, GetKeyName(configKey, isChineseLanguage: true), StringComparison.Ordinal); } return true; } private void TryLocalizeVisibleModConfigUiThrottled() { if (!(Time.unscaledTime < _nextModConfigUiLocalizationTime)) { _nextModConfigUiLocalizationTime = Time.unscaledTime + 0.25f; TryLocalizeVisibleModConfigUi(); } } private void TryLocalizeVisibleModConfigUi() { if (!TryGetModConfigMenuInstance(out var menuType, out var menuInstance)) { return; } Behaviour val = (Behaviour)((menuInstance is Behaviour) ? menuInstance : null); if (val == null || (Object)(object)val == (Object)null) { return; } try { if (!val.isActiveAndEnabled || !((Component)val).gameObject.activeInHierarchy) { return; } } catch { return; } Dictionary<string, string> map = BuildModConfigUiLocalizationMap(_lastDetectedChineseLanguage); foreach (Transform item in EnumerateModConfigUiRoots(menuInstance, menuType)) { ApplyTextLocalizationToRoot(item, map); } ApplyTextLocalizationToLoadedUi(map); } private static bool TryGetModConfigMenuInstance(out Type menuType, out object menuInstance) { menuType = null; menuInstance = null; if (!Chainloader.PluginInfos.TryGetValue("com.github.PEAKModding.PEAKLib.ModConfig", out var value) || value == null || (Object)(object)value.Instance == (Object)null) { return false; } Assembly assembly = ((object)value.Instance).GetType().Assembly; menuType = assembly.GetType("PEAKLib.ModConfig.Components.ModdedSettingsMenu"); menuInstance = menuType?.GetProperty("Instance", StaticFlags)?.GetValue(null); if (menuType != null) { return menuInstance != null; } return false; } private static IEnumerable<Transform> EnumerateModConfigUiRoots(object menuInstance, Type menuType) { HashSet<int> visited = new HashSet<int>(); foreach (Transform item in EnumerateCandidateTransforms(menuInstance, menuType)) { if ((Object)(object)item != (Object)null && visited.Add(((Object)item).GetInstanceID())) { yield return item; } } } private static IEnumerable<Transform> EnumerateCandidateTransforms(object menuInstance, Type menuType) { Component val = (Component)((menuInstance is Component) ? menuInstance : null); if (val != null) { yield return val.transform; } object? obj = menuType?.GetProperty("Content", InstanceFlags)?.GetValue(menuInstance); Transform val2 = (Transform)((obj is Transform) ? obj : null); if (val2 != null) { yield return val2; } } private static void ApplyTextLocalizationToRoot(Transform root, Dictionary<string, string> map) { if (!((Object)(object)root == (Object)null) && map != null && map.Count != 0) { TMP_Text[] componentsInChildren = ((Component)root).GetComponentsInChildren<TMP_Text>(true); for (int i = 0; i < componentsInChildren.Length; i++) { ApplyTextLocalization(componentsInChildren[i], map); } } } private static void ApplyTextLocalizationToLoadedUi(Dictionary<string, string> map) { if (map == null || map.Count == 0) { return; } try { TMP_Text[] array = Object.FindObjectsByType<TMP_Text>((FindObjectsInactive)1, (FindObjectsSortMode)0); foreach (TMP_Text val in array) { if (!((Object)(object)val == (Object)null) && !((Object)(object)((Component)val).gameObject == (Object)null)) { ApplyTextLocalization(val, map); } } } catch { } } private static void ApplyTextLocalization(TMP_Text text, Dictionary<string, string> map) { if (!((Object)(object)text == (Object)null) && map != null && map.Count != 0) { string text2 = ((text.text == null) ? string.Empty : text.text.Trim()); if (!string.IsNullOrWhiteSpace(text2) && map.TryGetValue(text2, out var value) && !string.Equals(text2, value, StringComparison.Ordinal)) { text.text = value; } } } private static Dictionary<string, string> BuildModConfigUiLocalizationMap(bool isChineseLanguage) { if (isChineseLanguage) { return _cachedUiLocalizationMapChinese ?? (_cachedUiLocalizationMapChinese = BuildUiLocalizationMapUncached(isChineseLanguage: true)); } return _cachedUiLocalizationMapEnglish ?? (_cachedUiLocalizationMapEnglish = BuildUiLocalizationMapUncached(isChineseLanguage: false)); } private static Dictionary<string, string> BuildUiLocalizationMapUncached(bool isChineseLanguage) { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal); AddUiLocalizationPair(dictionary, "ScallionMiku", GetLocalizedModDisplayName(isChineseLanguage)); AddUiLocalizationPair(dictionary, "ScallionMiku", GetLocalizedModDisplayName(isChineseLanguage)); AddUiLocalizationPair(dictionary, "com.github.Thanks.ScallionMiku", GetLocalizedModDisplayName(isChineseLanguage)); string localized = (isChineseLanguage ? "主要" : "Main"); AddUiLocalizationPair(dictionary, "Main", localized); AddUiLocalizationPair(dictionary, "主要", localized); foreach (ConfigKey value in Enum.GetValues(typeof(ConfigKey))) { string keyName = GetKeyName(value, isChineseLanguage); AddUiLocalizationPair(dictionary, "Main", localized); AddUiLocalizationPair(dictionary, GetConfigKeyName(value), keyName); AddUiLocalizationPair(dictionary, ToReadableName(GetConfigKeyName(value)), keyName); AddUiLocalizationPair(dictionary, GetKeyName(value, isChineseLanguage: true), keyName); AddUiLocalizationPair(dictionary, GetLocalizedDescription(value, isChineseLanguage: false), GetLocalizedDescription(value, isChineseLanguage)); AddUiLocalizationPair(dictionary, GetLocalizedDescription(value, isChineseLanguage: true), GetLocalizedDescription(value, isChineseLanguage)); } AddExpressionOptionUiLocalization(dictionary, isChineseLanguage); return dictionary; } internal static string GetExpressionOptionLabel(FaceExpressionOption option, bool isChineseLanguage) { if (!isChineseLanguage) { return option.ToString(); } return option switch { FaceExpressionOption.None => "无", FaceExpressionOption.Smile => "笑い", FaceExpressionOption.Blink => "眨眼", FaceExpressionOption.Wink => "眨左眼", FaceExpressionOption.WinkRight => "眨右眼", FaceExpressionOption.Wink2 => "眨左眼 2", FaceExpressionOption.Wink2Right => "眨右眼 2", FaceExpressionOption.A => "口型 あ", FaceExpressionOption.I => "口型 い", FaceExpressionOption.U => "口型 う", FaceExpressionOption.E => "口型 え", FaceExpressionOption.O => "口型 お", FaceExpressionOption.A2 => "口型 あ2", FaceExpressionOption.MouthCornerUp => "嘴角上扬", FaceExpressionOption.MouthCornerDown => "嘴角下垂", FaceExpressionOption.Hau => "はぅ", _ => option.ToString(), }; } private static void AddExpressionOptionUiLocalization(Dictionary<string, string> map, bool isChineseLanguage) { for (int i = 0; i <= 15; i++) { FaceExpressionOption option = (FaceExpressionOption)i; AddExpressionOptionUiLocalizationPair(map, option, isChineseLanguage, GetExpressionOptionLabel(option, isChineseLanguage: true)); } } private static void AddExpressionOptionUiLocalizationPair(Dictionary<string, string> map, FaceExpressionOption option, bool isChineseLanguage, string chineseLabel) { string text = option.ToString(); if (isChineseLanguage) { AddUiLocalizationPair(map, text, chineseLabel); } else { AddUiLocalizationPair(map, chineseLabel, text); } } private static void AddUiLocalizationPair(Dictionary<string, string> map, string source, string localized) { if (map != null && !string.IsNullOrWhiteSpace(source) && !string.IsNullOrWhiteSpace(localized)) { string text = source.Trim(); string text2 = (map[text] = localized.Trim()); map[text2] = text2; string key = text.Replace(" ", string.Empty); string key2 = text2.Replace(" ", string.Empty); if (!map.ContainsKey(key)) { map[key] = text2; } if (!map.ContainsKey(key2)) { map[key2] = text2; } map[text.ToUpperInvariant()] = text2; map[text2.ToUpperInvariant()] = text2; } } private static string ToReadableName(string keyName) { if (string.IsNullOrWhiteSpace(keyName)) { return string.Empty; } List<char> list = new List<char>(keyName.Length + 8); for (int i = 0; i < keyName.Length; i++) { char c = keyName[i]; if (i > 0 && char.IsUpper(c) && !char.IsWhiteSpace(keyName[i - 1]) && !char.IsUpper(keyName[i - 1])) { list.Add(' '); } list.Add(c); } return new string(list.ToArray()); } private static string GetLocalizedModDisplayName(bool isChineseLanguage) { if (!isChineseLanguage) { return "ScallionMiku"; } return "葱音未来"; } private void NormalizeMigratedScaleConfigIfNeeded() { try { string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath; if (!string.IsNullOrEmpty(configFilePath) && File.Exists(configFilePath) && (FileContainsSetting(configFilePath, "Miku Size In World") || FileContainsSetting(configFilePath, "Fufu Size In Backpack") || FileContainsSetting(configFilePath, "Miku Size In Backpack") || FileContainsSetting(configFilePath, "Enable Miku Replacement"))) { bool flag = false; if (TryReadFloatSetting(configFilePath, "Miku Size In World", out var value) && _worldScaleMultiplier != null && _worldScaleMultiplier.Value <= 0.4001f) { _worldScaleMultiplier.Value = Mathf.Clamp(value, 0.4f, 1.1f); flag = true; } if (!FileContainsSetting(configFilePath, "Backpack Model Scale") && TryReadFirstFloatSetting(configFilePath, out var value2, "Fufu Size In Backpack", "Miku Size In Backpack") && _backpackScaleMultiplier != null) { _backpackScaleMultiplier.Value = Mathf.Clamp(value2, 0.2f, 1f); flag = true; } if (flag) { ((BaseUnityPlugin)this).Config.Save(); VerboseLog("Normalized migrated ScallionMiku scale settings for current runtime model."); } } } catch (Exception ex) { Log.LogWarning((object)("Failed to normalize migrated config values: " + ex.Message)); } } private static bool FileContainsSetting(string configPath, string key) { if (string.IsNullOrEmpty(configPath) || string.IsNullOrEmpty(key) || !File.Exists(configPath)) { return false; } string[] array = File.ReadAllLines(configPath); foreach (string text in array) { if (text != null && text.StartsWith(key + " =", StringComparison.Ordinal)) { return true; } } return false; } private static bool TryReadFloatSetting(string configPath, string key, out float value) { value = 0f; if (string.IsNullOrEmpty(configPath) || string.IsNullOrEmpty(key) || !File.Exists(configPath)) { return false; } string[] array = File.ReadAllLines(configPath); foreach (string text in array) { if (text != null && text.StartsWith(key + " =", StringComparison.Ordinal)) { return float.TryParse(text.Substring((key + " =").Length).Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out value); } } return false; } private static bool TryReadFirstFloatSetting(string configPath, out float value, params string[] keys) { value = 0f; if (keys == null || keys.Length == 0) { return false; } foreach (string key in keys) { if (TryReadFloatSetting(configPath, key, out value)) { return true; } } return false; } private void LoadAssets() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) Texture2D val = LoadExternalIconTexture(); _bundledReplacementBaseScale = DefaultBundledReplacementBaseScale; string text = ResolveBundlePath(); RuntimeReplacementModel = null; Bundle = null; MochiPrefab = null; MochiMaterial = null; MochiTexture = val; MikuMainTexture = null; RuntimeMikuMaterial = null; if (File.Exists(text)) { if (!TryLoadReplacementBundle(text, val)) { Log.LogError((object)("Found ScallionMiku bundle but failed to initialize replacement from: " + text)); } } else { Log.LogError((object)("ScallionMiku bundle is missing. Bundle-only runtime is enabled, so no PMX fallback will be loaded: " + text)); } } private bool TryLoadReplacementBundle(string bundlePath, Texture2D externalIconTexture) { //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) Bundle = AssetBundle.LoadFromFile(bundlePath); if ((Object)(object)Bundle == (Object)null) { Log.LogError((object)("Failed to load ScallionMiku AssetBundle: " + bundlePath)); return false; } RuntimeReplacementModel = null; MochiPrefab = LoadBundleAssetWithFallback<GameObject>("assets/葱音未来_by_翼月生/scallionmiku.prefab", PrefabNameHints); MochiMaterial = LoadBundleAssetWithFallback<Material>("assets/葱音未来_by_翼月生/materials/scallionmiku-8.shintai.mat", MaterialNameHints); MochiTexture = externalIconTexture ?? LoadBundleAssetWithFallback<Texture2D>("assets/葱音未来_by_翼月生/textures/身体.png", IconTextureNameHints); Texture2D val = LoadBundleAssetWithFallback<Texture2D>("assets/葱音未来_by_翼月生/textures/身体.png", MainTextureNameHints); MikuMainTexture = CreateReadableTexture(val) ?? val; if ((Object)(object)MochiPrefab == (Object)null) { Log.LogError((object)"Failed to load replacement prefab from asset bundle."); Bundle.Unload(false); Bundle = null; return false; } _bundledReplacementBaseScale = CalculateBundledReplacementBaseScale(MochiPrefab); ConfigureMochiMaterial(MikuMainTexture); RuntimeMikuMaterial = CreateRuntimeMikuMaterial(MikuMainTexture); ItemPatch.SanitizeLoadedPrefabRendererMaterials(MochiPrefab, "TryLoadReplacementBundle"); if ((Object)(object)RuntimeMikuMaterial == (Object)null) { Log.LogWarning((object)"Runtime fallback material was not created. The bundled material will be used when available."); } if ((Object)(object)MochiTexture == (Object)null) { Log.LogWarning((object)"Failed to load replacement icon texture."); } if ((Object)(object)MikuMainTexture == (Object)null) { Log.LogWarning((object)"Failed to load replacement main texture."); } VerboseLog("Loaded ScallionMiku replacement from asset bundle: " + bundlePath); return true; } private static Vector3 CalculateBundledReplacementBaseScale(GameObject prefab) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)prefab == (Object)null) { return DefaultBundledReplacementBaseScale; } if (!TryCalculatePrefabBounds(prefab.transform, out var combinedBounds, out var rendererCount)) { Log.LogWarning((object)("Failed to calculate bundled prefab bounds. Falling back to default bundle base scale: " + ((Vector3)(ref DefaultBundledReplacementBaseScale)).ToString("F4"))); return DefaultBundledReplacementBaseScale; } float num = Mathf.Max(((Bounds)(ref combinedBounds)).size.y, 0.0001f); float num2 = RuntimeReferenceVisibleSizeAtBaseScale.y * 0.78f * 1.5f; float num3 = Mathf.Clamp(num2 / num, 0.04f, 1.2f); Vector3 result = default(Vector3); ((Vector3)(ref result))..ctor(num3, num3, num3); string[] obj = new string[8] { "Auto-calculated ScallionMiku bundle base scale. rendererCount=", rendererCount.ToString(CultureInfo.InvariantCulture), ", prefabBoundsSize=", null, null, null, null, null }; Vector3 size = ((Bounds)(ref combinedBounds)).size; obj[3] = ((Vector3)(ref size)).ToString("F4"); obj[4] = ", targetHeight="; obj[5] = num2.ToString("F4", CultureInfo.InvariantCulture); obj[6] = ", baseScale="; obj[7] = ((Vector3)(ref result)).ToString("F4"); VerboseLog(string.Concat(obj)); return result; } private static bool TryCalculatePrefabBounds(Transform root, out Bounds combinedBounds, out int rendererCount) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) combinedBounds = default(Bounds); rendererCount = 0; if ((Object)(object)root == (Object)null) { return false; } bool hasBounds = false; Matrix4x4 worldToLocalMatrix = root.worldToLocalMatrix; MeshRenderer[] componentsInChildren = ((Component)root).GetComponentsInChildren<MeshRenderer>(true); foreach (MeshRenderer val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { MeshFilter component = ((Component)val).GetComponent<MeshFilter>(); if (!((Object)(object)component == (Object)null) && !((Object)(object)component.sharedMesh == (Object)null)) { AppendTransformedBounds(worldToLocalMatrix, ((Component)val).transform.localToWorldMatrix, component.sharedMesh.bounds, ref combinedBounds, ref hasBounds); rendererCount++; } } } SkinnedMeshRenderer[] componentsInChildren2 = ((Component)root).GetComponentsInChildren<SkinnedMeshRenderer>(true); foreach (SkinnedMeshRenderer val2 in componentsInChildren2) { if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2.sharedMesh == (Object)null)) { Bounds localBounds = ((Renderer)val2).localBounds; Vector3 size = ((Bounds)(ref localBounds)).size; Bounds sourceBounds = ((((Vector3)(ref size)).sqrMagnitude > 1E-06f) ? ((Renderer)val2).localBounds : val2.sharedMesh.bounds); AppendTransformedBounds(worldToLocalMatrix, ((Component)val2).transform.localToWorldMatrix, sourceBounds, ref combinedBounds, ref hasBounds); rendererCount++; } } return hasBounds; } private static void AppendTransformedBounds(Matrix4x4 rootWorldToLocal, Matrix4x4 childLocalToWorld, Bounds sourceBounds, ref Bounds combinedBounds, ref bool hasBounds) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) Matrix4x4 val = rootWorldToLocal * childLocalToWorld; Vector3 center = ((Bounds)(ref sourceBounds)).center; Vector3 extents = ((Bounds)(ref sourceBounds)).extents; for (int i = -1; i <= 1; i += 2) { for (int j = -1; j <= 1; j += 2) { for (int k = -1; k <= 1; k += 2) { Vector3 val2 = center + Vector3.Scale(extents, new Vector3((float)i, (float)j, (float)k)); Vector3 val3 = ((Matrix4x4)(ref val)).MultiplyPoint3x4(val2); if (!hasBounds) { combinedBounds = new Bounds(val3, Vector3.zero); hasBounds = true; } else { ((Bounds)(ref combinedBounds)).Encapsulate(val3); } } } } } private static string ResolveBundlePath() { return Path.Combine(directory, "ScallionMiku"); } private Texture2D LoadExternalIconTexture() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Expected O, but got Unknown //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) string text = Path.Combine(directory, "ScallionMikuUI.png"); if (!File.Exists(text)) { return null; } try { using FileStream fileStream = File.OpenRead(text); Bitmap val = new Bitmap((Stream)fileStream); try { int width = ((Image)val).Width; int height = ((Image)val).Height; Color32[] array = (Color32[])(object)new Color32[width * height]; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { Color pixel = val.GetPixel(j, height - 1 - i); array[i * width + j] = new Color32(pixel.R, pixel.G, pixel.B, pixel.A); } } Texture2D val2 = new Texture2D(width, height, (TextureFormat)4, false, false) { name = Path.GetFileNameWithoutExtension(text) }; val2.SetPixels32(array); ((Texture)val2).filterMode = (FilterMode)1; ((Texture)val2).wrapMode = (TextureWrapMode)1; ((Texture)val2).anisoLevel = 1; val2.Apply(false, false); return val2; } finally { ((IDisposable)val)?.Dispose(); } } catch (Exception ex) { Log.LogWarning((object)("Failed to load external icon texture '" + text + "': " + ex.Message)); return null; } } internal static GameObject CreateReplacementVisualInstance(string name) { if (RuntimeReplacementModel != null) { return RuntimeReplacementModel.Instantiate(name); } if ((Object)(object)MochiPrefab == (Object)null) { return null; } return Object.Instantiate<GameObject>(MochiPrefab); } internal static void VerboseLog(string message) { } private static T LoadBundleAssetWithFallback<T>(string preferredPath, params string[] nameHints) where T : Object { if ((Object)(object)Bundle == (Object)null) { return default(T); } if (!string.IsNullOrEmpty(preferredPath)) { T val = Bundle.LoadAsset<T>(preferredPath); if ((Object)(object)val != (Object)null) { return val; } } string[] allAssetNames = Bundle.GetAllAssetNames(); if (allAssetNames != null) { foreach (string text in allAssetNames) { if (AssetNameLooksRelevant(text, preferredPath, nameHints)) { T val2 = Bundle.LoadAsset<T>(text); if ((Object)(object)val2 != (Object)null) { VerboseLog("Loaded fallback " + typeof(T).Name + " from bundle path: " + text); return val2; } } } } T[] array = Bundle.LoadAllAssets<T>(); if (array == null || array.Length == 0) { Log.LogWarning((object)("No " + typeof(T).Name + " assets found in bundle for requested path: " + preferredPath)); return default(T); } T val3 = FindAssetByObjectName(array, preferredPath, nameHints); if ((Object)(object)val3 != (Object)null) { VerboseLog("Loaded fallback " + typeof(T).Name + " by object name: " + ((Object)val3).name); return val3; } if (array.Length == 1) { Log.LogWarning((object)("Loaded only available " + typeof(T).Name + " asset as fallback: " + ((Object)array[0]).name)); return array[0]; } Log.LogWarning((object)("Unable to identify " + typeof(T).Name + " for requested path '" + preferredPath + "'. Candidates: " + string.Join(", ", Array.ConvertAll(array, (T asset) => (!((Object)(object)asset != (Object)null)) ? "<null>" : ((Object)asset).name)))); return default(T); } private static T FindAssetByObjectName<T>(T[] assets, string preferredPath, string[] nameHints) where T : Object { string needle = (string.IsNullOrEmpty(preferredPath) ? string.Empty : Path.GetFileNameWithoutExtension(preferredPath)); foreach (T val in assets) { if (!((Object)(object)val == (Object)null)) { string value = ((Object)val).name ?? string.Empty; if (NameMatches(value, needle) || NameMatchesAnyHint(value, nameHints)) { return val; } } } return default(T); } private static bool AssetNameLooksRelevant(string assetName, string preferredPath, string[] nameHints) { if (string.IsNullOrEmpty(assetName)) { return false; } if (!string.IsNullOrEmpty(preferredPath)) { if (assetName.Equals(preferredPath, StringComparison.OrdinalIgnoreCase)) { return true; } string fileName = Path.GetFileName(preferredPath); string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(preferredPath); if (!string.IsNullOrEmpty(fileName) && assetName.EndsWith(fileName, StringComparison.OrdinalIgnoreCase)) { return true; } if (NameMatches(assetName, fileNameWithoutExtension)) { return true; } } return NameMatchesAnyHint(assetName, nameHints); } private static bool NameMatchesAnyHint(string value, string[] hints) { if (string.IsNullOrEmpty(value) || hints == null) { return false; } for (int i = 0; i < hints.Length; i++) { if (NameMatches(value, hints[i])) { return true; } } return false; } private static bool NameMatches(string value, string needle) { if (string.IsNullOrEmpty(value) || string.IsNullOrEmpty(needle)) { return false; } return value.IndexOf(needle, StringComparison.OrdinalIgnoreCase) >= 0; } private static void ConfigureMochiMaterial(Texture2D mikuMainTexture) { if ((Object)(object)MochiMaterial == (Object)null) { return; } if ((Object)(object)MochiMaterial.shader == (Object)null || !MochiMaterial.shader.isSupported) { Shader val = Shader.Find("Universal Render Pipeline/Lit") ?? Shader.Find("Standard"); if ((Object)(object)val != (Object)null) { MochiMaterial.shader = val; } } if ((Object)(object)mikuMainTexture != (Object)null) { ApplyTextureSet(MochiMaterial, mikuMainTexture); } ApplyMikuColorStyle(MochiMaterial); VerboseLog("Configured bundled material: " + ((Object)MochiMaterial).name); } private static bool TryAssignTextureIfMissing(Material material, string propertyName, Texture2D texture) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)material == (Object)null || (Object)(object)texture == (Object)null || string.IsNullOrEmpty(propertyName) || !material.HasProperty(propertyName)) { return false; } try { if ((Object)(object)material.GetTexture(propertyName) != (Object)null) { return false; } material.SetTexture(propertyName, (Texture)(object)texture); material.SetTextureScale(propertyName, Vector2.one); material.SetTextureOffset(propertyName, Vector2.zero); return true; } catch (Exception ex) { VerboseLog("Skip texture assignment on material '" + ((Object)material).name + "', property '" + propertyName + "': " + ex.Message); return false; } } private static void TryAssignTextureIfMissing(Material material, string[] propertyNames, Texture2D texture) { if (!((Object)(object)material == (Object)null) && !((Object)(object)texture == (Object)null) && propertyNames != null) { for (int i = 0; i < propertyNames.Length; i++) { TryAssignTextureIfMissing(material, propertyNames[i], texture); } } } private static void ApplyTextureSet(Material material, Texture2D albedo) { if (!((Object)(object)material == (Object)null)) { TryAssignTextureIfMissing(material, PreferredTextureProps, albedo); ApplyMikuColorStyle(material); } } private static void ApplyMikuColorStyle(Material material) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)material == (Object)null)) { if (material.HasProperty("_Tint")) { material.SetColor("_Tint", MikuStyleTint); } if (material.HasProperty("_BaseColor")) { material.SetColor("_BaseColor", MikuStyleTint); } if (material.HasProperty("_Color")) { material.SetColor("_Color", MikuStyleTint); } if (material.HasProperty("_Smoothness")) { material.SetFloat("_Smoothness", 0.3f); } if (material.HasProperty("_Glossiness")) { material.SetFloat("_Glossiness", 0.3f); } if (material.HasProperty("_Metallic")) { material.SetFloat("_Metallic", 0f); } if (material.HasProperty("_BumpScale")) { material.SetFloat("_BumpScale", 0f); } if (material.HasProperty("_OcclusionStrength")) { material.SetFloat("_OcclusionStrength", 0f); } if (material.HasProperty("_SpecularHighlights")) { material.SetFloat("_SpecularHighlights", 0f); } if (material.HasProperty("_EnvironmentReflections")) { material.SetFloat("_EnvironmentReflections", 0f); } if (material.HasProperty("_EmissionColor")) { material.SetColor("_EmissionColor", Color.black); material.DisableKeyword("_EMISSION"); } material.DisableKeyword("_NORMALMAP"); material.DisableKeyword("_METALLICSPECGLOSSMAP"); material.DisableKeyword("_OCCLUSIONMAP"); } } private static Texture2D CreateReadableTexture(Texture2D source) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Expected O, but got Unknown if ((Object)(object)source == (Object)null) { return null; } try { source.GetPixel(0, 0); return source; } catch { } RenderTexture temporary = RenderTexture.GetTemporary(((Texture)source).width, ((Texture)source).height, 0, (RenderTextureFormat)0); RenderTexture active = RenderTexture.active; try { Graphics.Blit((Texture)(object)source, temporary); RenderTexture.active = temporary; Texture2D val = new Texture2D(((Texture)source).width, ((Texture)source).height, (TextureFormat)4, true, false) { name = ((Object)source).name + "_Readable" }; val.ReadPixels(new Rect(0f, 0f, (float)((Texture)source).width, (float)((Texture)source).height), 0, 0); val.Apply(true, false); return val; } finally { RenderTexture.active = active; RenderTexture.ReleaseTemporary(temporary); } } private static Material CreateRuntimeMikuMaterial(Texture2D texture) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown Shader val = Shader.Find("Universal Render Pipeline/Lit") ?? Shader.Find("Standard") ?? Shader.Find("W/Peak_Standard"); if ((Object)(object)val == (Object)null) { Log.LogError((object)"Failed to create runtime material because no compatible shader was found."); return null; } Material val2 = new Material(val) { name = "Miku_RuntimeMaterial", renderQueue = 2000, color = Color.white }; if ((Object)(object)texture != (Object)null) { TryAssignTextureIfMissing(val2, "_BaseMap", texture); TryAssignTextureIfMissing(val2, "_MainTex", texture); } if (val2.HasProperty("_Surface")) { val2.SetFloat("_Surface", 0f); } if (val2.HasProperty("_Blend")) { val2.SetFloat("_Blend", 0f); } if (val2.HasProperty("_ZWrite")) { val2.SetFloat("_ZWrite", 1f); } if (val2.HasProperty("_Cull")) { val2.SetFloat("_Cull", 0f); } val2.SetOverrideTag("RenderType", "Opaque"); val2.DisableKeyword("_SURFACE_TYPE_TRANSPARENT"); val2.EnableKeyword("_SURFACE_TYPE_OPAQUE"); ApplyMikuColorStyle(val2); return val2; } } public class MikuMarker : MonoBehaviour { public bool HasAppliedState; public int LastRefreshRevision = -1; public int LastLayer = int.MinValue; public ItemState LastItemState; public bool LastShouldShow; public bool HiddenByOriginalRule; public Vector3 LastLocalPosition; public Quaternion LastLocalRotation = Quaternion.identity; public Vector3 LastLocalScale; } public class MikuCollisionMarker : MonoBehaviour { } public class MikuTwinTailClothState : MonoBehaviour { public int MeshInstanceId; public int MovableVertexCount; } public class MikuScaleRefreshGuard : MonoBehaviour { private bool _pendingRefresh; private bool _hardReset; private int _targetFrame = -1; private bool _clothSuspendedForPause; public void QueueRefresh(bool hardReset) { _pendingRefresh = true; _hardReset |= hardReset; _targetFrame = Mathf.Max(_targetFrame, Time.frameCount + 1); } private void LateUpdate() { bool flag = Time.timeScale <= 0.0001f; if (flag) { SuspendClothsForPause(); } else if (_clothSuspendedForPause) { ResumeClothsAfterPause(); } if (_pendingRefresh && Time.frameCount >= _targetFrame) { _pendingRefresh = false; _targetFrame = -1; RefreshSkinnedMeshes(); RefreshCloths(_hardReset || flag); _hardReset = false; } } private void RefreshSkinnedMeshes() { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) SkinnedMeshRenderer[] componentsInChildren = ((Component)this).GetComponentsInChildren<SkinnedMeshRenderer>(true); foreach (SkinnedMeshRenderer val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } val.updateWhenOffscreen = true; Vector3 size; Bounds val2; if ((Object)(object)val.sharedMesh != (Object)null) { Bounds bounds = val.sharedMesh.bounds; size = ((Bounds)(ref bounds)).size; if (((Vector3)(ref size)).sqrMagnitude > 1E-06f) { val2 = val.sharedMesh.bounds; goto IL_006b; } } val2 = ((Renderer)val).localBounds; goto IL_006b; IL_006b: Bounds localBounds = val2; size = ((Bounds)(ref localBounds)).size; if (((Vector3)(ref size)).sqrMagnitude > 1E-06f) { ((Bounds)(ref localBounds)).Expand(new Vector3(0.2f, 0.25f, 0.2f)); ((Renderer)val).localBounds = localBounds; } } } private void RefreshCloths(bool hardReset) { Cloth[] componentsInChildren = ((Component)this).GetComponentsInChildren<Cloth>(true); foreach (Cloth val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } val.ClearTransformMotion(); if (hardReset && val.enabled) { val.enabled = false; if (!_clothSuspendedForPause) { val.enabled = true; } } val.ClearTransformMotion(); } } private void SuspendClothsForPause() { Cloth[] componentsInChildren = ((Component)this).GetComponentsInChildren<Cloth>(true); foreach (Cloth val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { val.ClearTransformMotion(); if (val.enabled) { val.enabled = false; } } } _clothSuspendedForPause = true; } private void ResumeClothsAfterPause() { RefreshSkinnedMeshes(); Cloth[] componentsInChildren = ((Component)this).GetComponentsInChildren<Cloth>(true); foreach (Cloth val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { val.enabled = true; val.ClearTransformMotion(); } } _clothSuspendedForPause = false; } } public class MikuDeformGuard : MonoBehaviour { private const float SqueezeDuration = 0.78f; private const float SqueezeCompressPhase = 0.42f; private Transform[] _allTransforms = Array.Empty<Transform>(); private Vector3[] _initialChildScales = Array.Empty<Vector3>(); private Vector3 _rootLocalPosition; private Quaternion _rootLocalRotation; private Vector3 _rootLocalScale; private Item _boundItem; private bool _wasUsing; private float _squeezeElapsed = 0.78f; internal Item BoundItem => _boundItem; public void Initialize(Vector3 rootLocalPosition, Quaternion rootLocalRotation, Vector3 rootLocalScale) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) _rootLocalPosition = rootLocalPosition; _rootLocalRotation = rootLocalRotation; _rootLocalScale = rootLocalScale; _wasUsing = false; _squeezeElapsed = 0.78f; Capture(); } public void Bind(Item item) { _boundItem = item; } public void SetRootTarget(Vector3 rootLocalPosition, Quaternion rootLocalRotation, Vector3 rootLocalScale) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) _rootLocalPosition = rootLocalPosition; _rootLocalRotation = rootLocalRotation; _rootLocalScale = rootLocalScale; } private bool IsHeldAndUsing() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 if ((Object)(object)_boundItem != (Object)null && (int)_boundItem.itemState == 1) { if (!_boundItem.isUsingPrimary) { return _boundItem.isUsingSecondary; } return true; } return false; } private void UpdateSingleSqueezeState() { bool flag = IsHeldAndUsing(); if (flag && !_wasUsing) { _squeezeElapsed = 0f; } _wasUsing = flag; if (_squeezeElapsed < 0.78f) { _squeezeElapsed += Time.deltaTime; } } private float EvaluateSingleSqueezeWeight() { if (_squeezeElapsed >= 0.78f) { return 0f; } float num = Mathf.Clamp01(_squeezeElapsed / 0.78f); if (num <= 0.42f) { return Mathf.SmoothStep(0f, 1f, num / 0.42f); } float num2 = (num - 0.42f) / 0.58000004f; return Mathf.SmoothStep(1f, 0f, Mathf.Clamp01(num2)); } private Vector3 GetDesiredRootScale(float squeezeWeight) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) if (squeezeWeight <= 0.0005f) { return _rootLocalScale; } Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(1f - 0.14f * squeezeWeight, 1f + 0.11f * squeezeWeight, 1f - 0.14f * squeezeWeight); return Vector3.Scale(_rootLocalScale, val); } public void Capture() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) _allTransforms = ((Component)this).GetComponentsInChildren<Transform>(true); _initialChildScales = (Vector3[])(object)new Vector3[_allTransforms.Length]; for (int i = 0; i < _allTransforms.Length; i++) { _initialChildScales[i] = _allTransforms[i].localScale; } } private void LateUpdate() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) if (_allTransforms.Length == 0 || _allTransforms.Length != _initialChildScales.Length) { Capture(); } if (((Component)this).transform.localPosition != _rootLocalPosition) { ((Component)this).transform.localPosition = _rootLocalPosition; } if (((Component)this).transform.localRotation != _rootLocalRotation) { ((Component)this).transform.localRotation = _rootLocalRotation; } UpdateSingleSqueezeState(); float squeezeWeight = EvaluateSingleSqueezeWeight(); Vector3 desiredRootScale = GetDesiredRootScale(squeezeWeight); if (((Component)this).transform.localScale != desiredRootScale) { ((Component)this).transform.localScale = desiredRootScale; } for (int i = 0; i < _allTransforms.Length; i++) { Transform val = _allTransforms[i]; if (!((Object)(object)val == (Object)null) && !((Object)(object)val == (Object)(object)((Component)this).transform) && val.localScale != _initialChildScales[i]) { val.localScale = _initialChildScales[i]; } } } } public class MikuRendererGuard : MonoBehaviour { private const float RefreshInterval = 0.1f; private float _nextRefreshTime; private Item _boundItem; private MikuMarker _cachedMarker; private Renderer[] _cachedRenderers = Array.Empty<Renderer>(); internal Item BoundItem => _boundItem; public void Bind(Item item) { _boundItem = item; } private void LateUpdate() { if (!Plugin.ModEnabled || !Plugin.EnableVisibilityGuard) { return; } if ((Object)(object)_cachedMarker == (Object)null) { _cachedMarker = ((Component)this).GetComponent<MikuMarker>(); } MikuMarker cachedMarker = _cachedMarker; if ((Object)(object)cachedMarker != (Object)null && cachedMarker.HiddenByOriginalRule) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup < _nextRefreshTime) { return; } _nextRefreshTime = realtimeSinceStartup + 0.1f; if (!((Component)this).gameObject.activeSelf) { ((Component)this).gameObject.SetActive(true); } int num = (((Object)(object)_boundItem != (Object)null) ? ((Component)_boundItem).gameObject.layer : ((Component)this).gameObject.layer); if (((Component)this).gameObject.layer != num) { ((Component)this).gameObject.layer = num; } Renderer[] renderableChildren = GetRenderableChildren(); foreach (Renderer val in renderableChildren) { if (!((Object)(object)val == (Object)null)) { EnsureRendererVisible(val, num); } } } private Renderer[] GetRenderableChildren() { if (_cachedRenderers.Length == 0 || HasNullRenderer(_cachedRenderers)) { _cachedRenderers = ((Component)this).GetComponentsInChildren<Renderer>(true); } return _cachedRenderers; } private static bool HasNullRenderer(Renderer[] renderers) { for (int i = 0; i < renderers.Length; i++) { if ((Object)(object)renderers[i] == (Object)null) { return true; } } return false; } private static void EnsureRendererVisible(Renderer renderer, int targetLayer) { if (!((Component)renderer).gameObject.activeSelf) { ((Component)renderer).gameObject.SetActive(true); } if (renderer.forceRenderingOff) { renderer.forceRenderingOff = false; } if (!renderer.enabled) { renderer.enabled = true; } if (renderer.HasPropertyBlock()) { renderer.SetPropertyBlock((MaterialPropertyBlock)null); } if (((Component)renderer).gameObject.layer != targetLayer) { ((Component)renderer).gameObject.layer = targetLayer; } if (renderer.allowOcclusionWhenDynamic) { renderer.allowOcclusionWhenDynamic = false; } ItemPatch.NormalizeRuntimeRendererMaterials(renderer, "RendererGuard"); } private static void NormalizeRendererMaterialColor(Renderer renderer) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) Material[] sharedMaterials = renderer.sharedMaterials; foreach (Material val in sharedMaterials) { if (!((Object)(object)val == (Object)null)) { Color white = Color.white; if (val.HasProperty("_Tint")) { val.SetColor("_Tint", white); } if (val.HasProperty("_BaseColor")) { val.SetColor("_BaseColor", white); } if (val.HasProperty("_Color")) { val.SetColor("_Color", white); } } } } } [HarmonyPatch(typeof(Item))] public class ItemPatch { private const int VisibleLayer = 0; private static readonly Color MikuMaterialTint = new Color(0.98f, 1f, 1f, 1f); private const string MikuVisualName = "MikuFumo_Visual"; private const string MikuCollisionName = "MikuCollisionProxy"; private static readonly Vector3 BaselineMikuLocalPosition = new Vector3(-0.06f, -0.5f, 0f); private static readonly Vector3 WorldMikuLocalPosition = BaselineMikuLocalPosition; private static readonly Quaternion WorldMikuLocalRotation = Quaternion.identity; private static readonly Quaternion BundledWorldMikuLocalRotation = Quaternion.Euler(0f, 180f, 0f); private static readonly Vector3 HeldMikuLocalPosition = BaselineMikuLocalPosition; private static readonly Quaternion HeldMikuLocalRotation = Quaternion.Euler(10f, 0f, 0f); private static readonly Quaternion BundledHeldMikuLocalRotation = Quaternion.Euler(10f, 180f, 0f); private const int TwinTailChainStartIndex = 3; private const int TwinTailChainEndIndex = 9; private const int TwinTailAnchorIndex = 2; private const float TwinTailMinInfluenceThreshold = 0.12f; private const float TwinTailMaxDistanceCap = 0.78f; private const float TwinTailStretchingStiffness = 0.98f; private const float TwinTailBendingStiffness = 0.9f; private const float TwinTailDamping = 0.2f; private const float TwinTailFriction = 0.2f; private const float TwinTailCollisionMassScale = 0.06f; private const float TwinTailSolverFrequency = 36f; private const float TwinTailWorldVelocityScale = 0.2f; private const float TwinTailWorldAccelerationScale = 0.75f; private const double SlowCreateProfileTotalThresholdMs = 12.0; private const double SlowCreateProfileStageThresholdMs = 4.0; private static readonly string[] UnwantedFootKeywords = new string[3] { "hand", "glove", "mitten" }; private static readonly string[] CollisionExcludedRendererKeywords = new string[8] { "hair", "tail", "twintail", "twin_tail", "pigtail", "ponytail", "bang", "ahoge" }; private static readonly FieldRef<Character, CharacterData> CharacterDataRef = AccessTools.FieldRefAccess<Character, CharacterData>("data"); private static readonly FieldRef<CharacterData, Item> CharacterCurrentItemRef = AccessTools.FieldRefAccess<CharacterData, Item>("_currentitem"); private static readonly HashSet<int> LoggedRuntimeRendererMaterialWarnings = new HashSet<int>(); private static bool SlowCreateProfileLogged; private static bool IsBingBong(Item item) { if ((Object)(object)item != (Object)null) { if (((Object)item).name.IndexOf("BingBong", StringComparison.OrdinalIgnoreCase) < 0) { return ((Object)item).name.IndexOf("Bing Bong", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } return false; } internal static bool IsMikuTransform(Transform transform) { if ((Object)(object)transform != (Object)null) { if (!((Object)(object)((Component)transform).GetComponentInParent<MikuMarker>() != (Object)null)) { return (Object)(object)((Component)transform).GetComponentInParent<MikuCollisionMarker>() != (Object)null; } return true; } return false; } private static bool IsInBackpack(Item item) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 if ((Object)(object)item != (Object)null) { return (int)item.itemState == 2; } return false; } private static bool IsHeld(Item item) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 if ((Object)(object)item != (Object)null) { return (int)item.itemState == 1; } return false; } private static bool ShouldShowReplacement(Item item) { if ((Object)(object)item != (Object)null && Plugin.ModEnabled) { return !IsReplacementHiddenByOriginal(item); } return false; } private static bool IsReplacementHiddenByOriginal(Item item) { return IsReplacementHiddenByOriginal(FindMikuRoot(item)); } private static bool IsReplacementHiddenByOriginal(Transform mikuRoot) { if ((Object)(object)mikuRoot == (Object)null) { return false; } MikuMarker component = ((Component)mikuRoot).GetComponent<MikuMarker>(); if ((Object)(object)component != (Object)null) { return component.HiddenByOriginalRule; } return false; } private static void SetReplacementHiddenByOriginal(Transform mikuRoot, bool hidden) { if (!((Object)(object)mikuRoot == (Object)null)) { MikuMarker component = ((Component)mikuRoot).GetComponent<MikuMarker>(); if ((Object)(object)component != (Object)null) { component.HiddenByOriginalRule = hidden; } } } private static void HideReplacementForOriginalRule(Item item, Transform mikuRoot) { if (!((Object)(object)item == (Object)null) && !((Object)(object)mikuRoot == (Object)null)) { SetReplacementHiddenByOriginal(mikuRoot, hidden: true); Transform val = FindMikuCollisionRoot(item); SetColliderArrayEnabled(((Object)(object)val != (Object)null) ? GetUsableProxyColliders(val) : Array.Empty<Collider>(), enabled: false); DisableOriginalColliders(item); RestoreOriginalItemRendererRefs(item); if (((Component)mikuRoot).gameObject.activeSelf) { ((Component)mikuRoot).gameObject.SetActive(false); } } } private static Vector3 ResolveScaleByState(Item item) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) float num = (IsInBackpack(item) ? Plugin.BackpackScaleMultiplier : Plugin.WorldScaleMultiplier); return Plugin.ReplacementBaseScale * num; } private static Vector3 ApplyPlayerFacingScale(Vector3 scale) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) return new Vector3(0f - Mathf.Abs(scale.x), Mathf.Abs(scale.y), 0f - Mathf.Abs(scale.z)); } private static bool UseBundledPositiveFacing() { return Plugin.RuntimeReplacementModel == null; } private static Vector3 CompensateRuntimeScalePosition(Vector3 basePosition, Vector3 currentScale, Vector3 referenceScale) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return basePosition; } private static void ResolvePoseByState(Item item, out Vector3 localPosition, out Quaternion localRotation, out Vector3 localScale) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Unknown result type (might be due to