mirror of
https://github.com/fadden/6502bench.git
synced 2024-08-20 11:28:58 +00:00
Added accelerator keys to Mixed and Null strings. (Issue #67) Added units to string counts. (Issue #68) Added proper handling for plural/singular for bytes and strings. Changed N/A indicator from "xx" to "--".
1422 lines
65 KiB
C#
1422 lines
65 KiB
C#
/*
|
|
* Copyright 2019 faddenSoft
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
|
|
using Asm65;
|
|
using CommonUtil;
|
|
using TextScanMode = SourceGen.ProjectProperties.AnalysisParameters.TextScanMode;
|
|
|
|
namespace SourceGen.WpfGui {
|
|
/// <summary>
|
|
/// Data operand editor.
|
|
/// </summary>
|
|
public partial class EditDataOperand : Window, INotifyPropertyChanged {
|
|
/// <summary>
|
|
/// Result set that describes the formatting to perform. Not all regions will have
|
|
/// the same format, e.g. the "mixed ASCII" mode will alternate strings and bytes
|
|
/// (rather than a dedicated "mixed ASCII" format type).
|
|
/// </summary>
|
|
public SortedList<int, FormatDescriptor> Results { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Selected offsets. An otherwise contiguous range of offsets can be broken up
|
|
/// by user-specified labels and address discontinuities, so this needs to be
|
|
/// processed by range.
|
|
/// </summary>
|
|
private TypedRangeSet mSelection;
|
|
|
|
/// <summary>
|
|
/// FormatDescriptor from the first offset. May be null if the offset doesn't
|
|
/// have a format descriptor specified. This will be used to configure the
|
|
/// dialog controls if the format is suited to the selection. The goal is to
|
|
/// make single-item editing work as expected.
|
|
/// </summary>
|
|
public FormatDescriptor mFirstFormatDescriptor;
|
|
|
|
/// <summary>
|
|
/// Project reference.
|
|
/// </summary>
|
|
private DisasmProject mProject;
|
|
|
|
/// <summary>
|
|
/// Raw file data.
|
|
/// </summary>
|
|
private byte[] mFileData;
|
|
|
|
/// <summary>
|
|
/// Symbol table to use when resolving symbolic values.
|
|
/// </summary>
|
|
private SymbolTable mSymbolTable;
|
|
|
|
/// <summary>
|
|
/// Map of offsets to addresses.
|
|
/// </summary>
|
|
private AddressMap mAddrMap;
|
|
|
|
/// <summary>
|
|
/// Formatter to use when displaying addresses and hex values.
|
|
/// </summary>
|
|
private Asm65.Formatter mFormatter;
|
|
|
|
/// <summary>
|
|
/// Set to true if, during the initial setup, the format defined by FirstFormatDescriptor
|
|
/// was unavailable.
|
|
/// </summary>
|
|
private bool mPreferredFormatUnavailable;
|
|
|
|
/// <summary>
|
|
/// Set to true when input is valid. Controls whether the OK button is enabled.
|
|
/// </summary>
|
|
public bool IsValid {
|
|
get { return mIsValid; }
|
|
set {
|
|
mIsValid = value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
private bool mIsValid;
|
|
|
|
public int MaxDenseBytesPerLine {
|
|
get { return mMaxDenseBytesPerLine; }
|
|
set {
|
|
if (mMaxDenseBytesPerLine != value) {
|
|
mMaxDenseBytesPerLine = value;
|
|
OnPropertyChanged();
|
|
UpdateControls();
|
|
|
|
if (IsLoaded) {
|
|
// Set the radio button when text is typed.
|
|
radioDenseHexLimited.IsChecked = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
private int mMaxDenseBytesPerLine;
|
|
|
|
/// <summary>
|
|
/// Text encoding combo box item. We use the same TextScanMode enum that the
|
|
/// uncategorized data analyzer uses.
|
|
/// </summary>
|
|
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; }
|
|
|
|
public class JunkAlignmentItem {
|
|
public string Description { get; private set; }
|
|
public FormatDescriptor.SubType FormatSubType { get; private set; }
|
|
|
|
public JunkAlignmentItem(string descr, FormatDescriptor.SubType subFmt) {
|
|
Description = descr;
|
|
FormatSubType = subFmt;
|
|
}
|
|
}
|
|
public List<JunkAlignmentItem> JunkAlignmentItems { get; private set; }
|
|
|
|
// INotifyPropertyChanged implementation
|
|
public event PropertyChangedEventHandler PropertyChanged;
|
|
private void OnPropertyChanged([CallerMemberName] string propertyName = "") {
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
}
|
|
|
|
|
|
public EditDataOperand(Window owner, DisasmProject project,
|
|
Asm65.Formatter formatter, TypedRangeSet trs, FormatDescriptor firstDesc) {
|
|
InitializeComponent();
|
|
Owner = owner;
|
|
DataContext = this;
|
|
|
|
mProject = project;
|
|
mFileData = project.FileData;
|
|
mSymbolTable = project.SymbolTable;
|
|
mAddrMap = project.AddrMap;
|
|
mFormatter = formatter;
|
|
mSelection = trs;
|
|
mFirstFormatDescriptor = firstDesc;
|
|
|
|
MaxDenseBytesPerLine = AppSettings.Global.GetInt(AppSettings.OPED_DENSE_HEX_LIMIT, 8);
|
|
|
|
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),
|
|
};
|
|
|
|
GetMinMaxAlignment(out FormatDescriptor.SubType min, out FormatDescriptor.SubType max);
|
|
//Debug.WriteLine("ALIGN: min=" + min + " max=" + max);
|
|
Debug.Assert(min == FormatDescriptor.SubType.None ^ // both or neither are None
|
|
max != FormatDescriptor.SubType.None);
|
|
|
|
int junkSel = 0;
|
|
string noAlign = (string)FindResource("str_AlignmentNone");
|
|
string alignFmt = (string)FindResource("str_AlignmentItemFmt");
|
|
JunkAlignmentItems = new List<JunkAlignmentItem>();
|
|
JunkAlignmentItems.Add(new JunkAlignmentItem(noAlign, FormatDescriptor.SubType.None));
|
|
if (min != FormatDescriptor.SubType.None) {
|
|
int index = 1;
|
|
// We assume the enum values are consecutive and ascending.
|
|
FormatDescriptor.SubType end = (FormatDescriptor.SubType)(((int)max) + 1);
|
|
while (min != end) {
|
|
int pwr = FormatDescriptor.AlignmentToPower(min);
|
|
string endStr = mFormatter.FormatHexValue(1 << pwr, 4);
|
|
JunkAlignmentItems.Add(new JunkAlignmentItem(
|
|
string.Format(alignFmt, 1 << pwr, endStr), min));
|
|
|
|
// See if this matches previous value.
|
|
if (mFirstFormatDescriptor != null &&
|
|
mFirstFormatDescriptor.FormatType == FormatDescriptor.Type.Junk &&
|
|
mFirstFormatDescriptor.FormatSubType == min) {
|
|
junkSel = index;
|
|
}
|
|
|
|
// Advance.
|
|
min = (FormatDescriptor.SubType)(((int)min) + 1);
|
|
index++;
|
|
}
|
|
}
|
|
junkAlignComboBox.SelectedIndex = junkSel;
|
|
}
|
|
|
|
private void Window_Loaded(object sender, RoutedEventArgs e) {
|
|
DateTime startWhen = DateTime.Now;
|
|
|
|
// Determine which of the various options is suitable for the selected offsets.
|
|
// 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);
|
|
|
|
if (mPreferredFormatUnavailable) {
|
|
// This can happen when e.g. a bunch of stuff is formatted as null-terminated
|
|
// strings. We don't recognize a lone zero as a string, but we allow it if
|
|
// it's next to a bunch of others. If you come back later and try to format
|
|
// just that one byte, you end up here.
|
|
// TODO(maybe): make it more obvious what's going on?
|
|
Debug.WriteLine("NOTE: preferred format unavailable");
|
|
}
|
|
|
|
UpdateControls();
|
|
|
|
Debug.WriteLine("EditData dialog load time: " +
|
|
(DateTime.Now - startWhen).TotalMilliseconds + " ms");
|
|
}
|
|
|
|
private void Window_ContentRendered(object sender, EventArgs e) {
|
|
// Start with the focus in the text box if the initial format allows for a
|
|
// symbolic reference. This way they can start typing immediately.
|
|
if (simpleDisplayAsGroupBox.IsEnabled) {
|
|
symbolEntryTextBox.Focus();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles Checked event for all buttons in Main group.
|
|
/// </summary>
|
|
private void MainGroup_CheckedChanged(object sender, EventArgs e) {
|
|
// Enable/disable the style group and the low/high/bank radio group.
|
|
// Update preview window.
|
|
UpdateControls();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles Checked event for radio buttons in the Display group.
|
|
/// group box.
|
|
/// </summary>
|
|
private void SimpleDisplay_CheckedChanged(object sender, EventArgs e) {
|
|
// Enable/disable the low/high/bank radio group.
|
|
UpdateControls();
|
|
}
|
|
|
|
private void SymbolEntryTextBox_TextChanged(object sender, TextChangedEventArgs e) {
|
|
// Make sure Symbol is checked if they're typing text in.
|
|
//Debug.Assert(radioSimpleDataSymbolic.IsEnabled);
|
|
radioSimpleDataSymbolic.IsChecked = true;
|
|
// Update OK button based on symbol validity.
|
|
UpdateControls();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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);
|
|
DialogResult = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates all of the controls to reflect the current internal state.
|
|
/// </summary>
|
|
private void UpdateControls() {
|
|
if (!IsLoaded) {
|
|
return;
|
|
}
|
|
|
|
// Configure the simple data "display as" style box.
|
|
bool wantStyle = false;
|
|
int simpleWidth = -1;
|
|
bool isBigEndian = false;
|
|
if (radioSingleBytes.IsChecked == true) {
|
|
wantStyle = true;
|
|
simpleWidth = 1;
|
|
} else if (radio16BitLittle.IsChecked == true) {
|
|
wantStyle = true;
|
|
simpleWidth = 2;
|
|
} else if (radio16BitBig.IsChecked == true) {
|
|
wantStyle = true;
|
|
simpleWidth = 2;
|
|
isBigEndian = true;
|
|
} else if (radio24BitLittle.IsChecked == true) {
|
|
wantStyle = true;
|
|
simpleWidth = 3;
|
|
} else if (radio32BitLittle.IsChecked == true) {
|
|
wantStyle = true;
|
|
simpleWidth = 4;
|
|
}
|
|
bool focusOnSymbol = !simpleDisplayAsGroupBox.IsEnabled && wantStyle;
|
|
simpleDisplayAsGroupBox.IsEnabled = wantStyle;
|
|
if (wantStyle) {
|
|
// 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.
|
|
// That way instead of "click 16-bit", "click symbol", "enter symbol", the user
|
|
// can skip the second step.
|
|
symbolEntryTextBox.IsEnabled = simpleDisplayAsGroupBox.IsEnabled;
|
|
|
|
// Part panel is enabled when Symbol is checked. (Now handled in XAML.)
|
|
//symbolPartPanel.IsEnabled = (radioSimpleDataSymbolic.IsChecked == true);
|
|
|
|
// If we just enabled the group box, set the focus on the symbol entry box. This
|
|
// removes another click from the steps, though it's a bit aggressive if you're
|
|
// trying to arrow your way through the items.
|
|
if (focusOnSymbol) {
|
|
symbolEntryTextBox.Focus();
|
|
}
|
|
|
|
// Disable the alignment pop-up unless Junk is selected.
|
|
junkAlignComboBox.IsEnabled = (radioJunk.IsChecked == true);
|
|
|
|
bool isOk = true;
|
|
if (radioSimpleDataSymbolic.IsChecked == true) {
|
|
// Just check for correct format. References to non-existent labels are allowed.
|
|
Symbol.TrimAndValidateLabel(symbolEntryTextBox.Text,
|
|
mFormatter.NonUniqueLabelPrefix, out isOk, out bool unused1,
|
|
out bool unused2, out bool unused3, out Symbol.LabelAnnotation unused4);
|
|
|
|
// Actually, let's discourage references to auto-labels and variables.
|
|
if (isOk && mSymbolTable.TryGetValue(symbolEntryTextBox.Text, out Symbol sym)) {
|
|
isOk = sym.SymbolSource != Symbol.Source.Auto &&
|
|
sym.SymbolSource != Symbol.Source.Variable;
|
|
}
|
|
}
|
|
IsValid = isOk;
|
|
|
|
// If dense hex with a limit is selected, check the value.
|
|
if (radioDenseHexLimited.IsChecked == true) {
|
|
if (MaxDenseBytesPerLine > 0) {
|
|
AppSettings.Global.SetInt(AppSettings.OPED_DENSE_HEX_LIMIT,
|
|
MaxDenseBytesPerLine);
|
|
} else {
|
|
IsValid = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
#region Setup
|
|
|
|
/// <summary>
|
|
/// Determines the minimum and maximum alignment values, based on the sizes of the
|
|
/// regions and the address they end on. Used for .align.
|
|
/// </summary>
|
|
/// <param name="min">Minimum allowed format, or None.</param>
|
|
/// <param name="max">Maximum allowed format, or None.</param>
|
|
private void GetMinMaxAlignment(out FormatDescriptor.SubType min,
|
|
out FormatDescriptor.SubType max) {
|
|
min = max = FormatDescriptor.SubType.None;
|
|
|
|
int maxLenPow = -1;
|
|
int minAlignPow = 65535;
|
|
|
|
IEnumerator<TypedRangeSet.TypedRange> iter = mSelection.RangeListIterator;
|
|
while (iter.MoveNext()) {
|
|
TypedRangeSet.TypedRange rng = iter.Current;
|
|
int length = rng.High - rng.Low + 1;
|
|
Debug.Assert(length > 0);
|
|
|
|
// The goal is to find an instruction that fills an entire region with zeroes
|
|
// or junk bytes for the sole purpose of ending at a specific boundary.
|
|
//
|
|
// If we have a 100-byte region that ends at address $103f (inclusive), it
|
|
// can't be the result of an assembler alignment directive. "align $40" would
|
|
// have stopped at $1000, "align $80" would have continued on to $107f.
|
|
//
|
|
// Alignment of junk whose last byte $103f could be due to Align2, Align4 (1-3
|
|
// bytes at $103d/e/f), Align8 (1-7 bytes at $1039-f), and so on, up to Align64.
|
|
// The size of the buffer determines the minimum value, the end address
|
|
// determines the maximum.
|
|
//
|
|
// Bear in mind that assembler alignment directives will do nothing if the
|
|
// address is already aligned: Align256 at $1000 generates no output. So we
|
|
// cannot use Align8 on a buffer of length 8.
|
|
|
|
// Count the trailing 1 bits in the address. This gets us the power of 2
|
|
// alignment value. Note alignPow will be zero if the last byte is stored at
|
|
// an even address.
|
|
int endAddress = mAddrMap.OffsetToAddress(rng.High) & 0x0000ffff;
|
|
int alignPow = BitTwiddle.CountTrailingZeroes(~endAddress);
|
|
|
|
// Round length up to next highest power of 2, and compute Log2(). Unfortunately
|
|
// .NET Standard 2.0 doesn't have Math.Log2(). Note we want the next-highest
|
|
// even if it's already a power of 2.
|
|
int lenRound = BitTwiddle.NextHighestPowerOf2(length);
|
|
int lenPow = BitTwiddle.CountTrailingZeroes(lenRound);
|
|
Debug.Assert(lenPow > 0); // length==1 -> lenRound=2 --> lenPow=1
|
|
|
|
// Want the biggest minimum value and the smallest maximum value.
|
|
if (maxLenPow < lenPow) {
|
|
maxLenPow = lenPow;
|
|
}
|
|
if (minAlignPow > alignPow) {
|
|
minAlignPow = alignPow;
|
|
}
|
|
|
|
if (maxLenPow > minAlignPow) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
min = FormatDescriptor.PowerToAlignment(maxLenPow);
|
|
max = FormatDescriptor.PowerToAlignment(minAlignPow);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Analyzes the selection to see which data formatting options are suitable.
|
|
/// Disables radio buttons and updates labels.
|
|
///
|
|
/// Call this once, when the dialog is first loaded.
|
|
/// </summary>
|
|
private void AnalyzeRanges() {
|
|
Debug.Assert(mSelection.Count != 0);
|
|
|
|
string fmt, infoStr;
|
|
if (mSelection.RangeCount == 1 && mSelection.Count == 1) {
|
|
infoStr = (string)FindResource("str_SingleByte");
|
|
} else if (mSelection.RangeCount == 1) {
|
|
fmt = (string)FindResource("str_SingleGroup");
|
|
infoStr = string.Format(fmt, mSelection.Count);
|
|
} else {
|
|
fmt = (string)FindResource("str_MultiGroup");
|
|
infoStr = string.Format(fmt, mSelection.Count, mSelection.RangeCount);
|
|
}
|
|
selectFormatLabel.Text = infoStr;
|
|
|
|
IEnumerator<TypedRangeSet.TypedRange> iter = mSelection.RangeListIterator;
|
|
|
|
// 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.
|
|
while (iter.MoveNext()) {
|
|
TypedRangeSet.TypedRange rng = iter.Current;
|
|
Debug.WriteLine("Testing [" + rng.Low + ", " + rng.High + "]");
|
|
|
|
// Note single-byte and dense are always enabled.
|
|
|
|
int count = rng.High - rng.Low + 1;
|
|
Debug.Assert(count > 0);
|
|
if ((count & 0x01) != 0) {
|
|
// not divisible by 2, disallow 16-bit entries
|
|
radio16BitLittle.IsEnabled = false;
|
|
radio16BitBig.IsEnabled = false;
|
|
}
|
|
if ((count & 0x03) != 0) {
|
|
// not divisible by 4, disallow 32-bit entries
|
|
radio32BitLittle.IsEnabled = false;
|
|
}
|
|
if ((count / 3) * 3 != count) {
|
|
// not divisible by 3, disallow 24-bit entries
|
|
radio24BitLittle.IsEnabled = false;
|
|
}
|
|
|
|
|
|
// Check for run of bytes (2 or more of the same thing). Remember that
|
|
// we check this one region at a time, and each region could have different
|
|
// bytes, but so long as the bytes are all the same within a region we're good.
|
|
if (radioFill.IsEnabled && count > 1 &&
|
|
DataAnalysis.RecognizeRun(mFileData, rng.Low, rng.High) == count) {
|
|
// LGTM
|
|
} else {
|
|
radioFill.IsEnabled = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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 = true;
|
|
|
|
IEnumerator<TypedRangeSet.TypedRange> 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+ printable characters, and require twice as many
|
|
// printable as non-printable.
|
|
if (radioStringMixed.IsEnabled) {
|
|
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
|
|
mixedCharOkCount += asciiCount;
|
|
mixedCharNotCount += nonAscii;
|
|
} else {
|
|
// Fail
|
|
radioStringMixed.IsEnabled = false;
|
|
radioStringMixedReverse.IsEnabled = false;
|
|
mixedCharOkCount = mixedCharNotCount = -1;
|
|
}
|
|
} else {
|
|
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
|
|
// 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, charTest, scanMode == TextScanMode.LowHighAscii);
|
|
if (strCount > 0) {
|
|
nullTermStringCount += strCount;
|
|
} else {
|
|
radioStringNullTerm.IsEnabled = false;
|
|
nullTermStringCount = -1;
|
|
}
|
|
}
|
|
|
|
// Check for strings prefixed with an 8-bit length.
|
|
if (radioStringLen8.IsEnabled) {
|
|
int strCount = DataAnalysis.RecognizeLen8Strings(mFileData, rng.Low, rng.High,
|
|
charTest, scanMode == TextScanMode.LowHighAscii);
|
|
if (strCount > 0) {
|
|
len8StringCount += strCount;
|
|
} else {
|
|
radioStringLen8.IsEnabled = false;
|
|
len8StringCount = -1;
|
|
}
|
|
}
|
|
|
|
// Check for strings prefixed with a 16-bit length.
|
|
if (radioStringLen16.IsEnabled) {
|
|
int strCount = DataAnalysis.RecognizeLen16Strings(mFileData, rng.Low, rng.High,
|
|
charTest, scanMode == TextScanMode.LowHighAscii);
|
|
if (strCount > 0) {
|
|
len16StringCount += strCount;
|
|
} else {
|
|
radioStringLen16.IsEnabled = false;
|
|
len16StringCount = -1;
|
|
}
|
|
}
|
|
|
|
// 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,
|
|
charTest);
|
|
if (strCount > 0) {
|
|
dciStringCount += strCount;
|
|
} else {
|
|
radioStringDci.IsEnabled = false;
|
|
dciStringCount = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the dialog with string and character counts, summed across all regions.
|
|
|
|
string UNSUP_STR = (string)FindResource("str_NotApplicable");
|
|
string fmt;
|
|
fmt = (string)FindResource("str_StringMixed");
|
|
string revfmt = (string)FindResource("str_StringMixedReverse");
|
|
if (mixedCharOkCount > 0) {
|
|
Debug.Assert(radioStringMixed.IsEnabled);
|
|
radioStringMixed.Content = string.Format(fmt,
|
|
FormatByteCount(mixedCharOkCount), FormatByteCount(mixedCharNotCount));
|
|
radioStringMixedReverse.Content = string.Format(revfmt,
|
|
FormatByteCount(mixedCharOkCount), FormatByteCount(mixedCharNotCount));
|
|
} else {
|
|
Debug.Assert(!radioStringMixed.IsEnabled);
|
|
radioStringMixed.Content = string.Format(fmt, UNSUP_STR, UNSUP_STR);
|
|
radioStringMixedReverse.Content = string.Format(revfmt, UNSUP_STR, UNSUP_STR);
|
|
}
|
|
|
|
Debug.Assert((nullTermStringCount > 0) ^ (radioStringNullTerm.IsEnabled == false));
|
|
fmt = (string)FindResource("str_StringNullTerm");
|
|
radioStringNullTerm.Content = FormatStringOption(fmt, nullTermStringCount);
|
|
|
|
Debug.Assert((len8StringCount > 0) ^ (radioStringLen8.IsEnabled == false));
|
|
fmt = (string)FindResource("str_StringLen8");
|
|
radioStringLen8.Content = FormatStringOption(fmt, len8StringCount);
|
|
|
|
Debug.Assert((len16StringCount > 0) ^ (radioStringLen16.IsEnabled == false));
|
|
fmt = (string)FindResource("str_StringLen16");
|
|
radioStringLen16.Content = FormatStringOption(fmt, len16StringCount);
|
|
|
|
Debug.Assert((dciStringCount > 0) ^ (radioStringDci.IsEnabled == false));
|
|
fmt = (string)FindResource("str_StringDci");
|
|
radioStringDci.Content = FormatStringOption(fmt, dciStringCount);
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
private string FormatByteCount(int count) {
|
|
string fmt;
|
|
if (count <= 0) {
|
|
return (string)FindResource("str_NotApplicable");
|
|
} else if (count == 1) {
|
|
fmt = (string)FindResource("str_ByteSingleFmt");
|
|
} else {
|
|
fmt = (string)FindResource("str_BytePluralFmt");
|
|
}
|
|
return string.Format(fmt, count);
|
|
}
|
|
|
|
private string FormatStringOption(string fmt, int count) {
|
|
if (count <= 0) {
|
|
return string.Format(fmt, (string)FindResource("str_NotApplicable"));
|
|
} else if (count == 1) {
|
|
string fmtSingleString = (string)FindResource("str_StringSingleFmt");
|
|
return string.Format(fmt,
|
|
string.Format(fmtSingleString, count));
|
|
} else {
|
|
string fmtPluralString = (string)FindResource("str_StringPluralFmt");
|
|
return string.Format(fmt,
|
|
string.Format(fmtPluralString, count));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="wordWidth">Number of bytes per character.</param>
|
|
/// <param name="isBigEndian">Word endian-ness.</param>
|
|
/// <param name="charTest">Character test delegate.</param>
|
|
/// <returns>True if data in all regions can be represented as a character.</returns>
|
|
private bool IsCompatibleWithCharSet(int wordWidth, bool isBigEndian,
|
|
CharEncoding.InclusionTest charTest) {
|
|
IEnumerator<TypedRangeSet.TypedRange> 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, i, wordWidth, isBigEndian);
|
|
if (val != (byte)val || !charTest((byte)val)) {
|
|
// bad value, fail
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Configures the dialog controls based on the provided format descriptor. If
|
|
/// the desired options are unavailable, a suitable default is selected instead.
|
|
///
|
|
/// Call from the Loaded event.
|
|
/// </summary>
|
|
/// <param name="dfd">FormatDescriptor to use.</param>
|
|
private void SetControlsFromDescriptor(FormatDescriptor dfd) {
|
|
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) {
|
|
case FormatDescriptor.Type.NumericLE:
|
|
case FormatDescriptor.Type.NumericBE:
|
|
switch (dfd.Length) {
|
|
case 1:
|
|
preferredFormat = radioSingleBytes;
|
|
break;
|
|
case 2:
|
|
preferredFormat =
|
|
(dfd.FormatType == FormatDescriptor.Type.NumericLE ?
|
|
radio16BitLittle : radio16BitBig);
|
|
break;
|
|
case 3:
|
|
preferredFormat = radio24BitLittle;
|
|
break;
|
|
case 4:
|
|
preferredFormat = radio32BitLittle;
|
|
break;
|
|
default:
|
|
Debug.Assert(false);
|
|
preferredFormat = radioDefaultFormat;
|
|
break;
|
|
}
|
|
if (preferredFormat.IsEnabled) {
|
|
switch (dfd.FormatSubType) {
|
|
case FormatDescriptor.SubType.None:
|
|
case FormatDescriptor.SubType.Hex:
|
|
radioSimpleDataHex.IsChecked = true;
|
|
break;
|
|
case FormatDescriptor.SubType.Decimal:
|
|
radioSimpleDataDecimal.IsChecked = true;
|
|
break;
|
|
case FormatDescriptor.SubType.Binary:
|
|
radioSimpleDataBinary.IsChecked = true;
|
|
break;
|
|
case FormatDescriptor.SubType.Ascii:
|
|
case FormatDescriptor.SubType.HighAscii:
|
|
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;
|
|
case FormatDescriptor.SubType.Symbol:
|
|
radioSimpleDataSymbolic.IsChecked = true;
|
|
switch (dfd.SymbolRef.ValuePart) {
|
|
case WeakSymbolRef.Part.Low:
|
|
radioSymbolPartLow.IsChecked = true;
|
|
break;
|
|
case WeakSymbolRef.Part.High:
|
|
radioSymbolPartHigh.IsChecked = true;
|
|
break;
|
|
case WeakSymbolRef.Part.Bank:
|
|
radioSymbolPartBank.IsChecked = true;
|
|
break;
|
|
default:
|
|
Debug.Assert(false);
|
|
break;
|
|
}
|
|
Debug.Assert(dfd.HasSymbol);
|
|
symbolEntryTextBox.Text = Symbol.ConvertLabelForDisplay(
|
|
dfd.SymbolRef.Label, Symbol.LabelAnnotation.None,
|
|
true, mFormatter);
|
|
break;
|
|
default:
|
|
Debug.Assert(false);
|
|
break;
|
|
}
|
|
} else {
|
|
// preferred format not enabled; leave Hex/Low checked
|
|
}
|
|
break;
|
|
case FormatDescriptor.Type.StringGeneric:
|
|
preferredFormat = radioStringMixed;
|
|
break;
|
|
case FormatDescriptor.Type.StringReverse:
|
|
preferredFormat = radioStringMixedReverse;
|
|
break;
|
|
case FormatDescriptor.Type.StringNullTerm:
|
|
preferredFormat = radioStringNullTerm;
|
|
break;
|
|
case FormatDescriptor.Type.StringL8:
|
|
preferredFormat = radioStringLen8;
|
|
break;
|
|
case FormatDescriptor.Type.StringL16:
|
|
preferredFormat = radioStringLen16;
|
|
break;
|
|
case FormatDescriptor.Type.StringDci:
|
|
preferredFormat = radioStringDci;
|
|
break;
|
|
case FormatDescriptor.Type.Dense:
|
|
preferredFormat = radioDenseHex;
|
|
break;
|
|
case FormatDescriptor.Type.Fill:
|
|
preferredFormat = radioFill;
|
|
break;
|
|
case FormatDescriptor.Type.Junk:
|
|
preferredFormat = radioJunk;
|
|
break;
|
|
default:
|
|
// Should not be here.
|
|
Debug.Assert(false);
|
|
preferredFormat = radioDefaultFormat;
|
|
break;
|
|
}
|
|
|
|
if (preferredFormat.IsEnabled) {
|
|
preferredFormat.IsChecked = true;
|
|
} else {
|
|
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
|
|
|
|
#region FormatDescriptor creation
|
|
|
|
/// <summary>
|
|
/// Creates a list of FormatDescriptors, based on the current control configuration.
|
|
///
|
|
/// The entries in the list are guaranteed to be sorted by start address and not
|
|
/// overlap.
|
|
///
|
|
/// We assume that whatever the control gives us is correct, e.g. it's not going
|
|
/// to tell us to put a buffer full of zeroes into a DCI string.
|
|
/// </summary>
|
|
/// <returns>Result list.</returns>
|
|
private void CreateDescriptorListFromControls() {
|
|
FormatDescriptor.Type type = FormatDescriptor.Type.Default;
|
|
FormatDescriptor.SubType subType = FormatDescriptor.SubType.None;
|
|
WeakSymbolRef symbolRef = null;
|
|
|
|
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) {
|
|
subType = FormatDescriptor.SubType.Hex;
|
|
} else if (radioSimpleDataDecimal.IsChecked == true) {
|
|
subType = FormatDescriptor.SubType.Decimal;
|
|
} else if (radioSimpleDataBinary.IsChecked == true) {
|
|
subType = FormatDescriptor.SubType.Binary;
|
|
} else if (radioSimpleDataAscii.IsChecked == true) {
|
|
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) {
|
|
WeakSymbolRef.Part part;
|
|
if (radioSymbolPartLow.IsChecked == true) {
|
|
part = WeakSymbolRef.Part.Low;
|
|
} else if (radioSymbolPartHigh.IsChecked == true) {
|
|
part = WeakSymbolRef.Part.High;
|
|
} else if (radioSymbolPartBank.IsChecked == true) {
|
|
part = WeakSymbolRef.Part.Bank;
|
|
} else {
|
|
Debug.Assert(false);
|
|
part = WeakSymbolRef.Part.Low;
|
|
}
|
|
subType = FormatDescriptor.SubType.Symbol;
|
|
|
|
string weakLabel = symbolEntryTextBox.Text;
|
|
// Deal with non-unique labels. If the label refers to an existing
|
|
// symbol, use its label, which will have the tag. If the label doesn't
|
|
// have a match, discard it -- we don't support weak refs to ambiguous
|
|
// non-unique symbols.
|
|
string trimLabel = Symbol.TrimAndValidateLabel(weakLabel,
|
|
mFormatter.NonUniqueLabelPrefix, out bool isValid, out bool unused1,
|
|
out bool unused2, out bool hasNonUniquePrefix,
|
|
out Symbol.LabelAnnotation unused3);
|
|
if (isValid && hasNonUniquePrefix) {
|
|
// We want to find the match that's closest to the thing we're
|
|
// referencing, but that's awkward when there's multiple ranges and
|
|
// multiple ways of interpreting the data. So as a simple measure
|
|
// we just grab the lowest offset in the first range.
|
|
IEnumerator<TypedRangeSet.TypedRange> oiter = mSelection.RangeListIterator;
|
|
oiter.MoveNext();
|
|
TypedRangeSet.TypedRange rng = oiter.Current;
|
|
int matchOffset = rng.Low;
|
|
|
|
Symbol osym = mProject.FindBestNonUniqueLabel(trimLabel, matchOffset);
|
|
if (osym != null) {
|
|
trimLabel = osym.Label;
|
|
} else {
|
|
Debug.WriteLine("Attempt to create ref to nonexistant non-unique sym");
|
|
subType = FormatDescriptor.SubType.Hex;
|
|
}
|
|
}
|
|
symbolRef = new WeakSymbolRef(trimLabel, part);
|
|
} else {
|
|
Debug.Assert(false);
|
|
}
|
|
} else {
|
|
subType = 0; // set later, or doesn't matter
|
|
}
|
|
|
|
// Decode the main format.
|
|
int chunkLength = -1;
|
|
if (radioDefaultFormat.IsChecked == true) {
|
|
// Default/None; note this would create a multi-byte Default format, which isn't
|
|
// really allowed. What we actually want to do is remove the explicit formatting
|
|
// from all spanned offsets, so we use a dedicated type for that.
|
|
type = FormatDescriptor.Type.REMOVE;
|
|
} else if (radioSingleBytes.IsChecked == true) {
|
|
type = FormatDescriptor.Type.NumericLE;
|
|
chunkLength = 1;
|
|
} else if (radio16BitLittle.IsChecked == true) {
|
|
type = FormatDescriptor.Type.NumericLE;
|
|
chunkLength = 2;
|
|
} else if (radio16BitBig.IsChecked == true) {
|
|
type = FormatDescriptor.Type.NumericBE;
|
|
chunkLength = 2;
|
|
} else if (radio24BitLittle.IsChecked == true) {
|
|
type = FormatDescriptor.Type.NumericLE;
|
|
chunkLength = 3;
|
|
} else if (radio32BitLittle.IsChecked == true) {
|
|
type = FormatDescriptor.Type.NumericLE;
|
|
chunkLength = 4;
|
|
} else if (radioDenseHex.IsChecked == true || radioDenseHexLimited.IsChecked == true) {
|
|
type = FormatDescriptor.Type.Dense;
|
|
} else if (radioFill.IsChecked == true) {
|
|
type = FormatDescriptor.Type.Fill;
|
|
} else if (radioJunk.IsChecked == true) {
|
|
type = FormatDescriptor.Type.Junk;
|
|
JunkAlignmentItem comboItem = (JunkAlignmentItem)junkAlignComboBox.SelectedItem;
|
|
subType = comboItem.FormatSubType;
|
|
} else if (radioStringMixed.IsChecked == true) {
|
|
type = FormatDescriptor.Type.StringGeneric;
|
|
subType = charSubType;
|
|
} else if (radioStringMixedReverse.IsChecked == true) {
|
|
type = FormatDescriptor.Type.StringReverse;
|
|
subType = charSubType;
|
|
} else if (radioStringNullTerm.IsChecked == true) {
|
|
type = FormatDescriptor.Type.StringNullTerm;
|
|
subType = charSubType;
|
|
} else if (radioStringLen8.IsChecked == true) {
|
|
type = FormatDescriptor.Type.StringL8;
|
|
subType = charSubType;
|
|
} else if (radioStringLen16.IsChecked == true) {
|
|
type = FormatDescriptor.Type.StringL16;
|
|
subType = charSubType;
|
|
} else if (radioStringDci.IsChecked == true) {
|
|
type = FormatDescriptor.Type.StringDci;
|
|
subType = charSubType;
|
|
} else {
|
|
Debug.Assert(false);
|
|
// default/none
|
|
}
|
|
|
|
|
|
Results = new SortedList<int, FormatDescriptor>();
|
|
|
|
IEnumerator<TypedRangeSet.TypedRange> iter = mSelection.RangeListIterator;
|
|
while (iter.MoveNext()) {
|
|
TypedRangeSet.TypedRange rng = iter.Current;
|
|
|
|
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, charTest);
|
|
break;
|
|
case FormatDescriptor.Type.StringNullTerm:
|
|
CreateCStringEntries(rng.Low, rng.High, type, subType);
|
|
break;
|
|
case FormatDescriptor.Type.StringL8:
|
|
case FormatDescriptor.Type.StringL16:
|
|
CreateLengthStringEntries(rng.Low, rng.High, type, subType);
|
|
break;
|
|
case FormatDescriptor.Type.StringDci:
|
|
CreateDciStringEntries(rng.Low, rng.High, type, subType);
|
|
break;
|
|
case FormatDescriptor.Type.Dense:
|
|
if (radioDenseHexLimited.IsChecked == true) {
|
|
int low = rng.Low;
|
|
while (low <= rng.High) {
|
|
int remaining = rng.High - low + 1;
|
|
int subLen = Math.Min(remaining, MaxDenseBytesPerLine);
|
|
CreateSimpleEntries(type, subType, -1, null, low, low + subLen - 1);
|
|
low += subLen;
|
|
chunkLength -= subLen;
|
|
}
|
|
} else {
|
|
CreateSimpleEntries(type, subType, chunkLength, null,
|
|
rng.Low, rng.High);
|
|
}
|
|
break;
|
|
default:
|
|
CreateSimpleEntries(type, subType, chunkLength, symbolRef,
|
|
rng.Low, rng.High);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates one or more FormatDescriptor entries for the specified range, adding them
|
|
/// to the Results list.
|
|
///
|
|
/// This will either create one entry that spans the entire range (for e.g. strings
|
|
/// and bulk data), or create equal-sized chunks.
|
|
/// </summary>
|
|
/// <param name="type">Region data type.</param>
|
|
/// <param name="subType">Region data sub-type.</param>
|
|
/// <param name="chunkLength">Length of a chunk, or -1 for full buffer.</param>
|
|
/// <param name="symbolRef">Symbol reference, or null if not applicable.</param>
|
|
/// <param name="low">Offset of first byte in range.</param>
|
|
/// <param name="high">Offset of last byte in range.</param>
|
|
private void CreateSimpleEntries(FormatDescriptor.Type type,
|
|
FormatDescriptor.SubType subType, int chunkLength,
|
|
WeakSymbolRef symbolRef, int low, int high) {
|
|
|
|
if (chunkLength == -1) {
|
|
chunkLength = (high - low) + 1;
|
|
}
|
|
Debug.Assert(((high - low + 1) / chunkLength) * chunkLength == high - low + 1);
|
|
|
|
// Either we have one chunk, or we have multiple chunks with the same type and
|
|
// length. Either way, we only need to create the descriptor once. (This is
|
|
// safe because FormatDescriptor instances are immutable.)
|
|
//
|
|
// The one exception to this is ASCII values for non-string data, because we have
|
|
// to dig the low vs. high value out of the data itself.
|
|
FormatDescriptor dfd;
|
|
if (subType == FormatDescriptor.SubType.Symbol) {
|
|
dfd = FormatDescriptor.Create(chunkLength, symbolRef,
|
|
type == FormatDescriptor.Type.NumericBE);
|
|
} else {
|
|
dfd = FormatDescriptor.Create(chunkLength, type, subType);
|
|
}
|
|
while (low <= high) {
|
|
if (subType == FormatDescriptor.SubType.ASCII_GENERIC) {
|
|
// should not be REMOVE with a meaningful subtype
|
|
Debug.Assert(dfd.IsNumeric);
|
|
int val = RawData.GetWord(mFileData, low, dfd.Length,
|
|
type == FormatDescriptor.Type.NumericBE);
|
|
FormatDescriptor.SubType actualSubType = (val > 0x7f) ?
|
|
FormatDescriptor.SubType.HighAscii : FormatDescriptor.SubType.Ascii;
|
|
if (actualSubType != dfd.FormatSubType) {
|
|
// replace the descriptor
|
|
dfd = FormatDescriptor.Create(chunkLength, type, actualSubType);
|
|
}
|
|
}
|
|
|
|
Results.Add(low, dfd);
|
|
low += chunkLength;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates one or more FormatDescriptor entries for the specified range, adding them
|
|
/// to the Results list. Runs of character data are output as generic strings, while any
|
|
/// non-character data is output as individual bytes.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This is the only string create function that accepts a mix of valid and invalid
|
|
/// characters.
|
|
/// </remarks>
|
|
/// <param name="low">Offset of first byte in range.</param>
|
|
/// <param name="high">Offset of last byte in range.</param>
|
|
/// <param name="type">String type (Generic or Reverse).</param>
|
|
/// <param name="subType">String sub-type.</param>
|
|
/// <param name="charTest">Character test delegate.</param>
|
|
private void CreateMixedStringEntries(int low, int high, FormatDescriptor.Type type,
|
|
FormatDescriptor.SubType subType, CharEncoding.InclusionTest charTest) {
|
|
int stringStart = -1;
|
|
int 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 {
|
|
// 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 {
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
}
|
|
if (stringStart >= 0) {
|
|
// close out the string
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a format descriptor for character data. If the data is only one byte long,
|
|
/// a single-byte character item is emitted instead.
|
|
/// </summary>
|
|
/// <param name="offset">Offset of first byte.</param>
|
|
/// <param name="length">Length of string.</param>
|
|
/// <param name="type">String type (Generic or Reverse).</param>
|
|
/// <param name="subType">String sub-type. If set to ASCII_GENERIC, this will
|
|
/// refine the sub-type.</param>
|
|
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, type, subType);
|
|
Results.Add(offset, dfd);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a format descriptor for a single-byte numeric value.
|
|
/// </summary>
|
|
/// <param name="offset">File offset.</param>
|
|
/// <param name="subType">How to format the item.</param>
|
|
private void CreateByteFD(int offset, FormatDescriptor.SubType subType) {
|
|
FormatDescriptor dfd = FormatDescriptor.Create(1,
|
|
FormatDescriptor.Type.NumericLE, subType);
|
|
Results.Add(offset, dfd);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates one or more FormatDescriptor entries for the specified range, adding them
|
|
/// to the Results list.
|
|
/// </summary>
|
|
/// <param name="low">Offset of first byte in range.</param>
|
|
/// <param name="high">Offset of last byte in range.</param>
|
|
/// <param name="subType">String sub-type.</param>
|
|
private void CreateCStringEntries(int low, int high, FormatDescriptor.Type type,
|
|
FormatDescriptor.SubType subType) {
|
|
int startOffset = low;
|
|
for (int i = low; i <= high; i++) {
|
|
if (mFileData[i] == 0x00) {
|
|
// End of string. Zero-length strings are allowed.
|
|
FormatDescriptor dfd = FormatDescriptor.Create(
|
|
i - startOffset + 1, type, ResolveAsciiGeneric(startOffset, subType));
|
|
Results.Add(startOffset, dfd);
|
|
startOffset = i + 1;
|
|
} else {
|
|
// keep going
|
|
}
|
|
}
|
|
|
|
// Earlier analysis guaranteed that the last byte in the buffer is 0x00.
|
|
Debug.Assert(startOffset == high + 1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates one or more FormatDescriptor entries for the specified range, adding them
|
|
/// to the Results list.
|
|
/// </summary>
|
|
/// <param name="low">Offset of first byte in range.</param>
|
|
/// <param name="high">Offset of last byte in range.</param>
|
|
/// <param name="subType">String sub-type.</param>
|
|
private void CreateLengthStringEntries(int low, int high, FormatDescriptor.Type type,
|
|
FormatDescriptor.SubType subType) {
|
|
int i;
|
|
for (i = low; i <= high;) {
|
|
int length = mFileData[i];
|
|
if (type == FormatDescriptor.Type.StringL16) {
|
|
length |= mFileData[i + 1] << 8;
|
|
length += 2;
|
|
} else {
|
|
length++;
|
|
}
|
|
// Zero-length strings are allowed.
|
|
FormatDescriptor dfd = FormatDescriptor.Create(length, type,
|
|
ResolveAsciiGeneric(i, subType));
|
|
Results.Add(i, dfd);
|
|
i += length;
|
|
}
|
|
|
|
Debug.Assert(i == high + 1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates one or more FormatDescriptor entries for the specified range, adding them
|
|
/// to the Results list.
|
|
/// </summary>
|
|
/// <param name="low">Offset of first byte in range.</param>
|
|
/// <param name="high">Offset of last byte in range.</param>
|
|
/// <param name="subType">String sub-type.</param>
|
|
private void CreateDciStringEntries(int low, int high, FormatDescriptor.Type type,
|
|
FormatDescriptor.SubType subType) {
|
|
int end, endMask;
|
|
|
|
end = high + 1;
|
|
|
|
// Zero-length strings aren't a thing for DCI. The analyzer requires that all
|
|
// strings in a region have the same polarity, so just grab the last byte.
|
|
endMask = mFileData[end - 1] & 0x80;
|
|
|
|
int stringStart = low;
|
|
for (int i = low; i != end; i++) {
|
|
byte val = mFileData[i];
|
|
if ((val & 0x80) == endMask) {
|
|
// found the end of a string
|
|
int length = (i - stringStart) + 1;
|
|
FormatDescriptor dfd = FormatDescriptor.Create(length, type,
|
|
ResolveAsciiGeneric(stringStart, subType));
|
|
Results.Add(stringStart, dfd);
|
|
stringStart = i + 1;
|
|
}
|
|
}
|
|
|
|
Debug.Assert(stringStart == end);
|
|
}
|
|
|
|
#endregion FormatDescriptor creation
|
|
}
|
|
}
|