diff --git a/CommonWPF/Helper.cs b/CommonWPF/Helper.cs index a278b70..94a131f 100644 --- a/CommonWPF/Helper.cs +++ b/CommonWPF/Helper.cs @@ -78,5 +78,22 @@ namespace CommonWPF { return fmt.Width; } + + /// + /// Converts a System.Windows.Media.Color value into 32-bit ARGB. + /// + public static int ColorToInt(Color color) { + return (color.A << 24) | (color.R << 16) | (color.G << 8) | color.B; + } + + /// + /// Creates a System.Windows.Media.Color value from 32-bit ARGB. + /// + /// + /// + public static Color ColorFromInt(int colorInt) { + return Color.FromArgb((byte)(colorInt >> 24), (byte)(colorInt >> 16), + (byte)(colorInt >> 8), (byte)colorInt); + } } } diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs index f2be80e..ce4e88e 100644 --- a/SourceGen/MainController.cs +++ b/SourceGen/MainController.cs @@ -4387,7 +4387,19 @@ namespace SourceGen { return; } string sgecPathName = Path.GetFullPath(fileDlg.FileName); - if (!Sgec.ExportToFile(sgecPathName, mProject, out string detailMsg)) { + + MessageBoxResult res = MessageBox.Show("Use relative offsets?", "Question", + MessageBoxButton.YesNoCancel, MessageBoxImage.Question); + bool resMode; + if (res == MessageBoxResult.Cancel) { + return; + } else if (res == MessageBoxResult.Yes) { + resMode = true; + } else { + resMode = false; + } + + if (!Sgec.ExportToFile(sgecPathName, mProject, resMode, out string detailMsg)) { MessageBox.Show("Failed: " + detailMsg); } else { MessageBox.Show("Success: " + detailMsg); diff --git a/SourceGen/ProjectFile.cs b/SourceGen/ProjectFile.cs index a3efe64..243d9de 100644 --- a/SourceGen/ProjectFile.cs +++ b/SourceGen/ProjectFile.cs @@ -273,7 +273,7 @@ namespace SourceGen { Text = mlc.Text; BoxMode = mlc.BoxMode; MaxWidth = mlc.MaxWidth; - BackgroundColor = ColorToInt(mlc.BackgroundColor); + BackgroundColor = CommonWPF.Helper.ColorToInt(mlc.BackgroundColor); } } public class SerSymbol { @@ -730,7 +730,7 @@ namespace SourceGen { continue; } proj.Notes[intKey] = new MultiLineComment(kvp.Value.Text, - ColorFromInt(kvp.Value.BackgroundColor)); + CommonWPF.Helper.ColorFromInt(kvp.Value.BackgroundColor)); } // Deserialize user-defined labels. @@ -1261,14 +1261,5 @@ namespace SourceGen { return false; } } - - private static int ColorToInt(Color color) { - return (color.A << 24) | (color.R << 16) | (color.G << 8) | color.B; - } - - private static Color ColorFromInt(int colorInt) { - return Color.FromArgb((byte)(colorInt >> 24), (byte)(colorInt >> 16), - (byte)(colorInt >> 8), (byte)colorInt); - } } } diff --git a/SourceGen/Sgec.cs b/SourceGen/Sgec.cs index ef84c82..d02d053 100644 --- a/SourceGen/Sgec.cs +++ b/SourceGen/Sgec.cs @@ -18,6 +18,10 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text; +using System.Text.RegularExpressions; +using System.Web.Script.Serialization; + +using CommonUtil; namespace SourceGen { /// @@ -30,6 +34,56 @@ namespace SourceGen { public static class Sgec { public const string SGEC_EXT = "_sgec.txt"; + // Commands. + private const string SET_COMMENT = "set-comment"; + private const string SET_LONG_COMMENT = "set-long-comment"; + private const string SET_NOTE = "set-note"; + + // Regular expression used for parsing lines. + private const string LINE_PATTERN = @"^([A-Za-z-]+)\s([^:]+):(.+)$"; + private static Regex sLineRegex = new Regex(LINE_PATTERN); + private const int GROUP_CMD = 1; + private const int GROUP_POS = 2; + private const int GROUP_VALUE = 3; + + private static bool OUTPUT_ADDR = true; // addr vs. offset + + // Lifted from ProjectFile. + public class SerMultiLineComment { + // NOTE: Text must be CRLF at line breaks. + public string Text { get; set; } + public bool BoxMode { get; set; } + public int MaxWidth { get; set; } + public int BackgroundColor { get; set; } + + public SerMultiLineComment() { } + public SerMultiLineComment(MultiLineComment mlc) { + Text = mlc.Text; + BoxMode = mlc.BoxMode; + MaxWidth = mlc.MaxWidth; + BackgroundColor = CommonWPF.Helper.ColorToInt(mlc.BackgroundColor); + } + } + + /// + /// Generates a position string, which may be a file offset, an address, or a + /// position delta. + /// + private static string PositionStr(int offset, int prevOffset, AddressMap addrMap, + bool relMode) { + if (prevOffset < 0 || !relMode) { + // hex offset or address + if (OUTPUT_ADDR) { + return '$' + addrMap.OffsetToAddress(offset).ToString("x4"); + } else { + return '+' + offset.ToString("x6"); + } + } else { + // decimal delta + return '>' + (offset - prevOffset).ToString(); + } + } + /// /// Exports comments in SGEC format. /// @@ -37,19 +91,43 @@ namespace SourceGen { /// Project object. /// Details on failure or success. /// True on success. - public static bool ExportToFile(string pathName, DisasmProject proj, out string detailMsg) { - int numComments = 0; + public static bool ExportToFile(string pathName, DisasmProject proj, bool relMode, + out string detailMsg) { + int numItems = 0; + JavaScriptSerializer ser = new JavaScriptSerializer(); + + int prevOffset = -1; using (StreamWriter sw = new StreamWriter(pathName, false, new UTF8Encoding(false))) { for (int offset = 0; offset < proj.FileDataLength; offset++) { if (!string.IsNullOrEmpty(proj.Comments[offset])) { - sw.WriteLine("set-comment +" + offset.ToString("x6") + ':' + + sw.WriteLine(SET_COMMENT + " " + + PositionStr(offset, prevOffset, proj.AddrMap, relMode) + ':' + proj.Comments[offset]); - numComments++; + prevOffset = offset; + numItems++; + } + if (proj.LongComments.TryGetValue(offset, out MultiLineComment lc)) { + SerMultiLineComment serCom = new SerMultiLineComment(lc); + string cereal = ser.Serialize(serCom); + sw.WriteLine(SET_LONG_COMMENT + " " + + PositionStr(offset, prevOffset, proj.AddrMap, relMode) + ':' + + cereal); + prevOffset = offset; + numItems++; + } + if (proj.Notes.TryGetValue(offset, out MultiLineComment nt)) { + SerMultiLineComment serCom = new SerMultiLineComment(nt); + string cereal = ser.Serialize(serCom); + sw.WriteLine(SET_NOTE + " " + + PositionStr(offset, prevOffset, proj.AddrMap, relMode) + ':' + + cereal); + prevOffset = offset; + numItems++; } } } - detailMsg = "Exported " + numComments + " comments."; + detailMsg = "exported " + numItems + " items."; return true; } @@ -72,49 +150,139 @@ namespace SourceGen { return false; } - List changed = new List(lines.Length); + JavaScriptSerializer ser = new JavaScriptSerializer(); - string setComment = "set-comment +"; + int lineNum = 0; + int prevOffset = -1; foreach (string line in lines) { - if (!line.StartsWith(setComment)) { - Debug.WriteLine("Ignoring " + line); + lineNum++; // first line is 1 + if (string.IsNullOrEmpty(line) || line[0] == '#') { + // ignore continue; } - - int offset; - try { - offset = Convert.ToInt32(line.Substring(setComment.Length, 6), 16); - } catch (Exception ex) { - Debug.WriteLine("Failed on " + line); - detailMsg = ex.Message; + MatchCollection matches = sLineRegex.Matches(line); + if (matches.Count != 1) { + detailMsg = "Line " + lineNum + ": unable to parse into tokens"; return false; } - if (changed.Contains(offset)) { - Debug.WriteLine("Skipping repeated entry +" + offset.ToString("X6")); - continue; + 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; } - string oldComment = proj.Comments[offset]; - string newComment = line.Substring(setComment.Length + 7); - if (oldComment == newComment) { - // no change - continue; - } - if (!string.IsNullOrEmpty(oldComment)) { - // overwriting existing entry - Debug.WriteLine("Replacing comment +" + offset.ToString("x6") + - " '" + oldComment + "'"); + prevOffset = offset; + + 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; } - UndoableChange uc = UndoableChange.CreateCommentChange(offset, - oldComment, newComment); - cs.Add(uc); - changed.Add(offset); } detailMsg = "applied " + cs.Count + " changes."; return true; } + + private static bool DeserializeMlc(JavaScriptSerializer ser, string cereal, bool isNote, + out MultiLineComment mlc) { + mlc = null; + SerMultiLineComment smlc; + try { + smlc = ser.Deserialize(cereal); + } catch (Exception ex) { + Debug.WriteLine("Deserialization failed: " + ex.Message); + return false; + } + + if (isNote) { + mlc = new MultiLineComment(smlc.Text, + CommonWPF.Helper.ColorFromInt(smlc.BackgroundColor)); + } else { + mlc = new MultiLineComment(smlc.Text, smlc.BoxMode, smlc.MaxWidth); + } + return true; + } } }