// Block Layout: // 000-001 U16 Index // 002-002 U8 Type // 003-003 U8 List Icon ID (e.g. 40-47 for the elemental-colored dots) // 004-005 U16 MP Cost // 006-007 U16 Unknown (used to be the cooldown time) // 008-009 U16 Valid Targets // 00a-029 TXT Name // 02a-129 TXT Description (exact length unknown) // 12a-3fe U8 Padding (NULs) // 3ff-3ff U8 End marker (0xff) public bool Read(BinaryReader BR) { this.Clear(); try { byte[] Bytes = BR.ReadBytes(0x400); if (Bytes[0x3ff] != 0xff || Bytes[9] != 0x00 || !FFXIEncryption.DecodeDataBlock(Bytes)) { return(false); } FFXIEncoding E = new FFXIEncoding(); BR = new BinaryReader(new MemoryStream(Bytes, false)); this.ID_ = BR.ReadUInt16(); this.Type_ = (AbilityType)BR.ReadByte(); this.ListIconID_ = BR.ReadByte(); this.MPCost_ = BR.ReadUInt16(); this.Unknown1_ = BR.ReadUInt16(); this.ValidTargets_ = (ValidTarget)BR.ReadUInt16(); this.Name_ = E.GetString(BR.ReadBytes(32)).TrimEnd('\0'); this.Description_ = E.GetString(BR.ReadBytes(256)).TrimEnd('\0'); BR.Close(); return(true); } catch { return(false); } }
// Block Layout: // 000-003 U32 Index // 004-007 U32 Unknown // 008-00b U32 Unknown // 00c-00f U32 Unknown // 010-013 U32 Unknown // 014-02b Unknown // 02c-0ab TXT Description // 0ac-27f Unknown // 280-281 U16 Icon Size // 282-bff IMG Icon (+ padding) public bool Read(BinaryReader BR) { this.Clear(); try { byte[] Bytes = BR.ReadBytes(0x280); byte[] IconBytes = BR.ReadBytes(0x980); if (!FFXIEncryption.DecodeDataBlock(Bytes)) { return(false); } if (IconBytes[0x97f] != 0xff) { return(false); } { // Verify that the icon info is valid Graphic StatusIcon = new Graphic(); BinaryReader IconBR = new BinaryReader(new MemoryStream(IconBytes, false)); int IconSize = IconBR.ReadInt32(); if (IconSize > 0 && IconSize <= 0x97b) { if (!StatusIcon.Read(IconBR) || IconBR.BaseStream.Position != 4 + IconSize) { IconBR.Close(); return(false); } } IconBR.Close(); if (StatusIcon == null) { return(false); } this.Icon_ = StatusIcon; } BR = new BinaryReader(new MemoryStream(Bytes, false)); } catch { return(false); } FFXIEncoding E = new FFXIEncoding(); this.ID_ = BR.ReadUInt16(); this.Unknown1_ = BR.ReadUInt16(); this.Unknown2_ = BR.ReadUInt32(); this.Unknown3_ = BR.ReadUInt32(); this.Unknown4_ = BR.ReadUInt32(); this.Unknown4_ = BR.ReadUInt32(); BR.ReadBytes(0x18); this.Description_ = E.GetString(BR.ReadBytes(128)).TrimEnd('\0'); BR.Close(); return(true); }
// Block Layout: // 000-001 U16 Index // 002-003 U16 Magic Type (1/2/3/4/5/6 - White/Black/Summon/Ninja/Bard/Blue) // 004-005 U16 Element // 006-007 U16 Valid Targets // 008-009 U16 Skill // 00a-00b U16 MP Cost // 00c-00c U8 Cast Time (1/4 second) // 00d-00d U8 Recast Delay (1/4 second) // 00e-025 U8 Level required (1 byte per job, 0xff if not learnable; first is for the NUL job, so always 0xff; only 24 slots despite 32 possible job flags) // 026-027 U16 ID (0 for "unused" spells; starts out equal to the index, but doesn't stay that way) // 028-028 U8 List Icon ID (not sure what this is an index of, but it seems to match differences in item icon) // 029-029 U8 Unknown #1 // 02a-02b U8 Unknown #2 // 02c-02d U8 Unknown #3 // 02e-02f U8 Unknown #4 // 030-03e U8 Padding (NULs) // 03f-03f U8 End marker (0xff) public bool Read(BinaryReader BR) { Clear(); try { var Bytes = BR.ReadBytes(0x64); if (Bytes[0x3] != 0x00 || Bytes[0x5] != 0x00 || Bytes[0x7] != 0x00 || Bytes[0x9] != 0x00 || Bytes[0xE] != 0xFF || Bytes[0x63] != 0xFF) { return(false); } if (!FFXIEncryption.DecodeDataBlockMask(Bytes)) { return(false); } BR = new BinaryReader(new MemoryStream(Bytes, false)); } catch { return(false); } Index_ = BR.ReadUInt16(); MagicType_ = (MagicType)BR.ReadUInt16(); Element_ = (Element)BR.ReadUInt16(); ValidTargets_ = (ValidTarget)BR.ReadUInt16(); Skill_ = (Skill)BR.ReadUInt16(); MPCost_ = BR.ReadUInt16(); CastingTime_ = BR.ReadByte(); RecastDelay_ = BR.ReadByte(); LevelRequired_ = new int[0x18]; for (var i = 0; i < 0x18; ++i) { LevelRequired_[i] = BR.ReadInt16(); } ID_ = BR.ReadUInt16(); ListIconID_ = BR.ReadByte(); Unknown1_ = BR.ReadByte(); Unknown2_ = BR.ReadByte(); Unknown3_ = BR.ReadByte(); Unknown4_ = BR.ReadByte(); Unknown5_ = BR.ReadUInt32(); #if DEBUG // Check the padding bytes for unexpected data for (byte i = 0; i < 26; ++i) { var PaddingByte = BR.ReadByte(); if (PaddingByte != 0) { Console.WriteLine("SpellInfo2: Entry #{0}: Padding Byte #{1} is non-zero: {2:X2} ({2})", Index_, i + 1, PaddingByte); } } #endif BR.Close(); return(true); }
// Block Layout: // 000-001 U16 Index // 002-003 U16 Magic Type (1/2/3/4/5/6 - White/Black/Summon/Ninja/Bard/Blue) // 004-005 U16 Element // 006-007 U16 Valid Targets // 008-009 U16 Skill // 00a-00b U16 MP Cost // 00c-00c U8 Cast Time (1/4 second) // 00d-00d U8 Recast Delay (1/4 second) // 00e-025 U8 Level required (1 byte per job, 0xff if not learnable; first is for the NUL job, so always 0xff; only 24 slots despite 32 possible job flags) // 026-027 U16 ID (0 for "unused" spells; starts out equal to the index, but doesn't stay that way) // 028-028 U8 List Icon ID (not sure what this is an index of, but it seems to match differences in item icon) // 029-029 U8 Unknown #1 // 02a-02b U8 Unknown #2 // 02c-02d U8 Unknown #3 // 02e-02f U8 Unknown #4 // 030-03e U8 Padding (NULs) // 03f-03f U8 End marker (0xff) public bool Read(BinaryReader BR) { this.Clear(); try { byte[] Bytes = BR.ReadBytes(0x58); //if (Bytes[0x3] != 0x00 || Bytes[0x5] != 0x00 || Bytes[0x7] != 0x00 || Bytes[0x9] != 0x00 || Bytes[0xf] != 0xff || Bytes[0x3f] != 0xff) //return false; if (!FFXIEncryption.DecodeDataBlockMask(Bytes)) { return(false); } BR = new BinaryReader(new MemoryStream(Bytes, false)); } catch { return(false); } this.Index_ = BR.ReadUInt16(); this.MagicType_ = (MagicType)BR.ReadUInt16(); this.Element_ = (Element)BR.ReadUInt16(); this.ValidTargets_ = (ValidTarget)BR.ReadUInt16(); this.Skill_ = (Skill)BR.ReadUInt16(); this.MPCost_ = BR.ReadUInt16(); this.CastingTime_ = BR.ReadByte(); this.RecastDelay_ = BR.ReadByte(); this.LevelRequired_ = new int[0x18]; for (var i = 0; i < 0x18; ++i) { this.LevelRequired_[i] = BR.ReadInt16(); } this.ID_ = BR.ReadUInt16(); this.ListIconID_ = BR.ReadByte(); this.Unknown1_ = BR.ReadByte(); this.Unknown2_ = BR.ReadByte(); this.Unknown3_ = BR.ReadByte(); this.Unknown4_ = BR.ReadByte(); this.Unknown5_ = BR.ReadUInt32(); #if DEBUG // Check the padding bytes for unexpected data for (byte i = 0; i < 14; ++i) { byte PaddingByte = BR.ReadByte(); if (PaddingByte != 0) { Console.WriteLine("MonsterSpellInfo2: Entry #{0}: Padding Byte #{1} is non-zero: {2:X2} ({2})", this.Index_, i + 1, PaddingByte); } } #endif BR.Close(); return(true); }
// Block Layout: // 000-001 U16 Index // 002-002 U8 Type // 003-003 U8 List Icon ID (e.g. 40-47 for the elemental-colored dots) // 004-005 U16 Unknown #1 // 006-007 U16 MP Cost // 008-009 U16 Shared Timer ID // 00a-00b U16 Valid Targets // 00c-00c I8 TP Cost (percentage, or -1 if not applicable) // 00d-00d U8 Category ID (for entries that are categories instead of real abilities) // 010-02e U8 Padding (NULs) // 02f-02f U8 End marker (0xff) public bool Read(BinaryReader BR) { this.Clear(); try { byte[] Bytes = BR.ReadBytes(0x30); if (Bytes[0x9] > 0xc0 || Bytes[0x2f] != 0xff) { return(false); } if (!FFXIEncryption.DecodeDataBlockMask(Bytes)) { return(false); } FFXIEncoding E = new FFXIEncoding(); BR = new BinaryReader(new MemoryStream(Bytes, false)); } catch { return(false); } this.ID_ = BR.ReadUInt16(); this.Type_ = (AbilityType)BR.ReadByte(); this.ListIconID_ = BR.ReadByte(); this.Unknown1_ = BR.ReadUInt16(); this.MPCost_ = BR.ReadUInt16(); this.SharedTimerID_ = BR.ReadUInt16(); this.ValidTargets_ = (ValidTarget)BR.ReadUInt16(); this.TPCost_ = BR.ReadByte(); this.CategoryID_ = BR.ReadByte(); this.Unknown1_ = BR.ReadUInt16(); #if DEBUG // Check the padding bytes for unexpected data for (byte i = 0; i < 31; ++i) { byte PaddingByte = BR.ReadByte(); if (PaddingByte != 0) { Console.WriteLine("AbilityInfo2: Entry #{0}: Padding Byte #{1} is non-zero: {2:X2} ({2})", this.ID_, i + 1, PaddingByte); } } #endif BR.Close(); return(true); }
// Block Layout: // 000-001 U16 Index // 002-003 U16 Magic Type (1/2/3/4/5/6 - White/Black/Summon/Ninja/Bard/Blue) // 004-005 U16 Element // 006-007 U16 Valid Targets // 008-009 U16 Skill // 00a-00b U16 MP Cost // 00c-00c U8 Cast Time (1/4 second) // 00d-00d U8 Recast Delay (1/4 second) // 00e-025 U8 Level required (1 byte per job, 0xff if not learnable; first is for the NUL job, so always 0xff; only 24 slots despite 32 possible job flags) // 026-027 U16 ID (0 for "unused" spells; starts out equal to the index, but doesn't stay that way) // 028-028 U8 List Icon ID (not sure what this is an index of, but it seems to match differences in item icon) // 029-03c CHR Japanese Name (20 bytes) // 03d-051 CHR English Name (20 bytes) // 052-0d1 CHR Japanese Description (128 bytes) // 0d2-151 CHR English Description (128 bytes) // 152-3fe U8 Padding (NULs) // 3ff-3ff U8 End marker (0xff) public bool Read(BinaryReader BR) { this.Clear(); try { byte[] Bytes = BR.ReadBytes(0x400); if (Bytes[0x3] != 0x00 || Bytes[0x5] != 0x00 || Bytes[0x7] != 0x00 || Bytes[0x9] != 0x00 || Bytes[0xf] != 0xff || Bytes[0x3ff] != 0xff) { return(false); } if (!FFXIEncryption.DecodeDataBlock(Bytes)) { return(false); } BR = new BinaryReader(new MemoryStream(Bytes, false)); } catch { return(false); } this.Index_ = BR.ReadUInt16(); this.MagicType_ = (MagicType)BR.ReadUInt16(); this.Element_ = (Element)BR.ReadUInt16(); this.ValidTargets_ = (ValidTarget)BR.ReadUInt16(); this.Skill_ = (Skill)BR.ReadUInt16(); this.MPCost_ = BR.ReadUInt16(); this.CastingTime_ = BR.ReadByte(); this.RecastDelay_ = BR.ReadByte(); this.LevelRequired_ = new int[0x18]; for (var i = 0; i < 0x18; ++i) { this.LevelRequired_[i] = BR.ReadInt16(); } this.ID_ = BR.ReadUInt16(); this.ListIconID_ = BR.ReadByte(); BR.ReadBytes(0x04); FFXIEncoding E = new FFXIEncoding(); this.JapaneseName_ = E.GetString(BR.ReadBytes(20)).TrimEnd('\0'); this.EnglishName_ = E.GetString(BR.ReadBytes(20)).TrimEnd('\0'); this.JapaneseDescription_ = E.GetString(BR.ReadBytes(128)).TrimEnd('\0'); this.EnglishDescription_ = E.GetString(BR.ReadBytes(128)).TrimEnd('\0'); // A slightly newer revision of this format no longer has the names, so mark them unset when empty if (this.JapaneseName_.Length == 0) { this.JapaneseName_ = null; } if (this.EnglishName_.Length == 0) { this.EnglishName_ = null; } if (this.JapaneseDescription_.Length == 0) { this.JapaneseDescription_ = null; } if (this.EnglishDescription_.Length == 0) { this.EnglishDescription_ = null; } #if DEBUG { // Read the next 64 bits, and report if it's not 0 (means there's new data to identify) ulong Next64 = BR.ReadUInt64(); if (Next64 != 0) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("Nonzero data after entry (Spell #{0}): {1:X16}", this.ID_, Next64); Console.ResetColor(); } } #endif BR.Close(); return(true); }
public bool Read(BinaryReader BR, Type T) { this.Clear(); try { byte[] ItemBytes = BR.ReadBytes(0xC00); FFXIEncryption.Rotate(ItemBytes, 5); BR = new BinaryReader(new MemoryStream(ItemBytes, false)); BR.BaseStream.Seek(0x280, SeekOrigin.Begin); Graphic G = new Graphic(); int GraphicSize = BR.ReadInt32(); if (GraphicSize < 0 || !G.Read(BR) || BR.BaseStream.Position != 0x280 + 4 + GraphicSize) { BR.Close(); return(false); } this.Icon_ = G; BR.BaseStream.Seek(0, SeekOrigin.Begin); } catch { return(false); } // Common Fields (14 bytes) this.ID_ = BR.ReadUInt32(); this.Flags_ = (ItemFlags)BR.ReadUInt16(); this.StackSize_ = BR.ReadUInt16(); // 0xe0ff for Currency, which kinda suggests this is really 2 separate bytes this.Type_ = (ItemType)BR.ReadUInt16(); this.ResourceID_ = BR.ReadUInt16(); this.ValidTargets_ = (ValidTarget)BR.ReadUInt16(); // Extra Fields (22/30/10/6/2 bytes for Armor/Weapon/Puppet/Item/UsableItem) if (T == Type.Armor || T == Type.Weapon) { this.Level_ = BR.ReadUInt16(); this.Slots_ = (EquipmentSlot)BR.ReadUInt16(); this.Races_ = (Race)BR.ReadUInt16(); this.Jobs_ = (Job)BR.ReadUInt32(); this.SuperiorLevel_ = BR.ReadUInt16(); if (T == Type.Armor) { this.ShieldSize_ = BR.ReadUInt16(); } else { // Weapon this.Unknown4_ = BR.ReadUInt16(); this.Damage_ = BR.ReadUInt16(); this.Delay_ = BR.ReadInt16(); this.DPS_ = BR.ReadUInt16(); this.Skill_ = (Skill)BR.ReadByte(); this.JugSize_ = BR.ReadByte(); this.Unknown1_ = BR.ReadUInt32(); } this.MaxCharges_ = BR.ReadByte(); this.CastingTime_ = BR.ReadByte(); this.UseDelay_ = BR.ReadUInt16(); this.ReuseDelay_ = BR.ReadUInt32(); this.Unknown2_ = BR.ReadUInt16(); this.iLevel_ = BR.ReadUInt16(); this.Unknown3_ = BR.ReadUInt32(); } else if (T == Type.PuppetItem) { this.PuppetSlot_ = (PuppetSlot)BR.ReadUInt16(); this.ElementCharge_ = BR.ReadUInt32(); this.Unknown3_ = BR.ReadUInt32(); } else if (T == Type.Instinct) { BR.ReadUInt32(); BR.ReadUInt32(); BR.ReadUInt16(); this.InstinctCost_ = BR.ReadUInt16(); BR.ReadUInt16(); BR.ReadUInt32(); BR.ReadUInt32(); BR.ReadUInt32(); } else if (T == Type.Item) { switch (this.Type_.Value) { case ItemType.Flowerpot: case ItemType.Furnishing: case ItemType.Mannequin: this.Element_ = (Element)BR.ReadUInt16(); this.StorageSlots_ = BR.ReadInt32(); this.Unknown3_ = BR.ReadUInt32(); break; default: this.Unknown2_ = BR.ReadUInt16(); this.Unknown3_ = BR.ReadUInt32(); this.Unknown3_ = BR.ReadUInt32(); break; } } else if (T == Type.UsableItem) { this.ActivationTime_ = BR.ReadUInt16(); this.Unknown1_ = BR.ReadUInt32(); this.Unknown3_ = BR.ReadUInt32(); this.Unknown4_ = BR.ReadUInt32(); } else if (T == Type.Currency) { this.Unknown2_ = BR.ReadUInt16(); } else if (T == Type.Slip) { this.Unknown1_ = BR.ReadUInt16(); for (int counter = 0; counter < 17; counter++) { BR.ReadUInt32(); } } else if (T == Type.Monipulator) { this.Unknown1_ = BR.ReadUInt16(); for (int counter = 0; counter < 24; counter++) { BR.ReadInt32(); } } // Next Up: Strings (variable size) long StringBase = BR.BaseStream.Position; uint StringCount = BR.ReadUInt32(); if (StringCount > 9) { // Sanity check, for safety - 0 strings is fine for now this.Clear(); return(false); } FFXIEncoding E = new FFXIEncoding(); string[] Strings = new string[StringCount]; for (byte i = 0; i < StringCount; ++i) { long Offset = StringBase + BR.ReadUInt32(); uint Flag = BR.ReadUInt32(); if (Offset < 0 || Offset + 0x20 > 0x280 || (Flag != 0 && Flag != 1)) { this.Clear(); return(false); } // Flag seems to be 1 if the offset is not actually an offset. Could just be padding to make StringCount unique per language, or it could be an indication // of the pronoun to use (a/an/the/...). The latter makes sense because of the increased number of such flags for french and german. if (Flag == 0) { BR.BaseStream.Position = Offset; Strings[i] = this.ReadString(BR, E); if (Strings[i] == null) { this.Clear(); return(false); } BR.BaseStream.Position = StringBase + 4 + 8 * (i + 1); } } // Assign the strings to the proper fields switch (StringCount) { case 1: this.Name_ = Strings[0]; break; case 2: // Japanese this.Name_ = Strings[0]; this.Description_ = Strings[1]; break; case 5: // English this.Name_ = Strings[0]; // unused: Strings[1] this.LogNameSingular_ = Strings[2]; this.LogNamePlural_ = Strings[3]; this.Description_ = Strings[4]; break; case 6: // French this.Name_ = Strings[0]; // unused: Strings[1] // unused: Strings[2] this.LogNameSingular_ = Strings[3]; this.LogNamePlural_ = Strings[4]; this.Description_ = Strings[5]; break; case 9: // German this.Name_ = Strings[0]; // unused: Strings[1] // unused: Strings[2] // unused: Strings[3] this.LogNameSingular_ = Strings[4]; // unused: Strings[5] // unused: Strings[6] this.LogNamePlural_ = Strings[7]; this.Description_ = Strings[8]; break; } BR.Close(); return(true); }
public static void DeduceType(BinaryReader BR, out Type T) { T = Type.Unknown; byte[] FirstItem = null; long Position = BR.BaseStream.Position; BR.BaseStream.Position = 0; try { while (BR.BaseStream.Position != BR.BaseStream.Length) { FirstItem = BR.ReadBytes(0x4); BR.BaseStream.Position += (0xc00 - 0x4); FFXIEncryption.Rotate(FirstItem, 5); { // Type -> Based on ID uint ID = 0; for (int i = 0; i < 4; ++i) { ID <<= 8; ID += FirstItem[3 - i]; } if (ID == 0xffff) { T = Type.Currency; } else if (ID < 0x1000) { T = Type.Item; } else if (ID < 0x2000) { T = Type.UsableItem; } else if (ID < 0x2200) { T = Type.PuppetItem; } else if (ID < 0x2800) { T = Type.Item; } else if (ID < 0x4000) { T = Type.Armor; } else if (ID < 0x5A00) { T = Type.Weapon; } else if (ID < 0x7000) { T = Type.Armor; } else if (ID < 0x7400) { T = Type.Slip; } else if (ID < 0x7800) { T = Type.Instinct; } else if (ID < 0xF200) { T = Type.Monipulator; } } if (T != Type.Unknown) { break; } } } catch { } BR.BaseStream.Position = Position; }
static public Dictionary <uint, FFXI_Item> ReadItemListFromDatFile(uint DatFileId) { Dictionary <uint, FFXI_Item> res = new Dictionary <uint, FFXI_Item>(); if (!FFXI_FTable.TryGetValue(DatFileId, out var FileDatInfo)) { return(res); } if (!File.Exists(FileDatInfo.FullFileName)) { return(res); } var FS = new FileStream(FileDatInfo.FullFileName, FileMode.Open, FileAccess.Read); // needs to be in 3072 byte block size, and a minimum of 16 entries if (((FS.Length % 0x0C00) != 0) || (FS.Length < 0xC000)) { return(res); } var BR = new BinaryReader(FS); FFXI_DeduceItemType(BR, out var ExpectedItemType); var itemCount = BR.BaseStream.Length / 0x0C00; for (var i = 0; i < itemCount; i++) { BR.BaseStream.Seek(i * 0x0C00, SeekOrigin.Begin); byte[] ItemBytes = BR.ReadBytes(0xC00); FFXIEncryption.Rotate(ItemBytes, 5); var MemBR = new BinaryReader(new MemoryStream(ItemBytes, false)); FFXI_Item item = new FFXI_Item(); // Common Fields (14 bytes) item.Id = MemBR.ReadUInt32(); item.Flags = (FFXI_ItemFlags)MemBR.ReadUInt16(); item.StackSize = MemBR.ReadUInt16(); // 0xe0ff for Currency, which kinda suggests this is really 2 separate bytes item.Type = (FFXI_ItemType)MemBR.ReadUInt16(); MemBR.ReadUInt16(); // item.ResourceID_ = MemBR.ReadUInt16(); MemBR.ReadUInt16(); // item.ValidTargets_ = (ValidTarget)MemBR.ReadUInt16(); // Extra Fields (22/30/10/6/2 bytes for Armor/Weapon/Puppet/Item/UsableItem) if (item.Type == FFXI_ItemType.Nothing) { continue; } switch (ExpectedItemType) { case FFXI_ItemDatFileTypes.Armor: case FFXI_ItemDatFileTypes.Weapon: MemBR.ReadUInt16(); // item.Level_ = MemBR.ReadUInt16(); MemBR.ReadUInt16(); // item.Slots_ = (EquipmentSlot)MemBR.ReadUInt16(); MemBR.ReadUInt16(); // item.Races_ = (Race)MemBR.ReadUInt16(); MemBR.ReadUInt32(); // item.Jobs_ = (Job)MemBR.ReadUInt32(); MemBR.ReadUInt16(); //item.SuperiorLevel_ = MemBR.ReadUInt16(); if (ExpectedItemType == FFXI_ItemDatFileTypes.Armor) { MemBR.ReadUInt16(); // item.ShieldSize_ = MemBR.ReadUInt16(); } else { // Weapon MemBR.ReadUInt16(); // item.Unknown4_ = MemBR.ReadUInt16(); MemBR.ReadUInt16(); // item.Damage_ = MemBR.ReadUInt16(); MemBR.ReadInt16(); // item.Delay_ = MemBR.ReadInt16(); MemBR.ReadUInt16(); // item.DPS_ = MemBR.ReadUInt16(); MemBR.ReadByte(); // item.Skill_ = (Skill)MemBR.ReadByte(); MemBR.ReadByte(); // item.JugSize_ = MemBR.ReadByte(); MemBR.ReadUInt32(); // item.Unknown1_ = MemBR.ReadUInt32(); } MemBR.ReadByte(); // item.MaxCharges_ = MemBR.ReadByte(); MemBR.ReadByte(); // item.CastingTime_ = MemBR.ReadByte(); MemBR.ReadUInt16(); // item.UseDelay_ = MemBR.ReadUInt16(); MemBR.ReadUInt32(); // item.ReuseDelay_ = MemBR.ReadUInt32(); MemBR.ReadUInt16(); // item.Unknown2_ = MemBR.ReadUInt16(); MemBR.ReadUInt16(); // item.iLevel_ = MemBR.ReadUInt16(); MemBR.ReadUInt32(); // item.Unknown3_ = MemBR.ReadUInt32(); break; case FFXI_ItemDatFileTypes.PuppetItem: MemBR.ReadUInt16(); // item.PuppetSlot_ = (PuppetSlot)MemBR.ReadUInt16(); MemBR.ReadUInt32(); // item.ElementCharge_ = MemBR.ReadUInt32(); MemBR.ReadUInt32(); // item.Unknown3_ = MemBR.ReadUInt32(); break; case FFXI_ItemDatFileTypes.Instinct: MemBR.ReadUInt32(); MemBR.ReadUInt32(); MemBR.ReadUInt16(); MemBR.ReadUInt16(); // item.InstinctCost_ = MemBR.ReadUInt16(); MemBR.ReadUInt16(); MemBR.ReadUInt32(); MemBR.ReadUInt32(); MemBR.ReadUInt32(); break; case FFXI_ItemDatFileTypes.Item: switch (item.Type) { case FFXI_ItemType.Flowerpot: case FFXI_ItemType.Furnishing: case FFXI_ItemType.Mannequin: MemBR.ReadUInt16(); // item.Element_ = (Element)MemBR.ReadUInt16(); MemBR.ReadInt32(); // item.StorageSlots_ = MemBR.ReadInt32(); MemBR.ReadUInt32(); // item.Unknown3_ = MemBR.ReadUInt32(); break; default: MemBR.ReadUInt16(); // item.Unknown2_ = MemBR.ReadUInt16(); MemBR.ReadUInt32(); // item.Unknown3_ = MemBR.ReadUInt32(); MemBR.ReadUInt32(); // item.Unknown3_ = MemBR.ReadUInt32(); break; } break; case FFXI_ItemDatFileTypes.UsableItem: MemBR.ReadUInt16(); // item.ActivationTime_ = MemBR.ReadUInt16(); MemBR.ReadUInt32(); // item.Unknown1_ = MemBR.ReadUInt32(); MemBR.ReadUInt32(); // item.Unknown3_ = MemBR.ReadUInt32(); MemBR.ReadUInt32(); // item.Unknown4_ = MemBR.ReadUInt32(); break; case FFXI_ItemDatFileTypes.Currency: MemBR.ReadUInt16(); // item.Unknown2_ = MemBR.ReadUInt16(); break; case FFXI_ItemDatFileTypes.ItemSlip: MemBR.ReadUInt16(); // item.Unknown1_ = MemBR.ReadUInt16(); for (int counter = 0; counter < 17; counter++) { MemBR.ReadUInt32(); } break; case FFXI_ItemDatFileTypes.Monipulator: MemBR.ReadUInt16(); // item.Unknown1_ = MemBR.ReadUInt16(); for (int counter = 0; counter < 24; counter++) { MemBR.ReadInt32(); } break; default: // If this is a unknown expected file type, ignore and exit return(res); } if (item.Type > FFXI_ItemType.Max) { // Invalid item type ? continue; } // Next Up: Strings (variable size) long StringBase = MemBR.BaseStream.Position; uint StringCount = MemBR.ReadUInt32(); if (StringCount > 9) { // Sanity check, for safety - 0 strings is fine for now // item.Clear(); continue; } FFXIEncoding E = new FFXIEncoding(); string[] Strings = new string[StringCount]; for (byte iStrings = 0; iStrings < StringCount; iStrings++) { long Offset = StringBase + MemBR.ReadUInt32(); uint Flag = MemBR.ReadUInt32(); if (Offset < 0 || Offset + 0x20 > 0x280 || (Flag != 0 && Flag != 1)) { // item.Clear(); StringCount = 0; break; } // Flag seems to be 1 if the offset is not actually an offset. Could just be padding to make StringCount unique per language, or it could be an indication // of the pronoun to use (a/an/the/...). The latter makes sense because of the increased number of such flags for french and german. if (Flag == 0) { MemBR.BaseStream.Position = Offset; Strings[iStrings] = FFXI_ReadString(MemBR, E); if (Strings[iStrings] == null) { // item.Clear(); StringCount = 0; break; } MemBR.BaseStream.Position = StringBase + 4 + 8 * (iStrings + 1); } } // Assign the strings to the proper fields switch (StringCount) { case 0: // Temporary hack until I can figure out what the hell is wrong with reading the strings if ((Strings.Length > 1) && (Strings[0] != null)) { item.Name = Strings[0]; } else { item.Name = "<no name found>"; } // item.Name = item.Type.ToString() + " - " + item.Id.ToString(); break; case 1: item.Name = Strings[0]; break; case 2: // Japanese item.Name = Strings[0]; item.Description = Strings[1]; break; case 5: // English item.Name = Strings[0]; // unused: Strings[1] item.NameSingle = Strings[2]; item.NameMultiple = Strings[3]; item.Description = Strings[4]; break; case 6: // French item.Name = Strings[0]; // unused: Strings[1] // unused: Strings[2] item.NameSingle = Strings[3]; item.NameMultiple = Strings[4]; item.Description = Strings[5]; break; case 9: // German item.Name = Strings[0]; // unused: Strings[1] // unused: Strings[2] // unused: Strings[3] item.NameSingle = Strings[4]; // unused: Strings[5] // unused: Strings[6] item.NameMultiple = Strings[7]; item.Description = Strings[8]; break; } MemBR.Close(); if ((item.Name != null) && (item.Name != string.Empty) && (item.Name != ".")) { res.Add(item.Id, item); } } return(res); }
public bool Read(BinaryReader BR, string Category, long MenuStart) { this.Clear(); this.Category_ = Category; try { this.ID_ = BR.ReadUInt32(); long Name1Start = BR.ReadInt32(); long Name2Start = BR.ReadInt32(); long BodyStart = BR.ReadInt32(); // Unknown (BodyEnd? not likely as it does not match "BodyStart + 4 * (1 + LineCount)" long Unknown = BR.ReadInt32(); if (Name1Start < 0 || Name2Start < 0 || BodyStart < 0 || Unknown < 0) { return(false); } FFXIEncoding E = new FFXIEncoding(); long CurPos = BR.BaseStream.Position; BR.BaseStream.Position = MenuStart + Name1Start; this.Name1_ = FFXIEncryption.ReadEncodedString(BR, E); BR.BaseStream.Position = MenuStart + Name2Start; this.Name2_ = FFXIEncryption.ReadEncodedString(BR, E); BR.BaseStream.Position = MenuStart + BodyStart; { int LineCount = BR.ReadInt32(); if (LineCount < 0) { BR.BaseStream.Position = CurPos; return(false); } { // Read entry description lines long[] LineStart = new long[LineCount]; for (int i = 0; i < LineCount; ++i) { LineStart[i] = BR.ReadInt32(); if (LineStart[i] < 0) { BR.BaseStream.Position = CurPos; return(false); } } this.Description_ = String.Empty; for (int i = 0; i < LineCount; ++i) { BR.BaseStream.Position = MenuStart + LineStart[i]; if (i > 0) { this.Description_ += "\r\n"; } this.Description_ += FFXIEncryption.ReadEncodedString(BR, E); } } } BR.BaseStream.Position = MenuStart + Unknown; { int LineCount = BR.ReadInt32(); if (LineCount < 0) { BR.BaseStream.Position = CurPos; return(false); } { // Read entry description lines long[] LineStart = new long[LineCount]; for (int i = 0; i < LineCount; ++i) { LineStart[i] = BR.ReadInt32(); if (LineStart[i] < 0) { BR.BaseStream.Position = CurPos; return(false); } } this.Extra_ = String.Empty; for (int i = 0; i < LineCount; ++i) { BR.BaseStream.Position = MenuStart + LineStart[i]; if (i > 0) { this.Extra_ += "\r\n"; } this.Extra_ += FFXIEncryption.ReadEncodedString(BR, E); } } } BR.BaseStream.Position = CurPos; return(true); } catch { return(false); } }
private void TranslateItemFile(int JPFileNumber, int ENFileNumber) { if (!this.mnuTranslateItemNames.Checked && !this.mnuTranslateItemDescriptions.Checked) { return; } if (!this.BackupFile(JPFileNumber)) { return; } try { string JFileName = FFXI.GetFilePath(JPFileNumber); string EFileName = FFXI.GetFilePath(ENFileNumber); this.AddLogEntry(String.Format(I18N.GetText("TranslatingItems"), JFileName)); this.AddLogEntry(String.Format(I18N.GetText("UsingEnglishFile"), EFileName)); FileStream JStream = new FileStream(JFileName, FileMode.Open, FileAccess.ReadWrite, FileShare.None); FileStream EStream = new FileStream(EFileName, FileMode.Open, FileAccess.Read, FileShare.Read); if ((JStream.Length % 0xc00) != 0) { this.AddLogEntry(I18N.GetText("ItemFileSizeBad")); goto TranslationDone; } if (JStream.Length != EStream.Length) { this.AddLogEntry(I18N.GetText("FileSizeMismatch")); goto TranslationDone; } Things.Item.Type T; { BinaryReader BR = new BinaryReader(JStream); Things.Item.DeduceType(BR, out T); BR = new BinaryReader(EStream); Things.Item.Type ET; Things.Item.DeduceType(BR, out ET); if (T != ET) { this.AddLogEntry(I18N.GetText("ItemTypeMismatch")); goto TranslationDone; } } ushort StringBase = 0; switch (T) { case Things.Item.Type.Armor: StringBase = 36; break; case Things.Item.Type.Item: StringBase = 20; break; case Things.Item.Type.PuppetItem: StringBase = 24; break; case Things.Item.Type.UsableItem: StringBase = 16; break; case Things.Item.Type.Weapon: StringBase = 44; break; case Things.Item.Type.Slip: StringBase = 80; break; default: this.AddLogEntry(I18N.GetText("ItemTypeBad")); goto TranslationDone; } long ItemCount = JStream.Length / 0xc00; byte[] JData = new byte[0x280]; byte[] EData = new byte[0x280]; MemoryStream JMemStream = new MemoryStream(JData, true); MemoryStream EMemStream = new MemoryStream(EData, false); FFXIEncoding E = new FFXIEncoding(); for (long i = 0; i < ItemCount; ++i) { JStream.Seek(i * 0xc00, SeekOrigin.Begin); JStream.Read(JData, 0, 0x280); EStream.Seek(i * 0xc00, SeekOrigin.Begin); EStream.Read(EData, 0, 0x280); FFXIEncryption.Rotate(JData, 5); FFXIEncryption.Rotate(EData, 5); // Read English or Japanese Name List <byte> Name = new List <byte>(); { MemoryStream MS = null; if (this.mnuTranslateItemNames.Checked) { MS = EMemStream; } else { MS = JMemStream; } MS.Position = StringBase + 4; BinaryReader BR = new BinaryReader(MS); MS.Position = StringBase + 0x1c + BR.ReadUInt32(); while (MS.Position < 0x280) { int B = MS.ReadByte(); if (B <= 0) { break; } Name.Add((byte)B); } Name.Add(0); while (Name.Count % 4 != 0) { Name.Add(0); } } // Read English or Japanese Description List <byte> Description = new List <byte>(); { MemoryStream MS = null; if (this.mnuTranslateItemDescriptions.Checked) { EMemStream.Position = StringBase + 4 + 8 * 4; MS = EMemStream; } else { JMemStream.Position = StringBase + 4 + 8 * 1; MS = JMemStream; } BinaryReader BR = new BinaryReader(MS); MS.Position = StringBase + 0x1c + BR.ReadUInt32(); while (MS.Position < 0x280) { int B = MS.ReadByte(); if (B <= 0) { break; } Description.Add((byte)B); } Description.Add(0); while (Description.Count % 4 != 0) { Description.Add(0); } } { // Construct a new string table BinaryWriter BW = new BinaryWriter(JMemStream); Array.Clear(JData, StringBase, 0x280 - StringBase); JMemStream.Position = StringBase; uint NameOffset = 0x14; // Right after the string table header uint DescOffset = NameOffset + (uint)Name.Count + 28; BW.Write((uint)2); // String Count BW.Write(NameOffset); // Entry #1 BW.Write((uint)0); BW.Write(DescOffset); // Entry #2 BW.Write((uint)0); // String #1 - Padding + text bytes BW.Write((uint)1); BW.Write((ulong)0); BW.Write((ulong)0); BW.Write((ulong)0); BW.Write(Name.ToArray()); // String #2 - Padding + text bytes BW.Write((uint)1); BW.Write((ulong)0); BW.Write((ulong)0); BW.Write((ulong)0); BW.Write(Description.ToArray()); // End marker BW.Write((uint)1); } // Update file data FFXIEncryption.Rotate(JData, 3); JStream.Seek(i * 0xc00, SeekOrigin.Begin); JStream.Write(JData, 0, 0x280); } TranslationDone: JStream.Close(); EStream.Close(); } catch { this.LogFailure("TranslateItemFile"); } }