diff --git a/Source/Data/CodeNote.cs b/Source/Data/CodeNote.cs index 4fb36ef8..80c133e2 100644 --- a/Source/Data/CodeNote.cs +++ b/Source/Data/CodeNote.cs @@ -26,6 +26,11 @@ public CodeNote(uint address, string note) public string Summary { get; private set; } + public IEnumerable> Values + { + get { return _values ?? Enumerable.Empty>(); } + } + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private List> _values; private class PointerData @@ -499,46 +504,68 @@ private static TokenType NextToken(Tokenizer tokenizer, out Token token) private static string TrimSize(string line, bool keepPointer) { int endIndex = -1; - var startIndex = line.IndexOf('['); - if (startIndex != -1) - { - endIndex = line.IndexOf(']', startIndex); - } - else + do { - startIndex = line.IndexOf('('); + var startIndex = line.IndexOf('[', endIndex + 1); if (startIndex != -1) - endIndex = line.IndexOf(')'); - } + { + endIndex = line.IndexOf(']', startIndex); + } + else + { + startIndex = line.IndexOf('(', endIndex + 1); + if (startIndex != -1) + endIndex = line.IndexOf(')', startIndex); + else + endIndex = -1; + } - if (endIndex == -1) - return line; + if (endIndex == -1) + return line; - var tokenizer = Tokenizer.CreateTokenizer(line.ToLower(), startIndex, endIndex - startIndex); - Token token; - bool isPointer = false; - while (tokenizer.NextChar != '\0') - { - var tokenType = NextToken(tokenizer, out token); - if (tokenType == TokenType.Other) + var tokenizer = Tokenizer.CreateTokenizer(line.ToLower(), startIndex, endIndex - startIndex); + Token token; + bool isPointer = false; + bool foundSize = false; + while (tokenizer.NextChar != '\0') { - if (token.CompareTo("pointer", StringComparison.InvariantCultureIgnoreCase) == 0) - isPointer = true; + var tokenType = NextToken(tokenizer, out token); + if (tokenType == TokenType.Other) + { + if (token.CompareTo("pointer", StringComparison.InvariantCultureIgnoreCase) == 0) + { + isPointer = true; + } + else + { + foundSize = false; + break; + } + } else - return line; + { + foundSize = true; + } } - }; - - while (startIndex > 0 && Char.IsWhiteSpace(line[startIndex - 1])) - --startIndex; - while (endIndex < line.Length - 1 && Char.IsWhiteSpace(line[endIndex + 1])) - ++endIndex; - line = line.Remove(startIndex, endIndex - startIndex + 1); - if (isPointer && keepPointer) - line = "[pointer] " + line; + if (foundSize) + { + while (startIndex > 0 && Char.IsWhiteSpace(line[startIndex - 1])) + --startIndex; + while (endIndex < line.Length - 1 && Char.IsWhiteSpace(line[endIndex + 1])) + ++endIndex; + + var removeCount = endIndex - startIndex + 1; + line = line.Remove(startIndex, removeCount); + if (isPointer && keepPointer) + { + line = "[pointer] " + line; + removeCount -= 10; + } - return line; + endIndex -= removeCount; + } + } while (true); } private void ExtractValuesFromSummary() @@ -548,7 +575,11 @@ private void ExtractValuesFromSummary() var commaIndex = Summary.IndexOfAny(new[] { ',', ';' }); if (commaIndex == -1) + { + if (CheckValue(new Token(Summary, 0, Summary.Length))) + Summary = "Unlabelled"; return; + } var newSummary = Summary; Tokenizer tokenizer = null; @@ -609,7 +640,7 @@ private void ExtractValuesFromSummary() } } - Summary = newSummary; + Summary = String.IsNullOrEmpty(newSummary) ? "Unlabelled" : newSummary; } private static bool IsHexDigit(char c) @@ -662,7 +693,7 @@ private static bool IsValue(Token token) return false; } - private void CheckValue(Token clause) + private bool CheckValue(Token clause) { int prefixIndex = 0; while (prefixIndex < clause.Length && !Char.IsLetterOrDigit(clause[prefixIndex]) && clause[prefixIndex] != '[') @@ -711,10 +742,18 @@ private void CheckValue(Token clause) var right = clause.SubToken(separator + separatorLength).Trim(); if (IsValue(left)) + { AddValue(left, right); + return true; + } else if (IsValue(right)) + { AddValue(right, left); + return true; + } } + + return false; } private void AddValue(Token value, Token note) diff --git a/Source/Data/RichPresence.cs b/Source/Data/RichPresence.cs index f6068b1c..4f44d2dd 100644 --- a/Source/Data/RichPresence.cs +++ b/Source/Data/RichPresence.cs @@ -1,4 +1,7 @@ -using System.Diagnostics; +using Jamiras.Components; +using System; +using System.Collections.Generic; +using System.Diagnostics; namespace RATools.Data { @@ -39,9 +42,193 @@ public string Script value = value.Trim(); _script = value; + _macros = null; + _displayStrings = null; Description = string.Format("{0}/{1} characters", _script.Length, ScriptMaxLength); } } + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private string _script; + + public class MacroDefinition + { + public MacroDefinition(string name, ValueFormat formatType) + : this(name, formatType, null) + { + } + + public MacroDefinition(string name, ValueFormat formatType, Dictionary lookupEntries) + { + Name = name; + FormatType = formatType; + LookupEntries = lookupEntries; + } + + public string Name { get; private set; } + public ValueFormat FormatType { get; private set; } + public Dictionary LookupEntries { get; private set; } + + public override string ToString() + { + if (LookupEntries != null) + return String.Format("\"{0}\" ({1} Entries)", Name, LookupEntries.Count); + + return String.Format("\"{0}\" ({1})", Name, FormatType); + } + } + + public IEnumerable Macros + { + get + { + if (_macros == null) + Parse(); + + return _macros; + } + } + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private List _macros; + + [DebuggerDisplay("{Text}")] + public class DisplayString + { + public DisplayString(Trigger condition, string text, IEnumerable macros) + { + Condition = condition; + Text = text; + Macros = macros; + } + + public Trigger Condition { get; private set; } + public string Text { get; private set; } + + [DebuggerDisplay("@{Name,nq}({Value})")] + public class Macro + { + public Macro(string name, Value value) + { + Name = name; + Value = value; + } + public string Name { get; private set; } + public Value Value { get; private set; } + } + + public IEnumerable Macros { get; private set; } + } + + public IEnumerable DisplayStrings + { + get + { + if (_macros == null) + Parse(); + + return _displayStrings; + } + } + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private List _displayStrings; + + private enum Part + { + None, + Format, + Lookup, + Display, + } + + private void Parse() + { + _macros = new List(); + _displayStrings = new List(); + + string macroName = null; + Part part = Part.None; + Dictionary lookups = null; + + var tokenizer = Tokenizer.CreateTokenizer(_script); + while (tokenizer.NextChar != '\0') + { + var line = tokenizer.ReadTo('\n'); + tokenizer.Advance(); + if (line.StartsWith("//")) + continue; + + if (line.EndsWith("\r")) + line = line.SubToken(0, line.Length - 1); + + if (line.Length == 0) + continue; + + if (line.StartsWith("Format:")) + { + macroName = line.Substring(7); + part = Part.Format; + } + else if (line.StartsWith("FormatType=")) + { + if (part == Part.Format) + { + var formatType = Leaderboard.ParseFormat(line.Substring(11)); + _macros.Add(new MacroDefinition(macroName, formatType)); + } + part = Part.None; + } + else if (line.StartsWith("Lookup:")) + { + macroName = line.Substring(7); + lookups = new Dictionary(); + _macros.Add(new MacroDefinition(macroName, ValueFormat.None, lookups)); + part = Part.Lookup; + } + else if (line.StartsWith("Display:")) + { + part = Part.Display; + } + else if (part == Part.Lookup) + { + var index = line.IndexOf('='); + if (index > 0) + lookups.Add(line.Substring(0, index), line.Substring(index + 1)); + } + else if (part == Part.Display) + { + Trigger condition = null; + int index = 0; + + if (line.StartsWith("?")) + { + index = line.IndexOf('?', 1); + if (index != -1) + condition = Trigger.Deserialize(line.Substring(1, index - 1)); + + ++index; + } + + var macros = new List(); + var text = line.Substring(index); + while ((index = line.IndexOf('@', index)) != -1) + { + var leftParen = line.IndexOf('(', index + 1); + if (leftParen == -1) + break; + + var rightParen = line.IndexOf(')', leftParen + 1); + if (rightParen == -1) + break; + + macroName = line.Substring(index + 1, leftParen - index - 1); + var value = Value.Deserialize(line.Substring(leftParen + 1, rightParen - leftParen - 1)); + macros.Add(new DisplayString.Macro(macroName, value)); + + index = rightParen + 1; + } + + _displayStrings.Add(new DisplayString(condition, text, macros.ToArray())); + } + } + } } } diff --git a/Source/Data/Trigger.cs b/Source/Data/Trigger.cs index 833ebc8c..1a105eb1 100644 --- a/Source/Data/Trigger.cs +++ b/Source/Data/Trigger.cs @@ -48,6 +48,19 @@ public Trigger(IEnumerable core, IEnumerable public IEnumerable Alts { get; private set; } + + public IEnumerable Groups + { + get + { + if (Core != null) + yield return Core; + + foreach (var group in Alts) + yield return group; + } + } + public override bool Equals(object obj) { var that = obj as Trigger; diff --git a/Source/Parser/MemoryAccessorAlias.cs b/Source/Parser/MemoryAccessorAlias.cs new file mode 100644 index 00000000..fa7f8aca --- /dev/null +++ b/Source/Parser/MemoryAccessorAlias.cs @@ -0,0 +1,535 @@ +using RATools.Data; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace RATools.Parser +{ + [DebuggerDisplay("{Address,h}: {Alias}")] + public class MemoryAccessorAlias : IComparer + { + public MemoryAccessorAlias(uint address) + { + Address = address; + } + + public MemoryAccessorAlias(uint address, CodeNote note) + : this(address) + { + SetNote(note); + } + + private void SetNote(CodeNote note) + { + Note = note; + + if (note != null) + { + PrimarySize = note.Size; + + // if note doesn't specify a size, assume 8-bit + if (PrimarySize == FieldSize.None) + PrimarySize = FieldSize.Byte; + } + } + + public CodeNote Note { get; private set; } + private string _aliasFromNote = String.Empty; + private string _subtextFromNote = null; + + public uint Address { get; private set; } + + public string Alias + { + get { return _alias ?? _aliasFromNote; } + set + { + if (value == _aliasFromNote) + _alias = null; + else + _alias = value; + } + } + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private string _alias; + private NameStyle _aliasStyle = NameStyle.None; + private Dictionary _aliases; + + public IEnumerable Children + { + get + { + return _children ?? Enumerable.Empty(); + } + } + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private List _children; + + [Flags] + private enum ReferencedSizeMask + { + None = 0, + Bit0 = 1 << FieldSize.Bit0, + Bit1 = 1 << FieldSize.Bit1, + Bit2 = 1 << FieldSize.Bit2, + Bit3 = 1 << FieldSize.Bit3, + Bit4 = 1 << FieldSize.Bit4, + Bit5 = 1 << FieldSize.Bit5, + Bit6 = 1 << FieldSize.Bit6, + Bit7 = 1 << FieldSize.Bit7, + LowNibble = 1 << FieldSize.LowNibble, + HighNibble = 1 << FieldSize.HighNibble, + Byte = 1 << FieldSize.Byte, + Word = 1 << FieldSize.Word, + TByte = 1 << FieldSize.TByte, + DWord = 1 << FieldSize.DWord, + BitCount = 1 << FieldSize.BitCount, + BigEndianWord = 1 << FieldSize.BigEndianWord, + BigEndianTByte = 1 << FieldSize.BigEndianTByte, + BigEndianDWord = 1 << FieldSize.BigEndianDWord, + Float = 1 << FieldSize.Float, + MBF32 = 1 << FieldSize.MBF32, + LittleEndianMBF32 = 1 << FieldSize.LittleEndianMBF32, + BigEndianFloat = 1 << FieldSize.BigEndianFloat, + Double32 = 1 << FieldSize.Double32, + BigEndianDouble32 = 1 << FieldSize.BigEndianDouble32, + } + + private ReferencedSizeMask _referencedSizes = ReferencedSizeMask.None; + + public FieldSize PrimarySize { get; private set; } + + public void ReferenceSize(FieldSize size) + { + _referencedSizes |= (ReferencedSizeMask)(1 << (int)size); + + if (PrimarySize == FieldSize.None) + { + if (Field.GetMaxValue(size) < 255) + PrimarySize = FieldSize.Byte; + else + PrimarySize = size; + } + } + + public IEnumerable ReferencedSizes + { + get + { + var referencedSizes = (int)_referencedSizes >> 1; + var size = 1; + while (referencedSizes != 0) + { + if ((referencedSizes & 1) == 1) + yield return (FieldSize)size; + + ++size; + referencedSizes >>= 1; + } + } + } + + public bool HasMultipleReferencedSizes + { + get + { + // masking with n-1 removes the rightmost non-zero bit. + // if any bits remain, there are multiple sizes. + var n = (int)_referencedSizes; + return (n & (n - 1)) != 0; + } + } + + public bool HasReferencedSize(FieldSize size) + { + return ((int)_referencedSizes & (1 << (int)size)) != 0; + } + + public bool IsOnlyReferencedSize(FieldSize size) + { + return ((int)_referencedSizes ^ (1 << (int)size)) == 0; + } + + public string GetAlias(FieldSize size) + { + if (!HasReferencedSize(size)) + return null; + + if (size == PrimarySize) + return Alias; + + _aliases ??= new Dictionary(); + + string alias; + if (!_aliases.TryGetValue(size, out alias)) + { + alias = Alias; + + var subNote = Note?.GetSubNote(size); + if (subNote == null) + { + // if size is the only referenced size, make it the primary size. + if (IsOnlyReferencedSize(size)) + { + PrimarySize = size; + return alias; + } + + // multiple sizes are referenced. we can't generate a unique name without a note alias + if (String.IsNullOrEmpty(alias)) + { + _aliases[size] = null; + return null; + } + + // if the primary size isn't referenced (note didn't specify size or all + // sizes are smaller than a byte), and the size is byte or larger, make + // it the primary size. + if (!HasReferencedSize(PrimarySize) && Field.GetMaxValue(size) >= 255) + { + PrimarySize = size; + return alias; + } + + // append the size to the note alias to differentiate it from the primary size accessor + subNote = Field.GetSizeFunction(size); + } + + if (String.IsNullOrEmpty(alias)) + { + alias = _aliasStyle.BuildName(subNote); + } + else + { + var suffix = _aliasStyle.BuildName("x " + subNote); + alias = String.Concat(Alias, suffix.AsSpan(1)); + } + + _aliases[size] = alias; + } + + return alias; + } + + public void UpdateAliasFromNote(NameStyle style) + { + _aliases = null; + _aliasStyle = style; + + if (style == NameStyle.None || Note == null) + { + _aliasFromNote = String.Empty; + + if (_children != null) + { + foreach (var child in _children) + child.UpdateAliasFromNote(style); + } + + return; + } + + var text = Note.Summary; + + if (text == "Unlabelled") + { + text = null; + + var enumerator = Note.Values.GetEnumerator(); + if (enumerator.MoveNext()) + { + var firstValue = enumerator.Current.Value; + if (!enumerator.MoveNext()) + text = firstValue.ToString(); + } + } + + if (!HasReferencedSize(PrimarySize) && !HasMultipleReferencedSizes && _referencedSizes != ReferencedSizeMask.None) + { + PrimarySize = ReferencedSizes.First(); + + var subNote = Note.GetSubNote(PrimarySize); + if (subNote != null && subNote != text) + { + if (String.IsNullOrEmpty(text)) + text = subNote; + else + text = text + " - " + subNote; + } + } + + if (!String.IsNullOrEmpty(text)) + { + // remove subtext: "score (bcd)" => "score" + int leftParen = -1; + if (text[text.Length - 1] == ')') + leftParen = text.LastIndexOf('('); + else if (text[text.Length - 1] == ']') + leftParen = text.LastIndexOf('['); + if (leftParen > 4) + { + var subtext = text.Substring(leftParen + 1, text.Length - leftParen - 2); + if (!KeepSubtext(subtext)) + { + _subtextFromNote = subtext; + text = text.Substring(0, leftParen).TrimEnd(); + } + } + + // if string starts with numbers, potentially treat it as subtext + if (_subtextFromNote == null && text.Length > 1 && Char.IsDigit(text[0])) + { + int index = 1; + while (index < text.Length && !Char.IsLetter(text[index])) + ++index; + + if (text.Length - index >= 4 && !Char.IsDigit(text[index - 1])) + { + _subtextFromNote = text.Substring(0, index).Trim(); + text = text.Substring(index); + } + } + } + + // build the function name + var functionName = style.BuildName(text); + if (!String.IsNullOrEmpty(functionName)) + { + if (AchievementScriptInterpreter.IsReservedFunctionName(functionName)) + functionName += '_'; + + _aliasFromNote = functionName.ToString(); + } + + if (_children != null) + { + foreach (var child in _children) + child.UpdateAliasFromNote(style); + } + } + + private static bool KeepSubtext(string text) + { + // keep regional indicators as part of note. [purposefully requires uppercase] + switch (text.Length) + { + case 1: + return (text == "E" || text == "U" || text == "J"); + case 2: + return (text == "EU" || text == "US" || text == "JP"); + default: + return false; + } + } + + public static void ResolveConflictingAliases(IEnumerable memoryAccessors) + { + var aliasMap = new Dictionary>(); + foreach (var memoryAccessor in memoryAccessors) + CaptureAliases(aliasMap, memoryAccessor); + + // have to put new keys in separate dictionary to avoid modified iterator error + var newAliases = new Dictionary>(); + foreach (var kvp in aliasMap) + { + if (kvp.Value.Count == 1) + continue; + + for (int i = kvp.Value.Count - 1; i >= 0; i--) + { + var memoryAccessor = kvp.Value[i]; + if (!String.IsNullOrEmpty(memoryAccessor._subtextFromNote)) + { + var suffix = memoryAccessor._aliasStyle.BuildName("x " + memoryAccessor._subtextFromNote); + var newAlias = String.Concat(memoryAccessor._aliasFromNote, suffix.AsSpan(1)); + memoryAccessor._aliasFromNote = newAlias; + + kvp.Value.RemoveAt(i); + + List list; + if (!aliasMap.TryGetValue(newAlias, out list) && + !newAliases.TryGetValue(newAlias, out list)) + { + list = new List(); + newAliases[newAlias] = list; + } + + list.Add(memoryAccessor); + } + } + } + + foreach (var kvp in newAliases) + { + if (kvp.Value.Count > 1) + aliasMap[kvp.Key] = kvp.Value; + } + + foreach (var kvp in aliasMap) + { + if (kvp.Value.Count < 2) + continue; + + kvp.Value.Sort(new MemoryAccessorAlias(0)); + + int suffix = 1; + foreach (var memoryAccessor in kvp.Value) + { + if (suffix > 1) + memoryAccessor.Alias = memoryAccessor.Alias + '_' + suffix; + + suffix++; + } + } + } + + private static void CaptureAliases(Dictionary> aliasMap, MemoryAccessorAlias memoryAccessor) + { + var alias = memoryAccessor.Alias; + if (!String.IsNullOrEmpty(alias)) + { + List list; + if (!aliasMap.TryGetValue(alias, out list)) + { + list = new List(); + list.Add(memoryAccessor); + aliasMap.Add(alias, list); + } + else + { + list.Add(memoryAccessor); + } + } + + foreach (var child in memoryAccessor.Children) + CaptureAliases(aliasMap, child); + } + + + public static void AddMemoryAccessors(List memoryAccessors, Achievement achievement, Dictionary codeNotes) + { + MemoryAccessorAlias root = new MemoryAccessorAlias(0); + root._children = memoryAccessors; + + AddMemoryAccessors(root, achievement.Trigger.Groups, codeNotes); + } + + public static void AddMemoryAccessors(List memoryAccessors, Leaderboard leaderboard, Dictionary codeNotes) + { + MemoryAccessorAlias root = new MemoryAccessorAlias(0); + root._children = memoryAccessors; + + AddMemoryAccessors(root, leaderboard.Start.Groups, codeNotes); + AddMemoryAccessors(root, leaderboard.Cancel.Groups, codeNotes); + AddMemoryAccessors(root, leaderboard.Submit.Groups, codeNotes); + AddMemoryAccessors(root, leaderboard.Value.Values, codeNotes); + } + + public static void AddMemoryAccessors(List memoryAccessors, RichPresence richPresence, Dictionary codeNotes) + { + MemoryAccessorAlias root = new MemoryAccessorAlias(0); + root._children = memoryAccessors; + + foreach (var displayString in richPresence.DisplayStrings) + { + if (displayString.Condition != null) + AddMemoryAccessors(root, displayString.Condition.Groups, codeNotes); + + foreach (var macro in displayString.Macros) + AddMemoryAccessors(root, macro.Value.Values, codeNotes); + } + } + + public static void AddMemoryAccessors(List memoryAccessors, Trigger trigger, Dictionary codeNotes) + { + MemoryAccessorAlias root = new MemoryAccessorAlias(0); + root._children = memoryAccessors; + + AddMemoryAccessors(root, trigger.Groups, codeNotes); + } + + public static void AddMemoryAccessors(List memoryAccessors, Value value, Dictionary codeNotes) + { + MemoryAccessorAlias root = new MemoryAccessorAlias(0); + root._children = memoryAccessors; + + AddMemoryAccessors(root, value.Values, codeNotes); + } + + private static void AddMemoryAccessors(MemoryAccessorAlias root, IEnumerable groups, Dictionary codeNotes) + { + foreach (var group in groups) + { + CodeNote parentNote = null; + MemoryAccessorAlias parentMemoryAccessor = root; + + foreach (var requirement in group.Requirements) + { + MemoryAccessorAlias leftMemoryAccessor = requirement.Left.IsMemoryReference ? + GetMemoryAccessor(parentMemoryAccessor, requirement.Left, codeNotes, parentNote) : null; + MemoryAccessorAlias rightMemoryAccessor = requirement.Right.IsMemoryReference ? + GetMemoryAccessor(parentMemoryAccessor, requirement.Right, codeNotes, parentNote) : null; + + if (requirement.Type != RequirementType.AddAddress) + { + // not an AddAddress. reset to root + parentMemoryAccessor = root; + parentNote = null; + } + else if (requirement.Operator.IsModifier() && requirement.Operator != RequirementOperator.BitwiseAnd) + { + // scaled value - assume indexing - keep current parent + } + else if (leftMemoryAccessor != null) + { + // chaining off left accessor + parentMemoryAccessor = leftMemoryAccessor; + parentNote = leftMemoryAccessor.Note; + } + else if (rightMemoryAccessor != null) + { + // chaining off right accessor + parentMemoryAccessor = rightMemoryAccessor; + parentNote = rightMemoryAccessor.Note; + } + } + } + } + + int IComparer.Compare(MemoryAccessorAlias x, MemoryAccessorAlias y) + { + return (int)x.Address - (int)y.Address; + } + + private static MemoryAccessorAlias GetMemoryAccessor(MemoryAccessorAlias parentMemoryAccessor, + Field field, Dictionary codeNotes, CodeNote parentNote) + { + parentMemoryAccessor._children ??= new List(); + + var memoryAccessor = new MemoryAccessorAlias(field.Value); + var index = parentMemoryAccessor._children.BinarySearch(memoryAccessor, memoryAccessor); + + if (index < 0) + { + CodeNote note = null; + if (parentNote == null) + codeNotes.TryGetValue(field.Value, out note); + else + note = parentNote.OffsetNotes.FirstOrDefault(n => n.Address == field.Value); + + if (note != null) + memoryAccessor.SetNote(note); + + parentMemoryAccessor._children.Insert(~index, memoryAccessor); + } + else + { + memoryAccessor = parentMemoryAccessor._children[index]; + } + + memoryAccessor.ReferenceSize(field.Size); + return memoryAccessor; + } + } +} diff --git a/Source/Parser/NameStyle.cs b/Source/Parser/NameStyle.cs new file mode 100644 index 00000000..d30d9fc1 --- /dev/null +++ b/Source/Parser/NameStyle.cs @@ -0,0 +1,86 @@ +using System; +using System.Text; + +namespace RATools.Parser +{ + public enum NameStyle + { + None = 0, + SnakeCase, // lower_with_underscore + PascalCase, // UpperEachFirst + CamelCase, // upperInMiddle + } + + public static class NameStyleExtension + { + public static string BuildName(this NameStyle style, string fromText) + { + if (fromText == null) + return null; + + if (fromText.Length == 0) + return String.Empty; + + var name = new StringBuilder(); + var valid = false; + var newWord = true; + + foreach (var c in fromText) + { + if (!Char.IsLetterOrDigit(c)) + { + // allow dashes and apostrophes as mid-word characters (don't -> dont) + if (c != '-' && c != '\'') + newWord = true; + else if (!newWord && name.Length > 0 && Char.IsDigit(name[name.Length - 1])) + newWord = true; + + continue; + } + + if (newWord) + { + newWord = false; + + if (!valid && Char.IsDigit(c)) + name.Append('_'); + + valid = true; + + switch (style) + { + case NameStyle.PascalCase: + if (Char.IsDigit(c) && Char.IsDigit(name[name.Length - 1])) + name.Append('_'); + + name.Append(Char.ToUpper(c)); + continue; + + case NameStyle.CamelCase: + if (name.Length != 0) + { + if (Char.IsDigit(c) && Char.IsDigit(name[name.Length - 1])) + name.Append('_'); + + name.Append(Char.ToUpper(c)); + continue; + } + break; + + case NameStyle.SnakeCase: + if (name.Length != 0 && name[name.Length - 1] != '_') + name.Append('_'); + break; + + default: + break; + } + } + + name.Append(Char.ToLower(c)); + } + + return valid ? name.ToString() : string.Empty; + } + } +} diff --git a/Source/Parser/PublishedAssets.cs b/Source/Parser/PublishedAssets.cs index bb01e689..bf1439a6 100644 --- a/Source/Parser/PublishedAssets.cs +++ b/Source/Parser/PublishedAssets.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; namespace RATools.Parser { @@ -31,6 +32,7 @@ public PublishedAssets(string filename, IFileSystemService fileSystemService) _achievements = new List(); _leaderboards = new List(); RichPresence = null; + Notes = new Dictionary(); _filename = filename; @@ -40,8 +42,14 @@ public PublishedAssets(string filename, IFileSystemService fileSystemService) private readonly IFileSystemService _fileSystemService; private readonly string _filename; + /// + /// Gets the unique identifier of the game. + /// public int GameId { get; private set; } + /// + /// Gets the unique identifier of the console associated to the game. + /// public int ConsoleId { get; private set; } /// @@ -49,6 +57,9 @@ public PublishedAssets(string filename, IFileSystemService fileSystemService) /// public string Title { get; set; } + /// + /// Gets the full path to the JSON file for the game's asset data. + /// public string Filename { get { return _filename; } } /// @@ -81,6 +92,9 @@ public IEnumerable Leaderboards [DebuggerBrowsable(DebuggerBrowsableState.Never)] private List _leaderboards; + /// + /// Gets the Rich Presence read from the file. + /// public RichPresence RichPresence { get; private set; } private readonly DateTime _unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); @@ -128,7 +142,7 @@ private void Read() var publishedRichPresence = publishedData.GetField("RichPresencePatch"); if (publishedRichPresence.Type == JsonFieldType.String) - { + { RichPresence = new RichPresence { Script = publishedRichPresence.StringValue, @@ -251,5 +265,56 @@ private void ReadLeaderboards(JsonField publishedLeaderboards, int gameId, int s _leaderboards.Add(leaderboard); } } + + public Dictionary Notes { get; private set; } + + public void LoadNotes() + { + Notes.Clear(); + + var filename = _filename.Replace(".json", "-Notes.json"); + using (var notesStream = _fileSystemService.OpenFile(filename, OpenFileMode.Read)) + { + if (notesStream != null) + { + var notes = new JsonObject(notesStream).GetField("items"); + if (notes.Type == JsonFieldType.ObjectArray) + { + foreach (var note in notes.ObjectArrayValue) + { + var address = UInt32.Parse(note.GetField("Address").StringValue.Substring(2), System.Globalization.NumberStyles.HexNumber); + var text = note.GetField("Note").StringValue; + if (text.Length > 0 && text != "''") // a long time ago notes were "deleted" by setting their text to '' + Notes[address] = new CodeNote(address, text); + } + } + } + } + } + + private List _memoryAccessors; + + public List GetMemoryAccessors() + { + if (_memoryAccessors != null) + return _memoryAccessors; + + if (Notes.Count == 0) + LoadNotes(); + + var memoryAccessors = new List(); + + foreach (var achievement in Achievements) + MemoryAccessorAlias.AddMemoryAccessors(memoryAccessors, achievement, Notes); + + foreach (var leaderboard in Leaderboards) + MemoryAccessorAlias.AddMemoryAccessors(memoryAccessors, leaderboard, Notes); + + if (RichPresence != null) + MemoryAccessorAlias.AddMemoryAccessors(memoryAccessors, RichPresence, Notes); + + _memoryAccessors = memoryAccessors; + return memoryAccessors; + } } } diff --git a/Source/ViewModels/GameViewModel.cs b/Source/ViewModels/GameViewModel.cs index 001cccc9..d9ba0f38 100644 --- a/Source/ViewModels/GameViewModel.cs +++ b/Source/ViewModels/GameViewModel.cs @@ -592,7 +592,6 @@ public void AssociateRACacheDirectory(string raCacheDirectory) { RACacheDirectory = raCacheDirectory; - ReadCodeNotes(); ReadPublished(); var fileName = Path.Combine(RACacheDirectory, GameId + "-User.txt"); @@ -605,30 +604,6 @@ public void AssociateRACacheDirectory(string raCacheDirectory) Notes[kvp.Key] = new CodeNote(kvp.Key, kvp.Value); } - private void ReadCodeNotes() - { - var filename = Path.Combine(RACacheDirectory, GameId + "-Notes.json"); - using (var notesStream = _fileSystemService.OpenFile(filename, OpenFileMode.Read)) - { - if (notesStream != null) - { - var notes = new JsonObject(notesStream).GetField("items"); - if (notes.Type == JsonFieldType.ObjectArray) - { - foreach (var note in notes.ObjectArrayValue) - { - var address = UInt32.Parse(note.GetField("Address").StringValue.Substring(2), System.Globalization.NumberStyles.HexNumber); - var text = note.GetField("Note").StringValue; - if (text.Length > 0 && text != "''") // a long time ago notes were "deleted" by setting their text to '' - Notes[address] = new CodeNote(address, text); - } - } - } - } - - _logger.WriteVerbose("Read " + Notes.Count + " code notes"); - } - private void ReadPublished() { _publishedAchievements.Clear(); @@ -671,6 +646,10 @@ private void ReadPublished() _logger.WriteVerbose(String.Format("Identified {0} core achievements ({1} points)", coreCount, corePoints)); _logger.WriteVerbose(String.Format("Identified {0} unofficial achievements ({1} points)", unofficialCount, unofficialPoints)); + + publishedAssets.LoadNotes(); + Notes = publishedAssets.Notes; + _logger.WriteVerbose("Read " + Notes.Count + " code notes"); } private void MergeAchievements(List editors, IEnumerable assets, diff --git a/Source/ViewModels/NewScriptDialogViewModel.cs b/Source/ViewModels/NewScriptDialogViewModel.cs index cee78775..10088212 100644 --- a/Source/ViewModels/NewScriptDialogViewModel.cs +++ b/Source/ViewModels/NewScriptDialogViewModel.cs @@ -26,18 +26,16 @@ public class NewScriptDialogViewModel : DialogViewModelBase public NewScriptDialogViewModel() : this(ServiceRepository.Instance.FindService(), ServiceRepository.Instance.FindService(), - ServiceRepository.Instance.FindService().GetLogger("RATools"), ServiceRepository.Instance.FindService()) { } internal NewScriptDialogViewModel(ISettings settings, IDialogService dialogService, - ILogger logger, IFileSystemService fileSystemService) + IFileSystemService fileSystemService) : base(dialogService) { _settings = settings; - _logger = logger; _fileSystemService = fileSystemService; DialogTitle = "New Script"; @@ -55,7 +53,6 @@ internal NewScriptDialogViewModel(ISettings settings, IDialogService dialogServi _assets = new ObservableCollection(); _memoryItems = new List(); _ticketNotes = new Dictionary(); - _macros = new List(); _achievementSetVariables = new Dictionary(); CodeNoteFilters = new[] @@ -71,12 +68,12 @@ internal NewScriptDialogViewModel(ISettings settings, IDialogService dialogServi new NoteDumpLookupItem(NoteDump.OnlyForDefinedMethods, "Only for Functions"), }; - FunctionNameStyles = new[] + NameStyles = new[] { - new FunctionNameStyleLookupItem(FunctionNameStyle.None, "None"), - new FunctionNameStyleLookupItem(FunctionNameStyle.SnakeCase, "snake_case"), - new FunctionNameStyleLookupItem(FunctionNameStyle.CamelCase, "camelCase"), - new FunctionNameStyleLookupItem(FunctionNameStyle.PascalCase, "PascalCase"), + new NameStyleLookupItem(NameStyle.None, "None"), + new NameStyleLookupItem(NameStyle.SnakeCase, "snake_case"), + new NameStyleLookupItem(NameStyle.CamelCase, "camelCase"), + new NameStyleLookupItem(NameStyle.PascalCase, "PascalCase"), }; MemoryAddresses = new GridViewModel(); @@ -88,7 +85,6 @@ internal NewScriptDialogViewModel(ISettings settings, IDialogService dialogServi private readonly ISettings _settings; private readonly IFileSystemService _fileSystemService; - private readonly ILogger _logger; public IntegerFieldViewModel GameId { get; private set; } @@ -115,13 +111,8 @@ private void Search() { LoadGame(gameId, dataDirectory); - var richPresenceFile = Path.Combine(dataDirectory, gameId + "-Rich.txt"); - if (File.Exists(richPresenceFile)) - LoadRichPresence(richPresenceFile); - - var userFile = Path.Combine(dataDirectory, gameId + "-User.txt"); - if (File.Exists(userFile)) - LoadUserFile(userFile); + LoadMemoryItems(); + UpdateMemoryGrid(); return; } @@ -134,25 +125,28 @@ private void Search() private void LoadGame(int gameId, string raCacheDirectory) { - _game = new GameViewModel(gameId, "", _logger, _fileSystemService); - _game.AssociateRACacheDirectory(raCacheDirectory); - _game.PopulateEditorList(null); - DialogTitle = "New Script - " + _game.Title; + var filename = Path.Combine(raCacheDirectory, gameId + ".json"); + _publishedAssets = new PublishedAssets(filename, _fileSystemService); + DialogTitle = "New Script - " + _publishedAssets.Title; _assets.Clear(); _ticketNotes.Clear(); _memoryItems.Clear(); MemoryAddresses.Rows.Clear(); + _memoryAccessors = _publishedAssets.GetMemoryAccessors(); + + var userFile = Path.Combine(raCacheDirectory, gameId + "-User.txt"); + _localAssets = new LocalAssets(userFile, _fileSystemService); + LoadAchievements(); LoadLeaderboards(); + LoadRichPresence(); LoadNotes(); if (_assets.Count == 0) SelectedCodeNotesFilter = CodeNoteFilter.All; - UpdateMemoryGrid(); - IsGameLoaded = true; GameId.IsEnabled = false; @@ -160,79 +154,16 @@ private void LoadGame(int gameId, string raCacheDirectory) ServiceRepository.Instance.FindService().RunAsync(MergeOpenTickets); } - private void LoadUserFile(string userFile) - { - var assets = new LocalAssets(userFile, _fileSystemService); - - if (assets.Achievements.Any()) - { - var achievementViewModel = new AchievementViewModel(null); - foreach (var achievement in assets.Achievements) - { - var dumpAchievement = new DumpAsset(achievement.Id, achievement.Title) - { - Type = DumpAssetType.Achievement, - ViewerImage = achievementViewModel.ViewerImage, - ViewerType = "Local Achievement", - IsUnofficial = true - }; - - achievementViewModel.Local.Asset = achievement; - AddMemoryReferences(achievementViewModel.Local, dumpAchievement); - - dumpAchievement.PropertyChanged += DumpAsset_PropertyChanged; - _assets.Add(dumpAchievement); - } - } - - if (assets.Leaderboards.Any()) - { - var leaderboardViewModel = new LeaderboardViewModel(null); - foreach (var leaderboard in assets.Leaderboards) - { - var dumpLeaderboard = new DumpAsset(leaderboard.Id, leaderboard.Title) - { - Type = DumpAssetType.Leaderboard, - ViewerImage = leaderboardViewModel.ViewerImage, - ViewerType = "Local Leaderboard", - IsUnofficial = true - }; - - leaderboardViewModel.Local.Asset = leaderboard; - AddMemoryReferences(leaderboardViewModel.Local, dumpLeaderboard); - - dumpLeaderboard.PropertyChanged += DumpAsset_PropertyChanged; - _assets.Add(dumpLeaderboard); - } - } - } - - private void AddMemoryReferences(AssetSourceViewModel asset, DumpAsset dumpAsset) - { - foreach (var trigger in asset.TriggerList) - { - foreach (var group in trigger.Groups) - { - AddMemoryReferences(dumpAsset, - group.Requirements.Where(r => r.Requirement != null).Select(r => r.Requirement)); - } - } - } - private void LoadAchievements() { var unofficialAchievements = new List(); - foreach (var achievement in _game.Editors.OfType()) + foreach (var publishedAchievement in _publishedAssets.Achievements) { - var publishedAchievement = achievement.Published.Asset as Achievement; - if (publishedAchievement == null) - continue; - - var dumpAchievement = new DumpAsset(achievement.Id, publishedAchievement.Title) + var dumpAchievement = new DumpAsset(publishedAchievement.Id, publishedAchievement.Title) { Type = DumpAssetType.Achievement, - ViewerImage = achievement.ViewerImage, - ViewerType = achievement.ViewerType + ViewerImage = "/RATools;component/Resources/achievement.png", + ViewerType = "Achievement", }; if (publishedAchievement.IsUnofficial) @@ -246,44 +177,90 @@ private void LoadAchievements() _assets.Add(dumpAchievement); } - AddMemoryReferences(achievement.Published, dumpAchievement); - dumpAchievement.IsSelected = true; dumpAchievement.PropertyChanged += DumpAsset_PropertyChanged; } foreach (var unofficialAchievement in unofficialAchievements) _assets.Add(unofficialAchievement); + + foreach (var localAchievement in _localAssets.Achievements) + { + var dumpAchievement = _assets.FirstOrDefault(a => a.Id == localAchievement.Id && a.Type == DumpAssetType.Achievement); + if (dumpAchievement != null) + { + dumpAchievement.IsUnofficial = true; + dumpAchievement.ViewerType = "Local Achievement"; + } + else + { + dumpAchievement = new DumpAsset(localAchievement.Id, localAchievement.Title) + { + Type = DumpAssetType.Achievement, + ViewerImage = "/RATools;component/Resources/achievement.png", + ViewerType = "Local Achievement", + IsUnofficial = true + }; + + dumpAchievement.PropertyChanged += DumpAsset_PropertyChanged; + _assets.Add(dumpAchievement); + } + } } private void LoadLeaderboards() { - foreach (var leaderboard in _game.Editors.OfType()) + foreach (var publishedLeaderboard in _publishedAssets.Leaderboards) { - var publishedLeaderboard = leaderboard.Published.Asset as Leaderboard; - if (publishedLeaderboard == null) - continue; - - var dumpLeaderboard = new DumpAsset(leaderboard.Id, publishedLeaderboard.Title) + var dumpLeaderboard = new DumpAsset(publishedLeaderboard.Id, publishedLeaderboard.Title) { Type = DumpAssetType.Leaderboard, - ViewerImage = leaderboard.ViewerImage, - ViewerType = leaderboard.ViewerType + ViewerImage = "/RATools;component/Resources/leaderboard.png", + ViewerType = "Leaderboard", }; _assets.Add(dumpLeaderboard); - AddMemoryReferences(leaderboard.Published, dumpLeaderboard); - dumpLeaderboard.IsSelected = true; dumpLeaderboard.PropertyChanged += DumpAsset_PropertyChanged; } + + foreach (var localLeaderboard in _localAssets.Leaderboards) + { + var dumpLeaderboard = _assets.FirstOrDefault(a => a.Id == localLeaderboard.Id && a.Type == DumpAssetType.Leaderboard); + if (dumpLeaderboard != null) + { + dumpLeaderboard.IsUnofficial = true; + dumpLeaderboard.ViewerType = "Local Leaderboard"; + } + else + { + dumpLeaderboard = new DumpAsset(localLeaderboard.Id, localLeaderboard.Title) + { + Type = DumpAssetType.Achievement, + ViewerImage = "/RATools;component/Resources/leaderboard.png", + ViewerType = "Local Leaderboard", + IsUnofficial = true + }; + + dumpLeaderboard.PropertyChanged += DumpAsset_PropertyChanged; + _assets.Add(dumpLeaderboard); + } + } } private void LoadNotes() { - foreach (var note in _game.Notes.Values) + _publishedAssets.LoadNotes(); + + foreach (var note in _publishedAssets.Notes.Values) { + var memoryAccessor = new MemoryAccessorAlias(note.Address, note); + var index = _memoryAccessors.BinarySearch(memoryAccessor, memoryAccessor); + + if (index >= 0) + continue; + var size = note.Size; switch (size) { @@ -294,77 +271,138 @@ private void LoadNotes() break; } - AddMemoryAddress(new Field { Size = size, Type = FieldType.MemoryAddress, Value = note.Address }, null); + memoryAccessor.ReferenceSize(size); + _memoryAccessors.Insert(~index, memoryAccessor); } } - - private class RichPresenceMacro + private static int CompareMemoryItems(MemoryItem left, MemoryItem right) { - public string Name; - public ValueFormat FormatType; - public Dictionary LookupEntries; - public List DisplayLines; + var diff = (int)(left.Address - right.Address); + if (diff == 0) + { + if (left.IsPrimarySize) + return right.IsPrimarySize ? 0 : -1; + else if (right.IsPrimarySize) + return 1; + + if (diff == 0) + diff = (int)left.Size - (int)right.Size; + } + return diff; } - private List _macros; - private void LoadRichPresence(string richPresenceFile) + private void LoadMemoryItems() { - RichPresenceMacro currentMacro = null; - RichPresenceMacro displayMacro = null; + LoadMemoryItems(_memoryItems, _memoryAccessors, SelectedNameStyle); + + if (SelectedNameStyle != NameStyle.None) + MemoryAccessorAlias.ResolveConflictingAliases(_memoryAccessors); - using (var file = File.OpenText(richPresenceFile)) + foreach (var asset in _assets) { - do + var memoryAccessors = new List(); + + switch (asset.Type) { - var line = file.ReadLine(); - if (line == null) + case DumpAssetType.Achievement: + var achievement = _localAssets?.Achievements.FirstOrDefault(a => a.Id == asset.Id); + if (achievement == null) + achievement = _publishedAssets.Achievements.FirstOrDefault(a => a.Id == asset.Id); + + if (achievement != null) + MemoryAccessorAlias.AddMemoryAccessors(memoryAccessors, achievement, _publishedAssets.Notes); break; - var index = line.IndexOf("//"); - if (index != -1) - line = line.Substring(0, index).TrimEnd(); + case DumpAssetType.Leaderboard: + var leaderboard = _localAssets?.Leaderboards.FirstOrDefault(l => l.Id == asset.Id); + if (leaderboard == null) + leaderboard = _publishedAssets.Leaderboards.FirstOrDefault(l => l.Id == asset.Id); - if (line.Length == 0) - continue; + if (leaderboard != null) + MemoryAccessorAlias.AddMemoryAccessors(memoryAccessors, leaderboard, _publishedAssets.Notes); + break; - if (line.StartsWith("Format:")) - { - currentMacro = new RichPresenceMacro { Name = line.Substring(7) }; - _macros.Add(currentMacro); - } - else if (line.StartsWith("FormatType=")) - { - currentMacro.FormatType = Leaderboard.ParseFormat(line.Substring(11)); - } - else if (line.StartsWith("Lookup:")) - { - currentMacro = new RichPresenceMacro { Name = line.Substring(7), LookupEntries = new Dictionary() }; - _macros.Add(currentMacro); - } - else if (line.StartsWith("Display:")) + case DumpAssetType.RichPresence: + var richPresence = _localAssets?.RichPresence ?? _publishedAssets.RichPresence; + if (richPresence != null) + MemoryAccessorAlias.AddMemoryAccessors(memoryAccessors, richPresence, _publishedAssets.Notes); + break; + + case DumpAssetType.Lookup: + var richPresence2 = _localAssets?.RichPresence ?? _publishedAssets.RichPresence; + if (richPresence2 != null) + { + foreach (var displayString in richPresence2.DisplayStrings) + { + foreach (var macro in displayString.Macros.Where(m => m.Name == asset.Label)) + MemoryAccessorAlias.AddMemoryAccessors(memoryAccessors, macro.Value, _publishedAssets.Notes); + } + } + break; + + } + + foreach (var memoryAccessor in memoryAccessors) + { + foreach (var size in memoryAccessor.ReferencedSizes) { - currentMacro = displayMacro = new RichPresenceMacro { Name = "Display", DisplayLines = new List() }; - _macros.Add(currentMacro); + var memoryItem = _memoryItems.FirstOrDefault(m => m.Address == memoryAccessor.Address && m.Size == size); + if (memoryItem != null) + asset.MemoryAddresses.Add(memoryItem); } - else if (currentMacro != null) + } + } + } + + private static void LoadMemoryItems(List memoryItems, + IEnumerable memoryAccessors, + NameStyle nameStyle) + { + foreach (var memoryAccessor in memoryAccessors) + { + memoryAccessor.UpdateAliasFromNote(nameStyle); + + var primarySize = memoryAccessor.PrimarySize; + var memoryItem = new MemoryItem(memoryAccessor, primarySize); + if (nameStyle != NameStyle.None) + memoryItem.UpdateFunctionName(); + + memoryItems.Add(memoryItem); + + if (!memoryAccessor.IsOnlyReferencedSize(primarySize)) + { + foreach (var size in memoryAccessor.ReferencedSizes) { - if (currentMacro.DisplayLines != null) - { - currentMacro.DisplayLines.Add(line); - } - else if (currentMacro.LookupEntries != null) + if (size != primarySize) { - index = line.IndexOf('='); - if (index > 0) - currentMacro.LookupEntries[line.Substring(0, index)] = line.Substring(index + 1); + memoryItem = new MemoryItem(memoryAccessor, size); + if (nameStyle != NameStyle.None) + memoryItem.UpdateFunctionName(); + + memoryItems.Add(memoryItem); } } + } - } while (true); + if (memoryAccessor.Children.Any()) + { + LoadMemoryItems(memoryItem.ChainedItems, memoryAccessor.Children, nameStyle); + foreach (var child in memoryItem.ChainedItems) + child.Parent = memoryItem; + } } - foreach (var macro in _macros) + memoryItems.Sort(CompareMemoryItems); + } + + private void LoadRichPresence() + { + var richPresence = _localAssets?.RichPresence ?? _publishedAssets.RichPresence; + if (richPresence == null) + return; + + foreach (var macro in richPresence.Macros) { if (macro.LookupEntries != null) { @@ -379,7 +417,7 @@ private void LoadRichPresence(string richPresenceFile) } } - if (displayMacro != null) + if (richPresence.DisplayStrings.Any()) { var dumpRichPresence = new DumpAsset(0, "Rich Presence Script") { @@ -388,106 +426,14 @@ private void LoadRichPresence(string richPresenceFile) ViewerType = "Rich Presence" }; - for (int i = 0; i < displayMacro.DisplayLines.Count; ++i) - { - var line = displayMacro.DisplayLines[i]; - if (line[0] == '?') - { - var index = line.IndexOf('?', 1); - if (index != -1) - { - var trigger = line.Substring(1, index - 1); - var achievement = new AchievementBuilder(); - achievement.ParseRequirements(Tokenizer.CreateTokenizer(trigger)); - - AddMemoryReferences(dumpRichPresence, achievement.CoreRequirements); - - foreach (var alt in achievement.AlternateRequirements) - AddMemoryReferences(dumpRichPresence, alt); - } - - AddMacroMemoryReferences(dumpRichPresence, line.Substring(index + 1)); - } - else - { - AddMacroMemoryReferences(dumpRichPresence, line); - - if (i < displayMacro.DisplayLines.Count) - displayMacro.DisplayLines.RemoveRange(i + 1, displayMacro.DisplayLines.Count - i - 1); - break; - } - } - dumpRichPresence.PropertyChanged += DumpAsset_PropertyChanged; _assets.Add(dumpRichPresence); } } - private void AddMacroMemoryReferences(DumpAsset displayRichPresence, string displayString) - { - var index = 0; - do - { - index = displayString.IndexOf('@', index); - if (index == -1) - return; - var index2 = displayString.IndexOf('(', index); - if (index2 == -1) - return; - var index3 = displayString.IndexOf(')', index2); - if (index3 == -1) - return; - - var name = displayString.Substring(index + 1, index2 - index - 1); - var parameter = displayString.Substring(index2 + 1, index3 - index2 - 1); - - var macro = _assets.FirstOrDefault(a => a.Type == DumpAssetType.Lookup && a.Label == name); - if (macro == null) - macro = displayRichPresence; - - if (parameter.Length > 2 && parameter[1] == ':') - { - var achievement = new AchievementBuilder(); - achievement.ParseRequirements(Tokenizer.CreateTokenizer(parameter)); - - AddMemoryReferences(macro, achievement.CoreRequirements); - - foreach (var alt in achievement.AlternateRequirements) - AddMemoryReferences(macro, alt); - } - else - { - foreach (var part in parameter.Split('_')) - { - foreach (var operand in part.Split('*')) - { - var field = Field.Deserialize(Tokenizer.CreateTokenizer(operand)); - if (field.IsMemoryReference) - { - var memoryItem = AddMemoryAddress(field, null); - if (memoryItem != null && !macro.MemoryAddresses.Contains(memoryItem)) - macro.MemoryAddresses.Add(memoryItem); - } - } - } - } - - if (macro.Type == DumpAssetType.Lookup) - { - foreach (var address in macro.MemoryAddresses) - { - if (!displayRichPresence.MemoryAddresses.Contains(address)) - displayRichPresence.MemoryAddresses.Add(address); - } - } - - index = index2 + 1; - } while (true); - } - private void MergeOpenTickets() { - var ticketsJson = RAWebCache.Instance.GetOpenTicketsForGame(_game.GameId); + var ticketsJson = RAWebCache.Instance.GetOpenTicketsForGame(_publishedAssets.GameId); if (ticketsJson == null) return; @@ -506,100 +452,9 @@ private void MergeOpenTickets() } } - private void AddMemoryReferences(DumpAsset dumpAsset, IEnumerable requirements) - { - MemoryItem parent = null; - foreach (var requirement in requirements) - { - MemoryItem leftMemoryItem = null; - if (requirement.Left.IsMemoryReference) - leftMemoryItem = AddMemoryAddress(requirement.Left, parent); - - MemoryItem rightMemoryItem = null; - if (requirement.Right.IsMemoryReference) - rightMemoryItem = AddMemoryAddress(requirement.Right, parent); - - if (leftMemoryItem == null && rightMemoryItem == null) - { - if (parent != null && (requirement.Left.IsMemoryReference || requirement.Right.IsMemoryReference)) - { - // offset not found in parent, capture the parent - if (!dumpAsset.MemoryAddresses.Contains(parent)) - dumpAsset.MemoryAddresses.Add(parent); - } - - if (requirement.Type == RequirementType.AddAddress) - { - // provide a dummy parent for the next condition - parent = new MemoryItem(uint.MaxValue, FieldSize.None, null); - continue; - } - } - - if (requirement.Type == RequirementType.AddAddress) - { - // only want to capture the leaves - parent = leftMemoryItem ?? rightMemoryItem; - } - else - { - parent = null; - } - - if (leftMemoryItem != null) - { - leftMemoryItem.IsReferenced = true; - if (!dumpAsset.MemoryAddresses.Contains(leftMemoryItem)) - dumpAsset.MemoryAddresses.Add(leftMemoryItem); - } - - if (rightMemoryItem != null) - { - rightMemoryItem.IsReferenced = true; - if (!dumpAsset.MemoryAddresses.Contains(rightMemoryItem)) - dumpAsset.MemoryAddresses.Add(rightMemoryItem); - } - } - } - - private MemoryItem AddMemoryAddress(Field field, MemoryItem parent) - { - var items = parent?.ChainedItems ?? _memoryItems; - - int index = 0; - while (index < items.Count) - { - if (items[index].Address > field.Value) - break; - if (items[index].Address == field.Value) - { - if (items[index].Size > field.Size) - break; - if (items[index].Size == field.Size) - return items[index]; - } - - index++; - } - - CodeNote note; - if (parent != null) - { - note = parent.Note?.OffsetNotes.FirstOrDefault(n => n.Address == field.Value); - if (note == null) - return null; - } - else if (!_game.Notes.TryGetValue(field.Value, out note)) - { - return null; - } - - var item = new MemoryItem(field.Value, field.Size, note) { Parent = parent }; - items.Insert(index, item); - return item; - } - - private GameViewModel _game; + private PublishedAssets _publishedAssets; + private LocalAssets _localAssets; + private List _memoryAccessors; private readonly Dictionary _ticketNotes; public enum DumpAssetType @@ -713,63 +568,6 @@ public enum NoteDump OnlyForDefinedMethods, } - public enum FunctionNameStyle - { - None = 0, - SnakeCase, // lower_with_underscore - PascalCase, // UpperEachFirst - CamelCase, // upperInMiddle - } - - private static string BuildVariableName(string fromText, FunctionNameStyle style) - { - bool valid = false; - var functionName = new StringBuilder(); - - var newWord = true; - foreach (var c in fromText) - { - if (Char.IsLetter(c) || (valid && Char.IsDigit(c))) - { - valid = true; - - if (newWord) - { - newWord = false; - - switch (style) - { - case FunctionNameStyle.PascalCase: - functionName.Append(Char.ToUpper(c)); - continue; - - case FunctionNameStyle.CamelCase: - if (functionName.Length != 0) - { - functionName.Append(Char.ToUpper(c)); - continue; - } - break; - - case FunctionNameStyle.SnakeCase: - if (functionName.Length != 0) - functionName.Append('_'); - break; - } - } - - functionName.Append(Char.ToLower(c)); - } - else if (valid) - { - newWord = true; - } - } - - return valid ? functionName.ToString() : string.Empty; - } - - public class CodeNoteFilterLookupItem { public CodeNoteFilterLookupItem(CodeNoteFilter id, string label) @@ -794,17 +592,17 @@ public NoteDumpLookupItem(NoteDump id, string label) } public IEnumerable NoteDumps { get; private set; } - public class FunctionNameStyleLookupItem + public class NameStyleLookupItem { - public FunctionNameStyleLookupItem(FunctionNameStyle id, string label) + public NameStyleLookupItem(NameStyle id, string label) { Id = id; Label = label; } - public FunctionNameStyle Id { get; private set; } + public NameStyle Id { get; private set; } public string Label { get; private set; } } - public IEnumerable FunctionNameStyles { get; private set; } + public IEnumerable NameStyles { get; private set; } public static readonly ModelProperty SelectedCodeNotesFilterProperty = ModelProperty.Register(typeof(NewScriptDialogViewModel), "SelectedCodeNotesFilter", typeof(CodeNoteFilter), CodeNoteFilter.ForSelectedAssets, OnSelectedCodeNotesFilterChanged); @@ -826,14 +624,14 @@ public NoteDump SelectedNoteDump set { SetValue(SelectedNoteDumpProperty, value); } } - public static readonly ModelProperty SelectedFunctionNameStyleProperty = ModelProperty.Register(typeof(NewScriptDialogViewModel), "SelectedFunctionNameStyle", typeof(FunctionNameStyle), FunctionNameStyle.None, OnSelectedFunctionNameStyleChanged); - public FunctionNameStyle SelectedFunctionNameStyle + public static readonly ModelProperty SelectedNameStyleProperty = ModelProperty.Register(typeof(NewScriptDialogViewModel), "SelectedNameStyle", typeof(NameStyle), NameStyle.None, OnSelectedNameStyleChanged); + public NameStyle SelectedNameStyle { - get { return (FunctionNameStyle)GetValue(SelectedFunctionNameStyleProperty); } - set { SetValue(SelectedFunctionNameStyleProperty, value); } + get { return (NameStyle)GetValue(SelectedNameStyleProperty); } + set { SetValue(SelectedNameStyleProperty, value); } } - private static void OnSelectedFunctionNameStyleChanged(object sender, ModelPropertyChangedEventArgs e) + private static void OnSelectedNameStyleChanged(object sender, ModelPropertyChangedEventArgs e) { ((NewScriptDialogViewModel)sender).UpdateFunctionNames(); } @@ -870,12 +668,12 @@ private void UpdateMemoryGrid() // will merge in all references from selected achievements } - foreach (var achievement in _assets) + foreach (var asset in _assets) { - if (!achievement.IsSelected) + if (!asset.IsSelected) continue; - foreach (var address in achievement.MemoryAddresses) + foreach (var address in asset.MemoryAddresses) { if (!visibleAddresses.Contains(address)) visibleAddresses.Add(address); @@ -884,15 +682,8 @@ private void UpdateMemoryGrid() } // update the grid - visibleAddresses.Sort((l,r) => - { - int diff = (int)l.Address - (int)r.Address; - if (diff == 0) - diff = (int)l.Size - (int)r.Size; - return diff; - }); + visibleAddresses.Sort(CompareMemoryItems); - var functionNameStyle = SelectedFunctionNameStyle; var memIndex = 0; for (int addrIndex = 0; addrIndex < visibleAddresses.Count; addrIndex++) { @@ -910,9 +701,6 @@ private void UpdateMemoryGrid() break; } - if (functionNameStyle != FunctionNameStyle.None) - memoryItem.UpdateFunctionName(functionNameStyle); - if (rowItem == null || rowItem.Address != memoryItem.Address || rowItem.Size != memoryItem.Size) MemoryAddresses.InsertRow(memIndex, memoryItem); @@ -925,26 +713,38 @@ private void UpdateMemoryGrid() private void UpdateFunctionNames() { - var functionNameStyle = SelectedFunctionNameStyle; + var nameStyle = SelectedNameStyle; + foreach (var memoryAccessor in _memoryAccessors) + memoryAccessor.UpdateAliasFromNote(nameStyle); + + if (nameStyle != NameStyle.None) + MemoryAccessorAlias.ResolveConflictingAliases(_memoryAccessors); foreach (var row in MemoryAddresses.Rows) { var memoryItem = (MemoryItem)row.Model; - memoryItem.UpdateFunctionName(functionNameStyle); + memoryItem.UpdateFunctionName(); } } - [DebuggerDisplay("{Size} {Address}")] + [DebuggerDisplay("{Size} {Address,h}")] public class MemoryItem : ViewModelBase { - public MemoryItem(uint address, FieldSize size, CodeNote note) + public MemoryItem(MemoryAccessorAlias memoryAccessor, FieldSize size) { - Address = address; + _memoryAccessor = memoryAccessor; + + Address = memoryAccessor.Address; Size = size; - Note = note; + Notes = memoryAccessor.Note?.Note ?? String.Empty; } - private CodeNote _note; + private readonly MemoryAccessorAlias _memoryAccessor; + + public bool IsPrimarySize + { + get { return _memoryAccessor.PrimarySize == Size; } + } public static readonly ModelProperty AddressProperty = ModelProperty.Register(typeof(MemoryItem), "Address", typeof(uint), (uint)0); @@ -971,22 +771,17 @@ public string FunctionName public static readonly ModelProperty NotesProperty = ModelProperty.Register(typeof(MemoryItem), "Notes", typeof(string), String.Empty); - public CodeNote Note - { - get { return _note; } - private set - { - _note = value; - Notes = value?.Summary ?? string.Empty; - } - } - public string Notes { get { return (string)GetValue(NotesProperty); } private set { SetValue(NotesProperty, value); } } + public CodeNote Note + { + get { return _memoryAccessor.Note; } + } + public bool HasChainedItems { get { return _chainedItems != null; } @@ -1008,65 +803,14 @@ public List ChainedItems public MemoryItem Parent { get; set; } public bool IsReferenced { get; set; } - public void UpdateFunctionName(FunctionNameStyle style) + public void UpdateFunctionName() { - if (style == FunctionNameStyle.None || _note == null) - { - FunctionName = String.Empty; - return; - } - - var text = _note.Summary; - var subNote = _note.GetSubNote(Size); - if (subNote != null) - text += ' ' + subNote; + FunctionName = _memoryAccessor.GetAlias(Size); - // remove value substrings: (1=a, 2=b) [1=a, 2=b] - var bracket = text.IndexOf('['); - if (bracket != -1) + if (_chainedItems != null) { - var bracket2 = text.IndexOf(']', bracket + 1); - if (bracket2 != -1) - { - var equal = text.IndexOf('=', bracket + 1); - if (equal != -1 && equal < bracket2) - text = text.Remove(bracket, bracket2 - bracket); - } - } - var paren = text.IndexOf('['); - if (paren != -1) - { - var paren2 = text.IndexOf(']', paren + 1); - if (paren2 != -1) - { - var equal = text.IndexOf('=', paren + 1); - if (equal != -1 && equal < paren2) - text = text.Remove(paren, paren2 - paren); - } - } - - // remove potential value assigments - var equalsIndex = text.IndexOfAny(new[] { '=', ':' }); - if (equalsIndex != -1) - { - var left = text.Substring(0, equalsIndex).Trim(); - var right = text.Substring(equalsIndex + 1).Trim(); - if (left.Length > 0 && Char.IsDigit(left[0])) - text = right; - else if (right.Length > 0 && Char.IsDigit(right[0])) - text = left; - } - - // build the function name - var functionName = BuildVariableName(text, style); - if (!String.IsNullOrEmpty(functionName)) - { - functionName = functionName.Replace("'s ", "s "); - - if (AchievementScriptInterpreter.IsReservedFunctionName(functionName)) - functionName += '_'; - - FunctionName = functionName.ToString(); + foreach (var child in _chainedItems) + child.UpdateFunctionName(); } } } @@ -1075,21 +819,23 @@ public void UpdateFunctionName(FunctionNameStyle style) public GameViewModel Finalize() { - _game.InitializeForUI(); + var gameViewModel = new GameViewModel(_publishedAssets.GameId, _publishedAssets.Title); + gameViewModel.AssociateRACacheDirectory(Path.GetDirectoryName(_publishedAssets.Filename)); + gameViewModel.InitializeForUI(); - var cleansed = _game.Title ?? "Untitled"; + var cleansed = _publishedAssets.Title ?? "Untitled"; foreach (var c in Path.GetInvalidFileNameChars()) cleansed = cleansed.Replace(c.ToString(), ""); if (String.IsNullOrEmpty(cleansed)) - cleansed = _game.GameId.ToString(); - _game.Script.Filename = cleansed + ".rascript"; + cleansed = _publishedAssets.GameId.ToString(); + gameViewModel.Script.Filename = cleansed + ".rascript"; var memoryStream = new MemoryStream(); Dump(memoryStream); - _game.Script.SetContent(Encoding.UTF8.GetString(memoryStream.ToArray())); - _game.Script.SetModified(); + gameViewModel.Script.SetContent(Encoding.UTF8.GetString(memoryStream.ToArray())); + gameViewModel.Script.SetModified(); - return _game; + return gameViewModel; } private static string EscapeString(string input) @@ -1101,7 +847,7 @@ private uint GetMask() { var mask = 0xFFFFFFFF; - switch (_game.ConsoleId) + switch (_publishedAssets.ConsoleId) { case 40: // Dreamcast case 78: // DSi @@ -1114,21 +860,18 @@ private uint GetMask() // GameCube docs suggest masking with 0x1FFFFFFF (extra F). // both work. check to see which the game is using. mask = 0x01FFFFFF; - foreach (var achievement in _game.Editors.OfType()) + foreach (var achievement in _publishedAssets.Achievements) { - foreach (var trigger in achievement.Published.TriggerList) + foreach (var group in achievement.Trigger.Groups) { - foreach (var group in trigger.Groups) + foreach (var requirement in group.Requirements) { - foreach (var requirement in group.Requirements) + if (requirement.Type == RequirementType.AddAddress && + requirement.Operator == RequirementOperator.BitwiseAnd && + requirement.Right.Type == FieldType.Value) { - if (requirement.Requirement.Type == RequirementType.AddAddress && - requirement.Requirement.Operator == RequirementOperator.BitwiseAnd && - requirement.Requirement.Right.Type == FieldType.Value) - { - if (requirement.Requirement.Right.Value >= mask) - return requirement.Requirement.Right.Value; - } + if (requirement.Right.Value >= mask) + return requirement.Right.Value; } } } @@ -1178,12 +921,12 @@ internal void Dump(Stream outStream) using (var stream = new StreamWriter(outStream)) { stream.Write("// "); - stream.WriteLine(_game.Title); + stream.WriteLine(_publishedAssets.Title); stream.Write("// #ID = "); - stream.WriteLine(String.Format("{0}", _game.GameId)); + stream.WriteLine(String.Format("{0}", _publishedAssets.GameId)); - if (_game.PublishedSets.Count() > 1) - DumpSets(stream, _game.PublishedSets); + if (_publishedAssets.Sets.Count() > 1) + DumpSets(stream, _publishedAssets.Sets); var lookupsToDump = _assets.Where(a => a.Type == DumpAssetType.Lookup && a.IsSelected).ToList(); @@ -1194,12 +937,7 @@ internal void Dump(Stream outStream) DumpAchievements(stream, scriptBuilderContext); DumpLeaderboards(stream, scriptBuilderContext); - - foreach (var dumpRichPresence in _assets.Where(a => a.Type == DumpAssetType.RichPresence && a.IsSelected)) - { - var displayMacro = _macros.FirstOrDefault(m => m.DisplayLines != null); - DumpRichPresence(stream, displayMacro, dumpRichPresence, scriptBuilderContext); - } + DumpRichPresence(stream, scriptBuilderContext); } } @@ -1261,11 +999,12 @@ private void AddAliases(ScriptBuilderContext scriptBuilderContext, IEnumerable published foreach (var set in publishedSets) { - if (set.OwnerGameId != _game.GameId) + if (set.OwnerGameId != _publishedAssets.GameId) { var name = "achievement set " + set.Title; - var variableName = BuildVariableName(name, SelectedFunctionNameStyle); + var variableName = SelectedNameStyle.BuildName(name); _achievementSetVariables[set.Id] = variableName; stream.Write(variableName); @@ -1446,7 +1196,7 @@ private bool DumpNestedMemoryFunctions(StreamWriter stream, List look foreach (var memoryItem in parent.ChainedItems) { - if (memoryItem.IsReferenced && !String.IsNullOrEmpty(memoryItem.FunctionName)) + if (!String.IsNullOrEmpty(memoryItem.FunctionName)) { DumpMemoryFunction(stream, lookupsToDump, scriptBuilderContext, memoryItem, ref needLine); hadFunction = true; @@ -1564,9 +1314,13 @@ private void DumpAchievements(StreamWriter stream, ScriptBuilderContext scriptBu foreach (var dumpAchievement in _assets.Where(a => a.IsSelected && a.Type == DumpAssetType.Achievement)) { - var achievementViewModel = _game.Editors.OfType().FirstOrDefault(a => a.Id == dumpAchievement.Id); - if (achievementViewModel == null) - continue; + var achievement = _localAssets?.Achievements.FirstOrDefault(a => a.Id == dumpAchievement.Id); + if (achievement == null) + { + achievement = _publishedAssets.Achievements.FirstOrDefault(a => a.Id == dumpAchievement.Id); + if (achievement == null) + continue; + } stream.WriteLine(); @@ -1574,27 +1328,24 @@ private void DumpAchievements(StreamWriter stream, ScriptBuilderContext scriptBu stream.WriteLine("achievement("); - var assetSource = (achievementViewModel.Published.Asset != null) ? achievementViewModel.Published : achievementViewModel.Local; - var achievementData = assetSource.Asset as Achievement; - stream.Write(" title = \""); - stream.Write(EscapeString(achievementData.Title)); + stream.Write(EscapeString(achievement.Title)); stream.Write("\", points = "); - stream.Write(achievementData.Points); - if (achievementData.Type != AchievementType.Standard) + stream.Write(achievement.Points); + if (achievement.Type != AchievementType.Standard) { stream.Write(", type=\""); - stream.Write(Achievement.GetTypeString(achievementData.Type)); + stream.Write(Achievement.GetTypeString(achievement.Type)); stream.Write("\""); } stream.WriteLine(","); stream.Write(" description = \""); - stream.Write(EscapeString(achievementData.Description)); + stream.Write(EscapeString(achievement.Description)); stream.WriteLine("\","); string setVariable; - if (_achievementSetVariables.TryGetValue(achievementData.OwnerSetId, out setVariable)) + if (_achievementSetVariables.TryGetValue(achievement.OwnerSetId, out setVariable)) { stream.Write(" set = "); stream.Write(setVariable); @@ -1604,18 +1355,18 @@ private void DumpAchievements(StreamWriter stream, ScriptBuilderContext scriptBu if (dumpAchievement.ViewerType != "Local Achievement") { stream.Write(" id = "); - stream.Write(achievementData.Id); + stream.Write(achievement.Id); stream.Write(", badge = \""); - stream.Write(achievementData.BadgeName); + stream.Write(achievement.BadgeName); stream.Write("\", published = \""); - stream.Write(achievementData.Published); + stream.Write(achievement.Published); stream.Write("\", modified = \""); - stream.Write(achievementData.LastModified); + stream.Write(achievement.LastModified); stream.WriteLine("\","); } stream.Write(" trigger = "); - DumpTrigger(stream, indentedContext, dumpAchievement, assetSource.TriggerList.First()); + DumpTrigger(stream, indentedContext, dumpAchievement, achievement.Trigger); stream.WriteLine(); stream.WriteLine(")"); @@ -1629,9 +1380,13 @@ private void DumpLeaderboards(StreamWriter stream, ScriptBuilderContext scriptBu foreach (var dumpLeaderboard in _assets.Where(a => a.IsSelected && a.Type == DumpAssetType.Leaderboard)) { - var leaderboardViewModel = _game.Editors.OfType().FirstOrDefault(a => a.Id == dumpLeaderboard.Id); - if (leaderboardViewModel == null) - continue; + var leaderboard = _localAssets?.Leaderboards.FirstOrDefault(l => l.Id == dumpLeaderboard.Id); + if (leaderboard == null) + { + leaderboard = _publishedAssets.Leaderboards.FirstOrDefault(l => l.Id == dumpLeaderboard.Id); + if (leaderboard == null) + continue; + } stream.WriteLine(); @@ -1639,20 +1394,17 @@ private void DumpLeaderboards(StreamWriter stream, ScriptBuilderContext scriptBu stream.WriteLine("leaderboard("); - var assetSource = (leaderboardViewModel.Published.Asset != null) ? leaderboardViewModel.Published : leaderboardViewModel.Local; - var leaderboardData = assetSource.Asset as Leaderboard; - stream.Write(" id = "); - stream.Write(leaderboardData.Id); + stream.Write(leaderboard.Id); stream.Write(", title = \""); - stream.Write(EscapeString(leaderboardData.Title)); + stream.Write(EscapeString(leaderboard.Title)); stream.WriteLine("\","); stream.Write(" description = \""); - stream.Write(EscapeString(leaderboardData.Description)); + stream.Write(EscapeString(leaderboard.Description)); stream.WriteLine("\","); string setVariable; - if (_achievementSetVariables.TryGetValue(leaderboardData.OwnerSetId, out setVariable)) + if (_achievementSetVariables.TryGetValue(leaderboard.OwnerSetId, out setVariable)) { stream.Write(" set = "); stream.Write(setVariable); @@ -1660,35 +1412,35 @@ private void DumpLeaderboards(StreamWriter stream, ScriptBuilderContext scriptBu } stream.Write(" start = "); - DumpTrigger(stream, indentedContext, dumpLeaderboard, assetSource.TriggerList.First()); + DumpTrigger(stream, indentedContext, dumpLeaderboard, leaderboard.Start); stream.WriteLine(","); stream.Write(" cancel = "); - DumpTrigger(stream, indentedContext, dumpLeaderboard, assetSource.TriggerList.ElementAt(1)); + DumpTrigger(stream, indentedContext, dumpLeaderboard, leaderboard.Cancel); stream.WriteLine(","); stream.Write(" submit = "); - DumpTrigger(stream, indentedContext, dumpLeaderboard, assetSource.TriggerList.ElementAt(2)); + DumpTrigger(stream, indentedContext, dumpLeaderboard, leaderboard.Submit); stream.WriteLine(","); stream.Write(" value = "); - var valueTrigger = assetSource.TriggerList.ElementAt(3); - if (valueTrigger.Groups.Count() > 1 || - valueTrigger.Groups.First().Requirements.Any(r => r.Requirement.IsMeasured)) + var valueTrigger = leaderboard.Value; + if (valueTrigger.Values.Count() > 1 || + valueTrigger.Values.First().Requirements.Any(r => r.IsMeasured)) { - DumpValue(stream, indentedContext, dumpLeaderboard, assetSource.TriggerList.ElementAt(3)); + DumpValue(stream, indentedContext, dumpLeaderboard, valueTrigger); } else { - DumpLegacyExpression(stream, leaderboardData.Value, dumpLeaderboard, indentedContext); + DumpLegacyExpression(stream, valueTrigger, dumpLeaderboard, indentedContext); } stream.WriteLine(","); stream.Write(" format = \""); - stream.Write(Leaderboard.GetFormatString(leaderboardData.Format)); + stream.Write(Leaderboard.GetFormatString(leaderboard.Format)); stream.Write("\""); - if (leaderboardData.LowerIsBetter) + if (leaderboard.LowerIsBetter) stream.Write(", lower_is_better = true"); stream.WriteLine(); @@ -1698,7 +1450,8 @@ private void DumpLeaderboards(StreamWriter stream, ScriptBuilderContext scriptBu private void DumpLookup(StreamWriter stream, DumpAsset dumpLookup) { - var macro = _macros.FirstOrDefault(m => m.Name == dumpLookup.Label); + var richPresence = _localAssets?.RichPresence ?? _publishedAssets?.RichPresence; + var macro = richPresence?.Macros.FirstOrDefault(m => m.Name == dumpLookup.Label); if (macro == null) return; @@ -1740,85 +1493,75 @@ private void DumpLookup(StreamWriter stream, DumpAsset dumpLookup) stream.WriteLine("}"); } - private void DumpRichPresence(StreamWriter stream, RichPresenceMacro displayMacro, DumpAsset dumpRichPresence, ScriptBuilderContext scriptBuilderContext) + private void DumpRichPresence(StreamWriter stream, ScriptBuilderContext scriptBuilderContext) { - int index; + var dumpRichPresence = _assets.FirstOrDefault(a => a.Type == DumpAssetType.RichPresence); + if (dumpRichPresence == null || !dumpRichPresence.IsSelected) + return; + + var richPresence = _localAssets?.RichPresence ?? _publishedAssets.RichPresence; + if (richPresence == null) + return; + var notes = new Dictionary(); var indentedContext = scriptBuilderContext.Clone(); indentedContext.Indent = 4; - foreach (var line in displayMacro.DisplayLines) + foreach (var displayString in richPresence.DisplayStrings) { - string displayString = line; stream.WriteLine(); - if (line[0] == '?') + if (displayString.Condition != null) { - index = line.IndexOf('?', 1); - if (index != -1) - { - var trigger = Trigger.Deserialize(line.Substring(1, index - 1)); - var vmTrigger = new TriggerViewModel("RichPresence", trigger, scriptBuilderContext.NumberFormat, notes); - - stream.Write("rich_presence_conditional_display("); - indentedContext.Indent = 4; - DumpTrigger(stream, indentedContext, dumpRichPresence, vmTrigger); - stream.Write(", \""); - } - - ++index; + stream.Write("rich_presence_conditional_display("); + indentedContext.Indent = 4; + DumpTrigger(stream, indentedContext, dumpRichPresence, displayString.Condition); + stream.Write(", \""); } else { stream.Write("rich_presence_display(\""); - index = 0; } - var macros = new List>(); + int index = 0; + int macroCount = 0; do { - var index1 = displayString.IndexOf('@', index); + var index1 = displayString.Text.IndexOf('@', index); if (index1 == -1) { - stream.Write(displayString.Substring(index)); + stream.Write(displayString.Text.Substring(index)); break; } if (index1 > index) - stream.Write(displayString.Substring(index, index1 - index)); + stream.Write(displayString.Text.Substring(index, index1 - index)); stream.Write('{'); - stream.Write(macros.Count()); + stream.Write(macroCount++); stream.Write('}'); - var index2 = displayString.IndexOf('(', index1); - var index3 = displayString.IndexOf(')', index2); - - var name = displayString.Substring(index1 + 1, index2 - index1 - 1); - var parameter = displayString.Substring(index2 + 1, index3 - index2 - 1); - - macros.Add(new KeyValuePair(name, parameter)); - - index = index3 + 1; + var rightParenIndex = displayString.Text.IndexOf(')', index1); + index = rightParenIndex + 1; } while (true); stream.Write('"'); - foreach (var kvp in macros) + foreach (var macro in displayString.Macros) { stream.WriteLine(","); stream.Write(" "); - var macro = _macros.FirstOrDefault(m => m.Name == kvp.Key); - if (macro == null) + var macroDefinition = richPresence.Macros.FirstOrDefault(m => m.Name == macro.Name); + if (macroDefinition == null) { stream.Write("rich_presence_value(\""); - stream.Write(kvp.Key); + stream.Write(macro.Name); stream.Write("\", "); indentedContext.Indent = 24; // " rich_presence_value(".length } - else if (macro.LookupEntries != null) + else if (macroDefinition.LookupEntries != null) { stream.Write("rich_presence_lookup(\""); stream.Write(macro.Name); @@ -1833,18 +1576,17 @@ private void DumpRichPresence(StreamWriter stream, RichPresenceMacro displayMacr indentedContext.Indent = 24; // " rich_presence_value(".length } - var value = Value.Deserialize(kvp.Value); - if (value.Values.Any()) + if (macro.Value.Values.Any()) { - var measured = value.Values.First().Requirements.FirstOrDefault(r => r.Type == RequirementType.Measured); + var measured = macro.Value.Values.First().Requirements.FirstOrDefault(r => r.Type == RequirementType.Measured); if (measured != null) measured.Type = RequirementType.None; // measured() is implicit } - DumpLegacyExpression(stream, value, dumpRichPresence, indentedContext); + DumpLegacyExpression(stream, macro.Value, dumpRichPresence, indentedContext); - if (macro == null) + if (macroDefinition == null) { - var macroFormat = RichPresenceBuilder.GetValueFormat(kvp.Key); + var macroFormat = RichPresenceBuilder.GetValueFormat(macro.Name); if (macroFormat != ValueFormat.None && macroFormat != ValueFormat.Value) { stream.Write(", format=\""); @@ -1854,14 +1596,14 @@ private void DumpRichPresence(StreamWriter stream, RichPresenceMacro displayMacr stream.Write(')'); } - else if (macro.LookupEntries != null) + else if (macroDefinition.LookupEntries != null) { stream.Write(", "); stream.Write(macro.Name); stream.Write("Lookup"); string defaultEntry; - if (macro.LookupEntries.TryGetValue("*", out defaultEntry)) + if (macroDefinition.LookupEntries.TryGetValue("*", out defaultEntry)) { stream.Write(", fallback=\""); stream.Write(EscapeString(defaultEntry)); @@ -1872,10 +1614,10 @@ private void DumpRichPresence(StreamWriter stream, RichPresenceMacro displayMacr } else { - if (macro.FormatType != ValueFormat.Value) + if (macroDefinition.FormatType != ValueFormat.Value) { stream.Write(", format=\""); - stream.Write(Leaderboard.GetFormatString(macro.FormatType)); + stream.Write(Leaderboard.GetFormatString(macroDefinition.FormatType)); stream.Write('"'); } @@ -1883,7 +1625,7 @@ private void DumpRichPresence(StreamWriter stream, RichPresenceMacro displayMacr } } - if (macros.Count() > 0) + if (displayString.Macros.Any()) stream.WriteLine(); stream.WriteLine(')'); @@ -1905,13 +1647,14 @@ private static void DumpLegacyExpression(StreamWriter stream, Value value, DumpA stream.Write(script); } - private static void DumpTrigger(StreamWriter stream, ScriptBuilderContext scriptBuilderContext, DumpAsset dumpAsset, TriggerViewModel triggerViewModel) + private static void DumpTrigger(StreamWriter stream, ScriptBuilderContext scriptBuilderContext, DumpAsset dumpAsset, Trigger trigger) { - var triggerWhenMeasuredGroups = new List(); - if (triggerViewModel.Groups.Count() > 2) - IdentifyTriggerWhenMeasured(triggerViewModel, triggerWhenMeasuredGroups); + var triggerWhenMeasuredGroups = new List(); + var triggerGroupCount = trigger.Groups.Count(); + if (triggerGroupCount > 2) + IdentifyTriggerWhenMeasured(trigger, triggerWhenMeasuredGroups); - var groupEnumerator = triggerViewModel.Groups.GetEnumerator(); + var groupEnumerator = trigger.Groups.GetEnumerator(); groupEnumerator.MoveNext(); bool isCoreEmpty = !groupEnumerator.Current.Requirements.Any(); @@ -1935,7 +1678,7 @@ private static void DumpTrigger(StreamWriter stream, ScriptBuilderContext script stream.Write('('); first = false; - if (triggerViewModel.Groups.Count() == 2) + if (triggerGroupCount == 2) { // only core and one alt, inject an always_false clause to prevent the compiler from joining them stream.Write("always_false() || "); @@ -1969,15 +1712,15 @@ private static void DumpTrigger(StreamWriter stream, ScriptBuilderContext script stream.Write(')'); } - private static void IdentifyTriggerWhenMeasured(TriggerViewModel triggerViewModel, List triggerWhenMeasuredGroups) + private static void IdentifyTriggerWhenMeasured(Trigger trigger, List triggerWhenMeasuredGroups) { RequirementEx triggerAlt = null; - foreach (var group in triggerViewModel.Groups.Skip(1)) + foreach (var group in trigger.Alts) { - if (!group.Requirements.Any(r => r.Requirement.Type == RequirementType.Trigger)) + if (!group.Requirements.Any(r => r.Type == RequirementType.Trigger)) continue; - var groupEx = RequirementEx.Combine(group.Requirements.Select(r => r.Requirement)); + var groupEx = RequirementEx.Combine(group.Requirements); if (groupEx.Count == 1) { triggerWhenMeasuredGroups.Add(group); @@ -1989,12 +1732,12 @@ private static void IdentifyTriggerWhenMeasured(TriggerViewModel triggerViewMode if (triggerAlt == null) return; - foreach (var group in triggerViewModel.Groups.Skip(1)) + foreach (var group in trigger.Alts) { - if (!group.Requirements.Any(r => r.Requirement.Type == RequirementType.Measured || r.Requirement.Type == RequirementType.MeasuredPercent)) + if (!group.Requirements.Any(r => r.IsMeasured)) continue; - var groupEx = RequirementEx.Combine(group.Requirements.Select(r => r.Requirement)); + var groupEx = RequirementEx.Combine(group.Requirements); if (groupEx.Count != 1) continue; @@ -2024,15 +1767,15 @@ private static void IdentifyTriggerWhenMeasured(TriggerViewModel triggerViewMode triggerWhenMeasuredGroups.Clear(); } - private static void DumpValue(StreamWriter stream, ScriptBuilderContext context, DumpAsset dumpAsset, TriggerViewModel triggerViewModel) + private static void DumpValue(StreamWriter stream, ScriptBuilderContext context, DumpAsset dumpAsset, Value value) { - if (triggerViewModel.Groups.Count() > 1) + if (value.Values.Count() > 1) { stream.WriteLine("max_of("); context.Indent += 4; bool first = true; - foreach (var value in triggerViewModel.Groups) + foreach (var scan in value.Values) { if (!first) stream.WriteLine(","); @@ -2040,7 +1783,7 @@ private static void DumpValue(StreamWriter stream, ScriptBuilderContext context, stream.Write(new string(' ', context.Indent)); first = false; - DumpPublishedRequirements(stream, dumpAsset, value, context, true); + DumpPublishedRequirements(stream, dumpAsset, scan, context, true); } context.Indent -= 4; @@ -2050,17 +1793,17 @@ private static void DumpValue(StreamWriter stream, ScriptBuilderContext context, } else { - DumpPublishedRequirements(stream, dumpAsset, triggerViewModel.Groups.First(), context, true); + DumpPublishedRequirements(stream, dumpAsset, value.Values.First(), context, true); } } private static void DumpPublishedRequirements(StreamWriter stream, DumpAsset dumpAsset, - RequirementGroupViewModel requirementGroupViewModel, ScriptBuilderContext scriptBuilderContext, bool isValue = false) + RequirementGroup requirementGroup, ScriptBuilderContext scriptBuilderContext, bool isValue = false) { var definition = new StringBuilder(); var context = scriptBuilderContext.Clone(); context.IsValue = isValue; - context.AppendRequirements(definition, requirementGroupViewModel.Requirements.Select(r => r.Requirement)); + context.AppendRequirements(definition, requirementGroup.Requirements); stream.Write(definition.ToString()); } diff --git a/Source/Views/NewScriptDialog.xaml b/Source/Views/NewScriptDialog.xaml index 528bd33b..093139de 100644 --- a/Source/Views/NewScriptDialog.xaml +++ b/Source/Views/NewScriptDialog.xaml @@ -148,7 +148,7 @@ - + diff --git a/Tests/Data/CodeNoteTests.cs b/Tests/Data/CodeNoteTests.cs index ff06499d..72a7a615 100644 --- a/Tests/Data/CodeNoteTests.cs +++ b/Tests/Data/CodeNoteTests.cs @@ -45,6 +45,7 @@ class CodeNoteTests [TestCase("[16-bit-BE] Test", 2, FieldSize.BigEndianWord)] [TestCase("[8-bit BE] Test", 1, FieldSize.Byte)] [TestCase("[4-bit BE] Test", 1, FieldSize.Byte)] + [TestCase("[US] Test [32-bit BE]", 4, FieldSize.BigEndianDWord)] [TestCase("8 BYTE Test", 8, FieldSize.Array)] [TestCase("Test 8 BYTE", 8, FieldSize.Array)] @@ -338,5 +339,37 @@ public void TestGetSubNoteWithExtra() Assert.That(n.GetSubNote(FieldSize.HighNibble), Is.Null); Assert.That(n.GetSubNote(FieldSize.LowNibble), Is.Null); } + + [Test] + public void TestUnlabelledEnum() + { + var n = new CodeNote(4, + "01=easy\r\n" + + "02=normal"); + + Assert.That(n.Summary, Is.EqualTo("Unlabelled")); + Assert.That(n.Size, Is.EqualTo(FieldSize.None)); + Assert.That(n.Values.Count(), Is.EqualTo(2)); + } + + [Test] + public void TestUnlabelledEnumCSV() + { + var n = new CodeNote(4,"01=easy,02=normal"); + + Assert.That(n.Summary, Is.EqualTo("Unlabelled")); + Assert.That(n.Size, Is.EqualTo(FieldSize.None)); + Assert.That(n.Values.Count(), Is.EqualTo(2)); + } + + [Test] + public void TestPossibleButNotEnum() + { + var n = new CodeNote(4, "Tuesday: timer"); + + Assert.That(n.Summary, Is.EqualTo("Tuesday: timer")); + Assert.That(n.Size, Is.EqualTo(FieldSize.None)); + Assert.That(n.Values.Count(), Is.EqualTo(0)); + } } } diff --git a/Tests/Parser/MemoryAccessorAliasTests.cs b/Tests/Parser/MemoryAccessorAliasTests.cs new file mode 100644 index 00000000..9e60dd30 --- /dev/null +++ b/Tests/Parser/MemoryAccessorAliasTests.cs @@ -0,0 +1,324 @@ +using Jamiras.Components; +using Jamiras.Core.Tests; +using Jamiras.Services; +using Moq; +using NUnit.Framework; +using RATools.Data; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace RATools.Parser.Tests +{ + [TestFixture] + class MemoryAccessorAliasTests + { + [Test] + public void TestInitialize() + { + var alias = new MemoryAccessorAlias(0x1234); + Assert.That(alias.Alias, Is.EqualTo("")); + Assert.That(alias.Address, Is.EqualTo(0x1234)); + Assert.That(alias.Children, Is.Empty); + Assert.That(alias.HasMultipleReferencedSizes, Is.False); + Assert.That(alias.Note, Is.Null); + Assert.That(alias.PrimarySize, Is.EqualTo(FieldSize.None)); + } + + [Test] + public void TestInitializeFromNote() + { + var note = new CodeNote(0x1230, "[32-bit] This is a note."); + var alias = new MemoryAccessorAlias(0x1234, note); + Assert.That(alias.Alias, Is.EqualTo("")); + Assert.That(alias.Address, Is.EqualTo(0x1234)); + Assert.That(alias.Children, Is.Empty); + Assert.That(alias.HasMultipleReferencedSizes, Is.False); + Assert.That(alias.Note, Is.SameAs(note)); + Assert.That(alias.PrimarySize, Is.EqualTo(FieldSize.DWord)); + } + + [Test] + public void TestReferenceSize() + { + var alias = new MemoryAccessorAlias(0x1234); + Assert.That(alias.PrimarySize, Is.EqualTo(FieldSize.None)); + Assert.That(alias.ReferencedSizes, Is.Empty); + Assert.That(alias.HasMultipleReferencedSizes, Is.False); + Assert.That(alias.HasReferencedSize(FieldSize.None), Is.False); + Assert.That(alias.IsOnlyReferencedSize(FieldSize.None), Is.False); + + alias.ReferenceSize(FieldSize.Byte); + Assert.That(alias.PrimarySize, Is.EqualTo(FieldSize.Byte)); + Assert.That(alias.ReferencedSizes.Count(), Is.EqualTo(1)); + Assert.That(alias.ReferencedSizes.First(), Is.EqualTo(FieldSize.Byte)); + Assert.That(alias.HasMultipleReferencedSizes, Is.False); + Assert.That(alias.HasReferencedSize(FieldSize.Byte), Is.True); + Assert.That(alias.IsOnlyReferencedSize(FieldSize.Byte), Is.True); + + alias.ReferenceSize(FieldSize.Byte); + Assert.That(alias.PrimarySize, Is.EqualTo(FieldSize.Byte)); + Assert.That(alias.ReferencedSizes.Count(), Is.EqualTo(1)); + Assert.That(alias.ReferencedSizes.First(), Is.EqualTo(FieldSize.Byte)); + Assert.That(alias.HasMultipleReferencedSizes, Is.False); + Assert.That(alias.HasReferencedSize(FieldSize.Byte), Is.True); + Assert.That(alias.IsOnlyReferencedSize(FieldSize.Byte), Is.True); + + alias.ReferenceSize(FieldSize.Word); + Assert.That(alias.PrimarySize, Is.EqualTo(FieldSize.Byte)); + Assert.That(alias.ReferencedSizes.Count(), Is.EqualTo(2)); + Assert.That(alias.ReferencedSizes.First(), Is.EqualTo(FieldSize.Byte)); + Assert.That(alias.ReferencedSizes.Last(), Is.EqualTo(FieldSize.Word)); + Assert.That(alias.HasMultipleReferencedSizes, Is.True); + Assert.That(alias.HasReferencedSize(FieldSize.Byte), Is.True); + Assert.That(alias.HasReferencedSize(FieldSize.Word), Is.True); + Assert.That(alias.IsOnlyReferencedSize(FieldSize.Byte), Is.False); + Assert.That(alias.IsOnlyReferencedSize(FieldSize.Word), Is.False); + } + + [Test] + public void TestReferenceSizeBits() + { + var alias = new MemoryAccessorAlias(0x1234); + Assert.That(alias.PrimarySize, Is.EqualTo(FieldSize.None)); + Assert.That(alias.ReferencedSizes, Is.Empty); + Assert.That(alias.HasMultipleReferencedSizes, Is.False); + Assert.That(alias.HasReferencedSize(FieldSize.None), Is.False); + Assert.That(alias.IsOnlyReferencedSize(FieldSize.None), Is.False); + + alias.ReferenceSize(FieldSize.Bit5); + Assert.That(alias.PrimarySize, Is.EqualTo(FieldSize.Byte)); + Assert.That(alias.ReferencedSizes.Count(), Is.EqualTo(1)); + Assert.That(alias.ReferencedSizes.First(), Is.EqualTo(FieldSize.Bit5)); + Assert.That(alias.HasMultipleReferencedSizes, Is.False); + Assert.That(alias.HasReferencedSize(FieldSize.Byte), Is.False); + Assert.That(alias.HasReferencedSize(FieldSize.Bit5), Is.True); + Assert.That(alias.IsOnlyReferencedSize(FieldSize.Byte), Is.False); + Assert.That(alias.IsOnlyReferencedSize(FieldSize.Bit5), Is.True); + + alias.ReferenceSize(FieldSize.Bit2); + Assert.That(alias.PrimarySize, Is.EqualTo(FieldSize.Byte)); + Assert.That(alias.ReferencedSizes.Count(), Is.EqualTo(2)); + Assert.That(alias.ReferencedSizes.First(), Is.EqualTo(FieldSize.Bit2)); + Assert.That(alias.ReferencedSizes.Last(), Is.EqualTo(FieldSize.Bit5)); + Assert.That(alias.HasMultipleReferencedSizes, Is.True); + Assert.That(alias.HasReferencedSize(FieldSize.Byte), Is.False); + Assert.That(alias.HasReferencedSize(FieldSize.Bit2), Is.True); + Assert.That(alias.HasReferencedSize(FieldSize.Bit5), Is.True); + Assert.That(alias.IsOnlyReferencedSize(FieldSize.Byte), Is.False); + Assert.That(alias.IsOnlyReferencedSize(FieldSize.Bit2), Is.False); + Assert.That(alias.IsOnlyReferencedSize(FieldSize.Bit5), Is.False); + + alias.ReferenceSize(FieldSize.Word); + Assert.That(alias.PrimarySize, Is.EqualTo(FieldSize.Byte)); + Assert.That(alias.ReferencedSizes.Count(), Is.EqualTo(3)); + Assert.That(alias.HasMultipleReferencedSizes, Is.True); + Assert.That(alias.HasReferencedSize(FieldSize.Byte), Is.False); + Assert.That(alias.HasReferencedSize(FieldSize.Bit2), Is.True); + Assert.That(alias.HasReferencedSize(FieldSize.Bit5), Is.True); + Assert.That(alias.HasReferencedSize(FieldSize.Word), Is.True); + } + + [Test] + public void TestReferenceSizeBitCount() + { + var alias = new MemoryAccessorAlias(0x1234); + Assert.That(alias.PrimarySize, Is.EqualTo(FieldSize.None)); + Assert.That(alias.ReferencedSizes, Is.Empty); + Assert.That(alias.HasMultipleReferencedSizes, Is.False); + Assert.That(alias.HasReferencedSize(FieldSize.None), Is.False); + Assert.That(alias.IsOnlyReferencedSize(FieldSize.None), Is.False); + + alias.ReferenceSize(FieldSize.BitCount); + Assert.That(alias.PrimarySize, Is.EqualTo(FieldSize.Byte)); + Assert.That(alias.ReferencedSizes.Count(), Is.EqualTo(1)); + Assert.That(alias.ReferencedSizes.First(), Is.EqualTo(FieldSize.BitCount)); + Assert.That(alias.HasMultipleReferencedSizes, Is.False); + Assert.That(alias.HasReferencedSize(FieldSize.Byte), Is.False); + Assert.That(alias.HasReferencedSize(FieldSize.BitCount), Is.True); + Assert.That(alias.IsOnlyReferencedSize(FieldSize.Byte), Is.False); + Assert.That(alias.IsOnlyReferencedSize(FieldSize.BitCount), Is.True); + + alias.ReferenceSize(FieldSize.Bit2); + Assert.That(alias.PrimarySize, Is.EqualTo(FieldSize.Byte)); + Assert.That(alias.ReferencedSizes.Count(), Is.EqualTo(2)); + Assert.That(alias.ReferencedSizes.First(), Is.EqualTo(FieldSize.Bit2)); + Assert.That(alias.ReferencedSizes.Last(), Is.EqualTo(FieldSize.BitCount)); + Assert.That(alias.HasMultipleReferencedSizes, Is.True); + Assert.That(alias.HasReferencedSize(FieldSize.Byte), Is.False); + Assert.That(alias.HasReferencedSize(FieldSize.Bit2), Is.True); + Assert.That(alias.HasReferencedSize(FieldSize.BitCount), Is.True); + Assert.That(alias.IsOnlyReferencedSize(FieldSize.Byte), Is.False); + Assert.That(alias.IsOnlyReferencedSize(FieldSize.Bit2), Is.False); + Assert.That(alias.IsOnlyReferencedSize(FieldSize.BitCount), Is.False); + } + + + [Test] + public void TestReferenceSizeEndianness() + { + var alias = new MemoryAccessorAlias(0x1234); + Assert.That(alias.PrimarySize, Is.EqualTo(FieldSize.None)); + Assert.That(alias.ReferencedSizes, Is.Empty); + Assert.That(alias.HasMultipleReferencedSizes, Is.False); + Assert.That(alias.HasReferencedSize(FieldSize.None), Is.False); + Assert.That(alias.IsOnlyReferencedSize(FieldSize.None), Is.False); + + alias.ReferenceSize(FieldSize.Word); + Assert.That(alias.PrimarySize, Is.EqualTo(FieldSize.Word)); + Assert.That(alias.ReferencedSizes.Count(), Is.EqualTo(1)); + Assert.That(alias.ReferencedSizes.First(), Is.EqualTo(FieldSize.Word)); + Assert.That(alias.HasMultipleReferencedSizes, Is.False); + Assert.That(alias.HasReferencedSize(FieldSize.Word), Is.True); + Assert.That(alias.HasReferencedSize(FieldSize.BigEndianWord), Is.False); + Assert.That(alias.IsOnlyReferencedSize(FieldSize.Word), Is.True); + Assert.That(alias.IsOnlyReferencedSize(FieldSize.BigEndianWord), Is.False); + + alias.ReferenceSize(FieldSize.BigEndianWord); + Assert.That(alias.PrimarySize, Is.EqualTo(FieldSize.Word)); + Assert.That(alias.ReferencedSizes.Count(), Is.EqualTo(2)); + Assert.That(alias.ReferencedSizes.First(), Is.EqualTo(FieldSize.Word)); + Assert.That(alias.ReferencedSizes.Last(), Is.EqualTo(FieldSize.BigEndianWord)); + Assert.That(alias.HasMultipleReferencedSizes, Is.True); + Assert.That(alias.HasReferencedSize(FieldSize.Word), Is.True); + Assert.That(alias.HasReferencedSize(FieldSize.BigEndianWord), Is.True); + Assert.That(alias.IsOnlyReferencedSize(FieldSize.Word), Is.False); + Assert.That(alias.IsOnlyReferencedSize(FieldSize.BigEndianWord), Is.False); + } + + [Test] + [TestCase("Test", "test")] + [TestCase("Stage/Level", "stage_level")] + [TestCase("[16-bit] Score", "score")] // size stripped by code note processing + [TestCase("Mario's Lives", "marios_lives")] // apostrophe doesn't separate words + [TestCase("Score (BCD)", "score")] // "BCD" stored in subtext for conflict resolution + [TestCase("Score (US)", "score_us")] // regional markers are kept + [TestCase("Score (EU)", "score_eu")] // regional markers are kept + [TestCase("Score (JP)", "score_jp")] // regional markers are kept + [TestCase("Stage (1=First, 2=Second)", "stage")] // value clause will be stripped by code note processing + [TestCase("Stage (1=First, 2=Second) - monday", "stage")] // "- monday" consumed by code note processing + [TestCase("Stage - 1=First, 2=Second - monday", "stage")] // assume "- monday" is part of 2= + [TestCase("1-1 Time Attack", "time_attack")] // "1-1" stored in subtext for conflict resolution + [TestCase("Bit7=Villager 1 exists", "villager_1_exists")] // use bit description if single entry and no header + [TestCase("01=Happy", "happy")] // use enum value if single entry and no header + public void TestUpdateAliasFromNote(string note, string expected) + { + var codeNote = new CodeNote(0x1234, note); + var alias = new MemoryAccessorAlias(0x1234, codeNote); + alias.ReferenceSize(FieldSize.Byte); + alias.UpdateAliasFromNote(NameStyle.SnakeCase); + + Assert.That(alias.Alias, Is.EqualTo(expected)); + } + + [Test] + public void TestUpdateAliasFromNoteBitSubset() + { + var codeNote = new CodeNote(0x1234, "Header\r\nupper4=hi\r\nlower4=lo"); + var alias = new MemoryAccessorAlias(0x1234, codeNote); + alias.ReferenceSize(FieldSize.HighNibble); + alias.UpdateAliasFromNote(NameStyle.SnakeCase); + + Assert.That(alias.Alias, Is.EqualTo("header_hi")); + } + + [Test] + public void TestUpdateAliasFromNoteNibbleSubset() + { + var codeNote = new CodeNote(0x1234, "Header\r\nupper4=hi\r\nlower4=lo"); + var alias = new MemoryAccessorAlias(0x1234, codeNote); + alias.ReferenceSize(FieldSize.HighNibble); + alias.UpdateAliasFromNote(NameStyle.SnakeCase); + + Assert.That(alias.PrimarySize, Is.EqualTo(FieldSize.HighNibble)); + Assert.That(alias.Alias, Is.EqualTo("header_hi")); + } + + [Test] + public void TestGetAliasLargeSizes() + { + var codeNote = new CodeNote(0x1234, "[16-bit] Header"); + var alias = new MemoryAccessorAlias(0x1234, codeNote); + alias.ReferenceSize(FieldSize.Word); + alias.ReferenceSize(FieldSize.DWord); + alias.ReferenceSize(FieldSize.Byte); + alias.UpdateAliasFromNote(NameStyle.SnakeCase); + + Assert.That(alias.PrimarySize, Is.EqualTo(FieldSize.Word)); + Assert.That(alias.Alias, Is.EqualTo("header")); + Assert.That(alias.GetAlias(FieldSize.Word), Is.EqualTo("header")); + Assert.That(alias.GetAlias(FieldSize.DWord), Is.EqualTo("header_dword")); + Assert.That(alias.GetAlias(FieldSize.Byte), Is.EqualTo("header_byte")); + Assert.That(alias.GetAlias(FieldSize.BigEndianWord), Is.Null); + } + + [Test] + public void TestResolveConflictingAliases() + { + var list = new List(); + list.Add(new MemoryAccessorAlias(0x1234, new CodeNote(0x1234, "Score"))); + list.Add(new MemoryAccessorAlias(0x1235, new CodeNote(0x1235, "Score"))); + list.Add(new MemoryAccessorAlias(0x1236, new CodeNote(0x1236, "Score"))); + list.Add(new MemoryAccessorAlias(0x1240, new CodeNote(0x1240, "Lives"))); + list.Add(new MemoryAccessorAlias(0x1248, new CodeNote(0x1248, "Level"))); + list.Add(new MemoryAccessorAlias(0x124C, new CodeNote(0x1248, "Sub-level"))); + list.Add(new MemoryAccessorAlias(0x1280, new CodeNote(0x1280, "Level (New Game+)"))); + list.Add(new MemoryAccessorAlias(0x12C0, new CodeNote(0x12C0, "Character (New Game+)"))); + + foreach (var alias in list) + alias.UpdateAliasFromNote(NameStyle.CamelCase); + + Assert.That(list[0].Alias, Is.EqualTo("score")); + Assert.That(list[1].Alias, Is.EqualTo("score")); + Assert.That(list[2].Alias, Is.EqualTo("score")); + Assert.That(list[3].Alias, Is.EqualTo("lives")); + Assert.That(list[4].Alias, Is.EqualTo("level")); + Assert.That(list[5].Alias, Is.EqualTo("sublevel")); + Assert.That(list[6].Alias, Is.EqualTo("level")); + Assert.That(list[7].Alias, Is.EqualTo("character")); + + MemoryAccessorAlias.ResolveConflictingAliases(list); + + Assert.That(list[0].Alias, Is.EqualTo("score")); + Assert.That(list[1].Alias, Is.EqualTo("score_2")); + Assert.That(list[2].Alias, Is.EqualTo("score_3")); + Assert.That(list[3].Alias, Is.EqualTo("lives")); + Assert.That(list[4].Alias, Is.EqualTo("level")); + Assert.That(list[5].Alias, Is.EqualTo("sublevel")); + Assert.That(list[6].Alias, Is.EqualTo("levelNewGame")); + Assert.That(list[7].Alias, Is.EqualTo("character")); + } + + [Test] + public void TestAddMemoryAccessorsAchievement() + { + var definition = "A:0xX1234_R:d0xH1111=67S0x 2000!=d0x 2000S0xH2000=0xH1337"; + var builder = new AchievementBuilder(); + builder.ParseRequirements(Tokenizer.CreateTokenizer(definition)); + var achievement = builder.ToAchievement(); + + var codeNotes = new Dictionary(); + codeNotes[0x2000] = new CodeNote(0x2000, "[8-bit] Column"); + codeNotes[0x1111] = new CodeNote(0x1111, "[8-bit] Row"); + + var list = new List(); + MemoryAccessorAlias.AddMemoryAccessors(list, achievement, codeNotes); + + Assert.That(list.Count, Is.EqualTo(4)); + Assert.That(list[0].Address, Is.EqualTo(0x1111)); + Assert.That(list[0].HasReferencedSize(FieldSize.Byte), Is.True); + Assert.That(list[0].Note, Is.SameAs(codeNotes[0x1111])); + Assert.That(list[1].Address, Is.EqualTo(0x1234)); + Assert.That(list[1].HasReferencedSize(FieldSize.DWord), Is.True); + Assert.That(list[1].Note, Is.Null); + Assert.That(list[2].Address, Is.EqualTo(0x1337)); + Assert.That(list[2].HasReferencedSize(FieldSize.Byte), Is.True); + Assert.That(list[2].Note, Is.Null); + Assert.That(list[3].Address, Is.EqualTo(0x2000)); + Assert.That(list[3].HasReferencedSize(FieldSize.Word), Is.True); + Assert.That(list[3].HasReferencedSize(FieldSize.Byte), Is.True); + Assert.That(list[3].Note, Is.SameAs(codeNotes[0x2000])); + } + } +} diff --git a/Tests/Parser/NameStyleTests.cs b/Tests/Parser/NameStyleTests.cs new file mode 100644 index 00000000..60c411df --- /dev/null +++ b/Tests/Parser/NameStyleTests.cs @@ -0,0 +1,42 @@ +using NUnit.Framework; + +namespace RATools.Parser.Tests +{ + [TestFixture] + class NameStyleTests + { + [TestCase("a", NameStyle.SnakeCase, "a")] + [TestCase("a", NameStyle.PascalCase, "A")] + [TestCase("a", NameStyle.CamelCase, "a")] + [TestCase("a", NameStyle.None, "a")] + [TestCase("big ball", NameStyle.SnakeCase, "big_ball")] + [TestCase("big ball", NameStyle.PascalCase, "BigBall")] + [TestCase("big ball", NameStyle.CamelCase, "bigBall")] + [TestCase("big ball", NameStyle.None, "bigball")] + [TestCase("ONE TWO 3", NameStyle.SnakeCase, "one_two_3")] + [TestCase("ONE TWO 3", NameStyle.PascalCase, "OneTwo3")] + [TestCase("ONE TWO 3", NameStyle.CamelCase, "oneTwo3")] + [TestCase("ONE TWO 3", NameStyle.None, "onetwo3")] + [TestCase("12.3", NameStyle.SnakeCase, "_12_3")] + [TestCase("12.3", NameStyle.PascalCase, "_12_3")] + [TestCase("12.3", NameStyle.CamelCase, "_12_3")] + [TestCase("12.3", NameStyle.None, "_123")] + [TestCase("Bob's one-off", NameStyle.SnakeCase, "bobs_oneoff")] + [TestCase("Bob's one-off", NameStyle.PascalCase, "BobsOneoff")] + [TestCase("Bob's one-off", NameStyle.CamelCase, "bobsOneoff")] + [TestCase("Bob's one-off", NameStyle.None, "bobsoneoff")] + [TestCase("_italic fun_", NameStyle.SnakeCase, "italic_fun")] + [TestCase("_italic fun_", NameStyle.PascalCase, "ItalicFun")] + [TestCase("_italic fun_", NameStyle.CamelCase, "italicFun")] + [TestCase("_italic fun_", NameStyle.None, "italicfun")] + [TestCase("1-2 Time Attack", NameStyle.SnakeCase, "_1_2_time_attack")] + [TestCase("1-2 Time Attack", NameStyle.PascalCase, "_1_2TimeAttack")] + [TestCase("1-2 Time Attack", NameStyle.CamelCase, "_1_2TimeAttack")] + [TestCase("1-2 Time Attack", NameStyle.None, "_12timeattack")] + public void TestBuildName(string input, NameStyle nameStyle, string expected) + { + var output = nameStyle.BuildName(input); + Assert.That(output, Is.EqualTo(expected)); + } + } +} diff --git a/Tests/Regression/DumpTests.cs b/Tests/Regression/DumpTests.cs index 3a401eec..2e274862 100644 --- a/Tests/Regression/DumpTests.cs +++ b/Tests/Regression/DumpTests.cs @@ -2,6 +2,7 @@ using Jamiras.Services; using Moq; using NUnit.Framework; +using RATools.Parser; using RATools.Services; using RATools.ViewModels; using System.Collections.Generic; @@ -69,22 +70,21 @@ public void DumpTest(string patchDataFileName) var mockDialogService = new Mock(); - var mockLogger = new Mock(); - var mockFileSystem = new Mock(); mockFileSystem.Setup(s => s.FileExists(It.IsAny())).Returns((string p) => File.Exists(p)); mockFileSystem.Setup(s => s.OpenFile(It.IsAny(), OpenFileMode.Read)). Returns((string p, OpenFileMode m) => File.Open(p, FileMode.Open, FileAccess.Read, FileShare.Read)); var vmNewScript = new NewScriptDialogViewModel(mockSettings.Object, - mockDialogService.Object, mockLogger.Object, mockFileSystem.Object); + mockDialogService.Object, mockFileSystem.Object); vmNewScript.GameId.Value = int.Parse(patchDataFileName); vmNewScript.SearchCommand.Execute(); vmNewScript.SelectedCodeNotesFilter = CodeNoteFilter.ForSelectedAssets; - vmNewScript.SelectedFunctionNameStyle = FunctionNameStyle.SnakeCase; vmNewScript.SelectedNoteDump = NoteDump.OnlyForDefinedMethods; + vmNewScript.CheckAllCommand.Execute(); + vmNewScript.SelectedNameStyle = NameStyle.SnakeCase; // name style must be changed after selecting all var expectedFileName = Path.Combine(baseDir, vmNewScript.GameId.Value + ".rascript"); var outputFileName = Path.ChangeExtension(expectedFileName, ".updated.rascript");