Decompiled source of TravelButton v1.1.1
TravelButton.dll
Decompiled 5 months 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.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using UnityEngine; using UnityEngine.AI; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("TravelButton")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+b010cf8fb4bf25c1740146cc060f906386a13ccc")] [assembly: AssemblyProduct("TravelButton")] [assembly: AssemblyTitle("TravelButton")] [assembly: AssemblyVersion("1.0.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; } } } [Serializable] public class CityConfig { public string name; public int price = -1; public float[] coords; public string targetGameObjectName; public string sceneName; public string desc; public bool visited = false; [JsonIgnore] public bool Visited { get { return visited; } set { visited = value; } } public CityConfig() { } public CityConfig(string name) { this.name = name; } public CityConfig(string name, int price, float[] coords, string targetGameObjectName, string sceneName, string desc) { this.name = name; this.price = price; this.coords = coords; this.targetGameObjectName = targetGameObjectName; this.sceneName = sceneName; this.desc = desc; visited = false; } } [Serializable] public class TravelConfig { public List<CityConfig> cities = new List<CityConfig>(); public static TravelConfig LoadFromFile(string filePath) { if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath)) { return null; } try { string text = File.ReadAllText(filePath); if (string.IsNullOrWhiteSpace(text)) { return null; } TravelConfig travelConfig = JsonUtility.FromJson<TravelConfig>(text); if (travelConfig != null && travelConfig.cities != null && travelConfig.cities.Count > 0) { return travelConfig; } return ParseObjectKeyedForm(text); } catch (Exception ex) { Debug.LogError((object)("[TravelButton] TravelConfig.LoadFromFile failed: " + ex.Message)); return null; } } private static TravelConfig ParseObjectKeyedForm(string json) { try { int num = json.IndexOf("\"cities\"", StringComparison.OrdinalIgnoreCase); if (num < 0) { return null; } int num2 = json.IndexOf(':', num); if (num2 < 0) { return null; } int i; for (i = num2 + 1; i < json.Length && char.IsWhiteSpace(json[i]); i++) { } if (i >= json.Length || json[i] != '{') { return null; } int num3 = i; int num4 = 0; bool flag = false; int num5 = -1; for (; i < json.Length; i++) { char c = json[i]; if (c == '"' && (i == 0 || json[i - 1] != '\\')) { flag = !flag; } if (!flag) { switch (c) { case '{': num4++; continue; case '}': break; default: continue; } num4--; if (num4 == 0) { num5 = i; break; } } } if (num5 < 0) { return null; } string text = json.Substring(num3 + 1, num5 - num3 - 1); List<CityConfig> list = new List<CityConfig>(); int j = 0; while (j < text.Length) { for (; j < text.Length && (char.IsWhiteSpace(text[j]) || text[j] == ','); j++) { } if (j >= text.Length || text[j] != '"') { break; } int num6 = j; int num7 = FindClosingQuote(text, num6); if (num7 < 0) { break; } string cityName = text.Substring(num6 + 1, num7 - num6 - 1); for (j = num7 + 1; j < text.Length && char.IsWhiteSpace(text[j]); j++) { } if (j < text.Length && text[j] == ':') { j++; } for (; j < text.Length && char.IsWhiteSpace(text[j]); j++) { } if (j >= text.Length || text[j] != '{') { break; } int num8 = j; int num9 = FindMatchingBrace(text, num8); if (num9 < 0) { break; } string cityJson = text.Substring(num8, num9 - num8 + 1); CityConfig cityConfig = ParseCityObject(cityName, cityJson); if (cityConfig != null) { list.Add(cityConfig); } j = num9 + 1; } if (list.Count > 0) { TravelConfig travelConfig = new TravelConfig(); travelConfig.cities = list; return travelConfig; } return null; } catch (Exception ex) { Debug.LogError((object)("[TravelButton] ParseObjectKeyedForm failed: " + ex.Message)); return null; } } private static CityConfig ParseCityObject(string cityName, string cityJson) { try { CityConfig cityConfig = new CityConfig(); cityConfig.name = cityName; cityConfig.coords = ExtractFloatArray(cityJson, "coords"); cityConfig.targetGameObjectName = ExtractString(cityJson, "targetGameObjectName"); cityConfig.sceneName = ExtractString(cityJson, "sceneName"); cityConfig.desc = ExtractString(cityJson, "desc"); cityConfig.price = ExtractInt(cityJson, "price").GetValueOrDefault(-1); cityConfig.visited = ExtractBool(cityJson, "visited").GetValueOrDefault(); return cityConfig; } catch (Exception ex) { Debug.LogError((object)("[TravelButton] ParseCityObject failed for " + cityName + ": " + ex.Message)); return null; } } private static string ExtractString(string json, string propName) { int num = json.IndexOf("\"" + propName + "\"", StringComparison.OrdinalIgnoreCase); if (num < 0) { return null; } int num2 = json.IndexOf(':', num); if (num2 < 0) { return null; } int i; for (i = num2 + 1; i < json.Length && char.IsWhiteSpace(json[i]); i++) { } if (i >= json.Length || json[i] != '"') { return null; } int num3 = FindClosingQuote(json, i); if (num3 < 0) { return null; } return json.Substring(i + 1, num3 - i - 1); } private static int? ExtractInt(string json, string propName) { int num = json.IndexOf("\"" + propName + "\"", StringComparison.OrdinalIgnoreCase); if (num < 0) { return null; } int num2 = json.IndexOf(':', num); if (num2 < 0) { return null; } int i; for (i = num2 + 1; i < json.Length && char.IsWhiteSpace(json[i]); i++) { } if (i >= json.Length) { return null; } int num3 = i; for (; i < json.Length && (char.IsDigit(json[i]) || json[i] == '-' || json[i] == '+'); i++) { } string s = json.Substring(num3, i - num3); if (int.TryParse(s, out var result)) { return result; } return null; } private static float[] ExtractFloatArray(string json, string propName) { int num = json.IndexOf("\"" + propName + "\"", StringComparison.OrdinalIgnoreCase); if (num < 0) { return null; } int num2 = json.IndexOf(':', num); if (num2 < 0) { return null; } int i; for (i = num2 + 1; i < json.Length && char.IsWhiteSpace(json[i]); i++) { } if (i >= json.Length || json[i] != '[') { return null; } int num3 = i; int num4 = FindMatchingBracket(json, num3); if (num4 < 0) { return null; } string text = json.Substring(num3 + 1, num4 - num3 - 1); string[] array = text.Split(new char[1] { ',' }); List<float> list = new List<float>(); string[] array2 = array; foreach (string text2 in array2) { string s = text2.Trim(); if (float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { list.Add(result); } } return (list.Count > 0) ? list.ToArray() : null; } private static bool? ExtractBool(string json, string propName) { int num = json.IndexOf("\"" + propName + "\"", StringComparison.OrdinalIgnoreCase); if (num < 0) { return null; } int num2 = json.IndexOf(':', num); if (num2 < 0) { return null; } int i; for (i = num2 + 1; i < json.Length && char.IsWhiteSpace(json[i]); i++) { } if (i >= json.Length) { return null; } int length = Math.Min(5, json.Length - i); string text = json.Substring(i, length).ToLowerInvariant(); if (text.StartsWith("true")) { return true; } if (text.StartsWith("false")) { return false; } return null; } private static int FindClosingQuote(string json, int startIdx) { if (startIdx < 0 || startIdx >= json.Length) { return -1; } if (json[startIdx] != '"') { return -1; } for (int i = startIdx + 1; i < json.Length; i++) { if (json[i] == '"' && json[i - 1] != '\\') { return i; } } return -1; } private static int FindMatchingBrace(string json, int startIdx) { if (startIdx < 0 || startIdx >= json.Length) { return -1; } if (json[startIdx] != '{') { return -1; } int num = 0; bool flag = false; for (int i = startIdx; i < json.Length; i++) { char c = json[i]; if (c == '"' && (i == 0 || json[i - 1] != '\\')) { flag = !flag; } if (flag) { continue; } switch (c) { case '{': num++; break; case '}': num--; if (num == 0) { return i; } break; } } return -1; } private static int FindMatchingBracket(string json, int startIdx) { if (startIdx < 0 || startIdx >= json.Length) { return -1; } if (json[startIdx] != '[') { return -1; } int num = 0; bool flag = false; for (int i = startIdx; i < json.Length; i++) { char c = json[i]; if (c == '"' && (i == 0 || json[i - 1] != '\\')) { flag = !flag; } if (flag) { continue; } switch (c) { case '[': num++; break; case ']': num--; if (num == 0) { return i; } break; } } return -1; } public static TravelConfig Default() { try { Type type = null; try { type = Type.GetType("ConfigManager") ?? typeof(ConfigManager); } catch { type = null; } if (type != null) { MethodInfo method = type.GetMethod("Default", BindingFlags.Static | BindingFlags.Public); if (method != null) { object obj2 = method.Invoke(null, null); if (obj2 != null) { object obj3 = null; PropertyInfo property = obj2.GetType().GetProperty("cities", BindingFlags.Instance | BindingFlags.Public); if (property != null) { obj3 = property.GetValue(obj2); } else { FieldInfo field = obj2.GetType().GetField("cities", BindingFlags.Instance | BindingFlags.Public); if (field != null) { obj3 = field.GetValue(obj2); } } if (obj3 != null) { TravelConfig travelConfig = new TravelConfig(); if (obj3 is IDictionary) { CityMappingHelpers.MapDefaultCityToTarget(obj3, travelConfig.cities); return travelConfig; } if (obj3 is IEnumerable) { CityMappingHelpers.MapParsedCityToTarget(obj3, travelConfig.cities); return travelConfig; } } } } } } catch (Exception ex) { Debug.LogError((object)("[TravelButton] TravelConfig.Default reflection/mapping failed: " + ex)); } return new TravelConfig(); } } [Serializable] public class JsonCityConfig { public string name; public int? price; public float[] coords; public string targetGameObjectName; public string sceneName; public string desc; public bool visited = false; public JsonCityConfig() { } public JsonCityConfig(string name) { this.name = name; } } [Serializable] public class JsonTravelConfig { public List<JsonCityConfig> cities = new List<JsonCityConfig>(); public static JsonTravelConfig Default() { JsonTravelConfig jsonTravelConfig = new JsonTravelConfig(); TBLog.Info("JsonTravelConfig.Default: invoked to build defaults."); try { object obj = null; try { obj = ConfigManager.Default(); TBLog.Info("JsonTravelConfig.Default: ConfigManager.Default() called directly."); } catch (Exception ex) { TBLog.Info("JsonTravelConfig.Default: direct call to ConfigManager.Default() failed: " + ex.Message); obj = null; } if (obj == null) { try { Type type = Type.GetType("ConfigManager"); if (type != null) { MethodInfo method = type.GetMethod("Default", BindingFlags.Static | BindingFlags.Public); if (method != null) { obj = method.Invoke(null, null); TBLog.Info("JsonTravelConfig.Default: obtained defaults via reflection from ConfigManager.Default()."); } } } catch (Exception ex2) { TBLog.Warn("JsonTravelConfig.Default: reflection call failed: " + ex2); obj = null; } } if (obj == null) { TBLog.Warn("JsonTravelConfig.Default: no defaults object available from ConfigManager.Default(); returning empty list."); return jsonTravelConfig; } object obj2 = null; try { Type type2 = obj.GetType(); PropertyInfo property = type2.GetProperty("cities", BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { obj2 = property.GetValue(obj); } else { FieldInfo field = type2.GetField("cities", BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { obj2 = field.GetValue(obj); } } } catch (Exception ex3) { TBLog.Warn("JsonTravelConfig.Default: failed to get 'cities' member from defaultsObj: " + ex3); obj2 = null; } if (obj2 == null) { TBLog.Warn("JsonTravelConfig.Default: 'cities' member null on defaults object; returning empty list."); return jsonTravelConfig; } if (obj2 is IDictionary dictionary) { foreach (DictionaryEntry item3 in dictionary) { try { string text = item3.Key as string; object value = item3.Value; if (string.IsNullOrEmpty(text)) { text = TryGetStringFromObject(value, "name") ?? TryGetStringFromObject(value, "Name") ?? "<unknown>"; } JsonCityConfig item = new JsonCityConfig(text) { price = TryGetIntFromObject(value, "price"), coords = TryGetFloatArrayFromObject(value, "coords"), targetGameObjectName = (TryGetStringFromObject(value, "targetGameObjectName") ?? TryGetStringFromObject(value, "target")), sceneName = (TryGetStringFromObject(value, "sceneName") ?? TryGetStringFromObject(value, "scene")), desc = (TryGetStringFromObject(value, "desc") ?? TryGetStringFromObject(value, "description")), visited = TryGetBoolFromObject(value, "visited").GetValueOrDefault() }; jsonTravelConfig.cities.Add(item); } catch { } } TBLog.Info($"JsonTravelConfig.Default: mapped {jsonTravelConfig.cities.Count} cities from dictionary defaults."); return jsonTravelConfig; } if (obj2 is IEnumerable enumerable) { int num = 0; foreach (object item4 in enumerable) { try { if (item4 != null) { string text2 = TryGetStringFromObject(item4, "name") ?? TryGetStringFromObject(item4, "Name") ?? item4.ToString(); JsonCityConfig item2 = new JsonCityConfig(text2 ?? string.Empty) { price = TryGetIntFromObject(item4, "price"), coords = TryGetFloatArrayFromObject(item4, "coords"), targetGameObjectName = (TryGetStringFromObject(item4, "targetGameObjectName") ?? TryGetStringFromObject(item4, "target")), sceneName = (TryGetStringFromObject(item4, "sceneName") ?? TryGetStringFromObject(item4, "scene")), desc = (TryGetStringFromObject(item4, "desc") ?? TryGetStringFromObject(item4, "description")), visited = TryGetBoolFromObject(item4, "visited").GetValueOrDefault() }; jsonTravelConfig.cities.Add(item2); num++; } } catch { } } TBLog.Info($"JsonTravelConfig.Default: mapped {num} cities from enumerable defaults (total {jsonTravelConfig.cities.Count})."); return jsonTravelConfig; } TBLog.Warn("JsonTravelConfig.Default: 'cities' member exists but is not IDictionary or IEnumerable; returning whatever was collected."); return jsonTravelConfig; } catch (Exception ex4) { TBLog.Warn("JsonTravelConfig.Default: unexpected error: " + ex4); return jsonTravelConfig; } } private static int? TryGetIntFromObject(object obj, string propName) { if (obj == null) { return null; } Type type = obj.GetType(); try { PropertyInfo property = type.GetProperty(propName, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanRead) { object value = property.GetValue(obj); if (value is int value2) { return value2; } try { return Convert.ToInt32(value); } catch { } } FieldInfo field = type.GetField(propName, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { object value3 = field.GetValue(obj); if (value3 is int value4) { return value4; } try { return Convert.ToInt32(value3); } catch { } } } catch { } return null; } private static bool? TryGetBoolFromObject(object obj, string propName) { if (obj == null) { return null; } Type type = obj.GetType(); try { PropertyInfo property = type.GetProperty(propName, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanRead) { object value = property.GetValue(obj); if (value is bool value2) { return value2; } if (value != null && bool.TryParse(value.ToString(), out var result)) { return result; } } FieldInfo field = type.GetField(propName, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { object value3 = field.GetValue(obj); if (value3 is bool value4) { return value4; } if (value3 != null && bool.TryParse(value3.ToString(), out var result2)) { return result2; } } } catch { } return null; } private static string TryGetStringFromObject(object obj, string propName) { if (obj == null) { return null; } Type type = obj.GetType(); try { PropertyInfo property = type.GetProperty(propName, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanRead && property.PropertyType == typeof(string)) { return property.GetValue(obj) as string; } FieldInfo field = type.GetField(propName, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.FieldType == typeof(string)) { return field.GetValue(obj) as string; } } catch { } return null; } private static float[] TryGetFloatArrayFromObject(object obj, string propName) { if (obj == null) { return null; } Type type = obj.GetType(); try { PropertyInfo property = type.GetProperty(propName, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); object obj2 = null; if (property != null && property.CanRead) { obj2 = property.GetValue(obj); } else { FieldInfo field = type.GetField(propName, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { obj2 = field.GetValue(obj); } } if (obj2 == null) { return null; } if (obj2 is float[] result) { return result; } if (obj2 is IEnumerable enumerable) { List<float> list = new List<float>(); foreach (object item in enumerable) { try { list.Add(Convert.ToSingle(item)); } catch { } } if (list.Count > 0) { return list.ToArray(); } } } catch { } return null; } public static JsonTravelConfig LoadFromJson(string path) { try { if (!File.Exists(path)) { return null; } string text = File.ReadAllText(path, Encoding.UTF8); if (string.IsNullOrWhiteSpace(text)) { return null; } List<string> list = new List<string>(); string[] array = text.Split(new char[1] { '\n' }); foreach (string text2 in array) { string text3 = text2.TrimStart(Array.Empty<char>()); if (!text3.StartsWith("//")) { list.Add(text2); } } text = string.Join("\n", list); JsonTravelConfig jsonTravelConfig = JsonUtility.FromJson<JsonTravelConfig>(text); if (jsonTravelConfig != null && jsonTravelConfig.cities != null) { return jsonTravelConfig; } return null; } catch (Exception ex) { TBLog.Warn("LoadFromJson failed for " + path + ": " + ex.Message); return null; } } public void SaveToJson(string path) { try { TBLog.Info("JsonTravelConfig.SaveToJson: begin write to '" + path + "'"); try { if (File.Exists(path)) { FileInfo fileInfo = new FileInfo(path); TBLog.Info($"JsonTravelConfig.SaveToJson: existing file detected. FullName='{fileInfo.FullName}', Length={fileInfo.Length}, LastWriteTime={fileInfo.LastWriteTimeUtc:O}"); } else { TBLog.Info("JsonTravelConfig.SaveToJson: no existing file at target path."); } } catch (Exception ex) { TBLog.Warn("JsonTravelConfig.SaveToJson: failed querying existing file info: " + ex); } string directoryName = Path.GetDirectoryName(path); if (!string.IsNullOrEmpty(directoryName) && !Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } if (cities == null) { cities = new List<JsonCityConfig>(); } try { TBLog.Info($"JsonTravelConfig.SaveToJson: cities.Count = {cities.Count}"); int num = 0; foreach (JsonCityConfig city in cities) { if (city == null) { TBLog.Info($" city[{num}]: <null>"); } else { string text = "<null>"; try { text = ((city.coords != null) ? ("[" + string.Join(", ", city.coords) + "]") : "<null>"); } catch { text = "<err>"; } TBLog.Info(string.Format(" city[{0}] name='{1}' scene='{2}' coords={3} price={4} visited={5}", num, city.name ?? "", city.sceneName ?? "", text, city.price, city.visited)); } num++; if (num >= 50) { TBLog.Info(" ...stopping city listing after 50 entries"); break; } } } catch (Exception ex2) { TBLog.Warn("JsonTravelConfig.SaveToJson: failed to enumerate cities for debug: " + ex2); } try { int num2 = 12; StackTrace stackTrace = new StackTrace(1, fNeedFileInfo: true); StackFrame[] frames = stackTrace.GetFrames(); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine($"JsonTravelConfig.SaveToJson: writing {cities.Count} entries to {path}. Callers:"); if (frames != null) { int num3 = Math.Min(num2, frames.Length); for (int i = 0; i < num3; i++) { StackFrame stackFrame = frames[i]; MethodBase method = stackFrame.GetMethod(); Type type = method?.DeclaringType; string text2 = ((type != null) ? (type.FullName + "." + method.Name) : (method?.Name ?? "<unknown>")); string fileName = stackFrame.GetFileName(); int fileLineNumber = stackFrame.GetFileLineNumber(); stringBuilder.AppendLine(" at " + text2 + " (file: " + (fileName ?? "<unknown>") + ":" + ((fileLineNumber > 0) ? fileLineNumber.ToString() : "0") + ")"); } if (frames.Length > num2) { stringBuilder.AppendLine($" ... ({frames.Length - num2} more frames)"); } } else { stringBuilder.AppendLine(" <no stack frames available>"); } TBLog.Info(stringBuilder.ToString()); } catch (Exception ex3) { TBLog.Warn("JsonTravelConfig.SaveToJson: failed to build trimmed stack trace: " + ex3.Message); TBLog.Info("JsonTravelConfig.SaveToJson full stack:\n" + Environment.StackTrace); } string text3 = "TravelButton_Cities.json"; string value; try { value = JsonConvert.SerializeObject((object)this, (Formatting)1); } catch (Exception ex4) { TBLog.Warn("JsonTravelConfig.SaveToJson: Newtonsoft serialization failed, falling back to JsonUtility: " + ex4); value = JsonUtility.ToJson((object)this, true) ?? "{}"; } StringBuilder stringBuilder2 = new StringBuilder(); stringBuilder2.AppendLine("// " + text3); stringBuilder2.AppendLine("// Schema: { \"cities\": [ { \"name\": \"...\", \"coords\": [x,y,z], \"targetGameObjectName\": \"...\", \"sceneName\": \"...\", \"desc\": \"...\" }, ... ] }"); stringBuilder2.AppendLine("// Note: 'enabled' and 'price' are managed by BepInEx config, not this file."); stringBuilder2.AppendLine(); stringBuilder2.Append(value); try { int length = stringBuilder2.Length; TBLog.Info($"JsonTravelConfig.SaveToJson: serialized JSON length = {length} chars"); int num4 = Math.Min(1024, length); if (num4 > 0) { string arg = stringBuilder2.ToString(0, num4).Replace("\r\n", "\\n"); TBLog.Info($"JsonTravelConfig.SaveToJson: JSON preview (first {num4} chars):\n{arg}"); } } catch (Exception ex5) { TBLog.Warn("JsonTravelConfig.SaveToJson: failed to log JSON preview: " + ex5); } try { File.WriteAllText(path, stringBuilder2.ToString(), Encoding.UTF8); } catch (Exception arg2) { TBLog.Warn($"JsonTravelConfig.SaveToJson: File.WriteAllText failed for {path}: {arg2}"); throw; } try { FileInfo fileInfo2 = new FileInfo(path); TBLog.Info($"SaveToJson: wrote {Path.GetFileName(path)} to {path} (length={fileInfo2.Length}, lastWriteUtc={fileInfo2.LastWriteTimeUtc:O})"); } catch (Exception ex6) { TBLog.Warn("JsonTravelConfig.SaveToJson: failed to stat file after write: " + ex6); } } catch (Exception arg3) { TBLog.Warn($"SaveToJson failed for {path}: {arg3}"); } } public static JsonTravelConfig TryLoadFromCommonLocations() { List<string> candidatePaths = GetCandidatePaths(); foreach (string item in candidatePaths) { try { if (File.Exists(item)) { JsonTravelConfig jsonTravelConfig = LoadFromJson(item); if (jsonTravelConfig != null) { TBLog.Info("TryLoadFromCommonLocations: loaded from " + item); return jsonTravelConfig; } } } catch (Exception ex) { TBLog.Warn("TryLoadFromCommonLocations: error checking " + item + ": " + ex.Message); } } return null; } private static List<string> GetCandidatePaths() { List<string> list = new List<string>(); string text = "TravelButton_Cities.json"; try { string text2 = AppDomain.CurrentDomain.BaseDirectory ?? ""; if (!string.IsNullOrEmpty(text2)) { list.Add(Path.Combine(text2, "BepInEx", "config", text)); list.Add(Path.Combine(text2, "config", text)); } } catch { } try { string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); if (!string.IsNullOrEmpty(directoryName)) { list.Add(Path.Combine(directoryName, text)); } } catch { } try { string currentDirectory = Directory.GetCurrentDirectory(); if (!string.IsNullOrEmpty(currentDirectory)) { list.Add(Path.Combine(currentDirectory, text)); } } catch { } try { string dataPath = Application.dataPath; if (!string.IsNullOrEmpty(dataPath)) { list.Add(Path.Combine(dataPath, text)); } } catch { } return list; } public static string GetPreferredWritePath() { string path = "TravelButton_Cities.json"; try { string text = AppDomain.CurrentDomain.BaseDirectory ?? ""; if (!string.IsNullOrEmpty(text)) { string text2 = Path.Combine(text, "BepInEx", "config"); if (Directory.Exists(text2)) { return Path.Combine(text2, path); } string path2 = Path.Combine(text, "BepInEx"); if (Directory.Exists(path2)) { try { Directory.CreateDirectory(text2); return Path.Combine(text2, path); } catch { } } } } catch { } try { string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); if (!string.IsNullOrEmpty(directoryName)) { return Path.Combine(directoryName, path); } } catch { } return Path.Combine(Directory.GetCurrentDirectory(), path); } } public class CityDiscovery : MonoBehaviour { private float timer = 0f; private const float PollInterval = 1f; private const float DiscoverRadius = 6f; private void Awake() { Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); } private void Start() { TBLog.Info("CityDiscovery.Start: initializing city discovery system."); try { TravelButtonVisitedManager.LogPlayerCandidateVisitedFields(); } catch { } } private void Update() { try { if (Input.GetKeyDown((KeyCode)290)) { LogPlayerPosition(); } if (Input.GetKeyDown((KeyCode)291)) { ForceMarkNearestCity(); } timer += Time.unscaledDeltaTime; if (timer >= 1f) { timer = 0f; PollForNearbyCities(); EnforceVisitedGating(); } } catch (Exception ex) { TBLog.Warn("CityDiscovery.Update failed: " + ex); } } private void PollForNearbyCities() { //IL_0028: 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_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) try { Transform val = FindPlayerTransform(); if ((Object)(object)val == (Object)null) { TBLog.Warn("CityDiscovery: PollForNearbyCities - player transform not found."); return; } Vector3 position = val.position; IList<TravelButton.City> citiesList = GetCitiesList(); if (citiesList == null) { TBLog.Warn("CityDiscovery: PollForNearbyCities - could not locate TravelButtonMod.Cities."); return; } Scene activeScene = SceneManager.GetActiveScene(); string text = ((Scene)(ref activeScene)).name ?? ""; foreach (TravelButton.City item in citiesList) { try { if (string.IsNullOrEmpty(item.name) || TravelButtonVisitedManager.IsCityVisited(item.name)) { continue; } Vector3? val2 = GetCityPosition(item); if (!val2.HasValue && !string.IsNullOrEmpty(text) && text.IndexOf(item.name, StringComparison.OrdinalIgnoreCase) >= 0) { TBLog.Info("CityDiscovery: Scene '" + text + "' matches city '" + item.name + "' - using player position as candidate."); val2 = position; } if (!val2.HasValue) { TBLog.Info("CityDiscovery: No candidate position for city '" + item.name + "' (skipping)."); continue; } float num = Vector3.Distance(position, val2.Value); TBLog.Info($"CityDiscovery: Dist to '{item.name}' = {num:F1} (threshold {6f})."); if (num <= 6f) { TravelButtonVisitedManager.MarkVisited(item.name, val2.Value); TBLog.Info($"CityDiscovery: Auto-discovered city '{item.name}' at distance {num:F1}."); } } catch (Exception ex) { TBLog.Warn("CityDiscovery.PollForNearbyCities failed for a city: " + ex); } } } catch (Exception ex2) { TBLog.Warn("CityDiscovery.PollForNearbyCities failed: " + ex2); } } private void ForceMarkNearestCity() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: 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_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: 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_00d6: Unknown result type (might be due to invalid IL or missing references) Transform val = FindPlayerTransform(); if ((Object)(object)val == (Object)null) { TBLog.Warn("CityDiscovery: ForceMarkNearestCity - player transform not found."); return; } Vector3 position = val.position; IList<TravelButton.City> citiesList = GetCitiesList(); if (citiesList == null) { TBLog.Warn("CityDiscovery: ForceMarkNearestCity - could not locate TravelButtonMod.Cities."); return; } float num = float.MaxValue; TravelButton.City city = null; Vector3? position2 = null; foreach (TravelButton.City item in citiesList) { try { Vector3? val2 = GetCityPosition(item); if (!val2.HasValue) { Scene activeScene = SceneManager.GetActiveScene(); string text = ((Scene)(ref activeScene)).name ?? ""; if (!string.IsNullOrEmpty(text) && text.IndexOf(item.name, StringComparison.OrdinalIgnoreCase) >= 0) { val2 = position; } } if (val2.HasValue) { float num2 = Vector3.Distance(position, val2.Value); if (num2 < num) { num = num2; city = item; position2 = val2; } } } catch { } } if (city != null) { TravelButtonVisitedManager.MarkVisited(city.name, position2); TBLog.Info($"CityDiscovery: Force-marked nearest city '{city.name}' (dist {num:F1})."); } else { TBLog.Warn("CityDiscovery: ForceMarkNearestCity - no city positions available to mark."); } } private void EnforceVisitedGating() { //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_0123: Unknown result type (might be due to invalid IL or missing references) try { Scene activeScene = SceneManager.GetActiveScene(); GameObject[] rootGameObjects = ((Scene)(ref activeScene)).GetRootGameObjects(); GameObject[] array = rootGameObjects; foreach (GameObject val in array) { if ((Object)(object)val == (Object)null || !val.activeInHierarchy) { continue; } Transform val2 = val.transform.Find("ScrollArea/Viewport/Content"); if ((Object)(object)val2 == (Object)null) { continue; } for (int j = 0; j < val2.childCount; j++) { Transform child = val2.GetChild(j); Button component = ((Component)child).GetComponent<Button>(); Image component2 = ((Component)child).GetComponent<Image>(); if ((Object)(object)component == (Object)null || (Object)(object)component2 == (Object)null) { continue; } string name = ((Object)child).name; if (name.StartsWith("CityButton_")) { string cityName = name.Substring("CityButton_".Length); if (!TravelButtonVisitedManager.IsCityVisited(cityName) && ((Selectable)component).interactable) { ((Selectable)component).interactable = false; ((Graphic)component2).color = new Color(0.18f, 0.18f, 0.18f, 1f); } } } break; } } catch (Exception ex) { TBLog.Warn("CityDiscovery.EnforceVisitedGating failed: " + ex); } } private IList<TravelButton.City> GetCitiesList() { IList<TravelButton.City> list = null; FieldInfo field = typeof(TravelButton).GetField("Cities", BindingFlags.Static | BindingFlags.Public); if (field != null) { list = field.GetValue(null) as IList<TravelButton.City>; } if (list == null) { PropertyInfo property = typeof(TravelButton).GetProperty("Cities", BindingFlags.Static | BindingFlags.Public); if (property != null) { list = property.GetValue(null, null) as IList<TravelButton.City>; } } return list; } private Transform FindPlayerTransform() { try { GameObject val = GameObject.FindWithTag("Player"); if ((Object)(object)val != (Object)null) { return val.transform; } } catch { } string[] array = new string[7] { "PlayerCharacter", "PlayerEntity", "Character", "PC_Player", "PlayerController", "LocalPlayer", "Player" }; string[] array2 = array; foreach (string text in array2) { try { Type type = ReflectionUtils.SafeGetType(text + ", Assembly-CSharp"); if (!(type != null)) { continue; } Object[] array3 = Object.FindObjectsOfType(type); if (array3 != null && array3.Length != 0) { Object obj2 = array3[0]; Component val2 = (Component)(object)((obj2 is Component) ? obj2 : null); if ((Object)(object)val2 != (Object)null) { return val2.transform; } } } catch { } } try { if ((Object)(object)Camera.main != (Object)null) { return ((Component)Camera.main).transform; } } catch { } try { Transform[] array4 = Object.FindObjectsOfType<Transform>(); Transform[] array5 = array4; foreach (Transform val3 in array5) { if (!((Object)(object)val3 == (Object)null) && ((Object)val3).name.IndexOf("player", StringComparison.OrdinalIgnoreCase) >= 0) { return val3; } } } catch { } return null; } public Vector3? GetCityPosition(TravelButton.City city) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) if (city == null) { return null; } try { if (!string.IsNullOrEmpty(city.targetGameObjectName)) { GameObject val = GameObject.Find(city.targetGameObjectName); if ((Object)(object)val != (Object)null) { return val.transform.position; } } if (city.coords != null && city.coords.Length >= 3) { return new Vector3(city.coords[0], city.coords[1], city.coords[2]); } try { Transform[] array = Object.FindObjectsOfType<Transform>(); Transform[] array2 = array; foreach (Transform val2 in array2) { if (!((Object)(object)val2 == (Object)null) && ((Object)val2).name.IndexOf(city.name ?? "", StringComparison.OrdinalIgnoreCase) >= 0) { return val2.position; } } } catch { } return null; } catch (Exception ex) { TBLog.Warn("GetCityPosition exception: " + ex); return null; } } private void LogPlayerPosition() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) Transform val = FindPlayerTransform(); if ((Object)(object)val == (Object)null) { TBLog.Info("CityDiscovery: player transform not found for LogPlayerPosition."); } else { TBLog.Info($"CityDiscovery: Player pos = {val.position}"); } } } public static class CityMappingHelpers { private static List<TravelButtonUI.CityEntry> loadedCities; public static void InitCities() { try { try { string text = TryGetCanonicalCitiesJsonPath(); if (!string.IsNullOrEmpty(text)) { try { string fullPath = Path.GetFullPath(text); if (File.Exists(fullPath)) { List<TravelButtonUI.CityEntry> list = ParseCitiesJsonFile(fullPath); if (list != null && list.Count > 0) { loadedCities = list; TBLog.Info($"InitCities: loaded {loadedCities.Count} cities from canonical path: {fullPath}"); DumpLoadedCitiesPreview(); return; } } else { TBLog.Info("InitCities: canonical TravelButton_Cities.json not found at: " + fullPath); } } catch (Exception ex) { TBLog.Warn("InitCities: error while trying canonical path '" + text + "': " + ex); } } } catch (Exception ex2) { TBLog.Warn("InitCities: canonical-path attempt threw: " + ex2); } List<string> list2 = new List<string>(); try { Type type = Type.GetType("BepInEx.Paths, BepInEx"); if (type != null) { PropertyInfo property = type.GetProperty("ConfigPath", BindingFlags.Static | BindingFlags.Public); if (property != null) { string text2 = property.GetValue(null) as string; if (!string.IsNullOrEmpty(text2)) { string text3 = "TravelButton_Cities.json"; try { text3 = ((typeof(TravelButtonPlugin).GetField("CitiesJsonFileName", BindingFlags.Static | BindingFlags.Public) != null) ? "TravelButton_Cities.json" : text3); } catch { } string item = Path.Combine(text2, text3); list2.Add(item); } } } } catch (Exception ex3) { TBLog.Warn("InitCities: reflection attempt to read BepInEx.Paths.ConfigPath failed: " + ex3); } try { list2.Add(Path.GetFullPath(Path.Combine(Application.dataPath, "..", "BepInEx", "config", "TravelButton_Cities.json"))); } catch { } try { list2.Add(Path.GetFullPath(Path.Combine(TravelButtonPlugin.GetCitiesJsonPath(), "TravelButton_Cities.json"))); } catch { } try { string text4 = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ""; if (!string.IsNullOrEmpty(text4)) { list2.Add(Path.Combine(text4, "TravelButton_Cities.json")); } } catch { } foreach (string item2 in list2) { try { if (string.IsNullOrEmpty(item2)) { continue; } string fullPath2 = Path.GetFullPath(item2); if (File.Exists(fullPath2)) { List<TravelButtonUI.CityEntry> list3 = ParseCitiesJsonFile(fullPath2); if (list3 != null && list3.Count > 0) { loadedCities = list3; TBLog.Info($"InitCities: loaded {loadedCities.Count} cities from: {fullPath2}"); DumpLoadedCitiesPreview(); return; } } } catch (Exception ex4) { TBLog.Warn("InitCities: error while trying candidate path '" + item2 + "': " + ex4); } } TBLog.Warn("InitCities: no valid TravelButton_Cities.json found in canonical or candidate locations; loadedCities is null or empty."); } catch (Exception ex5) { TBLog.Warn("InitCities: unexpected outer error: " + ex5); } } public static List<TravelButtonUI.CityEntry> ParseCitiesJsonFile(string filePath) { //IL_051e: Unknown result type (might be due to invalid IL or missing references) //IL_0525: Invalid comparison between Unknown and I4 //IL_0646: Unknown result type (might be due to invalid IL or missing references) //IL_064c: Invalid comparison between Unknown and I4 //IL_065e: Unknown result type (might be due to invalid IL or missing references) //IL_0665: Expected O, but got Unknown //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Invalid comparison between Unknown and I4 //IL_026b: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Expected O, but got Unknown //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Invalid comparison between Unknown and I4 //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Invalid comparison between Unknown and I4 //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Expected O, but got Unknown //IL_020b: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Expected O, but got Unknown //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Invalid comparison between Unknown and I4 //IL_03dc: Unknown result type (might be due to invalid IL or missing references) //IL_03e3: Invalid comparison between Unknown and I4 //IL_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Expected O, but got Unknown //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Expected O, but got Unknown try { if (string.IsNullOrEmpty(filePath)) { TBLog.Warn("ParseCitiesJsonFile: filePath is null/empty"); return null; } string fullPath = Path.GetFullPath(filePath); if (!File.Exists(fullPath)) { TBLog.Warn("ParseCitiesJsonFile: file not found: " + fullPath); return null; } string text = File.ReadAllText(fullPath); if (string.IsNullOrWhiteSpace(text)) { TBLog.Warn("ParseCitiesJsonFile: file is empty: " + fullPath); return null; } JToken val; try { string text4; using (StringReader stringReader = new StringReader(text)) { StringBuilder stringBuilder = new StringBuilder(); bool flag = false; string text2; while ((text2 = stringReader.ReadLine()) != null) { if (!flag) { if (string.IsNullOrWhiteSpace(text2)) { continue; } string text3 = text2.TrimStart(Array.Empty<char>()); if (text3.StartsWith("//")) { continue; } flag = true; } stringBuilder.AppendLine(text2); } text4 = stringBuilder.ToString(); } if (string.IsNullOrWhiteSpace(text4)) { TBLog.Warn("ParseCitiesJsonFile: file contained only comments/whitespace: " + fullPath); return null; } val = JToken.Parse(text4); } catch (Exception ex) { TBLog.Warn("ParseCitiesJsonFile: JSON parse error for file " + fullPath + ": " + ex.Message); return null; } JArray val2 = null; if ((int)val.Type == 1) { JObject val3 = (JObject)val; JToken val4 = val3["cities"] ?? val3["Cities"] ?? val3["list"]; if (val4 != null && (int)val4.Type == 2) { val2 = (JArray)val4; } else { TBLog.Info("ParseCitiesJsonFile: root is an object but does not contain 'cities' array; attempting to interpret root as single city object."); val2 = new JArray((object)val3); } } else { if ((int)val.Type != 2) { TBLog.Warn($"ParseCitiesJsonFile: unexpected JSON root type {val.Type} in file {fullPath}"); return null; } val2 = (JArray)val; } List<TravelButtonUI.CityEntry> list = new List<TravelButtonUI.CityEntry>(((JContainer)val2).Count); for (int i = 0; i < ((JContainer)val2).Count; i++) { try { JToken val5 = val2[i]; if (val5 == null || (int)val5.Type != 1) { continue; } JObject val6 = (JObject)val5; TravelButtonUI.CityEntry cityEntry = new TravelButtonUI.CityEntry(); SetStringMember(cityEntry, ((JToken)val6).Value<string>((object)"name") ?? ((JToken)val6).Value<string>((object)"Name") ?? string.Empty, "name", "Name"); SetStringMember(cityEntry, ((JToken)val6).Value<string>((object)"sceneName") ?? ((JToken)val6).Value<string>((object)"SceneName") ?? ((JToken)val6).Value<string>((object)"scene") ?? string.Empty, "sceneName", "SceneName", "scene"); SetStringMember(cityEntry, ((JToken)val6).Value<string>((object)"targetGameObjectName") ?? ((JToken)val6).Value<string>((object)"target") ?? string.Empty, "targetGameObjectName", "target", "targetName"); SetStringMember(cityEntry, ((JToken)val6).Value<string>((object)"desc") ?? ((JToken)val6).Value<string>((object)"description") ?? string.Empty, "desc", "description", "descText", "Description"); int num = -1; JToken val7 = val6["price"] ?? val6["Price"]; if (val7 != null && (int)val7.Type != 10) { try { num = Extensions.Value<int>((IEnumerable<JToken>)val7); } catch { try { num = Convert.ToInt32(Extensions.Value<double>((IEnumerable<JToken>)val7)); } catch { num = -1; } } } try { Type type = cityEntry.GetType(); PropertyInfo propertyInfo = type.GetProperty("price", BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetProperty("Price", BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (propertyInfo != null && propertyInfo.CanWrite && propertyInfo.PropertyType == typeof(int)) { propertyInfo.SetValue(cityEntry, num); } else { FieldInfo fieldInfo = type.GetField("price", BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetField("Price", BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (fieldInfo != null && fieldInfo.FieldType == typeof(int)) { fieldInfo.SetValue(cityEntry, num); } } } catch { } bool flag2 = false; JToken val8 = val6["visited"] ?? val6["Visited"]; if (val8 != null && (int)val8.Type != 10) { try { flag2 = Extensions.Value<bool>((IEnumerable<JToken>)val8); } catch { flag2 = false; } } try { Type type2 = cityEntry.GetType(); PropertyInfo propertyInfo2 = type2.GetProperty("visited", BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type2.GetProperty("Visited", BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (propertyInfo2 != null && propertyInfo2.CanWrite && propertyInfo2.PropertyType == typeof(bool)) { propertyInfo2.SetValue(cityEntry, flag2); } else { FieldInfo fieldInfo2 = type2.GetField("visited", BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type2.GetField("Visited", BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (fieldInfo2 != null && fieldInfo2.FieldType == typeof(bool)) { fieldInfo2.SetValue(cityEntry, flag2); } } } catch { } JToken val9 = val6["coords"] ?? val6["Coords"]; if (val9 != null && (int)val9.Type == 2) { try { JArray val10 = (JArray)val9; if (((JContainer)val10).Count >= 3) { SetFloatArrayOnTarget(cityEntry, new float[3] { Extensions.Value<float>((IEnumerable<JToken>)val10[0]), Extensions.Value<float>((IEnumerable<JToken>)val10[1]), Extensions.Value<float>((IEnumerable<JToken>)val10[2]) }, "coords", "Coords", "position", "Position"); } } catch { } } list.Add(cityEntry); } catch (Exception ex2) { TBLog.Warn($"ParseCitiesJsonFile: failed to parse city entry index {i} in {fullPath}: {ex2.Message}"); } } if (list.Count == 0) { TBLog.Warn("ParseCitiesJsonFile: parsed JSON but found 0 cities in " + fullPath); return null; } TBLog.Info($"ParseCitiesJsonFile: successfully parsed {list.Count} cities from: {fullPath}"); return list; } catch (Exception ex3) { TBLog.Warn("ParseCitiesJsonFile: unexpected error: " + ex3); return null; } } private static void DumpLoadedCitiesPreview() { try { for (int i = 0; i < Math.Min(loadedCities.Count, 10); i++) { TravelButtonUI.CityEntry cityEntry = loadedCities[i]; TBLog.Info(string.Format(" city[{0}] name='{1}' scene='{2}' coords={3}", i, cityEntry?.name, cityEntry?.sceneName, (cityEntry?.coords == null) ? "null" : string.Join(",", cityEntry.coords))); } } catch { } } private static bool TryFindCityByNameOrScene(string candidate, out TravelButtonUI.CityEntry outCity) { outCity = null; if (string.IsNullOrEmpty(candidate)) { return false; } try { List<TravelButtonUI.CityEntry> list = loadedCities; if (list == null || list.Count == 0) { return false; } string text = candidate.Trim().ToLowerInvariant(); foreach (TravelButtonUI.CityEntry item in list) { if (item != null) { if (!string.IsNullOrEmpty(item.name) && item.name.Trim().ToLowerInvariant() == text) { outCity = item; return true; } if (!string.IsNullOrEmpty(item.sceneName) && item.sceneName.Trim().ToLowerInvariant() == text) { outCity = item; return true; } } } } catch (Exception ex) { TBLog.Warn("TryFindCityByNameOrScene: lookup failed: " + ex); } return false; } private static Vector3 haveCoordsHintOrResolved(Vector3 coordsHint, bool haveCoordsHint, Vector3 resolvedCoords, bool resolvedHaveCoords, out bool haveCoordsFinal) { //IL_000b: 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_0021: 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_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) if (haveCoordsHint) { haveCoordsFinal = true; return coordsHint; } if (resolvedHaveCoords) { haveCoordsFinal = true; return resolvedCoords; } haveCoordsFinal = false; return Vector3.zero; } public static bool TryResolveCityDataFromObject(object cityObj, out string sceneName, out string targetName, out Vector3 coords, out bool haveCoords, out int price) { return TryResolveCityDataFromObject_Internal(cityObj, out sceneName, out targetName, out coords, out haveCoords, out price); } private static bool TryResolveCityDataFromObject_Internal(object cityObj, out string outSceneName, out string outTargetName, out Vector3 outCoords, out bool outHaveCoords, out int outPrice) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_0247: Unknown result type (might be due to invalid IL or missing references) //IL_0c2a: Unknown result type (might be due to invalid IL or missing references) //IL_0c2f: Unknown result type (might be due to invalid IL or missing references) //IL_0602: Unknown result type (might be due to invalid IL or missing references) //IL_0607: Unknown result type (might be due to invalid IL or missing references) //IL_0a15: Unknown result type (might be due to invalid IL or missing references) //IL_0a1a: Unknown result type (might be due to invalid IL or missing references) //IL_0655: Unknown result type (might be due to invalid IL or missing references) //IL_065a: Unknown result type (might be due to invalid IL or missing references) //IL_0a68: Unknown result type (might be due to invalid IL or missing references) //IL_0a6d: Unknown result type (might be due to invalid IL or missing references) //IL_059a: Unknown result type (might be due to invalid IL or missing references) //IL_059f: Unknown result type (might be due to invalid IL or missing references) //IL_04d6: Unknown result type (might be due to invalid IL or missing references) //IL_04db: Unknown result type (might be due to invalid IL or missing references) //IL_09ad: Unknown result type (might be due to invalid IL or missing references) //IL_09b2: Unknown result type (might be due to invalid IL or missing references) //IL_08e9: Unknown result type (might be due to invalid IL or missing references) //IL_08ee: Unknown result type (might be due to invalid IL or missing references) //IL_06d0: Unknown result type (might be due to invalid IL or missing references) //IL_06d5: Unknown result type (might be due to invalid IL or missing references) //IL_0ae3: Unknown result type (might be due to invalid IL or missing references) //IL_0ae8: Unknown result type (might be due to invalid IL or missing references) outSceneName = null; outTargetName = null; outCoords = Vector3.zero; outHaveCoords = false; outPrice = 0; if (cityObj == null) { return false; } if (cityObj is TravelButtonUI.CityEntry cityEntry) { outSceneName = cityEntry.sceneName; outTargetName = cityEntry.targetGameObjectName; if (cityEntry.coords != null && cityEntry.coords.Length >= 3) { outCoords = new Vector3(cityEntry.coords[0], cityEntry.coords[1], cityEntry.coords[2]); outHaveCoords = true; } outPrice = cityEntry.price; return !string.IsNullOrEmpty(outSceneName) | outHaveCoords; } if (cityObj is string text) { if (TryFindCityByNameOrScene(text, out var outCity)) { outSceneName = outCity.sceneName; outTargetName = outCity.targetGameObjectName; if (outCity.coords != null && outCity.coords.Length >= 3) { outCoords = new Vector3(outCity.coords[0], outCity.coords[1], outCity.coords[2]); outHaveCoords = true; } outPrice = outCity.price; return !string.IsNullOrEmpty(outSceneName) | outHaveCoords; } outSceneName = text; return true; } try { string text2 = null; PropertyInfo propertyInfo = cityObj.GetType().GetProperty("name", BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public) ?? cityObj.GetType().GetProperty("Name", BindingFlags.Instance | BindingFlags.Public); if (propertyInfo != null) { text2 = propertyInfo.GetValue(cityObj) as string; } if (string.IsNullOrEmpty(text2)) { try { text2 = cityObj.ToString(); } catch { text2 = null; } } if (!string.IsNullOrEmpty(text2) && TryFindCityByNameOrScene(text2, out var outCity2)) { outSceneName = outCity2.sceneName; outTargetName = outCity2.targetGameObjectName; if (outCity2.coords != null && outCity2.coords.Length >= 3) { outCoords = new Vector3(outCity2.coords[0], outCity2.coords[1], outCity2.coords[2]); outHaveCoords = true; } outPrice = outCity2.price; return !string.IsNullOrEmpty(outSceneName) | outHaveCoords; } } catch { } try { Type type = cityObj.GetType(); string[] array = new string[4] { "scenename", "scene", "scene_name", "sceneName" }; string[] array2 = new string[5] { "targetgameobjectname", "target", "targetname", "target_gameobject_name", "targetGameObjectName" }; string[] array3 = new string[3] { "name", "displayname", "title" }; string[] array4 = new string[3] { "price", "cost", "fee" }; string[] array5 = new string[7] { "coords", "position", "pos", "location", "transform", "vector", "coordinate" }; PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (PropertyInfo propertyInfo2 in properties) { try { object obj3 = null; try { obj3 = propertyInfo2.GetValue(cityObj); } catch { goto end_IL_0377; } if (obj3 == null) { continue; } string propName = propertyInfo2.Name.ToLowerInvariant(); if (obj3 is string text3) { TravelButtonUI.CityEntry outCity4; if (Array.Exists(array, (string k) => propName.Contains(k))) { if (string.IsNullOrEmpty(outSceneName)) { outSceneName = text3; } } else if (Array.Exists(array2, (string k) => propName.Contains(k))) { if (string.IsNullOrEmpty(outTargetName)) { outTargetName = text3; } } else if (Array.Exists(array3, (string k) => propName.Contains(k))) { if (TryFindCityByNameOrScene(text3, out var outCity3)) { if (string.IsNullOrEmpty(outSceneName)) { outSceneName = outCity3.sceneName; } if (string.IsNullOrEmpty(outTargetName)) { outTargetName = outCity3.targetGameObjectName; } if (!outHaveCoords && outCity3.coords != null && outCity3.coords.Length >= 3) { outCoords = new Vector3(outCity3.coords[0], outCity3.coords[1], outCity3.coords[2]); outHaveCoords = true; } if (outPrice == 0) { outPrice = outCity3.price; } } else if (string.IsNullOrEmpty(outSceneName)) { outSceneName = text3; } } else if (TryFindCityByNameOrScene(text3, out outCity4)) { if (string.IsNullOrEmpty(outSceneName)) { outSceneName = outCity4.sceneName; } if (string.IsNullOrEmpty(outTargetName)) { outTargetName = outCity4.targetGameObjectName; } if (!outHaveCoords && outCity4.coords != null && outCity4.coords.Length >= 3) { outCoords = new Vector3(outCity4.coords[0], outCity4.coords[1], outCity4.coords[2]); outHaveCoords = true; } if (outPrice == 0) { outPrice = outCity4.price; } } continue; } if (obj3 is float[] array6 && array6.Length >= 3) { if (!outHaveCoords) { outCoords = new Vector3(array6[0], array6[1], array6[2]); outHaveCoords = true; } continue; } if (obj3 is double[] array7 && array7.Length >= 3) { if (!outHaveCoords) { outCoords = new Vector3((float)array7[0], (float)array7[1], (float)array7[2]); outHaveCoords = true; } continue; } if (obj3 is IList list && list.Count >= 3) { try { float num = Convert.ToSingle(list[0]); float num2 = Convert.ToSingle(list[1]); float num3 = Convert.ToSingle(list[2]); if (!outHaveCoords) { outCoords = new Vector3(num, num2, num3); outHaveCoords = true; } } catch { } continue; } GameObject val = (GameObject)((obj3 is GameObject) ? obj3 : null); if ((Object)(object)val != (Object)null && string.IsNullOrEmpty(outTargetName)) { outTargetName = ((Object)val).name; continue; } Component val2 = (Component)((obj3 is Component) ? obj3 : null); if ((Object)(object)val2 != (Object)null && string.IsNullOrEmpty(outTargetName)) { GameObject gameObject = val2.gameObject; outTargetName = ((gameObject != null) ? ((Object)gameObject).name : null); } end_IL_0377:; } catch { } } FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { try { object obj7 = null; try { obj7 = fieldInfo.GetValue(cityObj); } catch { goto end_IL_078a; } if (obj7 == null) { continue; } string fieldName = fieldInfo.Name.ToLowerInvariant(); if (obj7 is string text4) { TravelButtonUI.CityEntry outCity6; if (Array.Exists(array, (string k) => fieldName.Contains(k))) { if (string.IsNullOrEmpty(outSceneName)) { outSceneName = text4; } } else if (Array.Exists(array2, (string k) => fieldName.Contains(k))) { if (string.IsNullOrEmpty(outTargetName)) { outTargetName = text4; } } else if (Array.Exists(array3, (string k) => fieldName.Contains(k))) { if (TryFindCityByNameOrScene(text4, out var outCity5)) { if (string.IsNullOrEmpty(outSceneName)) { outSceneName = outCity5.sceneName; } if (string.IsNullOrEmpty(outTargetName)) { outTargetName = outCity5.targetGameObjectName; } if (!outHaveCoords && outCity5.coords != null && outCity5.coords.Length >= 3) { outCoords = new Vector3(outCity5.coords[0], outCity5.coords[1], outCity5.coords[2]); outHaveCoords = true; } if (outPrice == 0) { outPrice = outCity5.price; } } else if (string.IsNullOrEmpty(outSceneName)) { outSceneName = text4; } } else if (TryFindCityByNameOrScene(text4, out outCity6)) { if (string.IsNullOrEmpty(outSceneName)) { outSceneName = outCity6.sceneName; } if (string.IsNullOrEmpty(outTargetName)) { outTargetName = outCity6.targetGameObjectName; } if (!outHaveCoords && outCity6.coords != null && outCity6.coords.Length >= 3) { outCoords = new Vector3(outCity6.coords[0], outCity6.coords[1], outCity6.coords[2]); outHaveCoords = true; } if (outPrice == 0) { outPrice = outCity6.price; } } continue; } if (obj7 is float[] array8 && array8.Length >= 3) { if (!outHaveCoords) { outCoords = new Vector3(array8[0], array8[1], array8[2]); outHaveCoords = true; } continue; } if (obj7 is double[] array9 && array9.Length >= 3) { if (!outHaveCoords) { outCoords = new Vector3((float)array9[0], (float)array9[1], (float)array9[2]); outHaveCoords = true; } continue; } if (obj7 is IList list2 && list2.Count >= 3) { try { float num4 = Convert.ToSingle(list2[0]); float num5 = Convert.ToSingle(list2[1]); float num6 = Convert.ToSingle(list2[2]); if (!outHaveCoords) { outCoords = new Vector3(num4, num5, num6); outHaveCoords = true; } } catch { } continue; } GameObject val3 = (GameObject)((obj7 is GameObject) ? obj7 : null); if ((Object)(object)val3 != (Object)null && string.IsNullOrEmpty(outTargetName)) { outTargetName = ((Object)val3).name; continue; } Component val4 = (Component)((obj7 is Component) ? obj7 : null); if ((Object)(object)val4 != (Object)null && string.IsNullOrEmpty(outTargetName)) { GameObject gameObject2 = val4.gameObject; outTargetName = ((gameObject2 != null) ? ((Object)gameObject2).name : null); } end_IL_078a:; } catch { } } } catch (Exception ex) { TBLog.Warn("TryResolveCityDataFromObject: reflection fallback failed: " + ex); } try { if (!string.IsNullOrEmpty(outSceneName) && TryFindCityByNameOrScene(outSceneName, out var outCity7)) { if (string.IsNullOrEmpty(outTargetName)) { outTargetName = outCity7.targetGameObjectName; } if (!outHaveCoords && outCity7.coords != null && outCity7.coords.Length >= 3) { outCoords = new Vector3(outCity7.coords[0], outCity7.coords[1], outCity7.coords[2]); outHaveCoords = true; } if (outPrice == 0) { outPrice = outCity7.price; } } } catch { } bool flag = !string.IsNullOrEmpty(outSceneName) | outHaveCoords; if (!flag) { try { Type type2 = cityObj.GetType(); List<string> list3 = new List<string>(); PropertyInfo[] properties2 = type2.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (PropertyInfo propertyInfo3 in properties2) { list3.Add("P:" + propertyInfo3.Name); } FieldInfo[] fields2 = type2.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo2 in fields2) { list3.Add("F:" + fieldInfo2.Name); } TBLog.Warn("TryResolveCityDataFromObject: failed to resolve data for object type=" + type2.FullName + ". Members: " + string.Join(", ", list3)); } catch { } } return flag; } public static object ConvertParsedCitiesToRuntime(List<TravelButtonUI.CityEntry> parsed) { if (parsed == null) { return null; } try { FieldInfo field = typeof(TravelButton).GetField("Cities", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); PropertyInfo property = typeof(TravelButton).GetProperty("Cities", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); Type type = null; if (field != null) { type = field.FieldType; } else if (property != null) { type = property.PropertyType; } if (type != null) { Type elementTypeFromCollectionType = GetElementTypeFromCollectionType(type); if (elementTypeFromCollectionType != null) { Type type2 = typeof(List<>).MakeGenericType(elementTypeFromCollectionType); IList list = (IList)Activator.CreateInstance(type2); foreach (TravelButtonUI.CityEntry item in parsed) { try { object obj = null; try { obj = Activator.CreateInstance(elementTypeFromCollectionType); MapParsedCityToTarget(item, obj); } catch { obj = null; } if (obj == null) { try { string text = JsonConvert.SerializeObject((object)item); obj = JsonConvert.DeserializeObject(text, elementTypeFromCollectionType); } catch (Exception ex) { TBLog.Warn($"ConvertParsedCitiesToRuntime: json roundtrip conversion failed for elementType={elementTypeFromCollectionType}: {ex.Message}"); obj = null; } } if (obj == null) { try { object uninitializedObject = FormatterServices.GetUninitializedObject(elementTypeFromCollectionType); if (uninitializedObject != null) { MapParsedCityToTarget(item, uninitializedObject); obj = uninitializedObject; } } catch (Exception ex2) { TBLog.Warn($"ConvertParsedCitiesToRuntime: GetUninitializedObject failed for {elementTypeFromCollectionType}: {ex2.Message}"); } } if (obj != null) { list.Add(obj); continue; } if (elementTypeFromCollectionType.FullName == typeof(TravelButtonUI.CityEntry).FullName) { list.Add(item); continue; } TBLog.Warn($"ConvertParsedCitiesToRuntime: unable to convert parsed item to {elementTypeFromCollectionType}; aborting conversion."); return null; } catch (Exception ex3) { TBLog.Warn("ConvertParsedCitiesToRuntime: failed converting a parsed city entry: " + ex3); } } return list; } } } catch (Exception ex4) { TBLog.Warn("CityMappingHelpers.ConvertParsedCitiesToRuntime: reflection-based conversion failed: " + ex4); } return parsed; } public static object ConvertConfigManagerDefaultsToRuntime(object configManagerDefaults) { if (configManagerDefaults == null) { return null; } try { Type type = configManagerDefaults.GetType(); IEnumerable<object> enumerable = null; PropertyInfo property = type.GetProperty("cities", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { object value = property.GetValue(configManagerDefaults); if (value is IEnumerable enumerable2) { List<object> list = new List<object>(); foreach (object item in enumerable2) { list.Add(item); } enumerable = list; } } else { FieldInfo field = type.GetField("cities", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { object value2 = field.GetValue(configManagerDefaults); if (value2 is IEnumerable enumerable3) { List<object> list2 = new List<object>(); foreach (object item2 in enumerable3) { list2.Add(item2); } enumerable = list2; } } } if (enumerable == null) { TBLog.Warn("CityMappingHelpers.ConvertConfigManagerDefaultsToRuntime: could not locate a 'cities' collection on defaults - falling back to empty list."); return new List<TravelButtonUI.CityEntry>(); } FieldInfo field2 = typeof(TravelButton).GetField("Cities", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); PropertyInfo property2 = typeof(TravelButton).GetProperty("Cities", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); Type type2 = ((field2 != null) ? field2.FieldType : ((property2 != null) ? property2.PropertyType : null)); if (type2 != null) { Type elementTypeFromCollectionType = GetElementTypeFromCollectionType(type2); if (elementTypeFromCollectionType != null) { Type type3 = typeof(List<>).MakeGenericType(elementTypeFromCollectionType); IList list3 = (IList)Activator.CreateInstance(type3); foreach (object item3 in enumerable) { object obj = Activator.CreateInstance(elementTypeFromCollectionType); MapDefaultCityToTarget(item3, obj); list3.Add(obj); } return list3; } } } catch (Exception ex) { TBLog.Warn("CityMappingHelpers.ConvertConfigManagerDefaultsToRuntime: conversion failed: " + ex); } try { List<TravelButtonUI.CityEntry> list4 = new List<TravelButtonUI.CityEntry>(); Type type4 = configManagerDefaults.GetType(); PropertyInfo property3 = type4.GetProperty("cities", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property3 != null && property3.GetValue(configManagerDefaults) is IEnumerable enumerable4) { foreach (object item4 in enumerable4) { TravelButtonUI.CityEntry cityEntry = new TravelButtonUI.CityEntry(); SetStringOnTarget(cityEntry, GetStringFromSource(item4, "name"), "name"); SetStringOnTarget(cityEntry, GetStringFromSource(item4, "sceneName") ?? GetStringFromSource(item4, "scene"), "sceneName"); SetStringOnTarget(cityEntry, GetStringFromSource(item4, "targetGameObjectName") ?? GetStringFromSource(item4, "target"), "targetGameObjectName"); SetStringOnTarget(cityEntry, GetStringFromSource(item4, "desc") ?? GetStringFromSource(item4, "description"), "desc"); cityEntry.price = GetIntFromSource(item4, "price").GetValueOrDefault(-1); cityEntry.visited = GetBoolFromSource(item4, "visited").GetValueOrDefault(); float[] floatArrayFromSource = GetFloatArrayFromSource(item4, "coords"); cityEntry.coords = floatArrayFromSource; list4.Add(cityEntry); } return list4; } } catch { } return new List<TravelButtonUI.CityEntry>(); } public static void EnsureCitiesInitializedFromJsonOrDefaults() { try { string citiesJsonPath = TravelButtonPlugin.GetCitiesJsonPath(); if (File.Exists(citiesJsonPath)) { TBLog.Info("EnsureCitiesInitializedFromJsonOrDefaults: loading cities from canonical JSON: " + citiesJsonPath); List<TravelButtonUI.CityEntry> list = ParseCitiesJsonFile(citiesJsonPath); if (list != null && list.Count > 0) { object converted = ConvertParsedCitiesToRuntime(list); if (AssignConvertedCitiesToTravelButton(converted)) { TBLog.Info($"EnsureCitiesInitializedFromJsonOrDefaults: loaded {GetRuntimeCitiesCount()} cities from JSON."); return; } TBLog.Warn("EnsureCitiesInitializedFromJsonOrDefaults: converted collection was not assignable to TravelButton.Cities (attempting fallbacks)."); TBLog.Info($"EnsureCitiesInitializedFromJsonOrDefaults: loaded {TravelButton.Cities.Count} cities from JSON."); return; } TBLog.Warn("EnsureCitiesInitializedFromJsonOrDefaults: JSON file present but parsing returned no entries. Falling back to defaults."); } else { TBLog.Info("EnsureCitiesInitializedFromJsonOrDefaults: canonical JSON not found; falling back to ConfigManager defaults."); } ConfigManager.LegacyTravelConfig configManagerDefaults = ConfigManager.Default(); object obj = ConvertConfigManagerDefaultsToRuntime(configManagerDefaults); if (AssignConvertedCitiesToTravelButton(obj)) { TBLog.Info($"EnsureCitiesInitializedFromJsonOrDefaults: populated TravelButton.Cities from ConfigManager defaults (count={GetRuntimeCitiesCount()})"); } else { TBLog.Warn("EnsureCitiesInitializedFromJsonOrDefaults: failed to assign ConfigManager defaults to TravelButton.Cities via reflection."); TryAssignFallbackListOfCityEntry(obj); } TBLog.Info($"EnsureCitiesInitializedFromJsonOrDefaults: populated TravelButton.Cities from ConfigManager defaults (count={TravelButton.Cities?.Count ?? 0})"); try { try { JsonTravelConfig jsonTravelConfig = JsonTravelConfig.Default(); int valueOrDefault = (jsonTravelConfig?.cities?.Count).GetValueOrDefault(); TBLog.Info($"EnsureCitiesInitializedFromJsonOrDefaults: JsonTravelConfig.Default() produced {valueOrDefault} entries."); try { string path = Path.Combine(Application.dataPath ?? ".", "..", "TravelButton_Cities_debug.json"); jsonTravelConfig.SaveToJson(Path.GetFullPath(path)); TBLog.Info("EnsureCitiesInitializedFromJsonOrDefaults: wrote debug JSON to " + Path.GetFullPath(path)); } catch (Exception ex) { TBLog.Warn("EnsureCitiesInitializedFromJsonOrDefaults: failed to write debug JSON: " + ex); } if (valueOrDefault > 0) { if (!File.Exists(citiesJsonPath)) { TBLog.Info("EnsureCitiesInitializedFromJsonOrDefaults: canonical JSON missing -> persisting initial JSON from defaults."); try { jsonTravelConfig.SaveToJson(citiesJsonPath); TBLog.Info("EnsureCitiesInitializedFromJsonOrDefaults: persisted canonical JSON from JsonTravelConfig.Default()."); return; } catch (Exception ex2) { TBLog.Warn("EnsureCitiesInitializedFromJsonOrDefaults: failed to persist canonical JSON: " + ex2); return; } } TBLog.Info("EnsureCitiesInitializedFromJsonOrDefaults: canonical JSON already exists -> not overwriting."); } else { TBLog.Warn("EnsureCitiesInitializedFromJsonOrDefaults: JsonTravelConfig.Default() produced 0 entries; skipping canonical JSON write."); } } catch (Exception ex3) { TBLog.Warn("EnsureCitiesInitializedFromJsonOrDefaults: error in guarded persist logic: " + ex3); } } catch (Exception ex4) { TBLog.Warn("EnsureCitiesInitializedFromJsonOrDefaults: failed to conditionally persist initial JSON: " + ex4); } } catch (Exception ex5) { TBLog.Warn("EnsureCitiesInitializedFromJsonOrDefaults: unexpected error: " + ex5); } } private static bool AssignConvertedCitiesToTravelButton(object converted) { if (converted == null) { return false; } Type typeFromHandle = typeof(TravelButton); FieldInfo field = typeFromHandle.GetField("Cities", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { if (field.FieldType.IsAssignableFrom(converted.GetType())) { field.SetValue(null, converted); return true; } if (TryBuildAndAssignCollectionFromEnumerable(converted as IEnumerable, field.FieldType, out var built)) { field.SetValue(null, built); return true; } TBLog.Warn($"AssignConvertedCitiesToTravelButton: field 'Cities' exists but type '{converted.GetType()}' is not assignable to '{field.FieldType}'."); return false; } PropertyInfo property = typeFromHandle.GetProperty("Cities", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite) { if (property.PropertyType.IsAssignableFrom(converted.GetType())) { property.SetValue(null, converted, null); return true; } if (TryBuildAndAssignCollectionFromEnumerable(converted as IEnumerable, property.PropertyType, out var built2)) { property.SetValue(null, built2, null); return true; } TBLog.Warn($"AssignConvertedCitiesToTravelButton: property 'Cities' exists but type '{converted.GetType()}' is not assignable to '{property.PropertyType}'."); return false; } TBLog.Warn("AssignConvertedCitiesToTravelButton: no static field/property named 'Cities' found on TravelButton."); return false; } private static bool TryBuildAndAssignCollectionFromEnumerable(IEnumerable sourceEnum, Type targetCollectionType, out object built) { built = null; if (sourceEnum == null || targetCollectionType == null) { return false; } Type elementTypeFromCollectionType = GetElementTypeFromCollectionType(targetCollectionType); if (elementTypeFromCollectionType == null) { return false; } try { Type type = typeof(List<>).MakeGenericType(elementTypeFromCollectionType); IList list = (IList)Activator.CreateInstance(type); foreach (object item in sourceEnum) { if (item == null) { list.Add(null); continue; } if (elementTypeFromCollectionType.IsAssignableFrom(item.GetType())) { list.Add(item); continue; } if (item is TravelButtonUI.CityEntry src) { object obj = Activator.CreateInstance(elementTypeFromCollectionType); MapParsedCityToTarget(src, obj); list.Add(obj); continue; } try { string text = JsonConvert.SerializeObject(item); object value = JsonConvert.DeserializeObject(text, elementTypeFromCollectionType); list.Add(value); } catch { TBLog.Warn($"TryBuildAndAssignCollectionFromEnumerable: unable to convert item of type {item.GetType()} to {elementTypeFromCollectionType}"); return false; } } if (targetCollectionType.IsArray) { MethodInfo method = type.GetMethod("ToArray"); object obj3 = method.Invoke(list, null); built = obj3; } else if (targetCollectionType.IsAssignableFrom(list.GetType())) { built = list; } else { try { string text2 = JsonConvert.SerializeObject((object)list); built = JsonConvert.DeserializeObject(text2, targetCollectionType); } catch { TBLog.Warn($"TryBuildAndAssignCollectionFromEnumerable: cannot create target collection of type {targetCollectionType}"); return false; } } return built != null; } catch (Exception ex) { TBLog.Warn("TryBuildAndAssignCollectionFromEnumerable: exception: " + ex); return false; } } private static void TryAssignFallbackListOfCityEntry(object convertedDefaults) { try { if (!(convertedDefaults is List<TravelButtonUI.CityEntry> value)) { return; } Type typeFromHandle = typeof(TravelButton); FieldInfo field = typeFromHandle.GetField("Cities", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.FieldType.IsAssignableFrom(typeof(List<TravelButtonUI.CityEntry>))) { field.SetValue(null, value); TBLog.Info("Assigned List<CityEntry> fallback to TravelButton.Cities."); return; } PropertyInfo property = typeFromHandle.GetProperty("Cities", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite && property.PropertyType.IsAssignableFrom(typeof(List<TravelButtonUI.CityEntry>))) { property.SetValue(null, value, null); TBLog.Info("Assigned List<CityEntry> fallback to TravelButton.Cities (prop)."); } } catch (Exception ex) { TBLog.Warn("TryAssignFallbackListOfCityEntry: " + ex); } } private static int GetRuntimeCitiesCount() { try { Type typeFromHandle = typeof(TravelButton); FieldInfo field = typeFromHandle.GetField("Cities", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); object obj = null; if (field != null) { obj = field.GetValue(null); } else { PropertyInfo property = typeFromHandle.GetProperty("Cities", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { obj = property.GetValue(null, null); } } if (obj is ICollection collection) { return collection.Count; } if (obj is IEnumerable enumerable) { int num = 0; foreach (object item in enumerable) { num++; } return num; } } catch { } return -1; } private static string TryGetCanonicalCitiesJsonPath() { try { Type typeFromHandle = typeof(TravelButton); if (typeFromHandle != null) { MethodInfo method = typeFromHandle.GetMethod("GetCitiesJsonPath", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (method != null) { string text = method.Invoke(null, null) as string; if (!string.IsNullOrEmpty(text)) { return text; } } } } catch (Exception ex) { TBLog.Warn("TryGetCanonicalCitiesJsonPath: TravelButton.GetCitiesJsonPath() reflection failed: " + ex.Message); } try { Type type = Type.GetType("BepInEx.Paths, BepInEx"); if (type != null) { PropertyInfo property = type.GetProperty("ConfigPath", BindingFlags.Static | BindingFlags.Public); if (property != null) { string text2 = property.GetValue(null) as string; if (!string.IsNullOrEmpty(text2)) { string text3 = null; try { text3 = "TravelButton_Cities.json"; } catch { } if (string.IsNullOrEmpty(text3)) { text3 = "TravelButton_Cities.json"; } string text4 = Path.Combine(text2, text3); if (!string.IsNullOrEmpty(text4)) { return text4; } } } } } catch (Exception ex2) { TBLog.Warn("TryGetCanonicalCitiesJsonPath: BepInEx.Paths.ConfigPath reflection failed: " + ex2.Message); } try { string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); if (!string.IsNullOrEmpty(directoryName)) { string path = "TravelButton_Cities.json"; try { FieldInfo field = typeof(TravelButtonPlugin).GetField("CitiesJsonFileName", BindingFlags.Static | BindingFlags.Public); if (field != null) { path = "TravelButton_Cities.json"; } } catch { } return Path.Combine(directoryName, path); } } catch { } try { string currentDirectory = Directory.GetCurrentDirectory(); if (!string.IsNullOrEmpty(currentDirectory)) { string path2 = "TravelButton_Cities.json"; try { FieldInfo field2 = typeof(TravelButtonPlugin).GetField("CitiesJsonFileName", BindingFlags.Static | BindingFlags.Public); if (field2 != null) { path2 = "TravelButton_Cities.json"; } } catch { } return Path.Combine(currentDirectory, path2); } } catch { } return null; } private static Type GetElementTypeFromCollectionType(Type collectionType) { if (collectionType.IsArray) { return collectionType.GetElementType(); } if (collectionType.IsGenericType) { Type[] genericArguments = collectionType.GetGenericArguments(); if (genericArguments != null && genericArguments.Length == 1) { return genericArguments[0]; } } Type type = collectionType.GetInterfaces().FirstOrDefault((Type i) => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)); if (type != null) { return type.GetGenericArguments()[0]; } return null; } public static void MapParsedCityToTarget(object src, object dst) { if (src == null || dst == null) { return; } try { SetStringOnTarget(dst, GetStringFromSource(src, "name") ?? GetStringFromSource(src, "Name"), "name"); SetIntOnTarget(dst, (GetIntFromSource(src, "price") ?? GetIntFromSource(src, "Price")).GetValueOrDefault(-1), "price"); float[] arr = GetFloatArrayFromSource(src, "coords") ?? GetFloatArrayFromSource(src, "Coords"); SetFloatArrayOnTarget(dst, arr, "coords"); string value = GetStringFromSource(src, "targetGameObjectName") ?? GetStringFromSource(src, "target") ?? GetStringFromSource(src, "targetName"); SetStringOnTarget(dst, value, "targetGameObjectName", "target", "targetName"); string value2 = GetStringFromSource(src, "sceneName") ?? GetStringFromSource(src, "scene"); SetStringOnTarget(dst, value2, "sceneName", "scene"); string value3 = GetStringFromSource(src, "desc") ?? GetStringFromSource(src, "description") ?? GetStringFromSource(src, "descText"); SetStringOnTarget(dst, value3, "desc", "description", "descText"); SetBoolOnTarget(dst, (GetBoolFromSource(src, "visited") ?? GetBoolFromSource(src, "Visited")).GetValueOrDefault(), "visited", "Visited"); } catch (Exception ex) { TBLog.Warn("CityMappingHelpers.MapParsedCityToTarget: failed mapping city '" + GetStringFromSource(src, "name") + "': " + ex); } } public static void MapDefaultCityToTarget(object src, object dst) { if (src == null || dst == null) { return; } try { SetStringOnTarget(dst, GetStringFromSource(src, "name"), "name"); SetIntOnTarget(dst, GetIntFromSource(src, "price").GetValueOrDefault(-1), "price"); SetFloatArrayOnTarget(dst, GetFloatArrayFromSource(src, "coords"), "coords"); SetStringOnTarget(dst, GetStringFromSource(src, "targetGameObjectName") ?? GetStringFromSource(src, "target"), "targetGameObjectName", "target", "targetName"); SetStringOnTarget(dst, GetStringFromSource(src, "sceneName") ?? GetStringFromSource(src, "scene"), "sceneName", "scene"); SetStringOnTarget(dst, GetStringFromSource(src, "desc") ?? GetStringFromSource(src, "description"), "desc", "description"); SetBoolOnTarget(dst, GetBoolFromSource(src, "visited").GetValueOrDefault(), "visited", "Visited"); } catch (Exception ex) { TBLog.Warn("CityMappingHelpers.MapDefaultCityToTarget: failed mapping default city: " + ex); } } private static void SetStringOnTarget(object target, string value, params string[] candidateNames) { if (target == null) { return; } Type type = target.GetType(); foreach (string name in candidateNames) { try { PropertyInfo property = type.GetProperty(name, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite && property.PropertyType == typeof(string)) { property.SetValue(target, value); break; } FieldInfo field = type.GetField(name, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.FieldType == typeof(string)) { field.SetValue(target, value); break; } } catch { } } } private static void SetIntOnTarget(object target, int value, params string[] candidateNames) { if (target == null) { return; } Type type = target.GetType(); foreach (string name in candidateNames) { try { PropertyInfo property = type.GetProperty(name, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite && (property.PropertyType == typeof(int) || property.PropertyType == typeof(int?))) { property.SetValue(target, value); break; } FieldInfo field = type.GetField(name, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && (field.FieldType == typeof(int) || field.FieldType == typeof(int?))) { field.SetValue(target, value); break; } } catch { } } } private static void SetBoolOnTarget(object target, bool value, params string[] candidateNames) { if (target == null) { return; } Type type = target.GetType(); foreach (string name in candidateNames) { try { PropertyInfo property = type.GetProperty(name, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite && (property.PropertyType == typeof(bool) || property.PropertyType == typeof(bool?))) { property.SetValue(target, value); break; } FieldInfo field = type.GetField(name, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && (field.FieldType == typeof(bool) || field.FieldType == typeof(bool?))) { field.SetValue(target, value); break; } } catch { } } } public static string GetStringFromSource(object src, string propName) { if (src == null) { return null; } Type type = src.GetType(); PropertyInfo property = type.GetProperty(propName, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanRead && property.PropertyType == typeof(string)) { return property.GetValue(src) as string; } FieldInfo field = type.GetField(propName, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.FieldType == typeof(string)) { return field.GetValue(src) as string; } return null; } private static int? GetIntFromSource(object src, string propName) { if (src == null) { return null; } Type type = src.GetType(); PropertyInfo property = type.GetProperty(propName, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanRead) { object value = property.GetValue(src); if (value is int value2) { return value2; } try { return Convert.ToInt32(value); } catch { } } FieldInfo field = type.GetField(propName, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { object value3 = field.GetValue(src); if (value3 is int value4) { return value4; } try { return Convert.ToInt32(value3); } catch { } } return null; } private static bool? GetBoolFromSource(object src, string propName) { if (src == null) { return null; } Type type = src.GetType(); PropertyInfo property = type.GetProperty(propName, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanRead) { object value = property.GetValue(src); if (value is bool value2) { return value2; } if (value != null && bool.TryParse(value.ToString(), out var result)) { return result; } } FieldInfo field = type.GetField(propName, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { object value3 = field.GetValue(src); if (value3 is bool value4) { return value4; } if (value3 != null && bool.TryParse(value3.ToString(), out var result2)) { return result2; } } return null; } private static float[] GetFloatArrayFromSource(object src, string propName) { if (src == null) { return null; } Type type = src.GetType(); PropertyInfo property = type.GetProperty(propName, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); object obj = null; if (property != null && property.CanRead) { obj = property.GetValue(src); } else { FieldInfo field = type.GetField(propName, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { obj = field.GetValue(src); } } if (obj == null) { return null; } if (obj is float[] result) { return result; } if (obj is IEnumerable enumerable) { List<float> list = new List<float>(); foreach (object item in enumerable) { try { list.Add(Convert.ToSingle(item)); } catch { } } if (list.Count > 0) { return list.ToArray(); } } return null; } private static void SetStringMember(object target, string value, params string[] candidateNames) { if (target == null || candidateNames == null || candidateNames.Length == 0) { return; } Type type = target.GetType(); foreach (string name in candidateNames) { try { PropertyInfo property = type.GetProperty(name, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite && property.PropertyType == typeof(string)) { property.SetValue(target, value); break; } FieldInfo field = type.GetField(name, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.FieldType == typeof(string)) { field.SetValue(target, value); break; } } catch { } } } private static void SetFloatArrayOnTarget(object target, float[] arr, params string[] candidateNames) { if (target == null || candidateNames == null || candidateNames.Length == 0) { return; } Type type = target.GetType(); foreach (string name in candidateNames) { try { PropertyInfo property = type.GetProperty(name, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite && property.PropertyType == typeof(float[])) { property.SetValue(target, arr); break; } FieldInfo field = type.GetField(name, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.FieldType == typeof(float[])) { field.SetValue(target, arr); break; } } catch { } } } } public static class ConfigManager { [Serializable] public class LegacyTravelConfig { public bool enabled = true; public string currencyItem = "Silver"; public int globalTeleportPrice = 100; public Dictionary<string, LegacyCityConfig> cities = new Dictionary<string, LegacyCityConfig>(StringComparer.OrdinalIgnoreCase); } [Serializable] public class LegacyCityConfig { public bool enabled = false; public int? price = null; public float[] coords = null; public string targetGameObjectName = null; public string sceneName = null; public string desc = null; public bool visited = false; } public static LegacyTravelConfig Default() { LegacyTravelConfig legacyTravelConfig = new LegacyTravelConfig { enabled = true, currencyItem = "Silver", globalTeleportPrice = 200, cities = new Dictionary<string, LegacyCityConfig>(StringComparer.OrdinalIgnoreCase) }; legacyTravelConfig.cities["Cierzo"] = new LegacyCityConfig { enabled = true, price = 200, coords = new float[3] { 1410.3f, 9.7f, 1665.6f }, targetGameObjectName = "Cierzo", sceneName = "CierzoNewTerrain", desc = "Cierzo - example description", visited = false }; legacyTravelConfig.cities["Levant"] = new LegacyCityConfig { enabled = true, price = 200, coords = new float[3] { -55.2f, 1f, 79.3f }, targetGameObjectName = "Levant_Location", sceneName = "Levant", desc = "Levant - example description", visited = false }; legacyTravelConfig.cities["Monsoon"] = new LegacyCityConfig { enabled = true, price = 200, coords = new float[3] { 56.893f, -4.853f, 114.147f }, targetGameObjectName = "Monsoon_Location", sceneName = "Monsoon", desc = "Monsoon - example description", visited = false }; legacyTravelConfig.cities["Berg"] = new LegacyCityConfig { enabled = true, price = 200, coords = new float[3] { 1202.4f, -10f, 1378.8f }, targetGameObjectName = "Berg", sceneName = "Berg", desc = "Berg - example description", visited = false }; legacyTravelConfig.cities["Harmattan"] = new LegacyCityConfig { enabled = true, price = 200, coords = new float[3] { 93.7f, 65.4f, 767.8f }, targetGameObjectName = "Harmattan_Location", sceneName = "Harmattan", desc = "Harmattan - example description", visited = false }; legacyTravelConfig.cities["Sirocco"] = new LegacyCityConfig { enabled = false, price = 200, coords = new float[3] { 62.5f, 56.8f, -54f }, targetGameObjectName = "Sirocco_Location", sceneName = "NewSirocco", desc = "Sirocco - example description", visited = false }; return legacyTravelConfig; } publi