CodeWalker/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs
2022-01-31 19:39:42 +11:00

2189 lines
93 KiB
C#

using SharpDX;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
namespace CodeWalker.GameFiles
{
public class MetaXml : MetaXmlBase
{
public static string GetXml(RpfFileEntry e, byte[] data, out string filename, string outputfolder = "")
{
var fn = e.Name;
var fnl = fn.ToLowerInvariant();
if (!string.IsNullOrEmpty(outputfolder))
{
outputfolder = Path.Combine(outputfolder, e.GetShortName());
}
if (fnl.EndsWith(".ymt"))
{
YmtFile ymt = RpfFile.GetFile<YmtFile>(e, data);
return GetXml(ymt, out filename);
}
else if (fnl.EndsWith(".ymf"))
{
YmfFile ymf = RpfFile.GetFile<YmfFile>(e, data);
return GetXml(ymf, out filename);
}
else if (fnl.EndsWith(".ymap"))
{
YmapFile ymap = RpfFile.GetFile<YmapFile>(e, data);
return GetXml(ymap, out filename);
}
else if (fnl.EndsWith(".ytyp"))
{
YtypFile ytyp = RpfFile.GetFile<YtypFile>(e, data);
return GetXml(ytyp, out filename);
}
else if (fnl.EndsWith(".pso"))
{
JPsoFile pso = RpfFile.GetFile<JPsoFile>(e, data);
return GetXml(pso, out filename);
}
else if (fnl.EndsWith(".cut"))
{
CutFile cut = RpfFile.GetFile<CutFile>(e, data);
return GetXml(cut, out filename);
}
else if (fnl.EndsWith(".rel"))
{
RelFile rel = RpfFile.GetFile<RelFile>(e, data);
return GetXml(rel, out filename);
}
else if (fnl.EndsWith(".ynd"))
{
YndFile ynd = RpfFile.GetFile<YndFile>(e, data);
return GetXml(ynd, out filename);
}
else if (fnl.EndsWith(".ynv"))
{
YnvFile ynv = RpfFile.GetFile<YnvFile>(e, data);
return GetXml(ynv, out filename);
}
else if (fnl.EndsWith(".ycd"))
{
YcdFile ycd = RpfFile.GetFile<YcdFile>(e, data);
return GetXml(ycd, out filename);
}
else if (fnl.EndsWith(".ybn"))
{
YbnFile ybn = RpfFile.GetFile<YbnFile>(e, data);
return GetXml(ybn, out filename);
}
else if (fnl.EndsWith(".ytd"))
{
YtdFile ytd = RpfFile.GetFile<YtdFile>(e, data);
return GetXml(ytd, out filename, outputfolder);
}
else if (fnl.EndsWith(".ydr"))
{
YdrFile ydr = RpfFile.GetFile<YdrFile>(e, data);
return GetXml(ydr, out filename, outputfolder);
}
else if (fnl.EndsWith(".ydd"))
{
YddFile ydd = RpfFile.GetFile<YddFile>(e, data);
return GetXml(ydd, out filename, outputfolder);
}
else if (fnl.EndsWith(".yft"))
{
YftFile yft = RpfFile.GetFile<YftFile>(e, data);
return GetXml(yft, out filename, outputfolder);
}
else if (fnl.EndsWith(".ypt"))
{
YptFile ypt = RpfFile.GetFile<YptFile>(e, data);
return GetXml(ypt, out filename, outputfolder);
}
else if (fnl.EndsWith(".yld"))
{
YldFile yld = RpfFile.GetFile<YldFile>(e, data);
return GetXml(yld, out filename);
}
else if (fnl.EndsWith(".yed"))
{
YedFile yed = RpfFile.GetFile<YedFile>(e, data);
return GetXml(yed, out filename);
}
else if (fnl.EndsWith(".awc"))
{
AwcFile awc = RpfFile.GetFile<AwcFile>(e, data);
return GetXml(awc, out filename, outputfolder);
}
else if (fnl.EndsWith("cache_y.dat"))
{
CacheDatFile cdf = RpfFile.GetFile<CacheDatFile>(e, data);
return GetXml(cdf, out filename, outputfolder);
}
else if (fnl.EndsWith(".dat") && fnl.StartsWith("heightmap"))
{
HeightmapFile hmf = RpfFile.GetFile<HeightmapFile>(e, data);
return GetXml(hmf, out filename, outputfolder);
}
filename = fn;
return string.Empty;
}
public static string GetXml(YmtFile ymt, out string filename)
{
var fn = (ymt?.RpfFileEntry?.Name) ?? "";
if (ymt.Meta != null) { filename = fn + ".xml"; return GetXml(ymt.Meta); }
else if (ymt.Pso != null) { filename = fn + ".pso.xml"; return PsoXml.GetXml(ymt.Pso); }
else if (ymt.Rbf != null) { filename = fn + ".rbf.xml"; return RbfXml.GetXml(ymt.Rbf); }
filename = string.Empty;
return string.Empty;
}
public static string GetXml(YmfFile ymf, out string filename)
{
var fn = (ymf?.FileEntry?.Name) ?? "";
if (ymf.Meta != null) { filename = fn + ".xml"; return GetXml(ymf.Meta); }
else if (ymf.Pso != null) { filename = fn + ".pso.xml"; return PsoXml.GetXml(ymf.Pso); }
else if (ymf.Rbf != null) { filename = fn + ".rbf.xml"; return RbfXml.GetXml(ymf.Rbf); }
filename = string.Empty;
return string.Empty;
}
public static string GetXml(YmapFile ymap, out string filename)
{
var fn = (ymap?.RpfFileEntry?.Name) ?? "";
if (ymap.Meta != null) { filename = fn + ".xml"; return GetXml(ymap.Meta); }
else if (ymap.Pso != null) { filename = fn + ".pso.xml"; return PsoXml.GetXml(ymap.Pso); }
else if (ymap.Rbf != null) { filename = fn + ".rbf.xml"; return RbfXml.GetXml(ymap.Rbf); }
filename = string.Empty;
return string.Empty;
}
public static string GetXml(YtypFile ytyp, out string filename)
{
var fn = (ytyp?.RpfFileEntry?.Name) ?? "";
if (ytyp.Meta != null) { filename = fn + ".xml"; return GetXml(ytyp.Meta); }
else if (ytyp.Pso != null) { filename = fn + ".pso.xml"; return PsoXml.GetXml(ytyp.Pso); }
else if (ytyp.Rbf != null) { filename = fn + ".rbf.xml"; return RbfXml.GetXml(ytyp.Rbf); }
filename = string.Empty;
return string.Empty;
}
public static string GetXml(JPsoFile pso, out string filename)
{
var fn = (pso?.FileEntry?.Name) ?? "";
if (pso.Pso != null) { filename = fn + ".pso.xml"; return PsoXml.GetXml(pso.Pso); }
filename = string.Empty;
return string.Empty;
}
public static string GetXml(CutFile cut, out string filename)
{
var fn = (cut?.FileEntry?.Name) ?? "";
if (cut.Pso != null) { filename = fn + ".pso.xml"; return PsoXml.GetXml(cut.Pso); }
filename = string.Empty;
return string.Empty;
}
public static string GetXml(RelFile rel, out string filename)
{
var fn = (rel?.RpfFileEntry?.Name) ?? "";
filename = fn + ".xml";
return RelXml.GetXml(rel);
}
public static string GetXml(YndFile ynd, out string filename)
{
var fn = (ynd?.RpfFileEntry?.Name) ?? "";
filename = fn + ".xml";
return YndXml.GetXml(ynd);
}
public static string GetXml(YnvFile ynv, out string filename)
{
var fn = (ynv?.RpfFileEntry?.Name) ?? "";
filename = fn + ".xml";
return YnvXml.GetXml(ynv);
}
public static string GetXml(YcdFile ycd, out string filename)
{
var fn = (ycd?.RpfFileEntry?.Name) ?? "";
filename = fn + ".xml";
return YcdXml.GetXml(ycd);
}
public static string GetXml(YbnFile ybn, out string filename)
{
var fn = (ybn?.RpfFileEntry?.Name) ?? "";
filename = fn + ".xml";
return YbnXml.GetXml(ybn);
}
public static string GetXml(YtdFile ytd, out string filename, string outputfolder)
{
var fn = (ytd?.Name) ?? "";
filename = fn + ".xml";
return YtdXml.GetXml(ytd, outputfolder);
}
public static string GetXml(YdrFile ydr, out string filename, string outputfolder)
{
var fn = (ydr?.Name) ?? "";
filename = fn + ".xml";
return YdrXml.GetXml(ydr, outputfolder);
}
public static string GetXml(YddFile ydd, out string filename, string outputfolder)
{
var fn = (ydd?.Name) ?? "";
filename = fn + ".xml";
return YddXml.GetXml(ydd, outputfolder);
}
public static string GetXml(YftFile yft, out string filename, string outputfolder)
{
var fn = (yft?.Name) ?? "";
filename = fn + ".xml";
return YftXml.GetXml(yft, outputfolder);
}
public static string GetXml(YptFile ypt, out string filename, string outputfolder)
{
var fn = (ypt?.Name) ?? "";
filename = fn + ".xml";
return YptXml.GetXml(ypt, outputfolder);
}
public static string GetXml(YldFile yld, out string filename)
{
var fn = (yld?.Name) ?? "";
filename = fn + ".xml";
return YldXml.GetXml(yld);
}
public static string GetXml(YedFile yed, out string filename)
{
var fn = (yed?.Name) ?? "";
filename = fn + ".xml";
return YedXml.GetXml(yed);
}
public static string GetXml(AwcFile awc, out string filename, string outputfolder)
{
var fn = (awc?.Name) ?? "";
filename = fn + ".xml";
return AwcXml.GetXml(awc, outputfolder);
}
public static string GetXml(CacheDatFile cdf, out string filename, string outputfolder)
{
var fn = (cdf?.FileEntry?.Name) ?? "";
filename = fn + ".xml";
return cdf.GetXml();
}
public static string GetXml(HeightmapFile hmf, out string filename, string outputfolder)
{
var fn = (hmf?.Name) ?? "";
filename = fn + ".xml";
return HmapXml.GetXml(hmf);
}
public static string GetXml(Meta meta)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine(XmlHeader);
if (meta != null)
{
var cont = new MetaCont(meta);
WriteNode(sb, 0, cont, meta.RootBlockIndex, 0, XmlTagMode.Structure, 0, (string)meta.Name);
}
return sb.ToString();
}
private static void WriteNode(StringBuilder sb, int indent, MetaCont cont, int blockId, int offset, XmlTagMode tagMode = XmlTagMode.None, MetaName structName = 0, string metaName = "")
{
var block = cont.Meta.GetBlock(blockId);
if (block == null)
{
ErrorXml(sb, indent, "Couldn't find block " + blockId + "!");
return;
}
if (structName == 0)
{
structName = block.StructureNameHash;
}
var name = HashString(structName);
var data = block.Data;
var structInfo = cont.GetStructureInfo(structName);
if (structInfo == null)
{
ErrorXml(sb, indent, "Couldn't find structure info " + name + "!");
return;
}
if (structInfo.Entries == null)
{
ErrorXml(sb, indent, "Couldn't find structure info entries for " + name + "!");
return;
}
switch (tagMode)
{
case XmlTagMode.Structure:
OpenTag(sb, indent, name, true, metaName);
break;
case XmlTagMode.Item:
OpenTag(sb, indent, "Item", true, metaName);
break;
case XmlTagMode.ItemAndType:
OpenTag(sb, indent, "Item type=\"" + name + "\"", true, metaName);
break;
}
var cind = indent + 1;
MetaStructureEntryInfo_s arrEntry = new MetaStructureEntryInfo_s();
for (int i = 0; i < structInfo.Entries.Length; i++)
{
var entry = structInfo.Entries[i];
if (entry.EntryNameHash == (MetaName)MetaTypeName.ARRAYINFO)
{
arrEntry = entry;
continue;
}
var ename = HashString(entry.EntryNameHash);
var eoffset = offset + entry.DataOffset;
switch (entry.DataType)
{
default:
ErrorXml(sb, cind, ename + ": Unexpected entry DataType: " + entry.DataType.ToString());
break;
case MetaStructureEntryDataType.Array:
WriteArrayNode(sb, cind, cont, data, arrEntry, ename, eoffset);
break;
case MetaStructureEntryDataType.ArrayOfBytes:
WriteParsedArrayOfBytesNode(sb, cind, data, ename, eoffset, entry, arrEntry);
break;
case MetaStructureEntryDataType.ArrayOfChars:
OpenTag(sb, cind, ename, false);
uint charArrLen = (uint)entry.ReferenceKey;
for (int n = 0; n < charArrLen; n++)
{
var bidx = eoffset + n;
if ((bidx >= 0) && (bidx < data.Length))
{
byte b = data[bidx];
if (b == 0) break;
sb.Append((char)b);
}
}
CloseTag(sb, 0, ename);
break;
case MetaStructureEntryDataType.Boolean:
var boolVal = BitConverter.ToBoolean(data, eoffset);
ValueTag(sb, cind, ename, boolVal?"true":"false");
break;
case MetaStructureEntryDataType.ByteEnum:
var byteEnumVal = data[eoffset];
ValueTag(sb, cind, ename, byteEnumVal.ToString());
break;
case MetaStructureEntryDataType.CharPointer:
var charPtr = MetaTypes.ConvertData<CharPointer>(data, eoffset);
string charStr = MetaTypes.GetString(cont.Meta, charPtr);
OneLineTag(sb, cind, ename, charStr);
break;
case MetaStructureEntryDataType.DataBlockPointer:
var dataPtr = MetaTypes.ConvertData<DataBlockPointer>(data, eoffset);
//need to just get all the data from that block, since this pointer is referring to the whole block! it should be of type BYTE!
var dblock = cont.Meta.GetBlock((int)dataPtr.PointerDataId);
WriteRawArray(sb, dblock.Data, cind, ename, "ByteArray", FormatHexByte, 32);
break;
case MetaStructureEntryDataType.Float:
var floatVal = BitConverter.ToSingle(data, eoffset);
ValueTag(sb, cind, ename, FloatUtil.ToString(floatVal));
break;
case MetaStructureEntryDataType.Float_XYZ:
var v3 = MetaTypes.ConvertData<Vector3>(data, eoffset);
SelfClosingTag(sb, cind, ename + " x=\"" + FloatUtil.ToString(v3.X) + "\" y=\"" + FloatUtil.ToString(v3.Y) + "\" z=\"" + FloatUtil.ToString(v3.Z) + "\"");
break;
case MetaStructureEntryDataType.Float_XYZW:
var v4 = MetaTypes.ConvertData<Vector4>(data, eoffset);
SelfClosingTag(sb, cind, ename + " x=\"" + FloatUtil.ToString(v4.X) + "\" y=\"" + FloatUtil.ToString(v4.Y) + "\" z=\"" + FloatUtil.ToString(v4.Z) + "\" w=\"" + FloatUtil.ToString(v4.W) + "\"");
break;
case MetaStructureEntryDataType.Hash:
var hashVal = MetaTypes.ConvertData<MetaHash>(data, eoffset);
var hashStr = HashString(hashVal);
StringTag(sb, cind, ename, hashStr);
break;
case MetaStructureEntryDataType.IntEnum:
var intEnumVal = BitConverter.ToInt32(data, eoffset);
var intEnumStr = GetEnumString(cont, entry, intEnumVal);
StringTag(sb, cind, ename, intEnumStr);
break;
case MetaStructureEntryDataType.IntFlags1:
var intFlags1Val = BitConverter.ToInt32(data, eoffset);
var intFlags1Str = GetEnumString(cont, entry, intFlags1Val);
StringTag(sb, cind, ename, intFlags1Str);
break;
case MetaStructureEntryDataType.IntFlags2:
var intFlags2Val = BitConverter.ToInt32(data, eoffset);
var intFlags2Str = GetEnumString(cont, entry, intFlags2Val);
StringTag(sb, cind, ename, intFlags2Str);
break;
case MetaStructureEntryDataType.ShortFlags:
var shortFlagsVal = BitConverter.ToInt16(data, eoffset);
var shortFlagsStr = GetEnumString(cont, entry, shortFlagsVal);
StringTag(sb, cind, ename, shortFlagsStr);
break;
case MetaStructureEntryDataType.SignedByte:
sbyte sbyteVal = (sbyte)data[eoffset];
ValueTag(sb, cind, ename, sbyteVal.ToString());
break;
case MetaStructureEntryDataType.SignedInt:
var intVal = BitConverter.ToInt32(data, eoffset);
ValueTag(sb, cind, ename, intVal.ToString());
break;
case MetaStructureEntryDataType.SignedShort:
var shortVal = BitConverter.ToInt16(data, eoffset);
ValueTag(sb, cind, ename, shortVal.ToString());
break;
case MetaStructureEntryDataType.Structure:
OpenTag(sb, cind, ename);
WriteNode(sb, cind, cont, blockId, eoffset, XmlTagMode.None, entry.ReferenceKey);
CloseTag(sb, cind, ename);
break;
case MetaStructureEntryDataType.StructurePointer:
OpenTag(sb, cind, ename);
ErrorXml(sb, cind + 1, "StructurePointer not supported here! Tell dexy!");
CloseTag(sb, cind, ename);
break;
case MetaStructureEntryDataType.UnsignedByte:
var byteVal = data[eoffset];
ValueTag(sb, cind, ename, byteVal.ToString());
//ValueTag(sb, cind, ename, "0x" + byteVal.ToString("X").PadLeft(2, '0'));
break;
case MetaStructureEntryDataType.UnsignedInt:
var uintVal = BitConverter.ToUInt32(data, eoffset);
switch (entry.EntryNameHash)
{
default:
ValueTag(sb, cind, ename, uintVal.ToString());
break;
case MetaName.color:
ValueTag(sb, cind, ename, "0x" + uintVal.ToString("X").PadLeft(8, '0'));
break;
}
break;
case MetaStructureEntryDataType.UnsignedShort:
var ushortVal = BitConverter.ToUInt16(data, eoffset);
ValueTag(sb, cind, ename, ushortVal.ToString());// "0x" + ushortVal.ToString("X").PadLeft(4, '0'));
break;
}
}
switch (tagMode)
{
case XmlTagMode.Structure:
CloseTag(sb, indent, name);
break;
case XmlTagMode.Item:
case XmlTagMode.ItemAndType:
CloseTag(sb, indent, "Item");
break;
}
}
private static void WriteArrayNode(StringBuilder sb, int indent, MetaCont cont, byte[] data, MetaStructureEntryInfo_s arrEntry, string ename, int eoffset)
{
int aCount = 0;
var aind = indent + 1;
string arrTag = ename;
switch (arrEntry.DataType)
{
default:
ErrorXml(sb, indent, ename + ": Unexpected array entry DataType: " + arrEntry.DataType.ToString());
break;
case MetaStructureEntryDataType.Structure:
var arrStruc = MetaTypes.ConvertData<Array_Structure>(data, eoffset);
var aBlockId = (int)arrStruc.PointerDataId;
var aOffset = (int)arrStruc.PointerDataOffset;
aCount = arrStruc.Count1;
arrTag += " itemType=\"" + HashString(arrEntry.ReferenceKey) + "\"";
if (aCount > 0)
{
OpenTag(sb, indent, arrTag);
var atyp = cont.GetStructureInfo(arrEntry.ReferenceKey);
var aBlock = cont.Meta.GetBlock(aBlockId);
for (int n = 0; n < aCount; n++)
{
WriteNode(sb, aind, cont, aBlockId, aOffset, XmlTagMode.Item, arrEntry.ReferenceKey);
aOffset += atyp.StructureSize;
if ((n < (aCount - 1)) && (aBlock != null) && (aOffset >= aBlock.DataLength))
{
aOffset = 0;
aBlockId++;
aBlock = cont.Meta.GetBlock(aBlockId);
}
}
CloseTag(sb, indent, ename);
}
else
{
SelfClosingTag(sb, indent, arrTag);
}
break;
case MetaStructureEntryDataType.StructurePointer:
var arrStrucP = MetaTypes.ConvertData<Array_StructurePointer>(data, eoffset);
var ptrArr = MetaTypes.GetPointerArray(cont.Meta, arrStrucP);
aCount = ptrArr?.Length ?? 0;
if (aCount > 0)
{
OpenTag(sb, indent, arrTag);
for (int n = 0; n < aCount; n++)
{
var ptr = ptrArr[n];
var offset = ptr.Offset;
WriteNode(sb, aind, cont, ptr.BlockID, offset, XmlTagMode.ItemAndType);
}
CloseTag(sb, indent, ename);
}
else
{
SelfClosingTag(sb, indent, arrTag);
}
break;
case MetaStructureEntryDataType.UnsignedInt:
var arrUint = MetaTypes.ConvertData<Array_uint>(data, eoffset);
var uintArr = MetaTypes.GetUintArray(cont.Meta, arrUint);
WriteRawArray(sb, uintArr, indent, ename, "uint");
break;
case MetaStructureEntryDataType.UnsignedShort:
var arrUshort = MetaTypes.ConvertData<Array_ushort>(data, eoffset);
var ushortArr = MetaTypes.GetUshortArray(cont.Meta, arrUshort);
WriteRawArray(sb, ushortArr, indent, ename, "ushort");
break;
case MetaStructureEntryDataType.UnsignedByte:
var arrUbyte = MetaTypes.ConvertData<Array_byte>(data, eoffset);
var byteArr = MetaTypes.GetByteArray(cont.Meta, arrUbyte);
WriteRawArray(sb, byteArr, indent, ename, "byte");
break;
case MetaStructureEntryDataType.Float:
var arrFloat = MetaTypes.ConvertData<Array_float>(data, eoffset);
var floatArr = MetaTypes.GetFloatArray(cont.Meta, arrFloat);
WriteRawArray(sb, floatArr, indent, ename, "float");
break;
case MetaStructureEntryDataType.Float_XYZ:
var arrV3 = MetaTypes.ConvertData<Array_Vector3>(data, eoffset);
var v4Arr = MetaTypes.ConvertDataArray<Vector4>(cont.Meta, (MetaName)MetaTypeName.VECTOR4, arrV3.Pointer, arrV3.Count1);
WriteItemArray(sb, v4Arr, indent, ename, "Vector3/4", FormatVector4);
break;
case MetaStructureEntryDataType.CharPointer:
ErrorXml(sb, indent, "CharPointer ARRAY not supported here! Tell dexy!");
break;
case MetaStructureEntryDataType.DataBlockPointer:
ErrorXml(sb, indent, "DataBlockPointer ARRAY not supported here! Tell dexy!");
break;
case MetaStructureEntryDataType.Hash:
var arrHash = MetaTypes.ConvertData<Array_uint>(data, eoffset);
var hashArr = MetaTypes.GetHashArray(cont.Meta, arrHash);
WriteItemArray(sb, hashArr, indent, ename, "Hash", FormatHash);
break;
}
}
private static void WriteParsedArrayOfBytesNode(StringBuilder sb, int indent, byte[] data, string ename, int eoffset, MetaStructureEntryInfo_s entry, MetaStructureEntryInfo_s arrEntry)
{
OpenTag(sb, indent, ename, false);
var byteArrLen = ((int)entry.ReferenceKey);
switch (arrEntry.DataType)
{
default:
for (int n = 0; n < byteArrLen; n++)
{
var bidx = eoffset + n;
byte b = ((bidx >= 0) && (bidx < data.Length)) ? data[bidx] : (byte)0;
sb.Append(b.ToString("X").PadLeft(2, '0'));
}
break;
case MetaStructureEntryDataType.SignedByte:
for (int n = 0; n < byteArrLen; n++)
{
var bidx = eoffset + n;
sbyte b = ((bidx >= 0) && (bidx < data.Length)) ? (sbyte)data[bidx] : (sbyte)0;
sb.Append(b.ToString()); //sb.Append(b.ToString("X").PadLeft(2, '0')); to show HEX values
if (n < byteArrLen - 1) sb.Append(" ");
}
break;
case MetaStructureEntryDataType.UnsignedByte:
for (int n = 0; n < byteArrLen; n++)
{
var bidx = eoffset + n;
byte b = ((bidx >= 0) && (bidx < data.Length)) ? data[bidx] : (byte)0;
sb.Append(b.ToString());
if (n < byteArrLen - 1) sb.Append(" ");
}
break;
case MetaStructureEntryDataType.SignedShort:
for (int n = 0; n < byteArrLen; n++)
{
var bidx = eoffset + n * 2;
short b = ((bidx >= 0) && (bidx < data.Length)) ? BitConverter.ToInt16(data, bidx) : (short)0;
sb.Append(b.ToString());
if (n < byteArrLen - 1) sb.Append(" ");
}
break;
case MetaStructureEntryDataType.UnsignedShort:
for (int n = 0; n < byteArrLen; n++)
{
var bidx = eoffset + n * 2;
ushort b = ((bidx >= 0) && (bidx < data.Length)) ? BitConverter.ToUInt16(data, bidx) : (ushort)0;
sb.Append(b.ToString());
if (n < byteArrLen - 1) sb.Append(" ");
}
break;
case MetaStructureEntryDataType.SignedInt:
for (int n = 0; n < byteArrLen; n++)
{
var bidx = eoffset + n * 4;
int b = ((bidx >= 0) && (bidx < data.Length)) ? BitConverter.ToInt32(data, bidx) : (int)0;
sb.Append(b.ToString());
if (n < byteArrLen - 1) sb.Append(" ");
}
break;
case MetaStructureEntryDataType.UnsignedInt:
for (int n = 0; n < byteArrLen; n++)
{
var bidx = eoffset + n * 4;
uint b = ((bidx >= 0) && (bidx < data.Length)) ? BitConverter.ToUInt32(data, bidx) : (uint)0;
sb.Append(b.ToString());
if (n < byteArrLen - 1) sb.Append(" ");
}
break;
case MetaStructureEntryDataType.Float:
for (int n = 0; n < byteArrLen; n++)
{
var bidx = eoffset + n * 4;
float b = ((bidx >= 0) && (bidx < data.Length)) ? BitConverter.ToSingle(data, bidx) : (float)0;
sb.Append(FloatUtil.ToString(b));
if (n < byteArrLen - 1) sb.Append(" ");
}
break;
}
CloseTag(sb, 0, ename);
}
private static string GetEnumString(MetaCont cont, MetaStructureEntryInfo_s entry, int value)
{
var eName = entry.ReferenceKey;
var eInfo = cont.GetEnumInfo(eName);
if ((eInfo == null) || (eInfo.Entries == null))
{
return value.ToString();
}
bool isFlags = (entry.DataType == MetaStructureEntryDataType.IntFlags1) ||
(entry.DataType == MetaStructureEntryDataType.IntFlags2);// ||
//(entry.DataType == MetaStructureEntryDataType.ShortFlags);
if (isFlags)
{
StringBuilder sb = new StringBuilder();
foreach (var ev in eInfo.Entries)
{
var v = ev.EntryValue;
var m = 1 << v;
if ((value & m) > 0)
{
if (sb.Length > 0) sb.Append(", ");
sb.Append(HashString(ev.EntryNameHash));
}
}
return sb.ToString();
}
else
{
foreach (var ev in eInfo.Entries)
{
if (ev.EntryValue == value)
{
return HashString(ev.EntryNameHash);
}
}
return value.ToString(); //if we got here, there was no match...
}
}
private class MetaCont
{
public Meta Meta { get; set; }
Dictionary<MetaName, MetaStructureInfo> structInfos = new Dictionary<MetaName, MetaStructureInfo>();
Dictionary<MetaName, MetaEnumInfo> enumInfos = new Dictionary<MetaName, MetaEnumInfo>();
public MetaCont(Meta meta)
{
Meta = meta;
if (meta.StructureInfos?.Data != null)
{
foreach (var si in meta.StructureInfos)
{
structInfos[si.StructureNameHash] = si;
}
}
if (meta.EnumInfos?.Data != null)
{
foreach (var ei in meta.EnumInfos)
{
enumInfos[ei.EnumNameHash] = ei;
}
}
}
public MetaStructureInfo GetStructureInfo(MetaName name)
{
MetaStructureInfo i = null;
structInfos.TryGetValue(name, out i);
return i;
}
public MetaEnumInfo GetEnumInfo(MetaName name)
{
MetaEnumInfo i = null;
enumInfos.TryGetValue(name, out i);
return i;
}
}
}
public class PsoXml : MetaXmlBase
{
public static string GetXml(PsoFile pso)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine(XmlHeader);
if ((pso != null) && (pso.DataSection != null) && (pso.DataMapSection != null))
{
var cont = new PsoCont(pso);
WriteNode(sb, 0, cont, pso.DataMapSection.RootId, 0, XmlTagMode.Structure);
}
return sb.ToString();
}
private static void WriteNode(StringBuilder sb, int indent, PsoCont cont, int blockId, int offset, XmlTagMode tagMode = XmlTagMode.None, MetaName structName = 0)
{
var block = cont.Pso.GetBlock(blockId);
if (block == null)
{
ErrorXml(sb, indent, "Couldn't find block " + blockId + "!");
return;
}
if (offset >= block.Length)
{
offset = offset >> 8; //how to tell when to do this??
}
var boffset = offset + block.Offset;
if (structName == 0)
{
structName = block.NameHash;
}
var name = HashString(structName);
var data = cont.Pso.DataSection.Data;
var structInfo = cont.GetStructureInfo(structName);
if (structInfo == null)
{
structInfo = PsoTypes.GetStructureInfo(structName);//fallback to builtin...
}
if (structInfo == null)
{
ErrorXml(sb, indent, "Couldn't find structure info " + name + "!");
return;
}
if (structInfo.Entries == null)
{
ErrorXml(sb, indent, "Couldn't find structure info entries for " + name + "!");
return;
}
switch (tagMode)
{
case XmlTagMode.Structure:
OpenTag(sb, indent, name);
break;
case XmlTagMode.Item:
OpenTag(sb, indent, "Item");
break;
case XmlTagMode.ItemAndType:
OpenTag(sb, indent, "Item type=\"" + name + "\"");
break;
}
var cind = indent + 1;
for (int i = 0; i < structInfo.Entries.Length; i++)
{
var entry = structInfo.Entries[i];
if (entry.EntryNameHash == (MetaName)MetaTypeName.ARRAYINFO)
{
continue;
}
var ename = HashString(entry.EntryNameHash);
var eoffset = boffset + entry.DataOffset;
switch (entry.Type)
{
default:
ErrorXml(sb, cind, ename + ": Unexpected entry DataType: " + entry.Type.ToString());
break;
case PsoDataType.Array:
WriteArrayNode(sb, cind, cont, blockId, offset, entry, structInfo, ename);
break;
case PsoDataType.Bool:
var boolVal = BitConverter.ToBoolean(data, eoffset);
ValueTag(sb, cind, ename, boolVal?"true":"false");
break;
case PsoDataType.SByte: //was LONG_01h //signed byte?
//var long1Val = MetaTypes.SwapBytes(BitConverter.ToUInt64(data, eoffset));
//ValueTag(sb, cind, ename, long1Val.ToString());
var byte1Val = (sbyte)data[eoffset];
ValueTag(sb, cind, ename, byte1Val.ToString());
break;
case PsoDataType.UByte:
var byte2Val = data[eoffset];
ValueTag(sb, cind, ename, byte2Val.ToString());
break;
case PsoDataType.Enum:
var enumInfo = cont.GetEnumInfo((MetaName)entry.ReferenceKey);
switch (entry.Unk_5h)
{
default:
ErrorXml(sb, cind, ename + ": Unexpected Enum subtype: " + entry.Unk_5h.ToString());
break;
case 0: //int enum
var intEVal = MetaTypes.SwapBytes(BitConverter.ToInt32(data, eoffset));
var intE = enumInfo.FindEntry(intEVal);
var intH = HashString(intE?.EntryNameHash ?? 0);
if (string.IsNullOrEmpty(intH))
{ }
StringTag(sb, cind, ename, intH);
break;
case 2: //byte enum
var byteEVal = data[eoffset];
var byteE = enumInfo.FindEntry(byteEVal);
StringTag(sb, cind, ename, HashString(byteE?.EntryNameHash ?? 0));
break;
}
break;
case PsoDataType.Flags:
//uint fCount = (entry.ReferenceKey >> 16) & 0x0000FFFF;
uint fEntry = (entry.ReferenceKey & 0xFFF);
var fEnt = (fEntry != 0xFFF) ? structInfo.GetEntry((int)fEntry) : null;
PsoEnumInfo flagsInfo = null;
if ((fEnt != null) && (fEnt.EntryNameHash == (MetaName)MetaTypeName.ARRAYINFO))
{
flagsInfo = cont.GetEnumInfo((MetaName)fEnt.ReferenceKey);
}
if (flagsInfo == null)
{
if (fEntry != 0xFFF)
{ }
//flagsInfo = cont.GetEnumInfo(entry.EntryNameHash);
}
uint? flagsVal = null;
switch (entry.Unk_5h)
{
default:
ErrorXml(sb, cind, ename + ": Unexpected Flags subtype: " + entry.Unk_5h.ToString());
break;
case 0: //int flags
flagsVal = MetaTypes.SwapBytes(BitConverter.ToUInt32(data, eoffset));
break;
case 1: //short flags
flagsVal = MetaTypes.SwapBytes(BitConverter.ToUInt16(data, eoffset));
break;
case 2: //byte flags
flagsVal = data[eoffset];
break;
}
if (flagsVal.HasValue)
{
uint fv = flagsVal.Value;
if (flagsInfo != null)
{
string fstr = "";
for (int n = 0; n < flagsInfo.EntriesCount; n++)
{
var fentry = flagsInfo.Entries[n];
var fmask = (1 << fentry.EntryKey);
if ((fv & fmask) > 0)
{
if (fstr != "") fstr += " ";
fstr += HashString(fentry.EntryNameHash);
}
}
StringTag(sb, cind, ename, fstr);
}
else
{
if (fv != 0) ValueTag(sb, cind, ename, fv.ToString());
else SelfClosingTag(sb, cind, ename);
}
}
break;
case PsoDataType.Float:
var floatVal = MetaTypes.SwapBytes(BitConverter.ToSingle(data, eoffset));
ValueTag(sb, cind, ename, FloatUtil.ToString(floatVal));
break;
case PsoDataType.Float2:
var v2 = MetaTypes.SwapBytes(MetaTypes.ConvertData<Vector2>(data, eoffset));
SelfClosingTag(sb, cind, ename + " x=\"" + FloatUtil.ToString(v2.X) + "\" y=\"" + FloatUtil.ToString(v2.Y) + "\"");
break;
case PsoDataType.Float3:
var v3 = MetaTypes.SwapBytes(MetaTypes.ConvertData<Vector3>(data, eoffset));
SelfClosingTag(sb, cind, ename + " x=\"" + FloatUtil.ToString(v3.X) + "\" y=\"" + FloatUtil.ToString(v3.Y) + "\" z=\"" + FloatUtil.ToString(v3.Z) + "\"");
break;
case PsoDataType.Float3a: //TODO: check this!
var v3a = MetaTypes.SwapBytes(MetaTypes.ConvertData<Vector3>(data, eoffset));
SelfClosingTag(sb, cind, ename + " x=\"" + FloatUtil.ToString(v3a.X) + "\" y=\"" + FloatUtil.ToString(v3a.Y) + "\" z=\"" + FloatUtil.ToString(v3a.Z) + "\"");
break;
case PsoDataType.Float4a: //TODO: check this! //...why are there 3 different types of float3?
var v3b = MetaTypes.SwapBytes(MetaTypes.ConvertData<Vector3>(data, eoffset));
SelfClosingTag(sb, cind, ename + " x=\"" + FloatUtil.ToString(v3b.X) + "\" y=\"" + FloatUtil.ToString(v3b.Y) + "\" z=\"" + FloatUtil.ToString(v3b.Z) + "\"");
break;
case PsoDataType.Float4:
var v4 = MetaTypes.SwapBytes(MetaTypes.ConvertData<Vector4>(data, eoffset));
SelfClosingTag(sb, cind, ename + " x=\"" + FloatUtil.ToString(v4.X) + "\" y=\"" + FloatUtil.ToString(v4.Y) + "\" z=\"" + FloatUtil.ToString(v4.Z) + "\" w=\"" + FloatUtil.ToString(v4.W) + "\"");
break;
case PsoDataType.SInt: //TODO: convert hashes?
var int5Val = MetaTypes.SwapBytes(BitConverter.ToInt32(data, eoffset));
ValueTag(sb, cind, ename, int5Val.ToString());
break;
case PsoDataType.UInt:
switch (entry.Unk_5h)
{
default:
ErrorXml(sb, cind, ename + ": Unexpected Integer subtype: " + entry.Unk_5h.ToString());
break;
case 0: //signed int (? flags?)
var int6aVal = MetaTypes.SwapBytes(BitConverter.ToInt32(data, eoffset));
ValueTag(sb, cind, ename, int6aVal.ToString());
break;
case 1: //unsigned int
var int6bVal = MetaTypes.SwapBytes(BitConverter.ToUInt32(data, eoffset));
//ValueTag(sb, cind, ename, int6bVal.ToString());
ValueTag(sb, cind, ename, "0x" + int6bVal.ToString("X").PadLeft(8, '0'));
break;
}
break;
case PsoDataType.Long:
var long2Val = MetaTypes.SwapBytes(BitConverter.ToUInt64(data, eoffset));
ValueTag(sb, cind, ename, long2Val.ToString());
break;
case PsoDataType.Map:
WriteMapNode(sb, indent, cont, eoffset, entry, structInfo, ename);
break;
case PsoDataType.SShort:
var short3Val = (short)MetaTypes.SwapBytes(BitConverter.ToUInt16(data, eoffset));
ValueTag(sb, cind, ename, short3Val.ToString());
break;
case PsoDataType.UShort:
var short4Val = MetaTypes.SwapBytes(BitConverter.ToUInt16(data, eoffset));
ValueTag(sb, cind, ename, short4Val.ToString());
break;
case PsoDataType.HFloat://half float?
var short1EVal = MetaTypes.SwapBytes(BitConverter.ToInt16(data, eoffset));
ValueTag(sb, cind, ename, short1EVal.ToString());
break;
case PsoDataType.String:
var str0 = XmlEscape(GetStringValue(cont.Pso, entry, data, eoffset));
//if (str0 == null)
//{
// ErrorXml(sb, cind, ename + ": Unexpected String subtype: " + entry.Unk_5h.ToString());
//}
//else
{
StringTag(sb, cind, ename, str0);
}
break;
case PsoDataType.Structure:
switch (entry.Unk_5h)
{
default:
ErrorXml(sb, cind, ename + ": Unexpected Structure subtype: " + entry.Unk_5h.ToString());
break;
case 0: //default structure
OpenTag(sb, cind, ename);
WriteNode(sb, cind, cont, blockId, offset + entry.DataOffset, XmlTagMode.None, (MetaName)entry.ReferenceKey);
CloseTag(sb, cind, ename);
break;
case 3: //structure pointer...
case 4: //also pointer? what's the difference?
var ptrVal = MetaTypes.ConvertData<PsoPOINTER>(data, eoffset);
ptrVal.SwapEnd();
var pbid = ptrVal.BlockID;
bool pbok = true;
if (pbid <= 0)
{
pbok = false; //no block specified?
}
if (pbid > cont.Pso.DataMapSection.EntriesCount)
{
pbok = false; //bad pointer? different type..? should output an error message here?
}
if (pbok)
{
var typename = HashString(cont.Pso.GetBlock(pbid).NameHash);
OpenTag(sb, cind, ename + " type=\"" + typename + "\"");
WriteNode(sb, cind, cont, ptrVal.BlockID, (int)ptrVal.ItemOffset, XmlTagMode.None, (MetaName)entry.ReferenceKey);
CloseTag(sb, cind, ename);
}
else
{
SelfClosingTag(sb, cind, ename);
}
break;
}
break;
}
}
switch (tagMode)
{
case XmlTagMode.Structure:
CloseTag(sb, indent, name);
break;
case XmlTagMode.Item:
case XmlTagMode.ItemAndType:
CloseTag(sb, indent, "Item");
break;
}
}
private static void WriteArrayNode(StringBuilder sb, int indent, PsoCont cont, int blockId, int offset, PsoStructureEntryInfo entry, PsoStructureInfo estruct, string ename)
{
var block = cont.Pso.GetBlock(blockId);
var boffset = offset + block.Offset;
var eoffset = boffset + entry.DataOffset;
var aOffset = offset + entry.DataOffset;
var abOffset = aOffset + block.Offset;
var aBlockId = blockId;
uint aCount = (entry.ReferenceKey >> 16) & 0x0000FFFF;
Array_Structure arrStruc = new Array_Structure();
arrStruc.PointerDataId = (uint)aBlockId;
arrStruc.PointerDataOffset = (uint)aOffset;
arrStruc.Count1 = arrStruc.Count2 = (ushort)aCount;
var aind = indent + 1;
string arrTag = ename;
var arrEntInd = (entry.ReferenceKey & 0xFFFF);
if (arrEntInd >= estruct.EntriesCount)
{
arrEntInd = (entry.ReferenceKey & 0xFFF);
}
PsoStructureEntryInfo arrEntry = estruct.GetEntry((int)arrEntInd);
if (arrEntry == null)
{
ErrorXml(sb, indent, "ARRAYINFO not found for " + ename + "!");
return;
}
var data = cont.Pso.DataSection.Data;
bool embedded = true;
switch (entry.Unk_5h)
{
default:
ErrorXml(sb, indent, ename + ": WIP! Unsupported Array subtype: " + entry.Unk_5h.ToString());
break;
case 0: //Array_Structure
arrStruc = MetaTypes.ConvertData<Array_Structure>(data, eoffset);
arrStruc.SwapEnd();
aBlockId = (int)arrStruc.PointerDataId;
aOffset = (int)arrStruc.PointerDataOffset;
aCount = arrStruc.Count1;
var aBlock = cont.Pso.GetBlock(aBlockId);
if (aBlock != null)
{
abOffset = aOffset + aBlock.Offset;
}
embedded = false;
break;
case 1: //Raw in-line array
break;
case 2: //also raw in-line array, but how different from above?
break;
case 4: //pointer array? default array?
if (arrEntry.Unk_5h == 3) //pointers...
{
arrStruc = MetaTypes.ConvertData<Array_Structure>(data, eoffset);
arrStruc.SwapEnd();
aBlockId = (int)arrStruc.PointerDataId;
aOffset = (int)arrStruc.PointerDataOffset;
aCount = arrStruc.Count1;
var aBlock2 = cont.Pso.GetBlock(aBlockId);
if (aBlock2 != null)
{
abOffset = aOffset + aBlock2.Offset;
}
embedded = false;
}
else
{
}
break;
case 129: //also raw inline array? in junctions.pso
break;
}
switch (arrEntry.Type)
{
default:
ErrorXml(sb, indent, ename + ": WIP! Unsupported array entry DataType: " + arrEntry.Type.ToString());
break;
case PsoDataType.Array:
var rk0 = (entry.ReferenceKey >> 16) & 0x0000FFFF;
//var rk1 = entry.ReferenceKey & 0x0000FFFF;
//var rk3 = (arrEntry.ReferenceKey >> 16) & 0x0000FFFF;
//var rk4 = arrEntry.ReferenceKey & 0x0000FFFF;
if (rk0 > 0)
{
aOffset = offset + entry.DataOffset;
OpenTag(sb, indent, arrTag);
for (int n = 0; n < rk0; n++) //ARRAY ARRAY!
{
WriteArrayNode(sb, aind, cont, blockId, aOffset, arrEntry, estruct, "Item");
aOffset += 16;//ptr size... todo: what if not pointer array?
}
CloseTag(sb, indent, ename);
}
else
{
SelfClosingTag(sb, indent, arrTag);
}
break;
case PsoDataType.Structure:
switch (arrEntry.Unk_5h)
{
case 0:
break;
case 3://structure pointer array
var arrStrucPtr = MetaTypes.ConvertData<Array_StructurePointer>(data, eoffset);
arrStrucPtr.SwapEnd();
aBlockId = (int)arrStrucPtr.PointerDataId;
aOffset = (int)arrStrucPtr.PointerDataOffset;
aCount = arrStrucPtr.Count1;
if (aCount > 0)
{
var ptrArr = PsoTypes.GetPointerArray(cont.Pso, arrStrucPtr);
OpenTag(sb, indent, arrTag);
for (int n = 0; n < aCount; n++)
{
var ptrVal = ptrArr[n];
if (ptrVal.Pointer == 0)
{
SelfClosingTag(sb, aind, "Item"); //"null" entry...
}
else
{
WriteNode(sb, aind, cont, ptrVal.BlockID, (int)ptrVal.ItemOffset, XmlTagMode.ItemAndType);
}
}
CloseTag(sb, indent, ename);
}
break;
default:
break;
}
arrTag += " itemType=\"" + HashString((MetaName)arrEntry.ReferenceKey) + "\"";
if (aCount > 0)
{
var aBlock = cont.Pso.GetBlock(aBlockId);
var atyp = cont.GetStructureInfo((MetaName)arrEntry.ReferenceKey);
if (aBlock == null)
{
ErrorXml(sb, indent, ename + ": Array block not found: " + aBlockId.ToString());
}
else if (aBlock.NameHash != (MetaName)MetaTypeName.PsoPOINTER)
{
OpenTag(sb, indent, arrTag);
if (atyp == null)
{
ErrorXml(sb, indent, ename + ": Array type not found: " + HashString((MetaHash)arrEntry.ReferenceKey));
}
else
{
for (int n = 0; n < aCount; n++)
{
WriteNode(sb, aind, cont, aBlockId, aOffset, XmlTagMode.Item, (MetaName)arrEntry.ReferenceKey);
aOffset += atyp.StructureLength;
if ((n < (aCount - 1)) && (aBlock != null) && (aOffset >= aBlock.Length))
{
break;
}
}
}
CloseTag(sb, indent, ename);
}
else
{ } //pointer array should get here, but it's already handled above. should improve this.
}
else
{
SelfClosingTag(sb, indent, arrTag);
}
break;
case PsoDataType.String:
switch (entry.Unk_5h)
{
default:
ErrorXml(sb, indent, ename + ": Unexpected String array subtype: " + entry.Unk_5h.ToString());
break;
case 0: //hash array...
if (embedded)
{ }
var arrHash = MetaTypes.ConvertData<Array_uint>(data, eoffset);
arrHash.SwapEnd();
var hashArr = PsoTypes.GetHashArray(cont.Pso, arrHash);
WriteItemArray(sb, hashArr, indent, ename, "Hash", HashString);
break;
}
break;
case PsoDataType.Float2:
aCount = (entry.ReferenceKey >> 16) & 0x0000FFFF;
arrTag += " itemType=\"Vector2\"";
var v2Arr = MetaTypes.ConvertDataArray<Vector2>(data, eoffset, (int)aCount);
WriteRawArray(sb, v2Arr, indent, ename, "Vector2", FormatVector2Swap, 1);
break;
case PsoDataType.Float3:
if (!embedded)
{ }
arrTag += " itemType=\"Vector3\""; //this is actually aligned as vector4, the W values are crazy in places
var v4Arr = MetaTypes.ConvertDataArray<Vector4>(data, eoffset, (int)aCount);
WriteRawArray(sb, v4Arr, indent, ename, "Vector3", FormatVector4SwapXYZOnly, 1);
break;
case PsoDataType.UByte:
if (embedded)
{ }
else
{ } //block type 2
var barr = new byte[aCount];
if (aCount > 0)
{
//var bblock = cont.Pso.GetBlock(aBlockId);
//var boffs = bblock.Offset + aOffset;
Buffer.BlockCopy(data, abOffset /*boffs*/, barr, 0, (int)aCount);
}
WriteRawArray(sb, barr, indent, ename, "byte");
break;
case PsoDataType.Bool:
if (embedded)
{ }
else
{ }
var barr2 = new byte[aCount];
if (aCount > 0)
{
//var bblock = cont.Pso.GetBlock(aBlockId);
//var boffs = bblock.Offset + aOffset;
Buffer.BlockCopy(data, abOffset /*boffs*/, barr2, 0, (int)aCount);
}
WriteRawArray(sb, barr2, indent, ename, "boolean"); //todo: true/false output
break;
case PsoDataType.Float:
if (embedded)
{ }
var arrFloat = new Array_float(arrStruc.Pointer, arrStruc.Count1); //block type 7
var floatArr = PsoTypes.GetFloatArray(cont.Pso, arrFloat);
WriteRawArray(sb, floatArr, indent, ename, "float", FloatUtil.ToString);
break;
case PsoDataType.UShort:
if (embedded)
{ }
var shortArr = PsoTypes.GetUShortArray(cont.Pso, arrStruc); //block type 4
WriteRawArray(sb, shortArr, indent, ename, "ushort");
break;
case PsoDataType.UInt:
if (embedded)
{ }
var arrUint = new Array_uint(arrStruc.Pointer, arrStruc.Count1); //block type 6
var intArr = PsoTypes.GetUintArray(cont.Pso, arrUint);
WriteRawArray(sb, intArr, indent, ename, "int");
break;
case PsoDataType.SInt:
if (embedded)
{ }
var arrUint2 = new Array_uint(arrStruc.Pointer, arrStruc.Count1); //block type 5
var intArr2 = PsoTypes.GetUintArray(cont.Pso, arrUint2);
WriteRawArray(sb, intArr2, indent, ename, "int");
break;
case PsoDataType.Enum:
if (embedded)
{ }
var arrEnum = MetaTypes.ConvertData<Array_uint>(data, eoffset);
arrEnum.SwapEnd();
var enumArr = PsoTypes.GetUintArray(cont.Pso, arrEnum);
var enumDef = cont.GetEnumInfo((MetaName)arrEntry.ReferenceKey);
WriteItemArray(sb, enumArr, indent, ename, "enum", (ie)=> {
var eval = enumDef?.FindEntry((int)ie);
return HashString(eval?.EntryNameHash ?? 0);
});
break;
}
}
private static void WriteMapNode(StringBuilder sb, int indent, PsoCont cont, int eoffset, PsoStructureEntryInfo entry, PsoStructureInfo structInfo, string ename)
{
var cind = indent + 1;
var data = cont.Pso.DataSection.Data;
switch (entry.Unk_5h)
{
default:
ErrorXml(sb, cind, ename + ": Unexpected Map subtype: " + entry.Unk_5h.ToString());
break;
case 1:
var mapidx1 = entry.ReferenceKey & 0x0000FFFF;
var mapidx2 = (entry.ReferenceKey >> 16) & 0x0000FFFF;
var mapreftype1 = structInfo.Entries[mapidx2];
var mapreftype2 = structInfo.Entries[mapidx1];
var x1 = MetaTypes.SwapBytes(BitConverter.ToInt32(data, eoffset));
var x2 = MetaTypes.SwapBytes(BitConverter.ToInt32(data, eoffset + 4));
var sptr = MetaTypes.ConvertData<Array_Structure>(data, eoffset + 8);
sptr.SwapEnd();
if (x1 != 0x1000000)
{
var c1 = MetaTypes.SwapBytes(BitConverter.ToUInt16(data, eoffset));
var c2 = MetaTypes.SwapBytes(BitConverter.ToUInt16(data, eoffset + 2));
var u1 = MetaTypes.SwapBytes(BitConverter.ToUInt32(data, eoffset + 4));
var c3 = MetaTypes.SwapBytes(BitConverter.ToUInt16(data, eoffset + 8));
var c4 = MetaTypes.SwapBytes(BitConverter.ToUInt16(data, eoffset + 10));
var u3 = MetaTypes.SwapBytes(BitConverter.ToUInt32(data, eoffset + 12));
ulong ptr = MetaTypes.SwapBytes(BitConverter.ToUInt64(data, eoffset + 16));
sptr = new Array_Structure(ptr, c2);
if (c3 != 256)
{ }
if (c1 != c2)
{ }
}
if (x2 != 0)
{ }
if (mapreftype2.ReferenceKey != 0)
{ }
var xBlockId = (int)sptr.PointerDataId;// x3 & 0xFFF;
var xOffset = sptr.PointerDataOffset;// (x3 >> 12) & 0xFFFFF;
var xCount1 = sptr.Count1;// x5 & 0xFFFF;
var xCount2 = sptr.Count2;// (x5 >> 16) & 0xFFFF;
var xBlock = cont.Pso.GetBlock(xBlockId);
if ((xBlock == null) && (xCount1 > 0))
{
ErrorXml(sb, cind, ename + ": Couldn't find Map xBlock: " + xBlockId.ToString());
}
else
{
if (xCount1 != xCount2)
{ }
if (xCount1 > 0)
{
var xStruct = cont.GetStructureInfo(xBlock.NameHash);
var xind = indent + 1;
var aind = indent + 2;
var kEntry = xStruct?.FindEntry(MetaName.Key);
var iEntry = xStruct?.FindEntry(MetaName.Item);
if (xOffset >= xBlock.Length)
{
xOffset = xOffset >> 8; //how to tell when to do this??
}
if ((xStruct == null) && (xBlock.NameHash == 0))
{
SelfClosingTag(sb, cind, ename);
}
else if (xStruct == null)
{
ErrorXml(sb, aind, ename + ": Map struct type not found: " + HashString(xBlock.NameHash));
}
else if ((xStruct.IndexInfo == null))// || (xStruct.IndexInfo.NameHash != (MetaName)MetaTypeName.ARRAYINFO))
{
ErrorXml(sb, aind, ename + ": Map struct was missing IndexInfo! " + (xStruct == null ? "" : xStruct.ToString()));
}
else if ((kEntry == null) || (iEntry == null))
{
ErrorXml(sb, aind, ename + ": Map Key/Item entries not found!");
}
else if (kEntry.Type != PsoDataType.String)
{
ErrorXml(sb, aind, ename + ": Map Key was not a string!");
}
else if ((iEntry.Type != PsoDataType.Structure) && (iEntry.Type != PsoDataType.String))
{
ErrorXml(sb, aind, ename + ": Map Item was not a structure or string!");
}
//else if (iEntry.Unk_5h != 3)
//{
// ErrorXml(sb, aind, ename + ": Map Item was not a structure pointer - TODO!");
//}
else
{
OpenTag(sb, xind, ename);
for (int n = 0; n < xCount1; n++)
{
if (xOffset >= xBlock.Length)
{
ErrorXml(sb, aind, "Offset out of range! Count is " + xCount1.ToString());
break; //out of range...
}
//WriteNode(sb, aind, cont, xBlockId, xOffset, XmlTagMode.Item, xStruct.IndexInfo.NameHash);
int sOffset = (int)xOffset + xBlock.Offset;
var kOffset = sOffset + kEntry.DataOffset;
var iOffset = sOffset + iEntry.DataOffset;
var kStr = GetStringValue(cont.Pso, kEntry, data, kOffset);
if (iEntry.Type == PsoDataType.String)
{
var iStr = GetStringValue(cont.Pso, iEntry, data, iOffset);
OpenTag(sb, aind, "Item type=\"String\" key=\"" + kStr + "\"", false);
sb.Append(XmlEscape(iStr));
CloseTag(sb, 0, "Item");
}
else if (iEntry.ReferenceKey != 0)//(xBlock.NameHash != (MetaName)MetaTypeName.ARRAYINFO)//257,258,259
{
//embedded map values
var vOffset = (int)xOffset + iEntry.DataOffset;
OpenTag(sb, aind, "Item type=\"" + HashString((MetaName)iEntry.ReferenceKey) + "\" key=\"" + kStr + "\"");
WriteNode(sb, aind, cont, xBlockId, vOffset, XmlTagMode.None, (MetaName)iEntry.ReferenceKey);
CloseTag(sb, aind, "Item");
}
else
{
var iPtr = MetaTypes.ConvertData<PsoPOINTER>(data, iOffset);
iPtr.SwapEnd();
var iBlock = cont.Pso.GetBlock(iPtr.BlockID);
if (iBlock == null)
{
ErrorXml(sb, aind, ename + ": Could not find iBlock for Map entry!");
}
else
{
var iStr = "Item type=\"" + HashString(iBlock.NameHash) + "\" key=\"" + kStr + "\"";
var iStruc = cont.GetStructureInfo(iBlock.NameHash);
if (iStruc?.EntriesCount == 0)
{
//SelfClosingTag(sb, aind, iStr);
OpenTag(sb, aind, iStr);
CloseTag(sb, aind, "Item");
}
else
{
var iOff = (int)iPtr.ItemOffset;
OpenTag(sb, aind, iStr);
WriteNode(sb, aind, cont, iPtr.BlockID, iOff, XmlTagMode.None);//, (MetaName)entry.ReferenceKey);
CloseTag(sb, aind, "Item");
}
}
}
xOffset += (uint)xStruct.StructureLength;
}
CloseTag(sb, xind, ename);
}
}
else
{
SelfClosingTag(sb, cind, ename);
}
}
break;
}
}
private static string GetStringValue(PsoFile pso, PsoStructureEntryInfo entry, byte[] data, int eoffset)
{
switch (entry.Unk_5h)
{
default:
return null;
case 0:
var str0len = (int)((entry.ReferenceKey >> 16) & 0xFFFF);
return Encoding.ASCII.GetString(data, eoffset, str0len).Replace("\0", "");
case 1:
case 2:
var dataPtr2 = MetaTypes.ConvertData<DataBlockPointer>(data, eoffset);
dataPtr2.SwapEnd();
return PsoTypes.GetString(pso, dataPtr2);
case 3:
var charPtr3 = MetaTypes.ConvertData<CharPointer>(data, eoffset);
charPtr3.SwapEnd();
var strval = PsoTypes.GetString(pso, charPtr3);
return strval ?? "";
case 7:
case 8:
var hashVal = (MetaHash)MetaTypes.SwapBytes(MetaTypes.ConvertData<MetaHash>(data, eoffset));
return HashString(hashVal);
}
}
public class PsoCont
{
public PsoFile Pso { get; set; }
public Dictionary<MetaName, PsoEnumInfo> EnumDict = new Dictionary<MetaName, PsoEnumInfo>();
public Dictionary<MetaName, PsoStructureInfo> StructDict = new Dictionary<MetaName, PsoStructureInfo>();
public PsoCont(PsoFile pso)
{
Pso = pso;
if ((pso.SchemaSection == null) || (pso.SchemaSection.Entries == null) || (pso.SchemaSection.EntriesIdx == null))
{
return;
}
for (int i = 0; i < pso.SchemaSection.Entries.Length; i++)
{
var entry = pso.SchemaSection.Entries[i];
var enuminfo = entry as PsoEnumInfo;
var structinfo = entry as PsoStructureInfo;
if (enuminfo != null)
{
if (!EnumDict.ContainsKey(enuminfo.IndexInfo.NameHash))
{
EnumDict.Add(enuminfo.IndexInfo.NameHash, enuminfo);
}
else
{
//PsoEnumInfo oldei = EnumDict[enuminfo.IndexInfo.NameHash];
//if (!ComparePsoEnumInfos(oldei, enuminfo))
//{
//}
}
}
else if (structinfo != null)
{
if (!StructDict.ContainsKey(structinfo.IndexInfo.NameHash))
{
StructDict.Add(structinfo.IndexInfo.NameHash, structinfo);
}
else
{
//PsoStructureInfo oldsi = StructDict[structinfo.IndexInfo.NameHash];
//if (!ComparePsoStructureInfos(oldsi, structinfo))
//{
//}
}
}
}
}
public PsoStructureInfo GetStructureInfo(MetaName name)
{
PsoStructureInfo i = null;
StructDict.TryGetValue(name, out i);
return i;
}
public PsoEnumInfo GetEnumInfo(MetaName name)
{
PsoEnumInfo i = null;
EnumDict.TryGetValue(name, out i);
return i;
}
}
}
public class RbfXml : MetaXmlBase
{
public static string GetXml(RbfFile rbf)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine(XmlHeader);
WriteNode(sb, 0, rbf.current);
return sb.ToString();
}
private static void WriteNode(StringBuilder sb, int indent, RbfStructure rs)
{
var attStr = "";
if (rs.Attributes.Count > 0)
{
var asb = new StringBuilder();
foreach (var attr in rs.Attributes)
{
if (attr is RbfString str)
{
asb.Append($" {attr.Name}=\"{str.Value}\"");
}
else if (attr is RbfFloat flt)
{
asb.Append($" {attr.Name}=\"{FloatUtil.ToString(flt.Value)}\"");
}
else if (attr is RbfUint32 unt)
{
asb.Append($" {attr.Name}=\"{unt.Value.ToString()}\"");
}
else if (attr is RbfBoolean bln)
{
asb.Append($" {attr.Name}=\"{bln.Value.ToString()}\"");
}
else
{ }
}
attStr = $"{asb.ToString()}";
}
if (rs.Children.Count == 0)
{
SelfClosingTag(sb, indent, rs.Name + attStr);
return;
}
int cind = indent + 1;
bool oneline = ((rs.Children.Count == 1) && (rs.Children[0].Name == null) && (rs.Attributes.Count == 0));
OpenTag(sb, indent, rs.Name + attStr, !oneline);
foreach (var child in rs.Children)
{
if (child is RbfBytes)
{
var bytesChild = (RbfBytes)child;
var contentField = rs.FindAttribute("content") as RbfString;//TODO: fix this to output nicer XML!
if (contentField != null)
{
if (contentField.Value == "char_array")
{
foreach (byte k in bytesChild.Value)
{
Indent(sb, cind);
sb.AppendLine(k.ToString());
}
}
else if (contentField.Value.Equals("short_array"))
{
var valueReader = new DataReader(new MemoryStream(bytesChild.Value));
while (valueReader.Position < valueReader.Length)
{
Indent(sb, cind);
var y = valueReader.ReadUInt16();
sb.AppendLine(y.ToString());
}
}
else
{
ErrorXml(sb, cind, "Unexpected content type: " + contentField.Value);
}
}
else
{
string stringValue = Encoding.ASCII.GetString(bytesChild.Value);
string str = stringValue.Substring(0, stringValue.Length - 1); //removes null terminator
sb.Append(str);
}
}
if (child is RbfFloat)
{
var floatChild = (RbfFloat)child;
ValueTag(sb, cind, child.Name, FloatUtil.ToString(floatChild.Value));
}
if (child is RbfString)
{
////// this doesn't seem to be used! it's always using RbfBytes child...
var stringChild = (RbfString)child;
StringTag(sb, cind, stringChild.Name, stringChild.Value);
//if (stringChild.Name.Equals("content"))
//else if (stringChild.Name.Equals("type"))
//else throw new Exception("Unexpected string content");
}
if (child is RbfStructure)
{
WriteNode(sb, cind, child as RbfStructure);
}
if (child is RbfUint32)
{
var intChild = (RbfUint32)child;
ValueTag(sb, cind, intChild.Name, UintString(intChild.Value));
}
if (child is RbfBoolean)
{
var booleanChild = (RbfBoolean)child;
ValueTag(sb, cind, booleanChild.Name, booleanChild.Value.ToString());
}
if (child is RbfFloat3)
{
var v3 = child as RbfFloat3;
SelfClosingTag(sb, cind, v3.Name + " x=\"" + FloatUtil.ToString(v3.X) + "\" y=\"" + FloatUtil.ToString(v3.Y) + "\" z=\"" + FloatUtil.ToString(v3.Z) + "\"");
}
}
CloseTag(sb, oneline ? 0 : indent, rs.Name);
}
}
public class MetaXmlBase
{
public const string XmlHeader = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
public static void Indent(StringBuilder sb, int indent)
{
for (int i = 0; i < indent; i++)
{
sb.Append(" ");
}
}
public static void ErrorXml(StringBuilder sb, int indent, string msg)
{
Indent(sb, indent);
sb.Append("<error>");
sb.Append(msg);
sb.Append("</error>");
sb.AppendLine();
}
public static void OpenTag(StringBuilder sb, int indent, string name, bool appendLine = true, string metaName = "")
{
Indent(sb, indent);
sb.Append("<");
sb.Append(name);
if (string.IsNullOrWhiteSpace(metaName))
{
sb.Append(">");
}
else
{
sb.Append(" name=\"" + metaName + "\">");
}
if (appendLine) sb.AppendLine();
}
public static void CloseTag(StringBuilder sb, int indent, string name, bool appendLine = true)
{
Indent(sb, indent);
sb.Append("</");
sb.Append(name);
sb.Append(">");
if (appendLine) sb.AppendLine();
}
public static void ValueTag(StringBuilder sb, int indent, string name, string val, string attr = "value")
{
Indent(sb, indent);
sb.Append("<");
sb.Append(name);
sb.Append(" ");
sb.Append(attr);
sb.Append("=\"");
sb.Append(val);
sb.Append("\" />");
sb.AppendLine();
}
public static void OneLineTag(StringBuilder sb, int indent, string name, string text)
{
Indent(sb, indent);
sb.Append("<");
sb.Append(name);
sb.Append(">");
sb.Append(text);
sb.Append("</");
sb.Append(name);
sb.Append(">");
sb.AppendLine();
}
public static void SelfClosingTag(StringBuilder sb, int indent, string val)
{
Indent(sb, indent);
sb.Append("<");
sb.Append(val);
sb.Append(" />");
sb.AppendLine();
}
public static void StringTag(StringBuilder sb, int indent, string name, string text)
{
if (!string.IsNullOrEmpty(text)) OneLineTag(sb, indent, name, text);
else SelfClosingTag(sb, indent, name);
}
public static void WriteRawArrayContent<T>(StringBuilder sb, T[] arr, int ind, Func<T, string> formatter = null, int arrRowSize = 10) where T : struct
{
var aCount = arr?.Length ?? 0;
for (int n = 0; n < aCount; n++)
{
var col = n % arrRowSize;
if (col == 0) Indent(sb, ind);
if (col > 0) sb.Append(" ");
string str = (formatter != null) ? formatter(arr[n]) : arr[n].ToString();
sb.Append(str);
bool lastcol = (col == (arrRowSize - 1));
bool lastn = (n == (aCount - 1));
if (lastcol || lastn) sb.AppendLine();
}
}
public static void WriteRawArray<T>(StringBuilder sb, T[] arr, int ind, string name, string typeName, Func<T, string> formatter = null, int arrRowSize = 10) where T : struct
{
var aCount = arr?.Length ?? 0;
//var arrRowSize = 10;
var aind = ind + 1;
var arrTag = name;// + " itemType=\"" + typeName + "\"";
if (aCount > 0)
{
if (aCount <= arrRowSize)
{
OpenTag(sb, ind, arrTag, false);
for (int n = 0; n < aCount; n++)
{
if (n > 0) sb.Append(" ");
string str = (formatter != null) ? formatter(arr[n]) : arr[n].ToString();
sb.Append(str);
}
CloseTag(sb, 0, name);
}
else
{
OpenTag(sb, ind, arrTag);
for (int n = 0; n < aCount; n++)
{
var col = n % arrRowSize;
if (col == 0) Indent(sb, aind);
if (col > 0) sb.Append(" ");
string str = (formatter != null) ? formatter(arr[n]) : arr[n].ToString();
sb.Append(str);
bool lastcol = (col == (arrRowSize - 1));
bool lastn = (n == (aCount - 1));
if (lastcol || lastn) sb.AppendLine();
}
CloseTag(sb, ind, name);
}
}
else
{
SelfClosingTag(sb, ind, arrTag);
}
}
public static void WriteItemArray<T>(StringBuilder sb, T[] arr, int ind, string name, string typeName, Func<T, string> formatter) where T : struct
{
var aCount = arr?.Length ?? 0;
var arrTag = name;// + " itemType=\"Hash\"";
var aind = ind + 1;
if (aCount > 0)
{
OpenTag(sb, ind, arrTag);
for (int n = 0; n < aCount; n++)
{
Indent(sb, aind);
sb.Append("<Item>");
sb.Append(formatter(arr[n]));
sb.AppendLine("</Item>");
}
CloseTag(sb, ind, name);
}
else
{
SelfClosingTag(sb, ind, arrTag);
}
}
public static void WriteItemArray<T>(StringBuilder sb, T[] arr, int ind, string name) where T : IMetaXmlItem
{
var itemCount = arr?.Length ?? 0;
if (itemCount > 0)
{
OpenTag(sb, ind, name);
var cind = ind + 1;
var cind2 = ind + 2;
for (int i = 0; i < itemCount; i++)
{
if (arr[i] != null)
{
OpenTag(sb, cind, "Item");
arr[i].WriteXml(sb, cind2);
CloseTag(sb, cind, "Item");
}
else
{
SelfClosingTag(sb, cind, "Item");
}
}
CloseTag(sb, ind, name);
}
else
{
SelfClosingTag(sb, ind, name);
}
}
public static void WriteCustomItemArray<T>(StringBuilder sb, T[] arr, int ind, string name) where T : IMetaXmlItem
{
var itemCount = arr?.Length ?? 0;
if (itemCount > 0)
{
OpenTag(sb, ind, name);
var cind = ind + 1;
for (int i = 0; i < itemCount; i++)
{
if (arr[i] != null)
{
arr[i].WriteXml(sb, cind);
}
}
CloseTag(sb, ind, name);
}
else
{
SelfClosingTag(sb, ind, name);
}
}
public static void WriteHashItemArray(StringBuilder sb, MetaHash[] arr, int ind, string name)
{
var itemCount = arr?.Length ?? 0;
if (itemCount > 0)
{
OpenTag(sb, ind, name);
var cind = ind + 1;
for (int i = 0; i < itemCount; i++)
{
var iname = HashString(arr[i]);
StringTag(sb, cind, "Item", iname);
}
CloseTag(sb, ind, name);
}
else
{
SelfClosingTag(sb, ind, name);
}
}
public static string FormatHash(MetaHash h) //for use with WriteItemArray
{
var str = JenkIndex.TryGetString(h);
if (!string.IsNullOrEmpty(str)) return str;
str = GlobalText.TryGetString(h);
if (!string.IsNullOrEmpty(str)) return str;
return HashString(h);// "hash_" + h.Hex;
//return h.ToString();
}
public static string FormatVector2(Vector2 v) //for use with WriteItemArray
{
return FloatUtil.GetVector2String(v);
}
public static string FormatVector3(Vector3 v) //for use with WriteItemArray
{
return FloatUtil.GetVector3String(v);
}
public static string FormatVector4(Vector4 v) //for use with WriteItemArray
{
return FloatUtil.GetVector4String(v);
}
public static string FormatHexByte(byte b)
{
return Convert.ToString(b, 16).ToUpperInvariant().PadLeft(2, '0'); //hex byte array
}
public static string FormatHashSwap(MetaHash h) //for use with WriteItemArray, swaps endianness
{
return MetaTypes.SwapBytes(h).ToString();
}
public static string FormatVector2Swap(Vector2 v) //for use with WriteItemArray, swaps endianness
{
return FloatUtil.GetVector2String(MetaTypes.SwapBytes(v));
}
public static string FormatVector3Swap(Vector3 v) //for use with WriteItemArray, swaps endianness
{
return FloatUtil.GetVector3String(MetaTypes.SwapBytes(v));
}
public static string FormatVector4Swap(Vector4 v) //for use with WriteItemArray, swaps endianness
{
return FloatUtil.GetVector4String(MetaTypes.SwapBytes(v));
}
public static string FormatVector4SwapXYZOnly(Vector4 v) //for use with WriteItemArray, swaps endianness, and outputs only XYZ components
{
return FloatUtil.GetVector3String(MetaTypes.SwapBytes(v.XYZ()));
}
public static string HashString(MetaName h)
{
uint uh = (uint)h;
if (uh == 0) return "";
string str;
if (MetaNames.TryGetString(uh, out str)) return str;
str = JenkIndex.TryGetString(uh);
if (!string.IsNullOrEmpty(str)) return str;
//TODO: do extra hash lookup here
//if(Lookup.TryGetValue(uh, out str)) ...
return "hash_" + uh.ToString("X").PadLeft(8, '0');
}
public static string HashString(MetaHash h)
{
if (h == 0) return "";
var str = JenkIndex.TryGetString(h);
if (string.IsNullOrEmpty(str))
{
if (MetaNames.TryGetString(h, out str)) return str;
}
//todo: make sure JenkIndex is built!
//todo: do extra hash lookup here
if (!string.IsNullOrEmpty(str)) return str;
return "hash_" + h.Hex;
}
public static string HashString(TextHash h)
{
uint uh = h.Hash;
if (uh == 0) return "";
var str = GlobalText.TryGetString(uh);
if (!string.IsNullOrEmpty(str)) return str;
//TODO: do extra hash lookup here
//if(Lookup.TryGetValue(uh, out str)) ...
return "hash_" + uh.ToString("X").PadLeft(8, '0');
}
public static string UintString(uint h)
{
//string str;
//if (MetaNames.TryGetString(h, out str)) return str;
//str = JenkIndex.TryGetString(h);
//if (!string.IsNullOrEmpty(str)) return str;
////TODO: do extra hash lookup here
////if(Lookup.TryGetValue(uh, out str)) ...
//if (h == 0) return "";
return "0x" + h.ToString("X");
}
public static string XmlEscape(string unescaped)
{
if (unescaped == null) return null;
XmlDocument doc = new XmlDocument();
XmlNode node = doc.CreateElement("root");
node.InnerText = unescaped;
var escaped = node.InnerXml.Replace("\"", "&quot;");
if (escaped != unescaped)
{ }
return escaped;
}
public enum XmlTagMode
{
None = 0,
Structure = 1,
Item = 2,
ItemAndType = 3,
}
}
public interface IMetaXmlItem
{
void WriteXml(StringBuilder sb, int indent);
void ReadXml(XmlNode node);
}
public enum MetaFormat
{
XML = 0,
RSC = 1,
PSO = 2,
RBF = 3,
CacheFile = 4,
AudioRel = 5,
Ynd = 6,
Ynv = 7,
Ycd = 8,
Ybn = 9,
Ytd = 10,
Ydr = 11,
Ydd = 12,
Yft = 13,
Ypt = 14,
Yld = 15,
Yed = 16,
Awc = 17,
Heightmap = 18,
}
}