Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bw localization plugin v6 on frosty1.6 #211

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d4b246b
Commit full version 1.1.0.5 of the BW Localization Plugin
KrrKsThunder Oct 31, 2022
c48eaa8
Add option to search with decimal text ids
KrrKsThunder Nov 6, 2022
c1c5652
Prepare data structure for editing declinated adjectives
KrrKsThunder Nov 6, 2022
643911e
Implement DragonAgeDeclinatedAdjectiveTuples- functions missing from …
KrrKsThunder Nov 6, 2022
e2d289c
Prepare writing capability for DA:I declinated crafting name adjectiv…
KrrKsThunder Nov 11, 2022
d996303
Add setter and remove methods in resource, fix test method
KrrKsThunder Nov 13, 2022
f1a1142
Fix incorrectly stated number of declinations
KrrKsThunder Nov 19, 2022
23ece3b
Add adjective getters to text resources
KrrKsThunder Nov 20, 2022
ea7bc09
Refactor xml export, write declinated adjectives.
KrrKsThunder Nov 27, 2022
1d5c6f9
Allow Importing of declinated adjectives from xml file
KrrKsThunder Nov 29, 2022
77f6db7
Add Toggle and (currently non functional) resource selector in ui
KrrKsThunder Dec 4, 2022
6b62fc5
Fix switching to adjectives
KrrKsThunder Dec 10, 2022
010b347
Enable display of declinated adjectives
KrrKsThunder Dec 11, 2022
7209e5a
Add first sign of adjective edit window
KrrKsThunder Dec 18, 2022
d0cf800
Working Edit dialog for adjectives
KrrKsThunder Dec 18, 2022
2858614
Try formatting code in accordance with frosty codingstandards
KrrKsThunder Dec 20, 2022
907a29b
Small fixes to the adjective edit window
KrrKsThunder Dec 21, 2022
afcec25
Add ReplaceWindow
KrrKsThunder Dec 23, 2022
b2641a7
Fix higlighting and display issues
KrrKsThunder Dec 25, 2022
2420d04
Make replacement window available form generic string editor
KrrKsThunder Dec 28, 2022
425f879
Merge remote-tracking branch 'remotes/origin/1.0.6.2' into BWLocaliza…
KrrKsThunder Dec 28, 2022
b8e00c6
Add settings for xml export
KrrKsThunder Dec 28, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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