Skip to content

Commit

Permalink
Bw localization plugin v6 on frosty1.6 (#211)
Browse files Browse the repository at this point in the history
* Commit full version 1.1.0.5 of the BW Localization Plugin

# Conflicts:
#	Plugins/BiowareLocalizationPlugin/BiowareLocalizedStringDatabase.cs
#	Plugins/BiowareLocalizationPlugin/LocalizedStringResource.cs

* Add option to search with decimal text ids

* Prepare data structure for editing declinated adjectives

* Implement DragonAgeDeclinatedAdjectiveTuples- functions missing from last commit

* Prepare writing capability for DA:I declinated crafting name adjectives, bump plugin version to reflect major change in produced mod file

* Add setter and remove methods in resource, fix test method

* Fix incorrectly stated number of declinations

* Add adjective getters to text resources

* Refactor xml export, write declinated adjectives.

* Allow Importing of declinated adjectives from xml file

* Add Toggle and (currently non functional) resource selector in ui

* Fix switching to adjectives

* Enable display of declinated adjectives

* Add first sign of adjective edit window

* Working Edit dialog for adjectives

* Try formatting code in accordance with frosty codingstandards

* Small fixes to the adjective edit window

* Add ReplaceWindow

* Fix higlighting and display issues

* Make replacement window available form generic string editor

* Add settings for xml export
  • Loading branch information
KrrKsThunder committed Jan 6, 2023
1 parent 5a67f9e commit 90dd6ae
Show file tree
Hide file tree
Showing 33 changed files with 7,592 additions and 287 deletions.
68 changes: 68 additions & 0 deletions Plugins/BiowareLocalizationPlugin/BW LocaliziationResourceBits.txt
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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));
});
}
}
Original file line number Diff line number Diff line change
@@ -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;
}

/// <summary>
/// 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:
/// <ul>
/// <li>ResRid as ulong (not sure if this is really necessary, i.e., actually read)
/// <li>resMeta length
/// <li>resMeta as byte array
/// </ul>
/// </summary>
/// <param name="writer"></param>
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 <ResourceName>;<ResourceType>;<Action> where action can be Modify or Merge (or Add!)
// and ResourceType can be Ebx,Res,Chunk
public IEnumerable<string> GetResourceActions(string name, byte[] data)
{

if( !Config.Get(BiowareLocalizationPluginOptions.SHOW_INDIVIDUAL_TEXTIDS_OPTION_NAME, false, ConfigScope.Global))
{
return new List<string>();
}

ModifiedLocalizationResource modified = ModifiedResource.Read(data) as ModifiedLocalizationResource;

List<uint> textIds = new List<uint>(modified.AlteredTexts.Keys);
textIds.Sort();

List<string> resourceActions = new List<string>(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<byte>();
return;
}

// load the original resource
ResAssetEntry originalResAsset = am.GetResEntry(origEntry.Name);
ModifiedLocalizationResource modified = data as ModifiedLocalizationResource;
LocalizedStringResource resource = am.GetResAs<LocalizedStringResource>(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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,9 @@
<ProjectReference Include="..\..\FrostySdk\FrostySdk.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\FrostyModSupport\FrostyModSupport.csproj">
<Private>false</Private>
</ProjectReference>
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Loading

0 comments on commit 90dd6ae

Please sign in to comment.