From 838a4815b9cb50e5b23aece32f6ec4aa5da95104 Mon Sep 17 00:00:00 2001 From: Vitalii Mikhailov Date: Sat, 6 Dec 2025 13:30:49 +0200 Subject: [PATCH] * Fixed crash related to Chinese, Japanese and Korean localizations\ * Updated Tpac asset reading based on new fork https://github.com/hunharibo/TpacTool --- build/common.props | 2 +- changelog.txt | 5 ++ .../ResourceManagers/FontFactoryManager.cs | 4 +- .../ResourceManagers/SpriteDataManager.cs | 50 ++++++++++++++++--- .../TPac/AssetPackage.cs | 14 +++++- src/Bannerlord.LauncherEx/TPac/Texture.cs | 42 +++++++++++++--- 6 files changed, 99 insertions(+), 18 deletions(-) diff --git a/build/common.props b/build/common.props index 951bb7e..02277bd 100644 --- a/build/common.props +++ b/build/common.props @@ -11,7 +11,7 @@ - 1.5.10 + 1.5.11 2.2.2 3.0.0.142 6.0.247 diff --git a/changelog.txt b/changelog.txt index f373ae2..12c00fc 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,9 @@ --------------------------------------------------------------------------------------------------- +Version: 1.5.11 +Game Versions: v1.0.x,v1.1.x,v1.2.x,v1.3.x +* Fixed crash related to Chinese, Japanese and Korean localizations\ +* Updated Tpac asset reading based on new fork https://github.com/hunharibo/TpacTool +--------------------------------------------------------------------------------------------------- Version: 1.5.10 Game Versions: v1.0.x,v1.1.x,v1.2.x,v1.3.x * Fixed an issue with Standalone diff --git a/src/Bannerlord.LauncherEx/ResourceManagers/FontFactoryManager.cs b/src/Bannerlord.LauncherEx/ResourceManagers/FontFactoryManager.cs index 1b8ccd2..61f67b3 100644 --- a/src/Bannerlord.LauncherEx/ResourceManagers/FontFactoryManager.cs +++ b/src/Bannerlord.LauncherEx/ResourceManagers/FontFactoryManager.cs @@ -35,7 +35,8 @@ internal static bool Enable(Harmony harmony) private delegate void SetSpriteNamesDelegate(SpriteData instance, Dictionary value); private static readonly SetSpriteNamesDelegate? SetSpriteNames = - AccessTools2.GetPropertySetterDelegate(typeof(SpriteData), "SpriteNames"); + AccessTools2.GetPropertySetterDelegate(typeof(SpriteData), "SpriteNames") ?? + AccessTools2.GetPropertySetterDelegate(typeof(SpriteData), "Sprites"); private delegate void AddFontDefinitionDelegate(FontFactory instance, string fontPath, string fontName, SpriteData spriteData); private static readonly AddFontDefinitionDelegate? AddFontDefinition = @@ -77,7 +78,6 @@ private static void LoadAllFontsPostfix(ref FontFactory __instance) addFont(__instance, Path.Combine(BasePath.Name, "GUI", "GauntletUI", "Fonts", "NanumGothicKR") + "/", "NanumGothicKR", new SpriteData("NanumGothicKR").WithData("NanumGothicKR")); break; } - } private static bool GetMappedFontForLocalizationPrefix(ref FontFactory __instance, ref Font __result) { diff --git a/src/Bannerlord.LauncherEx/ResourceManagers/SpriteDataManager.cs b/src/Bannerlord.LauncherEx/ResourceManagers/SpriteDataManager.cs index 3e6b258..e433b5a 100644 --- a/src/Bannerlord.LauncherEx/ResourceManagers/SpriteDataManager.cs +++ b/src/Bannerlord.LauncherEx/ResourceManagers/SpriteDataManager.cs @@ -188,13 +188,39 @@ private static void PopulateTextureCoordinates(float[] outUVs, int uvsStartIndex } } #elif v134 + private sealed class SpriteGenericFromTexture : SpriteGeneric + { + private delegate void SetIsLoadedDelegate(SpriteCategory instance, bool value); + private static readonly SetIsLoadedDelegate? SetIsLoaded = + AccessTools2.GetPropertySetterDelegate(typeof(SpriteCategory), "IsLoaded"); + + private static SpritePart GetSpritePart(string name, Texture texture) + { + var data = new SpriteData(name); + var category = new SpriteCategory(name, 1) + { + SpriteSheets = + { + texture, + }, + SpriteSheetCount = 1, + }; + SetIsLoaded?.Invoke(category, true); + + return new SpritePart(name, category, texture.Width, texture.Height) + { + SheetID = 1, + }; + } + public SpriteGenericFromTexture(string name, Texture texture) : base(name, GetSpritePart(name, texture), SpriteNinePatchParameters.Empty) { } + } internal class SpriteFromTexture : Sprite { private readonly Texture _texture; public override Texture Texture => _texture; - public SpriteFromTexture(Texture texture, int width, int height) - : base("Sprite", width, height, SpriteNinePatchParameters.Empty) + public SpriteFromTexture(string name, Texture texture, int width, int height) + : base(name, width, height, SpriteNinePatchParameters.Empty) { _texture = texture; } @@ -217,9 +243,13 @@ public SpriteFromTexture(Texture texture, int width, int height) ? new SpriteFromTexture(name, new Texture(gc.GetTexture(name))) : null; #elif v134 - return GraphicsContextManager.Instance.TryGetTarget(out var gc) && gc is not null - ? new Texture(gc.GetTexture(name)) is { } tex ? new SpriteFromTexture(tex, tex.Width, tex.Height) : null! - : null; + if (GraphicsContextManager.Instance.TryGetTarget(out var gc) && gc is not null) + if (new Texture(gc.GetTexture(name)) is { } tex) + return new SpriteFromTexture(name, tex, tex.Width, tex.Height); + else + return (SpriteFromTexture) null!; + else + return (SpriteFromTexture?) null; #else #error DEFINE #endif @@ -232,9 +262,13 @@ public SpriteFromTexture(Texture texture, int width, int height) ? new SpriteGenericFromTexture(name, new Texture(gc.GetTexture(name))) : null; #elif v134 - return GraphicsContextManager.Instance.TryGetTarget(out var gc) && gc is not null - ? new Texture(gc.GetTexture(name)) is { } tex ? new SpriteFromTexture(tex, tex.Width, tex.Height) : null! - : null; + if (GraphicsContextManager.Instance.TryGetTarget(out var gc) && gc is not null) + if (new Texture(gc.GetTexture(name)) is { } tex) + return new SpriteGenericFromTexture(name, new Texture(gc.GetTexture(name))); + else + return (SpriteFromTexture) null!; + else + return (SpriteFromTexture?) null; #else #error DEFINE #endif diff --git a/src/Bannerlord.LauncherEx/TPac/AssetPackage.cs b/src/Bannerlord.LauncherEx/TPac/AssetPackage.cs index a40c213..45e39ac 100644 --- a/src/Bannerlord.LauncherEx/TPac/AssetPackage.cs +++ b/src/Bannerlord.LauncherEx/TPac/AssetPackage.cs @@ -71,8 +71,20 @@ protected virtual void Load(BinaryReader stream) assetItem.Version = assetVersion; assetItem.Name = stream.ReadSizedString(); + // Updated code taken from https://github.com/hunharibo/TpacTool var metadataSize = stream.ReadUInt64(); - assetItem.ReadMetadata(stream, (int) metadataSize); + var metadataStartPos = stream.BaseStream.Position; + + assetItem.ReadMetadata(stream, (int)metadataSize); + + var expectedPos = metadataStartPos + (long)metadataSize; + var actualPos = stream.BaseStream.Position; + if (actualPos != expectedPos) + { + // Force alignment if metadata reading didn't consume exact size + stream.BaseStream.Seek(expectedPos, SeekOrigin.Begin); + } + var unknownMetadataChecknum = stream.ReadInt64(); var dataSegmentNum = stream.ReadInt32(); diff --git a/src/Bannerlord.LauncherEx/TPac/Texture.cs b/src/Bannerlord.LauncherEx/TPac/Texture.cs index d2a1317..842f11c 100644 --- a/src/Bannerlord.LauncherEx/TPac/Texture.cs +++ b/src/Bannerlord.LauncherEx/TPac/Texture.cs @@ -50,11 +50,13 @@ public Texture() : base(TYPE_GUID) Source = string.Empty; } + // Updated code taken from https://github.com/hunharibo/TpacTool public override void ReadMetadata(BinaryReader stream, int totalSize) { - var pos = stream.BaseStream.Position; + var startPos = stream.BaseStream.Position; + var version = stream.ReadUInt32(); - stream.ReadGuid(); + var BillboardMaterial = stream.ReadGuid(); var UnknownUint1 = stream.ReadUInt32(); Source = stream.ReadSizedString(); var UnknownUlong = stream.ReadUInt64(); @@ -80,11 +82,13 @@ public override void ReadMetadata(BinaryReader stream, int totalSize) var UnknownUint6 = stream.ReadUInt32(); var UnknownUint7 = stream.ReadUInt32(); } + + var currentPos = stream.BaseStream.Position; + var bytesRead = currentPos - startPos; + var remaining = totalSize - bytesRead; - // dirty hack for 1.5.0 - // TW introduced a new field for the metadata of texture since 1.5.0 - // but they didn't bump the version of metadata - if (version >= 1 || totalSize - (stream.BaseStream.Position - pos) == 4) + // Check if we should read generated assets + if (version >= 1 || remaining == 4) { var numPair = stream.ReadUInt32(); for (var i = 0; i < numPair; i++) @@ -94,9 +98,35 @@ public override void ReadMetadata(BinaryReader stream, int totalSize) } } + // Handle version 2 and 3 fields if (version >= 2) { var UnknownUlong2 = stream.ReadUInt64(); + remaining -= 8; + } + + if (version >= 3) + { + stream.ReadBytes(32); + remaining -= 32; + } + + // Ensure we consume exactly totalSize bytes by reading any remaining data + currentPos = stream.BaseStream.Position; + bytesRead = currentPos - startPos; + remaining = totalSize - bytesRead; + + if (remaining > 0) + { + stream.ReadBytes((int)remaining); + } + else if (remaining < 0) + { +#if DEBUG + // This indicates we read too much - this is a serious error + throw new InvalidDataException($"Texture metadata read {-remaining} bytes beyond expected size. " + + $"Asset: {Name}, Expected: {totalSize}, Position: {currentPos}, Start: {startPos}"); +#endif } }