public override ThingList Load(BinaryReader BR, ProgressCallback ProgressCallback) { ThingList TL = new ThingList(); if (ProgressCallback != null) ProgressCallback(I18N.GetText("FTM:CheckingFile"), 0); if (BR.BaseStream.Length < 0x40 || BR.BaseStream.Position != 0) return TL; FFXIEncoding E = new FFXIEncoding(); if (E.GetString(BR.ReadBytes(8)) != "d_msg".PadRight(8, '\0')) return TL; ushort Flag1 = BR.ReadUInt16(); if (Flag1 != 0 && Flag1 != 1) return TL; ushort Flag2 = BR.ReadUInt16(); if (Flag2 != 0 && Flag2 != 1) return TL; if (BR.ReadUInt32() != 3 || BR.ReadUInt32() != 3) return TL; uint FileSize = BR.ReadUInt32(); if (FileSize != BR.BaseStream.Length) return TL; uint HeaderBytes = BR.ReadUInt32(); if (HeaderBytes != 0x40) return TL; if (BR.ReadUInt32() != 0) return TL; int BytesPerEntry = BR.ReadInt32(); if (BytesPerEntry < 0) return TL; uint DataBytes = BR.ReadUInt32(); if (FileSize != (HeaderBytes + DataBytes) || (DataBytes % BytesPerEntry) != 0) return TL; uint EntryCount = BR.ReadUInt32(); if (EntryCount * BytesPerEntry != DataBytes) return TL; if (BR.ReadUInt32() != 1 || BR.ReadUInt64() != 0 || BR.ReadUInt64() != 0) return TL; if (ProgressCallback != null) ProgressCallback(I18N.GetText("FTM:LoadingData"), 0); for (uint i = 0; i < EntryCount; ++i) { BinaryReader EntryBR = new BinaryReader(new MemoryStream(BR.ReadBytes(BytesPerEntry))); EntryBR.BaseStream.Position = 0; bool ItemAdded = false; { Things.DMSGStringBlock SB = new Things.DMSGStringBlock(); if (SB.Read(EntryBR, E, i)) { TL.Add(SB); ItemAdded = true; } } EntryBR.Close(); if (!ItemAdded) { TL.Clear(); break; } if (ProgressCallback != null) ProgressCallback(null, (double) (i + 1) / EntryCount); } return TL; }
public override ThingList Load(BinaryReader BR, ProgressCallback ProgressCallback) { ThingList TL = new ThingList(); if (ProgressCallback != null) ProgressCallback(I18N.GetText("FTM:CheckingFile"), 0); if (BR.BaseStream.Length < 0x40 || BR.BaseStream.Position != 0) return TL; FFXIEncoding E = new FFXIEncoding(); // Skip (presumably) fixed portion of the header if ((E.GetString(BR.ReadBytes(8)) != "d_msg".PadRight(8, '\0')) || BR.ReadUInt16() != 1 || BR.ReadUInt16() != 1 || BR.ReadUInt32() != 3 || BR.ReadUInt32() != 3) return TL; // Read the useful header fields uint FileSize = BR.ReadUInt32(); if (FileSize != BR.BaseStream.Length) return TL; uint HeaderBytes = BR.ReadUInt32(); if (HeaderBytes != 0x40) return TL; uint EntryBytes = BR.ReadUInt32(); if (BR.ReadUInt32() != 0) return TL; uint DataBytes = BR.ReadUInt32(); if (FileSize != HeaderBytes + EntryBytes + DataBytes) return TL; uint EntryCount = BR.ReadUInt32(); if (EntryBytes != EntryCount * 8) return TL; if (BR.ReadUInt32() != 1 || BR.ReadUInt64() != 0 || BR.ReadUInt64() != 0) return TL; if (ProgressCallback != null) ProgressCallback(I18N.GetText("FTM:LoadingData"), 0); for (uint i = 0; i < EntryCount; ++i) { BR.BaseStream.Position = HeaderBytes + i * 8; int Offset = ~BR.ReadInt32(); int Length = ~BR.ReadInt32(); if (Length < 0 || Offset < 0 || Offset + Length > DataBytes) { TL.Clear(); break; } BR.BaseStream.Position = HeaderBytes + EntryBytes + Offset; BinaryReader EntryBR = new BinaryReader(new MemoryStream(BR.ReadBytes(Length))); bool ItemAdded = false; { Things.DMSGStringBlock SB = new Things.DMSGStringBlock(); if (SB.Read(EntryBR, E, i)) { TL.Add(SB); ItemAdded = true; } } EntryBR.Close(); if (!ItemAdded) { TL.Clear(); break; } if (ProgressCallback != null) ProgressCallback(null, (double) (i + 1) / EntryCount); } return TL; }
public override ThingList Load(BinaryReader BR, ProgressCallback ProgressCallback) { ThingList TL = new ThingList(); if (ProgressCallback != null) { ProgressCallback(I18N.GetText("FTM:CheckingFile"), 0); } if (BR.BaseStream.Length < 0x38 || BR.BaseStream.Position != 0) { return TL; } FFXIEncoding E = new FFXIEncoding(); // Read past the marker (32 bytes) if ((E.GetString(BR.ReadBytes(10)) != "XISTRING".PadRight(10, '\0')) || BR.ReadUInt16() != 2) { return TL; } foreach (byte B in BR.ReadBytes(20)) { if (B != 0) { return TL; } } // Read The Header uint FileSize = BR.ReadUInt32(); if (FileSize != BR.BaseStream.Length) { return TL; } uint EntryCount = BR.ReadUInt32(); uint EntryBytes = BR.ReadUInt32(); uint DataBytes = BR.ReadUInt32(); BR.ReadUInt32(); // Unknown BR.ReadUInt32(); // Unknown if (EntryBytes != EntryCount * 12 || FileSize != 0x38 + EntryBytes + DataBytes) { return TL; } if (ProgressCallback != null) { ProgressCallback(I18N.GetText("FTM:LoadingData"), 0); } for (uint i = 0; i < EntryCount; ++i) { Things.XIStringTableEntry XSTE = new Things.XIStringTableEntry(); if (!XSTE.Read(BR, E, i, EntryBytes, DataBytes)) { TL.Clear(); break; } if (ProgressCallback != null) { ProgressCallback(null, (double)(i + 1) / EntryCount); } TL.Add(XSTE); } return TL; }
public override ThingList Load(BinaryReader BR, ProgressCallback ProgressCallback) { ThingList TL = new ThingList(); if (ProgressCallback != null) ProgressCallback(I18N.GetText("FTM:CheckingFile"), 0); if (BR.BaseStream.Length < 0x38 || BR.BaseStream.Position != 0) return TL; FFXIEncoding E = new FFXIEncoding(); // Skip (presumably) fixed portion of the header if ((E.GetString(BR.ReadBytes(8)) != "d_msg".PadRight(8, '\0')) || BR.ReadUInt16() != 1 || BR.ReadUInt32() != 0 || BR.ReadUInt16() != 2 || BR.ReadUInt32() != 3) return TL; // Read the useful header fields uint EntryCount = BR.ReadUInt32(); if (BR.ReadUInt32() != 1) return TL; uint FileSize = BR.ReadUInt32(); if (FileSize != BR.BaseStream.Length) return TL; uint HeaderSize = BR.ReadUInt32(); if (HeaderSize != 0x38) return TL; uint EntryBytes = BR.ReadUInt32(); if (EntryBytes != EntryCount * 36) return TL; uint DataBytes = BR.ReadUInt32(); if (FileSize != 0x38 + EntryBytes + DataBytes) return TL; // 12 NUL bytes if (BR.ReadUInt32() != 0 || BR.ReadUInt32() != 0 || BR.ReadUInt32() != 0) return TL; if (ProgressCallback != null) ProgressCallback(I18N.GetText("FTM:LoadingData"), 0); for (uint i = 0; i < EntryCount; ++i) { Things.DMSGStringTableEntry DSTE = new Things.DMSGStringTableEntry(); if (!DSTE.Read(BR, E, i, EntryBytes, DataBytes)) { TL.Clear(); break; } if (ProgressCallback != null) ProgressCallback(null, (double) (i + 1) / EntryCount); TL.Add(DSTE); } return TL; }
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; } }
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; }
// 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; } }
public bool Read(BinaryReader BR) { this.Clear(); try { FFXIEncoding E = new FFXIEncoding(); this.Name_ = E.GetString(BR.ReadBytes(0x1C)).TrimEnd('\0'); this.ID_ = BR.ReadUInt32(); // ID seems to be 010 + zone id + mob id (=> there's a hard max of 0xFFF (= 4095) mobs per zone, which seems plenty :)) // Special 'instanced' zones like MMM or Meebles use 013 + 'zone id' + mob id. if ((this.ID_ != 0 && (this.ID_ & 0xFFF00000) != 0x01000000) && (this.ID_ != 0 && (this.ID_ & 0xFFF00000) != 0x01300000) && (this.ID_ != 0 && (this.ID_ & 0xFFF00000) != 0x01100000)) { return false; } 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; }
public bool Read(BinaryReader BR, uint? Index, long EntryStart, long EntryEnd) { this.Clear(); this.Index_ = Index; try { BR.BaseStream.Seek(4 + EntryStart, SeekOrigin.Begin); byte[] TextBytes = BR.ReadBytes((int) (EntryEnd - EntryStart)); for (int i = 0; i < TextBytes.Length; ++i) TextBytes[i] ^= 0x80; // <= Evil encryption-breaking! this.Text_ = String.Empty; FFXIEncoding E = new FFXIEncoding(); int LastPos = 0; for (int i = 0; i < TextBytes.Length; ++i) { if (TextBytes[i] == 0x07) { // Line Break if (LastPos < i) this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); this.Text_ += "\r\n"; LastPos = i + 1; } else if (TextBytes[i] == 0x08) { // Character Name (You) if (LastPos < i) this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); this.Text_ += String.Format("{0}Player Name{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd); LastPos = i + 1; } else if (TextBytes[i] == 0x09) { // Character Name (They) if (LastPos < i) this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); this.Text_ += String.Format("{0}Speaker Name{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd); LastPos = i + 1; } else if (TextBytes[i] == 0x0a && i + 1 < TextBytes.Length) { if (LastPos < i) this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); this.Text_ += String.Format("{0}Numeric Parameter {2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 1]); LastPos = i + 2; ++i; } else if (TextBytes[i] == 0x0b) { // Indicates that the lines after this are in a prompt window if (LastPos < i) this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); this.Text_ += String.Format("{0}Selection Dialog{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd); LastPos = i + 1; } else if (TextBytes[i] == 0x0c && i + 1 < TextBytes.Length) { if (LastPos < i) this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); this.Text_ += String.Format("{0}Multiple Choice (Parameter {2}){1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 1]); LastPos = i + 2; ++i; } else if (TextBytes[i] == 0x19 && i + 1 < TextBytes.Length) { if (LastPos < i) this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); this.Text_ += String.Format("{0}Item Parameter {2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 1]); LastPos = i + 2; ++i; } else if (TextBytes[i] == 0x1a && i + 1 < TextBytes.Length) { if (LastPos < i) this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); this.Text_ += String.Format("{0}Key Item Parameter {2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 1]); LastPos = i + 2; ++i; } else if (TextBytes[i] == 0x1c && i + 1 < TextBytes.Length) { // Chocobo Name if (LastPos < i) this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); this.Text_ += String.Format("{0}Player/Chocobo Parameter {2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 1]); LastPos = i + 2; ++i; } else if (TextBytes[i] == 0x1e && i + 1 < TextBytes.Length) { if (LastPos < i) this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); this.Text_ += String.Format("{0}Set Color #{2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 1]); LastPos = i + 2; ++i; } else if (TextBytes[i] == 0x7f && i + 1 < TextBytes.Length) { // Various stuff if (LastPos < i) this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); if (TextBytes[i + 1] == 0x31 && i + 2 < TextBytes.Length) { // Unknown, but seems to indicate user needs to hit RET if (TextBytes[i + 2] != 0) this.Text_ += String.Format("{0}{2}-Second Delay + Prompt{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 2]); else this.Text_ += String.Format("{0}Prompt{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd); ++LastPos; ++i; } else if (TextBytes[i + 1] == 0x85) // Multiple Choice: Player Gender this.Text_ += String.Format("{0}Multiple Choice (Player Gender){1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd); else if (TextBytes[i + 1] == 0x8D && i + 2 < TextBytes.Length) { this.Text_ += String.Format("{0}Weather Event Parameter {2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 2]); ++LastPos; ++i; } else if (TextBytes[i + 1] == 0x8E && i + 2 < TextBytes.Length) { this.Text_ += String.Format("{0}Weather Type Parameter {2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 2]); ++LastPos; ++i; } else if (TextBytes[i + 1] == 0x92 && i + 2 < TextBytes.Length) { this.Text_ += String.Format("{0}Singular/Plural Choice (Parameter {2}){1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 2]); ++LastPos; ++i; } else if (TextBytes[i + 1] == 0xB1 && i + 2 < TextBytes.Length) { // Usually found before an item name or key item name this.Text_ += String.Format("{0}Title Parameter {2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 2]); ++LastPos; ++i; } else if (i + 2 < TextBytes.Length) { this.Text_ += String.Format("{0}Unknown Parameter (Type: {2:X2}) {3}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 1], TextBytes[i + 2]); ++LastPos; ++i; } else this.Text_ += String.Format("{0}Unknown Marker Type: {2:X2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 1]); LastPos = i + 2; ++i; } else if (TextBytes[i] == 0x7f || TextBytes[i] < 0x20) { if (LastPos < i) this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); this.Text_ += String.Format("{0}Possible Special Code: {2:X2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i]); LastPos = i + 1; } } if (LastPos < TextBytes.Length) this.Text_ += E.GetString(TextBytes, LastPos, TextBytes.Length - LastPos); this.Text_ = this.Text_.TrimEnd('\0'); return true; } catch { return false; } }
// 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_ = BR.ReadBytes(24); this.ID_ = BR.ReadUInt16(); this.ListIconID_ = BR.ReadByte(); 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, uint?Index, long EntryStart, long EntryEnd) { this.Clear(); this.Index_ = Index; try { BR.BaseStream.Seek(4 + EntryStart, SeekOrigin.Begin); byte[] TextBytes = BR.ReadBytes((int)(EntryEnd - EntryStart)); for (int i = 0; i < TextBytes.Length; ++i) { TextBytes[i] ^= 0x80; // <= Evil encryption-breaking! } this.Text_ = String.Empty; FFXIEncoding E = new FFXIEncoding(); int LastPos = 0; for (int i = 0; i < TextBytes.Length; ++i) { if (TextBytes[i] == 0x07) // Line Break { if (LastPos < i) { this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); } this.Text_ += "\r\n"; LastPos = i + 1; } else if (TextBytes[i] == 0x08) // Character Name (You) { if (LastPos < i) { this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); } this.Text_ += String.Format("{0}Player Name{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd); LastPos = i + 1; } else if (TextBytes[i] == 0x09) // Character Name (They) { if (LastPos < i) { this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); } this.Text_ += String.Format("{0}Speaker Name{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd); LastPos = i + 1; } else if (TextBytes[i] == 0x0a && i + 1 < TextBytes.Length) { if (LastPos < i) { this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); } this.Text_ += String.Format("{0}Numeric Parameter {2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 1]); LastPos = i + 2; ++i; } else if (TextBytes[i] == 0x0b) // Indicates that the lines after this are in a prompt window { if (LastPos < i) { this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); } this.Text_ += String.Format("{0}Selection Dialog{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd); LastPos = i + 1; } else if (TextBytes[i] == 0x0c && i + 1 < TextBytes.Length) { if (LastPos < i) { this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); } this.Text_ += String.Format("{0}Multiple Choice (Parameter {2}){1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 1]); LastPos = i + 2; ++i; } else if (TextBytes[i] == 0x19 && i + 1 < TextBytes.Length) { if (LastPos < i) { this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); } this.Text_ += String.Format("{0}Item Parameter {2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 1]); LastPos = i + 2; ++i; } else if (TextBytes[i] == 0x1a && i + 1 < TextBytes.Length) { if (LastPos < i) { this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); } this.Text_ += String.Format("{0}Key Item Parameter {2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 1]); LastPos = i + 2; ++i; } else if (TextBytes[i] == 0x1c && i + 1 < TextBytes.Length) // Chocobo Name { if (LastPos < i) { this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); } this.Text_ += String.Format("{0}Player/Chocobo Parameter {2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 1]); LastPos = i + 2; ++i; } else if (TextBytes[i] == 0x1e && i + 1 < TextBytes.Length) { if (LastPos < i) { this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); } this.Text_ += String.Format("{0}Set Color #{2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 1]); LastPos = i + 2; ++i; } else if (TextBytes[i] == 0x7f && i + 1 < TextBytes.Length) // Various stuff { if (LastPos < i) { this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); } if (TextBytes[i + 1] == 0x31 && i + 2 < TextBytes.Length) // Unknown, but seems to indicate user needs to hit RET { if (TextBytes[i + 2] != 0) { this.Text_ += String.Format("{0}{2}-Second Delay + Prompt{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 2]); } else { this.Text_ += String.Format("{0}Prompt{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd); } ++LastPos; ++i; } else if (TextBytes[i + 1] == 0x85) // Multiple Choice: Player Gender { this.Text_ += String.Format("{0}Multiple Choice (Player Gender){1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd); } else if (TextBytes[i + 1] == 0x8D && i + 2 < TextBytes.Length) { this.Text_ += String.Format("{0}Weather Event Parameter {2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 2]); ++LastPos; ++i; } else if (TextBytes[i + 1] == 0x8E && i + 2 < TextBytes.Length) { this.Text_ += String.Format("{0}Weather Type Parameter {2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 2]); ++LastPos; ++i; } else if (TextBytes[i + 1] == 0x92 && i + 2 < TextBytes.Length) { this.Text_ += String.Format("{0}Singular/Plural Choice (Parameter {2}){1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 2]); ++LastPos; ++i; } else if (TextBytes[i + 1] == 0xB1 && i + 2 < TextBytes.Length) // Usually found before an item name or key item name { this.Text_ += String.Format("{0}Title Parameter {2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 2]); ++LastPos; ++i; } else if (i + 2 < TextBytes.Length) { this.Text_ += String.Format("{0}Unknown Parameter (Type: {2:X2}) {3}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 1], TextBytes[i + 2]); ++LastPos; ++i; } else { this.Text_ += String.Format("{0}Unknown Marker Type: {2:X2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 1]); } LastPos = i + 2; ++i; } else if (TextBytes[i] == 0x7f || TextBytes[i] < 0x20) { if (LastPos < i) { this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); } this.Text_ += String.Format("{0}Possible Special Code: {2:X2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i]); LastPos = i + 1; } } if (LastPos < TextBytes.Length) { this.Text_ += E.GetString(TextBytes, LastPos, TextBytes.Length - LastPos); } this.Text_ = this.Text_.TrimEnd('\0'); return(true); } catch { return(false); } }
private void TranslateAutoTranslatorFile(int JPFileNumber, int ENFileNumber) { if (!this.BackupFile(JPFileNumber)) { return; } try { string JFileName = FFXI.GetFilePath(JPFileNumber); string EFileName = FFXI.GetFilePath(ENFileNumber); this.AddLogEntry(String.Format(I18N.GetText("TranslatingAutoTrans"), JFileName)); this.AddLogEntry(String.Format(I18N.GetText("UsingEnglishFile"), EFileName)); FileStream ATStream = new FileStream(JFileName, FileMode.Open, FileAccess.ReadWrite, FileShare.None); MemoryStream NewStream = new MemoryStream(); BinaryReader JBR = new BinaryReader(ATStream); BinaryWriter JBW = new BinaryWriter(NewStream); BinaryReader EBR = new BinaryReader(new FileStream(EFileName, FileMode.Open, FileAccess.Read)); FFXIEncoding E = new FFXIEncoding(); while (ATStream.Position != ATStream.Length) { { // Validate & Copy ID uint JID = JBR.ReadUInt32(); uint EID = EBR.ReadUInt32(); if ((JID & 0xffff) != 0x102) { this.AddLogEntry(String.Format(I18N.GetText("ATBadJPID"), JID)); goto TranslationDone; } if ((EID & 0xffff) != 0x202) { this.AddLogEntry(String.Format(I18N.GetText("ATBadENID"), EID)); goto TranslationDone; } if ((EID & 0xffff0000) != (JID & 0xffff0000)) { this.AddLogEntry(String.Format(I18N.GetText("ATIDMismatch"), JID, EID)); goto TranslationDone; } JBW.Write(JID); } // Group name -> use English JBW.Write(EBR.ReadBytes(32)); JBR.BaseStream.Position += 32; // Completion Text -> based on config if (this.mnuPreserveJapaneseATCompletion.Checked) { JBW.Write(JBR.ReadBytes(32)); EBR.BaseStream.Position += 32; } else { byte[] EnglishCompletion = E.GetBytes(E.GetString(EBR.ReadBytes(32)).ToLowerInvariant()); // we want it lowercase if (EnglishCompletion.Length != 32) { this.AddLogEntry(String.Format(I18N.GetText("ATLowercaseProblem"), EnglishCompletion.Length)); goto TranslationDone; } JBW.Write(EnglishCompletion); JBR.BaseStream.Position += 32; } uint JEntryCount = JBR.ReadUInt32(); uint EEntryCount = EBR.ReadUInt32(); if (JEntryCount != EEntryCount) { this.AddLogEntry(String.Format(I18N.GetText("ATCountMismatch"), JEntryCount, EEntryCount)); goto TranslationDone; } JBW.Write(JEntryCount); long EntryBytesPos = JBW.BaseStream.Position; JBW.Write((uint)0); JBR.BaseStream.Position += 4; EBR.BaseStream.Position += 4; for (uint i = 0; i < JEntryCount; ++i) { // Validate & Copy ID uint JID = JBR.ReadUInt32(); uint EID = EBR.ReadUInt32(); if ((JID & 0xffff) != 0x102) { this.AddLogEntry(String.Format(I18N.GetText("ATBadJPID"), JID)); goto TranslationDone; } if ((EID & 0xffff) != 0x202) { this.AddLogEntry(String.Format(I18N.GetText("ATBadENID"), EID)); goto TranslationDone; } if ((EID & 0xffff0000) != (JID & 0xffff0000)) { this.AddLogEntry(String.Format(I18N.GetText("ATIDMismatch"), JID, EID)); goto TranslationDone; } JBW.Write(JID); // Display text -> use English byte[] EnglishText = EBR.ReadBytes(EBR.ReadByte()); JBW.Write((byte)EnglishText.Length); JBW.Write(EnglishText); JBR.BaseStream.Position += 1 + JBR.ReadByte(); // Completion Text -> based on config if (this.mnuPreserveJapaneseATCompletion.Checked) { byte[] JapaneseText = JBR.ReadBytes(JBR.ReadByte()); JBW.Write((byte)JapaneseText.Length); JBW.Write(JapaneseText); } else { byte[] LowerEnglishText = E.GetBytes(E.GetString(EnglishText).ToLowerInvariant()); JBW.Write((byte)LowerEnglishText.Length); JBW.Write(LowerEnglishText); JBR.BaseStream.Position += 1 + JBR.ReadByte(); } } long EndOfGroupPos = JBW.BaseStream.Position; JBW.BaseStream.Position = EntryBytesPos; JBW.Write((uint)(EndOfGroupPos - EntryBytesPos - 4)); JBW.BaseStream.Position = EndOfGroupPos; } ATStream.Seek(0, SeekOrigin.Begin); ATStream.Write(NewStream.ToArray(), 0, (int)NewStream.Length); ATStream.SetLength(NewStream.Length); TranslationDone: ATStream.Close(); NewStream.Close(); EBR.Close(); } catch { this.LogFailure("TranslateAutoTranslator"); } }
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"); } }
public bool Read(BinaryReader BR) { this.Clear(); try { this.ID_ = BR.ReadUInt32(); // Read entry byte[] TextBytes = BR.ReadBytes(0x3b); this.Text_ = String.Empty; // Trim trailing NULs int ByteCount = 0x3b; while (TextBytes[ByteCount - 1] == 0x00) --ByteCount; // Process the message FFXIEncoding E = new FFXIEncoding(); int LastPos = 0; for (int i = 0; i < ByteCount; ++i) { if (TextBytes[i] == 0x07) { // Line Break if (LastPos < i) this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); this.Text_ += "\r\n"; LastPos = i + 1; } else if (TextBytes[i] == 0x08) { // Character Name (You) if (LastPos < i) this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); this.Text_ += String.Format("{0}Player Name{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd); LastPos = i + 1; } else if (TextBytes[i] == 0x09) { // Character Name (They) if (LastPos < i) this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); this.Text_ += String.Format("{0}Speaker Name{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd); LastPos = i + 1; } else if (TextBytes[i] == 0x0a && i + 1 < TextBytes.Length) { if (LastPos < i) this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); this.Text_ += String.Format("{0}Numeric Parameter {2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 1]); LastPos = i + 2; ++i; } else if (TextBytes[i] == 0x0b) { // Indicates that the lines after this are in a prompt window if (LastPos < i) this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); this.Text_ += String.Format("{0}Selection Dialog{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd); LastPos = i + 1; } else if (TextBytes[i] == 0x0c && i + 1 < TextBytes.Length) { if (LastPos < i) this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); this.Text_ += String.Format("{0}Multiple Choice (Parameter {2}){1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 1]); LastPos = i + 2; ++i; } else if (TextBytes[i] == 0x19 && i + 1 < TextBytes.Length) { if (LastPos < i) this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); this.Text_ += String.Format("{0}Item Parameter {2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 1]); LastPos = i + 2; ++i; } else if (TextBytes[i] == 0x1a && i + 1 < TextBytes.Length) { if (LastPos < i) this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); this.Text_ += String.Format("{0}Key Item Parameter {2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 1]); LastPos = i + 2; ++i; } else if (TextBytes[i] == 0x1c && i + 1 < TextBytes.Length) { // Chocobo Name if (LastPos < i) this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); this.Text_ += String.Format("{0}Player/Chocobo Parameter {2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 1]); LastPos = i + 2; ++i; } else if (TextBytes[i] == 0x1e && i + 1 < TextBytes.Length) { if (LastPos < i) this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); this.Text_ += String.Format("{0}Set Color #{2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 1]); LastPos = i + 2; ++i; } else if (TextBytes[i] == 0x7f && i + 1 < TextBytes.Length) { // Various stuff if (LastPos < i) this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); if (TextBytes[i + 1] == 0x31 && i + 2 < TextBytes.Length) { // Unknown, but seems to indicate user needs to hit RET if (TextBytes[i + 2] != 0) this.Text_ += String.Format("{0}{2}-Second Delay + Prompt{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 2]); else this.Text_ += String.Format("{0}Prompt{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd); ++LastPos; ++i; } else if (TextBytes[i + 1] == 0x85) // Multiple Choice: Player Gender this.Text_ += String.Format("{0}Multiple Choice (Player Gender){1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd); else if (TextBytes[i + 1] == 0x8D && i + 2 < TextBytes.Length) { this.Text_ += String.Format("{0}Weather Event Parameter {2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 2]); ++LastPos; ++i; } else if (TextBytes[i + 1] == 0x8E && i + 2 < TextBytes.Length) { this.Text_ += String.Format("{0}Weather Type Parameter {2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 2]); ++LastPos; ++i; } else if (TextBytes[i + 1] == 0x92 && i + 2 < TextBytes.Length) { this.Text_ += String.Format("{0}Singular/Plural Choice (Parameter {2}){1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 2]); ++LastPos; ++i; } else if (TextBytes[i + 1] == 0xB1 && i + 2 < TextBytes.Length) { // Usually found before an item name or key item name this.Text_ += String.Format("{0}Title Parameter {2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 2]); ++LastPos; ++i; } else if (i + 2 < TextBytes.Length) { this.Text_ += String.Format("{0}Unknown Parameter (Type: {2:X2}) {3}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 1], TextBytes[i + 2]); ++LastPos; ++i; } else this.Text_ += String.Format("{0}Unknown Marker Type: {2:X2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i + 1]); LastPos = i + 2; ++i; } else if (TextBytes[i] == 0x7f || TextBytes[i] < 0x20) { if (LastPos < i) this.Text_ += E.GetString(TextBytes, LastPos, i - LastPos); this.Text_ += String.Format("{0}Possible Special Code: {2:X2}{1}", FFXIEncoding.SpecialMarkerStart, FFXIEncoding.SpecialMarkerEnd, TextBytes[i]); LastPos = i + 1; } } if (LastPos < ByteCount) this.Text_ += E.GetString(TextBytes, LastPos, ByteCount - LastPos); return (BR.ReadByte() == 0xff); } 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"); } }
private void TranslateAutoTranslatorFile(int JPFileNumber, int ENFileNumber) { if (!this.BackupFile(JPFileNumber)) return; try { string JFileName = FFXI.GetFilePath(JPFileNumber); string EFileName = FFXI.GetFilePath(ENFileNumber); this.AddLogEntry(String.Format(I18N.GetText("TranslatingAutoTrans"), JFileName)); this.AddLogEntry(String.Format(I18N.GetText("UsingEnglishFile"), EFileName)); FileStream ATStream = new FileStream(JFileName, FileMode.Open, FileAccess.ReadWrite, FileShare.None); MemoryStream NewStream = new MemoryStream(); BinaryReader JBR = new BinaryReader(ATStream); BinaryWriter JBW = new BinaryWriter(NewStream); BinaryReader EBR = new BinaryReader(new FileStream(EFileName, FileMode.Open, FileAccess.Read)); FFXIEncoding E = new FFXIEncoding(); while (ATStream.Position != ATStream.Length) { { // Validate & Copy ID uint JID = JBR.ReadUInt32(); uint EID = EBR.ReadUInt32(); if ((JID & 0xffff) != 0x102) { this.AddLogEntry(String.Format(I18N.GetText("ATBadJPID"), JID)); goto TranslationDone; } if ((EID & 0xffff) != 0x202) { this.AddLogEntry(String.Format(I18N.GetText("ATBadENID"), EID)); goto TranslationDone; } if ((EID & 0xffff0000) != (JID & 0xffff0000)) { this.AddLogEntry(String.Format(I18N.GetText("ATIDMismatch"), JID, EID)); goto TranslationDone; } JBW.Write(JID); } // Group name -> use English JBW.Write(EBR.ReadBytes(32)); JBR.BaseStream.Position += 32; // Completion Text -> based on config if (this.mnuPreserveJapaneseATCompletion.Checked) { JBW.Write(JBR.ReadBytes(32)); EBR.BaseStream.Position += 32; } else { byte[] EnglishCompletion = E.GetBytes(E.GetString(EBR.ReadBytes(32)).ToLowerInvariant()); // we want it lowercase if (EnglishCompletion.Length != 32) { this.AddLogEntry(String.Format(I18N.GetText("ATLowercaseProblem"), EnglishCompletion.Length)); goto TranslationDone; } JBW.Write(EnglishCompletion); JBR.BaseStream.Position += 32; } uint JEntryCount = JBR.ReadUInt32(); uint EEntryCount = EBR.ReadUInt32(); if (JEntryCount != EEntryCount) { this.AddLogEntry(String.Format(I18N.GetText("ATCountMismatch"), JEntryCount, EEntryCount)); goto TranslationDone; } JBW.Write(JEntryCount); long EntryBytesPos = JBW.BaseStream.Position; JBW.Write((uint) 0); JBR.BaseStream.Position += 4; EBR.BaseStream.Position += 4; for (uint i = 0; i < JEntryCount; ++i) { // Validate & Copy ID uint JID = JBR.ReadUInt32(); uint EID = EBR.ReadUInt32(); if ((JID & 0xffff) != 0x102) { this.AddLogEntry(String.Format(I18N.GetText("ATBadJPID"), JID)); goto TranslationDone; } if ((EID & 0xffff) != 0x202) { this.AddLogEntry(String.Format(I18N.GetText("ATBadENID"), EID)); goto TranslationDone; } if ((EID & 0xffff0000) != (JID & 0xffff0000)) { this.AddLogEntry(String.Format(I18N.GetText("ATIDMismatch"), JID, EID)); goto TranslationDone; } JBW.Write(JID); // Display text -> use English byte[] EnglishText = EBR.ReadBytes(EBR.ReadByte()); JBW.Write((byte) EnglishText.Length); JBW.Write(EnglishText); JBR.BaseStream.Position += 1 + JBR.ReadByte(); // Completion Text -> based on config if (this.mnuPreserveJapaneseATCompletion.Checked) { byte[] JapaneseText = JBR.ReadBytes(JBR.ReadByte()); JBW.Write((byte) JapaneseText.Length); JBW.Write(JapaneseText); } else { byte[] LowerEnglishText = E.GetBytes(E.GetString(EnglishText).ToLowerInvariant()); JBW.Write((byte) LowerEnglishText.Length); JBW.Write(LowerEnglishText); JBR.BaseStream.Position += 1 + JBR.ReadByte(); } } long EndOfGroupPos = JBW.BaseStream.Position; JBW.BaseStream.Position = EntryBytesPos; JBW.Write((uint) (EndOfGroupPos - EntryBytesPos - 4)); JBW.BaseStream.Position = EndOfGroupPos; } ATStream.Seek(0, SeekOrigin.Begin); ATStream.Write(NewStream.ToArray(), 0, (int) NewStream.Length); ATStream.SetLength(NewStream.Length); TranslationDone: ATStream.Close(); NewStream.Close(); EBR.Close(); } catch { this.LogFailure("TranslateAutoTranslator"); } }
// 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] > 0x81 || 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.ReadSByte(); this.CategoryID_ = BR.ReadByte(); #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; }