1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-07-07 07:28:57 +00:00
6502bench/SourceGen/ProjectFile.cs
Andy McFadden 0d0854bda7 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 16:19:13 -07:00

771 lines
35 KiB
C#

/*
* Copyright 2019 faddenSoft
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Web.Script.Serialization;
using System.Windows.Media;
using CommonUtil;
namespace SourceGen {
/// <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.
public const int CONTENT_VERSION = 2;
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.
/// </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
// number refers to the file format version, not the application version.
public const string MAGIC = "### 6502bench SourceGen dis65 v1.0 ###";
public SerializableProjectFile1() { }
public class SerProjectProperties {
public string CpuName { get; set; }
public bool IncludeUndocumentedInstr { get; set; }
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;
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; }
public int MinCharsForString { get; set; }
public bool SeekNearbyTargets { get; set; }
public SerAnalysisParameters() { }
public SerAnalysisParameters(ProjectProperties.AnalysisParameters src) {
AnalyzeUncategorizedData = src.AnalyzeUncategorizedData;
MinCharsForString = src.MinCharsForString;
SeekNearbyTargets = src.SeekNearbyTargets;
}
}
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; }
public SerSymbol() { }
public SerSymbol(Symbol sym) {
Label = sym.Label;
Value = sym.Value;
Source = sym.SymbolSource.ToString();
Type = sym.SymbolType.ToString();
}
}
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) {
Label = weakSym.Label;
Part = weakSym.ValuePart.ToString();
}
}
public class SerDefSymbol : SerSymbol {
public SerFormatDescriptor DataDescriptor { get; set; }
public string Comment { get; set; }
public SerDefSymbol() { }
public SerDefSymbol(DefSymbol defSym) : base(defSym) {
DataDescriptor = new SerFormatDescriptor(defSym.DataDescriptor);
Comment = defSym.Comment;
}
}
// 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; }
/// <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));
}
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;
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;
proj.ProjectProps.AnalysisParams.MinCharsForString =
spf.ProjectProps.AnalysisParams.MinCharsForString;
proj.ProjectProps.AnalysisParams.SeekNearbyTargets =
spf.ProjectProps.AnalysisParams.SeekNearbyTargets;
// 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) {
if (!CreateSymbol(kvp.Value, report, out Symbol sym)) {
continue;
}
if (!CreateFormatDescriptor(kvp.Value.DataDescriptor, report,
out FormatDescriptor dfd)) {
continue;
}
proj.ProjectProps.ProjectSyms[sym.Label] =
new DefSymbol(sym, dfd, kvp.Value.Comment);
}
// 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;
}
Symbol.Source source;
Symbol.Type type;
try {
source = (Symbol.Source)Enum.Parse(typeof(Symbol.Source), kvp.Value.Source);
type = (Symbol.Type)Enum.Parse(typeof(Symbol.Type), kvp.Value.Type);
if (source != Symbol.Source.User) {
// User labels are always source=user. I don't think it really matters,
// but best to keep junk out.
throw new Exception("wrong source for user label");
}
} catch (ArgumentException) {
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.
if (labelDupCheck.ContainsKey(kvp.Value.Label)) {
report.Add(FileLoadItem.Type.Warning,
string.Format(Res.Strings.ERR_DUPLICATE_LABEL_FMT,
kvp.Value.Label, intKey));
continue;
}
labelDupCheck.Add(kvp.Value.Label, string.Empty);
proj.UserLabels[intKey] = new Symbol(kvp.Value.Label, kvp.Value.Value,
source, type);
}
// Deserialize operand format descriptors.
foreach (KeyValuePair<string,SerFormatDescriptor> kvp in spf.OperandFormats) {
if (!ParseValidateKey(kvp.Key, spf.FileDataLength,
Res.Strings.PROJECT_FIELD_OPERAND_FORMAT, report,
out int intKey)) {
continue;
}
if (!CreateFormatDescriptor(kvp.Value, report, out FormatDescriptor dfd)) {
continue;
}
if (intKey < 0 || intKey + dfd.Length > spf.FileDataLength) {
report.Add(FileLoadItem.Type.Warning,
string.Format(Res.Strings.ERR_BAD_FD_FMT, intKey));
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;
}
return true;
}
/// <summary>
/// Creates a Symbol from a SerSymbol.
/// </summary>
/// <param name="ssym">Deserialized data.</param>
/// <param name="report">Error report object.</param>
/// <param name="outSym"></param>
/// <returns>True on success.</returns>
private static bool CreateSymbol(SerSymbol ssym, FileLoadReport report,
out Symbol outSym) {
outSym = null;
Symbol.Source source;
Symbol.Type type;
try {
source = (Symbol.Source)Enum.Parse(typeof(Symbol.Source), ssym.Source);
type = (Symbol.Type)Enum.Parse(typeof(Symbol.Type), ssym.Type);
} catch (ArgumentException) {
report.Add(FileLoadItem.Type.Warning, Res.Strings.ERR_BAD_SYMBOL_ST +
": " + ssym.Source + "/" + ssym.Type);
return false;
}
outSym = new Symbol(ssym.Label, ssym.Value, source, type/*, ssym.IsExport*/);
return true;
}
/// <summary>
/// Creates a FormatDescriptor from a SerFormatDescriptor.
/// </summary>
/// <param name="sfd">Deserialized data.</param>
/// <param name="report">Error report object.</param>
/// <param name="dfd">Created FormatDescriptor.</param>
/// <returns>True on success.</returns>
private static bool CreateFormatDescriptor(SerFormatDescriptor sfd,
FileLoadReport report, out FormatDescriptor dfd) {
dfd = null;
FormatDescriptor.Type format;
FormatDescriptor.SubType subFormat;
// File version 1 used a different set of enumerated values for defining strings.
// Parse it out here.
if ("String".Equals(sfd.Format)) {
subFormat = FormatDescriptor.SubType.Ascii;
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;
} else {
// DciReverse no longer supported; output as dense hex
format = FormatDescriptor.Type.Dense;
subFormat = FormatDescriptor.SubType.None;
}
Debug.WriteLine("Found v1 string, fmt=" + format + ", sub=" + subFormat);
dfd = FormatDescriptor.Create(sfd.Length, format, subFormat);
return true;
}
try {
format = (FormatDescriptor.Type)Enum.Parse(
typeof(FormatDescriptor.Type), sfd.Format);
subFormat = (FormatDescriptor.SubType)Enum.Parse(
typeof(FormatDescriptor.SubType), sfd.SubFormat);
} catch (ArgumentException) {
report.Add(FileLoadItem.Type.Warning, Res.Strings.ERR_BAD_FD_FORMAT +
": " + 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);
}
return true;
}
/// <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>
/// <returns>True on success, false on failure.</returns>
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 &&
(intKey >= 0 || intKey == LineListGen.Line.HEADER_COMMENT_OFFSET)) {
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);
}
}
}