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
This commit is contained in:
Kurt 2020-03-31 11:56:06 -07:00
parent 5b72117809
commit f664eab05e
23 changed files with 811 additions and 0 deletions

117
NHSE.Parsing/BCSV/BCSV.cs Normal file
View File

@ -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<BCSVFieldParam> 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);
}
}
}

View File

@ -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<uint, BCSVFieldParam> 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}");
}
}
}

View File

@ -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;
}
}
}

View File

@ -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<byte> bytes = new List<byte>();
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());
}
}
}

View File

@ -0,0 +1,8 @@
namespace NHSE.Parsing
{
public enum ByteOrder
{
LittleEndian = 0,
BigEndian = 1
}
}

View File

@ -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; }
}
}

12
NHSE.Parsing/MSBT/LBL1.cs Normal file
View File

@ -0,0 +1,12 @@
using System.Collections.Generic;
namespace NHSE.Parsing
{
public class LBL1 : MSBTSection
{
public uint NumberOfGroups;
public List<MSBTGroup> Groups = new List<MSBTGroup>();
public List<MSBTLabel> Labels = new List<MSBTLabel>();
}
}

171
NHSE.Parsing/MSBT/MSBT.cs Normal file
View File

@ -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<string> 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<string>();
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<uint>();
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<byte>();
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);
}
}
}

View File

@ -0,0 +1,8 @@
namespace NHSE.Parsing
{
public enum MSBTEncodingByte : byte
{
UTF8 = 0x00,
Unicode = 0x01
}
}

View File

@ -0,0 +1,8 @@
namespace NHSE.Parsing
{
public class MSBTGroup
{
public uint NumberOfLabels;
public uint Offset;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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
}
}

View File

@ -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);
}
}
}

View File

@ -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}");
}
}
}
}

11
NHSE.Parsing/MSBT/TXT2.cs Normal file
View File

@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace NHSE.Parsing
{
public class TXT2 : MSBTSection
{
public uint NumberOfStrings;
public List<MSBTTextString> Strings = new List<MSBTTextString>();
}
}

View File

@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>8</LangVersion>
</PropertyGroup>
</Project>

36
NHSE.Tests/MSBTTests.cs Normal file
View File

@ -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();
}
}
}

View File

@ -18,6 +18,26 @@
<ItemGroup>
<ProjectReference Include="..\NHSE.Core\NHSE.Core.csproj" />
<ProjectReference Include="..\NHSE.Parsing\NHSE.Parsing.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,83 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
namespace NHSE.Tests.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// 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() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[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;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
internal static byte[] STR_ItemName_41_Turnip {
get {
object obj = ResourceManager.GetObject("STR_ItemName_41_Turnip", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
internal static byte[] STR_TownName {
get {
object obj = ResourceManager.GetObject("STR_TownName", resourceCulture);
return ((byte[])(obj));
}
}
}
}

View File

@ -0,0 +1,127 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="STR_ItemName_41_Turnip" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\STR_ItemName_41_Turnip.msbt;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="STR_TownName" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\STR_TownName.msbt;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
</root>

Binary file not shown.

Binary file not shown.