Beispiel #1
0
        public void UnpackEvent(Event evt, StringBuilder code, bool compatibilityMode = false)
        {
            CurrentEventID = (int)evt.ID;

            string id           = evt.ID.ToString();
            string restBehavior = evt.RestBehavior.ToString();

            Dictionary <Parameter, string> paramNames  = ParamNames(evt);
            IEnumerable <string>           argNameList = paramNames.Values.Distinct();
            string evtArgs = string.Join(", ", argNameList);

            string eventName = EventName(evt.ID);

            if (eventName != null)
            {
                code.AppendLine($"// {eventName}");
            }
            code.AppendLine($"Event({id}, {restBehavior}, function({evtArgs}) {{");
            for (int insIndex = 0; insIndex < evt.Instructions.Count; insIndex++)
            {
                CurrentInsIndex = insIndex;
                Instruction    ins = evt.Instructions[insIndex];
                EMEDF.InstrDoc doc = docs.DOC[ins.Bank]?[ins.ID];
                if (doc == null)
                {
                    StringBuilder sb = new StringBuilder();
                    sb.AppendLine($@"Unable to read instruction at Event {CurrentEventID}, Index {CurrentInsIndex}.");
                    sb.AppendLine($@"Unknown instruction id: {InstructionDocs.InstrDebugString(ins)}");
                    throw new Exception(sb.ToString());
                }
                string funcName = doc.DisplayName;

                List <object> args;
                try
                {
                    args = docs.UnpackArgsWithParams(ins, insIndex, doc, paramNames, (argDoc, val) => argDoc.GetDisplayValue(val), compatibilityMode);
                }
                catch (Exception ex)
                {
                    var sb = new StringBuilder();
                    sb.AppendLine($@"Unable to unpack arguments for {funcName}({InstructionDocs.InstrDocDebugString(doc)}) at Event {CurrentEventID}, Index {CurrentInsIndex}.");
                    sb.AppendLine($@"Instruction arg data: {InstructionDocs.InstrDebugString(ins)}");
                    sb.AppendLine(ex.Message);
                    throw new Exception(sb.ToString());
                }

                if (ins.Layer.HasValue)
                {
                    args.Add(InstructionDocs.LayerString(ins.Layer.Value));
                }

                string lineOfCode = $"{doc.DisplayName}({string.Join(", ", args)});";
                code.AppendLine("\t" + lineOfCode);
            }
            code.AppendLine("});");
            code.AppendLine("");

            CurrentInsIndex = -1;
            CurrentEventID  = -1;
        }
Beispiel #2
0
 public FancyEventScripter(EventScripter scripter, InstructionDocs docs, EventCFG.CFGOptions options)
 {
     if (docs.Translator == null)
     {
         throw new ArgumentException($"Internal error: can't use fancy scripting with {docs.ResourceString}");
     }
     this.scripter = scripter;
     this.docs     = docs;
     this.options  = options;
 }
Beispiel #3
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>");
         }
     }
 }
Beispiel #4
0
 public ScriptSettings(InstructionDocs docs, Dictionary <string, string> existing = null)
 {
     this.docs = docs;
     settings  = new Dictionary <string, string>();
     if (existing != null)
     {
         string val;
         if (existing.TryGetValue("ds1r", out val))
         {
             IsRemastered = val == "true";
         }
         if (existing.TryGetValue("nopreprocess", out val))
         {
             AllowPreprocess = val == "true";
         }
     }
 }
Beispiel #5
0
 public EventScripter(string file, InstructionDocs docs, EMEVD evd = null)
 {
     EMEVDPath = file;
     this.docs = docs;
     EVD       = evd ?? EMEVD.Read(file);
     if (File.Exists(file.Replace(".emevd", ".emeld")))
     {
         try
         {
             ELD = EMELD.Read(file.Replace(".emevd", ".emeld"));
         }
         catch
         {
         }
     }
     InitAll();
 }
Beispiel #6
0
        public static void Generate(string[] args)
        {
            string game     = args[1];
            string outDir   = args[2];
            string emevdDir = args.Length > 3 ? string.Join(" ", args.Skip(3)) : null;

            Console.WriteLine($">>>>>> Generating {game}");
            InstructionDocs docs = new InstructionDocs($"{game}-common.emedf.json");
            EMEDF2HTML      gen  = new EMEDF2HTML();

            gen.AppendEMEDF(docs, emevdDir);
            string outPath = $@"{outDir}\{game}-emedf.html";

            Console.WriteLine($"<<<<<< Out to [{outPath}]");
            using (TextWriter writer = File.CreateText(outPath))
            {
                writer.Write(gen.ToString());
            }
        }
Beispiel #7
0
        public void AppendEMEDF(InstructionDocs docs, string emevdDir = null)
        {
            string game     = gameNames.Keys.FirstOrDefault(g => docs.ResourceString.StartsWith(g + "-common"));
            string gameName = game == null ? docs.ResourceString : gameNames[game];

            Dictionary <string, Usages> symbolUsages = new Dictionary <string, Usages>();
            bool showUsages = false;

            if (emevdDir != null)
            {
                symbolUsages = GetSymbolUsages(game, emevdDir, docs);
                showUsages   = true;
                // Hack to merge PTDE and DS1R into one
                if (emevdDir.Contains("DARK SOULS REMASTERED"))
                {
                    string ptdeDir = emevdDir.Replace(@"DARK SOULS REMASTERED\event", @"Dark Souls Prepare to Die Edition\DATA\event");
                    if (ptdeDir != emevdDir)
                    {
                        Dictionary <string, Usages> ptdeUsages = GetSymbolUsages(game, ptdeDir, docs);
                        foreach (string symbol in symbolUsages.Keys.Union(ptdeUsages.Keys).ToList())
                        {
                            symbolUsages.TryGetValue(symbol, out Usages ds1rUse);
                            ptdeUsages.TryGetValue(symbol, out Usages ptdeUse);
                            symbolUsages[symbol] = Usages.Reconcile(ds1rUse, "in DS1R", ptdeUse, "in PTDE");
                        }
                    }
                }
            }

            PageHeader(gameName + " EMEDF for DarkScript3", docs.Translator != null);

            string mainCondName(ConditionData.ConditionDoc condDoc)
            {
                if (condDoc.Name == "Compare")
                {
                    return(condDoc.Name);
                }
                string name = condDoc.AllBools.FirstOrDefault()?.Name;

                if (name != null)
                {
                    return(name);
                }
                name = condDoc.AllCompares.FirstOrDefault()?.Name;
                if (name != null)
                {
                    return(name);
                }
                return(condDoc.Name);
            }

            // Instructions
            // Classes section
            // Instructions per xyz
            // For each instruction: one-line method signature, links to enums, equivalent conditions' one-line signatures,
            // equivalent fancy commands, usages
            BigSectionHeader("Instructions");
            foreach (EMEDF.ClassDoc classDoc in docs.DOC.Classes)
            {
                string className = $"{classDoc.Index} - {classDoc.Name}";
                SubHeader(className, className);
                foreach (EMEDF.InstrDoc instrDoc in classDoc.Instructions)
                {
                    string id   = InstructionTranslator.InstructionID(classDoc.Index, instrDoc.Index);
                    string name = instrDoc.DisplayName;

                    InstructionTranslator.ConditionSelector condSelect = null;
                    docs.Translator?.Selectors.TryGetValue(id, out condSelect);

                    List <string> tags = new List <string> {
                        "instr"
                    };
                    if (condSelect != null || (docs.Translator?.LabelDocs.ContainsKey(id) ?? false))
                    {
                        tags.Add("condinstr");
                    }
                    bool unused = showUsages && !symbolUsages.ContainsKey(name);
                    if (unused)
                    {
                        tags.Add("unused");
                    }

                    Section(name, "Instruction " + id, tags, classDoc.Index != 1014, () =>
                    {
                        if (showUsages)
                        {
                            symbolUsages.TryGetValue(name, out Usages usages);
                            SectionUsageDetails(usages);
                        }
                        FunctionSignature(name, instrDoc.Arguments.ToList(), 0);
                        if (condSelect != null)
                        {
                            if (!condSelect.Cond.Hidden)
                            {
                                string condName = mainCondName(condSelect.Cond);
                                sb.Append($"<p>Condition function: <code>");
                                Link(condName, condName);
                                sb.AppendLine("</code></p>");
                            }
                            else if (specialCommands.TryGetValue(id, out string alt))
                            {
                                sb.Append($"<p><code>{Escape(alt)}</code> in MattScript</p>");
                            }
                        }
                    });
                }
            }
            BigSectionFooter();

            // Condition functions. Main head is first bool/compare if it exists
            if (docs.Translator != null)
            {
                BigSectionHeader("Condition Functions");
                // Reread it to get the original order and grouping and names, but use InstructionTranslator for everything else
                ConditionData         conds = ConditionData.ReadStream("conditions.json");
                InstructionTranslator info  = docs.Translator;
                // There are duplicate names for different games, so just bail out if the same name encountered again
                HashSet <string> condNames = new HashSet <string>();
                foreach (ConditionData.ConditionDoc storedCondDoc in conds.Conditions)
                {
                    if (storedCondDoc.Hidden || condNames.Contains(storedCondDoc.Name))
                    {
                        continue;
                    }
                    condNames.Add(storedCondDoc.Name);
                    if (!info.CondDocs.TryGetValue(storedCondDoc.Name, out InstructionTranslator.FunctionDoc baseDoc))
                    {
                        // Corresponding instructions do not exist in this game
                        continue;
                    }
                    // Make sure we have the right one for this game
                    ConditionData.ConditionDoc condDoc = baseDoc.ConditionDoc;
                    Usages usages = Usages.UnionAll(
                        baseDoc.Variants.Values
                        .Select(id => symbolUsages.TryGetValue(info.InstrDocs[id].DisplayName, out Usages instrUsages) ? instrUsages : null));

                    List <string> tags = new List <string> {
                        "cond"
                    };
                    bool unused = showUsages && usages == null;
                    if (unused)
                    {
                        tags.Add("unused");
                    }

                    Section(mainCondName(condDoc), "Condition function", tags, true, () =>
                    {
                        if (showUsages)
                        {
                            SectionUsageDetails(usages);
                        }
                        FunctionSignature(condDoc.Name, baseDoc.Args, baseDoc.OptionalArgs);

                        int variantCount = condDoc.AllBools.Count + condDoc.AllCompares.Count;
                        if (variantCount > 0)
                        {
                            sb.AppendLine($"<p class=\"liststart\">Simpler version{(variantCount == 1 ? "" : "s")}:</p><ul class=\"condlist\">");
                            foreach (ConditionData.BoolVersion b in condDoc.AllBools)
                            {
                                BoolConditionListItem(b, info.CondDocs[b.Name], baseDoc);
                            }
                            foreach (ConditionData.CompareVersion c in condDoc.AllCompares)
                            {
                                CompareConditionListItem(c, info.CondDocs[c.Name], baseDoc);
                            }
                            sb.AppendLine("</ul>");
                        }
                    });
                }
                BigSectionFooter();
            }

            // Enums
            // Name, all values. exclude bools tho
            BigSectionHeader("Enums");
            foreach (EMEDF.EnumDoc enumDoc in docs.DOC.Enums)
            {
                if (enumDoc.Name == "BOOL")
                {
                    continue;
                }

                string        name = enumDoc.DisplayName;
                List <string> tags = new List <string> {
                    "enum"
                };
                if (showUsages && !symbolUsages.ContainsKey(name))
                {
                    tags.Add("unused");
                }

                Section(name, "Enum", tags, true, () =>
                {
                    symbolUsages.TryGetValue(name, out Usages enumUsages);
                    if (showUsages)
                    {
                        SectionUsageDetails(enumUsages);
                    }
                    bool showDetails = !noDetailsEnums.Contains(name) && !enumDoc.DisplayValues.All(
                        e => symbolUsages.TryGetValue(e.Value, out Usages entryUsages) && Usages.Equals(entryUsages, enumUsages));
                    sb.AppendLine("<ul class=\"enumlist\">");
                    foreach (KeyValuePair <string, string> entry in enumDoc.DisplayValues)
                    {
                        string entryNum    = entry.Key;
                        string entryName   = entry.Value;
                        string unusedClass = showDetails && !symbolUsages.ContainsKey(entryName) ? " class=\"enumunused\"" : "";
                        sb.Append($"<li><code{unusedClass}>{entryName} = {entryNum}</code>");
                        if (showDetails && symbolUsages.TryGetValue(entryName, out Usages entryUsages))
                        {
                            sb.Append($" <span class=\"enumusage usageinfo\">Used in {entryUsages}</span>");
                        }
                        sb.AppendLine("</li>");
                    }
                    sb.AppendLine("</ul>");
                });
            }
            BigSectionFooter();

            PageFooter();
        }
Beispiel #8
0
        // Usages

        // Usages for display names of these symbols: instruction name, enum name, enum value
        private Dictionary <string, Usages> GetSymbolUsages(string game, string emevdDir, InstructionDocs docs)
        {
            Dictionary <string, HashSet <string> > symbolsByFile = new Dictionary <string, HashSet <string> >();
            HashSet <string> allSymbols = new HashSet <string>();

            interestingEmevds.TryGetValue(game, out Regex mainRegex);
            List <string> files = new List <string>();

            Console.WriteLine($"------ Usages from [{emevdDir}]");
            foreach (string emevdPath in Directory.GetFiles(emevdDir))
            {
                if (mainRegex != null && !mainRegex.Match(Path.GetFileName(emevdPath)).Success)
                {
                    continue;
                }
                string name = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(emevdPath));
                files.Add(name);
                Console.WriteLine($"--- {name}");
                HashSet <string> symbols = symbolsByFile[name] = new HashSet <string>();
                EMEVD            emevd   = EMEVD.Read(emevdPath);
                foreach (EMEVD.Event evt in emevd.Events)
                {
                    for (int insIndex = 0; insIndex < evt.Instructions.Count; insIndex++)
                    {
                        // This is all very best-effort
                        EMEVD.Instruction ins = evt.Instructions[insIndex];
                        EMEDF.InstrDoc    doc = docs.DOC[ins.Bank]?[ins.ID];
                        if (doc == null)
                        {
                            continue;
                        }
                        symbols.Add(doc.DisplayName);
                        Dictionary <EMEVD.Parameter, string> paramNames = docs.ParamNames(evt);
                        try
                        {
                            // A slight abuse of this function, ignoring the returned list
                            docs.UnpackArgsWithParams(ins, insIndex, doc, paramNames, (argDoc, val) =>
                            {
                                if (argDoc.GetDisplayValue(val) is string displayStr)
                                {
                                    symbols.Add(displayStr);
                                }
                                return(val);
                            });
                            // Also add a usage if the enum is present at all, even if parameterized
                            foreach (EMEDF.ArgDoc argDoc in doc.Arguments)
                            {
                                if (argDoc.EnumDoc != null && argDoc.EnumName != "BOOL")
                                {
                                    symbols.Add(argDoc.EnumDoc.DisplayName);
                                }
                            }
                        }
                        catch
                        {
                        }
                    }
                }
                allSymbols.UnionWith(symbols);
            }

            Dictionary <string, Usages> symbolUsages = new Dictionary <string, Usages>();
            List <string> primaryFiles = null;

            if (secondaryEmevd.TryGetValue(game, out Regex secondaryRegex))
            {
                primaryFiles = files.Where(f => !secondaryRegex.Match(f).Success).ToList();
            }
            foreach (string symbol in allSymbols)
            {
                List <string> matchFiles = files.Where(f => symbolsByFile[f].Contains(symbol)).ToList();
                List <string> target     = files;
                if (primaryFiles != null)
                {
                    List <string> primaryMatchFiles = matchFiles.Intersect(primaryFiles).ToList();
                    if (primaryMatchFiles.Count > 0)
                    {
                        matchFiles = primaryMatchFiles;
                        target     = primaryFiles;
                    }
                }
                // Combining PTDE and DS1R is done after.
                symbolUsages[symbol] = new Usages {
                    Files = matchFiles, AllFiles = target
                };
            }
            return(symbolUsages);
        }
Beispiel #9
0
 public override string ToString() => InstructionDocs.LayerString(Mask);
Beispiel #10
0
        private bool OpenEMEVDFile(
            string fileName,
            string gameDocs,
            EMEVD evd     = null,
            string jsText = null,
            bool isFancy  = false,
            Dictionary <string, string> extraFields = null)
        {
            // Can reuse docs if for the same game
            if (!AllDocs.TryGetValue(gameDocs, out InstructionDocs docs))
            {
                docs = AllDocs[gameDocs] = new InstructionDocs(gameDocs);
            }
            ScriptSettings settings = new ScriptSettings(docs, extraFields);
            EventScripter  scripter = new EventScripter(fileName, docs, evd);

            string fileVersion = ProgramVersion.VERSION;

            if (jsText == null)
            {
                try
                {
                    if (isFancy && docs.Translator != null)
                    {
                        jsText = new FancyEventScripter(scripter, docs, settings.CFGOptions).Unpack();
                    }
                    else
                    {
                        jsText = scripter.Unpack();
                    }
                }
                catch (Exception ex)
                {
                    // Also try to do it in compatibility mode, for emevd files which are no longer allowed, such as changing EMEDFs.
                    try
                    {
                        jsText = scripter.Unpack(compatibilityMode: true);
                    }
                    catch
                    {
                        // If this also fails, we only care about the original exception.
                    }
                    if (jsText == null)
                    {
                        MessageBox.Show(ex.Message);
                    }
                    else
                    {
                        StringBuilder sb = new StringBuilder();
                        sb.AppendLine(ex.Message);
                        sb.AppendLine("Proceed anyway? You will have to fix instruction arguments before resaving.");
                        DialogResult result = MessageBox.Show(sb.ToString(), "Error", MessageBoxButtons.YesNoCancel);
                        if (result != DialogResult.Yes)
                        {
                            jsText = null;
                        }
                    }
                    if (jsText == null)
                    {
                        return(false);
                    }
                }
            }
            else
            {
                fileVersion = extraFields != null && extraFields.TryGetValue("version", out string version) ? version : null;
            }
            if (Editor != null)
            {
                display.Panel2.Controls.Clear();
                SharedControls.RemoveEditor(Editor);
                Editor.Dispose();
            }
            Editor = new EditorGUI(SharedControls, scripter, docs, settings, fileVersion, jsText);
            SharedControls.AddEditor(Editor);
            SharedControls.RefreshGlobalStyles();
            display.Panel2.Controls.Add(Editor);
            // PerformLayout();
            Text = $"DARKSCRIPT 3 - {scripter.FileName}";
            // Notify about possible compatibility issues
            int versionCmp = ProgramVersion.CompareVersions(ProgramVersion.VERSION, fileVersion);

            if (versionCmp > 0)
            {
                SharedControls.SetStatus("Note: File was previously saved using an earlier version of DarkScript3");
            }
            else if (versionCmp < 0)
            {
                SharedControls.SetStatus("Note: File was previously saved using an newer version of DarkScript3. Please update!");
            }
            return(true);
        }
        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,
            });
        }
Beispiel #12
0
        public static void Run(string[] args)
        {
            string game  = args[0];
            string inDir = args[1];

            if (defaultGameDirs.TryGetValue(inDir, out string gameDir))
            {
                inDir = gameDir;
            }
            string outDir = args[2];
            // Fancy recompilation
            List <string>   emevdPaths = Directory.GetFiles(inDir, "*.emevd").Concat(Directory.GetFiles(inDir, "*.emevd.dcx")).ToList();
            InstructionDocs docs       = new InstructionDocs($"{game}-common.emedf.json");

            if (!Directory.Exists(outDir))
            {
                Directory.CreateDirectory(outDir);
            }
            Dictionary <string, StringBuilder> contents = new Dictionary <string, StringBuilder>();

            string recordText(string type, string name, Func <object> func)
            {
                if (!contents.TryGetValue(type, out StringBuilder sb))
                {
                    contents[type] = sb = new StringBuilder();
                }
                try
                {
                    object ret = func();
                    if (ret is string text)
                    {
                        sb.AppendLine($"/* ------------------- {name} ------------------- */");
                        sb.AppendLine(text);
                        return(text);
                    }
                    else
                    {
                        // Do nothing other than indicate success
                        return("");
                    }
                }
                catch (Exception e)
                {
                    sb.AppendLine($"/* ------------------- {name} ------------------- */");
                    sb.AppendLine($"/* {e} */");
                    Console.WriteLine(name + ": " + e + "\n");
                    return(null);
                }
            }

            foreach (string emevdPath in emevdPaths)
            {
                string name = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(emevdPath));
                if (game == "ds3" && name.StartsWith("m2"))
                {
                    continue;
                }
                Console.WriteLine("--------------------------" + name);
                EventScripter scripter = new EventScripter(emevdPath, docs);
                string        reg1     = recordText("reg1", name, () => scripter.Unpack());
                if (reg1 == null)
                {
                    continue;
                }
                if (args.Contains("reg"))
                {
                    if (recordText("reg2", name + "-compile", () => scripter.Pack(reg1, name)) != null)
                    {
                        recordText("reg2", name, () => scripter.Unpack());
                    }
                }
                if (args.Contains("fancy"))
                {
                    EventCFG.CFGOptions options = args.Contains("min") ? EventCFG.CFGOptions.GetMin() : EventCFG.CFGOptions.GetDefault();
                    scripter = new EventScripter(emevdPath, docs);
                    FancyEventScripter fes = new FancyEventScripter(scripter, docs, options);
                    if (args.Contains("unit"))
                    {
                        string testCases = Resource.Text("test.js");
                        fes.Pack(testCases);
                        Console.WriteLine(scripter.Unpack());
                        Console.WriteLine(fes.Repack(testCases));
                        return;
                    }
                    string fancy1 = recordText("fancy1", name, () => fes.Unpack());
                    if (fancy1 != null)
                    {
                        if (args.Contains("repack"))
                        {
                            recordText("fancy2", name, () => fes.Repack(fancy1));
                        }
                        else
                        {
                            if (recordText("reg3", name + "-compile", () => fes.Pack(fancy1, name)) != null)
                            {
                                recordText("reg3", name, () => scripter.Unpack());
                            }
                        }
                    }
                }
            }
            foreach (KeyValuePair <string, StringBuilder> entry in contents)
            {
                using (TextWriter writer = File.CreateText($@"{outDir}\{game}_{entry.Key}.js"))
                {
                    writer.Write(entry.Value);
                }
            }
        }
Beispiel #13
0
        private void Decompile(TextWriter writer)
        {
            EMEDF DOC = docs.DOC;
            InstructionTranslator info = docs.Translator;

            for (int i = 0; i < scripter.EVD.Events.Count; i++)
            {
                Event  evt          = scripter.EVD.Events[i];
                string id           = evt.ID.ToString();
                string restBehavior = evt.RestBehavior.ToString();

                Dictionary <Parameter, string> paramNames = docs.ParamNames(evt);
                List <string> argNameList = paramNames.Values.Distinct().ToList();
                Dictionary <Parameter, ParamArg> paramArgs = paramNames.ToDictionary(e => e.Key, e => new ParamArg {
                    Name = e.Value
                });

                EventFunction func = new EventFunction {
                    ID = (int)evt.ID, RestBehavior = evt.RestBehavior, Params = argNameList
                };

                string eventName = scripter.EventName(evt.ID);
                if (eventName != null)
                {
                    writer.WriteLine($"// {eventName}");
                }

                for (int insIndex = 0; insIndex < evt.Instructions.Count; insIndex++)
                {
                    Instruction    ins = evt.Instructions[insIndex];
                    EMEDF.InstrDoc doc = docs.DOC[ins.Bank]?[ins.ID];
                    if (doc == null)
                    {
                        throw new Exception($"Unknown instruction in event {id}: {ins.Bank}[{ins.ID}] {string.Join(" ", ins.ArgData.Select(b => $"{b:x2}"))}");
                    }
                    string funcName = doc.DisplayName;

                    IEnumerable <ArgType> argStruct = doc.Arguments.Select(arg => arg.Type == 8 ? ArgType.UInt32 : (ArgType)arg.Type);

                    Layers layers = ins.Layer is uint l ? new Layers {
                        Mask = l
                    } : null;
                    Instr instr = new Instr {
                        Inner = ins, Cmd = InstructionID(ins.Bank, ins.ID), Name = funcName, Layers = layers
                    };

                    try
                    {
                        instr.Args = docs.UnpackArgsWithParams(ins, insIndex, doc, paramArgs, (argDoc, val) =>
                        {
                            if (argDoc.GetDisplayValue(val) is string displayStr)
                            {
                                return(new DisplayArg {
                                    DisplayValue = displayStr, Value = val
                                });
                            }
                            return(val);
                        });
                    }
                    catch (Exception ex)
                    {
                        var sb = new StringBuilder();
                        sb.AppendLine($@"Unable to unpack arguments for {funcName} at Event {evt.ID}[{insIndex}]");
                        sb.AppendLine($@"Instruction arg data: {InstructionDocs.InstrDebugString(ins)}");
                        sb.AppendLine(ex.Message);
                        throw new Exception(sb.ToString());
                    }
                    func.Body.Add(instr);
                }
                EventCFG f = new EventCFG((int)evt.ID, options);
                try
                {
                    // This returns warnings, many of which exist in vanilla emevd.
                    // Ignored until we have a nice way to show them.
                    f.Decompile(func, info);
                }
                catch (FancyNotSupportedException)
                {
                    // For the moment, swallow this.
                    // Can find a way to expose the error, but these are basically intentional bail outs, also existing in vanilla emevd.
                    StringBuilder code = new StringBuilder();
                    scripter.UnpackEvent(evt, code);
                    writer.Write(code.ToString());
                    continue;
                }
                catch (Exception ex)
                {
                    var sb = new StringBuilder();
                    sb.AppendLine($@"Error decompiling Event {evt.ID}");
                    sb.AppendLine(ex.ToString());
                    throw new Exception(sb.ToString());
                }
                func.Print(writer);
                writer.WriteLine();
            }
        }