public UnitObject(bool debugOutputLoadingProgress, int version = CurrentVersion) { _version = version; _debugOutputLoadingProgress = debugOutputLoadingProgress; _bitBuffer = new BitBuffer(); _Init(); }
private UnitObject(BitBuffer bitBuffer, bool debugOutputLoadingProgress = false) : this(debugOutputLoadingProgress) { _bitBuffer = bitBuffer; }
private void _WriteStat(BitBuffer bitBuffer, UnitObjectStats statBlock, Stat stat) { /***** Stat Block ***** * if (Context == 1) * { * RowIndex 11 RowIndex to Stats table - used in MP context. * } * else * { * Code 16 Code from Stats excel table. * * ParamsCount 2 Count of following. * { * Exists 1 Simple bool test. * { * ParamBitCount 6 Number of bits used in file. * * ParamOperationsFlags 2 Operations to be done to param value * if (ParamOperationsFlags & 0x01) * { * ParamShift 3 Not seen used - but is in ASM. * } * if (ParamOperationsFlags & 0x02) * { * ParamIsOffset 1 Param is offset - unknown usage. * } * * NoTableCode 1 Bool type. * if (!NoTableCode) * { * ParamTableCode 16 Like StatCode, allows param value to refer to an excel table. * } * } * } * * BitCount 6 Number of bits used in file for stat value. * * ValueOperationsFlags 3 Operations to be performed on stat value. * if (otherAttributeFlag & 0x01) * { * ValueShift 4 Value shift - unknown usage. Probably used server-side to allow for greater ranges. * } * if (otherAttributeFlag & 0x02) * { * ValueOffset 12 Value if offset. Real value: Value -= ValueOffset; * } * if (otherAttributeFlag & 0x04) * { * ValueIsOffset 1 Not to be confused with ValueOffset - IsOffset usage unknown (similar to ParamIsOffset) * } * * NoValueTableCode 2 ASM only considered 0x00 - any other value and ValueTableCode is not read in. * if (!NoValueTableCode) * { * ValueTableCode 16 Allows value to refer to an excel table. * } * * } // end if (Context == 1) * * * HasMultipleValues 1 Bool type. * { * ValueCount 10 Number of times to read in stat values. * } * * for (ValueCount) If HasMultipleValues == false, then obviously we still want to read * { in at least once... So really it's like a do {} while() chunk. * for (ParamsCount) * { * ParamValue ParamBitCount The extra attribute for the applicable value below. * } * * StatValue BitCount The actual stat value. * } */ StatsRow statData = stat.StatRow; int valueBitCount; int[] paramsBitCounts = new int[MaxParams]; if (statBlock.Context == StatContext.UseRows) { valueBitCount = statData.valbits; paramsBitCounts[0] = statData.param1Bits; // todo: this could be an issue if we have to write a value greater than the bits can handle paramsBitCounts[1] = statData.param2Bits; paramsBitCounts[2] = statData.param3Bits; paramsBitCounts[3] = statData.param4Bits; int rowIndex = _fileManager.GetStatRowIndexFromCode((short) statData.code); if (rowIndex == -1) throw new Exceptions.UnitObjectException("Error: Stat row index not found for Stat = " + statData); bitBuffer.WriteBits(rowIndex, 11); if (statData.ParamCount > 0) bitBuffer.WriteBool(true); if (statData.ParamCount > 1) bitBuffer.WriteBool(true); if (statData.ParamCount > 2) bitBuffer.WriteBool(true); if (statData.ParamCount > 3) bitBuffer.WriteBool(true); } else { valueBitCount = (statData.valTable == -1) ? statData.valbits : 32; // on sp side - the bit count is equal to the bit count of the code field (32 for int, 16 for short, etc), but this will do paramsBitCounts[0] = (statData.param1Table == -1) ? statData.param1Bits : 32; // todo: this could be an issue if we have to write a value greater than the bits can handle paramsBitCounts[1] = (statData.param2Table == -1) ? statData.param2Bits : 32; paramsBitCounts[2] = (statData.param3Table == -1) ? statData.param3Bits : 32; paramsBitCounts[3] = (statData.param4Table == -1) ? statData.param4Bits : 32; bitBuffer.WriteUInt16((uint)statData.code); bitBuffer.WriteBits(statData.ParamCount, 2); if (statData.ParamCount > 0) _WriteStatParamData(bitBuffer, paramsBitCounts[0], statData.param1Shift, statData.param1Offs != 0, stat.Param1TableCode); if (statData.ParamCount > 1) _WriteStatParamData(bitBuffer, paramsBitCounts[1], statData.param2Shift, statData.param2Offs != 0, stat.Param2TableCode); if (statData.ParamCount > 2) _WriteStatParamData(bitBuffer, paramsBitCounts[2], statData.param3Shift, statData.param3Offs != 0, stat.Param3TableCode); if (statData.ParamCount > 3) _WriteStatParamData(bitBuffer, paramsBitCounts[3], statData.param4Shift, statData.param4Offs != 0, stat.Param4TableCode); bitBuffer.WriteBits(valueBitCount, 6); int valueOperationsFlags = 0; if (stat.StatRow.valShift > 0) valueOperationsFlags |= 0x01; if (stat.StatRow.valOffs > 0) valueOperationsFlags |= 0x02; if (stat.StatRow.offset > 0) valueOperationsFlags |= 0x04; bitBuffer.WriteBits(valueOperationsFlags, 3); if ((valueOperationsFlags & 0x01) != 0) { bitBuffer.WriteBits(stat.StatRow.valShift, 4); } if ((valueOperationsFlags & 0x02) != 0) { bitBuffer.WriteBits(stat.StatRow.valOffs, 12); } if ((valueOperationsFlags & 0x04) != 0) { bitBuffer.WriteBool(true); } int noValueTableCode = (stat.ValueTable != null) ? 0 : 1; bitBuffer.WriteBits(noValueTableCode, 2); if (stat.ValueTable != null) { bitBuffer.WriteUInt16((uint)stat.ValueTableCode); } } int valueCount = stat.Values.Count; bool hasMultipleValues = (valueCount > 1); bitBuffer.WriteBool(hasMultipleValues); if (hasMultipleValues) { int valueCountBits = (statBlock._version <= 7) ? 7 : 10; bitBuffer.WriteBits(valueCount, valueCountBits); } foreach (Stat.StatValue statValue in stat.Values) { if (statData.ParamCount >= 1) _WriteStatValue(bitBuffer, statValue.Param1, statValue.Param1Row, stat.Param1Table, paramsBitCounts[0], statBlock.Context); if (statData.ParamCount >= 2) _WriteStatValue(bitBuffer, statValue.Param2, statValue.Param2Row, stat.Param2Table, paramsBitCounts[1], statBlock.Context); if (statData.ParamCount >= 3) _WriteStatValue(bitBuffer, statValue.Param3, statValue.Param3Row, stat.Param3Table, paramsBitCounts[2], statBlock.Context); if (statData.ParamCount >= 4) _WriteStatValue(bitBuffer, statValue.Param4, statValue.Param4Row, stat.Param4Table, paramsBitCounts[3], statBlock.Context); int valueOffset = (stat.StatRow.valOffs + stat.StatRow.offset); int valueShift = (stat.StatRow.valShift - stat.StatRow.shift); statValue.Value += valueOffset; statValue.Value >>= valueShift; _WriteStatValue(bitBuffer, statValue.Value, statValue.ValueRow, stat.ValueTable, valueBitCount, statBlock.Context); } }
private void _WriteUnit(UInt64 bitField, ObjectContext context, UInt64 itemsBitField, ObjectContext itemsContext, UnitObjectStats.StatContext statContext, BitBuffer bitBuffer = null) { _bitField = bitField; _context = context; Stats.Context = statContext; if (bitBuffer != null) _bitBuffer = bitBuffer; /***** Unit Header ***** * Version 16 Should be 0x00BF for SP client, 0x00CD for Resurrection client. * Usage 8 0 for SP. For Resurrection client; 2 for char select, 4 for item drop * bitFieldCount 8 Must be <= 2. I haven't tested with it != 2 though. * { * bitField 32 Each bit determines if 'x' is read in or not. * } * * if (TestBit(bitField, 0x1D)) * { * bitCount 32 Bit count of entire unit object. * } * * if (TestBit(bitField, 0x00)) * { * BeginFlag 32 Flags used to check data/position alignment. * } * * if (TestBit(bitField, 0x1C)) * { * timeStamp1 32 I'm not actually sure what these three things are but they can be set to 0x00000000 * timeStamp2 32 and it will still be loaded fine. I call them time stamps simply because the first * timeStamp3 32 one changes every time the file is saved. * } * * if (TestBit(bitField, 0x1F)) * { * unknownCount 4 count + 8 is what must be written. e.g. If count = 3, then write 11. * { * unknown 16 // TO BE DETERMINED * unknown 16 // TO BE DETERMINED * } * } * * if (TestBit(bitField, 0x20)) * { * characterFlagCount 8 Character state flags * { e.g. Elite, Hardcore, Hardcore Dead, etc. * characterFlag 16 However it should be noted that the game doesn't actually appear to use these. * } It uses the ones located further down. * } * ***** Unit Body ***** (the header "chunk" is read in its own function in-game - hence I call it a header, lol) * * if (TestBit(bitField, 0x1B)) * { * BookmarkCount 5 Count of following block - only seen as 1 (only 1 row in Bookmarks table - "hotkeys") * { * Code 16 Code value of offset - code is from Bookmarks table. * Offset 32 Bit Offset value * } * } * * if (TestBit(bitField, 0x05)) * { * Unknown 32 Seen in MP only - unknown usage. * } * * UnitType 4 The type of .. Is this from UnitType table? * UnitCode 16 The code value of the * * if (TestBit(bitField, 0x17)) * { This chunk is read in by a secondary non-standard function. * ObjectId 64 Contains a unique id for this unit object (e.g. item hash id to stop duping, etc). * } * * if (TestBit(bitField, 0x03) || TestBit(bitField, 0x01)) * { * IsInventory 1 Is true if UnitObject is item in inventory. False if item is a "world drop" (i.e. drop from monster) * { * if (TestBit(bitField, 0x02)) * { * Unknown 32 // UNTESTED * } * * InventoryType 16 Inventory item is within. * InventoryPositionX 12 X Position of item in inventory. * InventoryPositionY 12 Y Position of item in inventory. * * Unknown 64 Non-standard function reading - unknown usage. * } * else * { * Unknown0103Int1 32 These values have something to do with an "item dropped" world positioning etc * i.e. only applicable to MP clients. * Unknown0103Float11 32 * Unknown0103Float12 32 * Unknown0103Float13 32 * * Unknown0103Float21 32 * Unknown0103Float22 32 * Unknown0103Float23 32 * * Unknown0103Float31 32 * Unknown0103Float32 32 * Unknown0103Float33 32 * * Unknown0103Int2 10 * * Unknown0103Float5 32 * * Unknown0103Float6 32 * } * } * * if (TestBit(bitField, 0x06)) * { * unknownBool 1 // TO BE DETEREMINED * } // If exists has always been 1. * * if (TestBit(bitField, 0x09)) * { * unknown 8 // TO BE DETERMINED * } * * if (TestBit(bitField, 0x07)) * { * CharacterHeight 8 Height of character. * CharacterWidth 8 Width of character. * } * * if (TestBit(bitField, 0x08)) * { * UnicodeCharCount 8 Number of (unicode) characters in following string. * CharacterName 8*2*count Character's name in Unicode (no \0) - doesn't appear to be actually used in-game... * } * * if (TestBit(bitField, 0x0A)) * { * characterFlagCount 8 Character state flags * { e.g. Elite, Hardcore, Hardcore Dead, etc. * characterFlag 16 These flags actually affect in-game (unlike the previous set which * } appear to be unused). * } * * if (Context > 2 && (Context <= 6 || Context != 7)) These would be applicable to MP only - SP has Usage == 0 * { * ContextBool 1 True if following value is present. * { * ContextBoolValu 4 // TO BE DETERMINED * } * } * * IsDead 1 Does NOT influence HC dead! If the character died right before saving this flag is set to 1 * * if (TestBit(bitField, 0x0D)) * { * UNIT STAT BLOCK See WriteStatBlock(). * } * * HasAppearanceDetails 1 Bool type. * { * EquippedItemCount 3 Count of equipped items. * { * if (TestBit(bitField, 0x0F)) * { * Unknown 32 // UNTESTED * } * * ItemCode 16 Code of item equipped. * * if (TestBit(bitField, 0x22)) * { * Unknown 8 Read from ReadBitsShift() * } * * if (TestBit(bitField, 0x18)) * { * AffixCount 3 (+1 Ver > 0xC0) Count of affix codes. * { * Code 32 Code of affix. * } * } * } * * Unknown 16 // TO BE DETEREMINED * * if (TestBit(bitField, 0x16)) * { * Unknown 64 Non-standard function reading (as above). * } * * if (TestBit(bitField, 0x23)) * { * Unknown 16 // TO BE DETEREMINED * } * * if (TestBit(bitField, 0x11)) * { * WardrobeLayerHeadCount 4 Count of WardrobeLayers for Head * { * Code 16 Code of WardrobeLayer. * } * * WardrobeAppearanceGroupCount 3 Count of model appearance parts. * { * Code 16 Code of WardrobeAppearanceGroup - Order is: body, head, hair, face accessory. * } * * ColorCount 4 Count of following block. * { * ColorPaletteIndicies 8 Not sure... Is this row index? Code? * } * } * * if (TestBit(bitField, 0x10)) * { * WardrobeLayerCount 16 Count of equipped gears. * { * Code 16 Code of equipped item wardrobe layer. * UnknownBool 1 Bool type. * { * Unknown 2 // TO BE DETEREMINED * } * } * } * } * * * ItemBitOffset 32 Bit offset to end of all item blocks. * ItemCount 10 Count of items. * { * ITEMS * } * * * if (TestBit(bitField1, 0x1A)) * { * WeaponConfigFlag 32 Must be 0x91103A74. * * EndFlagBitOffset 32 Bit offset to end flag. * WeaponConfigCount 6 Count of weapon configs. * { * Code 16 Code of weapon. * UnknownCount 4 Count of following block - Must be 0x02. * { * Exists 1 Bool type. * { * Unknown 32 // TO BE DETEREMINED * } * } * * UnknownCount 4 Count of...? * { * Exists 1 Bool type. * { * Unknown 32 // TO BE DETEREMINED * } * } * * Code 32 Code of something...? * } * } * * if (TestBit(bitField, 0x00)) * { * EndFlag 32 Flags used to check data/position alignment. * } */ int bitCountEofOffset = -1; bool isItem = (UnitType == UnitTypes.Item); bool isMonster = (UnitType == UnitTypes.Monster); /***** Unit Header *****/ int bitCountStart = _bitBuffer.BitOffset; _version = CurrentVersion; _bitBuffer.WriteBits(_version, 16); _bitBuffer.WriteBits((int)_context, 8); _bitBuffer.WriteBits(0x02, 8); { _bitBuffer.WriteUInt64(_bitField); } if (_TestBit(Bits.Bit1DBitCountEof)) { bitCountEofOffset = _bitBuffer.BitOffset; _bitBuffer.WriteInt32(0x00000000); } if (_TestBit(Bits.Bit00FlagAlignment)) { _bitBuffer.WriteUInt32((isItem || isMonster) ? ItemMagicWord : ObjectMagicWord); } if (_TestBit(Bits.Bit1CTimeStamps)) { _bitBuffer.WriteInt32(_timeStamp1); _bitBuffer.WriteInt32(_timeStamp2); _bitBuffer.WriteInt32(_timeStamp3); } if (_TestBit(Bits.Bit1FSaveLocations)) { int saveLocationsCount = SaveLocations.Count; _bitBuffer.WriteBitsShift(saveLocationsCount, 4); foreach (SaveLocation saveLocation in SaveLocations) { if (saveLocation.Level == null || saveLocation.Difficulty == null) { const ushort noCode = 0xFFFF; _bitBuffer.WriteUInt16(noCode); _bitBuffer.WriteUInt16(noCode); } else { _bitBuffer.WriteUInt16((ushort)saveLocation.Level.code); _bitBuffer.WriteUInt16((ushort)saveLocation.Difficulty.code); } } } if (_TestBit(Bits.Bit20States1) && (!isItem || !isMonster)) { int stateCount = StateCodes1.Count; _bitBuffer.WriteBits(stateCount, 8); foreach (short stateCode in StateCodes1) { _bitBuffer.WriteInt16(stateCode); } } /***** Unit Body *****/ int bitOffsetHotkeys = 0; if (_TestBit(Bits.Bit1BBookmarks)) { int bitOffsetsCount = Bookmarks.Count; _bitBuffer.WriteBits(bitOffsetsCount, 5); for (int i = 0; i < bitOffsetsCount; i++) { _bitBuffer.WriteUInt16(Bookmarks[i].Code); bitOffsetHotkeys = _bitBuffer.BitOffset; _bitBuffer.WriteInt32(0x00000000); } } if (_TestBit(Bits.Bit05Unknown)) { _bitBuffer.WriteInt32(UnitObjectId); } _bitBuffer.WriteBits((int)UnitType, 4); if (UnitData == null && UnitCode == 0) throw new Exceptions.UnitObjectException("Unexpected null UnitData object."); _bitBuffer.WriteInt16((UnitData == null) ? UnitCode : UnitData.code); // characters from SP or mods may have items not found - we don't want to delete them (todo: or do we?) if (_TestBit(Bits.Bit17ObjectId)) { _bitBuffer.WriteUInt64(ObjectId); } if (_TestBit(Bits.Bit01Unknown) || _TestBit(Bits.Bit03Unknown)) { _bitBuffer.WriteBool(IsInventory); if (IsInventory) // item is in inventory { if (_TestBit(Bits.Bit02Unknown)) { _bitBuffer.WriteInt32(Unknown02); } _bitBuffer.WriteBits(InventoryLocationIndex, 12); _bitBuffer.WriteBits(InventoryPositionX, 12); _bitBuffer.WriteBits(InventoryPositionY, 12); _bitBuffer.WriteBits(Unknown04, 4); _bitBuffer.WriteNonStandardFunc(Unknown0103Int64); } else // item is a "world drop" { _bitBuffer.WriteInt32(RoomId); _bitBuffer.WriteFloat(Position.X); _bitBuffer.WriteFloat(Position.Y); _bitBuffer.WriteFloat(Position.Z); _bitBuffer.WriteFloat(Unknown0103Float21); _bitBuffer.WriteFloat(Unknown0103Float22); _bitBuffer.WriteFloat(Unknown0103Float23); _bitBuffer.WriteFloat(Normal.X); _bitBuffer.WriteFloat(Normal.Y); _bitBuffer.WriteFloat(Normal.Z); _bitBuffer.WriteBits(Unknown0103Int2, 10); _bitBuffer.WriteFloat(Unknown0103Float4); _bitBuffer.WriteFloat(Unknown0103Float5); } } if (_TestBit(Bits.Bit06Unknown)) { _bitBuffer.WriteBool(UnknownBool06); if (!UnknownBool06) { throw new Exceptions.UnitObjectNotImplementedException("_WriteUnit : if (!UnknownBool06)"); } } if (_TestBit(Bits.Bit09ItemLookGroup)) { _bitBuffer.WriteByte(ItemLookGroupCode); } if (_TestBit(Bits.Bit07CharacterShape)) { _bitBuffer.WriteByte(CharacterHeight); _bitBuffer.WriteByte(CharacterBulk); } if (_TestBit(Bits.Bit08CharacterName) && !String.IsNullOrEmpty(Name)) { _bitBuffer.WriteBits(_charNameBytes.Length / 2, 8); // is Unicode string without \0 foreach (byte b in _charNameBytes) { _bitBuffer.WriteByte(b); } } if (_TestBit(Bits.Bit0AStates2)) { int stateCount = StateCodes2.Count; _bitBuffer.WriteBits(stateCount, 8); foreach (short stateCode in StateCodes2) { _bitBuffer.WriteInt16(stateCode); } } if (_context > ObjectContext.CharSelect && (_context <= ObjectContext.Unknown6 || _context != ObjectContext.Unknown7)) // so if == 0, 1, 2, 7, then *don't* do this { _bitBuffer.WriteBool(ContextBool); if (ContextBool) { _bitBuffer.WriteBits(ContextBoolValue, 4); } } _bitBuffer.WriteBool(IsDead); if (_TestBit(Bits.Bit0DStats)) { Stats.WriteStats(_bitBuffer, true); } else if (_TestBit(Bits.Bit14CharSelectStats)) { int charLevel = Stats.GetStatValueOrDefault("level"); int charPvpRank = Stats.GetStatValueOrDefault("pvp_rank"); _bitBuffer.WriteByte(charLevel); _bitBuffer.WriteByte(charPvpRank); if (_TestBit(Bits.Bit1ECharSelectStatsMaxDifficulty)) { int maxDifficulty = Stats.GetStatValueOrDefault("difficulty_max"); _bitBuffer.WriteBits(maxDifficulty, 3); } } _bitBuffer.WriteBool(HasAppearanceDetails); if (HasAppearanceDetails) { int equippedItemCount = Appearance.EquippedItems.Count; _bitBuffer.WriteBits(equippedItemCount, 3); for (int i = 0; i < equippedItemCount; i++) { UnitAppearance.EquippedItem equippedItem = Appearance.EquippedItems[i]; if (_TestBit(Bits.Bit0FEquippedItemUnknown)) { _bitBuffer.WriteInt32(equippedItem.Unknown0F); } _bitBuffer.WriteUInt16(equippedItem.ItemCode); if (_TestBit(Bits.Bit22EquippedItemRowIndex)) { _bitBuffer.WriteBitsShift(equippedItem.ItemRowIndex, 8); } if (_TestBit(Bits.Bit18EquippedItemAffix)) { int bitCount = (_version > 0xC0) ? 4 : 3; int affixCount = equippedItem.AffixCodes.Count; _bitBuffer.WriteBits(affixCount, bitCount); for (int j = 0; j < affixCount; j++) { _bitBuffer.WriteBits(equippedItem.AffixCodes[j], 32); } } } _bitBuffer.WriteBits(Appearance.ArmorColorSetCode, 16); if (_TestBit(Bits.Bit16AppearanceUnknown64Bits)) { _bitBuffer.WriteNonStandardFunc(Appearance.Unknown16); } if (_TestBit(Bits.Bit23AppearanceUnknownColorSetCode)) { _bitBuffer.WriteInt16(Appearance.Unknown23ColorSetsCode); } if (_TestBit(Bits.Bit11WardrobeLayers)) { int wardrobeLayerCount = Appearance.WardrobeLayerBases.Count; _bitBuffer.WriteBits(wardrobeLayerCount, 4); for (int i = 0; i < wardrobeLayerCount; i++) { _bitBuffer.WriteBits(Appearance.WardrobeLayerBases[i], 16); } } int wardrobeAppearanceGroupCount = Appearance.WardrobeAppearanceGroups.Count; _bitBuffer.WriteBits(wardrobeAppearanceGroupCount, 3); for (int i = 0; i < wardrobeAppearanceGroupCount; i++) { _bitBuffer.WriteBits(Appearance.WardrobeAppearanceGroups[i], 16); } int colorPaletteCount = Appearance.ColorPaletteIndicies.Count; _bitBuffer.WriteBits(colorPaletteCount, 4); for (int i = 0; i < colorPaletteCount; i++) { _bitBuffer.WriteBits(Appearance.ColorPaletteIndicies[i], 8); } if (_TestBit(Bits.Bit10LayerAppearances)) { _bitBuffer.WriteBits(Appearance.LayerAppearanceCount, 16); for (int i = 0; i < Appearance.LayerAppearanceCount; i++) { _bitBuffer.WriteBits(Appearance.LayerAppearances[i].WardrobeLayerCode, 16); _bitBuffer.WriteBits(Appearance.LayerAppearances[i].UnknownBool ? 1 : 0, 1); if (Appearance.LayerAppearances[i].UnknownBool) { _bitBuffer.WriteBits(Appearance.LayerAppearances[i].UnknownBoolValue, 2); } } } } if (_TestBit(Bits.Bit12Items)) { int itemBitOffset = _bitBuffer.BitOffset; _bitBuffer.WriteBits(0x00000000, 32); int itemCount = Items.Count; _bitBuffer.WriteBits(itemCount, 10); for (int i = 0; i < itemCount; i++) { Items[i]._WriteUnit(itemsBitField, itemsContext, itemsBitField, itemsContext, statContext, _bitBuffer); } _bitBuffer.WriteBits(_bitBuffer.BitOffset, 32, itemBitOffset); } if (_TestBit(Bits.Bit1AHotkeys)) { if (bitOffsetHotkeys > 0) { _bitBuffer.WriteBits(_bitBuffer.BitOffset, 32, bitOffsetHotkeys); } _bitBuffer.WriteUInt32(HotkeysMagicWord); int endFlagBitOffset = _bitBuffer.BitOffset; _bitBuffer.WriteBits(0x00000000, 32); int weaponConfigCount = Hotkeys.Count; _bitBuffer.WriteBits(weaponConfigCount, 6); foreach (Hotkey hotkey in Hotkeys) { _bitBuffer.WriteBits(hotkey.Code, 16); _bitBuffer.WriteBits(hotkey.UnknownCount, 4); for (int i = 0; i < hotkey.UnknownCount; i++) { _bitBuffer.WriteBits(hotkey.UnknownExists[i] ? 1 : 0, 1); if (hotkey.UnknownExists[i]) { _bitBuffer.WriteBits(hotkey.UnknownValues[i], 32); } } // yes this chunk looks the same as above - the above chunk though is in a specific function and can differ at 1 point // also, it can be != 2 _bitBuffer.WriteBits(hotkey.SkillCount, 4); for (int i = 0; i < hotkey.SkillCount; i++) { _bitBuffer.WriteBits(hotkey.SkillExists[i] ? 1 : 0, 1); if (hotkey.SkillExists[i]) { _bitBuffer.WriteBits(hotkey.SkillCode[i], 32); } } _bitBuffer.WriteBits(hotkey.UnitTypeCode, 32); } _bitBuffer.WriteBits(_bitBuffer.BitOffset, 32, endFlagBitOffset); } _bitBuffer.WriteUInt32(ItemMagicWord); // "`4R+" if (_TestBit(Bits.Bit1DBitCountEof)) { _bitBuffer.WriteBits(_bitBuffer.BitOffset - bitCountStart, 32, bitCountEofOffset); } }
private Stat _ReadStat(BitBuffer bitBuffer, UnitObjectStats statBlock) { Stat stat = new Stat(); int statIndex = statBlock.Stats.Count; int valueShift = 0; int valueOffset = 0; bool isOffset = false; StatsRow statData; int valueBitCount; int[] paramsBitCounts = new int[MaxParams]; if (statBlock.Context == StatContext.UseRows) { int rowIndex = bitBuffer.ReadBits(11); // todo: make this a static value - this bit count is determined by FileManager._GetTableRowBitMax() of the Stats row count statData = stat.StatRow = _fileManager.GetStatRowFromIndex(rowIndex); if (statData == null) throw new Exceptions.UnitObjectException(String.Format("Error stats[{0}].RowIndex = {1} not found.\nCannot have null stat - not known param count will break bit offset.", statIndex, rowIndex)); valueBitCount = statData.valbits; if (statData.ParamCount > 0 && bitBuffer.ReadBool()) paramsBitCounts[0] = statData.param1Bits; if (statData.ParamCount > 1 && bitBuffer.ReadBool()) paramsBitCounts[1] = statData.param2Bits; if (statData.ParamCount > 2 && bitBuffer.ReadBool()) paramsBitCounts[2] = statData.param3Bits; if (statData.ParamCount > 3 && bitBuffer.ReadBool()) paramsBitCounts[3] = statData.param4Bits; if (_debugOutputLoadingProgress) { Debug.WriteLine(String.Format("stats[{0}].Name = {1}, .Code = {2} (0x{3:X4}), .ParamCount = {4}", statIndex, stat.Name, stat.Code, (uint)stat.Code, statData.ParamCount)); Xls.TableCodes tableCode = (stat.ValueTable == null) ? Xls.TableCodes.Null : stat.ValueTable.TableCode; int noValueTableCode = (stat.ValueTable == null) ? 1 : 0; Debug.WriteLine(String.Format("stats[{0}].NoValueTableCode = {1}, .ValueTableCode = 0x{2:X4}, .ValueTable = {3}", statIndex, noValueTableCode, (uint)tableCode, tableCode)); if (statData.ParamCount >= 1) Debug.WriteLine(String.Format("stats[{0}].Param1.BitCount = {1}, .Table = {2}", statIndex, paramsBitCounts[0], stat.Param1TableCode)); if (statData.ParamCount >= 2) Debug.WriteLine(String.Format("stats[{0}].Param2.BitCount = {1}, .Table = {2}", statIndex, paramsBitCounts[1], stat.Param2TableCode)); if (statData.ParamCount >= 3) Debug.WriteLine(String.Format("stats[{0}].Param3.BitCount = {1}, .Table = {2}", statIndex, paramsBitCounts[2], stat.Param3TableCode)); if (statData.ParamCount >= 4) Debug.WriteLine(String.Format("stats[{0}].Param4.BitCount = {1}, .Table = {2}", statIndex, paramsBitCounts[3], stat.Param4TableCode)); } } else { ushort code = bitBuffer.ReadUInt16(); statData = stat.StatRow = _fileManager.GetStatRowFromCode((short)code); if (stat.StatRow == null) throw new Exceptions.UnitObjectException(String.Format("Error: stat.Code = {0} (0x{0:X4}) not found.\nCannot have null stat - not known param count will break bit offset.", code)); int paramsCount = bitBuffer.ReadBits(2); if (_debugOutputLoadingProgress) { Debug.WriteLine(String.Format("stats[{0}].Name = {1}, stat.Code = {2} (0x{3:X4}), stat.ParamCount = {4}", statIndex, stat.Name, code, (uint)code, paramsCount)); } if (paramsCount > statData.ParamCount) Debug.WriteLine(String.Format("Unexpected large params count of {0}. Expecting count <= {1}", paramsCount, statData.ParamCount)); for (int i = 0; i < paramsCount; i++) { bool exists = bitBuffer.ReadBool(); if (!exists) continue; paramsBitCounts[i] = bitBuffer.ReadBits(6); int paramOperationsFlags = bitBuffer.ReadBits(2); int paramShift = 0; bool paramIsOffset = false; if ((paramOperationsFlags & 0x01) != 0) { paramShift = bitBuffer.ReadBits(3); } if ((paramOperationsFlags & 0x02) != 0) { paramIsOffset = bitBuffer.ReadBool(); } bool hasTableCode = !bitBuffer.ReadBool(); Xls.TableCodes paramTableCode = Xls.TableCodes.Null; if (hasTableCode) { paramTableCode = (Xls.TableCodes)bitBuffer.ReadUInt16(); Xls.TableCodes expetedTableCode = Xls.TableCodes.Null; switch (i) { case 0: expetedTableCode = stat.Param1TableCode; break; case 1: expetedTableCode = stat.Param2TableCode; break; case 2: expetedTableCode = stat.Param3TableCode; break; case 3: expetedTableCode = stat.Param4TableCode; break; } if (expetedTableCode != paramTableCode) throw new Exceptions.UnitObjectException(String.Format("Unexpected param value table supplied. Expecting {0}, got {1}", expetedTableCode, paramTableCode)); } if (_debugOutputLoadingProgress) { Debug.WriteLine(String.Format("stats[{0}].Param{1}.BitCount = {2}, .ParamOperationsFlags = {3}, .ParamShift = {4}, .ParamIsOffset = {5}, .HasTableCode = {6}, .TableCode = {7}, .Table = {8}", statIndex, i, paramsBitCounts[i], paramOperationsFlags, paramShift, paramIsOffset, hasTableCode, (uint)paramTableCode, paramTableCode)); } } valueBitCount = bitBuffer.ReadBits(6); int valueOperationsFlags = bitBuffer.ReadBits(3); if ((valueOperationsFlags & 0x01) != 0) { valueShift = bitBuffer.ReadBits(4); // valShift } if ((valueOperationsFlags & 0x02) != 0) { valueOffset = bitBuffer.ReadBits(12); // valOffset } if ((valueOperationsFlags & 0x04) != 0) { isOffset = bitBuffer.ReadBool(); } if (_debugOutputLoadingProgress) { Debug.WriteLine(String.Format("stats[{0}].BitCount = {1}, .ValueOperationsFlags = {2}, .ValueShift = {3}, .ValueOffset = {4}, .IsOffset = {5}", statIndex, valueBitCount, valueOperationsFlags, valueShift, valueOffset, isOffset)); } int noValueTableCode = bitBuffer.ReadBits(2); Debug.Assert(noValueTableCode != 2); // never seen... seen in stats column in Monsters table Xls.TableCodes tableCode = Xls.TableCodes.Null; if (noValueTableCode == 0) { tableCode = (Xls.TableCodes)bitBuffer.ReadUInt16(); Xls.TableCodes expectedTableCode = stat.ValueTableCode; if (expectedTableCode != tableCode) throw new Exceptions.UnitObjectException(String.Format("Unexpected stat value table supplied. Expecting {0}, got {1}", expectedTableCode, tableCode)); } if (_debugOutputLoadingProgress) { Debug.WriteLine(String.Format("stats[{0}].NoValueTableCode = {1}, .ValueTableCode = 0x{2:X4}, .ValueTable = {3}", statIndex, noValueTableCode, (uint)tableCode, tableCode)); } } bool hasMultipleValues = bitBuffer.ReadBool(); int valueCount = 1; if (hasMultipleValues) { int valueCountBits = (statBlock._version <= 7) ? 8 : 10; valueCount = bitBuffer.ReadBits(valueCountBits); } if (_debugOutputLoadingProgress) { Debug.WriteLine(String.Format("stats[{0}].HasMultipleValues = {1}, .ValueCount = {2}", statIndex, hasMultipleValues, valueCount)); } for (int i = 0; i < valueCount; i++) { Stat.StatValue statValue = new Stat.StatValue(); _ReadStatValue(bitBuffer, paramsBitCounts[0], ref statValue.Param1, stat.Param1Table, ref statValue.Param1Row, statBlock.Context); _ReadStatValue(bitBuffer, paramsBitCounts[1], ref statValue.Param2, stat.Param2Table, ref statValue.Param2Row, statBlock.Context); _ReadStatValue(bitBuffer, paramsBitCounts[2], ref statValue.Param3, stat.Param3Table, ref statValue.Param3Row, statBlock.Context); _ReadStatValue(bitBuffer, paramsBitCounts[3], ref statValue.Param4, stat.Param4Table, ref statValue.Param4Row, statBlock.Context); _ReadStatValue(bitBuffer, valueBitCount, ref statValue.Value, stat.ValueTable, ref statValue.ValueRow, statBlock.Context); // this seems to work // todo: do this for writing stats if (valueOffset == 0) valueOffset = (stat.StatRow.valOffs + stat.StatRow.offset); if (valueShift == 0) valueShift = (stat.StatRow.valShift - stat.StatRow.shift); statValue.Value -= valueOffset; // not going to bother with this for now statValue.Value <<= valueShift; // not sure what this is for either - possibly server-side to increase accuracy/value range I guess - but values aren't saved with it... //if (value.IsOffset) statValue.Value--; // do this to *row index* though, not code - not sure about non-table relations values... stat.Values.Add(statValue); if (_debugOutputLoadingProgress) { if (paramsBitCounts[0] > 0) Debug.WriteLine(String.Format("stats[{0}].Param1 = {1}", statIndex, statValue.Param1)); if (paramsBitCounts[1] > 0) Debug.WriteLine(String.Format("stats[{0}].Param2 = {1}", statIndex, statValue.Param2)); if (paramsBitCounts[2] > 0) Debug.WriteLine(String.Format("stats[{0}].Param3 = {1}", statIndex, statValue.Param3)); if (paramsBitCounts[3] > 0) Debug.WriteLine(String.Format("stats[{0}].Param4 = {1}", statIndex, statValue.Param4)); Debug.WriteLine(String.Format("stats[{0}].Value = {1}", statIndex, statValue.Value)); } } return stat; }
private void _ReadStatValue(BitBuffer bitBuffer, int bitCount, ref int value, ExcelFile valueTable, ref Object valueRow, StatContext statContext) { if (bitCount <= 0) return; value = bitBuffer.ReadBits(bitCount); if (valueTable == null) return; if (statContext == StatContext.UseRows) { if (value >= 0 && value < valueTable.Rows.Count) { valueRow = valueTable.Rows[value]; } if (valueRow == null) Debug.WriteLine(String.Format("Warning: OutOfBounds stat index {0} on table {1}", value, valueTable.TableCode)); } else { valueRow = _fileManager.GetRowFromCode(valueTable.TableCode, (short)value); if (valueRow == null) Debug.WriteLine(String.Format("Warning: Unknown stat value code {0} (0x{0:X4}) on table {1}", value, valueTable.TableCode)); } }
public void WriteStats(BitBuffer bitBuffer, UnitObjectStats stats, bool writeNameCount = false) { /***** Stat Block Header ***** * Version 16 Stat block header - Must be 0x000A. * Context 3 Use 0 for Code usage, 1 for RowIndex usage, 2 for... ? * * StatSelectorCount 6 Not sure of use - but uses StatSelector table * { * Code 16 Code from StatSelector table (if Context != 1) * RowIndex 5 RowIndex from StatSelector table (if Context == 1) * * StatCount 16 Count of following stats. * { * STATS See WriteStat(). * } * } * * StatCount 16 Count of following stats. * { * STATS See WriteStat(). * } * * // todo: update me (same StatSelector stuff from above) * if (WriteNameCount) This is TRUE by default. Set to FALSE when writing a stat block * { from the below name stat block chunk. * NameCount 8 I think this has something to do with item names. * { * Code 16 Code from StatSelector table (if Context != 1) * RowIndex 5 RowIndex from StatSelector table (if Context == 1) * * STAT BLOCK See WriteStatBlock(). * } * } */ bitBuffer.WriteBits(stats._version, 16); bitBuffer.WriteBits((int)stats.Context, 3); bitBuffer.WriteBits(stats.StatSelectors.Count, 6); foreach (StatSelector statSelector in stats.StatSelectors) { if (stats.Context == StatContext.UseRows) { bitBuffer.WriteBits(statSelector.RowIndex, 5); } else { bitBuffer.WriteUInt16(statSelector.Code); } bitBuffer.WriteBits(statSelector.Stats.Count, 16); foreach (Stat stat in statSelector.Stats) { _WriteStat(bitBuffer, stats, stat); } } bitBuffer.WriteBits(stats.Stats.Count, 16); foreach (Stat stat in stats.Stats.Values.OrderBy(stat => stat.Code)) { _WriteStat(bitBuffer, stats, stat); } if (!writeNameCount) return; bitBuffer.WriteBits(stats.Names.Count, 8); foreach (StatName statName in stats.Names) { bitBuffer.WriteBits(statName.Unknown1, 16); WriteStats(bitBuffer, statName.Stats, false); } }
private static void _WriteStatParamData(BitBuffer bitBuffer, int bitCount, int paramShift, bool paramIsOffset, Xls.TableCodes tableCode) { if (bitCount <= 0) { bitBuffer.WriteBool(false); return; } bitBuffer.WriteBool(true); bitBuffer.WriteBits(bitCount, 6); int paramOperationsFlags = 0; if (paramShift > 0) paramOperationsFlags |= 0x01; if (paramIsOffset) paramOperationsFlags |= 0x02; bitBuffer.WriteBits(paramOperationsFlags, 2); if ((paramOperationsFlags & 0x01) != 0) { bitBuffer.WriteBits(paramShift, 3); } if ((paramOperationsFlags & 0x02) != 0) { bitBuffer.WriteBool(paramIsOffset); } bool hasTableCode = (tableCode != Xls.TableCodes.Null && tableCode != Xls.TableCodes.None); bitBuffer.WriteBool(!hasTableCode); if (hasTableCode) { bitBuffer.WriteUInt16((uint)tableCode); } }
public void WriteStats(BitBuffer bitBuffer, bool writeNameCount = false) { WriteStats(bitBuffer, this, writeNameCount); }
public byte[] WriteStats(bool writeNameCount = false) { BitBuffer bitBuffer = new BitBuffer(); bitBuffer.CreateBuffer(); WriteStats(bitBuffer, this, writeNameCount); return bitBuffer.GetBuffer(); }
/// <summary> /// Read stats from a serialised byte array /// </summary> /// <param name="bitBuffer">The current buffer state.</param> /// <param name="stats">The stats block to populate.</param> /// <param name="readNameCount">TODO</param> public void ReadStats(BitBuffer bitBuffer, UnitObjectStats stats, bool readNameCount = false) { stats._version = bitBuffer.ReadBits(16); stats.Context = (StatContext)bitBuffer.ReadBits(3); if (_debugOutputLoadingProgress) { Debug.WriteLine(String.Format("stats.Version = {0}, stats.Context = {1} (0x{2:X2})", stats._version, stats.Context, (int)stats.Context)); } if (stats._version != 0x0A) { throw new Exceptions.NotSupportedVersionException("0x0A", "0x" + stats._version.ToString("X2")); } int statSelectorCount = bitBuffer.ReadBits(6); if (_debugOutputLoadingProgress) { Debug.WriteLine(String.Format("stats.StatSelectorCount = {0}", statSelectorCount)); } for (int i = 0; i < statSelectorCount; i++) { StatSelector statSelector = new StatSelector(); if (stats.Context == StatContext.UseRows) { statSelector.RowIndex = bitBuffer.ReadBits(5); statSelector.StatSelectorRow = (ExcelTablesRow)_fileManager.GetRowFromIndex(Xls.TableCodes.STATS_SELECTOR, statSelector.RowIndex); // StatsSelector table } else { statSelector.Code = bitBuffer.ReadUInt16(); statSelector.StatSelectorRow = (ExcelTablesRow)_fileManager.GetRowFromCode(Xls.TableCodes.STATS_SELECTOR, (short)statSelector.Code); // StatsSelector table } int selectorStatCount = bitBuffer.ReadInt16(); if (_debugOutputLoadingProgress) { Debug.WriteLine(String.Format("StatSelectors[{0}].Name = {1}, .Code = 0x{2:X4}, .RowIndex = {3}, .StatCount = {4}", i, statSelector.Name, statSelector.Code, statSelector.RowIndex, selectorStatCount)); } for (int j = 0; j < selectorStatCount; j++) { Stat stat = _ReadStat(bitBuffer, stats); statSelector.Stats.Add(stat); } stats.StatSelectors.Add(statSelector); } int statCount = bitBuffer.ReadBits(16); if (_debugOutputLoadingProgress) { Debug.WriteLine(String.Format("stats.StatCount = {0}", statCount)); } for (int i = 0; i < statCount; i++) { Stat stat = _ReadStat(bitBuffer, stats); stats.Stats[stat.StatRow.code] = stat; } if (!readNameCount) return; stats.NameCount = bitBuffer.ReadBits(8); if (_debugOutputLoadingProgress) { Debug.WriteLine(String.Format("stats.NameCount = {0}", stats.NameCount)); } for (int i = 0; i < stats.NameCount; i++) { StatName name = new StatName { Unknown1 = (short)bitBuffer.ReadBits(5), Stats = new UnitObjectStats(_fileManager, _version) }; if (_debugOutputLoadingProgress) { Debug.WriteLine(String.Format("stats.Unknown1 = {0}", name.Unknown1)); } ReadStats(bitBuffer, name.Stats, false); stats.Names.Add(name); } }
/// <summary> /// Read stats from a serialised byte array /// </summary> /// <param name="bitBuffer">The current buffer state.</param> /// <param name="readNameCount">TODO</param> public void ReadStats(BitBuffer bitBuffer, bool readNameCount = false) { ReadStats(bitBuffer, this, readNameCount); }
private void _WriteStatValue(BitBuffer bitBuffer, int value, Object valueRow, ExcelFile valueTable, int bitCount, StatContext statContext) { if (bitCount <= 0) return; if (valueTable == null) { bitBuffer.WriteBits(value, bitCount); return; } if (valueRow == null) { bitBuffer.WriteBits(0, bitCount); return; } if (statContext == StatContext.UseRows) { int rowIndex = valueTable.Rows.IndexOf(valueRow); bitBuffer.WriteBits(rowIndex, bitCount); } else { int code = _fileManager.GetCodeFromRow(valueTable.TableCode, valueRow); bitBuffer.WriteBits(code, bitCount); } }