1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-12-01 07:50:37 +00:00

Add file slicer tool

The tool allows you to cut a piece out of a file by specifying an
offset and a length.  A pair of hex dumps helps you verify that the
positions are correct.

Also, minor cleanups elsewhere.
This commit is contained in:
Andy McFadden 2019-12-29 17:59:35 -08:00
parent 89413d11e4
commit 1373ffd8e3
16 changed files with 619 additions and 70 deletions

View File

@ -985,7 +985,8 @@ namespace Asm65 {
/// <param name="offset">Start offset.</param>
/// <returns>Formatted string.</returns>
public string FormatHexDump(byte[] data, int offset) {
FormatHexDumpCommon(data, offset);
int length = Math.Min(16, data.Length - offset);
FormatHexDumpCommon(data, offset, offset, length);
// this is the only allocation
return new string(mHexDumpBuffer);
}
@ -998,14 +999,21 @@ namespace Asm65 {
/// <param name="offset">Start offset.</param>
/// <param name="sb">StringBuilder that receives output.</param>
public void FormatHexDump(byte[] data, int offset, StringBuilder sb) {
FormatHexDumpCommon(data, offset);
int length = Math.Min(16, data.Length - offset);
FormatHexDumpCommon(data, offset, offset, length);
sb.Append(mHexDumpBuffer);
}
public void FormatHexDump(byte[] data, int offset, int addr, int length,
StringBuilder sb) {
FormatHexDumpCommon(data, offset, addr, length);
sb.Append(mHexDumpBuffer);
}
/// <summary>
/// Formats up to 16 bytes of data into mHexDumpBuffer.
/// </summary>
private void FormatHexDumpCommon(byte[] data, int offset) {
private void FormatHexDumpCommon(byte[] data, int offset, int addr, int length) {
Debug.Assert(offset >= 0 && offset < data.Length);
Debug.Assert(data.Length < (1 << 24));
const int dataCol = 8;
@ -1014,21 +1022,30 @@ namespace Asm65 {
char[] hexChars = mFormatConfig.mUpperHexDigits ? sHexCharsUpper : sHexCharsLower;
char[] outBuf = mHexDumpBuffer;
int skip = addr & 0x0f; // we skip this many entries...
offset -= skip; // ...so adjust offset to balance it
addr &= ~0x0f;
// address field
int addr = offset;
for (int i = 5; i >= 0; i--) {
outBuf[i] = hexChars[addr & 0x0f];
addr >>= 4;
}
// hex digits and characters
int length = Math.Min(16, data.Length - offset);
// If addr doesn't start at xxx0, pad it.
int index;
for (index = 0; index < length; index++) {
for (index = 0; index < skip; index++) {
outBuf[dataCol + index * 3] = outBuf[dataCol + index * 3 + 1] =
outBuf[asciiCol + index] = ' ';
}
// hex digits and characters
for (int i = 0; i < length; i++) {
byte val = data[offset + index];
outBuf[dataCol + index * 3] = hexChars[val >> 4];
outBuf[dataCol + index * 3 + 1] = hexChars[val & 0x0f];
outBuf[asciiCol + index] = CharConv(val);
index++;
}
// for partial line, clear out previous contents

View File

@ -57,5 +57,45 @@ namespace Asm65 {
return true;
}
/// <summary>
/// Parses a long integer in a variety of formats (hex, decimal, binary). We allow
/// hex to be identified with a leading '$' as well as "0x".
///
/// Trim whitespace before calling here.
/// </summary>
/// <param name="str">String to parse.</param>
/// <param name="val">Integer value of string.</param>
/// <param name="intBase">What base the string was in (2, 10, or 16).</param>
/// <returns>True if the parsing was successful.</returns>
public static bool TryParseLong(string str, out long val, out int intBase) {
if (string.IsNullOrEmpty(str)) {
val = intBase = 0;
return false;
}
if (str[0] == '$') {
intBase = 16;
str = str.Substring(1); // Convert functions don't like '$'
} else if (str.Length > 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) {
intBase = 16;
} else if (str[0] == '%') {
intBase = 2;
str = str.Substring(1); // Convert functions don't like '%'
} else {
intBase = 10; // try it as decimal
}
try {
val = Convert.ToInt64(str, intBase);
//Debug.WriteLine("GOT " + val + " - " + intBase);
} catch (Exception) {
//Debug.WriteLine("TryParseInt failed on '" + str + "': " + ex.Message);
val = 0;
return false;
}
return true;
}
}
}

View File

@ -141,7 +141,7 @@ namespace SourceGen {
///
/// This is shared with the DisplayList.
/// </summary>
private Formatter mOutputFormatter;
private Formatter mFormatter;
/// <summary>
/// Pseudo-op names.
@ -180,7 +180,7 @@ namespace SourceGen {
/// CPU definition used when the Formatter was created. If the CPU choice or
/// inclusion of undocumented opcodes changes, we need to wipe the formatter.
/// </summary>
private CpuDef mOutputFormatterCpuDef;
private CpuDef mFormatterCpuDef;
/// <summary>
/// Instruction description object. Used for Info window.
@ -486,10 +486,10 @@ namespace SourceGen {
}
// Update the formatter, and null out mOutputFormatterCpuDef to force a refresh
// Update the formatter, and null out mFormatterCpuDef to force a refresh
// of related items.
mOutputFormatter = new Formatter(mFormatterConfig);
mOutputFormatterCpuDef = null;
mFormatter = new Formatter(mFormatterConfig);
mFormatterCpuDef = null;
// Set pseudo-op names. Entries aren't allowed to be blank, so we start with the
// default values and merge in whatever the user has configured.
@ -737,7 +737,7 @@ namespace SourceGen {
private void FinishPrep() {
CodeLineList = new LineListGen(mProject, mMainWin.CodeDisplayList,
mOutputFormatter, mPseudoOpNames);
mFormatter, mPseudoOpNames);
SetCodeLineListColorMultiplier();
string messages = mProject.LoadExternalFiles();
@ -959,13 +959,13 @@ namespace SourceGen {
// Changing the CPU type or whether undocumented instructions are supported
// invalidates the Formatter's mnemonic cache. We can change these values
// through undo/redo, so we need to check it here.
if (mOutputFormatterCpuDef != mProject.CpuDef) { // reference equality is fine
if (mFormatterCpuDef != mProject.CpuDef) { // reference equality is fine
Debug.WriteLine("CpuDef has changed, resetting formatter (now " +
mProject.CpuDef + ")");
mOutputFormatter = new Formatter(mFormatterConfig);
CodeLineList.SetFormatter(mOutputFormatter);
mFormatter = new Formatter(mFormatterConfig);
CodeLineList.SetFormatter(mFormatter);
CodeLineList.SetPseudoOpNames(mPseudoOpNames);
mOutputFormatterCpuDef = mProject.CpuDef;
mFormatterCpuDef = mProject.CpuDef;
}
if (reanalysisRequired != UndoableChange.ReanalysisScope.DisplayOnly) {
@ -978,7 +978,7 @@ namespace SourceGen {
mReanalysisTimer.EndTask("Call DisasmProject.Analyze()");
mReanalysisTimer.StartTask("Update message list");
mMainWin.UpdateMessageList(mProject.Messages, mOutputFormatter);
mMainWin.UpdateMessageList(mProject.Messages, mFormatter);
mReanalysisTimer.EndTask("Update message list");
}
@ -1410,7 +1410,7 @@ namespace SourceGen {
} else if (format == ClipLineFormat.AllColumns) {
colFlags = Exporter.ActiveColumnFlags.ALL;
}
Exporter eport = new Exporter(mProject, CodeLineList, mOutputFormatter,
Exporter eport = new Exporter(mProject, CodeLineList, mFormatter,
colFlags, rightWidths);
eport.Selection = selection;
@ -1727,7 +1727,7 @@ namespace SourceGen {
}
EditAddress dlg = new EditAddress(mMainWin, firstOffset, nextOffset, nextAddr,
mProject, mOutputFormatter);
mProject, mFormatter);
if (dlg.ShowDialog() != true) {
return;
}
@ -1826,7 +1826,7 @@ namespace SourceGen {
Anattrib attr = mProject.GetAnattrib(offset);
EditLabel dlg = new EditLabel(mMainWin, attr.Symbol, attr.Address, offset,
mProject.SymbolTable, mOutputFormatter);
mProject.SymbolTable, mFormatter);
if (dlg.ShowDialog() != true) {
return;
}
@ -1907,7 +1907,7 @@ namespace SourceGen {
mProject.LvTables.TryGetValue(offset, out LocalVariableTable oldLvt);
EditLocalVariableTable dlg = new EditLocalVariableTable(mMainWin, mProject,
mOutputFormatter, oldLvt, offset);
mFormatter, oldLvt, offset);
if (dlg.ShowDialog() != true) {
return;
}
@ -1953,7 +1953,7 @@ namespace SourceGen {
}
private void EditLongComment(int offset) {
EditLongComment dlg = new EditLongComment(mMainWin, mOutputFormatter);
EditLongComment dlg = new EditLongComment(mMainWin, mFormatter);
if (mProject.LongComments.TryGetValue(offset, out MultiLineComment oldComment)) {
dlg.LongComment = oldComment;
}
@ -2042,7 +2042,7 @@ namespace SourceGen {
private void EditInstructionOperand(int offset) {
EditInstructionOperand dlg = new EditInstructionOperand(mMainWin, mProject,
offset, mOutputFormatter);
offset, mFormatter);
if (dlg.ShowDialog() != true) {
return;
}
@ -2130,7 +2130,7 @@ namespace SourceGen {
mProject.OperandFormats.TryGetValue(firstOffset.Value, out FormatDescriptor dfd);
EditDataOperand dlg =
new EditDataOperand(mMainWin, mProject, mOutputFormatter, trs, dfd);
new EditDataOperand(mMainWin, mProject, mFormatter, trs, dfd);
if (dlg.ShowDialog() == true) {
// Merge the changes into the OperandFormats list. We need to remove all
// FormatDescriptors that overlap the selected region. We don't need to
@ -2153,7 +2153,7 @@ namespace SourceGen {
projectDir = Path.GetDirectoryName(mProjectPathName);
}
EditProjectProperties dlg = new EditProjectProperties(mMainWin, mProject.ProjectProps,
projectDir, mOutputFormatter, initialTab);
projectDir, mFormatter, initialTab);
dlg.ShowDialog();
ProjectProperties newProps = dlg.NewProps;
@ -2185,7 +2185,7 @@ namespace SourceGen {
DefSymbol origDefSym = mProject.ActiveDefSymbolList[symIndex];
Debug.Assert(origDefSym.SymbolSource == Symbol.Source.Project);
EditDefSymbol dlg = new EditDefSymbol(mMainWin, mOutputFormatter,
EditDefSymbol dlg = new EditDefSymbol(mMainWin, mFormatter,
mProject.ProjectProps.ProjectSyms, origDefSym, null);
if (dlg.ShowDialog() == true) {
ProjectProperties newProps = new ProjectProperties(mProject.ProjectProps);
@ -2245,7 +2245,7 @@ namespace SourceGen {
mProject.VisualizationSets.TryGetValue(offset, out VisualizationSet curVisSet);
EditVisualizationSet dlg = new EditVisualizationSet(mMainWin, mProject,
mOutputFormatter, curVisSet, offset);
mFormatter, curVisSet, offset);
if (dlg.ShowDialog() != true) {
return;
}
@ -2307,7 +2307,7 @@ namespace SourceGen {
dlg.AsmLabelColWidth, dlg.AsmOpcodeColWidth,
dlg.AsmOperandColWidth, dlg.AsmCommentColWidth
};
Exporter eport = new Exporter(mProject, CodeLineList, mOutputFormatter,
Exporter eport = new Exporter(mProject, CodeLineList, mFormatter,
dlg.ColFlags, rightWidths);
eport.IncludeNotes = dlg.IncludeNotes;
eport.GenerateImageFiles = dlg.GenerateImageFiles;
@ -2526,7 +2526,7 @@ namespace SourceGen {
}
FormatAddressTable dlg = new FormatAddressTable(mMainWin, mProject, trs,
mOutputFormatter);
mFormatter);
dlg.ShowDialog();
if (dlg.DialogResult != true) {
@ -2583,7 +2583,7 @@ namespace SourceGen {
}
int offset = CodeLineList[index].FileOffset;
GotoBox dlg = new GotoBox(mMainWin, mProject, offset, mOutputFormatter);
GotoBox dlg = new GotoBox(mMainWin, mProject, offset, mFormatter);
if (dlg.ShowDialog() == true) {
GoToLocation(new NavStack.Location(dlg.TargetOffset, 0, false),
GoToMode.JumpToCodeData, true);
@ -2696,7 +2696,7 @@ namespace SourceGen {
// We're comparing to the formatted strings -- safer than trying to find the symbol
// in the table and then guess at how the table arranges itself for display -- so we
// need to compare the formatted form of the label.
string cmpStr = mOutputFormatter.FormatVariableLabel(symRef.Label);
string cmpStr = mFormatter.FormatVariableLabel(symRef.Label);
int lineIndex = CodeLineList.FindLineIndexByOffset(varOffset);
while (lineIndex < mProject.FileDataLength) {
LineListGen.Line line = CodeLineList[lineIndex];
@ -3125,7 +3125,7 @@ namespace SourceGen {
// when it's not on top, it can sit behind the main app window until you
// double-click something else.
mHexDumpDialog = new Tools.WpfGui.HexDumpViewer(null,
mProject.FileData, mOutputFormatter);
mProject.FileData, mFormatter);
mHexDumpDialog.Closing += (sender, e) => {
Debug.WriteLine("Hex dump dialog closed");
//showHexDumpToolStripMenuItem.Checked = false;
@ -3448,7 +3448,7 @@ namespace SourceGen {
}
// TODO(someday): localization
Asm65.Formatter formatter = mOutputFormatter;
Asm65.Formatter formatter = mFormatter;
bool showBank = !mProject.CpuDef.HasAddr16;
for (int i = 0; i < xrefs.Count; i++) {
XrefSet.Xref xr = xrefs[i];
@ -3565,7 +3565,7 @@ namespace SourceGen {
}
MainWindow.NotesListItem nli = new MainWindow.NotesListItem(offset,
mOutputFormatter.FormatOffset24(offset),
mFormatter.FormatOffset24(offset),
nocrlfStr,
mlc.BackgroundColor);
mMainWin.NotesList.Add(nli);
@ -3583,13 +3583,13 @@ namespace SourceGen {
private void PopulateSymbolsList() {
mMainWin.SymbolsList.Clear();
foreach (Symbol sym in mProject.SymbolTable) {
string valueStr = mOutputFormatter.FormatHexValue(sym.Value, 0);
string valueStr = mFormatter.FormatHexValue(sym.Value, 0);
string sourceTypeStr = sym.SourceTypeString;
if (sym is DefSymbol) {
DefSymbol defSym = (DefSymbol)sym;
if (defSym.MultiMask != null) {
valueStr += " & " +
mOutputFormatter.FormatHexValue(defSym.MultiMask.AddressMask, 4);
mFormatter.FormatHexValue(defSym.MultiMask.AddressMask, 4);
}
if (defSym.Direction == DefSymbol.DirectionFlags.Read) {
sourceTypeStr += '<';
@ -3599,7 +3599,7 @@ namespace SourceGen {
}
MainWindow.SymbolsListItem sli = new MainWindow.SymbolsListItem(sym,
sourceTypeStr, valueStr, sym.GenerateDisplayLabel(mOutputFormatter));
sourceTypeStr, valueStr, sym.GenerateDisplayLabel(mFormatter));
mMainWin.SymbolsList.Add(sli);
}
}
@ -3970,7 +3970,7 @@ namespace SourceGen {
public void ToggleInstructionChart() {
if (mInstructionChartDialog == null) {
// Create without owner so it doesn't have to be in front of main window.
mInstructionChartDialog = new Tools.WpfGui.InstructionChart(null, mOutputFormatter);
mInstructionChartDialog = new Tools.WpfGui.InstructionChart(null, mFormatter);
mInstructionChartDialog.Closing += (sender, e) => {
Debug.WriteLine("Instruction chart closed");
mInstructionChartDialog = null;
@ -4009,7 +4009,7 @@ namespace SourceGen {
// Create the dialog without an owner, and add it to the "unowned" list.
Tools.WpfGui.HexDumpViewer dlg = new Tools.WpfGui.HexDumpViewer(null,
data, mOutputFormatter);
data, mFormatter);
dlg.SetFileName(Path.GetFileName(fileName));
dlg.Closing += (sender, e) => {
Debug.WriteLine("Window " + dlg + " closed, removing from unowned list");
@ -4025,6 +4025,21 @@ namespace SourceGen {
concat.ShowDialog();
}
public void SliceFiles() {
OpenFileDialog fileDlg = new OpenFileDialog() {
Filter = Res.Strings.FILE_FILTER_ALL,
FilterIndex = 1
};
if (fileDlg.ShowDialog() != true) {
return;
}
string pathName = Path.GetFullPath(fileDlg.FileName);
Tools.WpfGui.FileSlicer slicer = new Tools.WpfGui.FileSlicer(this.mMainWin, pathName,
mFormatter);
slicer.ShowDialog();
}
#endregion Tools
#region Debug features

View File

@ -12,6 +12,23 @@
<div id="content">
<h1>6502bench SourceGen: Tools</h1>
<h2><a name="instruction-chart">Instruction Chart</a></h2>
<p>This opens a window with a summary of all 256 opcodes. The CPU can
be chosen from the pop-up list at the bottom. Undocumented opcodes are
shown in italics.</p>
<p>The status flags affected by each instruction reflect their behavior
on the 65816. The only significant difference between 65816 and
6502/65C02 is the way the BRK instruction affects the D and B/X flags.</p>
<h2><a name="ascii-chart">ASCII Chart</a></h2>
<p>This opens a window with the ASCII character set. Each character is
displayed next to its numeric value in decimal and hexadecimal. The
pop-up list at the bottom allows you to flip between standard and "high"
ASCII.</p>
<h2><a name="hexdump">Hex Dump Viewer</a></h2>
<p>You can use this to view the contents of the project data file
@ -53,25 +70,19 @@ external files.</p>
<p>The File Concatenator combines multiple files into a single file.
Select the files to add, arrange them in the proper order, then hit
"Save".</p>
"Save". CRC-32 values are shown for reference.</p>
<h2><a name="ascii-chart">ASCII Chart</a></h2>
<h2><a name="file-slicer">File Slicer</a></h2>
<p>This opens a window with the ASCII character set. Each character is
displayed next to its numeric value in decimal and hexadecimal. The
pop-up list at the bottom allows you to flip between standard and "high"
ASCII.</p>
<p>The File Slicer allows you to "slice" a piece out of a file, saving
it to a new file. Specify the start and length in decimal or hex. If
you leave a field blank, they will default to offset 0 and the remaining
length of the file, respectively.</p>
<p>The hex dumps show the area just before and after the chunk to be
sliced, allowing you to confirm the placement.</p>
<h2><a name="instruction-chart">Instruction Chart</a></h2>
<p>This opens a window with a summary of all 256 opcodes. The CPU can
be chosen from the pop-up list at the bottom. Undocumented opcodes are
shown in italics.</p>
<p>The status flags affected by each instruction are shown for the 65816
implementation. The only significant difference vs. 6502/65C02
is in the way the BRK instruction affects the D and B/X flags.</p>
</div>
<div id="footer">

View File

@ -89,6 +89,9 @@
<Compile Include="Tools\WpfGui\FileConcatenator.xaml.cs">
<DependentUpon>FileConcatenator.xaml</DependentUpon>
</Compile>
<Compile Include="Tools\WpfGui\FileSlicer.xaml.cs">
<DependentUpon>FileSlicer.xaml</DependentUpon>
</Compile>
<Compile Include="Tools\WpfGui\InstructionChart.xaml.cs">
<DependentUpon>InstructionChart.xaml</DependentUpon>
</Compile>
@ -269,6 +272,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Tools\WpfGui\FileSlicer.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Tools\WpfGui\InstructionChart.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>

View File

@ -47,7 +47,7 @@ limitations under the License.
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Select files:"/>
<TextBlock Grid.Row="0" Text="Select files:" Margin="0,0,0,4"/>
<DataGrid Name="concatGrid" Grid.Row="1" Height="300" Width="480"
IsReadOnly="True"
@ -95,6 +95,5 @@ limitations under the License.
IsEnabled="{Binding IsSaveEnabled}" Click="SaveButton_Click"/>
<Button Content="Cancel" IsCancel="True" Width="70" Margin="4,0,0,0"/>
</StackPanel>
</Grid>
</Window>

View File

@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Microsoft.Win32;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
@ -21,6 +20,7 @@ using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Windows;
using Microsoft.Win32;
using CommonUtil;
using System.Windows.Controls;

View File

@ -0,0 +1,93 @@
<!--
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.
-->
<Window x:Class="SourceGen.Tools.WpfGui.FileSlicer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SourceGen.Tools.WpfGui"
xmlns:system="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="File Slicer"
SizeToContent="WidthAndHeight" ResizeMode="NoResize"
ShowInTaskbar="False" WindowStartupLocation="CenterOwner"
Closing="Window_Closing">
<Window.Resources>
<system:String x:Key="str_LastByteAt" xml:space="preserve"> - last byte at </system:String>
<system:String x:Key="str_SuccessCaption">Success</system:String>
<system:String x:Key="str_SuccessMsg">File created.</system:String>
<system:String x:Key="str_FileAccessFailedCaption">File Access Error</system:String>
<system:String x:Key="str_FileAccessFailedFmt">Unable to access file: {0}</system:String>
</Window.Resources>
<Grid Margin="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Text="Select region to extract:"/>
<TextBlock Grid.Column="0" Grid.Row="1" HorizontalAlignment="Right" Margin="0,8,4,0"
Text="File size:"/>
<TextBlock Grid.Column="1" Grid.Row="1" Margin="0,8,0,0"
Text="{Binding FileLengthStr, FallbackValue=12345 ($abcd)}"/>
<TextBlock Grid.Column="0" Grid.Row="2" HorizontalAlignment="Right" Margin="0,5,4,0"
Foreground="{Binding SliceStartBrush}" Text="Slice start:"/>
<StackPanel Grid.Column="1" Grid.Row="2" Orientation="Horizontal" Margin="0,4,0,0">
<TextBox HorizontalAlignment="Left" Width="80"
Text="{Binding SliceStart, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Margin="8,0,0,0" Text="{Binding SliceStartDesc, FallbackValue=12345 ($abcd)}"/>
</StackPanel>
<TextBlock Grid.Column="0" Grid.Row="3" HorizontalAlignment="Right" Margin="0,5,4,0"
Foreground="{Binding SliceLengthBrush}" Text="Slice length:"/>
<StackPanel Grid.Column="1" Grid.Row="3" Orientation="Horizontal" Margin="0,4,0,0">
<TextBox HorizontalAlignment="Left" Width="80"
Text="{Binding SliceLength, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Margin="8,0,0,0" Text="{Binding SliceLengthDesc, FallbackValue=12345 ($abcd)}"/>
</StackPanel>
<TextBlock Grid.Column="0" Grid.Row="4" HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,4,4,0" Text="Start"/>
<TextBox Grid.Column="1" Grid.Row="4" Grid.ColumnSpan="2" Margin="0,8,0,0" Width="491" Height="74"
IsReadOnly="True" FontFamily="{StaticResource GeneralMonoFont}" Text="{Binding StartHexDump}"/>
<TextBlock Grid.Column="0" Grid.Row="5" HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,4,4,0" Text="End"/>
<TextBox Grid.Column="1" Grid.Row="5" Grid.ColumnSpan="2" Margin="0,8,0,0" Width="491" Height="74"
IsReadOnly="True" FontFamily="{StaticResource GeneralMonoFont}" Text="{Binding EndHexDump}"/>
<StackPanel Grid.Column="0" Grid.Row="6" Grid.ColumnSpan="3"
Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0">
<Button Content="Save..." IsDefault="True" Width="70"
IsEnabled="{Binding IsSaveEnabled}" Click="SaveButton_Click"/>
<Button Content="Cancel" IsCancel="True" Width="70" Margin="4,0,0,0"/>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,364 @@
/*
* 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.IO;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Win32;
using Asm65;
using System.Text;
using System.Windows.Media;
namespace SourceGen.Tools.WpfGui {
/// <summary>
/// File slicer tool.
/// </summary>
public partial class FileSlicer : Window, INotifyPropertyChanged {
/// <summary>
/// Path to file to slice.
/// </summary>
private string mPathName;
/// <summary>
/// Text formatter.
/// </summary>
private Formatter mFormatter;
/// <summary>
/// Length of file to slice.
/// </summary>
private long mFileLength;
/// <summary>
/// Open file.
/// </summary>
private FileStream mFileStream;
private bool mIsSaveEnabled;
public bool IsSaveEnabled {
get { return mIsSaveEnabled; }
set { mIsSaveEnabled = value; OnPropertyChanged(); }
}
public string FileLengthStr {
get { return FormatDecAndHex(mFileLength); }
}
// Start/length entry fields and dec+hex display.
private string mSliceStart;
public string SliceStart {
get { return mSliceStart; }
set { mSliceStart = value; OnPropertyChanged(); UpdateControls(); }
}
private string mSliceStartDesc;
public string SliceStartDesc {
get { return mSliceStartDesc; }
set { mSliceStartDesc = value; OnPropertyChanged(); }
}
private string mSliceLength;
public string SliceLength {
get { return mSliceLength; }
set { mSliceLength = value; OnPropertyChanged(); UpdateControls(); }
}
private string mSliceLengthDesc;
public string SliceLengthDesc {
get { return mSliceLengthDesc; }
set { mSliceLengthDesc = value; OnPropertyChanged(); }
}
private string mStartHexDump;
public string StartHexDump {
get { return mStartHexDump; }
set { mStartHexDump = value; OnPropertyChanged(); }
}
private string mEndHexDump;
public string EndHexDump {
get { return mEndHexDump; }
set { mEndHexDump = value; OnPropertyChanged(); }
}
// Text turns red on error.
private Brush mSliceStartBrush;
public Brush SliceStartBrush {
get { return mSliceStartBrush; }
set { mSliceStartBrush = value; OnPropertyChanged(); }
}
private Brush mSliceLengthBrush;
public Brush SliceLengthBrush {
get { return mSliceLengthBrush; }
set { mSliceLengthBrush = value; OnPropertyChanged(); }
}
private Brush mDefaultLabelColor = SystemColors.WindowTextBrush;
private Brush mErrorLabelColor = Brushes.Red;
// INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "") {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// Constructor.
/// </summary>
public FileSlicer(Window owner, string pathName, Formatter formatter) {
InitializeComponent();
Owner = owner;
DataContext = this;
mPathName = pathName;
mFormatter = formatter;
mFileLength = new FileInfo(pathName).Length;
mFileStream = new FileStream(pathName, FileMode.Open, FileAccess.Read);
mSliceStart = mSliceLength = string.Empty;
UpdateControls();
}
private void Window_Closing(object sender, CancelEventArgs e) {
mFileStream.Close();
}
/// <summary>
/// Formats a value in decimal and hex.
/// </summary>
/// <param name="val">Value to format.</param>
/// <returns>Formatted string.</returns>
private string FormatDecAndHex(long val) {
StringBuilder sb = new StringBuilder();
sb.Append(val.ToString());
sb.Append(" (");
sb.Append(mFormatter.FormatHexValue((int)val, 4));
sb.Append(")");
return sb.ToString();
}
/// <summary>
/// Updates the state of the controls after something changed.
/// </summary>
private void UpdateControls() {
ParseStartLength(out bool isStartValid, out long sliceStart,
out bool isLengthValid, out long sliceLength);
SliceStartDesc = FormatDecAndHex(sliceStart);
SliceLengthDesc = FormatDecAndHex(sliceLength) +
(string)FindResource("str_LastByteAt") +
mFormatter.FormatOffset24((int)(sliceStart + sliceLength - 1));
if (isStartValid && isLengthValid) {
// anchor is first byte in slice
StartHexDump = CreateSplitHexDump(0, sliceStart, sliceStart + sliceLength - 1);
// anchor is first byte after slice (may be off end)
EndHexDump = CreateSplitHexDump(sliceStart, sliceStart + sliceLength,
mFileLength - 1);
} else {
StartHexDump = EndHexDump = string.Empty;
}
SliceStartBrush = isStartValid ? mDefaultLabelColor : mErrorLabelColor;
SliceLengthBrush = isLengthValid ? mDefaultLabelColor : mErrorLabelColor;
IsSaveEnabled = isStartValid && isLengthValid;
}
private void ParseStartLength(out bool startOk, out long sliceStart,
out bool lengthOk, out long sliceLength) {
startOk = lengthOk = true;
if (string.IsNullOrEmpty(SliceStart)) {
sliceStart = 0;
} else if (Number.TryParseLong(SliceStart.Trim(), out sliceStart, out int unused1)) {
if (sliceStart < 0 || sliceStart >= mFileLength) {
startOk = false;
}
} else {
startOk = false;
}
if (string.IsNullOrEmpty(SliceLength)) {
sliceLength = mFileLength - sliceStart;
if (sliceLength < 0) {
sliceLength = 0;
}
} else if (Number.TryParseLong(SliceLength.Trim(), out sliceLength, out int unused2)) {
if (sliceLength <= 0 || sliceLength > mFileLength ||
sliceStart + sliceLength > mFileLength) {
lengthOk = false;
}
} else {
lengthOk = false;
}
}
/// <summary>
/// Creates a hex dump with up to 5 lines. Two lines before the anchor point, then
/// a gap, then two lines that start with the anchor.
/// </summary>
/// <param name="minFirst">Earliest position we're allowed to include.</param>
/// <param name="anchorPos">Anchor point.</param>
/// <param name="maxLast">Last position we're allowed to show (inclusive end).</param>
/// <returns>Multi-line formatted string.</returns>
private string CreateSplitHexDump(long minFirst, long anchorPos, long maxLast) {
const long AND_16_MASK = ~0x0f;
Debug.Assert(minFirst <= anchorPos && anchorPos <= maxLast + 1 && minFirst <= maxLast);
Debug.Assert(minFirst >= 0);
Debug.Assert(maxLast < mFileLength);
StringBuilder sb = new StringBuilder(5 * 64);
byte[] dataBuf = new byte[32];
// We show two lines of hex dump before the anchor, so we need up to 32 bytes.
long firstPos = Math.Max(anchorPos - 32, minFirst);
firstPos = (firstPos + 15) & AND_16_MASK;
long chunkLen = anchorPos - firstPos;
mFileStream.Seek(firstPos, SeekOrigin.Begin);
int actual = mFileStream.Read(dataBuf, 0, dataBuf.Length);
Debug.Assert(chunkLen <= actual);
if (chunkLen <= 0) {
// no pre-anchor data
sb.AppendLine(string.Empty);
sb.AppendLine(string.Empty);
} else {
long pos = firstPos;
long lineLen = 16 - (pos & 0x0f);
if (lineLen >= chunkLen) {
// top part fits on a single line; do it on the next one
lineLen = chunkLen;
sb.AppendLine(string.Empty);
} else {
mFormatter.FormatHexDump(dataBuf, (int)(pos - firstPos), (int)pos,
(int)lineLen, sb);
sb.Append("\r\n");
//sb.AppendLine(pos.ToString("x4") + ": " + lineLen);
pos += lineLen;
lineLen = chunkLen - lineLen;
}
mFormatter.FormatHexDump(dataBuf, (int)(pos - firstPos), (int)pos,
(int)lineLen, sb);
sb.Append("\r\n");
//sb.AppendLine(pos.ToString("x4") + ": " + lineLen);
}
sb.AppendLine("------");
// We show two lines of hex dump with the anchor, so we need up to 31 bytes
// following it.
//
// NOTE: anchorPos is inclusive and represents the first byte contained in the
// range. If we're doing the hex dump for the end of the file, the value can be
// one greater than maxLast if the pre-anchor range runs to EOF.
long lastPos = ((anchorPos + 32) & AND_16_MASK) - 1; // inclusive end
lastPos = Math.Min(lastPos, maxLast);
chunkLen = lastPos - anchorPos + 1;
mFileStream.Seek(anchorPos, SeekOrigin.Begin);
actual = mFileStream.Read(dataBuf, 0, dataBuf.Length);
Debug.Assert(chunkLen <= actual);
if (chunkLen <= 0) {
// anchor not visible
sb.AppendLine(string.Empty);
sb.AppendLine(string.Empty);
} else {
long pos = anchorPos;
long lineLen = 16 - (pos & 0x0f);
if (lineLen > chunkLen) {
lineLen = chunkLen;
}
mFormatter.FormatHexDump(dataBuf, (int)(pos - anchorPos), (int)pos,
(int)lineLen, sb);
sb.Append("\r\n");
//sb.AppendLine(pos.ToString("x4") + ": " + lineLen);
pos += lineLen;
lineLen = chunkLen - lineLen;
if (lineLen <= 0) {
sb.AppendLine(string.Empty);
} else {
mFormatter.FormatHexDump(dataBuf, (int)(pos - anchorPos), (int)pos,
(int)lineLen, sb);
sb.Append("\r\n");
//sb.AppendLine(pos.ToString("x4") + ": " + lineLen);
}
}
return sb.ToString();
}
private void SaveButton_Click(object sender, RoutedEventArgs e) {
SaveFileDialog fileDlg = new SaveFileDialog() {
Filter = Res.Strings.FILE_FILTER_ALL,
FilterIndex = 0,
ValidateNames = true,
FileName = "slice.bin"
};
if (fileDlg.ShowDialog() != true) {
return;
}
string pathName = Path.GetFullPath(fileDlg.FileName);
Debug.WriteLine("OUTPUT TO " + pathName);
try {
ParseStartLength(out bool startOk, out long sliceStart,
out bool lengthOk, out long sliceLength);
if (!(startOk && lengthOk)) {
throw new Exception("Internal error: start/length invalid");
}
using (FileStream outStream = new FileStream(pathName, FileMode.Create)) {
mFileStream.Seek(sliceStart, SeekOrigin.Begin);
CopyStreamToStream(mFileStream, outStream, sliceLength);
}
} catch (Exception ex) {
string ecaption = (string)FindResource("str_FileAccessFailedCaption");
string efmt = (string)FindResource("str_FileAccessFailedFmt");
MessageBox.Show(string.Format(efmt, ex.Message), ecaption,
MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
string caption = (string)FindResource("str_SuccessCaption");
string msg = (string)FindResource("str_SuccessMsg");
MessageBox.Show(msg, caption, MessageBoxButton.OK, MessageBoxImage.None);
}
private static void CopyStreamToStream(Stream inStream, Stream outStream, long length) {
byte[] buffer = new byte[256 * 1024];
while (length > 0) {
int getLen = (int)Math.Min(length, buffer.Length);
int actual = inStream.Read(buffer, 0, getLen);
if (actual != getLen) {
throw new IOException("Read failed: requested " + getLen + ", got " + actual);
}
outStream.Write(buffer, 0, getLen);
length -= getLen;
}
}
}
}

View File

@ -32,6 +32,7 @@ limitations under the License.
<system:String x:Key="str_LowHighAscii">Low/High ASCII</system:String>
<system:String x:Key="str_C64Petscii">C64 PETSCII</system:String>
<system:String x:Key="str_C64ScreenCode">C64 Screen Code</system:String>
<system:String x:Key="str_TitleAddon" xml:space="preserve"> - Hex Dump Viewer</system:String>
</Window.Resources>
<Grid Margin="8">

View File

@ -126,7 +126,7 @@ namespace SourceGen.Tools.WpfGui {
/// Sets the filename associated with the data. This is for display purposes only.
/// </summary>
public void SetFileName(string fileName) {
Title = fileName;
Title = fileName + (string)FindResource("str_TitleAddon");
}
private void CharConvComboBox_SelectionChanged(object sender,

View File

@ -40,7 +40,7 @@ namespace SourceGen.WpfGui {
}
private string mCommentText;
private Brush mDefaultLabelColor;
private Brush mDefaultLabelColor = SystemColors.WindowTextBrush;
// INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
@ -54,7 +54,6 @@ namespace SourceGen.WpfGui {
DataContext = this;
CommentText = comment;
mDefaultLabelColor = asciiOnlyLabel.Foreground;
}
public void Window_ContentRendered(object sender, EventArgs e) {

View File

@ -149,8 +149,7 @@ namespace SourceGen.WpfGui {
/// </summary>
private bool mIsWidthOptional;
// Saved off at dialog load time.
private Brush mDefaultLabelColor;
private Brush mDefaultLabelColor = SystemColors.WindowTextBrush;
// INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
@ -205,8 +204,6 @@ namespace SourceGen.WpfGui {
}
private void Window_Loaded(object sender, RoutedEventArgs e) {
mDefaultLabelColor = labelNotesLabel.Foreground;
if (mOldSym != null) {
Label = mOldSym.GenerateDisplayLabel(mNumFormatter);
Value = mNumFormatter.FormatValueInBase(mOldSym.Value,

View File

@ -53,8 +53,7 @@ namespace SourceGen.WpfGui {
/// </summary>
private Formatter mFormatter;
// Dialog label text color, saved off at dialog load time.
private Brush mDefaultLabelColor;
private Brush mDefaultLabelColor = SystemColors.WindowTextBrush;
/// <summary>
/// Recursion guard.
@ -146,8 +145,6 @@ namespace SourceGen.WpfGui {
}
private void Window_Loaded(object sender, RoutedEventArgs e) {
mDefaultLabelColor = maxLengthLabel.Foreground;
IsNonUniqueEnabled = IsLocalEnabled = IsGlobalEnabled = IsExportedEnabled = true;
if (LabelSym == null) {

View File

@ -164,13 +164,14 @@ limitations under the License.
</RoutedUICommand>
<RoutedUICommand x:Key="ShowFileHexDumpCmd" Text="File Hex Dump..."/>
<RoutedUICommand x:Key="ShowHexDumpCmd" Text="Show Hex Dump"/>
<RoutedUICommand x:Key="SliceFilesCmd" Text="Slice Files..."/>
<RoutedUICommand x:Key="ToggleAsciiChartCmd" Text="ASCII Chart"/>
<RoutedUICommand x:Key="ToggleDataScanCmd" Text="Toggle Data Scan">
<RoutedUICommand.InputGestures>
<KeyGesture>Ctrl+D</KeyGesture>
</RoutedUICommand.InputGestures>
</RoutedUICommand>
<RoutedUICommand x:Key="ToggleInstructionChartCmd" Text="Instruction Chart"/>
<RoutedUICommand x:Key="ToggleInstructionChartCmd" Text="65xx Instruction Chart"/>
<RoutedUICommand x:Key="ToggleSingleByteFormatCmd" Text="Toggle Single-Byte Format">
<RoutedUICommand.InputGestures>
<KeyGesture>Ctrl+B</KeyGesture>
@ -291,6 +292,8 @@ limitations under the License.
Executed="ShowFileHexDumpCmd_Executed"/>
<CommandBinding Command="{StaticResource ShowHexDumpCmd}"
CanExecute="IsProjectOpen" Executed="ShowHexDumpCmd_Executed"/>
<CommandBinding Command="{StaticResource SliceFilesCmd}"
Executed="SliceFilesCmd_Executed"/>
<CommandBinding Command="{StaticResource ToggleAsciiChartCmd}"
Executed="ToggleAsciiChartCmd_Executed"/>
<CommandBinding Command="{StaticResource ToggleDataScanCmd}"
@ -400,8 +403,10 @@ limitations under the License.
Command="{StaticResource ToggleInstructionChartCmd}" IsCheckable="True"/>
<MenuItem Name="toggleAsciiChartMenuItem"
Command="{StaticResource ToggleAsciiChartCmd}" IsCheckable="True"/>
<Separator/>
<MenuItem Command="{StaticResource ShowFileHexDumpCmd}"/>
<MenuItem Command="{StaticResource ConcatenateFilesCmd}"/>
<MenuItem Command="{StaticResource SliceFilesCmd}"/>
</MenuItem>
<MenuItem Header="_Help">
<MenuItem Command="Help"/>

View File

@ -1334,6 +1334,10 @@ namespace SourceGen.WpfGui {
mMainCtrl.ShowHexDump();
}
private void SliceFilesCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.SliceFiles();
}
private void ToggleAsciiChartCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
mMainCtrl.ToggleAsciiChart();
}