Progress toward OMF file handling

Added generation of info/error messages to segment parser, which
are displayed in the main OMF viewer window.

Added segment viewer window, which opens when a segment entry in the
viewer list is double-clicked.  Currently shows the "raw" header
fields, with place-holder UI for additional stuff.
This commit is contained in:
Andy McFadden 2020-06-26 16:58:42 -07:00
parent b77d9ba4c8
commit d1526e5f25
10 changed files with 596 additions and 85 deletions

View File

@ -54,6 +54,7 @@
</Compile>
<Compile Include="Helper.cs" />
<Compile Include="InverseBooleanConverter.cs" />
<Compile Include="ListToStringConverter.cs" />
<Compile Include="MultiKeyInputGesture.cs" />
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>

View File

@ -0,0 +1,50 @@
/*
* Copyright 2020 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.Globalization;
using System.Windows;
using System.Windows.Data;
namespace CommonWPF {
/// <summary>
/// Converts a List&lt;string&gt; to a multi-line string, suitable for presentation
/// in a TextBlock or other UI element.
/// </summary>
/// <remarks>
/// https://stackoverflow.com/a/345515/294248
///
/// In XAML, reference with:
/// xmlns:common="clr-namespace:CommonWPF;assembly=CommonWPF"
/// </remarks>
[ValueConversion(typeof(List<string>), typeof(string))]
public class ListToStringConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
if (targetType != typeof(string)) {
Debug.WriteLine("Invalid targetType for string conversion");
return DependencyProperty.UnsetValue;
}
return string.Join("\r\n", ((List<string>)value).ToArray());
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture) {
return DependencyProperty.UnsetValue;
}
}
}

View File

@ -4177,7 +4177,7 @@ namespace SourceGen {
}
Tools.Omf.WpfGui.OmfViewer ov =
new Tools.Omf.WpfGui.OmfViewer(this.mMainWin, pathName, fileData);
new Tools.Omf.WpfGui.OmfViewer(this.mMainWin, pathName, fileData, mFormatter);
ov.ShowDialog();
}

View File

@ -87,6 +87,9 @@
<Compile Include="Tools\ApplesoftToHtml.cs" />
<Compile Include="Tools\Omf\OmfFile.cs" />
<Compile Include="Tools\Omf\OmfSegment.cs" />
<Compile Include="Tools\Omf\WpfGui\OmfSegmentViewer.xaml.cs">
<DependentUpon>OmfSegmentViewer.xaml</DependentUpon>
</Compile>
<Compile Include="Tools\Omf\WpfGui\OmfViewer.xaml.cs">
<DependentUpon>OmfViewer.xaml</DependentUpon>
</Compile>
@ -281,6 +284,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Tools\Omf\WpfGui\OmfSegmentViewer.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Tools\Omf\WpfGui\OmfViewer.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>

View File

@ -38,8 +38,8 @@ namespace SourceGen.Tools.Omf {
/// Appendix F describes OMF v2.1, and Chapter 8 has some useful information about
/// how the loader works (e.g. page 205).
/// - "Undocumented Secrets of the Apple IIGS System Loader" by Neil Parker,
/// http://nparker.llx.com/a2/loader.html . Among other things it documents ExpressLoad
/// segments, something Apple apparently never did.
/// http://nparker.llx.com/a2/loader.html . Among other things it documents the
/// contents of ExpressLoad segments, which I haven't found in an official reference.
/// - Apple IIgs Tech Note #66, "ExpressLoad Philosophy".
///
/// Related:
@ -76,19 +76,20 @@ namespace SourceGen.Tools.Omf {
Object, // output of assembler/compiler, before linking
Library, // static code library
RunTimeLibrary, // dynamic shared library
Indeterminate, // valid OMF, but type is indeterminate
Foreign // not OMF, or not IIgs OMF
}
public FileKind OmfFileKind { get; private set; }
private bool mIsDamaged;
private string mDamageMsg = string.Empty;
private List<OmfSegment> mSegmentList = new List<OmfSegment>();
public List<OmfSegment> SegmentList {
get { return mSegmentList; }
}
public List<string> MessageList { get; private set; } = new List<string>();
/// <summary>
/// Constructor.
@ -105,7 +106,13 @@ namespace SourceGen.Tools.Omf {
OmfSegment.ParseResult result = DoAnalyze(false);
if (result == OmfSegment.ParseResult.IsLibrary ||
result == OmfSegment.ParseResult.Failure) {
DoAnalyze(true);
// Failed; try again as a library.
List<string> firstFail = new List<string>(MessageList);
result = DoAnalyze(true);
if (result == OmfSegment.ParseResult.Failure) {
// Failed both ways. Report the failures from the non-library attempt.
MessageList = firstFail;
}
}
}
@ -114,24 +121,32 @@ namespace SourceGen.Tools.Omf {
int offset = 0;
int len = mFileData.Length;
List<string> msgs = new List<string>();
while (len > 0) {
OmfSegment.ParseResult result =
OmfSegment.ParseSegment(mFileData, offset, parseAsLibrary, out OmfSegment seg);
if (result == OmfSegment.ParseResult.Failure) {
// parsing failed; reject file or stop early
if (first) {
OmfFileKind = FileKind.Foreign;
} else {
mIsDamaged = true;
mDamageMsg = string.Format("File may be damaged; ignoring last {0} bytes",
mFileData.Length - offset);
}
return result;
} else if (result == OmfSegment.ParseResult.IsLibrary) {
OmfSegment.ParseResult result = OmfSegment.ParseSegment(mFileData, offset,
parseAsLibrary, msgs, out OmfSegment seg);
MessageList.Clear();
foreach (string str in msgs) {
MessageList.Add(str);
}
if (result == OmfSegment.ParseResult.IsLibrary) {
// Need to start over in library mode.
Debug.WriteLine("Restarting in library mode");
return result;
} else if (result == OmfSegment.ParseResult.Failure) {
// Could be a library we failed to parse, could be a totally bad file.
// If we were on the first segment, fail immediately so we can retry as
// library. If not, it's probably not a library (assuming the Library
// Dictionary segment appears first), but rather a partially-bad OMF.
if (first) {
return result;
}
break;
}
first = false;
Debug.Assert(seg.FileLength > 0);
mSegmentList.Add(seg);

View File

@ -76,15 +76,23 @@ namespace SourceGen.Tools.Omf {
private const int LOAD_NAME_LEN = 10;
public class NameValueNote {
public string Name { get; set; }
public object Value { get; set; }
public string Note { get; set; }
public string Name { get; private set; }
public object Value { get; private set; }
public int Width { get; private set; }
public string Note { get; private set; }
public NameValueNote(string name, object value, int width, string note) {
Name = name;
Value = value;
Width = width;
Note = note;
}
}
/// <summary>
/// Values pulled from file header. Useful for display.
/// </summary>
List<NameValueNote> RawValues = new List<NameValueNote>();
public List<NameValueNote> RawValues = new List<NameValueNote>();
public enum SegmentVersion { v0_0, v1_0, v2_0, v2_1 }
@ -107,9 +115,9 @@ namespace SourceGen.Tools.Omf {
BankRel = 0x0100, // v2.1
Skip = 0x0200, // v2.1
Reloadable = 0x0400, // v2.0
AbsoluteBank = 0x0800, // v2.0
AbsBank = 0x0800, // v2.0
NoSpecial = 0x1000, // v2.0
PositionIndep = 0x2000, //
PosnIndep = 0x2000, //
Private = 0x4000, //
Dynamic = 0x8000 //
}
@ -118,6 +126,7 @@ namespace SourceGen.Tools.Omf {
// Header fields.
//
public int FileOffset { get; private set; }
public int FileLength { get; private set; } // from BLKCNT or BYTECNT
public int ResSpc { get; private set; }
public int Length { get; private set; }
@ -155,19 +164,31 @@ namespace SourceGen.Tools.Omf {
IsLibrary
}
/// <summary>
/// Parses an OMF segment header.
/// </summary>
/// <param name="data">File data.</param>
/// <param name="offset">Offset at which to start parsing.</param>
/// <param name="parseAsLibrary">Set to true to parse the header as if it were part
/// of a library file. Affects parsing of v1 headers.</param>
/// <param name="msgs">Notes and errors generated by the parser.</param>
/// <param name="segResult">Completed object, or null on failure.</param>
/// <returns>Result code.</returns>
public static ParseResult ParseSegment(byte[] data, int offset, bool parseAsLibrary,
out OmfSegment segResult) {
List<string> msgs, out OmfSegment segResult) {
segResult = null;
//Debug.WriteLine("PARSE offset=" + offset);
Debug.Assert(offset < data.Length);
if (data.Length - offset < MIN_HEADER_V0) {
// Definitely too small.
AddErrorMsg(msgs, offset, "remaining file space too small to hold segment");
return ParseResult.Failure;
}
//Debug.WriteLine("PARSE offset=" + offset);
OmfSegment newSeg = new OmfSegment();
newSeg.FileOffset = offset;
// Start with the version number. The meaning of everything else depends on this.
int minLen, expectedDispName;
@ -189,10 +210,13 @@ namespace SourceGen.Tools.Omf {
break;
default:
// invalid version, this is probably not OMF
AddErrorMsg(msgs, offset, "invalid segment type " + data[offset + 0x0f]);
return ParseResult.Failure;
}
if (data.Length - offset < minLen) {
// Too small for this version of the header.
AddErrorMsg(msgs, offset, "remaining file space too small to hold " +
newSeg.Version + " segment");
return ParseResult.Failure;
}
@ -227,6 +251,8 @@ namespace SourceGen.Tools.Omf {
expectedDispName += 4;
if (data.Length - offset < minLen + 4) {
AddErrorMsg(msgs, offset, "remaining file space too small to hold " +
newSeg.Version + " segment");
return ParseResult.Failure;
}
newSeg.TempOrg = RawData.GetWord(data, offset + 0x2c, 4, false);
@ -238,14 +264,14 @@ namespace SourceGen.Tools.Omf {
kindByte = data[offset + 0x0c];
if (!Enum.IsDefined(typeof(SegmentKind), kindByte & 0x1f)) {
// Example: Moria GS has a kind of $1F for its GLOBALS segment.
Debug.WriteLine("Invalid segment kind $" + kindByte.ToString("x2"));
AddErrorMsg(msgs, offset, "invalid segment kind $" + kindByte.ToString("x2"));
return ParseResult.Failure;
}
newSeg.Kind = (SegmentKind)(kindByte & 0x1f);
int kindAttrs = 0;
if ((kindByte & 0x20) != 0) {
kindAttrs |= (int)SegmentAttribute.PositionIndep;
kindAttrs |= (int)SegmentAttribute.PosnIndep;
}
if ((kindByte & 0x40) != 0) {
kindAttrs |= (int)SegmentAttribute.Private;
@ -258,7 +284,7 @@ namespace SourceGen.Tools.Omf {
// Yank all the attribute bits out at once. Don't worry about v2.0 vs. v2.1.
kindWord = RawData.GetWord(data, offset + 0x14, 2, false);
if (!Enum.IsDefined(typeof(SegmentKind), kindWord & 0x001f)) {
Debug.WriteLine("Invalid segment kind $" + kindWord.ToString("x4"));
AddErrorMsg(msgs, offset, "invalid segment kind $" + kindWord.ToString("x4"));
return ParseResult.Failure;
}
newSeg.Kind = (SegmentKind)(kindWord & 0x001f);
@ -268,6 +294,7 @@ namespace SourceGen.Tools.Omf {
// If we found a library dictionary segment, and we're not currently handling the
// file as a library, reject this and try again.
if (newSeg.Kind == SegmentKind.LibraryDict && !parseAsLibrary) {
AddInfoMsg(msgs, offset, "found Library Dictionary segment, retrying as library");
return ParseResult.IsLibrary;
}
@ -296,47 +323,57 @@ namespace SourceGen.Tools.Omf {
}
newSeg.FileLength = segLen;
//
// Perform validity checks. If any of these fail, we're probably reading something
// that isn't OMF (or, if this isn't the first segment, we might have gone off the
// rails at some point).
if (numLen != 4 || numSex != 0) {
Debug.WriteLine("Invalid NUMLEN (" + numLen + ") or NUMSEX (" + numSex + ")");
//
if (numLen != 4) {
AddErrorMsg(msgs, offset, "NUMLEN must be 4, was " + numLen);
return ParseResult.Failure;
}
if (numSex != 0) {
AddErrorMsg(msgs, offset, "NUMSEX must be 0, was " + numSex);
return ParseResult.Failure;
}
if (offset + segLen > data.Length) {
// Segment is longer than the file. (This can happen easily in a static lib.)
Debug.WriteLine("Segment exceeds EOF: offset=" + offset + " len=" + data.Length +
" segLen=" + segLen);
// Segment is longer than the file. (This can happen easily in a static lib if
// we're not parsing it as such.)
AddErrorMsg(msgs, offset, "segment file length exceeds EOF (segLen=" + segLen +
", remaining=" + (data.Length - offset) + ")");
return ParseResult.Failure;
}
if (dispName < expectedDispName || dispName > (segLen - LOAD_NAME_LEN)) {
Debug.WriteLine("Invalid DISPNAME " + dispName + " segLen=" + segLen);
AddErrorMsg(msgs, offset, "invalid DISPNAME " + dispName + " (expected " +
expectedDispName + ", segLen=" + segLen + ")");
return ParseResult.Failure;
}
if (dispData < expectedDispName + LOAD_NAME_LEN || dispData > (segLen - 1)) {
Debug.WriteLine("Invalid DISPDATA " + dispData + " segLen=" + segLen);
AddErrorMsg(msgs, offset, "invalid DISPDATA " + dispData + " (expected " +
(expectedDispName + LOAD_NAME_LEN) + ", segLen=" + segLen + ")");
return ParseResult.Failure;
}
if (newSeg.BankSize > 0x00010000) {
Debug.WriteLine("Invalid BANKSIZE $" + newSeg.BankSize.ToString("x"));
AddErrorMsg(msgs, offset, "invalid BANKSIZE $" + newSeg.BankSize.ToString("x"));
return ParseResult.Failure;
}
if (newSeg.Align > 0x00010000) {
Debug.WriteLine("Invalid ALIGN $" + newSeg.Align.ToString("x"));
AddErrorMsg(msgs, offset, "invalid ALIGN $" + newSeg.Align.ToString("x"));
return ParseResult.Failure;
}
if (newSeg.BankSize != 0x00010000 && newSeg.BankSize != 0) {
// This is fine, just a little weird.
Debug.WriteLine("Unusual BANKSIZE $" + newSeg.BankSize.ToString("x6"));
AddInfoMsg(msgs, offset, "unusual BANKSIZE $" + newSeg.BankSize.ToString("x6"));
}
if (newSeg.Align != 0 && newSeg.Align != 0x0100 && newSeg.Align != 0x00010000) {
// Unexpected; the loader will round up.
Debug.WriteLine("Unusual ALIGN $" + newSeg.Align.ToString("x6"));
AddInfoMsg(msgs, offset, "unusual ALIGN $" + newSeg.Align.ToString("x6"));
}
if (newSeg.Entry != 0 && newSeg.Entry >= newSeg.Length) {
// This is invalid, but if we got this far we might as well keep going.
Debug.WriteLine("Invalid ENTRY $" + newSeg.Entry.ToString("x6"));
AddInfoMsg(msgs, offset, "invalid ENTRY $" + newSeg.Entry.ToString("x6"));
}
// Extract LOADNAME. Fixed-width field, padded with spaces. Except for the
@ -354,29 +391,110 @@ namespace SourceGen.Tools.Omf {
// string preceded by length byte
int segNameLen = data[offset + segNameStart];
if (segNameStart + 1 + segNameLen > segLen) {
Debug.WriteLine("Var-width SEGNAME ran off end of segment (len=" +
segNameLen + ")");
AddInfoMsg(msgs, offset, "var-width SEGNAME ran off end of segment (len=" +
segNameLen + ", segLen=" + segLen + ")");
return ParseResult.Failure;
}
segName = Encoding.ASCII.GetString(data, offset + segNameStart + 1, segNameLen);
} else {
// fixed-width string
if (segNameStart + newSeg.LabLen > segLen) {
Debug.WriteLine("Fixed-width SEGNAME ran off end of segment (len=" +
newSeg.LabLen + ")");
AddInfoMsg(msgs, offset, "fixed-width SEGNAME ran off end of segment (LABLEN=" +
newSeg.LabLen + ", segLen=" + segLen + ")");
return ParseResult.Failure;
}
segName = ExtractString(data, offset + segNameStart, newSeg.LabLen);
}
Debug.WriteLine("LOADNAME='" + loadName + "' SEGNAME='" + segName + "'");
//AddInfoMsg(msgs, offset, "GOT LOADNAME='" + loadName + "' SEGNAME='" + segName + "'");
newSeg.LoadName = loadName;
newSeg.SegName = segName;
//
// Populate the "raw data" table. We add the fields shown in the specification in
// the order in which they appear.
//
if (newSeg.Version == SegmentVersion.v0_0 ||
(newSeg.Version == SegmentVersion.v1_0 && !parseAsLibrary)) {
newSeg.AddRaw("BLKCNT", blkByteCnt, 4, "blocks");
} else {
newSeg.AddRaw("BYTECNT", blkByteCnt, 4, "bytes");
}
newSeg.AddRaw("RESSPC", newSeg.ResSpc, 4, string.Empty);
newSeg.AddRaw("LENGTH", newSeg.Length, 4, string.Empty);
if (newSeg.Version <= SegmentVersion.v1_0) {
string attrStr = AttrsToString(newSeg.Attrs);
if (!string.IsNullOrEmpty(attrStr)) {
attrStr = " -" + attrStr;
}
newSeg.AddRaw("KIND", data[offset+0x0c], 1,
KindToString(newSeg.Kind) + attrStr);
} else {
newSeg.AddRaw("undefined", data[offset + 0x0c], 1, string.Empty);
}
newSeg.AddRaw("LABLEN", newSeg.LabLen, 1, string.Empty);
newSeg.AddRaw("NUMLEN", numLen, 1, "must be 4");
newSeg.AddRaw("VERSION", data[offset + 0x0f], 1, VersionToString(newSeg.Version));
newSeg.AddRaw("BANKSIZE", newSeg.BankSize, 4, string.Empty);
if (newSeg.Version >= SegmentVersion.v2_0) {
string attrStr = AttrsToString(newSeg.Attrs);
if (!string.IsNullOrEmpty(attrStr)) {
attrStr = " -" + attrStr;
}
newSeg.AddRaw("KIND", RawData.GetWord(data, offset + 0x14, 2, false), 2,
KindToString(newSeg.Kind) + attrStr);
newSeg.AddRaw("undefined", RawData.GetWord(data, offset + 0x16, 2, false), 2,
string.Empty);
} else {
newSeg.AddRaw("undefined", RawData.GetWord(data, offset + 0x14, 4, false), 4,
string.Empty);
}
newSeg.AddRaw("ORG", newSeg.Org, 4, string.Empty);
newSeg.AddRaw("ALIGN", newSeg.Align, 4, string.Empty);
newSeg.AddRaw("NUMSEX", numSex, 1, "must be 0");
if (newSeg.Version == SegmentVersion.v1_0) {
newSeg.AddRaw("LCBANK", newSeg.LcBank, 1, string.Empty);
} else {
newSeg.AddRaw("undefined", data[offset + 0x21], 1, string.Empty);
}
if (newSeg.Version >= SegmentVersion.v1_0) {
newSeg.AddRaw("SEGNUM", newSeg.SegNum, 2, string.Empty);
newSeg.AddRaw("ENTRY", newSeg.Entry, 4, string.Empty);
newSeg.AddRaw("DISPNAME", dispName, 2, string.Empty);
newSeg.AddRaw("DISPDATA", dispData, 2, string.Empty);
if (newSeg.Version >= SegmentVersion.v2_1) {
newSeg.AddRaw("tempORG", newSeg.TempOrg, 4, string.Empty);
}
newSeg.AddRaw("LOADNAME", loadName, 10, string.Empty);
}
newSeg.AddRaw("SEGNAME", segName, 0, string.Empty);
segResult = newSeg;
return ParseResult.Success;
}
//
// Helper functions.
//
private void AddRaw(string name, object value, int width, string note) {
if (value is byte) {
value = (int)(byte)value;
}
RawValues.Add(new NameValueNote(name, value, width, note));
}
private static void AddInfoMsg(List<string> msgs, int offset, string msg) {
msgs.Add("Note (+" + offset.ToString("x6") + "): " + msg);
}
private static void AddErrorMsg(List<string> msgs, int offset, string msg) {
msgs.Add("Error (+" + offset.ToString("x6") + "): " + msg);
}
/// <summary>
/// Extracts a fixed-length ASCII string, stopping early if a '\0' is encountered.
/// </summary>
private static string ExtractString(byte[] data, int offset, int len) {
StringBuilder sb = new StringBuilder();
for (int i = offset; i < offset + len; i++) {
@ -388,5 +506,54 @@ namespace SourceGen.Tools.Omf {
}
return sb.ToString();
}
/// <summary>
/// Converts a segment version to a human-readable string.
/// </summary>
public static string VersionToString(SegmentVersion vers) {
switch (vers) {
case SegmentVersion.v0_0: return "v0.0";
case SegmentVersion.v1_0: return "v1.0";
case SegmentVersion.v2_0: return "v2.0";
case SegmentVersion.v2_1: return "v2.1";
default: return "v?.?";
}
}
/// <summary>
/// Converts a segment kind to a human-readable string.
/// </summary>
public static string KindToString(SegmentKind kind) {
switch (kind) {
case SegmentKind.Code: return "Code";
case SegmentKind.Data: return "Data";
case SegmentKind.JumpTable: return "Jump Table";
case SegmentKind.PathName: return "Pathname";
case SegmentKind.LibraryDict: return "Library Dict";
case SegmentKind.Init: return "Init";
case SegmentKind.AbsoluteBank: return "Abs Bank";
case SegmentKind.DpStack: return "DP/Stack";
default: return "???";
}
}
public static string AttrsToString(SegmentAttribute attrs) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 16; i++) {
int bit = 1 << i;
if (((int)attrs & bit) != 0) {
SegmentAttribute attr = (SegmentAttribute)bit;
sb.Append(' ');
sb.Append(attr.ToString());
}
}
return sb.ToString();
}
public override string ToString() {
return "[OmfSegment " + SegNum + " '" + LoadName + "' '" + SegName + "']";
}
}
}

View File

@ -0,0 +1,135 @@
<!--
Copyright 2020 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.Omf.WpfGui.OmfSegmentViewer"
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.Omf.WpfGui"
xmlns:system="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="OMF Segment Viewer"
SizeToContent="Height" Width="600" ResizeMode="NoResize"
ShowInTaskbar="False" WindowStartupLocation="CenterOwner">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
<system:String x:Key="str_FileOffsetLenFmt">File offset {0}, length {1} ({2})</system:String>
</Window.Resources>
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<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.Row="0" Text="{Binding FileOffsetLen, FallbackValue=File Offset / Length}" Margin="0,8,0,0"/>
<TextBlock Grid.Row="1" Text="Header fields:" Margin="0,8,0,0"/>
<DataGrid Name="headerList" Grid.Row="2" Height="130" Margin="0,8,0,0"
IsReadOnly="True"
ItemsSource="{Binding HeaderItems}"
FontFamily="{StaticResource GeneralMonoFont}"
SnapsToDevicePixels="True"
GridLinesVisibility="Vertical"
VerticalGridLinesBrush="#FF7F7F7F"
AutoGenerateColumns="False"
HeadersVisibility="Column"
CanUserReorderColumns="False"
CanUserSortColumns="False"
SelectionMode="Single">
<DataGrid.Resources>
<!-- make the no-focus color the same as the in-focus color -->
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}"
Color="{x:Static SystemColors.HighlightColor}"/>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}"
Color="{x:Static SystemColors.HighlightTextColor}"/>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Width="100" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Value" Width="120" Binding="{Binding Value}"/>
<DataGridTextColumn Header="Notes" Width="280" Binding="{Binding Note}"/>
</DataGrid.Columns>
</DataGrid>
<TextBlock Grid.Row="3" Text="Records {N}:" Margin="0,8,0,0"/>
<DataGrid Name="recordList" Grid.Row="4" Height="130" Margin="0,8,0,0"
IsReadOnly="True"
ItemsSource="{Binding RecordItems}"
FontFamily="{StaticResource GeneralMonoFont}"
SnapsToDevicePixels="True"
GridLinesVisibility="Vertical"
VerticalGridLinesBrush="#FF7F7F7F"
AutoGenerateColumns="False"
HeadersVisibility="Column"
CanUserReorderColumns="False"
CanUserSortColumns="False"
SelectionMode="Single">
<DataGrid.Resources>
<!-- make the no-focus color the same as the in-focus color -->
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}"
Color="{x:Static SystemColors.HighlightColor}"/>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}"
Color="{x:Static SystemColors.HighlightTextColor}"/>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header="Type" Width="100" Binding="{Binding Type}"/>
<DataGridTextColumn Header="Len" Width="40" Binding="{Binding Len}"/>
<DataGridTextColumn Header="Value" Width="300" Binding="{Binding Value}"/>
</DataGrid.Columns>
</DataGrid>
<TextBlock Grid.Row="5" Text="Relocation table (for Load file segment):" Margin="0,8,0,0"/>
<DataGrid Name="relocList" Grid.Row="6" Height="130" Margin="0,8,0,0"
IsReadOnly="True"
ItemsSource="{Binding RelocItems}"
FontFamily="{StaticResource GeneralMonoFont}"
SnapsToDevicePixels="True"
GridLinesVisibility="Vertical"
VerticalGridLinesBrush="#FF7F7F7F"
AutoGenerateColumns="False"
HeadersVisibility="Column"
CanUserReorderColumns="False"
CanUserSortColumns="False"
SelectionMode="Single">
<DataGrid.Resources>
<!-- make the no-focus color the same as the in-focus color -->
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}"
Color="{x:Static SystemColors.HighlightColor}"/>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}"
Color="{x:Static SystemColors.HighlightTextColor}"/>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header="Offset" Width="100" Binding="{Binding Offset}"/>
<DataGridTextColumn Header="Reference" Width="120" Binding="{Binding Reference}"/>
<DataGridTextColumn Header="Width" Width="100" Binding="{Binding Width}"/>
<DataGridTextColumn Header="Shift" Width="280" Binding="{Binding Shift}"/>
</DataGrid.Columns>
</DataGrid>
<DockPanel Grid.Row="7" LastChildFill="False" Margin="0,16,0,0">
<Button DockPanel.Dock="Right" Content="Done" Width="70" IsCancel="True"/>
</DockPanel>
</Grid>
</Window>

View File

@ -0,0 +1,116 @@
/*
* Copyright 2020 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.Runtime.CompilerServices;
using System.Windows;
using Asm65;
namespace SourceGen.Tools.Omf.WpfGui {
/// <summary>
/// Apple IIgs OMF segment viewer.
/// </summary>
public partial class OmfSegmentViewer : Window, INotifyPropertyChanged {
private OmfFile mOmfFile;
private OmfSegment mOmfSeg;
private Formatter mFormatter;
// INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "") {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string mFileOffsetLen;
public string FileOffsetLen {
get { return mFileOffsetLen; }
set { mFileOffsetLen = value; OnPropertyChanged(); }
}
public class HeaderItem {
public string Name { get; private set; }
public string Value { get; private set; }
public string Note { get; private set; }
public HeaderItem(string name, string value, string note) {
Name = name;
Value = value;
Note = note;
}
}
public List<HeaderItem> HeaderItems { get; private set; } = new List<HeaderItem>();
public class RecordItem {
public string Type { get; private set; }
public string Len { get; private set; }
public string Value { get; private set; }
}
public List<RecordItem> RecordItems { get; private set; } = new List<RecordItem>();
public class RelocItem {
public string Offset { get; private set; }
public string Reference { get; private set; }
public string Width { get; private set; }
public string Shift { get; private set; }
}
public List<RelocItem> RelocItems { get; private set; } = new List<RelocItem>();
/// <summary>
/// Constructor.
/// </summary>
/// <param name="owner">Parent window.</param>
/// <param name="omfFile">OMF file object.</param>
/// <param name="omfSeg">Segment to view. Must be part of omfFile.</param>
/// <param name="formatter">Text formatter.</param>
public OmfSegmentViewer(Window owner, OmfFile omfFile, OmfSegment omfSeg,
Formatter formatter) {
InitializeComponent();
Owner = owner;
DataContext = this;
mOmfFile = omfFile;
mOmfSeg = omfSeg;
mFormatter = formatter;
string fmt = (string)FindResource("str_FileOffsetLenFmt");
FileOffsetLen = string.Format(fmt,
mFormatter.FormatOffset24(omfSeg.FileOffset),
omfSeg.FileLength,
mFormatter.FormatHexValue(omfSeg.FileLength, 4));
GenerateHeaderItems();
}
private void GenerateHeaderItems() {
foreach (OmfSegment.NameValueNote nvn in mOmfSeg.RawValues) {
string value;
if (nvn.Value is int) {
int byteWidth = nvn.Width;
if (byteWidth > 3) {
byteWidth = 3;
}
value = mFormatter.FormatHexValue((int)nvn.Value, byteWidth * 2);
} else {
value = nvn.Value.ToString();
}
HeaderItems.Add(new HeaderItem(nvn.Name, value, nvn.Note));
}
}
}
}

View File

@ -41,12 +41,12 @@ limitations under the License.
<DockPanel Grid.Row="0">
<TextBlock DockPanel.Dock="Left" Text="File:" Margin="0,1,0,0"/>
<TextBox DockPanel.Dock="Left" IsReadOnly="True" Margin="8,0,0,0" Text="C:\\File\Name\Here"/>
<TextBox DockPanel.Dock="Left" IsReadOnly="True" Margin="8,0,0,0" Text="{Binding PathName}"/>
</DockPanel>
<TextBlock Grid.Row="1" Text="File is a {something}, with {N} segments" Margin="0,8,0,0"/>
<DataGrid Name="segmentList" Grid.Row="2" Height="200" Margin="0,8,0,0"
<DataGrid Name="segmentList" Grid.Row="2" Height="210" Margin="0,8,0,0"
IsReadOnly="True"
ItemsSource="{Binding SegmentListItems}"
FontFamily="{StaticResource GeneralMonoFont}"
@ -67,17 +67,17 @@ limitations under the License.
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header="SEGNUM" Width="50" Binding="{Binding SegNum}"/>
<DataGridTextColumn Header="KIND" Width="80" Binding="{Binding Kind}"/>
<DataGridTextColumn Header="LOADNAME" Width="100" Binding="{Binding LoadName}"/>
<DataGridTextColumn Header="SEGNAME" Width="100" Binding="{Binding SegName}"/>
<DataGridTextColumn Header="LENGTH" Width="80" Binding="{Binding MemLength}"/>
<DataGridTextColumn Header="File Length" Width="100" Binding="{Binding FileLength}"/>
<DataGridTextColumn Header="VER" Width="40" Binding="{Binding Version}"/>
<DataGridTextColumn Header="KIND" Width="100" Binding="{Binding Kind}"/>
<DataGridTextColumn Header="LOADNAME" Width="80" Binding="{Binding LoadName}"/>
<DataGridTextColumn Header="SEGNAME" Width="130" Binding="{Binding SegName}"/>
<DataGridTextColumn Header="LENGTH" Width="80" Binding="{Binding Length}"/>
</DataGrid.Columns>
</DataGrid>
<TextBlock Grid.Row="3" Text="File notes:" Margin="0,8,0,0"/>
<TextBlock Grid.Row="3" Text="Notes and errors:" Margin="0,8,0,0"/>
<TextBox Grid.Row="4" Margin="0,4,0,0" Height="60"
Text="Test&#x0d;stuff1&#x0d;stuff2&#x0d;stuff3"
Text="{Binding MessageStrings}"
IsReadOnly="True" VerticalScrollBarVisibility="Auto">
</TextBox>

View File

@ -19,15 +19,19 @@ using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Asm65;
namespace SourceGen.Tools.Omf.WpfGui {
/// <summary>
/// Apple IIgs OMF file viewer.
/// </summary>
public partial class OmfViewer : Window, INotifyPropertyChanged {
private string mPathName;
private byte[] mFileData;
private OmfFile mOmfFile;
private Formatter mFormatter;
//private Brush mDefaultLabelColor = SystemColors.WindowTextBrush;
@ -38,65 +42,81 @@ namespace SourceGen.Tools.Omf.WpfGui {
}
public class SegmentListItem {
private OmfSegment mOmfSeg;
public OmfSegment OmfSeg { get; private set; }
public int SegNum {
get {
return mOmfSeg.SegNum;
}
get { return OmfSeg.SegNum; }
}
public string Version {
get { return OmfSegment.VersionToString(OmfSeg.Version); }
}
public string Kind {
get {
return mOmfSeg.Kind.ToString();
}
get { return OmfSegment.KindToString(OmfSeg.Kind); }
}
public string LoadName {
get {
return mOmfSeg.LoadName;
}
get { return OmfSeg.LoadName; }
}
public string SegName {
get {
return mOmfSeg.SegName;
}
get { return OmfSeg.SegName; }
}
public int MemLength {
get {
return mOmfSeg.Length;
}
public int Length {
get { return OmfSeg.Length; }
}
public int FileLength {
get {
return mOmfSeg.FileLength;
}
get { return OmfSeg.FileLength; }
}
public SegmentListItem(OmfSegment omfSeg) {
mOmfSeg = omfSeg;
OmfSeg = omfSeg;
}
public override string ToString() {
return "[SegmentListItem " + OmfSeg + "]";
}
}
public List<SegmentListItem> SegmentListItems { get; private set; } = new List<SegmentListItem>();
private string mPathName;
public string PathName {
get { return mPathName; }
set { mPathName = value; OnPropertyChanged(); }
}
public OmfViewer(Window owner, string pathName, byte[] data) {
private string mMessageStrings;
public string MessageStrings {
get { return mMessageStrings; }
set { mMessageStrings = value; OnPropertyChanged(); }
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="owner">Parent window.</param>
/// <param name="pathName">Path to file on disk. Only used for display.</param>
/// <param name="data">File contents.</param>
public OmfViewer(Window owner, string pathName, byte[] data, Formatter formatter) {
InitializeComponent();
Owner = owner;
DataContext = this;
mPathName = pathName;
PathName = pathName;
mFileData = data;
mFormatter = formatter;
OmfFile omfFile = new OmfFile(data);
omfFile.Analyze();
mOmfFile = new OmfFile(data);
mOmfFile.Analyze();
foreach (OmfSegment omfSeg in omfFile.SegmentList) {
foreach (OmfSegment omfSeg in mOmfFile.SegmentList) {
SegmentListItems.Add(new SegmentListItem(omfSeg));
}
MessageStrings = string.Join("\r\n", mOmfFile.MessageList.ToArray());
}
private void SegmentList_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
Debug.WriteLine("DCLICK");
SegmentListItem item = (SegmentListItem)((DataGrid)sender).SelectedItem;
OmfSegmentViewer dlg = new OmfSegmentViewer(this, mOmfFile, item.OmfSeg, mFormatter);
dlg.ShowDialog();
}
}
}