internal void Write(BinaryWriterEx bw, MQBVersion version, Dictionary <Disposition, long> offsetsByDispos, int cutIndex, List <CustomData> allCustomData, List <long> customDataValueOffsets) { int disposCount = Timelines.Sum(g => g.Dispositions.Count); bw.WriteFixStrW(Name, 0x40, 0x00); bw.WriteInt32(disposCount); bw.WriteInt32(Unk44); bw.WriteInt32(Duration); bw.WriteInt32(0); bw.WriteInt32(Timelines.Count); if (version == MQBVersion.DarkSouls2Scholar) { bw.WriteInt32(0); } bw.ReserveVarint($"TimelinesOffset{cutIndex}"); if (version != MQBVersion.DarkSouls2Scholar) { bw.WriteInt64(0); } foreach (Timeline timeline in Timelines) { timeline.WriteDispositions(bw, offsetsByDispos, allCustomData, customDataValueOffsets); } }
internal void Write(BinaryWriterEx bw, PARAMDEF def, int index) { if (def.Unicode) { bw.WriteFixStrW(DisplayName, 0x40, (byte)(def.Version >= 104 ? 0x00 : 0x20)); } else { bw.WriteFixStr(DisplayName, 0x40, (byte)(def.Version >= 104 ? 0x00 : 0x20)); } byte padding = (byte)(def.Version >= 201 ? 0x00 : 0x20); bw.WriteFixStr(DisplayType.ToString(), 8, padding); bw.WriteFixStr(DisplayFormat, 8, padding); bw.WriteSingle(Default); bw.WriteSingle(Minimum); bw.WriteSingle(Maximum); bw.WriteSingle(Increment); bw.WriteInt32((int)EditFlags); bw.WriteInt32(ParamUtil.GetValueSize(DisplayType) * (ParamUtil.IsArrayType(DisplayType) ? ArrayLength : 1)); if (def.Version >= 201) { bw.ReserveInt64($"DescriptionOffset{index}"); } else { bw.ReserveInt32($"DescriptionOffset{index}"); } bw.WriteFixStr(InternalType, 0x20, padding); if (def.Version >= 102) { string internalName = InternalName; // This is accurate except for "hasTarget : 1" in SpEffect if (BitSize != -1) { internalName = $"{internalName}:{BitSize}"; } // BB is not consistent about including [1] or not, but PTDE always does else if (ParamUtil.IsArrayType(DisplayType)) { internalName = $"{internalName}[{ArrayLength}]"; } bw.WriteFixStr(internalName, 0x20, padding); } if (def.Version >= 104) { bw.WriteInt32(SortID); } if (def.Version >= 201) { bw.WritePattern(0x1C, 0x00); } }
internal void Write(BinaryWriterEx bw, int resourceIndex, List <CustomData> allCustomData, List <long> customDataValueOffsets) { bw.WriteFixStrW(Name, 0x40, 0x00); bw.WriteInt32(ParentIndex); bw.WriteInt32(resourceIndex); bw.WriteInt32(Unk48); bw.WriteInt32(CustomData.Count); foreach (CustomData customData in CustomData) { customData.Write(bw, allCustomData, customDataValueOffsets); } }
internal void Write(BinaryWriterEx bw, PARAMDEF def, int index) { if (def.Unicode) { bw.WriteFixStrW(DisplayName, 0x40, (byte)(def.Version >= 104 ? 0x00 : 0x20)); } else { bw.WriteFixStr(DisplayName, 0x40, (byte)(def.Version >= 104 ? 0x00 : 0x20)); } byte padding = (byte)(def.Version >= 201 ? 0x00 : 0x20); bw.WriteFixStr(DisplayType.ToString(), 8, padding); bw.WriteFixStr(DisplayFormat, 8, padding); bw.WriteSingle(Default); bw.WriteSingle(Minimum); bw.WriteSingle(Maximum); bw.WriteSingle(Increment); bw.WriteInt32((int)EditFlags); bw.WriteInt32(ByteCount); if (def.Version >= 201) { bw.ReserveInt64($"DescriptionOffset{index}"); } else { bw.ReserveInt32($"DescriptionOffset{index}"); } bw.WriteFixStr(InternalType, 0x20, padding); if (def.Version >= 102) { bw.WriteFixStr(InternalName, 0x20, padding); } if (def.Version >= 104) { bw.WriteInt32(SortID); } if (def.Version >= 201) { bw.WritePattern(0x1C, 0x00); } }
internal void WriteCells(BinaryWriterEx bw, int i, Layout layout) { bw.FillInt64($"RowOffset{i}", bw.Position); for (int j = 0; j < layout.Count; j++) { Cell cell = Cells[j]; Layout.Entry entry = layout[j]; CellType type = entry.Type; object value = cell.Value; if (entry.Name != cell.Name || type != cell.Type) { throw new FormatException("Layout does not match cells."); } if (type == CellType.s8) { bw.WriteSByte((sbyte)value); } else if (type == CellType.u8 || type == CellType.x8) { bw.WriteByte((byte)value); } else if (type == CellType.s16) { bw.WriteInt16((short)value); } else if (type == CellType.u16 || type == CellType.x16) { bw.WriteUInt16((ushort)value); } else if (type == CellType.s32) { bw.WriteInt32((int)value); } else if (type == CellType.u32 || type == CellType.x32) { bw.WriteUInt32((uint)value); } else if (type == CellType.f32) { bw.WriteSingle((float)value); } else if (type == CellType.dummy8) { bw.WriteBytes((byte[])value); } else if (type == CellType.fixstr) { bw.WriteFixStr((string)value, entry.Size); } else if (type == CellType.fixstrW) { bw.WriteFixStrW((string)value, entry.Size); } else if (type == CellType.b8) { byte b = 0; int k; for (k = 0; k < 8; k++) { if (j + k >= layout.Count || layout[j + k].Type != CellType.b8) { break; } if ((bool)Cells[j + k].Value) { b |= (byte)(1 << k); } } j += k - 1; bw.WriteByte(b); } else if (type == CellType.b32) { byte[] b = new byte[4]; int k; for (k = 0; k < 32; k++) { if (j + k >= layout.Count || layout[j + k].Type != CellType.b32) { break; } if ((bool)Cells[j + k].Value) { b[k / 8] |= (byte)(1 << (k % 8)); } } j += k - 1; bw.WriteBytes(b); } } }
internal void WriteCells(BinaryWriterEx bw, byte format2D, int index) { if ((format2D & 0x7F) < 4) { bw.FillUInt32($"RowOffset{index}", (uint)bw.Position); } else { bw.FillInt64($"RowOffset{index}", bw.Position); } int bitOffset = -1; PARAMDEF.DefType bitType = PARAMDEF.DefType.u8; uint bitValue = 0; for (int i = 0; i < Cells.Count; i++) { Cell cell = Cells[i]; object value = cell.Value; PARAMDEF.Field field = cell.Def; PARAMDEF.DefType type = field.DisplayType; if (type == PARAMDEF.DefType.s8) { bw.WriteSByte((sbyte)value); } else if (type == PARAMDEF.DefType.s16) { bw.WriteInt16((short)value); } else if (type == PARAMDEF.DefType.s32) { bw.WriteInt32((int)value); } else if (type == PARAMDEF.DefType.f32) { bw.WriteSingle((float)value); } else if (type == PARAMDEF.DefType.fixstr) { bw.WriteFixStr((string)value, field.ArrayLength); } else if (type == PARAMDEF.DefType.fixstrW) { bw.WriteFixStrW((string)value, field.ArrayLength * 2); } else if (ParamUtil.IsBitType(type)) { if (field.BitSize == -1) { if (type == PARAMDEF.DefType.u8) { bw.WriteByte((byte)value); } else if (type == PARAMDEF.DefType.u16) { bw.WriteUInt16((ushort)value); } else if (type == PARAMDEF.DefType.u32) { bw.WriteUInt32((uint)value); } else if (type == PARAMDEF.DefType.dummy8) { bw.WriteBytes((byte[])value); } } else { if (bitOffset == -1) { bitOffset = 0; bitType = type == PARAMDEF.DefType.dummy8 ? PARAMDEF.DefType.u8 : type; bitValue = 0; } uint shifted = 0; if (bitType == PARAMDEF.DefType.u8) { shifted = (byte)value; } else if (bitType == PARAMDEF.DefType.u16) { shifted = (ushort)value; } else if (bitType == PARAMDEF.DefType.u32) { shifted = (uint)value; } // Shift left first to clear any out-of-range bits shifted = shifted << (32 - field.BitSize) >> (32 - field.BitSize - bitOffset); bitValue |= shifted; bitOffset += field.BitSize; bool write = false; if (i == Cells.Count - 1) { write = true; } else { PARAMDEF.Field nextField = Cells[i + 1].Def; PARAMDEF.DefType nextType = nextField.DisplayType; int bitLimit = ParamUtil.GetBitLimit(bitType); if (!ParamUtil.IsBitType(nextType) || nextField.BitSize == -1 || bitOffset + nextField.BitSize > bitLimit || (nextType == PARAMDEF.DefType.dummy8 ? PARAMDEF.DefType.u8 : nextType) != bitType) { write = true; } } if (write) { bitOffset = -1; if (bitType == PARAMDEF.DefType.u8) { bw.WriteByte((byte)bitValue); } else if (bitType == PARAMDEF.DefType.u16) { bw.WriteUInt16((ushort)bitValue); } else if (bitType == PARAMDEF.DefType.u32) { bw.WriteUInt32(bitValue); } } } } else { throw new NotImplementedException($"Unsupported field type: {type}"); } } }
protected override void Write(BinaryWriterEx bw) { bw.BigEndian = BigEndian; bw.VarintLong = Version == MQBVersion.DarkSouls2Scholar; bw.WriteASCII("MQB "); bw.WriteSByte((sbyte)(BigEndian ? -1 : 0)); bw.WriteByte(0); bw.WriteSByte((sbyte)(Version == MQBVersion.DarkSouls2Scholar ? -1 : 0)); bw.WriteByte(0); bw.WriteUInt32((uint)Version); switch (Version) { case MQBVersion.DarkSouls2: bw.WriteInt32(0x14); break; case MQBVersion.DarkSouls2Scholar: bw.WriteInt32(0x28); break; case MQBVersion.Bloodborne: bw.WriteInt32(0x20); break; case MQBVersion.DarkSouls3: bw.WriteInt32(0x24); break; default: throw new NotImplementedException($"Missing header size for version {Version}."); } bw.ReserveVarint("ResourcePathsOffset"); if (Version == MQBVersion.DarkSouls2Scholar) { bw.WriteInt32(0); bw.WriteInt32(0); bw.WriteInt32(0); bw.WriteInt32(0); } else if (Version >= MQBVersion.Bloodborne) { bw.WriteInt32(1); bw.WriteInt32(0); bw.WriteInt32(0); if (Version >= MQBVersion.DarkSouls3) { bw.WriteInt32(0); } } bw.WriteFixStrW(Name, 0x40, 0x00); bw.WriteSingle(Framerate); bw.WriteInt32(Resources.Count); bw.WriteInt32(Cuts.Count); bw.WriteInt32(0); bw.WriteInt32(0); bw.WriteInt32(0); bw.WriteInt32(0); bw.WriteInt32(0); var allCustomData = new List <CustomData>(); var customDataValueOffsets = new List <long>(); for (int i = 0; i < Resources.Count; i++) { Resources[i].Write(bw, i, allCustomData, customDataValueOffsets); } var offsetsByDispos = new Dictionary <Disposition, long>(); for (int i = 0; i < Cuts.Count; i++) { Cuts[i].Write(bw, Version, offsetsByDispos, i, allCustomData, customDataValueOffsets); } for (int i = 0; i < Cuts.Count; i++) { Cuts[i].WriteTimelines(bw, Version, i); } for (int i = 0; i < Cuts.Count; i++) { Cuts[i].WriteTimelineCustomData(bw, i, allCustomData, customDataValueOffsets); } for (int i = 0; i < Cuts.Count; i++) { Cuts[i].WriteDisposOffsets(bw, offsetsByDispos, i); } bw.FillVarint("ResourcePathsOffset", bw.Position); for (int i = 0; i < Resources.Count; i++) { bw.ReserveVarint($"ResourcePathOffset{i}"); } bw.WriteUTF16(ResourceDirectory, true); for (int i = 0; i < Resources.Count; i++) { if (Resources[i].Path == null) { bw.FillVarint($"ResourcePathOffset{i}", 0); } else { bw.FillVarint($"ResourcePathOffset{i}", bw.Position); bw.WriteUTF16(Resources[i].Path, true); } } // I know this is weird, but trust me. if (Version >= MQBVersion.Bloodborne) { bw.WriteInt16(0); bw.Pad(4); } for (int i = 0; i < allCustomData.Count; i++) { allCustomData[i].WriteSequences(bw, i, customDataValueOffsets[i]); } for (int i = 0; i < allCustomData.Count; i++) { allCustomData[i].WriteSequencePoints(bw, i); } }
internal void Write(BinaryWriterEx bw, PARAMDEF def, int index) { if (def.FormatVersion >= 202) { bw.ReserveInt64($"DisplayNameOffset{index}"); } else if (def.Unicode) { bw.WriteFixStrW(DisplayName, 0x40, (byte)(def.FormatVersion >= 104 ? 0x00 : 0x20)); } else { bw.WriteFixStr(DisplayName, 0x40, (byte)(def.FormatVersion >= 104 ? 0x00 : 0x20)); } byte padding = (byte)(def.FormatVersion >= 200 ? 0x00 : 0x20); bw.WriteFixStr(DisplayType.ToString(), 8, padding); bw.WriteFixStr(DisplayFormat, 8, padding); bw.WriteSingle(Default); bw.WriteSingle(Minimum); bw.WriteSingle(Maximum); bw.WriteSingle(Increment); bw.WriteInt32((int)EditFlags); bw.WriteInt32(ParamUtil.GetValueSize(DisplayType) * (ParamUtil.IsArrayType(DisplayType) ? ArrayLength : 1)); if (def.FormatVersion >= 200) { bw.ReserveInt64($"DescriptionOffset{index}"); } else { bw.ReserveInt32($"DescriptionOffset{index}"); } if (def.FormatVersion >= 202) { bw.ReserveInt64($"InternalTypeOffset{index}"); } else { bw.WriteFixStr(InternalType, 0x20, padding); } if (def.FormatVersion >= 202) { bw.ReserveInt64($"InternalNameOffset{index}"); } else if (def.FormatVersion >= 102) { bw.WriteFixStr(MakeInternalName(), 0x20, padding); } if (def.FormatVersion >= 104) { bw.WriteInt32(SortID); } if (def.FormatVersion >= 200) { bw.WriteInt32(0); bw.ReserveInt64($"UnkB8Offset{index}"); bw.ReserveInt64($"UnkC0Offset{index}"); bw.ReserveInt64($"UnkC8Offset{index}"); } }
internal void Write(BinaryWriterEx bw, List <CustomData> allCustomData, List <long> customDataValueOffsets) { bw.WriteFixStrW(Name, 0x40, 0x00); bw.WriteUInt32((uint)Type); bw.WriteInt32(Type == DataType.Color ? 3 : 0); int length = -1; if (Type == DataType.String) { length = SFEncoding.UTF16.GetByteCount((string)Value + '\0'); if (length % 0x10 != 0) { length += 0x10 - length % 0x10; } } else if (Type == DataType.Custom) { length = ((byte[])Value).Length; if (length % 4 != 0) { throw new InvalidDataException($"Unexpected custom data custom length: {length}"); } } else if (Type == DataType.Color) { length = 4; } long valueOffset = bw.Position; switch (Type) { case DataType.Bool: bw.WriteBoolean((bool)Value); break; case DataType.SByte: bw.WriteSByte((sbyte)Value); break; case DataType.Byte: bw.WriteByte((byte)Value); break; case DataType.Short: bw.WriteInt16((short)Value); break; case DataType.Int: bw.WriteInt32((int)Value); break; case DataType.UInt: bw.WriteUInt32((uint)Value); break; case DataType.Float: bw.WriteSingle((float)Value); break; case DataType.String: case DataType.Custom: case DataType.Color: bw.WriteInt32(length); break; default: throw new NotImplementedException($"Unimplemented custom data type: {Type}"); } if (Type == DataType.Bool || Type == DataType.SByte || Type == DataType.Byte) { bw.WriteByte(0); bw.WriteInt16(0); } else if (Type == DataType.Short) { bw.WriteInt16(0); } // This is probably wrong for the 64-bit format bw.WriteInt32(0); bw.ReserveInt32($"SequencesOffset[{allCustomData.Count}]"); bw.WriteInt32(Sequences.Count); bw.WriteInt32(0); bw.WriteInt32(0); if (Type == DataType.String) { bw.WriteFixStrW((string)Value, length, 0x00); } else if (Type == DataType.Custom) { bw.WriteBytes((byte[])Value); } else if (Type == DataType.Color) { var color = (Color)Value; valueOffset = bw.Position; bw.WriteByte(color.R); bw.WriteByte(color.G); bw.WriteByte(color.B); bw.WriteByte(0); } allCustomData.Add(this); customDataValueOffsets.Add(valueOffset); }