From 2832d22f1c38ea2053ae9bfbb2b8d2e93b93f803 Mon Sep 17 00:00:00 2001 From: Pyrdacor Date: Fri, 3 Mar 2023 15:09:20 +0100 Subject: [PATCH] Some additions for the new data loaders --- Ambermoon.Data.Common/Places.cs | 2 +- .../Serialization/DataReader.cs | 3 ++ .../Serialization/DataSerializer.cs | 2 + Ambermoon.Data.Pyrdacor/FileHeader.cs | 2 +- Ambermoon.Data.Pyrdacor/GameData.cs | 35 ++++++++++--- Ambermoon.Data.Pyrdacor/LazyFileLoader.cs | 10 ++-- Ambermoon.Data.Pyrdacor/Objects/Font.cs | 28 +++++++++- Ambermoon.Data.Pyrdacor/Objects/IngameFont.cs | 12 +++++ Ambermoon.Data.Pyrdacor/Objects/TextList.cs | 2 + Ambermoon.Data.Pyrdacor/PADF.cs | 6 +-- Ambermoon.Data.Pyrdacor/PADP.cs | 52 ++++++++----------- 11 files changed, 106 insertions(+), 48 deletions(-) diff --git a/Ambermoon.Data.Common/Places.cs b/Ambermoon.Data.Common/Places.cs index d9d81c23..a70857f3 100644 --- a/Ambermoon.Data.Common/Places.cs +++ b/Ambermoon.Data.Common/Places.cs @@ -48,7 +48,7 @@ public class Places { public List Entries { get; } = new List(); - private Places() + public Places() { } diff --git a/Ambermoon.Data.Legacy/Serialization/DataReader.cs b/Ambermoon.Data.Legacy/Serialization/DataReader.cs index 6f6b8563..75537770 100644 --- a/Ambermoon.Data.Legacy/Serialization/DataReader.cs +++ b/Ambermoon.Data.Legacy/Serialization/DataReader.cs @@ -139,6 +139,9 @@ public string ReadString(int length) public string ReadString(int length, Encoding encoding) { + if (length == 0) + return string.Empty; + CheckOutOfRange(length); var str = encoding.GetString(data, Position, length); str = str.Replace(encoding.GetString(new byte[] { 0xb4 }), "'"); diff --git a/Ambermoon.Data.Legacy/Serialization/DataSerializer.cs b/Ambermoon.Data.Legacy/Serialization/DataSerializer.cs index a7408e9b..f8a74d7a 100644 --- a/Ambermoon.Data.Legacy/Serialization/DataSerializer.cs +++ b/Ambermoon.Data.Legacy/Serialization/DataSerializer.cs @@ -5,9 +5,11 @@ namespace Ambermoon.Data.Legacy.Serialization { +#pragma warning disable CS8981 using word = UInt16; using dword = UInt32; using qword = UInt64; +#pragma warning restore CS8981 public class DataSerializer : IDataSerializer { diff --git a/Ambermoon.Data.Pyrdacor/FileHeader.cs b/Ambermoon.Data.Pyrdacor/FileHeader.cs index 790aa08d..88ff1621 100644 --- a/Ambermoon.Data.Pyrdacor/FileHeader.cs +++ b/Ambermoon.Data.Pyrdacor/FileHeader.cs @@ -13,7 +13,7 @@ public static bool CheckHeader(IDataReader dataReader, string header, bool skipI bool match = dataReader.ReadString(header.Length) == header; - if (!skipIfMatch) + if (!skipIfMatch || !match) dataReader.Position = position; return match; diff --git a/Ambermoon.Data.Pyrdacor/GameData.cs b/Ambermoon.Data.Pyrdacor/GameData.cs index 1d6c5f63..79c498a2 100644 --- a/Ambermoon.Data.Pyrdacor/GameData.cs +++ b/Ambermoon.Data.Pyrdacor/GameData.cs @@ -19,10 +19,10 @@ public class GameData : IGameData, IGraphicProvider LazyContainerLoader npcTextLoader; LazyContainerLoader partyTextLoader; LazyContainerLoader itemLoader; - LazyContainerLoader itemNameLoader; + LazyContainerLoader itemNameLoader; LazyContainerLoader itemTextLoader; LazyContainerLoader locationLoader; - LazyContainerLoader locationNameLoader; + LazyContainerLoader locationNameLoader; LazyContainerLoader tilesetLoader; LazyFileLoader gotoPointNameLoader; readonly Dictionary> fileHandlers = new Dictionary>(); @@ -35,6 +35,8 @@ public class GameData : IGameData, IGraphicProvider readonly Lazy outroLargeFont; readonly Lazy introSmallFont; readonly Lazy introLargeFont; + readonly Lazy places; + readonly Lazy ingameFontProvider; public bool Loaded { get; } = false; @@ -56,11 +58,11 @@ public class GameData : IGameData, IGraphicProvider public IReadOnlyList CursorHotspots => throw new NotImplementedException(); - public Places Places => throw new NotImplementedException(); + public Places Places => places!.Value; public IItemManager ItemManager => itemManager!.Value; - public IFontProvider FontProvider => throw new NotImplementedException(); + public IFontProvider FontProvider => ingameFontProvider!.Value; public IDataNameProvider DataNameProvider => throw new NotImplementedException(); @@ -176,6 +178,27 @@ public GameData(Stream stream) outroLargeFont = new Lazy(() => fontLoader!.Load(FontData.OutroLargeFontIndex)); introSmallFont = new Lazy(() => fontLoader!.Load(FontData.IntroSmallFontIndex)); introLargeFont = new Lazy(() => fontLoader!.Load(FontData.IntroLargeFontIndex)); + ingameFontProvider = new Lazy(() => new(ingameFont!.Value)); + places = new Lazy(() => + { + var places = new Places(); + var locationData = locationLoader!.LoadAll(); + var locationNames = locationNameLoader!.LoadAll(); + + if (locationData.Count != locationNames.Count) + throw new AmbermoonException(ExceptionScope.Data, "Mismatch between number of location data and location name entries."); + + foreach (var location in locationData) + { + if (!locationNames.TryGetValue(location.Key, out var name)) + throw new AmbermoonException(ExceptionScope.Data, $"Missing location name for location data {location.Key}."); + + location.Value.Name = name; + + places.Entries.Add(location.Value); + } + return places; + }); // Read all files int fileCount = reader.ReadWord(); @@ -244,7 +267,7 @@ void LoadItems(IDataReader dataReader) void LoadItemNames(IDataReader dataReader) { - itemNameLoader = new LazyContainerLoader(dataReader, this, t => t.TextList); + itemNameLoader = new LazyContainerLoader(dataReader, this, t => t.TextList.First()!); } void LoadItemTexts(IDataReader dataReader) @@ -259,7 +282,7 @@ void LoadLocations(IDataReader dataReader) void LoadLocationNames(IDataReader dataReader) { - + locationNameLoader = new LazyContainerLoader(dataReader, this, t => t.TextList.First()!); } void LoadOutro(IDataReader dataReader) diff --git a/Ambermoon.Data.Pyrdacor/LazyFileLoader.cs b/Ambermoon.Data.Pyrdacor/LazyFileLoader.cs index 4720d3e4..0f8a405a 100644 --- a/Ambermoon.Data.Pyrdacor/LazyFileLoader.cs +++ b/Ambermoon.Data.Pyrdacor/LazyFileLoader.cs @@ -13,7 +13,7 @@ public LazyFileLoader(IDataReader dataReader, GameData gameData, Func valu { loader = () => { - var item = new PADF().Read(dataReader, gameData); + var item = PADF.Read(dataReader, gameData); if (item is T typedItem) return typedItem; @@ -37,22 +37,20 @@ public U Load() public LazyContainerLoader(IDataReader dataReader, GameData gameData, Func valueProvider) { - loader = () => new PADP().Read(dataReader, gameData); + loader = () => PADP.Read(dataReader, gameData); this.valueProvider = valueProvider; } public U Load(ushort key) { - if (items == null) - items = loader(); + items ??= loader(); return valueProvider(items[key]); } public Dictionary LoadAll() { - if (items == null) - items = loader(); + items ??= loader(); return items.ToDictionary(i => (uint)i.Key, i => valueProvider(i.Value)); } diff --git a/Ambermoon.Data.Pyrdacor/Objects/Font.cs b/Ambermoon.Data.Pyrdacor/Objects/Font.cs index 79d303a2..ec633d8c 100644 --- a/Ambermoon.Data.Pyrdacor/Objects/Font.cs +++ b/Ambermoon.Data.Pyrdacor/Objects/Font.cs @@ -1,8 +1,32 @@ -using Ambermoon.Data.Legacy.Serialization; -using Ambermoon.Data.Serialization; +using Ambermoon.Data.Serialization; namespace Ambermoon.Data.Pyrdacor.Objects { + /** + * Fonts can be monospaced or regular. + * The font data always starts with a 4 byte header. + * - byte GlyphWidth + * - byte GlyphHeight + * - word GlyphCount + * + * If GlyphWidth is not 0, it is a monospace font and + * each glyph uses the given dimensions. The advance + * value equals the glyph width in this case. The glyph + * image data follows the header immediately. + * + * If GlyphWidth is 0, it is a regular font where each + * glyph has a different width and optionally also a + * different advance value. The glyph height however is + * again the same for each glyph as specified in the header. + * For each glyph an entry follows the header. The first byte + * gives the glyph width. Only the 7 lower bits are considered + * so the max glyph width is 127. A width of 0 is possible. The + * glyph is not visibile then. If the msb of the value is set, + * this means that the glyph has a custom advanced value. In + * this case another byte follows giving the advance (0 to 255). + * If the msb was instead 0 the advance value equals the given + * glyph width. + */ internal class Font { readonly List glyphs; diff --git a/Ambermoon.Data.Pyrdacor/Objects/IngameFont.cs b/Ambermoon.Data.Pyrdacor/Objects/IngameFont.cs index 99e25d99..b7dd2a88 100644 --- a/Ambermoon.Data.Pyrdacor/Objects/IngameFont.cs +++ b/Ambermoon.Data.Pyrdacor/Objects/IngameFont.cs @@ -15,4 +15,16 @@ public IngameFont(Func fontProvider, Func digitFontProvider) public Graphic GetDigitGlyphGraphic(uint glyphIndex) => digitFont.Value.GetGlyphGraphic(glyphIndex); } + + internal class IngameFontProvider : IFontProvider + { + private readonly IngameFont ingameFont; + + public IngameFontProvider(IngameFont ingameFont) + { + this.ingameFont = ingameFont; + } + + public IFont GetFont() => ingameFont; + } } diff --git a/Ambermoon.Data.Pyrdacor/Objects/TextList.cs b/Ambermoon.Data.Pyrdacor/Objects/TextList.cs index 3d8c80bb..ec14974b 100644 --- a/Ambermoon.Data.Pyrdacor/Objects/TextList.cs +++ b/Ambermoon.Data.Pyrdacor/Objects/TextList.cs @@ -16,6 +16,8 @@ public TextList(IDataReader dataReader) texts.Add(dataReader.ReadString()); } + public string? First() => GetText(0); + public string? GetText(int index) => index >= texts.Count ? null : texts[index]; public void Write(IDataWriter dataWriter) diff --git a/Ambermoon.Data.Pyrdacor/PADF.cs b/Ambermoon.Data.Pyrdacor/PADF.cs index ec4e509d..2f31b378 100644 --- a/Ambermoon.Data.Pyrdacor/PADF.cs +++ b/Ambermoon.Data.Pyrdacor/PADF.cs @@ -7,7 +7,7 @@ namespace Ambermoon.Data.Pyrdacor /// /// Pyrdacor's Ambermoon Data File /// - internal class PADF + internal static class PADF { public const string Header = "PADF"; @@ -43,7 +43,7 @@ static PADF() Compressions.Add(ICompression.RLE0.Identifier, ICompression.RLE0); } - public IFileSpec Read(IDataReader reader, GameData gameData) + public static IFileSpec Read(IDataReader reader, GameData gameData) { if (!FileHeader.CheckHeader(reader, Header, true)) throw new AmbermoonException(ExceptionScope.Data, "The file is no PADF"); @@ -72,7 +72,7 @@ public IFileSpec Read(IDataReader reader, GameData gameData) return fileSpec; } - public void Write(IDataWriter writer, IFileSpec fileSpec, ICompression? compression = null) + public static void Write(IDataWriter writer, IFileSpec fileSpec, ICompression? compression = null) { writer.WriteWithoutLength(Header); writer.WriteWithoutLength(fileSpec.Magic); diff --git a/Ambermoon.Data.Pyrdacor/PADP.cs b/Ambermoon.Data.Pyrdacor/PADP.cs index c5f6ab91..7db5415f 100644 --- a/Ambermoon.Data.Pyrdacor/PADP.cs +++ b/Ambermoon.Data.Pyrdacor/PADP.cs @@ -8,20 +8,17 @@ namespace Ambermoon.Data.Pyrdacor /// /// Pyrdacor's Ambermoon Data Package /// - internal class PADP + internal static class PADP { // Note: The max file count is 0xffff-1. As file indices are 1-based, file 0 is not valid. // So only indices 1 to 0xffff can be stored. In sum there is space for the given file count. public const string Header = "PADP"; - bool writeNoFileIndices = false; - bool wasPADF = false; - - public Dictionary Read(IDataReader reader, GameData gameData) where T : IFileSpec, new() + public static Dictionary Read(IDataReader reader, GameData gameData) where T : IFileSpec, new() { - wasPADF = false; - var result = InternalRead(reader, gameData, IFileSpec.GetSupportedVersion()); + bool wasPADF = false; + var result = InternalRead(reader, gameData, ref wasPADF, IFileSpec.GetSupportedVersion()); if (result.Count == 0) return new Dictionary(); @@ -38,17 +35,18 @@ internal class PADP return result.ToDictionary(r => r.Key, r => (T)r.Value); } - public Dictionary Read(IDataReader reader, GameData gameData) + public static Dictionary Read(IDataReader reader, GameData gameData) { - return InternalRead(reader, gameData); + bool wasPADF = false; + return InternalRead(reader, gameData, ref wasPADF); } - Dictionary InternalRead(IDataReader reader, GameData gameData, byte? supportedVersion = null) + private static Dictionary InternalRead(IDataReader reader, GameData gameData, ref bool wasPADF, byte? supportedVersion = null) { if (FileHeader.CheckHeader(reader, PADF.Header, false)) { wasPADF = true; - var spec = new PADF().Read(reader, gameData); + var spec = PADF.Read(reader, gameData); return new Dictionary { { (ushort)1u, spec } }; } @@ -111,7 +109,20 @@ Dictionary InternalRead(IDataReader reader, GameData gameData return files; } - public void Write(IDataWriter writer, IDictionary fileSpecs, ICompression? compression = null) where T : IFileSpec, new() + public static void Write(IDataWriter writer, IDictionary fileSpecs, ICompression? compression = null) where T : IFileSpec, new() + { + InternalWrite(writer, fileSpecs, compression, false); + } + + public static void Write(IDataWriter writer, IEnumerable fileSpecs, ICompression? compression = null) where T : IFileSpec, new() + { + if (fileSpecs.Count() >= ushort.MaxValue) + throw new AmbermoonException(ExceptionScope.Data, $"Too many files given for PADP. Allowed are {ushort.MaxValue - 1}, given are {fileSpecs.Count()}."); + + InternalWrite(writer, fileSpecs.Select((s, i) => new { i, s }).ToDictionary(s => (ushort)(1 + s.i), s => s.s), compression, true); + } + + private static void InternalWrite(IDataWriter writer, IDictionary fileSpecs, ICompression? compression, bool writeNoFileIndices) where T : IFileSpec, new() { if (fileSpecs.Count >= ushort.MaxValue) throw new AmbermoonException(ExceptionScope.Data, $"Too many files given for PADP. Allowed are {ushort.MaxValue - 1}, given are {fileSpecs.Count}."); @@ -164,22 +175,5 @@ Dictionary InternalRead(IDataReader reader, GameData gameData // Write data writer.Write(dataWriter.ToArray()); } - - public void Write(IDataWriter writer, IEnumerable fileSpecs, ICompression? compression = null) where T : IFileSpec, new() - { - if (fileSpecs.Count() >= ushort.MaxValue) - throw new AmbermoonException(ExceptionScope.Data, $"Too many files given for PADP. Allowed are {ushort.MaxValue - 1}, given are {fileSpecs.Count()}."); - - writeNoFileIndices = true; - - try - { - Write(writer, fileSpecs.Select((s, i) => new { i, s }).ToDictionary(s => (ushort)(1 + s.i), s => s.s), compression); - } - finally - { - writeNoFileIndices = false; - } - } } } \ No newline at end of file