diff --git a/Plugins/BiowareLocalizationPlugin/BW LocaliziationResourceBits.txt b/Plugins/BiowareLocalizationPlugin/BW LocaliziationResourceBits.txt new file mode 100644 index 000000000..61762e68b --- /dev/null +++ b/Plugins/BiowareLocalizationPlugin/BW LocaliziationResourceBits.txt @@ -0,0 +1,68 @@ +Part: Type and what Byte position after this + +MetaData (16 bytes, located in Frosty at Resource.resMeta, not part of the byte count ) + { + uint dataOffset + 3x byte value 0x0 + } +Header + { + uint magic = 0xd78b40eb (pos 4) + uint ??? (pos 8) // doesn't seem to affect anything + uint dataOffset (pos 12) // not actually used, instead the one from the metadata is the base for the game + 3x uint ??? (pos 24) // doens't seem to affect anything + + uint nodeCount (pos 28) + // nodeCount is an even integer! The rootNode as would-be last node in the node list is *not* actually part of the list! + + uint nodeOffset (pos 32) + + uint stringsCount (pos 36) + uint stringsOffset (pos 40) + + // Until the nodeOffset is reached + // If there are three or more entries in here, + // then the corresponding 8ByteBlockData after the second one contain ids and bit offsets for the declinated adjectives for text parts used for crafted items in DA:I (Note that these ids are not the same accross languages!) + N times Unknown8ByteBlockCountAndOffset + { + uint unknownCounts + uint unknownOffset + } + } + + // Position = nodeOffset -> pos most likely either 56 or 64 +HuffmanCoding + { + nodeCount x uint value == bitFlip char + } + (size = 4 per node) + + // Position = stringsOffset +StringData + { + stringsCount x + { + uint stringId + int stringIndex / positionOffset + } + (size = 8 per string) + } + + // Position = Unknown8ByteBlockCountAndOffset[0].unknownOffset + // The next data blocks appears the same N times as their Unknown8ByteBlockCountAndOffset counterpart in the header + // Everything past the second of these blocks contains the bit offsets of the text pieces used for crafted item names in DA:I +N times 8ByteBlockData + { + // Position = Unknown8ByteBlockCountAndOffset[index].unknownOffset + byte[].Length = Unknown8ByteBlockCountAndOffset[index].unknownCounts * 8 + } + + // Position = dataOffset +Strings + { + stringsCount * HuffmanEncodedChars + } + // stringIndex / positionOffset = bit offset from dataOffset == textBefore bitOffset + textBefore bitlentgh + // last symbol (only symbol of empty string) is huffman node with letter 0x00! I.e., _value_ = 0xFF + +Remaining positions to full byte filled with 0s \ No newline at end of file diff --git a/Plugins/BiowareLocalizationPlugin/BioWareLocalizedStringEditorMenuExtension.cs b/Plugins/BiowareLocalizationPlugin/BioWareLocalizedStringEditorMenuExtension.cs new file mode 100644 index 000000000..6be0a7b4b --- /dev/null +++ b/Plugins/BiowareLocalizationPlugin/BioWareLocalizedStringEditorMenuExtension.cs @@ -0,0 +1,28 @@ +using BiowareLocalizationPlugin.Controls; +using Frosty.Core; +using FrostySdk; + +namespace BiowareLocalizationPlugin +{ + public class BioWareLocalizedStringEditorMenuExtension : MenuExtension + { + + private const string ITEM_NAME = "Bioware Localized String Editor"; + + public override string TopLevelMenuName => "View"; + + public override string SubLevelMenuName => null; + public override string MenuItemName => ITEM_NAME; + + public override RelayCommand MenuItemClicked => new RelayCommand((o) => + { + if (ProfilesLibrary.DataVersion == (int)ProfileVersion.Anthem) + { + App.Logger.Log("Not applicable for Anthem, sorry for the inconvenience!"); + return; + } + var textDb = (BiowareLocalizedStringDatabase) LocalizedStringDatabase.Current; + App.EditorWindow.OpenEditor(ITEM_NAME, new BiowareLocalizedStringEditor(textDb)); + }); + } +} diff --git a/Plugins/BiowareLocalizationPlugin/BiowareLocalizationCustomActionHandler.cs b/Plugins/BiowareLocalizationPlugin/BiowareLocalizationCustomActionHandler.cs new file mode 100644 index 000000000..51765c31f --- /dev/null +++ b/Plugins/BiowareLocalizationPlugin/BiowareLocalizationCustomActionHandler.cs @@ -0,0 +1,188 @@ +using BiowareLocalizationPlugin.LocalizedResources; +using Frosty.Core; +using Frosty.Core.IO; +using Frosty.Core.Mod; +using Frosty.Hash; +using FrostySdk; +using FrostySdk.IO; +using FrostySdk.Managers; +using FrostySdk.Resources; +using System; +using System.Collections.Generic; +using System.Text; + +namespace BiowareLocalizationPlugin +{ + public class BiowareLocalizationCustomActionHandler : ICustomActionHandler + { + + public HandlerUsage Usage => HandlerUsage.Merge; + + // A mod is comprised of a series of base resources, embedded, ebx, res, and chunks. Embedded are used internally + // for the icon and images of a mod. Ebx/Res/Chunks are the core resources used for applying data to the game. + // When you create a custom handler, you need to provide your own resources for your custom handled data. This + // resource is unique however it is based on one of the three core types. + private class BiowareLocalizationModResource : EditorModResource + { + + // Defines which type of resource this resource is. + public override ModResourceType Type => ModResourceType.Res; + + + // The resType is vital to be kept (its always LocalizedStringResource, but whatever) + private readonly uint m_resType; + + // these other two fields may have to be written to the mod as well + private readonly ulong m_resRid; + private readonly byte[] m_resMeta; + + + public BiowareLocalizationModResource(ResAssetEntry inEntry, FrostyModWriter.Manifest inManifest) : base(inEntry) + { + + // This constructor does the exact same thing as the ones in the TestPlugin + + // obtain the modified data + ModifiedLocalizationResource md = inEntry.ModifiedEntry.DataObject as ModifiedLocalizationResource; + byte[] data = md.Save(); + + // store data and details about resource + name = inEntry.Name.ToLower(); + sha1 = Utils.GenerateSha1(data); + resourceIndex = inManifest.Add(sha1, data); + size = data.Length; + + // set the handler hash to the hash of the res type name + handlerHash = Fnv1.HashString(inEntry.Type.ToLower()); + + m_resType = inEntry.ResType; + m_resRid = inEntry.ResRid; + m_resMeta = inEntry.ResMeta; + } + + /// + /// This method is calles when writing the mod. For Res Types it is vital that some additional information is persisted that is not written by the base method. + /// Mainly that is the ResourceType as uint + /// Additional data that is read, but I'm not sure whether it is actually necessary: + /// + /// + /// + public override void Write(NativeWriter writer) + { + base.Write(writer); + + // write the required res type: + writer.Write(m_resType); + + writer.Write(m_resRid); + writer.Write((m_resMeta != null) ? m_resMeta.Length : 0); + if (m_resMeta != null) + { + writer.Write(m_resMeta); + } + } + } + + #region -- Editor Specific -- + + // This function is for writing resources to the mod file, this is where you would add your custom + // resources to be written. + public void SaveToMod(FrostyModWriter writer, AssetEntry entry) + { + writer.AddResource(new BiowareLocalizationModResource(entry as ResAssetEntry, writer.ResourceManifest)); + } + #endregion + + #region -- Mod Specific -- + + // This function is for the mod managers action view, to allow a handler to describe detailed actions performed + // format of the action string is ;; where action can be Modify or Merge (or Add!) + // and ResourceType can be Ebx,Res,Chunk + public IEnumerable GetResourceActions(string name, byte[] data) + { + + if( !Config.Get(BiowareLocalizationPluginOptions.SHOW_INDIVIDUAL_TEXTIDS_OPTION_NAME, false, ConfigScope.Global)) + { + return new List(); + } + + ModifiedLocalizationResource modified = ModifiedResource.Read(data) as ModifiedLocalizationResource; + + List textIds = new List(modified.AlteredTexts.Keys); + textIds.Sort(); + + List resourceActions = new List(textIds.Count); + foreach (uint textId in textIds) + { + string resourceName = new StringBuilder(name).Append(" (0x").Append(textId.ToString("X8")).Append(')').ToString(); + string resourceType = "res"; + string action = "Modify"; + + resourceActions.Add(resourceName + ";" + resourceType + ";" + action); + } + + return resourceActions; + } + + // This function is invoked when a mod with such a handler is loaded, if a previous mod with a handler for this + // particular asset was loaded previously, then existing will be populated with that data, allowing this function + // the chance to merge the two datasets together + public object Load(object existing, byte[] newData) + { + + ModifiedLocalizationResource edited = (ModifiedLocalizationResource)existing; + ModifiedLocalizationResource newTexts = (ModifiedLocalizationResource) ModifiedResource.Read(newData); + + if(edited == null) + { + return newTexts; + } + + edited.Merge(newTexts); + + return edited; + } + + // This function is invoked at the end of the mod loading, to actually modify the existing game data with the end + // result of the mod loaded data, it also allows for a handler to add new Resources to be replaced. + // ie. an Ebx handler might want to add a new Chunk resource that it is dependent on. + public void Modify(AssetEntry origEntry, AssetManager am, RuntimeResources runtimeResources, object data, out byte[] outData) + { + + // no idea what frosty does if the resource does not exist in the local game, so first check for null: + if(origEntry == null) + { + outData = System.Array.Empty(); + return; + } + + // load the original resource + ResAssetEntry originalResAsset = am.GetResEntry(origEntry.Name); + ModifiedLocalizationResource modified = data as ModifiedLocalizationResource; + LocalizedStringResource resource = am.GetResAs(originalResAsset, modified); + + // read about some weird null reference exception in here, so _maybe_ it was the resource? + if(resource == null) + { + throw new ArgumentNullException("resource", string.Format("Resource in BwLocalizationHandler Modify(...) is null after GetResAs call for <{0}>!", origEntry.Name)); + } + + byte[] uncompressedData = resource.SaveBytes(); + outData = Utils.CompressFile(uncompressedData); + + // update the metadata + byte[] alteredMetaData = resource.ResourceMeta; + ((ResAssetEntry)origEntry).ResMeta = alteredMetaData; + + // update relevant asset entry values + origEntry.OriginalSize = uncompressedData.Length; + origEntry.Size = outData.Length; + origEntry.Sha1 = Utils.GenerateSha1(outData); + } + #endregion + } +} diff --git a/Plugins/BiowareLocalizationPlugin/BiowareLocalizationPlugin.csproj b/Plugins/BiowareLocalizationPlugin/BiowareLocalizationPlugin.csproj index f84291f44..84f1caf1c 100644 --- a/Plugins/BiowareLocalizationPlugin/BiowareLocalizationPlugin.csproj +++ b/Plugins/BiowareLocalizationPlugin/BiowareLocalizationPlugin.csproj @@ -53,5 +53,9 @@ false + + false + + \ No newline at end of file diff --git a/Plugins/BiowareLocalizationPlugin/BiowareLocalizationPluginModManagerOptions.cs b/Plugins/BiowareLocalizationPlugin/BiowareLocalizationPluginModManagerOptions.cs new file mode 100644 index 000000000..5dcfb7183 --- /dev/null +++ b/Plugins/BiowareLocalizationPlugin/BiowareLocalizationPluginModManagerOptions.cs @@ -0,0 +1,46 @@ + +using Frosty.Core; +using FrostySdk.Attributes; + +namespace BiowareLocalizationPlugin +{ + [DisplayName("Bioware Localization Options")] + public class BiowareLocalizationPluginOptions : OptionsExtension + { + + // The name for the global mod manager variable. + public static readonly string SHOW_INDIVIDUAL_TEXTIDS_OPTION_NAME = "BwLoMoShowIndividualTextIds"; + + public static readonly string ASK_XML_EXPORT_OPTIONS = "BwLoEoAskXmlExportOptions"; + + [Category("Mod Manager Options")] + [Description("If enabled, all individual text ids in each resource (res) are shown in the mod manager's Actions tab. Otherwise only the resource iteself is shown as merged. This setting is only for the mod manager and has no effect in the editor.")] + [DisplayName("Show Individual Text Ids")] + [EbxFieldMeta(FrostySdk.IO.EbxFieldType.Boolean)] + public bool ShowIndividualTextIds { get; set; } = false; + + [Category("Editor Options")] + [DisplayName("Ask for Xml Export Options")] + [Description("If enabled, a popup prompt allows selecting whether to export all texts or only modified ones. If this value is false, then the default from below is used. This setting is only for the editor and has no effect in the mod manager.")] + [EbxFieldMeta(FrostySdk.IO.EbxFieldType.Boolean)] + public bool AskForXmlExportOptions { get; set; } = false; + + public override void Load() + { + // mod manager + ShowIndividualTextIds = Config.Get(SHOW_INDIVIDUAL_TEXTIDS_OPTION_NAME, false, ConfigScope.Global); + + // editor + AskForXmlExportOptions = Config.Get(ASK_XML_EXPORT_OPTIONS, false, ConfigScope.Global); + } + + public override void Save() + { + // mod manager + Config.Add(SHOW_INDIVIDUAL_TEXTIDS_OPTION_NAME, ShowIndividualTextIds, ConfigScope.Global); + + // editor + Config.Add(ASK_XML_EXPORT_OPTIONS, AskForXmlExportOptions, ConfigScope.Global); + } + } +} diff --git a/Plugins/BiowareLocalizationPlugin/BiowareLocalizedStringDatabase.cs b/Plugins/BiowareLocalizationPlugin/BiowareLocalizedStringDatabase.cs index ad2145047..a4b564a2f 100644 --- a/Plugins/BiowareLocalizationPlugin/BiowareLocalizedStringDatabase.cs +++ b/Plugins/BiowareLocalizationPlugin/BiowareLocalizedStringDatabase.cs @@ -1,124 +1,434 @@ -using Frosty.Core; +using BiowareLocalizationPlugin.Controls; +using BiowareLocalizationPlugin.LocalizedResources; +using Frosty.Core; using FrostySdk.Managers; using System; using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Windows; namespace BiowareLocalizationPlugin { public class BiowareLocalizedStringDatabase : ILocalizedStringDatabase { - private Dictionary strings = new Dictionary(); + /// + /// The default language to operate with if no other one is given. + /// + public string DefaultLanguage { get; private set; } + + /// + /// Holds all the languages supported by the local game and their bundles + /// + private SortedDictionary> m_languageLocalizationBundles; + + /// + /// Dictionary of all currently loaded localized texts. + /// + private readonly Dictionary m_loadedLocalizedTextDBs = new Dictionary(); + + /// + /// marker whether or not this was already initialized. + /// + private bool m_initialized = false; + + /// + /// Initializes the db. + /// public void Initialize() { - LoadLocalizedStringConfiguration("LocalizedStringTranslationsConfiguration"); - LoadLocalizedStringConfiguration("LocalizedStringPatchTranslationsConfiguration"); + + DefaultLanguage = "LanguageFormat_" + Config.Get("Language", "English", scope: ConfigScope.Game); + + if (m_initialized) + { + return; + } + + m_languageLocalizationBundles = GetLanguageDictionary(); + + LanguageTextsDB defaultLocalizedTexts = new LanguageTextsDB(); + defaultLocalizedTexts.Init(DefaultLanguage, m_languageLocalizationBundles[DefaultLanguage]); + + m_loadedLocalizedTextDBs.Add(DefaultLanguage, defaultLocalizedTexts); + + m_initialized = true; + } + + /// + /// Tries to return the text for the given uid, throws an exception if the text id is not known. + /// @see #FindText + /// + /// + /// + public string GetString(uint id) + { + return GetText(DefaultLanguage, id); + } + + public string GetString(string stringId) + { + + bool canRead = uint.TryParse(stringId, NumberStyles.HexNumber, null, out uint textId); + if (canRead) + { + return GetString(textId); + } + + App.Logger.LogError("Cannot read given textId <{0}>", stringId); + return stringId; + + } + + /// + /// Returns the language db for the requested language format, loading it if necessary. + /// + /// + /// + public LanguageTextsDB GetLocalizedTextDB(string languageFormat) + { + bool isLoaded = m_loadedLocalizedTextDBs.TryGetValue(languageFormat, out LanguageTextsDB localizedTextDb); + if (!isLoaded) + { + if (!m_languageLocalizationBundles.ContainsKey(languageFormat)) + { + throw new ArgumentException(string.Format("LanguageFormat <{0}> does not exist in this game!", languageFormat)); + } + + localizedTextDb = new LanguageTextsDB(); + localizedTextDb.Init(languageFormat, m_languageLocalizationBundles[languageFormat]); + + m_loadedLocalizedTextDBs.Add(languageFormat, localizedTextDb); + } + return localizedTextDb; + } + + /// + /// Tries to return the text for the given uid. Returns an error message if the text does not exist. + /// + /// + /// + /// + public string GetText(string languageFormat, uint textId) + { + return GetLocalizedTextDB(languageFormat).GetText(textId); } public IEnumerable EnumerateStrings() { - foreach (uint key in strings.Keys) - yield return key; + return GetAllTextIds(DefaultLanguage); } - public IEnumerable EnumerateModifiedStrings() + /// + /// Returns a language specific list of all text ids. + /// + /// + /// + public IEnumerable GetAllTextIds(string languageFormat) { - throw new NotImplementedException(); + return GetLocalizedTextDB(languageFormat).GetAllTextIds(); } - public string GetString(uint id) + /// + /// Returns only the ids of modified or new texts. + /// + /// + /// + public IEnumerable GetAllModifiedTextsIds(string languageFormat) + { + return GetLocalizedTextDB(languageFormat).GetAllModifiedTextsIds(); + } + + /// + /// Tries to return the text for the given uid, returns null if the textid does not exist. + /// @see #GetString + /// + /// + /// + /// + public string FindText(string languageFormat, uint textId) + { + return GetLocalizedTextDB(languageFormat).FindText(textId); + } + + /// + /// Returns the list of LocalizedStringResource in which the given text id can be found. + /// + /// + /// The text id to look for. + /// All resources in which the text id can be found. + public IEnumerable GetAllLocalizedStringResourcesForTextId(string languageFormat, uint textId) + { + return GetLocalizedTextDB(languageFormat).GetAllResourcesForTextId(textId); + } + + /// + /// Returns the list of LocalizedStringResource in which the given text id can be found by default. + /// + /// + /// The text id to look for. + /// All resources in which the text id can be found by default. + public IEnumerable GetDefaultLocalizedStringResourcesForTextId(string languageFormat, uint textId) { - if (!strings.ContainsKey(id)) + return GetLocalizedTextDB(languageFormat).GetDefaultResourcesForTextId(textId); + } + + /// + /// Returns the list of LocalizedStringResource in which the given text id can be found due to a mod. + /// + /// + /// The text id to look for. + /// All resources in which the text id can be found due to a mod. + public IEnumerable GetAddedLocalizedStringResourcesForTextId(string languageFormat, uint textId) + { + return GetLocalizedTextDB(languageFormat).GetAddedResourcesForTextId(textId); + } + + /// + /// Returns the names of all found resources + /// + /// + /// + public IEnumerable GetAllResourceNames(string languageFormat) + { + return GetLocalizedTextDB(languageFormat).GetAllResourceNames(); + } + + /// + /// Sets a text into a single resource + /// + /// + /// + /// + /// + public void SetText(string languageFormat, IEnumerable resourceNames, uint textId, string text) + { + + LanguageTextsDB localizedDB = GetLocalizedTextDB(languageFormat); + foreach (string resourceName in resourceNames) { - if (id == 0) - return ""; - return string.Format("Invalid StringId: {0}", id.ToString("X8")); + localizedDB.SetText(resourceName, textId, text); } - return strings[id]; + + localizedDB.UpdateTextCache(textId, text); } - public string GetString(string stringId) + /// + /// Removes the given text with the given id from the given resources for the given language. + /// + /// + /// + /// + public void RemoveText(string languageFormat, IEnumerable resourceNames, uint textId) { - throw new NotImplementedException(); + LanguageTextsDB localizedDB = GetLocalizedTextDB(languageFormat); + foreach (string resourceName in resourceNames) + { + localizedDB.RemoveText(resourceName, textId); + } + + localizedDB.RemoveTextFromCache(textId); } + public void RevertText(string languageFormat, uint textId) + { + LanguageTextsDB localizedDB = GetLocalizedTextDB(languageFormat); + localizedDB.RevertText(textId); + } + + public IEnumerable GellAllLanguages() + { + return new List(m_languageLocalizationBundles.Keys); + } + + // basically identical to SetText, this method was added in the 1.06 beta interface public void SetString(uint id, string value) { - throw new NotImplementedException(); + LanguageTextsDB localizedDB = GetLocalizedTextDB(DefaultLanguage); + + IEnumerable allTextResources = localizedDB.GetAllResourcesForTextId(id); + IEnumerable textResourceNames = allTextResources.Select(resource => resource.Name); + + SetText(DefaultLanguage, textResourceNames, id, value); } + // // Basically identical to SetText, this method was added in the 1.06 beta interface public void SetString(string id, string value) { - throw new NotImplementedException(); + bool canRead = uint.TryParse(id, NumberStyles.HexNumber, null, out uint textId); + if (canRead) + { + SetString(textId, value); + } + + App.Logger.LogError("Cannot read given textId <{0}>", id); } + // // Basically identical to RevertText, this method was added in the 1.06 beta interface public void RevertString(uint id) { - throw new NotImplementedException(); + RevertText(DefaultLanguage, id); } + // Returns whether the text with the given id was altered. + // Implements the interface method added in 1.0.6beta public bool isStringEdited(uint id) { + LanguageTextsDB localizedDB = GetLocalizedTextDB(DefaultLanguage); + + IEnumerable modifiedTextsIds = localizedDB.GetAllModifiedTextsIds(); + foreach (uint textId in modifiedTextsIds) + { + if (textId == id) + { + return true; + } + } + return false; } + /// + /// Opens a window to add strings to the localized string database. + /// + /// Note This method came with the 1.06.beta1 and i feel really uncomfortable displaying an edit dialog directly from what is supposed to be abackend class >_< public void AddStringWindow() { - throw new NotImplementedException(); + + AddEditWindow editWindow = new AddEditWindow(this, DefaultLanguage) + { + Owner = Application.Current.MainWindow + }; + editWindow.Init(0); + _ = editWindow.ShowDialog(); } + // This method came with 1.06.beta1, and i still believe bulk operations to be more a risk of breaking texts than working properly + // - or at least my implementation of that function would be ;D + // But now sequencial replacements should work somewhat decently public void BulkReplaceWindow() { - throw new NotImplementedException(); + List textsWithId = new List(); + + List textIds = GetAllTextIds(DefaultLanguage).ToList(); + textIds.Sort(); + + foreach (uint textId in textIds) + { + string text = GetText(DefaultLanguage, textId); + textsWithId.Add(textId.ToString("X8") + " - " + text); + } + + ReplaceWindow replaceWindow = new ReplaceWindow(this, DefaultLanguage, textsWithId) + { + Owner = Application.Current.MainWindow + }; + replaceWindow.ShowDialog(); + } + + /// + /// Retrieves a collection of string IDs that were modified from the localized string database. + /// This method came into the interface in 1.06.beta1, and is virtually identical to GetAllModifiedTextsIds + /// + /// A collection of string IDs, or an empty collection if no modified strings exist. + public IEnumerable EnumerateModifiedStrings() + { + return GetAllModifiedTextsIds(DefaultLanguage); } - private void LoadLocalizedStringConfiguration(string type) + public IEnumerable GetAllResourceNamesWithDeclinatedAdjectives(string languageFormat) { - foreach (EbxAssetEntry entry in App.AssetManager.EnumerateEbx(type)) + LanguageTextsDB textDb = GetLocalizedTextDB(languageFormat); + return textDb.GetAllResourceNamesWithDeclinatedAdjectives(); + } + + public IEnumerable GetAllResourceNamesWithModifiedDeclinatedAdjectives(string languageFormat) + { + LanguageTextsDB textDb = GetLocalizedTextDB(languageFormat); + return textDb.GetAllResourceNamesWithModifiedDeclinatedAdjectives(); + } + + public IEnumerable GetAllDeclinatedAdjectiveIdsFromResource(string languageFormat, string resourceName) + { + LanguageTextsDB textDb = GetLocalizedTextDB(languageFormat); + return textDb.GetAllDeclinatedAdjectiveIdsFromResource(resourceName); + } + + public IEnumerable GetModifiedDeclinatedAdjectiveIdsFromResource(string languageFormat, string resourceName) + { + LanguageTextsDB textDb = GetLocalizedTextDB(languageFormat); + return textDb.GetModifiedDeclinatedAdjectiveIdsFromResource(resourceName); + } + + public List GetDeclinatedAdjectives(string languageFormat, string resourceName, uint adjectiveId) + { + LanguageTextsDB textDb = GetLocalizedTextDB(languageFormat); + return textDb.GetDeclinatedAdjectives(resourceName, adjectiveId); + } + + public void SetDeclinatedAdjectve(string languageFormat, string resourceName, uint adjectiveId, List aAdjectives) + { + LanguageTextsDB textDb = GetLocalizedTextDB(languageFormat); + textDb.SetDeclinatedAdjectve(resourceName, adjectiveId, aAdjectives); + } + + public void RevertDeclinatedAdjective(string languageFormat, string resourceName, uint adjectiveId) + { + LanguageTextsDB textDb = GetLocalizedTextDB(languageFormat); + textDb.RevertDeclinatedAdjective(resourceName, adjectiveId); + } + + public IEnumerable GetAllTextIdsFromResource(string languageFormat, string resourceName) + { + LanguageTextsDB textDb = GetLocalizedTextDB(languageFormat); + return textDb.GetAllTextIdsFromResource(resourceName); + } + + public IEnumerable GetAllModifiedTextIdsFromResource(string languageFormat, string resourceName) + { + LanguageTextsDB textDb = GetLocalizedTextDB(languageFormat); + return textDb.GetAllModifiedTextIdsFromResource(resourceName); + } + + /// + /// Fills the language dictionary with all available languages and their bundles. + /// + /// Sorted Dictionary of LangugeFormat names and their text super bundles paths. + private static SortedDictionary> GetLanguageDictionary() + { + + var languagesRepository = new SortedDictionary>(); + + // There is no need to also search for 'LocalizedStringPatchTranslationsConfiguration', these are also found via their base type + foreach (EbxAssetEntry entry in App.AssetManager.EnumerateEbx("LocalizedStringTranslationsConfiguration")) { // read localization config dynamic localizationAsset = App.AssetManager.GetEbx(entry).RootObject; - // iterate thru language to bundle lists - foreach (dynamic languageBundleList in localizationAsset.LanguagesToBundlesList) + // iterate through language to bundle lists + foreach (dynamic languageBundleListEntry in localizationAsset.LanguagesToBundlesList) { - if (languageBundleList.Language.ToString().Equals("LanguageFormat_English")) + string languageName = languageBundleListEntry.Language.ToString(); + HashSet bundleNames; + if (languagesRepository.ContainsKey(languageName)) + { + bundleNames = languagesRepository[languageName]; + } + else { - foreach (string bundlePath in languageBundleList.BundlePaths) - { - string bundleFullPath = "win32/" + bundlePath.ToLower(); - foreach (ResAssetEntry resEntry in App.AssetManager.EnumerateRes(resType: (uint)ResourceType.LocalizedStringResource)) - { - bool bFound = false; - foreach (int bindex in resEntry.EnumerateBundles()) - { - BundleEntry be = App.AssetManager.GetBundleEntry(bindex); - if (be.Name.StartsWith(bundleFullPath, StringComparison.OrdinalIgnoreCase)) - { - bFound = true; - break; - } - } - - if (bFound) - { - LocalizedStringResource resource = App.AssetManager.GetResAs(resEntry); - if (resource != null) - { - foreach (KeyValuePair kvp in resource.Strings) - { - if (!strings.ContainsKey(kvp.Key)) - { - strings.Add(kvp.Key, kvp.Value); - } - } - } - } - } - } + bundleNames = new HashSet(); + languagesRepository[languageName] = bundleNames; + } + + foreach (string bundlepath in languageBundleListEntry.BundlePaths) + { + bundleNames.Add(bundlepath); } } } + + return languagesRepository; } } } diff --git a/Plugins/BiowareLocalizationPlugin/Controls/AddEditWindow.xaml b/Plugins/BiowareLocalizationPlugin/Controls/AddEditWindow.xaml new file mode 100644 index 000000000..179fbe318 --- /dev/null +++ b/Plugins/BiowareLocalizationPlugin/Controls/AddEditWindow.xaml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + +