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>"); }
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(); }
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(); } }