/* * Copyright 2018 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.Diagnostics; using System.Windows.Forms; using CommonUtil; namespace SourceGen.AppForms { public partial class EditData : Form { /// /// 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). /// public SortedList Results { get; private set; } /// /// 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. /// public TypedRangeSet Selection { private get; set; } /// /// 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. /// public FormatDescriptor FirstFormatDescriptor { private get; set; } /// /// Raw file data. /// private byte[] mFileData; /// /// Symbol table to use when resolving symbolic values. /// private SymbolTable mSymbolTable; /// /// Formatter to use when displaying addresses and hex values. /// 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; public EditData(byte[] fileData, SymbolTable symbolTable, Asm65.Formatter formatter) { InitializeComponent(); mFileData = fileData; mSymbolTable = symbolTable; mFormatter = formatter; //Results = new List(); } private void EditData_Load(object sender, EventArgs e) { DateTime startWhen = DateTime.Now; mIsInitialSetup = true; // Determine which of the various options is suitable for the selected offsets. // Disable any radio buttons that won't work. AnalyzeRanges(); // Configure the dialog from the FormatDescriptor, if one is available. Debug.WriteLine("First FD: " + FirstFormatDescriptor); SetControlsFromDescriptor(FirstFormatDescriptor); 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"); } mIsInitialSetup = false; UpdateControls(); Debug.WriteLine("EditData dialog load time: " + (DateTime.Now - startWhen).TotalMilliseconds + " ms"); } private void EditData_Shown(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.Enabled) { symbolEntryTextBox.Focus(); } } /// /// Handles CheckedChanged event for all radio buttons in main group. This will /// fire twice when a radio button is clicked (once to un-check the old, once /// to check the new). /// private void MainGroup_CheckedChanged(object sender, EventArgs e) { // Enable/disable the style group and the low/high/bank radio group. // Update preview window. UpdateControls(); } /// /// Handles CheckedChanged event for radio buttons in the simple-data "display as" /// group box. /// private void SimpleDisplay_CheckedChanged(object sender, EventArgs e) { // Enable/disable the low/high/bank radio group. UpdateControls(); } /// /// Handles CheckedChanged event for all radio buttons in symbol-part group. /// private void PartGroup_CheckedChanged(object sender, EventArgs e) { // not currently using a preview window; could add one for single items? } private void symbolEntryTextBox_TextChanged(object sender, EventArgs e) { // Make sure Symbol is checked if they're typing text in. Debug.Assert(radioSimpleDataSymbolic.Enabled); radioSimpleDataSymbolic.Checked = true; // Update OK button based on symbol validity. UpdateControls(); } private void okButton_Click(object sender, EventArgs e) { CreateDescriptorListFromControls(); FormatDescriptor.DebugDumpSortedList(Results); } /// /// Updates all of the controls to reflect the current internal state. /// private void UpdateControls() { if (mIsInitialSetup) { return; } // Configure the simple data "display as" style box. bool wantStyle = false; int simpleWidth = -1; bool isBigEndian = false; if (radioSingleBytes.Checked) { wantStyle = true; simpleWidth = 1; } else if (radio16BitLittle.Checked) { wantStyle = true; simpleWidth = 2; } else if (radio16BitBig.Checked) { wantStyle = true; simpleWidth = 2; isBigEndian = true; } else if (radio24BitLittle.Checked) { wantStyle = true; simpleWidth = 3; } else if (radio32BitLittle.Checked) { wantStyle = true; simpleWidth = 4; } bool focusOnSymbol = !simpleDisplayAsGroupBox.Enabled && wantStyle; simpleDisplayAsGroupBox.Enabled = wantStyle; if (wantStyle) { // TODO(soon): compute on first need and save results; this is getting called // 2x as radio buttons are hit, and might be slow on large data sets radioSimpleDataAscii.Enabled = IsRawAsciiCompatible(simpleWidth, isBigEndian); } // 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.Enabled = simpleDisplayAsGroupBox.Enabled; symbolPartPanel.Enabled = radioSimpleDataSymbolic.Checked; // 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(); } bool isOk = true; if (radioSimpleDataSymbolic.Checked) { // Just check for correct format. References to non-existent labels are allowed. isOk = Asm65.Label.ValidateLabel(symbolEntryTextBox.Text); // Actually, let's discourage references to auto-labels. if (isOk && mSymbolTable.TryGetValue(symbolEntryTextBox.Text, out Symbol sym)) { isOk = sym.SymbolSource != Symbol.Source.Auto; } } okButton.Enabled = isOk; } /// /// 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. /// private void AnalyzeRanges() { Debug.Assert(Selection.Count != 0); string fmt = (Selection.RangeCount == 1) ? Properties.Resources.FMT_FORMAT_SINGLE_GROUP : Properties.Resources.FMT_FORMAT_MULTIPLE_GROUPS; selectFormatLabel.Text = string.Format(fmt, Selection.Count, Selection.RangeCount); IEnumerator iter = Selection.RangeListIterator; int mixedAsciiOkCount = 0; int mixedAsciiNotCount = 0; int nullTermStringCount = 0; int len8StringCount = 0; int len16StringCount = 0; int dciStringCount = 0; //int revDciStringCount = 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. while (iter.MoveNext()) { TypedRangeSet.TypedRange rng = iter.Current; Debug.WriteLine("Testing [" + rng.Low + ", " + rng.High + "]"); // Start with the easy ones. 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.Enabled = false; radio16BitBig.Enabled = false; } if ((count & 0x03) != 0) { // not divisible by 4, disallow 32-bit entries radio32BitLittle.Enabled = false; } if ((count / 3) * 3 != count) { // not divisible by 3, disallow 24-bit entries radio24BitLittle.Enabled = 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.Enabled && count > 1 && DataAnalysis.RecognizeRun(mFileData, rng.Low, rng.High) == count) { // LGTM } else { radioFill.Enabled = false; } // 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?) if (radioStringMixed.Enabled) { 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 (asciiCount >= 2 && asciiCount >= nonAscii * 2) { // Looks good mixedAsciiOkCount += asciiCount; mixedAsciiNotCount += nonAscii; } else { // Fail radioStringMixed.Enabled = false; radioStringMixedReverse.Enabled = false; mixedAsciiOkCount = mixedAsciiNotCount = -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 // in a region have to be the same. if (radioStringNullTerm.Enabled) { int strCount = DataAnalysis.RecognizeNullTerminatedStrings(mFileData, rng.Low, rng.High); if (strCount > 0) { nullTermStringCount += strCount; } else { radioStringNullTerm.Enabled = false; nullTermStringCount = -1; } } // Check for strings prefixed with an 8-bit length. if (radioStringLen8.Enabled) { int strCount = DataAnalysis.RecognizeLen8Strings(mFileData, rng.Low, rng.High); if (strCount > 0) { len8StringCount += strCount; } else { radioStringLen8.Enabled = false; len8StringCount = -1; } } // Check for strings prefixed with a 16-bit length. if (radioStringLen16.Enabled) { int strCount = DataAnalysis.RecognizeLen16Strings(mFileData, rng.Low, rng.High); if (strCount > 0) { len16StringCount += strCount; } else { radioStringLen16.Enabled = false; len16StringCount = -1; } } // Check for DCI strings. All strings within a single range must have the // same "polarity", e.g. low ASCII terminated by high ASCII. if (radioStringDci.Enabled) { int strCount = DataAnalysis.RecognizeDciStrings(mFileData, rng.Low, rng.High); if (strCount > 0) { dciStringCount += strCount; } else { radioStringDci.Enabled = false; dciStringCount = -1; } } //// Check for reverse DCI strings. All strings within a single range must have the //// same "polarity", e.g. low ASCII terminated by high ASCII. //if (radioStringDciReverse.Enabled) { // int strCount = DataAnalysis.RecognizeReverseDciStrings(mFileData, // rng.Low, rng.High); // if (strCount > 0) { // revDciStringCount += strCount; // } else { // radioStringDciReverse.Enabled = false; // revDciStringCount = -1; // } //} } // Update the dialog with string and character counts, summed across all regions. if (mixedAsciiOkCount > 0) { Debug.Assert(radioStringMixed.Enabled); radioStringMixed.Text = string.Format(radioStringMixed.Text, mixedAsciiOkCount, mixedAsciiNotCount); radioStringMixedReverse.Text = string.Format(radioStringMixedReverse.Text, mixedAsciiOkCount, mixedAsciiNotCount); } else { Debug.Assert(!radioStringMixed.Enabled); radioStringMixed.Text = string.Format(radioStringMixed.Text, "xx", "xx"); radioStringMixedReverse.Text = string.Format(radioStringMixedReverse.Text, "xx", "xx"); } if (nullTermStringCount > 0) { Debug.Assert(radioStringNullTerm.Enabled); radioStringNullTerm.Text = string.Format(radioStringNullTerm.Text, nullTermStringCount); } else { Debug.Assert(!radioStringNullTerm.Enabled); radioStringNullTerm.Text = string.Format(radioStringNullTerm.Text, "xx"); } if (len8StringCount > 0) { Debug.Assert(radioStringLen8.Enabled); radioStringLen8.Text = string.Format(radioStringLen8.Text, len8StringCount); } else { Debug.Assert(!radioStringLen8.Enabled); radioStringLen8.Text = string.Format(radioStringLen8.Text, "xx"); } if (len16StringCount > 0) { Debug.Assert(radioStringLen16.Enabled); radioStringLen16.Text = string.Format(radioStringLen16.Text, len16StringCount); } else { Debug.Assert(!radioStringLen16.Enabled); radioStringLen16.Text = string.Format(radioStringLen16.Text, "xx"); } if (dciStringCount > 0) { Debug.Assert(radioStringDci.Enabled); radioStringDci.Text = string.Format(radioStringDci.Text, dciStringCount); } else { Debug.Assert(!radioStringDci.Enabled); radioStringDci.Text = string.Format(radioStringDci.Text, "xx"); } //if (revDciStringCount > 0) { // Debug.Assert(radioStringDciReverse.Enabled); // radioStringDciReverse.Text = // string.Format(radioStringDciReverse.Text, revDciStringCount); //} else { // Debug.Assert(!radioStringDciReverse.Enabled); // radioStringDciReverse.Text = string.Format(radioStringDciReverse.Text, "xx"); //} } /// /// Determines whether the data in the buffer can be represented as ASCII 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) { IEnumerator iter = Selection.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) { // bad value, fail return false; } } } return true; } /// /// Configures the dialog controls based on the provided format descriptor. If /// the desired options are unavailable, a suitable default is selected instead. /// /// FormatDescriptor to use. private void SetControlsFromDescriptor(FormatDescriptor dfd) { Debug.Assert(mIsInitialSetup); radioSimpleDataHex.Checked = true; radioSymbolPartLow.Checked = true; if (dfd == null) { radioDefaultFormat.Checked = true; return; } 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.Enabled) { switch (dfd.FormatSubType) { case FormatDescriptor.SubType.None: case FormatDescriptor.SubType.Hex: radioSimpleDataHex.Checked = true; break; case FormatDescriptor.SubType.Decimal: radioSimpleDataDecimal.Checked = true; break; case FormatDescriptor.SubType.Binary: radioSimpleDataBinary.Checked = true; break; case FormatDescriptor.SubType.Ascii: radioSimpleDataAscii.Checked = true; break; case FormatDescriptor.SubType.Address: radioSimpleDataAddress.Checked = true; break; case FormatDescriptor.SubType.Symbol: radioSimpleDataSymbolic.Checked = true; switch (dfd.SymbolRef.ValuePart) { case WeakSymbolRef.Part.Low: radioSymbolPartLow.Checked = true; break; case WeakSymbolRef.Part.High: radioSymbolPartHigh.Checked = true; break; case WeakSymbolRef.Part.Bank: radioSymbolPartBank.Checked = true; break; default: Debug.Assert(false); break; } Debug.Assert(dfd.HasSymbol); symbolEntryTextBox.Text = dfd.SymbolRef.Label; break; default: Debug.Assert(false); break; } } else { // preferred format not enabled; leave Hex/Low checked } break; case FormatDescriptor.Type.String: switch (dfd.FormatSubType) { case FormatDescriptor.SubType.None: preferredFormat = radioStringMixed; break; case FormatDescriptor.SubType.Reverse: preferredFormat = radioStringMixedReverse; break; case FormatDescriptor.SubType.CString: preferredFormat = radioStringNullTerm; break; case FormatDescriptor.SubType.L8String: preferredFormat = radioStringLen8; break; case FormatDescriptor.SubType.L16String: preferredFormat = radioStringLen16; break; case FormatDescriptor.SubType.Dci: preferredFormat = radioStringDci; break; case FormatDescriptor.SubType.DciReverse: preferredFormat = radioDefaultFormat; break; default: Debug.Assert(false); preferredFormat = radioDefaultFormat; break; } break; case FormatDescriptor.Type.Dense: preferredFormat = radioDenseHex; break; case FormatDescriptor.Type.Fill: preferredFormat = radioFill; break; default: // Should not be here. Debug.Assert(false); preferredFormat = radioDefaultFormat; break; } if (preferredFormat.Enabled) { preferredFormat.Checked = true; } else { mPreferredFormatUnavailable = true; radioDefaultFormat.Checked = true; } } /// /// 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. /// /// Result list. private void CreateDescriptorListFromControls() { FormatDescriptor.Type type = FormatDescriptor.Type.Default; FormatDescriptor.SubType subType = FormatDescriptor.SubType.None; WeakSymbolRef symbolRef = null; int chunkLength = -1; // Decode the "display as" panel, if it's relevant. if (radioSimpleDataHex.Enabled) { if (radioSimpleDataHex.Checked) { subType = FormatDescriptor.SubType.Hex; } else if (radioSimpleDataDecimal.Checked) { subType = FormatDescriptor.SubType.Decimal; } else if (radioSimpleDataBinary.Checked) { subType = FormatDescriptor.SubType.Binary; } else if (radioSimpleDataAscii.Checked) { subType = FormatDescriptor.SubType.Ascii; } else if (radioSimpleDataAddress.Checked) { subType = FormatDescriptor.SubType.Address; } else if (radioSimpleDataSymbolic.Checked) { WeakSymbolRef.Part part; if (radioSymbolPartLow.Checked) { part = WeakSymbolRef.Part.Low; } else if (radioSymbolPartHigh.Checked) { part = WeakSymbolRef.Part.High; } else if (radioSymbolPartBank.Checked) { part = WeakSymbolRef.Part.Bank; } else { Debug.Assert(false); part = WeakSymbolRef.Part.Low; } subType = FormatDescriptor.SubType.Symbol; symbolRef = new WeakSymbolRef(symbolEntryTextBox.Text, part); } else { Debug.Assert(false); } } else { subType = 0; // set later, or doesn't matter } // Decode the main format. if (radioDefaultFormat.Checked) { // 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.Checked) { type = FormatDescriptor.Type.NumericLE; chunkLength = 1; } else if (radio16BitLittle.Checked) { type = FormatDescriptor.Type.NumericLE; chunkLength = 2; } else if (radio16BitBig.Checked) { type = FormatDescriptor.Type.NumericBE; chunkLength = 2; } else if (radio24BitLittle.Checked) { type = FormatDescriptor.Type.NumericLE; chunkLength = 3; } else if (radio32BitLittle.Checked) { type = FormatDescriptor.Type.NumericLE; chunkLength = 4; } else if (radioDenseHex.Checked) { type = FormatDescriptor.Type.Dense; } else if (radioFill.Checked) { type = FormatDescriptor.Type.Fill; } else if (radioStringMixed.Checked) { type = FormatDescriptor.Type.String; } else if (radioStringMixedReverse.Checked) { type = FormatDescriptor.Type.String; subType = FormatDescriptor.SubType.Reverse; } else if (radioStringNullTerm.Checked) { type = FormatDescriptor.Type.String; subType = FormatDescriptor.SubType.CString; } else if (radioStringLen8.Checked) { type = FormatDescriptor.Type.String; subType = FormatDescriptor.SubType.L8String; } else if (radioStringLen16.Checked) { type = FormatDescriptor.Type.String; subType = FormatDescriptor.SubType.L16String; } else if (radioStringDci.Checked) { type = FormatDescriptor.Type.String; subType = FormatDescriptor.SubType.Dci; //} else if (radioStringDciReverse.Checked) { // type = FormatDescriptor.Type.String; // subType = FormatDescriptor.SubType.DciReverse; } else { Debug.Assert(false); // default/none } Results = new SortedList(); IEnumerator iter = Selection.RangeListIterator; while (iter.MoveNext()) { TypedRangeSet.TypedRange rng = iter.Current; if (type == FormatDescriptor.Type.String) { // We want to create one FormatDescriptor object per string. That way // each string gets its own line. if ((subType == FormatDescriptor.SubType.None || subType == FormatDescriptor.SubType.Reverse)) { CreateMixedStringEntries(rng.Low, rng.High, subType); } else if (subType == FormatDescriptor.SubType.CString) { CreateCStringEntries(rng.Low, rng.High, subType); } else if (subType == FormatDescriptor.SubType.L8String || subType == FormatDescriptor.SubType.L16String) { CreateLengthStringEntries(rng.Low, rng.High, subType); } else if (subType == FormatDescriptor.SubType.Dci || subType == FormatDescriptor.SubType.DciReverse) { CreateDciStringEntries(rng.Low, rng.High, subType); } else { Debug.Assert(false); CreateMixedStringEntries(rng.Low, rng.High, subType); // shrug } } else { CreateSimpleEntries(type, subType, chunkLength, symbolRef, rng.Low, rng.High); } } } /// /// 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. /// /// Region data type. /// Region data sub-type. /// Length of a chunk, or -1 for full buffer. /// Symbol reference, or null if not applicable. /// Offset of first byte in range. /// Offset of last byte in range. 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.) // // Because certain details, like the fill byte and high-vs-low ASCII, are pulled // out of the data stream at format time, we don't have to dig for them now. 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) { Results.Add(low, dfd); low += chunkLength; } } /// /// Creates one or more FormatDescriptor entries for the specified range, adding them /// to the Results list. /// /// Offset of first byte in range. /// Offset of last byte in range. /// String sub-type. private void CreateMixedStringEntries(int low, int high, FormatDescriptor.SubType subType) { 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; } 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 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); } } /// /// Creates a format descriptor for ASCII data. If the data is only one byte long, /// a single-byte ASCII char item is emitted instead. /// /// Offset of first byte. /// Length of string. /// String sub-type. private void CreateStringOrByte(int offset, int length, FormatDescriptor.SubType subType) { Debug.Assert(length > 0); if (length == 1) { // single byte, output as single ASCII char rather than 1-byte string CreateByteFD(offset, FormatDescriptor.SubType.Ascii); } else { FormatDescriptor dfd; dfd = FormatDescriptor.Create(length, FormatDescriptor.Type.String, subType); Results.Add(offset, dfd); } } /// /// Creates a format descriptor for a single-byte numeric value. /// /// File offset. /// How to format the item. private void CreateByteFD(int offset, FormatDescriptor.SubType subType) { FormatDescriptor dfd = FormatDescriptor.Create(1, FormatDescriptor.Type.NumericLE, subType); Results.Add(offset, dfd); } /// /// Creates one or more FormatDescriptor entries for the specified range, adding them /// to the Results list. /// /// Offset of first byte in range. /// Offset of last byte in range. /// String sub-type. private void CreateCStringEntries(int low, int high, 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, FormatDescriptor.Type.String, 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); } /// /// Creates one or more FormatDescriptor entries for the specified range, adding them /// to the Results list. /// /// Offset of first byte in range. /// Offset of last byte in range. /// String sub-type. private void CreateLengthStringEntries(int low, int high, FormatDescriptor.SubType subType) { int i; for (i = low; i <= high;) { int length = mFileData[i]; if (subType == FormatDescriptor.SubType.L16String) { length |= mFileData[i + 1] << 8; length += 2; } else { length++; } // Zero-length strings are allowed. FormatDescriptor dfd = FormatDescriptor.Create(length, FormatDescriptor.Type.String, subType); Results.Add(i, dfd); i += length; } Debug.Assert(i == high + 1); } /// /// Creates one or more FormatDescriptor entries for the specified range, adding them /// to the Results list. /// /// Offset of first byte in range. /// Offset of last byte in range. /// String sub-type. private void CreateDciStringEntries(int low, int high, FormatDescriptor.SubType subType) { int start, end, adj, endMask; if (subType == FormatDescriptor.SubType.Dci) { start = low; end = high + 1; adj = 1; } else if (subType == FormatDescriptor.SubType.DciReverse) { start = high; end = low - 1; adj = -1; } else { Debug.Assert(false); return; } // 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 = start; for (int i = start; i != end; i += adj) { byte val = mFileData[i]; if ((val & 0x80) == endMask) { // found the end of a string int length = (i - stringStart) * adj + 1; FormatDescriptor dfd = FormatDescriptor.Create(length, FormatDescriptor.Type.String, subType); Results.Add(stringStart < i ? stringStart : i, dfd); stringStart = i + adj; } } Debug.Assert(stringStart == end); } } }