public static Dictionary <int, byte[]> GetMateriaStrings(string materiaStringsFilePath) { FileStream fs = File.OpenRead(materiaStringsFilePath); try { int r = 0; // Navigates byte[] currentItem int o = 0; // Iterates through headers int c = 0; // Materia ID int k = 0; // Navigates byte[] data bool terminate = false; var materiaNames = new Dictionary <int, byte[]>(); byte[] stringHeader = new byte[2]; byte[][] currentMateria = new byte[96][]; byte[] data = new byte[fs.Length]; fs.Read(data, 0, Convert.ToInt32(fs.Length)); fs.Close(); while (!terminate) { stringHeader[0] = data[o]; stringHeader[1] = data[o + 1]; k = EndianMethods.GetLittleEndianIntTwofer(stringHeader, 0); currentMateria[c] = new byte[40]; // Read string until terminator FF is hit while (data[k + r] != 0xFF) { currentMateria[c][r] = data[k + r]; r++; } currentMateria[c][r] = 0xFF; materiaNames.Add(c, currentMateria[c]); // Check if we've hit end of file; if next byte is FF then we have if (data[k + r + 1] == 0xFF) { terminate = true; } else { o += 2; // Next header c++; // Next Materia ID r = 0; // Reset for next Materia string reading } } return(materiaNames); } finally { fs.Close(); } }
public static int GetItemID(List <byte> oldItemName) { if (oldItemName.Count == 0) { Trace.WriteLine("Empty item name was received"); return(0); } byte[] terminator = { 0xFF }; byte[] firstCharacter = new byte[] { oldItemName[0] }; int itemID = 0; bool falsePositive = false; FileStream item = File.OpenRead(Directory.GetCurrentDirectory() + "\\Kernel Strings\\kernel.bin19"); byte[] dataItem = new byte[item.Length]; item.Read(dataItem, 0, Convert.ToInt32(item.Length)); item.Close(); byte[] itemOffset = new byte[2]; itemOffset[0] = dataItem[0]; itemOffset[1] = dataItem[1]; int itemOffsetInt = EndianMethods.GetLittleEndianIntTwofer(itemOffset, 0); FileStream weapon = File.OpenRead(Directory.GetCurrentDirectory() + "\\Kernel Strings\\kernel.bin20"); byte[] dataWeapon = new byte[weapon.Length]; weapon.Read(dataWeapon, 0, Convert.ToInt32(weapon.Length)); weapon.Close(); byte[] weaponOffset = new byte[2]; weaponOffset[0] = dataWeapon[0]; weaponOffset[1] = dataWeapon[1]; int weaponOffsetInt = EndianMethods.GetLittleEndianIntTwofer(weaponOffset, 0); FileStream armour = File.OpenRead(Directory.GetCurrentDirectory() + "\\Kernel Strings\\kernel.bin21"); byte[] dataArmour = new byte[armour.Length]; armour.Read(dataArmour, 0, Convert.ToInt32(armour.Length)); armour.Close(); byte[] armourOffset = new byte[2]; armourOffset[0] = dataArmour[0]; armourOffset[1] = dataArmour[1]; int armourOffsetInt = EndianMethods.GetLittleEndianIntTwofer(armourOffset, 0); FileStream accessory = File.OpenRead(Directory.GetCurrentDirectory() + "\\Kernel Strings\\kernel.bin22"); byte[] dataAccessory = new byte[accessory.Length]; accessory.Read(dataAccessory, 0, Convert.ToInt32(accessory.Length)); accessory.Close(); byte[] accessoryOffset = new byte[2]; accessoryOffset[0] = dataAccessory[0]; accessoryOffset[1] = dataAccessory[1]; int accessoryOffsetInt = EndianMethods.GetLittleEndianIntTwofer(accessoryOffset, 0); try { for (int r = itemOffsetInt; r < dataItem.Length; r++) { if (dataItem.Skip(r).Take(terminator.Length).SequenceEqual(terminator)) { itemID++; // Prevents out of range if ((r + 1) < dataItem.Length) { // Some Item names appear within other Item names // This checks the first character after a terminator for a match and if it isn't it will // prevent the sequenceEqual from returning true. if (dataItem[r + 1] != firstCharacter[0]) { falsePositive = true; } else { falsePositive = false; r++; } } } if (!falsePositive) { if (dataItem.Skip(r).Take(oldItemName.Count).SequenceEqual(oldItemName)) { return(itemID); } } } itemID = 128; for (int o = weaponOffsetInt; o < dataWeapon.Length; o++) { if (dataWeapon.Skip(o).Take(terminator.Length).SequenceEqual(terminator)) { itemID++; // Prevents out of range if ((o + 1) < dataWeapon.Length) { // Some Item names appear within other Item names // This checks the first character after a terminator for a match and if it isn't it will // prevent the sequenceEqual from returning true. if (dataWeapon[o + 1] != firstCharacter[0]) { falsePositive = true; } else { falsePositive = false; o++; } } } if (!falsePositive) { if (dataWeapon.Skip(o).Take(oldItemName.Count).SequenceEqual(oldItemName)) { return(itemID); } } } itemID = 256; for (int c = armourOffsetInt; c < dataArmour.Length; c++) { if (dataArmour.Skip(c).Take(terminator.Length).SequenceEqual(terminator)) { itemID++; // Prevents out of range if ((c + 1) < dataArmour.Length) { // Some Item names appear within other Item names // This checks the first character after a terminator for a match and if it isn't it will // prevent the sequenceEqual from returning true. if (dataArmour[c + 1] != firstCharacter[0]) { falsePositive = true; } else { falsePositive = false; c++; } } } if (!falsePositive) { if (dataArmour.Skip(c).Take(oldItemName.Count).SequenceEqual(oldItemName)) { return(itemID); } } } itemID = 288; for (int k = accessoryOffsetInt; k < dataAccessory.Length; k++) { if (dataAccessory.Skip(k).Take(terminator.Length).SequenceEqual(terminator)) { itemID++; // Prevents out of range if ((k + 1) < dataAccessory.Length) { // Some Item names appear within other Item names // This checks the first character after a terminator for a match and if it isn't it will // prevent the sequenceEqual from returning true. if (dataAccessory[k + 1] != firstCharacter[0]) { falsePositive = true; } else { falsePositive = false; k++; } } } if (!falsePositive) { if (dataAccessory.Skip(k).Take(oldItemName.Count).SequenceEqual(oldItemName)) { return(itemID); } } } Trace.WriteLine("No matches in getting old Item ID - Used 0 as fallback"); return(0); } catch { Trace.WriteLine("Error in getting old Item ID"); return(0); } }
public static byte[] SwapFieldModels(byte[] data) { int r = 0; // Iteration through model entries int o = 0; // Array index int c = 0; // Iteration through model's anims // Model Loader header // 0x00: Always 0 // 0x02: Model Count // 0x04: Model Scale (unused) // 0x06: Model Loader Data starts try { // Get the number of models in this field byte[] modelCountByte = new byte[2]; modelCountByte[0] = data[o + 2]; modelCountByte[1] = data[o + 3]; int modelCount = EndianMethods.GetLittleEndianIntTwofer(modelCountByte, 0); Random rnd = new Random(); o += 6; // Skip data position past the header while (r < modelCount) { // Take size of model name and convert it into an int for array index byte[] modelNameSizeByte = new byte[2]; modelNameSizeByte[0] = data[o]; modelNameSizeByte[1] = data[o + 1]; int modelNameSize = EndianMethods.GetLittleEndianIntTwofer(modelNameSizeByte, 0); o += 2; // Skip 2-byte unknown value o += 2; // Jump past model name string to HRC location o += modelNameSize; // Read the current .HRC ID byte[] currentHRCBytes = new byte[4]; currentHRCBytes[0] = data[o]; currentHRCBytes[1] = data[o + 1]; currentHRCBytes[2] = data[o + 2]; currentHRCBytes[3] = data[o + 3]; // ASCII conversion - string from .HRC bytes - Output: "AAAA" string currentHRC = @"" + Encoding.ASCII.GetString(currentHRCBytes, 0, currentHRCBytes.Length) + @""; // If no options on, the current HRC will be used string newHRC = currentHRC; // temp var for testing, will be fed through to method from Form later bool rndModelSwap = true; // Complete Random Swap if (rndModelSwap) { newHRC = FieldModels.RandomModelSwap(rnd); } // Converts the returned string into bytes byte[] newHRCBytes = ConvertString.GetNameBytes(newHRC); // Writes the new bytes to the .HRC data[o] = newHRCBytes[0]; data[o + 1] = newHRCBytes[1]; data[o + 2] = newHRCBytes[2]; data[o + 3] = newHRCBytes[3]; o += 8; // Skip the .HRC part of the string // Model Scale - Definitely have this as an option // This is actually a string; '512' but written in ascii. Bear that in mind. o += 4; // Count the number of anims for this model byte[] animCountByte = new byte[2]; animCountByte[0] = data[o]; animCountByte[1] = data[o + 1]; int animCount = EndianMethods.GetLittleEndianIntTwofer(animCountByte, 0); o += 2; // Light/Shading data - Will probably not be modifying these values o += 30; // Anims - Each anim has the following: // 0x00: Size of anim name string // 0x02: Anim name string // 0x02 + Size: Unknown, 2-byte value while (c < animCount) { // Take size of anim name and convert it into an int for array index byte[] animNameSizeByte = new byte[2]; animNameSizeByte[0] = data[o]; animNameSizeByte[1] = data[o + 1]; int animNameSize = EndianMethods.GetLittleEndianIntTwofer(animNameSizeByte, 0); o += 2; // Read the current .HRC ID byte[] currentAnimBytes = new byte[4]; currentAnimBytes[0] = data[o]; currentAnimBytes[1] = data[o + 1]; currentAnimBytes[2] = data[o + 2]; currentAnimBytes[3] = data[o + 3]; // ASCII conversion - string from .HRC bytes - Output: "AAAA" string currentAnim = @"" + Encoding.ASCII.GetString(currentAnimBytes, 0, currentAnimBytes.Length) + @""; // If no options on, the current Anim will be used string newAnim = currentAnim; bool rndAnimSwap = false; // Changes the Anim string - Matches it to current HRC's anim pool if (rndAnimSwap == true) { newAnim = FieldModels.MatchedAnimSwap(currentAnim, newHRC); } bool animTest = true; // Complete Random if (animTest == true) { newAnim = FieldModels.RandomAnimSwap(rnd); } // Converts the string into bytes byte[] newAnimBytes = ConvertString.GetNameBytes(newAnim); // Writes the new bytes data[o] = newAnimBytes[0]; data[o + 1] = newAnimBytes[1]; data[o + 2] = newAnimBytes[2]; data[o + 3] = newAnimBytes[3]; o += animNameSize; // Move past the anim name // Unknown 2 bytes, skipped o += 2; c++; } c = 0; r++; } } catch { MessageBox.Show("Flevel Chunk #3 (Model Loader) has encountered an issue; skipping current field"); } return(data); }
public static byte[] ChangeItemsMateria(byte[] data, string name) { // Get the start of the text section offset byte[] textStart = new byte[2]; textStart[0] = data[4]; textStart[1] = data[5]; int textOffset = EndianMethods.GetLittleEndianIntTwofer(textStart, 0); if (name == "qd") { // Breakpoint sink to analyse qd's allocation of items } // Prevents an out of bounds exception and returns data unaltered if (textOffset >= data.Length) { return(data); } int textCount = data[textOffset]; // Now iterate through the event script to find references to used dialog // We have very little security available for a 3-byte opcode in which 2 // of the bytes are unknown, so anything available has been added to help // reduce possibility of false positives. var maxSearchRangeDialogue = data.Length - 3; var currentDialogue = new byte[1]; var searchDialogue = new byte[] { 0x40 }; // This is the opcode, the only 1 of the 3 bytes that is static List <int> usedTexts = new List <int>(); for (int k = 0; k < maxSearchRangeDialogue; k++) { // OpCode 0x40 currentDialogue[0] = data[k]; // If a match is found, we can call a method to change the string if (searchDialogue.SequenceEqual(currentDialogue)) { // Check that next part of opcode is a valid value of 0-3 if (data[k + 1] < 4) { // Then check that the dialogue ID is a valid value if (data[k + 2] < textCount) { // Add the text ID set to this opcode to our list of used texts // Exclude duplicates if (!usedTexts.Contains(data[k + 2])) { usedTexts.Add(data[k + 2]); } } } } } // Exceptions List // A number of texts aren't uniformly formatted; some have extra spaces, some omit the Key Item part, etc. // This removes them from the pool so that their strings aren't swapped. if (name == "blin62_1") { // Removes Keycard 65 reference from the pool usedTexts.Remove(19); } if (name == "bonevil") { // Removes Mop reference from the pool, as this can have its string size exceeded by certain items usedTexts.Remove(12); } if (name == "bugin1b") { // Master Magic/Command/Summon texts have false positives, prune them usedTexts.Remove(14); usedTexts.Remove(16); usedTexts.Remove(17); } if (name == "eals_1") { // Received texts on this field start with 2 spaces, throwing it off // TODO: Add logic to handle this field return(data); } if (name == "games-2") { // Removes Ink reference from the pool, as this can have its string size exceeded by certain items usedTexts.Remove(37); } if (name == "mkt_w") { // Removes Batteries reference from the pool usedTexts.Remove(13); } if (name == "zz1") { // Removes Mythril reference from pool usedTexts.Remove(5); } if (name == "zz6") { // Has no 'Materia!' at the end of its Materia string, gets flagged as an item instead. // TODO: Add logic to handle this case. return(data); } if (name == "zz8") { // Name of Materia differs to Kernel-stored name (KOTR) // TODO: Add logic to handle this case. return(data); } Random rnd = new Random(); int r = 0; // Iterates new item string string itemFileLocation = ""; //var itemNames = MateriaStrings.GetMateriaStrings(Directory.GetCurrentDirectory() + "\\Kernel Strings\\kernel.bin19"); //var weaponNames = MateriaStrings.GetMateriaStrings(Directory.GetCurrentDirectory() + "\\Kernel Strings\\kernel.bin20"); //var armourNames = MateriaStrings.GetMateriaStrings(Directory.GetCurrentDirectory() + "\\Kernel Strings\\kernel.bin21"); //var accessoryNames = MateriaStrings.GetMateriaStrings(Directory.GetCurrentDirectory() + "\\Kernel Strings\\kernel.bin22"); var materiaNames = MateriaStrings.GetMateriaStrings(Directory.GetCurrentDirectory() + "\\Kernel Strings\\kernel.bin23"); var terminator = new byte[1]; int offset = 0; var currentString = new byte[10]; var maxSearchRangeString = data.Length - 9; var maxSearchRangeItem = data.Length - 5; var currentMateria = new byte[7]; var currentItem = new byte[5]; var finalItem = new byte[3]; List <byte> oldName = new List <byte>(); int oldMateriaID = 0; int oldItemID = 0; // Searches for final items to update them back to 01 quantity var searchFinalItem = new byte[] { 0x58, 0x00, 0xFE }; // Searches for string 'Received "' for items var searchString = new byte[] { 0x32, 0x45, 0x43, 0x45, 0x49, 0x56, 0x45, 0x44, 0x00, 0x02 }; // Checks for '" Materia!' for Materia var appendSearchMateria = new byte[] { 0x02, 0x00, 0x2D, 0x41, 0x54, 0x45, 0x52, 0x49, 0x41, 0x01 }; // Tracks the current Text ID based on how many terminators have been read; compared to List of Used Text IDs int textID = 0; // Checks for Materia first, then Items; Received will be overwritten by Materia check so no overlap should occur. // If materia option is on for (var y = textOffset + (textCount * 2); y < maxSearchRangeString; y++) { terminator[0] = data[y]; currentString[0] = data[y]; currentString[1] = data[y + 1]; currentString[2] = data[y + 2]; currentString[3] = data[y + 3]; currentString[4] = data[y + 4]; currentString[5] = data[y + 5]; currentString[6] = data[y + 6]; currentString[7] = data[y + 7]; currentString[8] = data[y + 8]; currentString[9] = data[y + 9]; // Tracks location of the terminator, and moves up 1 to start of next string if (terminator[0] == 0xFF) { textID++; offset = y + 1; } if (appendSearchMateria.SequenceEqual(currentString)) { if (usedTexts.Contains(textID)) { // Selects new Item ID int newMateriaID = rnd.Next(91); // Re-rolls until ID is valid while (newMateriaID == 22 || newMateriaID == 38 || newMateriaID == 45 || newMateriaID == 46 || newMateriaID == 47 || newMateriaID == 63 || newMateriaID == 66 || newMateriaID == 67) { newMateriaID = rnd.Next(91); if (newMateriaID == 91) { // break } } // Skips past the Receieved " part of the string to the Materia Name int countCharacters = offset + 10; // Get old Materia name and match it to its ID; 0x02 is the terminator (") while (data[countCharacters] != 0x02) { oldName.Add(data[countCharacters]); countCharacters++; } // Figure out the Materia ID by matching the name in the kernel strings oldMateriaID = MateriaStrings.GetMateriaID(oldName); oldName.Clear(); // Replace string with new Materia name while (materiaNames[newMateriaID][r] != 0xFF) { // Sets position to start of string data[offset] = materiaNames[newMateriaID][r]; offset++; r++; } // Blank the rest of the string with 00s until the terminator FF while (data[offset] != 0xFF) { data[offset] = 0x00; offset++; } Trace.WriteLine("Rewrote Materia String"); y++; r = 0; // Now to find the Materia opcode; assuming they are in same order as text. // When a string is found, a search is conducted to find the next Materia opcode. var searchMateria = new byte[] { 0x5B, 0x00, 0x00, (byte)oldMateriaID, 0x00, 0x00, 0x00 }; maxSearchRangeItem = data.Length - 6; // You know you've hit rock bottom when you have to start assigning the value of a variable to itself. for (int c = 0; c < maxSearchRangeItem; c++) { currentMateria[0] = data[c]; // Always 0x5B currentMateria[1] = data[c + 1]; // Always 0x00 currentMateria[2] = data[c + 2]; // Always 0x00 currentMateria[3] = data[c + 3]; // Old Materia ID currentMateria[4] = data[c + 4]; // Always 0x00 currentMateria[5] = data[c + 5]; // Always 0x00 currentMateria[6] = data[c + 6]; // Always 0x00 // If a match is found, we can call a method to change the string if (searchMateria.SequenceEqual(currentMateria)) { data[c] = 0x5B; c++; data[c] = 0x00; c++; data[c] = 0x00; c++; data[c] = (byte)newMateriaID; c++; // Materia ID data[c] = 0x00; c++; data[c] = 0x00; c++; data[c] = 0x00; c++; Trace.WriteLine("A Materia was rewritten successfully"); } } usedTexts.Remove(textID); } } } offset = 0; r = 0; textID = 0; // If item option is on for (var o = textOffset + (textCount * 2); o < maxSearchRangeString; o++) { terminator[0] = data[o]; currentString[0] = data[o]; currentString[1] = data[o + 1]; currentString[2] = data[o + 2]; currentString[3] = data[o + 3]; currentString[4] = data[o + 4]; currentString[5] = data[o + 5]; currentString[6] = data[o + 6]; currentString[7] = data[o + 7]; currentString[8] = data[o + 8]; currentString[9] = data[o + 9]; // Tracks location of the terminator, and moves up 1 to start of next string if (terminator[0] == 0xFF) { textID++; offset = o + 1; } if (textID == 10) { // break } // Match found for the string in this chunk if (searchString.SequenceEqual(currentString)) { if (usedTexts.Contains(textID)) { // Rolls a new Item ID; will re-roll if it picks an empty item ID int newItemID = rnd.Next(0, 319); while (newItemID > 104 && newItemID < 128) { newItemID = rnd.Next(0, 319); // Selects new Item ID } if (newItemID < 128) { itemFileLocation = Directory.GetCurrentDirectory() + "\\Kernel Strings\\kernel.bin19"; } else if (newItemID < 256) { itemFileLocation = Directory.GetCurrentDirectory() + "\\Kernel Strings\\kernel.bin20"; } else if (newItemID < 288) { itemFileLocation = Directory.GetCurrentDirectory() + "\\Kernel Strings\\kernel.bin21"; } else if (newItemID < 320) { itemFileLocation = Directory.GetCurrentDirectory() + "\\Kernel Strings\\kernel.bin22"; } // Skips past the Receieved " part of the string to the Item Name int countItemCharacters = offset + 10; // Get old Item name and match it to its ID; 0x02 is the terminator (") while (data[countItemCharacters] != 0x02) { oldName.Add(data[countItemCharacters]); countItemCharacters++; } // Figure out the Item ID by matching the name in the kernel strings oldItemID = ItemStrings.GetItemID(oldName); oldName.Clear(); var itemNames = ItemStrings.GetItemStrings(itemFileLocation, newItemID); // Replace string with new item name while (itemNames[newItemID][r] != 0xFF) { data[o] = itemNames[newItemID][r]; o++; r++; } // Blank the rest of the string with 00s until the terminator FF while (data[o] != 0xFF) { data[o] = 0x00; o++; } Trace.WriteLine("Rewrote Item String"); r = 0; // Now to find the item opcode; assuming they are in same order as text. // When a string is found, a search is conducted to find the next item Opcode. ulong convertOldItemID = (ulong)oldItemID; byte[] oldItemIDByte = EndianMethods.GetLittleEndianConvert(convertOldItemID); var searchItem = new byte[] { 0x58, 0x00, oldItemIDByte[0], oldItemIDByte[1], 0x01 }; // You know you've hit rock bottom when you have to start assigning the value of a variable to itself. for (int c = 0; c < maxSearchRangeItem; c++) { currentItem[0] = data[c]; // Always 0x58 currentItem[1] = data[c + 1]; // Always 0x00 currentItem[2] = data[c + 2]; // Old Item ID - 2 Bytes currentItem[3] = data[c + 3]; currentItem[4] = data[c + 4]; // Always 0x01, but may be rare cases where it is higher number like Mt. Corel // If a match is found, we can call a method to change the string if (searchItem.SequenceEqual(currentItem)) { // Convert item ID into a 2 byte endian value ulong convertItemID = (ulong)newItemID; byte[] convertedItemID = EndianMethods.GetLittleEndianConvert(convertItemID); data[c] = 0x58; data[c + 1] = 0x00; data[c + 2] = convertedItemID[0]; // Item ID 1st byte data[c + 3] = convertedItemID[1]; // Item ID 2nd byte data[c + 4] = 0xFE; // Quantity Trace.WriteLine("An item ID was rewritten"); } } usedTexts.Remove(textID); } } } // Final Pass to revert items back to 01 quantity (or vary it) for (int f = 0; f < maxSearchRangeItem; f++) { finalItem[0] = data[f]; // Always 0x58 finalItem[1] = data[f + 1]; // Always 0x00 // Two bytes are skipped as they can vary (Item ID) finalItem[2] = data[f + 4]; // Always 0x01, but may be rare cases where it is higher number like Mt. Corel // If a match is found, we can call a method to change the string if (searchFinalItem.SequenceEqual(finalItem)) { data[f + 4] = 0x01; // Can change this if user specified it } } return(data); }