From f664eab05e59d801a5fd5ff9e5ddbcb83d4ee554 Mon Sep 17 00:00:00 2001 From: Kurt Date: Tue, 31 Mar 2020 11:56:06 -0700 Subject: [PATCH] Add msbt parse classes for docs Originally ported from https://github.com/Martin005/SMO_Translation_Helper , via https://github.com/IcySon55/3DLandMSBTeditor Add some tests to ensure functions --- NHSE.Parsing/BCSV/BCSV.cs | 117 ++++++++++++ NHSE.Parsing/BCSV/BCSVConverter.cs | 36 ++++ NHSE.Parsing/BCSV/BCSVFieldParam.cs | 17 ++ NHSE.Parsing/MSBT/BinaryReaderX.cs | 53 ++++++ NHSE.Parsing/MSBT/ByteOrder.cs | 8 + NHSE.Parsing/MSBT/IMSBTEntry.cs | 12 ++ NHSE.Parsing/MSBT/LBL1.cs | 12 ++ NHSE.Parsing/MSBT/MSBT.cs | 171 ++++++++++++++++++ NHSE.Parsing/MSBT/MSBTEncodingByte.cs | 8 + NHSE.Parsing/MSBT/MSBTGroup.cs | 8 + NHSE.Parsing/MSBT/MSBTHeader.cs | 17 ++ NHSE.Parsing/MSBT/MSBTLabel.cs | 21 +++ NHSE.Parsing/MSBT/MSBTSection.cs | 9 + NHSE.Parsing/MSBT/MSBTTextString.cs | 20 ++ NHSE.Parsing/MSBT/MSBTUtil.cs | 24 +++ NHSE.Parsing/MSBT/TXT2.cs | 11 ++ NHSE.Parsing/NHSE.Parsing.csproj | 1 + NHSE.Tests/MSBTTests.cs | 36 ++++ NHSE.Tests/NHSE.Tests.csproj | 20 ++ NHSE.Tests/Properties/Resources.Designer.cs | 83 +++++++++ NHSE.Tests/Properties/Resources.resx | 127 +++++++++++++ .../Resources/STR_ItemName_41_Turnip.msbt | Bin 0 -> 800 bytes NHSE.Tests/Resources/STR_TownName.msbt | Bin 0 -> 576 bytes 23 files changed, 811 insertions(+) create mode 100644 NHSE.Parsing/BCSV/BCSV.cs create mode 100644 NHSE.Parsing/BCSV/BCSVConverter.cs create mode 100644 NHSE.Parsing/BCSV/BCSVFieldParam.cs create mode 100644 NHSE.Parsing/MSBT/BinaryReaderX.cs create mode 100644 NHSE.Parsing/MSBT/ByteOrder.cs create mode 100644 NHSE.Parsing/MSBT/IMSBTEntry.cs create mode 100644 NHSE.Parsing/MSBT/LBL1.cs create mode 100644 NHSE.Parsing/MSBT/MSBT.cs create mode 100644 NHSE.Parsing/MSBT/MSBTEncodingByte.cs create mode 100644 NHSE.Parsing/MSBT/MSBTGroup.cs create mode 100644 NHSE.Parsing/MSBT/MSBTHeader.cs create mode 100644 NHSE.Parsing/MSBT/MSBTLabel.cs create mode 100644 NHSE.Parsing/MSBT/MSBTSection.cs create mode 100644 NHSE.Parsing/MSBT/MSBTTextString.cs create mode 100644 NHSE.Parsing/MSBT/MSBTUtil.cs create mode 100644 NHSE.Parsing/MSBT/TXT2.cs create mode 100644 NHSE.Tests/MSBTTests.cs create mode 100644 NHSE.Tests/Properties/Resources.Designer.cs create mode 100644 NHSE.Tests/Properties/Resources.resx create mode 100644 NHSE.Tests/Resources/STR_ItemName_41_Turnip.msbt create mode 100644 NHSE.Tests/Resources/STR_TownName.msbt diff --git a/NHSE.Parsing/BCSV/BCSV.cs b/NHSE.Parsing/BCSV/BCSV.cs new file mode 100644 index 0000000..520e16f --- /dev/null +++ b/NHSE.Parsing/BCSV/BCSV.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NHSE.Parsing +{ + public class BCSV + { + public const int MAGIC = 0x42435356; // BCSV + + public readonly byte[] Data; + + public uint EntryCount { get; set; } + public uint EntryLength { get; set; } + public ushort FieldCount { get; set; } + public bool HasBCSVHeader { get; set; } + public bool Flag2 { get; set; } + + public uint Magic { get; set; } + public int Unknown { get; set; } + public int Unknown1 { get; set; } + public int Unknown2 { get; set; } + + private readonly int FieldTableStart; + public readonly IReadOnlyList FieldOffsets; + + public BCSV(byte[] data) + { + Data = data; + + EntryCount = BitConverter.ToUInt32(data, 0x0); + EntryLength = BitConverter.ToUInt32(data, 0x4); + FieldCount = BitConverter.ToUInt16(data, 0x8); + HasBCSVHeader = data[0xA] == 1; + Flag2 = data[0xB] == 1; + if (HasBCSVHeader) + { + Magic = BitConverter.ToUInt32(data, 0xC); + if (Magic != MAGIC) + throw new ArgumentException(nameof(Magic)); + Unknown = BitConverter.ToInt32(data, 0x10); + Unknown1 = BitConverter.ToInt32(data, 0x14); + Unknown2 = BitConverter.ToInt32(data, 0x18); + FieldTableStart = 0x1C; + } + else + { + FieldTableStart = 0x0C; + } + + var fields = new BCSVFieldParam[FieldCount]; + for (int i = 0; i < fields.Length; i++) + { + var ofs = FieldTableStart + (i * BCSVFieldParam.SIZE); + var ident = BitConverter.ToUInt32(data, ofs); + var fo = BitConverter.ToInt32(data, ofs + 4); + + fields[i] = new BCSVFieldParam(ident, fo, i); + } + + FieldOffsets = fields; + } + + private int GetFirstEntryOffset() => FieldTableStart + (FieldCount * BCSVFieldParam.SIZE); + private int GetEntryOffset(int start, int entry) => start + (entry * (int)EntryLength); + + private string ReadFieldUnknownType(in int offset, in int fieldIndex) + { + var length = GetFieldLength(fieldIndex); + switch (length) + { + case 1: return Data[offset].ToString(); + case 2: return BitConverter.ToInt16(Data, offset).ToString(); + case 4: return $"0x{BitConverter.ToUInt32(Data, offset):X8}"; + case 8: return $"0x{BitConverter.ToUInt64(Data, offset):X16}"; + + default: return Encoding.UTF8.GetString(Data, offset, length); + } + } + + private int GetFieldLength(in int i) + { + var next = (i + 1 == FieldCount) ? (int)(EntryLength) : FieldOffsets[i + 1].Offset; + var ofs = FieldOffsets[i].Offset; + return next - ofs; + } + + public string[] ReadCSV(string delim = "\t") + { + var result = new string[EntryCount + 1]; + result[0] = string.Join(delim, FieldOffsets.Select(z => $"0x{z.ColumnKey:X8}")); + + var start = GetFirstEntryOffset(); + for (int entry = 0; entry < EntryCount; entry++) + { + var ofs = GetEntryOffset(start, entry); + string[] fields = new string[FieldCount]; + for (int f = 0; f < fields.Length; f++) + { + var fo = ofs + FieldOffsets[f].Offset; + fields[f] = ReadFieldUnknownType(fo, f); + } + result[entry + 1] = string.Join(delim, fields); + } + + return result; + } + + public string ReadValue(int entry, BCSVFieldParam f) + { + var start = GetFirstEntryOffset(); + var ofs = GetEntryOffset(start, entry); + return ReadFieldUnknownType(ofs + f.Offset, f.Index); + } + } +} \ No newline at end of file diff --git a/NHSE.Parsing/BCSV/BCSVConverter.cs b/NHSE.Parsing/BCSV/BCSVConverter.cs new file mode 100644 index 0000000..207695d --- /dev/null +++ b/NHSE.Parsing/BCSV/BCSVConverter.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace NHSE.Parsing +{ + public static class BCSVConverter + { + public static IReadOnlyDictionary GetFieldDictionary(this BCSV csv) + { + return csv.FieldOffsets.ToDictionary(z => z.ColumnKey, z => z); + } + + public static void DumpAll(string path, string dest, string delim = "\t") + { + var files = Directory.EnumerateFiles(path); + Directory.CreateDirectory(dest); + foreach (var f in files) + { + var fn = Path.GetFileNameWithoutExtension(f); + var df = Path.Combine(dest, $"{fn}.csv"); + Dump(f, df, delim); + } + } + + public static void Dump(string path, string dest, string delim = "\t") + { + var data = File.ReadAllBytes(path); + var bcsv = new BCSV(data); + var result = bcsv.ReadCSV(delim); + File.WriteAllLines(dest, result); + Console.WriteLine($"Dumped to CSV: {path}"); + } + } +} \ No newline at end of file diff --git a/NHSE.Parsing/BCSV/BCSVFieldParam.cs b/NHSE.Parsing/BCSV/BCSVFieldParam.cs new file mode 100644 index 0000000..9efebbb --- /dev/null +++ b/NHSE.Parsing/BCSV/BCSVFieldParam.cs @@ -0,0 +1,17 @@ +namespace NHSE.Parsing +{ + public class BCSVFieldParam + { + public const int SIZE = 8; + public readonly uint ColumnKey; + public readonly int Offset; + public readonly int Index; + + public BCSVFieldParam(uint key, int offset, int index) + { + ColumnKey = key; + Offset = offset; + Index = index; + } + } +} \ No newline at end of file diff --git a/NHSE.Parsing/MSBT/BinaryReaderX.cs b/NHSE.Parsing/MSBT/BinaryReaderX.cs new file mode 100644 index 0000000..1c19a65 --- /dev/null +++ b/NHSE.Parsing/MSBT/BinaryReaderX.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace NHSE.Parsing +{ + internal class BinaryReaderX : BinaryReader + { + public ByteOrder ByteOrder { get; set; } + + public BinaryReaderX(Stream input, ByteOrder byteOrder = ByteOrder.LittleEndian) + : base(input) + { + ByteOrder = byteOrder; + } + + public override ushort ReadUInt16() + { + if (ByteOrder == ByteOrder.LittleEndian) + return base.ReadUInt16(); + else + return BitConverter.ToUInt16(base.ReadBytes(2).Reverse().ToArray(), 0); + } + + public override uint ReadUInt32() + { + if (ByteOrder == ByteOrder.LittleEndian) + return base.ReadUInt32(); + else + return BitConverter.ToUInt32(base.ReadBytes(4).Reverse().ToArray(), 0); + } + + public string ReadString(int length) + { + return Encoding.ASCII.GetString(ReadBytes(length)).TrimEnd('\0'); + } + + public string PeekString(int length = 4) + { + List bytes = new List(); + long startOffset = BaseStream.Position; + + for (int i = 0; i < length; i++) + bytes.Add(ReadByte()); + + BaseStream.Seek(startOffset, SeekOrigin.Begin); + + return Encoding.ASCII.GetString(bytes.ToArray()); + } + } +} \ No newline at end of file diff --git a/NHSE.Parsing/MSBT/ByteOrder.cs b/NHSE.Parsing/MSBT/ByteOrder.cs new file mode 100644 index 0000000..77b051d --- /dev/null +++ b/NHSE.Parsing/MSBT/ByteOrder.cs @@ -0,0 +1,8 @@ +namespace NHSE.Parsing +{ + public enum ByteOrder + { + LittleEndian = 0, + BigEndian = 1 + } +} \ No newline at end of file diff --git a/NHSE.Parsing/MSBT/IMSBTEntry.cs b/NHSE.Parsing/MSBT/IMSBTEntry.cs new file mode 100644 index 0000000..b97b28a --- /dev/null +++ b/NHSE.Parsing/MSBT/IMSBTEntry.cs @@ -0,0 +1,12 @@ +using System.Text; + +namespace NHSE.Parsing +{ + public interface IMSBTEntry + { + string ToString(); + string ToString(Encoding encoding); + byte[] Value { get; set; } + uint Index { get; set; } + } +} \ No newline at end of file diff --git a/NHSE.Parsing/MSBT/LBL1.cs b/NHSE.Parsing/MSBT/LBL1.cs new file mode 100644 index 0000000..2b2500a --- /dev/null +++ b/NHSE.Parsing/MSBT/LBL1.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace NHSE.Parsing +{ + public class LBL1 : MSBTSection + { + public uint NumberOfGroups; + + public List Groups = new List(); + public List Labels = new List(); + } +} \ No newline at end of file diff --git a/NHSE.Parsing/MSBT/MSBT.cs b/NHSE.Parsing/MSBT/MSBT.cs new file mode 100644 index 0000000..dd92371 --- /dev/null +++ b/NHSE.Parsing/MSBT/MSBT.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace NHSE.Parsing +{ + public class MSBT + { + public readonly MSBTHeader Header = new MSBTHeader(); + public readonly LBL1 LBL1 = new LBL1(); + public readonly TXT2 TXT2 = new TXT2(); + public readonly Encoding FileEncoding; + public readonly List SectionOrder; + public bool HasLabels; + + public MSBT(byte[] rawBytes) + { + using var stream = new MemoryStream(rawBytes); + using var br = new BinaryReaderX(stream); + + // Header + Header.Identifier = br.ReadString(8); + if (Header.Identifier != "MsgStdBn") + throw new ArgumentException("The file provided is not a valid MSBT file.", nameof(rawBytes)); + + // Byte Order + Header.ByteOrderMark = br.ReadBytes(2); + br.ByteOrder = Header.ByteOrderMark[0] > Header.ByteOrderMark[1] ? ByteOrder.LittleEndian : ByteOrder.BigEndian; + + Header.Unknown1 = br.ReadUInt16(); + + // Encoding + Header.EncodingByte = (MSBTEncodingByte)br.ReadByte(); + FileEncoding = (Header.EncodingByte == MSBTEncodingByte.UTF8 ? Encoding.UTF8 : Encoding.Unicode); + + Header.Unknown2 = br.ReadByte(); + Header.NumberOfSections = br.ReadUInt16(); + Header.Unknown3 = br.ReadUInt16(); + Header.FileSizeOffset = (uint)br.BaseStream.Position; // Record offset for future use + Header.FileSize = br.ReadUInt32(); + Header.Unknown4 = br.ReadBytes(10); + + if (Header.FileSize != br.BaseStream.Length) + throw new ArgumentException("The file provided is not a valid MSBT file.", nameof(rawBytes)); + + SectionOrder = new List(); + for (int i = 0; i < Header.NumberOfSections; i++) + { + var peek = br.PeekString(); + switch (peek) + { + case "LBL1": + ReadLBL1(br); + SectionOrder.Add("LBL1"); + break; + + case "ATR1": + // don't care + br.ReadBytes(sizeof(uint) + sizeof(uint) + sizeof(long) + 1); + while (br.BaseStream.Position % 16 != 0 && br.BaseStream.Position != br.BaseStream.Length) + br.ReadByte(); + SectionOrder.Add("ATR1"); + break; + + case "TXT2": + ReadTXT2(br); + SectionOrder.Add("TXT2"); + break; + } + } + + br.Close(); + } + + private void ReadLBL1(BinaryReaderX br) + { + LBL1.Identifier = br.ReadString(4); + LBL1.SectionSize = br.ReadUInt32(); + LBL1.Padding1 = br.ReadBytes(8); + long startOfLabels = br.BaseStream.Position; + LBL1.NumberOfGroups = br.ReadUInt32(); + + for (int i = 0; i < LBL1.NumberOfGroups; i++) + { + var grp = new MSBTGroup + { + NumberOfLabels = br.ReadUInt32(), + Offset = br.ReadUInt32() + }; + LBL1.Groups.Add(grp); + } + + foreach (var grp in LBL1.Groups) + { + br.BaseStream.Seek(startOfLabels + grp.Offset, SeekOrigin.Begin); + + for (int i = 0; i < grp.NumberOfLabels; i++) + { + MSBTLabel lbl = new MSBTLabel {Length = Convert.ToUInt32(br.ReadByte())}; + lbl.Name = br.ReadString((int)lbl.Length); + lbl.Index = br.ReadUInt32(); + LBL1.Labels.Add(lbl); + } + } + + if (LBL1.Labels.Count > 0) + HasLabels = true; + + PaddingSeek(br); + } + + private void ReadTXT2(BinaryReaderX br) + { + TXT2.Identifier = br.ReadString(4); + TXT2.SectionSize = br.ReadUInt32(); + TXT2.Padding1 = br.ReadBytes(8); + long startOfStrings = br.BaseStream.Position; + TXT2.NumberOfStrings = br.ReadUInt32(); + + var offsets = new List(); + for (int i = 0; i < TXT2.NumberOfStrings; i++) + offsets.Add(br.ReadUInt32()); + + for (int i = 0; i < TXT2.NumberOfStrings; i++) + { + uint nextOffset = (i + 1 < offsets.Count) ? ((uint)startOfStrings + offsets[i + 1]) : ((uint)startOfStrings + TXT2.SectionSize); + + br.BaseStream.Seek(startOfStrings + offsets[i], SeekOrigin.Begin); + + var result = new List(); + var str = new MSBTTextString(); + while (br.BaseStream.Position < nextOffset && br.BaseStream.Position < Header.FileSize) + { + if (Header.EncodingByte == MSBTEncodingByte.UTF8) + { + result.Add(br.ReadByte()); + } + else + { + byte[] unichar = br.ReadBytes(2); + + if (br.ByteOrder == ByteOrder.BigEndian) + Array.Reverse(unichar); + + result.AddRange(unichar); + } + } + str.Value = result.ToArray(); + str.Index = (uint)i; + TXT2.Strings.Add(str); + } + + // Tie in LBL1 labels + foreach (var lbl in LBL1.Labels) + lbl.String = TXT2.Strings[(int)lbl.Index]; + + PaddingSeek(br); + } + + private static void PaddingSeek(BinaryReader br) + { + long remainder = br.BaseStream.Position % 16; + if (remainder <= 0) + return; + var _ = br.ReadByte(); + br.BaseStream.Seek(-1, SeekOrigin.Current); + br.BaseStream.Seek(16 - remainder, SeekOrigin.Current); + } + } +} diff --git a/NHSE.Parsing/MSBT/MSBTEncodingByte.cs b/NHSE.Parsing/MSBT/MSBTEncodingByte.cs new file mode 100644 index 0000000..7ad7259 --- /dev/null +++ b/NHSE.Parsing/MSBT/MSBTEncodingByte.cs @@ -0,0 +1,8 @@ +namespace NHSE.Parsing +{ + public enum MSBTEncodingByte : byte + { + UTF8 = 0x00, + Unicode = 0x01 + } +} \ No newline at end of file diff --git a/NHSE.Parsing/MSBT/MSBTGroup.cs b/NHSE.Parsing/MSBT/MSBTGroup.cs new file mode 100644 index 0000000..0a0ed08 --- /dev/null +++ b/NHSE.Parsing/MSBT/MSBTGroup.cs @@ -0,0 +1,8 @@ +namespace NHSE.Parsing +{ + public class MSBTGroup + { + public uint NumberOfLabels; + public uint Offset; + } +} \ No newline at end of file diff --git a/NHSE.Parsing/MSBT/MSBTHeader.cs b/NHSE.Parsing/MSBT/MSBTHeader.cs new file mode 100644 index 0000000..dfa72aa --- /dev/null +++ b/NHSE.Parsing/MSBT/MSBTHeader.cs @@ -0,0 +1,17 @@ +namespace NHSE.Parsing +{ + public class MSBTHeader + { + public string Identifier; // MsgStdBn + public byte[] ByteOrderMark; + public ushort Unknown1; // Always 0x0000 + public MSBTEncodingByte EncodingByte; + public byte Unknown2; // Always 0x03 + public ushort NumberOfSections; + public ushort Unknown3; // Always 0x0000 + public uint FileSize; + public byte[] Unknown4; // Always 0x0000 0000 0000 0000 0000 + + public uint FileSizeOffset; + } +} \ No newline at end of file diff --git a/NHSE.Parsing/MSBT/MSBTLabel.cs b/NHSE.Parsing/MSBT/MSBTLabel.cs new file mode 100644 index 0000000..cb96bb5 --- /dev/null +++ b/NHSE.Parsing/MSBT/MSBTLabel.cs @@ -0,0 +1,21 @@ +using System.Text; + +namespace NHSE.Parsing +{ + public class MSBTLabel : IMSBTEntry + { + public uint Length; + public string Name; + public MSBTTextString String; + + public byte[] Value + { + get => String.Value; + set => String.Value = value; + } + + public uint Index { get; set; } + public override string ToString() => Length > 0 ? Name : (Index + 1).ToString(); + public string ToString(Encoding encoding) => Length > 0 ? Name : (Index + 1).ToString(); + } +} \ No newline at end of file diff --git a/NHSE.Parsing/MSBT/MSBTSection.cs b/NHSE.Parsing/MSBT/MSBTSection.cs new file mode 100644 index 0000000..734e73e --- /dev/null +++ b/NHSE.Parsing/MSBT/MSBTSection.cs @@ -0,0 +1,9 @@ +namespace NHSE.Parsing +{ + public class MSBTSection + { + public string Identifier; + public uint SectionSize; // Begins after Unknown1 + public byte[] Padding1; // Always 0x0000 0000 + } +} \ No newline at end of file diff --git a/NHSE.Parsing/MSBT/MSBTTextString.cs b/NHSE.Parsing/MSBT/MSBTTextString.cs new file mode 100644 index 0000000..efc2b97 --- /dev/null +++ b/NHSE.Parsing/MSBT/MSBTTextString.cs @@ -0,0 +1,20 @@ +using System.Text; + +namespace NHSE.Parsing +{ + public class MSBTTextString : IMSBTEntry + { + public byte[] Value { get; set; } + public uint Index { get; set; } + + public override string ToString() + { + return (Index + 1).ToString(); + } + + public string ToString(Encoding encoding) + { + return encoding.GetString(Value); + } + } +} \ No newline at end of file diff --git a/NHSE.Parsing/MSBT/MSBTUtil.cs b/NHSE.Parsing/MSBT/MSBTUtil.cs new file mode 100644 index 0000000..c4035ea --- /dev/null +++ b/NHSE.Parsing/MSBT/MSBTUtil.cs @@ -0,0 +1,24 @@ +using System.Diagnostics; +using System.Linq; + +namespace NHSE.Parsing +{ + public static class MSBTUtil + { + public static void DebugDumpLines(this MSBT obj) + { + var sorted = obj.LBL1.Labels + .Where(z => !z.Name.EndsWith("_pl")) + .OrderBy(z => z.Index); + + foreach (var x in sorted) + { + var index = x.Index; + var name = x.Name; + var data = obj.TXT2.Strings[(int) index]; + var line = data.ToString(obj.FileEncoding).TrimEnd('\0'); + Debug.WriteLine($"{index}\t{name}\t{line}"); + } + } + } +} diff --git a/NHSE.Parsing/MSBT/TXT2.cs b/NHSE.Parsing/MSBT/TXT2.cs new file mode 100644 index 0000000..2ecea2e --- /dev/null +++ b/NHSE.Parsing/MSBT/TXT2.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace NHSE.Parsing +{ + public class TXT2 : MSBTSection + { + public uint NumberOfStrings; + + public List Strings = new List(); + } +} \ No newline at end of file diff --git a/NHSE.Parsing/NHSE.Parsing.csproj b/NHSE.Parsing/NHSE.Parsing.csproj index 9f5c4f4..0d97644 100644 --- a/NHSE.Parsing/NHSE.Parsing.csproj +++ b/NHSE.Parsing/NHSE.Parsing.csproj @@ -2,6 +2,7 @@ netstandard2.0 + 8 diff --git a/NHSE.Tests/MSBTTests.cs b/NHSE.Tests/MSBTTests.cs new file mode 100644 index 0000000..2444268 --- /dev/null +++ b/NHSE.Tests/MSBTTests.cs @@ -0,0 +1,36 @@ +using System.Diagnostics; +using System.Linq; +using FluentAssertions; +using NHSE.Parsing; +using NHSE.Tests.Properties; +using Xunit; + +namespace NHSE.Tests +{ + public static class MSBTTests + { + [Fact] + public static void TestTownDefaultNames() + { + var data = Resources.STR_TownName; + var obj = new MSBT(data); + obj.SectionOrder.Count.Should().Be(3); + obj.TXT2.Strings.Count.Should().BeGreaterThan(0); + + var str = obj.TXT2.Strings[8].ToString(obj.FileEncoding).TrimEnd('\0'); + str.Should().Be("Awesome Beach"); + + obj.DebugDumpLines(); + } + + [Fact] + public static void TestTurnip() + { + var data = Resources.STR_ItemName_41_Turnip; + var obj = new MSBT(data); + obj.SectionOrder.Count.Should().Be(3); + obj.TXT2.Strings.Count.Should().BeGreaterThan(0); + obj.DebugDumpLines(); + } + } +} diff --git a/NHSE.Tests/NHSE.Tests.csproj b/NHSE.Tests/NHSE.Tests.csproj index 70b847c..9902e99 100644 --- a/NHSE.Tests/NHSE.Tests.csproj +++ b/NHSE.Tests/NHSE.Tests.csproj @@ -18,6 +18,26 @@ + + + + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + diff --git a/NHSE.Tests/Properties/Resources.Designer.cs b/NHSE.Tests/Properties/Resources.Designer.cs new file mode 100644 index 0000000..ca053d8 --- /dev/null +++ b/NHSE.Tests/Properties/Resources.Designer.cs @@ -0,0 +1,83 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace NHSE.Tests.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NHSE.Tests.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] STR_ItemName_41_Turnip { + get { + object obj = ResourceManager.GetObject("STR_ItemName_41_Turnip", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] STR_TownName { + get { + object obj = ResourceManager.GetObject("STR_TownName", resourceCulture); + return ((byte[])(obj)); + } + } + } +} diff --git a/NHSE.Tests/Properties/Resources.resx b/NHSE.Tests/Properties/Resources.resx new file mode 100644 index 0000000..43b649f --- /dev/null +++ b/NHSE.Tests/Properties/Resources.resx @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\STR_ItemName_41_Turnip.msbt;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\STR_TownName.msbt;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/NHSE.Tests/Resources/STR_ItemName_41_Turnip.msbt b/NHSE.Tests/Resources/STR_ItemName_41_Turnip.msbt new file mode 100644 index 0000000000000000000000000000000000000000..0ffd5bca502b0c542d4d1e6bfa7fe0779e3c1ff6 GIT binary patch literal 800 zcmeY*P7f|gamxGukAZ=anVErsK>bExKEb=z?>CLz=91`z{G$9RlwK?$VV~M668@NTTBc&fdZ>nJB9=qf;7VbH_^Y<@ Joc)sa{sWn+SGWKG literal 0 HcmV?d00001