2019-05-02 22:45:40 +00:00
|
|
|
|
/*
|
|
|
|
|
* Copyright 2019 faddenSoft
|
|
|
|
|
*
|
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
|
*
|
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
*
|
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
|
* limitations under the License.
|
|
|
|
|
*/
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2019-12-04 23:50:19 +00:00
|
|
|
|
using System.Collections.ObjectModel;
|
2019-05-02 22:45:40 +00:00
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Web.Script.Serialization;
|
|
|
|
|
using System.Windows.Media;
|
|
|
|
|
|
|
|
|
|
using CommonUtil;
|
|
|
|
|
|
2019-07-20 20:28:10 +00:00
|
|
|
|
namespace SourceGen {
|
2019-05-02 22:45:40 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Load and save project data from/to a ".dis65" file.
|
|
|
|
|
///
|
|
|
|
|
/// The various data structures get cloned to avoid situations where you can't freely
|
|
|
|
|
/// rename and rearrange code because it's serialized directly to the save file. We
|
|
|
|
|
/// want to provide a layer of indirection on fields, output enums as strings rather
|
|
|
|
|
/// than digits, etc.
|
|
|
|
|
///
|
|
|
|
|
/// Also, the JavaScriptSerializer can't deal with integer keys, so we have to convert
|
|
|
|
|
/// dictionaries that use those to have string keys.
|
|
|
|
|
///
|
|
|
|
|
/// On the deserialization side, we want to verify the inputs to avoid anything strange
|
|
|
|
|
/// getting loaded that could cause a crash or weird behavior. The goal is to discard
|
|
|
|
|
/// anything that looks wrong, providing a useful notification to the user, rather than
|
|
|
|
|
/// failing outright.
|
|
|
|
|
///
|
|
|
|
|
/// I'm expecting the save file format to expand and evolve over time, possibly in
|
|
|
|
|
/// incompatible ways that require independent load routines for old and new formats.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static class ProjectFile {
|
|
|
|
|
public const string FILENAME_EXT = ".dis65";
|
|
|
|
|
public static readonly string FILENAME_FILTER = Res.Strings.FILE_FILTER_DIS65;
|
|
|
|
|
|
|
|
|
|
// This is the version of content we're writing. Bump this any time we add anything.
|
|
|
|
|
// This doesn't create forward or backward compatibility issues, because JSON will
|
|
|
|
|
// ignore stuff that's in one side but not the other. However, if we're opening a
|
|
|
|
|
// newer file in an older program, it's worth letting the user know that some stuff
|
|
|
|
|
// may get lost as soon as they save the file.
|
2019-11-09 04:44:45 +00:00
|
|
|
|
public const int CONTENT_VERSION = 3;
|
2019-05-02 22:45:40 +00:00
|
|
|
|
|
|
|
|
|
private static readonly bool ADD_CRLF = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Serializes the project and writes it to the specified file.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="proj">Project to serialize.</param>
|
|
|
|
|
/// <param name="pathName">Output path name.</param>
|
|
|
|
|
/// <param name="errorMessage">Human-readable error string, or an empty string if all
|
|
|
|
|
/// went well.</param>
|
|
|
|
|
/// <returns>True on success.</returns>
|
|
|
|
|
public static bool SerializeToFile(DisasmProject proj, string pathName,
|
|
|
|
|
out string errorMessage) {
|
|
|
|
|
try {
|
|
|
|
|
string serializedData = SerializableProjectFile1.SerializeProject(proj);
|
|
|
|
|
if (ADD_CRLF) {
|
|
|
|
|
// Add some line breaks. This looks awful, but it makes text diffs
|
|
|
|
|
// much more useful.
|
|
|
|
|
serializedData = TextUtil.NonQuoteReplace(serializedData, "{", "{\r\n");
|
|
|
|
|
serializedData = TextUtil.NonQuoteReplace(serializedData, "},", "},\r\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check to see if the project file is read-only. We want to fail early
|
|
|
|
|
// so we don't leave our .TMP file sitting around -- the File.Delete() call
|
|
|
|
|
// will fail if the destination is read-only.
|
|
|
|
|
if (File.Exists(pathName) &&
|
|
|
|
|
(File.GetAttributes(pathName) & FileAttributes.ReadOnly) != 0) {
|
|
|
|
|
throw new IOException(string.Format(Res.Strings.ERR_FILE_READ_ONLY_FMT,
|
|
|
|
|
pathName));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The BOM is not required or recommended for UTF-8 files, but anecdotal
|
|
|
|
|
// evidence suggests that it's sometimes useful. Shouldn't cause any harm
|
|
|
|
|
// to have it in the project file. The explicit Encoding.UTF8 argument
|
|
|
|
|
// causes it to appear -- WriteAllText normally doesn't.
|
|
|
|
|
//
|
|
|
|
|
// Write to a temp file, then rename over original after write has succeeded.
|
|
|
|
|
string tmpPath = pathName + ".TMP";
|
|
|
|
|
File.WriteAllText(tmpPath, serializedData, Encoding.UTF8);
|
|
|
|
|
if (File.Exists(pathName)) {
|
|
|
|
|
File.Delete(pathName);
|
|
|
|
|
}
|
|
|
|
|
File.Move(tmpPath, pathName);
|
|
|
|
|
errorMessage = string.Empty;
|
|
|
|
|
return true;
|
|
|
|
|
} catch (Exception ex) {
|
|
|
|
|
errorMessage = ex.Message;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Reads the specified file and deserializes it into the project.
|
2019-08-10 21:24:19 +00:00
|
|
|
|
///
|
|
|
|
|
/// The deserialized form may include place-holder entries that can't be resolved
|
|
|
|
|
/// until the data file is available (see the ASCII_GENERIC string sub-type).
|
2019-05-02 22:45:40 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="pathName">Input path name.</param>
|
|
|
|
|
/// <param name="proj">Project to deserialize into.</param>
|
|
|
|
|
/// <param name="report">File load report, which may contain errors or warnings.</param>
|
|
|
|
|
/// <returns>True on success.</returns>
|
|
|
|
|
public static bool DeserializeFromFile(string pathName, DisasmProject proj,
|
|
|
|
|
out FileLoadReport report) {
|
|
|
|
|
Debug.WriteLine("Deserializing '" + pathName + "'");
|
|
|
|
|
report = new FileLoadReport(pathName);
|
|
|
|
|
string serializedData;
|
|
|
|
|
try {
|
|
|
|
|
serializedData = File.ReadAllText(pathName);
|
|
|
|
|
} catch (Exception ex) {
|
|
|
|
|
report.Add(FileLoadItem.Type.Error, Res.Strings.ERR_PROJECT_LOAD_FAIL +
|
|
|
|
|
": " + ex.Message);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (serializedData.StartsWith(SerializableProjectFile1.MAGIC)) {
|
|
|
|
|
// File is a match for SerializableProjectFile1. Strip header and deserialize.
|
|
|
|
|
serializedData = serializedData.Substring(SerializableProjectFile1.MAGIC.Length);
|
|
|
|
|
try {
|
|
|
|
|
bool ok = SerializableProjectFile1.DeserializeProject(serializedData,
|
|
|
|
|
proj, report);
|
|
|
|
|
if (ok) {
|
|
|
|
|
proj.UpdateCpuDef();
|
|
|
|
|
}
|
|
|
|
|
return ok;
|
|
|
|
|
} catch (Exception ex) {
|
|
|
|
|
// Ideally this won't happen -- errors should be caught explicitly. This
|
|
|
|
|
// is a catch-all to keep us from crashing on expectedly bad input.
|
|
|
|
|
report.Add(FileLoadItem.Type.Error,
|
|
|
|
|
Res.Strings.ERR_PROJECT_FILE_CORRUPT + ": " + ex);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
report.Add(FileLoadItem.Type.Error, Res.Strings.ERR_NOT_PROJECT_FILE);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if false
|
|
|
|
|
public Dictionary<string, object> IntKeysToStrings(Dictionary<int, object> input) {
|
|
|
|
|
Dictionary<string, object> output = new Dictionary<string, object>();
|
|
|
|
|
|
|
|
|
|
foreach (KeyValuePair<int, object> entry in input) {
|
|
|
|
|
output.Add(entry.Key.ToString(), entry.Value);
|
|
|
|
|
}
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
public Dictionary<int, object> StringKeysToInts(Dictionary<string, object> input) {
|
|
|
|
|
Dictionary<int, object> output = new Dictionary<int, object>();
|
|
|
|
|
|
|
|
|
|
foreach (KeyValuePair<string, object> entry in input) {
|
|
|
|
|
if (!int.TryParse(entry.Key, out int intKey)) {
|
|
|
|
|
throw new InvalidOperationException("bad non-int key: " + entry.Key);
|
|
|
|
|
}
|
|
|
|
|
output.Add(intKey, entry.Value);
|
|
|
|
|
}
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Somewhat sloppy-looking JSON state dump.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal class SerializableProjectFile1 {
|
|
|
|
|
// This appears at the top of the file, not as part of the JSON data. The version
|
2019-08-13 00:01:50 +00:00
|
|
|
|
// number refers to the file format version, not the application version. Only
|
|
|
|
|
// change this if a change is made that renders the file unreadable by previous versions.
|
2019-05-02 22:45:40 +00:00
|
|
|
|
public const string MAGIC = "### 6502bench SourceGen dis65 v1.0 ###";
|
|
|
|
|
|
|
|
|
|
public SerializableProjectFile1() { }
|
|
|
|
|
|
|
|
|
|
public class SerProjectProperties {
|
|
|
|
|
public string CpuName { get; set; }
|
|
|
|
|
public bool IncludeUndocumentedInstr { get; set; }
|
Optionally treat BRKs as two-byte instructions
Early data sheets listed BRK as one byte, but RTI after a BRK skips
the following byte, effectively making BRK a 2-byte instruction.
Sometimes, such as when diassembling Apple /// SOS code, it's handy
to treat it that way explicitly.
This change makes two-byte BRKs optional, controlled by a checkbox
in the project settings. In the system definitions it defaults to
true for Apple ///, false for all others.
ACME doesn't allow BRK to have an arg, and cc65 only allows it for
65816 code (?), so it's emitted as a hex blob for those assemblers.
Anyone wishing to target those assemblers should stick to 1-byte mode.
Extension scripts have to switch between formatting one byte of
inline data and formatting an instruction with a one-byte operand.
A helper function has been added to the plugin Util class.
To get some regression test coverage, 2022-extension-scripts has
been configured to use two-byte BRK.
Also, added/corrected some SOS constants.
See also issue #44.
2019-10-09 21:55:56 +00:00
|
|
|
|
public bool TwoByteBrk { get; set; }
|
2019-05-02 22:45:40 +00:00
|
|
|
|
public int EntryFlags { get; set; }
|
|
|
|
|
public string AutoLabelStyle { get; set; }
|
|
|
|
|
public SerAnalysisParameters AnalysisParams { get; set; }
|
|
|
|
|
public List<string> PlatformSymbolFileIdentifiers { get; set; }
|
|
|
|
|
public List<string> ExtensionScriptFileIdentifiers { get; set; }
|
|
|
|
|
public SortedList<string, SerDefSymbol> ProjectSyms { get; set; }
|
|
|
|
|
|
|
|
|
|
public SerProjectProperties() { }
|
|
|
|
|
public SerProjectProperties(ProjectProperties props) {
|
|
|
|
|
CpuName = Asm65.CpuDef.GetCpuNameFromType(props.CpuType);
|
|
|
|
|
IncludeUndocumentedInstr = props.IncludeUndocumentedInstr;
|
Optionally treat BRKs as two-byte instructions
Early data sheets listed BRK as one byte, but RTI after a BRK skips
the following byte, effectively making BRK a 2-byte instruction.
Sometimes, such as when diassembling Apple /// SOS code, it's handy
to treat it that way explicitly.
This change makes two-byte BRKs optional, controlled by a checkbox
in the project settings. In the system definitions it defaults to
true for Apple ///, false for all others.
ACME doesn't allow BRK to have an arg, and cc65 only allows it for
65816 code (?), so it's emitted as a hex blob for those assemblers.
Anyone wishing to target those assemblers should stick to 1-byte mode.
Extension scripts have to switch between formatting one byte of
inline data and formatting an instruction with a one-byte operand.
A helper function has been added to the plugin Util class.
To get some regression test coverage, 2022-extension-scripts has
been configured to use two-byte BRK.
Also, added/corrected some SOS constants.
See also issue #44.
2019-10-09 21:55:56 +00:00
|
|
|
|
TwoByteBrk = props.TwoByteBrk;
|
2019-05-02 22:45:40 +00:00
|
|
|
|
EntryFlags = props.EntryFlags.AsInt;
|
|
|
|
|
AutoLabelStyle = props.AutoLabelStyle.ToString();
|
|
|
|
|
AnalysisParams = new SerAnalysisParameters(props.AnalysisParams);
|
|
|
|
|
|
|
|
|
|
// External file identifiers require no conversion.
|
|
|
|
|
PlatformSymbolFileIdentifiers = props.PlatformSymbolFileIdentifiers;
|
|
|
|
|
ExtensionScriptFileIdentifiers = props.ExtensionScriptFileIdentifiers;
|
|
|
|
|
|
|
|
|
|
// Convert project-defined symbols to serializable form.
|
|
|
|
|
ProjectSyms = new SortedList<string, SerDefSymbol>();
|
|
|
|
|
foreach (KeyValuePair<string, DefSymbol> kvp in props.ProjectSyms) {
|
|
|
|
|
ProjectSyms.Add(kvp.Key, new SerDefSymbol(kvp.Value));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public class SerAnalysisParameters {
|
|
|
|
|
public bool AnalyzeUncategorizedData { get; set; }
|
2019-08-13 00:01:50 +00:00
|
|
|
|
public string DefaultTextScanMode { get; set; }
|
2019-05-02 22:45:40 +00:00
|
|
|
|
public int MinCharsForString { get; set; }
|
|
|
|
|
public bool SeekNearbyTargets { get; set; }
|
2019-09-02 22:57:59 +00:00
|
|
|
|
public bool SmartPlpHandling { get; set; }
|
2019-05-02 22:45:40 +00:00
|
|
|
|
|
|
|
|
|
public SerAnalysisParameters() { }
|
|
|
|
|
public SerAnalysisParameters(ProjectProperties.AnalysisParameters src) {
|
|
|
|
|
AnalyzeUncategorizedData = src.AnalyzeUncategorizedData;
|
2019-08-13 00:01:50 +00:00
|
|
|
|
DefaultTextScanMode = src.DefaultTextScanMode.ToString();
|
2019-05-02 22:45:40 +00:00
|
|
|
|
MinCharsForString = src.MinCharsForString;
|
|
|
|
|
SeekNearbyTargets = src.SeekNearbyTargets;
|
2019-09-02 22:57:59 +00:00
|
|
|
|
SmartPlpHandling = src.SmartPlpHandling;
|
2019-05-02 22:45:40 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public class SerAddressMap {
|
|
|
|
|
public int Offset { get; set; }
|
|
|
|
|
public int Addr { get; set; }
|
|
|
|
|
// Length is computed field, no need to serialize
|
|
|
|
|
|
|
|
|
|
public SerAddressMap() { }
|
|
|
|
|
public SerAddressMap(AddressMap.AddressMapEntry ent) {
|
|
|
|
|
Offset = ent.Offset;
|
|
|
|
|
Addr = ent.Addr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public class SerTypeHintRange {
|
|
|
|
|
public int Low { get; set; }
|
|
|
|
|
public int High { get; set; }
|
|
|
|
|
public string Hint { get; set; }
|
|
|
|
|
|
|
|
|
|
public SerTypeHintRange() { }
|
|
|
|
|
public SerTypeHintRange(int low, int high, string hintStr) {
|
|
|
|
|
Low = low;
|
|
|
|
|
High = high;
|
|
|
|
|
Hint = hintStr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public class SerMultiLineComment {
|
|
|
|
|
// NOTE: Text must be CRLF at line breaks.
|
|
|
|
|
public string Text { get; set; }
|
|
|
|
|
public bool BoxMode { get; set; }
|
|
|
|
|
public int MaxWidth { get; set; }
|
|
|
|
|
public int BackgroundColor { get; set; }
|
|
|
|
|
|
|
|
|
|
public SerMultiLineComment() { }
|
|
|
|
|
public SerMultiLineComment(MultiLineComment mlc) {
|
|
|
|
|
Text = mlc.Text;
|
|
|
|
|
BoxMode = mlc.BoxMode;
|
|
|
|
|
MaxWidth = mlc.MaxWidth;
|
|
|
|
|
BackgroundColor = ColorToInt(mlc.BackgroundColor);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public class SerSymbol {
|
|
|
|
|
public string Label { get; set; }
|
|
|
|
|
public int Value { get; set; }
|
|
|
|
|
public string Source { get; set; }
|
|
|
|
|
public string Type { get; set; }
|
2019-11-09 04:44:45 +00:00
|
|
|
|
public string LabelAnno { get; set; }
|
2019-05-02 22:45:40 +00:00
|
|
|
|
|
|
|
|
|
public SerSymbol() { }
|
|
|
|
|
public SerSymbol(Symbol sym) {
|
2019-11-17 00:34:42 +00:00
|
|
|
|
Label = sym.LabelWithoutTag; // use bare label here
|
2019-05-02 22:45:40 +00:00
|
|
|
|
Value = sym.Value;
|
|
|
|
|
Source = sym.SymbolSource.ToString();
|
|
|
|
|
Type = sym.SymbolType.ToString();
|
2019-11-09 04:44:45 +00:00
|
|
|
|
LabelAnno = sym.LabelAnno.ToString();
|
2019-05-02 22:45:40 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public class SerFormatDescriptor {
|
|
|
|
|
public int Length { get; set; }
|
|
|
|
|
public string Format { get; set; }
|
|
|
|
|
public string SubFormat { get; set; }
|
|
|
|
|
public SerWeakSymbolRef SymbolRef { get; set; }
|
|
|
|
|
|
|
|
|
|
public SerFormatDescriptor() { }
|
|
|
|
|
public SerFormatDescriptor(FormatDescriptor dfd) {
|
|
|
|
|
Length = dfd.Length;
|
|
|
|
|
Format = dfd.FormatType.ToString();
|
|
|
|
|
SubFormat = dfd.FormatSubType.ToString();
|
|
|
|
|
if (dfd.SymbolRef != null) {
|
|
|
|
|
SymbolRef = new SerWeakSymbolRef(dfd.SymbolRef);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public class SerWeakSymbolRef {
|
|
|
|
|
public string Label { get; set; }
|
|
|
|
|
public string Part { get; set; }
|
|
|
|
|
|
|
|
|
|
public SerWeakSymbolRef() { }
|
|
|
|
|
public SerWeakSymbolRef(WeakSymbolRef weakSym) {
|
2019-11-16 04:40:14 +00:00
|
|
|
|
Label = weakSym.Label; // retain non-unique tag in weak refs
|
2019-05-02 22:45:40 +00:00
|
|
|
|
Part = weakSym.ValuePart.ToString();
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-10-15 23:37:14 +00:00
|
|
|
|
public class SerMultiMask {
|
|
|
|
|
public int CompareMask;
|
|
|
|
|
public int CompareValue;
|
|
|
|
|
public int AddressMask;
|
|
|
|
|
|
|
|
|
|
public SerMultiMask() { }
|
|
|
|
|
public SerMultiMask(DefSymbol.MultiAddressMask multiMask) {
|
|
|
|
|
CompareMask = multiMask.CompareMask;
|
|
|
|
|
CompareValue = multiMask.CompareValue;
|
|
|
|
|
AddressMask = multiMask.AddressMask;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-02 22:45:40 +00:00
|
|
|
|
public class SerDefSymbol : SerSymbol {
|
|
|
|
|
public SerFormatDescriptor DataDescriptor { get; set; }
|
|
|
|
|
public string Comment { get; set; }
|
Allow explicit widths in project/platform symbols, part 1
The ability to give explicit widths to local variables worked out
pretty well, so we're going to try adding the same thing to project
and platform symbols.
The first step is to allow widths to be specified in platform files,
and set with the project symbol editor. The DefSymbol editor is
also used for local variables, so a bit of dancing is required.
For platform/project symbols the width is optional, and is totally
ignored for constants. (For variables, constants are used for the
StackRel args, so the width is meaningful and required.)
We also now show the symbol's type (address or constant) and width
in the listing. This gets really distracting when overused, so we
only show it when the width is explicitly set. The default width
is 1, which most things will be, so users can make an aesthetic
choice there. (The place where widths make very little sense is when
the symbol represents a code entry point, rather than a data item.)
The maximum width of a local variable is now 256, but it's not
allowed to overlap with other variables or run of the end of the
direct page. The maximum width of a platform/project symbol is
65536, with bank-wrap behavior TBD.
The local variable table editor now refers to stack-relative
constants as such, rather than simply "constant", to make it clear
that it's not just defining an 8-bit constant.
Widths have been added to a handful of Apple II platform defs.
2019-10-01 21:58:24 +00:00
|
|
|
|
public bool HasWidth { get; set; }
|
2019-10-15 23:37:14 +00:00
|
|
|
|
public string Direction { get; set; }
|
|
|
|
|
public SerMultiMask MultiMask { get; set; }
|
Allow explicit widths in project/platform symbols, part 1
The ability to give explicit widths to local variables worked out
pretty well, so we're going to try adding the same thing to project
and platform symbols.
The first step is to allow widths to be specified in platform files,
and set with the project symbol editor. The DefSymbol editor is
also used for local variables, so a bit of dancing is required.
For platform/project symbols the width is optional, and is totally
ignored for constants. (For variables, constants are used for the
StackRel args, so the width is meaningful and required.)
We also now show the symbol's type (address or constant) and width
in the listing. This gets really distracting when overused, so we
only show it when the width is explicitly set. The default width
is 1, which most things will be, so users can make an aesthetic
choice there. (The place where widths make very little sense is when
the symbol represents a code entry point, rather than a data item.)
The maximum width of a local variable is now 256, but it's not
allowed to overlap with other variables or run of the end of the
direct page. The maximum width of a platform/project symbol is
65536, with bank-wrap behavior TBD.
The local variable table editor now refers to stack-relative
constants as such, rather than simply "constant", to make it clear
that it's not just defining an 8-bit constant.
Widths have been added to a handful of Apple II platform defs.
2019-10-01 21:58:24 +00:00
|
|
|
|
// Tag not relevant, Xrefs not recorded
|
2019-10-16 21:55:10 +00:00
|
|
|
|
// MultiMask currently not set for project symbols, but we support it anyway.
|
2019-05-02 22:45:40 +00:00
|
|
|
|
|
|
|
|
|
public SerDefSymbol() { }
|
|
|
|
|
public SerDefSymbol(DefSymbol defSym) : base(defSym) {
|
|
|
|
|
DataDescriptor = new SerFormatDescriptor(defSym.DataDescriptor);
|
|
|
|
|
Comment = defSym.Comment;
|
2019-10-15 23:37:14 +00:00
|
|
|
|
HasWidth = defSym.HasWidth;
|
|
|
|
|
Direction = defSym.Direction.ToString();
|
|
|
|
|
if (defSym.MultiMask != null) {
|
|
|
|
|
MultiMask = new SerMultiMask(defSym.MultiMask);
|
|
|
|
|
}
|
2019-05-02 22:45:40 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-08-26 23:58:53 +00:00
|
|
|
|
public class SerLocalVariableTable {
|
|
|
|
|
public List<SerDefSymbol> Variables { get; set; }
|
|
|
|
|
public bool ClearPrevious { get; set; }
|
|
|
|
|
|
|
|
|
|
public SerLocalVariableTable() { }
|
|
|
|
|
public SerLocalVariableTable(LocalVariableTable varTab) {
|
2019-08-29 00:34:29 +00:00
|
|
|
|
Variables = new List<SerDefSymbol>(varTab.Count);
|
|
|
|
|
for (int i = 0; i < varTab.Count; i++) {
|
|
|
|
|
DefSymbol defSym = varTab[i];
|
|
|
|
|
Variables.Add(new SerDefSymbol(defSym));
|
2019-08-26 23:58:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ClearPrevious = varTab.ClearPrevious;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-12-03 00:38:32 +00:00
|
|
|
|
public class SerVisualization {
|
|
|
|
|
public string Tag { get; set; }
|
|
|
|
|
public string VisGenIdent { get; set; }
|
|
|
|
|
public Dictionary<string, object> VisGenParams { get; set; }
|
|
|
|
|
|
|
|
|
|
public SerVisualization() { }
|
|
|
|
|
public SerVisualization(Visualization vis) {
|
|
|
|
|
Tag = vis.Tag;
|
|
|
|
|
VisGenIdent = vis.VisGenIdent;
|
2019-12-04 23:50:19 +00:00
|
|
|
|
VisGenParams = new Dictionary<string, object>(vis.VisGenParams);
|
2019-12-03 00:38:32 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-12-23 00:56:57 +00:00
|
|
|
|
public class SerVisualizationAnimation : SerVisualization {
|
|
|
|
|
public List<string> Tags { get; set; }
|
|
|
|
|
|
|
|
|
|
public SerVisualizationAnimation() { }
|
|
|
|
|
public SerVisualizationAnimation(VisualizationAnimation visAnim,
|
|
|
|
|
SortedList<int, VisualizationSet> visSets)
|
|
|
|
|
: base(visAnim) {
|
|
|
|
|
Tags = new List<string>(visAnim.Count);
|
|
|
|
|
for (int i = 0; i < visAnim.Count; i++) {
|
|
|
|
|
Visualization vis =
|
|
|
|
|
VisualizationSet.FindVisualizationBySerial(visSets, visAnim[i]);
|
|
|
|
|
Tags.Add(vis.Tag);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-12-03 00:38:32 +00:00
|
|
|
|
public class SerVisualizationSet {
|
2019-12-23 00:56:57 +00:00
|
|
|
|
public List<string> Tags { get; set; }
|
2019-12-03 00:38:32 +00:00
|
|
|
|
|
|
|
|
|
public SerVisualizationSet() { }
|
|
|
|
|
public SerVisualizationSet(VisualizationSet visSet) {
|
2019-12-23 00:56:57 +00:00
|
|
|
|
Tags = new List<string>(visSet.Count);
|
2019-12-03 00:38:32 +00:00
|
|
|
|
foreach (Visualization vis in visSet) {
|
2019-12-23 00:56:57 +00:00
|
|
|
|
Tags.Add(vis.Tag);
|
2019-12-03 00:38:32 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-02 22:45:40 +00:00
|
|
|
|
|
|
|
|
|
// Fields are serialized to/from JSON. Do not change the field names.
|
|
|
|
|
public int _ContentVersion { get; set; }
|
|
|
|
|
public int FileDataLength { get; set; }
|
|
|
|
|
public int FileDataCrc32 { get; set; }
|
|
|
|
|
public SerProjectProperties ProjectProps { get; set; }
|
|
|
|
|
public List<SerAddressMap> AddressMap { get; set; }
|
|
|
|
|
public List<SerTypeHintRange> TypeHints { get; set; }
|
|
|
|
|
public Dictionary<string, int> StatusFlagOverrides { get; set; }
|
|
|
|
|
public Dictionary<string, string> Comments { get; set; }
|
|
|
|
|
public Dictionary<string, SerMultiLineComment> LongComments { get; set; }
|
|
|
|
|
public Dictionary<string, SerMultiLineComment> Notes { get; set; }
|
|
|
|
|
public Dictionary<string, SerSymbol> UserLabels { get; set; }
|
|
|
|
|
public Dictionary<string, SerFormatDescriptor> OperandFormats { get; set; }
|
2019-08-26 23:58:53 +00:00
|
|
|
|
public Dictionary<string, SerLocalVariableTable> LvTables { get; set; }
|
2019-12-23 00:56:57 +00:00
|
|
|
|
public List<SerVisualization> Visualizations { get; set; }
|
|
|
|
|
public List<SerVisualizationAnimation> VisualizationAnimations { get; set; }
|
2019-12-03 00:38:32 +00:00
|
|
|
|
public Dictionary<string, SerVisualizationSet> VisualizationSets { get; set; }
|
2019-05-02 22:45:40 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Serializes a DisasmProject into an augmented JSON string.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="proj">Project to serialize.</param>
|
|
|
|
|
/// <returns>Augmented JSON string.</returns>
|
|
|
|
|
public static string SerializeProject(DisasmProject proj) {
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
sb.Append(MAGIC); // augment with version string, which will be stripped
|
|
|
|
|
sb.Append("\r\n"); // will be ignored by deserializer; might get converted to \n
|
|
|
|
|
|
|
|
|
|
SerializableProjectFile1 spf = new SerializableProjectFile1();
|
|
|
|
|
spf._ContentVersion = ProjectFile.CONTENT_VERSION;
|
|
|
|
|
|
|
|
|
|
Debug.Assert(proj.FileDataLength == proj.FileData.Length);
|
|
|
|
|
spf.FileDataLength = proj.FileDataLength;
|
|
|
|
|
spf.FileDataCrc32 = (int)proj.FileDataCrc32;
|
|
|
|
|
|
|
|
|
|
// Convert AddressMap to serializable form.
|
|
|
|
|
spf.AddressMap = new List<SerAddressMap>();
|
|
|
|
|
foreach (AddressMap.AddressMapEntry ent in proj.AddrMap) {
|
|
|
|
|
spf.AddressMap.Add(new SerAddressMap(ent));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reduce TypeHints to a collection of ranges. Output the type enum as a string
|
|
|
|
|
// so we're not tied to a specific value.
|
|
|
|
|
spf.TypeHints = new List<SerTypeHintRange>();
|
|
|
|
|
TypedRangeSet trs = new TypedRangeSet();
|
|
|
|
|
for (int i = 0; i < proj.TypeHints.Length; i++) {
|
|
|
|
|
trs.Add(i, (int)proj.TypeHints[i]);
|
|
|
|
|
}
|
|
|
|
|
IEnumerator<TypedRangeSet.TypedRange> iter = trs.RangeListIterator;
|
|
|
|
|
while (iter.MoveNext()) {
|
|
|
|
|
if (iter.Current.Type == (int)CodeAnalysis.TypeHint.NoHint) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
spf.TypeHints.Add(new SerTypeHintRange(iter.Current.Low, iter.Current.High,
|
|
|
|
|
((CodeAnalysis.TypeHint)iter.Current.Type).ToString()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert StatusFlagOverrides to serializable form. Just write the state out
|
|
|
|
|
// as an integer... not expecting it to change. If it does, we can convert.
|
|
|
|
|
spf.StatusFlagOverrides = new Dictionary<string, int>();
|
|
|
|
|
for (int i = 0; i < proj.StatusFlagOverrides.Length; i++) {
|
|
|
|
|
if (proj.StatusFlagOverrides[i] == Asm65.StatusFlags.DefaultValue) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
spf.StatusFlagOverrides.Add(i.ToString(), proj.StatusFlagOverrides[i].AsInt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert Comments to serializable form.
|
|
|
|
|
spf.Comments = new Dictionary<string, string>();
|
|
|
|
|
for (int i = 0; i < proj.Comments.Length; i++) {
|
|
|
|
|
if (string.IsNullOrEmpty(proj.Comments[i])) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
spf.Comments.Add(i.ToString(), proj.Comments[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert multi-line comments to serializable form.
|
|
|
|
|
spf.LongComments = new Dictionary<string, SerMultiLineComment>();
|
|
|
|
|
foreach (KeyValuePair<int, MultiLineComment> kvp in proj.LongComments) {
|
|
|
|
|
spf.LongComments.Add(kvp.Key.ToString(), new SerMultiLineComment(kvp.Value));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert multi-line notes to serializable form.
|
|
|
|
|
spf.Notes = new Dictionary<string, SerMultiLineComment>();
|
|
|
|
|
foreach (KeyValuePair<int, MultiLineComment> kvp in proj.Notes) {
|
|
|
|
|
spf.Notes.Add(kvp.Key.ToString(), new SerMultiLineComment(kvp.Value));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert user-defined labels to serializable form.
|
|
|
|
|
spf.UserLabels = new Dictionary<string, SerSymbol>();
|
|
|
|
|
foreach (KeyValuePair<int,Symbol> kvp in proj.UserLabels) {
|
|
|
|
|
spf.UserLabels.Add(kvp.Key.ToString(), new SerSymbol(kvp.Value));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert operand and data item format descriptors to serializable form.
|
|
|
|
|
spf.OperandFormats = new Dictionary<string, SerFormatDescriptor>();
|
|
|
|
|
foreach (KeyValuePair<int,FormatDescriptor> kvp in proj.OperandFormats) {
|
|
|
|
|
spf.OperandFormats.Add(kvp.Key.ToString(), new SerFormatDescriptor(kvp.Value));
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-26 23:58:53 +00:00
|
|
|
|
// Convert local variable tables to serializable form.
|
|
|
|
|
spf.LvTables = new Dictionary<string, SerLocalVariableTable>();
|
|
|
|
|
foreach (KeyValuePair<int, LocalVariableTable> kvp in proj.LvTables) {
|
|
|
|
|
spf.LvTables.Add(kvp.Key.ToString(), new SerLocalVariableTable(kvp.Value));
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-23 00:56:57 +00:00
|
|
|
|
// Output Visualizations, VisualizationAnimations, and VisualizationSets
|
|
|
|
|
spf.Visualizations = new List<SerVisualization>();
|
|
|
|
|
spf.VisualizationAnimations = new List<SerVisualizationAnimation>();
|
2019-12-03 00:38:32 +00:00
|
|
|
|
spf.VisualizationSets = new Dictionary<string, SerVisualizationSet>();
|
|
|
|
|
foreach (KeyValuePair<int, VisualizationSet> kvp in proj.VisualizationSets) {
|
2019-12-23 00:56:57 +00:00
|
|
|
|
foreach (Visualization vis in kvp.Value) {
|
|
|
|
|
if (vis is VisualizationAnimation) {
|
|
|
|
|
VisualizationAnimation visAnim = (VisualizationAnimation)vis;
|
|
|
|
|
spf.VisualizationAnimations.Add(new SerVisualizationAnimation(visAnim,
|
|
|
|
|
proj.VisualizationSets));
|
|
|
|
|
} else {
|
|
|
|
|
spf.Visualizations.Add(new SerVisualization(vis));
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-12-03 00:38:32 +00:00
|
|
|
|
spf.VisualizationSets.Add(kvp.Key.ToString(), new SerVisualizationSet(kvp.Value));
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-02 22:45:40 +00:00
|
|
|
|
spf.ProjectProps = new SerProjectProperties(proj.ProjectProps);
|
|
|
|
|
|
|
|
|
|
JavaScriptSerializer ser = new JavaScriptSerializer();
|
|
|
|
|
string cereal = ser.Serialize(spf);
|
|
|
|
|
sb.Append(cereal);
|
|
|
|
|
|
|
|
|
|
// Stick a linefeed at the end. Makes git happier.
|
|
|
|
|
sb.Append("\r\n");
|
|
|
|
|
|
|
|
|
|
return sb.ToString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Deserializes an augmented JSON string into a DisasmProject.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="cereal">Serialized data.</param>
|
|
|
|
|
/// <param name="proj">Project to populate.</param>
|
|
|
|
|
/// <param name="report">Error report object.</param>
|
|
|
|
|
/// <returns>True on success, false on fatal error.</returns>
|
|
|
|
|
public static bool DeserializeProject(string cereal, DisasmProject proj,
|
|
|
|
|
FileLoadReport report) {
|
|
|
|
|
JavaScriptSerializer ser = new JavaScriptSerializer();
|
|
|
|
|
SerializableProjectFile1 spf;
|
|
|
|
|
try {
|
|
|
|
|
spf = ser.Deserialize<SerializableProjectFile1>(cereal);
|
|
|
|
|
} catch (Exception ex) {
|
|
|
|
|
// The deserializer seems to be stuffing the entire data stream into the
|
|
|
|
|
// exception, which we don't really want, so cap it at 256 chars.
|
|
|
|
|
string msg = ex.Message;
|
|
|
|
|
if (msg.Length > 256) {
|
|
|
|
|
msg = msg.Substring(0, 256) + " [...]";
|
|
|
|
|
}
|
|
|
|
|
report.Add(FileLoadItem.Type.Error, Res.Strings.ERR_PROJECT_FILE_CORRUPT +
|
|
|
|
|
": " + msg);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (spf._ContentVersion > ProjectFile.CONTENT_VERSION) {
|
|
|
|
|
// Post a warning.
|
|
|
|
|
report.Add(FileLoadItem.Type.Notice, Res.Strings.PROJECT_FROM_NEWER_APP);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (spf.FileDataLength <= 0) {
|
|
|
|
|
report.Add(FileLoadItem.Type.Error, Res.Strings.ERR_BAD_FILE_LENGTH +
|
|
|
|
|
": " + spf.FileDataLength);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialize the object and set a few simple items.
|
|
|
|
|
proj.Initialize(spf.FileDataLength);
|
|
|
|
|
proj.SetFileCrc((uint)spf.FileDataCrc32);
|
|
|
|
|
|
|
|
|
|
// Deserialize ProjectProperties: misc items.
|
|
|
|
|
proj.ProjectProps.CpuType = Asm65.CpuDef.GetCpuTypeFromName(spf.ProjectProps.CpuName);
|
|
|
|
|
proj.ProjectProps.IncludeUndocumentedInstr = spf.ProjectProps.IncludeUndocumentedInstr;
|
Optionally treat BRKs as two-byte instructions
Early data sheets listed BRK as one byte, but RTI after a BRK skips
the following byte, effectively making BRK a 2-byte instruction.
Sometimes, such as when diassembling Apple /// SOS code, it's handy
to treat it that way explicitly.
This change makes two-byte BRKs optional, controlled by a checkbox
in the project settings. In the system definitions it defaults to
true for Apple ///, false for all others.
ACME doesn't allow BRK to have an arg, and cc65 only allows it for
65816 code (?), so it's emitted as a hex blob for those assemblers.
Anyone wishing to target those assemblers should stick to 1-byte mode.
Extension scripts have to switch between formatting one byte of
inline data and formatting an instruction with a one-byte operand.
A helper function has been added to the plugin Util class.
To get some regression test coverage, 2022-extension-scripts has
been configured to use two-byte BRK.
Also, added/corrected some SOS constants.
See also issue #44.
2019-10-09 21:55:56 +00:00
|
|
|
|
proj.ProjectProps.TwoByteBrk = spf.ProjectProps.TwoByteBrk;
|
2019-05-02 22:45:40 +00:00
|
|
|
|
proj.ProjectProps.EntryFlags = Asm65.StatusFlags.FromInt(spf.ProjectProps.EntryFlags);
|
|
|
|
|
if (Enum.TryParse<AutoLabel.Style>(spf.ProjectProps.AutoLabelStyle,
|
|
|
|
|
out AutoLabel.Style als)) {
|
|
|
|
|
proj.ProjectProps.AutoLabelStyle = als;
|
|
|
|
|
} else {
|
|
|
|
|
// unknown value, leave as default
|
|
|
|
|
}
|
|
|
|
|
proj.ProjectProps.AnalysisParams = new ProjectProperties.AnalysisParameters();
|
|
|
|
|
proj.ProjectProps.AnalysisParams.AnalyzeUncategorizedData =
|
|
|
|
|
spf.ProjectProps.AnalysisParams.AnalyzeUncategorizedData;
|
2019-08-13 00:01:50 +00:00
|
|
|
|
if (Enum.TryParse<ProjectProperties.AnalysisParameters.TextScanMode>(
|
|
|
|
|
spf.ProjectProps.AnalysisParams.DefaultTextScanMode,
|
|
|
|
|
out ProjectProperties.AnalysisParameters.TextScanMode mode)) {
|
|
|
|
|
proj.ProjectProps.AnalysisParams.DefaultTextScanMode = mode;
|
|
|
|
|
} else {
|
|
|
|
|
// unknown value, leave as default
|
|
|
|
|
}
|
2019-05-02 22:45:40 +00:00
|
|
|
|
proj.ProjectProps.AnalysisParams.MinCharsForString =
|
|
|
|
|
spf.ProjectProps.AnalysisParams.MinCharsForString;
|
|
|
|
|
proj.ProjectProps.AnalysisParams.SeekNearbyTargets =
|
|
|
|
|
spf.ProjectProps.AnalysisParams.SeekNearbyTargets;
|
2019-09-02 22:57:59 +00:00
|
|
|
|
if (spf._ContentVersion < 2) {
|
|
|
|
|
// This was made optional in v1.3. Default it to true for older projects.
|
|
|
|
|
proj.ProjectProps.AnalysisParams.SmartPlpHandling = true;
|
|
|
|
|
} else {
|
|
|
|
|
proj.ProjectProps.AnalysisParams.SmartPlpHandling =
|
|
|
|
|
spf.ProjectProps.AnalysisParams.SmartPlpHandling;
|
|
|
|
|
}
|
2019-05-02 22:45:40 +00:00
|
|
|
|
|
|
|
|
|
// Deserialize ProjectProperties: external file identifiers.
|
|
|
|
|
Debug.Assert(proj.ProjectProps.PlatformSymbolFileIdentifiers.Count == 0);
|
|
|
|
|
foreach (string str in spf.ProjectProps.PlatformSymbolFileIdentifiers) {
|
|
|
|
|
proj.ProjectProps.PlatformSymbolFileIdentifiers.Add(str);
|
|
|
|
|
}
|
|
|
|
|
Debug.Assert(proj.ProjectProps.ExtensionScriptFileIdentifiers.Count == 0);
|
|
|
|
|
foreach (string str in spf.ProjectProps.ExtensionScriptFileIdentifiers) {
|
|
|
|
|
proj.ProjectProps.ExtensionScriptFileIdentifiers.Add(str);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Deserialize ProjectProperties: project symbols.
|
|
|
|
|
foreach (KeyValuePair<string, SerDefSymbol> kvp in spf.ProjectProps.ProjectSyms) {
|
2019-08-26 23:58:53 +00:00
|
|
|
|
if (!CreateDefSymbol(kvp.Value, spf._ContentVersion, report,
|
|
|
|
|
out DefSymbol defSym)) {
|
2019-05-02 22:45:40 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
2019-08-26 23:58:53 +00:00
|
|
|
|
proj.ProjectProps.ProjectSyms[defSym.Label] = defSym;
|
2019-05-02 22:45:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Deserialize address map.
|
|
|
|
|
foreach (SerAddressMap addr in spf.AddressMap) {
|
|
|
|
|
proj.AddrMap.Set(addr.Offset, addr.Addr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Deserialize type hints. Default value in new array as NoHint, so we don't
|
|
|
|
|
// need to write those. They should not have been recorded in the file.
|
|
|
|
|
foreach (SerTypeHintRange range in spf.TypeHints) {
|
|
|
|
|
if (range.Low < 0 || range.High >= spf.FileDataLength || range.Low > range.High) {
|
|
|
|
|
report.Add(FileLoadItem.Type.Warning, Res.Strings.ERR_BAD_RANGE +
|
|
|
|
|
": " + Res.Strings.PROJECT_FIELD_TYPE_HINT +
|
|
|
|
|
" +" + range.Low.ToString("x6") + " - +" + range.High.ToString("x6"));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
CodeAnalysis.TypeHint hint;
|
|
|
|
|
try {
|
|
|
|
|
hint = (CodeAnalysis.TypeHint) Enum.Parse(
|
|
|
|
|
typeof(CodeAnalysis.TypeHint), range.Hint);
|
|
|
|
|
} catch (ArgumentException) {
|
|
|
|
|
report.Add(FileLoadItem.Type.Warning, Res.Strings.ERR_BAD_TYPE_HINT +
|
|
|
|
|
": " + range.Hint);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
for (int i = range.Low; i <= range.High; i++) {
|
|
|
|
|
proj.TypeHints[i] = hint;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Deserialize status flag overrides.
|
|
|
|
|
foreach (KeyValuePair<string,int> kvp in spf.StatusFlagOverrides) {
|
|
|
|
|
if (!ParseValidateKey(kvp.Key, spf.FileDataLength,
|
|
|
|
|
Res.Strings.PROJECT_FIELD_STATUS_FLAGS, report, out int intKey)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
proj.StatusFlagOverrides[intKey] = Asm65.StatusFlags.FromInt(kvp.Value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Deserialize comments.
|
|
|
|
|
foreach (KeyValuePair<string,string> kvp in spf.Comments) {
|
|
|
|
|
if (!ParseValidateKey(kvp.Key, spf.FileDataLength,
|
|
|
|
|
Res.Strings.PROJECT_FIELD_COMMENT, report, out int intKey)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
proj.Comments[intKey] = kvp.Value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Deserialize long comments.
|
|
|
|
|
foreach (KeyValuePair<string, SerMultiLineComment> kvp in spf.LongComments) {
|
|
|
|
|
if (!ParseValidateKey(kvp.Key, spf.FileDataLength,
|
|
|
|
|
Res.Strings.PROJECT_FIELD_LONG_COMMENT, report, out int intKey)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
proj.LongComments[intKey] = new MultiLineComment(kvp.Value.Text,
|
|
|
|
|
kvp.Value.BoxMode, kvp.Value.MaxWidth);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Deserialize notes.
|
|
|
|
|
foreach (KeyValuePair<string, SerMultiLineComment> kvp in spf.Notes) {
|
|
|
|
|
if (!ParseValidateKey(kvp.Key, spf.FileDataLength,
|
|
|
|
|
Res.Strings.PROJECT_FIELD_NOTE, report, out int intKey)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
proj.Notes[intKey] = new MultiLineComment(kvp.Value.Text,
|
|
|
|
|
ColorFromInt(kvp.Value.BackgroundColor));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Deserialize user-defined labels.
|
|
|
|
|
SortedList<string, string> labelDupCheck =
|
|
|
|
|
new SortedList<string, string>(spf.UserLabels.Count);
|
|
|
|
|
foreach (KeyValuePair<string, SerSymbol> kvp in spf.UserLabels) {
|
|
|
|
|
if (!ParseValidateKey(kvp.Key, spf.FileDataLength,
|
|
|
|
|
Res.Strings.PROJECT_FIELD_USER_LABEL, report, out int intKey)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-16 04:40:14 +00:00
|
|
|
|
if (!CreateSymbol(kvp.Value, intKey, report, out Symbol newSym)) {
|
2019-11-09 04:44:45 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (newSym.SymbolSource != Symbol.Source.User) {
|
|
|
|
|
// User labels are always source=user. I don't think it really matters,
|
|
|
|
|
// but best to keep junk out.
|
2019-05-02 22:45:40 +00:00
|
|
|
|
report.Add(FileLoadItem.Type.Warning, Res.Strings.ERR_BAD_SYMBOL_ST +
|
|
|
|
|
": " + kvp.Value.Source + "/" + kvp.Value.Type);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for duplicate labels. We only want to compare label strings, so we
|
|
|
|
|
// can't test UserLabels.ContainsValue (which might be a linear search anyway).
|
|
|
|
|
// Dump the labels into a sorted list.
|
2019-11-16 04:40:14 +00:00
|
|
|
|
//
|
|
|
|
|
// We want to use newSym.Label rather than kvp.Value.Label, because the latter
|
|
|
|
|
// won't have the non-unique local tag.
|
|
|
|
|
if (labelDupCheck.ContainsKey(newSym.Label)) {
|
2019-05-02 22:45:40 +00:00
|
|
|
|
report.Add(FileLoadItem.Type.Warning,
|
2019-11-16 04:40:14 +00:00
|
|
|
|
string.Format(Res.Strings.ERR_DUPLICATE_LABEL_FMT, newSym.Label, intKey));
|
2019-05-02 22:45:40 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
2019-11-16 04:40:14 +00:00
|
|
|
|
labelDupCheck.Add(newSym.Label, string.Empty);
|
2019-05-02 22:45:40 +00:00
|
|
|
|
|
2019-11-09 04:44:45 +00:00
|
|
|
|
proj.UserLabels[intKey] = newSym;
|
2019-05-02 22:45:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Deserialize operand format descriptors.
|
2019-08-26 23:58:53 +00:00
|
|
|
|
foreach (KeyValuePair<string, SerFormatDescriptor> kvp in spf.OperandFormats) {
|
2019-05-02 22:45:40 +00:00
|
|
|
|
if (!ParseValidateKey(kvp.Key, spf.FileDataLength,
|
2019-08-26 23:58:53 +00:00
|
|
|
|
Res.Strings.PROJECT_FIELD_OPERAND_FORMAT, report, out int intKey)) {
|
2019-05-02 22:45:40 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-12 00:59:20 +00:00
|
|
|
|
if (!CreateFormatDescriptor(kvp.Value, spf._ContentVersion, report,
|
|
|
|
|
out FormatDescriptor dfd)) {
|
2019-10-19 03:28:02 +00:00
|
|
|
|
report.Add(FileLoadItem.Type.Warning,
|
|
|
|
|
string.Format(Res.Strings.ERR_BAD_FD_FMT, intKey));
|
2019-05-02 22:45:40 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
2019-08-26 23:58:53 +00:00
|
|
|
|
// Extra validation: make sure dfd doesn't run off end.
|
2019-05-02 22:45:40 +00:00
|
|
|
|
if (intKey < 0 || intKey + dfd.Length > spf.FileDataLength) {
|
|
|
|
|
report.Add(FileLoadItem.Type.Warning,
|
2019-07-13 18:29:05 +00:00
|
|
|
|
string.Format(Res.Strings.ERR_BAD_FD_FMT, intKey));
|
2019-05-02 22:45:40 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO(maybe): check to see if the descriptor straddles an address change.
|
|
|
|
|
// Not fatal but it'll make things look weird.
|
|
|
|
|
|
|
|
|
|
proj.OperandFormats[intKey] = dfd;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-26 23:58:53 +00:00
|
|
|
|
// Deserialize local variable tables. These were added in v1.3.
|
|
|
|
|
if (spf.LvTables != null) {
|
|
|
|
|
foreach (KeyValuePair<string, SerLocalVariableTable> kvp in spf.LvTables) {
|
|
|
|
|
if (!ParseValidateKey(kvp.Key, spf.FileDataLength,
|
|
|
|
|
Res.Strings.PROJECT_FIELD_LV_TABLE, report, out int intKey)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!CreateLocalVariableTable(kvp.Value, spf._ContentVersion, report,
|
|
|
|
|
out LocalVariableTable lvt)) {
|
|
|
|
|
report.Add(FileLoadItem.Type.Warning,
|
|
|
|
|
string.Format(Res.Strings.ERR_BAD_LV_TABLE_FMT, intKey));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
proj.LvTables[intKey] = lvt;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-03 00:38:32 +00:00
|
|
|
|
// Deserialize visualization sets. These were added in v1.5.
|
2019-12-23 00:56:57 +00:00
|
|
|
|
if (spf.VisualizationSets != null && spf.Visualizations != null) {
|
|
|
|
|
Dictionary<string, Visualization> visDict =
|
|
|
|
|
new Dictionary<string, Visualization>(spf.Visualizations.Count);
|
|
|
|
|
|
|
|
|
|
// Extract the Visualizations.
|
|
|
|
|
foreach (SerVisualization serVis in spf.Visualizations) {
|
|
|
|
|
if (CreateVisualization(serVis, report, out Visualization vis)) {
|
|
|
|
|
try {
|
|
|
|
|
visDict.Add(vis.Tag, vis);
|
|
|
|
|
} catch (ArgumentException) {
|
|
|
|
|
string str = string.Format(Res.Strings.ERR_BAD_VISUALIZATION_FMT,
|
|
|
|
|
"duplicate tag " + vis.Tag);
|
|
|
|
|
report.Add(FileLoadItem.Type.Warning, str);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Extract the VisualizationAnimations, which link to Visualizations by tag.
|
|
|
|
|
foreach (SerVisualizationAnimation serVisAnim in spf.VisualizationAnimations) {
|
|
|
|
|
if (CreateVisualizationAnimation(serVisAnim, visDict, report,
|
|
|
|
|
out VisualizationAnimation visAnim)) {
|
|
|
|
|
try {
|
|
|
|
|
visDict.Add(visAnim.Tag, visAnim);
|
|
|
|
|
} catch (ArgumentException) {
|
|
|
|
|
string str = string.Format(Res.Strings.ERR_BAD_VISUALIZATION_FMT,
|
|
|
|
|
"duplicate tag " + visAnim.Tag);
|
|
|
|
|
report.Add(FileLoadItem.Type.Warning, str);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Extract the VisualizationSets, which link to Visualizations of all types by tag.
|
2019-12-03 00:38:32 +00:00
|
|
|
|
foreach (KeyValuePair<string, SerVisualizationSet> kvp in spf.VisualizationSets) {
|
|
|
|
|
if (!ParseValidateKey(kvp.Key, spf.FileDataLength,
|
|
|
|
|
Res.Strings.PROJECT_FIELD_LV_TABLE, report, out int intKey)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-23 00:56:57 +00:00
|
|
|
|
if (!CreateVisualizationSet(kvp.Value, visDict, report,
|
|
|
|
|
out VisualizationSet visSet)) {
|
2019-12-03 00:38:32 +00:00
|
|
|
|
report.Add(FileLoadItem.Type.Warning,
|
|
|
|
|
string.Format(Res.Strings.ERR_BAD_VISUALIZATION_SET_FMT, intKey));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
proj.VisualizationSets[intKey] = visSet;
|
2019-12-23 00:56:57 +00:00
|
|
|
|
}
|
2019-12-03 00:38:32 +00:00
|
|
|
|
|
2019-12-23 00:56:57 +00:00
|
|
|
|
if (visDict.Count != 0) {
|
|
|
|
|
// We remove visualizations as we add them to sets, so this indicates a
|
|
|
|
|
// problem.
|
|
|
|
|
Debug.WriteLine("WARNING: visDict still has " + visDict.Count + " entries");
|
2019-12-03 00:38:32 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-02 22:45:40 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2019-11-09 04:44:45 +00:00
|
|
|
|
/// Creates a Symbol from a SerSymbol. If it fails to parse correctly, an entry
|
|
|
|
|
/// is generated in the FileLoadReport.
|
2019-05-02 22:45:40 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="ssym">Deserialized data.</param>
|
2019-11-16 04:40:14 +00:00
|
|
|
|
/// <param name="userLabelOffset">If the symbol is a user label, this is the file offset.
|
|
|
|
|
/// If not, pass -1. Used for non-unique locals.</param>
|
2019-05-02 22:45:40 +00:00
|
|
|
|
/// <param name="report">Error report object.</param>
|
2019-08-26 23:58:53 +00:00
|
|
|
|
/// <param name="outSym">Created symbol.</param>
|
2019-05-02 22:45:40 +00:00
|
|
|
|
/// <returns>True on success.</returns>
|
2019-11-16 04:40:14 +00:00
|
|
|
|
private static bool CreateSymbol(SerSymbol ssym, int userLabelOffset,
|
|
|
|
|
FileLoadReport report, out Symbol outSym) {
|
2019-05-02 22:45:40 +00:00
|
|
|
|
outSym = null;
|
|
|
|
|
Symbol.Source source;
|
|
|
|
|
Symbol.Type type;
|
2019-11-09 04:44:45 +00:00
|
|
|
|
Symbol.LabelAnnotation labelAnno = Symbol.LabelAnnotation.None;
|
2019-05-02 22:45:40 +00:00
|
|
|
|
try {
|
|
|
|
|
source = (Symbol.Source)Enum.Parse(typeof(Symbol.Source), ssym.Source);
|
|
|
|
|
type = (Symbol.Type)Enum.Parse(typeof(Symbol.Type), ssym.Type);
|
2019-11-09 04:44:45 +00:00
|
|
|
|
if (!string.IsNullOrEmpty(ssym.LabelAnno)) {
|
|
|
|
|
labelAnno = (Symbol.LabelAnnotation)Enum.Parse(
|
|
|
|
|
typeof(Symbol.LabelAnnotation), ssym.LabelAnno);
|
|
|
|
|
}
|
2019-11-16 04:40:14 +00:00
|
|
|
|
if (type == Symbol.Type.NonUniqueLocalAddr && source != Symbol.Source.User) {
|
|
|
|
|
throw new ArgumentException("unexpected source for non-unique local");
|
|
|
|
|
}
|
2019-05-02 22:45:40 +00:00
|
|
|
|
} catch (ArgumentException) {
|
|
|
|
|
report.Add(FileLoadItem.Type.Warning, Res.Strings.ERR_BAD_SYMBOL_ST +
|
|
|
|
|
": " + ssym.Source + "/" + ssym.Type);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-11-16 04:40:14 +00:00
|
|
|
|
|
|
|
|
|
if (!Asm65.Label.ValidateLabel(ssym.Label)) {
|
|
|
|
|
report.Add(FileLoadItem.Type.Warning, Res.Strings.ERR_BAD_SYMBOL_LABEL +
|
|
|
|
|
": " + ssym.Label);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (type == Symbol.Type.NonUniqueLocalAddr) {
|
|
|
|
|
outSym = new Symbol(ssym.Label, ssym.Value, labelAnno, userLabelOffset);
|
|
|
|
|
} else {
|
|
|
|
|
outSym = new Symbol(ssym.Label, ssym.Value, source, type, labelAnno);
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-02 22:45:40 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-26 23:58:53 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Creates a DefSymbol from a SerDefSymbol.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="serDefSym">Deserialized data.</param>
|
|
|
|
|
/// <param name="contentVersion">Serialization version.</param>
|
|
|
|
|
/// <param name="report">Error report object.</param>
|
|
|
|
|
/// <param name="outDefSym">Created symbol.</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
private static bool CreateDefSymbol(SerDefSymbol serDefSym, int contentVersion,
|
|
|
|
|
FileLoadReport report, out DefSymbol outDefSym) {
|
|
|
|
|
outDefSym = null;
|
|
|
|
|
|
2019-11-16 04:40:14 +00:00
|
|
|
|
if (!CreateSymbol(serDefSym, -1, report, out Symbol sym)) {
|
2019-08-26 23:58:53 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (!CreateFormatDescriptor(serDefSym.DataDescriptor, contentVersion, report,
|
|
|
|
|
out FormatDescriptor dfd)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-10-15 23:37:14 +00:00
|
|
|
|
DefSymbol.DirectionFlags direction;
|
|
|
|
|
if (string.IsNullOrEmpty(serDefSym.Direction)) {
|
|
|
|
|
direction = DefSymbol.DirectionFlags.ReadWrite;
|
|
|
|
|
} else try {
|
|
|
|
|
direction = (DefSymbol.DirectionFlags)
|
|
|
|
|
Enum.Parse(typeof(DefSymbol.DirectionFlags), serDefSym.Direction);
|
|
|
|
|
} catch (ArgumentException) {
|
|
|
|
|
report.Add(FileLoadItem.Type.Warning, Res.Strings.ERR_BAD_DEF_SYMBOL_DIR +
|
|
|
|
|
": " + serDefSym.Direction);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DefSymbol.MultiAddressMask multiMask = null;
|
|
|
|
|
if (serDefSym.MultiMask != null) {
|
|
|
|
|
multiMask = new DefSymbol.MultiAddressMask(serDefSym.MultiMask.CompareMask,
|
|
|
|
|
serDefSym.MultiMask.CompareValue, serDefSym.MultiMask.AddressMask);
|
|
|
|
|
}
|
2019-08-26 23:58:53 +00:00
|
|
|
|
|
2019-10-15 23:37:14 +00:00
|
|
|
|
outDefSym = DefSymbol.Create(sym, dfd, serDefSym.HasWidth, serDefSym.Comment,
|
|
|
|
|
direction, multiMask);
|
2019-08-26 23:58:53 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-02 22:45:40 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Creates a FormatDescriptor from a SerFormatDescriptor.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="sfd">Deserialized data.</param>
|
2019-08-12 00:59:20 +00:00
|
|
|
|
/// <param name="version">Serialization version (CONTENT_VERSION).</param>
|
2019-05-02 22:45:40 +00:00
|
|
|
|
/// <param name="report">Error report object.</param>
|
|
|
|
|
/// <param name="dfd">Created FormatDescriptor.</param>
|
|
|
|
|
/// <returns>True on success.</returns>
|
2019-08-12 00:59:20 +00:00
|
|
|
|
private static bool CreateFormatDescriptor(SerFormatDescriptor sfd, int version,
|
2019-05-02 22:45:40 +00:00
|
|
|
|
FileLoadReport report, out FormatDescriptor dfd) {
|
|
|
|
|
dfd = null;
|
|
|
|
|
FormatDescriptor.Type format;
|
|
|
|
|
FormatDescriptor.SubType subFormat;
|
Change the way string formats are defined
We used to use type="String", with the sub-type indicating whether
the string was null-terminated, prefixed with a length, or whatever.
This didn't leave much room for specifying a character encoding,
which is orthogonal to the sub-type.
What we actually want is to have the type specify the string type,
and then have the sub-type determine the character encoding. These
sub-types can also be used with the Numeric type to specify the
encoding of character operands.
This change updates the enum definitions and the various bits of
code that use them, but does not add any code for working with
non-ASCII character encodings.
The project file version number was incremented to 2, since the new
FormatDescriptor serialization is mildly incompatible with the old.
(Won't explode, but it'll post a complaint and ignore the stuff
it doesn't recognize.)
While I was at it, I finished removing DciReverse. It's still part
of the 2005-string-types regression test, which currently fails
because the generated source doesn't match.
2019-08-07 22:23:23 +00:00
|
|
|
|
|
|
|
|
|
if ("String".Equals(sfd.Format)) {
|
2019-08-10 21:24:19 +00:00
|
|
|
|
// File version 1 used a different set of enumerated values for defining strings.
|
|
|
|
|
// Parse it out here.
|
2019-08-12 00:59:20 +00:00
|
|
|
|
Debug.Assert(version <= 1);
|
2019-08-10 21:24:19 +00:00
|
|
|
|
subFormat = FormatDescriptor.SubType.ASCII_GENERIC;
|
Change the way string formats are defined
We used to use type="String", with the sub-type indicating whether
the string was null-terminated, prefixed with a length, or whatever.
This didn't leave much room for specifying a character encoding,
which is orthogonal to the sub-type.
What we actually want is to have the type specify the string type,
and then have the sub-type determine the character encoding. These
sub-types can also be used with the Numeric type to specify the
encoding of character operands.
This change updates the enum definitions and the various bits of
code that use them, but does not add any code for working with
non-ASCII character encodings.
The project file version number was incremented to 2, since the new
FormatDescriptor serialization is mildly incompatible with the old.
(Won't explode, but it'll post a complaint and ignore the stuff
it doesn't recognize.)
While I was at it, I finished removing DciReverse. It's still part
of the 2005-string-types regression test, which currently fails
because the generated source doesn't match.
2019-08-07 22:23:23 +00:00
|
|
|
|
if ("None".Equals(sfd.SubFormat)) {
|
|
|
|
|
format = FormatDescriptor.Type.StringGeneric;
|
|
|
|
|
} else if ("Reverse".Equals(sfd.SubFormat)) {
|
|
|
|
|
format = FormatDescriptor.Type.StringReverse;
|
|
|
|
|
} else if ("CString".Equals(sfd.SubFormat)) {
|
|
|
|
|
format = FormatDescriptor.Type.StringNullTerm;
|
|
|
|
|
} else if ("L8String".Equals(sfd.SubFormat)) {
|
|
|
|
|
format = FormatDescriptor.Type.StringL8;
|
|
|
|
|
} else if ("L16String".Equals(sfd.SubFormat)) {
|
|
|
|
|
format = FormatDescriptor.Type.StringL16;
|
|
|
|
|
} else if ("Dci".Equals(sfd.SubFormat)) {
|
|
|
|
|
format = FormatDescriptor.Type.StringDci;
|
2019-08-09 23:41:05 +00:00
|
|
|
|
} else if ("DciReverse".Equals(sfd.SubFormat)) {
|
2019-08-10 21:24:19 +00:00
|
|
|
|
// No longer supported. Nobody ever used this but the regression tests,
|
|
|
|
|
// though, so there's no reason to handle this nicely.
|
2019-08-09 23:41:05 +00:00
|
|
|
|
format = FormatDescriptor.Type.Dense;
|
|
|
|
|
subFormat = FormatDescriptor.SubType.None;
|
Change the way string formats are defined
We used to use type="String", with the sub-type indicating whether
the string was null-terminated, prefixed with a length, or whatever.
This didn't leave much room for specifying a character encoding,
which is orthogonal to the sub-type.
What we actually want is to have the type specify the string type,
and then have the sub-type determine the character encoding. These
sub-types can also be used with the Numeric type to specify the
encoding of character operands.
This change updates the enum definitions and the various bits of
code that use them, but does not add any code for working with
non-ASCII character encodings.
The project file version number was incremented to 2, since the new
FormatDescriptor serialization is mildly incompatible with the old.
(Won't explode, but it'll post a complaint and ignore the stuff
it doesn't recognize.)
While I was at it, I finished removing DciReverse. It's still part
of the 2005-string-types regression test, which currently fails
because the generated source doesn't match.
2019-08-07 22:23:23 +00:00
|
|
|
|
} else {
|
2019-08-09 23:41:05 +00:00
|
|
|
|
// No idea what this is; output as dense hex.
|
Change the way string formats are defined
We used to use type="String", with the sub-type indicating whether
the string was null-terminated, prefixed with a length, or whatever.
This didn't leave much room for specifying a character encoding,
which is orthogonal to the sub-type.
What we actually want is to have the type specify the string type,
and then have the sub-type determine the character encoding. These
sub-types can also be used with the Numeric type to specify the
encoding of character operands.
This change updates the enum definitions and the various bits of
code that use them, but does not add any code for working with
non-ASCII character encodings.
The project file version number was incremented to 2, since the new
FormatDescriptor serialization is mildly incompatible with the old.
(Won't explode, but it'll post a complaint and ignore the stuff
it doesn't recognize.)
While I was at it, I finished removing DciReverse. It's still part
of the 2005-string-types regression test, which currently fails
because the generated source doesn't match.
2019-08-07 22:23:23 +00:00
|
|
|
|
format = FormatDescriptor.Type.Dense;
|
|
|
|
|
subFormat = FormatDescriptor.SubType.None;
|
|
|
|
|
}
|
|
|
|
|
Debug.WriteLine("Found v1 string, fmt=" + format + ", sub=" + subFormat);
|
|
|
|
|
dfd = FormatDescriptor.Create(sfd.Length, format, subFormat);
|
2019-10-19 03:28:02 +00:00
|
|
|
|
return dfd != null;
|
Change the way string formats are defined
We used to use type="String", with the sub-type indicating whether
the string was null-terminated, prefixed with a length, or whatever.
This didn't leave much room for specifying a character encoding,
which is orthogonal to the sub-type.
What we actually want is to have the type specify the string type,
and then have the sub-type determine the character encoding. These
sub-types can also be used with the Numeric type to specify the
encoding of character operands.
This change updates the enum definitions and the various bits of
code that use them, but does not add any code for working with
non-ASCII character encodings.
The project file version number was incremented to 2, since the new
FormatDescriptor serialization is mildly incompatible with the old.
(Won't explode, but it'll post a complaint and ignore the stuff
it doesn't recognize.)
While I was at it, I finished removing DciReverse. It's still part
of the 2005-string-types regression test, which currently fails
because the generated source doesn't match.
2019-08-07 22:23:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-02 22:45:40 +00:00
|
|
|
|
try {
|
|
|
|
|
format = (FormatDescriptor.Type)Enum.Parse(
|
|
|
|
|
typeof(FormatDescriptor.Type), sfd.Format);
|
2019-08-12 00:59:20 +00:00
|
|
|
|
if (version <= 1 && "Ascii".Equals(sfd.SubFormat)) {
|
2019-08-10 21:24:19 +00:00
|
|
|
|
// File version 1 used "Ascii" for all character data in numeric operands.
|
|
|
|
|
// It applied to both low and high ASCII.
|
|
|
|
|
subFormat = FormatDescriptor.SubType.ASCII_GENERIC;
|
2019-08-12 00:59:20 +00:00
|
|
|
|
Debug.WriteLine("Found v1 char, fmt=" + sfd.Format + ", sub=" + sfd.SubFormat);
|
2019-08-10 21:24:19 +00:00
|
|
|
|
} else {
|
|
|
|
|
subFormat = (FormatDescriptor.SubType)Enum.Parse(
|
|
|
|
|
typeof(FormatDescriptor.SubType), sfd.SubFormat);
|
|
|
|
|
}
|
2019-05-02 22:45:40 +00:00
|
|
|
|
} catch (ArgumentException) {
|
2019-05-09 01:00:17 +00:00
|
|
|
|
report.Add(FileLoadItem.Type.Warning, Res.Strings.ERR_BAD_FD_FORMAT +
|
2019-05-02 22:45:40 +00:00
|
|
|
|
": " + sfd.Format + "/" + sfd.SubFormat);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (sfd.SymbolRef == null) {
|
|
|
|
|
dfd = FormatDescriptor.Create(sfd.Length, format, subFormat);
|
|
|
|
|
} else {
|
|
|
|
|
WeakSymbolRef.Part part;
|
|
|
|
|
try {
|
|
|
|
|
part = (WeakSymbolRef.Part)Enum.Parse(
|
|
|
|
|
typeof(WeakSymbolRef.Part), sfd.SymbolRef.Part);
|
|
|
|
|
} catch (ArgumentException) {
|
|
|
|
|
report.Add(FileLoadItem.Type.Warning,
|
|
|
|
|
Res.Strings.ERR_BAD_SYMREF_PART +
|
|
|
|
|
": " + sfd.SymbolRef.Part);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
dfd = FormatDescriptor.Create(sfd.Length,
|
|
|
|
|
new WeakSymbolRef(sfd.SymbolRef.Label, part),
|
|
|
|
|
format == FormatDescriptor.Type.NumericBE);
|
|
|
|
|
}
|
2019-10-19 03:28:02 +00:00
|
|
|
|
return dfd != null;
|
2019-05-02 22:45:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-26 23:58:53 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Creates a LocalVariableTable from a SerLocalVariableTable.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="serTable">Deserialized data.</param>
|
|
|
|
|
/// <param name="contentVersion">Serialization version.</param>
|
|
|
|
|
/// <param name="report">Error report object.</param>
|
|
|
|
|
/// <param name="outLvt">Created LocalVariableTable</param>
|
|
|
|
|
/// <returns>True on success.</returns>
|
|
|
|
|
private static bool CreateLocalVariableTable(SerLocalVariableTable serTable,
|
|
|
|
|
int contentVersion, FileLoadReport report, out LocalVariableTable outLvt) {
|
|
|
|
|
outLvt = new LocalVariableTable();
|
|
|
|
|
outLvt.ClearPrevious = serTable.ClearPrevious;
|
|
|
|
|
foreach (SerDefSymbol serDef in serTable.Variables) {
|
Allow explicit widths in project/platform symbols, part 1
The ability to give explicit widths to local variables worked out
pretty well, so we're going to try adding the same thing to project
and platform symbols.
The first step is to allow widths to be specified in platform files,
and set with the project symbol editor. The DefSymbol editor is
also used for local variables, so a bit of dancing is required.
For platform/project symbols the width is optional, and is totally
ignored for constants. (For variables, constants are used for the
StackRel args, so the width is meaningful and required.)
We also now show the symbol's type (address or constant) and width
in the listing. This gets really distracting when overused, so we
only show it when the width is explicitly set. The default width
is 1, which most things will be, so users can make an aesthetic
choice there. (The place where widths make very little sense is when
the symbol represents a code entry point, rather than a data item.)
The maximum width of a local variable is now 256, but it's not
allowed to overlap with other variables or run of the end of the
direct page. The maximum width of a platform/project symbol is
65536, with bank-wrap behavior TBD.
The local variable table editor now refers to stack-relative
constants as such, rather than simply "constant", to make it clear
that it's not just defining an 8-bit constant.
Widths have been added to a handful of Apple II platform defs.
2019-10-01 21:58:24 +00:00
|
|
|
|
// Force the "has width" field to true for local variables, because it's
|
|
|
|
|
// non-optional there. This is really only needed for loading projects
|
|
|
|
|
// created in v1.3, which didn't have the "has width" property.
|
|
|
|
|
serDef.HasWidth = true;
|
2019-08-26 23:58:53 +00:00
|
|
|
|
if (!CreateDefSymbol(serDef, contentVersion, report, out DefSymbol defSym)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-08-31 01:33:05 +00:00
|
|
|
|
if (!defSym.IsVariable) {
|
Allow explicit widths in project/platform symbols, part 1
The ability to give explicit widths to local variables worked out
pretty well, so we're going to try adding the same thing to project
and platform symbols.
The first step is to allow widths to be specified in platform files,
and set with the project symbol editor. The DefSymbol editor is
also used for local variables, so a bit of dancing is required.
For platform/project symbols the width is optional, and is totally
ignored for constants. (For variables, constants are used for the
StackRel args, so the width is meaningful and required.)
We also now show the symbol's type (address or constant) and width
in the listing. This gets really distracting when overused, so we
only show it when the width is explicitly set. The default width
is 1, which most things will be, so users can make an aesthetic
choice there. (The place where widths make very little sense is when
the symbol represents a code entry point, rather than a data item.)
The maximum width of a local variable is now 256, but it's not
allowed to overlap with other variables or run of the end of the
direct page. The maximum width of a platform/project symbol is
65536, with bank-wrap behavior TBD.
The local variable table editor now refers to stack-relative
constants as such, rather than simply "constant", to make it clear
that it's not just defining an 8-bit constant.
Widths have been added to a handful of Apple II platform defs.
2019-10-01 21:58:24 +00:00
|
|
|
|
// not expected to happen; skip it
|
2019-08-29 00:34:29 +00:00
|
|
|
|
Debug.WriteLine("Found local variable with bad source: " +
|
|
|
|
|
defSym.SymbolSource);
|
Allow explicit widths in project/platform symbols, part 1
The ability to give explicit widths to local variables worked out
pretty well, so we're going to try adding the same thing to project
and platform symbols.
The first step is to allow widths to be specified in platform files,
and set with the project symbol editor. The DefSymbol editor is
also used for local variables, so a bit of dancing is required.
For platform/project symbols the width is optional, and is totally
ignored for constants. (For variables, constants are used for the
StackRel args, so the width is meaningful and required.)
We also now show the symbol's type (address or constant) and width
in the listing. This gets really distracting when overused, so we
only show it when the width is explicitly set. The default width
is 1, which most things will be, so users can make an aesthetic
choice there. (The place where widths make very little sense is when
the symbol represents a code entry point, rather than a data item.)
The maximum width of a local variable is now 256, but it's not
allowed to overlap with other variables or run of the end of the
direct page. The maximum width of a platform/project symbol is
65536, with bank-wrap behavior TBD.
The local variable table editor now refers to stack-relative
constants as such, rather than simply "constant", to make it clear
that it's not just defining an 8-bit constant.
Widths have been added to a handful of Apple II platform defs.
2019-10-01 21:58:24 +00:00
|
|
|
|
string str = string.Format(Res.Strings.ERR_BAD_LOCAL_VARIABLE_FMT,
|
|
|
|
|
defSym);
|
|
|
|
|
report.Add(FileLoadItem.Type.Warning, str);
|
2019-08-29 00:34:29 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
outLvt.AddOrReplace(defSym);
|
2019-08-26 23:58:53 +00:00
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-23 00:56:57 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Creates a Visualization from its serialized form.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static bool CreateVisualization(SerVisualization serVis, FileLoadReport report,
|
|
|
|
|
out Visualization vis) {
|
|
|
|
|
if (!CheckVis(serVis, report, out Dictionary<string, object> parms)) {
|
|
|
|
|
vis = null;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
vis = new Visualization(serVis.Tag, serVis.VisGenIdent,
|
|
|
|
|
new ReadOnlyDictionary<string, object>(parms));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Creates a VisualizationAnimation from its serialized form.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static bool CreateVisualizationAnimation(SerVisualizationAnimation serVisAnim,
|
|
|
|
|
Dictionary<string, Visualization> visList, FileLoadReport report,
|
|
|
|
|
out VisualizationAnimation visAnim) {
|
|
|
|
|
if (!CheckVis(serVisAnim, report, out Dictionary<string, object> parms)) {
|
|
|
|
|
visAnim = null;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
List<int> serialNumbers = new List<int>(serVisAnim.Tags.Count);
|
|
|
|
|
foreach (string tag in serVisAnim.Tags) {
|
|
|
|
|
if (!visList.TryGetValue(tag, out Visualization vis)) {
|
|
|
|
|
string str = string.Format(Res.Strings.ERR_BAD_VISUALIZATION_FMT,
|
|
|
|
|
"unknown tag in animation: " + tag);
|
|
|
|
|
report.Add(FileLoadItem.Type.Warning, str);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (vis is VisualizationAnimation) {
|
|
|
|
|
string str = string.Format(Res.Strings.ERR_BAD_VISUALIZATION_FMT,
|
|
|
|
|
"animation in animation: " + tag);
|
|
|
|
|
report.Add(FileLoadItem.Type.Warning, str);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
serialNumbers.Add(vis.SerialNumber);
|
|
|
|
|
}
|
|
|
|
|
visAnim = new VisualizationAnimation(serVisAnim.Tag, serVisAnim.VisGenIdent,
|
|
|
|
|
new ReadOnlyDictionary<string, object>(parms), serialNumbers, null);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Checks for errors common to Visualization objects. Generates a replacement
|
|
|
|
|
/// parameter object to work around JavaScript type conversion.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static bool CheckVis(SerVisualization serVis, FileLoadReport report,
|
|
|
|
|
out Dictionary<string, object> parms) {
|
|
|
|
|
parms = null;
|
|
|
|
|
|
|
|
|
|
string unused = Visualization.TrimAndValidateTag(serVis.Tag, out bool isTagValid);
|
|
|
|
|
if (!isTagValid) {
|
|
|
|
|
Debug.WriteLine("Visualization with invalid tag: " + serVis.Tag);
|
|
|
|
|
string str = string.Format(Res.Strings.ERR_BAD_VISUALIZATION_FMT, serVis.Tag);
|
|
|
|
|
report.Add(FileLoadItem.Type.Warning, str);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (string.IsNullOrEmpty(serVis.VisGenIdent) || serVis.VisGenParams == null) {
|
|
|
|
|
string str = string.Format(Res.Strings.ERR_BAD_VISUALIZATION_FMT,
|
|
|
|
|
"ident/params");
|
|
|
|
|
report.Add(FileLoadItem.Type.Warning, str);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The JavaScript deserialization turns floats into Decimal. Change it back
|
|
|
|
|
// so we don't have to deal with it later.
|
|
|
|
|
parms = new Dictionary<string, object>(serVis.VisGenParams.Count);
|
|
|
|
|
foreach (KeyValuePair<string, object> kvp in serVis.VisGenParams) {
|
|
|
|
|
object val = kvp.Value;
|
|
|
|
|
if (val is decimal) {
|
|
|
|
|
val = (double)((decimal)val);
|
|
|
|
|
}
|
|
|
|
|
parms.Add(kvp.Key, val);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Creates a VisualizationSet from its serialized form.
|
|
|
|
|
/// </summary>
|
2019-12-03 00:38:32 +00:00
|
|
|
|
private static bool CreateVisualizationSet(SerVisualizationSet serVisSet,
|
2019-12-23 00:56:57 +00:00
|
|
|
|
Dictionary<string, Visualization> visList, FileLoadReport report,
|
|
|
|
|
out VisualizationSet outVisSet) {
|
2019-12-03 00:38:32 +00:00
|
|
|
|
outVisSet = new VisualizationSet();
|
2019-12-23 00:56:57 +00:00
|
|
|
|
foreach (string rawTag in serVisSet.Tags) {
|
|
|
|
|
string trimTag = Visualization.TrimAndValidateTag(rawTag, out bool isTagValid);
|
2019-12-03 00:38:32 +00:00
|
|
|
|
if (!isTagValid) {
|
2019-12-23 00:56:57 +00:00
|
|
|
|
Debug.WriteLine("VisualizationSet with invalid tag: " + rawTag);
|
|
|
|
|
string str = string.Format(Res.Strings.ERR_BAD_VISUALIZATION_FMT, rawTag);
|
2019-12-03 00:38:32 +00:00
|
|
|
|
report.Add(FileLoadItem.Type.Warning, str);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2019-12-23 00:56:57 +00:00
|
|
|
|
if (!visList.TryGetValue(trimTag, out Visualization vis)) {
|
|
|
|
|
Debug.WriteLine("VisSet ref to unknown tag: " + trimTag);
|
2019-12-03 00:38:32 +00:00
|
|
|
|
string str = string.Format(Res.Strings.ERR_BAD_VISUALIZATION_FMT,
|
2019-12-23 00:56:57 +00:00
|
|
|
|
"unknown tag: " + trimTag);
|
2019-12-03 00:38:32 +00:00
|
|
|
|
report.Add(FileLoadItem.Type.Warning, str);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
outVisSet.Add(vis);
|
2019-12-23 00:56:57 +00:00
|
|
|
|
|
|
|
|
|
// Each Visualization should only appear in one VisualizationSet. Things
|
|
|
|
|
// might get weird when we remove one if this isn't true. So we remove
|
|
|
|
|
// it from the dictionary.
|
|
|
|
|
visList.Remove(trimTag);
|
2019-12-03 00:38:32 +00:00
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-02 22:45:40 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Parses an integer key that was stored as a string, and checks to see if the
|
|
|
|
|
/// value falls within an acceptable range.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="keyStr">Integer key, in string form.</param>
|
|
|
|
|
/// <param name="fileLen">Length of file, for range check.</param>
|
|
|
|
|
/// <param name="fieldName">Name of field, for error messages.</param>
|
|
|
|
|
/// <param name="report">Error report object.</param>
|
|
|
|
|
/// <param name="intKey">Returned integer key.</param>
|
2019-08-26 23:58:53 +00:00
|
|
|
|
/// <returns>True on success.</returns>
|
2019-05-02 22:45:40 +00:00
|
|
|
|
private static bool ParseValidateKey(string keyStr, int fileLen, string fieldName,
|
|
|
|
|
FileLoadReport report, out int intKey) {
|
|
|
|
|
if (!int.TryParse(keyStr, out intKey)) {
|
|
|
|
|
report.Add(FileLoadItem.Type.Warning,
|
|
|
|
|
Res.Strings.ERR_INVALID_INT_VALUE + " (" +
|
|
|
|
|
fieldName + ": " + keyStr + ")");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Shouldn't allow DisplayList.Line.HEADER_COMMENT_OFFSET on anything but
|
|
|
|
|
// LongComment. Maybe "bool allowNegativeKeys"?
|
|
|
|
|
if (intKey < fileLen &&
|
2019-05-28 01:46:09 +00:00
|
|
|
|
(intKey >= 0 || intKey == LineListGen.Line.HEADER_COMMENT_OFFSET)) {
|
2019-05-02 22:45:40 +00:00
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
report.Add(FileLoadItem.Type.Warning,
|
|
|
|
|
Res.Strings.ERR_INVALID_KEY_VALUE +
|
|
|
|
|
" (" + fieldName + ": " + intKey + ")");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static int ColorToInt(Color color) {
|
|
|
|
|
return (color.A << 24) | (color.R << 16) | (color.G << 8) | color.B;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Color ColorFromInt(int colorInt) {
|
|
|
|
|
return Color.FromArgb((byte)(colorInt >> 24), (byte)(colorInt >> 16),
|
|
|
|
|
(byte)(colorInt >> 8), (byte)colorInt);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|