using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using Microsoft.CodeAnalysis; using TerrainCustomiserCN.UI; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("TerrainCustomiserCNCollector")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0+fbb84b7ed077928f27ed9fe61becd6796df38ca2")] [assembly: AssemblyProduct("TerrainCustomiserCNCollector")] [assembly: AssemblyTitle("TerrainCustomiserCNCollector")] [assembly: AssemblyVersion("0.1.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace TerrainCustomiserCNCollector { [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("com.wuyachiyu.terraincustomisercn.collector", "TerrainCustomiserCN Translation Collector", "0.1.0")] public class Plugin : BaseUnityPlugin { private static readonly SortedDictionary> Missing = new SortedDictionary>(StringComparer.Ordinal); private static readonly object SyncRoot = new object(); private static string outputPath; private static bool dirty; private static float nextWriteTime; private void Awake() { outputPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? Paths.PluginPath, "TerrainCustomiserCN_missing_translations.tsv"); DisplayNameTranslator.MissingTranslationFound += OnMissingTranslationFound; CollectStaticReflectionNames(); WriteOutput(); Debug.Log((object)("[TCCN Collector] Translation collector enabled. Missing output: " + outputPath)); } private void Update() { if (dirty && Time.unscaledTime >= nextWriteTime) { WriteOutput(); dirty = false; } } private void OnDestroy() { DisplayNameTranslator.MissingTranslationFound -= OnMissingTranslationFound; WriteOutput(); } private static void OnMissingTranslationFound(MissingTranslation missing) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) if (Add(((MissingTranslation)(ref missing)).Kind, ((MissingTranslation)(ref missing)).Key, ((MissingTranslation)(ref missing)).Source, "Runtime")) { dirty = true; nextWriteTime = Time.unscaledTime + 1f; } } public static void CollectStaticReflectionNames() { Type[] inspectableTypes = GetInspectableTypes(); foreach (Type type in inspectableTypes) { AddIfMissing((TranslationKind)2, type.Name, type.FullName, "StaticType"); FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { AddIfMissing((TranslationKind)1, fieldInfo.Name, type.FullName, "StaticField"); Type fieldType = fieldInfo.FieldType; if (fieldType.IsEnum) { CollectEnum(fieldType, type.FullName + "." + fieldInfo.Name); } else if (fieldType.IsArray && fieldType.GetElementType() != null && fieldType.GetElementType().IsEnum) { CollectEnum(fieldType.GetElementType(), type.FullName + "." + fieldInfo.Name); } else { if (!fieldType.IsGenericType) { continue; } Type[] genericArguments = fieldType.GetGenericArguments(); foreach (Type type2 in genericArguments) { if (type2.IsEnum) { CollectEnum(type2, type.FullName + "." + fieldInfo.Name); } } } } } } private static Type[] GetInspectableTypes() { Type[] types = typeof(PropSpawner).Assembly.GetTypes(); Type[] source = new Type[5] { typeof(LevelGenStep), typeof(PropSpawnerMod), typeof(PropSpawnerConstraint), typeof(PropSpawnerConstraintPost), typeof(PostSpawnBehavior) }; HashSet hashSet = new HashSet(); Type[] array = types; foreach (Type type2 in array) { if (!(type2 == null) && !type2.IsAbstract && !type2.IsInterface && source.Any((Type baseType) => baseType.IsAssignableFrom(type2))) { hashSet.Add(type2); } } hashSet.Add(typeof(PropGrouper)); hashSet.Add(typeof(RockMaterialSwapper)); hashSet.Add(typeof(SpecialDayZone)); return hashSet.OrderBy((Type type) => type.FullName, StringComparer.Ordinal).ToArray(); } private static void CollectEnum(Type enumType, string source) { string[] names = Enum.GetNames(enumType); foreach (string text in names) { if (!DisplayNameTranslator.HasTranslation((TranslationKind)3, text, enumType)) { Add((TranslationKind)3, enumType.FullName + "." + text, source, "StaticEnum"); } } } private static void AddIfMissing(TranslationKind kind, string key, string source, string origin) { //IL_0000: 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) if (!DisplayNameTranslator.HasTranslation(kind, key, (Type)null)) { Add(kind, key, source, origin); } } private static bool Add(TranslationKind kind, string key, string source, string origin) { if (string.IsNullOrWhiteSpace(key) || !key.Any(IsAsciiLetter)) { return false; } string value = (string.IsNullOrEmpty(source) ? "-" : source); string item = Escape(key) + "\t" + Escape(value) + "\t" + Escape(origin); lock (SyncRoot) { string key2 = ((object)(TranslationKind)(ref kind)).ToString(); if (!Missing.TryGetValue(key2, out var value2)) { value2 = new SortedSet(StringComparer.Ordinal); Missing.Add(key2, value2); } return value2.Add(item); } } private static void WriteOutput() { if (string.IsNullOrEmpty(outputPath)) { return; } lock (SyncRoot) { Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); using StreamWriter streamWriter = new StreamWriter(outputPath, append: false, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true)); streamWriter.WriteLine("kind\tkey\tsource\torigin"); foreach (KeyValuePair> item in Missing) { foreach (string item2 in item.Value) { streamWriter.Write(item.Key); streamWriter.Write('\t'); streamWriter.WriteLine(item2); } } } } private static bool IsAsciiLetter(char c) { if (c < 'A' || c > 'Z') { if (c >= 'a') { return c <= 'z'; } return false; } return true; } private static string Escape(string value) { return value.Replace("\t", " ").Replace("\r", " ").Replace("\n", " "); } } }