예제 #1
0
 private void FunctionArguments(List <EMEDF.ArgDoc> args, int optCount, bool multiLine)
 {
     for (int i = 0; i < args.Count; i++)
     {
         EMEDF.ArgDoc argDoc   = args[i];
         bool         optional = i >= args.Count - optCount;
         if (optional && i == args.Count - optCount)
         {
             sb.Append("<span class=\"optarg\">");
         }
         if (i > 0)
         {
             sb.Append(", ");
         }
         if (multiLine)
         {
             sb.Append(Environment.NewLine + "    ");
         }
         string typeMod = argDoc.Vararg ? "..." : "";
         if (argDoc.EnumName == null)
         {
             sb.Append($"{Escape(InstructionDocs.TypeString(argDoc.Type))}{typeMod} {Escape(argDoc.DisplayName)}");
             if (optional && multiLine)
             {
                 sb.Append($" = {argDoc.Default}");
             }
         }
         else if (argDoc.EnumName == "BOOL")
         {
             sb.Append($"bool{typeMod} {Escape(argDoc.DisplayName)}");
             if (optional && multiLine)
             {
                 sb.Append($" = {(argDoc.Default == 0 ? "false" : "true")}");
             }
         }
         else if (argDoc.EnumDoc != null)
         {
             sb.Append($"enum{Escape("<")}");
             Link(argDoc.EnumDoc.DisplayName, argDoc.EnumDoc.DisplayName);
             sb.Append($"{Escape(">")}{typeMod} {Escape(argDoc.DisplayName)}");
             if (optional && multiLine)
             {
                 sb.Append($" = {argDoc.GetDisplayValue(argDoc.Default)}");
             }
         }
         if (optional && i == args.Count - 1)
         {
             sb.Append("</span>");
         }
     }
 }
예제 #2
0
        private void BoolConditionListItem(ConditionData.BoolVersion b, InstructionTranslator.FunctionDoc doc, InstructionTranslator.FunctionDoc baseDoc)
        {
            sb.Append($"<li><code>{Escape(doc.Name)}(");
            FunctionArguments(doc.Args, doc.OptionalArgs, false);
            sb.AppendLine($")</code>");

            EMEDF.ArgDoc  negateArg  = baseDoc.Args.Find(a => a.Name == doc.ConditionDoc.NegateField);
            EMEDF.EnumDoc negateEnum = doc.NegateEnum;
            List <string> details    = new List <string>();

            string getReq(EMEDF.ArgDoc arg, object val, object val2)
            {
                string showVal(object v) => Escape(arg.GetDisplayValue(v).ToString());

                return($"<code>{Escape(arg.DisplayName)} = {showVal(val)}{(val2 == null ? "" : " or " + showVal(val2))}</code>");
            }

            if (b.Required != null)
            {
                foreach (ConditionData.FieldValue req in b.Required)
                {
                    EMEDF.ArgDoc reqArg = baseDoc.Args.Find(a => a.Name == req.Field);
                    if (reqArg != null)
                    {
                        details.Add(getReq(reqArg, req.Value, null));
                    }
                }
            }
            if (negateArg != null && negateEnum != null)
            {
                if (b.True != null)
                {
                    int trueNum = int.Parse(negateEnum.Values.FirstOrDefault(e => e.Value == b.True).Key);
                    if (b.False == null)
                    {
                        details.Add(getReq(negateArg, trueNum, InstructionTranslator.OppositeOp(doc.ConditionDoc, negateEnum, trueNum)));
                    }
                    else
                    {
                        int falseNum = int.Parse(negateEnum.Values.FirstOrDefault(e => e.Value == b.False).Key);
                        details.Add(getReq(negateArg, trueNum, falseNum));
                    }
                }
                else
                {
                    details.Add($"{Escape(negateArg.DisplayName)} = true or false");
                }
            }
            sb.AppendLine($"<br/><span class=\"conddetails\">Where <code>{string.Join(" and ", details)}</code></span></li>");
        }
예제 #3
0
        private void CompareConditionListItem(ConditionData.CompareVersion c, InstructionTranslator.FunctionDoc doc, InstructionTranslator.FunctionDoc baseDoc)
        {
            string ops = Escape("== != > < >= <=");

            if (c.Lhs != null)
            {
                // Prebake rather than showing Op()
                sb.AppendLine($"<li><code>{ops}</code>");
                sb.AppendLine($"<br/><span class=\"conddetails\">Comparing <code>leftHandSide</code> and <code>rightHandSize</code></span>");
                return;
            }
            sb.Append($"<li><code>{doc.Name}(");
            FunctionArguments(doc.Args, doc.OptionalArgs, false);
            sb.AppendLine($") <span class=\"condcomp\">== value</span></code>");

            EMEDF.ArgDoc compareArg = baseDoc.Args.Find(a => a.Name == c.Rhs);
            sb.Append("<br/><span class=\"conddetails\">");
            if (compareArg != null)
            {
                sb.Append($"Comparing <code>{Escape(compareArg.DisplayName)}</code> (<code>{ops}</code>)");
            }
            sb.AppendLine("</span></li>");
        }
예제 #4
0
        public static InstructionTranslator GetTranslator(InstructionDocs docs)
        {
            ConditionData conds;

            if (File.Exists("conditions.json"))
            {
                conds = ReadFile("conditions.json");
            }
            else if (File.Exists(@"Resources\conditions.json"))
            {
                conds = ReadFile(@"Resources\conditions.json");
            }
            else
            {
                conds = ReadStream("conditions.json");
            }

            List <string> games = conds.Games.Select(g => g.Name).ToList();
            string        game  = games.Find(g => docs.ResourceString.StartsWith(g + "-common"));

            if (game == null)
            {
                return(null);
            }

            EMEDF emedf = docs.DOC;
            // Mapping from instruction id, like 3[03] or 1003[11], to EMEDF doc
            // This is the shorthand used by the config.
            Dictionary <string, EMEDF.InstrDoc> instrs = emedf.Classes
                                                         .Where(c => c.Index < 2000 && c.Index != 1014)
                                                         .SelectMany(c => c.Instructions.Select(i => (InstructionID(c.Index, i.Index), i)))
                                                         .ToDictionary(e => e.Item1, e => e.Item2);

            // Account for a command. Each condition/control flow statement should have a unique treatment.
            HashSet <string> visited = new HashSet <string>();

            bool processInfo(string type, string instr)
            {
                if (instr == null)
                {
                    return(false);
                }
                if (!instrs.TryGetValue(instr, out EMEDF.InstrDoc doc))
                {
                    return(false);
                }
                // Console.WriteLine($"{type} {instr}: {doc.Name}");
                if (visited.Contains(instr))
                {
                    throw new Exception($"{instr} appears twice in condition config for {game}");
                }
                visited.Add(instr);
                return(true);
            }

            foreach (string instr in conds.NoControlFlow)
            {
                processInfo("Excluded", instr);
            }
            int expectArg(string cmd, string name, object requiredType = null, int pos = -1)
            {
                EMEDF.InstrDoc doc = instrs[cmd];
                int            arg;
                List <string>  names = name.Split('|').ToList();

                if (pos >= 0)
                {
                    if (pos >= doc.Arguments.Length)
                    {
                        throw new ArgumentException($"{doc.Name} doesn't have arg {pos}");
                    }
                    if (!names.Contains(doc.Arguments[pos].Name))
                    {
                        throw new ArgumentException($"{doc.Name} arg {pos} dooesn't have name {name}, has {string.Join(", ", doc.Arguments.Select(s => s.Name))}");
                    }
                    arg = pos;
                }
                else
                {
                    arg = doc.Arguments.ToList().FindIndex(a => names.Contains(a.Name));
                    if (arg == -1)
                    {
                        throw new ArgumentException($"{doc.Name} doesn't have arg named {name}, has {string.Join(", ", doc.Arguments.Select(s => s.Name))}");
                    }
                }
                if (requiredType is string enumName)
                {
                    if (doc.Arguments[arg].EnumName != enumName)
                    {
                        throw new ArgumentException($"{doc.Name} arg {name} has enum type {doc.Arguments[arg].EnumName}, not {enumName}");
                    }
                }
                else if (requiredType is ArgType argType)
                {
                    if (doc.Arguments[arg].Type != (long)argType)
                    {
                        throw new ArgumentException($"{doc.Name} arg {name} has type {doc.Arguments[arg].Type}, not {argType}");
                    }
                }
                else if (requiredType != null)
                {
                    throw new Exception(requiredType.ToString());
                }
                return(arg);
            }

            // Indexed by condition function name
            Dictionary <string, FunctionDoc> condDocs = new Dictionary <string, FunctionDoc>();
            // Indexed by command id
            Dictionary <string, ConditionSelector> selectors = new Dictionary <string, ConditionSelector>();

            int asInt(object obj) => int.Parse(obj.ToString());

            void addVariants(ConditionSelector selector, ConditionDoc cond, ControlType use, string cmd, int control = -1)
            {
                if (selector.Variants.ContainsKey(cmd))
                {
                    throw new Exception($"Already added variants of {cond.Name} for {cmd}");
                }
                selector.Variants[cmd] = new List <ConditionVariant>();
                selectors[cmd]         = selector;
                EMEDF.InstrDoc doc        = instrs[cmd];
                int            negateArg  = -1;
                string         negateName = cond.IsCompare ? "Comparison Type" : cond.NegateField;

                EMEDF.EnumDoc negateDoc = null;
                if (negateName != null)
                {
                    negateArg = expectArg(cmd, negateName, cond.IsCompare ? "Comparison Type" : null);
                    string negateEnum = doc.Arguments[negateArg].EnumName;
                    // Pretend these are compatible
                    if (negateEnum == "ON/OFF")
                    {
                        negateEnum = "ON/OFF/CHANGE";
                    }
                    negateDoc = emedf.Enums.Where(d => d.Name == negateEnum).FirstOrDefault();
                    if (negateDoc == null)
                    {
                        throw new ArgumentException($"Command {cmd} for cond {cond.Name} at arg {negateArg} uses enum which does not exist");
                    }
                    if (selector.NegateEnum == null)
                    {
                        selector.NegateEnum = negateDoc;
                    }
                    else if (selector.NegateEnum.Name != negateEnum)
                    {
                        throw new ArgumentException($"Command {cmd} for {cond.Name} has negate enum {negateEnum} but {selector.NegateEnum.Name} was already used in a different command");
                    }
                }
                void addVariant(BoolVersion bv = null, CompareVersion cv = null)
                {
                    ConditionVariant variant = new ConditionVariant {
                        Variant = use
                    };
                    string     name   = cond.Name;
                    List <int> ignore = new List <int>();

                    if (control >= 0)
                    {
                        variant.ControlArg = control;
                        ignore.Add(control);
                    }
                    if (negateArg >= 0 && (cv != null || bv != null))
                    {
                        variant.NegateArg = negateArg;
                        ignore.Add(negateArg);
                    }
                    if (bv != null)
                    {
                        name = bv.Name;
                        if (negateDoc == null)
                        {
                            throw new ArgumentException($"Cond {cond.Name} has boolean variant {name} but no negate_field");
                        }
                        string trueVal = negateDoc.Name == "BOOL" ? "TRUE" : bv.True;
                        string trueKey = negateDoc.Values.Where(e => e.Value == trueVal).First().Key;
                        variant.TrueOp = asInt(trueKey);
                        if (bv.Required != null)
                        {
                            foreach (FieldValue req in bv.Required)
                            {
                                int reqArg = expectArg(cmd, req.Field);
                                variant.ExtraArgs[reqArg] = req.Value;
                                ignore.Add(reqArg);
                            }
                        }
                    }
                    else if (cv != null)
                    {
                        name = cv.Name;
                        if (cv.Rhs == null)
                        {
                            throw new ArgumentException($"Cond {cond.Name} has compare variant {name} with no RHS specified");
                        }
                        variant.CompareArg = expectArg(cmd, cv.Rhs);
                        ignore.Add(variant.CompareArg);
                        if (cv.Lhs != null)
                        {
                            variant.CompareArg2 = expectArg(cmd, cv.Lhs);
                            ignore.Add(variant.CompareArg2);
                        }
                    }
                    if (name == null)
                    {
                        throw new ArgumentException($"Name for {cond.Name} and {cmd} is missing");
                    }
                    EMEDF.ArgDoc optArgDoc = null;
                    // Optional args serve two purposes: to hide noisy default values, and to address inconsistencies between variants.
                    // In all cases, they can be established from the COND variant.
                    if (use == ControlType.COND && cond.OptFields != null)
                    {
                        for (int i = 0; i < cond.OptFields.Count; i++)
                        {
                            if (doc.Arguments[doc.Arguments.Length - cond.OptFields.Count + i].Name != cond.OptFields[i])
                            {
                                break;
                            }
                        }
                        bool matching = cond.OptFields.Select((a, i) => a == doc.Arguments[doc.Arguments.Length - cond.OptFields.Count + i].Name).All(b => b);
                        // Missing opt args can happen when "# of target character"-type arguments get added over time.
                        // This is mostly a pretty-printing feature. so it might be tedious to specify each individual change between games.
                        if (matching)
                        {
                            int optArg = doc.Arguments.Length - cond.OptFields.Count;
                            if (ignore.Contains(optArg))
                            {
                                throw new Exception($"Optional arg {cond.OptFields[0]} is not part of the normal argument list for {name} with {cmd}");
                            }
                            optArgDoc = doc.Arguments[optArg];
                        }
                    }
                    // Non-control-flow arguments should match for all uses of a condition, across goto/skip/etc. commands
                    // (other than for optional args)
                    List <EMEDF.ArgDoc> condArgs = doc.Arguments.Where((a, i) => !ignore.Contains(i)).ToList();

                    if (condDocs.TryGetValue(name, out FunctionDoc condDoc))
                    {
                        string older    = string.Join(", ", condDoc.Args.Select(a => a.Name));
                        string newer    = string.Join(", ", condArgs.Select(a => a.Name));
                        bool   matching = older == newer;
                        if (!matching && condDoc.OptionalArgs > 0)
                        {
                            // This is only permissible if the existing definition has optional args, in which case the shared segment should still match.
                            older    = string.Join(", ", condDoc.Args.Take(condDoc.Args.Count - condDoc.OptionalArgs).Select(a => a.Name));
                            matching = older == newer;
                        }
                        if (!matching)
                        {
                            throw new Exception($"Multiple possible definitions found for {name}: [{older}] existing vs [{newer}] for {cmd}. opt args {condDoc.OptionalArgs}");
                        }
                    }
                    else
                    {
                        condDocs[name] = condDoc = new FunctionDoc
                        {
                            Name         = name,
                            Args         = condArgs,
                            ConditionDoc = cond,
                            NegateEnum   = negateDoc,
                        };
                        if (optArgDoc != null)
                        {
                            condDoc.OptionalArgs = condArgs.Count - condArgs.IndexOf(optArgDoc);
                        }
                    }
                    condDoc.Variants[use] = cmd;
                    variant.Doc           = condDoc;
                    selector.Variants[cmd].Add(variant);
                }

                foreach (BoolVersion version in cond.AllBools)
                {
                    addVariant(bv: version);
                }
                foreach (CompareVersion version in cond.AllCompares)
                {
                    addVariant(cv: version);
                }
                addVariant();
            }

            foreach (ConditionDoc cond in conds.Conditions)
            {
                ConditionSelector selector = new ConditionSelector {
                    Cond = cond
                };
                if (cond.Games != null && !cond.Games.Contains(game))
                {
                    continue;
                }
                if (processInfo($"Cond {cond.Name}", cond.Cond))
                {
                    // The cond variant should go first, as it can have a superset of other variants' args if marked in the config.
                    expectArg(cond.Cond, "Result Condition Group", "Condition Group", 0);
                    addVariants(selector, cond, ControlType.COND, cond.Cond, 0);
                }
                if (processInfo($"Skip {cond.Name}", cond.Skip))
                {
                    expectArg(cond.Skip, "Number Of Skipped Lines", EMEVD.Instruction.ArgType.Byte, 0);
                    addVariants(selector, cond, ControlType.SKIP, cond.Skip, 0);
                }
                if (processInfo($"End  {cond.Name}", cond.End))
                {
                    expectArg(cond.End, "Execution End Type", "Event End Type", 0);
                    addVariants(selector, cond, ControlType.END, cond.End, 0);
                }
                if (processInfo($"Goto {cond.Name}", cond.Goto))
                {
                    expectArg(cond.Goto, "Label", "Label", 0);
                    addVariants(selector, cond, ControlType.GOTO, cond.Goto, 0);
                }
                if (processInfo($"Wait {cond.Name}", cond.Wait))
                {
                    // Implicit main arg
                    addVariants(selector, cond, ControlType.WAIT, cond.Wait);
                }
            }
            string undocError = string.Join(", ", instrs.Where(e => !visited.Contains(e.Key)).Select(e => $"{e.Key}:{e.Value.Name}"));

            if (undocError.Length > 0)
            {
                // This doesn't have to be an error, but it does mean that condition group decompilation is impossible when these commands are present.
                throw new ArgumentException($"Present in emedf but not condition config for {game}: {undocError}");
            }
            Dictionary <string, int> labels = new Dictionary <string, int>();

            EMEDF.ClassDoc labelClass = emedf[1014];
            if (labelClass != null)
            {
                labels = labelClass.Instructions.ToDictionary(i => InstructionID(1014, i.Index), i => (int)i.Index);
            }
            return(new InstructionTranslator
            {
                CondDocs = condDocs,
                Selectors = selectors,
                InstrDocs = instrs,
                LabelDocs = labels,
            });
        }
예제 #5
0
        /// <summary>
        /// Called by JS to add instructions to the event currently being edited.
        /// </summary>
        public Instruction MakeInstruction(Event evt, int bank, int index, object[] args)
        {
            CurrentEventID  = (int)evt.ID;
            CurrentInsIndex = evt.Instructions.Count + 1;

            try
            {
                EMEDF.InstrDoc doc   = docs.DOC[bank][index];
                bool           isVar = docs.IsVariableLength(doc);
                if (args.Length < doc.Arguments.Length)
                {
                    throw new Exception($"Instruction {bank}[{index}] ({doc.Name}) requires {doc.Arguments.Length} arguments, given {args.Length}.");
                }
                if (!isVar && args.Length > doc.Arguments.Length)
                {
                    throw new Exception($"Instruction {bank}[{index}] ({doc.Name}) given {doc.Arguments.Length} arguments, only permits {args.Length}.");
                }

                for (int i = 0; i < args.Length; i++)
                {
                    if (args[i] is bool)
                    {
                        args[i] = (bool)args[i] ? 1 : 0;
                    }
                    else if (args[i] is string)
                    {
                        if (isVar)
                        {
                            throw new Exception("Event initializers cannot be dependent on parameters.");
                        }

                        IEnumerable <int> nums = (args[i] as string).Substring(1).Split('_').Select(s => int.Parse(s));
                        if (nums.Count() != 2)
                        {
                            throw new Exception("Invalid parameter string: {" + args[i] + "}");
                        }

                        int sourceStartByte = nums.ElementAt(0);
                        int length          = nums.ElementAt(1);
                        int targetStartByte = docs.FuncBytePositions[doc][i];

                        Parameter p = new Parameter(evt.Instructions.Count, targetStartByte, sourceStartByte, length);
                        evt.Parameters.Add(p);
                        evt.Parameters = evt.Parameters.OrderBy(prm => prm.SourceStartByte).ToList();

                        args[i] = doc.Arguments[i].Default;
                    }
                }

                List <object> properArgs = new List <object>();
                if (isVar)
                {
                    foreach (object arg in args)
                    {
                        properArgs.Add(Convert.ToInt32(arg));
                    }
                }
                else
                {
                    for (int i = 0; i < doc.Arguments.Length; i++)
                    {
                        EMEDF.ArgDoc argDoc = doc.Arguments[i];
                        if (argDoc.Type == 0)
                        {
                            properArgs.Add(Convert.ToByte(args[i]));                   //u8
                        }
                        else if (argDoc.Type == 1)
                        {
                            properArgs.Add(Convert.ToUInt16(args[i]));                        //u16
                        }
                        else if (argDoc.Type == 2)
                        {
                            properArgs.Add(Convert.ToUInt32(args[i]));                        //u32
                        }
                        else if (argDoc.Type == 3)
                        {
                            properArgs.Add(Convert.ToSByte(args[i]));                        //s8
                        }
                        else if (argDoc.Type == 4)
                        {
                            properArgs.Add(Convert.ToInt16(args[i]));                        //s16
                        }
                        else if (argDoc.Type == 5)
                        {
                            properArgs.Add(Convert.ToInt32(args[i]));                        //s32
                        }
                        else if (argDoc.Type == 6)
                        {
                            properArgs.Add(Convert.ToSingle(args[i]));                        //f32
                        }
                        else if (argDoc.Type == 8)
                        {
                            properArgs.Add(Convert.ToUInt32(args[i]));                        //string position
                        }
                        else
                        {
                            throw new Exception("Invalid type in argument definition.");
                        }
                    }
                }
                Instruction ins = new Instruction(bank, index, properArgs);
                evt.Instructions.Add(ins);
                CurrentEventID  = -1;
                CurrentInsIndex = -1;
                return(ins);
            }
            catch (Exception ex)
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine($"EXCEPTION\nCould not write instruction at Event {CurrentEventID}, index {CurrentInsIndex}.\n");
                sb.AppendLine($"INSTRUCTION\n{CurrentInsName} | {bank}[{index}]\n");
                sb.AppendLine(ex.Message);
                throw new Exception(sb.ToString());
            }
        }