public static void PrepareKernel(string directory, byte[] kernelLookup, bool[] options, Random rnd, int seed) { string kernelDirectory = directory + "\\Target Files\\"; // The battle folder where scene.bin resides string targetKernel = kernelDirectory + "KERNEL.bin"; // The kernel.bin for updating the lookup table //string backupKernel = kernelDirectory + "KERNEL - Backup.bin"; int[][] jaggedKernelInfo = new int[27][]; // An array of arrays, containing compressed size, uncompressed size, section ID ArrayList listedKernelData = new ArrayList(); // Contains all the compressed kernel section data byte[] header = new byte[4]; //Retrieves header information for conversion to int int compressedSize; // Stores the compressed size of the file int uncompressedSize; // Stores the uncompressed size of the file int sectionID; // Stores the section ID of the file int offset = 0; // Tracks where we are in the kernel.bin int headerOffset = 0; // Stores the absolute offset value for each section's header (updated on each loop) int r = 0; // ro ro int o = 0; // fight the powah // Step 1: Read the kernel headers while (r < 27) // 27 sections in the kernel { // Opens and reads the headers in the kernel.bin FileStream stepOne = new FileStream(targetKernel, FileMode.Open, FileAccess.Read); stepOne.Seek(headerOffset, SeekOrigin.Begin); stepOne.Read(header, 0, 2); // Header never exceeds 64 bytes compressedSize = EndianConvert.GetLittleEndianInt(header, 0); stepOne.Read(header, 0, 2); // Header never exceeds 64 bytes uncompressedSize = EndianConvert.GetLittleEndianInt(header, 0); stepOne.Read(header, 0, 2); // Header never exceeds 64 bytes sectionID = EndianConvert.GetLittleEndianInt(header, 0); // Stored kernel header information in a jaggy array jaggedKernelInfo[o] = new int[] { compressedSize, uncompressedSize, sectionID }; stepOne.Close(); headerOffset += compressedSize + 6; r++; o++; stepOne.Close(); } r = 0; o = 0; // Step 2: Get the compressed data, uncompress it, and then randomise it while (r < 27) { int bytesRead; int size = jaggedKernelInfo[o][1]; byte[] uncompressedKernel = new byte[size]; // Used to hold the decompressed kernel section using (BinaryReader brg = new BinaryReader(new FileStream(targetKernel, FileMode.Open))) { // Calls method to convert little endian values into an integer byte[] compressedKernel = new byte[jaggedKernelInfo[o][0]]; // Used to hold the compressed scene file, where [o][1] is scene size brg.BaseStream.Seek(offset + 6, SeekOrigin.Begin); // Starts reading the compressed scene file brg.Read(compressedKernel, 0, compressedKernel.Length); using (MemoryStream inputWrapper = new MemoryStream(compressedKernel)) { using (MemoryStream decompressedOutput = new MemoryStream()) { using (GZipStream zipInput = new GZipStream(inputWrapper, CompressionMode.Decompress, true)) { while ((bytesRead = zipInput.Read(uncompressedKernel, 0, size)) != 0) { decompressedOutput.Write(uncompressedKernel, 0, bytesRead); } zipInput.Close(); } decompressedOutput.Close(); } inputWrapper.Close(); } brg.Close(); } // Sends decompressed scene data to be randomised by section switch (r) { case 0: Kernel.RandomiseSection0(uncompressedKernel, options, rnd, seed); break; case 1: Kernel.RandomiseSection1(uncompressedKernel, options, rnd, seed); break; case 2: Kernel.RandomiseSection2(uncompressedKernel, options, rnd, seed, kernelLookup); break; case 3: Kernel.RandomiseSection3(uncompressedKernel, options, rnd, seed); break; case 4: Kernel.RandomiseSection4(uncompressedKernel, options, rnd, seed); break; case 5: Kernel.RandomiseSection5(uncompressedKernel, options, rnd, seed); break; case 6: Kernel.RandomiseSection6(uncompressedKernel, options, rnd, seed); break; case 7: Kernel.RandomiseSection7(uncompressedKernel, options, rnd, seed); break; case 8: Kernel.RandomiseSection8(uncompressedKernel, options, rnd, seed); break; } // Recompress the altered uncompressed data back into GZip byte[] recompressedKernel; using (var result = new MemoryStream()) { using (var compressionStream = new GZipStream(result, CompressionMode.Compress)) { compressionStream.Write(uncompressedKernel, 0, uncompressedKernel.Length); compressionStream.Close(); } recompressedKernel = result.ToArray(); result.Close(); } // Offset is updated for the next pass before we write in our new value offset += jaggedKernelInfo[o][0] + 6; // The size is updated with the newly compressed/padded scene's length jaggedKernelInfo[o][0] = recompressedKernel.Length; // Byte array is added to the ArrayList listedKernelData.Add(recompressedKernel); r++; o++; } r = 0; o = 0; // Step 3: Rebuilding the Kernel.bin using (var outputStream = File.Create(targetKernel)) { // Loops until all 27 sections are headered and written while (r < 27) { // Write the header first byte[] bytes = new byte[2]; byte[] kernelHead = new byte[6]; ulong comSize = (ulong)jaggedKernelInfo[o][0]; ulong uncomSize = (ulong)jaggedKernelInfo[o][1]; ulong sectID = (ulong)jaggedKernelInfo[o][2]; bytes = EndianConvert.GetLittleEndianConvert(comSize); kernelHead[0] = bytes[0]; kernelHead[1] = bytes[1]; bytes = EndianConvert.GetLittleEndianConvert(uncomSize); kernelHead[2] = bytes[0]; kernelHead[3] = bytes[1]; bytes = EndianConvert.GetLittleEndianConvert(sectID); kernelHead[4] = bytes[0]; kernelHead[5] = bytes[1]; // Takes the header data, converts it into a stream, and then appends it to the file-in-progress outputStream.Position = outputStream.Length; outputStream.Write(kernelHead, 0, kernelHead.Length); // Takes the byte data from the ArrayList, converts it into a stream, and then appends it to the file-in-progress byte[] kernelData = (byte[])listedKernelData[o]; outputStream.Position = outputStream.Length; outputStream.Write(kernelData, 0, kernelData.Length); r++; o++; } r = 0; o = 0; } }
public static byte[] PrepareScene(string directory, bool[] options, Random rnd, int seed) { string sceneDirectory = directory + "\\Target Files\\"; // The folder where scene.bin will be placed string targetScene = sceneDirectory + "scene.bin"; // The target file itself //string backupScene = sceneDirectory + "scene - default.bin"; // Makes a backup of the scene.bin byte[] header = new byte[64]; /* Stores the block header * [0-4] = Offset for first GZipped data file (3 enemies per file) * Header total size must be 40h - empty entries == FF FF FF FF */ int[][] jaggedSceneInfo = new int[256][]; // An array of arrays, containing offset, size, and absoluteoffset for each scene file ArrayList listedSceneData = new ArrayList(); // Contains all the compressed scene data int[][][] jaggedModelAttackTypes = new int[3000][][]; // Contains all the uncompressed Attack Anim data long initialSize; // The size of the initial scene.bin (can vary, up to 63 blocks but typically 32-33 int size; // The size of the compressed file int offset; // Stores the current scene offset int nextOffset; // Stores the next scene offset int absoluteOffset; // Stores the scene's absolute offset in the scene.bin int finalOffset = 0; // Stores the scene's adjusted offset in the scene.bin int headerOffset = 0; // Offset of the current block header; goes up in 2000h (8192) increments byte[] padder = new byte[1]; // Scene files, after compression, need to be FF padded to make them multiplicable by 4 padder[0] = 255; //Random rnd = new Random(Guid.NewGuid().GetHashCode()); byte[] kernelLookup = new byte[64]; // Stores the new lookup table to be written to the kernel.bin; blank values are FF int i = 0; while (i < 64) { kernelLookup[i] = 255; i++; } int r = 0; // C'mon get up and make some noise int o = 0; // while your whiles get looped by int c = 0; // the var street boys int k = 0; // *DJ scratching noises* int s = 0; // *DJ scratching noises intensify* /* Step 1: Read the Scene.bin and retrieve its data for use later * The goal of this step is to build an array containing information about each scene. * We then use this information to derive other important information (for instance, adjusting the header offsets) * To get the info we need, the header of each 'block' is read (2000h per block) and this tells us where to find each scene. * We need to use GZip compression to get the data out, but we cannot let the Gzipper hit the header or it will break. */ // Entire file is read; offsets and sizes for scenes are extracted and placed in a jagged array (an array of arrays) FileStream fs = new FileStream(targetScene, FileMode.Open, FileAccess.Read); initialSize = fs.Length; fs.Close(); while (headerOffset < initialSize) // 32 blocks of 2000h/8192 bytes each { // Opens and reads the default scene.bin FileStream stepOne = new FileStream(targetScene, FileMode.Open, FileAccess.Read); stepOne.Seek(headerOffset, SeekOrigin.Begin); stepOne.Read(header, 0, 64); // Header never exceeds 64 bytes stepOne.Close(); // Max of 16 sections in a header (is usually less however) while (r < 16) { // If the 2nd byte of the current header is FF then assume there are no more valid scene headers in this block if (header[c + 1] != 0xFF) { // Fetches the current header byte[] currentHeader = new byte[4]; currentHeader[0] = header[c]; currentHeader[1] = header[c + 1]; currentHeader[2] = header[c + 2]; currentHeader[3] = header[c + 3]; // Fetches the next header - ignored if we're at the end of the block header byte[] nextHeader = new byte[4]; if (r < 15) { nextHeader[0] = header[c + 4]; nextHeader[1] = header[c + 5]; nextHeader[2] = header[c + 6]; nextHeader[3] = header[c + 7]; } // Converts the current offset and the next offset into integer offset = EndianConvert.GetLittleEndianInt(currentHeader, 0); nextOffset = EndianConvert.GetLittleEndianInt(nextHeader, 0); // Checks that next header is not empty or if we are at the last header if (currentHeader[1] == 0xFF || nextHeader[1] == 0xFF || r == 15) { // If next header is FF FF FF FF, then we're at the end of file and should deduct 2000h to get current file size size = 8192 - (offset * 4); } else { // Difference between this offset and next offset provides size; offsets need to be *4 to get actual offset size = (nextOffset - offset) * 4; } // Gets absolute offset in the scene.bin for the current scene absoluteOffset = (offset * 4) + headerOffset; // Store our retrieved/derived information in our jagged array (watch out for the pointy bits) jaggedSceneInfo[o] = new int[] { offset, size, absoluteOffset, finalOffset }; o++; } c += 4; r++; } headerOffset += 8192; r = 0; c = 0; } o = 0; headerOffset = 0; /* Step 2: Randomising the scene data * Using absolute offset + compressed size, we locate and decompress the file. * We run the scene data through the randomiser. * We then recompress the returned data. * The size will now have changed; we will update this later while generating our new scene.bin */ // But first, we acquire the camera data of the target scene.bin ArrayList listedCameraData = Indexer.GetCameraData(jaggedSceneInfo, targetScene); // And the valid Animation Types for each ModelID jaggedModelAttackTypes = Indexer.GetAttackData(jaggedSceneInfo, targetScene); while (r < 256) { int bytesRead; byte[] uncompressedScene = new byte[7808]; // Used to hold the decompressed scene file using (BinaryReader brg = new BinaryReader(new FileStream(targetScene, FileMode.Open))) { // Calls method to convert little endian values into an integer byte[] compressedScene = new byte[jaggedSceneInfo[o][1]]; // Used to hold the compressed scene file, where [o][1] is scene size brg.BaseStream.Seek(jaggedSceneInfo[o][2], SeekOrigin.Begin); // Starts reading the compressed scene file brg.Read(compressedScene, 0, compressedScene.Length); using (MemoryStream inputWrapper = new MemoryStream(compressedScene)) { using (MemoryStream decompressedOutput = new MemoryStream()) { using (GZipStream zipInput = new GZipStream(inputWrapper, CompressionMode.Decompress, true)) { while ((bytesRead = zipInput.Read(uncompressedScene, 0, 7808)) != 0) { decompressedOutput.Write(uncompressedScene, 0, bytesRead); } zipInput.Close(); } decompressedOutput.Close(); } inputWrapper.Close(); } brg.Close(); } // Sends random camera data to be used int rand = (byte)rnd.Next(listedCameraData.Count); byte[] initCam = Indexer.InitialCamera(); byte[] randCam = (byte[])listedCameraData[rand]; int sceneID = r; // Sends decompressed scene data to be randomised Scene.RandomiseScene(uncompressedScene, randCam, sceneID, options, rnd, jaggedModelAttackTypes, seed, initCam); // Recompress the altered uncompressed data back into GZip byte[] recompressedScene; using (var result = new MemoryStream()) { using (var compressionStream = new GZipStream(result, CompressionMode.Compress)) { compressionStream.Write(uncompressedScene, 0, uncompressedScene.Length); compressionStream.Close(); } recompressedScene = result.ToArray(); result.Close(); } // Checks that the file is divisible by 4; FF padding is applied otherwise if (recompressedScene.Length % 4 == 3) // Remainder of 3, add 1 FF { recompressedScene = recompressedScene.Concat(padder).ToArray(); } else if (recompressedScene.Length % 4 == 2) // Remainder of 2, add 2 FFs { recompressedScene = recompressedScene.Concat(padder).ToArray(); recompressedScene = recompressedScene.Concat(padder).ToArray(); } else if (recompressedScene.Length % 4 == 1) // Remainder of 1, add 3 FFs { recompressedScene = recompressedScene.Concat(padder).ToArray(); recompressedScene = recompressedScene.Concat(padder).ToArray(); recompressedScene = recompressedScene.Concat(padder).ToArray(); } // The size is updated with the newly compressed/padded scene's length jaggedSceneInfo[o][1] = recompressedScene.Length; // Byte array is added to the ArrayList listedSceneData.Add(recompressedScene); r++; o++; } r = 0; o = 0; /* Step 3: Rebuilding the Scene.bin * We dynamically put scenes into a block until it would exceed 8192 bytes; then we create a new block. * The header is constantly updated with each new scene added to the block, using previous header to determine size. * When all 255 scenes are allocated, we finish up by padding off the last block to get a 40,000h/262,144 byte file. * The size will now have changed; we will update this later while generating our new scene.bin */ int sizeLimit = 8193; // Want to start by making a new header so we set size higher than limit to trigger that int headerInt; byte[] finalHeader = new byte[64]; using (var outputStream = File.Create(targetScene)) { int blockCount = 0; // Counts blocks for the kernel lookup table index // Loops until all 255 scenes are assigned to a block while (r < 256) { // Checks if the next scene will 'fit' into the current block. // No scene is added yet at this time, that is only done if there's space. sizeLimit += jaggedSceneInfo[o][1]; // If this returns true, then our block is 'full' and now needs to be padded to 8192 bytes exactly // 's' represents the number of scenes currently in the block, only 16 scenes can fit into one block if (sizeLimit >= 8192 || s == 16) { if (blockCount != 0) { s += kernelLookup[blockCount - 1]; } kernelLookup[blockCount] = (byte)s; blockCount++; // Pads the end of the block until it hits a divisor of 8192. outputStream.Position = outputStream.Length; while (outputStream.Length % 8192 > 0) { outputStream.Write(padder, 0, 1); } // A new blank header of FFs is made for the start of the next new block while (c < 64) { finalHeader[c] = 255; c++; } finalHeader[0] = 16; //First offset is always 16h in a header finalHeader[1] = 0; finalHeader[2] = 0; finalHeader[3] = 0; if (s != 0) { headerOffset += 8192; // Increment headerOffset } // Writes the header to the file at 8192 increments outputStream.Position = outputStream.Length; outputStream.Write(finalHeader, 0, 64); c = 0; k = 0; s = 0; sizeLimit = jaggedSceneInfo[o][1]; // Resets size to that of the first added scene in this new block sizeLimit += 64; } // When we write a compressed file in, we want to update the header for the next file. // We'll have a +4 incrementing value to write it into the appropriate header address. // This needs to avoid writing to the first header offset. // Takes the byte data from the ArrayList, converts it into a stream, and then appends it to the file-in-progress byte[] sceneData = (byte[])listedSceneData[o]; outputStream.Position = outputStream.Length; outputStream.Write(sceneData, 0, sceneData.Length); // Skips offset calculation if it is the first scene in the block as this is always 16h and has been written already if (s != 0) { // Calculates this scene's offset using the previous offset + that offset's file size. headerInt = EndianConvert.GetPreviousLittleEndianInt(finalHeader, k); headerInt *= 4; headerInt += jaggedSceneInfo[o - 1][1]; headerInt /= 4; // Now we have new offset calculated, time to convert it into bytes and write to header. byte[] bytes = BitConverter.GetBytes(headerInt); finalHeader[k] = bytes[0]; finalHeader[k + 1] = bytes[1]; finalHeader[k + 2] = bytes[2]; finalHeader[k + 3] = bytes[3]; // Write bytes to offset of k + headerOffset to our file now. outputStream.Position = headerOffset; outputStream.Write(finalHeader, 0, 64); } r++; o++; k += 4; s++; } r = 0; // ahhhh o = 0; // We're gonna loop through whiles c = 0; // all night k = 0; // and try-catch every day s = 0; // *gene falls over a rogue assignment* // All scenes allocated, the final file must now be padded to 8192 bytes outputStream.Position = outputStream.Length; while (outputStream.Length % 8192 > 0) { outputStream.Write(padder, 0, 1); } // New scene.bin ready to go. Hopefully. outputStream.Close(); } return(kernelLookup); }