public void Parse() { this.memoryBytes = new List <MemoryPatch>(); lastOffset = 0; var reMeta = new Regex(@"^//![\t ]+([A-Za-z0-9]+)[\t ]+(.+)$"); var reInfo = new Regex(@"^///[\t ]+(.+)$"); var reAddr = new Regex(@"^[\t ]*(|\$[\+\-][A-Fa-f0-9]+[\t ]+|\$[\t ]+[=]+>[\t ]+)([A-Fa-f0-9]+)"); var reMallocVar = new Regex(@"\[\[([A-Za-z0-9]+)\]\]"); // Changes "///" behavior to include comment inside macro/procedure body bool bodyStart = false; // Current line in body bodyIndex = 0; Match match; ParseMode mode = ParseMode.ASM; for (int i = 0; i < lines.Count; i++) { lineReporter?.Invoke(i + 1); var line = lines[i]; // Find additional patch configuration or mode switches match = reMeta.Match(line); if (match.Success) { string var = match.Groups[1].Value.Trim().ToUpper(); string val = match.Groups[2].Value.Trim(); if (var == "NAME") { ssl.Name = val; } else if (var == "SSL") { mode = ParseMode.SSL; bodyStart = true; } else if (var == "ASM") { mode = ParseMode.ASM; bodyStart = true; } continue; } // Find public comments, included in generated macro/procedure code match = reInfo.Match(line); if (match.Success) { if (!bodyStart) { ssl.AddInfo(match.Groups[1].Value.Trim()); } else { ssl.AddComment(match.Groups[1].Value.Trim()); bodyIndex++; } continue; } // A comment for the reader of the .asm file. if (line.Length >= 2 && line[0] == '/' && line[1] == '/') { continue; } if (line == "" && mallocMode == true) { MallocEnds(); continue; } if (mode == ParseMode.ASM) { if (!line.Contains('|')) { continue; } // 0044A785 | E9 FE06FDFF | jmp fallout2.41AE88 // Address | Byte sequence | Mnemonic / comment var spl = line.Split('|'); if (spl.Length < 3) { continue; } // Extract address, in case first column contains detailed address info and/or label match = reAddr.Match(line); if (match.Success) { spl[0] = match.Groups[2].Value; } else // Might be a variable also, [memory_var] { // Extract and resolve it if (spl[0].Count(x => x == '[') == 1 && spl[0].Count(x => x == ']') == 1) { int address = memoryArgs.ResolveAddress(spl[0], out string resolvedVariable); spl[0] = address.ToString("x"); this.parseEvents.Add(new ParseEventInfo() { @event = ParseEvent.ResolvedAddressMemoryArg, info = new MemoryArgInfo() { address = address, name = resolvedVariable } }); } // Or a malloc variable [[var]] var m = reMallocVar.Match(spl[0]); if (m.Success) { if (!ssl.Malloc) { Error.Fatal("Malloc needs to be enabled for this patch.", ErrorCodes.MallocRequired); } if (mallocMode) { MallocEnds(); } spl[0] = "0"; currentMallocVar = m.Groups[1].Value; mallocMode = true; mallocHead = bodyIndex; mallocBytes = 0; } } if (spl[0].Length == 0) { continue; } // changes "///" behavior to include comment inside macro/procedure body bodyStart = true; currentOffset = Convert.ToInt32(spl[0].Trim(), 16); // We encountered a new line with an address that doesn't use zero addressing while stile in active malloc mode // This means that another writegroup started. if (mallocMode && mallocBytes > 0 && currentOffset != 0) { MallocEnds(); } if (currentOffset == 0) { currentOffset = lastOffset; } var bytes = new ByteString(spl[1]); bool opLine = true; while (!bytes.EOF) { // Instructions which supports using absolute->rel addressing or memory variable. // https://github.com/ghost2238/sfall-asm/issues/3 // This only works for 32-bit, if we are in 16-bit mode it won't work. // But since we don't care about that at the moment it should be fine. if ((bytes.PeekByte() == 0xE9) || (bytes.PeekByte() == 0xE8)) { string mallocAddr = ""; if (bytes.PeekChar(2) == '[') { bytes.ResolveMemoryArg(memoryArgs, currentOffset, out string resolvedLiteral); mallocAddr = resolvedLiteral; } var isJump = (bytes.PeekByte() == 0xE9); var isCall = (bytes.PeekByte() == 0xE8); if (mallocMode) { mallocAddr = mallocAddr.StartsWith("0x") ? mallocAddr : "0x" + memoryArgs[mallocAddr].ToString("x"); var from = "$addr+" + currentOffset; var to = mallocAddr; var code = (isJump ? voodoo.MakeJump(from, to) : voodoo.MakeCall(from, to)).Code; ssl.AddCustomCode(code); mallocBytes += 5; currentOffset += 5; lastOffset = currentOffset; bytes.ReadChars(2 * 5); bodyIndex++; continue; } } if (runMode == RunMode.Memory) { memoryBytes.Add(new MemoryPatch() { data = new byte[] { bytes.ReadByte() }, offset = currentOffset }); } else { int writeSize = 0; if (ssl.Pack && bytes.HasBytesLeft(4)) { writeSize = 4; } else if (ssl.Pack && bytes.HasBytesLeft(2)) { writeSize = 2; } else { writeSize = 1; } ssl.AddWrite(writeSize, currentOffset, bytes.AsInt(writeSize), opLine ? spl[2].Trim() : "", mallocMode); // this is to decide if comment should be written or not. opLine = false; currentOffset += writeSize - 1; if (mallocMode) { mallocBytes += writeSize; } bodyIndex++; } currentOffset++; lastOffset = currentOffset; } } else if (mode == ParseMode.SSL) { // This needs to be more robust probably. // Right now we don't really care about which context we are in. // Since [] is used for arrays in SSL scripting with sfall there could be issues. // Thankfully for us, SSL does not (at this moment in time) allow declaring arrays inside a function call. // https://fakelshub.github.io/sfall-documentation/arrays/ int idx = 0; do { idx = line.IndexOf('[', idx); if (idx == -1) { break; } int value = memoryArgs.ResolveAddress(line, out string literal); line = line.Replace($"[{literal}]", "0x" + value.ToString("x")); } while (idx != -1); ssl.AddCustomCode(line); } } if (mallocMode) { MallocEnds(); } }