From 7bbe5692bd4162b6f3bbce1805c0e52d878c919c Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Thu, 15 Aug 2019 17:53:12 -0700 Subject: [PATCH] Add C64 encodings to instruction and data operand editors Both dialogs got a couple extra radio buttons for selection of single character operands. The data operand editor got a combo box that lets you specify how it scans for viable strings. Various string scanning methods were made more generic. This got a little strange with auto-detection of low/high ASCII, but that was mostly a matter of keeping the previous code around as a special case. Made C64 Screen Code DCI strings a thing that works. --- Asm65/CharEncoding.cs | 14 +- Asm65/Formatter.cs | 10 +- SourceGen/AppSettings.cs | 9 +- SourceGen/DataAnalysis.cs | 121 +++-- SourceGen/DisplayListSelection.cs | 4 +- SourceGen/FormatDescriptor.cs | 1 + SourceGen/MainController.cs | 2 +- SourceGen/PseudoOp.cs | 28 +- SourceGen/Res/Strings.xaml | 4 + SourceGen/Res/Strings.xaml.cs | 8 + .../SGTestData/Source/2016-char-encoding.S | 6 +- SourceGen/WpfGui/EditAppSettings.xaml.cs | 3 + SourceGen/WpfGui/EditDataOperand.xaml | 47 +- SourceGen/WpfGui/EditDataOperand.xaml.cs | 468 +++++++++++++----- SourceGen/WpfGui/EditInstructionOperand.xaml | 6 +- .../WpfGui/EditInstructionOperand.xaml.cs | 27 +- SourceGen/WpfGui/EditProjectProperties.xaml | 5 - .../WpfGui/EditProjectProperties.xaml.cs | 8 +- 18 files changed, 560 insertions(+), 211 deletions(-) diff --git a/Asm65/CharEncoding.cs b/Asm65/CharEncoding.cs index 511cae7..76134e1 100644 --- a/Asm65/CharEncoding.cs +++ b/Asm65/CharEncoding.cs @@ -106,11 +106,12 @@ namespace Asm65 { return IsExtendedAscii((byte)(val & 0x7f)); } public static char ConvertLowAndHighAscii(byte val) { - if (IsPrintableAscii(val) || IsPrintableHighAscii(val)) { - return (char)(val & 0x7f); - } else { - return UNPRINTABLE_CHAR; - } + //if (IsPrintableAscii(val) || IsPrintableHighAscii(val)) { + // return (char)(val & 0x7f); + //} else { + // return UNPRINTABLE_CHAR; + //} + return ConvertAscii((byte)(val & 0x7f)); } // @@ -256,5 +257,8 @@ namespace Asm65 { return UNPRINTABLE_CHAR; } } + public static char ConvertLowAndHighC64ScreenCode(byte val) { + return ConvertC64ScreenCode((byte)(val & 0x7f)); + } } } diff --git a/Asm65/Formatter.cs b/Asm65/Formatter.cs index dab5172..87ade55 100644 --- a/Asm65/Formatter.cs +++ b/Asm65/Formatter.cs @@ -152,12 +152,10 @@ namespace Asm65 { new Dictionary(); /// - /// Returns the specified DelimiterDef, or a default if not found. + /// Returns the specified DelimiterDef, or null if not found. /// public DelimiterDef Get(CharEncoding.Encoding enc) { - if (!mDelimiters.TryGetValue(enc, out DelimiterDef def)) { - return DOUBLE_QUOTE_DELIM; - } + mDelimiters.TryGetValue(enc, out DelimiterDef def); return def; } public void Set(CharEncoding.Encoding enc, DelimiterDef def) { @@ -528,6 +526,10 @@ namespace Asm65 { } DelimiterDef delimDef = mFormatConfig.mCharDelimiters.Get(enc); + if (delimDef == null) { + return FormatHexValue(value, 2); + } + string fmt = delimDef.FormatStr; Debug.Assert(fmt != null); diff --git a/SourceGen/AppSettings.cs b/SourceGen/AppSettings.cs index f82fd3a..00174f5 100644 --- a/SourceGen/AppSettings.cs +++ b/SourceGen/AppSettings.cs @@ -68,6 +68,9 @@ namespace SourceGen { public const string CLIP_LINE_FORMAT = "clip-line-format"; + // Main project view settings. + public const string PRVW_RECENT_PROJECT_LIST = "prvw-recent-project-list"; + // Symbol-list window options. public const string SYMWIN_SHOW_USER = "symwin-show-user"; public const string SYMWIN_SHOW_AUTO = "symwin-show-auto"; @@ -91,6 +94,9 @@ namespace SourceGen { public const string CDLV_FONT_FAMILY = "cdlv-font-family"; public const string CDLV_FONT_SIZE = "cdlv-font-size"; + // Operand edit settings. + public const string OPED_DEFAULT_STRING_ENCODING = "oped-default-string-encoding"; + // Hex dump viewer settings. public const string HEXD_ASCII_ONLY = "hexd-ascii-only"; public const string HEXD_CHAR_CONV = "hexd-char-conv1"; @@ -105,9 +111,6 @@ namespace SourceGen { public const string SRCGEN_LONG_LABEL_NEW_LINE = "srcgen-long-label-new-line"; public const string SRCGEN_SHOW_CYCLE_COUNTS = "srcgen-show-cycle-counts"; - // Main project view settings. - public const string PRVW_RECENT_PROJECT_LIST = "prvw-recent-project-list"; - // Assembler settings prefix public const string ASM_CONFIG_PREFIX = "asm-config-"; diff --git a/SourceGen/DataAnalysis.cs b/SourceGen/DataAnalysis.cs index d2ff844..1cd62e3 100644 --- a/SourceGen/DataAnalysis.cs +++ b/SourceGen/DataAnalysis.cs @@ -810,7 +810,7 @@ namespace SourceGen { #endif } -#region Static analyzer methods + #region Static analyzer methods /// /// Checks for a repeated run of the same byte. @@ -837,43 +837,63 @@ namespace SourceGen { /// Raw data. /// Offset of first byte in range. /// Offset of last byte in range - /// Set to the number of low-ASCII bytes found. - /// Set to the number of high-ASCII bytes found. - /// Set to the number of non-ASCII bytes found. - public static void CountAsciiBytes(byte[] fileData, int start, int end, - out int lowAscii, out int highAscii, out int nonAscii) { - lowAscii = highAscii = nonAscii = 0; + /// Character test delegate. Must match on both high and + /// low characters. + /// Set to the number of low-range characters found. + /// Set to the number of high-range characters found. + /// Set to the number of non-character bytes found. + public static void CountHighLowBytes(byte[] fileData, int start, int end, + CharEncoding.InclusionTest charTest, + out int lowVal, out int highVal, out int nonChar) { + lowVal = highVal = nonChar = 0; for (int i = start; i <= end; i++) { byte val = fileData[i]; - if (val < 0x20) { - nonAscii++; - } else if (val < 0x7f) { - lowAscii++; - } else if (val < 0xa0) { - nonAscii++; - } else if (val < 0xff) { - highAscii++; + if (!charTest(val)) { + nonChar++; + } else if ((val & 0x80) == 0) { + lowVal++; } else { - nonAscii++; + highVal++; } } } + /// + /// Counts the number of bytes that match the character test. + /// + /// Raw data. + /// Offset of first byte in range. + /// Offset of last byte in range. + /// Character test delegate. + /// Number of matching characters. + public static int CountCharacterBytes(byte[] fileData, int start, int end, + CharEncoding.InclusionTest charTest) { + int count = 0; + for (int i = start; i <= end; i++) { + if (charTest(fileData[i])) { + count++; + } + } + return count; + } + /// /// Counts the number of null-terminated strings in the buffer. /// /// Zero-length strings are allowed but not included in the count. /// - /// Each string must be either high-ASCII or low-ASCII, not a mix. - /// /// If any bad data is found, the scan aborts and returns -1. /// /// Raw data. /// Offset of first byte in range. /// Offset of last byte in range. + /// Character test delegate. + /// If set, the high bit in all character must be the + /// same. Used to enforce a single encoding when "low or high ASCII" is used. /// Number of strings found, or -1 if bad data identified. - public static int RecognizeNullTerminatedStrings(byte[] fileData, int start, int end) { + public static int RecognizeNullTerminatedStrings(byte[] fileData, int start, int end, + CharEncoding.InclusionTest charTest, bool limitHiBit) { // Quick test. if (fileData[end] != 0x00) { return -1; @@ -892,16 +912,17 @@ namespace SourceGen { stringLen = 0; expectedHiBit = -1; } else { - if (expectedHiBit == -1) { - // First byte in string, set hi/lo expectation. - expectedHiBit = val & 0x80; - } else if ((val & 0x80) != expectedHiBit) { - // Mixed ASCII or non-ASCII, fail. - return -1; + if (limitHiBit) { + if (expectedHiBit == -1) { + // First byte in string, set hi/lo expectation. + expectedHiBit = val & 0x80; + } else if ((val & 0x80) != expectedHiBit) { + // Mixed ASCII or non-ASCII, fail. + return -1; + } } - val &= 0x7f; - if (val < 0x20 || val == 0x7f) { - // Non-ASCII, fail. + if (!charTest(val)) { + // Not a matching character, fail. return -1; } stringLen++; @@ -913,16 +934,18 @@ namespace SourceGen { /// /// Counts strings prefixed with an 8-bit length. - /// - /// Each string must be either high-ASCII or low-ASCII, not a mix. /// /// Zero-length strings are allowed but not counted. /// /// Raw data. /// Offset of first byte in range. /// Offset of last byte in range. + /// Character test delegate. + /// If set, the high bit in all character must be the + /// same. Used to enforce a single encoding when "low or high ASCII" is used. /// Number of strings found, or -1 if bad data identified. - public static int RecognizeLen8Strings(byte[] fileData, int start, int end) { + public static int RecognizeLen8Strings(byte[] fileData, int start, int end, + CharEncoding.InclusionTest charTest, bool limitHiBit) { int posn = start; int remaining = end - start + 1; int stringCount = 0; @@ -944,13 +967,12 @@ namespace SourceGen { while (strLen-- != 0) { byte val = fileData[posn++]; - if ((val & 0x80) != expectedHiBit) { + if (limitHiBit && (val & 0x80) != expectedHiBit) { // Mixed ASCII, fail. return -1; } - val &= 0x7f; - if (val < 0x20 || val == 0x7f) { - // Non-ASCII, fail. + if (!charTest(val)) { + // Not a matching character, fail. return -1; } } @@ -961,16 +983,18 @@ namespace SourceGen { /// /// Counts strings prefixed with a 16-bit length. - /// - /// Each string must be either high-ASCII or low-ASCII, not a mix. /// /// Zero-length strings are allowed but not counted. /// /// Raw data. /// Offset of first byte in range. /// Offset of last byte in range. + /// Character test delegate. + /// If set, the high bit in all character must be the + /// same. Used to enforce a single encoding when "low or high ASCII" is used. /// Number of strings found, or -1 if bad data identified. - public static int RecognizeLen16Strings(byte[] fileData, int start, int end) { + public static int RecognizeLen16Strings(byte[] fileData, int start, int end, + CharEncoding.InclusionTest charTest, bool limitHiBit) { int posn = start; int remaining = end - start + 1; int stringCount = 0; @@ -998,13 +1022,12 @@ namespace SourceGen { while (strLen-- != 0) { byte val = fileData[posn++]; - if ((val & 0x80) != expectedHiBit) { + if (limitHiBit && (val & 0x80) != expectedHiBit) { // Mixed ASCII, fail. return -1; } - val &= 0x7f; - if (val < 0x20 || val == 0x7f) { - // Non-ASCII, fail. + if (!charTest(val)) { + // Not a matching character, fail. return -1; } } @@ -1020,11 +1043,16 @@ namespace SourceGen { /// Each string must be at least two bytes. To reduce false-positives, we require /// that all strings have the same hi/lo pattern. /// + /// + /// Not useful for C64Petscii, which mixes high/low characters. + /// /// Raw data. /// Offset of first byte in range. /// Offset of last byte in range. + /// Character test delegate. /// Number of strings found, or -1 if bad data identified. - public static int RecognizeDciStrings(byte[] fileData, int start, int end) { + public static int RecognizeDciStrings(byte[] fileData, int start, int end, + CharEncoding.InclusionTest charTest) { int expectedHiBit = fileData[start] & 0x80; int stringCount = 0; int stringLen = 0; @@ -1048,9 +1076,8 @@ namespace SourceGen { stringLen++; } - val &= 0x7f; - if (val < 0x20 || val == 0x7f) { - // Non-ASCII, fail. + if (!charTest((byte)(val & 0x7f))) { + // Not a matching character, fail. return -1; } } @@ -1104,7 +1131,7 @@ namespace SourceGen { return stringCount; } -#endregion // Static analyzers + #endregion // Static analyzers } } diff --git a/SourceGen/DisplayListSelection.cs b/SourceGen/DisplayListSelection.cs index c974830..04900b5 100644 --- a/SourceGen/DisplayListSelection.cs +++ b/SourceGen/DisplayListSelection.cs @@ -106,8 +106,8 @@ namespace SourceGen { /// /// Argument from SelectionChanged event. public void SelectionChanged(SelectionChangedEventArgs e) { - Debug.WriteLine("SelectionChanged event: Add=" + e.AddedItems.Count + - " Rem=" + e.RemovedItems.Count); + //Debug.WriteLine("SelectionChanged event: Add=" + e.AddedItems.Count + + // " Rem=" + e.RemovedItems.Count); foreach (DisplayList.FormattedParts parts in e.AddedItems) { Debug.Assert(parts.ListIndex >= 0 && parts.ListIndex < mSelection.Length); this[parts.ListIndex] = true; diff --git a/SourceGen/FormatDescriptor.cs b/SourceGen/FormatDescriptor.cs index 715db5d..55c6ee2 100644 --- a/SourceGen/FormatDescriptor.cs +++ b/SourceGen/FormatDescriptor.cs @@ -393,6 +393,7 @@ namespace SourceGen { descr += " ???"; break; } + return descr; } switch (FormatSubType) { diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs index 45b8318..d9a96f4 100644 --- a/SourceGen/MainController.cs +++ b/SourceGen/MainController.cs @@ -2359,7 +2359,7 @@ namespace SourceGen { /// is selected. public SelectionState UpdateSelectionState() { int selCount = mMainWin.CodeListView_GetSelectionCount(); - Debug.WriteLine("UpdateSelectionState: selCount=" + selCount); + //Debug.WriteLine("UpdateSelectionState: selCount=" + selCount); SelectionState state = new SelectionState(); diff --git a/SourceGen/PseudoOp.cs b/SourceGen/PseudoOp.cs index 2ff4230..1d23439 100644 --- a/SourceGen/PseudoOp.cs +++ b/SourceGen/PseudoOp.cs @@ -318,11 +318,19 @@ namespace SourceGen { CharEncoding.Convert charConv; switch (dfd.FormatSubType) { case FormatDescriptor.SubType.Ascii: - charConv = CharEncoding.ConvertAscii; + if (dfd.FormatType == FormatDescriptor.Type.StringDci) { + charConv = CharEncoding.ConvertLowAndHighAscii; + } else { + charConv = CharEncoding.ConvertAscii; + } delDef = delSet.Get(CharEncoding.Encoding.Ascii); break; case FormatDescriptor.SubType.HighAscii: - charConv = CharEncoding.ConvertHighAscii; + if (dfd.FormatType == FormatDescriptor.Type.StringDci) { + charConv = CharEncoding.ConvertLowAndHighAscii; + } else { + charConv = CharEncoding.ConvertHighAscii; + } delDef = delSet.Get(CharEncoding.Encoding.HighAscii); break; case FormatDescriptor.SubType.C64Petscii: @@ -330,7 +338,11 @@ namespace SourceGen { delDef = delSet.Get(CharEncoding.Encoding.C64Petscii); break; case FormatDescriptor.SubType.C64Screen: - charConv = CharEncoding.ConvertC64ScreenCode; + if (dfd.FormatType == FormatDescriptor.Type.StringDci) { + charConv = CharEncoding.ConvertLowAndHighC64ScreenCode; + } else { + charConv = CharEncoding.ConvertC64ScreenCode; + } delDef = delSet.Get(CharEncoding.Encoding.C64ScreenCode); break; default: @@ -340,13 +352,17 @@ namespace SourceGen { break; } + if (delDef == null) { + delDef = Formatter.DOUBLE_QUOTE_DELIM; + } + switch (dfd.FormatType) { case FormatDescriptor.Type.StringGeneric: // Generic character data. popcode = opNames.StrGeneric; break; case FormatDescriptor.Type.StringReverse: - // High or low ASCII, full width specified by formatter. Show characters + // Character data, full width specified by formatter. Show characters // in reverse order. popcode = opNames.StrReverse; revMode = StringOpFormatter.ReverseMode.FullReverse; @@ -377,10 +393,8 @@ namespace SourceGen { popcode = opNames.StrLen16; break; case FormatDescriptor.Type.StringDci: - // High or low ASCII, with high bit on last byte flipped. Only useful - // for ASCII strings. + // High bit on last byte is flipped. popcode = opNames.StrDci; - charConv = CharEncoding.ConvertLowAndHighAscii; break; default: Debug.Assert(false); diff --git a/SourceGen/Res/Strings.xaml b/SourceGen/Res/Strings.xaml index 7342466..6b9e566 100644 --- a/SourceGen/Res/Strings.xaml +++ b/SourceGen/Res/Strings.xaml @@ -112,6 +112,10 @@ limitations under the License. RuntimeData Not Found Please save your project before assembling. The generated source code will be placed in the same directory as the project file. Save Project First + Plain ASCII + Low or High ASCII + C64 PETSCII + C64 Screen Code {1} CPU @ {2} MHz Show Ready diff --git a/SourceGen/Res/Strings.xaml.cs b/SourceGen/Res/Strings.xaml.cs index 74a1cef..753e366 100644 --- a/SourceGen/Res/Strings.xaml.cs +++ b/SourceGen/Res/Strings.xaml.cs @@ -205,6 +205,14 @@ namespace SourceGen.Res { (string)Application.Current.FindResource("str_SaveBeforeAsm"); public static string SAVE_BEFORE_ASM_CAPTION = (string)Application.Current.FindResource("str_SaveBeforeAsmCaption"); + public static string SCAN_LOW_ASCII = + (string)Application.Current.FindResource("str_ScanLowAscii"); + public static string SCAN_LOW_HIGH_ASCII = + (string)Application.Current.FindResource("str_ScanLowHighAscii"); + public static string SCAN_C64_PETSCII = + (string)Application.Current.FindResource("str_ScanC64Petscii"); + public static string SCAN_C64_SCREEN_CODE = + (string)Application.Current.FindResource("str_ScanC64ScreenCode"); public static string SETUP_SYSTEM_SUMMARY_FMT = (string)Application.Current.FindResource("str_SetupSystemSummaryFmt"); public static string SHOW_COL = diff --git a/SourceGen/SGTestData/Source/2016-char-encoding.S b/SourceGen/SGTestData/Source/2016-char-encoding.S index 6f7dd37..64b2e93 100644 --- a/SourceGen/SGTestData/Source/2016-char-encoding.S +++ b/SourceGen/SGTestData/Source/2016-char-encoding.S @@ -1,7 +1,7 @@ ; Copyright 2019 faddenSoft. All Rights Reserved. ; See the LICENSE.txt file for distribution terms (Apache 2.0). ; -; Assembler: ACME (good PETSCII/screen code support) +; Assembler: ACME (for the PETSCII/screen code support) !cpu 65816 * = $1000 @@ -28,6 +28,8 @@ lda #$7f ;EDIT: force to PETSCII lda #$7f ;EDIT: force to screen code + lda #$0d ;verify the instruction operand editor only allows C64SC + ; Single letter in a 16-bit immediate rep #$30 !al @@ -63,7 +65,7 @@ ; Start with the basics !byte $80 !text "low ASCII str" - !byte $80 +; !byte $80 ; let them run together to test scan / dialog behavior !xor $80 { !text "high ASCII str" } diff --git a/SourceGen/WpfGui/EditAppSettings.xaml.cs b/SourceGen/WpfGui/EditAppSettings.xaml.cs index 2fe1c3b..b46656e 100644 --- a/SourceGen/WpfGui/EditAppSettings.xaml.cs +++ b/SourceGen/WpfGui/EditAppSettings.xaml.cs @@ -512,6 +512,9 @@ namespace SourceGen.WpfGui { private void ImportDelimiters(Formatter.DelimiterSet delSet, DelimiterTextBoxes[] boxarr) { foreach (DelimiterTextBoxes boxes in boxarr) { Formatter.DelimiterDef def = delSet.Get(boxes.mEncoding); + if (def == null) { + def = Formatter.DOUBLE_QUOTE_DELIM; + } boxes.mPrefix.Text = def.Prefix; boxes.mOpen.Text = "" + def.OpenDelim; boxes.mClose.Text = "" + def.CloseDelim; diff --git a/SourceGen/WpfGui/EditDataOperand.xaml b/SourceGen/WpfGui/EditDataOperand.xaml index 6cd39aa..78562f7 100644 --- a/SourceGen/WpfGui/EditDataOperand.xaml +++ b/SourceGen/WpfGui/EditDataOperand.xaml @@ -33,8 +33,8 @@ limitations under the License. Select data format ({0} bytes selected): Select data format ({0} bytes selected in {1} groups): - Mixed ASCII ({0} bytes) and non-ASCII ({1} bytes) - Reversed ASCII ({0} bytes) and non-ASCII ({1} bytes) + Mixed character ({0} bytes) and non-character ({1} bytes) + Reversed character ({0} bytes) and non-character ({1} bytes) Null-terminated strings ({0}) Strings prefixed with 8-bit length ({0}) Strings prefixed with 16-bit length ({0}) @@ -64,29 +64,44 @@ limitations under the License. - + - + + + + + + Checked="SimpleDisplay_CheckedChanged"/> + Margin="0,4,18,0" Checked="SimpleDisplay_CheckedChanged"/> - - - + + + + + + - - + + + @@ -106,6 +121,12 @@ limitations under the License. + + + + diff --git a/SourceGen/WpfGui/EditDataOperand.xaml.cs b/SourceGen/WpfGui/EditDataOperand.xaml.cs index 76060e0..757b265 100644 --- a/SourceGen/WpfGui/EditDataOperand.xaml.cs +++ b/SourceGen/WpfGui/EditDataOperand.xaml.cs @@ -21,7 +21,9 @@ using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Controls; +using Asm65; using CommonUtil; +using TextScanMode = SourceGen.ProjectProperties.AnalysisParameters.TextScanMode; namespace SourceGen.WpfGui { /// @@ -77,18 +79,27 @@ namespace SourceGen.WpfGui { /// private Asm65.Formatter mFormatter; - ///// - ///// Set this during initial control configuration, so we know to ignore the CheckedChanged - ///// events. - ///// - //private bool mIsInitialSetup; - /// /// Set to true if, during the initial setup, the format defined by FirstFormatDescriptor /// was unavailable. /// private bool mPreferredFormatUnavailable; + /// + /// Text encoding combo box item. We use the same TextScanMode enum that the + /// uncategorized data analyzer uses. + /// + public class StringEncodingItem { + public string Name { get; private set; } + public TextScanMode Mode { get; private set; } + + public StringEncodingItem(string name, TextScanMode mode) { + Name = name; + Mode = mode; + } + } + public StringEncodingItem[] StringEncodingItems { get; private set; } + // INotifyPropertyChanged implementation public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged([CallerMemberName] string propertyName = "") { @@ -107,6 +118,17 @@ namespace SourceGen.WpfGui { mFormatter = formatter; mSelection = trs; mFirstFormatDescriptor = firstDesc; + + StringEncodingItems = new StringEncodingItem[] { + new StringEncodingItem(Res.Strings.SCAN_LOW_ASCII, + TextScanMode.LowAscii), + new StringEncodingItem(Res.Strings.SCAN_LOW_HIGH_ASCII, + TextScanMode.LowHighAscii), + new StringEncodingItem(Res.Strings.SCAN_C64_PETSCII, + TextScanMode.C64Petscii), + new StringEncodingItem(Res.Strings.SCAN_C64_SCREEN_CODE, + TextScanMode.C64ScreenCode), + }; } private void Window_Loaded(object sender, RoutedEventArgs e) { @@ -116,6 +138,9 @@ namespace SourceGen.WpfGui { // Disable any radio buttons that won't work. AnalyzeRanges(); + // This gets invoked a bit later, from the "selection changed" callback. + //AnalyzeStringRanges(TextScanMode.LowHighAscii); + // Configure the dialog from the FormatDescriptor, if one is available. Debug.WriteLine("First FD: " + mFirstFormatDescriptor); SetControlsFromDescriptor(mFirstFormatDescriptor); @@ -169,6 +194,36 @@ namespace SourceGen.WpfGui { UpdateControls(); } + /// + /// Sets the string encoding combo box to an item that matches the specified mode. If + /// the mode can't be found, an arbitrary entry will be chosen. + /// + private void SetStringEncoding(TextScanMode mode) { + StringEncodingItem choice = null; + foreach (StringEncodingItem item in StringEncodingItems) { + if (item.Mode == mode) { + choice = item; + break; + } + } + if (choice == null) { + choice = StringEncodingItems[1]; + } + stringEncodingComboBox.SelectedItem = choice; + } + + private void StringEncodingComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { + if (!IsLoaded) { + return; + } + StringEncodingItem item = (StringEncodingItem)stringEncodingComboBox.SelectedItem; + AnalyzeStringRanges(item.Mode); + UpdateControls(); + + AppSettings.Global.SetEnum(AppSettings.OPED_DEFAULT_STRING_ENCODING, + typeof(TextScanMode), (int)item.Mode); + } + private void OkButton_Click(object sender, RoutedEventArgs e) { CreateDescriptorListFromControls(); FormatDescriptor.DebugDumpSortedList(Results); @@ -207,7 +262,14 @@ namespace SourceGen.WpfGui { bool focusOnSymbol = !simpleDisplayAsGroupBox.IsEnabled && wantStyle; simpleDisplayAsGroupBox.IsEnabled = wantStyle; if (wantStyle) { - radioSimpleDataAscii.IsEnabled = IsRawAsciiCompatible(simpleWidth, isBigEndian); + // Because this covers multiple items in a data area, we allow the + // "extended" set, which includes some control characters. + radioSimpleDataAscii.IsEnabled = IsCompatibleWithCharSet(simpleWidth, + isBigEndian, CharEncoding.IsExtendedLowOrHighAscii); + radioSimpleDataPetscii.IsEnabled = IsCompatibleWithCharSet(simpleWidth, + isBigEndian, CharEncoding.IsExtendedC64Petscii); + radioSimpleDataScreenCode.IsEnabled = IsCompatibleWithCharSet(simpleWidth, + isBigEndian, CharEncoding.IsExtendedC64ScreenCode); } // Enable the symbolic reference entry box if the "display as" group is enabled. @@ -263,13 +325,6 @@ namespace SourceGen.WpfGui { IEnumerator iter = mSelection.RangeListIterator; - int mixedAsciiOkCount = 0; - int mixedAsciiNotCount = 0; - int nullTermStringCount = 0; - int len8StringCount = 0; - int len16StringCount = 0; - int dciStringCount = 0; - // For each range, check to see if the data within qualifies for the various // options. If any of them fail to meet the criteria, the option is disabled // for all ranges. @@ -277,7 +332,7 @@ namespace SourceGen.WpfGui { TypedRangeSet.TypedRange rng = iter.Current; Debug.WriteLine("Testing [" + rng.Low + ", " + rng.High + "]"); - // Start with the easy ones. Single-byte and dense are always enabled. + // Note single-byte and dense are always enabled. int count = rng.High - rng.Low + 1; Debug.Assert(count > 0); @@ -305,46 +360,110 @@ namespace SourceGen.WpfGui { } else { radioFill.IsEnabled = false; } + } + } + + /// + /// Analyzes the selection to see which string formatting options are suitable. + /// Disables radio buttons and updates labels. + /// + /// Call this when the character encoding selection changes. + /// + private void AnalyzeStringRanges(TextScanMode scanMode) { + Debug.WriteLine("Analyzing string ranges"); + Debug.Assert(IsLoaded); + + int mixedCharOkCount = 0; + int mixedCharNotCount = 0; + int nullTermStringCount = 0; + int len8StringCount = 0; + int len16StringCount = 0; + int dciStringCount = 0; + + CharEncoding.InclusionTest charTest; + switch (scanMode) { + case TextScanMode.LowAscii: + charTest = CharEncoding.IsExtendedAscii; + break; + case TextScanMode.LowHighAscii: + charTest = CharEncoding.IsExtendedLowOrHighAscii; + break; + case TextScanMode.C64Petscii: + charTest = CharEncoding.IsExtendedC64Petscii; + break; + case TextScanMode.C64ScreenCode: + charTest = CharEncoding.IsExtendedC64ScreenCode; + break; + default: + Debug.Assert(false); + charTest = CharEncoding.IsExtendedAscii; + break; + } + + radioStringMixed.IsEnabled = true; + radioStringMixedReverse.IsEnabled = true; + radioStringNullTerm.IsEnabled = (scanMode != TextScanMode.C64ScreenCode); + radioStringLen8.IsEnabled = true; + radioStringLen16.IsEnabled = true; + radioStringDci.IsEnabled = (scanMode != TextScanMode.C64Petscii); + + IEnumerator iter = mSelection.RangeListIterator; + while (iter.MoveNext()) { + TypedRangeSet.TypedRange rng = iter.Current; + Debug.WriteLine("Testing [" + rng.Low + ", " + rng.High + "]"); // See if there's enough string data to make it worthwhile. We use an - // arbitrary threshold of 2+ ASCII characters, and require twice as many - // ASCII as non-ASCII. We arbitrarily require the strings to be either - // high or low ASCII, and treat the other as non-ASCII. (We could relax - // this -- we generate separate items for each string and non-ASCII chunk -- - // but I'm trying to hide the option when the buffer doesn't really seem - // to be holding strings. Could replace with some sort of minimum string - // length requirement?) + // arbitrary threshold of 2+ printable characters, and require twice as many + // printable as non-printable. if (radioStringMixed.IsEnabled) { - int asciiCount; - DataAnalysis.CountAsciiBytes(mFileData, rng.Low, rng.High, - out int lowAscii, out int highAscii, out int nonAscii); - if (highAscii > lowAscii) { - asciiCount = highAscii; - nonAscii += lowAscii; - } else { - asciiCount = lowAscii; - nonAscii += highAscii; - } + if (scanMode == TextScanMode.LowHighAscii) { + // We use a special test that counts low, high, and non-ASCII. + // Whichever form of ASCII has the highest count is the winner, and + // the loser is counted as non-ASCII. + int asciiCount; + DataAnalysis.CountHighLowBytes(mFileData, rng.Low, rng.High, charTest, + out int lowAscii, out int highAscii, out int nonAscii); + if (highAscii > lowAscii) { + asciiCount = highAscii; + nonAscii += lowAscii; + } else { + asciiCount = lowAscii; + nonAscii += highAscii; + } - if (asciiCount >= 2 && asciiCount >= nonAscii * 2) { - // Looks good - mixedAsciiOkCount += asciiCount; - mixedAsciiNotCount += nonAscii; + if (asciiCount >= 2 && asciiCount >= nonAscii * 2) { + // Looks good + mixedCharOkCount += asciiCount; + mixedCharNotCount += nonAscii; + } else { + // Fail + radioStringMixed.IsEnabled = false; + radioStringMixedReverse.IsEnabled = false; + mixedCharOkCount = mixedCharNotCount = -1; + } } else { - // Fail - radioStringMixed.IsEnabled = false; - radioStringMixedReverse.IsEnabled = false; - mixedAsciiOkCount = mixedAsciiNotCount = -1; + int matchCount = DataAnalysis.CountCharacterBytes(mFileData, + rng.Low, rng.High, charTest); + int missCount = (rng.High - rng.Low + 1) - matchCount; + if (matchCount >= 2 && matchCount >= missCount * 2) { + mixedCharOkCount += matchCount; + mixedCharNotCount += missCount; + } else { + // Fail + radioStringMixed.IsEnabled = false; + radioStringMixedReverse.IsEnabled = false; + mixedCharOkCount = mixedCharNotCount = -1; + } } } // Check for null-terminated strings. Zero-length strings are allowed, but // not counted -- we want to have some actual character data. Individual - // strings need to be entirely high-ASCII or low-ASCII, but not all strings + // ASCII strings need to be entirely high-ASCII or low-ASCII, but not all strings // in a region have to be the same. if (radioStringNullTerm.IsEnabled) { int strCount = DataAnalysis.RecognizeNullTerminatedStrings(mFileData, - rng.Low, rng.High); + rng.Low, rng.High, charTest, scanMode == TextScanMode.LowHighAscii); if (strCount > 0) { nullTermStringCount += strCount; } else { @@ -355,7 +474,8 @@ namespace SourceGen.WpfGui { // Check for strings prefixed with an 8-bit length. if (radioStringLen8.IsEnabled) { - int strCount = DataAnalysis.RecognizeLen8Strings(mFileData, rng.Low, rng.High); + int strCount = DataAnalysis.RecognizeLen8Strings(mFileData, rng.Low, rng.High, + charTest, scanMode == TextScanMode.LowHighAscii); if (strCount > 0) { len8StringCount += strCount; } else { @@ -366,7 +486,8 @@ namespace SourceGen.WpfGui { // Check for strings prefixed with a 16-bit length. if (radioStringLen16.IsEnabled) { - int strCount = DataAnalysis.RecognizeLen16Strings(mFileData, rng.Low, rng.High); + int strCount = DataAnalysis.RecognizeLen16Strings(mFileData, rng.Low, rng.High, + charTest, scanMode == TextScanMode.LowHighAscii); if (strCount > 0) { len16StringCount += strCount; } else { @@ -375,10 +496,11 @@ namespace SourceGen.WpfGui { } } - // Check for DCI strings. All strings within a single range must have the + // Check for DCI strings. All strings within the entire range must have the // same "polarity", e.g. low ASCII terminated by high ASCII. if (radioStringDci.IsEnabled) { - int strCount = DataAnalysis.RecognizeDciStrings(mFileData, rng.Low, rng.High); + int strCount = DataAnalysis.RecognizeDciStrings(mFileData, rng.Low, rng.High, + charTest); if (strCount > 0) { dciStringCount += strCount; } else { @@ -390,15 +512,16 @@ namespace SourceGen.WpfGui { // Update the dialog with string and character counts, summed across all regions. + string fmt; const string UNSUP_STR = "xx"; fmt = (string)FindResource("str_StringMixed"); string revfmt = (string)FindResource("str_StringMixedReverse"); - if (mixedAsciiOkCount > 0) { + if (mixedCharOkCount > 0) { Debug.Assert(radioStringMixed.IsEnabled); radioStringMixed.Content = string.Format(fmt, - mixedAsciiOkCount, mixedAsciiNotCount); + mixedCharOkCount, mixedCharNotCount); radioStringMixedReverse.Content = string.Format(revfmt, - mixedAsciiOkCount, mixedAsciiNotCount); + mixedCharOkCount, mixedCharNotCount); } else { Debug.Assert(!radioStringMixed.IsEnabled); radioStringMixed.Content = string.Format(fmt, UNSUP_STR, UNSUP_STR); @@ -440,32 +563,40 @@ namespace SourceGen.WpfGui { Debug.Assert(!radioStringDci.IsEnabled); radioStringDci.Content = string.Format(fmt, UNSUP_STR); } + + // If this invalidated the selected item, reset to Default. + if ((radioStringMixed.IsChecked == true && !radioStringMixed.IsEnabled) || + (radioStringMixedReverse.IsChecked == true && !radioStringMixedReverse.IsEnabled) || + (radioStringNullTerm.IsChecked == true && !radioStringNullTerm.IsEnabled) || + (radioStringLen8.IsChecked == true && !radioStringLen8.IsEnabled) || + (radioStringLen8.IsChecked == true && !radioStringLen8.IsEnabled) || + (radioStringDci.IsChecked == true && !radioStringDci.IsEnabled)) { + + Debug.WriteLine("Previous selection invalidated"); + radioDefaultFormat.IsChecked = true; + } } /// - /// Determines whether the data in the buffer can be represented as ASCII values. + /// Determines whether the data in the buffer can be represented as character values. /// Using ".DD1 'A'" for 0x41 is obvious, but we also allow ".DD2 'A'" for /// 0x41 0x00. 16-bit character constants are more likely as intermediate /// operands, but could be found in data areas. - /// - /// High and low ASCII are allowed, and may be freely mixed. - /// - /// Testing explicitly is probably excessive, and possibly counter-productive if - /// the user is trying to flag an area that is a mix of ASCII and non-ASCII and - /// just wants hex for the rest, but we'll give it a try. /// /// Number of bytes per character. /// Word endian-ness. - /// True if data in all regions can be represented as high or low ASCII. - private bool IsRawAsciiCompatible(int wordWidth, bool isBigEndian) { + /// Character test delegate. + /// True if data in all regions can be represented as a character. + private bool IsCompatibleWithCharSet(int wordWidth, bool isBigEndian, + CharEncoding.InclusionTest charTest) { IEnumerator iter = mSelection.RangeListIterator; while (iter.MoveNext()) { TypedRangeSet.TypedRange rng = iter.Current; Debug.Assert(((rng.High - rng.Low + 1) / wordWidth) * wordWidth == rng.High - rng.Low + 1); for (int i = rng.Low; i <= rng.High; i += wordWidth) { - int val = RawData.GetWord(mFileData, rng.Low, wordWidth, isBigEndian); - if (val < 0x20 || (val >= 0x7f && val < 0xa0) || val >= 0xff) { + int val = RawData.GetWord(mFileData, i, wordWidth, isBigEndian); + if (val != (byte)val || !charTest((byte)val)) { // bad value, fail return false; } @@ -485,11 +616,22 @@ namespace SourceGen.WpfGui { radioSimpleDataHex.IsChecked = true; radioSymbolPartLow.IsChecked = true; + // Get the previous mode selected in the combo box. If the format descriptor + // doesn't specify a string, we'll use this. + TextScanMode textMode = (TextScanMode)AppSettings.Global.GetEnum( + AppSettings.OPED_DEFAULT_STRING_ENCODING, typeof(TextScanMode), + (int)TextScanMode.LowHighAscii); + if (dfd == null) { radioDefaultFormat.IsChecked = true; + SetStringEncoding(textMode); return; } + if (dfd.IsString) { + textMode = TextScanModeFromDescriptor(dfd); + } + RadioButton preferredFormat; switch (dfd.FormatType) { @@ -529,11 +671,14 @@ namespace SourceGen.WpfGui { break; case FormatDescriptor.SubType.Ascii: case FormatDescriptor.SubType.HighAscii: - case FormatDescriptor.SubType.C64Petscii: - case FormatDescriptor.SubType.C64Screen: - // TODO(petscii): update UI radioSimpleDataAscii.IsChecked = true; break; + case FormatDescriptor.SubType.C64Petscii: + radioSimpleDataPetscii.IsChecked = true; + break; + case FormatDescriptor.SubType.C64Screen: + radioSimpleDataScreenCode.IsChecked = true; + break; case FormatDescriptor.SubType.Address: radioSimpleDataAddress.IsChecked = true; break; @@ -601,6 +746,24 @@ namespace SourceGen.WpfGui { mPreferredFormatUnavailable = true; radioDefaultFormat.IsChecked = true; } + + SetStringEncoding(textMode); + } + + private TextScanMode TextScanModeFromDescriptor(FormatDescriptor dfd) { + Debug.Assert(dfd.IsString); + switch (dfd.FormatSubType) { + case FormatDescriptor.SubType.Ascii: + case FormatDescriptor.SubType.HighAscii: + return TextScanMode.LowHighAscii; + case FormatDescriptor.SubType.C64Petscii: + return TextScanMode.C64Petscii; + case FormatDescriptor.SubType.C64Screen: + return TextScanMode.C64ScreenCode; + default: + Debug.Assert(false); + return TextScanMode.LowHighAscii; + } } #endregion Setup @@ -623,6 +786,33 @@ namespace SourceGen.WpfGui { WeakSymbolRef symbolRef = null; int chunkLength = -1; + FormatDescriptor.SubType charSubType; + CharEncoding.InclusionTest charTest; + StringEncodingItem item = (StringEncodingItem)stringEncodingComboBox.SelectedItem; + switch (item.Mode) { + case TextScanMode.LowAscii: + charSubType = FormatDescriptor.SubType.Ascii; + charTest = CharEncoding.IsExtendedAscii; + break; + case TextScanMode.LowHighAscii: + charSubType = FormatDescriptor.SubType.ASCII_GENERIC; + charTest = CharEncoding.IsExtendedLowOrHighAscii; + break; + case TextScanMode.C64Petscii: + charSubType = FormatDescriptor.SubType.C64Petscii; + charTest = CharEncoding.IsExtendedC64Petscii; + break; + case TextScanMode.C64ScreenCode: + charSubType = FormatDescriptor.SubType.C64Screen; + charTest = CharEncoding.IsExtendedC64ScreenCode; + break; + default: + Debug.Assert(false); + charSubType = FormatDescriptor.SubType.ASCII_GENERIC; + charTest = CharEncoding.IsExtendedLowOrHighAscii; + break; + } + // Decode the "display as" panel, if it's relevant. if (radioSimpleDataHex.IsEnabled) { if (radioSimpleDataHex.IsChecked == true) { @@ -632,8 +822,11 @@ namespace SourceGen.WpfGui { } else if (radioSimpleDataBinary.IsChecked == true) { subType = FormatDescriptor.SubType.Binary; } else if (radioSimpleDataAscii.IsChecked == true) { - // TODO(petscii): add PETSCII buttons subType = FormatDescriptor.SubType.ASCII_GENERIC; + } else if (radioSimpleDataPetscii.IsChecked == true) { + subType = FormatDescriptor.SubType.C64Petscii; + } else if (radioSimpleDataScreenCode.IsChecked == true) { + subType = FormatDescriptor.SubType.C64Screen; } else if (radioSimpleDataAddress.IsChecked == true) { subType = FormatDescriptor.SubType.Address; } else if (radioSimpleDataSymbolic.IsChecked == true) { @@ -683,26 +876,23 @@ namespace SourceGen.WpfGui { } else if (radioFill.IsChecked == true) { type = FormatDescriptor.Type.Fill; } else if (radioStringMixed.IsChecked == true) { - // TODO(petscii): encoding format will come from a combo box; that determines - // the subType and the arg to the string-creation functions, which use the - // appropriate char encoding methods to break up the strings type = FormatDescriptor.Type.StringGeneric; - subType = FormatDescriptor.SubType.Ascii; + subType = charSubType; } else if (radioStringMixedReverse.IsChecked == true) { type = FormatDescriptor.Type.StringReverse; - subType = FormatDescriptor.SubType.Ascii; + subType = charSubType; } else if (radioStringNullTerm.IsChecked == true) { type = FormatDescriptor.Type.StringNullTerm; - subType = FormatDescriptor.SubType.Ascii; + subType = charSubType; } else if (radioStringLen8.IsChecked == true) { type = FormatDescriptor.Type.StringL8; - subType = FormatDescriptor.SubType.Ascii; + subType = charSubType; } else if (radioStringLen16.IsChecked == true) { type = FormatDescriptor.Type.StringL16; - subType = FormatDescriptor.SubType.Ascii; + subType = charSubType; } else if (radioStringDci.IsChecked == true) { type = FormatDescriptor.Type.StringDci; - subType = FormatDescriptor.SubType.Ascii; + subType = charSubType; } else { Debug.Assert(false); // default/none @@ -715,11 +905,12 @@ namespace SourceGen.WpfGui { while (iter.MoveNext()) { TypedRangeSet.TypedRange rng = iter.Current; - // TODO(petscii): handle encoding on all four calls switch (type) { case FormatDescriptor.Type.StringGeneric: + CreateMixedStringEntries(rng.Low, rng.High, type, subType, charTest); + break; case FormatDescriptor.Type.StringReverse: - CreateMixedStringEntries(rng.Low, rng.High, type, subType); + CreateMixedStringEntries(rng.Low, rng.High, type, subType, charTest); break; case FormatDescriptor.Type.StringNullTerm: CreateCStringEntries(rng.Low, rng.High, type, subType); @@ -794,69 +985,118 @@ namespace SourceGen.WpfGui { /// /// Creates one or more FormatDescriptor entries for the specified range, adding them - /// to the Results list. + /// to the Results list. Runs of character data are output as generic strings, while any + /// non-character data is output as individual bytes. /// + /// + /// This is the only string create function that accepts a mix of valid and invalid + /// characters. + /// /// Offset of first byte in range. /// Offset of last byte in range. + /// String type (Generic or Reverse). /// String sub-type. + /// Character test delegate. private void CreateMixedStringEntries(int low, int high, FormatDescriptor.Type type, - FormatDescriptor.SubType subType) { + FormatDescriptor.SubType subType, CharEncoding.InclusionTest charTest) { int stringStart = -1; - int highBit = 0; int cur; - for (cur = low; cur <= high; cur++) { - byte val = mFileData[cur]; - if (CommonUtil.TextUtil.IsHiLoAscii(val)) { - // is ASCII - if (stringStart >= 0) { - // was in a string - if (highBit != (val & 0x80)) { - // end of string due to high bit flip, output - CreateStringOrByte(stringStart, cur - stringStart, subType); - // start a new string - stringStart = cur; + + if (subType == FormatDescriptor.SubType.ASCII_GENERIC) { + int highBit = 0; + for (cur = low; cur <= high; cur++) { + byte val = mFileData[cur]; + if (charTest(val)) { + // is ASCII + if (stringStart >= 0) { + // was in a string + if (highBit != (val & 0x80)) { + // end of string due to high bit flip, output + CreateGenericStringOrByte(stringStart, cur - stringStart, + type, subType); + // start a new string + stringStart = cur; + } else { + // still in string, keep going + } } else { - // still in string, keep going + // wasn't in a string, start one + stringStart = cur; + } + highBit = val & 0x80; + } else { + // not ASCII + if (stringStart >= 0) { + // was in a string, output it + CreateGenericStringOrByte(stringStart, cur - stringStart, + type, subType); + stringStart = -1; + } + // output as single byte + CreateByteFD(cur, FormatDescriptor.SubType.Hex); + } + } + } else { + for (cur = low; cur <= high; cur++) { + byte val = mFileData[cur]; + if (charTest(val)) { + // is character + if (stringStart < 0) { + // mark this as the start of the string + stringStart = cur; } } else { - // wasn't in a string, start one - stringStart = cur; + // not character + if (stringStart >= 0) { + // was in a string, output it + CreateGenericStringOrByte(stringStart, cur - stringStart, + type, subType); + stringStart = -1; + } + // output as single byte + CreateByteFD(cur, FormatDescriptor.SubType.Hex); } - highBit = val & 0x80; - } else { - // not ASCII - if (stringStart >= 0) { - // was in a string, output it - CreateStringOrByte(stringStart, cur - stringStart, subType); - stringStart = -1; - } - // output as single byte - CreateByteFD(cur, FormatDescriptor.SubType.Hex); } + } if (stringStart >= 0) { // close out the string - CreateStringOrByte(stringStart, cur - stringStart, subType); + CreateGenericStringOrByte(stringStart, cur - stringStart, type, subType); } } + private FormatDescriptor.SubType ResolveAsciiGeneric(int offset, + FormatDescriptor.SubType subType) { + if (subType == FormatDescriptor.SubType.ASCII_GENERIC) { + if ((mFileData[offset] & 0x80) != 0) { + subType = FormatDescriptor.SubType.HighAscii; + } else { + subType = FormatDescriptor.SubType.Ascii; + } + } + return subType; + } + /// - /// Creates a format descriptor for ASCII data. If the data is only one byte long, - /// a single-byte ASCII char item is emitted instead. + /// Creates a format descriptor for character data. If the data is only one byte long, + /// a single-byte character item is emitted instead. /// /// Offset of first byte. /// Length of string. - /// String sub-type. - private void CreateStringOrByte(int offset, int length, FormatDescriptor.SubType subType) { + /// String type (Generic or Reverse). + /// String sub-type. If set to ASCII_GENERIC, this will + /// refine the sub-type. + private void CreateGenericStringOrByte(int offset, int length, + FormatDescriptor.Type type, FormatDescriptor.SubType subType) { Debug.Assert(length > 0); + subType = ResolveAsciiGeneric(offset, subType); if (length == 1) { // Single byte, output as single char rather than 1-byte string. We use the // same encoding as the rest of the string. CreateByteFD(offset, subType); } else { FormatDescriptor dfd; - dfd = FormatDescriptor.Create(length, - FormatDescriptor.Type.StringGeneric, subType); + dfd = FormatDescriptor.Create(length, type, subType); Results.Add(offset, dfd); } } @@ -886,7 +1126,7 @@ namespace SourceGen.WpfGui { if (mFileData[i] == 0x00) { // End of string. Zero-length strings are allowed. FormatDescriptor dfd = FormatDescriptor.Create( - i - startOffset + 1, type, subType); + i - startOffset + 1, type, ResolveAsciiGeneric(startOffset, subType)); Results.Add(startOffset, dfd); startOffset = i + 1; } else { @@ -917,7 +1157,8 @@ namespace SourceGen.WpfGui { length++; } // Zero-length strings are allowed. - FormatDescriptor dfd = FormatDescriptor.Create(length, type, subType); + FormatDescriptor dfd = FormatDescriptor.Create(length, type, + ResolveAsciiGeneric(i, subType)); Results.Add(i, dfd); i += length; } @@ -948,8 +1189,9 @@ namespace SourceGen.WpfGui { if ((val & 0x80) == endMask) { // found the end of a string int length = (i - stringStart) + 1; - FormatDescriptor dfd = FormatDescriptor.Create(length, type, subType); - Results.Add(stringStart < i ? stringStart : i, dfd); + FormatDescriptor dfd = FormatDescriptor.Create(length, type, + ResolveAsciiGeneric(stringStart, subType)); + Results.Add(stringStart, dfd); stringStart = i + 1; } } diff --git a/SourceGen/WpfGui/EditInstructionOperand.xaml b/SourceGen/WpfGui/EditInstructionOperand.xaml index 9c4c837..c4c01e6 100644 --- a/SourceGen/WpfGui/EditInstructionOperand.xaml +++ b/SourceGen/WpfGui/EditInstructionOperand.xaml @@ -36,7 +36,11 @@ limitations under the License. Checked="MainGroup_CheckedChanged"/> - + + diff --git a/SourceGen/WpfGui/EditInstructionOperand.xaml.cs b/SourceGen/WpfGui/EditInstructionOperand.xaml.cs index 4dcc9c2..99b6295 100644 --- a/SourceGen/WpfGui/EditInstructionOperand.xaml.cs +++ b/SourceGen/WpfGui/EditInstructionOperand.xaml.cs @@ -171,8 +171,19 @@ namespace SourceGen.WpfGui { private void Window_Loaded(object sender, RoutedEventArgs e) { mIsInitialSetup = true; - // Can this be represented as high or low ASCII? - asciiButton.IsEnabled = CommonUtil.TextUtil.IsHiLoAscii(mOperandValue); + // Can this be represented as a character? We only allow the printable set + // here, not the extended set (which includes control characters). + if (mOperandValue == (byte) mOperandValue) { + asciiButton.IsEnabled = + CharEncoding.IsPrintableLowOrHighAscii((byte)mOperandValue); + petsciiButton.IsEnabled = + CharEncoding.IsPrintableC64Petscii((byte)mOperandValue); + screenCodeButton.IsEnabled = + CharEncoding.IsPrintableC64ScreenCode((byte)mOperandValue); + } else { + asciiButton.IsEnabled = petsciiButton.IsEnabled = screenCodeButton.IsEnabled = + false; + } // Configure the dialog from the FormatDescriptor, if one is available. SetControlsFromDescriptor(FormatDescriptor); @@ -475,9 +486,14 @@ namespace SourceGen.WpfGui { break; case FormatDescriptor.SubType.Ascii: case FormatDescriptor.SubType.HighAscii: - // TODO(petscii): encoding asciiButton.IsChecked = true; break; + case FormatDescriptor.SubType.C64Petscii: + petsciiButton.IsChecked = true; + break; + case FormatDescriptor.SubType.C64Screen: + screenCodeButton.IsChecked = true; + break; case FormatDescriptor.SubType.Symbol: Debug.Assert(dfd.HasSymbol); symbolButton.IsChecked = true; @@ -555,12 +571,15 @@ namespace SourceGen.WpfGui { } else if (binaryButton.IsChecked == true) { subType = FormatDescriptor.SubType.Binary; } else if (asciiButton.IsChecked == true) { - // TODO(petscii): encoding if (mOperandValue > 0x7f) { subType = FormatDescriptor.SubType.HighAscii; } else { subType = FormatDescriptor.SubType.Ascii; } + } else if (petsciiButton.IsChecked == true) { + subType = FormatDescriptor.SubType.C64Petscii; + } else if (screenCodeButton.IsChecked == true) { + subType = FormatDescriptor.SubType.C64Screen; } else if (symbolButton.IsChecked == true) { subType = FormatDescriptor.SubType.Symbol; } else { diff --git a/SourceGen/WpfGui/EditProjectProperties.xaml b/SourceGen/WpfGui/EditProjectProperties.xaml index 9a030e8..5112965 100644 --- a/SourceGen/WpfGui/EditProjectProperties.xaml +++ b/SourceGen/WpfGui/EditProjectProperties.xaml @@ -38,11 +38,6 @@ limitations under the License. WDC W65C02S WDC W65C816S - Plain ASCII - Plain or High ASCII - C64 PETSCII - C64 Screen Code - None (disabled) Simple ("L1234") diff --git a/SourceGen/WpfGui/EditProjectProperties.xaml.cs b/SourceGen/WpfGui/EditProjectProperties.xaml.cs index 83aaab1..e5195d8 100644 --- a/SourceGen/WpfGui/EditProjectProperties.xaml.cs +++ b/SourceGen/WpfGui/EditProjectProperties.xaml.cs @@ -97,13 +97,13 @@ namespace SourceGen.WpfGui { new CpuItem((string)FindResource("str_65816"), CpuDef.CpuType.Cpu65816), }; DefaultTextScanModeItems = new DefaultTextScanMode[] { - new DefaultTextScanMode((string)FindResource("str_LowAscii"), + new DefaultTextScanMode(Res.Strings.SCAN_LOW_ASCII, TextScanMode.LowAscii), - new DefaultTextScanMode((string)FindResource("str_LowHighAscii"), + new DefaultTextScanMode(Res.Strings.SCAN_LOW_HIGH_ASCII, TextScanMode.LowHighAscii), - new DefaultTextScanMode((string)FindResource("str_C64Petscii"), + new DefaultTextScanMode(Res.Strings.SCAN_C64_PETSCII, TextScanMode.C64Petscii), - new DefaultTextScanMode((string)FindResource("str_C64ScreenCode"), + new DefaultTextScanMode(Res.Strings.SCAN_C64_SCREEN_CODE, TextScanMode.C64ScreenCode), }; MinCharsItems = new MinCharsItem[] {