public void ToBinary_ShouldMatchSourceBinary_Version1BigEndian() { var binary = MessageScriptBinary.FromFile("TestResources\\Version1BigEndian.bmd"); var script = MessageScript.FromBinary(binary); var newBinary = script.ToBinary(); Compare(binary, newBinary); }
private void WriteMessageScriptDisassembly() { mWriter.WriteLine(".msg"); using (var messageScriptDecompiler = new MessageScriptDecompiler(mWriter)) { messageScriptDecompiler.Decompile(MessageScript.FromBinary(mScript.MessageScriptSection)); } }
static void ScanForMessageScripts(string prettyFileName, Stream stream, CancellationToken cancellationToken) { byte[] magic = new byte[4]; while (stream.Position <= stream.Length) { if (cancellationToken.IsCancellationRequested) { break; } if (stream.Position + BinaryHeader.SIZE < stream.Length) { // Read 4 bytes magic[0] = ( byte )stream.ReadByte(); magic[1] = ( byte )stream.ReadByte(); magic[2] = ( byte )stream.ReadByte(); magic[3] = ( byte )stream.ReadByte(); if (magic.SequenceEqual(BinaryHeader.MAGIC_V0) || magic.SequenceEqual(BinaryHeader.MAGIC_V1) || magic.SequenceEqual(BinaryHeader.MAGIC_V1_BE)) { long scriptStartPosition = stream.Position - 12; var scriptBinary = MessageScriptBinary.FromStream(new StreamView(stream, scriptStartPosition, stream.Length - scriptStartPosition)); var script = MessageScript.FromBinary(scriptBinary); Console.WriteLine($"Found message script at 0x{scriptStartPosition:X8}. Writing to file..."); if (UseDecompiler) { WriteMessageScriptWithDecompiler($"{prettyFileName} @ 0x{scriptStartPosition:X8}", script); } else { WriteMessageScript($"{prettyFileName} @ 0x{scriptStartPosition:X8}", script); } stream.Position = scriptStartPosition + scriptBinary.Header.FileSize; } else if (DisableScanAlignment) { // Scan alignment is disabled, so we make sure to retry every byte // 4 steps forward, 3 steps back stream.Position -= 3; } } else { break; } } }
public void FromBinary_ShouldNotThrow_Version1BigEndian() { var binary = MessageScriptBinary.FromFile("TestResources\\Version1BigEndian.bmd"); var script = MessageScript.FromBinary(binary); }
static void ExtractMessageScript(string file, Stream stream, string parentArchiveFile) { string prettyFileName; if (parentArchiveFile == null) { prettyFileName = file.Remove(0, DirectoryPath.Length); } else { prettyFileName = Path.Combine(parentArchiveFile, file); } // print some useful info if (parentArchiveFile == null) { Console.WriteLine($"Processing file: {prettyFileName}"); } else { Console.WriteLine($"Processing archive file: {prettyFileName}"); } // extract script MessageScript script = null; string fileExtension = Path.GetExtension(file); // Check if it is a plain message script file if (fileExtension.Equals(".bmd", StringComparison.InvariantCultureIgnoreCase)) { script = MessageScript.FromStream(stream, FormatVersion.Detect, Encoding, true); } // Check if it is a flow script file that can maybe contain a message script else if (fileExtension.Equals(".bf", StringComparison.InvariantCultureIgnoreCase)) { var flowScriptBinary = FlowScriptBinary.FromStream(stream, true); if (flowScriptBinary.MessageScriptSection != null) { script = MessageScript.FromBinary(flowScriptBinary.MessageScriptSection, FormatVersion.Detect, Encoding); } else { return; } } if (script != null) { // We have found a script, yay! Console.WriteLine("Writing message script to file..."); if (UseDecompiler) { WriteMessageScriptWithDecompiler(prettyFileName, script); } else { WriteMessageScript(prettyFileName, script); } } else { // Try to open the file as an archive if (!Archive.TryOpenArchive(stream, out var archive)) { // If we can't open the file as an archive, try brute force scanning if it is enabled if (EnableBruteforceScanning && (BruteforceExclusionList == null || BruteforceExclusionList != null && !BruteforceExclusionList.Any(x => x.Equals(fileExtension, StringComparison.InvariantCultureIgnoreCase))) && (BruteforceInclusionList == null || BruteforceInclusionList != null && BruteforceInclusionList.Any(x => x.Equals(fileExtension, StringComparison.InvariantCultureIgnoreCase))) ) { Console.WriteLine($"Bruteforce scanning..."); var scanCancel = new CancellationTokenSource(); var scanTask = Task.Factory.StartNew(() => ScanForMessageScripts(prettyFileName, stream, scanCancel.Token)); while (!scanTask.IsCompleted) { // Don't want to block, so wait for key to be available if (Console.KeyAvailable) { // Blocking is fine after this point var key = Console.ReadKey(true); if (key.Key == ConsoleKey.S) { Console.WriteLine("Do you want to skip scanning this file? Y/N"); if (Console.ReadKey(true).Key == ConsoleKey.Y) { scanCancel.Cancel(); Console.WriteLine("Do you want to add this file extension to the list of excluded files? Y/N"); if (Console.ReadKey(true).Key == ConsoleKey.Y) { if (BruteforceExclusionList == null) { BruteforceExclusionList = new List <string>(); } BruteforceExclusionList.Add(Path.GetExtension(prettyFileName)); } } } } } } } else { foreach (var entry in archive) { ExtractMessageScript(entry, archive.OpenFile(entry), prettyFileName); } } } }
/// <summary> /// Creates a <see cref="FlowScript"/> from a <see cref="FlowScriptBinary"/> object. /// </summary> /// <param name="binary">A <see cref="FlowScriptBinary"/> instance.</param> /// <returns>A <see cref="FlowScript"/> instance.</returns> public static FlowScript FromBinary(FlowScriptBinary binary, Encoding encoding = null) { var instance = new FlowScript { mUserId = binary.Header.UserId }; // assign labels later after convert the instructions because we need to update the instruction indices // to reference the instructions in the list, and not the instructions in the array of instructions in the binary // assign strings before instructions so we can assign proper string indices as we convert the instructions var stringBinaryIndexToListIndexMap = new Dictionary <short, short>(); var strings = new List <string>(); if (binary.StringSection != null) { short curStringBinaryIndex = 0; var curStringBytes = new List <byte>(); for (short i = 0; i < binary.StringSection.Count; i++) { // check for string terminator or end of string section if (binary.StringSection[i] == 0 || i + 1 == binary.StringSection.Count) { strings.Add(ShiftJISEncoding.Instance.GetString(curStringBytes.ToArray())); stringBinaryIndexToListIndexMap[curStringBinaryIndex] = ( short )(strings.Count - 1); // next string will start at the next byte if there are any left curStringBinaryIndex = ( short )(i + 1); curStringBytes = new List <byte>(); } else { curStringBytes.Add(binary.StringSection[i]); } } } var instructionBinaryIndexToListIndexMap = new Dictionary <int, int>(); var instructions = new List <Instruction>(); // assign instructions // TODO: optimize this away later as i don't feel like it right now if (binary.TextSection != null) { int instructionIndex = 0; int instructionBinaryIndex = 0; while (instructionBinaryIndex < binary.TextSection.Count) { // Convert each instruction var binaryInstruction = binary.TextSection[instructionBinaryIndex]; Instruction instruction; // Handle instructions we need to alter seperately if (binaryInstruction.Opcode == Opcode.PUSHSTR) { // Update the string offset to reference the strings inside of the string list instruction = Instruction.PUSHSTR(strings[stringBinaryIndexToListIndexMap[binaryInstruction.OperandShort]]); } else if (binaryInstruction.Opcode == Opcode.PUSHI) { instruction = Instruction.PUSHI(binary.TextSection[instructionBinaryIndex + 1].OperandInt); } else if (binaryInstruction.Opcode == Opcode.PUSHF) { instruction = Instruction.PUSHF(binary.TextSection[instructionBinaryIndex + 1].OperandFloat); } else { instruction = Instruction.FromBinaryInstruction(binaryInstruction); } // Add to list instructions.Add(instruction); instructionBinaryIndexToListIndexMap[instructionBinaryIndex] = instructionIndex++; // Increment the instruction binary index by 2 if the current instruction takes up 2 instructions if (instruction.UsesTwoBinaryInstructions) { instructionBinaryIndex += 2; } else { instructionBinaryIndex += 1; } } } // assign labels as the instruction index remap table has been built var sortedProcedureLabels = binary.ProcedureLabelSection.OrderBy(x => x.InstructionIndex).ToList(); for (int i = 0; i < binary.ProcedureLabelSection.Count; i++) { var procedureLabel = binary.ProcedureLabelSection[i]; int procedureListStartIndex = instructionBinaryIndexToListIndexMap[procedureLabel.InstructionIndex]; int nextProcedureLabelIndex = sortedProcedureLabels.FindIndex(x => x.InstructionIndex == procedureLabel.InstructionIndex) + 1; int procedureInstructionCount; int procedureBinaryEndIndex; // inclusive // Calculate the number of instructions in the procedure bool isLastProcedure = nextProcedureLabelIndex == binary.ProcedureLabelSection.Count; if (isLastProcedure) { procedureInstructionCount = (instructions.Count - procedureListStartIndex); procedureBinaryEndIndex = binary.TextSection.Count - 1; } else { var nextProcedureLabel = binary.ProcedureLabelSection[nextProcedureLabelIndex]; procedureInstructionCount = (instructionBinaryIndexToListIndexMap[nextProcedureLabel.InstructionIndex] - procedureListStartIndex); procedureBinaryEndIndex = nextProcedureLabel.InstructionIndex - 1; } // Copy the instruction range var procedureInstructions = new List <Instruction>(procedureInstructionCount); for (int j = 0; j < procedureInstructionCount; j++) { procedureInstructions.Add(instructions[procedureListStartIndex + j]); } // Create the new procedure representation Procedure procedure; if (binary.JumpLabelSection != null) { // Find jump labels within instruction range of procedure var procedureBinaryJumpLabels = binary.JumpLabelSection .Where(x => x.InstructionIndex >= procedureLabel.InstructionIndex && x.InstructionIndex <= procedureBinaryEndIndex) .ToList(); if (procedureBinaryJumpLabels.Count > 0) { // Generate mapping between label name and the procedure-local index of the label var procedureJumpLabelNameToLocalIndexMap = new Dictionary <string, int>(procedureBinaryJumpLabels.Count); for (int k = 0; k < procedureBinaryJumpLabels.Count; k++) { procedureJumpLabelNameToLocalIndexMap[procedureBinaryJumpLabels[k].Name] = k; } // Convert the labels to the new representation var procedureJumpLabels = new List <Label>(procedureBinaryJumpLabels.Count); foreach (var procedureBinaryJumpLabel in procedureBinaryJumpLabels) { int globalInstructionListIndex = instructionBinaryIndexToListIndexMap[procedureBinaryJumpLabel.InstructionIndex]; int localInstructionListIndex = globalInstructionListIndex - procedureListStartIndex; procedureJumpLabels.Add(new Label(procedureBinaryJumpLabel.Name, localInstructionListIndex)); } // Create the procedure procedure = new Procedure(procedureLabel.Name, procedureInstructions, procedureJumpLabels); // Loop over the instructions and update the instructions that reference labels // so that they refer to the proper procedure-local label index for (int j = 0; j < procedure.Instructions.Count; j++) { var instruction = procedure.Instructions[j]; if (instruction.Opcode == Opcode.GOTO || instruction.Opcode == Opcode.IF) { short globalIndex = instruction.Operand.Int16Value; var binaryLabel = binary.JumpLabelSection[globalIndex]; short localIndex = ( short )procedureJumpLabelNameToLocalIndexMap[binaryLabel.Name]; instruction.Operand.Int16Value = localIndex; Debug.Assert(procedure.Labels[localIndex].Name == binaryLabel.Name); } procedure.Instructions[j] = instruction; } } else { // Create the procedure procedure = new Procedure(procedureLabel.Name, procedureInstructions); } } else { // Create the procedure procedure = new Procedure(procedureLabel.Name, procedureInstructions); } instance.mProcedures.Add(procedure); } // assign message script if (binary.MessageScriptSection != null) { instance.mMessageScript = MessageScript.FromBinary(binary.MessageScriptSection, MessageScriptLanguage.FormatVersion.Detect, encoding); } // strings have already been assigned previously, // so last up is the version instance.mFormatVersion = (FormatVersion)binary.FormatVersion; // everything is assigned, return the constructed instance return(instance); }