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; }
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; }
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>"); } } }
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"; } } }
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(); }
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()); } }
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(); }
// 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); }
public override string ToString() => InstructionDocs.LayerString(Mask);
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, }); }
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); } } }
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(); } }