using System; using System.Collections; using System.Collections.Generic; using NW4RTools.Models; using NW4RTools.Models.Animation; namespace NW4RTools { public class BrresWriter { public static byte[] WriteFile(ResFile file) { return new BrresWriter().Save(file); } private ResFile File; private ILogger Debug; private SortedDictionary OffsetMap; private OutputStream Output; private BrresWriter() { Debug = new ConsoleLogger(); OffsetMap = new SortedDictionary(); } private byte[] Save(ResFile file) { Output = new OutputStream(ByteEndian.BigEndian); File = file; using (var c = Debug.Push("Offset Calculation")) CalculateRoot(); using (var c = Debug.Push("Data")) WriteRoot(); using (var c = Debug.Push("Offset Map")) { foreach (var e in OffsetMap) { // Commented for easier output reading atm Debug.Send("0x{0:X} : {1}", e.Key, e.Value); } } return Output.GetBuffer(); } private void LogPosition(string text) { Debug.Send("0x{0:X} : {1}", CurrentPos, text); OffsetMap.Add(CurrentPos, text); } // OK, here's how I'm going to code this: kinda like how BrawlLib works, first I'll calculate the size of // each written element, and use that to build up a list of offsets. Then, I'll actually write out the data. // This code will also handle building the string table. #region Offset Data Storage #region Model Data Storage private Dictionary ModelOffsets; private Dictionary BytecodeOffsets; private Dictionary NodeOffsets; private Dictionary VtxPosOffsets; private Dictionary VtxNrmOffsets; private Dictionary VtxClrOffsets; private Dictionary VtxTexCoordOffsets; private Dictionary MaterialOffsets; private Dictionary MaterialDLOffsets; private Dictionary ShaderOffsets; private Dictionary ShapeOffsets; private Dictionary ShapeDL1Offsets; private Dictionary ShapeDL2Offsets; private Dictionary ShapeSizes; private Dictionary, int> PairingOffsets; private Dictionary TextureInfoOffsets; // Models have lots of extra offset data attached private class ModelCalcInfo { public int MatrixIDtoNodeID, Bytecode, Nodes; public int VtxPosData, VtxNrmData, VtxClrData, VtxTexCoordData; public int Materials, Shaders, Shapes; public int PairingLookupByTexture; public int BlockSize; // Duplicate shader structs are removed from the model, so they require special tracking public List UniqueShaders; public Dictionary MaterialSizes; public ModelCalcInfo() { UniqueShaders = new List(); MaterialSizes = new Dictionary(); } } private Dictionary ModelCalcInfos; #endregion #region Character Animation Data Storage private class ChrAnmNodeInfo { public CharacterAnim.Flags Flags; } private class ChrAnmInfo { public KeyframeWriterInfo KFInfo; public Dictionary NodeInfo; public Dictionary NodeOffsets; public int BlockSize; public ChrAnmInfo() { KFInfo = new KeyframeWriterInfo(); NodeInfo = new Dictionary(); NodeOffsets = new Dictionary(); } } private Dictionary ChrAnmInfos; private Dictionary ChrAnmOffsets; #endregion #region Color Animation Data Storage private class ClrAnmInfo { public Dictionary NodeOffsets; public int BlockSize; public List UsedArrays; public List UsedArrayPositions; public ClrAnmInfo() { NodeOffsets = new Dictionary(); UsedArrays = new List(); UsedArrayPositions = new List(); } } private Dictionary ClrAnmInfos; private Dictionary ClrAnmOffsets; #endregion #region Texture SRT Animation Data Storage private class TexSRTAnmNodeInfo { public int[] TextureOffsets; public int[] IndirectTextureOffsets; public TexSRTAnmNodeInfo() { TextureOffsets = new int[8]; IndirectTextureOffsets = new int[3]; } } private class TexSRTAnmInfo { public KeyframeWriterInfo KFInfo; public Dictionary NodeInfo; public Dictionary NodeOffsets; public int BlockSize; public TexSRTAnmInfo() { KFInfo = new KeyframeWriterInfo(); NodeInfo = new Dictionary(); NodeOffsets = new Dictionary(); } } private Dictionary TexSRTAnmInfos; private Dictionary TexSRTAnmOffsets; #endregion private class KeyframeWriterInfo { public Dictionary, int> KeyframeAnimOffsets; public List> KeyframeAnims; public KeyframeWriterInfo() { KeyframeAnimOffsets = new Dictionary, int>(); KeyframeAnims = new List>(); } public void Add(KeyframeAnim anim) { Add(new Pair(null, anim)); } public void Add(object obj, KeyframeAnim anim) { Add(new Pair(obj, anim)); } public void Add(Pair pair) { if (pair.two.IsConstant) return; if (KeyframeAnimOffsets.ContainsKey(pair)) return; KeyframeAnims.Add(pair); KeyframeAnimOffsets.Add(pair, -1); } public int GetPosition(KeyframeAnim anim) { return KeyframeAnimOffsets[new Pair(null, anim)]; } public int GetPosition(object obj, KeyframeAnim anim) { return KeyframeAnimOffsets[new Pair(obj, anim)]; } } private Dictionary TextureOffsets; private Dictionary TextureDataOffsets; private Dictionary RootDictOffsets; private int CurrentPos; private int BlockCount; private int RootBlockSize; #endregion #region Offset/String Table Calculation private void CalculateRoot() { // Where it all starts! InitialiseStringTable(); // First up: BRRES header CurrentPos = 0x10; // First block, and ResDict CurrentPos += 8; CurrentPos += GetSizeForResDict(File.Count); RootBlockSize = 8 + GetSizeForResDict(File.Count); // "root" block counts BlockCount = 1; RootDictOffsets = new Dictionary(); // Now do each ResDict in the File foreach (var kv in File) { LogPosition("ResDict: " + kv.Key); RootDictOffsets[kv.Value] = CurrentPos; CurrentPos += GetSizeForResDict((kv.Value as ICollection).Count); RootBlockSize += GetSizeForResDict((kv.Value as ICollection).Count); BlockCount += (kv.Value as ICollection).Count; } // OK, so that's done. Process each type foreach (var name in File.Keys) { AddString(name); switch (name) { case "3DModels(NW4R)": CalculateModels(); break; case "Textures(NW4R)": CalculateTextures(); break; case "AnmChr(NW4R)": CalculateAnmChr(); break; case "AnmClr(NW4R)": CalculateAnmClr(); break; case "AnmTexSrt(NW4R)": CalculateAnmTexSrt(); break; default: Debug.Send("[[ UNIMPLEMENTED {0} ]]", name); break; } } // ... and done with that. Build the string table and go! AlignCalcPos(4); LogPosition("String Table"); CalculateStringTable(); } #region Model Calculation private void CalculateModels() { ModelOffsets = new Dictionary(); ModelCalcInfos = new Dictionary(); BytecodeOffsets = new Dictionary(); NodeOffsets = new Dictionary(); VtxPosOffsets = new Dictionary(); VtxNrmOffsets = new Dictionary(); VtxClrOffsets = new Dictionary(); VtxTexCoordOffsets = new Dictionary(); MaterialOffsets = new Dictionary(); MaterialDLOffsets = new Dictionary(); ShaderOffsets = new Dictionary(); ShapeOffsets = new Dictionary(); ShapeDL1Offsets = new Dictionary(); ShapeDL2Offsets = new Dictionary(); ShapeSizes = new Dictionary(); PairingOffsets = new Dictionary, int>(); TextureInfoOffsets = new Dictionary(); foreach (var kv in File.GetGroup("3DModels(NW4R)")) { AddString(kv.Key); AlignCalcPos(0x20); // 0x40? dunno Model model = kv.Value; var calcInfo = ModelCalcInfos[model] = new BrresWriter.ModelCalcInfo(); // This is used later to calculate the MDL0 block size easily int startPos = CurrentPos; LogPosition("Model: " + kv.Key); ModelOffsets.Add(model, CurrentPos); CurrentPos += 0x4C; LogPosition("Model Info Struct for: " + kv.Key); CurrentPos += 0x40; LogPosition("Matrix ID to Node ID Data for: " + kv.Key); calcInfo.MatrixIDtoNodeID = CurrentPos; CurrentPos += 4 + (model.MatrixIDtoNodeID.Length * 4); LogPosition("ResDict: ByteCode"); calcInfo.Bytecode = CurrentPos; CurrentPos += GetSizeForResDict(model.Bytecode.Count); LogPosition("ResDict: Nodes"); calcInfo.Nodes = CurrentPos; CurrentPos += GetSizeForResDict(model.Nodes.Count); LogPosition("ResDict: VertexPosData"); calcInfo.VtxPosData = CurrentPos; CurrentPos += GetSizeForResDict(model.VtxPosData.Count); if (model.VtxNrmData.Count > 0) { LogPosition("ResDict: VertexNrmData"); calcInfo.VtxNrmData = CurrentPos; CurrentPos += GetSizeForResDict(model.VtxNrmData.Count); } if (model.VtxClrData.Count > 0) { LogPosition("ResDict: VertexClrData"); calcInfo.VtxClrData = CurrentPos; CurrentPos += GetSizeForResDict(model.VtxClrData.Count); } if (model.VtxTexCoordData.Count > 0) { LogPosition("ResDict: VertexTexCoordData"); calcInfo.VtxTexCoordData = CurrentPos; CurrentPos += GetSizeForResDict(model.VtxTexCoordData.Count); } LogPosition("ResDict: Materials"); calcInfo.Materials = CurrentPos; CurrentPos += GetSizeForResDict(model.Materials.Count); LogPosition("ResDict: Shaders"); calcInfo.Shaders = CurrentPos; CurrentPos += GetSizeForResDict(model.Shaders.Count); LogPosition("ResDict: Shapes"); calcInfo.Shapes = CurrentPos; CurrentPos += GetSizeForResDict(model.Shapes.Count); if (model.PairingLookupByTexture.Count > 0) { LogPosition("ResDict: Texture Lookup"); calcInfo.PairingLookupByTexture = CurrentPos; CurrentPos += GetSizeForResDict(model.PairingLookupByTexture.Count); } // todo: palette lookup, checking if dicts are empty or do not exist // todo: can dicts even NOT exist? must find this out CalculatePairings(model, model.PairingLookupByTexture); CalculateBytecode(model); CalculateNodes(model); CalculateMaterials(model); CalculateShaders(model); CalculateShapes(model); CalculateVtxPosData(model); CalculateVtxNrmData(model); CalculateVtxClrData(model); CalculateVtxTexCoordData(model); // Here we go! calcInfo.BlockSize = CurrentPos - startPos; } } private void CalculatePairings(Model m, ResDict> dict) { foreach (var kv in dict) { LogPosition("Texture/Material Pairing List for: " + kv.Key); PairingOffsets.Add(kv.Value, CurrentPos); CurrentPos += 4 + (kv.Value.Count * 8); } } private void CalculateBytecode(Model m) { foreach (var kv in m.Bytecode) { AddString(kv.Key); ByteCode bc = kv.Value; LogPosition("ByteCode: " + kv.Key); BytecodeOffsets.Add(kv.Value, CurrentPos); foreach (var insn in bc.Instructions) { switch (insn.GetOp()) { case ByteCode.OpType.None: CurrentPos += 1; break; case ByteCode.OpType.Done: CurrentPos += 1; break; case ByteCode.OpType.AssignNodeToParentMtx: CurrentPos += 5; break; case ByteCode.OpType.BlendMatrices: CurrentPos += 4 + (6 * (insn as ByteCode.BlendMatricesInstruction).BlendedMatrices.Length); break; case ByteCode.OpType.DrawShape: CurrentPos += 8; break; case ByteCode.OpType.AssignMtxToNode: CurrentPos += 5; break; } } } AlignCalcPos(4); // should be per-bytecode maybe? } private void CalculateNodes(Model m) { foreach (var kv in m.Nodes) { AddString(kv.Key); LogPosition("Node: " + kv.Key); NodeOffsets.Add(kv.Value, CurrentPos); CurrentPos += 0xD0; } } private void CalculateMaterials(Model m) { foreach (var kv in m.Materials) { AddString(kv.Key); LogPosition("Material: " + kv.Key); MaterialOffsets.Add(kv.Value, CurrentPos); // Base material struct CurrentPos += 0x14; // ResGenMode CurrentPos += 8; // ResMatMode CurrentPos += 0xC; // other stuff CurrentPos += 0x18; // ResTexObj LogPosition("Material ResTexObj: " + kv.Key); CurrentPos += 0x104; // ResTlutObj LogPosition("Material ResTlutObj: " + kv.Key); CurrentPos += 0x64; // ResTexSrt LogPosition("Material ResTexSrt: " + kv.Key); CurrentPos += 8 + (0x14 * 8) + (0x34 * 8); // ResMatChan LogPosition("Material ResMatChan: " + kv.Key); CurrentPos += 0x28; // Texture Infos if (kv.Value.TextureInfos.Count > 0) LogPosition("Material Texture Infos: " + kv.Key); for (int i = 0; i < kv.Value.TextureInfos.Count; i++) { TextureInfoOffsets[kv.Value.TextureInfos[i]] = CurrentPos; CurrentPos += 0x34; } // Display Lists AlignCalcPos(0x20); LogPosition("Material Display Lists: " + kv.Key); MaterialDLOffsets[kv.Value] = CurrentPos; CurrentPos += 0x20 + 0x80 + 0x40 + 0xA0; // Sized! int size = CurrentPos - MaterialOffsets[kv.Value]; ModelCalcInfos[m].MaterialSizes[kv.Value] = size; } } private void CalculateShaders(Model m) { // Oh great, now I need to optimise this by removing duplicate shaders. // I'm doing it like this: I'll loop through every shader in the ResDict. // I'll check each one against the UniqueShaders list. // If it's not in the list, I'll add it there and add it to the resource data. var calcInfo = ModelCalcInfos[m]; foreach (var kv in m.Shaders) { AddString(kv.Key); // Check to see if it's in the UniqueShaders list already // This is made slightly difficult -- shaders can be different object instances // yet still match, so simple reference comparison isn't good enough. // So, I added a "DataMatches" method to the Shader class that checks the // data in two Shaders and sees if they match. bool wasFound = false; Shader match = null; foreach (var checkAgainst in calcInfo.UniqueShaders) { if (checkAgainst.DataMatches(kv.Value)) { wasFound = true; match = checkAgainst; break; } } if (wasFound) { // It's already in there. int positionOfMatch = ShaderOffsets[match]; OffsetMap[positionOfMatch] = OffsetMap[positionOfMatch] + ", " + kv.Key; // Add it to ShaderOffsets so we can link it up to the shader ResDict ShaderOffsets.Add(kv.Value, positionOfMatch); } else { // This is a new shader, add it! calcInfo.UniqueShaders.Add(kv.Value); LogPosition("Shader: " + kv.Key); ShaderOffsets.Add(kv.Value, CurrentPos); CurrentPos += 0x200; } } } private void CalculateShapes(Model m) { foreach (var kv in m.Shapes) { AddString(kv.Key); LogPosition("Shape: " + kv.Key); ShapeOffsets.Add(kv.Value, CurrentPos); CurrentPos += 0x68; // extra data CurrentPos += 4; CurrentPos += (kv.Value.ExtraData.Length * 2); // display lists AlignCalcPos(0x20); ShapeDL1Offsets.Add(kv.Value, CurrentPos); LogPosition("Shape DL 1: " + kv.Key); CurrentPos += (int)kv.Value.DLBufferSize1; AlignCalcPos(0x20); ShapeDL2Offsets.Add(kv.Value, CurrentPos); LogPosition("Shape DL 2: " + kv.Key); CurrentPos += (int)kv.Value.DLBufferSize2; // Should this line be after the final alignment? // Does it even matter? I'd assume DL sizes will be aligned to 0x20... ShapeSizes[kv.Value] = CurrentPos - ShapeOffsets[kv.Value]; AlignCalcPos(0x20); } } private void CalculateVtxPosData(Model m) { foreach (var kv in m.VtxPosData) { AddString(kv.Key); LogPosition("VertexPosData: " + kv.Key); VtxPosOffsets.Add(kv.Value, CurrentPos); // Main data CurrentPos += 0x20; // Minimum/maximum VEC3 (specific to VtxPosData) CurrentPos += 0x18; AlignCalcPos(0x20); LogPosition("Data: " + kv.Key); CurrentPos += kv.Value.EntryCount * kv.Value.EntrySize; AlignCalcPos(0x20); } } private void CalculateVtxNrmData(Model m) { foreach (var kv in m.VtxNrmData) { AddString(kv.Key); LogPosition("VertexNrmData: " + kv.Key); VtxNrmOffsets.Add(kv.Value, CurrentPos); // Main data CurrentPos += 0x20; AlignCalcPos(0x20); LogPosition("Data: " + kv.Key); CurrentPos += kv.Value.EntryCount * kv.Value.EntrySize; AlignCalcPos(0x20); } } private void CalculateVtxClrData(Model m) { foreach (var kv in m.VtxClrData) { AddString(kv.Key); LogPosition("VertexClrData: " + kv.Key); VtxClrOffsets.Add(kv.Value, CurrentPos); // Main data CurrentPos += 0x20; AlignCalcPos(0x20); LogPosition("Data: " + kv.Key); CurrentPos += kv.Value.EntryCount * kv.Value.EntrySize; AlignCalcPos(0x20); } } private void CalculateVtxTexCoordData(Model m) { foreach (var kv in m.VtxTexCoordData) { AddString(kv.Key); LogPosition("VertexTexCoordData: " + kv.Key); VtxTexCoordOffsets.Add(kv.Value, CurrentPos); // Main data CurrentPos += 0x20; // Minimum/maximum VEC2 (specific to VtxTexCoordData) CurrentPos += 0x10; AlignCalcPos(0x20); LogPosition("Data: " + kv.Key); CurrentPos += kv.Value.EntryCount * kv.Value.EntrySize; AlignCalcPos(0x20); } } #endregion #region Texture Calculation private void CalculateTextures() { TextureOffsets = new Dictionary(); TextureDataOffsets = new Dictionary(); var textureDict = File.GetGroup("Textures(NW4R)"); foreach (var kv in textureDict) { AddString(kv.Key); AlignCalcPos(0x20); Texture texture = kv.Value; Debug.Send("Current: {0}", kv.Key); LogPosition("Texture: " + kv.Key); TextureOffsets.Add(kv.Value, CurrentPos); CurrentPos += 0x30; AlignCalcPos(0x20); LogPosition("Texture Data for: " + kv.Key); TextureDataOffsets.Add(kv.Value, CurrentPos); CurrentPos += texture.GetDataSize(); AlignCalcPos(0x20); } } #endregion #region Character Animation Calculation private void CalculateAnmChr() { ChrAnmOffsets = new Dictionary(); ChrAnmInfos = new Dictionary(); var chrAnmDict = File.GetGroup("AnmChr(NW4R)"); foreach (var kv in chrAnmDict) { AddString(kv.Key); AlignCalcPos(0x20); CharacterAnim anim = kv.Value; LogPosition("Character Anim: " + kv.Key); ChrAnmOffsets.Add(anim, CurrentPos); CurrentPos += 0x20; LogPosition("Character Anim Info Struct: " + kv.Key); CurrentPos += 0xC; LogPosition("Character Anim Node Dictionary: " + kv.Key); CurrentPos += GetSizeForResDict(anim.Nodes.Count); var anminfo = new ChrAnmInfo(); ChrAnmInfos.Add(anim, anminfo); // Now size each node foreach (var nodepair in anim.Nodes) { AddString(nodepair.Key); CharacterAnim.Node node = nodepair.Value; anminfo.NodeOffsets.Add(node, CurrentPos); LogPosition("Character Anim Node: " + nodepair.Key); CurrentPos += 8; var nodeinfo = new ChrAnmNodeInfo(); anminfo.NodeInfo.Add(node, nodeinfo); nodeinfo.Flags = CharacterAnim.Flags.AlwaysSet | (node.Flags & CharacterAnim.Flags.NonComputableFlags); if (node.ScaleUseModel) nodeinfo.Flags |= CharacterAnim.Flags.ScaleUseModel; if (node.RotateUseModel) nodeinfo.Flags |= CharacterAnim.Flags.RotateUseModel; if (node.TranslateUseModel) nodeinfo.Flags |= CharacterAnim.Flags.TranslateUseModel; // Scale if (node.ScaleX.IsConstWith(1) && node.ScaleY.IsConstWith(1) && node.ScaleZ.IsConstWith(1)) { nodeinfo.Flags |= CharacterAnim.Flags.ScaleOne; nodeinfo.Flags |= CharacterAnim.Flags.ScaleUniform; nodeinfo.Flags |= CharacterAnim.Flags.ScaleXConstant; nodeinfo.Flags |= CharacterAnim.Flags.ScaleYConstant; nodeinfo.Flags |= CharacterAnim.Flags.ScaleZConstant; } else { nodeinfo.Flags |= CharacterAnim.Flags.CalcScale; anminfo.KFInfo.Add(node.ScaleFormat, node.ScaleX); if (node.ScaleX.IsConstant) nodeinfo.Flags |= CharacterAnim.Flags.ScaleXConstant; CurrentPos += 4; if (node.ScaleX.Equals(node.ScaleY) && node.ScaleX.Equals(node.ScaleZ)) { nodeinfo.Flags |= CharacterAnim.Flags.ScaleUniform; if (node.ScaleX.IsConstant) { nodeinfo.Flags |= CharacterAnim.Flags.ScaleYConstant; nodeinfo.Flags |= CharacterAnim.Flags.ScaleZConstant; } } else { anminfo.KFInfo.Add(node.ScaleFormat, node.ScaleY); anminfo.KFInfo.Add(node.ScaleFormat, node.ScaleZ); if (node.ScaleY.IsConstant) nodeinfo.Flags |= CharacterAnim.Flags.ScaleYConstant; if (node.ScaleZ.IsConstant) nodeinfo.Flags |= CharacterAnim.Flags.ScaleZConstant; CurrentPos += 8; } } // Rotation if (node.RotateX.IsConstWith(0) && node.RotateY.IsConstWith(0) && node.RotateZ.IsConstWith(0)) { nodeinfo.Flags |= CharacterAnim.Flags.RotateZero; nodeinfo.Flags |= CharacterAnim.Flags.RotateXConstant; nodeinfo.Flags |= CharacterAnim.Flags.RotateYConstant; nodeinfo.Flags |= CharacterAnim.Flags.RotateZConstant; } else { nodeinfo.Flags |= CharacterAnim.Flags.CalcRotate; anminfo.KFInfo.Add(node.RotateFormat, node.RotateX); anminfo.KFInfo.Add(node.RotateFormat, node.RotateY); anminfo.KFInfo.Add(node.RotateFormat, node.RotateZ); if (node.RotateX.IsConstant) nodeinfo.Flags |= CharacterAnim.Flags.RotateXConstant; if (node.RotateY.IsConstant) nodeinfo.Flags |= CharacterAnim.Flags.RotateYConstant; if (node.RotateZ.IsConstant) nodeinfo.Flags |= CharacterAnim.Flags.RotateZConstant; CurrentPos += 12; } // Translation if (node.TranslateX.IsConstWith(0) && node.TranslateY.IsConstWith(0) && node.TranslateZ.IsConstWith(0)) { nodeinfo.Flags |= CharacterAnim.Flags.TranslateZero; nodeinfo.Flags |= CharacterAnim.Flags.TranslateXConstant; nodeinfo.Flags |= CharacterAnim.Flags.TranslateYConstant; nodeinfo.Flags |= CharacterAnim.Flags.TranslateZConstant; } else { nodeinfo.Flags |= CharacterAnim.Flags.CalcTranslate; anminfo.KFInfo.Add(node.TranslateFormat, node.TranslateX); anminfo.KFInfo.Add(node.TranslateFormat, node.TranslateY); anminfo.KFInfo.Add(node.TranslateFormat, node.TranslateZ); if (node.TranslateX.IsConstant) nodeinfo.Flags |= CharacterAnim.Flags.TranslateXConstant; if (node.TranslateY.IsConstant) nodeinfo.Flags |= CharacterAnim.Flags.TranslateYConstant; if (node.TranslateZ.IsConstant) nodeinfo.Flags |= CharacterAnim.Flags.TranslateZConstant; CurrentPos += 12; } if ((nodeinfo.Flags & CharacterAnim.Flags.RotateZero) != 0 && (nodeinfo.Flags & CharacterAnim.Flags.TranslateZero) != 0) nodeinfo.Flags |= CharacterAnim.Flags.RtZero; if ((nodeinfo.Flags & CharacterAnim.Flags.RtZero) != 0 && (nodeinfo.Flags & CharacterAnim.Flags.ScaleOne) != 0) nodeinfo.Flags |= CharacterAnim.Flags.Identity; nodeinfo.Flags |= (CharacterAnim.Flags)((uint)node.ScaleFormat << 25); nodeinfo.Flags |= (CharacterAnim.Flags)((uint)node.RotateFormat << 27); nodeinfo.Flags |= (CharacterAnim.Flags)((uint)node.TranslateFormat << 30); } // And do all keyframes foreach (var pair in anminfo.KFInfo.KeyframeAnims) { CharacterAnim.RotateFormatType format = (CharacterAnim.RotateFormatType)pair.one; KeyframeAnim kf = pair.two; LogPosition("Keyframes"); anminfo.KFInfo.KeyframeAnimOffsets[pair] = CurrentPos; switch (format) { case CharacterAnim.RotateFormatType.Data32: CurrentPos += 8 + (kf.Keyframes.Length * 4); break; case CharacterAnim.RotateFormatType.Data48: CurrentPos += 8 + (kf.Keyframes.Length * 6); break; case CharacterAnim.RotateFormatType.Data96: CurrentPos += 8 + (kf.Keyframes.Length * 12); break; case CharacterAnim.RotateFormatType.DataFrm8: CurrentPos += 8 + (kf.Values.Length); break; case CharacterAnim.RotateFormatType.DataFrm16: CurrentPos += 8 + (kf.Values.Length * 2); break; case CharacterAnim.RotateFormatType.DataFrm32: CurrentPos += (kf.FloatValues.Length * 4); break; default: throw new NotImplementedException(); } AlignCalcPos(4); } anminfo.BlockSize = CurrentPos - ChrAnmOffsets[anim]; } } #endregion #region Color Animation Calculation private void CalculateAnmClr() { ClrAnmOffsets = new Dictionary(); ClrAnmInfos = new Dictionary(); var clrAnmDict = File.GetGroup("AnmClr(NW4R)"); foreach (var kv in clrAnmDict) { AddString(kv.Key); ColorAnim anim = kv.Value; LogPosition("Color Anim: " + kv.Key); ClrAnmOffsets.Add(anim, CurrentPos); CurrentPos += 0x20; LogPosition("Color Anim Info Struct: " + kv.Key); CurrentPos += 0x8; LogPosition("Color Anim Node Dictionary: " + kv.Key); CurrentPos += GetSizeForResDict(anim.Nodes.Count); var anminfo = new ClrAnmInfo(); ClrAnmInfos.Add(anim, anminfo); // Now size each node foreach (var nodepair in anim.Nodes) { AddString(nodepair.Key); ColorAnim.Node node = nodepair.Value; anminfo.NodeOffsets.Add(node, CurrentPos); LogPosition("Color Anim Node: " + nodepair.Key); CurrentPos += 8; for (int i = 0; i < node.Elements.Length; i++) { if (node.Elements[i].Exists) { CurrentPos += 8; if (!node.Elements[i].IsConstant) { // Add the array // But check to make sure it's not already there bool wasFound = false; foreach (var arr in anminfo.UsedArrays) { if (Misc.ArrayCompare(node.Elements[i].Colors, arr)) { wasFound = true; break; } } if (!wasFound) anminfo.UsedArrays.Add(node.Elements[i].Colors); } } } } // Finally, add every array foreach (var arr in anminfo.UsedArrays) { anminfo.UsedArrayPositions.Add(CurrentPos); CurrentPos += (arr.Length * 4); } anminfo.BlockSize = CurrentPos - ClrAnmOffsets[anim]; } } #endregion #region Texture SRT Animation Calculation private void CalculateAnmTexSrt() { TexSRTAnmOffsets = new Dictionary(); TexSRTAnmInfos = new Dictionary(); var chrAnmDict = File.GetGroup("AnmTexSrt(NW4R)"); foreach (var kv in chrAnmDict) { AddString(kv.Key); AlignCalcPos(0x20); TextureSRTAnim anim = kv.Value; LogPosition("TextureSRT Anim: " + kv.Key); TexSRTAnmOffsets.Add(anim, CurrentPos); CurrentPos += 0x20; LogPosition("TextureSRT Anim Info Struct: " + kv.Key); CurrentPos += 0xC; LogPosition("TextureSRT Anim Node Dictionary: " + kv.Key); CurrentPos += GetSizeForResDict(anim.Nodes.Count); var anminfo = new TexSRTAnmInfo(); TexSRTAnmInfos.Add(anim, anminfo); // Now size each node foreach (var nodepair in anim.Nodes) { AddString(nodepair.Key); TextureSRTAnim.Node node = nodepair.Value; anminfo.NodeOffsets.Add(node, CurrentPos); LogPosition("TextureSRT Anim Node: " + nodepair.Key); CurrentPos += 12; var nodeinfo = new TexSRTAnmNodeInfo(); anminfo.NodeInfo.Add(node, nodeinfo); // Figure out what anims we're using for (int i = 0; i < 8; i++) if (node.Textures[i].Exists) CurrentPos += 4; for (int i = 0; i < 3; i++) if (node.IndirectTextures[i].Exists) CurrentPos += 4; for (int i = 0; i < 8; i++) { if (node.Textures[i].Exists) { nodeinfo.TextureOffsets[i] = CurrentPos; CalculateAnmTexSrtElement(anminfo.KFInfo, node.Textures[i]); } } for (int i = 0; i < 3; i++) { if (node.IndirectTextures[i].Exists) { nodeinfo.IndirectTextureOffsets[i] = CurrentPos; CalculateAnmTexSrtElement(anminfo.KFInfo, node.IndirectTextures[i]); } } } CalculateKeyframeData(anminfo.KFInfo); anminfo.BlockSize = CurrentPos - TexSRTAnmOffsets[anim]; } } private void CalculateAnmTexSrtElement(KeyframeWriterInfo kfinfo, TextureSRTAnim.Element elem) { CurrentPos += 4; if (!(elem.ScaleS.IsConstWith(1) && elem.ScaleT.IsConstWith(1))) { // Scale is not all 1 kfinfo.Add(elem.ScaleS); CurrentPos += 4; if (elem.ScaleT != elem.ScaleS) { // Not uniform kfinfo.Add(elem.ScaleT); CurrentPos += 4; } } if (!elem.Rotate.IsConstWith(0)) { // Rotate is not zero kfinfo.Add(elem.Rotate); CurrentPos += 4; } if (!(elem.TransS.IsConstWith(0) && elem.TransT.IsConstWith(0))) { // Translate is not all zero kfinfo.Add(elem.TransS); kfinfo.Add(elem.TransT); CurrentPos += 8; } } #endregion #region Keyframe Animation Calculation private void CalculateKeyframeData(KeyframeWriterInfo kfinfo) { foreach (var pair in kfinfo.KeyframeAnims) { var anim = pair.two; kfinfo.KeyframeAnimOffsets[pair] = CurrentPos; CurrentPos += 8 + (anim.Keyframes.Length * 12); } } #endregion private void AlignCalcPos(int alignTo) { if ((CurrentPos & (alignTo - 1)) == 0) return; CurrentPos += (alignTo - (CurrentPos & (alignTo - 1))); } private int GetSizeForResDict(int entryCount) { return 8 + ((entryCount + 1) * 0x10); } #endregion #region Writing Data private void WriteRoot() { // magic "bres", endian, version, file size Output.WriteUInt32(0x62726573); Output.WriteUInt16(0xFEFF); Output.WriteUInt16(File.Version); Output.WriteUInt32((uint)(((CurrentPos + StringTableData.Position) + 0x3F) & ~0x3F)); // header size, block count Output.WriteUInt16(0x10); Output.WriteUInt16((ushort)BlockCount); // first block: magic "root", block size Output.WriteUInt32(0x726F6F74); Output.WriteUInt32((uint)RootBlockSize); // root dictionaries WriteResDict(File, RootDictOffsets); using (var c = Debug.Push("Root Dictionaries")) { foreach (var kv in File) { Debug.Send(kv.Key); switch (kv.Key) { case "3DModels(NW4R)": WriteResDict(kv.Value as ResDict, ModelOffsets); break; case "Textures(NW4R)": WriteResDict(kv.Value as ResDict, TextureOffsets); break; case "AnmChr(NW4R)": WriteResDict(kv.Value as ResDict, ChrAnmOffsets); break; case "AnmClr(NW4R)": WriteResDict(kv.Value as ResDict, ClrAnmOffsets); break; case "AnmTexSrt(NW4R)": WriteResDict(kv.Value as ResDict, TexSRTAnmOffsets); break; default: // for testing, boo Output.AddPadding(GetSizeForResDict((kv.Value as ICollection).Count)); Debug.Send("UNHANDLED RESOURCE TYPE: {0}", kv.Key); break; } } } // now write the actual data using (var c = Debug.Push("Root Data")) { foreach (var kv in File) { switch (kv.Key) { case "3DModels(NW4R)": WriteModels(); break; case "Textures(NW4R)": WriteTextures(); break; case "AnmChr(NW4R)": WriteAnmChr(); break; case "AnmClr(NW4R)": WriteAnmClr(); break; case "AnmTexSrt(NW4R)": WriteAnmTexSrt(); break; default: Debug.Send("UNHANDLED RESOURCE TYPE: {0}", kv.Key); break; } } } Output.AlignTo(4); Output.WriteBytes(StringTableData.GetBuffer()); // before we finish. pad the file Output.AlignTo(0x40); } #region Model Writing private void WriteModels() { foreach (var kv in File.GetGroup("3DModels(NW4R)")) { using (var c = Debug.Push("Model: {0}", kv.Key)) { Output.AlignTo(0x20); int startPos = Output.Position; Model model = kv.Value; var calcInfo = ModelCalcInfos[model]; Misc.Assert(startPos == ModelOffsets[model]); // Base struct: magic 'MDL0', block size, version[currently 11], resfile offset Output.WriteUInt32(0x4D444C30); Output.WriteUInt32((uint)calcInfo.BlockSize); Output.WriteUInt32(11); // Note: "0 - startPos" *DOESN'T* work when compiling under Mono! // Details: https://bugzilla.novell.com/show_bug.cgi?id=675777 // So just use "-startPos". Output.WriteInt32(-startPos); // More offsets for a ton of crap Output.WriteInt32(calcInfo.Bytecode - startPos); Output.WriteInt32(calcInfo.Nodes - startPos); Output.WriteInt32(calcInfo.VtxPosData - startPos); Output.WriteInt32((calcInfo.VtxNrmData == 0) ? 0 : (calcInfo.VtxNrmData - startPos)); Output.WriteInt32((calcInfo.VtxClrData == 0) ? 0 : (calcInfo.VtxClrData - startPos)); Output.WriteInt32((calcInfo.VtxTexCoordData == 0) ? 0 : (calcInfo.VtxTexCoordData - startPos)); // VtxFurVec, VtxFurPos (not handled) Output.WriteInt32(0); Output.WriteInt32(0); Output.WriteInt32(calcInfo.Materials - startPos); Output.WriteInt32(calcInfo.Shaders - startPos); Output.WriteInt32(calcInfo.Shapes - startPos); Output.WriteInt32((calcInfo.PairingLookupByTexture == 0) ? 0 : (calcInfo.PairingLookupByTexture - startPos)); // Pairing lookup by palette, unhandled atm Output.WriteInt32(0); // Unknown extra data Output.WriteInt32(0); // Name offset Output.WriteInt32(StringPositions[kv.Key] - startPos); // Model Info struct: struct size, model offset, some other stuff int infoStructPos = Output.Position; Output.WriteUInt32(0x40); Output.WriteInt32(startPos - infoStructPos); Output.WriteUInt32((uint)model.ScaleMode); Output.WriteUInt32((uint)model.TexMatrixMode); Output.WriteUInt32((uint)model.VertexCount); Output.WriteUInt32((uint)model.TriangleCount); Output.WriteUInt32(0); // TODO: This is not right, fix it Output.WriteUInt32((uint)model.MatrixIDtoNodeID.Length); Output.WriteByte(model.UsesNrmMtxArray ? (byte)1 : (byte)0); Output.WriteByte(model.UsesTexMtxArray ? (byte)1 : (byte)0); Output.AddPadding(2); Output.WriteInt32(calcInfo.MatrixIDtoNodeID - infoStructPos); Output.WriteVec3(model.Minimum); Output.WriteVec3(model.Maximum); // Matrix ID to Node ID data Output.WriteUInt32((uint)model.MatrixIDtoNodeID.Length); for (int i = 0; i < model.MatrixIDtoNodeID.Length; i++) { Output.WriteInt32(model.MatrixIDtoNodeID[i]); } // ResDicts WriteResDict(model.Bytecode, BytecodeOffsets); WriteResDict(model.Nodes, NodeOffsets); WriteResDict(model.VtxPosData, VtxPosOffsets); if (model.VtxNrmData.Count > 0) WriteResDict(model.VtxNrmData, VtxNrmOffsets); if (model.VtxClrData.Count > 0) WriteResDict(model.VtxClrData, VtxClrOffsets); if (model.VtxTexCoordData.Count > 0) WriteResDict(model.VtxTexCoordData, VtxTexCoordOffsets); WriteResDict(model.Materials, MaterialOffsets); WriteResDict(model.Shaders, ShaderOffsets); WriteResDict(model.Shapes, ShapeOffsets); if (model.PairingLookupByTexture.Count > 0) WriteResDict>(model.PairingLookupByTexture, PairingOffsets); // TODO: Palette pairing lookups WritePairings(model, model.PairingLookupByTexture); WriteBytecode(model); WriteNodes(model); WriteMaterials(model); WriteShaders(model); WriteShapes(model); WriteVertexData(model, model.VtxPosData); WriteVertexData(model, model.VtxNrmData); WriteVertexData(model, model.VtxClrData); WriteVertexData(model, model.VtxTexCoordData); } } } private void WritePairings(Model m, ResDict> dict) { foreach (var kv in dict) { int startPos = Output.Position; Output.WriteUInt32((uint)kv.Value.Count); foreach (var pairing in kv.Value) { // Material offset Output.WriteInt32(MaterialOffsets[pairing.Material] - startPos); // Texture info offset, points to the TextureInfo struct in the Material data Output.WriteInt32(TextureInfoOffsets[pairing.Texture] - startPos); } } } private void WriteBytecode(Model m) { foreach (var kv in m.Bytecode) { Debug.Send("Writing bytecode {0} @ offset {1:X}", kv.Key, Output.Position); ByteCode bc = kv.Value; foreach (var insn in bc.Instructions) { Output.WriteByte((byte)insn.GetOp()); switch (insn.GetOp()) { case ByteCode.OpType.None: break; case ByteCode.OpType.Done: break; case ByteCode.OpType.AssignNodeToParentMtx: var op2 = insn as ByteCode.AssignNodeToParentMtxInstruction; Output.WriteUInt16(op2.NodeID); Output.WriteUInt16(op2.ParentMatrixID); break; case ByteCode.OpType.BlendMatrices: var op3 = insn as ByteCode.BlendMatricesInstruction; Output.WriteUInt16(op3.MatrixID); Output.WriteByte((byte)op3.BlendedMatrices.Length); foreach (var bm in op3.BlendedMatrices) { Output.WriteUInt16(bm.MatrixID); Output.WriteFloat(bm.Ratio); } break; case ByteCode.OpType.DrawShape: var op4 = insn as ByteCode.DrawShapeInstruction; Output.WriteUInt16(op4.MaterialID); Output.WriteUInt16(op4.ShapeID); Output.WriteUInt16(op4.NodeID); Output.WriteByte(op4.Unk); break; case ByteCode.OpType.AssignMtxToNode: var op5 = insn as ByteCode.AssignMtxToNodeInstruction; Output.WriteUInt16(op5.MatrixID); Output.WriteUInt16(op5.NodeID); break; } } } Output.AlignTo(4); } private void WriteNodes(Model m) { int currentIndex = 0; foreach (var kv in m.Nodes) { Debug.Send("Writing node {0} @ offset {1:X}", kv.Key, Output.Position); Node node = kv.Value; int startPos = Output.Position; // Size, model offset, name offset, index // Reminds me... TODO: remove "Index" from the Node/Material/... classes Output.WriteUInt32(0xD0); Output.WriteInt32(ModelOffsets[m] - startPos); Output.WriteInt32(StringPositions[kv.Key] - startPos); Output.WriteUInt32((uint)currentIndex); Output.WriteUInt32(node.MatrixID); Output.WriteUInt32(node.Flags); Output.WriteUInt32((uint)node.BillboardMode); Output.WriteUInt32(0); // TODO: might be swapped with bbmode, check asm to confirm Output.WriteVec3(node.Scale); Output.WriteVec3(node.Rotation); Output.WriteVec3(node.Translation); Output.WriteVec3(node.BoxMin); Output.WriteVec3(node.BoxMax); // node offsets: parent, child, next, previous Output.WriteInt32((node.Parent == null) ? 0 : (NodeOffsets[node.Parent] - startPos)); Output.WriteInt32((node.FirstChild == null) ? 0 : (NodeOffsets[node.FirstChild] - startPos)); Output.WriteInt32((node.Next == null) ? 0 : (NodeOffsets[node.Next] - startPos)); Output.WriteInt32((node.Previous == null) ? 0 : (NodeOffsets[node.Previous] - startPos)); // extra data offset (currently unhandled) Output.WriteInt32(0); // matrices Output.WriteMatrix(node.NodeMatrix); Output.WriteMatrix(node.NodeInvMatrix); // done currentIndex++; } } private void WriteMaterials(Model m) { int currentIndex = 0; foreach (var kv in m.Materials) { Debug.Send("Writing material {0} @ offset {1:X}", kv.Key, Output.Position); Material mat = kv.Value; int startPos = Output.Position; // size, model offset, name offset, index var modelInfo = ModelCalcInfos[m]; Output.WriteUInt32((UInt32)modelInfo.MaterialSizes[mat]); Output.WriteInt32(ModelOffsets[m] - startPos); Output.WriteInt32(StringPositions[kv.Key] - startPos); Output.WriteUInt32((uint)currentIndex); Output.WriteUInt32(mat.Flags); // ResGenMode Output.WriteByte(mat.TexCoordGenCount); Output.WriteByte(mat.ChanCount); Output.WriteByte(mat.TevStageCount); Output.WriteByte(mat.IndStageCount); Output.WriteUInt32(mat.CullMode); // ResMatMisc Output.WriteByte(mat.ZCompLoc); Output.WriteByte(mat.LightSetID); Output.WriteByte(mat.FogID); // are these even correct? maybe not Output.WriteBytes(mat.IndirectTexMtxCalcMethod1); Output.WriteBytes(mat.IndirectTexMtxCalcMethod2); // padding -- this is weird, the previous two might be incorrect Output.WriteByte(0xFF); // more stuff: shader offset, texinfo count, texinfo offset, fur offset, unk offset, DL offset Output.WriteInt32(ShaderOffsets[mat.ShaderRef] - startPos); Output.WriteInt32(mat.TextureInfos.Count); if (mat.TextureInfos.Count > 0) Output.WriteInt32(TextureInfoOffsets[mat.TextureInfos[0]] - startPos); else Output.WriteInt32(0); Output.WriteInt32(0); Output.WriteInt32(0); Output.WriteInt32(MaterialDLOffsets[mat] - startPos); // ResTexObj UInt32 textureFlag = 0; int texObjPadding = 0x100; // first calculate the flag for (int i = 0; i < 8; i++) { if (mat.TexObj[i] != null) { textureFlag |= (uint)(1 << i); texObjPadding -= 0x20; } } // now write it Output.WriteUInt32(textureFlag); for (int i = 0; i < 8; i++) { if (mat.TexObj[i] != null) { Output.WriteBytes(mat.TexObj[i]); } } Output.AddPadding(texObjPadding); // ResTlutObj Output.WriteUInt32(mat.TlutFlag); if (mat.TlutObj == null) { Output.AddPadding(0x60); } else { Output.WriteBytes(mat.TlutObj); Output.AddPadding(0x60 - mat.TlutObj.Length); } // ResTexSrt // this one is a bit of a pain UInt32 srtFlag = 0; int unusedSlots = 8; // first, calculate the flag for (int i = 0; i < 8; i++) { byte thisFlag = 0; if (mat.SRTSettings[i] != null) { thisFlag |= 1; if (mat.SRTSettings[i].ScaleX == 1.0f && mat.SRTSettings[i].ScaleY == 1.0f) thisFlag |= 2; if (mat.SRTSettings[i].Rotate == 0.0f) thisFlag |= 4; if (mat.SRTSettings[i].TranslateX == 0.0f && mat.SRTSettings[i].TranslateY == 0.0f) thisFlag |= 8; unusedSlots--; } srtFlag |= ((uint)thisFlag << (i * 4)); } Output.WriteUInt32(srtFlag); Output.WriteUInt32(mat.TexMatrixType); // now write out TexSrt for (int i = 0; i < 8; i++) { if (mat.SRTSettings[i] != null) { Output.WriteFloat(mat.SRTSettings[i].ScaleX); Output.WriteFloat(mat.SRTSettings[i].ScaleY); Output.WriteFloat(mat.SRTSettings[i].Rotate); Output.WriteFloat(mat.SRTSettings[i].TranslateX); Output.WriteFloat(mat.SRTSettings[i].TranslateY); } } // frustratingly, the padding here is NOT a section of zeroes, but instead // a blank section (scale=1.0 rotate=0.0 translate=0.0). for (int i = 0; i < unusedSlots; i++) { Output.WriteFloat(1.0f); Output.WriteFloat(1.0f); Output.WriteFloat(0.0f); Output.WriteFloat(0.0f); Output.WriteFloat(0.0f); } // then write out TexSet for (int i = 0; i < 8; i++) { if (mat.SRTSettings[i] != null) { Output.WriteByte(mat.SRTSettings[i].CameraID); Output.WriteByte(mat.SRTSettings[i].LightID); Output.WriteByte(mat.SRTSettings[i].MapType); Output.WriteByte(mat.SRTSettings[i].Flags); Output.WriteMatrix(mat.SRTSettings[i].TexMatrix); } } // and TexSet does the same thing for padding -_- for (int i = 0; i < unusedSlots; i++) { Output.WriteByte(0xFF); Output.WriteByte(0xFF); Output.WriteByte(0x00); Output.WriteByte(0x01); Output.WriteFloat(1.0f); Output.WriteFloat(0.0f); Output.WriteFloat(0.0f); Output.WriteFloat(0.0f); Output.WriteFloat(0.0f); Output.WriteFloat(1.0f); Output.WriteFloat(0.0f); Output.WriteFloat(0.0f); Output.WriteFloat(0.0f); Output.WriteFloat(0.0f); Output.WriteFloat(1.0f); Output.WriteFloat(0.0f); } // ResMatChan for (int i = 0; i < 2; i++) { Output.WriteUInt32(mat.ChanCtrls[i].Flags); Output.WriteColor(mat.ChanCtrls[i].MatColor); Output.WriteColor(mat.ChanCtrls[i].AmbColor); Output.WriteUInt32(mat.ChanCtrls[i].FlagC); Output.WriteUInt32(mat.ChanCtrls[i].FlagA); } // Texture Infos foreach (var texInfo in mat.TextureInfos) { int texInfoPos = Output.Position; Output.WriteInt32(texInfo.TextureName == null ? 0 : (StringPositions[texInfo.TextureName] - texInfoPos)); Output.WriteInt32(texInfo.PaletteName == null ? 0 : (StringPositions[texInfo.PaletteName] - texInfoPos)); // placeholder pointers Output.WriteUInt32(0); Output.WriteUInt32(0); Output.WriteUInt32(texInfo.TexMapID); Output.WriteUInt32(texInfo.TlutID); Output.WriteUInt32((uint)texInfo.WrapS); Output.WriteUInt32((uint)texInfo.WrapT); Output.WriteUInt32(texInfo.MinFilt); Output.WriteUInt32(texInfo.MagFilt); Output.WriteFloat(texInfo.LODBias); Output.WriteUInt32(texInfo.MaxAniso); Output.WriteBool(texInfo.BiasClamp); Output.WriteBool(texInfo.DoEdgeLOD); Output.AddPadding(2); } // Display Lists Output.AlignTo(0x20); if (mat.PixDL.Length != 0x20) throw new Exception("Material " + kv.Key + " PixDL must be size 0x20"); if (mat.TevColorDL.Length != 0x80) throw new Exception("Material " + kv.Key + " TevColorDL must be size 0x80"); if (mat.IndMtxAndScaleDL.Length != 0x40) throw new Exception("Material " + kv.Key + " IndMtxAndScaleDL must be size 0x40"); if (mat.TexCoordGenDL.Length != 0xA0) throw new Exception("Material " + kv.Key + " TexCoordGenDL must be size 0xA0"); Output.WriteBytes(mat.PixDL); Output.WriteBytes(mat.TevColorDL); Output.WriteBytes(mat.IndMtxAndScaleDL); Output.WriteBytes(mat.TexCoordGenDL); // I can't believe this is done currentIndex++; } } private void WriteShaders(Model m) { int currentIndex = 0; foreach (var shader in ModelCalcInfos[m].UniqueShaders) { Debug.Send("Writing shader @ offset {0:X}", Output.Position); int startPos = Output.Position; // Size, model offset, index Output.WriteUInt32((uint)(0x20 + shader.DisplayList.Length)); Output.WriteInt32(ModelOffsets[m] - startPos); // What do I write here...? Output.WriteUInt32((uint)currentIndex); Output.WriteByte(shader.TevStageCount); Output.AddPadding(3); Output.WriteUInt32(shader.Unk1); Output.WriteUInt32(shader.Unk2); Output.AddPadding(8); Output.WriteBytes(shader.DisplayList); // done currentIndex++; } } private void WriteShapes(Model m) { int currentIndex = 0; foreach (var kv in m.Shapes) { Debug.Send("Writing shape {0} @ offset {1:X}", kv.Key, Output.Position); Shape shape = kv.Value; int startPos = Output.Position; // Size, model offset, matrix ID, unknown data Output.WriteUInt32((uint)ShapeSizes[shape]); Output.WriteInt32(ModelOffsets[m] - startPos); Output.WriteInt32(shape.MatrixID); Output.WriteBytes(shape.Unk); // Display Lists int dlBase1 = Output.Position; Output.WriteUInt32(shape.DLBufferSize1); Output.WriteUInt32((uint)shape.DisplayList1.Length); Output.WriteInt32(ShapeDL1Offsets[shape] - dlBase1); int dlBase2 = Output.Position; Output.WriteUInt32(shape.DLBufferSize2); Output.WriteUInt32((uint)shape.DisplayList2.Length); Output.WriteInt32(ShapeDL2Offsets[shape] - dlBase2); // Indexes and flags and other stuff Output.WriteUInt32(shape.DataFlags); Output.WriteUInt32(shape.Flags); Output.WriteInt32(StringPositions[kv.Key] - startPos); Output.WriteUInt32((uint)currentIndex); Output.WriteUInt32(shape.VertexCount); Output.WriteUInt32(shape.PolygonCount); Output.WriteInt16((short)m.VtxPosData.GetIndexForValue(shape.PosData)); if (shape.NrmData != null) Output.WriteInt16((short)m.VtxNrmData.GetIndexForValue(shape.NrmData)); else Output.WriteInt16(-1); for (int i = 0; i < 2; i++) { if (shape.ClrData[i] != null) Output.WriteInt16((short)m.VtxClrData.GetIndexForValue(shape.ClrData[i])); else Output.WriteInt16(-1); } for (int i = 0; i < 8; i++) { if (shape.TexCoordData[i] != null) Output.WriteInt16((short)m.VtxTexCoordData.GetIndexForValue(shape.TexCoordData[i])); else Output.WriteInt16(-1); } // Indexes for VtxFurVec and VtxFurPos, not used here Output.WriteInt16(-1); Output.WriteInt16(-1); // Extra data offset (fixed) Output.WriteInt32(0x68); // Extra data Output.WriteInt32(shape.ExtraData.Length); for (int i = 0; i < shape.ExtraData.Length; i++) Output.WriteUInt16(shape.ExtraData[i]); // Display lists // Padding is added to fill up the buffer (Display List data size can be smaller than the buffer size) Output.AlignTo(0x20); Output.WriteBytes(shape.DisplayList1); Output.AddPadding((int)(shape.DLBufferSize1 - shape.DisplayList1.Length)); Output.AlignTo(0x20); Output.WriteBytes(shape.DisplayList2); Output.AddPadding((int)(shape.DLBufferSize2 - shape.DisplayList2.Length)); Output.AlignTo(0x20); // done! currentIndex++; } } private void WriteVertexData(Model m, ResDict dict) where T : VertexDataBase { int currentIndex = 0; foreach (var kv in dict) { Debug.Send("Writing vtxdata {0} @ offset {1:X}", kv.Key, Output.Position); int startPos = Output.Position; int structSize = 0x20; if (kv.Value is VertexPosData || kv.Value is VertexTexCoordData) { // Min/max fields, plus alignment structSize += 0x20; } // Size, model offset, data offset, name offset, index // For some reason, the raw data length added to the size is aligned... Output.WriteUInt32((uint)(((structSize + kv.Value.RawData.Length) + 0x1F) & ~0x1F)); Output.WriteInt32(ModelOffsets[m] - startPos); Output.WriteInt32(structSize); Output.WriteInt32(StringPositions[kv.Key] - startPos); Output.WriteUInt32((uint)currentIndex); // Parameters Output.WriteUInt32((uint)kv.Value.ComponentCount); Output.WriteUInt32((uint)kv.Value.ComponentType); if (kv.Value is VertexClrData) { Output.WriteByte(kv.Value.EntrySize); Output.AddPadding(1); } else { Output.WriteByte(kv.Value.Fraction); Output.WriteByte(kv.Value.EntrySize); } Output.WriteUInt16(kv.Value.EntryCount); // Type-specific stuff if (kv.Value is VertexPosData) { var posData = kv.Value as VertexPosData; Output.WriteVec3(posData.Minimum); Output.WriteVec3(posData.Maximum); } else if (kv.Value is VertexTexCoordData) { var tcData = kv.Value as VertexTexCoordData; Output.WriteVec2(tcData.Minimum); Output.WriteVec2(tcData.Maximum); } Output.AlignTo(0x20); Output.WriteBytes(kv.Value.RawData); Output.AlignTo(0x20); // done! currentIndex++; } } #endregion #region Texture Writing private void WriteTextures() { foreach (var kv in File.GetGroup("Textures(NW4R)")) { using (var c = Debug.Push("Texture: {0}", kv.Key)) { Output.AlignTo(0x20); int startPos = Output.Position; Texture tex = kv.Value; Misc.Assert(startPos == TextureOffsets[tex]); // Base struct: magic 'TEX0', block size, version[currently 3], resfile offset Output.WriteUInt32(0x54455830); Output.WriteUInt32((uint)(0x40 + tex.GetDataSize())); Output.WriteUInt32(3); Output.WriteInt32(-startPos); // Data offset, name offset Output.WriteInt32(0x40); Output.WriteInt32(StringPositions[kv.Key] - startPos); // Flags -- Stores nothing interesting, just an "is CI" flag (0x1) // We don't handle that atm, so ignore it Output.WriteUInt32(0); Output.WriteInt16((short)tex.Images[0].Width); Output.WriteInt16((short)tex.Images[0].Height); Output.WriteUInt32((uint)tex.Format); Output.WriteInt32(tex.Images.Length); Output.WriteFloat(tex.MinLOD); Output.WriteFloat(tex.MaxLOD); Output.AlignTo(0x20); for (int i = 0; i < tex.Images.Length; i++) #if DEV_DONT_MODIFY_TEXTURE Output.WriteBytes(tex.ImageData[i]); #else Output.WriteBytes(tex.ExportData(i)); #endif } } } #endregion #region Character Animation Writing private void WriteAnmChr() { foreach (var kv in File.GetGroup("AnmChr(NW4R)")) { using (var c = Debug.Push("Character Animation: {0}", kv.Key)) { Output.AlignTo(0x20); int startPos = Output.Position; CharacterAnim anm = kv.Value; var info = ChrAnmInfos[anm]; Misc.Assert(startPos == ChrAnmOffsets[anm]); // Base struct: magic 'CHR0', block size, version[currently 5], resfile offset Output.WriteUInt32(0x43485230); Output.WriteUInt32((UInt32)info.BlockSize); Output.WriteUInt32(5); Output.WriteInt32(-startPos); // Data offset, name offset, other crap Output.WriteInt32(0x2C); Output.WriteUInt32(0); Output.WriteInt32(StringPositions[kv.Key] - startPos); Output.WriteUInt32(0); Output.WriteUInt16((UInt16)anm.FrameCount); Output.WriteUInt16((UInt16)anm.Nodes.Count); Output.WriteUInt32(anm.Loop ? 1U : 0U); Output.WriteUInt32((UInt32)anm.ScaleMode); WriteResDict(anm.Nodes, info.NodeOffsets); WriteAnmChrNodes(anm); WriteAnmChrKeyframes(anm); } } } private void WriteAnmChrNodes(CharacterAnim anm) { var info = ChrAnmInfos[anm]; foreach (var kv in anm.Nodes) { using (var c = Debug.Push("Element: {0}", kv.Key)) { int startPos = Output.Position; CharacterAnim.Node node = kv.Value; ChrAnmNodeInfo nodeinfo = info.NodeInfo[node]; Misc.Assert(startPos == info.NodeOffsets[node]); Output.WriteInt32(StringPositions[kv.Key] - startPos); Output.WriteUInt32((UInt32)nodeinfo.Flags); if ((nodeinfo.Flags & CharacterAnim.Flags.ScaleXNotExist) == 0) { if (node.ScaleX.IsConstant) Output.WriteFloat(node.ScaleX.ConstValue); else Output.WriteInt32(info.KFInfo.GetPosition(node.ScaleFormat, node.ScaleX) - startPos); } if ((nodeinfo.Flags & CharacterAnim.Flags.ScaleYNotExist) == 0) { if (node.ScaleY.IsConstant) Output.WriteFloat(node.ScaleY.ConstValue); else Output.WriteInt32(info.KFInfo.GetPosition(node.ScaleFormat, node.ScaleY) - startPos); } if ((nodeinfo.Flags & CharacterAnim.Flags.ScaleZNotExist) == 0) { if (node.ScaleZ.IsConstant) Output.WriteFloat(node.ScaleZ.ConstValue); else Output.WriteInt32(info.KFInfo.GetPosition(node.ScaleFormat, node.ScaleZ) - startPos); } if ((nodeinfo.Flags & CharacterAnim.Flags.RotateXNotExist) == 0) { if (node.RotateX.IsConstant) Output.WriteFloat(node.RotateX.ConstValue); else Output.WriteInt32(info.KFInfo.GetPosition(node.RotateFormat, node.RotateX) - startPos); } if ((nodeinfo.Flags & CharacterAnim.Flags.RotateYNotExist) == 0) { if (node.RotateY.IsConstant) Output.WriteFloat(node.RotateY.ConstValue); else Output.WriteInt32(info.KFInfo.GetPosition(node.RotateFormat, node.RotateY) - startPos); } if ((nodeinfo.Flags & CharacterAnim.Flags.RotateZNotExist) == 0) { if (node.RotateZ.IsConstant) Output.WriteFloat(node.RotateZ.ConstValue); else Output.WriteInt32(info.KFInfo.GetPosition(node.RotateFormat, node.RotateZ) - startPos); } if ((nodeinfo.Flags & CharacterAnim.Flags.TranslateXNotExist) == 0) { if (node.TranslateX.IsConstant) Output.WriteFloat(node.TranslateX.ConstValue); else Output.WriteInt32(info.KFInfo.GetPosition(node.TranslateFormat, node.TranslateX) - startPos); } if ((nodeinfo.Flags & CharacterAnim.Flags.TranslateYNotExist) == 0) { if (node.TranslateY.IsConstant) Output.WriteFloat(node.TranslateY.ConstValue); else Output.WriteInt32(info.KFInfo.GetPosition(node.TranslateFormat, node.TranslateY) - startPos); } if ((nodeinfo.Flags & CharacterAnim.Flags.TranslateZNotExist) == 0) { if (node.TranslateZ.IsConstant) Output.WriteFloat(node.TranslateZ.ConstValue); else Output.WriteInt32(info.KFInfo.GetPosition(node.TranslateFormat, node.TranslateZ) - startPos); } } } } private void WriteAnmChrKeyframes(CharacterAnim anm) { var anminfo = ChrAnmInfos[anm]; foreach (var kv in anminfo.KFInfo.KeyframeAnimOffsets) { var format = (CharacterAnim.RotateFormatType)kv.Key.one; var kf = kv.Key.two; var expectedOffset = kv.Value; Misc.Assert(Output.Position == expectedOffset); if (format == CharacterAnim.RotateFormatType.Data32 || format == CharacterAnim.RotateFormatType.Data48 || format == CharacterAnim.RotateFormatType.Data96) { // Header for FVS Output.WriteUInt16((UInt16)kf.Keyframes.Length); Output.AddPadding(2); float delta = kf.Keyframes[kf.Keyframes.Length - 1].Frame - kf.Keyframes [0].Frame; float invKeyFrameRange = (1.0f / delta); Output.WriteFloat(invKeyFrameRange); } if (format == CharacterAnim.RotateFormatType.DataFrm8 || format == CharacterAnim.RotateFormatType.DataFrm16) { // Header for CV 8 and 16 Output.WriteFloat(kf.Multiplier); Output.WriteFloat(kf.BaseValue); Misc.Assert(kf.Values.Length == (anm.FrameCount + 1)); } if (format == CharacterAnim.RotateFormatType.DataFrm32) { // Assert this Misc.Assert(kf.FloatValues.Length == (anm.FrameCount + 1)); } switch (format) { case CharacterAnim.RotateFormatType.Data32: for (int i = 0; i < kf.Keyframes.Length; i++) { Output.WriteUInt32( (((UInt32)kf.Keyframes[i].Frame) << 24) | ((((UInt32)kf.Keyframes[i].Value) & 0xFFF) << 12) | (((UInt32)(kf.Keyframes[i].Slope * 32.0f)) & 0xFFF)); } break; case CharacterAnim.RotateFormatType.Data48: for (int i = 0; i < kf.Keyframes.Length; i++) { Output.WriteInt16((Int16)(kf.Keyframes[i].Frame * 32.0f)); Output.WriteUInt16((UInt16)(kf.Keyframes[i].Value / (360.0f / 0x10000))); Output.WriteInt16((Int16)(kf.Keyframes[i].Slope * 256.0f)); } break; case CharacterAnim.RotateFormatType.Data96: for (int i = 0; i < kf.Keyframes.Length; i++) { Output.WriteFloat(kf.Keyframes[i].Frame); Output.WriteFloat(kf.Keyframes[i].Value); Output.WriteFloat(kf.Keyframes[i].Slope); } break; case CharacterAnim.RotateFormatType.DataFrm8: for (int i = 0; i < (anm.FrameCount + 1); i++) { Output.WriteByte((byte)kf.Values[i]); } break; case CharacterAnim.RotateFormatType.DataFrm16: for (int i = 0; i < (anm.FrameCount + 1); i++) { Output.WriteUInt16((ushort)kf.Values[i]); } break; case CharacterAnim.RotateFormatType.DataFrm32: for (int i = 0; i < (anm.FrameCount + 1); i++) { Output.WriteFloat(kf.FloatValues[i]); } break; default: throw new NotImplementedException(String.Format("Unknown format: {0}", format)); } Output.AlignTo(4); } } #endregion #region Color Animation Writing private void WriteAnmClr() { foreach (var kv in File.GetGroup("AnmClr(NW4R)")) { using (var c = Debug.Push("Color Animation: {0}", kv.Key)) { int startPos = Output.Position; ColorAnim anm = kv.Value; var info = ClrAnmInfos[anm]; Misc.Assert(startPos == ClrAnmOffsets[anm]); // Base struct: magic 'CLR0', block size, version[currently 5], resfile offset Output.WriteUInt32(0x434C5230); Output.WriteUInt32((UInt32)info.BlockSize); Output.WriteUInt32(4); Output.WriteInt32(-startPos); // Data offset, name offset, other crap Output.WriteInt32(0x28); Output.WriteUInt32(0); Output.WriteInt32(StringPositions[kv.Key] - startPos); Output.WriteUInt32(0); Output.WriteUInt16((UInt16)anm.FrameCount); Output.WriteUInt16((UInt16)anm.Nodes.Count); Output.WriteUInt32(anm.Loop ? 1U : 0U); WriteResDict(anm.Nodes, info.NodeOffsets); WriteAnmClrNodes(anm); WriteAnmClrArrays(anm); } } } private void WriteAnmClrNodes(ColorAnim anm) { ClrAnmInfo info = ClrAnmInfos[anm]; foreach (var kv in anm.Nodes) { using (var c = Debug.Push("Element: {0}", kv.Key)) { int startPos = Output.Position; ColorAnim.Node node = kv.Value; Misc.Assert(startPos == info.NodeOffsets[node]); Output.WriteInt32(StringPositions[kv.Key] - startPos); UInt32 flags = 0; for (int i = node.Elements.Length - 1; i >= 0; i--) { flags <<= 2; if (node.Elements[i].Exists) flags |= 1; if (node.Elements[i].IsConstant) flags |= 2; } Output.WriteUInt32(flags); for (int i = 0; i < node.Elements.Length; i++) { if (node.Elements[i].Exists) { Output.WriteUInt32(node.Elements[i].Mask); if (node.Elements[i].IsConstant) { Output.WriteColor(node.Elements[i].ConstValue); } else { // Search for the matching array int find = 0; bool wasFound = false; foreach (var arr in info.UsedArrays) { if (Misc.ArrayCompare(node.Elements[i].Colors, arr)) { wasFound = true; break; } find++; } if (!wasFound) throw new Exception(); int arrayPos = info.UsedArrayPositions[find]; Output.WriteInt32(arrayPos - Output.Position); } } } } } } private void WriteAnmClrArrays(ColorAnim anm) { var info = ClrAnmInfos[anm]; int i = 0; foreach (var arr in info.UsedArrays) { Misc.Assert(info.UsedArrayPositions[i] == Output.Position); for (int j = 0; j < arr.Length; j++) Output.WriteColor(arr[j]); i++; } } #endregion #region Texture SRT Animation Writing private void WriteAnmTexSrt() { foreach (var kv in File.GetGroup("AnmTexSrt(NW4R)")) { using (var c = Debug.Push("TextureSRT Animation: {0}", kv.Key)) { Output.AlignTo(0x20); int startPos = Output.Position; TextureSRTAnim anm = kv.Value; var info = TexSRTAnmInfos[anm]; Misc.Assert(startPos == TexSRTAnmOffsets[anm]); // Base struct: magic 'SRT0', block size, version[currently 5], resfile offset Output.WriteUInt32(0x53525430); Output.WriteUInt32((UInt32)info.BlockSize); Output.WriteUInt32(5); Output.WriteInt32(-startPos); // Data offset, name offset, other crap Output.WriteInt32(0x2C); Output.WriteUInt32(0); Output.WriteInt32(StringPositions[kv.Key] - startPos); Output.WriteUInt32(0); Output.WriteUInt16((UInt16)anm.FrameCount); Output.WriteUInt16((UInt16)anm.Nodes.Count); Output.WriteUInt32((UInt32)anm.MatrixMode); Output.WriteUInt32(anm.Loop ? 1U : 0U); WriteResDict(anm.Nodes, info.NodeOffsets); WriteAnmTexSrtNodes(anm); WriteKeyframeData(info.KFInfo); } } } private void WriteAnmTexSrtNodes(TextureSRTAnim anm) { var info = TexSRTAnmInfos[anm]; foreach (var kv in anm.Nodes) { using (var c = Debug.Push("Element: {0}", kv.Key)) { int startPos = Output.Position; TextureSRTAnim.Node node = kv.Value; TexSRTAnmNodeInfo nodeinfo = info.NodeInfo[node]; Misc.Assert(startPos == info.NodeOffsets[node]); Output.WriteInt32(StringPositions[kv.Key] - startPos); UInt32 flags = 0, indFlags = 0; for (int i = 0; i < 8; i++) if (node.Textures[i].Exists) flags |= (UInt32)(1 << i); for (int i = 0; i < 3; i++) if (node.IndirectTextures[i].Exists) indFlags |= (UInt32)(1 << i); Output.WriteUInt32(flags); Output.WriteUInt32(indFlags); var elemsToAddLater = new List(); for (int i = 0; i < 8; i++) if (node.Textures[i].Exists) { Output.WriteInt32(nodeinfo.TextureOffsets[i] - startPos); elemsToAddLater.Add(node.Textures[i]); } for (int i = 0; i < 3; i++) if (node.IndirectTextures[i].Exists) { Output.WriteInt32(nodeinfo.IndirectTextureOffsets[i] - startPos); elemsToAddLater.Add(node.IndirectTextures[i]); } foreach (var elem in elemsToAddLater) WriteAnmTexSrtElement(elem, info.KFInfo); } } } private void WriteAnmTexSrtElement(TextureSRTAnim.Element elem, KeyframeWriterInfo kfinfo) { UInt32 flags = 1; int flagPos = Output.Position; Output.WriteUInt32(0); // placeholder! if (elem.ScaleS.IsConstWith(1) && elem.ScaleT.IsConstWith(1)) { flags |= 2 | 0x10 | 0x20 | 0x40; } else { WriteKeyframeAnim(elem.ScaleS, kfinfo); if (elem.ScaleS.IsConstant) flags |= 0x20; if (elem.ScaleS == elem.ScaleT) { flags |= 0x10; if (elem.ScaleS.IsConstant) flags |= 0x40; } else { WriteKeyframeAnim(elem.ScaleT, kfinfo); if (elem.ScaleT.IsConstant) flags |= 0x40; } } if (elem.Rotate.IsConstWith(0)) { flags |= 4 | 0x80; } else { WriteKeyframeAnim(elem.Rotate, kfinfo); if (elem.Rotate.IsConstant) flags |= 0x80; } if (elem.TransS.IsConstWith(0) && elem.TransT.IsConstWith(0)) { flags |= 8 | 0x100 | 0x200; } else { WriteKeyframeAnim(elem.TransS, kfinfo); WriteKeyframeAnim(elem.TransT, kfinfo); if (elem.TransS.IsConstant) flags |= 0x100; if (elem.TransT.IsConstant) flags |= 0x200; } // Replace the flags int savePos = Output.Position; Output.Seek(flagPos); Output.WriteUInt32(flags); Output.Seek(savePos); } #endregion #region Keyframe Data Writing private void WriteKeyframeAnim(KeyframeAnim anim, KeyframeWriterInfo kfinfo) { if (anim.IsConstant) Output.WriteFloat(anim.ConstValue); else Output.WriteInt32(kfinfo.GetPosition(anim) - Output.Position); } private void WriteKeyframeData(KeyframeWriterInfo kfinfo) { foreach (var pair in kfinfo.KeyframeAnims) { var anim = pair.two; var kf = pair.two; var expectedOffset = kfinfo.KeyframeAnimOffsets[pair]; Misc.Assert(expectedOffset == Output.Position); Output.WriteUInt16((UInt16)kf.Keyframes.Length); Output.AddPadding(2); float delta = kf.Keyframes[kf.Keyframes.Length - 1].Frame - kf.Keyframes[0].Frame; float invKeyFrameRange = (1.0f / delta); Output.WriteFloat(invKeyFrameRange); for (int i = 0; i < kf.Keyframes.Length; i++) { Output.WriteFloat(kf.Keyframes[i].Frame); Output.WriteFloat(kf.Keyframes[i].Value); Output.WriteFloat(kf.Keyframes[i].Slope); } } } #endregion #region ResDicts private struct RawDictEntry { public ushort Ref; public ushort Unk; public ushort ILeft; public ushort IRight; } private unsafe void WriteResDict(ResDict dict, Dictionary positions) { int dictPos = Output.Position; // First, I've got to build an in-memory representation of the raw ResDict. // Next, I've got to write it out. This'll be fun. // This is partly based off the BrawlLib code. RawDictEntry[] rd = new RawDictEntry[dict.Count + 1]; byte[][] encodedNames = new byte[dict.Count + 1][]; // Before the actual calculation, build that list rd[0].Ref = 0xFFFF; rd[0].Unk = 0; rd[0].ILeft = 0; rd[0].IRight = 0; encodedNames[0] = new byte[] { }; for (int i = 1; i <= dict.Count; i++) { rd[i].Ref = 0; rd[i].Unk = 0; rd[i].ILeft = 0; rd[i].IRight = 0; // I wanted to store the encoded name as a member of RawDictEntry, but C# doesn't let me // get a pointer to the struct if I do that -- see Compiler Error CS0208 on MSDN. string theName = dict.GetKeyForIndex(i - 1); encodedNames[i] = System.Text.Encoding.GetEncoding("Shift_JIS").GetBytes(theName); } // Now calculate indexes, etc for (ushort i = 1; i <= dict.Count; i++) { // Using unsafe pointers for convenience fixed (RawDictEntry* entry = &rd[i]) { ushort prev = 0; ushort current = rd[prev].ILeft; bool isRight = false; int strLen = encodedNames[i].Length; byte[] pChar = encodedNames[i]; byte[] sChar; int eIndex = strLen - 1; int eBits = CompareBits(pChar[eIndex], 0); int val; entry->Ref = (ushort)((eIndex << 3) | eBits); entry->ILeft = (ushort)i; entry->IRight = (ushort)i; while ((entry->Ref <= rd[current].Ref) && (rd[prev].Ref > rd[current].Ref)) { if (entry->Ref == rd[current].Ref) { sChar = encodedNames[current]; for (eIndex = strLen; (--eIndex > 0) && (pChar[eIndex] == sChar[eIndex]);); eBits = CompareBits(pChar[eIndex], sChar[eIndex]); entry->Ref = (ushort)((eIndex << 3) | eBits); if (((sChar[eIndex] >> eBits) & 1) != 0) { entry->ILeft = (ushort)i; entry->IRight = current; } else { entry->ILeft = current; entry->IRight = (ushort)i; } } isRight = ((val = rd[current].Ref >> 3) < strLen) && (((pChar[val] >> (rd[current].Ref & 7)) & 1) != 0); prev = current; current = isRight ? rd[current].IRight : rd[current].ILeft; } sChar = encodedNames[current]; val = sChar == null ? 0 : sChar.Length; if ((val == strLen) && (((sChar[eIndex] >> eBits) & 1) != 0)) entry->IRight = current; else entry->ILeft = current; if (isRight) rd[prev].IRight = i; else rd[prev].ILeft = i; } } // Now write it Output.WriteUInt32((uint)(8 + (rd.Length * 0x10))); Output.WriteUInt32((uint)(rd.Length - 1)); for (int entryID = 0; entryID < rd.Length; entryID++) { Output.WriteUInt16(rd[entryID].Ref); Output.WriteUInt16(rd[entryID].Unk); Output.WriteUInt16(rd[entryID].ILeft); Output.WriteUInt16(rd[entryID].IRight); if (entryID == 0) { Output.WriteInt32(0); Output.WriteInt32(0); } else { // Name offset Output.WriteInt32(StringPositions[dict.GetKeyForIndex(entryID - 1)] - dictPos); // Data offset Output.WriteInt32(positions[dict[entryID - 1]] - dictPos); } } } private static int CompareBits(byte b1, byte b2) { int b = 0x80; for (int i = 7; i != 0; i--) { if ((b1 & b) != (b2 & b)) return i; b >>= 1; } return 0; } #endregion #endregion #region String Table Handling // Contains the strings while they're being pulled from the ResFile data private List StringTable; // Once every string used in the .brres is found, CalculateStringTable() is called, which // writes the table to an OutputStream and saves every offset private Dictionary StringPositions; private OutputStream StringTableData; // Called at the start of the process private void InitialiseStringTable() { StringTable = new List(); } private void AddString(string str) { if (!StringTable.Contains(str)) { StringTable.Add(str); } } private void CalculateStringTable() { // Hmm... this doesn't work for whatever reason? Might be a Mono thing. //StringTable.Sort(StringComparer.Create(System.Globalization.CultureInfo.InvariantCulture, false)); StringTable.Sort(StringComparer.Ordinal); StringPositions = new Dictionary(); StringTableData = new OutputStream(ByteEndian.BigEndian); // Note: once all calculation is done, CurrentPos stores the address of the string table foreach (var s in StringTable) { // Add 4 because the Names are referenced by the string data, and not by the ResName struct itself StringPositions[s] = CurrentPos + StringTableData.Position + 4; StringTableData.WriteName(s); } } #endregion } }