/// <summary> /// Creates an UndoableChange for a label update. /// </summary> /// <param name="offset">Affected offset.</param> /// <param name="oldSymbol">Current label. May be null.</param> /// <param name="newSymbol">New label. May be null.</param> /// <returns>Change record.</returns> public static UndoableChange CreateLabelChange(int offset, Symbol oldSymbol, Symbol newSymbol) { if (oldSymbol == newSymbol) { Debug.WriteLine("No-op label change at +" + offset.ToString("x6") + ": " + oldSymbol); } UndoableChange uc = new UndoableChange(); uc.Type = ChangeType.SetLabel; uc.Offset = offset; uc.OldValue = oldSymbol; uc.NewValue = newSymbol; // Data analysis can change if we add or remove a label in a data area. Label // selection can change as well, e.g. switching from an auto-label to a user // label with an adjustment. So renaming a user-defined label doesn't require // reanalysis, but adding or removing one does. // // Do the reanalysis if either is empty. This will cause an unnecessary // reanalysis if we change an empty label to an empty label, but that shouldn't // be allowed by the UI anyway. Debug.Assert(newSymbol == null || newSymbol.SymbolSource == Symbol.Source.User); if ((oldSymbol == null) || (newSymbol == null) /*|| * (oldSymbol.SymbolSource != newSymbol.SymbolSource)*/) { uc.ReanalysisRequired = ReanalysisScope.DataOnly; } else { uc.ReanalysisRequired = ReanalysisScope.None; } return(uc); }
/// <summary> /// Creates an UndoableChange for an address map update. /// </summary> /// <param name="offset">Affected offset.</param> /// <param name="oldEntry">Previous address map entry, or null if none.</param> /// <param name="newEntry">New address map entry, or null for deletion.</param> /// <returns>Change record.</returns> public static UndoableChange CreateAddressChange(AddressMap.AddressMapEntry oldEntry, AddressMap.AddressMapEntry newEntry) { int offset; if (oldEntry != null) { offset = oldEntry.Offset; } else if (newEntry != null) { offset = newEntry.Offset; } else { // Shouldn't happen. Debug.Assert(false); offset = -1; } if (oldEntry == newEntry) { Debug.WriteLine("No-op address change at +" + offset.ToString("x6")); } UndoableChange uc = new UndoableChange(); uc.Type = ChangeType.SetAddress; uc.Offset = offset; uc.OldValue = oldEntry; uc.NewValue = newEntry; uc.ReanalysisRequired = ReanalysisScope.CodeAndData; return(uc); }
/// <summary> /// Creates an UndoableChange that does nothing but force an update. /// </summary> /// <param name="flags">Desired reanalysis flags.</param> /// <returns>Change record.</returns> public static UndoableChange CreateDummyChange(ReanalysisScope flags) { UndoableChange uc = new UndoableChange(); uc.Type = ChangeType.Dummy; uc.Offset = -1; uc.ReanalysisRequired = flags; return(uc); }
/// <summary> /// Creates an UndoableChange for an operand or data format update. /// </summary> /// <param name="offset">Affected offset.</param> /// <param name="oldFormat">Current format. May be null.</param> /// <param name="newFormat">New format. May be null.</param> /// <returns>Change record.</returns> public static UndoableChange CreateOperandFormatChange(int offset, FormatDescriptor oldFormat, FormatDescriptor newFormat) { if (oldFormat == newFormat) { Debug.WriteLine("No-op format change at +" + offset.ToString("x6") + ": " + oldFormat); } // We currently allow old/new formats with different lengths. There doesn't // seem to be a reason not to, and a slight performance advantage to doing so. // Also, if a change set has two changes at the same offset, undo requires // enumerating the list in reverse order. UndoableChange uc = new UndoableChange(); uc.Type = ChangeType.SetOperandFormat; uc.Offset = offset; uc.OldValue = oldFormat; uc.NewValue = newFormat; // Data-only reanalysis is required if the old or new format has a label. Simply // changing from e.g. default to decimal, or decimal to binary, doesn't matter. // (The format editing code ensures that labels don't appear in the middle of // a formatted region.) Adding, removing, or changing a symbol can change the // layout of uncategorized data, affect data targets, xrefs, etc. // // We can't only check for a symbol, though, because Numeric/Address will // create an auto-label if the reference is within the file. // // If the number of bytes covered by the format changes, or we're adding or // removing a format, we need to redo the analysis of uncategorized data. For // example, an auto-detected string could get larger or smaller. We don't // currently have a separate flag for just that. Also, because we're focused // on just one change, we can't skip reanalysis when (say) one 4-byte numeric // is converted to two two-byte numerics. if ((oldFormat != null && oldFormat.HasSymbolOrAddress) || (newFormat != null && newFormat.HasSymbolOrAddress)) { uc.ReanalysisRequired = ReanalysisScope.DataOnly; } else if (oldFormat == null || newFormat == null || oldFormat.Length != newFormat.Length) { uc.ReanalysisRequired = ReanalysisScope.DataOnly; } else { uc.ReanalysisRequired = ReanalysisScope.None; } return(uc); }
/// <summary> /// Creates an UndoableChange for an address map update. /// </summary> /// <param name="offset">Affected offset.</param> /// <param name="oldAddress">Previous address map entry, or -1 if none.</param> /// <param name="newAddress">New address map entry, or -1 if none.</param> /// <returns>Change record.</returns> public static UndoableChange CreateAddressChange(int offset, int oldAddress, int newAddress) { if (oldAddress == newAddress) { Debug.WriteLine("No-op address change at +" + offset.ToString("x6") + ": " + oldAddress); } UndoableChange uc = new UndoableChange(); uc.Type = ChangeType.SetAddress; uc.Offset = offset; uc.OldValue = oldAddress; uc.NewValue = newAddress; uc.ReanalysisRequired = ReanalysisScope.CodeAndData; return(uc); }
/// <summary> /// Creates an UndoableChange for a visualization set update. /// </summary> /// <param name="offset">Affected offset.</param> /// <param name="oldVisSet">Old visualization set.</param> /// <param name="newVisSet">New visualization set.</param> /// <returns>Change record.</returns> public static UndoableChange CreateVisualizationSetChange(int offset, VisualizationSet oldVisSet, VisualizationSet newVisSet) { if (oldVisSet == newVisSet) { Debug.WriteLine("No-op visualization set change"); } UndoableChange uc = new UndoableChange(); uc.Type = ChangeType.SetVisualizationSet; uc.Offset = offset; uc.OldValue = oldVisSet; uc.NewValue = newVisSet; uc.ReanalysisRequired = ReanalysisScope.DisplayOnly; // no change to code/data return(uc); }
/// <summary> /// Creates an UndoableChange for a local variable table update. /// </summary> /// <param name="offset">Affected offset.</param> /// <param name="oldLvTable">Old table.</param> /// <param name="newLvTable">New table.</param> /// <returns>Change record.</returns> public static UndoableChange CreateLocalVariableTableChange(int offset, LocalVariableTable oldLvTable, LocalVariableTable newLvTable) { if (oldLvTable == newLvTable) { Debug.WriteLine("No-op local variable table change"); } UndoableChange uc = new UndoableChange(); uc.Type = ChangeType.SetLocalVariableTable; uc.Offset = offset; uc.OldValue = oldLvTable; uc.NewValue = newLvTable; uc.ReanalysisRequired = ReanalysisScope.DataOnly; // update dfds in Anattribs return(uc); }
/// <summary> /// Creates an UndoableChange for a note update. /// </summary> /// <param name="offset">Affected offset.</param> /// <param name="oldNote">Current note.</param> /// <param name="newNote">New note.</param> /// <returns>Change record.</returns> public static UndoableChange CreateNoteChange(int offset, MultiLineComment oldNote, MultiLineComment newNote) { if (oldNote == newNote) { Debug.WriteLine("No-op note change at +" + offset.ToString("x6") + ": " + oldNote); } UndoableChange uc = new UndoableChange(); uc.Type = ChangeType.SetNote; uc.Offset = offset; uc.OldValue = oldNote; uc.NewValue = newNote; uc.ReanalysisRequired = ReanalysisScope.None; return(uc); }
/// <summary> /// Creates an UndoableChange for a long comment update. /// </summary> /// <param name="offset">Affected offset.</param> /// <param name="oldComment">Current comment.</param> /// <param name="newComment">New comment.</param> /// <returns>Change record.</returns> public static UndoableChange CreateLongCommentChange(int offset, MultiLineComment oldComment, MultiLineComment newComment) { if (oldComment == newComment) { Debug.WriteLine("No-op long comment change at +" + offset.ToString("x6") + ": " + oldComment); } UndoableChange uc = new UndoableChange(); uc.Type = ChangeType.SetLongComment; uc.Offset = offset; uc.OldValue = oldComment; uc.NewValue = newComment; uc.ReanalysisRequired = ReanalysisScope.None; return(uc); }
/// <summary> /// Creates an UndoableChange for a status flag override update. /// </summary> /// <param name="offset">Affected offset.</param> /// <param name="oldFlags">Current flags.</param> /// <param name="newFlags">New flags.</param> /// <returns></returns> public static UndoableChange CreateStatusFlagChange(int offset, StatusFlags oldFlags, StatusFlags newFlags) { if (oldFlags == newFlags) { Debug.WriteLine("No-op status flag change at " + offset); } UndoableChange uc = new UndoableChange(); uc.Type = ChangeType.SetStatusFlagOverride; uc.Offset = offset; uc.OldValue = oldFlags; uc.NewValue = newFlags; // This can affect instruction widths (for M/X) and conditional branches. We // don't need to re-analyze for changes to I/D, but users don't really need to // change those anyway, so it's not worth optimizing. uc.ReanalysisRequired = ReanalysisScope.CodeAndData; return(uc); }
/// <summary> /// Creates an UndoableChange for a data bank register update. /// </summary> /// <param name="offset"></param> /// <param name="oldValue"></param> /// <param name="newValue"></param> /// <returns></returns> public static UndoableChange CreateDataBankChange(int offset, CodeAnalysis.DbrValue oldValue, CodeAnalysis.DbrValue newValue) { if (oldValue == newValue) { Debug.WriteLine("No-op DBR change at +" + offset.ToString("x6") + ": " + oldValue); } UndoableChange uc = new UndoableChange(); uc.Type = ChangeType.SetDataBank; uc.Offset = offset; uc.OldValue = oldValue; uc.NewValue = newValue; // We don't strictly need to re-analyze the code, since the current implementation // handles it as a post-analysis fixup, but this lets us avoid having to compute the // affected offsets. uc.ReanalysisRequired = ReanalysisScope.CodeAndData; return(uc); }
/// <summary> /// Creates an UndoableChange for a type hint update. Rather than adding a /// separate UndoableChange for each affected offset -- which could span the /// entire file -- we use range sets to record the before/after state. /// </summary> /// <param name="undoSet">Current values.</param> /// <param name="newSet">New values.</param> /// <returns>Change record.</returns> public static UndoableChange CreateTypeHintChange(TypedRangeSet undoSet, TypedRangeSet newSet) { if (newSet.Count == 0) { Debug.WriteLine("Empty hint change?"); } UndoableChange uc = new UndoableChange(); uc.Type = ChangeType.SetTypeHint; uc.Offset = -1; uc.OldValue = undoSet; uc.NewValue = newSet; // Any hint change can affect whether something is treated as code. // Either we're deliberately setting it as code or non-code, or we're // setting it to "no hint", which means the code analyzer gets // to make the decision now. This requires a full code+data re-analysis. uc.ReanalysisRequired = ReanalysisScope.CodeAndData; return(uc); }
/// <summary> /// Creates an UndoableChange for a change to the project properties. /// </summary> /// <param name="oldNote">Current note.</param> /// <param name="newNote">New note.</param> /// <returns>Change record.</returns> public static UndoableChange CreateProjectPropertiesChange(ProjectProperties oldProps, ProjectProperties newProps) { Debug.Assert(oldProps != null && newProps != null); if (oldProps == newProps) // doesn't currently work except as reference check { Debug.WriteLine("No-op property change: " + oldProps); } UndoableChange uc = new UndoableChange(); uc.Type = ChangeType.SetProjectProperties; uc.Offset = -1; uc.OldValue = oldProps; uc.NewValue = newProps; // Project properties could change the CPU type, requiring a full code+data // reanalysis. We could scan the objects to see what actually changed, but that // doesn't seem worthwhile. uc.ReanalysisRequired = ReanalysisScope.CodeAndData; return(uc); }
/// <summary> /// Import comments in SGEC format. /// </summary> /// <param name="pathName">File to read from.</param> /// <param name="proj">Project object.</param> /// <param name="cs">Change set that will hold changes.</param> /// <param name="detailMsg">Failure detail, or null on success.</param> /// <returns>True on success.</returns> public static bool ImportFromFile(string pathName, DisasmProject proj, ChangeSet cs, out string detailMsg) { string[] lines; try { lines = File.ReadAllLines(pathName); } catch (IOException ex) { // not expecting this to happen detailMsg = ex.Message; return(false); } JavaScriptSerializer ser = new JavaScriptSerializer(); int lineNum = 0; int prevOffset = -1; foreach (string line in lines) { lineNum++; // first line is 1 if (string.IsNullOrEmpty(line) || line[0] == '#') { // ignore continue; } MatchCollection matches = sLineRegex.Matches(line); if (matches.Count != 1) { detailMsg = "Line " + lineNum + ": unable to parse into tokens"; return(false); } string posStr = matches[0].Groups[GROUP_POS].Value; int offset; if (posStr[0] == '+') { // offset if (!Asm65.Number.TryParseIntHex(posStr.Substring(1), out offset)) { detailMsg = "Line " + lineNum + ": unable to parse offset '" + posStr + "'"; return(false); } } else if (posStr[0] == '$') { // address if (!Asm65.Address.ParseAddress(posStr, (1 << 24) - 1, out int addr)) { detailMsg = "Line " + lineNum + ": unable to parse address '" + posStr + "'"; return(false); } offset = proj.AddrMap.AddressToOffset(0, addr); } else if (posStr[0] == '>') { // relative offset if (prevOffset < 0) { detailMsg = "Line " + lineNum + ": first address/offset cannot be relative"; return(false); } if (!Asm65.Number.TryParseInt(posStr.Substring(1), out int delta, out int _)) { detailMsg = "Line " + lineNum + ": unable to parse delta"; return(false); } offset = prevOffset + delta; } else { detailMsg = "Line " + lineNum + ": unknown position type '" + posStr[0] + "'"; return(false); } prevOffset = offset; if (!proj.GetAnattrib(offset).IsStart) { // This causes problems when we try to do a LineListGen update, because // we specifically request it to do the modified offset, which happens to // be in the middle of an instruction, and it gets very confused. detailMsg = "Line " + lineNum + ": attempt to modify middle of instr/data item"; return(false); } string cmdStr = matches[0].Groups[GROUP_CMD].Value; string valueStr = matches[0].Groups[GROUP_VALUE].Value; switch (cmdStr) { case SET_COMMENT: { string oldComment = proj.Comments[offset]; string newComment = valueStr; if (oldComment == newComment) { // no change break; } if (!string.IsNullOrEmpty(oldComment)) { // overwriting existing entry; make a note Debug.WriteLine("Replacing comment +" + offset.ToString("x6") + " '" + oldComment + "'"); } UndoableChange uc = UndoableChange.CreateCommentChange(offset, oldComment, newComment); cs.Add(uc); } break; case SET_LONG_COMMENT: { if (!DeserializeMlc(ser, valueStr, false, out MultiLineComment newComment)) { detailMsg = "Line " + lineNum + ": failed to deserialize value"; return(false); } proj.LongComments.TryGetValue(offset, out MultiLineComment oldComment); if (oldComment == newComment) { // no change break; } UndoableChange uc = UndoableChange.CreateLongCommentChange(offset, oldComment, newComment); cs.Add(uc); } break; case SET_NOTE: { if (!DeserializeMlc(ser, valueStr, true, out MultiLineComment newNote)) { detailMsg = "Line " + lineNum + ": failed to deserialize value"; return(false); } proj.Notes.TryGetValue(offset, out MultiLineComment oldNote); if (oldNote == newNote) { // no change break; } UndoableChange uc = UndoableChange.CreateNoteChange(offset, oldNote, newNote); cs.Add(uc); } break; default: detailMsg = "Line " + lineNum + ": unknown command '" + cmdStr + "'"; return(false); } } detailMsg = "applied " + cs.Count + " changes."; return(true); }