diff --git a/SourceGen/DisasmProject.cs b/SourceGen/DisasmProject.cs
index a2b9d06..2d79ade 100644
--- a/SourceGen/DisasmProject.cs
+++ b/SourceGen/DisasmProject.cs
@@ -288,7 +288,8 @@ namespace SourceGen {
/// Prepares the DisasmProject for use as a new project.
///
/// 65xx data file contents.
- /// Data file's filename (not pathname).
+ /// Data file's filename (not pathname). Only used for
+ /// cosmetic stuff, e.g. exporting to text; not stored in project.
public void PrepForNew(byte[] fileData, string dataFileName) {
Debug.Assert(fileData.Length == FileDataLength);
diff --git a/SourceGen/Res/Strings.xaml b/SourceGen/Res/Strings.xaml
index c28d9ba..8b24a58 100644
--- a/SourceGen/Res/Strings.xaml
+++ b/SourceGen/Res/Strings.xaml
@@ -130,6 +130,7 @@ limitations under the License.
Visualization ignored
no files available
No exported symbols found.
+
Segment {0}: Kind={2}, SegName='{1}'
The file doesn't exist.
File is empty
Unable to load data file
@@ -141,6 +142,7 @@ limitations under the License.
The file is {0:N0} bytes long, but the project expected {1:N0}.
The file has CRC {0}, but the project expected {1}.
Failed
+ Success!
(none)
Failed while preparing the plugin directory {0}
Failed Preparing Plugin Directory
diff --git a/SourceGen/Res/Strings.xaml.cs b/SourceGen/Res/Strings.xaml.cs
index 86fd659..8a76d76 100644
--- a/SourceGen/Res/Strings.xaml.cs
+++ b/SourceGen/Res/Strings.xaml.cs
@@ -241,6 +241,8 @@ namespace SourceGen.Res {
(string)Application.Current.FindResource("str_NoFilesAvailable");
public static string NO_EXPORTED_SYMBOLS_FOUND =
(string)Application.Current.FindResource("str_NoExportedSymbolsFound");
+ public static string OMF_SEG_COMMENT_FMT =
+ (string)Application.Current.FindResource("str_OmfSegCommentFmt");
public static string OPEN_DATA_DOESNT_EXIST =
(string)Application.Current.FindResource("str_OpenDataDoesntExist");
public static string OPEN_DATA_EMPTY =
@@ -263,6 +265,8 @@ namespace SourceGen.Res {
(string)Application.Current.FindResource("str_OpenDataWrongLengthFmt");
public static string OPERATION_FAILED =
(string)Application.Current.FindResource("str_OperationFailed");
+ public static string OPERATION_SUCCEEDED =
+ (string)Application.Current.FindResource("str_OperationSucceeded");
public static string PARENTHETICAL_NONE =
(string)Application.Current.FindResource("str_ParentheticalNone");
public static string PLUGIN_DIR_FAIL_FMT =
diff --git a/SourceGen/SourceGen.csproj b/SourceGen/SourceGen.csproj
index 0d668b6..1d342fc 100644
--- a/SourceGen/SourceGen.csproj
+++ b/SourceGen/SourceGen.csproj
@@ -85,6 +85,7 @@
GenTestRunner.xaml
+
diff --git a/SourceGen/SystemDefs.cs b/SourceGen/SystemDefs.cs
index 8c79bd0..2431186 100644
--- a/SourceGen/SystemDefs.cs
+++ b/SourceGen/SystemDefs.cs
@@ -204,10 +204,19 @@ namespace SourceGen {
"': Unexpected contents '" + sdf.Contents + "'");
}
- foreach (SystemDef sd in sdf.Defs) {
- Debug.WriteLine("### " + sd);
- }
+ //foreach (SystemDef sd in sdf.Defs) {
+ // Debug.WriteLine("### " + sd);
+ //}
return sdf;
}
+
+ public SystemDef FindEntryByName(string name) {
+ foreach (SystemDef sd in Defs) {
+ if (sd.Name == name) {
+ return sd;
+ }
+ }
+ return null;
+ }
}
}
diff --git a/SourceGen/Tools/Omf/Loader.cs b/SourceGen/Tools/Omf/Loader.cs
new file mode 100644
index 0000000..0bcb762
--- /dev/null
+++ b/SourceGen/Tools/Omf/Loader.cs
@@ -0,0 +1,389 @@
+/*
+ * 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.IO;
+
+using CommonUtil;
+
+namespace SourceGen.Tools.Omf {
+ ///
+ /// Apple IIgs OMF loader. This works like the GS/OS System Loader, reading the contents
+ /// of an executable file and resolving the relocation records. This only handles Load
+ /// files, as Object and Library files contain unresolved references.
+ ///
+ public class Loader {
+ private const string IIGS_SYSTEM_DEF = "Apple IIgs (GS/OS)";
+
+ private OmfFile mOmfFile;
+
+ private byte[] mLoadedData;
+ private DisasmProject mNewProject;
+
+ private class SegmentMapEntry {
+ public OmfSegment Segment { get; private set; }
+ public int Address { get; private set; }
+
+ public SegmentMapEntry(OmfSegment omfSeg, int address) {
+ Segment = omfSeg;
+ Address = address;
+ }
+ }
+ private List mSegmentMap;
+
+
+ ///
+ /// Constructor.
+ ///
+ /// OMF file to load.
+ public Loader(OmfFile omfFile) {
+ Debug.Assert(omfFile.OmfFileKind == OmfFile.FileKind.Load);
+
+ mOmfFile = omfFile;
+ }
+
+ ///
+ /// Prepares the loaded form of the binary and the disassembly project.
+ ///
+ public bool Prepare() {
+ if (!CreateMap()) {
+ mSegmentMap = null;
+ return false;
+ }
+
+ Debug.WriteLine("Segment map:");
+ for (int i = 0; i < mSegmentMap.Count; i++) {
+ SegmentMapEntry ent = mSegmentMap[i];
+ if (ent == null) {
+ Debug.Assert(i == 0 || i == 1); // initial hole and optional ~ExpressLoad
+ continue;
+ }
+ OmfSegment omfSeg = ent.Segment;
+ Debug.WriteLine(i + " " + ent.Address.ToString("x6") + " SegNum=" + omfSeg.SegNum +
+ " '" + omfSeg.SegName + "'");
+
+ Debug.Assert(i == ent.Segment.SegNum);
+ }
+
+ if (!GenerateOutput()) {
+ mSegmentMap = null;
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Writes the data and disasm project files.
+ ///
+ ///
+ ///
+ public bool WriteProjectFiles(string dataPathName, string projectPathName,
+ out string errMsg) {
+ Debug.WriteLine("Writing " + dataPathName + " and " + projectPathName);
+
+ using (FileStream fs = new FileStream(dataPathName, FileMode.Create)) {
+ fs.Write(mLoadedData, 0, mLoadedData.Length);
+ }
+
+ if (!ProjectFile.SerializeToFile(mNewProject, projectPathName, out errMsg)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Creates a map of file segments. The position of each segment in the list will
+ /// match the segment's position in the file, i.e. the segment in Map[5] will have
+ /// SEGNUM==5.
+ ///
+ ///
+ /// I'm assuming that the SEGNUM in the file matches the position. This seems to be
+ /// the case everywhere. ExpressLoad goes to some lengths to ensure this is still the
+ /// case after a file is "expressed", including a remap table so that loader calls made
+ /// by the application go to the right place (instead of, say, giving ~ExpressLoad a
+ /// SEGNUM of 255.)
+ ///
+ /// True on success.
+ private bool CreateMap() {
+ // Segments are numbered 1-N, so create a map with N+1 entries and leave first blank.
+ mSegmentMap = new List(mOmfFile.SegmentList.Count + 1);
+ mSegmentMap.Add(null);
+
+ // Create a bank in-use map.
+ bool[] inUse = new bool[256];
+
+ // Flag special memory as in-use.
+ inUse[0x00] = inUse[0x01] = inUse[0xe0] = inUse[0xe1] = true;
+
+ // Find segments that require specific addresses, and mark those banks as in use.
+ foreach (OmfSegment omfSeg in mOmfFile.SegmentList) {
+ if (omfSeg.Kind == OmfSegment.SegmentKind.DpStack) {
+ // This just allocates space in bank 0.
+ continue;
+ }
+ if (omfSeg.Length == 0) {
+ // Nothing to do here.
+ continue;
+ }
+
+ int addr;
+
+ if (omfSeg.Org == 0) {
+ // The docs say that a value of zero always means relocatable, but that
+ // would mean you can't set the "absolute bank" flag to position code or
+ // data in bank 0. I'm going to assume that's intentional, since people
+ // (a) shouldn't be doing that, and (b) can use DP/Stack instead (?).
+ continue;
+ }
+
+ addr = omfSeg.Org;
+ if ((omfSeg.Attrs & OmfSegment.SegmentAttribute.AbsBank) != 0) {
+ // Bank is specified, rest of address is not.
+ addr &= 0x00ff0000;
+ }
+
+ // Mark the banks as being in use. It's okay if multiple segments want the
+ // same space.
+ MarkBanks(addr, omfSeg.Length, inUse);
+ }
+
+ //
+ // Assign segments to banks. Note we always start at offset $0000 within a bank.
+ //
+
+ int nextBank = 0;
+ int dpAddr = 0x1000; // somewhat arbitrary
+ foreach (OmfSegment omfSeg in mOmfFile.SegmentList) {
+ if (omfSeg.Kind == OmfSegment.SegmentKind.DpStack || omfSeg.Length == 0) {
+ mSegmentMap.Add(new SegmentMapEntry(omfSeg, dpAddr));
+ dpAddr += omfSeg.Length;
+ if (dpAddr > 0x00010000) {
+ Debug.WriteLine("Stack/DP overflow");
+ return false;
+ }
+ continue;
+ }
+ if (omfSeg.IsExpressLoad) {
+ // We totally ignore these. Add a null ref as a placeholder.
+ mSegmentMap.Add(null);
+ continue;
+ }
+
+ int addr;
+
+ if (omfSeg.Org != 0) {
+ // Specific address requested.
+ addr = omfSeg.Org;
+ if ((omfSeg.Attrs & OmfSegment.SegmentAttribute.AbsBank) != 0) {
+ // just keep the bank
+ addr &= 0x00ff0000;
+ }
+ } else {
+ // Find next available spot with enough space.
+ while (true) {
+ while (nextBank < 256 && inUse[nextBank]) {
+ nextBank++;
+ }
+ if (nextBank == 256) {
+ // Should be impossible on any sane Apple IIgs Load file.
+ Debug.Assert(false);
+ return false;
+ }
+ if (!CheckBanks(nextBank << 16, omfSeg.Length, inUse)) {
+ // Didn't fit in the space.
+ nextBank++;
+ continue;
+ }
+
+ // We only go forward, so no need to mark them.
+
+ break;
+ }
+
+ addr = nextBank << 16;
+
+ // Advance nextBank. We do this by identifying the last address touched,
+ // and moving to the next bank.
+ int lastAddr = addr + omfSeg.Length - 1;
+ nextBank = (lastAddr >> 16) + 1;
+ }
+
+ SegmentMapEntry ent = new SegmentMapEntry(omfSeg, addr);
+ mSegmentMap.Add(ent);
+ }
+
+ return true;
+ }
+
+ private static bool CheckBanks(int addr, int memLen, bool[] inUse) {
+ Debug.Assert(memLen > 0);
+ while (memLen > 0) {
+ if (inUse[(addr >> 16) & 0xff]) {
+ return false;
+ }
+ addr += 65536;
+ memLen -= 65536;
+ }
+ return true;
+
+ }
+
+ private static bool MarkBanks(int addr, int memLen, bool[] inUse) {
+ Debug.Assert(memLen > 0);
+ while (memLen > 0) {
+ inUse[(addr >> 16) & 0xff] = true;
+ addr += 65536;
+ memLen -= 65536;
+ }
+ return true;
+ }
+
+ private bool GenerateOutput() {
+ // Sum up the segment lengths to get the total project size.
+ int totalLen = 0;
+ foreach (SegmentMapEntry ent in mSegmentMap) {
+ if (ent == null) {
+ continue;
+ }
+ totalLen += ent.Segment.Length;
+ }
+ Debug.WriteLine("Total length of loaded binary is " + totalLen);
+
+ byte[] data = new byte[totalLen];
+
+ // Create the project object.
+ DisasmProject proj = new DisasmProject();
+ proj.Initialize(data.Length);
+
+ // Try to get the Apple IIgs system definition. This is fragile, because it
+ // relies on the name in the JSON file, but it's optional. (If the default CPU
+ // type stops being 65816, we should be sure to set that here.)
+ try {
+ // TODO(maybe): encapsulate this somewhere else
+ string sysDefsPath = RuntimeDataAccess.GetPathName("SystemDefs.json");
+ SystemDefSet sds = SystemDefSet.ReadFile(sysDefsPath);
+ SystemDef sd = sds.FindEntryByName(IIGS_SYSTEM_DEF);
+ if (sd != null) {
+ proj.ApplySystemDef(sd);
+ } else {
+ Debug.WriteLine("Unable to find Apple IIgs system definition");
+ }
+ } catch (Exception) {
+ // never mind
+ Debug.WriteLine("Failed to apply Apple IIgs system definition");
+ }
+
+ // Add header comment.
+ string cmt = string.Format(Res.Strings.DEFAULT_HEADER_COMMENT_FMT, App.ProgramVersion);
+ proj.LongComments.Add(LineListGen.Line.HEADER_COMMENT_OFFSET,
+ new MultiLineComment(cmt));
+
+ ChangeSet cs = new ChangeSet(mSegmentMap.Count * 2);
+
+ // Load the segments, and add entries to the project.
+ int bufOffset = 0;
+ foreach (SegmentMapEntry ent in mSegmentMap) {
+ if (ent != null) {
+ // Perform relocation.
+ if (!RelocSegment(ent, data, bufOffset)) {
+ return false;
+ }
+
+ // Add an address entry.
+
+ // TODO: need to add multiple address entries if this straddles a segment.
+
+ int origAddr = proj.AddrMap.Get(bufOffset);
+ UndoableChange uc = UndoableChange.CreateAddressChange(bufOffset,
+ origAddr, ent.Address);
+ cs.Add(uc);
+
+ // Add a comment identifying the segment.
+ string segCmt = string.Format(Res.Strings.OMF_SEG_COMMENT_FMT,
+ ent.Segment.SegNum, ent.Segment.SegName, ent.Segment.Kind);
+ uc = UndoableChange.CreateLongCommentChange(bufOffset, null,
+ new MultiLineComment(segCmt));
+ cs.Add(uc);
+
+ bufOffset += ent.Segment.Length;
+ }
+ }
+
+ proj.PrepForNew(data, "new_proj");
+ proj.ApplyChanges(cs, false, out RangeSet unused);
+
+ mLoadedData = data;
+ mNewProject = proj;
+ return true;
+ }
+
+ private bool RelocSegment(SegmentMapEntry ent, byte[] data, int bufOffset) {
+ //const int INVALID_RELOC = 0x00ffffff;
+ byte[] srcData = ent.Segment.GetConstData();
+ Array.Copy(srcData, 0, data, bufOffset, srcData.Length);
+
+ foreach (OmfReloc omfRel in ent.Segment.Relocs) {
+ int relocAddr = omfRel.RelOffset;
+ if (omfRel.FileNum != -1 && omfRel.FileNum != 1) {
+ // Some other file; not much we can do with this.
+ Debug.WriteLine("Unable to process reloc with FileNum=" + omfRel.FileNum);
+ return false;
+ } else if (omfRel.SegNum == -1) {
+ // Within this segment.
+ relocAddr += ent.Address;
+ } else {
+ // Find other segment. This may fail if the file is damaged.
+ if (omfRel.SegNum < 0 || omfRel.SegNum >= mSegmentMap.Count ||
+ mSegmentMap[omfRel.SegNum] == null) {
+ Debug.WriteLine("Reloc SegNum=" + omfRel.SegNum + " not in map");
+ return false;
+ } else {
+ relocAddr += mSegmentMap[omfRel.SegNum].Address;
+ }
+ }
+
+ if (omfRel.Shift < 0) {
+ relocAddr >>= -omfRel.Shift;
+ } else if (omfRel.Shift > 0) {
+ relocAddr <<= omfRel.Shift;
+ }
+
+ switch (omfRel.Width) {
+ case 1:
+ data[bufOffset + omfRel.Offset] = (byte)(relocAddr);
+ break;
+ case 2:
+ data[bufOffset + omfRel.Offset] = (byte)(relocAddr);
+ data[bufOffset + omfRel.Offset + 1] = (byte)(relocAddr >> 8);
+ break;
+ case 3:
+ data[bufOffset + omfRel.Offset] = (byte)(relocAddr);
+ data[bufOffset + omfRel.Offset + 1] = (byte)(relocAddr >> 8);
+ data[bufOffset + omfRel.Offset + 2] = (byte)(relocAddr >> 16);
+ break;
+ default:
+ Debug.WriteLine("Invalid reloc width " + omfRel.Width);
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/SourceGen/Tools/Omf/OmfFile.cs b/SourceGen/Tools/Omf/OmfFile.cs
index afe3b0e..7eb976b 100644
--- a/SourceGen/Tools/Omf/OmfFile.cs
+++ b/SourceGen/Tools/Omf/OmfFile.cs
@@ -95,6 +95,7 @@ namespace SourceGen.Tools.Omf {
Debug.Assert(fileData.Length >= MIN_FILE_SIZE && fileData.Length <= MAX_FILE_SIZE);
mFileData = fileData;
+ // Set to Unknown until analysis completes.
OmfFileKind = FileKind.Unknown;
}
diff --git a/SourceGen/Tools/Omf/OmfSegment.cs b/SourceGen/Tools/Omf/OmfSegment.cs
index b2576df..d87b1b2 100644
--- a/SourceGen/Tools/Omf/OmfSegment.cs
+++ b/SourceGen/Tools/Omf/OmfSegment.cs
@@ -174,6 +174,31 @@ namespace SourceGen.Tools.Omf {
///
public List Relocs = new List();
+ ///
+ /// True if this is an ExpressLoad segment.
+ ///
+ public bool IsExpressLoad {
+ get {
+ if (Kind != SegmentKind.Data) {
+ return false;
+ }
+ if ((Attrs & SegmentAttribute.Dynamic) == 0) {
+ return false;
+ }
+ // Should be case-insensitive? I'm assuming it's not padded with spaces since
+ // it's longer than 10 chars.
+ if (!(SegName == EXPRESSLOAD || SegName == EXPRESSLOAD_OLD)) {
+ return false;
+ }
+ if (SegNum != 1) {
+ Debug.WriteLine("WEIRD: ~ExpressLoad not first segment");
+ }
+ return true;
+ }
+ }
+ private const string EXPRESSLOAD = "~ExpressLoad";
+ private const string EXPRESSLOAD_OLD = "ExpressLoad";
+
// Constructor is private; use ParseHeader() to create an instance.
private OmfSegment() { }
diff --git a/SourceGen/Tools/Omf/WpfGui/OmfViewer.xaml b/SourceGen/Tools/Omf/WpfGui/OmfViewer.xaml
index c882068..773aef4 100644
--- a/SourceGen/Tools/Omf/WpfGui/OmfViewer.xaml
+++ b/SourceGen/Tools/Omf/WpfGui/OmfViewer.xaml
@@ -38,6 +38,9 @@ limitations under the License.
This is {0}, with {1} segment
This is {0}, with {1} segments
This is not an Apple II OMF file
+
+ Unable to prepare data file for project.
+ Data file and project created.
@@ -93,7 +96,8 @@ limitations under the License.
-
+
diff --git a/SourceGen/Tools/Omf/WpfGui/OmfViewer.xaml.cs b/SourceGen/Tools/Omf/WpfGui/OmfViewer.xaml.cs
index 6bc983f..cf79bdb 100644
--- a/SourceGen/Tools/Omf/WpfGui/OmfViewer.xaml.cs
+++ b/SourceGen/Tools/Omf/WpfGui/OmfViewer.xaml.cs
@@ -17,10 +17,12 @@ 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 System.Windows.Input;
+using Microsoft.Win32;
using Asm65;
@@ -29,6 +31,8 @@ namespace SourceGen.Tools.Omf.WpfGui {
/// Apple IIgs OMF file viewer.
///
public partial class OmfViewer : Window, INotifyPropertyChanged {
+ private const string OUT_FILE_SUFFIX = "_ld";
+
private OmfFile mOmfFile;
private Formatter mFormatter;
@@ -93,6 +97,10 @@ namespace SourceGen.Tools.Omf.WpfGui {
set { mMessageStrings = value; OnPropertyChanged(); }
}
+ public bool IsLoadFile {
+ get { return mOmfFile.OmfFileKind == OmfFile.FileKind.Load; }
+ }
+
///
/// Constructor.
@@ -156,5 +164,43 @@ namespace SourceGen.Tools.Omf.WpfGui {
OmfSegmentViewer dlg = new OmfSegmentViewer(this, mOmfFile, item.OmfSeg, mFormatter);
dlg.ShowDialog();
}
+
+ private void GenerateProject_Click(object sender, RoutedEventArgs e) {
+ Loader loader = new Loader(mOmfFile);
+ if (!loader.Prepare()) {
+ // Unexpected. If there's a valid reason for this, we need to add details
+ // to the error message.
+ string msg = (string)FindResource("str_OmfLoaderFail");
+ MessageBox.Show(msg, Res.Strings.OPERATION_FAILED);
+ return;
+ }
+
+ SaveFileDialog fileDlg = new SaveFileDialog() {
+ Filter = Res.Strings.FILE_FILTER_ALL,
+ FilterIndex = 1,
+ ValidateNames = true,
+ AddExtension = true,
+ FileName = Path.GetFileName(PathName) + OUT_FILE_SUFFIX
+ };
+ if (fileDlg.ShowDialog() != true) {
+ Debug.WriteLine("Save canceled by user");
+ return;
+ }
+ string pathName = Path.GetFullPath(fileDlg.FileName);
+
+ if (!loader.WriteProjectFiles(pathName, pathName + ProjectFile.FILENAME_EXT,
+ out string errorMessage)) {
+ MessageBox.Show(Res.Strings.ERR_PROJECT_SAVE_FAIL + ": " + errorMessage,
+ Res.Strings.OPERATION_FAILED,
+ MessageBoxButton.OK, MessageBoxImage.Error);
+ return;
+ }
+
+ // Success! Show a message then close the dialog.
+ string smsg = (string)FindResource("str_OmfConvertSuccessful");
+ MessageBox.Show(smsg, Res.Strings.OPERATION_SUCCEEDED,
+ MessageBoxButton.OK, MessageBoxImage.Information);
+ DialogResult = true;
+ }
}
}