diff --git a/CodeWalker.Core/CodeWalker.Core.csproj b/CodeWalker.Core/CodeWalker.Core.csproj index 3e40522..e1ee816 100644 --- a/CodeWalker.Core/CodeWalker.Core.csproj +++ b/CodeWalker.Core/CodeWalker.Core.csproj @@ -84,9 +84,11 @@ + + diff --git a/CodeWalker.Core/GameFiles/GameFileCache.cs b/CodeWalker.Core/GameFiles/GameFileCache.cs index 271f128..896868c 100644 --- a/CodeWalker.Core/GameFiles/GameFileCache.cs +++ b/CodeWalker.Core/GameFiles/GameFileCache.cs @@ -2212,16 +2212,27 @@ namespace CodeWalker.GameFiles var exceptions = new List(); var allpsos = new List(); + var diffpsos = new List(); foreach (RpfFile file in AllRpfs) { foreach (RpfEntry entry in file.AllEntries) { +#if !DEBUG try +#endif { var n = entry.NameLower; + if (!(n.EndsWith(".pso") || + n.EndsWith(".ymt") || + n.EndsWith(".ymf") || + n.EndsWith(".ymap") || + n.EndsWith(".ytyp") || + n.EndsWith(".cut"))) + continue; //PSO files seem to only have these extensions + var fentry = entry as RpfFileEntry; - var data = entry.File.ExtractFile(fentry); //kind of slow, but sure to catch all PSO files + var data = entry.File.ExtractFile(fentry); if (data != null) { using (MemoryStream ms = new MemoryStream(data)) @@ -2236,19 +2247,43 @@ namespace CodeWalker.GameFiles allpsos.Add(fentry.Path); PsoTypes.EnsurePsoTypes(pso); + + var xml = PsoXml.GetXml(pso); + if (!string.IsNullOrEmpty(xml)) + { } + + var xdoc = new XmlDocument(); + xdoc.LoadXml(xml); + var pso2 = XmlPso.GetPso(xdoc); + var pso2b = pso2.Save(); + + var pso3 = new PsoFile(); + pso3.Load(pso2b); + var xml3 = PsoXml.GetXml(pso3); + + if (xml.Length != xml3.Length) + { } + if (xml != xml3) + { + diffpsos.Add(fentry.Path); + } + } } } } +#if !DEBUG catch (Exception ex) { UpdateStatus("Error! " + ex.ToString()); exceptions.Add(ex); } +#endif } } string allpsopaths = string.Join("\r\n", allpsos); + string diffpsopaths = string.Join("\r\n", diffpsos); string str = PsoTypes.GetTypesInitString(); if (!string.IsNullOrEmpty(str)) diff --git a/CodeWalker.Core/GameFiles/MetaTypes/Meta.cs b/CodeWalker.Core/GameFiles/MetaTypes/Meta.cs index c617940..5d457c8 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/Meta.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/Meta.cs @@ -621,9 +621,9 @@ namespace CodeWalker.GameFiles public ushort Count2 { get; set; } public uint Unk1 { get; set; } - public uint PointerDataId { get { return (Pointer & 0xFFF); } } - public uint PointerDataIndex { get { return (Pointer & 0xFFF) - 1; } } - public uint PointerDataOffset { get { return ((Pointer >> 12) & 0xFFFFF); } } + public uint PointerDataId { get { return (Pointer & 0xFFF); } set { Pointer = (Pointer & 0xFFFFF000) + (value & 0xFFF); } } + public uint PointerDataIndex { get { return (Pointer & 0xFFF) - 1; } set { PointerDataId = value + 1; } } + public uint PointerDataOffset { get { return ((Pointer >> 12) & 0xFFFFF); } set { Pointer = (Pointer & 0xFFF) + ((value << 12) & 0xFFFFF000); } } diff --git a/CodeWalker.Core/GameFiles/MetaTypes/MetaTypes.cs b/CodeWalker.Core/GameFiles/MetaTypes/MetaTypes.cs index c0000b1..0939b17 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/MetaTypes.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/MetaTypes.cs @@ -2007,6 +2007,10 @@ namespace CodeWalker.GameFiles { return (ushort)(((x & 0xFF00) >> 8) | ((x & 0x00FF) << 8)); } + public static short SwapBytes(short x) + { + return (short)SwapBytes((ushort)x); + } public static uint SwapBytes(uint x) { // swap adjacent 16-bit blocks diff --git a/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs b/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs index b40c0ef..453aaf8 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Xml; namespace CodeWalker.GameFiles { @@ -640,6 +641,10 @@ namespace CodeWalker.GameFiles 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; @@ -708,7 +713,10 @@ namespace CodeWalker.GameFiles case 0: //int enum var intEVal = MetaTypes.SwapBytes(BitConverter.ToInt32(data, eoffset)); var intE = enumInfo.FindEntry(intEVal); - StringTag(sb, cind, ename, HashString(intE?.EntryNameHash ?? 0)); + var intH = HashString(intE?.EntryNameHash ?? 0); + if (string.IsNullOrEmpty(intH)) + { } + StringTag(sb, cind, ename, intH); break; case 2: //byte enum var byteEVal = data[eoffset]; @@ -718,9 +726,9 @@ namespace CodeWalker.GameFiles } break; case PsoDataType.Flags: - uint fCount = (entry.ReferenceKey >> 16) & 0x0000FFFF; - uint fEntry = (entry.ReferenceKey & 0xFFFF); - var fEnt = structInfo.GetEntry((int)fEntry); + //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.ARRAYINFO)) { @@ -728,7 +736,9 @@ namespace CodeWalker.GameFiles } if (flagsInfo == null) { - flagsInfo = cont.GetEnumInfo(entry.EntryNameHash); + if (fEntry != 0xFFF) + { } + //flagsInfo = cont.GetEnumInfo(entry.EntryNameHash); } uint? flagsVal = null; switch (entry.Unk_5h) @@ -805,13 +815,14 @@ namespace CodeWalker.GameFiles default: ErrorXml(sb, cind, ename + ": Unexpected Integer subtype: " + entry.Unk_5h.ToString()); break; - case 0: //signed int + 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, "0x" + int6bVal.ToString("X").PadLeft(8, '0')); + ValueTag(sb, cind, ename, int6bVal.ToString()); + //ValueTag(sb, cind, ename, "0x" + int6bVal.ToString("X").PadLeft(8, '0')); break; } break; @@ -833,16 +844,16 @@ namespace CodeWalker.GameFiles ValueTag(sb, cind, ename, short4Val.ToString()); break; case PsoDataType.HFloat://half float? - var short1EVal = MetaTypes.SwapBytes(BitConverter.ToUInt16(data, eoffset)); + var short1EVal = MetaTypes.SwapBytes(BitConverter.ToInt16(data, eoffset)); ValueTag(sb, cind, ename, short1EVal.ToString()); break; case PsoDataType.String: - var str0 = GetStringValue(cont.Pso, entry, data, eoffset); - if (str0 == null) - { - ErrorXml(sb, cind, ename + ": Unexpected String subtype: " + entry.Unk_5h.ToString()); - } - else + 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); } @@ -909,8 +920,13 @@ namespace CodeWalker.GameFiles 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; PsoStructureEntryInfo arrEntry = estruct.GetEntry((int)(entry.ReferenceKey & 0xFFFF)); @@ -922,17 +938,24 @@ namespace CodeWalker.GameFiles 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 - var arrStruc = MetaTypes.ConvertData(data, eoffset); + arrStruc = MetaTypes.ConvertData(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; @@ -941,11 +964,20 @@ namespace CodeWalker.GameFiles case 4: //pointer array? default array? if (arrEntry.Unk_5h == 3) //pointers... { - var arrStruc4 = MetaTypes.ConvertData(data, eoffset); - arrStruc4.SwapEnd(); - aBlockId = (int)arrStruc4.PointerDataId; - aOffset = (int)arrStruc4.PointerDataOffset; - aCount = arrStruc4.Count1; + arrStruc = MetaTypes.ConvertData(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 @@ -959,10 +991,11 @@ namespace CodeWalker.GameFiles 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) { - //var arrStruc5 = MetaTypes.ConvertDataArray(data, eoffset, (int)rk0); - //for (int n = 0; n < rk0; n++) arrStruc5[n].SwapEnd(); aOffset = offset + entry.DataOffset; OpenTag(sb, indent, arrTag); @@ -997,7 +1030,14 @@ namespace CodeWalker.GameFiles for (int n = 0; n < aCount; n++) { var ptrVal = ptrArr[n]; - WriteNode(sb, aind, cont, ptrVal.BlockID, (int)ptrVal.ItemOffset, XmlTagMode.ItemAndType); + 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); } @@ -1050,6 +1090,8 @@ namespace CodeWalker.GameFiles ErrorXml(sb, indent, ename + ": Unexpected String array subtype: " + entry.Unk_5h.ToString()); break; case 0: //hash array... + if (embedded) + { } var arrHash = MetaTypes.ConvertData(data, eoffset); arrHash.SwapEnd(); var hashArr = PsoTypes.GetHashArray(cont.Pso, arrHash); @@ -1064,54 +1106,70 @@ namespace CodeWalker.GameFiles WriteRawArray(sb, v2Arr, indent, ename, "Vector2", FormatVector2Swap, 1); break; case PsoDataType.Float3: - aCount = (entry.ReferenceKey >> 16) & 0x0000FFFF; + if (!embedded) + { } arrTag += " itemType=\"Vector3\""; //this is actually aligned as vector4, the W values are crazy in places var v4Arr = MetaTypes.ConvertDataArray(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, boffs, barr, 0, (int)aCount); + //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, boffs, barr2, 0, (int)aCount); + //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: - var arrFloat = MetaTypes.ConvertData(data, eoffset); - arrFloat.SwapEnd(); + 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"); break; case PsoDataType.UShort: - var arrShort = MetaTypes.ConvertData(data, eoffset); - arrShort.SwapEnd(); - var shortArr = PsoTypes.GetUShortArray(cont.Pso, arrShort); + if (embedded) + { } + var shortArr = PsoTypes.GetUShortArray(cont.Pso, arrStruc); //block type 4 WriteRawArray(sb, shortArr, indent, ename, "ushort"); break; case PsoDataType.UInt: - var intArr = MetaTypes.ConvertDataArray(data, eoffset, (int)aCount); + 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: - var arrUint2 = MetaTypes.ConvertData(data, eoffset); - arrUint2.SwapEnd(); + 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(data, eoffset); arrEnum.SwapEnd(); var enumArr = PsoTypes.GetUintArray(cont.Pso, arrEnum); @@ -1139,41 +1197,23 @@ namespace CodeWalker.GameFiles var mapidx2 = (entry.ReferenceKey >> 16) & 0x0000FFFF; var mapreftype1 = structInfo.Entries[mapidx2]; var mapreftype2 = structInfo.Entries[mapidx1]; - var x1 = MetaTypes.SwapBytes(BitConverter.ToInt32(data, eoffset));//same as ref key? - var x2 = MetaTypes.SwapBytes(BitConverter.ToInt32(data, eoffset + 4));//0? - var x3 = MetaTypes.SwapBytes(BitConverter.ToInt32(data, eoffset + 8));//pointer? - var x4 = MetaTypes.SwapBytes(BitConverter.ToInt32(data, eoffset + 12));// - var x5 = MetaTypes.SwapBytes(BitConverter.ToInt32(data, eoffset + 16));//count/capacity? - var x6 = MetaTypes.SwapBytes(BitConverter.ToInt32(data, eoffset + 20));// + var x1 = MetaTypes.SwapBytes(BitConverter.ToInt32(data, eoffset)); + var x2 = MetaTypes.SwapBytes(BitConverter.ToInt32(data, eoffset + 4)); + var sptr = MetaTypes.ConvertData(data, eoffset + 8); + sptr.SwapEnd(); - //File.WriteAllText("C:\\CodeWalker.Projects\\testxml.xml", sb.ToString()); if (x1 != 0x1000000) { } if (x2 != 0) { } - if (x4 != 0) - { } - if (x6 != 0) + if (mapreftype2.ReferenceKey != 0) { } - - var xBlockId = x3 & 0xFFF; - var xOffset = (x3 >> 12) & 0xFFFFF; - var xCount1 = x5 & 0xFFFF; - var xCount2 = (x5 >> 16) & 0xFFFF; - - //var x1a = x1 & 0xFFF; //block id? for another pointer? - //var x1b = (x1 >> 12) & 0xFFFFF; //offset? - //var x4u = (uint)x4; - //var x4a = x4 & 0xFFF; //block id? - //var x4b = (x4 >> 12) & 0xFFFFF; //offset? - //var x2h = (MetaHash)(uint)x2; - //var x6h = (MetaHash)(uint)x6; - //if (x1a > 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)) @@ -1183,8 +1223,7 @@ namespace CodeWalker.GameFiles else { if (xCount1 != xCount2) - { - } + { } if (xCount1 > 0) { var xStruct = cont.GetStructureInfo(xBlock.NameHash); @@ -1218,10 +1257,10 @@ namespace CodeWalker.GameFiles { ErrorXml(sb, aind, ename + ": Map Item was not a structure!"); } - else if (iEntry.Unk_5h != 3) - { - ErrorXml(sb, aind, ename + ": Map Item was not a structure pointer - TODO!"); - } + //else if (iEntry.Unk_5h != 3) + //{ + // ErrorXml(sb, aind, ename + ": Map Item was not a structure pointer - TODO!"); + //} else { OpenTag(sb, xind, ename); @@ -1236,30 +1275,39 @@ namespace CodeWalker.GameFiles var kOffset = sOffset + kEntry.DataOffset; var iOffset = sOffset + iEntry.DataOffset; var kStr = GetStringValue(cont.Pso, kEntry, data, kOffset); - var iPtr = MetaTypes.ConvertData(data, iOffset); - iPtr.SwapEnd(); - var iBlock = cont.Pso.GetBlock(iPtr.BlockID); - if (iBlock == null) + if (iEntry.ReferenceKey != 0)//(xBlock.NameHash != MetaName.ARRAYINFO)//257,258,259 { - OpenTag(sb, aind, "Item type=\"" + HashString((MetaName)entry.ReferenceKey) + "\" key=\"" + kStr + "\""); - WriteNode(sb, aind, cont, iPtr.BlockID, (int)iPtr.ItemOffset, XmlTagMode.None, (MetaName)entry.ReferenceKey); + //embedded map values + var vOffset = xOffset2 + 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 iStr = "Item type=\"" + HashString(iBlock.NameHash) + "\" key=\"" + kStr + "\""; - var iStruc = cont.GetStructureInfo(iBlock.NameHash); - if (iStruc?.EntriesCount == 0) + var iPtr = MetaTypes.ConvertData(data, iOffset); + iPtr.SwapEnd(); + var iBlock = cont.Pso.GetBlock(iPtr.BlockID); + if (iBlock == null) { - //SelfClosingTag(sb, aind, iStr); - OpenTag(sb, aind, iStr); - CloseTag(sb, aind, "Item"); + ErrorXml(sb, aind, ename + ": Could not find iBlock for Map entry!"); } else { - OpenTag(sb, aind, iStr); - WriteNode(sb, aind, cont, iPtr.BlockID, (int)iPtr.ItemOffset, XmlTagMode.None);//, (MetaName)entry.ReferenceKey); - CloseTag(sb, aind, "Item"); + 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 + { + OpenTag(sb, aind, iStr); + WriteNode(sb, aind, cont, iPtr.BlockID, (int)iPtr.ItemOffset, XmlTagMode.None);//, (MetaName)entry.ReferenceKey); + CloseTag(sb, aind, "Item"); + } } } xOffset2 += xStruct.StructureLength; @@ -1311,7 +1359,17 @@ namespace CodeWalker.GameFiles } - + 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; + if (escaped != unescaped) + { } + return node.InnerXml; + } public class PsoCont diff --git a/CodeWalker.Core/GameFiles/MetaTypes/Pso.cs b/CodeWalker.Core/GameFiles/MetaTypes/Pso.cs index a92ff3e..f63fa26 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/Pso.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/Pso.cs @@ -205,6 +205,13 @@ namespace CodeWalker.GameFiles public PsoSTRESection STRESection { get; set; } public PsoCHKSSection CHKSSection { get; set; } + + public void Load(byte[] data) + { + using (var ms = new MemoryStream(data)) + Load(ms); + } + public void Load(string fileName) { using (var stream = new FileStream(fileName, FileMode.Open)) @@ -268,6 +275,19 @@ namespace CodeWalker.GameFiles } } + + public byte[] Save() + { + var ms = new MemoryStream(); + Save(ms); + + var buf = new byte[ms.Length]; + ms.Position = 0; + ms.Read(buf, 0, buf.Length); + + return buf; + } + public void Save(string fileName) { using (var stream = new FileStream(fileName, FileMode.Create)) @@ -280,6 +300,11 @@ namespace CodeWalker.GameFiles if (DataSection != null) DataSection.Write(writer); if (DataMapSection != null) DataMapSection.Write(writer); if (SchemaSection != null) SchemaSection.Write(writer); + if (STRFSection != null) STRFSection.Write(writer); + if (STRSSection != null) STRSSection.Write(writer); + if (STRESection != null) STRESection.Write(writer); + if (PSIGSection != null) PSIGSection.Write(writer); + if (CHKSSection != null) CHKSSection.Write(writer); } @@ -342,11 +367,13 @@ namespace CodeWalker.GameFiles public void Write(DataWriter writer) { + Length = Data.Length; + writer.Write(Data); - writer.Position -= Data.Length; - writer.Write((uint)0x5053494E); - writer.Write((uint)(Data.Length)); - writer.Position += Data.Length - 8; + writer.Position -= Length; + writer.Write(Ident); + writer.Write((uint)(Length)); + writer.Position += Length - 8; } public override string ToString() @@ -413,10 +440,10 @@ namespace CodeWalker.GameFiles public void Read(DataReader reader) { - this.NameHash = (MetaName)reader.ReadUInt32(); - this.Offset = reader.ReadInt32(); - this.Unknown_8h = reader.ReadInt32(); - this.Length = reader.ReadInt32(); + NameHash = (MetaName)reader.ReadUInt32(); + Offset = reader.ReadInt32(); + Unknown_8h = reader.ReadInt32(); + Length = reader.ReadInt32(); } public void Write(DataWriter writer) @@ -449,7 +476,7 @@ namespace CodeWalker.GameFiles Length = reader.ReadInt32(); Count = reader.ReadUInt32(); - this.EntriesIdx = new PsoElementIndexInfo[Count]; + EntriesIdx = new PsoElementIndexInfo[Count]; for (int i = 0; i < Count; i++) { var entry = new PsoElementIndexInfo(); @@ -457,7 +484,7 @@ namespace CodeWalker.GameFiles EntriesIdx[i] = entry; } - this.Entries = new PsoElementInfo[Count]; + Entries = new PsoElementInfo[Count]; for (int i = 0; i < Count; i++) { reader.Position = EntriesIdx[i].Offset; @@ -506,7 +533,7 @@ namespace CodeWalker.GameFiles writer.Write(Ident); writer.Write((int)(12 + entriesStream.Length + indexStream.Length)); - writer.Write((int)(Entries.Length)); + writer.Write((uint)(Entries.Length)); // write entries index data var buf1 = new byte[indexStream.Length]; @@ -536,8 +563,8 @@ namespace CodeWalker.GameFiles public void Read(DataReader reader) { - this.NameHash = (MetaName)reader.ReadUInt32(); - this.Offset = reader.ReadInt32(); + NameHash = (MetaName)reader.ReadUInt32(); + Offset = reader.ReadInt32(); } public void Write(DataWriter writer) @@ -590,11 +617,11 @@ namespace CodeWalker.GameFiles public override void Read(DataReader reader) { uint x = reader.ReadUInt32(); - this.Type = (byte)((x & 0xFF000000) >> 24); - this.EntriesCount = (short)(x & 0xFFFF); - this.Unk = (byte)((x & 0x00FF0000) >> 16); - this.StructureLength = reader.ReadInt32(); - this.Unk_Ch = reader.ReadUInt32(); + Type = (byte)((x & 0xFF000000) >> 24); + EntriesCount = (short)(x & 0xFFFF); + Unk = (byte)((x & 0x00FF0000) >> 16); + StructureLength = reader.ReadInt32(); + Unk_Ch = reader.ReadUInt32(); if (Unk_Ch != 0) { } @@ -655,7 +682,7 @@ namespace CodeWalker.GameFiles { public MetaName EntryNameHash { get; set; } public PsoDataType Type { get; set; } - public byte Unk_5h { get; set; } + public byte Unk_5h { get; set; } //0 = default, 3 = pointer array? public ushort DataOffset { get; set; } public uint ReferenceKey { get; set; } // when array -> entry index with type @@ -673,11 +700,11 @@ namespace CodeWalker.GameFiles public void Read(DataReader reader) { - this.EntryNameHash = (MetaName)reader.ReadUInt32(); - this.Type = (PsoDataType)reader.ReadByte(); - this.Unk_5h = reader.ReadByte(); - this.DataOffset = reader.ReadUInt16(); - this.ReferenceKey = reader.ReadUInt32(); + EntryNameHash = (MetaName)reader.ReadUInt32(); + Type = (PsoDataType)reader.ReadByte(); + Unk_5h = reader.ReadByte(); + DataOffset = reader.ReadUInt16(); + ReferenceKey = reader.ReadUInt32(); } public void Write(DataWriter writer) @@ -721,8 +748,8 @@ namespace CodeWalker.GameFiles public override void Read(DataReader reader) { uint x = reader.ReadUInt32(); - this.Type = (byte)((x & 0xFF000000) >> 24); - this.EntriesCount = (int)(x & 0x00FFFFFF); + Type = (byte)((x & 0xFF000000) >> 24); + EntriesCount = (int)(x & 0x00FFFFFF); Entries = new PsoEnumEntryInfo[EntriesCount]; for (int i = 0; i < EntriesCount; i++) @@ -762,6 +789,21 @@ namespace CodeWalker.GameFiles return null; } + public PsoEnumEntryInfo FindEntryByName(MetaName name) + { + if (Entries == null) return null; + for (int i = 0; i < Entries.Length; i++) + { + var entry = Entries[i]; + if (entry.EntryNameHash == name) + { + return entry; + } + } + return null; + } + + public override string ToString() { @@ -785,8 +827,8 @@ namespace CodeWalker.GameFiles public void Read(DataReader reader) { - this.EntryNameHash = (MetaName)reader.ReadUInt32(); - this.EntryKey = reader.ReadInt32(); + EntryNameHash = (MetaName)reader.ReadUInt32(); + EntryKey = reader.ReadInt32(); } public void Write(DataWriter writer) @@ -827,10 +869,25 @@ namespace CodeWalker.GameFiles public void Write(DataWriter writer) { + var strStream = new MemoryStream(); + var strWriter = new DataWriter(strStream, Endianess.BigEndian); + foreach (var str in Strings) + { + strWriter.Write(str); + } + + Length = (int)strStream.Length + 8; writer.Write(Ident); writer.Write(Length); + if (strStream.Length > 0) + { + var buf1 = new byte[strStream.Length]; + strStream.Position = 0; + strStream.Read(buf1, 0, buf1.Length); + writer.Write(buf1); + } } @@ -867,10 +924,25 @@ namespace CodeWalker.GameFiles public void Write(DataWriter writer) { + var strStream = new MemoryStream(); + var strWriter = new DataWriter(strStream, Endianess.BigEndian); + foreach (var str in Strings) + { + strWriter.Write(str); + } + + Length = (int)strStream.Length + 8; writer.Write(Ident); writer.Write(Length); + if (strStream.Length > 0) + { + var buf1 = new byte[strStream.Length]; + strStream.Position = 0; + strStream.Read(buf1, 0, buf1.Length); + writer.Write(buf1); + } } @@ -886,7 +958,6 @@ namespace CodeWalker.GameFiles public int Length { get; set; } public byte[] Data { get; set; } - //public MetaHash[] Hashes { get; set; } //public byte[] Decr1 { get; set; } //public byte[] Decr2 { get; set; } @@ -899,26 +970,25 @@ namespace CodeWalker.GameFiles { Data = reader.ReadBytes(Length - 8); - //reader.Position = 8; - //List hashes = new List(); - //while (reader.Position < reader.Length) - //{ - // hashes.Add(reader.ReadUInt32()); - //} - //Hashes = hashes.ToArray(); - //Decr1 = GTACrypto.DecryptAES(Data); //Decr2 = GTACrypto.DecryptNG(Data, ) + //TODO: someone plz figure out that encryption } } public void Write(DataWriter writer) { + Length = (Data?.Length??0) + 8; + writer.Write(Ident); writer.Write(Length); + if (Length > 8) + { + writer.Write(Data); + } } @@ -947,10 +1017,15 @@ namespace CodeWalker.GameFiles public void Write(DataWriter writer) { + Length = (Data?.Length ?? 0) + 8; writer.Write(Ident); writer.Write(Length); + if (Length > 8) + { + writer.Write(Data); + } } @@ -983,11 +1058,13 @@ namespace CodeWalker.GameFiles public void Write(DataWriter writer) { + Length = 20; writer.Write(Ident); writer.Write(Length); - - + writer.Write(FileSize); + writer.Write(Checksum); + writer.Write(Unk0); } public override string ToString() diff --git a/CodeWalker.Core/GameFiles/MetaTypes/PsoBuilder.cs b/CodeWalker.Core/GameFiles/MetaTypes/PsoBuilder.cs new file mode 100644 index 0000000..ceeb96f --- /dev/null +++ b/CodeWalker.Core/GameFiles/MetaTypes/PsoBuilder.cs @@ -0,0 +1,504 @@ +using SharpDX; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodeWalker.GameFiles +{ + public class PsoBuilder + { + + + public PsoBuilderPointer RootPointer { get; set; } + + List STRFStrings = new List(); + List STRSStrings = new List(); + + + Dictionary StructureInfos = new Dictionary(); + Dictionary EnumInfos = new Dictionary(); + + + List Blocks = new List(); + int MaxBlockLength = 0x100000; //TODO: figure what this should be! + + public PsoBuilderBlock EnsureBlock(MetaName type) + { + foreach (var block in Blocks) + { + if (block.StructureNameHash == type) + { + if (block.TotalSize < MaxBlockLength) + { + return block; + } + } + } + PsoBuilderBlock b = new PsoBuilderBlock(); + b.StructureNameHash = type; + b.Index = Blocks.Count; + Blocks.Add(b); + return b; + } + + + public PsoBuilderPointer AddItem(MetaName type, T item) where T : struct + { + byte[] data = MetaTypes.ConvertToBytes(item); + return AddItem(type, data); + } + + public PsoBuilderPointer AddItem(MetaName type, byte[] data) + { + PsoBuilderBlock block = EnsureBlock(type); + int brem = data.Length % 16; + if (brem > 0) + { + int newlen = data.Length - brem + 16; + byte[] newdata = new byte[newlen]; + Buffer.BlockCopy(data, 0, newdata, 0, data.Length); + data = newdata; //make sure item size is multiple of 16... so pointers don't need sub offsets! + } + int idx = block.AddItem(data); + PsoBuilderPointer r = new PsoBuilderPointer(); + r.BlockID = block.Index + 1; + r.Offset = (idx * data.Length); + r.Length = data.Length; + return r; + } + + public PsoBuilderPointer AddItemArray(MetaName type, T[] items) where T : struct + { + byte[] data = MetaTypes.ConvertArrayToBytes(items); + return AddItemArray(type, data, items.Length); + } + + public PsoBuilderPointer AddItemArray(MetaName type, byte[] data, int length) + { + PsoBuilderBlock block = EnsureBlock(type); + int datalen = data.Length; + int newlen = datalen; + //int lenrem = newlen % 16; + //if (lenrem != 0) + //{ + // newlen += (16 - lenrem); + //} + byte[] newdata = new byte[newlen]; + Buffer.BlockCopy(data, 0, newdata, 0, datalen); + int offs = block.TotalSize; + int idx = block.AddItem(newdata); + PsoBuilderPointer r = new PsoBuilderPointer(); + r.BlockID = block.Index + 1; + r.Offset = offs; //(idx * data.Length);; + r.Length = length; + return r; + } + + + + public PsoPOINTER AddItemPtr(MetaName type, T item) where T : struct //helper method for AddItem + { + var ptr = AddItem(type, item); + return new PsoPOINTER(ptr.BlockID, ptr.Offset, 0); + } + + public PsoPOINTER AddItemPtr(MetaName type, byte[] data)//helper method for AddItem + { + var ptr = AddItem(type, data); + return new PsoPOINTER(ptr.BlockID, ptr.Offset, 0); + } + + public Array_Structure AddItemArrayPtr(MetaName type, T[] items) where T : struct //helper method for AddItemArray + { + if ((items == null) || (items.Length == 0)) return new Array_Structure(); + var ptr = AddItemArray(type, items); + return new Array_Structure(ptr.Pointer, ptr.Length); + } + + public Array_Structure AddItemArrayPtr(MetaName type, byte[][] data) //helper method for AddItemArray + { + if ((data == null) || (data.Length == 0)) return new Array_Structure(); + + int len = 0; + + for (int i = 0; i < data.Length; i++) + { + len += data[i].Length; + } + + var newdata = new byte[len]; + + int offset = 0; + + for (int i = 0; i < data.Length; i++) + { + Buffer.BlockCopy(data[i], 0, newdata, offset, data[i].Length); + offset += data[i].Length; + } + + var ptr = AddItemArray(type, newdata, data.Length); + return new Array_Structure(ptr.Pointer, ptr.Length); + } + + + + public Array_StructurePointer AddPointerArray(PsoPOINTER[] arr) + { + if ((arr == null) || (arr.Length == 0)) return new Array_StructurePointer(); + var ptr = AddItemArray(MetaName.PsoPOINTER, arr); + Array_StructurePointer sp = new Array_StructurePointer(); + sp.Count1 = (ushort)arr.Length; + sp.Count2 = sp.Count1; + sp.Pointer = ptr.Pointer; + return sp; + } + + + + public PsoBuilderPointer AddString(string str) + { + PsoBuilderBlock block = EnsureBlock((MetaName)1); //PsoSTRING seems to be 1 + byte[] data = Encoding.ASCII.GetBytes(str + (char)0); + int datalen = data.Length; + int newlen = datalen; + //int lenrem = newlen % 16; + //if (lenrem != 0) //pad the data length up to multiple of 16. + //{ + // newlen += (16 - lenrem); + //} + byte[] newdata = new byte[newlen]; + Buffer.BlockCopy(data, 0, newdata, 0, datalen); + int offs = block.TotalSize; + int idx = block.AddItem(newdata); + PsoBuilderPointer r = new PsoBuilderPointer(); + r.BlockID = block.Index + 1; + r.Offset = offs;// (idx * data.Length); + r.Length = datalen; //actual length of string. + return r; + } + + + public Array_Vector3 AddPaddedVector3ArrayPtr(Vector4[] items) + { + if ((items == null) || (items.Length == 0)) return new Array_Vector3(); + var ptr = AddItemArray((MetaName)1, items); //MetaName.VECTOR4 padded to vec4... + return new Array_Vector3(ptr.Pointer, items.Length); + } + public Array_Vector3 AddVector2ArrayPtr(Vector2[] items) + { + if ((items == null) || (items.Length == 0)) return new Array_Vector3(); + var ptr = AddItemArray((MetaName)1, items); //MetaName.VECTOR4 padded to vec4...? + return new Array_Vector3(ptr.Pointer, items.Length); + } + public Array_uint AddHashArrayPtr(MetaHash[] items) + { + if ((items == null) || (items.Length == 0)) return new Array_uint(); + var ptr = AddItemArray((MetaName)6, items); //MetaName.HASH + return new Array_uint(ptr.Pointer, items.Length); + } + public Array_uint AddUIntArrayPtr(uint[] items) + { + if ((items == null) || (items.Length == 0)) return new Array_uint(); + var ptr = AddItemArray((MetaName)6, items); + return new Array_uint(ptr.Pointer, items.Length); + } + public Array_uint AddSIntArrayPtr(int[] items) + { + if ((items == null) || (items.Length == 0)) return new Array_uint(); + var ptr = AddItemArray((MetaName)5, items); + return new Array_uint(ptr.Pointer, items.Length); + } + public Array_ushort AddUShortArrayPtr(ushort[] items) + { + if ((items == null) || (items.Length == 0)) return new Array_ushort(); + var ptr = AddItemArray((MetaName)4, items); + return new Array_ushort(ptr.Pointer, items.Length); + } + public Array_byte AddByteArrayPtr(byte[] items) + { + if ((items == null) || (items.Length == 0)) return new Array_byte(); + var ptr = AddItemArray((MetaName)2, items); + return new Array_byte(ptr.Pointer, items.Length); + } + public Array_float AddFloatArrayPtr(float[] items) + { + if ((items == null) || (items.Length == 0)) return new Array_float(); + var ptr = AddItemArray((MetaName)7, items); //MetaName.PsoFLOAT ? comes up as MetaName.POINTER due to RSC meta values + return new Array_float(ptr.Pointer, items.Length); + } + + + + + public void AddStringToSTRF(string str) + { + STRFStrings.Add(str); + } + public void AddStringToSTRS(string str) + { + STRSStrings.Add(str); + } + + + + + public void AddStructureInfo(MetaName name) + { + if (!StructureInfos.ContainsKey(name)) + { + PsoStructureInfo si = PsoTypes.GetStructureInfo(name); + if (si != null) + { + StructureInfos[name] = si; + } + } + } + public void AddEnumInfo(MetaName name) + { + if (!EnumInfos.ContainsKey(name)) + { + PsoEnumInfo ei = PsoTypes.GetEnumInfo(name); + if (ei != null) + { + EnumInfos[name] = ei; + } + } + } + + + public PsoStructureInfo AddMapNodeStructureInfo(MetaName valType) + { + PsoStructureInfo inf = null; + + if (valType == 0) + { + inf = PsoTypes.GetStructureInfo(MetaName.ARRAYINFO); //default ARRAYINFO with pointer + if (!StructureInfos.ContainsKey(inf.IndexInfo.NameHash)) + { + StructureInfos[inf.IndexInfo.NameHash] = inf; + } + return inf; + } + + var structInfo = PsoTypes.GetStructureInfo(valType); + if (structInfo == null) + { }//error? + + MetaName xName = MetaName.ARRAYINFO + 1; //257 + bool nameOk = !StructureInfos.ContainsKey(xName); + while (!nameOk) + { + var exInfo = StructureInfos[xName]; + var exInfoItem = exInfo.FindEntry(MetaName.Item); + if (((MetaName)(exInfoItem?.ReferenceKey ?? 0) == valType)) + { + return exInfo; //this one already exists.. use it! + } + xName++; + nameOk = !StructureInfos.ContainsKey(xName); + } + + + + inf = new PsoStructureInfo(xName, 0, 2, 8 + structInfo.StructureLength, + new PsoStructureEntryInfo(MetaName.Key, PsoDataType.String, 0, 7, 0), + new PsoStructureEntryInfo(MetaName.Item, PsoDataType.Structure, 8, 0, valType) + ); + + if (!StructureInfos.ContainsKey(xName)) + { + StructureInfos[xName] = inf; + } + + return inf; + + + //switch (valType) + //{ + // case (MetaName)331140115: return PsoTypes.GetStructureInfo((MetaName)257); + // case (MetaName)2046450505: return PsoTypes.GetStructureInfo((MetaName)258); + // case (MetaName)3219912345: return PsoTypes.GetStructureInfo((MetaName)259); + // case (MetaName)0: return PsoTypes.GetStructureInfo(MetaName.ARRAYINFO); + // default: + // return PsoTypes.GetStructureInfo(MetaName.ARRAYINFO);//error? + //} + //case (MetaName)257: + // return new PsoStructureInfo((MetaName)257, 0, 2, 32, + // new PsoStructureEntryInfo(MetaName.Key, PsoDataType.String, 0, 7, 0), + // new PsoStructureEntryInfo(MetaName.Item, PsoDataType.Structure, 8, 0, (MetaName)331140115) + // ); + //case (MetaName)258: + // return new PsoStructureInfo((MetaName)258, 0, 2, 24, + // new PsoStructureEntryInfo(MetaName.Key, PsoDataType.String, 0, 7, 0), + // new PsoStructureEntryInfo(MetaName.Item, PsoDataType.Structure, 8, 0, (MetaName)2046450505) + // ); + //case (MetaName)259: + // return new PsoStructureInfo((MetaName)259, 0, 2, 32, + // new PsoStructureEntryInfo(MetaName.Key, PsoDataType.String, 0, 7, 0), + // new PsoStructureEntryInfo(MetaName.Item, PsoDataType.Structure, 8, 0, (MetaName)3219912345) + // ); + //case (MetaName)3219912345: + // return new PsoStructureInfo((MetaName)3219912345, 0, 0, 24, + // new PsoStructureEntryInfo(MetaName.ARRAYINFO, PsoDataType.Structure, 0, 0, (MetaName)2356519750), + // new PsoStructureEntryInfo((MetaName)4147768898, PsoDataType.Array, 8, 0, 0) + // ); + } + + + + + public byte[] GetData() + { + int totlen = 16; + for (int i = 0; i < Blocks.Count; i++) + { + totlen += Blocks[i].TotalSize; + } + byte[] data = new byte[totlen]; + int offset = 16; //reserved space for headers + for (int i = 0; i < Blocks.Count; i++) + { + var block = Blocks[i]; + for (int j = 0; j < block.Items.Count; j++) + { + var bdata = block.Items[j]; + Buffer.BlockCopy(bdata, 0, data, offset, bdata.Length); + offset += bdata.Length; + } + } + if (offset != data.Length) + { } + return data; + } + + + + + + + public PsoFile GetPso() + { + PsoFile pso = new PsoFile(); + pso.SchemaSection = new PsoSchemaSection(); + + var schEntries = new List(); + foreach (var structInfo in StructureInfos.Values) + { + schEntries.Add(structInfo); + } + foreach (var enumInfo in EnumInfos.Values) + { + schEntries.Add(enumInfo); + } + pso.SchemaSection.Entries = schEntries.ToArray(); + pso.SchemaSection.EntriesIdx = new PsoElementIndexInfo[schEntries.Count]; + for (int i = 0; i < schEntries.Count; i++) + { + pso.SchemaSection.EntriesIdx[i] = new PsoElementIndexInfo(); + pso.SchemaSection.EntriesIdx[i].NameHash = schEntries[i].IndexInfo.NameHash; + } + + if (STRFStrings.Count > 0) + { + pso.STRFSection = new PsoSTRFSection(); + pso.STRFSection.Strings = STRFStrings.ToArray(); + } + if (STRSStrings.Count > 0) + { + pso.STRSSection = new PsoSTRSSection(); + pso.STRSSection.Strings = STRSStrings.ToArray(); + } + + + pso.DataSection = new PsoDataSection(); + pso.DataSection.Data = GetData(); + + pso.DataMapSection = new PsoDataMapSection(); + pso.DataMapSection.Entries = new PsoDataMappingEntry[Blocks.Count]; + pso.DataMapSection.RootId = RootPointer.BlockID; + var offset = 16; + for (int i = 0; i < Blocks.Count; i++) + { + var b = Blocks[i]; + var e = new PsoDataMappingEntry(); + e.NameHash = b.StructureNameHash; + e.Length = b.TotalSize; + e.Offset = offset; + offset += b.TotalSize; + pso.DataMapSection.Entries[i] = e; + } + + + + return pso; + } + + } + + + + public class PsoBuilderBlock + { + public MetaName StructureNameHash { get; set; } + public List Items { get; set; } = new List(); + public int TotalSize { get; set; } = 0; + public int Index { get; set; } = 0; + + public int AddItem(byte[] item) + { + int idx = Items.Count; + Items.Add(item); + TotalSize += item.Length; + return idx; + } + + public uint BasePointer + { + get + { + return (((uint)Index + 1) & 0xFFF); + } + } + + + //public MetaDataBlock GetMetaDataBlock() + //{ + // if (TotalSize <= 0) return null; + // byte[] data = new byte[TotalSize]; + // int offset = 0; + // for (int j = 0; j < Items.Count; j++) + // { + // var bdata = Items[j]; + // Buffer.BlockCopy(bdata, 0, data, offset, bdata.Length); + // offset += bdata.Length; + // } + // MetaDataBlock db = new MetaDataBlock(); + // db.StructureNameHash = StructureNameHash; + // db.DataLength = TotalSize; + // db.Data = data; + // return db; + //} + + + } + + public struct PsoBuilderPointer + { + public int BlockID { get; set; } //1-based id + public int Offset { get; set; } //byte offset + public int Length { get; set; } //for temp use... + public uint Pointer + { + get + { + uint bidx = (((uint)BlockID) & 0xFFF); + uint offs = (((uint)Offset) & 0xFFFFF) << 12; + return bidx + offs; + } + } + } + + +} diff --git a/CodeWalker.Core/GameFiles/MetaTypes/PsoTypes.cs b/CodeWalker.Core/GameFiles/MetaTypes/PsoTypes.cs index 83a5c58..8f148ff 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/PsoTypes.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/PsoTypes.cs @@ -647,11 +647,11 @@ namespace CodeWalker.GameFiles new PsoStructureEntryInfo(MetaName.Probabilities, PsoDataType.Array, 8, 0, 0) ); case (MetaName)2575850962: - return new PsoStructureInfo((MetaName)2575850962, 0, 0, 40, + return new PsoStructureInfo((MetaName)2575850962, 0, 0, 44 /*40*/, new PsoStructureEntryInfo(MetaName.ARRAYINFO, PsoDataType.UByte, 0, 0, 0), - new PsoStructureEntryInfo(MetaName.indices, PsoDataType.Array, 8, 4, (MetaName)262144), + new PsoStructureEntryInfo(MetaName.indices, PsoDataType.Array, 8, 4, (MetaName)393216 /*262144*/), new PsoStructureEntryInfo(MetaName.ARRAYINFO, PsoDataType.Bool, 0, 0, 0), - new PsoStructureEntryInfo(MetaName.liveries, PsoDataType.Array, 12, 4, (MetaName)1638402) + new PsoStructureEntryInfo(MetaName.liveries, PsoDataType.Array, 14 /*12*/, 4, (MetaName)1966082 /*1638402*/) ); case (MetaName)938618322: return new PsoStructureInfo((MetaName)938618322, 0, 0, 16, @@ -16104,10 +16104,13 @@ namespace CodeWalker.GameFiles var blocki = (int)ptr.PointerDataId;// (ptr.Pointer & 0xFFF) - 1; var offset = (int)ptr.PointerDataOffset;// (ptr.Pointer >> 12) & 0xFFFFF; - var block = pso.GetBlock(blocki); + var block = pso.GetBlock(blocki); //nameHash = 1 if (block == null) { return null; } + //if (block.NameHash != (MetaName)1) + //{ } + var length = ptr.Count1; var lastbyte = offset + length; if (lastbyte >= block.Length) @@ -16133,10 +16136,13 @@ namespace CodeWalker.GameFiles var blocki = (int)ptr.PointerDataId;// (ptr.Pointer & 0xFFF) - 1; var offset = (int)ptr.PointerDataOffset;// (ptr.Pointer >> 12) & 0xFFFFF; - var block = pso.GetBlock(blocki); + var block = pso.GetBlock(blocki); //nameHash = 1 if (block == null) { return null; } + //if (block.NameHash != (MetaName)1) + //{ } + //var length = ptr.Count1; //var lastbyte = offset + length; //if (lastbyte >= block.Length) @@ -16217,6 +16223,13 @@ namespace CodeWalker.GameFiles public uint ItemOffset { get { return ((Pointer>>12) & 0xFFFFF); } } //byte offset + public PsoPOINTER(int blockID, int itemOffset, uint extra) + { + Pointer = (((uint)itemOffset << 12) & 0xFFFFF000) + ((uint)blockID & 0xFFF); + Unk2 = extra; + } + + public override string ToString() { return BlockID.ToString() + ", " + ItemOffset.ToString() + ", " + Unk2.ToString(); diff --git a/CodeWalker.Core/GameFiles/MetaTypes/XmlPso.cs b/CodeWalker.Core/GameFiles/MetaTypes/XmlPso.cs new file mode 100644 index 0000000..6584027 --- /dev/null +++ b/CodeWalker.Core/GameFiles/MetaTypes/XmlPso.cs @@ -0,0 +1,1222 @@ +using SharpDX; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Xml; + +namespace CodeWalker.GameFiles +{ + public class XmlPso + { + + public static PsoFile GetPso(XmlDocument doc) + { + PsoBuilder pb = new PsoBuilder(); + + Traverse(doc.DocumentElement, pb, 0, true); + + var pso = pb.GetPso(); + + return pso; + } + + + + private static byte[] Traverse(XmlNode node, PsoBuilder pb, MetaName type = 0, bool isRoot = false) + { + if (type == 0) + { + type = (MetaName)(uint)GetHash(node.Name); + } + + var infos = PsoTypes.GetStructureInfo(type); + if (infos != null) + { + byte[] data = new byte[infos.StructureLength]; + var arrayResults = new PsoArrayResults(); + + arrayResults.Structures = new Dictionary(); + arrayResults.StructurePointers = new Dictionary(); + arrayResults.UInts = new Dictionary(); + arrayResults.UShorts = new Dictionary(); + arrayResults.UBytes = new Dictionary(); + arrayResults.Floats = new Dictionary(); + arrayResults.Float_XYZs = new Dictionary(); + arrayResults.Hashes = new Dictionary(); + + Array.Clear(data, 0, infos.StructureLength); //shouldn't really be necessary... + + PsoStructureEntryInfo arrEntry = null; + + + //if (isRoot) + //{ + // pb.EnsureBlock(type); + //} + + for (int i = 0; i < infos.Entries.Length; i++) + { + var entry = infos.Entries[i]; + var cnode = GetEntryNode(node.ChildNodes, entry.EntryNameHash); + + if (entry.EntryNameHash == MetaName.ARRAYINFO) + { + arrEntry = entry; + continue; + } + + if (cnode == null) + { + //warning: node not found in XML for this entry! + continue; + } + + switch (entry.Type) + { + case PsoDataType.Array: + { + TraverseArray(cnode, pb, entry, arrEntry, arrayResults, data, infos); + break; + } + case PsoDataType.Structure: + { + var struc = Traverse(cnode, pb, (MetaName)entry.ReferenceKey); + if (struc != null) + { + switch (entry.Unk_5h) + { + default: + //ErrorXml(sb, cind, ename + ": Unexpected Structure subtype: " + entry.Unk_5h.ToString()); + break; + case 0: //default structure + + Buffer.BlockCopy(struc, 0, data, entry.DataOffset, struc.Length); + + break; + case 3: //structure pointer... + case 4: //also pointer? what's the difference? + + var bptr = pb.AddItem((MetaName)entry.ReferenceKey, struc); + var ptr = new PsoPOINTER(bptr.BlockID, bptr.Offset, 0); + ptr.SwapEnd(); + var ptrb = MetaTypes.ConvertToBytes(ptr); + + Buffer.BlockCopy(ptrb, 0, data, entry.DataOffset, ptrb.Length); + + break; + } + } + break; + } + case PsoDataType.Map: + { + TraverseMap(cnode, pb, entry, infos, data, arrayResults); + + break; + } + + case PsoDataType.Bool: + { + byte val = (cnode.Attributes["value"].Value == "false") ? (byte)0 : (byte)1; + data[entry.DataOffset] = val; + break; + } + case PsoDataType.SByte: + { + var val = Convert.ToSByte(cnode.Attributes["value"].Value); + data[entry.DataOffset] = (byte)val; + break; + } + case PsoDataType.UByte: + { + var val = Convert.ToByte(cnode.Attributes["value"].Value); + data[entry.DataOffset] = val; + break; + } + case PsoDataType.SShort: + { + var val = Convert.ToInt16(cnode.Attributes["value"].Value); + Write(val, data, entry.DataOffset); + break; + } + case PsoDataType.UShort: + { + var val = Convert.ToUInt16(cnode.Attributes["value"].Value); + Write(val, data, entry.DataOffset); + break; + } + case PsoDataType.SInt: + { + var val = Convert.ToInt32(cnode.Attributes["value"].Value); + Write(val, data, entry.DataOffset); + 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 sval = Convert.ToInt32(cnode.Attributes["value"].Value); + Write(sval, data, entry.DataOffset); + break; + case 1: //unsigned int + var uval = Convert.ToUInt32(cnode.Attributes["value"].Value); + Write(uval, data, entry.DataOffset); + break; + } + + break; + } + case PsoDataType.Float: + { + float val = FloatUtil.Parse(cnode.Attributes["value"].Value); + Write(val, data, entry.DataOffset); + break; + } + case PsoDataType.Float2: + { + float x = FloatUtil.Parse(cnode.Attributes["x"].Value); + float y = FloatUtil.Parse(cnode.Attributes["y"].Value); + Write(x, data, entry.DataOffset); + Write(y, data, entry.DataOffset + sizeof(float)); + break; + } + case PsoDataType.Float3: + { + float x = FloatUtil.Parse(cnode.Attributes["x"].Value); + float y = FloatUtil.Parse(cnode.Attributes["y"].Value); + float z = FloatUtil.Parse(cnode.Attributes["z"].Value); + Write(x, data, entry.DataOffset); + Write(y, data, entry.DataOffset + sizeof(float)); + Write(z, data, entry.DataOffset + sizeof(float) * 2); + break; + } + case PsoDataType.Float4: + { + float x = FloatUtil.Parse(cnode.Attributes["x"].Value); + float y = FloatUtil.Parse(cnode.Attributes["y"].Value); + float z = FloatUtil.Parse(cnode.Attributes["z"].Value); + float w = FloatUtil.Parse(cnode.Attributes["w"].Value); + Write(x, data, entry.DataOffset); + Write(y, data, entry.DataOffset + sizeof(float)); + Write(z, data, entry.DataOffset + sizeof(float) * 2); + Write(w, data, entry.DataOffset + sizeof(float) * 3); + break; + } + case PsoDataType.String: + { + TraverseString(cnode, pb, entry, data); + break; + } + case PsoDataType.Enum: + { + pb.AddEnumInfo((MetaName)entry.ReferenceKey); + switch (entry.Unk_5h) + { + default: + //ErrorXml(sb, cind, ename + ": Unexpected Enum subtype: " + entry.Unk_5h.ToString()); + break; + case 0: //int enum + int ival = GetEnumInt((MetaName)entry.ReferenceKey, cnode.InnerText, entry.Type); + Write(ival, data, entry.DataOffset); + break; + case 1: //short enum? + short sval = (short)GetEnumInt((MetaName)entry.ReferenceKey, cnode.InnerText, entry.Type); + Write(sval, data, entry.DataOffset); + break; + case 2: //byte enum + byte bval = (byte)GetEnumInt((MetaName)entry.ReferenceKey, cnode.InnerText, entry.Type); + data[entry.DataOffset] = bval; + break; + } + break; + } + case PsoDataType.Flags: + { + //uint fCount = (entry.ReferenceKey >> 16) & 0x0000FFFF; + uint fEntry = (entry.ReferenceKey & 0xFFF); + var fEnt = (fEntry != 0xFFF) ? infos.GetEntry((int)fEntry) : null; + PsoEnumInfo flagsInfo = null; + MetaName fEnum = (MetaName)(fEnt?.ReferenceKey ?? 0); + if ((fEnt != null) && (fEnt.EntryNameHash == MetaName.ARRAYINFO)) + { + flagsInfo = PsoTypes.GetEnumInfo(fEnum); + } + if (flagsInfo == null) + { + if (fEntry != 0xFFF) + { } + //flagsInfo = PsoTypes.GetEnumInfo(entry.EntryNameHash); + } + if (flagsInfo != null) + { + pb.AddEnumInfo(flagsInfo.IndexInfo.NameHash); + } + else + { }//error? + + switch (entry.Unk_5h) + { + default: + //ErrorXml(sb, cind, ename + ": Unexpected Flags subtype: " + entry.Unk_5h.ToString()); + break; + case 0: //int flags + int ival = GetEnumInt(fEnum, cnode.InnerText, entry.Type); + Write(ival, data, entry.DataOffset); + break; + case 1: //short flags + short sval = (short)GetEnumInt(fEnum, cnode.InnerText, entry.Type); + Write(sval, data, entry.DataOffset); + break; + case 2: //byte flags + byte bval = (byte)GetEnumInt(fEnum, cnode.InnerText, entry.Type); + data[entry.DataOffset] = bval; + break; + } + break; + } + case PsoDataType.Float3a: + { + float x = FloatUtil.Parse(cnode.Attributes["x"].Value); + float y = FloatUtil.Parse(cnode.Attributes["y"].Value); + float z = FloatUtil.Parse(cnode.Attributes["z"].Value); + Write(x, data, entry.DataOffset); + Write(y, data, entry.DataOffset + sizeof(float)); + Write(z, data, entry.DataOffset + sizeof(float) * 2); + break; + } + case PsoDataType.Float4a: + { + float x = FloatUtil.Parse(cnode.Attributes["x"].Value); + float y = FloatUtil.Parse(cnode.Attributes["y"].Value); + float z = FloatUtil.Parse(cnode.Attributes["z"].Value); + //float w = FloatUtil.Parse(cnode.Attributes["w"].Value); + Write(x, data, entry.DataOffset); + Write(y, data, entry.DataOffset + sizeof(float)); + Write(z, data, entry.DataOffset + sizeof(float) * 2); + //Write(w, data, entry.DataOffset + sizeof(float) * 3); + break; + } + case PsoDataType.HFloat: + { + var val = Convert.ToInt16(cnode.Attributes["value"].Value); + Write(val, data, entry.DataOffset); + break; + } + case PsoDataType.Long: + { + var uval = Convert.ToUInt64(cnode.Attributes["value"].Value); + Write(uval, data, entry.DataOffset); + break; + } + + + default: + break; + + } + + + } + + + arrayResults.WriteArrays(data); + + pb.AddStructureInfo(infos.IndexInfo.NameHash); + + if (isRoot) + { + pb.RootPointer = pb.AddItem(type, data); + } + + return data; + + } + + return null; + } + + private static void TraverseMap(XmlNode node, PsoBuilder pb, PsoStructureEntryInfo entry, PsoStructureInfo infos, byte[] data, PsoArrayResults arrayResults) + { + var mapidx1 = entry.ReferenceKey & 0x0000FFFF; + var mapidx2 = (entry.ReferenceKey >> 16) & 0x0000FFFF; + var mapreftype1 = infos.Entries[mapidx2]; + var mapreftype2 = infos.Entries[mapidx1]; + + if (mapreftype2.ReferenceKey != 0) + { } + + var xStruct = pb.AddMapNodeStructureInfo((MetaName)mapreftype2.ReferenceKey); + var xName = xStruct.IndexInfo.NameHash; + var kEntry = xStruct?.FindEntry(MetaName.Key); + var iEntry = xStruct?.FindEntry(MetaName.Item); + + if (kEntry.Type != PsoDataType.String) + { } + + + + List nodesData = new List(); + + foreach (XmlNode cnode in node.ChildNodes) + { + var kattr = cnode.Attributes["key"].Value; + var tattr = cnode.Attributes["type"].Value;//CW invention for convenience..! + var khash = (MetaName)(uint)GetHash(kattr); + var thash = (MetaName)(uint)GetHash(tattr); + + byte[] strucBytes = Traverse(cnode, pb, thash); + byte[] nodeBytes = new byte[xStruct.StructureLength]; + + TraverseStringRaw(kattr, pb, kEntry, nodeBytes); //write the key + + if (xName != MetaName.ARRAYINFO)// (mapreftype2.ReferenceKey != 0) + { + //value struct embedded in ARRAYINFO node + Buffer.BlockCopy(strucBytes, 0, nodeBytes, iEntry.DataOffset, strucBytes.Length); + } + else + { + //normal ARRAYINFO with pointer value + var itemptr = pb.AddItemPtr(thash, strucBytes); + itemptr.SwapEnd(); //big schmigg + var ptrbytes = MetaTypes.ConvertToBytes(itemptr); + Buffer.BlockCopy(ptrbytes, 0, nodeBytes, iEntry.DataOffset, ptrbytes.Length); + } + + nodesData.Add(nodeBytes); + + } + + + + Write(0x1000000, data, entry.DataOffset); + Write(0, data, entry.DataOffset + 4); + + arrayResults.Structures[entry.DataOffset + 8] = pb.AddItemArrayPtr(xName, nodesData.ToArray()); //pb.AddPointerArray(nodeptrs); + } + + private static void TraverseArray(XmlNode node, PsoBuilder pb, PsoStructureEntryInfo entry, PsoStructureEntryInfo arrEntry, PsoArrayResults results, byte[] data, PsoStructureInfo structInfo) + { + int offset = entry.DataOffset; + uint aCount = (entry.ReferenceKey >> 16) & 0x0000FFFF; + uint aPtr = (entry.ReferenceKey) & 0x0000FFFF; + byte[] adata = null; + + //how do we know when it's an "embedded" array? + 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 + //var arrStruc = MetaTypes.ConvertData(data, eoffset); + 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... + { + //var arrStruc4 = MetaTypes.ConvertData(data, eoffset); + embedded = false; + } + else + { + } + break; + case 129: //also raw inline array? in junctions.pso (AutoJunctionAdjustments) + break; + } + + + + + switch (arrEntry.Type) + { + case PsoDataType.Structure: + { + if (embedded) + { + if (arrEntry.ReferenceKey != 0) + { + var datas = TraverseArrayStructureRaw(node, pb, (MetaName)arrEntry.ReferenceKey); + int aoffset = offset; + for (int i = 0; i < datas.Length; i++) + { + Buffer.BlockCopy(datas[i], 0, data, aoffset, datas[i].Length); + aoffset += datas[i].Length; + } + } + else + { + var ptrs = TraverseArrayStructurePointerRaw(node, pb); + adata = MetaTypes.ConvertArrayToBytes(ptrs); + } + } + else + { + if (arrEntry.ReferenceKey != 0) + { + results.Structures[offset] = TraverseArrayStructure(node, pb, (MetaName)arrEntry.ReferenceKey); + } + else + { + results.StructurePointers[offset] = TraverseArrayStructurePointer(node, pb); + } + } + break; + } + + case PsoDataType.Float2: + { + var arr = TraverseVector2ArrayRaw(node); + if (embedded) + { + adata = MetaTypes.ConvertArrayToBytes(arr); + aCount *= 8; + } + else + { + results.Float_XYZs[offset] = pb.AddVector2ArrayPtr(arr); + } + break; + } + case PsoDataType.Float3: + { + var arr = TraverseVector3ArrayRaw(node); + if (embedded) + { + adata = MetaTypes.ConvertArrayToBytes(arr); + aCount *= 16; + } + else + { + results.Float_XYZs[offset] = pb.AddPaddedVector3ArrayPtr(arr); + } + break; + } + case PsoDataType.UByte: + { + var arr = TraverseUByteArrayRaw(node); + if (embedded) + { + adata = arr; + } + else + { + results.UBytes[offset] = pb.AddByteArrayPtr(arr); + } + break; + } + case PsoDataType.Bool: + { + var arr = TraverseUByteArrayRaw(node); + if (embedded) + { + adata = arr; + } + else + { + results.UBytes[offset] = pb.AddByteArrayPtr(arr); + } + break; + } + case PsoDataType.UInt: + { + var arr = TraverseUIntArrayRaw(node); + if (embedded) + { + adata = MetaTypes.ConvertArrayToBytes(arr); + aCount *= 4; + } + else + { + results.UInts[offset] = pb.AddUIntArrayPtr(arr); + } + break; + } + case PsoDataType.SInt: + { + var arr = TraverseSIntArrayRaw(node); + if (embedded) + { + adata = MetaTypes.ConvertArrayToBytes(arr); + aCount *= 4; + } + else + { + results.UInts[offset] = pb.AddSIntArrayPtr(arr); + } + break; + } + case PsoDataType.Float: + { + var arr = TraverseFloatArrayRaw(node); + if (embedded) + { + adata = MetaTypes.ConvertArrayToBytes(arr); + aCount *= 4; + } + else + { + results.Floats[offset] = pb.AddFloatArrayPtr(arr); + } + break; + } + case PsoDataType.UShort: + { + var arr = TraverseUShortArrayRaw(node); + if (embedded) + { + adata = MetaTypes.ConvertArrayToBytes(arr); + aCount *= 2; + } + else + { + results.UShorts[offset] = pb.AddUShortArrayPtr(arr); + } + 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... + var hashes = TraverseHashArrayRaw(node); + if (embedded) + { + adata = MetaTypes.ConvertArrayToBytes(hashes); + aCount *= 4; + } + else + { + results.Hashes[offset] = pb.AddHashArrayPtr(hashes); + } + break; + } + + + break; + } + + + case PsoDataType.Enum: + { + var hashes = TraverseHashArrayRaw(node); + + if (arrEntry.ReferenceKey != 0) + { + var _infos = PsoTypes.GetEnumInfo((MetaName)arrEntry.ReferenceKey); + pb.AddEnumInfo(_infos.IndexInfo.NameHash); + + var values = new uint[hashes.Length]; + for (int i = 0; i < hashes.Length; i++) + { + var enumname = (MetaName)MetaTypes.SwapBytes(hashes[i]);//yeah swap it back to little endian..! + var enuminf = _infos.FindEntryByName(enumname); + if (enuminf != null) + { + values[i] = MetaTypes.SwapBytes((uint)enuminf.EntryKey); + } + else + { } //error? + } + + if (embedded) + { } //TODO? + else + { + results.UInts[offset] = pb.AddUIntArrayPtr(values); + } + + } + else + { } + + + break; + } + + + case PsoDataType.Array: + { + //array array... + var rk0 = (entry.ReferenceKey >> 16) & 0x0000FFFF; + var rk1 = arrEntry.ReferenceKey & 0x0000FFFF; + if (rk0 > 0) //should be count of items + { + var subarrEntry = structInfo.GetEntry((int)rk1); + var subarrType = (MetaName)subarrEntry.ReferenceKey; + + var origOffset = arrEntry.DataOffset; + arrEntry.DataOffset = entry.DataOffset;//slight hack for traversing array array + foreach (XmlNode cnode in node.ChildNodes) + { + TraverseArray(cnode, pb, arrEntry, subarrEntry, results, data, structInfo); + + arrEntry.DataOffset += 16;//ptr size... todo: what if not pointer array? + } + arrEntry.DataOffset = origOffset; + + + } + + + break; + } + + + + + default: + break; + } + + if (embedded) + { + if (adata?.Length > 0) + { + if (adata.Length > aCount) + { }//bad! old data won't fit in new slot... + + Buffer.BlockCopy(adata, 0, data, offset, adata.Length); + } + else + { } + } + } + + private static void TraverseString(XmlNode node, PsoBuilder pb, PsoStructureEntryInfo entry, byte[] data) + { + TraverseStringRaw(node.InnerText, pb, entry, data); + } + private static void TraverseStringRaw(string str, PsoBuilder pb, PsoStructureEntryInfo entry, byte[] data) + { + switch (entry.Unk_5h) + { + default: + break; + case 0: + var str0len = (int)((entry.ReferenceKey >> 16) & 0xFFFF); + if (!string.IsNullOrEmpty(str)) + { + byte[] strdata = Encoding.ASCII.GetBytes(str); + Buffer.BlockCopy(strdata, 0, data, entry.DataOffset, strdata.Length); + if (strdata.Length > str0len) + { } + } + break; + case 1: + case 2: + if (!string.IsNullOrEmpty(str)) + { + var bptr = pb.AddString(str); + var ptr = new PsoPOINTER(bptr.BlockID, bptr.Offset, 0); + ptr.SwapEnd(); + var val = MetaTypes.ConvertToBytes(ptr); + Buffer.BlockCopy(val, 0, data, entry.DataOffset, val.Length); + } + break; + case 3: + if (!string.IsNullOrEmpty(str)) + { + var bptr = pb.AddString(str); + var ptr = new CharPointer(bptr.Pointer, str.Length); + ptr.SwapEnd(); + var val = MetaTypes.ConvertToBytes(ptr); + Buffer.BlockCopy(val, 0, data, entry.DataOffset, val.Length); + } + break; + case 7://hash only? + case 8://hash with STRF entry? + + var hashVal = string.IsNullOrEmpty(str) ? 0 : GetHash(str); + Write(hashVal, data, entry.DataOffset); + + if (entry.Unk_5h == 8) + { + pb.AddStringToSTRF(str); + } + break; + } + } + + + private static byte[][] TraverseArrayStructureRaw(XmlNode node, PsoBuilder pb, MetaName type) + { + var strucs = new List(); + + foreach (XmlNode cnode in node.ChildNodes) + { + var struc = Traverse(cnode, pb, type); + + if (struc != null) + { + strucs.Add(struc); + } + } + + return strucs.ToArray(); + } + private static Array_Structure TraverseArrayStructure(XmlNode node, PsoBuilder pb, MetaName type) + { + var bytes = TraverseArrayStructureRaw(node, pb, type); + + return pb.AddItemArrayPtr(type, bytes); + } + + private static PsoPOINTER[] TraverseArrayStructurePointerRaw(XmlNode node, PsoBuilder pb) + { + var ptrs = new List(); + + foreach (XmlNode cnode in node.ChildNodes) + { + var type = (MetaName)(uint)GetHash(cnode.Attributes["type"]?.Value ?? ""); + if (type != 0) + { + var struc = Traverse(cnode, pb, type); + + if (struc != null) + { + var ptr = pb.AddItemPtr(type, struc); + ptr.SwapEnd(); //big schmigg + ptrs.Add(ptr); + } + else + { } //error? + } + else + { + ptrs.Add(new PsoPOINTER());//null value? + } + } + + return ptrs.ToArray(); + } + private static Array_StructurePointer TraverseArrayStructurePointer(XmlNode node, PsoBuilder pb) + { + var ptrs = TraverseArrayStructurePointerRaw(node, pb); + + return pb.AddPointerArray(ptrs); + + } + + private static int[] TraverseSIntArrayRaw(XmlNode node) + { + var data = new List(); + + if (node.InnerText != "") + { + var split = Regex.Split(node.InnerText, @"[\s\r\n\t]"); + + for (int i = 0; i < split.Length; i++) + { + if (!string.IsNullOrEmpty(split[i])) + { + var val = Convert.ToInt32(split[i]); + data.Add(MetaTypes.SwapBytes(val)); + } + + } + } + + return data.ToArray(); + } + private static uint[] TraverseUIntArrayRaw(XmlNode node) + { + var data = new List(); + + if (node.InnerText != "") + { + var split = Regex.Split(node.InnerText, @"[\s\r\n\t]"); + + for (int i = 0; i < split.Length; i++) + { + if (!string.IsNullOrEmpty(split[i])) + { + var val = Convert.ToUInt32(split[i]); + data.Add(MetaTypes.SwapBytes(val)); + } + + } + } + + return data.ToArray(); + } + private static byte[] TraverseUByteArrayRaw(XmlNode node) + { + var data = new List(); + + if (node.InnerText != "") + { + var split = Regex.Split(node.InnerText, @"[\s\r\n\t]"); + + for (int i = 0; i < split.Length; i++) + { + if (!string.IsNullOrEmpty(split[i])) + { + var val = Convert.ToByte(split[i]); + data.Add(val); + } + } + } + + return data.ToArray(); + } + private static ushort[] TraverseUShortArrayRaw(XmlNode node) + { + var data = new List(); + + if (node.InnerText != "") + { + var split = Regex.Split(node.InnerText, @"[\s\r\n\t]"); + + for (int i = 0; i < split.Length; i++) + { + if (!string.IsNullOrEmpty(split[i])) + { + var val = Convert.ToUInt16(split[i]); + data.Add(MetaTypes.SwapBytes(val)); + } + + } + } + + return data.ToArray(); + } + private static float[] TraverseFloatArrayRaw(XmlNode node) + { + var data = new List(); + + if (node.InnerText != "") + { + var split = Regex.Split(node.InnerText, @"[\s\r\n\t]"); + + for (int i = 0; i < split.Length; i++) + { + if (!string.IsNullOrEmpty(split[i])) + { + var val = FloatUtil.Parse(split[i]); + data.Add(MetaTypes.SwapBytes(val)); + } + + } + } + + return data.ToArray(); + } + private static Vector4[] TraverseVector3ArrayRaw(XmlNode node) + { + var items = new List(); + + + var split = node.InnerText.Split('\n');// Regex.Split(node.InnerText, @"[\s\r\n\t]"); + + + float x = 0f; + float y = 0f; + float z = 0f; + float w = 0f; + + for (int i = 0; i < split.Length; i++) + { + var s = split[i]?.Trim(); + if (string.IsNullOrEmpty(s)) continue; + var split2 = Regex.Split(s, @"[\s\t]"); + int c = 0; + x = 0f; y = 0f; z = 0f; + for (int n = 0; n < split2.Length; n++) + { + var ts = split2[n]?.Trim(); + if (ts.EndsWith(",")) ts = ts.Substring(0, ts.Length - 1); + if (string.IsNullOrEmpty(ts)) continue; + var f = FloatUtil.Parse(ts); + switch (c) + { + case 0: x = f; break; + case 1: y = f; break; + case 2: z = f; break; + } + c++; + } + if (c >= 3) + { + var val = new Vector4(x, y, z, w); + items.Add(MetaTypes.SwapBytes(val)); //big schmig + } + } + + + return items.ToArray(); + } + private static Vector2[] TraverseVector2ArrayRaw(XmlNode node) + { + var items = new List(); + + + var split = node.InnerText.Split('\n');// Regex.Split(node.InnerText, @"[\s\r\n\t]"); + + + float x = 0f; + float y = 0f; + + for (int i = 0; i < split.Length; i++) + { + var s = split[i]?.Trim(); + if (string.IsNullOrEmpty(s)) continue; + var split2 = Regex.Split(s, @"[\s\t]"); + int c = 0; + x = 0f; y = 0f; + for (int n = 0; n < split2.Length; n++) + { + var ts = split2[n]?.Trim(); + if (ts.EndsWith(",")) ts = ts.Substring(0, ts.Length - 1); + if (string.IsNullOrEmpty(ts)) continue; + var f = FloatUtil.Parse(ts); + switch (c) + { + case 0: x = f; break; + case 1: y = f; break; + } + c++; + } + if (c >= 3) + { + var val = new Vector2(x, y); + items.Add(MetaTypes.SwapBytes(val)); //big schmig + } + } + + + return items.ToArray(); + } + private static MetaHash[] TraverseHashArrayRaw(XmlNode node) + { + var items = new List(); + + foreach (XmlNode cnode in node.ChildNodes) + { + var val = GetHash(cnode.InnerText); + items.Add(MetaTypes.SwapBytes(val)); + } + + return items.ToArray(); + } + + + + + private static void Write(int val, byte[] data, int offset) + { + byte[] bytes = BitConverter.GetBytes(MetaTypes.SwapBytes(val)); + Buffer.BlockCopy(bytes, 0, data, offset, sizeof(int)); + } + + private static void Write(uint val, byte[] data, int offset) + { + byte[] bytes = BitConverter.GetBytes(MetaTypes.SwapBytes(val)); + Buffer.BlockCopy(bytes, 0, data, offset, sizeof(uint)); + } + + private static void Write(short val, byte[] data, int offset) + { + byte[] bytes = BitConverter.GetBytes(MetaTypes.SwapBytes(val)); + Buffer.BlockCopy(bytes, 0, data, offset, sizeof(short)); + } + + private static void Write(ushort val, byte[] data, int offset) + { + byte[] bytes = BitConverter.GetBytes(MetaTypes.SwapBytes(val)); + Buffer.BlockCopy(bytes, 0, data, offset, sizeof(ushort)); + } + + private static void Write(float val, byte[] data, int offset) + { + byte[] bytes = BitConverter.GetBytes(MetaTypes.SwapBytes(val));//big fkn end + Buffer.BlockCopy(bytes, 0, data, offset, sizeof(float)); + } + + private static void Write(ulong val, byte[] data, int offset) + { + byte[] bytes = BitConverter.GetBytes(MetaTypes.SwapBytes(val)); + Buffer.BlockCopy(bytes, 0, data, offset, sizeof(ulong)); + } + + private static MetaHash GetHash(string str) + { + if (str.StartsWith("hash_")) + { + return (MetaHash)Convert.ToUInt32(str.Substring(5), 16); + } + else + { + return JenkHash.GenHash(str); + } + } + + private static XmlNode GetEntryNode(XmlNodeList nodes, MetaName name) + { + foreach (XmlNode node in nodes) + { + if (GetHash(node.Name) == (uint)name) + { + return node; + } + } + return null; + } + + + private static int GetEnumInt(MetaName type, string enumString, PsoDataType dataType) + { + var infos = PsoTypes.GetEnumInfo(type); + + if (infos == null) + { + return 0; + } + + + bool isFlags = (dataType == PsoDataType.Flags);// || + //(dataType == PsoDataType.IntFlags2);// || + //(dataType == PsoDataType.ShortFlags); + + if (isFlags) + { + //flags enum. (multiple values, comma-separated) + var split = enumString.Split(new[] { ',' ,' '}, StringSplitOptions.RemoveEmptyEntries); + int enumVal = 0; + + for (int i = 0; i < split.Length; i++) + { + var enumName = (MetaName)(uint)GetHash(split[i].Trim()); + + for (int j = 0; j < infos.Entries.Length; j++) + { + var entry = infos.Entries[j]; + if (entry.EntryNameHash == enumName) + { + enumVal += (1 << entry.EntryKey); + break; + } + } + } + + return enumVal; + } + else + { + //single value enum. + + var enumName = (MetaName)(uint)GetHash(enumString); + + for (int j = 0; j < infos.Entries.Length; j++) + { + var entry = infos.Entries[j]; + + if (entry.EntryNameHash == enumName) + { + return entry.EntryKey; + } + } + } + + return -1; + } + + } + + struct PsoArrayResults + { + public Dictionary Structures; + public Dictionary StructurePointers; + public Dictionary UInts; + public Dictionary UShorts; + public Dictionary UBytes; + public Dictionary Floats; + public Dictionary Float_XYZs; + public Dictionary Hashes; + + public void WriteArrays(byte[] data) + { + foreach (KeyValuePair ptr in Structures) + { + var val = ptr.Value; + val.SwapEnd(); + var _data = MetaTypes.ConvertToBytes(val); + Buffer.BlockCopy(_data, 0, data, ptr.Key, _data.Length); + } + + foreach (KeyValuePair ptr in StructurePointers) + { + var val = ptr.Value; + val.SwapEnd(); + var _data = MetaTypes.ConvertToBytes(val); + Buffer.BlockCopy(_data, 0, data, ptr.Key, _data.Length); + } + + foreach (KeyValuePair ptr in UInts) + { + var val = ptr.Value; + val.SwapEnd(); + var _data = MetaTypes.ConvertToBytes(val); + Buffer.BlockCopy(_data, 0, data, ptr.Key, _data.Length); + } + + foreach (KeyValuePair ptr in UShorts) + { + var val = ptr.Value; + val.SwapEnd(); + var _data = MetaTypes.ConvertToBytes(val); + Buffer.BlockCopy(_data, 0, data, ptr.Key, _data.Length); + } + + foreach (KeyValuePair ptr in UBytes) + { + var val = ptr.Value; + val.SwapEnd(); + var _data = MetaTypes.ConvertToBytes(val); + Buffer.BlockCopy(_data, 0, data, ptr.Key, _data.Length); + } + + foreach (KeyValuePair ptr in Floats) + { + var val = ptr.Value; + val.SwapEnd(); + var _data = MetaTypes.ConvertToBytes(val); + Buffer.BlockCopy(_data, 0, data, ptr.Key, _data.Length); + } + + foreach (KeyValuePair ptr in Float_XYZs) + { + var val = ptr.Value; + val.SwapEnd(); + var _data = MetaTypes.ConvertToBytes(val); + Buffer.BlockCopy(_data, 0, data, ptr.Key, _data.Length); + } + + foreach (KeyValuePair ptr in Hashes) + { + var val = ptr.Value; + val.SwapEnd(); + var _data = MetaTypes.ConvertToBytes(val); + Buffer.BlockCopy(_data, 0, data, ptr.Key, _data.Length); + } + } + } + +} diff --git a/ExploreForm.cs b/ExploreForm.cs index 1fc911e..efe0d4c 100644 --- a/ExploreForm.cs +++ b/ExploreForm.cs @@ -2251,6 +2251,8 @@ namespace CodeWalker var fi = new FileInfo(fpath); var fname = fi.Name; var fnamel = fname.ToLowerInvariant(); + var mformat = MetaFormat.RSC; + var trimlength = 4; if (!fnamel.EndsWith(".xml")) { @@ -2259,8 +2261,8 @@ namespace CodeWalker } if (fnamel.EndsWith(".pso.xml")) { - MessageBox.Show(fname + ": PSO XML import not yet supported.", "Cannot import XML"); - continue; + mformat = MetaFormat.PSO; + trimlength = 8; } if (fnamel.EndsWith(".rbf.xml")) { @@ -2268,8 +2270,8 @@ namespace CodeWalker continue; } - fname = fname.Substring(0, fname.Length - 4); - fnamel = fnamel.Substring(0, fnamel.Length - 4); + fname = fname.Substring(0, fname.Length - trimlength); + fnamel = fnamel.Substring(0, fnamel.Length - trimlength); var doc = new XmlDocument(); string text = File.ReadAllText(fpath); @@ -2278,21 +2280,46 @@ namespace CodeWalker doc.LoadXml(text); } - var meta = XmlMeta.GetMeta(doc); + byte[] data = null; - - if ((meta.DataBlocks?.Data == null) || (meta.DataBlocks.Count == 0)) + switch (mformat) { - MessageBox.Show(fname + ": Schema not supported.", "Cannot import XML"); - continue; + case MetaFormat.RSC: + { + var meta = XmlMeta.GetMeta(doc); + if ((meta.DataBlocks?.Data == null) || (meta.DataBlocks.Count == 0)) + { + MessageBox.Show(fname + ": Schema not supported.", "Cannot import Meta XML"); + continue; + } + data = ResourceBuilder.Build(meta, 2); //meta is RSC V:2 + break; + } + case MetaFormat.PSO: + { + var pso = XmlPso.GetPso(doc); + if ((pso.DataSection == null) || (pso.DataMapSection == null) || (pso.SchemaSection == null)) + { + MessageBox.Show(fname + ": Schema not supported.", "Cannot import PSO XML"); + continue; + } + data = pso.Save(); + break; + } + case MetaFormat.RBF: + { + //todo! + break; + } } - byte[] data = ResourceBuilder.Build(meta, 2); //meta is RSC V:2 + if (data != null) + { + RpfFile.CreateFile(parentrpffldr, fname, data); + } - RpfFile.CreateFile(parentrpffldr, fname, data); - } #if !DEBUG catch (Exception ex) diff --git a/Forms/MetaForm.cs b/Forms/MetaForm.cs index 3076493..bc8db25 100644 --- a/Forms/MetaForm.cs +++ b/Forms/MetaForm.cs @@ -350,8 +350,14 @@ namespace CodeWalker.Forms data = ResourceBuilder.Build(meta, 2); //meta is RSC "Version":2 (it's actually a type identifier, not a version!) break; case MetaFormat.PSO: - MessageBox.Show("Sorry, PSO import is not supported yet.", "Cannot import PSO XML"); - return false; + var pso = XmlPso.GetPso(doc); + if ((pso.DataSection == null) || (pso.DataMapSection == null) || (pso.SchemaSection == null)) + { + MessageBox.Show("Schema not supported.", "Cannot import PSO XML"); + return false; + } + data = pso.Save(); + break; case MetaFormat.RBF: MessageBox.Show("Sorry, RBF import is not supported.", "Cannot import RBF XML"); return false;