private static void ApplyFixes() { DisplayMessage("Applying fixes..."); #if !DEBUG try { #endif var fixes = XDocument.Load("fixes.xml"); if (fixes.Root == null) { throw new InvalidDataException("fixes.xml file corrupted."); } foreach (var fixset in fixes.Root.Elements()) { if (!model.ContainsKey(fixset.Name.LocalName)) { model[fixset.Name.LocalName] = new List <dynamic>(); } var data = (List <dynamic>)model[fixset.Name.LocalName]; var update = fixset.Element("update"); if (update != null) { foreach (var fix in update.Elements()) { var elements = data.Where(e => e.id == Convert.ToInt32(fix.Attribute("id").Value, CultureInfo.InvariantCulture)).ToList(); if (!elements.Any()) { dynamic el = new ModelObject(); foreach (var attr in fix.Attributes()) { el[attr.Name.LocalName] = attr.Parse(); } data.Add(el); continue; } var element = elements.Single(); foreach (var attr in fix.Attributes()) { element[attr.Name.LocalName] = attr.Parse(); } } } var remove = fixset.Element("remove"); if (remove == null) { continue; } foreach (var fix in remove.Elements()) { data.RemoveAll(x => x.id == Convert.ToInt32(fix.Attribute("id").Value, CultureInfo.InvariantCulture)); } } #if !DEBUG } catch { DisplayError(); throw; } #endif DisplaySuccess(); }
internal static dynamic[] Parse(Stream stream, IDictionary <int, string> fields) { if (stream == null) { throw new ArgumentNullException(nameof(stream)); } var header = stream.Read <Header>(0); if (header.Format != 0x00000067736D5F64) { throw new InvalidOperationException("Invalid data format."); } if (header.Version != 0x0000000300000003) { throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Invalid format version. [{0:X8}]", header.Version)); } long[] table; if (header.TableSize != 0) { if (header.TableSize != header.Count * 8 || header.EntrySize != 0) { throw new InvalidOperationException("Data is corrupt."); } table = stream.ReadArray <long>(header.Count, header.HeaderSize); } else { if (header.DataSize != header.Count * header.EntrySize) { throw new InvalidOperationException("Data is corrupt."); } table = new long[] { }; } stream.Position = header.HeaderSize + header.TableSize; byte[] data = new byte[header.DataSize]; stream.Read(data, 0, data.Length); if (header.Encrypted) { for (int i = 0; i < table.Length; i++) { table[i] = ~table[i]; } for (int i = 0; i < data.Length; i++) { data[i] = (byte)~data[i]; } } dynamic objects = new ModelObject[header.Count]; int length = (int)header.EntrySize; for (int i = 0; i < objects.Length; i++) { int offset; if (header.TableSize != 0) { length = (int)(table[i] >> 32); offset = (int)table[i]; } else { offset = i * (int)header.EntrySize; } int count = BitConverter.ToInt32(data, offset); dynamic resource = new ModelObject(); for (int j = 0; j < count; j++) { if (!fields.ContainsKey(j)) { continue; } int entryoffset = BitConverter.ToInt32(data, offset + j * 8 + 4) + offset; int entrytype = BitConverter.ToInt32(data, offset + j * 8 + 8); dynamic value; switch (entrytype) { case 0: entryoffset += 0x1C; int stringlength = 0; for (int k = 0; k < length; k++) { if (data[entryoffset + k] == 0) { break; } stringlength++; } value = ShiftJISFF11Encoding.ShiftJISFF11.GetString(data, entryoffset, stringlength); break; case 1: value = BitConverter.ToInt32(data, entryoffset); break; default: value = null; break; } resource[fields[j]] = value; } objects[i] = resource; } return(objects); }
private static void PostProcess() { Console.WriteLine("Post-processing parsed data..."); var success = false; try { // Add log names for non-english languages foreach (var buff in model.buffs) { if (buff.ContainsKey("ja")) { buff.jal = buff.ja; } } // Populate ability recast table with proper names foreach (var recast in model.ability_recasts) { foreach (var action in model.actions) { if (recast.id != action.recast_id) { continue; } recast.en = action.en; recast.ja = action.ja; break; } } // Add categories to key items var category = ""; for (var i = model.key_items.Count - 1; i >= 0; --i) { dynamic ki = model.key_items[i]; if (ki.en.StartsWith("-")) { category = ki.en.Substring(1); model.key_items.Remove(ki); } else { ki.category = category; } } // Move item descriptions into separate table //TODO: Remove when shared resources are implemented model.item_descriptions = new List <dynamic>(); foreach (var item in model.items) { dynamic item_description = new ModelObject(); item_description.id = item.id; item_description.en = item.endesc; item_description.ja = item.jadesc; item.endesc = null; item.jadesc = null; model.item_descriptions.Add(item_description); } // Fill in linked auto-translate names foreach (var at in model.auto_translates) { if (!at.en.StartsWith("@")) { continue; } int id = int.Parse(at.en.Substring(2), NumberStyles.HexNumber); string key; switch ((char)at.en[1]) { case 'A': key = "zones"; break; case 'C': key = "spells"; break; case 'J': key = "jobs"; break; case 'Y': key = "actions"; break; default: throw new InvalidDataException(string.Format("Unknown auto-translate code: {0}", at.en)); } dynamic item = null; foreach (var i in model[key]) { if (i.id != id) { continue; } item = i; break; } if (item != null) { at.en = item.en; at.ja = item.ja; } else { //throw new InvalidDataException(string.Format("Unknown auto-translate ID for {0}: {1}", key, id)); } } // Split abilities into categories foreach (var action in model.actions) { // Weapon skill if (action.id >= 0x0000 && action.id < 0x0200) { action.monster_level = null; action.mp_cost = null; action.recast_id = null; action.tp_cost = null; action.type = null; model.weapon_skills.Add(action); } // Job ability else if (action.id >= 0x0200 && action.id < 0x0600) { action.id -= 0x0200; action.monster_level = null; model.job_abilities.Add(action); } // Job traits else if (action.id >= 0x0600 && action.id < 0x0700) { action.id -= 0x0600; action.monster_level = null; action.mp_cost = null; action.prefix = null; action.recast_id = null; action.tp_cost = null; action.type = null; model.job_traits.Add(action); } // Monstrosity else if (action.id >= 0x0700) { action.id -= 0x0700; action.mp_cost = null; action.recast_id = null; action.type = null; // Remove names, as they are parsed separately action.en = null; action.ja = null; if (action.id < model.monster_abilities.Count) { model.monster_abilities[action.id].Merge(action); } } } model.actions = null; // Shift monster abilities up by 0x100 foreach (var monster_ability in model.monster_abilities) { monster_ability.id += 0x100; } // Split merit point names/descriptions and filter garbage values foreach (var merit_point in model.merit_points) { // The first 64 entries contain the category names if (merit_point.id < 0x40 || merit_point.en.StartsWith("Meripo")) { merit_point.id = 0; continue; } // Uneven entries contain the descriptions for the previous entry if (merit_point.id % 2 != 1) { continue; } model.merit_points[merit_point.id - 1].endesc = merit_point.en; model.merit_points[merit_point.id - 1].jadesc = merit_point.ja; merit_point.id = 0; } ((List <dynamic>)model.merit_points).RemoveAll(merit_point => merit_point.id == 0); // Split job point names/descriptions and filter garbage values foreach (var job_point in model.job_points) { // The first 64 entries contain the category names if (job_point.id < 0x40 || job_point.en == "カテゴリー名" || job_point.en == "ヘルプ文") { job_point.id = 0; continue; } // Uneven entries contain the descriptions for the previous entry if (job_point.id % 2 != 1) { continue; } model.job_points[job_point.id - 1].endesc = job_point.en; model.job_points[job_point.id - 1].jadesc = job_point.ja; job_point.id = 0; } ((List <dynamic>)model.job_points).RemoveAll(job_point => job_point.id == 0); foreach (var mount in model.mounts) { mount.prefix = "/mount"; } success = true; } finally { DisplayResult(success); } }
private static ModelObject ParseSpell(BinaryReader reader) { dynamic spell = new ModelObject(); spell.id = reader.ReadInt16(); if (spell.id == 0) { return(null); } spell.type = (MagicType)reader.ReadInt16(); spell.element = reader.ReadByte(); var padding = reader.ReadByte(); // Unknown, possibly just padding or element being a short. Always 0x00 Debug.Assert(padding == 0); spell.targets = reader.ReadUInt16(); spell.skill = reader.ReadInt16(); spell.mp_cost = reader.ReadInt16(); spell.cast_time = reader.ReadByte() / 4.0; spell.recast = reader.ReadByte() / 4.0; spell.levels = new Dictionary <int, int>(); for (var job = 0; job < 0x18; ++job) { var level = reader.ReadInt16(); if (level != -1) { spell.levels[job] = level; } } // Currently the last job slot occasionally mirrors the lowest level that any job learns the spell. // May be NPC-related information. This may be removed at some point, if they expand on jobs more spell.levels.Remove(0x17); // SE changed spell recast times in memory to be indexed by spell ID, not recast ID spell._oldrecast = reader.ReadInt16(); // old spell.recast_id spell.recast_id = spell.id; spell.icon_id_nq = reader.ReadInt16(); spell.icon_id = reader.ReadInt16(); spell.requirements = reader.ReadByte(); var range = reader.ReadByte(); spell.range = range == 15 ? 0 : range; // AoE information? spell._aoe_range = reader.ReadByte(); // spell.aoe_range : 11 for uncastable AOE enfeebling. 15 for self target spells spell._target_shape = reader.ReadByte(); // spell.target_shape : Target type. 1 for centered on target. 2 for conal. 3 for spell._cursor_behavior = reader.ReadByte(); // spell.cursor_behavior : 1 for self-target non-AoE, 2 for self-target AoE, 5 for self-target AoE that needs an enemy in range, 6 for Geomancy, 8 for Enemy, etc. reader.ReadBytes(0x03); // Unknown bytes spell._unknown12 = reader.ReadByte(); // The first and eighth bits are used. 0, 1, 128, and 129 are observed. They're systematic but I can't see what the covariate is. spell._unknown13 = reader.ReadByte(); // // Another requirements field? spell._unknown14 = reader.ReadByte(); // 128 if the spell can be stacked with Accession. Seems redundant with the "requirements" field. Takes no other values. spell._unknown15 = reader.ReadByte(); // 1 for Manifestation, 2 for Enlightenment, 4 for Embrava, 8 for Meteor, 16 for Geo-spells, 32 for -ra spells, 64 for spells that take all MP (Full Cure and Death) //Unknown section //reader.ReadBytes(0x0A); spell._unknown16 = reader.ReadByte(); spell._unknown17 = reader.ReadByte(); spell._unknown18 = reader.ReadByte(); spell._unknown19 = reader.ReadByte(); spell._unknown20 = reader.ReadByte(); spell._unknown21 = reader.ReadByte(); spell._unknown22 = reader.ReadByte(); spell._unknown23 = reader.ReadByte(); spell._unknown24 = reader.ReadByte(); spell._unknown25 = reader.ReadByte(); spell._unknown26 = reader.ReadUInt16(); // Always 0x0000 Debug.Assert((Boolean)(spell._unknown26 == 0)); // These next 3 bytes indicate whether a spell is accessible through Gifts for specific jobs. reader.ReadByte(); // spells.gift_spell_flags : 0x08 WHM, 0x10 BLM, 0x20 RDM, 0x80 PLD reader.ReadByte(); // spells.gift_spell_flags : 0x01 DRK, 0x04 BRD, 0x20 NIN reader.ReadByte(); // spells.gift_spell_flags : 0x10 SCH, 0x20 GEO, 0x40 RUN var trail = reader.ReadBytes(0x4); // 0x00000000 foreach (var b in trail) { Debug.Assert(b == 0); } Debug.Assert(reader.ReadSByte() == -1); // 0xFF, last byte // Derived data spell.prefix = ((MagicType)spell.type).Prefix(); return(spell); }