Example #1
0
        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();
            }
        }