using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BetterPortuguese.Core;
using BetterPortuguese.Runtime;
using HarmonyLib;
using UnityEngine;
using UnityEngine.SceneManagement;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("BetterPortuguese")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0")]
[assembly: AssemblyProduct("BetterPortuguese")]
[assembly: AssemblyTitle("BetterPortuguese")]
[assembly: AssemblyVersion("0.1.0.0")]
namespace BetterPortuguese
{
[BepInPlugin("com.gumatos.betterportuguese", "BetterPortuguese", "0.1.0")]
public sealed class Plugin : BaseUnityPlugin
{
public const string Guid = "com.gumatos.betterportuguese";
private static readonly StateQueue States = new StateQueue();
private static Overlay<LocalizationEntry> overlay;
private static ISet<string> changedKeys = new HashSet<string>(StringComparer.Ordinal);
private static OnceLog limited;
private static ManualLogSource log;
private static Action poll;
private static bool firstLookup;
private static bool refreshPending;
private static float nextPoll;
private ConfigEntry<bool> active;
private ConfigEntry<bool> integrate;
private ConfigEntry<bool> diagnostic;
private ConfigEntry<Conjunto> variant;
private string lastFile;
private Harmony harmony;
private void Awake()
{
//IL_029a: Unknown result type (might be due to invalid IL or missing references)
//IL_02a4: Expected O, but got Unknown
log = ((BaseUnityPlugin)this).Logger;
limited = new OnceLog(delegate(string s)
{
log.LogWarning((object)s);
});
try
{
bool num = !File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath);
((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
active = ((BaseUnityPlugin)this).Config.Bind<bool>("Geral", "Ativo", true, "Ativa somente as camadas do BetterPortuguese.");
variant = ((BaseUnityPlugin)this).Config.Bind<Conjunto>("Geral", "Conjunto", Conjunto.Corrigido, "Corrigido (padrao) ou Original. Original deixa a localizacao vigente do jogo intacta.");
integrate = ((BaseUnityPlugin)this).Config.Bind<bool>("Geral", "IntegracaoMods", true, "Reserva a camada de textos aprovados de mods. 0.1.0: registro vazio, nenhum adaptador ativo.");
diagnostic = ((BaseUnityPlugin)this).Config.Bind<bool>("Geral", "Diagnostico", false, "Log de estado e patches, sem log por frame/chave.");
if (num)
{
((BaseUnityPlugin)this).Config.Save();
}
((BaseUnityPlugin)this).Config.SaveOnConfigSet = true;
((BaseUnityPlugin)this).Config.SettingChanged += delegate
{
QueueEntries();
};
poll = PollFile;
QueueEntries();
PollFile();
}
catch (Exception ex)
{
poll = null;
States.Enqueue(new Settings(active: false, Conjunto.Corrigido, integrateMods: true, diagnostic: false, valid: false));
log.LogError((object)("BetterPortuguese: config indisponivel, overrides desativados: " + ex.Message));
}
try
{
Catalog catalog = Resources.Load(typeof(Plugin).Assembly);
changedKeys = new HashSet<string>(catalog.Changed.Keys, StringComparer.Ordinal);
overlay = new Overlay<LocalizationEntry>(catalog, Bindings.Equal, Bindings.Clone, limited);
log.LogInfo((object)("BetterPortuguese 0.1.0: snapshots completos; " + catalog.Original.Records.Count + " registros; " + catalog.Changed.Count + " chaves corrigidas aprovadas."));
}
catch (Exception ex2)
{
overlay = null;
log.LogError((object)("BetterPortuguese: camada base desativada: " + ex2.Message));
}
try
{
log.LogInfo((object)("BetterPortuguese: registry de mods = " + Resources.LoadMods(typeof(Plugin).Assembly) + "; nenhum adaptador ativo / manuais sem traducao aprovada."));
}
catch (Exception ex3)
{
log.LogWarning((object)("BetterPortuguese: camada de mods desativada, base independente: " + ex3.Message));
}
harmony = new Harmony("com.gumatos.betterportuguese");
Install("consulta da lista", () => Bindings.ListTarget(), "ListPostfix");
Install("fila/config/refresh", () => Bindings.Exact(typeof(GUIManager), "Update", typeof(void), isStatic: false, Type.EmptyTypes), "MainThreadPostfix");
log.LogInfo((object)"BetterPortuguese 0.1.0 carregado.");
}
private void Install(string label, Func<MethodInfo> target, string patch)
{
//IL_0021: Unknown result type (might be due to invalid IL or missing references)
//IL_002e: Expected O, but got Unknown
try
{
MethodInfo methodInfo = target();
harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin).GetMethod(patch, BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
Patches patchInfo = Harmony.GetPatchInfo((MethodBase)methodInfo);
int num = 0;
foreach (Patch postfix in patchInfo.Postfixes)
{
if (postfix.owner == "com.gumatos.betterportuguese")
{
num++;
}
}
if (num != 1)
{
throw new InvalidOperationException("Expected one owned postfix, got " + num);
}
log.LogInfo((object)("BetterPortuguese: patch " + label + " registrado (owner=com.gumatos.betterportuguese, postfixes proprios=" + num + ")."));
foreach (Patch postfix2 in patchInfo.Postfixes)
{
if (postfix2.owner != "com.gumatos.betterportuguese")
{
limited.Write("Outro postfix no alvo " + methodInfo.Name + ": " + postfix2.owner + "; nao ha garantia contra mudancas posteriores.");
}
}
}
catch (Exception ex)
{
log.LogError((object)("BetterPortuguese: alvo " + label + " indisponivel: " + ex.Message));
if (patch == "MainThreadPostfix")
{
States.Enqueue(new Settings(active: false, Conjunto.Corrigido, integrateMods: true, diagnostic: false, valid: false));
}
}
}
private void QueueEntries()
{
States.Enqueue(new Settings(active.Value, variant.Value, integrate.Value, diagnostic.Value));
}
private void PollFile()
{
try
{
string text = File.ReadAllText(((BaseUnityPlugin)this).Config.ConfigFilePath);
if (!(text == lastFile))
{
lastFile = text;
Settings settings = ConfigReader.Parse(text, new Settings());
bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet;
try
{
((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
((BaseUnityPlugin)this).Config.Reload();
}
finally
{
((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet;
}
States.Enqueue(settings);
}
}
catch (Exception ex)
{
States.Enqueue(new Settings(active: false, Conjunto.Corrigido, integrateMods: true, diagnostic: false, valid: false));
limited.Write("Config invalida/ilegivel; overrides desativados: " + ex.GetType().Name + ": " + ex.Message);
lastFile = null;
}
}
private static void ListPostfix(string original, LanguageChoice languageChoice, bool fallbackToTranslatorKey, ref List<LocalizationEntry> __result)
{
//IL_0033: Unknown result type (might be due to invalid IL or missing references)
//IL_0036: Invalid comparison between Unknown and I4
//IL_0038: Unknown result type (might be due to invalid IL or missing references)
//IL_003f: Invalid comparison between Unknown and I4
List<LocalizationEntry> list = __result;
try
{
if (!firstLookup)
{
firstLookup = true;
ManualLogSource obj = log;
if (obj != null)
{
obj.LogInfo((object)"BetterPortuguese: gancho da lista vivo; selecao de genero/fallback permanece no jogo.");
}
}
__result = overlay?.Resolve(original, (int)languageChoice == 10, (int)OptionsManager.CurrentLanguage == 10, States.Current, list) ?? list;
}
catch (Exception ex)
{
__result = list;
limited?.Write("Postfix preservou retorno: " + ex.GetType().Name);
}
}
private static void MainThreadPostfix()
{
//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
try
{
if (Time.realtimeSinceStartup >= nextPoll)
{
nextPoll = Time.realtimeSinceStartup + 1f;
poll?.Invoke();
}
Settings current = States.Current;
if (States.ApplyPending())
{
refreshPending |= RefreshPlan.Needed(current, States.Current);
Settings current2 = States.Current;
ManualLogSource obj = log;
if (obj != null)
{
obj.LogInfo((object)("BetterPortuguese: estado aplicado na thread principal: " + ((current2.Valid && current2.Active) ? current2.Variant.ToString() : "desativado") + ", IntegracaoMods=" + current2.IntegrateMods + " (0 adaptadores)."));
}
}
if (!refreshPending)
{
return;
}
Scene activeScene = SceneManager.GetActiveScene();
if (!((Scene)(ref activeScene)).IsValid() || (Object)(object)OptionsManager.instance == (Object)null)
{
return;
}
refreshPending = false;
int num = UiRefresh.Apply(changedKeys, limited);
if (States.Current.Diagnostic)
{
ManualLogSource obj2 = log;
if (obj2 != null)
{
obj2.LogInfo((object)("BetterPortuguese: refresh dirigido de " + num + " texto(s) aprovado(s); sem LocalizeUI global; tooltips no proximo hover."));
}
}
}
catch (Exception ex)
{
limited?.Write("Refresh/config falhou; retorno do jogo preservado: " + ex.GetType().Name);
}
}
}
}
namespace BetterPortuguese.Runtime
{
internal static class Bindings
{
public static MethodInfo Exact(Type type, string name, Type result, bool isStatic, params Type[] parameters)
{
BindingFlags bindingAttr = (BindingFlags)(0x30 | (isStatic ? 8 : 4) | 2);
MethodInfo method = type.GetMethod(name, bindingAttr, null, parameters, null);
if (method == null || method.IsStatic != isStatic || method.ReturnType != result || method.ContainsGenericParameters)
{
throw new MissingMethodException(type.FullName, name);
}
return method;
}
public static MethodInfo ListTarget()
{
MethodInfo methodInfo = Exact(typeof(OptionsManager), "GetLocalizationEntryList", typeof(List<LocalizationEntry>), true, typeof(string), typeof(LanguageChoice), typeof(bool));
ParameterInfo[] parameters = methodInfo.GetParameters();
string[] array = new string[3] { "original", "languageChoice", "fallbackToTranslatorKey" };
for (int i = 0; i < array.Length; i++)
{
if (parameters[i].Name != array[i])
{
throw new MissingMethodException("Localization parameter names changed");
}
}
return methodInfo;
}
public static bool Equal(LocalizationEntry game, Entry baseline)
{
//IL_007c: Unknown result type (might be due to invalid IL or missing references)
//IL_0087: Invalid comparison between Unknown and I4
if (game != null && game.Value == baseline.Value && game.Key == baseline.Key && game.TranslatorKey == baseline.TranslatorKey && game.Feminine == baseline.Feminine && game.Masculine == baseline.Masculine && game.Neutral == baseline.Neutral && (int)game.LanguageGender == baseline.LanguageGender && game.CanBeGendered == baseline.CanBeGendered)
{
return game.NeedsGenderInfo == baseline.NeedsGenderInfo;
}
return false;
}
public static LocalizationEntry Clone(Entry entry)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_0011: Unknown result type (might be due to invalid IL or missing references)
//IL_001d: 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)
//IL_0035: Unknown result type (might be due to invalid IL or missing references)
//IL_0041: Unknown result type (might be due to invalid IL or missing references)
//IL_004d: Unknown result type (might be due to invalid IL or missing references)
//IL_0054: Unknown result type (might be due to invalid IL or missing references)
//IL_0059: Unknown result type (might be due to invalid IL or missing references)
//IL_0065: Unknown result type (might be due to invalid IL or missing references)
//IL_0072: Expected O, but got Unknown
return new LocalizationEntry
{
Value = entry.Value,
Key = entry.Key,
TranslatorKey = entry.TranslatorKey,
Feminine = entry.Feminine,
Masculine = entry.Masculine,
Neutral = entry.Neutral,
LanguageGender = (LanguageGender)entry.LanguageGender,
CanBeGendered = entry.CanBeGendered,
NeedsGenderInfo = entry.NeedsGenderInfo
};
}
}
internal static class ConfigReader
{
public static Settings Parse(string text, Settings defaults)
{
Dictionary<string, string> values = new Dictionary<string, string>(StringComparer.Ordinal);
string text2 = "";
string[] array = text.Split('\n');
for (int i = 0; i < array.Length; i++)
{
string text3 = array[i].Trim();
if (text3.Length == 0 || text3.StartsWith("#") || text3.StartsWith(";"))
{
continue;
}
if (text3.StartsWith("[") && text3.EndsWith("]"))
{
text2 = text3.Substring(1, text3.Length - 2);
}
else if (!(text2 != "Geral"))
{
int num = text3.IndexOf('=');
if (num < 0)
{
throw new FormatException("Malformed BetterPortuguese config line");
}
string text4 = text3.Substring(0, num).Trim();
if (text4 != "Ativo" && text4 != "Conjunto" && text4 != "IntegracaoMods" && text4 != "Diagnostico")
{
throw new FormatException("Unknown BetterPortuguese setting");
}
if (values.ContainsKey(text4))
{
throw new FormatException("Duplicate BetterPortuguese setting");
}
values.Add(text4, text3.Substring(num + 1).Trim());
}
}
Conjunto variant = defaults.Variant;
if (values.TryGetValue("Conjunto", out var value))
{
if (value != "Original" && value != "Corrigido")
{
throw new FormatException("Conjunto must be Original or Corrigido");
}
variant = ((!(value == "Original")) ? Conjunto.Corrigido : Conjunto.Original);
}
return new Settings(Boolean("Ativo", defaults.Active), variant, Boolean("IntegracaoMods", defaults.IntegrateMods), Boolean("Diagnostico", defaults.Diagnostic));
bool Boolean(string key, bool fallback)
{
if (!values.TryGetValue(key, out var value2))
{
return fallback;
}
if (!bool.TryParse(value2, out var result))
{
throw new FormatException("Invalid " + key);
}
return result;
}
}
}
internal static class UiRefresh
{
private static FieldInfo originals;
public static int Apply(ISet<string> changed, OnceLog log)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0007: Invalid comparison between Unknown and I4
if ((int)OptionsManager.CurrentLanguage != 10 || changed.Count == 0)
{
return 0;
}
if (originals == null)
{
originals = typeof(OptionsManager).GetField("OriginalTextValues", BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.NonPublic);
if (originals == null || originals.FieldType != typeof(Dictionary<TextOrTMPText, string>))
{
originals = null;
throw new MissingFieldException("OptionsManager.OriginalTextValues dictionary unavailable; reopen UI to refresh.");
}
}
Dictionary<TextOrTMPText, string> dictionary = (Dictionary<TextOrTMPText, string>)originals.GetValue(null);
if (dictionary == null)
{
return 0;
}
int num = 0;
KeyValuePair<TextOrTMPText, string>[] array = RefreshPlan.Select(dictionary, changed);
for (int i = 0; i < array.Length; i++)
{
KeyValuePair<TextOrTMPText, string> keyValuePair = array[i];
try
{
if (keyValuePair.Key != null && !((Object)(object)keyValuePair.Key.GameObject == (Object)null))
{
OptionsManager.LocalizeUIText(keyValuePair.Key);
num++;
}
}
catch (Exception ex)
{
log?.Write("Refresh de texto aprovado preservou demais alvos: " + ex.GetType().Name);
}
}
return num;
}
}
}
namespace BetterPortuguese.Core
{
internal sealed class Entry
{
internal static readonly string[] Fields = new string[9] { "Value", "Key", "TranslatorKey", "Feminine", "Masculine", "Neutral", "LanguageGender", "CanBeGendered", "NeedsGenderInfo" };
private readonly IReadOnlyDictionary<string, object> values;
public string Value => (string)values["Value"];
public string Key => (string)values["Key"];
public string TranslatorKey => (string)values["TranslatorKey"];
public string Feminine => (string)values["Feminine"];
public string Masculine => (string)values["Masculine"];
public string Neutral => (string)values["Neutral"];
public int LanguageGender => JsonShape.Int(values["LanguageGender"]);
public bool CanBeGendered => (bool)values["CanBeGendered"];
public bool NeedsGenderInfo => (bool)values["NeedsGenderInfo"];
public object this[string field] => values[field];
public Entry(Dictionary<string, object> obj)
{
JsonShape.Fields(obj, Fields);
foreach (string item in Fields.Take(6))
{
JsonShape.Text(obj[item], nullable: true);
}
int num = JsonShape.Int(obj["LanguageGender"]);
if (num < 0 || num > 6)
{
throw new FormatException("Unknown LanguageGender");
}
JsonShape.Bool(obj["CanBeGendered"]);
JsonShape.Bool(obj["NeedsGenderInfo"]);
values = new ReadOnlyDictionary<string, object>(new Dictionary<string, object>(obj, StringComparer.Ordinal));
}
public bool Same(Entry other)
{
if (other != null)
{
return Fields.All((string f) => object.Equals(this[f], other[f]));
}
return false;
}
public static bool SameLists(IReadOnlyList<Entry> a, IReadOnlyList<Entry> b)
{
if (a == null || b == null)
{
if (a == null)
{
return b == null;
}
return false;
}
if (a.Count != b.Count)
{
return false;
}
for (int i = 0; i < a.Count; i++)
{
if (!a[i].Same(b[i]))
{
return false;
}
}
return true;
}
}
internal sealed class Record
{
public string Id { get; }
public string Key { get; }
public bool PresentInSource { get; }
public IReadOnlyList<Entry> Entries { get; }
public Record(Dictionary<string, object> obj)
{
JsonShape.Fields(obj, "id", "key", "presentInSource", "entries");
Id = JsonShape.Text(obj["id"]);
Key = JsonShape.Text(obj["key"]);
if (!Regex.IsMatch(Id, "^LANG-[0-9]{5}$"))
{
throw new FormatException("Invalid editorial ID");
}
PresentInSource = JsonShape.Bool(obj["presentInSource"]);
Entries = ((obj["entries"] == null) ? null : new ReadOnlyCollection<Entry>((from e in JsonShape.Array(obj["entries"])
select new Entry(JsonShape.Object(e))).ToList()));
}
}
internal sealed class Snapshot
{
private readonly object source;
private readonly List<object> manual;
public string Variant { get; }
public IReadOnlyDictionary<string, Record> Records { get; }
public int ManualCount => manual.Count;
private Snapshot(Dictionary<string, object> obj, string expectedVariant)
{
JsonShape.Fields(obj, "schemaVersion", "locale", "variant", "source", "records", "manualRecords");
if (JsonShape.Int(obj["schemaVersion"]) != 1 || JsonShape.Text(obj["locale"]) != "pt-BR" || JsonShape.Text(obj["variant"]) != expectedVariant)
{
throw new FormatException("Unsupported snapshot");
}
Variant = expectedVariant;
Dictionary<string, object> dictionary = JsonShape.Object(obj["source"]);
JsonShape.Fields(dictionary, "taskId", "artifactSha256", "resourcePath", "resourcePathId", "resourceAssetSha256", "assemblySha256", "canonicalMemberSha256");
string[] array = new string[4] { "artifactSha256", "resourceAssetSha256", "assemblySha256", "canonicalMemberSha256" };
foreach (string key in array)
{
if (!Regex.IsMatch(JsonShape.Text(dictionary[key]), "^[a-f0-9]{64}$"))
{
throw new FormatException("Invalid source hash");
}
}
JsonShape.Text(dictionary["taskId"]);
JsonShape.Text(dictionary["resourcePath"]);
JsonShape.Int(dictionary["resourcePathId"]);
source = dictionary;
Dictionary<string, Record> dictionary2 = new Dictionary<string, Record>(StringComparer.Ordinal);
HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
string text = null;
foreach (object item in JsonShape.Array(obj["records"]))
{
Record record = new Record(JsonShape.Object(item));
if (!hashSet.Add(record.Id) || dictionary2.ContainsKey(record.Key))
{
throw new FormatException("Duplicate ID/key");
}
if (text != null && string.CompareOrdinal(text, record.Id) >= 0)
{
throw new FormatException("Records not ordered by ID");
}
text = record.Id;
if (expectedVariant == "Original" && record.PresentInSource != (record.Entries != null))
{
throw new FormatException("Presence/entries mismatch");
}
dictionary2.Add(record.Key, record);
}
Records = new ReadOnlyDictionary<string, Record>(dictionary2);
manual = JsonShape.Array(obj["manualRecords"]);
HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal);
foreach (object item2 in manual)
{
Dictionary<string, object> dictionary3 = JsonShape.Object(item2);
JsonShape.Fields(dictionary3, "id", "sourceFile", "sourcePathId", "englishText", "originalEntries", "ptBRText");
if (!hashSet2.Add(JsonShape.Text(dictionary3["id"])))
{
throw new FormatException("Duplicate manual ID");
}
JsonShape.Text(dictionary3["sourceFile"]);
JsonShape.Int(dictionary3["sourcePathId"]);
JsonShape.Text(dictionary3["englishText"]);
JsonShape.Text(dictionary3["ptBRText"], nullable: true);
foreach (object item3 in JsonShape.Array(dictionary3["originalEntries"]))
{
Dictionary<string, object> dictionary4 = JsonShape.Object(item3);
JsonShape.Fields(dictionary4, "Language", "Text");
JsonShape.Int(dictionary4["Language"]);
JsonShape.Text(dictionary4["Text"], nullable: true);
}
}
}
public static Snapshot Parse(string json, string variant)
{
return new Snapshot(JsonShape.Object(StrictJson.Parse(json)), variant);
}
public void ValidateIdentity(Snapshot corrected)
{
if (Records.Count != corrected.Records.Count || !JsonShape.Equal(source, corrected.source) || !JsonShape.Equal(manual, corrected.manual))
{
throw new FormatException("Snapshot identity/manual drift (manual adapter unsupported)");
}
foreach (KeyValuePair<string, Record> record in Records)
{
if (!corrected.Records.TryGetValue(record.Key, out var value) || record.Value.Id != value.Id || record.Value.PresentInSource != value.PresentInSource)
{
throw new FormatException("Immutable ID/key/presence changed");
}
}
}
}
internal sealed class Catalog
{
public Snapshot Original { get; }
public Snapshot Corrected { get; }
public IReadOnlyDictionary<string, Record> Changed { get; }
private Catalog(Snapshot original, Snapshot corrected, List<object> diff)
{
Original = original;
Corrected = corrected;
original.ValidateIdentity(corrected);
Dictionary<string, Dictionary<string, object>> dictionary = new Dictionary<string, Dictionary<string, object>>(StringComparer.Ordinal);
Dictionary<string, EntryAddition> dictionary2 = new Dictionary<string, EntryAddition>(StringComparer.Ordinal);
foreach (object item in diff)
{
Dictionary<string, object> dictionary3 = JsonShape.Object(item);
if (dictionary3.ContainsKey("operation"))
{
EntryAddition value = EntryAddition.Parse(dictionary3, original);
string key = JsonShape.Text(dictionary3["key"]);
if (dictionary2.ContainsKey(key))
{
throw new FormatException("Duplicate addEntry target");
}
dictionary2.Add(key, value);
continue;
}
JsonShape.Fields(dictionary3, "id", "key", "entryIndex", "field", "before", "after", "approvalTaskId");
string key2 = JsonShape.Text(dictionary3["key"]);
string text = JsonShape.Text(dictionary3["field"]);
int num = JsonShape.Int(dictionary3["entryIndex"]);
if (!Regex.IsMatch(JsonShape.Text(dictionary3["approvalTaskId"]), "^t_[a-f0-9]{8}$"))
{
throw new FormatException("Approval task missing/invalid");
}
if (!new string[4] { "Value", "Feminine", "Masculine", "Neutral" }.Contains(text))
{
throw new FormatException("Unsupported diff field/operation");
}
if (!original.Records.TryGetValue(key2, out var value2) || JsonShape.Text(dictionary3["id"]) != value2.Id || value2.Entries == null || num < 0 || num >= value2.Entries.Count)
{
throw new FormatException("Invalid diff target");
}
string text2 = JsonShape.Text(dictionary3["before"], nullable: true);
string text3 = JsonShape.Text(dictionary3["after"], nullable: true);
if (text2 == text3 || !object.Equals(value2.Entries[num][text], text2))
{
throw new FormatException("Invalid diff before/after");
}
string[] array = TokenGuard.Inventory(text2).ToArray();
string[] array2 = TokenGuard.Inventory(text3).ToArray();
if (!array.SequenceEqual<string>(array2, StringComparer.Ordinal))
{
TokenGuard.RegisterException(JsonShape.Text(dictionary3["approvalTaskId"]), "0.1.0", array, array2);
}
string key3 = value2.Id + ":" + num + ":" + text;
if (dictionary.ContainsKey(key3))
{
throw new FormatException("Duplicate diff target");
}
dictionary.Add(key3, dictionary3);
}
Dictionary<string, Record> dictionary4 = new Dictionary<string, Record>(StringComparer.Ordinal);
int num2 = 0;
foreach (KeyValuePair<string, Record> record2 in original.Records)
{
Record value3 = record2.Value;
Record record = corrected.Records[record2.Key];
if (dictionary2.TryGetValue(record2.Key, out var value4))
{
if (record.Entries == null || record.Entries.Count != 1 || !value4.After.Same(record.Entries[0]))
{
throw new FormatException("addEntry not exactly reflected by corrected snapshot: " + value3.Id);
}
num2++;
dictionary4.Add(record2.Key, record);
}
else
{
if (Entry.SameLists(value3.Entries, record.Entries))
{
continue;
}
if (value3.Entries == null || record.Entries == null || value3.Entries.Count != record.Entries.Count)
{
throw new FormatException("List/absence operations require a newer validated loader");
}
for (int i = 0; i < value3.Entries.Count; i++)
{
string[] fields = Entry.Fields;
foreach (string text4 in fields)
{
if (!object.Equals(value3.Entries[i][text4], record.Entries[i][text4]))
{
if (!dictionary.TryGetValue(value3.Id + ":" + i + ":" + text4, out var value5) || !object.Equals(value5["after"], record.Entries[i][text4]))
{
throw new FormatException("Unapproved change");
}
num2++;
}
}
}
dictionary4.Add(record2.Key, record);
}
}
if (num2 != dictionary.Count + dictionary2.Count)
{
throw new FormatException("Diff not reflected by corrected snapshot");
}
Changed = new ReadOnlyDictionary<string, Record>(dictionary4);
}
public static Catalog Parse(string original, string corrected, string diff)
{
return new Catalog(Snapshot.Parse(original, "Original"), Snapshot.Parse(corrected, "Corrigido"), JsonShape.Array(StrictJson.Parse(diff)));
}
}
internal static class TokenGuard
{
private static readonly Regex Tokens = new Regex("\\[\\[[^\\]]*\\]\\]|\\[[^\\]]*\\]|\\{[^}]*\\}|\\*[0-9]+|<[^>]*>", RegexOptions.CultureInvariant);
private static volatile List<(string taskId, string version, string[] beforeTokens, string[] afterTokens)> validatedExceptions;
private static readonly object validatedExceptionsLock = new object();
private static List<(string taskId, string version, string[] beforeTokens, string[] afterTokens)> ValidatedExceptions
{
get
{
if (validatedExceptions == null)
{
lock (validatedExceptionsLock)
{
if (validatedExceptions == null)
{
validatedExceptions = new List<(string, string, string[], string[])>();
}
}
}
return validatedExceptions;
}
}
internal static IEnumerable<string> Inventory(string s)
{
return (from Match m in Tokens.Matches(s ?? "")
select m.Value).OrderBy<string, string>((string x) => x, StringComparer.Ordinal);
}
public static void Check(string before, string after)
{
string[] array = Inventory(before).ToArray();
string[] second = Inventory(after).ToArray();
if (array.SequenceEqual<string>(second, StringComparer.Ordinal))
{
return;
}
foreach (var validatedException in ValidatedExceptions)
{
if (validatedException.beforeTokens.SequenceEqual<string>(array, StringComparer.Ordinal) && validatedException.afterTokens.SequenceEqual<string>(second, StringComparer.Ordinal))
{
return;
}
}
throw new FormatException("Functional tokens/tags changed; explicit validated exception unsupported in 0.1.0");
}
public static void RegisterException(string taskId, string version, string[] beforeTokens, string[] afterTokens)
{
if (!Regex.IsMatch(taskId, "^t_[a-f0-9]{8}$"))
{
throw new FormatException("Invalid exception taskId");
}
if (string.IsNullOrEmpty(version))
{
throw new FormatException("Invalid exception version");
}
if (beforeTokens == null || afterTokens == null)
{
throw new FormatException("Null inventory in exception");
}
if (beforeTokens.SequenceEqual<string>(afterTokens, StringComparer.Ordinal))
{
throw new FormatException("Exception before/after identical");
}
ValidatedExceptions.Add((taskId, version, beforeTokens, afterTokens));
}
}
internal sealed class EntryAddition
{
public Entry After { get; }
private EntryAddition(Entry after)
{
After = after;
}
public static EntryAddition Parse(Dictionary<string, object> d, Snapshot original)
{
JsonShape.Fields(d, "operation", "id", "key", "entryIndex", "before", "after", "approvalTaskId");
if (JsonShape.Text(d["operation"]) != "addEntry")
{
throw new FormatException("Unsupported diff operation");
}
string text = JsonShape.Text(d["id"]);
string text2 = JsonShape.Text(d["key"]);
string text3 = "addEntry " + text + ": ";
if (!Regex.IsMatch(JsonShape.Text(d["approvalTaskId"]), "^t_[a-f0-9]{8}$"))
{
throw new FormatException(text3 + "Approval task missing/invalid");
}
if (!original.Records.TryGetValue(text2, out var value) || value.Id != text)
{
throw new FormatException(text3 + "Unknown ID/key");
}
if (JsonShape.Int(d["entryIndex"]) != 0)
{
throw new FormatException(text3 + "Only entryIndex 0 supported");
}
if (value.Entries != null && value.Entries.Count != 0)
{
throw new FormatException(text3 + "Entry already exists; populated baseline");
}
bool num;
if (value.Entries != null)
{
if (d["before"] == null)
{
goto IL_0189;
}
num = JsonShape.Array(d["before"]).Count != 0;
}
else
{
num = d["before"] != null;
}
if (!num)
{
Entry entry = new Entry(JsonShape.Object(d["after"]));
if (string.IsNullOrWhiteSpace(entry.Value))
{
throw new FormatException(text3 + "Value must be nonblank");
}
if (entry.Key != text2 || entry.TranslatorKey != null || entry.Feminine != null || entry.Masculine != null || entry.Neutral != null || entry.LanguageGender != 0 || entry.CanBeGendered || entry.NeedsGenderInfo)
{
throw new FormatException(text3 + "Unsupported metadata; expected neutral Value-only entry");
}
if (!TokenGuard.Inventory(text2).SequenceEqual<string>(TokenGuard.Inventory(entry.Value), StringComparer.Ordinal))
{
throw new FormatException(text3 + "Functional tokens/tags changed against source key");
}
return new EntryAddition(entry);
}
goto IL_0189;
IL_0189:
throw new FormatException(text3 + "Baseline null/empty mismatch");
}
}
public enum Conjunto
{
Original,
Corrigido
}
internal sealed class Settings
{
public bool Active { get; }
public Conjunto Variant { get; }
public bool IntegrateMods { get; }
public bool Diagnostic { get; }
public bool Valid { get; }
public Settings(bool active = true, Conjunto variant = Conjunto.Corrigido, bool integrateMods = true, bool diagnostic = false, bool valid = true)
{
Active = active;
Variant = variant;
IntegrateMods = integrateMods;
Diagnostic = diagnostic;
Valid = valid && Enum.IsDefined(typeof(Conjunto), variant);
}
}
internal sealed class StateQueue
{
private Settings current = new Settings(active: false);
private Settings pending;
public Settings Current => Volatile.Read(in current);
public void Enqueue(Settings settings)
{
Interlocked.Exchange(ref pending, settings ?? new Settings(active: false, Conjunto.Corrigido, integrateMods: true, diagnostic: false, valid: false));
}
public bool ApplyPending()
{
Settings settings = Interlocked.Exchange(ref pending, null);
if (settings == null)
{
return false;
}
Settings settings2 = Current;
if (settings2.Active == settings.Active && settings2.Variant == settings.Variant && settings2.IntegrateMods == settings.IntegrateMods && settings2.Diagnostic == settings.Diagnostic && settings2.Valid == settings.Valid)
{
return false;
}
Interlocked.Exchange(ref current, settings);
return true;
}
}
internal sealed class OnceLog
{
private readonly HashSet<string> seen = new HashSet<string>(StringComparer.Ordinal);
private readonly Action<string> log;
private readonly int maximum;
public int Count
{
get
{
lock (seen)
{
return seen.Count;
}
}
}
public OnceLog(Action<string> log, int maximum = 128)
{
this.log = log;
this.maximum = maximum;
}
public void Write(string reason)
{
bool flag;
lock (seen)
{
flag = seen.Count < maximum && seen.Add(reason);
}
if (flag)
{
try
{
log?.Invoke(reason);
}
catch
{
}
}
}
}
internal sealed class Overlay<T>
{
private readonly Catalog catalog;
private readonly Func<T, Entry, bool> equal;
private readonly Func<Entry, T> clone;
private readonly OnceLog log;
public Overlay(Catalog catalog, Func<T, Entry, bool> equal, Func<Entry, T> clone, OnceLog log)
{
this.catalog = catalog;
this.equal = equal;
this.clone = clone;
this.log = log;
}
public List<T> Resolve(string key, bool requestedPortuguese, bool effectivePortuguese, Settings settings, List<T> incoming)
{
try
{
if (catalog == null || settings == null || !settings.Valid || !settings.Active || settings.Variant != Conjunto.Corrigido || !requestedPortuguese || !effectivePortuguese || key == null || !catalog.Changed.TryGetValue(key, out var value))
{
return incoming;
}
IReadOnlyList<Entry> entries = catalog.Original.Records[key].Entries;
bool flag = ((entries == null) ? (incoming == null) : (incoming != null && entries.Count == incoming.Count));
if (flag && entries != null)
{
for (int i = 0; i < entries.Count; i++)
{
if (!equal(incoming[i], entries[i]))
{
flag = false;
break;
}
}
}
if (!flag)
{
log?.Write("Conflito de baseline: " + value.Id);
return incoming;
}
if (value.Entries == null)
{
return incoming;
}
List<T> list = new List<T>(value.Entries.Count);
foreach (Entry entry in value.Entries)
{
list.Add(clone(entry));
}
return list;
}
catch (Exception ex)
{
log?.Write("Overlay preservou retorno: " + ex.GetType().Name);
return incoming;
}
}
}
internal static class RefreshPlan
{
private static bool Effective(Settings s)
{
if (s != null && s.Valid && s.Active)
{
return s.Variant == Conjunto.Corrigido;
}
return false;
}
public static bool Needed(Settings before, Settings after)
{
return Effective(before) != Effective(after);
}
public static KeyValuePair<T, string>[] Select<T>(IEnumerable<KeyValuePair<T, string>> registered, ISet<string> changed)
{
return registered.Where((KeyValuePair<T, string> p) => p.Value != null && changed.Contains(p.Value)).ToArray();
}
}
public static class ResourceHashes
{
public static readonly IReadOnlyDictionary<string, string> Expected = new Dictionary<string, string>
{
{ "correcoes.diff.json", "78d520a40d0ed3187cb913f0e719fcd9f4e6d1cfa3e272d83625e7d42bbf19e2" },
{ "manifest.json", "3408218505d8eda9ec2b9ea10b82460d00fc829e6d6f236f801a6455bcd84d8b" },
{ "pendencias.json", "778e0f9e0d269b15802ab620a18a5ffec1768aeb761428bbd0de75d66fb6dfaf" },
{ "pt-BR.corrigido.json", "88e9cb7f2e606962664f1dcce9f7083bd0fba7c2387479252c17179e446cfe68" },
{ "pt-BR.mods.json", "cb7e2437cf1ff68497c2223be77c10f1847ae8314d904943a53ceb6dac1aff65" },
{ "pt-BR.original.json", "80d48629b6f0d6681badee323ee97201d7c445876e9d9d17a6ae0dadab9f2224" },
{ "upstream.BP3.manifest.json", "2f9e7d57198881d1c2ec31b3332cdbe7e9162d24b34b5a7665299e61888c870e" }
};
}
internal static class Resources
{
public static string Sha256(byte[] bytes)
{
using SHA256 sHA = SHA256.Create();
return BitConverter.ToString(sHA.ComputeHash(bytes)).Replace("-", "").ToLowerInvariant();
}
public static string Decode(byte[] bytes)
{
return new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetString(bytes);
}
public static string Read(Assembly assembly, string name)
{
using Stream stream = assembly.GetManifestResourceStream("BetterPortuguese.Data." + name) ?? throw new InvalidDataException("Missing embedded resource " + name);
using MemoryStream memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
byte[] bytes = memoryStream.ToArray();
if (!ResourceHashes.Expected.TryGetValue(name, out var value) || Sha256(bytes) != value)
{
throw new InvalidDataException("Resource SHA-256 mismatch " + name);
}
return Decode(bytes);
}
public static Catalog Load(Assembly assembly)
{
foreach (KeyValuePair<string, string> item in ResourceHashes.Expected)
{
if (item.Key != "pt-BR.mods.json")
{
Read(assembly, item.Key);
}
}
return Catalog.Parse(Read(assembly, "pt-BR.original.json"), Read(assembly, "pt-BR.corrigido.json"), Read(assembly, "correcoes.diff.json"));
}
public static int LoadMods(Assembly assembly)
{
Dictionary<string, object> dictionary = JsonShape.Object(StrictJson.Parse(Read(assembly, "pt-BR.mods.json")));
JsonShape.Fields(dictionary, "schemaVersion", "locale", "records");
if (JsonShape.Int(dictionary["schemaVersion"]) != 1 || JsonShape.Text(dictionary["locale"]) != "pt-BR")
{
throw new FormatException("Invalid mod registry");
}
int count = JsonShape.Array(dictionary["records"]).Count;
if (count != 0)
{
throw new NotSupportedException("Versioned mod adapters require an approved dataset and a newer plugin");
}
return count;
}
}
internal sealed class StrictJson
{
private readonly string text;
private int pos;
private StrictJson(string text)
{
this.text = text ?? throw new ArgumentNullException("text");
}
public static object Parse(string text)
{
StrictJson strictJson = new StrictJson(text);
object result = strictJson.Value(0);
strictJson.White();
if (strictJson.pos != text.Length)
{
throw strictJson.Error("Trailing content");
}
return result;
}
private FormatException Error(string reason)
{
return new FormatException(reason + " at JSON offset " + pos);
}
private void White()
{
while (pos < text.Length && " \t\r\n".IndexOf(text[pos]) >= 0)
{
pos++;
}
}
private bool Take(char c)
{
White();
if (pos < text.Length && text[pos] == c)
{
pos++;
return true;
}
return false;
}
private void Need(char c)
{
if (!Take(c))
{
throw Error("Expected " + c);
}
}
private object Value(int depth)
{
if (depth > 64)
{
throw Error("Maximum nesting exceeded");
}
White();
if (pos >= this.text.Length)
{
throw Error("Unexpected end");
}
switch (this.text[pos])
{
case '"':
return String();
case '{':
{
pos++;
Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
if (Take('}'))
{
return dictionary;
}
do
{
White();
if (pos >= this.text.Length || this.text[pos] != '"')
{
throw Error("Expected member name");
}
string text2 = String();
Need(':');
object value = Value(depth + 1);
if (dictionary.ContainsKey(text2))
{
throw Error("Duplicate member " + text2);
}
dictionary.Add(text2, value);
}
while (Take(','));
Need('}');
return dictionary;
}
case '[':
{
pos++;
List<object> list = new List<object>();
if (Take(']'))
{
return list;
}
do
{
list.Add(Value(depth + 1));
}
while (Take(','));
Need(']');
return list;
}
default:
{
string[] array = new string[3] { "true", "false", "null" };
foreach (string text in array)
{
if (pos + text.Length <= this.text.Length && string.CompareOrdinal(this.text, pos, text, 0, text.Length) == 0)
{
pos += text.Length;
if (!(text == "null"))
{
return text == "true";
}
return null;
}
}
int num = pos;
if (this.text[pos] == '-')
{
pos++;
}
if (pos >= this.text.Length)
{
throw Error("Number truncated");
}
if (this.text[pos] == '0')
{
pos++;
}
else
{
if (this.text[pos] < '1' || this.text[pos] > '9')
{
throw Error("Invalid value");
}
Digits();
}
if (pos < this.text.Length && this.text[pos] == '.')
{
pos++;
int num2 = pos;
Digits();
if (num2 == pos)
{
throw Error("Fraction missing");
}
}
if (pos < this.text.Length && (this.text[pos] == 'e' || this.text[pos] == 'E'))
{
pos++;
if (pos < this.text.Length && (this.text[pos] == '+' || this.text[pos] == '-'))
{
pos++;
}
int num3 = pos;
Digits();
if (num3 == pos)
{
throw Error("Exponent missing");
}
}
if (!decimal.TryParse(this.text.Substring(num, pos - num), NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
{
throw Error("Number out of range");
}
return result;
}
}
}
private void Digits()
{
while (pos < text.Length && text[pos] >= '0' && text[pos] <= '9')
{
pos++;
}
}
private char Hex()
{
if (pos + 4 > text.Length)
{
throw Error("Truncated unicode escape");
}
if (!ushort.TryParse(text.Substring(pos, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result))
{
throw Error("Invalid unicode escape");
}
pos += 4;
return (char)result;
}
private string String()
{
Need('"');
StringBuilder stringBuilder = new StringBuilder();
while (pos < this.text.Length)
{
char c = this.text[pos++];
if (c == '"')
{
string text = stringBuilder.ToString();
for (int i = 0; i < text.Length; i++)
{
if (char.IsHighSurrogate(text[i]))
{
if (++i >= text.Length || !char.IsLowSurrogate(text[i]))
{
throw Error("Unpaired surrogate");
}
}
else if (char.IsLowSurrogate(text[i]))
{
throw Error("Unpaired surrogate");
}
}
return text;
}
if (c < ' ')
{
throw Error("Control character in string");
}
if (c != '\\')
{
stringBuilder.Append(c);
continue;
}
if (pos >= this.text.Length)
{
throw Error("Truncated escape");
}
c = this.text[pos++];
switch (c)
{
case '"':
case '/':
case '\\':
stringBuilder.Append(c);
break;
case 'b':
stringBuilder.Append('\b');
break;
case 'f':
stringBuilder.Append('\f');
break;
case 'n':
stringBuilder.Append('\n');
break;
case 'r':
stringBuilder.Append('\r');
break;
case 't':
stringBuilder.Append('\t');
break;
case 'u':
stringBuilder.Append(Hex());
break;
default:
throw Error("Invalid escape");
}
}
throw Error("Unterminated string");
}
}
internal static class JsonShape
{
public static Dictionary<string, object> Object(object value)
{
return (value as Dictionary<string, object>) ?? throw new FormatException("Expected object");
}
public static List<object> Array(object value)
{
return (value as List<object>) ?? throw new FormatException("Expected array");
}
public static string Text(object value, bool nullable = false)
{
return (value == null && nullable) ? null : ((value as string) ?? throw new FormatException("Expected string"));
}
public static bool Bool(object value)
{
if (value is bool)
{
return (bool)value;
}
throw new FormatException("Expected boolean");
}
public static int Int(object value)
{
if (!(value is decimal num) || !(decimal.Truncate(num) == num) || !(num >= -2147483648m) || !(num <= 2147483647m))
{
throw new FormatException("Expected integer");
}
return (int)num;
}
public static void Fields(Dictionary<string, object> obj, params string[] fields)
{
if (obj.Count != fields.Length)
{
throw new FormatException("Unexpected/missing fields");
}
foreach (string text in fields)
{
if (!obj.ContainsKey(text))
{
throw new FormatException("Missing field " + text);
}
}
}
public static bool Equal(object a, object b)
{
if (a == b)
{
return true;
}
if (a is Dictionary<string, object> dictionary && b is Dictionary<string, object> dictionary2)
{
if (dictionary.Count != dictionary2.Count)
{
return false;
}
foreach (KeyValuePair<string, object> item in dictionary)
{
if (!dictionary2.TryGetValue(item.Key, out var value) || !Equal(item.Value, value))
{
return false;
}
}
return true;
}
if (a is List<object> list && b is List<object> list2)
{
if (list.Count != list2.Count)
{
return false;
}
for (int i = 0; i < list.Count; i++)
{
if (!Equal(list[i], list2[i]))
{
return false;
}
}
return true;
}
return object.Equals(a, b);
}
}
}