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

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>
<Compile Include="Helper.cs" /> <Compile Include="Helper.cs" />
<Compile Include="InverseBooleanConverter.cs" /> <Compile Include="InverseBooleanConverter.cs" />
<Compile Include="ListToStringConverter.cs" />
<Compile Include="MultiKeyInputGesture.cs" /> <Compile Include="MultiKeyInputGesture.cs" />
<Compile Include="Properties\AssemblyInfo.cs"> <Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType> <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 = 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(); ov.ShowDialog();
} }

View File

@ -87,6 +87,9 @@
<Compile Include="Tools\ApplesoftToHtml.cs" /> <Compile Include="Tools\ApplesoftToHtml.cs" />
<Compile Include="Tools\Omf\OmfFile.cs" /> <Compile Include="Tools\Omf\OmfFile.cs" />
<Compile Include="Tools\Omf\OmfSegment.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"> <Compile Include="Tools\Omf\WpfGui\OmfViewer.xaml.cs">
<DependentUpon>OmfViewer.xaml</DependentUpon> <DependentUpon>OmfViewer.xaml</DependentUpon>
</Compile> </Compile>
@ -281,6 +284,10 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="Tools\Omf\WpfGui\OmfSegmentViewer.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Tools\Omf\WpfGui\OmfViewer.xaml"> <Page Include="Tools\Omf\WpfGui\OmfViewer.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <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 /// Appendix F describes OMF v2.1, and Chapter 8 has some useful information about
/// how the loader works (e.g. page 205). /// how the loader works (e.g. page 205).
/// - "Undocumented Secrets of the Apple IIGS System Loader" by Neil Parker, /// - "Undocumented Secrets of the Apple IIGS System Loader" by Neil Parker,
/// http://nparker.llx.com/a2/loader.html . Among other things it documents ExpressLoad /// http://nparker.llx.com/a2/loader.html . Among other things it documents the
/// segments, something Apple apparently never did. /// contents of ExpressLoad segments, which I haven't found in an official reference.
/// - Apple IIgs Tech Note #66, "ExpressLoad Philosophy". /// - Apple IIgs Tech Note #66, "ExpressLoad Philosophy".
/// ///
/// Related: /// Related:
@ -76,19 +76,20 @@ namespace SourceGen.Tools.Omf {
Object, // output of assembler/compiler, before linking Object, // output of assembler/compiler, before linking
Library, // static code library Library, // static code library
RunTimeLibrary, // dynamic shared library RunTimeLibrary, // dynamic shared library
Indeterminate, // valid OMF, but type is indeterminate
Foreign // not OMF, or not IIgs OMF Foreign // not OMF, or not IIgs OMF
} }
public FileKind OmfFileKind { get; private set; } public FileKind OmfFileKind { get; private set; }
private bool mIsDamaged; private bool mIsDamaged;
private string mDamageMsg = string.Empty;
private List<OmfSegment> mSegmentList = new List<OmfSegment>(); private List<OmfSegment> mSegmentList = new List<OmfSegment>();
public List<OmfSegment> SegmentList { public List<OmfSegment> SegmentList {
get { return mSegmentList; } get { return mSegmentList; }
} }
public List<string> MessageList { get; private set; } = new List<string>();
/// <summary> /// <summary>
/// Constructor. /// Constructor.
@ -105,7 +106,13 @@ namespace SourceGen.Tools.Omf {
OmfSegment.ParseResult result = DoAnalyze(false); OmfSegment.ParseResult result = DoAnalyze(false);
if (result == OmfSegment.ParseResult.IsLibrary || if (result == OmfSegment.ParseResult.IsLibrary ||
result == OmfSegment.ParseResult.Failure) { 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 offset = 0;
int len = mFileData.Length; int len = mFileData.Length;
List<string> msgs = new List<string>();
while (len > 0) { while (len > 0) {
OmfSegment.ParseResult result = OmfSegment.ParseResult result = OmfSegment.ParseSegment(mFileData, offset,
OmfSegment.ParseSegment(mFileData, offset, parseAsLibrary, out OmfSegment seg); parseAsLibrary, msgs, out OmfSegment seg);
if (result == OmfSegment.ParseResult.Failure) {
// parsing failed; reject file or stop early MessageList.Clear();
if (first) { foreach (string str in msgs) {
OmfFileKind = FileKind.Foreign; MessageList.Add(str);
} else { }
mIsDamaged = true;
mDamageMsg = string.Format("File may be damaged; ignoring last {0} bytes", if (result == OmfSegment.ParseResult.IsLibrary) {
mFileData.Length - offset);
}
return result;
} else if (result == OmfSegment.ParseResult.IsLibrary) {
// Need to start over in library mode. // Need to start over in library mode.
Debug.WriteLine("Restarting in library mode"); Debug.WriteLine("Restarting in library mode");
return result; 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); Debug.Assert(seg.FileLength > 0);
mSegmentList.Add(seg); mSegmentList.Add(seg);

View File

@ -76,15 +76,23 @@ namespace SourceGen.Tools.Omf {
private const int LOAD_NAME_LEN = 10; private const int LOAD_NAME_LEN = 10;
public class NameValueNote { public class NameValueNote {
public string Name { get; set; } public string Name { get; private set; }
public object Value { get; set; } public object Value { get; private set; }
public string Note { get; 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> /// <summary>
/// Values pulled from file header. Useful for display. /// Values pulled from file header. Useful for display.
/// </summary> /// </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 } public enum SegmentVersion { v0_0, v1_0, v2_0, v2_1 }
@ -107,9 +115,9 @@ namespace SourceGen.Tools.Omf {
BankRel = 0x0100, // v2.1 BankRel = 0x0100, // v2.1
Skip = 0x0200, // v2.1 Skip = 0x0200, // v2.1
Reloadable = 0x0400, // v2.0 Reloadable = 0x0400, // v2.0
AbsoluteBank = 0x0800, // v2.0 AbsBank = 0x0800, // v2.0
NoSpecial = 0x1000, // v2.0 NoSpecial = 0x1000, // v2.0
PositionIndep = 0x2000, // PosnIndep = 0x2000, //
Private = 0x4000, // Private = 0x4000, //
Dynamic = 0x8000 // Dynamic = 0x8000 //
} }
@ -118,6 +126,7 @@ namespace SourceGen.Tools.Omf {
// Header fields. // Header fields.
// //
public int FileOffset { get; private set; }
public int FileLength { get; private set; } // from BLKCNT or BYTECNT public int FileLength { get; private set; } // from BLKCNT or BYTECNT
public int ResSpc { get; private set; } public int ResSpc { get; private set; }
public int Length { get; private set; } public int Length { get; private set; }
@ -155,19 +164,31 @@ namespace SourceGen.Tools.Omf {
IsLibrary 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, public static ParseResult ParseSegment(byte[] data, int offset, bool parseAsLibrary,
out OmfSegment segResult) { List<string> msgs, out OmfSegment segResult) {
segResult = null; segResult = null;
//Debug.WriteLine("PARSE offset=" + offset);
Debug.Assert(offset < data.Length); Debug.Assert(offset < data.Length);
if (data.Length - offset < MIN_HEADER_V0) { if (data.Length - offset < MIN_HEADER_V0) {
// Definitely too small. // Definitely too small.
AddErrorMsg(msgs, offset, "remaining file space too small to hold segment");
return ParseResult.Failure; return ParseResult.Failure;
} }
//Debug.WriteLine("PARSE offset=" + offset);
OmfSegment newSeg = new OmfSegment(); OmfSegment newSeg = new OmfSegment();
newSeg.FileOffset = offset;
// Start with the version number. The meaning of everything else depends on this. // Start with the version number. The meaning of everything else depends on this.
int minLen, expectedDispName; int minLen, expectedDispName;
@ -189,10 +210,13 @@ namespace SourceGen.Tools.Omf {
break; break;
default: default:
// invalid version, this is probably not OMF // invalid version, this is probably not OMF
AddErrorMsg(msgs, offset, "invalid segment type " + data[offset + 0x0f]);
return ParseResult.Failure; return ParseResult.Failure;
} }
if (data.Length - offset < minLen) { if (data.Length - offset < minLen) {
// Too small for this version of the header. // Too small for this version of the header.
AddErrorMsg(msgs, offset, "remaining file space too small to hold " +
newSeg.Version + " segment");
return ParseResult.Failure; return ParseResult.Failure;
} }
@ -227,6 +251,8 @@ namespace SourceGen.Tools.Omf {
expectedDispName += 4; expectedDispName += 4;
if (data.Length - offset < minLen + 4) { if (data.Length - offset < minLen + 4) {
AddErrorMsg(msgs, offset, "remaining file space too small to hold " +
newSeg.Version + " segment");
return ParseResult.Failure; return ParseResult.Failure;
} }
newSeg.TempOrg = RawData.GetWord(data, offset + 0x2c, 4, false); newSeg.TempOrg = RawData.GetWord(data, offset + 0x2c, 4, false);
@ -238,14 +264,14 @@ namespace SourceGen.Tools.Omf {
kindByte = data[offset + 0x0c]; kindByte = data[offset + 0x0c];
if (!Enum.IsDefined(typeof(SegmentKind), kindByte & 0x1f)) { if (!Enum.IsDefined(typeof(SegmentKind), kindByte & 0x1f)) {
// Example: Moria GS has a kind of $1F for its GLOBALS segment. // 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; return ParseResult.Failure;
} }
newSeg.Kind = (SegmentKind)(kindByte & 0x1f); newSeg.Kind = (SegmentKind)(kindByte & 0x1f);
int kindAttrs = 0; int kindAttrs = 0;
if ((kindByte & 0x20) != 0) { if ((kindByte & 0x20) != 0) {
kindAttrs |= (int)SegmentAttribute.PositionIndep; kindAttrs |= (int)SegmentAttribute.PosnIndep;
} }
if ((kindByte & 0x40) != 0) { if ((kindByte & 0x40) != 0) {
kindAttrs |= (int)SegmentAttribute.Private; 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. // 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); kindWord = RawData.GetWord(data, offset + 0x14, 2, false);
if (!Enum.IsDefined(typeof(SegmentKind), kindWord & 0x001f)) { 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; return ParseResult.Failure;
} }
newSeg.Kind = (SegmentKind)(kindWord & 0x001f); 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 // If we found a library dictionary segment, and we're not currently handling the
// file as a library, reject this and try again. // file as a library, reject this and try again.
if (newSeg.Kind == SegmentKind.LibraryDict && !parseAsLibrary) { if (newSeg.Kind == SegmentKind.LibraryDict && !parseAsLibrary) {
AddInfoMsg(msgs, offset, "found Library Dictionary segment, retrying as library");
return ParseResult.IsLibrary; return ParseResult.IsLibrary;
} }
@ -296,47 +323,57 @@ namespace SourceGen.Tools.Omf {
} }
newSeg.FileLength = segLen; newSeg.FileLength = segLen;
//
// Perform validity checks. If any of these fail, we're probably reading something // 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 // that isn't OMF (or, if this isn't the first segment, we might have gone off the
// rails at some point). // 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; return ParseResult.Failure;
} }
if (offset + segLen > data.Length) { if (offset + segLen > data.Length) {
// Segment is longer than the file. (This can happen easily in a static lib.) // Segment is longer than the file. (This can happen easily in a static lib if
Debug.WriteLine("Segment exceeds EOF: offset=" + offset + " len=" + data.Length + // we're not parsing it as such.)
" segLen=" + segLen); AddErrorMsg(msgs, offset, "segment file length exceeds EOF (segLen=" + segLen +
", remaining=" + (data.Length - offset) + ")");
return ParseResult.Failure; return ParseResult.Failure;
} }
if (dispName < expectedDispName || dispName > (segLen - LOAD_NAME_LEN)) { 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; return ParseResult.Failure;
} }
if (dispData < expectedDispName + LOAD_NAME_LEN || dispData > (segLen - 1)) { 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; return ParseResult.Failure;
} }
if (newSeg.BankSize > 0x00010000) { if (newSeg.BankSize > 0x00010000) {
Debug.WriteLine("Invalid BANKSIZE $" + newSeg.BankSize.ToString("x")); AddErrorMsg(msgs, offset, "invalid BANKSIZE $" + newSeg.BankSize.ToString("x"));
return ParseResult.Failure; return ParseResult.Failure;
} }
if (newSeg.Align > 0x00010000) { if (newSeg.Align > 0x00010000) {
Debug.WriteLine("Invalid ALIGN $" + newSeg.Align.ToString("x")); AddErrorMsg(msgs, offset, "invalid ALIGN $" + newSeg.Align.ToString("x"));
return ParseResult.Failure; return ParseResult.Failure;
} }
if (newSeg.BankSize != 0x00010000 && newSeg.BankSize != 0) { if (newSeg.BankSize != 0x00010000 && newSeg.BankSize != 0) {
// This is fine, just a little weird. // 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) { if (newSeg.Align != 0 && newSeg.Align != 0x0100 && newSeg.Align != 0x00010000) {
// Unexpected; the loader will round up. // 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) { if (newSeg.Entry != 0 && newSeg.Entry >= newSeg.Length) {
// This is invalid, but if we got this far we might as well keep going. // 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 // Extract LOADNAME. Fixed-width field, padded with spaces. Except for the
@ -354,29 +391,110 @@ namespace SourceGen.Tools.Omf {
// string preceded by length byte // string preceded by length byte
int segNameLen = data[offset + segNameStart]; int segNameLen = data[offset + segNameStart];
if (segNameStart + 1 + segNameLen > segLen) { if (segNameStart + 1 + segNameLen > segLen) {
Debug.WriteLine("Var-width SEGNAME ran off end of segment (len=" + AddInfoMsg(msgs, offset, "var-width SEGNAME ran off end of segment (len=" +
segNameLen + ")"); segNameLen + ", segLen=" + segLen + ")");
return ParseResult.Failure; return ParseResult.Failure;
} }
segName = Encoding.ASCII.GetString(data, offset + segNameStart + 1, segNameLen); segName = Encoding.ASCII.GetString(data, offset + segNameStart + 1, segNameLen);
} else { } else {
// fixed-width string // fixed-width string
if (segNameStart + newSeg.LabLen > segLen) { if (segNameStart + newSeg.LabLen > segLen) {
Debug.WriteLine("Fixed-width SEGNAME ran off end of segment (len=" + AddInfoMsg(msgs, offset, "fixed-width SEGNAME ran off end of segment (LABLEN=" +
newSeg.LabLen + ")"); newSeg.LabLen + ", segLen=" + segLen + ")");
return ParseResult.Failure; return ParseResult.Failure;
} }
segName = ExtractString(data, offset + segNameStart, newSeg.LabLen); 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.LoadName = loadName;
newSeg.SegName = segName; 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; segResult = newSeg;
return ParseResult.Success; 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) { private static string ExtractString(byte[] data, int offset, int len) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (int i = offset; i < offset + len; i++) { for (int i = offset; i < offset + len; i++) {
@ -388,5 +506,54 @@ namespace SourceGen.Tools.Omf {
} }
return sb.ToString(); 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"> <DockPanel Grid.Row="0">
<TextBlock DockPanel.Dock="Left" Text="File:" Margin="0,1,0,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> </DockPanel>
<TextBlock Grid.Row="1" Text="File is a {something}, with {N} segments" Margin="0,8,0,0"/> <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" IsReadOnly="True"
ItemsSource="{Binding SegmentListItems}" ItemsSource="{Binding SegmentListItems}"
FontFamily="{StaticResource GeneralMonoFont}" FontFamily="{StaticResource GeneralMonoFont}"
@ -67,17 +67,17 @@ limitations under the License.
</DataGrid.Resources> </DataGrid.Resources>
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTextColumn Header="SEGNUM" Width="50" Binding="{Binding SegNum}"/> <DataGridTextColumn Header="SEGNUM" Width="50" Binding="{Binding SegNum}"/>
<DataGridTextColumn Header="KIND" Width="80" Binding="{Binding Kind}"/> <DataGridTextColumn Header="VER" Width="40" Binding="{Binding Version}"/>
<DataGridTextColumn Header="LOADNAME" Width="100" Binding="{Binding LoadName}"/> <DataGridTextColumn Header="KIND" Width="100" Binding="{Binding Kind}"/>
<DataGridTextColumn Header="SEGNAME" Width="100" Binding="{Binding SegName}"/> <DataGridTextColumn Header="LOADNAME" Width="80" Binding="{Binding LoadName}"/>
<DataGridTextColumn Header="LENGTH" Width="80" Binding="{Binding MemLength}"/> <DataGridTextColumn Header="SEGNAME" Width="130" Binding="{Binding SegName}"/>
<DataGridTextColumn Header="File Length" Width="100" Binding="{Binding FileLength}"/> <DataGridTextColumn Header="LENGTH" Width="80" Binding="{Binding Length}"/>
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </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" <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"> IsReadOnly="True" VerticalScrollBarVisibility="Auto">
</TextBox> </TextBox>

View File

@ -19,15 +19,19 @@ using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Windows; using System.Windows;
using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using Asm65;
namespace SourceGen.Tools.Omf.WpfGui { namespace SourceGen.Tools.Omf.WpfGui {
/// <summary> /// <summary>
/// Apple IIgs OMF file viewer. /// Apple IIgs OMF file viewer.
/// </summary> /// </summary>
public partial class OmfViewer : Window, INotifyPropertyChanged { public partial class OmfViewer : Window, INotifyPropertyChanged {
private string mPathName;
private byte[] mFileData; private byte[] mFileData;
private OmfFile mOmfFile;
private Formatter mFormatter;
//private Brush mDefaultLabelColor = SystemColors.WindowTextBrush; //private Brush mDefaultLabelColor = SystemColors.WindowTextBrush;
@ -38,65 +42,81 @@ namespace SourceGen.Tools.Omf.WpfGui {
} }
public class SegmentListItem { public class SegmentListItem {
private OmfSegment mOmfSeg; public OmfSegment OmfSeg { get; private set; }
public int SegNum { public int SegNum {
get { get { return OmfSeg.SegNum; }
return mOmfSeg.SegNum; }
} public string Version {
get { return OmfSegment.VersionToString(OmfSeg.Version); }
} }
public string Kind { public string Kind {
get { get { return OmfSegment.KindToString(OmfSeg.Kind); }
return mOmfSeg.Kind.ToString();
}
} }
public string LoadName { public string LoadName {
get { get { return OmfSeg.LoadName; }
return mOmfSeg.LoadName;
}
} }
public string SegName { public string SegName {
get { get { return OmfSeg.SegName; }
return mOmfSeg.SegName;
}
} }
public int MemLength { public int Length {
get { get { return OmfSeg.Length; }
return mOmfSeg.Length;
}
} }
public int FileLength { public int FileLength {
get { get { return OmfSeg.FileLength; }
return mOmfSeg.FileLength;
}
} }
public SegmentListItem(OmfSegment omfSeg) { 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>(); 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(); InitializeComponent();
Owner = owner; Owner = owner;
DataContext = this; DataContext = this;
mPathName = pathName; PathName = pathName;
mFileData = data; mFileData = data;
mFormatter = formatter;
OmfFile omfFile = new OmfFile(data); mOmfFile = new OmfFile(data);
omfFile.Analyze(); mOmfFile.Analyze();
foreach (OmfSegment omfSeg in omfFile.SegmentList) { foreach (OmfSegment omfSeg in mOmfFile.SegmentList) {
SegmentListItems.Add(new SegmentListItem(omfSeg)); SegmentListItems.Add(new SegmentListItem(omfSeg));
} }
MessageStrings = string.Join("\r\n", mOmfFile.MessageList.ToArray());
} }
private void SegmentList_MouseDoubleClick(object sender, MouseButtonEventArgs e) { 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();
} }
} }
} }