diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30f0112 --- /dev/null +++ b/.gitignore @@ -0,0 +1,312 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# 6502bench-specific stuff +SourceGen/SourceGen-settings +SourceGen/PluginDll +DIST_Debug +DIST_release + + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Typescript v1 declaration files +typings/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ diff --git a/Asm65/Address.cs b/Asm65/Address.cs new file mode 100644 index 0000000..204ca50 --- /dev/null +++ b/Asm65/Address.cs @@ -0,0 +1,92 @@ +/* + * Copyright 2018 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.Diagnostics; + +namespace Asm65 { + public static class Address { + /// + /// Converts a 16- or 24-bit address to a string. + /// + /// + /// + public static string AddressToString(int addr, bool always24) { + if (!always24 && addr < 65536) { + return addr.ToString("x4"); + } else { + return (addr >> 16).ToString("x2") + "/" + (addr & 0xffff).ToString("x4"); + } + } + + /// + /// Parses and validates a 16- or 24-bit address, expressed in hexadecimal. Bits + /// 16-23 may be specified with a slash. + /// + /// The following all evaluate to the same thing: 1000, $1000, 0x1000, 00/1000. + /// + /// String to validate. + /// Maximum valid address value. + /// Integer form. + /// True if the address is valid. + public static bool ParseAddress(string addrStr, int max, out int addr) { + string trimStr = addrStr.Trim(); // strip whitespace + if (trimStr.Length < 1) { + addr = -1; + return false; + } + if (trimStr[0] == '$') { + trimStr = trimStr.Remove(0, 1); + } + + int slashIndex = trimStr.IndexOf('/'); + try { + if (slashIndex < 0) { + addr = Convert.ToInt32(trimStr, 16); + if (addr < 0 || addr > max) { + Debug.WriteLine("Simple value out of range"); + addr = -1; + return false; + } + } else { + string[] splitStr = trimStr.Split('/'); + if (splitStr.Length == 2) { + int addr1 = Convert.ToInt32(splitStr[0], 16); + int addr2 = Convert.ToInt32(splitStr[1], 16); + addr = (addr1 << 16) | addr2; + // Check components separately to catch overflow. + if (addr1 < 0 || addr1 > 255 || addr2 < 0 || addr2 > 65535 || + addr > max) { + Debug.WriteLine("Slash value out of range"); + addr = -1; + return false; + } + } else { + addr = -1; + } + } + } catch (Exception) { + // Thrown from Convert.ToInt32 + //Debug.WriteLine("ValidateAddress: conversion of '" + addrStr + "' failed: " + + // ex.Message); + addr = -1; + return false; + } + + //Debug.WriteLine("Conv " + addrStr + " --> " + addr.ToString("x6")); + return true; + } + } +} diff --git a/Asm65/Asm65.csproj b/Asm65/Asm65.csproj new file mode 100644 index 0000000..7556a05 --- /dev/null +++ b/Asm65/Asm65.csproj @@ -0,0 +1,26 @@ + + + + netstandard2.0 + + + + + + + + + True + True + Resources.resx + + + + + + PublicResXFileCodeGenerator + Resources.Designer.cs + + + + diff --git a/Asm65/CpuDef.cs b/Asm65/CpuDef.cs new file mode 100644 index 0000000..0d6d20d --- /dev/null +++ b/Asm65/CpuDef.cs @@ -0,0 +1,1184 @@ +/* + * Copyright 2018 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.Diagnostics; + +namespace Asm65 { + /// + /// CPU definition. Includes a set of 256 opcodes. + /// + public class CpuDef { + /// + /// Human-readable name. + /// + public string Name { get; private set; } + + /// + /// Maximum possible address. + /// + public int MaxAddressValue { get; private set; } + + /// + /// Does this CPU have a 16-bit address space? + /// + public bool HasAddr16 { get { return MaxAddressValue <= 0xffff; } } + + /// + /// Does this CPU support the emulation flag (65802/65816)? + /// + public bool HasEmuFlag { get; private set; } + + /// + /// CPU type value. + /// + public CpuType Type { get; private set; } + + /// + /// True if undocumented opcodes are included. (This does not mean that the CPU has + /// undocumented opcodes, only that they haven't been stripped out if they exist.) + /// + public bool HasUndocumented { get; private set; } + + /// + /// Instruction set, 256 entries. + /// + private OpDef[] mOpDefs; + + /// + /// Cycle counts, 256 entries. + /// + private int[] mCycleCounts; + + /// + /// Cycle count modifiers, 256 entries. + /// + private OpDef.CycleMod[] mCycleMods; + + /// + /// List of "interesting" CPUs. These will be presented in the system definition + /// file when starting a new project. The actual set of CPUs that are unique (from + /// our perspective) is much smaller, as most of these can be represented accurately + /// (for disassembly purposes) by a handful of archetypes. + /// + public enum CpuType { + CpuUnknown = 0, + Cpu6502, // Apple ][+ + Cpu6502B, // Atari 800 + Cpu6507, // Atari 2600 + Cpu6502C, // Atari 5200 + Cpu6510, // Commodore 64 + Cpu8502, // Commodore 128 + Cpu2A03, // NES + Cpu65C02, // Apple //e + Cpu65802, // ? + Cpu65816, // Apple IIgs + Cpu5A22 // SNES + } + + /// + /// Converts a CPU type name string to a CpuType value. Used for deserialization. + /// + /// Case-sensitive CPU name + /// CpuType value, or CpuUnknown if name wasn't recognized. + public static CpuType GetCpuTypeFromName(string name) { + switch (name) { + case "6502": return CpuType.Cpu6502; + case "6502B": return CpuType.Cpu6502B; + case "6502C": return CpuType.Cpu6502C; + case "6507": return CpuType.Cpu6507; + case "6510": return CpuType.Cpu6510; + case "8502": return CpuType.Cpu8502; + case "2A03": return CpuType.Cpu2A03; + case "65C02": return CpuType.Cpu65C02; + case "65802": return CpuType.Cpu65802; + case "65816": return CpuType.Cpu65816; + case "5A22": return CpuType.Cpu5A22; + default: + return CpuType.CpuUnknown; + } + } + + /// + /// Converts a CpuType value to a CPU type name string. Used for serialization. + /// + /// CPU type. + /// CPU name string. + public static string GetCpuNameFromType(CpuType type) { + switch (type) { + case CpuType.Cpu6502: return "6502"; + case CpuType.Cpu6502B: return "6502B"; + case CpuType.Cpu6502C: return "6502C"; + case CpuType.Cpu6507: return "6507"; + case CpuType.Cpu6510: return "6510"; + case CpuType.Cpu8502: return "8502"; + case CpuType.Cpu2A03: return "2A03"; + case CpuType.Cpu65C02: return "65C02"; + case CpuType.Cpu65802: return "65802"; + case CpuType.Cpu65816: return "65816"; + case CpuType.Cpu5A22: return "5A22"; + default: + return "65??"; + } + } + + /// + /// Generates a CpuDef that best matches the parameters. + /// + /// Specific CPU we want. + /// Set to true if "undocumented" opcodes should + /// be included in the definition. + /// Best CpuDef. + public static CpuDef GetBestMatch(CpuType type, bool includeUndocumented) { + // Pretty much everything boils down to a 6502, 65C02, or 65816. + // The Rockwell R65C02, described in an appendix in Eyes & Lichty, doesn't, + // and would need its own CpuDef. + CpuDef cpuDef; + switch (type) { + case CpuType.Cpu65802: + case CpuType.Cpu65816: + case CpuType.Cpu5A22: + cpuDef = Cpu65816; + break; + case CpuType.Cpu65C02: + cpuDef = Cpu65C02; + break; + default: + cpuDef = Cpu6502; + break; + } + + cpuDef.GenerateCycleCounts(); + + // If we don't want undocumented opcodes, strip them out of the definition. + // Entries are replaced with OpInvalid. + if (!includeUndocumented) { + CpuDef stripped = new CpuDef(cpuDef); + for (int i = 0; i < 256; i++) { + if (cpuDef.mOpDefs[i].IsUndocumented) { + stripped.mOpDefs[i] = OpDef.OpInvalid; + } else { + stripped.mOpDefs[i] = cpuDef.mOpDefs[i]; + } + } + cpuDef = stripped; + } + + cpuDef.HasUndocumented = includeUndocumented; + + return cpuDef; + } + + /// + /// Constructor. + /// + /// Human-readable name. + /// Address space bits (16 or 24). + private CpuDef(string name, int maxAddressValue, bool hasEmuFlag) { + Name = name; + MaxAddressValue = maxAddressValue; + HasEmuFlag = hasEmuFlag; + HasUndocumented = true; + } + + /// + /// Copy constructor. Does not copy the contents of mOpDefs. Only used internally. + /// + /// Object to copy from. + private CpuDef(CpuDef src) { + Name = src.Name; + MaxAddressValue = src.MaxAddressValue; + HasEmuFlag = src.HasEmuFlag; + Type = src.Type; + mCycleCounts = src.mCycleCounts; + mCycleMods = src.mCycleMods; + + mOpDefs = new OpDef[256]; + } + + + /// + /// Returns an entry from the OpDef array for the specified opcode, 0-255. (We could + /// probably just make this the class indexer.) + /// + /// Instruction opcode + /// Instruction definition. + public OpDef GetOpDef(int op) { return mOpDefs[op]; } + + /// + /// Returns the number of cycles required to execute the instruction. If the value + /// is negative, the negated value represents the minimum number of cycles for an + /// instruction with variable timing. + /// + /// The value returned will factor in any CPU-specific aspects. + /// + /// Instruction opcode value. + /// Cycle count. + public int GetCycles(int opNum, StatusFlags flags, OpDef.BranchTaken branchTaken, + bool branchCrossesPage) { + // The irrelevant modifiers have already been stripped out. + OpDef.CycleMod mods = mCycleMods[opNum]; + int cycles = mCycleCounts[opNum]; + + // Walk through the various cycle mods. If we can evaluate them definitively, + // do so now and remove them from the set. + + // The M/X flags are defined to be in one state or the other, even when the flag + // value is indeterminate, because we have to be able to size immediate operands + // appropriately. So there's no ambiguity here even when there's ambiguity. We + // make a similar statement about the E flag. + if ((mods & OpDef.CycleMod.OneIfM0) != 0) { + if (!flags.ShortM) { + cycles++; + } + mods &= ~OpDef.CycleMod.OneIfM0; + } + if ((mods & OpDef.CycleMod.TwoIfM0) != 0) { + if (!flags.ShortM) { + cycles += 2; + } + mods &= ~OpDef.CycleMod.TwoIfM0; + } + if ((mods & OpDef.CycleMod.OneIfX0) != 0) { + if (!flags.ShortX) { + cycles++; + } + mods &= ~OpDef.CycleMod.OneIfX0; + } + if ((mods & OpDef.CycleMod.OneIfE0) != 0) { + if (flags.E == 0) { + cycles++; + } + mods &= ~OpDef.CycleMod.OneIfE0; + } + + // Some of these can be known, some can't. + if ((mods & OpDef.CycleMod.OneIfD1) != 0) { + if (flags.D == 1) { + cycles++; + } + if (flags.D == 0 || flags.D == 1) { + mods &= ~OpDef.CycleMod.OneIfD1; + } + } + if ((mods & OpDef.CycleMod.OneIfBranchTaken) != 0) { + if (branchTaken == OpDef.BranchTaken.Always) { + cycles++; + } + if (branchTaken != OpDef.BranchTaken.Indeterminate) { + mods &= ~OpDef.CycleMod.OneIfBranchTaken; + } + } + if ((mods & OpDef.CycleMod.OneIfBranchPage) != 0) { + if (branchCrossesPage && flags.E != 0) { + cycles++; // +1 unless we're in native mode on 65816 + } + mods &= ~OpDef.CycleMod.OneIfBranchPage; + } + + // We can't evaluate OneIfDpNonzero, OneIfIndexPage, or MinusOneIfNoPage. + // OneIf65C02 was handled earlier. + // TODO(maybe): in some cases we can know that the index doesn't cross a + // page boundary by checking the address, e.g. "LDA $2000,X" can't cross. + + if (mods != 0) { + // Some unresolved mods remain. + cycles = -cycles; + } + return cycles; + } + + + /// + /// Consistency test. + /// + /// True on success. + public static bool DebugValidate() { + InternalValidate(Cpu6502); + InternalValidate(Cpu65C02); + InternalValidate(Cpu65816); + Debug.WriteLine("CpuDefs okay"); + return true; + } + private static void InternalValidate(CpuDef cdef) { + for (int i = 0; i < 256; i++) { + OpDef op = cdef.mOpDefs[i]; + if (op.Opcode != i && op.AddrMode != OpDef.AddressMode.Unknown) { + throw new Exception("CpuDef for " + cdef.Type + ": entry 0x" + + i.ToString("x") + " has value " + op.Opcode.ToString("x")); + } + if (op.AddrMode != OpDef.AddressMode.Unknown && op.Cycles == 0) { + throw new Exception("Instruction 0x" + i.ToString("x2") + ": " + + op + " missing cycles"); + } + } + } + + public override string ToString() { + return Name + " (has16=" + HasAddr16 + ", hasEmu=" + HasEmuFlag + ")"; + } + + /// + /// Generates the mCycleCounts and mCycleMods arrays. + /// + private void GenerateCycleCounts() { + if (mCycleCounts != null) { + return; + } + mCycleCounts = new int[256]; + mCycleMods = new OpDef.CycleMod[256]; + + // Figure out which mods apply for this CPU. + OpDef.CycleMod ignoreMask = 0; + switch (Type) { + case CpuType.Cpu6502: + ignoreMask = OpDef.CycleMod.OneIfM0 | + OpDef.CycleMod.TwoIfM0 | + OpDef.CycleMod.OneIfX0 | + OpDef.CycleMod.OneIfDpNonzero | + OpDef.CycleMod.OneIfD1 | + OpDef.CycleMod.OneIfE0 | + OpDef.CycleMod.OneIf65C02 | + OpDef.CycleMod.MinusOneIfNoPage | + OpDef.CycleMod.BlockMove; + break; + case CpuType.Cpu65C02: + ignoreMask = OpDef.CycleMod.OneIfM0 | + OpDef.CycleMod.TwoIfM0 | + OpDef.CycleMod.OneIfX0 | + OpDef.CycleMod.OneIfDpNonzero | + OpDef.CycleMod.OneIfE0 | + OpDef.CycleMod.BlockMove; + break; + case CpuType.Cpu65816: + ignoreMask = OpDef.CycleMod.OneIfD1 | + OpDef.CycleMod.OneIf65C02 | + OpDef.CycleMod.MinusOneIfNoPage; + break; + default: + Debug.Assert(false, "unsupported cpu type " + Type); + return; + } + + // If an instruction has one or more applicable mods, declare it as variable. + for (int i = 0; i < 256; i++) { + OpDef op = mOpDefs[i]; + int baseCycles = op.Cycles; + OpDef.CycleMod mods = op.CycleMods & ~ignoreMask; + if ((mods & OpDef.CycleMod.OneIf65C02) != 0) { + // This isn't variable -- the instruction always takes one cycle longer + // on the 65C02. (Applies to $6C, JMP (addr).) + Debug.Assert(Type == CpuType.Cpu65C02); + baseCycles++; + mods &= ~OpDef.CycleMod.OneIf65C02; + } + mCycleCounts[i] = baseCycles; + mCycleMods[i] = mods; + } + } + + + // Classic MOS 6502, with full set of undocumented opcodes. + private static CpuDef Cpu6502 { get; } = new CpuDef("MOS 6502", (1 << 16) - 1, false) { + Type = CpuType.Cpu6502, + mOpDefs = new OpDef[] { + OpDef.OpBRK_StackInt, // 0x00 + OpDef.OpORA_DPIndexXInd, + OpDef.GenerateUndoc(0x02, OpDef.OpHLT_Implied), + OpDef.OpSLO_DPIndexXInd, + OpDef.GenerateUndoc(0x04, OpDef.OpDOP_DP), + OpDef.OpORA_DP, + OpDef.OpASL_DP, + OpDef.OpSLO_DP, + OpDef.OpPHP_StackPush, // 0x08 + OpDef.OpORA_Imm, + OpDef.OpASL_Acc, + OpDef.GenerateUndoc(0x0b, OpDef.OpANC_Imm), + OpDef.OpTOP_Abs, + OpDef.OpORA_Abs, + OpDef.OpASL_Abs, + OpDef.OpSLO_Absolute, + OpDef.OpBPL_PCRel, // 0x10 + OpDef.OpORA_DPIndIndexY, + OpDef.GenerateUndoc(0x12, OpDef.OpHLT_Implied), + OpDef.OpSLO_DPIndIndexY, + OpDef.GenerateUndoc(0x14, OpDef.OpDOP_DPIndexX), + OpDef.OpORA_DPIndexX, + OpDef.OpASL_DPIndexX, + OpDef.OpSLO_DPIndexX, + OpDef.OpCLC_Implied, // 0x18 + OpDef.OpORA_AbsIndexY, + OpDef.GenerateUndoc(0x1a, OpDef.OpNOP_Implied), + OpDef.OpSLO_AbsIndexY, + OpDef.GenerateUndoc(0x1c, OpDef.OpTOP_AbsIndeX), + OpDef.OpORA_AbsIndexX, + OpDef.OpASL_AbsIndexX, + OpDef.OpSLO_AbsIndexX, + OpDef.OpJSR_Abs, // 0x20 + OpDef.OpAND_DPIndexXInd, + OpDef.GenerateUndoc(0x22, OpDef.OpHLT_Implied), + OpDef.OpRLA_DPIndexXInd, + OpDef.OpBIT_DP, + OpDef.OpAND_DP, + OpDef.OpROL_DP, + OpDef.OpRLA_DP, + OpDef.OpPLP_StackPull, // 0x28 + OpDef.OpAND_Imm, + OpDef.OpROL_Acc, + OpDef.GenerateUndoc(0x2b, OpDef.OpANC_Imm), + OpDef.OpBIT_Abs, + OpDef.OpAND_Abs, + OpDef.OpROL_Abs, + OpDef.OpRLA_Absolute, + OpDef.OpBMI_PCRel, // 0x30 + OpDef.OpAND_DPIndIndexY, + OpDef.GenerateUndoc(0x32, OpDef.OpHLT_Implied), + OpDef.OpRLA_DPIndIndexY, + OpDef.GenerateUndoc(0x34, OpDef.OpDOP_DPIndexX), + OpDef.OpAND_DPIndexX, + OpDef.OpROL_DPIndexX, + OpDef.OpRLA_DPIndexX, + OpDef.OpSEC_Implied, // 0x38 + OpDef.OpAND_AbsIndexY, + OpDef.GenerateUndoc(0x3a, OpDef.OpNOP_Implied), + OpDef.OpRLA_AbsIndexY, + OpDef.GenerateUndoc(0x3c, OpDef.OpTOP_AbsIndeX), + OpDef.OpAND_AbsIndexX, + OpDef.OpROL_AbsIndexX, + OpDef.OpRLA_AbsIndexX, + OpDef.OpRTI_StackRTI, // 0x40 + OpDef.OpEOR_DPIndexXInd, + OpDef.GenerateUndoc(0x42, OpDef.OpHLT_Implied), + OpDef.OpSRE_DPIndexXInd, + OpDef.GenerateUndoc(0x44, OpDef.OpDOP_DP), + OpDef.OpEOR_DP, + OpDef.OpLSR_DP, + OpDef.OpSRE_DP, + OpDef.OpPHA_StackPush, // 0x48 + OpDef.OpEOR_Imm, + OpDef.OpLSR_Acc, + OpDef.OpASR_Imm, + OpDef.OpJMP_Abs, + OpDef.OpEOR_Abs, + OpDef.OpLSR_Abs, + OpDef.OpSRE_Absolute, + OpDef.OpBVC_PCRel, // 0x50 + OpDef.OpEOR_DPIndIndexY, + OpDef.GenerateUndoc(0x52, OpDef.OpHLT_Implied), + OpDef.OpSRE_DPIndIndexY, + OpDef.GenerateUndoc(0x54, OpDef.OpDOP_DPIndexX), + OpDef.OpEOR_DPIndexX, + OpDef.OpLSR_DPIndexX, + OpDef.OpSRE_DPIndexX, + OpDef.OpCLI_Implied, // 0x58 + OpDef.OpEOR_AbsIndexY, + OpDef.GenerateUndoc(0x5a, OpDef.OpNOP_Implied), + OpDef.OpSRE_AbsIndexY, + OpDef.GenerateUndoc(0x5c, OpDef.OpTOP_AbsIndeX), + OpDef.OpEOR_AbsIndexX, + OpDef.OpLSR_AbsIndexX, + OpDef.OpSRE_AbsIndexX, + OpDef.OpRTS_StackRTS, // 0x60 + OpDef.OpADC_DPIndexXInd, + OpDef.GenerateUndoc(0x62, OpDef.OpHLT_Implied), + OpDef.OpRRA_DPIndexXInd, + OpDef.GenerateUndoc(0x64, OpDef.OpDOP_DP), + OpDef.OpADC_DP, + OpDef.OpROR_DP, + OpDef.OpRRA_DP, + OpDef.OpPLA_StackPull, // 0x68 + OpDef.OpADC_Imm, + OpDef.OpROR_Acc, + OpDef.OpARR_Imm, + OpDef.OpJMP_AbsInd, + OpDef.OpADC_Abs, + OpDef.OpROR_Abs, + OpDef.OpRRA_Absolute, + OpDef.OpBVS_PCRel, // 0x70 + OpDef.OpADC_DPIndIndexY, + OpDef.GenerateUndoc(0x72, OpDef.OpHLT_Implied), + OpDef.OpRRA_DPIndIndexY, + OpDef.GenerateUndoc(0x74, OpDef.OpDOP_DPIndexX), + OpDef.OpADC_DPIndexX, + OpDef.OpROR_DPIndexX, + OpDef.OpRRA_DPIndexX, + OpDef.OpSEI_Implied, // 0x78 + OpDef.OpADC_AbsIndexY, + OpDef.GenerateUndoc(0x7a, OpDef.OpNOP_Implied), + OpDef.OpRRA_AbsIndexY, + OpDef.GenerateUndoc(0x7c, OpDef.OpTOP_AbsIndeX), + OpDef.OpADC_AbsIndexX, + OpDef.OpROR_AbsIndexX, + OpDef.OpRRA_AbsIndexX, + OpDef.GenerateUndoc(0x80, OpDef.OpDOP_Imm), // 0x80 + OpDef.OpSTA_DPIndexXInd, + OpDef.GenerateUndoc(0x82, OpDef.OpDOP_Imm), + OpDef.OpSAX_DPIndexXInd, + OpDef.OpSTY_DP, + OpDef.OpSTA_DP, + OpDef.OpSTX_DP, + OpDef.OpSAX_DP, + OpDef.OpDEY_Implied, // 0x88 + OpDef.GenerateUndoc(0x89, OpDef.OpDOP_Imm), + OpDef.OpTXA_Implied, + OpDef.OpANE_Imm, + OpDef.OpSTY_Abs, + OpDef.OpSTA_Abs, + OpDef.OpSTX_Abs, + OpDef.OpSAX_Absolute, + OpDef.OpBCC_PCRel, // 0x90 + OpDef.OpSTA_DPIndIndexY, + OpDef.GenerateUndoc(0x92, OpDef.OpHLT_Implied), + OpDef.OpSHA_DPIndIndexY, + OpDef.OpSTY_DPIndexX, + OpDef.OpSTA_DPIndexX, + OpDef.OpSTX_DPIndexY, + OpDef.OpSAX_DPIndexY, + OpDef.OpTYA_Implied, // 0x98 + OpDef.OpSTA_AbsIndexY, + OpDef.OpTXS_Implied, + OpDef.OpSHS_AbsIndexY, + OpDef.OpSHY_AbsIndexX, + OpDef.OpSTA_AbsIndexX, + OpDef.OpSHX_AbsIndexY, + OpDef.OpSHA_AbsIndexY, + OpDef.OpLDY_Imm, // 0xa0 + OpDef.OpLDA_DPIndexXInd, + OpDef.OpLDX_Imm, + OpDef.OpLAX_DPIndexXInd, + OpDef.OpLDY_DP, + OpDef.OpLDA_DP, + OpDef.OpLDX_DP, + OpDef.OpLAX_DP, + OpDef.OpTAY_Implied, // 0xa8 + OpDef.OpLDA_Imm, + OpDef.OpTAX_Implied, + OpDef.OpLXA_Imm, + OpDef.OpLDY_Abs, + OpDef.OpLDA_Abs, + OpDef.OpLDX_Abs, + OpDef.OpLAX_Absolute, + OpDef.OpBCS_PCRel, // 0xb0 + OpDef.OpLDA_DPIndIndexY, + OpDef.GenerateUndoc(0xb2, OpDef.OpHLT_Implied), + OpDef.OpLAX_DPIndIndexY, + OpDef.OpLDY_DPIndexX, + OpDef.OpLDA_DPIndexX, + OpDef.OpLDX_DPIndexY, + OpDef.OpLAX_DPIndexY, + OpDef.OpCLV_Implied, // 0xb8 + OpDef.OpLDA_AbsIndexY, + OpDef.OpTSX_Implied, + OpDef.OpLAE_AbsIndexY, + OpDef.OpLDY_AbsIndexX, + OpDef.OpLDA_AbsIndexX, + OpDef.OpLDX_AbsIndexY, + OpDef.OpLAX_AbsIndexY, + OpDef.OpCPY_Imm, // 0xc0 + OpDef.OpCMP_DPIndexXInd, + OpDef.GenerateUndoc(0xc2, OpDef.OpDOP_Imm), + OpDef.OpDCP_DPIndexXInd, + OpDef.OpCPY_DP, + OpDef.OpCMP_DP, + OpDef.OpDEC_DP, + OpDef.OpDCP_DP, + OpDef.OpINY_Implied, // 0xc8 + OpDef.OpCMP_Imm, + OpDef.OpDEX_Implied, + OpDef.OpSBX_Imm, + OpDef.OpCPY_Abs, + OpDef.OpCMP_Abs, + OpDef.OpDEC_Abs, + OpDef.OpDCP_Abs, + OpDef.OpBNE_PCRel, // 0xd0 + OpDef.OpCMP_DPIndIndexY, + OpDef.GenerateUndoc(0xd2, OpDef.OpHLT_Implied), + OpDef.OpDCP_DPIndIndexY, + OpDef.GenerateUndoc(0xd4, OpDef.OpDOP_DPIndexX), + OpDef.OpCMP_DPIndexX, + OpDef.OpDEC_DPIndexX, + OpDef.OpDCP_DPIndexX, + OpDef.OpCLD_Implied, // 0xd8 + OpDef.OpCMP_AbsIndexY, + OpDef.GenerateUndoc(0xda, OpDef.OpNOP_Implied), + OpDef.OpDCP_AbsIndexY, + OpDef.GenerateUndoc(0xdc, OpDef.OpTOP_AbsIndeX), + OpDef.OpCMP_AbsIndexX, + OpDef.OpDEC_AbsIndexX, + OpDef.OpDCP_AbsIndexX, + OpDef.OpCPX_Imm, // 0xe0 + OpDef.OpSBC_DPIndexXInd, + OpDef.GenerateUndoc(0xe2, OpDef.OpDOP_Imm), + OpDef.OpISB_DPIndexXInd, + OpDef.OpCPX_DP, + OpDef.OpSBC_DP, + OpDef.OpINC_DP, + OpDef.OpISB_DP, + OpDef.OpINX_Implied, // 0xe8 + OpDef.OpSBC_Imm, + OpDef.OpNOP_Implied, + OpDef.GenerateUndoc(0xeb, OpDef.OpSBC_Imm), + OpDef.OpCPX_Abs, + OpDef.OpSBC_Abs, + OpDef.OpINC_Abs, + OpDef.OpISB_Abs, + OpDef.OpBEQ_PCRel, // 0xf0 + OpDef.OpSBC_DPIndIndexY, + OpDef.GenerateUndoc(0xf2, OpDef.OpHLT_Implied), + OpDef.OpISB_DPIndIndexY, + OpDef.GenerateUndoc(0xf4, OpDef.OpDOP_DPIndexX), + OpDef.OpSBC_DPIndexX, + OpDef.OpINC_DPIndexX, + OpDef.OpISB_DPIndexX, + OpDef.OpSED_Implied, // 0xf8 + OpDef.OpSBC_AbsIndexY, + OpDef.GenerateUndoc(0xfa, OpDef.OpNOP_Implied), + OpDef.OpISB_AbsIndexY, + OpDef.GenerateUndoc(0xfc, OpDef.OpTOP_AbsIndeX), + OpDef.OpSBC_AbsIndexX, + OpDef.OpINC_AbsIndexX, + OpDef.OpISB_AbsIndexX, + } + }; + + + // WDC's 65C02, with new opcodes and a handful of slightly strange NOPs. + private static CpuDef Cpu65C02 { get; } = new CpuDef("WDC W65C02S", (1 << 16) - 1, false) { + Type = CpuType.Cpu65C02, + mOpDefs = new OpDef[] { + OpDef.OpBRK_StackInt, // 0x00 + OpDef.OpORA_DPIndexXInd, + OpDef.GenerateUndoc(0x02, OpDef.OpLDD_Imm), + OpDef.GenerateUndoc(0x03, OpDef.OpNOP_65C02), + OpDef.OpTSB_DP, + OpDef.OpORA_DP, + OpDef.OpASL_DP, + OpDef.GenerateUndoc(0x07, OpDef.OpNOP_65C02), + OpDef.OpPHP_StackPush, // 0x08 + OpDef.OpORA_Imm, + OpDef.OpASL_Acc, + OpDef.GenerateUndoc(0x0b, OpDef.OpNOP_65C02), + OpDef.OpTSB_Abs, + OpDef.OpORA_Abs, + OpDef.OpASL_Abs, + OpDef.GenerateUndoc(0x0f, OpDef.OpNOP_65C02), + OpDef.OpBPL_PCRel, // 0x10 + OpDef.OpORA_DPIndIndexY, + OpDef.OpORA_DPInd, + OpDef.GenerateUndoc(0x13, OpDef.OpNOP_65C02), + OpDef.OpTRB_DP, + OpDef.OpORA_DPIndexX, + OpDef.OpASL_DPIndexX, + OpDef.GenerateUndoc(0x17, OpDef.OpNOP_65C02), + OpDef.OpCLC_Implied, // 0x18 + OpDef.OpORA_AbsIndexY, + OpDef.OpINC_Acc, + OpDef.GenerateUndoc(0x1b, OpDef.OpNOP_65C02), + OpDef.OpTRB_Abs, + OpDef.OpORA_AbsIndexX, + OpDef.OpASL_AbsIndexX, + OpDef.GenerateUndoc(0x1f, OpDef.OpNOP_65C02), + OpDef.OpJSR_Abs, // 0x20 + OpDef.OpAND_DPIndexXInd, + OpDef.GenerateUndoc(0x22, OpDef.OpLDD_Imm), + OpDef.GenerateUndoc(0x23, OpDef.OpNOP_65C02), + OpDef.OpBIT_DP, + OpDef.OpAND_DP, + OpDef.OpROL_DP, + OpDef.GenerateUndoc(0x27, OpDef.OpNOP_65C02), + OpDef.OpPLP_StackPull, // 0x28 + OpDef.OpAND_Imm, + OpDef.OpROL_Acc, + OpDef.GenerateUndoc(0x2b, OpDef.OpNOP_65C02), + OpDef.OpBIT_Abs, + OpDef.OpAND_Abs, + OpDef.OpROL_Abs, + OpDef.GenerateUndoc(0x2f, OpDef.OpNOP_65C02), + OpDef.OpBMI_PCRel, // 0x30 + OpDef.OpAND_DPIndIndexY, + OpDef.OpAND_DPInd, + OpDef.GenerateUndoc(0x33, OpDef.OpNOP_65C02), + OpDef.OpBIT_DPIndexX, + OpDef.OpAND_DPIndexX, + OpDef.OpROL_DPIndexX, + OpDef.GenerateUndoc(0x37, OpDef.OpNOP_65C02), + OpDef.OpSEC_Implied, // 0x38 + OpDef.OpAND_AbsIndexY, + OpDef.OpDEC_Acc, + OpDef.GenerateUndoc(0x3b, OpDef.OpNOP_65C02), + OpDef.OpBIT_AbsIndexX, + OpDef.OpAND_AbsIndexX, + OpDef.OpROL_AbsIndexX, + OpDef.GenerateUndoc(0x3f, OpDef.OpNOP_65C02), + OpDef.OpRTI_StackRTI, // 0x40 + OpDef.OpEOR_DPIndexXInd, + OpDef.GenerateUndoc(0x42, OpDef.OpLDD_Imm), + OpDef.GenerateUndoc(0x43, OpDef.OpNOP_65C02), + OpDef.GenerateUndoc(0x44, OpDef.OpLDD_DP), + OpDef.OpEOR_DP, + OpDef.OpLSR_DP, + OpDef.GenerateUndoc(0x47, OpDef.OpNOP_65C02), + OpDef.OpPHA_StackPush, // 0x48 + OpDef.OpEOR_Imm, + OpDef.OpLSR_Acc, + OpDef.GenerateUndoc(0x4b, OpDef.OpNOP_65C02), + OpDef.OpJMP_Abs, + OpDef.OpEOR_Abs, + OpDef.OpLSR_Abs, + OpDef.GenerateUndoc(0x4f, OpDef.OpNOP_65C02), + OpDef.OpBVC_PCRel, // 0x50 + OpDef.OpEOR_DPIndIndexY, + OpDef.OpEOR_DPInd, + OpDef.GenerateUndoc(0x53, OpDef.OpNOP_65C02), + OpDef.GenerateUndoc(0x54, OpDef.OpLDD_DPIndexX), + OpDef.OpEOR_DPIndexX, + OpDef.OpLSR_DPIndexX, + OpDef.GenerateUndoc(0x57, OpDef.OpNOP_65C02), + OpDef.OpCLI_Implied, // 0x58 + OpDef.OpEOR_AbsIndexY, + OpDef.OpPHY_StackPush, + OpDef.GenerateUndoc(0x5b, OpDef.OpNOP_65C02), + OpDef.GenerateUndoc(0x5c, OpDef.OpLDD_Weird), + OpDef.OpEOR_AbsIndexX, + OpDef.OpLSR_AbsIndexX, + OpDef.GenerateUndoc(0x5f, OpDef.OpNOP_65C02), + OpDef.OpRTS_StackRTS, // 0x60 + OpDef.OpADC_DPIndexXInd, + OpDef.GenerateUndoc(0x62, OpDef.OpLDD_Imm), + OpDef.GenerateUndoc(0x63, OpDef.OpNOP_65C02), + OpDef.OpSTZ_DP, + OpDef.OpADC_DP, + OpDef.OpROR_DP, + OpDef.GenerateUndoc(0x67, OpDef.OpNOP_65C02), + OpDef.OpPLA_StackPull, // 0x68 + OpDef.OpADC_Imm, + OpDef.OpROR_Acc, + OpDef.GenerateUndoc(0x6b, OpDef.OpNOP_65C02), + OpDef.OpJMP_AbsInd, + OpDef.OpADC_Abs, + OpDef.OpROR_Abs, + OpDef.GenerateUndoc(0x6f, OpDef.OpNOP_65C02), + OpDef.OpBVS_PCRel, // 0x70 + OpDef.OpADC_DPIndIndexY, + OpDef.OpADC_DPInd, + OpDef.GenerateUndoc(0x73, OpDef.OpNOP_65C02), + OpDef.OpSTZ_DPIndexX, + OpDef.OpADC_DPIndexX, + OpDef.OpROR_DPIndexX, + OpDef.GenerateUndoc(0x77, OpDef.OpNOP_65C02), + OpDef.OpSEI_Implied, // 0x78 + OpDef.OpADC_AbsIndexY, + OpDef.OpPLY_StackPull, + OpDef.GenerateUndoc(0x7b, OpDef.OpNOP_65C02), + OpDef.OpJMP_AbsIndexXInd, + OpDef.OpADC_AbsIndexX, + OpDef.OpROR_AbsIndexX, + OpDef.GenerateUndoc(0x7f, OpDef.OpNOP_65C02), + OpDef.OpBRA_PCRel, // 0x80 + OpDef.OpSTA_DPIndexXInd, + OpDef.GenerateUndoc(0x82, OpDef.OpLDD_Imm), + OpDef.GenerateUndoc(0x83, OpDef.OpNOP_65C02), + OpDef.OpSTY_DP, + OpDef.OpSTA_DP, + OpDef.OpSTX_DP, + OpDef.GenerateUndoc(0x87, OpDef.OpNOP_65C02), + OpDef.OpDEY_Implied, // 0x88 + OpDef.OpBIT_Imm, + OpDef.OpTXA_Implied, + OpDef.GenerateUndoc(0x8b, OpDef.OpNOP_65C02), + OpDef.OpSTY_Abs, + OpDef.OpSTA_Abs, + OpDef.OpSTX_Abs, + OpDef.GenerateUndoc(0x8f, OpDef.OpNOP_65C02), + OpDef.OpBCC_PCRel, // 0x90 + OpDef.OpSTA_DPIndIndexY, + OpDef.OpSTA_DPInd, + OpDef.GenerateUndoc(0x93, OpDef.OpNOP_65C02), + OpDef.OpSTY_DPIndexX, + OpDef.OpSTA_DPIndexX, + OpDef.OpSTX_DPIndexY, + OpDef.GenerateUndoc(0x97, OpDef.OpNOP_65C02), + OpDef.OpTYA_Implied, // 0x98 + OpDef.OpSTA_AbsIndexY, + OpDef.OpTXS_Implied, + OpDef.GenerateUndoc(0x9b, OpDef.OpNOP_65C02), + OpDef.OpSTZ_Abs, + OpDef.OpSTA_AbsIndexX, + OpDef.OpSTZ_AbsIndexX, + OpDef.GenerateUndoc(0x9f, OpDef.OpNOP_65C02), + OpDef.OpLDY_Imm, // 0xa0 + OpDef.OpLDA_DPIndexXInd, + OpDef.OpLDX_Imm, + OpDef.GenerateUndoc(0xa3, OpDef.OpNOP_65C02), + OpDef.OpLDY_DP, + OpDef.OpLDA_DP, + OpDef.OpLDX_DP, + OpDef.GenerateUndoc(0xa7, OpDef.OpNOP_65C02), + OpDef.OpTAY_Implied, // 0xa8 + OpDef.OpLDA_Imm, + OpDef.OpTAX_Implied, + OpDef.GenerateUndoc(0xab, OpDef.OpNOP_65C02), + OpDef.OpLDY_Abs, + OpDef.OpLDA_Abs, + OpDef.OpLDX_Abs, + OpDef.GenerateUndoc(0xaf, OpDef.OpNOP_65C02), + OpDef.OpBCS_PCRel, // 0xb0 + OpDef.OpLDA_DPIndIndexY, + OpDef.OpLDA_DPInd, + OpDef.GenerateUndoc(0xb3, OpDef.OpNOP_65C02), + OpDef.OpLDY_DPIndexX, + OpDef.OpLDA_DPIndexX, + OpDef.OpLDX_DPIndexY, + OpDef.GenerateUndoc(0xb7, OpDef.OpNOP_65C02), + OpDef.OpCLV_Implied, // 0xb8 + OpDef.OpLDA_AbsIndexY, + OpDef.OpTSX_Implied, + OpDef.GenerateUndoc(0xbb, OpDef.OpNOP_65C02), + OpDef.OpLDY_AbsIndexX, + OpDef.OpLDA_AbsIndexX, + OpDef.OpLDX_AbsIndexY, + OpDef.GenerateUndoc(0xbf, OpDef.OpNOP_65C02), + OpDef.OpCPY_Imm, // 0xc0 + OpDef.OpCMP_DPIndexXInd, + OpDef.GenerateUndoc(0xc2, OpDef.OpLDD_Imm), + OpDef.GenerateUndoc(0xc3, OpDef.OpNOP_65C02), + OpDef.OpCPY_DP, + OpDef.OpCMP_DP, + OpDef.OpDEC_DP, + OpDef.GenerateUndoc(0xc7, OpDef.OpNOP_65C02), + OpDef.OpINY_Implied, // 0xc8 + OpDef.OpCMP_Imm, + OpDef.OpDEX_Implied, + OpDef.GenerateUndoc(0xcb, OpDef.OpNOP_65C02), + OpDef.OpCPY_Abs, + OpDef.OpCMP_Abs, + OpDef.OpDEC_Abs, + OpDef.GenerateUndoc(0xcf, OpDef.OpNOP_65C02), + OpDef.OpBNE_PCRel, // 0xd0 + OpDef.OpCMP_DPIndIndexY, + OpDef.OpCMP_DPInd, + OpDef.GenerateUndoc(0xd3, OpDef.OpNOP_65C02), + OpDef.GenerateUndoc(0xd4, OpDef.OpLDD_DPIndexX), + OpDef.OpCMP_DPIndexX, + OpDef.OpDEC_DPIndexX, + OpDef.GenerateUndoc(0xd7, OpDef.OpNOP_65C02), + OpDef.OpCLD_Implied, // 0xd8 + OpDef.OpCMP_AbsIndexY, + OpDef.OpPHX_StackPush, + OpDef.GenerateUndoc(0xdb, OpDef.OpNOP_65C02), + OpDef.GenerateUndoc(0xdc, OpDef.OpLDD_Absolute), + OpDef.OpCMP_AbsIndexX, + OpDef.OpDEC_AbsIndexX, + OpDef.GenerateUndoc(0xdf, OpDef.OpNOP_65C02), + OpDef.OpCPX_Imm, // 0xe0 + OpDef.OpSBC_DPIndexXInd, + OpDef.GenerateUndoc(0xe2, OpDef.OpLDD_Imm), + OpDef.GenerateUndoc(0xe3, OpDef.OpNOP_65C02), + OpDef.OpCPX_DP, + OpDef.OpSBC_DP, + OpDef.OpINC_DP, + OpDef.GenerateUndoc(0xe7, OpDef.OpNOP_65C02), + OpDef.OpINX_Implied, // 0xe8 + OpDef.OpSBC_Imm, + OpDef.OpNOP_Implied, + OpDef.GenerateUndoc(0xeb, OpDef.OpNOP_65C02), + OpDef.OpCPX_Abs, + OpDef.OpSBC_Abs, + OpDef.OpINC_Abs, + OpDef.GenerateUndoc(0xef, OpDef.OpNOP_65C02), + OpDef.OpBEQ_PCRel, // 0xf0 + OpDef.OpSBC_DPIndIndexY, + OpDef.OpSBC_DPInd, + OpDef.GenerateUndoc(0xf3, OpDef.OpNOP_65C02), + OpDef.GenerateUndoc(0xf4, OpDef.OpLDD_DPIndexX), + OpDef.OpSBC_DPIndexX, + OpDef.OpINC_DPIndexX, + OpDef.GenerateUndoc(0xf7, OpDef.OpNOP_65C02), + OpDef.OpSED_Implied, // 0xf8 + OpDef.OpSBC_AbsIndexY, + OpDef.OpPLX_StackPull, + OpDef.GenerateUndoc(0xfb, OpDef.OpNOP_65C02), + OpDef.GenerateUndoc(0xfc, OpDef.OpLDD_Absolute), + OpDef.OpSBC_AbsIndexX, + OpDef.OpINC_AbsIndexX, + OpDef.GenerateUndoc(0xff, OpDef.OpNOP_65C02), + } + }; + + + // WDC 65802 and 65816. No undocumented opcodes -- all 256 are used. + private static CpuDef Cpu65816 { get; } = new CpuDef("WDC W65C816S", (1 << 24) - 1, true) { + Type = CpuType.Cpu65816, + mOpDefs = new OpDef[] { + OpDef.OpBRK_StackInt, // 0x00 + OpDef.OpORA_DPIndexXInd, + OpDef.OpCOP_StackInt, + OpDef.OpORA_StackRel, + OpDef.OpTSB_DP, + OpDef.OpORA_DP, + OpDef.OpASL_DP, + OpDef.OpORA_DPIndLong, + OpDef.OpPHP_StackPush, // 0x08 + OpDef.OpORA_ImmLongA, + OpDef.OpASL_Acc, + OpDef.OpPHD_StackPush, + OpDef.OpTSB_Abs, + OpDef.OpORA_Abs, + OpDef.OpASL_Abs, + OpDef.OpORA_AbsLong, + OpDef.OpBPL_PCRel, // 0x10 + OpDef.OpORA_DPIndIndexY, + OpDef.OpORA_DPInd, + OpDef.OpORA_StackRelIndIndexY, + OpDef.OpTRB_DP, + OpDef.OpORA_DPIndexX, + OpDef.OpASL_DPIndexX, + OpDef.OpORA_DPIndIndexYLong, + OpDef.OpCLC_Implied, // 0x18 + OpDef.OpORA_AbsIndexY, + OpDef.OpINC_Acc, + OpDef.OpTCS_Implied, + OpDef.OpTRB_Abs, + OpDef.OpORA_AbsIndexX, + OpDef.OpASL_AbsIndexX, + OpDef.OpORA_AbsIndexXLong, + OpDef.OpJSR_Abs, // 0x20 + OpDef.OpAND_DPIndexXInd, + OpDef.OpJSR_AbsLong, + OpDef.OpAND_StackRel, + OpDef.OpBIT_DP, + OpDef.OpAND_DP, + OpDef.OpROL_DP, + OpDef.OpAND_DPIndLong, + OpDef.OpPLP_StackPull, // 0x28 + OpDef.OpAND_ImmLongA, + OpDef.OpROL_Acc, + OpDef.OpPLD_StackPull, + OpDef.OpBIT_Abs, + OpDef.OpAND_Abs, + OpDef.OpROL_Abs, + OpDef.OpAND_AbsLong, + OpDef.OpBMI_PCRel, // 0x30 + OpDef.OpAND_DPIndIndexY, + OpDef.OpAND_DPInd, + OpDef.OpAND_StackRelIndIndexY, + OpDef.OpBIT_DPIndexX, + OpDef.OpAND_DPIndexX, + OpDef.OpROL_DPIndexX, + OpDef.OpAND_DPIndIndexYLong, + OpDef.OpSEC_Implied, // 0x38 + OpDef.OpAND_AbsIndexY, + OpDef.OpDEC_Acc, + OpDef.OpTSC_Implied, + OpDef.OpBIT_AbsIndexX, + OpDef.OpAND_AbsIndexX, + OpDef.OpROL_AbsIndexX, + OpDef.OpAND_AbsIndexXLong, + OpDef.OpRTI_StackRTI, // 0x40 + OpDef.OpEOR_DPIndexXInd, + OpDef.OpWDM_WDM, + OpDef.OpEOR_StackRel, + OpDef.OpMVP_BlockMove, + OpDef.OpEOR_DP, + OpDef.OpLSR_DP, + OpDef.OpEOR_DPIndLong, + OpDef.OpPHA_StackPush, // 0x48 + OpDef.OpEOR_ImmLongA, + OpDef.OpLSR_Acc, + OpDef.OpPHK_StackPush, + OpDef.OpJMP_Abs, + OpDef.OpEOR_Abs, + OpDef.OpLSR_Abs, + OpDef.OpEOR_AbsLong, + OpDef.OpBVC_PCRel, // 0x50 + OpDef.OpEOR_DPIndIndexY, + OpDef.OpEOR_DPInd, + OpDef.OpEOR_StackRelIndIndexY, + OpDef.OpMVN_BlockMove, + OpDef.OpEOR_DPIndexX, + OpDef.OpLSR_DPIndexX, + OpDef.OpEOR_DPIndIndexYLong, + OpDef.OpCLI_Implied, // 0x58 + OpDef.OpEOR_AbsIndexY, + OpDef.OpPHY_StackPush, + OpDef.OpTCD_Implied, + OpDef.OpJMP_AbsLong, + OpDef.OpEOR_AbsIndexX, + OpDef.OpLSR_AbsIndexX, + OpDef.OpEOR_AbsIndexXLong, + OpDef.OpRTS_StackRTS, // 0x60 + OpDef.OpADC_DPIndexXInd, + OpDef.OpPER_StackPCRelLong, + OpDef.OpADC_StackRel, + OpDef.OpSTZ_DP, + OpDef.OpADC_DP, + OpDef.OpROR_DP, + OpDef.OpADC_DPIndLong, + OpDef.OpPLA_StackPull, // 0x68 + OpDef.OpADC_ImmLongA, + OpDef.OpROR_Acc, + OpDef.OpRTL_StackRTL, + OpDef.OpJMP_AbsInd, + OpDef.OpADC_Abs, + OpDef.OpROR_Abs, + OpDef.OpADC_AbsLong, + OpDef.OpBVS_PCRel, // 0x70 + OpDef.OpADC_DPIndIndexY, + OpDef.OpADC_DPInd, + OpDef.OpADC_StackRelIndIndexY, + OpDef.OpSTZ_DPIndexX, + OpDef.OpADC_DPIndexX, + OpDef.OpROR_DPIndexX, + OpDef.OpADC_DPIndIndexYLong, + OpDef.OpSEI_Implied, // 0x78 + OpDef.OpADC_AbsIndexY, + OpDef.OpPLY_StackPull, + OpDef.OpTDC_Implied, + OpDef.OpJMP_AbsIndexXInd, + OpDef.OpADC_AbsIndexX, + OpDef.OpROR_AbsIndexX, + OpDef.OpADC_AbsIndexXLong, + OpDef.OpBRA_PCRel, // 0x80 + OpDef.OpSTA_DPIndexXInd, + OpDef.OpBRL_PCRelLong, + OpDef.OpSTA_StackRel, + OpDef.OpSTY_DP, + OpDef.OpSTA_DP, + OpDef.OpSTX_DP, + OpDef.OpSTA_DPIndLong, + OpDef.OpDEY_Implied, // 0x88 + OpDef.OpBIT_ImmLongA, + OpDef.OpTXA_Implied, + OpDef.OpPHB_StackPush, + OpDef.OpSTY_Abs, + OpDef.OpSTA_Abs, + OpDef.OpSTX_Abs, + OpDef.OpSTA_AbsLong, + OpDef.OpBCC_PCRel, // 0x90 + OpDef.OpSTA_DPIndIndexY, + OpDef.OpSTA_DPInd, + OpDef.OpSTA_StackRelIndIndexY, + OpDef.OpSTY_DPIndexX, + OpDef.OpSTA_DPIndexX, + OpDef.OpSTX_DPIndexY, + OpDef.OpSTA_DPIndIndexYLong, + OpDef.OpTYA_Implied, // 0x98 + OpDef.OpSTA_AbsIndexY, + OpDef.OpTXS_Implied, + OpDef.OpTXY_Implied, + OpDef.OpSTZ_Abs, + OpDef.OpSTA_AbsIndexX, + OpDef.OpSTZ_AbsIndexX, + OpDef.OpSTA_AbsIndexXLong, + OpDef.OpLDY_ImmLongXY, // 0xa0 + OpDef.OpLDA_DPIndexXInd, + OpDef.OpLDX_ImmLongXY, + OpDef.OpLDA_StackRel, + OpDef.OpLDY_DP, + OpDef.OpLDA_DP, + OpDef.OpLDX_DP, + OpDef.OpLDA_DPIndLong, + OpDef.OpTAY_Implied, // 0xa8 + OpDef.OpLDA_ImmLongA, + OpDef.OpTAX_Implied, + OpDef.OpPLB_StackPull, + OpDef.OpLDY_Abs, + OpDef.OpLDA_Abs, + OpDef.OpLDX_Abs, + OpDef.OpLDA_AbsLong, + OpDef.OpBCS_PCRel, // 0xb0 + OpDef.OpLDA_DPIndIndexY, + OpDef.OpLDA_DPInd, + OpDef.OpLDA_StackRelIndIndexY, + OpDef.OpLDY_DPIndexX, + OpDef.OpLDA_DPIndexX, + OpDef.OpLDX_DPIndexY, + OpDef.OpLDA_DPIndIndexYLong, + OpDef.OpCLV_Implied, // 0xb8 + OpDef.OpLDA_AbsIndexY, + OpDef.OpTSX_Implied, + OpDef.OpTYX_Implied, + OpDef.OpLDY_AbsIndexX, + OpDef.OpLDA_AbsIndexX, + OpDef.OpLDX_AbsIndexY, + OpDef.OpLDA_AbsIndexXLong, + OpDef.OpCPY_ImmLongXY, // 0xc0 + OpDef.OpCMP_DPIndexXInd, + OpDef.OpREP_Imm, + OpDef.OpCMP_StackRel, + OpDef.OpCPY_DP, + OpDef.OpCMP_DP, + OpDef.OpDEC_DP, + OpDef.OpCMP_DPIndLong, + OpDef.OpINY_Implied, // 0xc8 + OpDef.OpCMP_ImmLongA, + OpDef.OpDEX_Implied, + OpDef.OpWAI_Implied, + OpDef.OpCPY_Abs, + OpDef.OpCMP_Abs, + OpDef.OpDEC_Abs, + OpDef.OpCMP_AbsLong, + OpDef.OpBNE_PCRel, // 0xd0 + OpDef.OpCMP_DPIndIndexY, + OpDef.OpCMP_DPInd, + OpDef.OpCMP_StackRelIndIndexY, + OpDef.OpPEI_StackDPInd, + OpDef.OpCMP_DPIndexX, + OpDef.OpDEC_DPIndexX, + OpDef.OpCMP_DPIndIndexYLong, + OpDef.OpCLD_Implied, // 0xd8 + OpDef.OpCMP_AbsIndexY, + OpDef.OpPHX_StackPush, + OpDef.OpSTP_Implied, + OpDef.OpJMP_AbsIndLong, + OpDef.OpCMP_AbsIndexX, + OpDef.OpDEC_AbsIndexX, + OpDef.OpCMP_AbsIndexXLong, + OpDef.OpCPX_ImmLongXY, // 0xe0 + OpDef.OpSBC_DPIndexXInd, + OpDef.OpSEP_Imm, + OpDef.OpSBC_StackRel, + OpDef.OpCPX_DP, + OpDef.OpSBC_DP, + OpDef.OpINC_DP, + OpDef.OpSBC_DPIndLong, + OpDef.OpINX_Implied, // 0xe8 + OpDef.OpSBC_ImmLongA, + OpDef.OpNOP_Implied, + OpDef.OpXBA_Implied, + OpDef.OpCPX_Abs, + OpDef.OpSBC_Abs, + OpDef.OpINC_Abs, + OpDef.OpSBC_AbsLong, + OpDef.OpBEQ_PCRel, // 0xf0 + OpDef.OpSBC_DPIndIndexY, + OpDef.OpSBC_DPInd, + OpDef.OpSBC_StackRelIndIndexY, + OpDef.OpPEA_StackAbs, + OpDef.OpSBC_DPIndexX, + OpDef.OpINC_DPIndexX, + OpDef.OpSBC_DPIndIndexYLong, + OpDef.OpSED_Implied, // 0xf8 + OpDef.OpSBC_AbsIndexY, + OpDef.OpPLX_StackPull, + OpDef.OpXCE_Implied, + OpDef.OpJSR_AbsIndexXInd, + OpDef.OpSBC_AbsIndexX, + OpDef.OpINC_AbsIndexX, + OpDef.OpSBC_AbsIndexXLong + } + }; + } +} diff --git a/Asm65/Formatter.cs b/Asm65/Formatter.cs new file mode 100644 index 0000000..8f9398b --- /dev/null +++ b/Asm65/Formatter.cs @@ -0,0 +1,819 @@ +/* + * Copyright 2018 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.Text; + +using AddressMode = Asm65.OpDef.AddressMode; + +namespace Asm65 { + /// + /// Functions used for formatting bits of 65xx code into human-readable form. + /// + /// There are a variety of ways to format a given thing, based on personal preference + /// (e.g. whether opcodes are upper- or lower-case) and assembler syntax requirements. + /// + /// The functions in this class serve two purposes: (1) produce consistent output + /// throughout the program; (2) cache format strings and other components to reduce + /// string manipulation overhead. Note the caching is per-Formatter, so it's best to + /// create just one and share it around. + /// + /// The configuration of a Formatter may not be altered once created. This is important + /// in situations where we compute output size in one pass and generate it in another, + /// because it guarantees that a given Formatter object will produce the same number of + /// lines of output. + /// + /// NOTE: if the CpuDef changes, the cached values in the Formatter will become invalid. + /// Discard the Formatter and create a new one. (This could be fixed by keying off of + /// the OpDef instead of OpDef.Opcode, but that's less convenient.) + /// + public class Formatter { + /// + /// Various format configuration options. Fill one of these out and pass it to + /// the Formatter constructor. + /// + public struct FormatConfig { + // alpha case for some case-insensitive items + public bool mUpperHexDigits; // display hex values in upper case? + public bool mUpperOpcodes; // display opcodes in upper case? + public bool mUpperPseudoOpcodes; // display pseudo-opcodes in upper case? + public bool mUpperOperandA; // display acc operand in upper case? + public bool mUpperOperandS; // display stack operand in upper case? + public bool mUpperOperandXY; // display index register operand in upper case? + public bool mAddSpaceLongComment; // insert space after delimiter for long comments? + + // functional changes to assembly output + public bool mSuppressHexNotation; // omit '$' before hex digits + + public bool mAllowHighAsciiCharConst; // can we do high-ASCII character constants? + // (this might need to be generalized) + + public string mForceAbsOpcodeSuffix; // these may be null or empty + public string mForceAbsOperandPrefix; + public string mForceLongOpcodeSuffix; + public string mForceLongOperandPrefix; + + public string mEndOfLineCommentDelimiter; // usually ';' + public string mFullLineCommentDelimiterBase; // usually ';' or '*', WITHOUT extra space + public string mBoxLineCommentDelimiter; // usually blank or ';' + + // miscellaneous + public bool mHexDumpAsciiOnly; // disallow non-ASCII chars in hex dumps? + + public enum CharConvMode { Unknown = 0, PlainAscii, HighLowAscii }; + public CharConvMode mHexDumpCharConvMode; // character conversion mode for dumps + + public enum ExpressionMode { Unknown = 0, Simple, Merlin }; + public ExpressionMode mExpressionMode; // symbol rendering mode + + // Deserialization helper. + public static ExpressionMode ParseExpressionMode(string str) { + ExpressionMode em = ExpressionMode.Simple; + if (!string.IsNullOrEmpty(str)) { + if (Enum.TryParse(str, out ExpressionMode pem)) { + em = pem; + } + } + return em; + } + } + + private static readonly char[] sHexCharsLower = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + private static readonly char[] sHexCharsUpper = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + + /// + /// Formatter configuration options. Fixed at construction time. + /// + private FormatConfig mFormatConfig; + + /// + /// Get a copy of the format config. + /// + public FormatConfig Config { get { return mFormatConfig; } } + + // Bits and pieces. + char mHexFmtChar; + string mHexPrefix; + char mAccChar; + char mXregChar; + char mYregChar; + char mSregChar; + + // Format string for offsets. + private string mOffset20Format; + + // Format strings for addresses. + private string mAddrFormatNoBank; + private string mAddrFormatWithBank; + + // Generated opcode strings. The index is the bitwise OR of the opcode value and + // the disambiguation value. In most cases this just helps us avoid calling + // ToUpper incessantly. + private Dictionary mOpcodeStrings = new Dictionary(); + + // Generated pseudo-opcode strings. + private Dictionary mPseudoOpStrings = new Dictionary(); + + // Generated format strings for operands. The index is the bitwise OR of the + // address mode and the disambiguation value. + private Dictionary mOperandFormats = new Dictionary(); + + // Generated format strings for bytes. + private const int MAX_BYTE_DUMP = 4; + private string[] mByteDumpFormats = new string[MAX_BYTE_DUMP]; + + // Generated format strings for hex values. + private string[] mHexValueFormats = new string[4]; + + private string mFullLineCommentDelimiterPlus; + + // Buffer to use when generating hex dump lines. + private char[] mHexDumpBuffer; + + + /// + /// A 16-character array with 0-9a-f, for hex conversions. The letters will be + /// upper or lower case, per the format config. + /// + public char[] HexDigits { + get { + return mFormatConfig.mUpperHexDigits ? sHexCharsUpper : sHexCharsLower; + } + } + + /// + /// String to put between the operand and the end-of-line comment. + /// + public string EndOfLineCommentDelimiter { + get { return mFormatConfig.mEndOfLineCommentDelimiter; } + } + + /// + /// String to put at the start of a line with a full-line comment. + /// + public string FullLineCommentDelimiter { + get { return mFullLineCommentDelimiterPlus; } + } + + /// + /// String to put at the start of a line that has a box comment. This is usually + /// blank, as it's only needed if the assembler doesn't recognize the box character + /// as a comment. + /// + public string BoxLineCommentDelimiter { + get { return mFormatConfig.mBoxLineCommentDelimiter; } + } + + /// + /// When formatting a symbol with an offset, if this flag is set, generate code that + /// assumes the assembler applies the adjustment, then shifts the result. If not, + /// assume the assembler shifts the operand before applying the adjustment. + /// + public FormatConfig.ExpressionMode ExpressionMode { + get { return mFormatConfig.mExpressionMode; } + } + + + public Formatter(FormatConfig config) { + mFormatConfig = config; + if (mFormatConfig.mEndOfLineCommentDelimiter == null) { + mFormatConfig.mEndOfLineCommentDelimiter = string.Empty; + } + if (mFormatConfig.mFullLineCommentDelimiterBase == null) { + mFormatConfig.mFullLineCommentDelimiterBase = string.Empty; + } + if (mFormatConfig.mBoxLineCommentDelimiter == null) { + mFormatConfig.mBoxLineCommentDelimiter = string.Empty; + } + + if (mFormatConfig.mAddSpaceLongComment) { + mFullLineCommentDelimiterPlus = mFormatConfig.mFullLineCommentDelimiterBase + " "; + } else { + mFullLineCommentDelimiterPlus = mFormatConfig.mFullLineCommentDelimiterBase; + } + + Reset(); + + // Prep the static parts of the hex dump buffer. + mHexDumpBuffer = new char[73]; + for (int i = 0; i < mHexDumpBuffer.Length; i++) { + mHexDumpBuffer[i] = ' '; + } + mHexDumpBuffer[6] = ':'; + } + + /// + /// Resets the pieces we use to build format strings. + /// + private void Reset() { + // Clear old data. (No longer needed.) + //mAddrFormatNoBank = mAddrFormatWithBank = null; + //mOffset20Format = null; + //mOpcodeStrings.Clear(); + //mPseudoOpStrings.Clear(); + //mOperandFormats.Clear(); + //for (int i = 0; i < MAX_BYTE_DUMP; i++) { + // mByteDumpFormats[i] = null; + //} + + if (mFormatConfig.mUpperHexDigits) { + mHexFmtChar = 'X'; + } else { + mHexFmtChar = 'x'; + } + if (mFormatConfig.mSuppressHexNotation) { + mHexPrefix = ""; + } else { + mHexPrefix = "$"; + } + if (mFormatConfig.mUpperOperandA) { + mAccChar = 'A'; + } else { + mAccChar = 'a'; + } + if (mFormatConfig.mUpperOperandXY) { + mXregChar = 'X'; + mYregChar = 'Y'; + } else { + mXregChar = 'x'; + mYregChar = 'y'; + } + if (mFormatConfig.mUpperOperandS) { + mSregChar = 'S'; + } else { + mSregChar = 's'; + } + } + + /// + /// Formats a 24-bit offset value as hex. + /// + /// Offset to format. + /// Formatted string. + public string FormatOffset24(int offset) { + if (string.IsNullOrEmpty(mOffset20Format)) { + mOffset20Format = "+{0:" + mHexFmtChar + "6}"; + } + return string.Format(mOffset20Format, offset & 0x0fffff); + } + + /// + /// Formats a value in hexadecimal. The width is padded with zeroes to make the + /// length even (so it'll be $00, $0100, $010000, etc.) If minDigits is nonzero, + /// additional zeroes may be added. + /// + /// Value to format, up to 32 bits. + /// Minimum width, in printed digits (e.g. 4 is "0000"). + /// Formatted string. + public string FormatHexValue(int value, int minDigits) { + int width = minDigits > 2 ? minDigits : 2; + if (width < 8 && value > 0xffffff) { + width = 8; + } else if (width < 6 && value > 0xffff) { + width = 6; + } else if (width < 4 && value > 0xff) { + width = 4; + } + int index = (width / 2) - 1; + if (mHexValueFormats[index] == null) { + mHexValueFormats[index] = mHexFmtChar + width.ToString(); + } + return mHexPrefix + value.ToString(mHexValueFormats[index]); + } + + /// + /// Format a value as a number in the specified base. + /// + /// Value to format. + /// Numeric base (2, 10, or 16). + /// Formatted string. + public string FormatValueInBase(int value, int numBase) { + switch (numBase) { + case 2: + return FormatBinaryValue(value, 8); + case 10: + return FormatDecimalValue(value); + case 16: + return FormatHexValue(value, 2); + default: + Debug.Assert(false); + return "???"; + } + } + + /// + /// Formats a value as decimal. + /// + /// Value to convert. + /// Formatted string. + public string FormatDecimalValue(int value) { + return value.ToString(); + } + + /// + /// Formats a value in binary, padding with zeroes so the length is a multiple of 8. + /// + /// Value to convert. + /// Minimum width, in printed digits. Will be rounded up to + /// a multiple of 8. + /// Formatted string. + public string FormatBinaryValue(int value, int minDigits) { + string binaryStr = Convert.ToString(value, 2); + int desiredWidth = ((binaryStr.Length + 7) / 8) * 8; + if (desiredWidth < minDigits) { + desiredWidth = ((minDigits + 7) / 8) * 8; + } + return '%' + binaryStr.PadLeft(desiredWidth, '0'); + } + + /// + /// Formats a value as an ASCII character, surrounded by quotes. Must be a valid + /// low- or high-ASCII value. + /// + /// Value to format. + /// Formatted string. + private string FormatAsciiChar(int value) { + Debug.Assert(CommonUtil.TextUtil.IsHiLoAscii(value)); + char ch = (char)(value & 0x7f); + bool hiAscii = ((value & 0x80) != 0); + + StringBuilder sb; + int method = -1; + switch (method) { + case 0: + default: + // Convention is from Merlin: single quote for low-ASCII, double-quote + // for high-ASCII. Add a backslash if we're quoting the delimiter. + sb = new StringBuilder(4); + char quoteChar = ((value & 0x80) == 0) ? '\'' : '"'; + sb.Append(quoteChar); + if (quoteChar == ch) { + sb.Append('\\'); + } + sb.Append(ch); + sb.Append(quoteChar); + break; + case 1: + // Convention is similar to Merlin, but with curly-quotes so it doesn't + // look weird when quoting ' or ". + sb = new StringBuilder(3); + sb.Append(hiAscii ? '\u201c' : '\u2018'); + sb.Append(ch); + sb.Append(hiAscii ? '\u201d' : '\u2019'); + break; + case 2: + // Always use apostrophe, but follow it with an up-arrow to indicate + // that it's high-ASCII. + sb = new StringBuilder(4); + sb.Append("'"); + sb.Append(ch); + sb.Append("'"); + if (hiAscii) { + sb.Append('\u21e1'); // UPWARDS DASHED ARROW + //sb.Append('\u2912'); // UPWARDS ARROW TO BAR + } + break; + } + return sb.ToString(); + } + + /// + /// Formats a value as an ASCII character, if possible, or as a hex value. + /// + /// Value to format. + /// Formatted string. + public string FormatAsciiOrHex(int value) { + bool hiAscii = ((value & 0x80) != 0); + if (hiAscii && !mFormatConfig.mAllowHighAsciiCharConst) { + return FormatHexValue(value, 2); + } else if (CommonUtil.TextUtil.IsHiLoAscii(value)) { + return FormatAsciiChar(value); + } else { + return FormatHexValue(value, 2); + } + } + + /// + /// Formats a 16- or 24-bit address value. This is intended for the left column + /// of something (hex dump, code listing), not as an operand. + /// + /// Address to format. + /// Set to true for CPUs with 24-bit address spaces. + /// Formatted string. + public string FormatAddress(int address, bool showBank) { + if (mAddrFormatNoBank == null) { + mAddrFormatNoBank = "{0:" + mHexFmtChar + "4}"; + mAddrFormatWithBank = "{0:" + mHexFmtChar + "2}/{1:" + mHexFmtChar + "4}"; + } + if (showBank) { + return string.Format(mAddrFormatWithBank, address >> 16, address & 0xffff); + } else { + return string.Format(mAddrFormatNoBank, address & 0xffff); + } + } + + /// + /// Formats an adjustment, as "+decimal" or "-decimal". If no adjustment + /// is required, an empty string is returned. + /// + /// Adjustment value. + /// Formatted string. + public string FormatAdjustment(int adjValue) { + if (adjValue == 0) { + return string.Empty; + } + // This formats in decimal with a leading '+' or '-'. To avoid adding a plus + // on zero, we'd use "+#;-#;0", but we took care of the zero case above. + return adjValue.ToString("+0;-#"); + } + + /// + /// Formats the instruction opcode mnemonic, and caches the result. + /// + /// It may be necessary to modify the mnemonic for some assemblers, e.g. LDA from a + /// 24-bit address might need to be LDAL, even if the high byte is nonzero. + /// + /// Opcode to format + /// Width disambiguation specifier. + /// Formatted string. + public string FormatOpcode(OpDef op, OpDef.WidthDisambiguation wdis) { + // TODO(someday): using op.Opcode as the key is a bad idea, as the operation may + // not be the same on different CPUs. We currently rely on the caller to discard + // the Formatter when the CPU definition changes. We'd be better off keying off of + // the OpDef object and factoring wdis in some other way. + int key = op.Opcode | ((int)wdis << 8); + if (!mOpcodeStrings.TryGetValue(key, out string opcodeStr)) { + // Not found, generate value. + opcodeStr = FormatMnemonic(op.Mnemonic, wdis); + + // Memoize. + mOpcodeStrings[key] = opcodeStr; + } + return opcodeStr; + } + + /// + /// Formats the string as an opcode mnemonic. + /// + /// It may be necessary to modify the mnemonic for some assemblers, e.g. LDA from a + /// 24-bit address might need to be LDAL, even if the high byte is nonzero. + /// + /// Instruction mnemonic string. + /// Width disambiguation specifier. + /// + public string FormatMnemonic(string mnemonic, OpDef.WidthDisambiguation wdis) { + string opcodeStr = mnemonic; + if (wdis == OpDef.WidthDisambiguation.ForceAbs) { + if (!string.IsNullOrEmpty(mFormatConfig.mForceAbsOpcodeSuffix)) { + opcodeStr += mFormatConfig.mForceAbsOpcodeSuffix; + } + } else if (wdis == OpDef.WidthDisambiguation.ForceLong || + wdis == OpDef.WidthDisambiguation.ForceLongMaybe) { + if (!string.IsNullOrEmpty(mFormatConfig.mForceLongOpcodeSuffix)) { + opcodeStr += mFormatConfig.mForceLongOpcodeSuffix; + } + } else { + Debug.Assert(wdis == OpDef.WidthDisambiguation.None); + } + if (mFormatConfig.mUpperOpcodes) { + opcodeStr = opcodeStr.ToUpperInvariant(); + } + return opcodeStr; + } + + /// + /// Generates an operand format. + /// + /// Addressing mode. + /// Format string. + private string GenerateOperandFormat(OpDef.AddressMode addrMode, + OpDef.WidthDisambiguation wdis) { + string fmt; + string wdisStr = string.Empty; + + if (wdis == OpDef.WidthDisambiguation.ForceAbs) { + if (!string.IsNullOrEmpty(mFormatConfig.mForceAbsOperandPrefix)) { + wdisStr = mFormatConfig.mForceAbsOperandPrefix; + } + } else if (wdis == OpDef.WidthDisambiguation.ForceLong) { + if (!string.IsNullOrEmpty(mFormatConfig.mForceLongOperandPrefix)) { + wdisStr = mFormatConfig.mForceLongOperandPrefix; + } + } else if (wdis == OpDef.WidthDisambiguation.ForceLongMaybe) { + // Don't add a width disambiguator to an operand that is unambiguously long. + } else { + Debug.Assert(wdis == OpDef.WidthDisambiguation.None); + } + + switch (addrMode) { + case AddressMode.Abs: + case AddressMode.AbsLong: + case AddressMode.BlockMove: + case AddressMode.StackAbs: + case AddressMode.DP: + case AddressMode.PCRel: + case AddressMode.PCRelLong: // BRL + case AddressMode.StackInt: // BRK/COP + case AddressMode.StackPCRelLong: // PER + case AddressMode.WDM: + fmt = wdisStr + "{0}"; + break; + case AddressMode.AbsIndexX: + case AddressMode.AbsIndexXLong: + case AddressMode.DPIndexX: + fmt = wdisStr + "{0}," + mXregChar; + break; + case AddressMode.DPIndexY: + case AddressMode.AbsIndexY: + fmt = wdisStr + "{0}," + mYregChar; + break; + case AddressMode.AbsIndexXInd: + case AddressMode.DPIndexXInd: + fmt = wdisStr + "({0}," + mXregChar + ")"; + break; + case AddressMode.AbsInd: + case AddressMode.DPInd: + case AddressMode.StackDPInd: // PEI + fmt = "({0})"; + break; + case AddressMode.AbsIndLong: + case AddressMode.DPIndLong: + // IIgs monitor uses "()" for AbsIndLong, E&L says "[]". Assemblers + // seem to expect the latter. + fmt = "[{0}]"; + break; + case AddressMode.Acc: + fmt = "" + mAccChar; + break; + case AddressMode.DPIndIndexY: + fmt = "({0})," + mYregChar; + break; + case AddressMode.DPIndIndexYLong: + fmt = "[{0}]," + mYregChar; + break; + case AddressMode.Imm: + case AddressMode.ImmLongA: + case AddressMode.ImmLongXY: + fmt = "#{0}"; + break; + case AddressMode.Implied: + case AddressMode.StackPull: + case AddressMode.StackPush: + case AddressMode.StackRTI: + case AddressMode.StackRTL: + case AddressMode.StackRTS: + fmt = ""; + break; + case AddressMode.StackRel: + fmt = "{0}," + mSregChar; + break; + case AddressMode.StackRelIndIndexY: + fmt = "({0}," + mSregChar + ")," + mYregChar; + break; + + case AddressMode.Unknown: + default: + Debug.Assert(false); + fmt = "???"; + break; + } + + return fmt; + } + + /// + /// Formats a pseudo-opcode. + /// + /// Pseudo-op string to format. + /// Formatted string. + public string FormatPseudoOp(string opstr) { + if (!mPseudoOpStrings.TryGetValue(opstr, out string result)) { + if (mFormatConfig.mUpperPseudoOpcodes) { + result = mPseudoOpStrings[opstr] = opstr.ToUpperInvariant(); + } else { + result = mPseudoOpStrings[opstr] = opstr; + } + } + return result; + } + + /// + /// Formats the instruction operand. + /// + /// Opcode definition (needed for address mode). + /// Label or numeric operand value. + /// Width disambiguation value. + /// Formatted string. + public string FormatOperand(OpDef op, string contents, OpDef.WidthDisambiguation wdis) { + Debug.Assert(((int)op.AddrMode & 0xff) == (int) op.AddrMode); + int key = (int) op.AddrMode | ((int)wdis << 8); + + if (!mOperandFormats.TryGetValue(key, out string format)) { + format = mOperandFormats[key] = GenerateOperandFormat(op.AddrMode, wdis); + } + return string.Format(format, contents); + } + + /// + /// Generates a format string for N hex bytes. + /// + /// Number of bytes to handle in the format. + private void GenerateByteFormat(int len) { + Debug.Assert(len <= MAX_BYTE_DUMP); + + StringBuilder sb = new StringBuilder(len * 6); + for (int i = 0; i < len; i++) { + //. e.g. "{0:x2}" + sb.Append("{" + i + ":" + mHexFmtChar + "2}"); + } + mByteDumpFormats[len - 1] = sb.ToString(); + } + + /// + /// Formats 1-4 bytes as hex values. + /// + /// Data source. + /// Start offset within data array. + /// Number of bytes to print. Fewer than this many may + /// actually appear. + /// Formatted data string. + public string FormatBytes(byte[] data, int offset, int length) { + Debug.Assert(length > 0); + int printLen = length < MAX_BYTE_DUMP ? length : MAX_BYTE_DUMP; + if (string.IsNullOrEmpty(mByteDumpFormats[printLen - 1])) { + GenerateByteFormat(printLen); + } + string format = mByteDumpFormats[printLen - 1]; + string result; + + // The alternative is to allocate a temporary object[] and copy the integers + // into it, which requires boxing. We know we're only printing 1-4 bytes, so + // it's easier to just handle each case individually. + switch (printLen) { + case 1: + result = string.Format(format, data[offset]); + break; + case 2: + result = string.Format(format, data[offset], data[offset + 1]); + break; + case 3: + result = string.Format(format, + data[offset], data[offset + 1], data[offset + 2]); + break; + case 4: + result = string.Format(format, + data[offset], data[offset + 1], data[offset + 2], data[offset + 3]); + break; + default: + result = "INTERNAL ERROR"; + break; + } + if (length > printLen) { + result += "..."; + } + + return result; + } + + /// + /// Formats an end-of-line comment, prepending an end-of-line comment delimiter. + /// + /// Comment string; may be empty. + /// Formatted string. + public string FormatEolComment(string comment) { + if (string.IsNullOrEmpty(comment) || + string.IsNullOrEmpty(mFormatConfig.mEndOfLineCommentDelimiter)) { + return comment; + } else { + return mFormatConfig.mEndOfLineCommentDelimiter + comment; + } + } + + /// + /// Formats a collection of bytes as a dense hex string. + /// + /// Data source. + /// Start offset within data array. + /// Number of bytes to print. + /// Formatted data string. + public string FormatDenseHex(byte[] data, int offset, int length) { + char[] hexChars = mFormatConfig.mUpperHexDigits ? sHexCharsUpper : sHexCharsLower; + char[] text = new char[length * 2]; + for (int i = 0; i < length; i++) { + byte val = data[offset + i]; + text[i * 2] = hexChars[val >> 4]; + text[i * 2 + 1] = hexChars[val & 0x0f]; + } + return new string(text); + } + + /// + /// Formats up to 16 bytes of data into a single line hex dump, in this format: + /// 012345: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 0123456789abcdef + /// + /// Reference to data. + /// Start offset. + /// Formatted string. + public string FormatHexDump(byte[] data, int offset) { + FormatHexDumpCommon(data, offset); + // this is the only allocation + return new string(mHexDumpBuffer); + } + + /// + /// Formats up to 16 bytes of data into a single line hex dump. The output is + /// appended to the StringBuilder. + /// + /// Reference to data. + /// Start offset. + /// StringBuilder that receives output. + public void FormatHexDump(byte[] data, int offset, StringBuilder sb) { + FormatHexDumpCommon(data, offset); + sb.Append(mHexDumpBuffer); + } + + /// + /// Formats up to 16 bytes of data into mHexDumpBuffer. + /// + private void FormatHexDumpCommon(byte[] data, int offset) { + Debug.Assert(offset >= 0 && offset < data.Length); + Debug.Assert(data.Length < (1 << 24)); + const int dataCol = 8; + const int asciiCol = 57; + + char[] hexChars = mFormatConfig.mUpperHexDigits ? sHexCharsUpper : sHexCharsLower; + char[] outBuf = mHexDumpBuffer; + + // address field + int addr = offset; + for (int i = 5; i >= 0; i--) { + outBuf[i] = hexChars[addr & 0x0f]; + addr >>= 4; + } + + // hex digits and characters + int length = Math.Min(16, data.Length - offset); + int index; + for (index = 0; index < length; index++) { + byte val = data[offset + index]; + outBuf[dataCol + index * 3] = hexChars[val >> 4]; + outBuf[dataCol + index * 3 + 1] = hexChars[val & 0x0f]; + outBuf[asciiCol + index] = CharConv(val); + } + + // for partial line, clear out previous contents + for (; index < 16; index++) { + outBuf[dataCol + index * 3] = + outBuf[dataCol + index * 3 + 1] = + outBuf[asciiCol + index] = ' '; + } + } + + /// + /// Converts a byte into printable form according to the current hex dump + /// character conversion mode. + /// + /// Value to convert. + /// Printable character. + private char CharConv(byte val) { + char ch; + if (mFormatConfig.mHexDumpCharConvMode == FormatConfig.CharConvMode.HighLowAscii) { + ch = (char)(val & 0x7f); + } else { + ch = (char)val; + } + if (CommonUtil.TextUtil.IsPrintableAscii(ch)) { + return ch; + } else if (mFormatConfig.mHexDumpAsciiOnly) { + return '.'; + } else { + // These values makes the hex dump ListView freak out. + //if (ch < 0x20) { + // return (char)(ch + '\u2400'); // Unicode "control pictures" block + //} + //return '\ufffd'; // Unicode "replacement character" + + //return '\u00bf'; // INVERTED QUESTION MARK + return '\u00b7'; // MIDDLE DOT + } + } + } +} diff --git a/Asm65/Helper.cs b/Asm65/Helper.cs new file mode 100644 index 0000000..e4954c5 --- /dev/null +++ b/Asm65/Helper.cs @@ -0,0 +1,56 @@ +/* + * Copyright 2018 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.Diagnostics; + +namespace Asm65 { + /// + /// Small utility functions. + /// + public static class Helper { + /// + /// Computes the target address of an 8-bit relative branch instruction. + /// + /// 24-bit address of branch instruction opcode. + /// Branch operand. + /// Target address. + public static int RelOffset8(int addr, sbyte branchOffset) { + // Branch is relative to the start of the following instruction, so add 2. + // Branches wrap around the current bank (in both directions), and the target + // is in the same bank as source addr. + Debug.Assert(addr >= 0 && addr <= 0xffffff); + int target = (addr + 2 + branchOffset) & 0xffff; + target |= addr & 0x7fff0000; + return target; + } + + /// + /// Computes the target address of a 16-bit relative branch instruction. + /// + /// 24-bit address of branch instruction opcode. + /// Branch operand. + /// Target address. + public static int RelOffset16(int addr, short branchOffset) { + // Branch is relative to the start of the following instruction, so add 3. + // Branches wrap around the current bank (in both directions), and the target + // is in the same bank as source addr. + Debug.Assert(addr >= 0 && addr <= 0xffffff); + int target = (addr + 3 + branchOffset) & 0xffff; + target |= addr & 0x7fff0000; + return target; + } + } +} diff --git a/Asm65/Label.cs b/Asm65/Label.cs new file mode 100644 index 0000000..3bc833c --- /dev/null +++ b/Asm65/Label.cs @@ -0,0 +1,78 @@ +/* + * Copyright 2018 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.Text.RegularExpressions; + +namespace Asm65 { + /// + /// Utility classes for working with labels. + /// + /// The decision of whether to treat labels as case-sensitive or case-insensitive is + /// encapsulated here. All code should be case-preserving, but the comparison method + /// and "normal form" are defined here. + /// + public class Label { + // Arbitrary choice for SourceGen. Different assemblers have different limits. + public const int MAX_LABEL_LEN = 32; + + public const bool LABELS_CASE_SENSITIVE = true; + + /// + /// String comparer to use when comparing labels. + /// + /// We may want case-insensitive string compares, and we want the "invariant culture" + /// version for consistent results across users in multiple locales. (The labels are + /// expected to be ASCII strings, so the latter isn't crucial unless we change the + /// allowed set.) + /// + public static readonly StringComparer LABEL_COMPARER = LABELS_CASE_SENSITIVE ? + StringComparer.InvariantCulture : + StringComparer.InvariantCultureIgnoreCase; + + /// + /// Regex pattern for a valid label. + /// + /// ASCII-only, starts with letter or underscore, followed by at least + /// one alphanumeric or underscore. Some assemblers may allow single-letter + /// labels, but I don't want to risk confusion with A/S/X/Y. So either we + /// reserve those, or we just mandate a two-character minimum. + /// + private static string sValidLabelPattern = @"^[a-zA-Z_][a-zA-Z0-9_]+$"; + private static Regex sValidLabelCharRegex = new Regex(sValidLabelPattern); + + /// + /// Validates a label, confirming that it is correctly formed. + /// + /// Label to validate. + /// True if the label is correctly formed. + public static bool ValidateLabel(string label) { + if (label.Length > MAX_LABEL_LEN) { + return false; + } + MatchCollection matches = sValidLabelCharRegex.Matches(label); + return matches.Count == 1; + } + + /// + /// Returns "normal form" of label. This matches LABEL_COMPARER behavior. + /// + /// Label to transform. + /// Transformed label. + public static string ToNormal(string label) { + return LABELS_CASE_SENSITIVE ? label : label.ToUpperInvariant(); + } + } +} diff --git a/Asm65/Number.cs b/Asm65/Number.cs new file mode 100644 index 0000000..ae97999 --- /dev/null +++ b/Asm65/Number.cs @@ -0,0 +1,60 @@ +/* + * Copyright 2018 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.Diagnostics; + +namespace Asm65 { + public class Number { + /// + /// Parses an integer in a variety of formats (hex, decimal, binary). + /// + /// Trim whitespace before calling here. + /// + /// String to parse. + /// Integer value of string. + /// What base the string was in (2, 10, or 16). + /// True if the parsing was successful. + public static bool TryParseInt(string str, out int val, out int intBase) { + if (string.IsNullOrEmpty(str)) { + val = intBase = 0; + return false; + } + + if (str[0] == '$') { + intBase = 16; + str = str.Substring(1); // Convert functions don't like '$' + } else if (str.Length > 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { + intBase = 16; + } else if (str[0] == '%') { + intBase = 2; + str = str.Substring(1); // Convert functions don't like '%' + } else { + intBase = 10; // try it as decimal + } + + try { + val = Convert.ToInt32(str, intBase); + //Debug.WriteLine("GOT " + val + " - " + intBase); + } catch (Exception) { + //Debug.WriteLine("TryParseInt failed on '" + str + "': " + ex.Message); + val = 0; + return false; + } + + return true; + } + } +} diff --git a/Asm65/OpDef.cs b/Asm65/OpDef.cs new file mode 100644 index 0000000..1463966 --- /dev/null +++ b/Asm65/OpDef.cs @@ -0,0 +1,3225 @@ +/* + * Copyright 2018 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.Diagnostics; + +namespace Asm65 { + /// + /// Operation code definitions for all 65xx-series CPUs. + /// + /// Instances are immutable. + /// + public class OpDef { + /// + /// Effect an instruction has on code flow. + /// + public enum FlowEffect { + Unknown = 0, + Branch, // JMP, BRA, BRL, ... (jump to new address specified in operand) + Cont, // LDA, STA, PHP, NOP, ... (always continue to next instruction) + NoCont, // RTS, BRK, ... (jump to new address, not specified in operand) + CallSubroutine, // JSR, JSL (jump to new address, and also continue to next) + ConditionalBranch // BCC, BEQ, ... (jump to new address and/or continue to next) + }; + + /// + /// Addressing mode. This uses the same distinctions as Eyes & Lichty, which for + /// most purposes has some redundancy (e.g. StackRTI is only ever used with RTI, so + /// in most cases it could be considered Implied). + /// + /// The mode, combined with the processor flags on the 65816, determines the number + /// of bytes required by the instruction. + /// + public enum AddressMode : byte { + Unknown = 0, + Abs, // OP addr 3 + AbsInd, // OP (addr) 3 (JMP) + AbsIndLong, // OP [addr] 3 (JML) + AbsIndexX, // OP addr,X 3 + AbsIndexXInd, // OP (addr,X) 3 (JMP/JSR) + AbsIndexXLong, // OP long,X 4 + AbsIndexY, // OP addr,Y 3 + AbsLong, // OP long 4 + Acc, // OP A 1 + BlockMove, // OP srcb,dstb 3 (MVN/MVP) + DP, // OP dp 2 + DPInd, // OP (dp) 2 + DPIndIndexY, // OP (dp),Y 2 + DPIndIndexYLong, // OP [dp],Y 2 + DPIndLong, // OP [dp] 2 + DPIndexX, // OP dp,X 2 + DPIndexXInd, // OP (dp,X) 2 + DPIndexY, // OP dp,Y 2 + Imm, // OP #const8 2 + ImmLongA, // OP #const8/16 2 or 3, depending on 'm' flag + ImmLongXY, // OP #const8/16 2 or 3, depending on 'x' flag + Implied, // OP 1 + PCRel, // OP label 2 (branch instructions) + PCRelLong, // OP label 3 (BRL) + StackAbs, // OP addr 3 (PEA) + StackDPInd, // OP (dp) 2 (PEI) + StackInt, // OP 2 (BRK, COP) + StackPCRelLong, // OP label 3 (PER) + StackPull, // OP 1 (PLA, PLX, ...) + StackPush, // OP 1 (PHA, PHX, ...) + StackRTI, // OP 1 (RTI) + StackRTL, // OP 1 (RTL) + StackRTS, // OP 1 (RTS) + StackRel, // OP sr,S 2 + StackRelIndIndexY, // OP (sr,S),Y 2 + WDM // OP 2? + } + + /// + /// Cycle count modifiers. This is meant to be bitwise-ORed with the base value. + /// + /// This defines things generally, with some modifiers noted as being specific to + /// certain CPUs. The actual instruction definitions may choose to include or + /// exclude bits according to the CPU being defined. + /// + [FlagsAttribute] + public enum CycleMod { + // Data from chapter 19 of Eyes & Lichty. + OneIfM0 = 1 << 8, // +1 if M=0 (16-bit mem/accumulator) + TwoIfM0 = 1 << 9, // +2 if M=0 (16-bit mem/accumulator) + OneIfX0 = 1 << 10, // +1 if X=0 (16-bit index regs) + OneIfDpNonzero = 1 << 11, // +1 if DP reg != 0 + OneIfIndexPage = 1 << 12, // +1 if indexing crosses page boundary + OneIfD1 = 1 << 13, // +1 if D=1 (decimal mode) ONLY on 65C02 + OneIfBranchTaken = 1 << 14, // +1 if conditional branch taken + OneIfBranchPage = 1 << 15, // +1 if branch crosses page UNLESS '816 native + OneIfE0 = 1 << 16, // +1 if 65816/865816 native mode (E=0) + OneIf65C02 = 1 << 17, // +1 if 65C02 + MinusOneIfNoPage = 1 << 18, // -1 if 65C02 and no page boundary crossed + BlockMove = 1 << 19, // +7 per byte moved + } + + /// + /// Width disambiguation requirement. + /// + public enum WidthDisambiguation : byte { + None = 0, + ForceAbs, + ForceLong, + ForceLongMaybe + + // May want an additional item: "force long if operand suffix specified". This + // would let us generate LDAL for assemblers that like to have that made explicit, + // while avoiding prepending it to operands that are unambiguously long (e.g. + // $ff1122). The counter-argument is that the operand prefix is still useful + // for humans when looking at labels, e.g. "a:FOO" vs. "f:FOO", because the value + // of the label may not be apparent. + } + + + /// + /// Opcode numeric value, e.g. BRK is $00. + /// + public byte Opcode { get; private set; } + + /// + /// Addressing mode. Determines length of instruction (mostly) and decoding of operand. + /// + public AddressMode AddrMode { get; private set; } + + /// + /// True if this is an undocumented opcode. + /// + public bool IsUndocumented { get; private set; } + + /// + /// Instruction mnemonic, e.g. LDA for Load Accumulator. + /// + public string Mnemonic { get; private set; } + + /// + /// Indication of which flags are affected by an instruction. Unaffected flags + /// will have the value TriState16.UNSPECIFIED. + /// + /// This is not equivalent to the code flow status flag updater -- this just notes which + /// flags are modified directly by the instruction. + /// + public StatusFlags FlagsAffected { get; private set; } + + /// + /// Effect this instruction has on code flow. + /// + public FlowEffect Effect { get; private set; } + + /// + /// Cycles required. The low 8 bits hold the base cycle count, the remaining bits + /// are defined by the CycleMod enum. + /// + private int CycDef { get; set; } + public int Cycles { get { return CycDef & 0xff; } } + public CycleMod CycleMods { get { return (CycleMod)(CycDef & ~0xff); } } + + /// + /// True if the operand's width is uniquely determined by the opcode mnemonic, even + /// if the operation supports operands with varying widths. + /// + /// Certain ops (ADC AND CMP EOR LDA ORA SBC STA) can be direct page, absolute, or + /// absolute long. "LDA 0" could mean LDA $00 (A5), LDA $0000 (AD), or LDA $000000 (AF). + /// Similar ambiguities exist for some indexed modes. Assemblers generally use the + /// smallest form, but allow a longer form to be selected by modifying the opcode + /// (e.g. LDA: and LDAL) or operand (e.g. LDA >0). + /// + /// Most operations that access memory require disambiguation for direct page vs. + /// absolute. Generally speaking, if the operand's high byte is empty, disambiguation + /// is required. + /// + /// The JMP/JML and JSR/JSL mnemonics are commonly used (and, in some assemblers, + /// required to be used) to avoid the ambiguity. For these instructions, we want + /// to avoid modifying the mnemonic, so we set this flag. + /// + private bool IsOperandWidthUnambiguous { get; set; } + + /// + /// Flag update delegate, used for code flow analysis. + /// + /// Previous flags value. + /// Immediate mode value, if any. Value may be 0-255 + /// for an 8-bit operand, or 0-65535 for a 16-bit operand, or -1 if this is + /// not an immediate-mode instruction. + /// New flags value. For conditional branches, this is + /// the value to use when the branch is not taken. + /// For conditional branches, this is the updated + /// value when the branch is taken. + private delegate StatusFlags FlagUpdater(StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags); + private FlagUpdater StatusFlagUpdater { get; set; } + + /// + /// Nullary constructor. Most things are left at default values. + /// + public OpDef() { + StatusFlagUpdater = FlagUpdater_NoChange; + } + + /// + /// Copy constructor. + /// + /// Object to copy from + public OpDef(OpDef src) { + this.Opcode = src.Opcode; + this.AddrMode = src.AddrMode; + this.IsUndocumented = src.IsUndocumented; + this.Mnemonic = src.Mnemonic; + this.FlagsAffected = src.FlagsAffected; + this.Effect = src.Effect; + this.CycDef = src.CycDef; + this.IsOperandWidthUnambiguous = src.IsOperandWidthUnambiguous; + this.StatusFlagUpdater = src.StatusFlagUpdater; + } + + private static StatusFlags FlagsAffected_V = + new StatusFlags() { V = 1 }; + private static StatusFlags FlagsAffected_D = + new StatusFlags() { D = 1 }; + private static StatusFlags FlagsAffected_I = + new StatusFlags() { I = 1 }; + private static StatusFlags FlagsAffected_Z = + new StatusFlags() { Z = 1 }; + private static StatusFlags FlagsAffected_C = + new StatusFlags() { C = 1 }; + private static StatusFlags FlagsAffected_NZ = + new StatusFlags() { N = 1, Z = 1 }; + private static StatusFlags FlagsAffected_NZC = + new StatusFlags() { N = 1, Z = 1, C = 1 }; + private static StatusFlags FlagsAffected_NVZC = + new StatusFlags() { N = 1, V = 1, Z = 1, C = 1 }; + private static StatusFlags FlagsAffected_All = + new StatusFlags() { N = 1, V = 1, M = 1, X = 1, D = 1, I = 1, Z = 1, C = 1 }; + + + /// + /// True if this operation is a branch instruction (conditional branch, + /// unconditional branch/jump, subroutine call). + /// + public bool IsBranch { + get { + return Effect == FlowEffect.Branch || + Effect == FlowEffect.ConditionalBranch || + Effect == FlowEffect.CallSubroutine; + } + } + + /// + /// True if the operand is an immediate value, which should be prefixed with '#'. + /// + public bool IsImmediate { + get { + return AddrMode == AddressMode.Imm || + AddrMode == AddressMode.ImmLongA || + AddrMode == AddressMode.ImmLongXY; + } + } + + /// + /// True if the operand is an "extended immediate" value, which includes PEA + /// as well as Imm. + /// + public bool IsExtendedImmediate { + get { // should this include PER as well? + return IsImmediate || AddrMode == AddressMode.StackAbs; + } + } + + /// + /// True if the operand's width could be ambiguous. Generally speaking, assemblers + /// will use the shortest form possible, so disambiguation is about using a longer + /// form than may appear to be required. + /// + public bool IsWidthPotentiallyAmbiguous { + get { + if (IsOperandWidthUnambiguous) { + // JMP has Abs and AbsLong forms, but those are universally distinguished + // with unique mnemonics (JMP vs. JML). We don't want to generate "JMPL" + // or "JMP >long". Ditto for JSR/JSL. + return false; + } + switch (AddrMode) { + case AddressMode.Abs: // LDA $0000 vs LDA $00 + case AddressMode.AbsLong: // LDA $000000 vs LDA $0000/LDA $00 + case AddressMode.AbsIndexX: // LDA $0000,X vs LDA $00,X + case AddressMode.AbsIndexXLong: // LDA $000000,X vs LDA $0000,X/LDA $00,X + return true; + case AddressMode.AbsIndexY: // LDX $0000,Y vs LDX $00,Y + // AbsIndexY is widely used, but DPIndexY is only available for LDX/STX, + // and STX doesn't have AbsIndexY. So this is only ambiguous for LDX. + // We want to compare by opcode instance, rather than the byte code + // numeric value, to manage different instruction sets. + if (this == OpLDX_AbsIndexY) { + return true; + } + return false; + default: + return false; + } + } + } + + /// + /// Get a value that indicates what sort of disambiguation is required. Only call + /// this if IsWidthPotentiallyAmbiguous is true. + /// + /// Instruction width, including opcode. + /// Operand value, extracted from byte stream. + /// Width disambiguation value. + public static WidthDisambiguation GetWidthDisambiguation(int instrWidth, + int operandValue) { + Debug.Assert(instrWidth > 2 && instrWidth <= 4); // zero-page ops are not ambiguous + if (instrWidth == 3 && operandValue < 0x100) { + return WidthDisambiguation.ForceAbs; + } else if (instrWidth == 4) { + if (operandValue < 0x10000) { + return WidthDisambiguation.ForceLong; + } else { + // The width disambiguator may be helpful for humans when reading labels + // whose value is not immediately apparent. "LDA a:FOO" vs. "LDA f:FOO" + // could be nice. + return WidthDisambiguation.ForceLongMaybe; + } + } else { + return WidthDisambiguation.None; + } + } + + /// + /// Returns the full length of the instruction. Some 65816 operations use + /// a different number of bytes in 16-bit mode, so we need to know what the + /// M/X status flags are. + /// + /// Current status flags. + /// Length, in bytes. + public int GetLength(StatusFlags flags) { + switch (AddrMode) { + case AddressMode.Unknown: + case AddressMode.Acc: + case AddressMode.Implied: + case AddressMode.StackPull: + case AddressMode.StackPush: + case AddressMode.StackRTI: + case AddressMode.StackRTL: + case AddressMode.StackRTS: + return 1; + case AddressMode.DP: + case AddressMode.DPIndexX: + case AddressMode.DPIndexY: + case AddressMode.DPIndexXInd: + case AddressMode.DPInd: + case AddressMode.DPIndLong: + case AddressMode.DPIndIndexY: + case AddressMode.DPIndIndexYLong: + case AddressMode.Imm: + case AddressMode.PCRel: + case AddressMode.StackDPInd: + case AddressMode.StackInt: + case AddressMode.StackRel: + case AddressMode.StackRelIndIndexY: + case AddressMode.WDM: + return 2; + case AddressMode.Abs: + case AddressMode.AbsIndexX: + case AddressMode.AbsIndexY: + case AddressMode.AbsIndexXInd: + case AddressMode.AbsInd: + case AddressMode.AbsIndLong: + case AddressMode.BlockMove: + case AddressMode.PCRelLong: + case AddressMode.StackAbs: + case AddressMode.StackPCRelLong: + return 3; + + case AddressMode.AbsLong: + case AddressMode.AbsIndexXLong: + return 4; + + case AddressMode.ImmLongA: + bool shortM = flags.ShortM; + return shortM ? 2 : 3; + case AddressMode.ImmLongXY: + bool shortX = flags.ShortX; + return shortX ? 2 : 3; + + default: + Debug.Assert(false, "Unknown address mode " + AddrMode); + return -1; + } + } + + + /// + /// Return value from branch evaluation functions. + /// + public enum BranchTaken { Indeterminate = 0, Never, Always }; + + /// + /// Determines whether a branch is always taken, never taken, or is indeterminate + /// (i.e. it's taken some of the time but not taken others). + /// + /// Processor status flag value. + /// Bit value corresponding to branch-taken. + /// Branch taken indication. + private static BranchTaken FlagToBT(int flagVal, int branchVal) { + if (flagVal == TriState16.INDETERMINATE) { + return BranchTaken.Indeterminate; + } else if (flagVal == TriState16.UNSPECIFIED) { + // should never happen + Debug.Assert(false); + return BranchTaken.Indeterminate; + } else if (flagVal == branchVal) { + return BranchTaken.Always; + } else { + return BranchTaken.Never; + } + } + + /// + /// Determines if a conditional branch is always taken, based on the status flags. + /// + /// Conditional branch instruction. + /// Processor status flags. + /// Whether the branch is sometimes, always, or never taken. + public static BranchTaken IsBranchTaken(OpDef op, StatusFlags flags) { + // Could add a delegate to every OpDef, but that seems silly. + switch (op.Opcode) { + case 0x10: // BPL + return FlagToBT(flags.N, 0); + case 0x30: // BMI + return FlagToBT(flags.N, 1); + case 0x50: // BVC + return FlagToBT(flags.V, 0); + case 0x70: // BVS + return FlagToBT(flags.V, 1); + case 0x90: // BCC + return FlagToBT(flags.C, 0); + case 0xb0: // BCS + return FlagToBT(flags.C, 1); + case 0xd0: // BNE + return FlagToBT(flags.Z, 0); + case 0xf0: // BEQ + return FlagToBT(flags.Z, 1); + default: + // Not a conditional branch. + throw new Exception("Not a conditional branch"); + } + } + + /// + /// Get the raw operand value. + /// + /// 65xx code. + /// Offset of opcode. + /// Current status flags. + /// Operand value. + public int GetOperand(byte[] data, int offset, StatusFlags flags) { + switch (GetLength(flags)) { + case 1: + return -1; + case 2: + return data[offset + 1]; + case 3: + return (data[offset + 2] << 8) | data[offset + 1]; + case 4: + return (data[offset + 3] << 16) | (data[offset + 2] << 8) | data[offset + 1]; + default: + return -1; + } + } + + /// + /// Computes the changes the instruction will have on the status flags. + /// + /// Current status flags. This is used as the initial value + /// for the return values, and determines whether 65816 immediate operands are + /// treated as 8- or 16-bit. + /// 65xx data stream. + /// Offset of opcode. + /// Effect this instruction has on flags. + /// Effect this conditional branch instruction + /// has on flags when the branch is taken. If this is not a conditional branch, + /// the value can be ignored. + public void ComputeFlagChanges(StatusFlags curFlags, byte[] data, int offset, + out StatusFlags newFlags, out StatusFlags condBranchTakenFlags) { + int immVal = -1; + + switch (AddrMode) { + case AddressMode.Imm: + case AddressMode.ImmLongA: + case AddressMode.ImmLongXY: + if (GetLength(curFlags) == 2) { + // 8-bit operand + immVal = data[offset + 1]; + } else { + // 16-bit operand; make as such by negating it + // (if it's zero, 8 vs. 16 doesn't matter) + immVal = -((data[offset + 2]) << 16 | data[offset + 1]); + } + break; + default: + break; + } + + condBranchTakenFlags = curFlags; + // Invoke the flag update delegate. + newFlags = StatusFlagUpdater(curFlags, immVal, ref condBranchTakenFlags); + } + + private static StatusFlags FlagUpdater_NoChange(StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + return flags; + } + private static StatusFlags FlagUpdater_Subroutine(StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + // The correct way to do this is to merge the flags from all code + // that RTS/RTLs back to here, but that's generally hard to do, especially + // since this will often be used to call into external code. The easiest + // thing to do is scramble CZVN and leave IDXM alone. + return FlagUpdater_NVZC(flags, immVal, ref condBranchTakenFlags); + } + private static StatusFlags FlagUpdater_Z(StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + flags.Z = TriState16.INDETERMINATE; + return flags; + } + private static StatusFlags FlagUpdater_C(StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + flags.C = TriState16.INDETERMINATE; + return flags; + } + private static StatusFlags FlagUpdater_NZ(StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + flags.Z = flags.N = TriState16.INDETERMINATE; + return flags; + } + private static StatusFlags FlagUpdater_NZC(StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + flags.C = flags.Z = flags.N = TriState16.INDETERMINATE; + return flags; + } + private static StatusFlags FlagUpdater_NVZC(StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + flags.C = flags.Z = flags.V = flags.N = TriState16.INDETERMINATE; + return flags; + } + private static StatusFlags FlagUpdater_LoadImm(StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + // We can set the N/Z flags based on the value loaded. + flags.Z = (immVal != 0) ? 0 : 1; + if (immVal >= 0) { + // 8-bit operand + flags.N = (immVal >> 7) & 0x01; + } else { + // 16-bit operand + immVal = -immVal; + flags.N = (immVal >> 15) & 0x01; + } + return flags; + } + private static StatusFlags FlagUpdater_ANDImm(StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + // AND #00 --> Z=1, else Z=prev + // AND #7f --> N=0, else N=prev + if (immVal == 0) { + flags.Z = 1; + } + bool hiBitClear; + if (immVal >= 0) { + // 8-bit operand + hiBitClear = ((immVal >> 7) & 0x01) == 0; + } else { + // 16-bit operand + immVal = -immVal; + hiBitClear = ((immVal >> 15) & 0x01) == 0; + } + if (hiBitClear) { + flags.N = 0; + } + return flags; + } + private static StatusFlags FlagUpdater_ORAImm(StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + // ORA #00 --> Z=prev, else Z=0 + // ORA #80 --> N=1, else N=prev + if (immVal != 0) { + flags.Z = 0; + } + bool hiBitSet; + if (immVal >= 0) { + // 8-bit operand + hiBitSet = ((immVal >> 7) & 0x01) != 0; + } else { + // 16-bit operand + immVal = -immVal; + hiBitSet = ((immVal >> 15) & 0x01) != 0; + } + if (hiBitSet) { + flags.N = 1; + } + return flags; + } + private static StatusFlags FlagUpdater_PLP(StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + // All flags are unknown. The caller may be able to match with a previous + // PHP to get something less fuzzy. + flags.C = flags.Z = flags.I = flags.D = flags.X = flags.M = flags.V = flags.N = + TriState16.INDETERMINATE; + return flags; + } + private static StatusFlags FlagUpdater_REP(StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + if ((immVal & (1 << (int)StatusFlags.FlagBits.C)) != 0) { + flags.C = 0; + } + if ((immVal & (1 << (int)StatusFlags.FlagBits.Z)) != 0) { + flags.Z = 0; + } + if ((immVal & (1 << (int)StatusFlags.FlagBits.I)) != 0) { + flags.I = 0; + } + if ((immVal & (1 << (int)StatusFlags.FlagBits.D)) != 0) { + flags.D = 0; + } + if ((immVal & (1 << (int)StatusFlags.FlagBits.X)) != 0) { + flags.X = 0; + } + if ((immVal & (1 << (int)StatusFlags.FlagBits.M)) != 0) { + flags.M = 0; + } + if ((immVal & (1 << (int)StatusFlags.FlagBits.V)) != 0) { + flags.V = 0; + } + if ((immVal & (1 << (int)StatusFlags.FlagBits.N)) != 0) { + flags.N = 0; + } + return flags; + } + private static StatusFlags FlagUpdater_SEP(StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + if ((immVal & (1 << (int)StatusFlags.FlagBits.C)) != 0) { + flags.C = 1; + } + if ((immVal & (1 << (int)StatusFlags.FlagBits.Z)) != 0) { + flags.Z = 1; + } + if ((immVal & (1 << (int)StatusFlags.FlagBits.I)) != 0) { + flags.I = 1; + } + if ((immVal & (1 << (int)StatusFlags.FlagBits.D)) != 0) { + flags.D = 1; + } + if ((immVal & (1 << (int)StatusFlags.FlagBits.X)) != 0) { + flags.X = 1; + } + if ((immVal & (1 << (int)StatusFlags.FlagBits.M)) != 0) { + flags.M = 1; + } + if ((immVal & (1 << (int)StatusFlags.FlagBits.V)) != 0) { + flags.V = 1; + } + if ((immVal & (1 << (int)StatusFlags.FlagBits.N)) != 0) { + flags.N = 1; + } + return flags; + } + private static StatusFlags FlagUpdater_XCE(StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + // We want the 'E' flag to always have a definite value. If we don't know + // what's in the carry flag, guess it's a return to emulation. + StatusFlags newFlags = flags; + if (flags.C == 0) { + // transition to native + newFlags.E = 0; + newFlags.X = newFlags.M = 1; + } else /*C==1 or C==?*/ { + // transition to emulation + // The registers will be treated as short by the CPU, ignoring M/X, but + // the assembler won't generally know that. Set the flags to definite + // values here so we generate the necessary directives. + newFlags.E = 1; + newFlags.X = newFlags.M = 1; + } + newFlags.C = flags.E; + return newFlags; + } + + + // ====================================================================================== + // Base operation definitions, one per op. + // + + private static OpDef OpUnknown = new OpDef() { + Mnemonic = OpName.Unknown, + Effect = FlowEffect.Cont + }; + private static OpDef OpADC = new OpDef() { + Mnemonic = OpName.ADC, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NVZC, + StatusFlagUpdater = FlagUpdater_NVZC + }; + private static OpDef OpAND = new OpDef() { + Mnemonic = OpName.AND, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ // special handling for imm + }; + private static OpDef OpASL = new OpDef() { + Mnemonic = OpName.ASL, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZC, + StatusFlagUpdater = FlagUpdater_NZC + }; + private static OpDef OpBCC = new OpDef() { + Mnemonic = OpName.BCC, + Effect = FlowEffect.ConditionalBranch, + StatusFlagUpdater = delegate(StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + condBranchTakenFlags.C = 0; + flags.C = 1; + return flags; + } + }; + private static OpDef OpBCS = new OpDef() { + Mnemonic = OpName.BCS, + Effect = FlowEffect.ConditionalBranch, + StatusFlagUpdater = delegate (StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + condBranchTakenFlags.C = 1; + flags.C = 0; + return flags; + } + }; + private static OpDef OpBEQ = new OpDef() { + Mnemonic = OpName.BEQ, + Effect = FlowEffect.ConditionalBranch, + StatusFlagUpdater = delegate (StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + condBranchTakenFlags.Z = 1; + flags.Z = 0; + return flags; + } + }; + private static OpDef OpBIT = new OpDef() { + Mnemonic = OpName.BIT, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZC, // special handling for imm + StatusFlagUpdater = FlagUpdater_NZC // special handling for imm + }; + private static OpDef OpBMI = new OpDef() { + Mnemonic = OpName.BMI, + Effect = FlowEffect.ConditionalBranch, + StatusFlagUpdater = delegate (StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + condBranchTakenFlags.N = 1; + flags.N = 0; + return flags; + } + }; + private static OpDef OpBNE = new OpDef() { + Mnemonic = OpName.BNE, + Effect = FlowEffect.ConditionalBranch, + StatusFlagUpdater = delegate (StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + condBranchTakenFlags.Z = 0; + flags.Z = 1; + return flags; + } + }; + private static OpDef OpBPL = new OpDef() { + Mnemonic = OpName.BPL, + Effect = FlowEffect.ConditionalBranch, + StatusFlagUpdater = delegate (StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + condBranchTakenFlags.N = 0; + flags.N = 1; + return flags; + } + }; + private static OpDef OpBRA = new OpDef() { + Mnemonic = OpName.BRA, + Effect = FlowEffect.Branch + }; + private static OpDef OpBRK = new OpDef() { + Mnemonic = OpName.BRK, + Effect = FlowEffect.NoCont + }; + private static OpDef OpBRL = new OpDef() { + Mnemonic = OpName.BRL, + Effect = FlowEffect.Branch + }; + private static OpDef OpBVC = new OpDef() { + Mnemonic = OpName.BVC, + Effect = FlowEffect.ConditionalBranch, + StatusFlagUpdater = delegate (StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + condBranchTakenFlags.V = 0; + flags.V = 1; + return flags; + } + }; + private static OpDef OpBVS = new OpDef() { + Mnemonic = OpName.BVS, + Effect = FlowEffect.ConditionalBranch, + StatusFlagUpdater = delegate (StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + condBranchTakenFlags.V = 1; + flags.V = 0; + return flags; + } + }; + private static OpDef OpCLC = new OpDef() { + Mnemonic = OpName.CLC, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_C, + StatusFlagUpdater = delegate (StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + flags.C = 0; + return flags; + } + }; + private static OpDef OpCLD = new OpDef() { + Mnemonic = OpName.CLD, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_D, + StatusFlagUpdater = delegate (StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + flags.D = 0; + return flags; + } + }; + private static OpDef OpCLI = new OpDef() { + Mnemonic = OpName.CLI, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_I, + StatusFlagUpdater = delegate (StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + flags.I = 0; + return flags; + } + }; + private static OpDef OpCLV = new OpDef() { + Mnemonic = OpName.CLV, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_V, + StatusFlagUpdater = delegate (StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + flags.V = 0; + return flags; + } + }; + private static OpDef OpCMP = new OpDef() { + Mnemonic = OpName.CMP, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZC, + StatusFlagUpdater = FlagUpdater_NZC + }; + private static OpDef OpCOP = new OpDef() { + Mnemonic = OpName.COP, + Effect = FlowEffect.Cont + }; + private static OpDef OpCPX = new OpDef() { + Mnemonic = OpName.CPX, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZC, + StatusFlagUpdater = FlagUpdater_NZC + }; + private static OpDef OpCPY = new OpDef() { + Mnemonic = OpName.CPY, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZC, + StatusFlagUpdater = FlagUpdater_NZC + }; + private static OpDef OpDEC = new OpDef() { + Mnemonic = OpName.DEC, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpDEX = new OpDef() { + Mnemonic = OpName.DEX, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpDEY = new OpDef() { + Mnemonic = OpName.DEY, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpEOR = new OpDef() { + Mnemonic = OpName.EOR, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpINC = new OpDef() { + Mnemonic = OpName.INC, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpINX = new OpDef() { + Mnemonic = OpName.INX, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpINY = new OpDef() { + Mnemonic = OpName.INY, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpJML = new OpDef() { + Mnemonic = OpName.JML, // technically JMP with long operand, but JML is convention + Effect = FlowEffect.Branch, + IsOperandWidthUnambiguous = true, + }; + private static OpDef OpJMP = new OpDef() { + Mnemonic = OpName.JMP, + Effect = FlowEffect.Branch, + IsOperandWidthUnambiguous = true + }; + private static OpDef OpJSL = new OpDef() { + Mnemonic = OpName.JSL, // technically JSR with long operand, but JSL is convention + Effect = FlowEffect.CallSubroutine, + IsOperandWidthUnambiguous = true, + StatusFlagUpdater = FlagUpdater_Subroutine + }; + private static OpDef OpJSR = new OpDef() { + Mnemonic = OpName.JSR, + Effect = FlowEffect.CallSubroutine, + IsOperandWidthUnambiguous = true, + StatusFlagUpdater = FlagUpdater_Subroutine + }; + private static OpDef OpLDA = new OpDef() { + Mnemonic = OpName.LDA, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ // special handling for imm + }; + private static OpDef OpLDX = new OpDef() { + Mnemonic = OpName.LDX, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ // special handling for imm + }; + private static OpDef OpLDY = new OpDef() { + Mnemonic = OpName.LDY, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ // special handling for imm + }; + private static OpDef OpLSR = new OpDef() { + Mnemonic = OpName.LSR, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZC, + StatusFlagUpdater = FlagUpdater_NZC + }; + private static OpDef OpMVN = new OpDef() { + Mnemonic = OpName.MVN, + Effect = FlowEffect.Cont + }; + private static OpDef OpMVP = new OpDef() { + Mnemonic = OpName.MVP, + Effect = FlowEffect.Cont + }; + private static OpDef OpNOP = new OpDef() { + Mnemonic = OpName.NOP, + Effect = FlowEffect.Cont + }; + private static OpDef OpORA = new OpDef() { + Mnemonic = OpName.ORA, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ // special handling for imm + }; + private static OpDef OpPEA = new OpDef() { + Mnemonic = OpName.PEA, + Effect = FlowEffect.Cont + }; + private static OpDef OpPEI = new OpDef() { + Mnemonic = OpName.PEI, + Effect = FlowEffect.Cont + }; + private static OpDef OpPER = new OpDef() { + Mnemonic = OpName.PER, + Effect = FlowEffect.Cont + }; + private static OpDef OpPHA = new OpDef() { + Mnemonic = OpName.PHA, + Effect = FlowEffect.Cont + }; + private static OpDef OpPHB = new OpDef() { + Mnemonic = OpName.PHB, + Effect = FlowEffect.Cont + }; + private static OpDef OpPHD = new OpDef() { + Mnemonic = OpName.PHD, + Effect = FlowEffect.Cont + }; + private static OpDef OpPHK = new OpDef() { + Mnemonic = OpName.PHK, + Effect = FlowEffect.Cont + }; + private static OpDef OpPHP = new OpDef() { + Mnemonic = OpName.PHP, + Effect = FlowEffect.Cont + }; + private static OpDef OpPHX = new OpDef() { + Mnemonic = OpName.PHX, + Effect = FlowEffect.Cont + }; + private static OpDef OpPHY = new OpDef() { + Mnemonic = OpName.PHY, + Effect = FlowEffect.Cont + }; + private static OpDef OpPLA = new OpDef() { + Mnemonic = OpName.PLA, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpPLB = new OpDef() { + Mnemonic = OpName.PLB, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpPLD = new OpDef() { + Mnemonic = OpName.PLD, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpPLP = new OpDef() { + Mnemonic = OpName.PLP, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_All, + StatusFlagUpdater = FlagUpdater_PLP + }; + private static OpDef OpPLX = new OpDef() { + Mnemonic = OpName.PLX, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpPLY = new OpDef() { + Mnemonic = OpName.PLY, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpREP = new OpDef() { + Mnemonic = OpName.REP, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_All, + StatusFlagUpdater = FlagUpdater_REP + }; + private static OpDef OpROL = new OpDef() { + Mnemonic = OpName.ROL, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZC, + StatusFlagUpdater = FlagUpdater_NZC + }; + private static OpDef OpROR = new OpDef() { + Mnemonic = OpName.ROR, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZC, + StatusFlagUpdater = FlagUpdater_NZC + }; + private static OpDef OpRTI = new OpDef() { + Mnemonic = OpName.RTI, + Effect = FlowEffect.NoCont + }; + private static OpDef OpRTL = new OpDef() { + Mnemonic = OpName.RTL, + Effect = FlowEffect.NoCont + }; + private static OpDef OpRTS = new OpDef() { + Mnemonic = OpName.RTS, + Effect = FlowEffect.NoCont + }; + private static OpDef OpSBC = new OpDef() { + Mnemonic = OpName.SBC, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NVZC, + StatusFlagUpdater = FlagUpdater_NVZC + }; + private static OpDef OpSEC = new OpDef() { + Mnemonic = OpName.SEC, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_C, + StatusFlagUpdater = delegate (StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + flags.C = 1; + return flags; + } + }; + private static OpDef OpSED = new OpDef() { + Mnemonic = OpName.SED, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_D, + StatusFlagUpdater = delegate (StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + flags.D = 1; + return flags; + } + }; + private static OpDef OpSEI = new OpDef() { + Mnemonic = OpName.SEI, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_I, + StatusFlagUpdater = delegate (StatusFlags flags, int immVal, + ref StatusFlags condBranchTakenFlags) { + flags.I = 1; + return flags; + } + }; + private static OpDef OpSEP = new OpDef() { + Mnemonic = OpName.SEP, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_All, + StatusFlagUpdater = FlagUpdater_SEP + }; + private static OpDef OpSTA = new OpDef() { + Mnemonic = OpName.STA, + Effect = FlowEffect.Cont + }; + private static OpDef OpSTP = new OpDef() { + Mnemonic = OpName.STP, + Effect = FlowEffect.NoCont + }; + private static OpDef OpSTX = new OpDef() { + Mnemonic = OpName.STX, + Effect = FlowEffect.Cont + }; + private static OpDef OpSTY = new OpDef() { + Mnemonic = OpName.STY, + Effect = FlowEffect.Cont + }; + private static OpDef OpSTZ = new OpDef() { + Mnemonic = OpName.STZ, + Effect = FlowEffect.Cont + }; + private static OpDef OpTAX = new OpDef() { + Mnemonic = OpName.TAX, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpTAY = new OpDef() { + Mnemonic = OpName.TAY, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpTCD = new OpDef() { + Mnemonic = OpName.TCD, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpTCS = new OpDef() { + Mnemonic = OpName.TCS, + Effect = FlowEffect.Cont + }; + private static OpDef OpTDC = new OpDef() { + Mnemonic = OpName.TDC, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpTRB = new OpDef() { + Mnemonic = OpName.TRB, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_Z, + StatusFlagUpdater = FlagUpdater_Z + }; + private static OpDef OpTSB = new OpDef() { + Mnemonic = OpName.TSB, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_Z, + StatusFlagUpdater = FlagUpdater_Z + }; + private static OpDef OpTSC = new OpDef() { + Mnemonic = OpName.TSC, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpTSX = new OpDef() { + Mnemonic = OpName.TSX, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpTXA = new OpDef() { + Mnemonic = OpName.TXA, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpTXS = new OpDef() { + Mnemonic = OpName.TXS, + Effect = FlowEffect.Cont + }; + private static OpDef OpTXY = new OpDef() { + Mnemonic = OpName.TXY, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpTYA = new OpDef() { + Mnemonic = OpName.TYA, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpTYX = new OpDef() { + Mnemonic = OpName.TYX, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpWAI = new OpDef() { + Mnemonic = OpName.WAI, + Effect = FlowEffect.Cont // when I=1 (interrupts disabled), continues on interrupt + }; + private static OpDef OpWDM = new OpDef() { + Mnemonic = OpName.WDM, + Effect = FlowEffect.Cont + }; + private static OpDef OpXBA = new OpDef() { + Mnemonic = OpName.XBA, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpXCE = new OpDef() { + Mnemonic = OpName.XCE, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_C, + StatusFlagUpdater = FlagUpdater_XCE + }; + + + #region 65816 Instructions + + // ====================================================================================== + // Operation + address mode definitions. + // + // It's possible for more than one definition to have the same opcode number. The + // 6502 only supports 8-bit LDA imm, while the 65816 version can be 8 or 16. There + // are minor differences in behavior for some opcodes on different CPUs. + // + // Significantly, the "invalid" 6502 ops overlap with official ops defined on later CPUs. + // + + public static readonly OpDef OpInvalid = new OpDef(OpUnknown) { + AddrMode = AddressMode.Unknown + }; + + public static readonly OpDef OpBRK_StackInt = new OpDef(OpBRK) { + Opcode = 0x00, + AddrMode = AddressMode.StackInt, + CycDef = 7 | (int)(CycleMod.OneIfE0) + }; + public static readonly OpDef OpORA_DPIndexXInd = new OpDef(OpORA) { + Opcode = 0x01, + AddrMode = AddressMode.DPIndexXInd, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpCOP_StackInt = new OpDef(OpCOP) { + Opcode = 0x02, + AddrMode = AddressMode.StackInt, + CycDef = 7 | (int)(CycleMod.OneIfE0) + }; + public static readonly OpDef OpORA_StackRel = new OpDef(OpORA) { + Opcode = 0x03, + AddrMode = AddressMode.StackRel, + CycDef = 4 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpTSB_DP = new OpDef(OpTSB) { + Opcode = 0x04, + AddrMode = AddressMode.DP, + CycDef = 5 | (int)(CycleMod.OneIfDpNonzero | CycleMod.TwoIfM0) + }; + public static readonly OpDef OpORA_DP = new OpDef(OpORA) { + Opcode = 0x05, + AddrMode = AddressMode.DP, + CycDef = 3 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpASL_DP = new OpDef(OpASL) { + Opcode = 0x06, + AddrMode = AddressMode.DP, + CycDef = 5 | (int)(CycleMod.OneIfDpNonzero | CycleMod.TwoIfM0) + }; + public static readonly OpDef OpORA_DPIndLong = new OpDef(OpORA) { + Opcode = 0x07, + AddrMode = AddressMode.DPIndLong, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpPHP_StackPush = new OpDef(OpPHP) { + Opcode = 0x08, + AddrMode = AddressMode.StackPush, + CycDef = 3 + }; + public static readonly OpDef OpORA_ImmLongA = new OpDef(OpORA) { + Opcode = 0x09, + AddrMode = AddressMode.ImmLongA, + StatusFlagUpdater = FlagUpdater_ORAImm, + CycDef = 2 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpORA_Imm = new OpDef(OpORA) { + Opcode = 0x09, + AddrMode = AddressMode.Imm, + StatusFlagUpdater = FlagUpdater_ORAImm, + CycDef = 2 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpASL_Acc = new OpDef(OpASL) { + Opcode = 0x0a, + AddrMode = AddressMode.Acc, + CycDef = 2 + }; + public static readonly OpDef OpPHD_StackPush = new OpDef(OpPHD) { + Opcode = 0x0b, + AddrMode = AddressMode.StackPush, + CycDef = 4 + }; + public static readonly OpDef OpTSB_Abs = new OpDef(OpTSB) { + Opcode = 0x0c, + AddrMode = AddressMode.Abs, + CycDef = 6 | (int)(CycleMod.TwoIfM0) + }; + public static readonly OpDef OpORA_Abs = new OpDef(OpORA) { + Opcode = 0x0d, + AddrMode = AddressMode.Abs, + CycDef = 4 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpASL_Abs = new OpDef(OpASL) { + Opcode = 0x0e, + AddrMode = AddressMode.Abs, + CycDef = 6 | (int)(CycleMod.TwoIfM0) + }; + public static readonly OpDef OpORA_AbsLong = new OpDef(OpORA) { + Opcode = 0x0f, + AddrMode = AddressMode.AbsLong, + CycDef = 5 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpBPL_PCRel = new OpDef(OpBPL) { + Opcode = 0x10, + AddrMode = AddressMode.PCRel, + CycDef = 2 | (int)(CycleMod.OneIfBranchTaken | CycleMod.OneIfBranchPage) + }; + public static readonly OpDef OpORA_DPIndIndexY = new OpDef(OpORA) { + Opcode = 0x11, + AddrMode = AddressMode.DPIndIndexY, + CycDef = 5 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero | CycleMod.OneIfIndexPage) + }; + public static readonly OpDef OpORA_DPInd = new OpDef(OpORA) { + Opcode = 0x12, + AddrMode = AddressMode.DPInd, + CycDef = 5 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpORA_StackRelIndIndexY = new OpDef(OpORA) { + Opcode = 0x13, + AddrMode = AddressMode.StackRelIndIndexY, + CycDef = 7 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpTRB_DP = new OpDef(OpTRB) { + Opcode = 0x14, + AddrMode = AddressMode.DP, + CycDef = 5 | (int)(CycleMod.OneIfDpNonzero | CycleMod.TwoIfM0) + }; + public static readonly OpDef OpORA_DPIndexX = new OpDef(OpORA) { + Opcode = 0x15, + AddrMode = AddressMode.DPIndexX, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpASL_DPIndexX = new OpDef(OpASL) { + Opcode = 0x16, + AddrMode = AddressMode.DPIndexX, + CycDef = 6 | (int)(CycleMod.OneIfDpNonzero | CycleMod.TwoIfM0) + }; + public static readonly OpDef OpORA_DPIndIndexYLong = new OpDef(OpORA) { + Opcode = 0x17, + AddrMode = AddressMode.DPIndIndexYLong, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpCLC_Implied = new OpDef(OpCLC) { + Opcode = 0x18, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpORA_AbsIndexY = new OpDef(OpORA) { + Opcode = 0x19, + AddrMode = AddressMode.AbsIndexY, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfIndexPage) + }; + public static readonly OpDef OpINC_Acc = new OpDef(OpINC) { + Opcode = 0x1a, + AddrMode = AddressMode.Acc, + CycDef = 2 + }; + public static readonly OpDef OpTCS_Implied = new OpDef(OpTCS) { + Opcode = 0x1b, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpTRB_Abs = new OpDef(OpTRB) { + Opcode = 0x1c, + AddrMode = AddressMode.Abs, + CycDef = 6 | (int)(CycleMod.TwoIfM0) + }; + public static readonly OpDef OpORA_AbsIndexX = new OpDef(OpORA) { + Opcode = 0x1d, + AddrMode = AddressMode.AbsIndexX, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfIndexPage) + }; + public static readonly OpDef OpASL_AbsIndexX = new OpDef(OpASL) { + Opcode = 0x1e, + AddrMode = AddressMode.AbsIndexX, + CycDef = 7 | (int)(CycleMod.TwoIfM0 | CycleMod.MinusOneIfNoPage) + }; + public static readonly OpDef OpORA_AbsIndexXLong = new OpDef(OpORA) { + Opcode = 0x1f, + AddrMode = AddressMode.AbsIndexXLong, + CycDef = 5 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpJSR_Abs = new OpDef(OpJSR) { + Opcode = 0x20, + AddrMode = AddressMode.Abs, + CycDef = 6 + }; + public static readonly OpDef OpAND_DPIndexXInd = new OpDef(OpAND) { + Opcode = 0x21, + AddrMode = AddressMode.DPIndexXInd, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpJSR_AbsLong = new OpDef(OpJSL) { + Opcode = 0x22, + AddrMode = AddressMode.AbsLong, + CycDef = 8 + }; + public static readonly OpDef OpAND_StackRel = new OpDef(OpAND) { + Opcode = 0x23, + AddrMode = AddressMode.StackRel, + CycDef = 4 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpBIT_DP = new OpDef(OpBIT) { + Opcode = 0x24, + AddrMode = AddressMode.DP, + CycDef = 3 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpAND_DP = new OpDef(OpAND) { + Opcode = 0x25, + AddrMode = AddressMode.DP, + CycDef = 3 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpROL_DP = new OpDef(OpROL) { + Opcode = 0x26, + AddrMode = AddressMode.DP, + CycDef = 5 | (int)(CycleMod.OneIfDpNonzero | CycleMod.TwoIfM0) + }; + public static readonly OpDef OpAND_DPIndLong = new OpDef(OpAND) { + Opcode = 0x27, + AddrMode = AddressMode.DPIndLong, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpPLP_StackPull = new OpDef(OpPLP) { + Opcode = 0x28, + AddrMode = AddressMode.StackPull, + CycDef = 4 + }; + public static readonly OpDef OpAND_ImmLongA = new OpDef(OpAND) { + Opcode = 0x29, + AddrMode = AddressMode.ImmLongA, + StatusFlagUpdater = FlagUpdater_ANDImm, + CycDef = 2 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpAND_Imm = new OpDef(OpAND) { + Opcode = 0x29, + AddrMode = AddressMode.Imm, + StatusFlagUpdater = FlagUpdater_ANDImm, + CycDef = 2 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpROL_Acc = new OpDef(OpROL) { + Opcode = 0x2a, + AddrMode = AddressMode.Acc, + CycDef = 2 + }; + public static readonly OpDef OpPLD_StackPull = new OpDef(OpPLD) { + Opcode = 0x2b, + AddrMode = AddressMode.StackPull, + CycDef = 5 + }; + public static readonly OpDef OpBIT_Abs = new OpDef(OpBIT) { + Opcode = 0x2c, + AddrMode = AddressMode.Abs, + CycDef = 4 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpAND_Abs = new OpDef(OpAND) { + Opcode = 0x2d, + AddrMode = AddressMode.Abs, + CycDef = 4 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpROL_Abs = new OpDef(OpROL) { + Opcode = 0x2e, + AddrMode = AddressMode.Abs, + CycDef = 6 | (int)(CycleMod.TwoIfM0) + }; + public static readonly OpDef OpAND_AbsLong = new OpDef(OpAND) { + Opcode = 0x2f, + AddrMode = AddressMode.AbsLong, + CycDef = 5 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpBMI_PCRel = new OpDef(OpBMI) { + Opcode = 0x30, + AddrMode = AddressMode.PCRel, + CycDef = 2 | (int)(CycleMod.OneIfBranchTaken | CycleMod.OneIfBranchPage) + }; + public static readonly OpDef OpAND_DPIndIndexY = new OpDef(OpAND) { + Opcode = 0x31, + AddrMode = AddressMode.DPIndIndexY, + CycDef = 5 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero | CycleMod.OneIfIndexPage) + }; + public static readonly OpDef OpAND_DPInd = new OpDef(OpAND) { + Opcode = 0x32, + AddrMode = AddressMode.DPInd, + CycDef = 5 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpAND_StackRelIndIndexY = new OpDef(OpAND) { + Opcode = 0x33, + AddrMode = AddressMode.StackRelIndIndexY, + CycDef = 7 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpBIT_DPIndexX = new OpDef(OpBIT) { + Opcode = 0x34, + AddrMode = AddressMode.DPIndexX, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpAND_DPIndexX = new OpDef(OpAND) { + Opcode = 0x35, + AddrMode = AddressMode.DPIndexX, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpROL_DPIndexX = new OpDef(OpROL) { + Opcode = 0x36, + AddrMode = AddressMode.DPIndexX, + CycDef = 6 | (int)(CycleMod.OneIfDpNonzero | CycleMod.TwoIfM0) + }; + public static readonly OpDef OpAND_DPIndIndexYLong = new OpDef(OpAND) { + Opcode = 0x37, + AddrMode = AddressMode.DPIndIndexYLong, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpSEC_Implied = new OpDef(OpSEC) { + Opcode = 0x38, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpAND_AbsIndexY = new OpDef(OpAND) { + Opcode = 0x39, + AddrMode = AddressMode.AbsIndexY, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfIndexPage) + }; + public static readonly OpDef OpDEC_Acc = new OpDef(OpDEC) { + Opcode = 0x3a, + AddrMode = AddressMode.Acc, + CycDef = 2 + }; + public static readonly OpDef OpTSC_Implied = new OpDef(OpTSC) { + Opcode = 0x3b, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpBIT_AbsIndexX = new OpDef(OpBIT) { + Opcode = 0x3c, + AddrMode = AddressMode.AbsIndexX, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfIndexPage) + }; + public static readonly OpDef OpAND_AbsIndexX = new OpDef(OpAND) { + Opcode = 0x3d, + AddrMode = AddressMode.AbsIndexX, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfIndexPage) + }; + public static readonly OpDef OpROL_AbsIndexX = new OpDef(OpROL) { + Opcode = 0x3e, + AddrMode = AddressMode.AbsIndexX, + CycDef = 7 | (int)(CycleMod.TwoIfM0 | CycleMod.MinusOneIfNoPage) + }; + public static readonly OpDef OpAND_AbsIndexXLong = new OpDef(OpAND) { + Opcode = 0x3f, + AddrMode = AddressMode.AbsIndexXLong, + CycDef = 5 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpRTI_StackRTI = new OpDef(OpRTI) { + Opcode = 0x40, + AddrMode = AddressMode.StackRTI, + CycDef = 6 | (int)(CycleMod.OneIfE0) + }; + public static readonly OpDef OpEOR_DPIndexXInd = new OpDef(OpEOR) { + Opcode = 0x41, + AddrMode = AddressMode.DPIndexXInd, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpWDM_WDM = new OpDef(OpWDM) { + Opcode = 0x42, + AddrMode = AddressMode.WDM, + CycDef = 2 // arbitrary; actual time is undefined + }; + public static readonly OpDef OpEOR_StackRel = new OpDef(OpEOR) { + Opcode = 0x43, + AddrMode = AddressMode.StackRel, + CycDef = 4 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpMVP_BlockMove = new OpDef(OpMVP) { + Opcode = 0x44, + AddrMode = AddressMode.BlockMove, + CycDef = 7 | (int)(CycleMod.BlockMove) + }; + public static readonly OpDef OpEOR_DP = new OpDef(OpEOR) { + Opcode = 0x45, + AddrMode = AddressMode.DP, + CycDef = 3 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpLSR_DP = new OpDef(OpLSR) { + Opcode = 0x46, + AddrMode = AddressMode.DP, + CycDef = 5 | (int)(CycleMod.OneIfDpNonzero | CycleMod.TwoIfM0) + }; + public static readonly OpDef OpEOR_DPIndLong = new OpDef(OpEOR) { + Opcode = 0x47, + AddrMode = AddressMode.DPIndLong, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpPHA_StackPush = new OpDef(OpPHA) { + Opcode = 0x48, + AddrMode = AddressMode.StackPush, + CycDef = 3 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpEOR_ImmLongA = new OpDef(OpEOR) { + Opcode = 0x49, + AddrMode = AddressMode.ImmLongA, + CycDef = 2 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpEOR_Imm = new OpDef(OpEOR) { + Opcode = 0x49, + AddrMode = AddressMode.Imm, + CycDef = 2 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpLSR_Acc = new OpDef(OpLSR) { + Opcode = 0x4a, + AddrMode = AddressMode.Acc, + CycDef = 2 + }; + public static readonly OpDef OpPHK_StackPush = new OpDef(OpPHK) { + Opcode = 0x4b, + AddrMode = AddressMode.StackPush, + CycDef = 3 + }; + public static readonly OpDef OpJMP_Abs = new OpDef(OpJMP) { + Opcode = 0x4c, + AddrMode = AddressMode.Abs, + CycDef = 3 + }; + public static readonly OpDef OpEOR_Abs = new OpDef(OpEOR) { + Opcode = 0x4d, + AddrMode = AddressMode.Abs, + CycDef = 4 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpLSR_Abs = new OpDef(OpLSR) { + Opcode = 0x4e, + AddrMode = AddressMode.Abs, + CycDef = 6 | (int)(CycleMod.TwoIfM0) + }; + public static readonly OpDef OpEOR_AbsLong = new OpDef(OpEOR) { + Opcode = 0x4f, + AddrMode = AddressMode.AbsLong, + CycDef = 5 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpBVC_PCRel = new OpDef(OpBVC) { + Opcode = 0x50, + AddrMode = AddressMode.PCRel, + CycDef = 2 | (int)(CycleMod.OneIfBranchTaken | CycleMod.OneIfBranchPage) + }; + public static readonly OpDef OpEOR_DPIndIndexY = new OpDef(OpEOR) { + Opcode = 0x51, + AddrMode = AddressMode.DPIndIndexY, + CycDef = 5 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero | CycleMod.OneIfIndexPage) + }; + public static readonly OpDef OpEOR_DPInd = new OpDef(OpEOR) { + Opcode = 0x52, + AddrMode = AddressMode.DPInd, + CycDef = 5 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpEOR_StackRelIndIndexY = new OpDef(OpEOR) { + Opcode = 0x53, + AddrMode = AddressMode.StackRelIndIndexY, + CycDef = 7 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpMVN_BlockMove = new OpDef(OpMVN) { + Opcode = 0x54, + AddrMode = AddressMode.BlockMove, + CycDef = 7 | (int)(CycleMod.BlockMove) + }; + public static readonly OpDef OpEOR_DPIndexX = new OpDef(OpEOR) { + Opcode = 0x55, + AddrMode = AddressMode.DPIndexX, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpLSR_DPIndexX = new OpDef(OpLSR) { + Opcode = 0x56, + AddrMode = AddressMode.DPIndexX, + CycDef = 6 | (int)(CycleMod.OneIfDpNonzero | CycleMod.TwoIfM0) + }; + public static readonly OpDef OpEOR_DPIndIndexYLong = new OpDef(OpEOR) { + Opcode = 0x57, + AddrMode = AddressMode.DPIndIndexYLong, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpCLI_Implied = new OpDef(OpCLI) { + Opcode = 0x58, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpEOR_AbsIndexY = new OpDef(OpEOR) { + Opcode = 0x59, + AddrMode = AddressMode.AbsIndexY, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfIndexPage) + }; + public static readonly OpDef OpPHY_StackPush = new OpDef(OpPHY) { + Opcode = 0x5a, + AddrMode = AddressMode.StackPush, + CycDef = 3 | (int)(CycleMod.OneIfX0) + }; + public static readonly OpDef OpTCD_Implied = new OpDef(OpTCD) { + Opcode = 0x5b, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpJMP_AbsLong = new OpDef(OpJML) { + Opcode = 0x5c, + AddrMode = AddressMode.AbsLong, + CycDef = 4 + }; + public static readonly OpDef OpEOR_AbsIndexX = new OpDef(OpEOR) { + Opcode = 0x5d, + AddrMode = AddressMode.AbsIndexX, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfIndexPage) + }; + public static readonly OpDef OpLSR_AbsIndexX = new OpDef(OpLSR) { + Opcode = 0x5e, + AddrMode = AddressMode.AbsIndexX, + CycDef = 7 | (int)(CycleMod.TwoIfM0 | CycleMod.MinusOneIfNoPage) + }; + public static readonly OpDef OpEOR_AbsIndexXLong = new OpDef(OpEOR) { + Opcode = 0x5f, + AddrMode = AddressMode.AbsIndexXLong, + CycDef = 5 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpRTS_StackRTS = new OpDef(OpRTS) { + Opcode = 0x60, + AddrMode = AddressMode.StackRTS, + CycDef = 6 + }; + public static readonly OpDef OpADC_DPIndexXInd = new OpDef(OpADC) { + Opcode = 0x61, + AddrMode = AddressMode.DPIndexXInd, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero | CycleMod.OneIfD1) + }; + public static readonly OpDef OpPER_StackPCRelLong = new OpDef(OpPER) { + Opcode = 0x62, + AddrMode = AddressMode.StackPCRelLong, + CycDef = 6 + }; + public static readonly OpDef OpADC_StackRel = new OpDef(OpADC) { + Opcode = 0x63, + AddrMode = AddressMode.StackRel, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfD1) + }; + public static readonly OpDef OpSTZ_DP = new OpDef(OpSTZ) { + Opcode = 0x64, + AddrMode = AddressMode.DP, + CycDef = 3 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpADC_DP = new OpDef(OpADC) { + Opcode = 0x65, + AddrMode = AddressMode.DP, + CycDef = 3 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero | CycleMod.OneIfD1) + }; + public static readonly OpDef OpROR_DP = new OpDef(OpROR) { + Opcode = 0x66, + AddrMode = AddressMode.DP, + CycDef = 5 | (int)(CycleMod.OneIfDpNonzero | CycleMod.TwoIfM0) + }; + public static readonly OpDef OpADC_DPIndLong = new OpDef(OpADC) { + Opcode = 0x67, + AddrMode = AddressMode.DPIndLong, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero | CycleMod.OneIfD1) + }; + public static readonly OpDef OpPLA_StackPull = new OpDef(OpPLA) { + Opcode = 0x68, + AddrMode = AddressMode.StackPull, + CycDef = 4 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpADC_ImmLongA = new OpDef(OpADC) { + Opcode = 0x69, + AddrMode = AddressMode.ImmLongA, + CycDef = 2 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfD1) + }; + public static readonly OpDef OpADC_Imm = new OpDef(OpADC) { + Opcode = 0x69, + AddrMode = AddressMode.Imm, + CycDef = 2 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfD1) + }; + public static readonly OpDef OpROR_Acc = new OpDef(OpROR) { + Opcode = 0x6a, + AddrMode = AddressMode.Acc, + CycDef = 2 + }; + public static readonly OpDef OpRTL_StackRTL = new OpDef(OpRTL) { + Opcode = 0x6b, + AddrMode = AddressMode.StackRTL, + CycDef = 6 + }; + public static readonly OpDef OpJMP_AbsInd = new OpDef(OpJMP) { + Opcode = 0x6c, + AddrMode = AddressMode.AbsInd, + CycDef = 5 | (int)(CycleMod.OneIf65C02) + }; + public static readonly OpDef OpADC_Abs = new OpDef(OpADC) { + Opcode = 0x6d, + AddrMode = AddressMode.Abs, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfD1) + }; + public static readonly OpDef OpROR_Abs = new OpDef(OpROR) { + Opcode = 0x6e, + AddrMode = AddressMode.Abs, + CycDef = 6 | (int)(CycleMod.TwoIfM0) + }; + public static readonly OpDef OpADC_AbsLong = new OpDef(OpADC) { + Opcode = 0x6f, + AddrMode = AddressMode.AbsLong, + CycDef = 5 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfD1) + }; + public static readonly OpDef OpBVS_PCRel = new OpDef(OpBVS) { + Opcode = 0x70, + AddrMode = AddressMode.PCRel, + CycDef = 2 | (int)(CycleMod.OneIfBranchTaken | CycleMod.OneIfBranchPage) + }; + public static readonly OpDef OpADC_DPIndIndexY = new OpDef(OpADC) { + Opcode = 0x71, + AddrMode = AddressMode.DPIndIndexY, + CycDef = 5 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero | + CycleMod.OneIfIndexPage | CycleMod.OneIfD1) + }; + public static readonly OpDef OpADC_DPInd = new OpDef(OpADC) { + Opcode = 0x72, + AddrMode = AddressMode.DPInd, + CycDef = 5 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero | CycleMod.OneIfD1) + }; + public static readonly OpDef OpADC_StackRelIndIndexY = new OpDef(OpADC) { + Opcode = 0x73, + AddrMode = AddressMode.StackRelIndIndexY, + CycDef = 7 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfD1) + }; + public static readonly OpDef OpSTZ_DPIndexX = new OpDef(OpSTZ) { + Opcode = 0x74, + AddrMode = AddressMode.DPIndexX, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpADC_DPIndexX = new OpDef(OpADC) { + Opcode = 0x75, + AddrMode = AddressMode.DPIndexX, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero | CycleMod.OneIfD1) + }; + public static readonly OpDef OpROR_DPIndexX = new OpDef(OpROR) { + Opcode = 0x76, + AddrMode = AddressMode.DPIndexX, + CycDef = 6 | (int)(CycleMod.OneIfDpNonzero | CycleMod.TwoIfM0) + }; + public static readonly OpDef OpADC_DPIndIndexYLong = new OpDef(OpADC) { + Opcode = 0x77, + AddrMode = AddressMode.DPIndIndexYLong, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero | CycleMod.OneIfD1) + }; + public static readonly OpDef OpSEI_Implied = new OpDef(OpSEI) { + Opcode = 0x78, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpADC_AbsIndexY = new OpDef(OpADC) { + Opcode = 0x79, + AddrMode = AddressMode.AbsIndexY, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfIndexPage | CycleMod.OneIfD1) + }; + public static readonly OpDef OpPLY_StackPull = new OpDef(OpPLY) { + Opcode = 0x7a, + AddrMode = AddressMode.StackPull, + CycDef = 4 | (int)(CycleMod.OneIfX0) + }; + public static readonly OpDef OpTDC_Implied = new OpDef(OpTDC) { + Opcode = 0x7b, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpJMP_AbsIndexXInd = new OpDef(OpJMP) { + Opcode = 0x7c, + AddrMode = AddressMode.AbsIndexXInd, + CycDef = 6 + }; + public static readonly OpDef OpADC_AbsIndexX = new OpDef(OpADC) { + Opcode = 0x7d, + AddrMode = AddressMode.AbsIndexX, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfIndexPage | CycleMod.OneIfD1) + }; + public static readonly OpDef OpROR_AbsIndexX = new OpDef(OpROR) { + Opcode = 0x7e, + AddrMode = AddressMode.AbsIndexX, + CycDef = 7 | (int)(CycleMod.TwoIfM0 | CycleMod.MinusOneIfNoPage) + }; + public static readonly OpDef OpADC_AbsIndexXLong = new OpDef(OpADC) { + Opcode = 0x7f, + AddrMode = AddressMode.AbsIndexXLong, + CycDef = 5 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfD1) + }; + public static readonly OpDef OpBRA_PCRel = new OpDef(OpBRA) { + Opcode = 0x80, + AddrMode = AddressMode.PCRel, + CycDef = 3 | (int)(CycleMod.OneIfBranchPage) + }; + public static readonly OpDef OpSTA_DPIndexXInd = new OpDef(OpSTA) { + Opcode = 0x81, + AddrMode = AddressMode.DPIndexXInd, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpBRL_PCRelLong = new OpDef(OpBRL) { + Opcode = 0x82, + AddrMode = AddressMode.PCRelLong, + CycDef = 4 + }; + public static readonly OpDef OpSTA_StackRel = new OpDef(OpSTA) { + Opcode = 0x83, + AddrMode = AddressMode.StackRel, + CycDef = 4 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpSTY_DP = new OpDef(OpSTY) { + Opcode = 0x84, + AddrMode = AddressMode.DP, + CycDef = 3 | (int)(CycleMod.OneIfDpNonzero | CycleMod.OneIfX0) + }; + public static readonly OpDef OpSTA_DP = new OpDef(OpSTA) { + Opcode = 0x85, + AddrMode = AddressMode.DP, + CycDef = 3 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpSTX_DP = new OpDef(OpSTX) { + Opcode = 0x86, + AddrMode = AddressMode.DP, + CycDef = 3 | (int)(CycleMod.OneIfDpNonzero | CycleMod.OneIfX0) + }; + public static readonly OpDef OpSTA_DPIndLong = new OpDef(OpSTA) { + Opcode = 0x87, + AddrMode = AddressMode.DPIndLong, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpDEY_Implied = new OpDef(OpDEY) { + Opcode = 0x88, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpBIT_ImmLongA = new OpDef(OpBIT) { + Opcode = 0x89, + AddrMode = AddressMode.ImmLongA, + FlagsAffected = FlagsAffected_Z, // special case + StatusFlagUpdater = FlagUpdater_Z, + CycDef = 2 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpBIT_Imm = new OpDef(OpBIT) { + Opcode = 0x89, + AddrMode = AddressMode.Imm, + FlagsAffected = FlagsAffected_Z, // special case + StatusFlagUpdater = FlagUpdater_Z, + CycDef = 2 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpTXA_Implied = new OpDef(OpTXA) { + Opcode = 0x8a, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpPHB_StackPush = new OpDef(OpPHB) { + Opcode = 0x8b, + AddrMode = AddressMode.StackPush, + CycDef = 3 + }; + public static readonly OpDef OpSTY_Abs = new OpDef(OpSTY) { + Opcode = 0x8c, + AddrMode = AddressMode.Abs, + CycDef = 4 | (int)(CycleMod.OneIfX0) + }; + public static readonly OpDef OpSTA_Abs = new OpDef(OpSTA) { + Opcode = 0x8d, + AddrMode = AddressMode.Abs, + CycDef = 4 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpSTX_Abs = new OpDef(OpSTX) { + Opcode = 0x8e, + AddrMode = AddressMode.Abs, + CycDef = 4 | (int)(CycleMod.OneIfX0) + }; + public static readonly OpDef OpSTA_AbsLong = new OpDef(OpSTA) { + Opcode = 0x8f, + AddrMode = AddressMode.AbsLong, + CycDef = 5 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpBCC_PCRel = new OpDef(OpBCC) { + Opcode = 0x90, + AddrMode = AddressMode.PCRel, + CycDef = 2 | (int)(CycleMod.OneIfBranchTaken | CycleMod.OneIfBranchPage) + }; + public static readonly OpDef OpSTA_DPIndIndexY = new OpDef(OpSTA) { + Opcode = 0x91, + AddrMode = AddressMode.DPIndIndexY, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpSTA_DPInd = new OpDef(OpSTA) { + Opcode = 0x92, + AddrMode = AddressMode.DPInd, + CycDef = 5 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpSTA_StackRelIndIndexY = new OpDef(OpSTA) { + Opcode = 0x93, + AddrMode = AddressMode.StackRelIndIndexY, + CycDef = 7 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpSTY_DPIndexX = new OpDef(OpSTY) { + Opcode = 0x94, + AddrMode = AddressMode.DPIndexX, + CycDef = 4 | (int)(CycleMod.OneIfDpNonzero | CycleMod.OneIfX0) + }; + public static readonly OpDef OpSTA_DPIndexX = new OpDef(OpSTA) { + Opcode = 0x95, + AddrMode = AddressMode.DPIndexX, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpSTX_DPIndexY = new OpDef(OpSTX) { + Opcode = 0x96, + AddrMode = AddressMode.DPIndexY, + CycDef = 4 | (int)(CycleMod.OneIfDpNonzero | CycleMod.OneIfX0) + }; + public static readonly OpDef OpSTA_DPIndIndexYLong = new OpDef(OpSTA) { + Opcode = 0x97, + AddrMode = AddressMode.DPIndIndexYLong, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpTYA_Implied = new OpDef(OpTYA) { + Opcode = 0x98, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpSTA_AbsIndexY = new OpDef(OpSTA) { + Opcode = 0x99, + AddrMode = AddressMode.AbsIndexY, + CycDef = 5 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpTXS_Implied = new OpDef(OpTXS) { + Opcode = 0x9a, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpTXY_Implied = new OpDef(OpTXY) { + Opcode = 0x9b, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpSTZ_Abs = new OpDef(OpSTZ) { + Opcode = 0x9c, + AddrMode = AddressMode.Abs, + CycDef = 4 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpSTA_AbsIndexX = new OpDef(OpSTA) { + Opcode = 0x9d, + AddrMode = AddressMode.AbsIndexX, + CycDef = 5 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpSTZ_AbsIndexX = new OpDef(OpSTZ) { + Opcode = 0x9e, + AddrMode = AddressMode.AbsIndexX, + CycDef = 5 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpSTA_AbsIndexXLong = new OpDef(OpSTA) { + Opcode = 0x9f, + AddrMode = AddressMode.AbsIndexXLong, + CycDef = 5 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpLDY_ImmLongXY = new OpDef(OpLDY) { + Opcode = 0xa0, + AddrMode = AddressMode.ImmLongXY, + StatusFlagUpdater = FlagUpdater_LoadImm, + CycDef = 2 | (int)(CycleMod.OneIfX0) + }; + public static readonly OpDef OpLDY_Imm = new OpDef(OpLDY) { + Opcode = 0xa0, + AddrMode = AddressMode.Imm, + StatusFlagUpdater = FlagUpdater_LoadImm, + CycDef = 2 | (int)(CycleMod.OneIfX0) + }; + public static readonly OpDef OpLDA_DPIndexXInd = new OpDef(OpLDA) { + Opcode = 0xa1, + AddrMode = AddressMode.DPIndexXInd, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpLDX_ImmLongXY = new OpDef(OpLDX) { + Opcode = 0xa2, + AddrMode = AddressMode.ImmLongXY, + StatusFlagUpdater = FlagUpdater_LoadImm, + CycDef = 2 | (int)(CycleMod.OneIfX0) + }; + public static readonly OpDef OpLDX_Imm = new OpDef(OpLDX) { + Opcode = 0xa2, + AddrMode = AddressMode.Imm, + StatusFlagUpdater = FlagUpdater_LoadImm, + CycDef = 2 | (int)(CycleMod.OneIfX0) + }; + public static readonly OpDef OpLDA_StackRel = new OpDef(OpLDA) { + Opcode = 0xa3, + AddrMode = AddressMode.StackRel, + CycDef = 4 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpLDY_DP = new OpDef(OpLDY) { + Opcode = 0xa4, + AddrMode = AddressMode.DP, + CycDef = 3 | (int)(CycleMod.OneIfDpNonzero | CycleMod.OneIfX0) + }; + public static readonly OpDef OpLDA_DP = new OpDef(OpLDA) { + Opcode = 0xa5, + AddrMode = AddressMode.DP, + CycDef = 3 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpLDX_DP = new OpDef(OpLDX) { + Opcode = 0xa6, + AddrMode = AddressMode.DP, + CycDef = 3 | (int)(CycleMod.OneIfDpNonzero | CycleMod.OneIfX0) + }; + public static readonly OpDef OpLDA_DPIndLong = new OpDef(OpLDA) { + Opcode = 0xa7, + AddrMode = AddressMode.DPIndLong, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpTAY_Implied = new OpDef(OpTAY) { + Opcode = 0xa8, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpLDA_ImmLongA = new OpDef(OpLDA) { // 16-bit CPU + Opcode = 0xa9, + AddrMode = AddressMode.ImmLongA, + StatusFlagUpdater = FlagUpdater_LoadImm, + CycDef = 2 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpLDA_Imm = new OpDef(OpLDA) { // 8-bit CPU + Opcode = 0xa9, + AddrMode = AddressMode.Imm, + StatusFlagUpdater = FlagUpdater_LoadImm, + CycDef = 2 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpTAX_Implied = new OpDef(OpTAX) { + Opcode = 0xaa, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpPLB_StackPull = new OpDef(OpPLB) { + Opcode = 0xab, + AddrMode = AddressMode.StackPull, + CycDef = 4 + }; + public static readonly OpDef OpLDY_Abs = new OpDef(OpLDY) { + Opcode = 0xac, + AddrMode = AddressMode.Abs, + CycDef = 4 | (int)(CycleMod.OneIfX0) + }; + public static readonly OpDef OpLDA_Abs = new OpDef(OpLDA) { + Opcode = 0xad, + AddrMode = AddressMode.Abs, + CycDef = 4 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpLDX_Abs = new OpDef(OpLDX) { + Opcode = 0xae, + AddrMode = AddressMode.Abs, + CycDef = 4 | (int)(CycleMod.OneIfX0) + }; + public static readonly OpDef OpLDA_AbsLong = new OpDef(OpLDA) { + Opcode = 0xaf, + AddrMode = AddressMode.AbsLong, + CycDef = 5 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpBCS_PCRel = new OpDef(OpBCS) { + Opcode = 0xb0, + AddrMode = AddressMode.PCRel, + CycDef = 2 | (int)(CycleMod.OneIfBranchTaken | CycleMod.OneIfBranchPage) + }; + public static readonly OpDef OpLDA_DPIndIndexY = new OpDef(OpLDA) { + Opcode = 0xb1, + AddrMode = AddressMode.DPIndIndexY, + CycDef = 5 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero | CycleMod.OneIfIndexPage) + }; + public static readonly OpDef OpLDA_DPInd = new OpDef(OpLDA) { + Opcode = 0xb2, + AddrMode = AddressMode.DPInd, + CycDef = 5 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpLDA_StackRelIndIndexY = new OpDef(OpLDA) { + Opcode = 0xb3, + AddrMode = AddressMode.StackRelIndIndexY, + CycDef = 7 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpLDY_DPIndexX = new OpDef(OpLDY) { + Opcode = 0xb4, + AddrMode = AddressMode.DPIndexX, + CycDef = 4 | (int)(CycleMod.OneIfDpNonzero | CycleMod.OneIfX0) + }; + public static readonly OpDef OpLDA_DPIndexX = new OpDef(OpLDA) { + Opcode = 0xb5, + AddrMode = AddressMode.DPIndexX, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpLDX_DPIndexY = new OpDef(OpLDX) { + Opcode = 0xb6, + AddrMode = AddressMode.DPIndexY, + CycDef = 4 | (int)(CycleMod.OneIfDpNonzero | CycleMod.OneIfX0) + }; + public static readonly OpDef OpLDA_DPIndIndexYLong = new OpDef(OpLDA) { + Opcode = 0xb7, + AddrMode = AddressMode.DPIndIndexYLong, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpCLV_Implied = new OpDef(OpCLV) { + Opcode = 0xb8, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpLDA_AbsIndexY = new OpDef(OpLDA) { + Opcode = 0xb9, + AddrMode = AddressMode.AbsIndexY, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfIndexPage) + }; + public static readonly OpDef OpTSX_Implied = new OpDef(OpTSX) { + Opcode = 0xba, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpTYX_Implied = new OpDef(OpTYX) { + Opcode = 0xbb, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpLDY_AbsIndexX = new OpDef(OpLDY) { + Opcode = 0xbc, + AddrMode = AddressMode.AbsIndexX, + CycDef = 4 | (int)(CycleMod.OneIfIndexPage | CycleMod.OneIfX0) + }; + public static readonly OpDef OpLDA_AbsIndexX = new OpDef(OpLDA) { + Opcode = 0xbd, + AddrMode = AddressMode.AbsIndexX, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfIndexPage) + }; + public static readonly OpDef OpLDX_AbsIndexY = new OpDef(OpLDX) { + Opcode = 0xbe, + AddrMode = AddressMode.AbsIndexY, + CycDef = 4 | (int)(CycleMod.OneIfIndexPage | CycleMod.OneIfX0) + }; + public static readonly OpDef OpLDA_AbsIndexXLong = new OpDef(OpLDA) { + Opcode = 0xbf, + AddrMode = AddressMode.AbsIndexXLong, + CycDef = 5 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpCPY_ImmLongXY = new OpDef(OpCPY) { + Opcode = 0xc0, + AddrMode = AddressMode.ImmLongXY, + CycDef = 2 | (int)(CycleMod.OneIfX0) + }; + public static readonly OpDef OpCPY_Imm = new OpDef(OpCPY) { + Opcode = 0xc0, + AddrMode = AddressMode.Imm, + CycDef = 2 | (int)(CycleMod.OneIfX0) + }; + public static readonly OpDef OpCMP_DPIndexXInd = new OpDef(OpCMP) { + Opcode = 0xc1, + AddrMode = AddressMode.DPIndexXInd, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpREP_Imm = new OpDef(OpREP) { + Opcode = 0xc2, + AddrMode = AddressMode.Imm, + CycDef = 3 + }; + public static readonly OpDef OpCMP_StackRel = new OpDef(OpCMP) { + Opcode = 0xc3, + AddrMode = AddressMode.StackRel, + CycDef = 4 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpCPY_DP = new OpDef(OpCPY) { + Opcode = 0xc4, + AddrMode = AddressMode.DP, + CycDef = 3 | (int)(CycleMod.OneIfDpNonzero | CycleMod.OneIfX0) + }; + public static readonly OpDef OpCMP_DP = new OpDef(OpCMP) { + Opcode = 0xc5, + AddrMode = AddressMode.DP, + CycDef = 3 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpDEC_DP = new OpDef(OpDEC) { + Opcode = 0xc6, + AddrMode = AddressMode.DP, + CycDef = 5 | (int)(CycleMod.OneIfDpNonzero | CycleMod.TwoIfM0) + }; + public static readonly OpDef OpCMP_DPIndLong = new OpDef(OpCMP) { + Opcode = 0xc7, + AddrMode = AddressMode.DPIndLong, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpINY_Implied = new OpDef(OpINY) { + Opcode = 0xc8, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpCMP_ImmLongA = new OpDef(OpCMP) { + Opcode = 0xc9, + AddrMode = AddressMode.ImmLongA, + CycDef = 2 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpCMP_Imm = new OpDef(OpCMP) { + Opcode = 0xc9, + AddrMode = AddressMode.Imm, + CycDef = 2 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpDEX_Implied = new OpDef(OpDEX) { + Opcode = 0xca, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpWAI_Implied = new OpDef(OpWAI) { + Opcode = 0xcb, + AddrMode = AddressMode.Implied, + CycDef = 3 // 3 to shut down, more to restart + }; + public static readonly OpDef OpCPY_Abs = new OpDef(OpCPY) { + Opcode = 0xcc, + AddrMode = AddressMode.Abs, + CycDef = 4 | (int)(CycleMod.OneIfX0) + }; + public static readonly OpDef OpCMP_Abs = new OpDef(OpCMP) { + Opcode = 0xcd, + AddrMode = AddressMode.Abs, + CycDef = 4 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpDEC_Abs = new OpDef(OpDEC) { + Opcode = 0xce, + AddrMode = AddressMode.Abs, + CycDef = 6 | (int)(CycleMod.TwoIfM0) + }; + public static readonly OpDef OpCMP_AbsLong = new OpDef(OpCMP) { + Opcode = 0xcf, + AddrMode = AddressMode.AbsLong, + CycDef = 5 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpBNE_PCRel = new OpDef(OpBNE) { + Opcode = 0xd0, + AddrMode = AddressMode.PCRel, + CycDef = 2 | (int)(CycleMod.OneIfBranchTaken | CycleMod.OneIfBranchPage) + }; + public static readonly OpDef OpCMP_DPIndIndexY = new OpDef(OpCMP) { + Opcode = 0xd1, + AddrMode = AddressMode.DPIndIndexY, + CycDef = 5 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero | CycleMod.OneIfIndexPage) + }; + public static readonly OpDef OpCMP_DPInd = new OpDef(OpCMP) { + Opcode = 0xd2, + AddrMode = AddressMode.DPInd, + CycDef = 5 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpCMP_StackRelIndIndexY = new OpDef(OpCMP) { + Opcode = 0xd3, + AddrMode = AddressMode.StackRelIndIndexY, + CycDef = 7 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpPEI_StackDPInd = new OpDef(OpPEI) { + Opcode = 0xd4, + AddrMode = AddressMode.StackDPInd, + CycDef = 6 | (int)(CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpCMP_DPIndexX = new OpDef(OpCMP) { + Opcode = 0xd5, + AddrMode = AddressMode.DPIndexX, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpDEC_DPIndexX = new OpDef(OpDEC) { + Opcode = 0xd6, + AddrMode = AddressMode.DPIndexX, + CycDef = 6 | (int)(CycleMod.OneIfDpNonzero | CycleMod.TwoIfM0) + }; + public static readonly OpDef OpCMP_DPIndIndexYLong = new OpDef(OpCMP) { + Opcode = 0xd7, + AddrMode = AddressMode.DPIndIndexYLong, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero) + }; + public static readonly OpDef OpCLD_Implied = new OpDef(OpCLD) { + Opcode = 0xd8, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpCMP_AbsIndexY = new OpDef(OpCMP) { + Opcode = 0xd9, + AddrMode = AddressMode.AbsIndexY, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfIndexPage) + }; + public static readonly OpDef OpPHX_StackPush = new OpDef(OpPHX) { + Opcode = 0xda, + AddrMode = AddressMode.StackPush, + CycDef = 3 | (int)(CycleMod.OneIfX0) + }; + public static readonly OpDef OpSTP_Implied = new OpDef(OpSTP) { + Opcode = 0xdb, + AddrMode = AddressMode.Implied, + CycDef = 3 // 3 to shut down, more to reset out + }; + public static readonly OpDef OpJMP_AbsIndLong = new OpDef(OpJML) { + Opcode = 0xdc, + AddrMode = AddressMode.AbsIndLong, + CycDef = 6 + }; + public static readonly OpDef OpCMP_AbsIndexX = new OpDef(OpCMP) { + Opcode = 0xdd, + AddrMode = AddressMode.AbsIndexX, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfIndexPage) + }; + public static readonly OpDef OpDEC_AbsIndexX = new OpDef(OpDEC) { + Opcode = 0xde, + AddrMode = AddressMode.AbsIndexX, + CycDef = 7 | (int)(CycleMod.TwoIfM0 | CycleMod.MinusOneIfNoPage) + }; + public static readonly OpDef OpCMP_AbsIndexXLong = new OpDef(OpCMP) { + Opcode = 0xdf, + AddrMode = AddressMode.AbsIndexXLong, + CycDef = 5 | (int)(CycleMod.OneIfM0) + }; + public static readonly OpDef OpCPX_ImmLongXY = new OpDef(OpCPX) { + Opcode = 0xe0, + AddrMode = AddressMode.ImmLongXY, + CycDef = 2 | (int)(CycleMod.OneIfX0) + }; + public static readonly OpDef OpCPX_Imm = new OpDef(OpCPX) { + Opcode = 0xe0, + AddrMode = AddressMode.Imm, + CycDef = 2 | (int)(CycleMod.OneIfX0) + }; + public static readonly OpDef OpSBC_DPIndexXInd = new OpDef(OpSBC) { + Opcode = 0xe1, + AddrMode = AddressMode.DPIndexXInd, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero | CycleMod.OneIfD1) + }; + public static readonly OpDef OpSEP_Imm = new OpDef(OpSEP) { + Opcode = 0xe2, + AddrMode = AddressMode.Imm, + CycDef = 3 + }; + public static readonly OpDef OpSBC_StackRel = new OpDef(OpSBC) { + Opcode = 0xe3, + AddrMode = AddressMode.StackRel, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfD1) + }; + public static readonly OpDef OpCPX_DP = new OpDef(OpCPX) { + Opcode = 0xe4, + AddrMode = AddressMode.DP, + CycDef = 3 | (int)(CycleMod.OneIfDpNonzero | CycleMod.OneIfX0) + }; + public static readonly OpDef OpSBC_DP = new OpDef(OpSBC) { + Opcode = 0xe5, + AddrMode = AddressMode.DP, + CycDef = 3 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero | CycleMod.OneIfD1) + }; + public static readonly OpDef OpINC_DP = new OpDef(OpINC) { + Opcode = 0xe6, + AddrMode = AddressMode.DP, + CycDef = 5 | (int)(CycleMod.OneIfDpNonzero | CycleMod.TwoIfM0) + }; + public static readonly OpDef OpSBC_DPIndLong = new OpDef(OpSBC) { + Opcode = 0xe7, + AddrMode = AddressMode.DPIndLong, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero | CycleMod.OneIfD1) + }; + public static readonly OpDef OpINX_Implied = new OpDef(OpINX) { + Opcode = 0xe8, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpSBC_ImmLongA = new OpDef(OpSBC) { + Opcode = 0xe9, + AddrMode = AddressMode.ImmLongA, + CycDef = 2 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfD1) + }; + public static readonly OpDef OpSBC_Imm = new OpDef(OpSBC) { + Opcode = 0xe9, + AddrMode = AddressMode.Imm, + CycDef = 2 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfD1) + }; + public static readonly OpDef OpNOP_Implied = new OpDef(OpNOP) { + Opcode = 0xea, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpXBA_Implied = new OpDef(OpXBA) { + Opcode = 0xeb, + AddrMode = AddressMode.Implied, + CycDef = 3 + }; + public static readonly OpDef OpCPX_Abs = new OpDef(OpCPX) { + Opcode = 0xec, + AddrMode = AddressMode.Abs, + CycDef = 4 | (int)(CycleMod.OneIfX0) + }; + public static readonly OpDef OpSBC_Abs = new OpDef(OpSBC) { + Opcode = 0xed, + AddrMode = AddressMode.Abs, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfD1) + }; + public static readonly OpDef OpINC_Abs = new OpDef(OpINC) { + Opcode = 0xee, + AddrMode = AddressMode.Abs, + CycDef = 6 | (int)(CycleMod.TwoIfM0) + }; + public static readonly OpDef OpSBC_AbsLong = new OpDef(OpSBC) { + Opcode = 0xef, + AddrMode = AddressMode.AbsLong, + CycDef = 5 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfD1) + }; + public static readonly OpDef OpBEQ_PCRel = new OpDef(OpBEQ) { + Opcode = 0xf0, + AddrMode = AddressMode.PCRel, + CycDef = 2 | (int)(CycleMod.OneIfBranchTaken | CycleMod.OneIfBranchPage) + }; + public static readonly OpDef OpSBC_DPIndIndexY = new OpDef(OpSBC) { + Opcode = 0xf1, + AddrMode = AddressMode.DPIndIndexY, + CycDef = 5 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero | + CycleMod.OneIfIndexPage | CycleMod.OneIfD1) + }; + public static readonly OpDef OpSBC_DPInd = new OpDef(OpSBC) { + Opcode = 0xf2, + AddrMode = AddressMode.DPInd, + CycDef = 5 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero | CycleMod.OneIfD1) + }; + public static readonly OpDef OpSBC_StackRelIndIndexY = new OpDef(OpSBC) { + Opcode = 0xf3, + AddrMode = AddressMode.StackRelIndIndexY, + CycDef = 7 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfD1) + }; + public static readonly OpDef OpPEA_StackAbs = new OpDef(OpPEA) { + Opcode = 0xf4, + AddrMode = AddressMode.StackAbs, + CycDef = 5 + }; + public static readonly OpDef OpSBC_DPIndexX = new OpDef(OpSBC) { + Opcode = 0xf5, + AddrMode = AddressMode.DPIndexX, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero | CycleMod.OneIfD1) + }; + public static readonly OpDef OpINC_DPIndexX = new OpDef(OpINC) { + Opcode = 0xf6, + AddrMode = AddressMode.DPIndexX, + CycDef = 6 | (int)(CycleMod.OneIfDpNonzero | CycleMod.TwoIfM0) + }; + public static readonly OpDef OpSBC_DPIndIndexYLong = new OpDef(OpSBC) { + Opcode = 0xf7, + AddrMode = AddressMode.DPIndIndexYLong, + CycDef = 6 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfDpNonzero | CycleMod.OneIfD1) + }; + public static readonly OpDef OpSED_Implied = new OpDef(OpSED) { + Opcode = 0xf8, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpSBC_AbsIndexY = new OpDef(OpSBC) { + Opcode = 0xf9, + AddrMode = AddressMode.AbsIndexY, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfIndexPage | CycleMod.OneIfD1) + }; + public static readonly OpDef OpPLX_StackPull = new OpDef(OpPLX) { + Opcode = 0xfa, + AddrMode = AddressMode.StackPull, + CycDef = 4 | (int)(CycleMod.OneIfX0) + }; + public static readonly OpDef OpXCE_Implied = new OpDef(OpXCE) { + Opcode = 0xfb, + AddrMode = AddressMode.Implied, + CycDef = 2 + }; + public static readonly OpDef OpJSR_AbsIndexXInd = new OpDef(OpJSR) { + Opcode = 0xfc, + AddrMode = AddressMode.AbsIndexXInd, + CycDef = 8 + }; + public static readonly OpDef OpSBC_AbsIndexX = new OpDef(OpSBC) { + Opcode = 0xfd, + AddrMode = AddressMode.AbsIndexX, + CycDef = 4 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfIndexPage | CycleMod.OneIfD1) + }; + public static readonly OpDef OpINC_AbsIndexX = new OpDef(OpINC) { + Opcode = 0xfe, + AddrMode = AddressMode.AbsIndexX, + CycDef = 7 | (int)(CycleMod.TwoIfM0 | CycleMod.MinusOneIfNoPage) + }; + public static readonly OpDef OpSBC_AbsIndexXLong = new OpDef(OpSBC) { + Opcode = 0xff, + AddrMode = AddressMode.AbsIndexXLong, + CycDef = 5 | (int)(CycleMod.OneIfM0 | CycleMod.OneIfD1) + }; + + #endregion 65816 Instructions + + #region Undocumented 6502 + + // ====================================================================================== + // Undocumented 6502 instructions. + // + // There are 151 defined opcodes. The rest officially have undefined behavior. In + // most cases it's pretty stable, in others the behavior can differ between CPU + // variants. + // + // There is no generally agreed-upon set of mnemonics for these instructions. The + // mnemonics "XAS" and "AXS" sometimes mean one thing and sometimes another. I've + // chosen a set that seems reasonable. If a consensus is reached, the mnemonics + // are easy enough to change throughout (yay Visual Studio refactoring). + // + // References: + // http://nesdev.com/undocumented_opcodes.txt + // http://visual6502.org/wiki/index.php?title=6502_all_256_Opcodes + // http://www.ffd2.com/fridge/docs/6502-NMOS.extra.opcodes + // + + private static OpDef OpANC = new OpDef() { + IsUndocumented = true, + Mnemonic = OpName.ANC, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZC, + StatusFlagUpdater = FlagUpdater_NZC + }; + private static OpDef OpANE = new OpDef() { + IsUndocumented = true, + Mnemonic = OpName.ANE, + Effect = FlowEffect.Cont + }; + private static OpDef OpARR = new OpDef() { + IsUndocumented = true, + Mnemonic = OpName.ARR, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NVZC, + StatusFlagUpdater = FlagUpdater_NVZC + }; + private static OpDef OpASR = new OpDef() { + IsUndocumented = true, + Mnemonic = OpName.ASR, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZC, + StatusFlagUpdater = FlagUpdater_NZC + }; + private static OpDef OpDCP = new OpDef() { + IsUndocumented = true, + Mnemonic = OpName.DCP, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_C, + StatusFlagUpdater = FlagUpdater_C + }; + private static OpDef OpDOP = new OpDef() { + IsUndocumented = true, + Mnemonic = OpName.DOP, + Effect = FlowEffect.Cont + }; + private static OpDef OpHLT = new OpDef() { + IsUndocumented = true, + Mnemonic = OpName.HLT, + Effect = FlowEffect.NoCont + }; + private static OpDef OpISB = new OpDef() { + IsUndocumented = true, + Mnemonic = OpName.ISB, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NVZC, + StatusFlagUpdater = FlagUpdater_NVZC + }; + private static OpDef OpLAE = new OpDef() { + IsUndocumented = true, + Mnemonic = OpName.LAE, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpLAX = new OpDef() { + IsUndocumented = true, + Mnemonic = OpName.LAX, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpLXA = new OpDef() { + IsUndocumented = true, + Mnemonic = OpName.LXA, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpRLA = new OpDef() { + IsUndocumented = true, + Mnemonic = OpName.RLA, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZC, + StatusFlagUpdater = FlagUpdater_NZC + }; + private static OpDef OpRRA = new OpDef() { + IsUndocumented = true, + Mnemonic = OpName.RRA, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NVZC, + StatusFlagUpdater = FlagUpdater_NVZC + }; + private static OpDef OpSAX = new OpDef() { + IsUndocumented = true, + Mnemonic = OpName.SAX, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZ, + StatusFlagUpdater = FlagUpdater_NZ + }; + private static OpDef OpSBX = new OpDef() { + IsUndocumented = true, + Mnemonic = OpName.SBX, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZC, + StatusFlagUpdater = FlagUpdater_NZC + }; + private static OpDef OpSHA = new OpDef() { + IsUndocumented = true, + Mnemonic = OpName.SHA, + Effect = FlowEffect.Cont + }; + private static OpDef OpSHS = new OpDef() { + IsUndocumented = true, + Mnemonic = OpName.SHS, + Effect = FlowEffect.Cont + }; + private static OpDef OpSHX = new OpDef() { + IsUndocumented = true, + Mnemonic = OpName.SHX, + Effect = FlowEffect.Cont + }; + private static OpDef OpSHY = new OpDef() { + IsUndocumented = true, + Mnemonic = OpName.SHY, + Effect = FlowEffect.Cont + }; + private static OpDef OpSLO = new OpDef() { + IsUndocumented = true, + Mnemonic = OpName.SLO, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZC, + StatusFlagUpdater = FlagUpdater_NZC + }; + private static OpDef OpSRE = new OpDef() { + IsUndocumented = true, + Mnemonic = OpName.SRE, + Effect = FlowEffect.Cont, + FlagsAffected = FlagsAffected_NZC, + StatusFlagUpdater = FlagUpdater_NZC + }; + private static OpDef OpTOP = new OpDef() { + IsUndocumented = true, + Mnemonic = OpName.TOP, + Effect = FlowEffect.Cont + }; + public static readonly OpDef OpSLO_DPIndexXInd = new OpDef(OpSLO) { + Opcode = 0x03, + AddrMode = AddressMode.DPIndexXInd, + CycDef = 8 + }; + public static readonly OpDef OpSLO_DP = new OpDef(OpSLO) { + Opcode = 0x07, + AddrMode = AddressMode.DP, + CycDef = 5 + }; + public static readonly OpDef OpSLO_Absolute = new OpDef(OpSLO) { + Opcode = 0x0f, + AddrMode = AddressMode.Abs, + CycDef = 6 + }; + public static readonly OpDef OpSLO_DPIndIndexY = new OpDef(OpSLO) { + Opcode = 0x13, + AddrMode = AddressMode.DPIndIndexY, + CycDef = 8 + }; + public static readonly OpDef OpSLO_DPIndexX = new OpDef(OpSLO) { + Opcode = 0x17, + AddrMode = AddressMode.DPIndexX, + CycDef = 6 + }; + public static readonly OpDef OpSLO_AbsIndexY = new OpDef(OpSLO) { + Opcode = 0x1b, + AddrMode = AddressMode.AbsIndexY, + CycDef = 7 + }; + public static readonly OpDef OpSLO_AbsIndexX = new OpDef(OpSLO) { + Opcode = 0x1f, + AddrMode = AddressMode.AbsIndexX, + CycDef = 7 + }; + public static readonly OpDef OpRLA_DPIndexXInd = new OpDef(OpRLA) { + Opcode = 0x23, + AddrMode = AddressMode.DPIndexXInd, + CycDef = 8 + }; + public static readonly OpDef OpRLA_DP = new OpDef(OpRLA) { + Opcode = 0x27, + AddrMode = AddressMode.DP, + CycDef = 5 + }; + public static readonly OpDef OpRLA_Absolute = new OpDef(OpRLA) { + Opcode = 0x2f, + AddrMode = AddressMode.Abs, + CycDef = 6 + }; + public static readonly OpDef OpRLA_DPIndIndexY = new OpDef(OpRLA) { + Opcode = 0x33, + AddrMode = AddressMode.DPIndIndexY, + CycDef = 8 + }; + public static readonly OpDef OpRLA_DPIndexX = new OpDef(OpRLA) { + Opcode = 0x37, + AddrMode = AddressMode.DPIndexX, + CycDef = 6 + }; + public static readonly OpDef OpRLA_AbsIndexY = new OpDef(OpRLA) { + Opcode = 0x3b, + AddrMode = AddressMode.AbsIndexY, + CycDef = 7 + }; + public static readonly OpDef OpRLA_AbsIndexX = new OpDef(OpRLA) { + Opcode = 0x3f, + AddrMode = AddressMode.AbsIndexX, + CycDef = 7 + }; + public static readonly OpDef OpSRE_DPIndexXInd = new OpDef(OpSRE) { + Opcode = 0x43, + AddrMode = AddressMode.DPIndexXInd, + CycDef = 8 + }; + public static readonly OpDef OpSRE_DP = new OpDef(OpSRE) { + Opcode = 0x47, + AddrMode = AddressMode.DP, + CycDef = 5 + }; + public static readonly OpDef OpSRE_Absolute = new OpDef(OpSRE) { + Opcode = 0x4f, + AddrMode = AddressMode.Abs, + CycDef = 6 + }; + public static readonly OpDef OpSRE_DPIndIndexY = new OpDef(OpSRE) { + Opcode = 0x53, + AddrMode = AddressMode.DPIndIndexY, + CycDef = 8 + }; + public static readonly OpDef OpSRE_DPIndexX = new OpDef(OpSRE) { + Opcode = 0x57, + AddrMode = AddressMode.DPIndexX, + CycDef = 6 + }; + public static readonly OpDef OpSRE_AbsIndexY = new OpDef(OpSRE) { + Opcode = 0x5b, + AddrMode = AddressMode.AbsIndexY, + CycDef = 7 + }; + public static readonly OpDef OpSRE_AbsIndexX = new OpDef(OpSRE) { + Opcode = 0x5f, + AddrMode = AddressMode.AbsIndexX, + CycDef = 7 + }; + public static readonly OpDef OpRRA_DPIndexXInd = new OpDef(OpRRA) { + Opcode = 0x63, + AddrMode = AddressMode.DPIndexXInd, + CycDef = 8 + }; + public static readonly OpDef OpRRA_DP = new OpDef(OpRRA) { + Opcode = 0x67, + AddrMode = AddressMode.DP, + CycDef = 5 + }; + public static readonly OpDef OpRRA_Absolute = new OpDef(OpRRA) { + Opcode = 0x6f, + AddrMode = AddressMode.Abs, + CycDef = 6 + }; + public static readonly OpDef OpRRA_DPIndIndexY = new OpDef(OpRRA) { + Opcode = 0x73, + AddrMode = AddressMode.DPIndIndexY, + CycDef = 8 + }; + public static readonly OpDef OpRRA_DPIndexX = new OpDef(OpRRA) { + Opcode = 0x77, + AddrMode = AddressMode.DPIndexX, + CycDef = 6 + }; + public static readonly OpDef OpRRA_AbsIndexY = new OpDef(OpRRA) { + Opcode = 0x7b, + AddrMode = AddressMode.AbsIndexY, + CycDef = 7 + }; + public static readonly OpDef OpRRA_AbsIndexX = new OpDef(OpRRA) { + Opcode = 0x7f, + AddrMode = AddressMode.AbsIndexX, + CycDef = 7 + }; + public static readonly OpDef OpSAX_DPIndexXInd = new OpDef(OpSAX) { + Opcode = 0x83, + AddrMode = AddressMode.DPIndexXInd, + CycDef = 6 + }; + public static readonly OpDef OpSAX_DP = new OpDef(OpSAX) { + Opcode = 0x87, + AddrMode = AddressMode.DP, + CycDef = 3 + }; + public static readonly OpDef OpSAX_Absolute = new OpDef(OpSAX) { + Opcode = 0x8f, + AddrMode = AddressMode.Abs, + CycDef = 4 + }; + public static readonly OpDef OpSAX_DPIndexY = new OpDef(OpSAX) { + Opcode = 0x97, + AddrMode = AddressMode.DPIndexY, + CycDef = 4 + }; + public static readonly OpDef OpLAX_DPIndexXInd = new OpDef(OpLAX) { + Opcode = 0xa3, + AddrMode = AddressMode.DPIndexXInd, + CycDef = 6 + }; + public static readonly OpDef OpLAX_DP = new OpDef(OpLAX) { + Opcode = 0xa7, + AddrMode = AddressMode.DP, + CycDef = 3 + }; + public static readonly OpDef OpLAX_Absolute = new OpDef(OpLAX) { + Opcode = 0xaf, + AddrMode = AddressMode.Abs, + CycDef = 4 + }; + public static readonly OpDef OpLAX_DPIndIndexY = new OpDef(OpLAX) { + Opcode = 0xb3, + AddrMode = AddressMode.DPIndIndexY, + CycDef = 5 | (int)(CycleMod.OneIfIndexPage) + }; + public static readonly OpDef OpLAX_DPIndexY = new OpDef(OpLAX) { + Opcode = 0xb7, + AddrMode = AddressMode.DPIndexY, + CycDef = 4 + }; + public static readonly OpDef OpLAX_AbsIndexY = new OpDef(OpLAX) { + Opcode = 0xbf, + AddrMode = AddressMode.AbsIndexY, + CycDef = 4 | (int)(CycleMod.OneIfIndexPage) + }; + public static readonly OpDef OpDCP_DPIndexXInd = new OpDef(OpDCP) { + Opcode = 0xc3, + AddrMode = AddressMode.DPIndexXInd, + CycDef = 8 + }; + public static readonly OpDef OpDCP_DP = new OpDef(OpDCP) { + Opcode = 0xc7, + AddrMode = AddressMode.DP, + CycDef = 5 + }; + public static readonly OpDef OpDCP_Abs = new OpDef(OpDCP) { + Opcode = 0xcf, + AddrMode = AddressMode.Abs, + CycDef = 6 + }; + public static readonly OpDef OpDCP_DPIndIndexY = new OpDef(OpDCP) { + Opcode = 0xd3, + AddrMode = AddressMode.DPIndIndexY, + CycDef = 8 + }; + public static readonly OpDef OpDCP_DPIndexX = new OpDef(OpDCP) { + Opcode = 0xd7, + AddrMode = AddressMode.DPIndexX, + CycDef = 6 + }; + public static readonly OpDef OpDCP_AbsIndexY = new OpDef(OpDCP) { + Opcode = 0xdb, + AddrMode = AddressMode.AbsIndexY, + CycDef = 7 + }; + public static readonly OpDef OpDCP_AbsIndexX = new OpDef(OpDCP) { + Opcode = 0xdf, + AddrMode = AddressMode.AbsIndexX, + CycDef = 7 + }; + public static readonly OpDef OpISB_DPIndexXInd = new OpDef(OpISB) { + Opcode = 0xe3, + AddrMode = AddressMode.DPIndexXInd, + CycDef = 8 + }; + public static readonly OpDef OpISB_DP = new OpDef(OpISB) { + Opcode = 0xe7, + AddrMode = AddressMode.DP, + CycDef = 5 + }; + public static readonly OpDef OpISB_Abs = new OpDef(OpISB) { + Opcode = 0xef, + AddrMode = AddressMode.Abs, + CycDef = 6 + }; + public static readonly OpDef OpISB_DPIndIndexY = new OpDef(OpISB) { + Opcode = 0xf3, + AddrMode = AddressMode.DPIndIndexY, + CycDef = 8 + }; + public static readonly OpDef OpISB_DPIndexX = new OpDef(OpISB) { + Opcode = 0xf7, + AddrMode = AddressMode.DPIndexX, + CycDef = 6 + }; + public static readonly OpDef OpISB_AbsIndexY = new OpDef(OpISB) { + Opcode = 0xfb, + AddrMode = AddressMode.AbsIndexY, + CycDef = 7 + }; + public static readonly OpDef OpISB_AbsIndexX = new OpDef(OpISB) { + Opcode = 0xff, + AddrMode = AddressMode.AbsIndexX, + CycDef = 7 + }; + public static readonly OpDef OpASR_Imm = new OpDef(OpASR) { + Opcode = 0x4b, + AddrMode = AddressMode.Imm, + CycDef = 2 + }; + public static readonly OpDef OpARR_Imm = new OpDef(OpARR) { + Opcode = 0x6b, + AddrMode = AddressMode.Imm, + CycDef = 2 + }; + public static readonly OpDef OpANE_Imm = new OpDef(OpANE) { + Opcode = 0x8b, + AddrMode = AddressMode.Imm, + CycDef = 2 + }; + public static readonly OpDef OpLXA_Imm = new OpDef(OpLXA) { + Opcode = 0xab, + AddrMode = AddressMode.Imm, + CycDef = 2 + }; + public static readonly OpDef OpSBX_Imm = new OpDef(OpSBX) { + Opcode = 0xcb, + AddrMode = AddressMode.Imm, + CycDef = 2 + }; + public static readonly OpDef OpDOP_Imm = new OpDef(OpDOP) { + // multiple opcodes + AddrMode = AddressMode.Imm, + CycDef = 2 + }; + public static readonly OpDef OpDOP_DP = new OpDef(OpDOP) { + // multiple opcodes + AddrMode = AddressMode.DP, + CycDef = 3 + }; + public static readonly OpDef OpDOP_DPIndexX = new OpDef(OpDOP) { + // multiple opcodes + AddrMode = AddressMode.DPIndexX, + CycDef = 4 + }; + public static readonly OpDef OpTOP_Abs = new OpDef(OpTOP) { + Opcode = 0x0c, + AddrMode = AddressMode.Abs, + CycDef = 4 + }; + public static readonly OpDef OpTOP_AbsIndeX = new OpDef(OpTOP) { + // multiple opcodes + AddrMode = AddressMode.AbsIndexX, + CycDef = 4 | (int)(CycleMod.OneIfIndexPage) + }; + public static readonly OpDef OpHLT_Implied = new OpDef(OpHLT) { + // multiple opcodes + AddrMode = AddressMode.Implied, + CycDef = 1 + }; + public static readonly OpDef OpSHS_AbsIndexY = new OpDef(OpSHS) { + Opcode = 0x9b, + AddrMode = AddressMode.AbsIndexY, + CycDef = 5 + }; + public static readonly OpDef OpSHY_AbsIndexX = new OpDef(OpSHY) { + Opcode = 0x9c, + AddrMode = AddressMode.AbsIndexX, + CycDef = 5 + }; + public static readonly OpDef OpSHX_AbsIndexY = new OpDef(OpSHX) { + Opcode = 0x9e, + AddrMode = AddressMode.AbsIndexY, + CycDef = 5 + }; + public static readonly OpDef OpSHA_DPIndIndexY = new OpDef(OpSHA) { + Opcode = 0x93, + AddrMode = AddressMode.DPIndIndexY, + CycDef = 6 + }; + public static readonly OpDef OpSHA_AbsIndexY = new OpDef(OpSHA) { + Opcode = 0x9f, + AddrMode = AddressMode.AbsIndexY, + CycDef = 5 + }; + public static readonly OpDef OpANC_Imm = new OpDef(OpANC) { + // multiple opcodes + AddrMode = AddressMode.Imm, + CycDef = 2 + }; + public static readonly OpDef OpLAE_AbsIndexY = new OpDef(OpLAE) { + Opcode = 0xbb, + AddrMode = AddressMode.AbsIndexY, + CycDef = 4 | (int)(CycleMod.OneIfIndexPage) + }; + + #endregion Undocumented 6502 + + #region Undocumented 65C02 + + // ====================================================================================== + // Undocumented 65C02 instructions. + // + // The 65C02 declared all undefined opcodes to be NOPs, but some of them can have + // side effects. + // + // References: + // http://laughtonelectronics.com/Arcana/KimKlone/Kimklone_opcode_mapping.html + // + + // Most undocumented instructions are a single-byte, single-cycle NOP. I don't know + // if these are "undocumented" in the strictest sense of the word, but we'll treat + // them that way for disassembly purposes. + public static readonly OpDef OpNOP_65C02 = new OpDef() { + IsUndocumented = true, + Mnemonic = OpName.NOP, + Effect = FlowEffect.Cont, + AddrMode = AddressMode.Implied, + CycDef = 1 + }; + + // 65C02 undocumented "Load and Discard" instruction. These cause bus traffic. + private static OpDef OpLDD = new OpDef() { + IsUndocumented = true, + Mnemonic = OpName.LDD, + Effect = FlowEffect.Cont, + AddrMode = AddressMode.Implied + }; + public static readonly OpDef OpLDD_Absolute = new OpDef(OpLDD) { + // multiple opcodes + AddrMode = AddressMode.Abs, + CycDef = 4 + }; + public static readonly OpDef OpLDD_DP = new OpDef(OpLDD) { + // multiple opcodes + AddrMode = AddressMode.DP, + CycDef = 3 + }; + public static readonly OpDef OpLDD_DPIndexX = new OpDef(OpLDD) { + // multiple opcodes + AddrMode = AddressMode.DPIndexX, + CycDef = 4 + }; + public static readonly OpDef OpLDD_Imm = new OpDef(OpLDD) { + // multiple opcodes + AddrMode = AddressMode.Imm, + CycDef = 2 + }; + public static readonly OpDef OpLDD_Weird = new OpDef(OpLDD) { + // multiple opcodes + AddrMode = AddressMode.Abs, // not really, but has the right number of bytes + CycDef = 8 + }; + + #endregion Undocumented + + + /// + /// Generates one of the multiply-defined opcodes from a prototype. This is + /// particularly useful for undocumented opcodes that are repeated several times. In + /// a couple of cases (SBC, some NOPs), the undocumented opcode is just another + /// reference to an existing instruction. + /// + /// Instruction opcode. + /// Instruction prototype. + /// Newly-created OpDef. + public static OpDef GenerateUndoc(byte opcode, OpDef proto) { + return new OpDef(proto) { Opcode = opcode, IsUndocumented = true }; + } + + public override string ToString() { + return Opcode.ToString("x2") + "/" + Mnemonic + " " + AddrMode; + } + } +} diff --git a/Asm65/OpDescription.cs b/Asm65/OpDescription.cs new file mode 100644 index 0000000..d283154 --- /dev/null +++ b/Asm65/OpDescription.cs @@ -0,0 +1,738 @@ +/* + * Copyright 2018 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.Text; + +namespace Asm65 { + /// + /// Human-readable text describing instructions. + /// + /// The expectation is that the long description will include information about all + /// address modes and any differences in behavior between CPUs. So there will be one + /// entry per instruction mnemonic, and one global table for all CPUs, rather than one + /// entry per opcode and one instance per CpuDef. + /// + /// There may, however, be different instances for different Cultures. Also, the 65816 + /// traditionally splits JSR/JSL and JMP/JML, so in that case there will be two entries + /// for the same instruction. + /// + public class OpDescription { + private Dictionary mShortDescriptions; + + private Dictionary mLongDescriptions; + + private Dictionary mAddressModeDescriptions; + + private Dictionary mCycleModDescriptions; + + private OpDescription(Dictionary sd, Dictionary ld, + Dictionary am, Dictionary cm) { + mShortDescriptions = sd; + mLongDescriptions = ld; + mAddressModeDescriptions = am; + mCycleModDescriptions = cm; + } + + /// + /// Returns an OpDescription instance for the requested region. + /// + /// TBD + public static OpDescription GetOpDescription(string region) { + // ignoring region for now + return new OpDescription(sShort_enUS, sLong_enUS, sAddrMode_enUS, sCycleMod_enUS); + } + + /// + /// Short description of instruction, e.g. "Load Accumulator". + /// + /// Instruction mnemonic. + /// Short description string, or empty string if not found. + public string GetShortDescription(string mnemonic) { + if (mShortDescriptions.TryGetValue(mnemonic, out string desc)) { + return desc; + } else { + return string.Empty; + } + } + + /// + /// Long description of instruction. May span multiple lines, with embedded CRLF at + /// paragraph breaks. + /// + /// Instruction mnemonic. + /// Long description string, or empty string if not found. + public string GetLongDescription(string mnemonic) { + if (mLongDescriptions.TryGetValue(mnemonic, out string desc)) { + return desc; + } else { + return string.Empty; + } + } + + /// + /// Address mode short description. + /// + /// Address mode to look up. + /// Description string, or an empty string for instructions + /// with implied address modes. + public string GetAddressModeDescription(OpDef.AddressMode addrMode) { + if (mAddressModeDescriptions.TryGetValue(addrMode, out string desc)) { + return desc; + } else { + return string.Empty; + } + } + + /// + /// Cycle modifier description. + /// + /// A single-bit item from the CycleMod enum. + /// Description string, or question marks if not found. + public string GetCycleModDescription(OpDef.CycleMod modBit) { + if (mCycleModDescriptions.TryGetValue(modBit, out string desc)) { + return desc; + } else { + return "???"; + } + } + + /// + /// Short descriptions, USA English. + /// + /// Text is adapted from instruction summaries in Eyes & Lichty, which are slightly + /// shorter than those in the CPU data sheet. + /// + private static Dictionary sShort_enUS = new Dictionary() { + { OpName.ADC, "Add With Carry" }, + { OpName.AND, "AND Accumulator With Memory" }, + { OpName.ASL, "Shift Memory or Accumulator Left" }, + { OpName.BCC, "Branch If Carry Clear" }, + { OpName.BCS, "Branch If Carry Set" }, + { OpName.BEQ, "Branch If Equal" }, + { OpName.BIT, "Test Memory Against Accumulator" }, + { OpName.BMI, "Branch If Minus" }, + { OpName.BNE, "Branch If Not Equal" }, + { OpName.BPL, "Branch If Plus" }, + { OpName.BRA, "Branch Always" }, + { OpName.BRK, "Software Break" }, + { OpName.BRL, "Branch Always Long" }, + { OpName.BVC, "Branch If Overflow Clear" }, + { OpName.BVS, "Branch If Overflow Set" }, + { OpName.CLC, "Clear Carry Flag" }, + { OpName.CLD, "Clear Decimal Flag" }, + { OpName.CLI, "Clear Interrupt Disable Flag" }, + { OpName.CLV, "Clear Overflow Flag" }, + { OpName.CMP, "Compare Accumulator With Memory" }, + { OpName.COP, "Co-Processor" }, + { OpName.CPX, "Compare Index X With Memory" }, + { OpName.CPY, "Compare Index Y With Memory" }, + { OpName.DEC, "Decrement Accumulator" }, + { OpName.DEX, "Decrement Index X" }, + { OpName.DEY, "Decrement Index Y" }, + { OpName.EOR, "XOR Accumulator With Memory" }, + { OpName.INC, "Increment Accumulator" }, + { OpName.INX, "Increment Index X" }, + { OpName.INY, "Increment Index Y" }, + { OpName.JMP, "Jump" }, + { OpName.JSL, "Jump to Subroutine Long" }, + { OpName.JSR, "Jump to Subroutine" }, + { OpName.LDA, "Load Accumulator from Memory" }, + { OpName.LDX, "Load Index X from Memory" }, + { OpName.LDY, "Load Index Y from Memory" }, + { OpName.LSR, "Logical Shift Memory or Accumulator Right" }, + { OpName.MVN, "Block Move Next" }, + { OpName.MVP, "Block Move Previous" }, + { OpName.NOP, "No Operation" }, + { OpName.ORA, "OR Accumulator With Memory" }, + { OpName.PEA, "Push Effective Absolute Address" }, + { OpName.PEI, "Push Effective Indirect Address" }, + { OpName.PER, "Push Effective Relative Indirect Address" }, + { OpName.PHA, "Push Accumulator" }, + { OpName.PHB, "Push Data Bank Register" }, + { OpName.PHD, "Push Direct Page Register" }, + { OpName.PHK, "Push Program Bank Register" }, + { OpName.PHP, "Push Processor Status Register" }, + { OpName.PHX, "Push Index Register X" }, + { OpName.PHY, "Push Index Register Y" }, + { OpName.PLA, "Pull Accumulator" }, + { OpName.PLB, "Pull Data Bank Register" }, + { OpName.PLD, "Pull Direct Page Register" }, + { OpName.PLP, "Pull Processor Status Register" }, + { OpName.PLX, "Pull Index Register X" }, + { OpName.PLY, "Pull Index Register Y" }, + { OpName.REP, "Reset Status Bits" }, + { OpName.ROL, "Rotate Memory or Accumulator Left" }, + { OpName.ROR, "Rotate Memory or Accumulator Right" }, + { OpName.RTI, "Return from Interrupt" }, + { OpName.RTL, "Return from Subroutine Long" }, + { OpName.RTS, "Return from Subroutine" }, + { OpName.SBC, "Subtract With Borrow" }, + { OpName.SEC, "Set Carry Flag" }, + { OpName.SED, "Set Decimal Flag" }, + { OpName.SEI, "Set Interrupt Disable Flag" }, + { OpName.SEP, "Set Status Bits" }, + { OpName.STA, "Store Accumulator to Memory" }, + { OpName.STP, "Stop Processor" }, + { OpName.STX, "Store Index X to Memory" }, + { OpName.STY, "Store Index Y to Memory" }, + { OpName.STZ, "Store Zero to Memory" }, + { OpName.TAX, "Transfer Accumulator to Index X" }, + { OpName.TAY, "Transfer Accumulator to Index Y" }, + { OpName.TCD, "Transfer 16-Bit Accumulator to Direct Page Register" }, + { OpName.TCS, "Transfer Accumulator to Stack Pointer" }, + { OpName.TDC, "Transfer Direct Page Register to 16-Bit Accumulator" }, + { OpName.TRB, "Test and Reset Memory Bits" }, + { OpName.TSB, "Test and Set Memory Bits" }, + { OpName.TSC, "Transfer Stack Pointer to 16-Bit Accumulator" }, + { OpName.TSX, "Transfer Stack Pointer to Index X" }, + { OpName.TXA, "Transfer Index X to Accumulator" }, + { OpName.TXS, "Transfer Index X to Stack Pointer" }, + { OpName.TXY, "Transfer Index X to Index Y" }, + { OpName.TYA, "Transfer Index Y to Accumulator" }, + { OpName.TYX, "Transfer Index Y to Index X" }, + { OpName.WAI, "Wait for Interrupt" }, + { OpName.WDM, "Future Expansion" }, + { OpName.XBA, "Exchange Accumulator B and A" }, + { OpName.XCE, "Exchange Carry and Emulation Bits" }, + + // MOS 6502 undocumented ops + { OpName.ANC, "AND Accumulator With Value and Set Carry" }, + { OpName.ANE, "Transfer Index X to Accumulator and AND" }, + { OpName.ARR, "AND and Rotate Right" }, + { OpName.ASR, "AND and Shift Right" }, + { OpName.DCP, "Decrement and Compare" }, + { OpName.DOP, "Double-Byte NOP" }, + { OpName.HLT, "Halt CPU" }, + { OpName.ISB, "Increment and Subtract" }, + { OpName.LAE, "Load Acc, X, and Stack Pointer with Memory AND Stack Pointer" }, + { OpName.LAX, "Load Accumulator and Index X" }, + { OpName.LXA, "OR, AND, and Transfer to X" }, + { OpName.RLA, "Rotate Left and AND" }, + { OpName.RRA, "Rotate Right and Add" }, + { OpName.SAX, "Store Accumulator AND Index X" }, // AXS + { OpName.SBX, "AND Acc With Index X, Subtract, and Store in X" }, // SAX + { OpName.SHA, "AND Acc With Index X and High Byte, and Store" }, // AXA + { OpName.SHS, "AND Acc with Index X, Transfer to Stack, AND High Byte" }, // TAS + { OpName.SHX, "AND Acc With Index X and High Byte, and Store" }, // XAS + { OpName.SHY, "AND Acc With Index Y and High Byte, and Store" }, // SAY + { OpName.SLO, "Shift Left and OR" }, + { OpName.SRE, "Shift right and EOR" }, + { OpName.TOP, "Triple-Byte NOP" }, + + // WDC 65C02 undocumented + { OpName.LDD, "Load and Discard" }, + }; + + /// + /// Long descriptions, USA English. + /// + private static Dictionary sLong_enUS = new Dictionary() { + { OpName.ADC, + "Adds the accumulator and a value in memory, storing the result in the " + + "accumulator. Adds one if the carry is set." + }, + { OpName.AND, + "Performs a bitwise AND of the accumulator with a value in memory, storing " + + "the result in the accumulator." + }, + { OpName.ASL, + "Shifts memory or the accumulator one bit left. The low bit is set to zero, " + + "and the carry flag receives the high bit." + }, + { OpName.BCC, + "Branches to a relative address if the processor carry flag (C) is zero. " + + "Sometimes referred to as Branch If Less Than, or BLT." + }, + { OpName.BCS, + "Branches to a relative address if the processor carry flag (C) is one. " + + "Sometimes referred to as Branch If Greater Than or Equal, or BGE." + }, + { OpName.BEQ, + "Branches to a relative address if the processor zero flag (Z) is one." + }, + { OpName.BIT, + "Sets processor flags based on the result of two operations. The N and V flags " + + "are set according to bits 7 and 6, and the Z flag is set based on an AND of " + + "the accumulator and memory. However, when used with immediate addressing, " + + "the N and V flags are not affected." + }, + { OpName.BMI, + "Branches to a relative address if the processor negative flag (N) is one." + }, + { OpName.BNE, + "Branches to a relative address if the processor zero flag (Z) is zero." + }, + { OpName.BPL, + "Branches to a relative address if the processor negative flag (N) is zero." + }, + { OpName.BRA, + "Branches to a relative address." + }, + { OpName.BRK, + "Pushes state onto the stack, and jumps to the software break vector at " + + "$fffe-ffff. While this is technically a single-byte instruction, the " + + "program counter pushed onto the stack is incremented by two." + }, + { OpName.BRL, + "Branches to a long relative address." + }, + { OpName.BVC, + "Branches to a relative address if the processor overflow flag (V) is zero." + }, + { OpName.BVS, + "Branches to a relative address if the processor overflow flag (V) is one." + }, + { OpName.CLC, + "Sets the processor carry flag (C) to zero." + }, + { OpName.CLD, + "Sets the processor decimal flag (D) to zero." + }, + { OpName.CLI, + "Sets the processor interrupt disable flag (I) to zero." + }, + { OpName.CLV, + "Sets the processor overflow flag (V) to zero." + }, + { OpName.CMP, + "Subtracts the value specified by the operand from the contents of the " + + "accumulator. Sets the carry, zero, and negative flags, but does not alter " + + "memory or the accumulator." + }, + { OpName.COP, + "Pushes state onto the stack, and jumps to the software interrupt vector at " + + "$fff4-fff5." + }, + { OpName.CPX, + "Subtracts the value specified by the operand from the contents of the " + + "X register. Sets the carry, zero, and negative flags, but does not alter " + + "memory or the X register." + }, + { OpName.CPY, + "Subtracts the value specified by the operand from the contents of the " + + "Y register. Sets the carry, zero, and negative flags, but does not alter " + + "memory or the Y register." + }, + { OpName.DEC, + "Decrements the contents of the location specified by the operand by one." + }, + { OpName.DEX, + "Decrements the X register by one." + }, + { OpName.DEY, + "Decrements the Y register by one." + }, + { OpName.EOR, + "Performs a bitwise EOR of the accumulator with a value in memory, storing " + + "the result in the accumulator." + }, + { OpName.INC, + "Increments the contents of the location specified by the operand by one." + }, + { OpName.INX, + "Increments the X register by one." + }, + { OpName.INY, + "Increments the Y register by one." + }, + { OpName.JML, + "Branches to a long absolute address." + }, + { OpName.JMP, + "Branches to an absolute address." + }, + { OpName.JSL, + "Branches to a long absolute address after pushing the current address onto " + + "the stack. The value pushed is the address of the last operand byte." + }, + { OpName.JSR, + "Branches to an absolute address after pushing the current address onto " + + "the stack. The value pushed is the address of the last operand byte." + }, + { OpName.LDA, + "Loads the accumulator from memory." + }, + { OpName.LDX, + "Loads the X register from memory." + }, + { OpName.LDY, + "Loads the Y register from memory." + }, + { OpName.LSR, + "Shifts memory or the accumulator one bit right. The high bit is set to zero, " + + "and the carry flag receives the low bit." + }, + { OpName.MVN, + "Moves a block of memory, starting from a low address and incrementing. " + + "The source and destination addresses are in the X and Y registers, " + + "respectively. The accumulator holds the number of bytes to move minus 1, " + + "and the source and destination banks are specified by the operands." + }, + { OpName.MVP, + "Moves a block of memory, starting from a high address and decrementing. " + + "The source and destination addresses are in the X and Y registers, " + + "respectively. The accumulator holds the number of bytes to move minus 1, " + + "and the source and destination banks are specified by the operands." + }, + { OpName.NOP, + "No operation." + }, + { OpName.ORA, + "Performs a bitwise OR of the accumulator with a value in memory, storing " + + "the result in the accumulator." + }, + { OpName.PEA, + "Pushes the 16-bit operand onto the stack. This always pushes two bytes, " + + "regardless of the M/X processor flags." + }, + { OpName.PEI, + "Pushes a 16-bit value from the direct page onto the stack." + }, + { OpName.PER, + "Converts a relative offset to an absolute address, and pushes it onto the stack." + }, + { OpName.PHA, + "Pushes the accumulator onto the stack." + }, + { OpName.PHB, + "Pushes the data bank register onto the stack." + }, + { OpName.PHD, + "Pushes the direct page register onto the stack." + }, + { OpName.PHK, + "Pushes the program bank register onto the stack." + }, + { OpName.PHP, + "Pushes the processor status register onto the stack." + }, + { OpName.PHX, + "Pushes the X register onto the stack." + }, + { OpName.PHY, + "Pushes the Y register onto the stack." + }, + { OpName.PLA, + "Pulls the accumulator off of the stack." + }, + { OpName.PLB, + "Pulls the data bank register off of the stack." + }, + { OpName.PLD, + "Pulls the direct page register off of the stack." + }, + { OpName.PLP, + "Pulls the processor status register off of the stack." + }, + { OpName.PLX, + "Pulls the X register off of the stack." + }, + { OpName.PLY, + "Pulls the Y register off of the stack." + }, + { OpName.REP, + "Sets specific bits in the processor status register to zero." + }, + { OpName.ROL, + "Rotates memory or the accumulator one bit left. The low bit is set to the " + + "carry flag, and the carry flag receives the high bit." + }, + { OpName.ROR, + "Rotates memory or the accumulator one bit right. The high bit is set to the " + + "carry flag, and the carry flag receives the low bit." + }, + { OpName.RTI, + "Pulls the status register and return address from the stack, and jumps " + + "to the exact address pulled (note this is different from RTL/RTS)." + }, + { OpName.RTL, + "Pulls the 24-bit return address from the stack, increments it, and jumps to it." + }, + { OpName.RTS, + "Pulls the 16-bit return address from the stack, increments it, and jumps to it." + }, + { OpName.SBC, + "Subtracts the value specified by the operand from the contents of the " + + "accumulator, and leaves the result in the accumulator. Sets the carry, " + + "zero, and negative flags." + }, + { OpName.SEC, + "Sets the processor carry flag (C) to one." + }, + { OpName.SED, + "Sets the processor decimal flag (D) to one." + }, + { OpName.SEI, + "Sets the processor interrupt disable flag (I) to one." + }, + { OpName.SEP, + "Sets specific bits in the processor status register to one." + }, + { OpName.STA, + "Stores the value in the accumulator into memory." + }, + { OpName.STP, + "Stops the processor until a CPU reset occurs." + }, + { OpName.STX, + "Stores the value in the X register into memory." + }, + { OpName.STY, + "Stores the value in the Y register into memory." + }, + { OpName.STZ, + "Stores zero into memory." + }, + { OpName.TAX, + "Transfers the contents of the accumulator to the X register." + }, + { OpName.TAY, + "Transfers the contents of the accumulator to the Y register." + }, + { OpName.TCD, + "Transfers the 16-bit accumulator to the direct page register." + }, + { OpName.TCS, + "Transfers the 16-bit accumulator to the stack pointer register." + }, + { OpName.TDC, + "Transfers the direct page register to the 16-bit accumulator." + }, + { OpName.TRB, + "Logically ANDs the complement of the value in the accumulator with a value " + + "in memory, and stores it in memory. This can be used to clear specific bits " + + "in memory." + }, + { OpName.TSB, + "Logically ORs the value in the accumulator with a value in memory, and " + + "stores it in memory. This can be used to set specific bits in memory." + }, + { OpName.TSC, + "Transfers the stack pointer register to the 16-bit accumulator." + }, + { OpName.TSX, + "Transfers the stack pointer register to the X register." + }, + { OpName.TXA, + "Transfers the X register to the accumulator." + }, + { OpName.TXS, + "Transfers the X register to the stack pointer register." + }, + { OpName.TXY, + "Transfers the X register to the Y register." + }, + { OpName.TYA, + "Transfers the Y register to the accumulator." + }, + { OpName.TYX, + "Transfers the Y register to the X register." + }, + { OpName.WAI, + "Stalls the processor until an interrupt is received. If the interrupt " + + "disable flag (I) is set to one, execution will continue with the next " + + "instruction rather than calling through an interrupt vector." + }, + { OpName.WDM, + "Reserved for future expansion. (Behaves as a two-byte NOP.)" + }, + { OpName.XBA, + "Swaps the high and low bytes in the 16-bit accumulator. Sometimes referred " + + "to as SWA." + }, + { OpName.XCE, + "Exchanges carry and emulation bits." + }, + + // + // 6502 undocumented instructions. + // + // References: + // http://www.ffd2.com/fridge/docs/6502-NMOS.extra.opcodes + // http://nesdev.com/undocumented_opcodes.txt + // + { OpName.ANC, + "AND byte with accumulator. If result is negative then carry is set." + + "\r\n\r\nAlt mnemonic: AAC" + }, + { OpName.ANE, + "Transfer X register to accumulator, then AND accumulator with value." + + "\r\n\r\nAlt mnemonic: XAA" + }, + { OpName.ARR, + "AND byte with accumulator, then rotate one bit right. Equivalent to " + + "AND + ROR." + }, + { OpName.ASR, + "AND byte with accumulator, then shift right one bit. Equivalent to AND + LSR." + + "\r\n\r\nAlt mnemonic: ALR" + }, + { OpName.DCP, + "Decrement memory location, then compare result to accumulator. Equivalent " + + "to DEC + CMP." + + "\r\n\r\nAlt mnemonic: DCM" + }, + { OpName.DOP, + "Double-byte no-operation." + + "\r\n\r\nAlt mnemonic: NOP / SKB" + }, + { OpName.HLT, + "Crash the CPU, halting execution and ignoring interrupts." + + "\r\n\r\nAlt mnemonic: KIL / JAM" + }, + { OpName.ISB, + "Increment memory, then subtract memory from accumulator with borrow. " + + "Equivalent to INC + SBC." + + "\r\n\r\nAlt mnemonic: ISC / INS" + }, + { OpName.LAE, + "AND memory with stack pointer, then transfer result to accumulator, " + + "X register, and stack pointer. (Note: possibly unreliable.)" + + "\r\n\r\nAlt mnemonic: LAR / LAS" + }, + { OpName.LAX, + "Load accumulator and X register from memory. Equivalent to LDA + LDX." + }, + { OpName.LXA, + "ORs accumulator with a value, ANDs result with immediate value, then stores " + + "the result in accumulator and X register." + + "Equivalent to ORA + AND + TAX." + + "\r\n\r\nAlt mnemonic: ATX / OAL" + }, + { OpName.RLA, + "Rotate memory one bit left, then AND accumulator with memory. Equivalent " + + "to ROL + AND." + }, + { OpName.RRA, + "Rotate memory one bit right, then add accumulator to memory with carry. " + + "Equivalent to ROR + ADC." + }, + { OpName.SAX, + "AND X register with accumulator, without changing the contents of either " + + "register, subtract an immediate value, then store result in X register." + + "\r\n\r\nAlt mnemonic: AAX / AXS" + }, + { OpName.SBX, + "AND X register with accumulator and transfer to X register, then " + + "subtract byte from X register without borrow." + + "\r\n\r\nAlt mnemonic: AXS / SAX" + }, + { OpName.SHA, + "AND X register with accumulator, then AND result with 7 and store." + + "\r\n\r\nAlt mnemonic: AXA" + }, + { OpName.SHS, + "AND X register with accumulator, without changing the contents of either" + + "register, and transfer to stack pointer. Then " + + "AND stack pointer with high byte of operand + 1." + + "\r\n\r\nAlt mnemonic: XAS / TAS" + }, + { OpName.SHX, + "AND X register with the high byte of the argument + 1, and store the result." + + "\r\n\r\nAlt mnemonic: SXA / XAS" + }, + { OpName.SHY, + "AND Y register with the high byte of the argument + 1, and store the result." + + "\r\n\r\nAlt mnemonic: SYA / SAY" + }, + { OpName.SLO, + "Shift memory left one bit, then OR accumulator with memory. Equivalent to " + + "ASL + ORA." + + "\r\n\r\nAlt mnemonic: ASO" + }, + { OpName.SRE, + "Shift memory right one bit, then EOR accumulator with memory. Equivalent to " + + "LSR + EOR." + + "\r\n\r\nAlt mnemonic: LSE" + }, + { OpName.TOP, + "Triple-byte no-operation. This actually performs a load." + + "\r\n\r\nAlt mnemonic: NOP / SKW" + }, + + // + // 65C02 undocumented instructions. + // + { OpName.LDD, + "Load and Discard. Usually a no-op, but the activity on the address bus " + + "can affect memory-mapped I/O." + }, + }; + + /// + /// Address mode short descriptions, USA English. + /// + private static Dictionary sAddrMode_enUS = + new Dictionary() { + { OpDef.AddressMode.Abs, "Absolute" }, + { OpDef.AddressMode.AbsInd, "Absolute Indirect" }, + { OpDef.AddressMode.AbsIndLong, "Absolute Indirect Long" }, + { OpDef.AddressMode.AbsIndexX, "Absolute Indexed X" }, + { OpDef.AddressMode.AbsIndexXInd, "Absolute Indexed X Indirect" }, + { OpDef.AddressMode.AbsIndexXLong, "Absolute Indexed X Long" }, + { OpDef.AddressMode.AbsIndexY, "Absolute Indexed Y" }, + { OpDef.AddressMode.AbsLong, "Absolute Long" }, + { OpDef.AddressMode.Acc, "Accumulator" }, + { OpDef.AddressMode.BlockMove, "Block Move" }, + { OpDef.AddressMode.DP, "Direct Page" }, + { OpDef.AddressMode.DPInd, "Direct Page Indirect" }, + { OpDef.AddressMode.DPIndIndexY, "Direct Page Indirect Indexed Y" }, + { OpDef.AddressMode.DPIndIndexYLong, "Direct Page Indirect Indexed Y Long" }, + { OpDef.AddressMode.DPIndLong, "Direct Page Indirect Long" }, + { OpDef.AddressMode.DPIndexX, "Direct Page Indexed X" }, + { OpDef.AddressMode.DPIndexXInd, "Direct Page Indexed X Indirect" }, + { OpDef.AddressMode.DPIndexY, "Direct Page Indexed Y" }, + { OpDef.AddressMode.Imm, "Immediate" }, + { OpDef.AddressMode.ImmLongA, "Immediate" }, + { OpDef.AddressMode.ImmLongXY, "Immediate" }, + { OpDef.AddressMode.Implied, "" }, + { OpDef.AddressMode.PCRel, "PC Relative" }, + { OpDef.AddressMode.PCRelLong, "PC Relative Long" }, + { OpDef.AddressMode.StackAbs, "Stack Absolute" }, + { OpDef.AddressMode.StackDPInd, "Stack Direct Page Indirect" }, + { OpDef.AddressMode.StackInt, "" }, + { OpDef.AddressMode.StackPCRelLong, "Stack PC Relative Long" }, + { OpDef.AddressMode.StackPull, "" }, + { OpDef.AddressMode.StackPush, "" }, + { OpDef.AddressMode.StackRTI, "" }, + { OpDef.AddressMode.StackRTL, "" }, + { OpDef.AddressMode.StackRTS, "" }, + { OpDef.AddressMode.StackRel, "" }, + { OpDef.AddressMode.StackRelIndIndexY, "Stack Relative Indirect Index Y" }, + { OpDef.AddressMode.WDM, "" } + }; + + /// + /// Cycle modifier descriptions. These are intended to be very terse. + /// + private static Dictionary sCycleMod_enUS = + new Dictionary() { + { OpDef.CycleMod.OneIfM0, "+1 if M=0" }, + { OpDef.CycleMod.TwoIfM0, "+2 if M=0" }, + { OpDef.CycleMod.OneIfX0, "+1 if X=0" }, + { OpDef.CycleMod.OneIfDpNonzero, "+1 if DL != 0" }, + { OpDef.CycleMod.OneIfIndexPage, "+1 if index across page" }, + { OpDef.CycleMod.OneIfD1, "+1 if D=1 on 65C02" }, + { OpDef.CycleMod.OneIfBranchTaken, "+1 if branch taken" }, + { OpDef.CycleMod.OneIfBranchPage, "+1 if branch across page unless E=0" }, + { OpDef.CycleMod.OneIfE0, "+1 if E=0" }, + { OpDef.CycleMod.OneIf65C02, "+1 if 65C02" }, + { OpDef.CycleMod.MinusOneIfNoPage, "-1 if 65C02 and not across page" }, + { OpDef.CycleMod.BlockMove, "+7 per byte" }, + }; + } +} diff --git a/Asm65/OpName.cs b/Asm65/OpName.cs new file mode 100644 index 0000000..73de99e --- /dev/null +++ b/Asm65/OpName.cs @@ -0,0 +1,148 @@ +/* + * Copyright 2018 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; + +namespace Asm65 { + /// + /// String constants for opcodes. These are not (and should not be) localized. They + /// must be lower-case. + /// + public static class OpName { + // NOTE: these all happen to be three characters, but I don't think we want to + // guarantee that. On the 65816 some mnemonics are extended (e.g. LDAL for LDA with + // a 24-bit operand), but that's assembler-specific and handled elsewhere. + public const string Unknown = "???"; + public const string ADC = "adc"; + public const string AND = "and"; + public const string ASL = "asl"; + public const string BCC = "bcc"; + public const string BCS = "bcs"; + public const string BEQ = "beq"; + public const string BIT = "bit"; + public const string BMI = "bmi"; + public const string BNE = "bne"; + public const string BPL = "bpl"; + public const string BRA = "bra"; + public const string BRK = "brk"; + public const string BRL = "brl"; + public const string BVC = "bvc"; + public const string BVS = "bvs"; + public const string CLC = "clc"; + public const string CLD = "cld"; + public const string CLI = "cli"; + public const string CLV = "clv"; + public const string CMP = "cmp"; + public const string COP = "cop"; + public const string CPX = "cpx"; + public const string CPY = "cpy"; + public const string DEC = "dec"; + public const string DEX = "dex"; + public const string DEY = "dey"; + public const string EOR = "eor"; + public const string INC = "inc"; + public const string INX = "inx"; + public const string INY = "iny"; + public const string JML = "jml"; + public const string JMP = "jmp"; + public const string JSL = "jsl"; + public const string JSR = "jsr"; + public const string LDA = "lda"; + public const string LDX = "ldx"; + public const string LDY = "ldy"; + public const string LSR = "lsr"; + public const string MVN = "mvn"; + public const string MVP = "mvp"; + public const string NOP = "nop"; + public const string ORA = "ora"; + public const string PEA = "pea"; + public const string PEI = "pei"; + public const string PER = "per"; + public const string PHA = "pha"; + public const string PHB = "phb"; + public const string PHD = "phd"; + public const string PHK = "phk"; + public const string PHP = "php"; + public const string PHX = "phx"; + public const string PHY = "phy"; + public const string PLA = "pla"; + public const string PLB = "plb"; + public const string PLD = "pld"; + public const string PLP = "plp"; + public const string PLX = "plx"; + public const string PLY = "ply"; + public const string REP = "rep"; + public const string ROL = "rol"; + public const string ROR = "ror"; + public const string RTI = "rti"; + public const string RTL = "rtl"; + public const string RTS = "rts"; + public const string SBC = "sbc"; + public const string SEC = "sec"; + public const string SED = "sed"; + public const string SEI = "sei"; + public const string SEP = "sep"; + public const string STA = "sta"; + public const string STP = "stp"; + public const string STX = "stx"; + public const string STY = "sty"; + public const string STZ = "stz"; + public const string TAX = "tax"; + public const string TAY = "tay"; + public const string TCD = "tcd"; + public const string TCS = "tcs"; + public const string TDC = "tdc"; + public const string TRB = "trb"; + public const string TSB = "tsb"; + public const string TSC = "tsc"; + public const string TSX = "tsx"; + public const string TXA = "txa"; + public const string TXS = "txs"; + public const string TXY = "txy"; + public const string TYA = "tya"; + public const string TYX = "tyx"; + public const string WAI = "wai"; + public const string WDM = "wdm"; + public const string XBA = "xba"; + public const string XCE = "xce"; + + // Undocumented 6502 instructions. + public const string ANC = "anc"; + public const string ANE = "ane"; + public const string ARR = "arr"; + public const string ASR = "asr"; + public const string DCP = "dcp"; + public const string DOP = "dop"; + public const string HLT = "hlt"; + public const string ISB = "isb"; + public const string LAE = "lae"; + public const string LAX = "lax"; + public const string LXA = "lxa"; + public const string RLA = "rla"; + public const string RRA = "rra"; + public const string SAX = "sax"; + public const string SBX = "sbx"; + public const string SHA = "sha"; + public const string SHS = "shs"; + public const string SHX = "shx"; + public const string SHY = "shy"; + public const string SLO = "slo"; + public const string SRE = "sre"; + public const string TOP = "top"; + + // Undocumented 65C02 instructions. + public const string LDD = "ldd"; + } +} diff --git a/Asm65/Properties/Resources.Designer.cs b/Asm65/Properties/Resources.Designer.cs new file mode 100644 index 0000000..3f729e3 --- /dev/null +++ b/Asm65/Properties/Resources.Designer.cs @@ -0,0 +1,73 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Asm65.Properties { + using System; + using System.Reflection; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Asm65.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Fourth time. + /// + public static string TEST_STRING { + get { + return ResourceManager.GetString("TEST_STRING", resourceCulture); + } + } + } +} diff --git a/Asm65/Properties/Resources.resx b/Asm65/Properties/Resources.resx new file mode 100644 index 0000000..bd42ef5 --- /dev/null +++ b/Asm65/Properties/Resources.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Fourth time + + \ No newline at end of file diff --git a/Asm65/StatusFlags.cs b/Asm65/StatusFlags.cs new file mode 100644 index 0000000..77417f3 --- /dev/null +++ b/Asm65/StatusFlags.cs @@ -0,0 +1,328 @@ +/* + * Copyright 2018 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.Text; + +namespace Asm65 { + /// + /// Status flag holder. Each flag may be known to be zero, known to be one, or + /// hold an indeterminate value (represented as a negative number). + /// + /// For the 65802/65816, we also keep track of the E flag (emulation bit), even though + /// that's not actually held in the P register. + /// + /// Note this is a value type, not a reference type. + /// + /// The default value is UNSPECIFIED for all bits. + /// + public struct StatusFlags { + private TriState16 mState; + + + /// + /// Flag bits, from processor status register definition. The 'e' (emulation) + /// flag from the 65816 is tacked onto the end. + /// + /// The enumerated value matches the bit number in the P register. + /// + public enum FlagBits { + C = 0, + Z = 1, + I = 2, + D = 3, + B = 4, // all CPUs except 65802/65816 in native mode + X = 4, // 65802/65816 in native mode + M = 5, // 65802/65816 in native mode (always 1 on other CPUs) + V = 6, + N = 7, + E = 8 // not actually part of P-reg; accessible only through XCE + } + + /// + /// Default value (all flags UNSPECIFIED). A newly-created array of StatusFlags will + /// all have this value. + /// + public static readonly StatusFlags DefaultValue = + new StatusFlags { mState = new TriState16(0, 0) }; + + /// + /// All flags are INDETERMINATE. + /// + public static readonly StatusFlags AllIndeterminate = + new StatusFlags() { mState = new TriState16(0x01ff, 0x01ff) }; + + public int C { + get { + return mState.GetBit((int)FlagBits.C); + } + set { + if (value == 0) { + mState.SetZero((int)FlagBits.C); + } else if (value == 1) { + mState.SetOne((int)FlagBits.C); + } else if (value == TriState16.UNSPECIFIED) { + mState.SetUnspecified((int)FlagBits.C); + } else { + mState.SetIndeterminate((int)FlagBits.C); + } + } + } + + public int Z { + get { + return mState.GetBit((int)FlagBits.Z); + } + set { + if (value == 0) { + mState.SetZero((int)FlagBits.Z); + } else if (value == 1) { + mState.SetOne((int)FlagBits.Z); + } else if (value == TriState16.UNSPECIFIED) { + mState.SetUnspecified((int)FlagBits.Z); + } else { + mState.SetIndeterminate((int)FlagBits.Z); + } + } + } + + public int I { + get { + return mState.GetBit((int)FlagBits.I); + } + set { + if (value == 0) { + mState.SetZero((int)FlagBits.I); + } else if (value == 1) { + mState.SetOne((int)FlagBits.I); + } else if (value == TriState16.UNSPECIFIED) { + mState.SetUnspecified((int)FlagBits.I); + } else { + mState.SetIndeterminate((int)FlagBits.I); + } + } + } + + public int D { + get { + return mState.GetBit((int)FlagBits.D); + } + set { + if (value == 0) { + mState.SetZero((int)FlagBits.D); + } else if (value == 1) { + mState.SetOne((int)FlagBits.D); + } else if (value == TriState16.UNSPECIFIED) { + mState.SetUnspecified((int)FlagBits.D); + } else { + mState.SetIndeterminate((int)FlagBits.D); + } + } + } + + public int X { + get { + return mState.GetBit((int)FlagBits.X); + } + set { + if (value == 0) { + mState.SetZero((int)FlagBits.X); + } else if (value == 1) { + mState.SetOne((int)FlagBits.X); + } else if (value == TriState16.UNSPECIFIED) { + mState.SetUnspecified((int)FlagBits.X); + } else { + mState.SetIndeterminate((int)FlagBits.X); + } + } + } + + public int M { + get { + return mState.GetBit((int)FlagBits.M); + } + set { + if (value == 0) { + mState.SetZero((int)FlagBits.M); + } else if (value == 1) { + mState.SetOne((int)FlagBits.M); + } else if (value == TriState16.UNSPECIFIED) { + mState.SetUnspecified((int)FlagBits.M); + } else { + mState.SetIndeterminate((int)FlagBits.M); + } + } + } + + public int V { + get { + return mState.GetBit((int)FlagBits.V); + } + set { + if (value == 0) { + mState.SetZero((int)FlagBits.V); + } else if (value == 1) { + mState.SetOne((int)FlagBits.V); + } else if (value == TriState16.UNSPECIFIED) { + mState.SetUnspecified((int)FlagBits.V); + } else { + mState.SetIndeterminate((int)FlagBits.V); + } + } + } + + public int N { + get { + return mState.GetBit((int)FlagBits.N); + } + set { + if (value == 0) { + mState.SetZero((int)FlagBits.N); + } else if (value == 1) { + mState.SetOne((int)FlagBits.N); + } else if (value == TriState16.UNSPECIFIED) { + mState.SetUnspecified((int)FlagBits.N); + } else { + mState.SetIndeterminate((int)FlagBits.N); + } + } + } + + public int E { + get { + return mState.GetBit((int)FlagBits.E); + } + set { + if (value == 0) { + mState.SetZero((int)FlagBits.E); + } else if (value == 1) { + mState.SetOne((int)FlagBits.E); + } else if (value == TriState16.UNSPECIFIED) { + mState.SetUnspecified((int)FlagBits.E); + } else { + mState.SetIndeterminate((int)FlagBits.E); + } + } + } + + public int GetBit(FlagBits index) { + return mState.GetBit((int) index); + } + + /// + /// Returns true if the current processor status flags are configured for a short + /// (8-bit) accumulator. + /// + public bool ShortM { + get { + // E==1 --> true (we're in emulation mode) + // E==0 || E==? : native / assumed native + // M==1 || M==? --> true (native mode, configured short or assumed short) + // M==0 --> false (native mode, configured long) + return (E == 1) || (M != 0); + } + } + + /// + /// Returns true if the current processor status flags are configured for short + /// (8-bit) X/Y registers. + /// + public bool ShortX { + get { + // (same logic as ShortM) + return (E == 1) || (X != 0); + } + } + + /// + /// Access the value as a single integer. Used for serialization. + /// + public int AsInt { + get { + return mState.AsInt; + } + } + + /// + /// Set the value from an integer. Used for serialization. + /// + public static StatusFlags FromInt(int value) { + if ((value & ~0x01ff01ff) != 0) { + throw new InvalidOperationException("Bad StatusFlags value " + + value.ToString("x8")); + } + StatusFlags newFlags = new StatusFlags(); + newFlags.mState.AsInt = value; + return newFlags; + } + + /// + /// Merge a set of status flags into this one. + /// + public void Merge(StatusFlags other) { + mState.Merge(other.mState); + } + + /// + /// Applies flags, overwriting existing values. This will set one or more flags + /// to 0, 1, or indeterminate. Unspecified (0/0) values have no effect. + /// + /// This is useful when merging "overrides" in. + /// + public void Apply(StatusFlags overrides) { + mState.Apply(overrides.mState); + } + + /// + /// Returns a string representation of the flags. + /// + /// If set, include the 'E' flag, and show M/X. + public string ToString(bool showMXE) { + StringBuilder sb = new StringBuilder(showMXE ? 10 : 8); + sb.Append("-?nN"[N + 2]); + sb.Append("-?vV"[V + 2]); + sb.Append(showMXE ? "-?mM"[M + 2] : '-'); + sb.Append(showMXE ? "-?xX"[X + 2] : '-'); + sb.Append("-?dD"[D + 2]); + sb.Append("-?iI"[I + 2]); + sb.Append("-?zZ"[Z + 2]); + sb.Append("-?cC"[C + 2]); + if (showMXE) { + sb.Append(' '); + sb.Append("-?eE"[E + 2]); + } + return sb.ToString(); + } + + + public static bool operator ==(StatusFlags a, StatusFlags b) { + return a.mState == b.mState; + } + public static bool operator !=(StatusFlags a, StatusFlags b) { + return !(a == b); + } + public override bool Equals(object obj) { + return obj is StatusFlags && this == (StatusFlags)obj; + } + public override int GetHashCode() { + return mState.GetHashCode(); + } + + public override string ToString() { + return ToString(true); + // + " [" + mState.ToString() + "]" + } + } +} diff --git a/Asm65/TriState16.cs b/Asm65/TriState16.cs new file mode 100644 index 0000000..571d6d4 --- /dev/null +++ b/Asm65/TriState16.cs @@ -0,0 +1,172 @@ +/* + * Copyright 2018 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.Diagnostics; + +namespace Asm65 { + /// + /// A value with 16 tri-state bits. + /// + public struct TriState16 { + /// + /// Two 16-bit values. The low 16 bits indicate that bit N is zero, the high + /// 16 bits indicate that bit N is one. If neither or both are set, the value + /// is undetermined and could be either. + /// + /// While 0/0 and 1/1 both represent an indeterminate value, they're not equivalent. + /// When two values are merged, a 0/0 bit has no effect on the result, while + /// a 1/1 bit will force the merged bit to be indeterminate. We use UNSPECIFIED for + /// 0/0 and INDETERMINATE for 1/1. + /// + /// The default value for new instances is UNSPECIFIED for all bits. + /// + private ushort mZero, mOne; + + public const int INDETERMINATE = -1; + public const int UNSPECIFIED = -2; + + /// + /// Constructor; sets initial zero/one values. + /// + /// 16-bit value, with bits set for each known-zero. + /// 16-bit value, with bits set for each known-one. + public TriState16(ushort zeroes, ushort ones) { + mZero = zeroes; + mOne = ones; + } + + /// + /// Access the value as a single integer. Used for serialization. + /// + public int AsInt { + get { + return (int)mZero | ((int)mOne << 16); + } + set { + mZero = (ushort) value; + mOne = (ushort) (value >> 16); + } + } + + /// + /// Sets bit N to zero. + /// + public void SetZero(int bit) { + Debug.Assert(bit >= 0 && bit < 16); + + // clear 1-flag, set 0-flag + //mValue = (mValue & ~(1U << (bit + 16))) | (1U << bit); + mZero |= (ushort) (1U << bit); + mOne &= (ushort) ~(1U << bit); + } + + /// + /// Sets bit N to one. + /// + public void SetOne(int bit) { + Debug.Assert(bit >= 0 && bit < 16); + + // clear 0-flag, set 1-flag + //mValue = (mValue & ~(1U << bit)) | (1U << (bit + 16)); + mZero &= (ushort)~(1U << bit); + mOne |= (ushort)(1U << bit); + } + + /// + /// Sets bit N to indeterminate. + /// + public void SetIndeterminate(int bit) { + Debug.Assert(bit >= 0 && bit < 16); + + // set both flags + mZero |= (ushort)(1U << bit); + mOne |= (ushort)(1U << bit); + } + + /// + /// Sets bit N to unspecified. + /// + public void SetUnspecified(int bit) { + Debug.Assert(bit >= 0 && bit < 16); + + // clear both flags + mZero &= (ushort)~(1U << bit); + mOne &= (ushort)~(1U << bit); + } + + /// + /// Merges bit states. + /// + /// Value to merge in. + public void Merge(TriState16 other) { + //mValue |= other.mValue; + mZero |= other.mZero; + mOne |= other.mOne; + } + + /// + /// Applies a set of bits to an existing set, overriding any bits that aren't set + /// to "unspecified" in the input. + /// + public void Apply(TriState16 overrides) { + ushort mask = (ushort) ~(overrides.mZero | overrides.mOne); + mZero = (ushort)((mZero & mask) | overrides.mZero); + mOne = (ushort)((mOne & mask) | overrides.mOne); + } + + /// + /// Returns 0, 1, -1, or -2 depending on whether the specified bit is 0, 1, + /// indeterminate, or unspecified. + /// + public int GetBit(int bit) { + bool zero = ((mZero >> bit) & 0x01) != 0; + bool one = ((mOne >> bit) & 0x01) != 0; + if (zero ^ one) { + // Only one of the bits is set. + if (one) { + return 1; + } else { + return 0; + } + } else { + // Both or neither are set. + if (zero) { + return INDETERMINATE; + } else { + return UNSPECIFIED; + } + } + } + + + public static bool operator ==(TriState16 a, TriState16 b) { + return a.mZero == b.mZero && a.mOne == b.mOne; + } + public static bool operator !=(TriState16 a, TriState16 b) { + return !(a == b); + } + public override bool Equals(object obj) { + return obj is TriState16 && this == (TriState16)obj; + } + public override int GetHashCode() { + return (mOne << 16) | mZero; + } + + public override string ToString() { + return mZero.ToString("x4") + "-" + mOne.ToString("x4"); + } + } +} diff --git a/CodeLab/App.config b/CodeLab/App.config new file mode 100644 index 0000000..731f6de --- /dev/null +++ b/CodeLab/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/CodeLab/CodeLab.csproj b/CodeLab/CodeLab.csproj new file mode 100644 index 0000000..0bba042 --- /dev/null +++ b/CodeLab/CodeLab.csproj @@ -0,0 +1,84 @@ + + + + + Debug + AnyCPU + {3B66ABD8-7129-4D2B-B48E-B03FEC835CE1} + WinExe + CodeLab + CodeLab + v4.6.1 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + Form + + + MainWindow.cs + + + + + MainWindow.cs + Designer + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + \ No newline at end of file diff --git a/CodeLab/Form1.resx b/CodeLab/Form1.resx new file mode 100644 index 0000000..75db02c --- /dev/null +++ b/CodeLab/Form1.resx @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 132, 17 + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIDSURBVDhPpZLrS5NhGMb3j4SWh0oRQVExD4gonkDpg4hG + YKxG6WBogkMZKgPNCEVJFBGdGETEvgwyO9DJE5syZw3PIlPEE9pgBCLZ5XvdMB8Ew8gXbl54nuf63dd9 + 0OGSnwCahxbPRNPAPMw9Xpg6ZmF46kZZ0xSKzJPIrhpDWsVnpBhGkKx3nAX8Pv7z1zg8OoY/cITdn4fw + bf/C0kYAN3Ma/w3gWfZL5kzTKBxjWyK2DftwI9tyMYCZKXbNHaD91bLYJrDXsYbrWfUKwJrPE9M2M1Oc + VzOOpHI7Jr376Hi9ogHqFIANO0/MmmmbmSmm9a8ze+I4MrNWAdjtoJgWcx+PSzg166yZZ8xM8XvXDix9 + c4jIqFYAjoriBV9AhEPv1mH/sonogha0afbZMMZz+yreTGyhpusHwtNNCsA5U1zS4BLxzJIfg299qO32 + Ir7UJtZfftyATqeT+8o2D8JSjQrAJblrncYL7ZJ2+bfaFnC/1S1NjL3diRat7qrO7wLRP3HjWsojBeCo + mDEo5mNjuweFGvjWg2EBhCbpkW78htSHHwRyNdmgAFzPEee2iFkzayy2OLXzT4gr6UdUnlXrullsxxQ+ + kx0g8BTA3aZlButjSTyjODq/WcQcW/B/Je4OQhLvKQDnzN1mp0nnkvAhR8VuMzNrpm1mpjgkoVwB/v8D + TgDQASA1MVpwzwAAAABJRU5ErkJggg== + + + \ No newline at end of file diff --git a/CodeLab/MainWindow.Designer.cs b/CodeLab/MainWindow.Designer.cs new file mode 100644 index 0000000..a5641bb --- /dev/null +++ b/CodeLab/MainWindow.Designer.cs @@ -0,0 +1,46 @@ +namespace WorkBench +{ + partial class MainWindow + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.SuspendLayout(); + // + // MainWindow + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(284, 261); + this.Name = "MainWindow"; + this.Text = "6502bench"; + this.ResumeLayout(false); + + } + + #endregion + } +} \ No newline at end of file diff --git a/CodeLab/MainWindow.cs b/CodeLab/MainWindow.cs new file mode 100644 index 0000000..359f39e --- /dev/null +++ b/CodeLab/MainWindow.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace WorkBench +{ + public partial class MainWindow : Form + { + public MainWindow() + { + InitializeComponent(); + } + } +} diff --git a/CodeLab/MainWindow.resx b/CodeLab/MainWindow.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/CodeLab/MainWindow.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/CodeLab/Program.cs b/CodeLab/Program.cs new file mode 100644 index 0000000..8bfd5f0 --- /dev/null +++ b/CodeLab/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace WorkBench +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainWindow()); + } + } +} diff --git a/CodeLab/Properties/AssemblyInfo.cs b/CodeLab/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..fbf61d5 --- /dev/null +++ b/CodeLab/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("WorkBench")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("WorkBench")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("3b66abd8-7129-4d2b-b48e-b03fec835ce1")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/CodeLab/Properties/Resources.Designer.cs b/CodeLab/Properties/Resources.Designer.cs new file mode 100644 index 0000000..4b8ae6a --- /dev/null +++ b/CodeLab/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace CodeLab.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CodeLab.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/CodeLab/Properties/Resources.resx b/CodeLab/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/CodeLab/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/CodeLab/Properties/Settings.Designer.cs b/CodeLab/Properties/Settings.Designer.cs new file mode 100644 index 0000000..99d3a5e --- /dev/null +++ b/CodeLab/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace CodeLab.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.6.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/CodeLab/Properties/Settings.settings b/CodeLab/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/CodeLab/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/CodeLab/README.md b/CodeLab/README.md new file mode 100644 index 0000000..b8ff772 --- /dev/null +++ b/CodeLab/README.md @@ -0,0 +1 @@ +This is just a place-holder. Nothing to see here. diff --git a/CommonUtil/CRC32.cs b/CommonUtil/CRC32.cs new file mode 100644 index 0000000..c492bb7 --- /dev/null +++ b/CommonUtil/CRC32.cs @@ -0,0 +1,109 @@ +/* + * Copyright 2018 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.Diagnostics; +using System.IO; + +namespace CommonUtil { + /// + /// Compute a standard CRC-32 (polynomial 0xedb88320, or + /// x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1). + /// + public static class CRC32 { + private static readonly uint[] sTable = ComputeTable(); + + private const uint INVERT = 0xffffffff; + + /// + /// Generates 256-entry CRC table. + /// + /// Table. + private static uint[] ComputeTable() { + uint[] table = new uint[256]; + + uint poly = 0xedb88320; + for (int i = 0; i < 256; i++) { + uint val = (uint) i; + for (int j = 0; j < 8; j++) { + val = (val & 1) != 0 ? poly ^ (val >> 1) : val >> 1; + } + table[i] = val; + } + + return table; + } + + /// + /// Computes a CRC on part of a buffer of data. + /// + /// Previously computed CRC value. Initially zero. + /// Data to compute CRC on. + /// Start offset within buffer. + /// Number of bytes to process. + /// New CRC value. + public static uint OnBuffer(uint crc, byte[] buffer, int offset, int count) { + crc = crc ^ INVERT; + while (count-- != 0) { + crc = sTable[(crc ^ buffer[offset]) & 0xff] ^ (crc >> 8); + offset++; + } + return crc ^ INVERT; + } + + /// + /// Computes a CRC on a buffer of data. + /// + /// Previously computed CRC value. Initially zero. + /// Data to compute CRC on. + /// New CRC value. + public static uint OnWholeBuffer(uint crc, byte[] buffer) { + return OnBuffer(crc, buffer, 0, buffer.Length); + } + + /// + /// Computes a CRC on an entire file. + /// + /// Full path to file to open. + /// Receives the CRC. + /// True on success, false on file error. + public static bool OnWholeFile(string pathName, out uint ocrc) { + try { + using (FileStream fs = File.Open(pathName, FileMode.Open, FileAccess.Read)) { + byte[] buffer = new byte[8192]; + uint crc = 0; + long remain = fs.Length; + while (remain != 0) { + int toRead = (remain < buffer.Length) ? (int)remain : buffer.Length; + int actual = fs.Read(buffer, 0, toRead); + if (toRead != actual) { + throw new IOException("Expected " + toRead + ", got " + actual); + } + + crc = OnBuffer(crc, buffer, 0, toRead); + remain -= toRead; + } + + ocrc = crc; + return true; + } + } catch (IOException ioe) { + Debug.WriteLine("Fail: " + ioe); + ocrc = 0; + return false; + } + } + } +} diff --git a/CommonUtil/CommonUtil.csproj b/CommonUtil/CommonUtil.csproj new file mode 100644 index 0000000..93850c8 --- /dev/null +++ b/CommonUtil/CommonUtil.csproj @@ -0,0 +1,22 @@ + + + + netstandard2.0 + + + + + True + True + Resources.resx + + + + + + PublicResXFileCodeGenerator + Resources.Designer.cs + + + + diff --git a/CommonUtil/Container.cs b/CommonUtil/Container.cs new file mode 100644 index 0000000..9974fa5 --- /dev/null +++ b/CommonUtil/Container.cs @@ -0,0 +1,40 @@ +/* + * Copyright 2018 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.Linq; + +namespace CommonUtil { + public class Container { + /// + /// Compares two lists of strings to see if their contents are equal. The lists + /// must contain the same strings, in the same order. + /// + /// List #1. + /// List #2. + /// String comparer (e.g. StringComparer.InvariantCulture). If + /// null, the default string comparer is used. + /// True if the lists are equal. + public static bool StringListEquals(IList l1, IListl2, + StringComparer comparer) { + // Quick check for reference equality. + if (l1 == l2) { + return true; + } + return Enumerable.SequenceEqual(l1, l2, comparer); + } + } +} diff --git a/CommonUtil/DebugLog.cs b/CommonUtil/DebugLog.cs new file mode 100644 index 0000000..c74096a --- /dev/null +++ b/CommonUtil/DebugLog.cs @@ -0,0 +1,233 @@ +/* + * Copyright 2018 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.IO; +using System.Text; + +namespace CommonUtil { + /// + /// Debug log facility, with priority levels and time stamps. + /// + /// The logs are held in memory. The internal storage expands as logs are added + /// until the maximum size is reached, then switches to circular buffering. This + /// minimizes overhead for small logs while avoiding infinite expansion. + /// + public class DebugLog { + /// + /// Log priority levels, in ascending order. "Silent" is only used as an argument + /// when setting the minimum priority level. + /// + public enum Priority { + Verbose = 0, Debug, Info, Warning, Error, Silent + } + + private static char[] sSingleLetter = { 'V', 'D', 'I', 'W', 'E', 'S' }; + + /// + /// Holds a single log entry. + /// + private struct LogEntry { + public DateTime mWhen; + public Priority mPriority; + public string mText; + + public LogEntry(Priority prio, string msg) { + mWhen = DateTime.Now; + mPriority = prio; + mText = msg; + } + } + + /// + /// Log collection. + /// + private List mEntries = new List(); + private int mTopEntry = 0; + + /// + /// Date/time when the log object was created. Used for relative time display mode. + /// + private DateTime mStartWhen; + + /// + /// If set, display time stamps as relative time rather than absolute. + /// + private bool mShowRelTime = false; + + /// + /// Minimum priority level. Anything below this is ignored. + /// + private Priority mMinPriority = Priority.Debug; + + /// + /// Maximum number of lines we'll hold in memory. This is a simple measure + /// to keep the process from expanding without bound. + /// + private int mMaxLines = 100000; + + /// + /// Constructor. Configures min priority to Info. + /// + public DebugLog() : this(Priority.Info) { } + + /// + /// Constructor. + /// + /// Minimum log priority level. + public DebugLog(Priority prio) { + mMinPriority = prio; + mStartWhen = DateTime.Now; + LogI("Log started at " + DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss zzz")); + } + + /// + /// Sets the message priority threshold. Messages below the specified priority + /// will be ignored. + /// + /// Minimum priority value. + public void SetMinPriority(Priority prio) { + mMinPriority = prio; + } + + /// + /// Sets the "show relative time" flag. If set, the timestamp in the log is + /// relative to when the log object was created, instead of wall-clock time. + /// + /// + public void SetShowRelTime(bool showRelTime) { + mShowRelTime = showRelTime; + } + + /// + /// Returns true if a message logged at the specified priority would be accepted. + /// + /// + /// + public bool IsLoggable(Priority prio) { + return prio >= mMinPriority; + } + + /// + /// Clears all entries. + /// + public void Clear() { + mEntries.Clear(); + } + + /// + /// Adds a message to the log buffer. + /// + /// Log priority. + /// Message to log. + public void Log(Priority prio, string message) { + if (prio < mMinPriority) { + return; + } + LogEntry ent = new LogEntry(prio, message); + if (mEntries.Count < mMaxLines) { + // Still growing. + mEntries.Add(ent); + } else { + // Circular replacement. Adding to the end then removing [0] has + // significant performance issues. + mEntries[mTopEntry++] = ent; + if (mTopEntry == mMaxLines) { + mTopEntry = 0; + } + } + } + + public void LogV(string message) { + Log(Priority.Verbose, message); + } + public void LogD(string message) { + Log(Priority.Debug, message); + } + public void LogI(string message) { + Log(Priority.Info, message); + } + public void LogW(string message) { + Log(Priority.Warning, message); + } + public void LogE(string message) { + Log(Priority.Error, message); + } + + /// + /// Dumps the contents of the log to a file. + /// + /// Full or partial pathname. + public void WriteToFile(string pathName) { + StringBuilder sb = new StringBuilder(); + using (StreamWriter sw = new StreamWriter(pathName, false, Encoding.UTF8)) { + for (int i = mTopEntry; i < mEntries.Count; i++) { + WriteEntry(sw, mEntries[i], sb); + } + for (int i = 0; i < mTopEntry; i++) { + WriteEntry(sw, mEntries[i], sb); + } + } + } + + /// + /// Writes a single entry to a file. Pass in a StringBuilder so we don't have + /// to create a new one every time. + /// + private void WriteEntry(StreamWriter sw, LogEntry ent, StringBuilder sb) { + sb.Clear(); + FormatEntry(ent, sb); + sw.WriteLine(sb.ToString()); + } + + /// + /// Formats an entry, appending the text to the provided StringBuilder. + /// + private void FormatEntry(LogEntry ent, StringBuilder sb) { + if (mShowRelTime) { + sb.Append((ent.mWhen - mStartWhen).ToString(@"mm\:ss\.fff")); + } else { + sb.Append(ent.mWhen.ToString(@"hh\:mm\:ss\.fff")); + } + sb.Append(' '); + sb.Append(sSingleLetter[(int)ent.mPriority]); + sb.Append(' '); + sb.Append(ent.mText); + } + + /// + /// Dumps the contents of the log to a string. This is intended for display in a + /// text box, so lines are separated with CRLF. + /// + /// + public string WriteToString() { + StringBuilder sb = new StringBuilder(); + for (int i = mTopEntry; i < mEntries.Count; i++) { + FormatEntry(mEntries[i], sb); + sb.Append("\r\n"); + } + for (int i = 0; i < mTopEntry; i++) { + FormatEntry(mEntries[i], sb); + sb.Append("\r\n"); + } + return sb.ToString(); + } + + public override string ToString() { + return "DebugLog has " + mEntries.Count + " entries"; + } + } +} diff --git a/CommonUtil/FileLoadReport.cs b/CommonUtil/FileLoadReport.cs new file mode 100644 index 0000000..1efc3b2 --- /dev/null +++ b/CommonUtil/FileLoadReport.cs @@ -0,0 +1,136 @@ +/* + * Copyright 2018 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; +using System.Collections.Generic; +using System.Text; + +namespace CommonUtil { + /// + /// File load item, identifying the location, severity, and details of the issue. + /// + public class FileLoadItem { + public const int NO_LINE = -1; + public const int NO_COLUMN = -1; + + public enum Type { + Unknown = 0, + Notice, + Warning, + Error + } + + public int Line { get; private set; } + public int Column { get; private set; } + public Type MsgType { get; private set; } + public string Message { get; private set; } + + public FileLoadItem(int line, int col, Type msgType, string msg) { + Line = line; + Column = col; + MsgType = msgType; ; + Message = msg; + } + } + + /// + /// A structured collection of errors and warnings generated when reading data from a file. + /// + public class FileLoadReport : IEnumerable { + // List of items. Currently unsorted; items will appear in the order they were added. + private List mItems = new List(); + + public string FileName { get; private set; } + public bool HasWarnings { get; private set; } + public bool HasErrors { get; private set; } + + /// + /// Constructor. + /// + /// Name of file being loaded. This is just for output; this + /// class doesn't actually try to access the file. + public FileLoadReport(string fileName) { + FileName = fileName; + } + + public IEnumerator GetEnumerator() { + return mItems.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() { + return mItems.GetEnumerator(); + } + + public int Count { get { return mItems.Count; } } + + /// + /// Adds a new message to the report. + /// + /// Is this a warning or an error? + /// Human-readable message. + public void Add(FileLoadItem.Type msgType, string msg) { + Add(FileLoadItem.NO_LINE, FileLoadItem.NO_COLUMN, msgType, msg); + } + + /// + /// Adds a new message to the report. + /// + /// Line where the issue was seen. + /// Column where the problem starts, or NO_COLUMN. + /// Is this a warning or an error? + /// Human-readable message. + public void Add(int line, int col, FileLoadItem.Type msgType, string msg) { + mItems.Add(new FileLoadItem(line, col, msgType, msg)); + switch (msgType) { + case FileLoadItem.Type.Warning: + HasWarnings = true; + break; + case FileLoadItem.Type.Error: + HasErrors = true; + break; + } + } + + /// + /// Formats the entire collection into a single multi-line string. + /// + /// Formatted string. + public string Format() { + StringBuilder sb = new StringBuilder(); + if (mItems.Count > 0) { + sb.AppendFormat("File {0}:\r\n", FileName); + } + foreach (FileLoadItem item in mItems) { + if (item.Line != FileLoadItem.NO_LINE && item.Column != FileLoadItem.NO_COLUMN) { + sb.AppendFormat(" Line {0}.{1}: {2}: {3}\r\n", item.Line, item.Column, + item.MsgType.ToString().ToLower(), item.Message); + } else if (item.Line != FileLoadItem.NO_LINE) { + sb.AppendFormat(" Line {0}: {1}: {2}\r\n", item.Line, + item.MsgType.ToString().ToLower(), item.Message); + } else { + // Capitalized form looks nicer here. + sb.AppendFormat(" {0}: {1}\r\n", item.MsgType.ToString(), item.Message); + } + } + return sb.ToString(); + } + + public override string ToString() { + return "FileLoadReport: count=" + mItems.Count + " hasWarn=" + + HasWarnings + " hasErr=" + HasErrors; + } + } +} diff --git a/CommonUtil/FileUtil.cs b/CommonUtil/FileUtil.cs new file mode 100644 index 0000000..3cb0289 --- /dev/null +++ b/CommonUtil/FileUtil.cs @@ -0,0 +1,141 @@ +/* + * Copyright 2018 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.Diagnostics; +using System.IO; +using System.Text; + +namespace CommonUtil { + public static class FileUtil { + /// + /// Compares the contents of a file against a byte array. + /// + /// Expected data values. + /// Pathname of file to inspect. + /// Offset of first mismatch. If this is equal to expected.Length + /// or File(pathName).Length, then the contents were equal but one source stopped + /// early. The badFileVal parameter will be zero. + /// Mismatched value, from the file. + /// True if the contents match, false if not. + public static bool CompareBinaryFile(byte[] expected, string pathName, + out int badOffset, out byte badFileVal) { + badOffset = -1; + badFileVal = 0; + + int chunkOffset = 0; + byte[] buffer = new byte[65536]; + using (FileStream fs = File.Open(pathName, FileMode.Open, FileAccess.Read)) { + //if (fs.Length != expected.Length) { + // return false; + //} + int fileRemain = (int)fs.Length; + + while (fileRemain != 0) { + int toRead = Math.Min(buffer.Length, fileRemain); + int actual = fs.Read(buffer, 0, toRead); + if (actual != toRead) { + // File I/O problem; unexpected. + return false; + } + + for (int i = 0; i < toRead; i++) { + if (chunkOffset + i >= expected.Length) { + // File on disk was too long. + Debug.Assert(fs.Length > expected.Length); + badOffset = chunkOffset + i; + return false; + } + if (expected[chunkOffset + i] != buffer[i]) { + badOffset = chunkOffset + i; + badFileVal = buffer[i]; + return false; + } + } + + fileRemain -= toRead; + chunkOffset += toRead; + } + + if (fs.Length != expected.Length) { + Debug.Assert(fs.Length < expected.Length); + badOffset = (int) fs.Length; + return false; + } + } + + return true; + } + + /// + /// Compares two text files line-by-line. Ignores line termination characters. + /// Assumes UTF-8 encoding. + /// + /// Full path of first file. + /// Full path of second file. + /// Line number of first differing line. + /// Differing line from first file. + /// Differing line from second file. + /// True if the files are equal. + public static bool CompareTextFiles(string pathName1, string pathName2, + out int firstDiffLine, out string line1, out string line2) { + int line = 0; + using (StreamReader sr1 = new StreamReader(pathName1, Encoding.UTF8)) { + using (StreamReader sr2 = new StreamReader(pathName2, Encoding.UTF8)) { + while (true) { + // ReadLine strips the EOL char(s) + string str1 = sr1.ReadLine(); + string str2 = sr2.ReadLine(); + line++; + if (str1 != str2) { + firstDiffLine = line; + line1 = str1; + line2 = str2; + return false; + } + if (str1 == null) { + Debug.Assert(str2 == null); + break; + } + } + } + } + + firstDiffLine = -1; + line1 = line2 = null; + return true; + } + + /// + /// Determines whether the destination file is missing or older than the source file. + /// This can be used do decide whether it's necessary to copy srcFile over. + /// + /// File of interest. + /// File to compare dates with. + /// True if dstFile is missing or older than srcFile. + public static bool FileMissingOrOlder(string dstFile, string srcFile) { + FileInfo fid = new FileInfo(dstFile); + if (!fid.Exists) { + return true; // not there + } + FileInfo fis = new FileInfo(srcFile); + if (!fis.Exists) { + return false; // nothing to compare against + } + + return (fid.LastWriteTimeUtc < fis.LastWriteTimeUtc); + } + } +} diff --git a/CommonUtil/Misc.cs b/CommonUtil/Misc.cs new file mode 100644 index 0000000..71625fe --- /dev/null +++ b/CommonUtil/Misc.cs @@ -0,0 +1,32 @@ +/* + * Copyright 2018 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.Linq; + +namespace CommonUtil { + public static class Misc { + // Given a type, dump all namespaces found in the same assembly. + // https://stackoverflow.com/a/1549216/294248 + public static void DumpNamespacesInAssembly(Type type) { + Console.WriteLine("Assembly: " + type.Assembly.Location); + Type[] typeList = type.Assembly.GetTypes(); + var namespaces = typeList.Select(t => t.Namespace).Distinct(); + foreach (string ns in namespaces) { + Console.WriteLine(" " + ns); + } + } + } +} diff --git a/CommonUtil/Properties/Resources.Designer.cs b/CommonUtil/Properties/Resources.Designer.cs new file mode 100644 index 0000000..b61cbb7 --- /dev/null +++ b/CommonUtil/Properties/Resources.Designer.cs @@ -0,0 +1,108 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace CommonUtil.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CommonUtil.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to File not found. + /// + public static string ERR_FILE_NOT_FOUND { + get { + return ResourceManager.GetString("ERR_FILE_NOT_FOUND", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid address. + /// + public static string ERR_INVALID_ADDRESS { + get { + return ResourceManager.GetString("ERR_INVALID_ADDRESS", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid numeric constant. + /// + public static string ERR_INVALID_NUMERIC_CONSTANT { + get { + return ResourceManager.GetString("ERR_INVALID_NUMERIC_CONSTANT", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Syntax error. + /// + public static string ERR_SYNTAX { + get { + return ResourceManager.GetString("ERR_SYNTAX", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to -.-.-. + /// + public static string NO_VERSION { + get { + return ResourceManager.GetString("NO_VERSION", resourceCulture); + } + } + } +} diff --git a/CommonUtil/Properties/Resources.resx b/CommonUtil/Properties/Resources.resx new file mode 100644 index 0000000..88d70bb --- /dev/null +++ b/CommonUtil/Properties/Resources.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + File not found + + + Invalid address + + + Invalid numeric constant + + + Syntax error + + + -.-.- + + \ No newline at end of file diff --git a/CommonUtil/RangeSet.cs b/CommonUtil/RangeSet.cs new file mode 100644 index 0000000..e13014d --- /dev/null +++ b/CommonUtil/RangeSet.cs @@ -0,0 +1,426 @@ +/* + * Copyright 2018 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; +using System.Collections.Generic; +using System.Diagnostics; + +namespace CommonUtil { + /// + /// Compact representation of a set of integers that tend to be adjacent. + /// + /// The default enumeration is a series of integers, not a series of ranges. Use + /// RangeListIterator to get the latter. + /// + /// Most operations operate in log(N) time, where N is the number of + /// regions. + /// + public class RangeSet : IEnumerable { + /// + /// List of ranges, in sorted order. + /// + private List mRangeList = new List(); + + /// + /// Number of values in the set. + /// + public int Count { get; private set; } + + /// + /// For unit tests: return the number of Range elements in the list. + /// + public int DebugRangeCount { get { return mRangeList.Count; } } + + /// + /// Represents a contiguous range of values. + /// + public struct Range { + /// + /// Lowest value (inclusive). + /// + public int Low { get; set; } + + /// + /// Highest value (inclusive). + /// + public int High { get; set; } + + public Range(int low, int high) { + Debug.Assert(low <= high); + Low = low; + High = high; + } + + /// + /// Returns true if the specified value falls in this range. + /// + public bool Contains(int val) { + return (val >= Low && val <= High); + } + } + + /// + /// Iterator definition. + /// + private class RangeSetIterator : IEnumerator { + /// + /// The RangeSet we're iterating over. + /// + private RangeSet mSet; + + // Index of current Range element in mSet.mRangeList. + private int mListIndex = -1; + + // Current range, extracted from mRangeList. + private Range mCurrentRange; + + // Current value in mCurrentRange. + private int mCurrentVal; + + + /// + /// Constructor. + /// + /// RangeSet to iterate over. + public RangeSetIterator(RangeSet set) { + mSet = set; + Reset(); + } + + // IEnumerator: current element + public object Current { + get { + if (mListIndex < 0) { + // not started + return null; + } + return mCurrentVal; + } + } + + /// + /// Puts the next range in the list in mCurrentRange. + /// + /// True on success, false if we reached the end of the list. + private bool GetNextRange() { + mListIndex++; // increments to 0 on first invocation + if (mListIndex == mSet.mRangeList.Count) { + // no more ranges + return false; + } + + mCurrentRange = mSet.mRangeList[mListIndex]; + mCurrentVal = mCurrentRange.Low; + return true; + } + + // IEnumerator: move to the next element, returning false if there isn't one + public bool MoveNext() { + if (mListIndex < 0) { + // just started + return GetNextRange(); + } else { + // iterating within range object + mCurrentVal++; + if (mCurrentVal > mCurrentRange.High) { + // finished with this one, move on to the next + return GetNextRange(); + } else { + return true; + } + } + } + + // IEnumerator: reset state + public void Reset() { + mListIndex = -1; + } + } + + + /// + /// General constructor. Creates an empty set. + /// + public RangeSet() { + Count = 0; + } + + /// + /// Constructs set from an iterator. + /// + /// Iterator that generates a set of integers in ascending order. + public RangeSet(IEnumerator iter) : this() { + if (!iter.MoveNext()) { + return; + } + int first = (int) iter.Current; + Count++; + Range curRange = new Range(first, first); + + while (iter.MoveNext()) { + int val = (int) iter.Current; + Count++; + if (val == curRange.High + 1) { + // Add to current range. + curRange.High = val; + } else { + // Not contiguous, create new range. + mRangeList.Add(curRange); + curRange = new Range(val, val); + } + } + + mRangeList.Add(curRange); + } + + /// + /// Returns an enumerator that iterates through the range list, returning Range objects. + /// + public IEnumerator RangeListIterator { + get { return mRangeList.GetEnumerator(); } + } + + /// + /// Removes all values from the set. + /// + public void Clear() { + mRangeList.Clear(); + Count = 0; + } + + // IEnumerable: get an enumerator instance that returns integer values + public IEnumerator GetEnumerator() { + return new RangeSetIterator(this); + } + + // IEnumerable + IEnumerator IEnumerable.GetEnumerator() { + return (IEnumerator)GetEnumerator(); + } + + /// + /// Finds the range that contains "val", or an appropriate place in the list to + /// insert a new range. + /// + /// Value to find. + /// The index of the matching element, or a negative value indicating + /// the index to insert at. 2C doesn't support negative 0, so the insertion + /// index will be incremented before negation. + private int FindValue(int val) { + int low = 0; + int high = mRangeList.Count - 1; + while (low <= high) { + int mid = (low + high) / 2; + Range midRange = mRangeList[mid]; + + if (midRange.Contains(val)) { + // found it + return mid; + } else if (val < midRange.Low) { + // too big, move the high end in + high = mid - 1; + } else if (val > midRange.High) { + // too small, move the low end in + low = mid + 1; + } else { + // WTF... list not sorted? + throw new Exception("Bad binary search"); + } + } + + // Not found, insert before "low". + return -(low + 1); + } + + /// + /// Determines whether val is a member of the set. + /// + /// Value to check. + /// True if the value is a member of the set. + public bool Contains(int val) { + return (FindValue(val) >= 0); + } + + /// + /// Adds a value to the set. If the value is already present, nothing changes. + /// + /// Value to add. + public void Add(int val) { + int listIndex = FindValue(val); + if (listIndex >= 0) { + // Already present in set. + return; + } + Count++; + + if (mRangeList.Count == 0) { + // Empty list, skip the gymnastics. + mRangeList.Add(new Range(val, val)); + return; + } + + // Negate and decrement to get insertion index. This value may == Count if + // the value is higher than all current members. + listIndex = -listIndex - 1; + + if (listIndex > 0 && mRangeList[listIndex - 1].High == val - 1) { + // Expand prior range. Check to see if it blends into next. + if (listIndex < mRangeList.Count && mRangeList[listIndex].Low == val + 1) { + // Combine ranges. + Range prior = mRangeList[listIndex - 1]; + Range next = mRangeList[listIndex]; + Debug.Assert(prior.High + 2 == next.Low); + prior.High = next.High; + mRangeList[listIndex - 1] = prior; + mRangeList.RemoveAt(listIndex); + } else { + // Nope, just expand the prior range. + Range prior = mRangeList[listIndex - 1]; + Debug.Assert(prior.High == val - 1); + prior.High = val; + mRangeList[listIndex - 1] = prior; + } + } else if (listIndex < mRangeList.Count && mRangeList[listIndex].Low == val + 1) { + // Expand next range. + Range next = mRangeList[listIndex]; + Debug.Assert(next.Low == val + 1); + next.Low = val; + mRangeList[listIndex] = next; + } else { + // Add a new single-entry element. + mRangeList.Insert(listIndex, new Range(val, val)); + } + } + + /// + /// Adds a range of contiguous values to the set. + /// + /// Lowest value (inclusive). + /// Highest value (inclusive). + public void AddRange(int low, int high) { + // There's probably some very efficient way to do this. Keeping it simple for now. + for (int i = low; i <= high; i++) { + Add(i); + } + } + + /// + /// Removes a value from the set. If the value is not present, nothing changes. + /// + /// Value to remove. + public void Remove(int val) { + int listIndex = FindValue(val); + if (listIndex < 0) { + // not found + return; + } + + Count--; + + Range rng = mRangeList[listIndex]; + if (rng.Low == val && rng.High == val) { + // Single-value range. Remove. + mRangeList.RemoveAt(listIndex); + } else if (rng.Low == val) { + // We're at the low end, reduce range. + rng.Low = val + 1; + mRangeList[listIndex] = rng; + } else if (rng.High == val) { + // We're at the high end, reduce range. + rng.High = val - 1; + mRangeList[listIndex] = rng; + } else { + // We're in the middle, split the range. + Range next = new Range(val + 1, rng.High); + rng.High = val - 1; + mRangeList[listIndex] = rng; + mRangeList.Insert(listIndex + 1, next); + } + } + + + /// + /// Internal test function. + /// + private static bool CheckRangeSet(RangeSet set, int expectedRanges, int[] expected) { + if (set.DebugRangeCount != expectedRanges) { + Debug.WriteLine("Expected " + expectedRanges + " ranges, got " + + set.DebugRangeCount); + return false; + } + + // Compare actual vs. expected. If we have more actual than expected we'll + // throw on the array access. + int expIndex = 0; + foreach (int val in set) { + if (val != expected[expIndex]) { + Debug.WriteLine("Expected " + expected[expIndex] + ", got " + val); + return false; + } + expIndex++; + } + + // See if we have more expected than actual. + if (expIndex != expected.Length) { + Debug.WriteLine("Expected " + expected.Length + " elements, found " + expIndex); + return false; + } + + // The count is maintained separately, so check it. + if (set.Count != expected.Length) { + Debug.WriteLine("Expected Count=" + expected.Length + ", got " + set.Count); + return false; + } + return true; + } + + /// + /// Executes unit tests. + /// + /// True if all goes well. + public static bool Test() { + bool result = true; + + RangeSet one = new RangeSet(); + one.Add(7); + one.Add(5); + one.Add(3); + one.Add(9); + one.Add(7); + one.Add(8); + one.Add(2); + one.Add(4); + result &= CheckRangeSet(one, 2, new int[] { 2, 3, 4, 5, 7, 8, 9 }); + + one.Remove(2); + one.Remove(9); + one.Remove(4); + result &= CheckRangeSet(one, 3, new int[] { 3, 5, 7, 8 }); + + one.Clear(); + one.AddRange(10, 15); + result &= CheckRangeSet(one, 1, new int[] { 10, 11, 12, 13, 14, 15 }); + + one.Add(-1); + one.Add(0); + one.Add(-2); + result &= CheckRangeSet(one, 2, new int[] { -2, -1, 0, 10, 11, 12, 13, 14, 15 }); + + Debug.WriteLine("RangeSet: test complete (ok=" + result + ")"); + return result; + } + } +} diff --git a/CommonUtil/RawData.cs b/CommonUtil/RawData.cs new file mode 100644 index 0000000..55a4298 --- /dev/null +++ b/CommonUtil/RawData.cs @@ -0,0 +1,63 @@ +/* + * Copyright 2018 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.Diagnostics; + +namespace CommonUtil { + public class RawData { + /// + /// Extracts an integer from the data stream. + /// + /// Raw data stream. + /// Start offset. + /// Word width, which may be 1-4 bytes. + /// True if word is in big-endian order. + /// Value found. + public static int GetWord(byte[] data, int offset, int width, bool isBigEndian) { + if (width < 1 || width > 4 || offset + width > data.Length) { + throw new ArgumentOutOfRangeException("GetWord(offset=" + offset + " width=" + + width + "), data.Length=" + data.Length); + } + if (isBigEndian) { + switch (width) { + case 1: + return data[offset]; + case 2: + return (data[offset] << 8) | data[offset + 1]; + case 3: + return (data[offset] << 16) | (data[offset + 1] << 8) | data[offset + 2]; + case 4: + return (data[offset] << 24) | (data[offset + 1] << 16) | + (data[offset + 2] << 8) | data[offset + 3]; + } + } else { + switch (width) { + case 1: + return data[offset]; + case 2: + return data[offset] | (data[offset + 1] << 8); + case 3: + return data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16); + case 4: + return data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | + data[offset + 3] << 24; + } + } + + throw new Exception("GetWord(): should not be here"); + } + } +} diff --git a/CommonUtil/ShellCommand.cs b/CommonUtil/ShellCommand.cs new file mode 100644 index 0000000..234f8fe --- /dev/null +++ b/CommonUtil/ShellCommand.cs @@ -0,0 +1,222 @@ +/* + * Copyright 2018 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.Diagnostics; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Runtime.InteropServices; + +namespace CommonUtil { + /// + /// Execute a shell command and return stdout/stderr. + /// + /// Returning stdout/stderr separately loses the interleave, but it's unclear whether + /// that gets lost anyway with output buffering and the asynchronous I/O facility. + /// + public class ShellCommand { + // These were handy: + // https://stackoverflow.com/a/32872174/294248 + // https://stackoverflow.com/a/18616369/294248 + // https://stackoverflow.com/a/7334029/294248 + // https://stackoverflow.com/a/5187715/294248 + + private const int CMD_TIMEOUT_MS = 10000; // 10 sec + private bool USE_CMD_EXE = false; + + /// + /// Filename of shell command to execute. + /// + public string CommandFileName { get; private set; } + + /// + /// Arguments to pass to command. Individual arguments are separated by spaces. + /// If an argument may contain spaces (e.g. it's a filename), surround it with + /// double quotes ("). + /// + public string Arguments { get; private set; } + + /// + /// Working directory for command. The directory will be changed for the + /// command only. + /// + public string WorkDirectory { get; private set; } + + public Dictionary EnvVars { get; private set; } + + /// + /// The full command line, for display purposes. This is just CommandFileName + Arguments + /// unless some funny business is going on under the hood. + /// + public string FullCommandLine { get; private set; } + + /// + /// Output from stdout. + /// + public string Stdout { get; private set; } + + /// + /// Output from stderr. + /// + public string Stderr { get; private set; } + + /// + /// Command exit code. Will be 0 on success. + /// + public int ExitCode { get; private set; } + + /// + /// Buffers for gathering stdout/stderr. + /// + private StringBuilder mStdout, mStderr; + + + /// + /// Constructor. + /// + /// Filename of command to execute. + /// Command arguments, separated by spaces. Surround args with + /// embedded spaces with double quotes. Pass empty string if no args. + /// Working directory for command. Pass an empty string if you + /// want to use the default. + /// Dictionary of values to set in the shell environment. + public ShellCommand(string commandFileName, string arguments, string workDir, + Dictionary env) { + Debug.Assert(commandFileName != null); + Debug.Assert(arguments != null); + Debug.Assert(workDir != null); + + CommandFileName = commandFileName; + Arguments = arguments; + WorkDirectory = workDir; + EnvVars = env; + + ExitCode = -100; + + mStdout = new StringBuilder(); + mStderr = new StringBuilder(); + } + + /// + /// Execute the command. + /// + /// Process exit code. 0 on success. + public void Execute() { + // Works for full paths to command, but not for shell stuff like "dir". + FileInfo fi = new FileInfo(CommandFileName); + if (!fi.Exists) { + Debug.WriteLine("Warning: file '" + CommandFileName + "' does not exist"); + } + + ProcessStartInfo psi = new ProcessStartInfo(); + if (USE_CMD_EXE) { + // Run inside cmd.exe on Windows. + psi.FileName = "cmd.exe"; + psi.Arguments = "/C " + CommandFileName + + (string.IsNullOrEmpty(Arguments) ? "" : " " + Arguments); + } else { + psi.FileName = CommandFileName; + psi.Arguments = Arguments; + } + FullCommandLine = psi.FileName + " " + psi.Arguments; + psi.CreateNoWindow = true; + psi.RedirectStandardInput = true; + psi.RedirectStandardOutput = true; + psi.RedirectStandardError = true; + psi.UseShellExecute = false; // required for stdin/stdout redirect + if (!string.IsNullOrEmpty(WorkDirectory)) { + psi.WorkingDirectory = WorkDirectory; + } + + if (EnvVars != null) { + foreach (KeyValuePair kvp in EnvVars) { + Debug.WriteLine("ENV: " + kvp.Key + "=" + kvp.Value); + psi.Environment.Add(kvp); + } + } + + try { + using (Process process = Process.Start(psi)) { + process.OutputDataReceived += (sendingProcess, outLine) => + mStdout.AppendLine(outLine.Data); + process.ErrorDataReceived += (sendingProcess, errLine) => + mStderr.AppendLine(errLine.Data); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + // Close stdin so interactive programs don't stall. + process.StandardInput.Close(); + + // I'm calling with a (fairly long) timeout, just in case. + process.WaitForExit(CMD_TIMEOUT_MS); + if (!process.HasExited) { + Debug.WriteLine("Process stalled, killing"); + process.Kill(); + } + + // WaitForExit(timeout) can return before the async stdout/stderr stuff + // has completed. Calling it without a timeout ensures correct behavior. + process.WaitForExit(); + + // This will be zero on success. I've seen 1 when the command wasn't + // found, and -1 when the process was killed. + ExitCode = process.ExitCode; + } + } catch (Exception ex) { + // This can happen if the command doesn't exist. + ExitCode = -2000; + Stdout = string.Empty; + Stderr = "Failed to execute command: " + FullCommandLine + "\r\n" + ex.ToString(); + return; + } + + Stdout = mStdout.ToString(); + Stderr = mStderr.ToString(); + } + + /// + /// Opens a tab in the system web browser for the specified URL. + /// + /// NOTE: on Windows 10, as of 2018/09/02, this loses the anchor (the "#thing" at the + /// end of the URL). Chasing through various stackoverflow posts, it appears the + /// only way around this is to invoke the specific browser (which you dredge out of + /// the Registry). + /// + public static void OpenUrl(string url) { + // See https://stackoverflow.com/a/43232486/294248 + // The idea is to see if Start() will just do it, and if it doesn't then fall + // back on something platform-specific. I don't know if this is actually + // necessary -- I suspect Mono will do the right thing. + try { + Process.Start(url); + } catch { + // hack because of this: https://github.com/dotnet/corefx/issues/10361 + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + url = url.Replace("&", "^&"); + Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { + CreateNoWindow = true + }); + } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { + Process.Start("xdg-open", url); + } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { + Process.Start("open", url); + } else { + throw; + } + } + } + } +} diff --git a/CommonUtil/TaskTimer.cs b/CommonUtil/TaskTimer.cs new file mode 100644 index 0000000..765dce0 --- /dev/null +++ b/CommonUtil/TaskTimer.cs @@ -0,0 +1,199 @@ +/* + * Copyright 2018 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.Text; + +namespace CommonUtil { + /// + /// Collects timestamps from a series of events. Events may be nested, but may not overlap. + /// A summary of task durations can be written to a log. + /// + public class TaskTimer { + // TODO(maybe): create a start/end pair that works with a "using" directive to ensure + // that a given item is always closed. + + /// + /// Timed task info. + /// + private class TimedItem { + public string mTag; + public int mIndentLevel; + public DateTime mStartWhen; + public DateTime mEndWhen; + + public TimedItem(string tag, int indentLevel) { + mTag = tag; + mIndentLevel = indentLevel; + mStartWhen = DateTime.Now; + } + } + + /// + /// List of items. They are ordered by when the tasks ended. + /// + private List mItems = new List(); + + /// + /// Indentation level. Cosmetic. + /// + private int mIndentLevel = 0; + + /// + /// Place where next record is to be inserted. + /// + /// We keep inserting records ahead of whatever we inserted last, only advancing + /// the insertion point when we close a record. Essentially a stack that moves + /// forward when you pop() instead of removing the element. This lets us handle + /// nested tasks correctly. + /// + private int mInsertPoint = 0; + + + /// + /// Resets object to initial state. + /// + public void Clear() { + mItems.Clear(); + mIndentLevel = mInsertPoint = 0; + } + + /// + /// Adds a start record for a task. + /// + /// Task tag. + public void StartTask(string tag) { + TimedItem ti = new TimedItem(tag, mIndentLevel); + mItems.Insert(mInsertPoint, ti); + mIndentLevel++; + } + + /// + /// Closes out a record. The tag must match the most recently started task. + /// + /// Task tag. + public void EndTask(string tag) { + TimedItem lastItem = mItems[mInsertPoint]; + if (lastItem.mTag != tag) { + Debug.WriteLine("ERROR: tag mismatch: " + tag + " vs. " + lastItem.mTag); + Debug.Assert(false); + return; + } + + lastItem.mEndWhen = DateTime.Now; + mIndentLevel--; + mInsertPoint++; + Debug.Assert(mIndentLevel >= 0); + } + + /// + /// Prints the timing data into a log object. + /// + /// Output destination. + /// Header message. + public void DumpTimes(string msg, DebugLog log) { + if (mItems.Count == 0) { + return; + } + if (!string.IsNullOrEmpty(msg)) { + log.LogI(msg); + } + StringBuilder sb = new StringBuilder(); + int lastIndent = 0; + foreach (TimedItem ti in mItems) { + sb.Clear(); + FormatItem(ti, ref lastIndent, sb); + log.LogI(sb.ToString()); + } + + //DateTime firstStart = mItems[0].mStartWhen; + //DateTime lastEnd = mItems[mItems.Count - 1].mEndWhen; + //log.LogI(" Total: " + (lastEnd - firstStart).TotalMilliseconds + " ms"); + } + + /// + /// Prints the timing data into the debug log. + /// + /// Header message. + public void DumpTimes(string msg) { + if (mItems.Count == 0) { + return; + } + if (!string.IsNullOrEmpty(msg)) { + Debug.WriteLine(msg); + } + StringBuilder sb = new StringBuilder(); + int lastIndent = 0; + foreach (TimedItem ti in mItems) { + sb.Clear(); + FormatItem(ti, ref lastIndent, sb); + Debug.WriteLine(sb.ToString()); + } + } + + /// + /// Prints the timing data into a string with newlines. + /// + /// Output destination. + /// Header message. + public string DumpToString(string msg) { + if (mItems.Count == 0) { + return msg; + } + StringBuilder sb = new StringBuilder(); +#if DEBUG + sb.Append("[NOTE: debug build -- assertions and extra checks are enabled]\r\n\r\n"); +#endif + if (!string.IsNullOrEmpty(msg)) { + sb.Append(msg); + sb.Append("\r\n\r\n"); + } + int lastIndent = 0; + foreach (TimedItem ti in mItems) { + FormatItem(ti, ref lastIndent, sb); + sb.Append("\r\n"); + } + + return sb.ToString(); + } + + + /// + /// Formats the specified item, appending it to the StringBuilder. + /// + /// Item to format. + /// Previous indentation level. + /// StringBuilder to append to. + private void FormatItem(TimedItem ti, ref int lastIndent, StringBuilder sb) { + for (int i = 0; i <= ti.mIndentLevel - 1; i++) { + sb.Append("| "); + } + if (lastIndent < ti.mIndentLevel) { + //sb.Append("/-"); + sb.Append("/ "); + } else /*if (lastIndent == ti.mIndentLevel)*/ { + sb.Append("| "); + } + sb.Append(ti.mTag); + sb.Append(": "); + sb.Append((ti.mEndWhen - ti.mStartWhen).TotalMilliseconds.ToString()); + sb.Append(" ms"); + + lastIndent = ti.mIndentLevel; + } + } +} diff --git a/CommonUtil/TextUtil.cs b/CommonUtil/TextUtil.cs new file mode 100644 index 0000000..3473343 --- /dev/null +++ b/CommonUtil/TextUtil.cs @@ -0,0 +1,241 @@ +/* + * Copyright 2018 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.Diagnostics; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace CommonUtil { + /// + /// Text utility functions. + /// + public static class TextUtil { + // 100 spaces, useful when padding things out. + private const string SPACES = " " + + " "; + private static readonly char[] CSV_ESCAPE_CHARS = { ',', '"' }; + private const string NonPrintableAsciiPattern = @"[^\u0020-\u007e]"; + private static Regex sNonPrintableAsciiRegex = new Regex(NonPrintableAsciiPattern); + + /// + /// Converts a string to an ASCII-only string, replacing iso-latin characters + /// with their ASCII equivalents. This may change the length of the string. + /// + /// For example, "¿Dónde está Über bären?" becomes "Donde esta Uber baren?". + /// + /// + /// Converted string. + public static string LatinToAscii(string inString) { + // https://stackoverflow.com/questions/140422/ + var newStringBuilder = new StringBuilder(); + newStringBuilder.Append(inString.Normalize(NormalizationForm.FormKD) + .Where(x => x < 128) + .ToArray()); + return newStringBuilder.ToString(); + + // Alternatively? + // System.Text.Encoding.ASCII.GetString( + // System.Text.Encoding.GetEncoding(1251).GetBytes(text)) + } + + /// + /// Returns true if the value is valid high- or low-ASCII. + /// + /// Value to test. + /// True if val is valid ASCII. + public static bool IsHiLoAscii(int val) { + return (val >= 0x20 && val < 0x7f) || (val >= 0xa0 && val < 0xff); + } + + /// + /// Determines whether the character is printable ASCII. + /// + /// Character to evaluate. + /// True if the character is printable ASCII. + public static bool IsPrintableAscii(char ch) { + return ch >= 0x20 && ch < 0x7f; + } + + /// + /// Determines whether the string has nothing but printable ASCII characters in it. + /// + /// String to evaluate. + /// True if all characters are printable ASCII. + public static bool IsPrintableAscii(string str) { + // Linq version: return str.Any(c => c < 0x20 || c > 0x7e); + + MatchCollection matches = sNonPrintableAsciiRegex.Matches(str); + return matches.Count == 0; + } + + /// + /// Converts high-ASCII bytes to a string. + /// + /// Array of bytes with ASCII data. + /// Start offset. + /// String length. + /// Converted string. + public static string HighAsciiToString(byte[] data, int offset, int length) { + StringBuilder sb = new StringBuilder(length); + for (int i = offset; i < offset + length; i++) { + sb.Append((char)(data[i] & 0x7f)); + } + return sb.ToString(); + } + + /// + /// Trims whitespace off the end of a StringBuilder. + /// + /// StringBuilder reference. + public static void TrimEnd(StringBuilder sb) { + const string WSPC = " \t\r\n"; + int len = sb.Length; + while (WSPC.IndexOf(sb[len - 1]) >= 0) { + len--; + } + sb.Length = len; + } + + /// + /// Replaces all occurrences of a string with another string, but only if they appear + /// outside quoted text. Useful for replacing structural bits of a JSON string without + /// messing with the quoted items. Assumes that quoted quotes (backslash-quote) only + /// appear inside quoted text. + /// + /// + /// + /// + public static string NonQuoteReplace(string inStr, string findStr, string repStr) { + // There's probably a better way to do this... + StringBuilder sb = new StringBuilder(inStr.Length + inStr.Length / 20); + int cmpLen = findStr.Length; + bool findStrQuote = findStr.Contains('\"'); // find/rep str switches to in-quote + Debug.Assert(findStrQuote == repStr.Contains('\"')); + + bool inQuote = false; + for (int i = 0; i < inStr.Length; i++) { + char ch = inStr[i]; + if (inQuote) { + // Check to see if the double-quote is quoted. It's safe to back up + // one because we don't start in-quote. + if (ch == '\"' && inStr[i-1] != '\\') { + inQuote = false; + } else { + // in quoted text, keep going + } + sb.Append(ch); + } else { + if (string.Compare(inStr, i, findStr, 0, cmpLen) == 0) { + sb.Append(repStr); + i += cmpLen - 1; + inQuote = findStrQuote; + } else { + if (ch == '"') { + // There are no quoted-quotes outside of quotes, so we don't need + // to check for a '\'. + inQuote = true; + } + sb.Append(ch); + } + } + } + + return sb.ToString(); + } + + /// + /// Pads a string with trailing spaces so that the total length of the line, including + /// previous contents, is the length specified. One trailing space will be added even + /// if the string's length is >= toLen. + /// + /// You must begin with an empty StringBuilder at the start of each line. + /// + /// StringBuilder to append to. + /// String to add. + /// Total line width to pad to. + public static void AppendPaddedString(StringBuilder sb, string str, int toLen) { + if (str == null) { + str = string.Empty; + } + int newLen = sb.Length + str.Length; + if (newLen >= toLen) { + sb.Append(str); + sb.Append(' '); + } else { + sb.Append(str); + // would be nice to avoid this allocation/copy + sb.Append(SPACES.Substring(0, toLen - newLen)); + } + } + + /// + /// Escapes a string for CSV. + /// + /// String to process. + /// Escaped string, or an empty string if the input was null. + public static string EscapeCSV(string str) { + if (str == null) { + return string.Empty; + } + bool needQuote = (str.IndexOfAny(CSV_ESCAPE_CHARS) >= 0); + if (needQuote) { + return '"' + str.Replace("\"", "\"\"") + '"'; + } else { + return str; + } + } + + /// + /// Serializes an integer array into a string. + /// + /// Array to serialize. + /// Serialized data. + public static string SerializeIntArray(int[] values) { + StringBuilder sb = new StringBuilder(64); + sb.Append("int[]"); + for (int i = 0; i < values.Length; i++) { + sb.Append(','); + sb.Append(values[i]); + } + return sb.ToString(); + } + + /// + /// Deserializes an integer array from a string. + /// + /// + /// + public static int[] DeserializeIntArray(string cereal) { + string[] splitted = cereal.Split(','); + if (splitted.Length == 0) { + throw new Exception("Bad serialized int[]"); + } + if (splitted[0] != "int[]") { + throw new Exception("Bad serialized int[], started with " + splitted[0]); + } + int[] arr = new int[splitted.Length - 1]; + try { + for (int i = 1; i < splitted.Length; i++) { + arr[i - 1] = int.Parse(splitted[i]); + } + } catch (Exception ex) { + throw new Exception("Bad serialized int[]: " + ex.Message); + } + return arr; + } + } +} diff --git a/CommonUtil/TypedRangeSet.cs b/CommonUtil/TypedRangeSet.cs new file mode 100644 index 0000000..6380b09 --- /dev/null +++ b/CommonUtil/TypedRangeSet.cs @@ -0,0 +1,511 @@ +/* + * Copyright 2018 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; +using System.Collections.Generic; +using System.Diagnostics; + +namespace CommonUtil { + /// + /// Compact representation of a set of typed integers that tend to be adjacent. + /// We expect there to be relatively few different types of things. + /// + /// The default enumeration is a series of integers, not a series of ranges. Use + /// RangeListIterator to get the latter. + /// + /// Most operations operate in log(N) time, where N is the number of + /// regions. + /// + public class TypedRangeSet : IEnumerable { + /// + /// List of ranges, in sorted order. + /// + private List mRangeList = new List(); + + /// + /// Number of values in the set. + /// + public int Count { get; private set; } + + /// + /// Returns the number of Range elements in the list. + /// + public int RangeCount { get { return mRangeList.Count; } } + + /// + /// Represents a contiguous range of values. + /// + public struct TypedRange { + /// + /// Lowest value (inclusive). + /// + public int Low { get; set; } + + /// + /// Highest value (inclusive). + /// + public int High { get; set; } + + /// + /// Value type in this range. + /// + public int Type { get; set; } + + public TypedRange(int low, int high, int type) { + Debug.Assert(low <= high); + Low = low; + High = high; + Type = type; + } + + public bool Contains(int val) { + return (val >= Low && val <= High); + } + } + + /// + /// Value + type pair. Returned from foreach enumerator. + /// + public struct Tuple { + public int Value; + public int Type; + + public Tuple(int value, int type) { + Value = value; + Type = type; + } + + public static bool operator ==(Tuple a, Tuple b) { + return a.Value == b.Value && a.Type == b.Type; + } + public static bool operator !=(Tuple a, Tuple b) { + return !(a == b); + } + public override bool Equals(object obj) { + return obj is Tuple && this == (Tuple)obj; + } + public override int GetHashCode() { + return Value ^ Type; + } + public override string ToString() { + return Value + " (" + Type + ")"; + } + } + + /// + /// Iterator definition. + /// + private class TypedRangeSetIterator : IEnumerator { + /// + /// The TypedRangeSet we're iterating over. + /// + private TypedRangeSet mSet; + + // Index of current Range element in mSet.mRangeList. + private int mListIndex = -1; + + // Current range, extracted from mRangeList. + private TypedRange mCurrentRange; + + // Current value in mCurrentRange. + private int mCurrentVal; + + + /// + /// Constructor. + /// + /// TypedRangeSet to iterate over. + public TypedRangeSetIterator(TypedRangeSet set) { + mSet = set; + Reset(); + } + + // IEnumerator: current element + public object Current { + get { + if (mListIndex < 0) { + // not started + return null; + } + return new Tuple(mCurrentVal, mCurrentRange.Type); + } + } + + // IEnumerator + Tuple IEnumerator.Current { + get { + return (Tuple)Current; + } + } + + /// + /// Puts the next range in the list in mCurrentRange. + /// + /// True on success, false if we reached the end of the list. + private bool GetNextRange() { + mListIndex++; // increments to 0 on first invocation + if (mListIndex == mSet.mRangeList.Count) { + // no more ranges + return false; + } + + mCurrentRange = mSet.mRangeList[mListIndex]; + mCurrentVal = mCurrentRange.Low; + return true; + } + + // IEnumerator: move to the next element, returning false if there isn't one + public bool MoveNext() { + if (mListIndex < 0) { + // just started + return GetNextRange(); + } else { + // iterating within range object + mCurrentVal++; + if (mCurrentVal > mCurrentRange.High) { + // finished with this one, move on to the next + return GetNextRange(); + } else { + return true; + } + } + } + + // IEnumerator: reset state + public void Reset() { + mListIndex = -1; + } + + // IEnumerator + public void Dispose() { + mSet = null; + } + } + + + /// + /// Constructor. Creates an empty set. + /// + public TypedRangeSet() { + Count = 0; + } + + /// + /// Returns an enumerator that iterates through the range list, returning Range objects. + /// + public IEnumerator RangeListIterator { + get { return mRangeList.GetEnumerator(); } + } + + /// + /// Removes all values from the set. + /// + public void Clear() { + mRangeList.Clear(); + Count = 0; + } + + // IEnumerable: get an enumerator instance that returns integer values + public IEnumerator GetEnumerator() { + return new TypedRangeSetIterator(this); + } + + // IEnumerable + IEnumerator IEnumerable.GetEnumerator() { + return (IEnumerator)GetEnumerator(); + } + + /// + /// Finds the range that contains "val", or an appropriate place in the list to + /// insert a new range. + /// + /// Value to find. + /// The index of the matching element, or a negative value indicating + /// the index to insert at. 2C doesn't support negative 0, so the insertion + /// index will be incremented before negation. + private int FindValue(int val) { + int low = 0; + int high = mRangeList.Count - 1; + while (low <= high) { + int mid = (low + high) / 2; + TypedRange midRange = mRangeList[mid]; + + if (midRange.Contains(val)) { + // found it + return mid; + } else if (val < midRange.Low) { + // too big, move the high end in + high = mid - 1; + } else if (val > midRange.High) { + // too small, move the low end in + low = mid + 1; + } else { + // WTF... list not sorted? + throw new Exception("Bad binary search"); + } + } + + // Not found, insert before "low". + return -(low + 1); + } + + /// + /// Determines whether val is a member of the set. + /// + /// Value to check. + /// True if the value is a member of the set. + public bool Contains(int val) { + return (FindValue(val) >= 0); + } + + /// + /// Gets the type of the specified value. + /// + /// Value to query. + /// Receives the type, or -1 if the value is not in the set. + /// True if the value is in the set. + public bool GetType(int val, out int type) { + int listIndex = FindValue(val); + if (listIndex >= 0) { + type = mRangeList[listIndex].Type; + return true; + } else { + type = -1; + return false; + } + } + + /// + /// Adds or changes a value to the set. If the value is already present and has + /// a matching type, nothing changes. + /// + /// Value to add. + /// Value's type. + public void Add(int val, int type) { + int listIndex = FindValue(val); + if (listIndex >= 0) { + // Value is present in set, check type. + if (mRangeList[listIndex].Type == type) { + // It's a match, do nothing. + return; + } + + // Wrong type. Remove previous entry, then fall through to add new. + Remove(val); + listIndex = FindValue(val); // get insertion point + } + Count++; + + if (mRangeList.Count == 0) { + // Empty list, skip the gymnastics. + mRangeList.Add(new TypedRange(val, val, type)); + return; + } + + // Negate and decrement to get insertion index. This value may == Count if + // the value is higher than all current members. + listIndex = -listIndex - 1; + + if (listIndex > 0 && mRangeList[listIndex - 1].High == val - 1 && + mRangeList[listIndex - 1].Type == type) { + // Expand prior range. Check to see if it blends into next as well. + if (listIndex < mRangeList.Count && mRangeList[listIndex].Low == val + 1 && + mRangeList[listIndex].Type == type) { + // Combine ranges. + TypedRange prior = mRangeList[listIndex - 1]; + TypedRange next = mRangeList[listIndex]; + Debug.Assert(prior.High + 2 == next.Low); + prior.High = next.High; + mRangeList[listIndex - 1] = prior; + mRangeList.RemoveAt(listIndex); + } else { + // Nope, just expand the prior range. + TypedRange prior = mRangeList[listIndex - 1]; + Debug.Assert(prior.High == val - 1); + prior.High = val; + mRangeList[listIndex - 1] = prior; + } + } else if (listIndex < mRangeList.Count && mRangeList[listIndex].Low == val + 1 && + mRangeList[listIndex].Type == type) { + // Expand next range. + TypedRange next = mRangeList[listIndex]; + Debug.Assert(next.Low == val + 1); + next.Low = val; + mRangeList[listIndex] = next; + } else { + // Nothing adjacent, add a new single-entry element. + mRangeList.Insert(listIndex, new TypedRange(val, val, type)); + } + } + + /// + /// Adds a range of contiguous values to the set. + /// + /// Lowest value (inclusive). + /// Highest value (inclusive). + /// Value type. + public void AddRange(int low, int high, int type) { + // There's probably some very efficient way to do this. Keeping it simple for now. + for (int i = low; i <= high; i++) { + Add(i, type); + } + } + + /// + /// Removes a value from the set. If the value is not present, nothing changes. + /// + /// Value to remove. + public void Remove(int val) { + int listIndex = FindValue(val); + if (listIndex < 0) { + // not found + return; + } + + Count--; + + TypedRange rng = mRangeList[listIndex]; + if (rng.Low == val && rng.High == val) { + // Single-value range. Remove. + mRangeList.RemoveAt(listIndex); + } else if (rng.Low == val) { + // We're at the low end, reduce range. + rng.Low = val + 1; + mRangeList[listIndex] = rng; + } else if (rng.High == val) { + // We're at the high end, reduce range. + rng.High = val - 1; + mRangeList[listIndex] = rng; + } else { + // We're in the middle, split the range. + TypedRange next = new TypedRange(val + 1, rng.High, rng.Type); + rng.High = val - 1; + mRangeList[listIndex] = rng; + mRangeList.Insert(listIndex + 1, next); + } + } + + + /// + /// Internal test function. + /// + private static bool CheckTypedRangeSet(TypedRangeSet set, int expectedRanges, + Tuple[] expected) { + if (set.RangeCount != expectedRanges) { + Debug.WriteLine("Expected " + expectedRanges + " ranges, got " + + set.RangeCount); + return false; + } + + // Compare actual vs. expected. If we have more actual than expected we'll + // throw on the array access. + int expIndex = 0; + foreach (TypedRangeSet.Tuple val in set) { + if (val != expected[expIndex]) { + Debug.WriteLine("Expected " + expected[expIndex] + ", got " + val); + return false; + } + expIndex++; + } + + // See if we have more expected than actual. + if (expIndex != expected.Length) { + Debug.WriteLine("Expected " + expected.Length + " elements, found " + expIndex); + return false; + } + + // The count is maintained separately, so check it. + if (set.Count != expected.Length) { + Debug.WriteLine("Expected Count=" + expected.Length + ", got " + set.Count); + return false; + } + return true; + } + + /// + /// Executes unit tests. + /// + /// True if all goes well. + public static bool Test() { + bool result = true; + + TypedRangeSet one = new TypedRangeSet(); + one.Add(7, 100); + one.Add(5, 100); + one.Add(3, 100); + one.Add(9, 100); + one.Add(7, 100); + one.Add(8, 100); + one.Add(2, 100); + one.Add(4, 100); + result &= CheckTypedRangeSet(one, 2, new Tuple[] { + new Tuple(2, 100), + new Tuple(3, 100), + new Tuple(4, 100), + new Tuple(5, 100), + new Tuple(7, 100), + new Tuple(8, 100), + new Tuple(9, 100) }); + + one.Remove(2); + one.Remove(9); + one.Remove(4); + result &= CheckTypedRangeSet(one, 3, new Tuple[] { + new Tuple(3, 100), + new Tuple(5, 100), + new Tuple(7, 100), + new Tuple(8, 100) }); + + one.Clear(); + one.Add(1, 200); + one.Add(3, 100); + one.Add(7, 100); + one.Add(5, 100); + one.Add(9, 100); + one.Add(6, 100); + one.Add(8, 100); + one.Add(6, 200); + one.Add(2, 200); + one.Add(4, 300); + one.Add(4, 100); + result &= CheckTypedRangeSet(one, 4, new Tuple[] { + new Tuple(1, 200), + new Tuple(2, 200), + new Tuple(3, 100), + new Tuple(4, 100), + new Tuple(5, 100), + new Tuple(6, 200), + new Tuple(7, 100), + new Tuple(8, 100), + new Tuple(9, 100) }); + + one.Add(6, 100); + result &= CheckTypedRangeSet(one, 2, new Tuple[] { + new Tuple(1, 200), + new Tuple(2, 200), + new Tuple(3, 100), + new Tuple(4, 100), + new Tuple(5, 100), + new Tuple(6, 100), + new Tuple(7, 100), + new Tuple(8, 100), + new Tuple(9, 100) }); + + Debug.WriteLine("TypedRangeSet: test complete (ok=" + result + ")"); + return result; + } + } +} diff --git a/CommonUtil/Version.cs b/CommonUtil/Version.cs new file mode 100644 index 0000000..4d9655d --- /dev/null +++ b/CommonUtil/Version.cs @@ -0,0 +1,192 @@ +/* + * Copyright 2018 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.Diagnostics; + +namespace CommonUtil { + /// + /// Version number container. Instances are immutable. + /// + /// See https://semver.org/ for explanation of system. + /// + public struct Version { + // Must be in ascending order, e.g. Alpha release comes before Beta. + public enum PreRelType { Dev, Alpha, Beta, Final }; + + /// + /// Major version number. + /// + public int Major { get; private set; } + + /// + /// Minor version number. + /// + public int Minor { get; private set; } + + /// + /// Bug fix release number. + /// + public int Patch { get; private set; } + + /// + /// Software grade, for pre-release versions. + /// + public PreRelType PreReleaseType { get; private set; } + + /// + /// Pre-release version. + /// + public int PreRelease { get; private set; } + + /// + /// Version instance to use when no version information is available. This will + /// always compare as less than a "real" version. + /// + public static readonly Version NO_VERSION = new Version(-1, -1, -1); + + /// + /// Shortcut for comparing vs. NO_VERSION. + /// + public bool IsValid { + get { + return this != NO_VERSION; + } + } + + + public Version(int major, int minor) : + this(major, minor, 0, PreRelType.Final, 0) { } + + public Version(int major, int minor, int patch) : + this(major, minor, patch, PreRelType.Final, 0) { } + + public Version(int major, int minor, int patch, PreRelType preRelType, int preRel) { + Debug.Assert(preRelType != PreRelType.Final || preRel == 0); + Major = major; + Minor = minor; + Patch = patch; + PreReleaseType = preRelType; + PreRelease = preRel; + } + + /// + /// Attempts to parse the argument into version components. + /// + /// Version string. + /// New Version object, or NO_VERSION on parsing failure. + public static Version Parse(string str) { + try { + int major, minor, patch; + major = minor = patch = 0; + + string[] parts = str.Split(new char[] { '.', '-' }); + major = int.Parse(parts[0]); + if (parts.Length > 1) { + minor = int.Parse(parts[1]); + } + if (parts.Length > 2) { + patch = int.Parse(parts[2]); + } + // parse the preRel thing someday + return new Version(major, minor, patch); + } catch (Exception ex) { + Debug.WriteLine("Version parse failed: '" + str + "': " + ex.Message); + return NO_VERSION; + } + } + + + public static bool operator ==(Version a, Version b) { + return a.Major == b.Major && a.Minor == b.Minor && a.Patch == b.Patch && + a.PreReleaseType == b.PreReleaseType && a.PreRelease == b.PreRelease; + } + public static bool operator !=(Version a, Version b) { + return !(a == b); + } + public override bool Equals(object obj) { + return obj is Version && this == (Version)obj; + } + public override int GetHashCode() { + return Major * 10000 + Minor * 1000 + Patch * 100 + + (int)PreReleaseType * 10 + PreRelease; + } + + public static bool operator <(Version a, Version b) { + if (a.Major != b.Major) { + return a.Major < b.Major; + } + if (a.Minor != b.Minor) { + return a.Minor < b.Minor; + } + if (a.Patch != b.Patch) { + return a.Patch < b.Patch; + } + if (a.PreReleaseType != b.PreReleaseType) { + return (int)a.PreReleaseType < (int)b.PreReleaseType; + } + if (a.PreRelease != b.PreRelease) { + return a.PreRelease < b.PreRelease; + } + Debug.Assert(a == b); + return false; + } + public static bool operator >(Version a, Version b) { + return b < a; + } + public static bool operator <=(Version a, Version b) { + return a == b || a < b; + } + public static bool operator >=(Version a, Version b) { + return a == b || a > b; + } + + public override string ToString() { + if (this == NO_VERSION) { + return Properties.Resources.NO_VERSION; + } else if (PreReleaseType == PreRelType.Final) { + return string.Format("{0}.{1}.{2}", Major, Minor, Patch); + } else { + return string.Format("{0}.{1}.{2}-{3}{4}", Major, Minor, Patch, + PreReleaseType.ToString().ToLower(), PreRelease); + } + } + + /// + /// Simple unit test. + /// + public static bool Test() { + bool ok = true; + + Version checkVers = new Version(1, 2, 3, PreRelType.Beta, 4); + Version sameVers = new Version(1, 2, 3, PreRelType.Beta, 4); + ok &= (checkVers == sameVers); + ok &= (checkVers <= sameVers); + ok &= (checkVers >= sameVers); + ok &= (!(checkVers < sameVers)); + ok &= (!(checkVers > sameVers)); + + ok &= (checkVers != new Version(1, 2, 3)); + ok &= (checkVers < new Version(1, 2, 3)); + ok &= (checkVers > new Version(1, 2, 3, PreRelType.Beta, 3)); + ok &= (checkVers < new Version(2, 0)); + ok &= (checkVers > new Version(1, 2, 2)); + ok &= (checkVers < new Version(1, 3, 1)); + + Debug.WriteLine("Version struct test complete (ok=" + ok + ")"); + return ok; + } + } +} diff --git a/CommonWinForms/CommonWinForms.csproj b/CommonWinForms/CommonWinForms.csproj new file mode 100644 index 0000000..ea54c28 --- /dev/null +++ b/CommonWinForms/CommonWinForms.csproj @@ -0,0 +1,49 @@ + + + + + Debug + AnyCPU + {08EC328D-078E-4236-B574-BE6B3FD85915} + Library + Properties + CommonWinForms + CommonWinForms + v4.6.1 + 512 + true + + + true + full + false + bin\Debug\ + TRACE;DEBUG;BUILD_FOR_WINDOWS + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE;BUILD_FOR_WINDOWS + prompt + 4 + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CommonWinForms/NativeMethods.cs b/CommonWinForms/NativeMethods.cs new file mode 100644 index 0000000..dc5847e --- /dev/null +++ b/CommonWinForms/NativeMethods.cs @@ -0,0 +1,85 @@ +/* + * Copyright 2018 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.Runtime.InteropServices; +using System.Windows.Forms; + +#if BUILD_FOR_WINDOWS // I don't know if other platforms will emulate the LVITEM stuff +namespace CommonWinForms { + // From https://stackoverflow.com/questions/1019388/ + + /// + /// Unpleasant hackery to make "select all" faster. With 500K items it takes about 24 + /// seconds to select everything individually, and there isn't a better way. With this + /// it only takes a few milliseconds. + /// + public class NativeMethods { + private const int LVM_FIRST = 0x1000; + private const int LVM_SETITEMSTATE = LVM_FIRST + 43; + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct LVITEM { + public int mask; + public int iItem; + public int iSubItem; + public int state; + public int stateMask; + [MarshalAs(UnmanagedType.LPTStr)] + public string pszText; + public int cchTextMax; + public int iImage; + public IntPtr lParam; + public int iIndent; + public int iGroupId; + public int cColumns; + public IntPtr puColumns; + }; + + [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)] + public static extern IntPtr SendMessageLVItem(IntPtr hWnd, int msg, int wParam, ref LVITEM lvi); + + /// + /// Select all rows on the given listview + /// + /// The listview whose items are to be selected + public static void SelectAllItems(ListView list) { + NativeMethods.SetItemState(list, -1, 2, 2); + } + + /// + /// Deselect all rows on the given listview + /// + /// The listview whose items are to be deselected + public static void DeselectAllItems(ListView list) { + NativeMethods.SetItemState(list, -1, 2, 0); + } + + /// + /// Set the item state on the given item + /// + /// The listview whose item's state is to be changed + /// The index of the item to be changed + /// Which bits of the value are to be set? + /// The value to be set + public static void SetItemState(ListView list, int itemIndex, int mask, int value) { + LVITEM lvItem = new LVITEM(); + lvItem.stateMask = mask; + lvItem.state = value; + SendMessageLVItem(list.Handle, LVM_SETITEMSTATE, itemIndex, ref lvItem); + } + } +} +#endif //BUILD_FOR_WINDOWS diff --git a/CommonWinForms/Properties/AssemblyInfo.cs b/CommonWinForms/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..5306502 --- /dev/null +++ b/CommonWinForms/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("CommonWinForms")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("CommonWinForms")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("08ec328d-078e-4236-b574-be6b3fd85915")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/CommonWinForms/WinFormsExtensions.cs b/CommonWinForms/WinFormsExtensions.cs new file mode 100644 index 0000000..9f54ea0 --- /dev/null +++ b/CommonWinForms/WinFormsExtensions.cs @@ -0,0 +1,121 @@ +/* + * Copyright 2018 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.Drawing; +using System.Windows.Forms; + +namespace CommonWinForms { + /// + /// Overload RichTextBox.AppendText() with a version that takes a color as an argument. + /// + /// From https://stackoverflow.com/a/1926822/294248 + /// + public static class RichTextBoxExtensions { + public static void AppendText(this RichTextBox box, string text, Color color) { + box.SelectionStart = box.TextLength; + box.SelectionLength = 0; + box.SelectionColor = color; + box.AppendText(text); + box.SelectionColor = box.ForeColor; + } + } + + /// + /// Add functions to select and deselect all items. + /// + public static class ListViewExtensions { + /// + /// Selects all items in the list view. + /// + public static void SelectAll(this ListView listView) { + // Neither I nor the Internet can figure out how to do this efficiently for + // large lists without P/Invoke interop. With 554253 lines, it takes 24.3 seconds + // to select each item individually, but only 4 milliseconds to do it through an + // LVITEM. The latter causes a single VirtualItemsSelectionRangeChanged event + // instead of 554K ItemSelectionChanged events. + // + // https://stackoverflow.com/questions/9039989/ + // https://stackoverflow.com/questions/1019388/ + +#if BUILD_FOR_WINDOWS // defined in our project properties + NativeMethods.SelectAllItems(listView); +#else + try { + Application.UseWaitCursor = true; + Cursor.Current = Cursors.WaitCursor; + listView.BeginUpdate(); + int max = listView.VirtualListSize; + for (int i = 0; i < max; i++) { + //codeListView.Items[i].Selected = true; + listView.SelectedIndices.Add(i); + } + } finally { + listView.EndUpdate(); + Application.UseWaitCursor = false; + } +#endif + } + + /// + /// Deselects all items in the list view. + /// + public static void DeselectAll(this ListView listView) { + // This is as fast as the native DeselectAllItems(), so just use it. + listView.SelectedIndices.Clear(); + } + + /// + /// Sets the double-buffered status of the list view. + /// + public static void SetDoubleBuffered(this ListView listView, bool enable) { + WinFormsUtil.SetDoubleBuffered(listView, enable); + } + + /// + /// Determines whether the specified item is visible in the list view. + /// + public static bool IsItemVisible(this ListView listView, ListViewItem item) { + Rectangle lvBounds = listView.ClientRectangle; + if (listView.HeaderStyle != ColumnHeaderStyle.None) { + // Need to factor the header height out. There's no easy way to do that, + // but the header should be (almost) the same height as an item. + // https://stackoverflow.com/q/538906/294248 + int headerHeight = item.Bounds.Height + 5; // 5 is magic, will probably break + lvBounds = new Rectangle(lvBounds.X, lvBounds.Y + headerHeight, + lvBounds.Width, lvBounds.Height - headerHeight); + } + //Console.WriteLine("IsVis LV: " + lvBounds + " IT: " + + // item.GetBounds(ItemBoundsPortion.Entire)); + return lvBounds.IntersectsWith(item.GetBounds(ItemBoundsPortion.Entire)); + } + } + + public static class WinFormsUtil { + /// + /// Sets the "DoubleBuffered" property on a Control. For some reason the + /// property is defined as "protected", but I don't want to subclass a ListView + /// just so I can enable double-buffering. + /// + /// Control to update. + /// New state. + public static void SetDoubleBuffered(Control ctrl, bool enable) { + System.Reflection.PropertyInfo prop = ctrl.GetType().GetProperty("DoubleBuffered", + System.Reflection.BindingFlags.Instance | + System.Reflection.BindingFlags.NonPublic); + prop.SetValue(ctrl, enable, null); + } + } +} diff --git a/ImageSrc/SourceGenIcon.ico b/ImageSrc/SourceGenIcon.ico new file mode 100644 index 0000000..7e00bdf Binary files /dev/null and b/ImageSrc/SourceGenIcon.ico differ diff --git a/ImageSrc/SourceGenIcon.xcf b/ImageSrc/SourceGenIcon.xcf new file mode 100644 index 0000000..62f04d0 Binary files /dev/null and b/ImageSrc/SourceGenIcon.xcf differ diff --git a/ImageSrc/SourceGenIconSmall.ico b/ImageSrc/SourceGenIconSmall.ico new file mode 100644 index 0000000..267a343 Binary files /dev/null and b/ImageSrc/SourceGenIconSmall.ico differ diff --git a/ImageSrc/left-arrow.png b/ImageSrc/left-arrow.png new file mode 100644 index 0000000..5905050 Binary files /dev/null and b/ImageSrc/left-arrow.png differ diff --git a/ImageSrc/left-arrow.xcf b/ImageSrc/left-arrow.xcf new file mode 100644 index 0000000..36d4384 Binary files /dev/null and b/ImageSrc/left-arrow.xcf differ diff --git a/ImageSrc/right-arrow.png b/ImageSrc/right-arrow.png new file mode 100644 index 0000000..54758c7 Binary files /dev/null and b/ImageSrc/right-arrow.png differ diff --git a/MakeDist/App.config b/MakeDist/App.config new file mode 100644 index 0000000..731f6de --- /dev/null +++ b/MakeDist/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/MakeDist/CopyProgress.Designer.cs b/MakeDist/CopyProgress.Designer.cs new file mode 100644 index 0000000..596ddc5 --- /dev/null +++ b/MakeDist/CopyProgress.Designer.cs @@ -0,0 +1,103 @@ +/* + * Copyright 2018 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. + */ +namespace MakeDist { + partial class CopyProgress { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.progressRichTextBox = new System.Windows.Forms.RichTextBox(); + this.cancelButton = new System.Windows.Forms.Button(); + this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker(); + this.SuspendLayout(); + // + // progressRichTextBox + // + this.progressRichTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.progressRichTextBox.Location = new System.Drawing.Point(13, 13); + this.progressRichTextBox.Name = "progressRichTextBox"; + this.progressRichTextBox.ReadOnly = true; + this.progressRichTextBox.Size = new System.Drawing.Size(459, 507); + this.progressRichTextBox.TabIndex = 0; + this.progressRichTextBox.Text = ""; + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(397, 526); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 1; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click); + // + // backgroundWorker1 + // + this.backgroundWorker1.WorkerReportsProgress = true; + this.backgroundWorker1.WorkerSupportsCancellation = true; + this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork); + this.backgroundWorker1.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(this.backgroundWorker1_ProgressChanged); + this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted); + // + // CopyProgress + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(484, 561); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.progressRichTextBox); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "CopyProgress"; + this.ShowInTaskbar = false; + this.Text = "CopyDistFiles"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.CopyProgress_FormClosing); + this.Load += new System.EventHandler(this.CopyProgress_Load); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.RichTextBox progressRichTextBox; + private System.Windows.Forms.Button cancelButton; + private System.ComponentModel.BackgroundWorker backgroundWorker1; + } +} \ No newline at end of file diff --git a/MakeDist/CopyProgress.cs b/MakeDist/CopyProgress.cs new file mode 100644 index 0000000..2b8842f --- /dev/null +++ b/MakeDist/CopyProgress.cs @@ -0,0 +1,134 @@ +/* + * Copyright 2018 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.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.Windows.Forms; + +using CommonWinForms; + +namespace MakeDist { + public partial class CopyProgress : Form { + /// + /// Progress message, with colorful text. This is generated by the worker thread and + /// passed to the UI thread. + /// + public class ProgressMessage { + public string Text { get; private set; } + public Color Color { get; private set; } + public bool HasColor { get { return Color.A != 0; } } + + public ProgressMessage(string msg) : this(msg, Color.FromArgb(0, 0, 0, 0)) { } + + public ProgressMessage(string msg, Color color) { + Text = msg; + Color = color; + } + } + + private bool mClosedWhileRunning; + + // Copy parameters. + private FileCopier.BuildType mBuildType; + private bool mCopyTestFiles; + + + public CopyProgress(FileCopier.BuildType buildType, bool copyTestFiles) { + InitializeComponent(); + + this.Size = new Size(1200, 600); + + mBuildType = buildType; + mCopyTestFiles = copyTestFiles; + } + + private void CopyProgress_Load(object sender, EventArgs e) { + backgroundWorker1.RunWorkerAsync(); + } + + private void CopyProgress_FormClosing(object sender, FormClosingEventArgs e) { + if (backgroundWorker1.IsBusy) { + backgroundWorker1.CancelAsync(); + DialogResult = DialogResult.Cancel; + + // First close attempt is converted to a cancel. + if (!mClosedWhileRunning) { + e.Cancel = true; + mClosedWhileRunning = true; + } + } else { + DialogResult = DialogResult.OK; + } + } + + private void cancelButton_Click(object sender, EventArgs e) { + if (backgroundWorker1.IsBusy) { + backgroundWorker1.CancelAsync(); + } else { + Close(); + } + } + + private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { + BackgroundWorker worker = sender as BackgroundWorker; + + FileCopier copier = new FileCopier(mBuildType, mCopyTestFiles); + e.Result = copier.CopyAllFiles(worker); + } + + private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) { + if (e.UserState is ProgressMessage) { + ProgressMessage msg = e.UserState as ProgressMessage; + if (msg.HasColor) { + progressRichTextBox.AppendText(msg.Text, msg.Color); + } else { + // plain foreground text color + progressRichTextBox.AppendText(msg.Text); + } + progressRichTextBox.SelectionStart = progressRichTextBox.Text.Length; + progressRichTextBox.ScrollToCaret(); + } else { + if (!string.IsNullOrEmpty((string)e.UserState)) { + Debug.WriteLine("Weird progress: " + e.UserState); + } + } + } + + private void backgroundWorker1_RunWorkerCompleted(object sender, + RunWorkerCompletedEventArgs e) { + if (e.Cancelled) { + Debug.WriteLine("Test halted -- user cancellation"); + } else if (e.Error != null) { + // test harness shouldn't be throwing errors like this + Debug.WriteLine("Test failed: " + e.Error.ToString()); + progressRichTextBox.AppendText("\r\n"); + progressRichTextBox.AppendText(e.Error.ToString()); + progressRichTextBox.SelectionStart = progressRichTextBox.Text.Length; + progressRichTextBox.ScrollToCaret(); + } else { + bool ok = (bool)e.Result; + Debug.WriteLine("Tests complete, success=" + ok); + } + + if (mClosedWhileRunning) { + Close(); + } + + cancelButton.Text = "Close"; + } + } +} diff --git a/MakeDist/CopyProgress.resx b/MakeDist/CopyProgress.resx new file mode 100644 index 0000000..59099f2 --- /dev/null +++ b/MakeDist/CopyProgress.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/MakeDist/FileCopier.cs b/MakeDist/FileCopier.cs new file mode 100644 index 0000000..d33e09d --- /dev/null +++ b/MakeDist/FileCopier.cs @@ -0,0 +1,316 @@ +/* + * Copyright 2018 faddenSoft + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace MakeDist { + public class FileCopier { + private const string SOURCEGEN_DIRNAME = "SourceGen"; + + /// + /// Type of build to gather files for. + /// + public enum BuildType { Unknown, Release, Debug }; + + private enum SourceFileSpec { + Unknown = 0, + All, + List, + RegressionTests, + AsmSources, + NotBins, + } + + private class CopySpec { + public string SourceDir { get; private set; } + public string DestDir { get; private set; } + public SourceFileSpec FileSpec { get; private set; } + public bool IsRecursive { get; private set; } + public string[] FileList { get; private set; } + + public CopySpec(string srcDir, string dstDir, SourceFileSpec spec, bool recursive, + string[] fileList) { + SourceDir = srcDir; + DestDir = dstDir; + FileSpec = spec; + IsRecursive = recursive; + FileList = fileList; + } + } + + private static CopySpec[] sMainSpec = { + new CopySpec(".", ".", + SourceFileSpec.List, false, new string[] { "README.md" }), + new CopySpec("Asm65/bin/{BUILD_TYPE}/netstandard2.0/", ".", + SourceFileSpec.List, false, new string[] { "Asm65.dll" }), + new CopySpec("CommonUtil/bin/{BUILD_TYPE}/netstandard2.0/", ".", + SourceFileSpec.List, false, new string[] { "CommonUtil.dll" }), + new CopySpec("CommonWinForms/bin/{BUILD_TYPE}/", ".", + SourceFileSpec.List, false, new string[] { "CommonWinForms.dll" }), + new CopySpec("PluginCommon/bin/{BUILD_TYPE}/netstandard2.0/", ".", + SourceFileSpec.List, false, new string[] { "PluginCommon.dll" }), + new CopySpec("SourceGen/bin/{BUILD_TYPE}/", ".", + SourceFileSpec.List, false, new string[] { "SourceGen.exe" }), + new CopySpec("SourceGen/RuntimeData", "RuntimeData", + SourceFileSpec.NotBins, true, null), + new CopySpec("SourceGen/Examples", "Examples", + SourceFileSpec.All, true, null), + }; + private static CopySpec[] sTestSpec = { + new CopySpec("SourceGen/SGTestData", "SGTestData", + SourceFileSpec.RegressionTests, false, null), + new CopySpec("SourceGen/SGTestData", "SGTestData", + SourceFileSpec.List, false, new string[] { "README.md" }), + new CopySpec("SourceGen/SGTestData/Expected", "SGTestData/Expected", + SourceFileSpec.AsmSources, false, null), + new CopySpec("SourceGen/SGTestData/Source", "SGTestData/Source", + SourceFileSpec.AsmSources, false, null), + new CopySpec("SourceGen/SGTestData/FunkyProjects", "SGTestData/FunkyProjects", + SourceFileSpec.All, false, null), + }; + + private static string sBasePath; + + // We want all of the regression test binaries, plus the .sym65, .dis65, and .cs, + // but nothing with an underscore in the part before the extension. + private const string TestCasePattern = @"^\d\d\d\d-[A-Za-z0-9-]+(\..*)?$"; + private static Regex sTestCaseRegex = new Regex(TestCasePattern); + + + private BuildType mBuildType; + private bool mCopyTestFiles; + private BackgroundWorker mWorker; + + + public FileCopier(BuildType buildType, bool copyTestFiles) { + mBuildType = buildType; + mCopyTestFiles = copyTestFiles; + } + + private void ReportProgress(string msg) { + mWorker.ReportProgress(0, new CopyProgress.ProgressMessage(msg + "\r\n")); + } + + private void ReportProgress(string msg, Color color) { + mWorker.ReportProgress(0, new CopyProgress.ProgressMessage(msg + "\r\n", color)); + } + + private void ReportErrMsg(string msg) { + ReportProgress(msg, Color.Red); + } + + /// + /// Main entry point. + /// + /// Background task interface object. + /// True on success. + public bool CopyAllFiles(BackgroundWorker worker) { + mWorker = worker; + + ReportProgress("Preparing... build type is " + mBuildType + ", test files are " + + (mCopyTestFiles ? "" : "NOT ") + "included."); + + string buildStr = mBuildType.ToString(); + string basePath = FindBasePath(); + Debug.Assert(basePath != null); + string distPath = Path.Combine(basePath, "DIST_" + buildStr); + + // TODO(maybe): recursively delete distPath + + if (!CopySpecList(sMainSpec, basePath, distPath, buildStr)) { + return false; + } + if (mCopyTestFiles) { + if (!CopySpecList(sTestSpec, basePath, distPath, buildStr)) { + return false; + } + } + return true; + } + + private bool CopySpecList(CopySpec[] specList, string basePath, string distPath, + string buildStr) { + foreach (CopySpec cs in specList) { + string srcDir = Path.GetFullPath(Path.Combine(basePath, + cs.SourceDir.Replace("{BUILD_TYPE}", buildStr))); + string dstDir = Path.GetFullPath(Path.Combine(distPath, cs.DestDir)); + + ReportProgress("Scanning [" + cs.FileSpec + "] " + srcDir); + + if (!CopyBySpec(srcDir, dstDir, cs.FileSpec, cs.FileList, cs.IsRecursive)) { + return false; + } + } + + return true; + } + + private bool CopyBySpec(string srcDir, string dstDir, SourceFileSpec sfspec, + string[] specFileList, bool isRecursive) { + if (!EnsureDirectoryExists(dstDir)) { + return false; + } + + string[] fileList; + if (sfspec == SourceFileSpec.List) { + fileList = specFileList; + } else { + fileList = Directory.GetFiles(srcDir); + } + + foreach (string str in fileList) { + // Spec list is filenames, GetFiles is paths; convert to simple filename. + string fileName = Path.GetFileName(str); + + switch (sfspec) { + case SourceFileSpec.All: + case SourceFileSpec.List: + // keep all + break; + case SourceFileSpec.NotBins: + // Mostly this means "skip obj and bin dirs", which happens later. + // Rather than specify everything we do want, just omit this one thing. + if (fileName == "RuntimeData.csproj") { + continue; + } + break; + case SourceFileSpec.AsmSources: + if (!fileName.ToUpperInvariant().EndsWith(".S")) { + continue; + } + break; + case SourceFileSpec.RegressionTests: + MatchCollection matches = sTestCaseRegex.Matches(fileName); + if (matches.Count != 1) { + continue; + } + // Could probably do this with regex... but why. + if (fileName.StartsWith("1") && fileName.EndsWith(".dis65")) { + continue; + } + break; + default: + throw new Exception("Unsupported spec " + sfspec); + } + + string srcPath = Path.Combine(srcDir, fileName); + string dstPath = Path.Combine(dstDir, fileName); + if (!CopyFile(srcPath, dstPath)) { + return false; + } + } + + if (isRecursive) { + string[] dirList = Directory.GetDirectories(srcDir); + + foreach (string str in dirList) { + string dirFileName = Path.GetFileName(str); + if (sfspec == SourceFileSpec.NotBins && + (dirFileName == "obj" || dirFileName == "bin")) { + continue; + } + + if (!CopyBySpec(Path.Combine(srcDir, dirFileName), + Path.Combine(dstDir, dirFileName), + sfspec, specFileList, isRecursive)) { + return false; + } + } + } + + return true; + } + + private bool EnsureDirectoryExists(string dirPath) { + if (Directory.Exists(dirPath)) { + return true; + } + if (File.Exists(dirPath)) { + ReportErrMsg("File exists and is not directory: " + dirPath); + return false; + } + try { + Directory.CreateDirectory(dirPath); + ReportProgress(" Created " + dirPath); + } catch (Exception ex) { + ReportErrMsg("Failed creating directory " + dirPath + ": " + ex.Message); + return false; + } + return true; + } + + private bool CopyFile(string srcPath, string dstPath) { + // Poll cancel button. + if (mWorker.CancellationPending) { + ReportErrMsg("Cancel\r\n"); + return false; + } + + ReportProgress(" Copy " + srcPath + " --> " + dstPath); + + try { + File.Copy(srcPath, dstPath, true); + } catch (Exception ex) { + ReportErrMsg("Failed: " + ex.Message); + return false; + } + return true; + } + + /// + /// Returns the base directory of the 6502bench installation. + /// + /// + private static string FindBasePath() { + if (sBasePath != null) { + return sBasePath; + } + + string exeName = Process.GetCurrentProcess().MainModule.FileName; + string baseDir = Path.GetDirectoryName(exeName); + if (string.IsNullOrEmpty(baseDir)) { + return null; + } + + string tryPath; + + // Use the SourceGen directory as a sentinel. + tryPath = Path.Combine(baseDir, SOURCEGEN_DIRNAME); + if (Directory.Exists(tryPath)) { + sBasePath = Path.GetFullPath(tryPath); + return sBasePath; + } + + string upThree = Path.GetDirectoryName( + Path.GetDirectoryName(Path.GetDirectoryName(baseDir))); + tryPath = Path.Combine(upThree, SOURCEGEN_DIRNAME); + if (Directory.Exists(tryPath)) { + sBasePath = Path.GetFullPath(upThree); + return sBasePath; + } + + Debug.WriteLine("Unable to find RuntimeData dir near " + exeName); + return null; + } + } +} diff --git a/MakeDist/MakeDist.Designer.cs b/MakeDist/MakeDist.Designer.cs new file mode 100644 index 0000000..9284792 --- /dev/null +++ b/MakeDist/MakeDist.Designer.cs @@ -0,0 +1,162 @@ +/* + * Copyright 2018 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. + */ +namespace MakeDist { + partial class MakeDist { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.descriptionLabel = new System.Windows.Forms.Label(); + this.distributionTypeGroupBox = new System.Windows.Forms.GroupBox(); + this.releaseDistRadio = new System.Windows.Forms.RadioButton(); + this.debugDistRadio = new System.Windows.Forms.RadioButton(); + this.includeTestsCheckBox = new System.Windows.Forms.CheckBox(); + this.goButton = new System.Windows.Forms.Button(); + this.cancelButton = new System.Windows.Forms.Button(); + this.distributionTypeGroupBox.SuspendLayout(); + this.SuspendLayout(); + // + // descriptionLabel + // + this.descriptionLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.descriptionLabel.Location = new System.Drawing.Point(13, 13); + this.descriptionLabel.Name = "descriptionLabel"; + this.descriptionLabel.Size = new System.Drawing.Size(371, 35); + this.descriptionLabel.TabIndex = 0; + this.descriptionLabel.Text = "This program gathers up all the files needed for a 6502bench distribution. A full" + + " debug or release build should be performed before running this."; + // + // distributionTypeGroupBox + // + this.distributionTypeGroupBox.Controls.Add(this.releaseDistRadio); + this.distributionTypeGroupBox.Controls.Add(this.debugDistRadio); + this.distributionTypeGroupBox.Location = new System.Drawing.Point(16, 51); + this.distributionTypeGroupBox.Name = "distributionTypeGroupBox"; + this.distributionTypeGroupBox.Size = new System.Drawing.Size(120, 67); + this.distributionTypeGroupBox.TabIndex = 1; + this.distributionTypeGroupBox.TabStop = false; + this.distributionTypeGroupBox.Text = "Distribution Type"; + // + // releaseDistRadio + // + this.releaseDistRadio.AutoSize = true; + this.releaseDistRadio.Location = new System.Drawing.Point(7, 20); + this.releaseDistRadio.Name = "releaseDistRadio"; + this.releaseDistRadio.Size = new System.Drawing.Size(64, 17); + this.releaseDistRadio.TabIndex = 0; + this.releaseDistRadio.TabStop = true; + this.releaseDistRadio.Text = "Release"; + this.releaseDistRadio.UseVisualStyleBackColor = true; + // + // debugDistRadio + // + this.debugDistRadio.AutoSize = true; + this.debugDistRadio.Location = new System.Drawing.Point(7, 43); + this.debugDistRadio.Name = "debugDistRadio"; + this.debugDistRadio.Size = new System.Drawing.Size(57, 17); + this.debugDistRadio.TabIndex = 1; + this.debugDistRadio.TabStop = true; + this.debugDistRadio.Text = "Debug"; + this.debugDistRadio.UseVisualStyleBackColor = true; + // + // includeTestsCheckBox + // + this.includeTestsCheckBox.AutoSize = true; + this.includeTestsCheckBox.Location = new System.Drawing.Point(16, 125); + this.includeTestsCheckBox.Name = "includeTestsCheckBox"; + this.includeTestsCheckBox.Size = new System.Drawing.Size(153, 17); + this.includeTestsCheckBox.TabIndex = 2; + this.includeTestsCheckBox.Text = "Include regression test files"; + this.includeTestsCheckBox.UseVisualStyleBackColor = true; + // + // goButton + // + this.goButton.Location = new System.Drawing.Point(192, 59); + this.goButton.Name = "goButton"; + this.goButton.Size = new System.Drawing.Size(88, 47); + this.goButton.TabIndex = 3; + this.goButton.Text = "BUILD"; + this.goButton.UseVisualStyleBackColor = true; + this.goButton.Click += new System.EventHandler(this.goButton_Click); + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(309, 120); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 4; + this.cancelButton.Text = "Close"; + this.cancelButton.UseVisualStyleBackColor = true; + this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click); + // + // MakeDist + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(396, 155); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.goButton); + this.Controls.Add(this.includeTestsCheckBox); + this.Controls.Add(this.distributionTypeGroupBox); + this.Controls.Add(this.descriptionLabel); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "MakeDist"; + this.Text = "6502bench Distribution Maker"; + this.Load += new System.EventHandler(this.MakeDist_Load); + this.distributionTypeGroupBox.ResumeLayout(false); + this.distributionTypeGroupBox.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label descriptionLabel; + private System.Windows.Forms.GroupBox distributionTypeGroupBox; + private System.Windows.Forms.RadioButton releaseDistRadio; + private System.Windows.Forms.RadioButton debugDistRadio; + private System.Windows.Forms.CheckBox includeTestsCheckBox; + private System.Windows.Forms.Button goButton; + private System.Windows.Forms.Button cancelButton; + } +} + diff --git a/MakeDist/MakeDist.cs b/MakeDist/MakeDist.cs new file mode 100644 index 0000000..b432ada --- /dev/null +++ b/MakeDist/MakeDist.cs @@ -0,0 +1,47 @@ +/* + * Copyright 2018 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.Windows.Forms; + +namespace MakeDist { + public partial class MakeDist : Form { + public MakeDist() { + InitializeComponent(); + } + + private void MakeDist_Load(object sender, EventArgs e) { + releaseDistRadio.Checked = true; + } + + private void goButton_Click(object sender, EventArgs e) { + FileCopier.BuildType buildType; + if (releaseDistRadio.Checked) { + buildType = FileCopier.BuildType.Release; + } else { + buildType = FileCopier.BuildType.Debug; + } + bool copyTestFiles = includeTestsCheckBox.Checked; + + CopyProgress dlg = new CopyProgress(buildType, copyTestFiles); + dlg.ShowDialog(); + dlg.Dispose(); + } + + private void cancelButton_Click(object sender, EventArgs e) { + Close(); + } + } +} diff --git a/MakeDist/MakeDist.csproj b/MakeDist/MakeDist.csproj new file mode 100644 index 0000000..f603182 --- /dev/null +++ b/MakeDist/MakeDist.csproj @@ -0,0 +1,99 @@ + + + + + Debug + AnyCPU + {2415F337-2CE2-42E0-A8A7-4127FEEC94C4} + WinExe + MakeDist + MakeDist + v4.6.1 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + Form + + + CopyProgress.cs + + + + Form + + + MakeDist.cs + + + + + CopyProgress.cs + + + MakeDist.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + {08ec328d-078e-4236-b574-be6b3fd85915} + CommonWinForms + + + + \ No newline at end of file diff --git a/MakeDist/MakeDist.resx b/MakeDist/MakeDist.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/MakeDist/MakeDist.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/MakeDist/Program.cs b/MakeDist/Program.cs new file mode 100644 index 0000000..5e9668d --- /dev/null +++ b/MakeDist/Program.cs @@ -0,0 +1,31 @@ +/* + * Copyright 2018 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.Windows.Forms; + +namespace MakeDist { + static class Program { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MakeDist()); + } + } +} diff --git a/MakeDist/Properties/AssemblyInfo.cs b/MakeDist/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..52e423e --- /dev/null +++ b/MakeDist/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MakeDist")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MakeDist")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("2415f337-2ce2-42e0-a8a7-4127feec94c4")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/MakeDist/Properties/Resources.Designer.cs b/MakeDist/Properties/Resources.Designer.cs new file mode 100644 index 0000000..5b9b6ea --- /dev/null +++ b/MakeDist/Properties/Resources.Designer.cs @@ -0,0 +1,62 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace MakeDist.Properties { + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if ((resourceMan == null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MakeDist.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/MakeDist/Properties/Resources.resx b/MakeDist/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/MakeDist/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/MakeDist/Properties/Settings.Designer.cs b/MakeDist/Properties/Settings.Designer.cs new file mode 100644 index 0000000..ac325e7 --- /dev/null +++ b/MakeDist/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace MakeDist.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/MakeDist/Properties/Settings.settings b/MakeDist/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/MakeDist/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/PluginCommon/Interfaces.cs b/PluginCommon/Interfaces.cs new file mode 100644 index 0000000..bebdfc2 --- /dev/null +++ b/PluginCommon/Interfaces.cs @@ -0,0 +1,134 @@ +/* + * Copyright 2018 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; + +namespace PluginCommon { + /// + /// Script "plugins" must implement this interface. + /// + public interface IPlugin { + /// + /// Identification string. Contents are arbitrary, but should briefly identify the + /// purpose of the plugin, e.g. "Apple II ProDOS 8 MLI call handler". It may + /// contain version information, but should not be expected to be machine-readable. + /// + string Identifier { get; } + + /// + /// Returns true if this plugin checks JSR/JSL for inline data. + /// + //bool HasInlineDataAnalyzer { get; } + + /// + /// Initializes the plugin with an application reference and a buffer with file + /// data. Called before each analysis pass. + /// + /// In the current implementation, the file data will be the same every time, + /// because plugins are discarded when a project is closed. However, this may + /// change if we add a descramble feature. + /// + /// Reference to application interface. + /// 65xx code and data. + /// Platform symbols, in no particular order. + void Prepare(IApplication appRef, byte[] fileData, List platSyms); + + /// + /// Checks to see if code/data near a JSR instruction should be formatted. + /// + /// The file data is guaranteed to hold the JSR (offset + 2). + /// + /// Offset of the JSR instruction. + /// Set to true if the JSR doesn't actually return. + void CheckJsr(int offset, out bool noContinue); + + /// + /// Checks to see if code/data near a JSL instruction should be formatted. + /// + /// The file data is guaranteed to hold the JSL (offset + 3). + /// + /// Offset of the JSL instruction. + /// Set to true if the JSL doesn't actually return. + void CheckJsl(int offset, out bool noContinue); + } + + /// + /// Interfaces provided by the application for use by plugins. + /// + public interface IApplication { + /// + /// Sends a debug message to the application. This can be useful when debugging scripts. + /// (Use DEBUG > Show Analyzer Output to view it.) + /// + /// Message to send. + void DebugLog(string msg); + + /// + /// Specifies operand formatting. + /// + /// File offset of opcode. + /// Sub-type. Must be appropriate for NumericLE. + /// Optional symbolic label. + /// True if the change was made, false if it was rejected. + bool SetOperandFormat(int offset, DataSubType subType, string label); + + /// + /// Formats file data as inline data. + /// + /// File offset. + /// Length of item. + /// Type of item. Must be NumericLE, NumericBE, or Dense. + /// Sub-type. Must be appropriate for type. + /// Optional symbolic label. + /// True if the change was made, false if it was rejected. + bool SetInlineDataFormat(int offset, int length, DataType type, + DataSubType subType, string label); + + // Might want to add: + // int AddressToOffset(int address) // returns 24-bit offset, or -1 if outside file + // int OffsetToAddress(int offset) // returns 24-bit address + // (although we could also just pass the address map in at Prepare() -- more efficient + // if this gets called frequently) + } + + /// + /// Data format type. + /// + public enum DataType { + Unknown = 0, + NumericLE, + NumericBE, + String, + Dense, + Fill + } + + /// + /// Data format sub-type. + /// + public enum DataSubType { + // No sub-type specified. + None = 0, + + // For NumericLE/BE + Hex, + Decimal, + Binary, + Ascii, + Address, + Symbol + } +} diff --git a/PluginCommon/PlatSym.cs b/PluginCommon/PlatSym.cs new file mode 100644 index 0000000..9862100 --- /dev/null +++ b/PluginCommon/PlatSym.cs @@ -0,0 +1,84 @@ +/* + * Copyright 2018 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.Text; + +namespace PluginCommon { + /// + /// Symbols loaded from platform symbol files, for use in extension scripts. + /// + /// Instances are immutable. + /// + [Serializable] + public class PlatSym { + public string Label { get; private set; } + public int Value { get; private set; } + public string Tag { get; private set; } + + /// + /// Nullary constructor, for deserialization. + /// + private PlatSym() { } + + /// + /// Constructor. + /// + /// Symbol label. + /// Symbol value. + /// Symbol group tag. + public PlatSym(string label, int value, string tag) { + Label = label; + Value = value; + Tag = tag; + } + + /// + /// Generates a dictionary of platform symbols, keyed by value. Only symbols with + /// a matching tag are included. If more than one symbol has the same value, only + /// one will be included; which one it will be is undefined. + /// + /// List of platform symbols to select from. + /// Tag to match, or null to collect all symbols. + /// Application reference, for debug log output. + /// + public static Dictionary GenerateValueList(List platSyms, + string tag, IApplication appRef) { + Dictionary dict = new Dictionary(); + + foreach (PlatSym ps in platSyms) { + if (tag == null || tag == ps.Tag) { + try { + dict.Add(ps.Value, ps); + } catch (ArgumentException) { + appRef.DebugLog("WARNING: GenerateValueList: multiple entries with " + + "value " + ps.Value.ToString("x4")); + } + } + } + + if (dict.Count == 0) { + appRef.DebugLog("PlatSym: no symbols found for tag=" + tag); + } + + return dict; + } + + public override string ToString() { + return Label + "=" + Value.ToString("x4") + " [" + Tag + "]"; + } + } +} diff --git a/PluginCommon/PluginCommon.csproj b/PluginCommon/PluginCommon.csproj new file mode 100644 index 0000000..d0b956e --- /dev/null +++ b/PluginCommon/PluginCommon.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + + + + + + + diff --git a/PluginCommon/PluginManager.cs b/PluginCommon/PluginManager.cs new file mode 100644 index 0000000..bc13afb --- /dev/null +++ b/PluginCommon/PluginManager.cs @@ -0,0 +1,205 @@ +/* + * Copyright 2018 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.Linq; +using System.Reflection; +using System.Threading; + +namespace PluginCommon { + /// + /// Manages loaded plugins, in the "remote" AppDomain. + /// + public sealed class PluginManager : MarshalByRefObject { + /// + /// Collection of instances of active plugins, keyed by script identifier. Other + /// plugin assemblies may be present in the AppDomain, but have not been identified + /// by the application as being of interest. + /// + private Dictionary mActivePlugins = new Dictionary(); + + /// + /// Reference to file data. + /// + private byte[] mFileData; + + + /// + /// Constructor, invoked from CreateInstanceAndUnwrap(). + /// + public PluginManager() { + Debug.WriteLine("PluginManager ctor (id=" + AppDomain.CurrentDomain.Id + ")"); + + // Seems to require [SecurityCritical] + //Type lsc = Type.GetType("System.Runtime.Remoting.Lifetime.LifetimeServices"); + //PropertyInfo prop = lsc.GetProperty("LeaseTime"); + //prop.SetValue(null, TimeSpan.FromSeconds(30)); + } + + ~PluginManager() { + Debug.WriteLine("~PluginManager (id=" + AppDomain.CurrentDomain.Id + ")"); + } + + /// + /// Sets the file data to use for all plugins. + /// + /// The file data argument will be an AppDomain-local copy of the data, made by the + /// argument marshalling code. So plugins can scribble all over it without trashing + /// the original. We want to store it in PluginManager so we don't make a new copy + /// for each individual plugin. + /// + /// 65xx code and data. + public void SetFileData(byte[] fileData) { + mFileData = fileData; + } + + /// + /// Tests simple round-trip communication. This may be called from an arbitrary thread. + /// + public int Ping(int val) { + Debug.WriteLine("PluginManager Ping tid=" + Thread.CurrentThread.ManagedThreadId + + " (id=" + AppDomain.CurrentDomain.Id + "): " + val); + return val + 1; + } + + /// + /// Creates a plugin instance from a compiled assembly. Pass in the script identifier + /// for future lookups. If the plugin has already been instantiated, that object + /// will be returned. + /// + /// Full path to compiled assembly. + /// Identifier to use in e.g. GetPlugin(). + /// Reference to plugin instance. + public IPlugin LoadPlugin(string dllPath, string scriptIdent) { + if (mActivePlugins.TryGetValue(dllPath, out IPlugin ip)) { + Debug.WriteLine("PM: returning cached plugin for " + dllPath); + return ip; + } + + Assembly asm = Assembly.LoadFile(dllPath); + + foreach (Type type in asm.GetExportedTypes()) { + // Using a System.Linq extension method. + if (type.IsClass && !type.IsAbstract && + type.GetInterfaces().Contains(typeof(IPlugin))) { + + ConstructorInfo ctor = type.GetConstructor(Type.EmptyTypes); + IPlugin iplugin = (IPlugin)ctor.Invoke(null); + Debug.WriteLine("PM created instance: " + iplugin); + mActivePlugins.Add(scriptIdent, iplugin); + return iplugin; + } + } + throw new Exception("No IPlugin class found in " + dllPath); + } + + /// + /// Gets an instance of a previously-loaded plugin. + /// + /// Script identifier that was passed to LoadPlugin(). + /// Reference to instance of plugin. + public IPlugin GetPlugin(string scriptIdent) { + if (mActivePlugins.TryGetValue(scriptIdent, out IPlugin plugin)) { + return plugin; + } + return null; + } + + /// + /// Returns a string with the assembly's location. + /// + public string GetPluginAssemblyLocation(IPlugin plugin) { + return plugin.GetType().Assembly.Location; + } + + /// + /// Generates a list of references to instances of active plugins. + /// + /// Newly-created list of plugin references. + public List GetActivePlugins() { + List list = new List(mActivePlugins.Count); + foreach (KeyValuePair kvp in mActivePlugins) { + list.Add(kvp.Value); + } + Debug.WriteLine("PluginManager: returning " + list.Count + " plugins (id=" + + AppDomain.CurrentDomain.Id + ")"); + return list; + } + + /// + /// Clears the list of loaded plugins. This does not unload the assemblies from + /// the AppDomain. + /// + public void ClearPluginList() { + mActivePlugins.Clear(); + } + + /// + /// Invokes the Prepare() method on all active plugins. + /// + /// Reference to host object providing app services. + public void PreparePlugins(IApplication appRef, List platSyms) { + foreach (KeyValuePair kvp in mActivePlugins) { + kvp.Value.Prepare(appRef, mFileData, platSyms); + } + } + +#if false + /// + /// DEBUG ONLY: establish a fast lease timeout. Normally the lease + /// is five minutes; this reduces it to a few seconds. (The actual time is + /// also affected by LifetimeServices.LeaseManagerPollTime, which defaults + /// to 10 seconds.) + /// + /// Unfortunately this must be tagged [SecurityCritical] to match the method being + /// overridden, but in a partially-trusted sandbox that's not allowed. You have + /// to relax security entirely for this to work. + /// + //[SecurityPermissionAttribute(SecurityAction.Demand, + // Flags = SecurityPermissionFlag.Infrastructure)] + [System.Security.SecurityCritical] + public override object InitializeLifetimeService() { + object lease = base.InitializeLifetimeService(); + + // netstandard2.0 doesn't have System.Runtime.Remoting.Lifetime, so use reflection + PropertyInfo leaseState = lease.GetType().GetProperty("CurrentState"); + PropertyInfo initialLeaseTime = lease.GetType().GetProperty("InitialLeaseTime"); + PropertyInfo sponsorshipTimeout = lease.GetType().GetProperty("SponsorshipTimeout"); + PropertyInfo renewOnCallTime = lease.GetType().GetProperty("RenewOnCallTime"); + + Console.WriteLine("Default lease: ini=" + + initialLeaseTime.GetValue(lease) + " spon=" + + sponsorshipTimeout.GetValue(lease) + " renOC=" + + renewOnCallTime.GetValue(lease)); + + if ((int)leaseState.GetValue(lease) == 1 /*LeaseState.Initial*/) { + // Initial lease duration. + initialLeaseTime.SetValue(lease, TimeSpan.FromSeconds(8)); + + // How long we will wait for the sponsor to respond + // with a lease renewal time. + sponsorshipTimeout.SetValue(lease, TimeSpan.FromSeconds(5)); + + // Each call to the remote object extends the lease so that + // it has at least this much time left. + renewOnCallTime.SetValue(lease, TimeSpan.FromSeconds(2)); + } + return lease; + } +#endif + } +} diff --git a/PluginCommon/Util.cs b/PluginCommon/Util.cs new file mode 100644 index 0000000..d1fefca --- /dev/null +++ b/PluginCommon/Util.cs @@ -0,0 +1,50 @@ +/* + * Copyright 2018 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 CommonUtil; + +namespace PluginCommon { + /// + /// Utility functions available for plugins to use. + /// + /// The idea is to make CommonUtil functions available to plugins while isolating + /// them from changes to the library. Anything here is guaranteed to keep working, + /// while other classes and functions in CommonUtil may change between releases. + /// + public static class Util { + /// + /// Extracts an integer from the data stream. + /// + /// Raw data stream. + /// Start offset. + /// Word width, which may be 1-4 bytes. + /// True if word is in big-endian order. + /// Value found. + public static int GetWord(byte[] data, int offset, int width, bool isBigEndian) { + return RawData.GetWord(data, offset, width, isBigEndian); + } + + /// + /// Compute a standard CRC-32 (polynomial 0xedb88320) on a buffer of data. + /// + /// Buffer to process. + /// CRC value. + public static uint ComputeBufferCRC(byte[] data) { + return CRC32.OnBuffer(0, data, 0, data.Length); + } + } +} diff --git a/README.md b/README.md index 6b2d317..5f27b05 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,105 @@ -# 6502bench -A workbench for developing 6502 code +# 6502bench # + +6502bench is a code development "workbench" for 6502, 65C02, and 65802/65816 +code. It currently features one tool, the SourceGen disassembler. + +You can download the source code and build it yourself, or click the +"Releases" tab near the top of the github page for pre-built downloads. + + +## SourceGen ## + +SourceGen converts machine-language programs to assembly-language source +code. It has most of the features you will find in other 6502 disassemblers, +as well as many less-common ones. + +A demo video is available: https://youtu.be/dalISyBPQq8 + +#### Features #### + +Analysis: +- Support for 6502 (including undocumented opcodes), 65C02, and 65816. +- Code flow analyzer traces execution to find all reachable + instructions. Hinting mechanism allows manual specification of + entry points. +- Processor status flags are tracked, allowing automatic detection + of branch-always and branch-never, as well as register widths on + 16-bit CPUs. The analyzer tracks these across subroutine calls and + branches. Cycle counts factor these in automatically. +- Editable labels are generated for every branch and data target. +- Automatic detection and classification of ASCII strings and runs of + identical bytes. +- All target-platform-specific stuff is stored in plain text files + loaded at runtime. This includes symbols for ROM entry points + and standard zero-page locations. Additional symbols and overrides + can be specified at the project level. +- Extension scripts can be used to reformat code and identify inline + data that follows JSR/JSL. Code is compiled at run time and + executes in a sandbox. + +UI: +- Fully interactive point-and-click GUI. Add labels and multi-line + comments, change addresses, and see the changes immediately. +- Instruction operand formats (hex, decimal, etc) can be set for + individual lines. Symbols that don't match the operands are automatically + offset, allowing simple expressions like "address - 1". +- Full-line comments are automatically word-wrapped, and can be + "boxed" for an authentic retro feel. +- Data areas can be formatted as bytes, words, addresses, and more. + Several types of strings are recognized (null-terminated, length + prefixed, etc). +- "Infinite" undo/redo of all actions. +- Notes can be added that aren't included in generated output. Very + useful for marking up a work in progress. +- Cross-reference tables are generated for every branch and data + target address, as well as for external platform symbols. +- Instruction summaries, including cpu cycles and flags modified, are + shown along with a description of the opcode function. +- Display is configurable for upper/lower case, choice of pseudo-op + names, expression formats, and more. + +Output: +- Assembly source can be generated for multiple assemblers (currently + cc65 and Merlin 32). +- Cross-assemblers can be launched directly from the generation GUI to + verify output correctness. +- Optional automatic conversion of labels from global to local. +- Symbols may be exported from one project and imported into another + to facilitate multi-binary disassembly. + +Misc: +- Preset project attributes (CPU type, platform symbol file sets) are defined + for a variety of platforms. +- Project file is stored in a text format, and only holds metadata. None + of the original file is included, allowing the project file to be shared + without violating copyrights (note: may vary depending on local laws). + +There are a couple of significant areas where support is currently lacking: +- Poor support for multi-bank 65816 files (IIgs OMF, SNES). +- No support for alternate character sets (e.g. PETSCII). + + +## About the Code ## + +All of the code is written in C# .NET, using the (free to download) Visual +Studio Community 2017 IDE as the primary development environment. The user +interface uses the WinForms API. Efforts have been made to avoid doing +anything Windows-specific, in the hope that the applications will be +straightforward to port to other platforms. + +The solution is called "WorkBench.sln" rather than "6502bench.sln" because +some things in Visual Studio got weird when it didn't start with a letter. + +The code style is closer to what Android uses than "standard" C#. Lines +are folded to fit 100 columns. + +The source code is licensed under Apache 2.0 +(http://www.apache.org/licenses/LICENSE-2.0), which makes it free for use in +both open-source programs and closed-source commercial software. The license +terms are similar to BSD or MIT, but with some additional constraints on +patent licensing. (This is the same license Google uses for the Android +Open Source Project.) + +Images are licensed under Creative Commons ShareAlike 4.0 International +(https://creativecommons.org/licenses/by-sa/4.0/). + diff --git a/SourceGen/AddressMap.cs b/SourceGen/AddressMap.cs new file mode 100644 index 0000000..6061a46 --- /dev/null +++ b/SourceGen/AddressMap.cs @@ -0,0 +1,284 @@ +/* + * Copyright 2018 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; +using System.Collections.Generic; +using System.Diagnostics; + +namespace SourceGen { + /// + /// Map file offsets to 65xx addresses and vice-versa. Useful for sources with + /// multiple ORG directives. + /// + /// It's possible to generate code that would overlap once relocated at run time, + /// which means a given address could map to multiple offsets. For this reason + /// it's useful to know the offset of the referring code when evaluating a + /// reference, so that a "local" match can take priority. + /// + public class AddressMap : IEnumerable { + /// + /// Code starting at the specified offset will have the specified address. + /// + /// The entries are held in the list in order, sorted by offset, with no gaps. + /// This makes the "length" field redundant, as it can be computed by + /// (entry[N+1].mOffset - entry[N].mOffset), with a special case for the last + /// entry in the list. It's convenient to maintain it explicitly however, as + /// the list is read far more often than it is updated. + /// + /// Entries are mutable, but must only be altered by AddressMap. Don't retain + /// instances of this across other activity. + /// + public class AddressMapEntry { + public int Offset { get; set; } + public int Addr { get; set; } + public int Length { get; set; } + + public AddressMapEntry(int offset, int addr, int len) { + Offset = offset; + Addr = addr; + Length = len; + } + } + + /// + /// Total length, in bytes, spanned by this map. + /// + private int mTotalLength; + + /// + /// List of definitions, in sorted order. + /// + private List mAddrList = new List(); + + /// + /// Constructor. + /// + /// Total length, in bytes, spanned by this map. + public AddressMap(int length) { + /// There must always be at least one entry, defining the target address + /// for file offset 0. This can be changed, but can't be removed. + mTotalLength = length; + mAddrList.Add(new AddressMapEntry(0, 0, length)); + } + + // IEnumerable + public IEnumerator GetEnumerator() { + return ((IEnumerable)mAddrList).GetEnumerator(); + } + + // IEnumerable + IEnumerator IEnumerable.GetEnumerator() { + return ((IEnumerable)mAddrList).GetEnumerator(); + } + + /// + /// Returns the address map entry index associated with the specified offset, or -1 + /// if there is no address map entry there. + /// + public int Get(int offset) { + foreach (AddressMapEntry ad in mAddrList) { + if (ad.Offset == offset) { + return ad.Addr; + } + } + return -1; + } + + /// + /// Adds, updates, or removes a map entry. + /// + /// File offset at which the address changes. + /// 24-bit address. + public void Set(int offset, int addr) { + Debug.Assert(offset >= 0); + if (addr == -1) { + if (offset != 0) { // ignore attempts to remove entry at offset zero + Remove(offset); + } + return; + } + Debug.Assert(addr >= 0 && addr < 0x01000000); // 24-bit address space + + int i; + for (i = 0; i < mAddrList.Count; i++) { + AddressMapEntry ad = mAddrList[i]; + if (ad.Offset == offset) { + // update existing + ad.Addr = addr; + mAddrList[i] = ad; + return; + } else if (ad.Offset > offset) { + // The i'th entry is one past the interesting part. + break; + } + } + + // Carve a chunk out of the previous entry. + AddressMapEntry prev = mAddrList[i - 1]; + int prevOldLen = prev.Length; + int prevNewLen = offset - prev.Offset; + prev.Length = prevNewLen; + mAddrList[i - 1] = prev; + + mAddrList.Insert(i, + new AddressMapEntry(offset, addr, prevOldLen - prevNewLen)); + + DebugValidate(); + } + + /// + /// Removes an entry from the set. + /// + /// The initial offset of the mapping to remove. This + /// must be the initial value, not a mid-range value. + /// True if something was removed. + public bool Remove(int offset) { + if (offset == 0) { + throw new Exception("Not allowed to remove entry 0"); + } + + for (int i = 1; i < mAddrList.Count; i++) { + if (mAddrList[i].Offset == offset) { + // Add the length to the previous entry. + AddressMapEntry prev = mAddrList[i - 1]; + prev.Length += mAddrList[i].Length; + mAddrList[i - 1] = prev; + + mAddrList.RemoveAt(i); + DebugValidate(); + return true; + } + } + return false; + } + + /// + /// Returns the index of the address map entry that contains the given offset. + /// We assume the offset is valid. + /// + private int IndexForOffset(int offset) { + for (int i = 1; i < mAddrList.Count; i++) { + if (mAddrList[i].Offset > offset) { + return i - 1; + } + } + + return mAddrList.Count - 1; + } + + /// + /// Returns true if the given address falls into the range spanned by the + /// address map entry. + /// + /// Address map entry index. + /// Address to check. + /// + private bool IndexContainsAddress(int index, int addr) { + return addr >= mAddrList[index].Addr && + addr < mAddrList[index].Addr + mAddrList[index].Length; + } + + /// + /// Determines the file offset that best contains the specified target address. + /// + /// Offset of the address reference. + /// Address to look up. + /// The file offset, or -1 if the address falls outside the file. + public int AddressToOffset(int srcOffset, int targetAddr) { + if (mAddrList.Count == 1) { + // Trivial case. + if (IndexContainsAddress(0, targetAddr)) { + Debug.Assert(targetAddr >= mAddrList[0].Addr); + return targetAddr - mAddrList[0].Addr; + } else { + return -1; + } + } + + // We have multiple, potentially overlapping address ranges. Start by + // looking for a match in the srcOffset range; if that fails, scan + // forward from the start. + int srcOffIndex = IndexForOffset(srcOffset); + if (IndexContainsAddress(srcOffIndex, targetAddr)) { + Debug.Assert(targetAddr >= mAddrList[srcOffIndex].Addr); + return (targetAddr - mAddrList[srcOffIndex].Addr) + mAddrList[srcOffIndex].Offset; + } + + for (int i = 0; i < mAddrList.Count; i++) { + if (i == srcOffIndex) { + // optimization -- we already checked this one + continue; + } + if (IndexContainsAddress(i, targetAddr)) { + Debug.Assert(targetAddr >= mAddrList[i].Addr); + return (targetAddr - mAddrList[i].Addr) + mAddrList[i].Offset; + } + } + + return -1; + } + + /// + /// Converts a file offset to an address. + /// + /// File offset. + /// 24-bit address. + public int OffsetToAddress(int offset) { + int srcOffIndex = IndexForOffset(offset); + return mAddrList[srcOffIndex].Addr + (offset - mAddrList[srcOffIndex].Offset); + } + + + /// + /// Internal consistency checks. + /// + private void DebugValidate() { + if (mAddrList.Count < 1) { + throw new Exception("AddressMap: empty"); + } + if (mAddrList[0].Offset != 0) { + throw new Exception("AddressMap: bad offset 0"); + } + + if (mAddrList.Count == 1) { + if (mAddrList[0].Length != mTotalLength) { + throw new Exception("AddressMap: single entry len bad"); + } + } else { + int totalLen = 0; + for (int i = 0; i < mAddrList.Count; i++) { + AddressMapEntry ent = mAddrList[i]; + if (i != 0) { + if (ent.Offset != mAddrList[i - 1].Offset + mAddrList[i - 1].Length) { + throw new Exception("Bad offset step to " + i); + } + } + + totalLen += ent.Length; + } + + if (totalLen != mTotalLength) { + throw new Exception("AddressMap: bad length sum (" + totalLen + " vs " + + mTotalLength + ")"); + } + } + } + + public override string ToString() { + return "[AddressMap: " + mAddrList.Count + " entries]"; + } + } +} diff --git a/SourceGen/Anattrib.cs b/SourceGen/Anattrib.cs new file mode 100644 index 0000000..3a38bc0 --- /dev/null +++ b/SourceGen/Anattrib.cs @@ -0,0 +1,355 @@ +/* + * Copyright 2018 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.Diagnostics; +using System.Text; + +using Asm65; + +namespace SourceGen { + /// + /// Analyzer attribute holder. Contains the output of the instruction and data analyzers. + /// Every byte in the input file has one of these associated with it. + /// + /// (Yes, it's a mutable struct. Yes, that fact has bitten me a few times. The array + /// of these may have millions of elements, so the reduction in overhead seems worthwhile.) + /// + public struct Anattrib { + [FlagsAttribute] + private enum AttribFlags { + InstrStart = 1 << 0, // byte is first of an instruction + Instruction = 1 << 1, // byte is part of an instruction or inline data + InlineData = 1 << 2, // byte is inline data + Data = 1 << 3, // byte is data + + EntryPoint = 1 << 8, // external code branches here + BranchTarget = 1 << 9, // internal code branches here + ExternalBranch = 1 << 10, // this abs/rel branch lands outside input file + + NoContinue = 1 << 12, // execution does not continue to following instruction + + Visited = 1 << 16, // has the analyzer visited this byte? + Changed = 1 << 17, // set/cleared as the analyzer works + + Hinted = 1 << 18, // was this byte affected by a type hint? + } + + // Flags indicating what type of data is here. Use the following Is* properties + // to set/clear. + private AttribFlags mAttribFlags; + + public bool IsInstructionStart { + get { + return (mAttribFlags & AttribFlags.InstrStart) != 0; + } + set { + IsInstruction = value; + if (value) { + mAttribFlags |= AttribFlags.InstrStart; + } else { + mAttribFlags &= ~AttribFlags.InstrStart; + } + } + } + public bool IsInstruction { + get { + return (mAttribFlags & AttribFlags.Instruction) != 0; + } + set { + Debug.Assert(value == false || + (mAttribFlags & (AttribFlags.InlineData | AttribFlags.Data)) == 0); + if (value) { + mAttribFlags |= AttribFlags.Instruction; + } else { + mAttribFlags &= ~AttribFlags.Instruction; + } + } + } + public bool IsInlineData { + get { + return (mAttribFlags & AttribFlags.InlineData) != 0; + } + set { + Debug.Assert(value == false || + (mAttribFlags & (AttribFlags.Instruction | AttribFlags.Data)) == 0); + if (value) { + mAttribFlags |= AttribFlags.InlineData; + } else { + mAttribFlags &= ~AttribFlags.InlineData; + } + } + } + public bool IsData { + get { + return (mAttribFlags & AttribFlags.Data) != 0; + } + set { + Debug.Assert(value == false || + (mAttribFlags & (AttribFlags.InlineData | AttribFlags.Instruction)) == 0); + if (value) { + mAttribFlags |= AttribFlags.Data; + } else { + mAttribFlags &= ~AttribFlags.Data; + } + } + } + public bool IsStart { + get { + return IsInstructionStart || IsDataStart || IsInlineDataStart; + } + } + public bool IsEntryPoint { + get { + return (mAttribFlags & AttribFlags.EntryPoint) != 0; + } + set { + if (value) { + mAttribFlags |= AttribFlags.EntryPoint; + } else { + mAttribFlags &= ~AttribFlags.EntryPoint; + } + } + } + public bool IsBranchTarget { + get { + return (mAttribFlags & AttribFlags.BranchTarget) != 0; + } + set { + if (value) { + mAttribFlags |= AttribFlags.BranchTarget; + } else { + mAttribFlags &= ~AttribFlags.BranchTarget; + } + } + } + public bool IsExternalBranch { + get { + return (mAttribFlags & AttribFlags.ExternalBranch) != 0; + } + set { + if (value) { + mAttribFlags |= AttribFlags.ExternalBranch; + } else { + mAttribFlags &= ~AttribFlags.ExternalBranch; + } + } + } + public bool DoesNotContinue { + get { + return (mAttribFlags & AttribFlags.NoContinue) != 0; + } + set { + if (value) { + mAttribFlags |= AttribFlags.NoContinue; + } else { + mAttribFlags &= ~AttribFlags.NoContinue; + } + } + } + public bool DoesNotBranch { + get { + return (BranchTaken == OpDef.BranchTaken.Never); + } + } + public bool IsVisited { + get { + return (mAttribFlags & AttribFlags.Visited) != 0; + } + set { + if (value) { + mAttribFlags |= AttribFlags.Visited; + } else { + mAttribFlags &= ~AttribFlags.Visited; + } + } + } + public bool IsChanged { + get { + return (mAttribFlags & AttribFlags.Changed) != 0; + } + set { + if (value) { + mAttribFlags |= AttribFlags.Changed; + } else { + mAttribFlags &= ~AttribFlags.Changed; + } + } + } + public bool IsHinted { + get { + return (mAttribFlags & AttribFlags.Hinted) != 0; + } + set { + if (value) { + mAttribFlags |= AttribFlags.Hinted; + } else { + mAttribFlags &= ~AttribFlags.Hinted; + } + } + } + + public bool IsDataStart { + get { + return IsData && DataDescriptor != null; + } + } + public bool IsInlineDataStart { + get { + return IsInlineData && DataDescriptor != null; + } + } + + /// + /// Get the target memory address for this byte. + /// + public int Address { get; set; } + + /// + /// Instructions: length of the instruction (for InstrStart). If a FormatDescriptor + /// is assigned, the length must match. + /// Inline data: FormatDescriptor length, or zero if no descriptor is defined. + /// Data: FormatDescriptor length, or zero if no descriptor is defined. + /// + /// This field should only be set by CodeAnalysis methods, although the "get" value + /// can be changed for data/inline-data by setting the DataDescriptor field. + /// + public int Length { + get { + // For data we don't even use the field; this ensures that we're always + // using the FormatDescriptor's length. + if (IsData || IsInlineData) { + Debug.Assert(mLength == 0); + if (DataDescriptor != null) { + return DataDescriptor.Length; + } else { + return 0; + } + } + return mLength; + } + set { + Debug.Assert(!IsData); + mLength = value; + } + } + private int mLength; + + /// + /// Instructions only: processor status flags. + /// + /// Note this returns a copy of a struct, so modifications to the returned value + /// (including calls to Merge and Apply) are not permanent. + /// + public StatusFlags StatusFlags { + get { return mStatusFlags; } + set { mStatusFlags = value; } + } + private StatusFlags mStatusFlags; + + public void MergeStatusFlags(StatusFlags other) { + mStatusFlags.Merge(other); + } + public void ApplyStatusFlags(StatusFlags other) { + mStatusFlags.Apply(other); + } + + /// + /// Branch instructions only: outcome of branch. + /// + public OpDef.BranchTaken BranchTaken { get; set; } + + /// + /// Instructions only: decoded operand address value. Will be -1 if not + /// yet computed or not applicable. For a relative branch instruction, + /// this will have the absolute branch target address. On the 65816, this + /// will be a 24-bit address. + /// + public int OperandAddress { + get { return mOperandAddressSet ? mOperandAddress : -1; } + set { + Debug.Assert(mOperandAddress >= -1); + mOperandAddress = value; + mOperandAddressSet = (value >= 0); + } + } + private int mOperandAddress; + private bool mOperandAddressSet; + + /// + /// Instructions only: offset referenced by OperandAddress. Will be -1 if not + /// yet computed, not applicable, or if OperandAddress refers to a location + /// outside the scope of the file. + /// + public int OperandOffset { + get { return mOperandOffsetSet ? mOperandOffset : -1; } + set { + Debug.Assert(mOperandOffset >= -1); + mOperandOffset = value; + mOperandOffsetSet = (value >= 0); + } + } + private int mOperandOffset; + private bool mOperandOffsetSet; + + /// + /// Instructions only: is OperandOffset a direct target offset? (This is used when + /// tracing jump instructions, to know if we should add the offset to the scan list. + /// It's determined by the opcode, e.g. "JMP addr" -> true, "JMP (addr,X)" -> false.) + /// + public bool IsOperandOffsetDirect { get; set; } + + /// + /// Symbol defined as the label for this offset. All offsets that are instruction + /// or data target offsets will have one of these defined. Users can define additional + /// symbols as well. + /// + /// Will be null if no label is defined for this offset. + /// + public Symbol Symbol { get; set; } + + /// + /// Format descriptor for operands and data items. Will be null if no descriptor + /// is defined for this offset. + /// + public FormatDescriptor DataDescriptor { get; set; } + + /// + /// Is this an instruction with an operand (i.e. not impl/acc)? + /// + public bool IsInstructionWithOperand { + get { + if (!IsInstructionStart) { + return false; + } + return Length != 1; + } + } + + /// + /// Returns a fixed-width string with indicators for items of interest. + /// + public string ToAttrString() { + StringBuilder sb = new StringBuilder(5); + char blank = '.'; + sb.Append(IsEntryPoint ? '@' : blank); + sb.Append(IsHinted ? 'H' : blank); + sb.Append(DoesNotBranch ? '!' : blank); + sb.Append(DoesNotContinue ? '#' : blank); + sb.Append(IsBranchTarget ? '>' : blank); + return sb.ToString(); + } + } +} diff --git a/SourceGen/App.config b/SourceGen/App.config new file mode 100644 index 0000000..731f6de --- /dev/null +++ b/SourceGen/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/SourceGen/AppForms/AboutBox.Designer.cs b/SourceGen/AppForms/AboutBox.Designer.cs new file mode 100644 index 0000000..ff759ff --- /dev/null +++ b/SourceGen/AppForms/AboutBox.Designer.cs @@ -0,0 +1,187 @@ +/* + * Copyright 2018 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. + */ +namespace SourceGen.AppForms { + partial class AboutBox { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.boardPictureBox = new System.Windows.Forms.PictureBox(); + this.sourceGenLabel = new System.Windows.Forms.Label(); + this.versionLabel = new System.Windows.Forms.Label(); + this.createdLabel = new System.Windows.Forms.Label(); + this.okButton = new System.Windows.Forms.Button(); + this.legalStuffTextBox = new System.Windows.Forms.TextBox(); + this.label1 = new System.Windows.Forms.Label(); + this.osPlatformLabel = new System.Windows.Forms.Label(); + this.debugEnabledLabel = new System.Windows.Forms.Label(); + ((System.ComponentModel.ISupportInitialize)(this.boardPictureBox)).BeginInit(); + this.SuspendLayout(); + // + // boardPictureBox + // + this.boardPictureBox.Location = new System.Drawing.Point(13, 13); + this.boardPictureBox.Name = "boardPictureBox"; + this.boardPictureBox.Size = new System.Drawing.Size(320, 236); + this.boardPictureBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.boardPictureBox.TabIndex = 0; + this.boardPictureBox.TabStop = false; + // + // sourceGenLabel + // + this.sourceGenLabel.AutoSize = true; + this.sourceGenLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 24F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.sourceGenLabel.Location = new System.Drawing.Point(340, 13); + this.sourceGenLabel.Name = "sourceGenLabel"; + this.sourceGenLabel.Size = new System.Drawing.Size(349, 37); + this.sourceGenLabel.TabIndex = 1; + this.sourceGenLabel.Text = "6502bench SourceGen"; + // + // versionLabel + // + this.versionLabel.AutoSize = true; + this.versionLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 20.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.versionLabel.Location = new System.Drawing.Point(340, 60); + this.versionLabel.Name = "versionLabel"; + this.versionLabel.Size = new System.Drawing.Size(273, 31); + this.versionLabel.TabIndex = 2; + this.versionLabel.Text = "Version X.Y.Z Alpha1"; + // + // createdLabel + // + this.createdLabel.AutoSize = true; + this.createdLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.createdLabel.Location = new System.Drawing.Point(407, 142); + this.createdLabel.Name = "createdLabel"; + this.createdLabel.Size = new System.Drawing.Size(206, 40); + this.createdLabel.TabIndex = 3; + this.createdLabel.Text = "Copyright 2018 faddenSoft\r\nCreated by Andy McFadden\r\n"; + // + // okButton + // + this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.okButton.Location = new System.Drawing.Point(617, 526); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(75, 23); + this.okButton.TabIndex = 0; + this.okButton.Text = "OK"; + this.okButton.UseVisualStyleBackColor = true; + // + // legalStuffTextBox + // + this.legalStuffTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.legalStuffTextBox.Location = new System.Drawing.Point(12, 305); + this.legalStuffTextBox.MaxLength = 0; + this.legalStuffTextBox.Multiline = true; + this.legalStuffTextBox.Name = "legalStuffTextBox"; + this.legalStuffTextBox.ReadOnly = true; + this.legalStuffTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.legalStuffTextBox.Size = new System.Drawing.Size(680, 215); + this.legalStuffTextBox.TabIndex = 4; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 289); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(59, 13); + this.label1.TabIndex = 5; + this.label1.Text = "Legal stuff:"; + // + // osPlatformLabel + // + this.osPlatformLabel.AutoSize = true; + this.osPlatformLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.osPlatformLabel.Location = new System.Drawing.Point(12, 263); + this.osPlatformLabel.Name = "osPlatformLabel"; + this.osPlatformLabel.Size = new System.Drawing.Size(86, 16); + this.osPlatformLabel.TabIndex = 6; + this.osPlatformLabel.Text = "[OS platform]"; + // + // debugEnabledLabel + // + this.debugEnabledLabel.AutoSize = true; + this.debugEnabledLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.debugEnabledLabel.Location = new System.Drawing.Point(340, 233); + this.debugEnabledLabel.Name = "debugEnabledLabel"; + this.debugEnabledLabel.Size = new System.Drawing.Size(293, 16); + this.debugEnabledLabel.TabIndex = 7; + this.debugEnabledLabel.Text = "Assertions and extended validation are enabled"; + this.debugEnabledLabel.Visible = false; + // + // AboutBox + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(704, 561); + this.Controls.Add(this.debugEnabledLabel); + this.Controls.Add(this.osPlatformLabel); + this.Controls.Add(this.label1); + this.Controls.Add(this.legalStuffTextBox); + this.Controls.Add(this.okButton); + this.Controls.Add(this.createdLabel); + this.Controls.Add(this.versionLabel); + this.Controls.Add(this.sourceGenLabel); + this.Controls.Add(this.boardPictureBox); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "AboutBox"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "AboutBox"; + this.Load += new System.EventHandler(this.AboutBox_Load); + ((System.ComponentModel.ISupportInitialize)(this.boardPictureBox)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.PictureBox boardPictureBox; + private System.Windows.Forms.Label sourceGenLabel; + private System.Windows.Forms.Label versionLabel; + private System.Windows.Forms.Label createdLabel; + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.TextBox legalStuffTextBox; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label osPlatformLabel; + private System.Windows.Forms.Label debugEnabledLabel; + } +} \ No newline at end of file diff --git a/SourceGen/AppForms/AboutBox.cs b/SourceGen/AppForms/AboutBox.cs new file mode 100644 index 0000000..3dc0ae5 --- /dev/null +++ b/SourceGen/AppForms/AboutBox.cs @@ -0,0 +1,51 @@ +/* + * Copyright 2018 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.IO; +using System.Windows.Forms; + +namespace SourceGen.AppForms { + public partial class AboutBox : Form { + private const string IMAGE_FILE_NAME = "AboutImage.png"; + private const string LEGAL_STUFF_FILE_NAME = "LegalStuff.txt"; + + public AboutBox() { + InitializeComponent(); + + boardPictureBox.ImageLocation = RuntimeDataAccess.GetPathName(IMAGE_FILE_NAME); + versionLabel.Text = string.Format(Properties.Resources.VERSION_FMT, + Program.ProgramVersion); + + osPlatformLabel.Text = "OS: " + + System.Runtime.InteropServices.RuntimeInformation.OSDescription; +#if DEBUG + debugEnabledLabel.Visible = true; +#endif + } + + private void AboutBox_Load(object sender, EventArgs e) { + string text; + string pathName = RuntimeDataAccess.GetPathName(LEGAL_STUFF_FILE_NAME); + try { + text = File.ReadAllText(pathName); + } catch (Exception ex) { + text = ex.ToString(); + } + + legalStuffTextBox.Text = text; + } + } +} diff --git a/SourceGen/AppForms/AboutBox.resx b/SourceGen/AppForms/AboutBox.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/SourceGen/AppForms/AboutBox.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/SourceGen/AppForms/DataFileLoadIssue.Designer.cs b/SourceGen/AppForms/DataFileLoadIssue.Designer.cs new file mode 100644 index 0000000..6ef16cd --- /dev/null +++ b/SourceGen/AppForms/DataFileLoadIssue.Designer.cs @@ -0,0 +1,143 @@ +/* + * Copyright 2018 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. + */ +namespace SourceGen.AppForms { + partial class DataFileLoadIssue { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.problemWithFileLabel = new System.Windows.Forms.Label(); + this.pathNameTextBox = new System.Windows.Forms.TextBox(); + this.problemLabel = new System.Windows.Forms.Label(); + this.cancelButton = new System.Windows.Forms.Button(); + this.okButton = new System.Windows.Forms.Button(); + this.doYouWantLabel = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // problemWithFileLabel + // + this.problemWithFileLabel.AutoSize = true; + this.problemWithFileLabel.Location = new System.Drawing.Point(13, 13); + this.problemWithFileLabel.Name = "problemWithFileLabel"; + this.problemWithFileLabel.Size = new System.Drawing.Size(221, 13); + this.problemWithFileLabel.TabIndex = 2; + this.problemWithFileLabel.Text = "There was an error while loading the data file:"; + // + // pathNameTextBox + // + this.pathNameTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.pathNameTextBox.Location = new System.Drawing.Point(13, 39); + this.pathNameTextBox.Name = "pathNameTextBox"; + this.pathNameTextBox.ReadOnly = true; + this.pathNameTextBox.Size = new System.Drawing.Size(488, 20); + this.pathNameTextBox.TabIndex = 3; + // + // problemLabel + // + this.problemLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.problemLabel.Location = new System.Drawing.Point(13, 73); + this.problemLabel.Name = "problemLabel"; + this.problemLabel.Size = new System.Drawing.Size(488, 31); + this.problemLabel.TabIndex = 4; + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(428, 113); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 1; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + // + // okButton + // + this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.okButton.Location = new System.Drawing.Point(347, 113); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(75, 23); + this.okButton.TabIndex = 0; + this.okButton.Text = "OK"; + this.okButton.UseVisualStyleBackColor = true; + // + // doYouWantLabel + // + this.doYouWantLabel.AutoSize = true; + this.doYouWantLabel.Location = new System.Drawing.Point(13, 117); + this.doYouWantLabel.Name = "doYouWantLabel"; + this.doYouWantLabel.Size = new System.Drawing.Size(175, 13); + this.doYouWantLabel.TabIndex = 5; + this.doYouWantLabel.Text = "Do you want to locate the data file?\r\n"; + // + // DataFileLoadIssue + // + this.AcceptButton = this.okButton; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(515, 148); + this.Controls.Add(this.doYouWantLabel); + this.Controls.Add(this.okButton); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.problemLabel); + this.Controls.Add(this.pathNameTextBox); + this.Controls.Add(this.problemWithFileLabel); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "DataFileLoadIssue"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Data File Load Issue"; + this.Load += new System.EventHandler(this.DataFileLoadIssue_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label problemWithFileLabel; + private System.Windows.Forms.TextBox pathNameTextBox; + private System.Windows.Forms.Label problemLabel; + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.Label doYouWantLabel; + } +} \ No newline at end of file diff --git a/SourceGen/AppForms/DataFileLoadIssue.cs b/SourceGen/AppForms/DataFileLoadIssue.cs new file mode 100644 index 0000000..2cc1c0d --- /dev/null +++ b/SourceGen/AppForms/DataFileLoadIssue.cs @@ -0,0 +1,40 @@ +/* + * Copyright 2018 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.Windows.Forms; + +namespace SourceGen.AppForms { + public partial class DataFileLoadIssue : Form { + /// + /// Path name of problematic file. + /// + public string PathName { get; set; } + + /// + /// Message to show in the dialog. + /// + public string Message { get; set; } + + public DataFileLoadIssue() { + InitializeComponent(); + } + + private void DataFileLoadIssue_Load(object sender, EventArgs e) { + pathNameTextBox.Text = PathName; + problemLabel.Text = Message; + } + } +} diff --git a/SourceGen/AppForms/DataFileLoadIssue.resx b/SourceGen/AppForms/DataFileLoadIssue.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/SourceGen/AppForms/DataFileLoadIssue.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/SourceGen/AppForms/EditAddress.Designer.cs b/SourceGen/AppForms/EditAddress.Designer.cs new file mode 100644 index 0000000..70c9381 --- /dev/null +++ b/SourceGen/AppForms/EditAddress.Designer.cs @@ -0,0 +1,140 @@ +/* + * Copyright 2018 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. + */ +namespace SourceGen.AppForms { + partial class EditAddress { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + System.Windows.Forms.Label addressLabel; + this.okButton = new System.Windows.Forms.Button(); + this.cancelButton = new System.Windows.Forms.Button(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.instructionLabel1 = new System.Windows.Forms.Label(); + this.instructionLabel2 = new System.Windows.Forms.Label(); + addressLabel = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // addressLabel + // + addressLabel.AutoSize = true; + addressLabel.Location = new System.Drawing.Point(12, 16); + addressLabel.Name = "addressLabel"; + addressLabel.Size = new System.Drawing.Size(48, 13); + addressLabel.TabIndex = 1; + addressLabel.Text = "Address:"; + // + // okButton + // + this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.okButton.Location = new System.Drawing.Point(190, 91); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(75, 23); + this.okButton.TabIndex = 3; + this.okButton.Text = "OK"; + this.okButton.UseVisualStyleBackColor = true; + this.okButton.Click += new System.EventHandler(this.okButton_Click); + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(271, 91); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 4; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + // + // textBox1 + // + this.textBox1.Location = new System.Drawing.Point(68, 13); + this.textBox1.Name = "textBox1"; + this.textBox1.Size = new System.Drawing.Size(275, 20); + this.textBox1.TabIndex = 0; + // + // instructionLabel1 + // + this.instructionLabel1.AutoSize = true; + this.instructionLabel1.Location = new System.Drawing.Point(12, 36); + this.instructionLabel1.Name = "instructionLabel1"; + this.instructionLabel1.Size = new System.Drawing.Size(331, 13); + this.instructionLabel1.TabIndex = 2; + this.instructionLabel1.Text = "Enter 16-bit or 24-bit address in hexadecimal, e.g. $1000 or 00/be00.\r\n"; + // + // instructionLabel2 + // + this.instructionLabel2.AutoSize = true; + this.instructionLabel2.Location = new System.Drawing.Point(12, 58); + this.instructionLabel2.Name = "instructionLabel2"; + this.instructionLabel2.Size = new System.Drawing.Size(258, 13); + this.instructionLabel2.TabIndex = 5; + this.instructionLabel2.Text = "Leave the field blank to remove the address override."; + // + // EditAddress + // + this.AcceptButton = this.okButton; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(358, 126); + this.Controls.Add(this.instructionLabel2); + this.Controls.Add(this.instructionLabel1); + this.Controls.Add(this.textBox1); + this.Controls.Add(addressLabel); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.okButton); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "EditAddress"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Edit Address"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.Label instructionLabel1; + private System.Windows.Forms.Label instructionLabel2; + } +} \ No newline at end of file diff --git a/SourceGen/AppForms/EditAddress.cs b/SourceGen/AppForms/EditAddress.cs new file mode 100644 index 0000000..7cca9db --- /dev/null +++ b/SourceGen/AppForms/EditAddress.cs @@ -0,0 +1,124 @@ +/* + * Copyright 2018 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.Diagnostics; +using System.Windows.Forms; + +namespace SourceGen.AppForms { + public partial class EditAddress : Form { +#if false + private bool mAllowLastChar; +#endif + + /// + /// Maximum allowed address value. + /// + public int MaxAddressValue { get; set; } + + /// + /// Address typed by user. Only valid after the dialog returns OK. Will be set to -1 + /// if the user is attempting to delete the address. + /// + public int Address { get; private set; } + + public EditAddress() { + InitializeComponent(); + Address = -2; + MaxAddressValue = (1 << 24) - 1; + +#if false + // This is probably not all that useful. We're not preventing + // invalid inputs, e.g. excessively large values or "$/$/$/", by restricting + // the keys that can be typed. + textBox1.KeyDown += textBox1_KeyDown; + textBox1.KeyPress += textBox1_KeyPress; +#endif + + // Update the OK button based on current contents. + textBox1.TextChanged += textBox1_TextChanged; + } + + public void SetInitialAddress(int addr) { + textBox1.Text = Asm65.Address.AddressToString(addr, false); + textBox1.SelectAll(); + } + + /// + /// Handles a click on the OK button by setting the Address property to the + /// decoded value from the text field. + /// + /// + /// + private void okButton_Click(object sender, EventArgs e) { + if (textBox1.Text.Length == 0) { + Address = -1; + } else { + Asm65.Address.ParseAddress(textBox1.Text, MaxAddressValue, out int addr); + Address = addr; + } + } + + /// + /// Enables or disables the OK button depending on whether the current input is + /// valid. We allow valid addresses and an empty box. + /// + private void UpdateOkEnabled() { + string text = textBox1.Text; + okButton.Enabled = (text.Length == 0) || + Asm65.Address.ParseAddress(text, MaxAddressValue, out int unused); + } + +#if false + /// + /// Limits characters to [A-F][a-f][0-9][/]. + /// + private void textBox1_KeyDown(object sender, KeyEventArgs e) { + bool allow = false; + if (e.KeyCode == Keys.D4 && e.Modifiers == Keys.Shift) { + allow = true; // allow '$'; not sure this works on non-US keyboards? + } else if (e.KeyCode >= Keys.A && e.KeyCode <= Keys.F) { + allow = !(e.Alt || e.Control); // allow shift + } else if ((e.KeyCode >= Keys.D0 && e.KeyCode <= Keys.D9) || + e.KeyCode == Keys.OemQuestion) { + allow = (e.Modifiers == 0); + } else if (e.KeyCode == Keys.Back || e.KeyCode == Keys.Delete) { + allow = true; + } + + mAllowLastChar = allow; + //Debug.WriteLine("DOWN " + e.KeyCode + " allow=" + allow); + } + + /// + /// Rejects invalid characters. + /// + private void textBox1_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e) { + //Debug.WriteLine("PRESS " + e.KeyChar + " : " + mAllowLastChar); + if (!mAllowLastChar) { + e.Handled = true; + } + } +#endif + + /// + /// Updates the OK button whenever the text changes. This works for all change sources, + /// including programmatic. + /// + private void textBox1_TextChanged(object sender, EventArgs e) { + UpdateOkEnabled(); + } + } +} diff --git a/SourceGen/AppForms/EditAddress.resx b/SourceGen/AppForms/EditAddress.resx new file mode 100644 index 0000000..e38ed36 --- /dev/null +++ b/SourceGen/AppForms/EditAddress.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + \ No newline at end of file diff --git a/SourceGen/AppForms/EditAppSettings.Designer.cs b/SourceGen/AppForms/EditAppSettings.Designer.cs new file mode 100644 index 0000000..fc6a70b --- /dev/null +++ b/SourceGen/AppForms/EditAppSettings.Designer.cs @@ -0,0 +1,1583 @@ +/* + * Copyright 2018 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. + */ +namespace SourceGen.AppForms { + partial class EditAppSettings { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.cancelButton = new System.Windows.Forms.Button(); + this.okButton = new System.Windows.Forms.Button(); + this.applyButton = new System.Windows.Forms.Button(); + this.settingsTabControl = new System.Windows.Forms.TabControl(); + this.codeViewTabPage = new System.Windows.Forms.TabPage(); + this.clipboardGroupBox = new System.Windows.Forms.GroupBox(); + this.clipboardFormatLabel = new System.Windows.Forms.Label(); + this.clipboardFormatComboBox = new System.Windows.Forms.ComboBox(); + this.enableDebugCheckBox = new System.Windows.Forms.CheckBox(); + this.upperCaseGroupBox = new System.Windows.Forms.GroupBox(); + this.upperAllUpperButton = new System.Windows.Forms.Button(); + this.upperAllLowerButton = new System.Windows.Forms.Button(); + this.upperXYCheckBox = new System.Windows.Forms.CheckBox(); + this.upperSCheckBox = new System.Windows.Forms.CheckBox(); + this.upperACheckBox = new System.Windows.Forms.CheckBox(); + this.upperPseudoOpCheckBox = new System.Windows.Forms.CheckBox(); + this.upperOpcodeCheckBox = new System.Windows.Forms.CheckBox(); + this.upperHexCheckBox = new System.Windows.Forms.CheckBox(); + this.codeViewFontGroupBox = new System.Windows.Forms.GroupBox(); + this.currentFontLabel = new System.Windows.Forms.Label(); + this.selectFontButton = new System.Windows.Forms.Button(); + this.currentFontDisplayLabel = new System.Windows.Forms.Label(); + this.columnVisGroup = new System.Windows.Forms.GroupBox(); + this.showCol0 = new System.Windows.Forms.Button(); + this.showCol8 = new System.Windows.Forms.Button(); + this.showCol1 = new System.Windows.Forms.Button(); + this.showCol7 = new System.Windows.Forms.Button(); + this.showCol2 = new System.Windows.Forms.Button(); + this.showCol6 = new System.Windows.Forms.Button(); + this.showCol3 = new System.Windows.Forms.Button(); + this.showCol5 = new System.Windows.Forms.Button(); + this.showCol4 = new System.Windows.Forms.Button(); + this.asmConfigTabPage = new System.Windows.Forms.TabPage(); + this.showCycleCountsCheckBox = new System.Windows.Forms.CheckBox(); + this.configAsmGenLabel = new System.Windows.Forms.Label(); + this.longLabelNewLineCheckBox = new System.Windows.Forms.CheckBox(); + this.showAsmIdentCheckBox = new System.Windows.Forms.CheckBox(); + this.disableLabelLocalizationCheckBox = new System.Windows.Forms.CheckBox(); + this.clearMerlin32Button = new System.Windows.Forms.Button(); + this.clearCc65Button = new System.Windows.Forms.Button(); + this.browseMerlin32Button = new System.Windows.Forms.Button(); + this.browseCc65Button = new System.Windows.Forms.Button(); + this.cc65PathTextBox = new System.Windows.Forms.TextBox(); + this.merlin32PathTextBox = new System.Windows.Forms.TextBox(); + this.asmMerln32Label = new System.Windows.Forms.Label(); + this.asmCc65Label = new System.Windows.Forms.Label(); + this.asmExplanationLabel = new System.Windows.Forms.Label(); + this.displayFormatTabPage = new System.Windows.Forms.TabPage(); + this.fmtExplanationLabel = new System.Windows.Forms.Label(); + this.quickDisplayFormatGroup = new System.Windows.Forms.GroupBox(); + this.quickFmtMerlin32Button = new System.Windows.Forms.Button(); + this.quickFmtCc65Button = new System.Windows.Forms.Button(); + this.quickFmtDefaultButton = new System.Windows.Forms.Button(); + this.useMerlinExpressions = new System.Windows.Forms.CheckBox(); + this.operandWidthGroupBox = new System.Windows.Forms.GroupBox(); + this.disambPrefix24TextBox = new System.Windows.Forms.TextBox(); + this.disambPrefix16TextBox = new System.Windows.Forms.TextBox(); + this.disambPrefix24Label = new System.Windows.Forms.Label(); + this.disambPrefix16Label = new System.Windows.Forms.Label(); + this.disambOperandPrefixLabel = new System.Windows.Forms.Label(); + this.disambSuffix24Label = new System.Windows.Forms.Label(); + this.disambSuffix16Label = new System.Windows.Forms.Label(); + this.disambOpcodeSuffixLabel = new System.Windows.Forms.Label(); + this.disambSuffix24TextBox = new System.Windows.Forms.TextBox(); + this.disambSuffix16TextBox = new System.Windows.Forms.TextBox(); + this.pseudoOpTabPage = new System.Windows.Forms.TabPage(); + this.quickPseudoSetGroup = new System.Windows.Forms.GroupBox(); + this.quickPseudoMerlin32 = new System.Windows.Forms.Button(); + this.quickPseudoCc65Button = new System.Windows.Forms.Button(); + this.quickPseudoDefaultButton = new System.Windows.Forms.Button(); + this.strDciHiTextBox = new System.Windows.Forms.TextBox(); + this.strDciHiLabel = new System.Windows.Forms.Label(); + this.strDciTextBox = new System.Windows.Forms.TextBox(); + this.strDciLabel = new System.Windows.Forms.Label(); + this.strLen16HiTextBox = new System.Windows.Forms.TextBox(); + this.strLen16HiLabel = new System.Windows.Forms.Label(); + this.strLen16TextBox = new System.Windows.Forms.TextBox(); + this.strLen16Label = new System.Windows.Forms.Label(); + this.strReverseHiTextBox = new System.Windows.Forms.TextBox(); + this.strReverseHiLabel = new System.Windows.Forms.Label(); + this.strReverseTextBox = new System.Windows.Forms.TextBox(); + this.strReverseLabel = new System.Windows.Forms.Label(); + this.strNullTermHiTextBox = new System.Windows.Forms.TextBox(); + this.strNullTermHiLabel = new System.Windows.Forms.Label(); + this.strNullTermTextBox = new System.Windows.Forms.TextBox(); + this.strNullTermLabel = new System.Windows.Forms.Label(); + this.strLen8HiTextBox = new System.Windows.Forms.TextBox(); + this.strLen8HiLabel = new System.Windows.Forms.Label(); + this.strLen8TextBox = new System.Windows.Forms.TextBox(); + this.strLen8Label = new System.Windows.Forms.Label(); + this.strGenericHiTextBox = new System.Windows.Forms.TextBox(); + this.strGenericHiLabel = new System.Windows.Forms.Label(); + this.strGenericTextBox = new System.Windows.Forms.TextBox(); + this.strGenericLabel = new System.Windows.Forms.Label(); + this.popExplanationLabel = new System.Windows.Forms.Label(); + this.denseTextBox = new System.Windows.Forms.TextBox(); + this.denseLabel = new System.Windows.Forms.Label(); + this.fillTextBox = new System.Windows.Forms.TextBox(); + this.fillLabel = new System.Windows.Forms.Label(); + this.defineBigData2TextBox = new System.Windows.Forms.TextBox(); + this.defineBigData2Label = new System.Windows.Forms.Label(); + this.defineData4TextBox = new System.Windows.Forms.TextBox(); + this.defineData4Label = new System.Windows.Forms.Label(); + this.defineData3TextBox = new System.Windows.Forms.TextBox(); + this.defineData3Label = new System.Windows.Forms.Label(); + this.defineData2TextBox = new System.Windows.Forms.TextBox(); + this.defineData2Label = new System.Windows.Forms.Label(); + this.regWidthDirectiveTextBox = new System.Windows.Forms.TextBox(); + this.regWidthDirectiveLabel = new System.Windows.Forms.Label(); + this.orgDirectiveTextBox = new System.Windows.Forms.TextBox(); + this.orgDirectiveLabel = new System.Windows.Forms.Label(); + this.defineData1TextBox = new System.Windows.Forms.TextBox(); + this.defineData1Label = new System.Windows.Forms.Label(); + this.equDirectiveTextBox = new System.Windows.Forms.TextBox(); + this.equDirectiveLabel = new System.Windows.Forms.Label(); + this.settingsTabControl.SuspendLayout(); + this.codeViewTabPage.SuspendLayout(); + this.clipboardGroupBox.SuspendLayout(); + this.upperCaseGroupBox.SuspendLayout(); + this.codeViewFontGroupBox.SuspendLayout(); + this.columnVisGroup.SuspendLayout(); + this.asmConfigTabPage.SuspendLayout(); + this.displayFormatTabPage.SuspendLayout(); + this.quickDisplayFormatGroup.SuspendLayout(); + this.operandWidthGroupBox.SuspendLayout(); + this.pseudoOpTabPage.SuspendLayout(); + this.quickPseudoSetGroup.SuspendLayout(); + this.SuspendLayout(); + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(537, 406); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 3; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + // + // okButton + // + this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.okButton.Location = new System.Drawing.Point(456, 406); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(75, 23); + this.okButton.TabIndex = 2; + this.okButton.Text = "OK"; + this.okButton.UseVisualStyleBackColor = true; + this.okButton.Click += new System.EventHandler(this.okButton_Click); + // + // applyButton + // + this.applyButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.applyButton.Location = new System.Drawing.Point(354, 406); + this.applyButton.Name = "applyButton"; + this.applyButton.Size = new System.Drawing.Size(75, 23); + this.applyButton.TabIndex = 1; + this.applyButton.Text = "Apply"; + this.applyButton.UseVisualStyleBackColor = true; + this.applyButton.Click += new System.EventHandler(this.applyButton_Click); + // + // settingsTabControl + // + this.settingsTabControl.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.settingsTabControl.Controls.Add(this.codeViewTabPage); + this.settingsTabControl.Controls.Add(this.asmConfigTabPage); + this.settingsTabControl.Controls.Add(this.displayFormatTabPage); + this.settingsTabControl.Controls.Add(this.pseudoOpTabPage); + this.settingsTabControl.Location = new System.Drawing.Point(2, 2); + this.settingsTabControl.Name = "settingsTabControl"; + this.settingsTabControl.SelectedIndex = 0; + this.settingsTabControl.Size = new System.Drawing.Size(622, 398); + this.settingsTabControl.TabIndex = 0; + // + // codeViewTabPage + // + this.codeViewTabPage.Controls.Add(this.clipboardGroupBox); + this.codeViewTabPage.Controls.Add(this.enableDebugCheckBox); + this.codeViewTabPage.Controls.Add(this.upperCaseGroupBox); + this.codeViewTabPage.Controls.Add(this.codeViewFontGroupBox); + this.codeViewTabPage.Controls.Add(this.columnVisGroup); + this.codeViewTabPage.Location = new System.Drawing.Point(4, 22); + this.codeViewTabPage.Name = "codeViewTabPage"; + this.codeViewTabPage.Padding = new System.Windows.Forms.Padding(3); + this.codeViewTabPage.Size = new System.Drawing.Size(614, 372); + this.codeViewTabPage.TabIndex = 0; + this.codeViewTabPage.Text = "Code View"; + this.codeViewTabPage.UseVisualStyleBackColor = true; + // + // clipboardGroupBox + // + this.clipboardGroupBox.Controls.Add(this.clipboardFormatLabel); + this.clipboardGroupBox.Controls.Add(this.clipboardFormatComboBox); + this.clipboardGroupBox.Location = new System.Drawing.Point(406, 6); + this.clipboardGroupBox.Name = "clipboardGroupBox"; + this.clipboardGroupBox.Size = new System.Drawing.Size(200, 89); + this.clipboardGroupBox.TabIndex = 4; + this.clipboardGroupBox.TabStop = false; + this.clipboardGroupBox.Text = "Clipboard"; + // + // clipboardFormatLabel + // + this.clipboardFormatLabel.AutoSize = true; + this.clipboardFormatLabel.Location = new System.Drawing.Point(6, 22); + this.clipboardFormatLabel.Name = "clipboardFormatLabel"; + this.clipboardFormatLabel.Size = new System.Drawing.Size(174, 13); + this.clipboardFormatLabel.TabIndex = 1; + this.clipboardFormatLabel.Text = "Format for lines copied to clipboard:"; + // + // clipboardFormatComboBox + // + this.clipboardFormatComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.clipboardFormatComboBox.FormattingEnabled = true; + this.clipboardFormatComboBox.Items.AddRange(new object[] { + "Assembler Source", + "Disassembly"}); + this.clipboardFormatComboBox.Location = new System.Drawing.Point(6, 49); + this.clipboardFormatComboBox.Name = "clipboardFormatComboBox"; + this.clipboardFormatComboBox.Size = new System.Drawing.Size(188, 21); + this.clipboardFormatComboBox.TabIndex = 0; + this.clipboardFormatComboBox.SelectedIndexChanged += new System.EventHandler(this.clipboardFormatComboBox_SelectedIndexChanged); + // + // enableDebugCheckBox + // + this.enableDebugCheckBox.AutoSize = true; + this.enableDebugCheckBox.Location = new System.Drawing.Point(12, 349); + this.enableDebugCheckBox.Name = "enableDebugCheckBox"; + this.enableDebugCheckBox.Size = new System.Drawing.Size(129, 17); + this.enableDebugCheckBox.TabIndex = 3; + this.enableDebugCheckBox.Text = "Enable DEBUG menu"; + this.enableDebugCheckBox.UseVisualStyleBackColor = true; + this.enableDebugCheckBox.CheckedChanged += new System.EventHandler(this.enableDebugCheckBox_CheckedChanged); + // + // upperCaseGroupBox + // + this.upperCaseGroupBox.Controls.Add(this.upperAllUpperButton); + this.upperCaseGroupBox.Controls.Add(this.upperAllLowerButton); + this.upperCaseGroupBox.Controls.Add(this.upperXYCheckBox); + this.upperCaseGroupBox.Controls.Add(this.upperSCheckBox); + this.upperCaseGroupBox.Controls.Add(this.upperACheckBox); + this.upperCaseGroupBox.Controls.Add(this.upperPseudoOpCheckBox); + this.upperCaseGroupBox.Controls.Add(this.upperOpcodeCheckBox); + this.upperCaseGroupBox.Controls.Add(this.upperHexCheckBox); + this.upperCaseGroupBox.Location = new System.Drawing.Point(162, 101); + this.upperCaseGroupBox.Name = "upperCaseGroupBox"; + this.upperCaseGroupBox.Size = new System.Drawing.Size(224, 197); + this.upperCaseGroupBox.TabIndex = 0; + this.upperCaseGroupBox.TabStop = false; + this.upperCaseGroupBox.Text = "Upper Case Display"; + // + // upperAllUpperButton + // + this.upperAllUpperButton.Location = new System.Drawing.Point(88, 164); + this.upperAllUpperButton.Name = "upperAllUpperButton"; + this.upperAllUpperButton.Size = new System.Drawing.Size(75, 23); + this.upperAllUpperButton.TabIndex = 7; + this.upperAllUpperButton.Text = "All Upper"; + this.upperAllUpperButton.UseVisualStyleBackColor = true; + this.upperAllUpperButton.Click += new System.EventHandler(this.upperAllUpperButton_Click); + // + // upperAllLowerButton + // + this.upperAllLowerButton.Location = new System.Drawing.Point(7, 164); + this.upperAllLowerButton.Name = "upperAllLowerButton"; + this.upperAllLowerButton.Size = new System.Drawing.Size(75, 23); + this.upperAllLowerButton.TabIndex = 6; + this.upperAllLowerButton.Text = "All Lower"; + this.upperAllLowerButton.UseVisualStyleBackColor = true; + this.upperAllLowerButton.Click += new System.EventHandler(this.upperAllLowerButton_Click); + // + // upperXYCheckBox + // + this.upperXYCheckBox.AutoSize = true; + this.upperXYCheckBox.Location = new System.Drawing.Point(8, 139); + this.upperXYCheckBox.Name = "upperXYCheckBox"; + this.upperXYCheckBox.Size = new System.Drawing.Size(89, 17); + this.upperXYCheckBox.TabIndex = 5; + this.upperXYCheckBox.Text = "Operand X/Y"; + this.upperXYCheckBox.UseVisualStyleBackColor = true; + this.upperXYCheckBox.CheckedChanged += new System.EventHandler(this.upperXYCheckBox_CheckedChanged); + // + // upperSCheckBox + // + this.upperSCheckBox.AutoSize = true; + this.upperSCheckBox.Location = new System.Drawing.Point(8, 115); + this.upperSCheckBox.Name = "upperSCheckBox"; + this.upperSCheckBox.Size = new System.Drawing.Size(77, 17); + this.upperSCheckBox.TabIndex = 4; + this.upperSCheckBox.Text = "Operand S"; + this.upperSCheckBox.UseVisualStyleBackColor = true; + this.upperSCheckBox.CheckedChanged += new System.EventHandler(this.upperSCheckBox_CheckedChanged); + // + // upperACheckBox + // + this.upperACheckBox.AutoSize = true; + this.upperACheckBox.Location = new System.Drawing.Point(8, 91); + this.upperACheckBox.Name = "upperACheckBox"; + this.upperACheckBox.Size = new System.Drawing.Size(77, 17); + this.upperACheckBox.TabIndex = 3; + this.upperACheckBox.Text = "Operand A"; + this.upperACheckBox.UseVisualStyleBackColor = true; + this.upperACheckBox.CheckedChanged += new System.EventHandler(this.upperACheckBox_CheckedChanged); + // + // upperPseudoOpCheckBox + // + this.upperPseudoOpCheckBox.AutoSize = true; + this.upperPseudoOpCheckBox.Location = new System.Drawing.Point(8, 67); + this.upperPseudoOpCheckBox.Name = "upperPseudoOpCheckBox"; + this.upperPseudoOpCheckBox.Size = new System.Drawing.Size(106, 17); + this.upperPseudoOpCheckBox.TabIndex = 2; + this.upperPseudoOpCheckBox.Text = "Pseudo-opcodes"; + this.upperPseudoOpCheckBox.UseVisualStyleBackColor = true; + this.upperPseudoOpCheckBox.CheckedChanged += new System.EventHandler(this.upperPseudoOpCheckBox_CheckedChanged); + // + // upperOpcodeCheckBox + // + this.upperOpcodeCheckBox.AutoSize = true; + this.upperOpcodeCheckBox.Location = new System.Drawing.Point(8, 44); + this.upperOpcodeCheckBox.Name = "upperOpcodeCheckBox"; + this.upperOpcodeCheckBox.Size = new System.Drawing.Size(69, 17); + this.upperOpcodeCheckBox.TabIndex = 1; + this.upperOpcodeCheckBox.Text = "Opcodes"; + this.upperOpcodeCheckBox.UseVisualStyleBackColor = true; + this.upperOpcodeCheckBox.CheckedChanged += new System.EventHandler(this.upperOpcodeCheckBox_CheckedChanged); + // + // upperHexCheckBox + // + this.upperHexCheckBox.AutoSize = true; + this.upperHexCheckBox.Location = new System.Drawing.Point(8, 20); + this.upperHexCheckBox.Name = "upperHexCheckBox"; + this.upperHexCheckBox.Size = new System.Drawing.Size(121, 17); + this.upperHexCheckBox.TabIndex = 0; + this.upperHexCheckBox.Text = "Hexadecimal values"; + this.upperHexCheckBox.UseVisualStyleBackColor = true; + this.upperHexCheckBox.CheckedChanged += new System.EventHandler(this.upperHexCheckBox_CheckedChanged); + // + // codeViewFontGroupBox + // + this.codeViewFontGroupBox.Controls.Add(this.currentFontLabel); + this.codeViewFontGroupBox.Controls.Add(this.selectFontButton); + this.codeViewFontGroupBox.Controls.Add(this.currentFontDisplayLabel); + this.codeViewFontGroupBox.Location = new System.Drawing.Point(162, 6); + this.codeViewFontGroupBox.Name = "codeViewFontGroupBox"; + this.codeViewFontGroupBox.Size = new System.Drawing.Size(224, 89); + this.codeViewFontGroupBox.TabIndex = 2; + this.codeViewFontGroupBox.TabStop = false; + this.codeViewFontGroupBox.Text = "Code List Font"; + // + // currentFontLabel + // + this.currentFontLabel.AutoSize = true; + this.currentFontLabel.Location = new System.Drawing.Point(7, 18); + this.currentFontLabel.Name = "currentFontLabel"; + this.currentFontLabel.Size = new System.Drawing.Size(65, 13); + this.currentFontLabel.TabIndex = 0; + this.currentFontLabel.Text = "Current font:"; + // + // selectFontButton + // + this.selectFontButton.Location = new System.Drawing.Point(6, 60); + this.selectFontButton.Name = "selectFontButton"; + this.selectFontButton.Size = new System.Drawing.Size(125, 23); + this.selectFontButton.TabIndex = 2; + this.selectFontButton.Text = "Select Font..."; + this.selectFontButton.UseVisualStyleBackColor = true; + this.selectFontButton.Click += new System.EventHandler(this.selectFontButton_Click); + // + // currentFontDisplayLabel + // + this.currentFontDisplayLabel.AutoSize = true; + this.currentFontDisplayLabel.Location = new System.Drawing.Point(7, 33); + this.currentFontDisplayLabel.Name = "currentFontDisplayLabel"; + this.currentFontDisplayLabel.Size = new System.Drawing.Size(181, 13); + this.currentFontDisplayLabel.TabIndex = 1; + this.currentFontDisplayLabel.Text = "Constantia, 14.25pt, style=Bold, Italic"; + // + // columnVisGroup + // + this.columnVisGroup.Controls.Add(this.showCol0); + this.columnVisGroup.Controls.Add(this.showCol8); + this.columnVisGroup.Controls.Add(this.showCol1); + this.columnVisGroup.Controls.Add(this.showCol7); + this.columnVisGroup.Controls.Add(this.showCol2); + this.columnVisGroup.Controls.Add(this.showCol6); + this.columnVisGroup.Controls.Add(this.showCol3); + this.columnVisGroup.Controls.Add(this.showCol5); + this.columnVisGroup.Controls.Add(this.showCol4); + this.columnVisGroup.Location = new System.Drawing.Point(6, 6); + this.columnVisGroup.Name = "columnVisGroup"; + this.columnVisGroup.Size = new System.Drawing.Size(122, 292); + this.columnVisGroup.TabIndex = 0; + this.columnVisGroup.TabStop = false; + this.columnVisGroup.Text = "Column Visibility"; + // + // showCol0 + // + this.showCol0.Location = new System.Drawing.Point(6, 19); + this.showCol0.Name = "showCol0"; + this.showCol0.Size = new System.Drawing.Size(110, 23); + this.showCol0.TabIndex = 0; + this.showCol0.Text = "{0} Offset"; + this.showCol0.UseVisualStyleBackColor = true; + // + // showCol8 + // + this.showCol8.Location = new System.Drawing.Point(6, 259); + this.showCol8.Name = "showCol8"; + this.showCol8.Size = new System.Drawing.Size(110, 23); + this.showCol8.TabIndex = 8; + this.showCol8.Text = "{0} Comment"; + this.showCol8.UseVisualStyleBackColor = true; + // + // showCol1 + // + this.showCol1.Location = new System.Drawing.Point(6, 49); + this.showCol1.Name = "showCol1"; + this.showCol1.Size = new System.Drawing.Size(110, 23); + this.showCol1.TabIndex = 1; + this.showCol1.Text = "{0} Address"; + this.showCol1.UseVisualStyleBackColor = true; + // + // showCol7 + // + this.showCol7.Location = new System.Drawing.Point(6, 229); + this.showCol7.Name = "showCol7"; + this.showCol7.Size = new System.Drawing.Size(110, 23); + this.showCol7.TabIndex = 7; + this.showCol7.Text = "{0} Operand"; + this.showCol7.UseVisualStyleBackColor = true; + // + // showCol2 + // + this.showCol2.Location = new System.Drawing.Point(6, 79); + this.showCol2.Name = "showCol2"; + this.showCol2.Size = new System.Drawing.Size(110, 23); + this.showCol2.TabIndex = 2; + this.showCol2.Text = "{0} Bytes"; + this.showCol2.UseVisualStyleBackColor = true; + // + // showCol6 + // + this.showCol6.Location = new System.Drawing.Point(6, 199); + this.showCol6.Name = "showCol6"; + this.showCol6.Size = new System.Drawing.Size(110, 23); + this.showCol6.TabIndex = 6; + this.showCol6.Text = "{0} Opcode"; + this.showCol6.UseVisualStyleBackColor = true; + // + // showCol3 + // + this.showCol3.Location = new System.Drawing.Point(6, 109); + this.showCol3.Name = "showCol3"; + this.showCol3.Size = new System.Drawing.Size(110, 23); + this.showCol3.TabIndex = 3; + this.showCol3.Text = "{0} Flags"; + this.showCol3.UseVisualStyleBackColor = true; + // + // showCol5 + // + this.showCol5.Location = new System.Drawing.Point(6, 169); + this.showCol5.Name = "showCol5"; + this.showCol5.Size = new System.Drawing.Size(110, 23); + this.showCol5.TabIndex = 5; + this.showCol5.Text = "{0} Label"; + this.showCol5.UseVisualStyleBackColor = true; + // + // showCol4 + // + this.showCol4.Location = new System.Drawing.Point(6, 139); + this.showCol4.Name = "showCol4"; + this.showCol4.Size = new System.Drawing.Size(110, 23); + this.showCol4.TabIndex = 4; + this.showCol4.Text = "{0} Attributes"; + this.showCol4.UseVisualStyleBackColor = true; + // + // asmConfigTabPage + // + this.asmConfigTabPage.Controls.Add(this.showCycleCountsCheckBox); + this.asmConfigTabPage.Controls.Add(this.configAsmGenLabel); + this.asmConfigTabPage.Controls.Add(this.longLabelNewLineCheckBox); + this.asmConfigTabPage.Controls.Add(this.showAsmIdentCheckBox); + this.asmConfigTabPage.Controls.Add(this.disableLabelLocalizationCheckBox); + this.asmConfigTabPage.Controls.Add(this.clearMerlin32Button); + this.asmConfigTabPage.Controls.Add(this.clearCc65Button); + this.asmConfigTabPage.Controls.Add(this.browseMerlin32Button); + this.asmConfigTabPage.Controls.Add(this.browseCc65Button); + this.asmConfigTabPage.Controls.Add(this.cc65PathTextBox); + this.asmConfigTabPage.Controls.Add(this.merlin32PathTextBox); + this.asmConfigTabPage.Controls.Add(this.asmMerln32Label); + this.asmConfigTabPage.Controls.Add(this.asmCc65Label); + this.asmConfigTabPage.Controls.Add(this.asmExplanationLabel); + this.asmConfigTabPage.Location = new System.Drawing.Point(4, 22); + this.asmConfigTabPage.Name = "asmConfigTabPage"; + this.asmConfigTabPage.Padding = new System.Windows.Forms.Padding(3); + this.asmConfigTabPage.Size = new System.Drawing.Size(614, 372); + this.asmConfigTabPage.TabIndex = 1; + this.asmConfigTabPage.Text = "Asm Config"; + this.asmConfigTabPage.UseVisualStyleBackColor = true; + // + // showCycleCountsCheckBox + // + this.showCycleCountsCheckBox.AutoSize = true; + this.showCycleCountsCheckBox.Location = new System.Drawing.Point(10, 217); + this.showCycleCountsCheckBox.Name = "showCycleCountsCheckBox"; + this.showCycleCountsCheckBox.Size = new System.Drawing.Size(116, 17); + this.showCycleCountsCheckBox.TabIndex = 53; + this.showCycleCountsCheckBox.Text = "Show cycle counts"; + this.showCycleCountsCheckBox.UseVisualStyleBackColor = true; + this.showCycleCountsCheckBox.CheckedChanged += new System.EventHandler(this.showCycleCountsCheckBox_CheckedChanged); + // + // configAsmGenLabel + // + this.configAsmGenLabel.AutoSize = true; + this.configAsmGenLabel.Location = new System.Drawing.Point(7, 128); + this.configAsmGenLabel.Name = "configAsmGenLabel"; + this.configAsmGenLabel.Size = new System.Drawing.Size(154, 13); + this.configAsmGenLabel.TabIndex = 52; + this.configAsmGenLabel.Text = "Configure assembly generation:"; + // + // longLabelNewLineCheckBox + // + this.longLabelNewLineCheckBox.AutoSize = true; + this.longLabelNewLineCheckBox.Location = new System.Drawing.Point(10, 193); + this.longLabelNewLineCheckBox.Name = "longLabelNewLineCheckBox"; + this.longLabelNewLineCheckBox.Size = new System.Drawing.Size(173, 17); + this.longLabelNewLineCheckBox.TabIndex = 51; + this.longLabelNewLineCheckBox.Text = "Put long labels on separate line"; + this.longLabelNewLineCheckBox.UseVisualStyleBackColor = true; + // + // showAsmIdentCheckBox + // + this.showAsmIdentCheckBox.AutoSize = true; + this.showAsmIdentCheckBox.Location = new System.Drawing.Point(10, 147); + this.showAsmIdentCheckBox.Name = "showAsmIdentCheckBox"; + this.showAsmIdentCheckBox.Size = new System.Drawing.Size(154, 17); + this.showAsmIdentCheckBox.TabIndex = 10; + this.showAsmIdentCheckBox.Text = "Identify assembler in output"; + this.showAsmIdentCheckBox.UseVisualStyleBackColor = true; + this.showAsmIdentCheckBox.CheckedChanged += new System.EventHandler(this.showAsmIdentCheckBox_CheckedChanged); + // + // disableLabelLocalizationCheckBox + // + this.disableLabelLocalizationCheckBox.AutoSize = true; + this.disableLabelLocalizationCheckBox.Location = new System.Drawing.Point(10, 170); + this.disableLabelLocalizationCheckBox.Name = "disableLabelLocalizationCheckBox"; + this.disableLabelLocalizationCheckBox.Size = new System.Drawing.Size(141, 17); + this.disableLabelLocalizationCheckBox.TabIndex = 9; + this.disableLabelLocalizationCheckBox.Text = "Disable label localization"; + this.disableLabelLocalizationCheckBox.UseVisualStyleBackColor = true; + this.disableLabelLocalizationCheckBox.CheckedChanged += new System.EventHandler(this.disableLabelLocalizationCheckBox_CheckedChanged); + // + // clearMerlin32Button + // + this.clearMerlin32Button.Location = new System.Drawing.Point(531, 76); + this.clearMerlin32Button.Name = "clearMerlin32Button"; + this.clearMerlin32Button.Size = new System.Drawing.Size(75, 23); + this.clearMerlin32Button.TabIndex = 8; + this.clearMerlin32Button.Text = "Clear"; + this.clearMerlin32Button.UseVisualStyleBackColor = true; + this.clearMerlin32Button.Click += new System.EventHandler(this.clearMerlin32Button_Click); + // + // clearCc65Button + // + this.clearCc65Button.Location = new System.Drawing.Point(531, 35); + this.clearCc65Button.Name = "clearCc65Button"; + this.clearCc65Button.Size = new System.Drawing.Size(75, 23); + this.clearCc65Button.TabIndex = 4; + this.clearCc65Button.Text = "Clear"; + this.clearCc65Button.UseVisualStyleBackColor = true; + this.clearCc65Button.Click += new System.EventHandler(this.clearCc65Button_Click); + // + // browseMerlin32Button + // + this.browseMerlin32Button.Location = new System.Drawing.Point(450, 76); + this.browseMerlin32Button.Name = "browseMerlin32Button"; + this.browseMerlin32Button.Size = new System.Drawing.Size(75, 23); + this.browseMerlin32Button.TabIndex = 7; + this.browseMerlin32Button.Text = "Browse..."; + this.browseMerlin32Button.UseVisualStyleBackColor = true; + this.browseMerlin32Button.Click += new System.EventHandler(this.browseMerlin32Button_Click); + // + // browseCc65Button + // + this.browseCc65Button.Location = new System.Drawing.Point(450, 35); + this.browseCc65Button.Name = "browseCc65Button"; + this.browseCc65Button.Size = new System.Drawing.Size(75, 23); + this.browseCc65Button.TabIndex = 3; + this.browseCc65Button.Text = "Browse..."; + this.browseCc65Button.UseVisualStyleBackColor = true; + this.browseCc65Button.Click += new System.EventHandler(this.browseCc65Button_Click); + // + // cc65PathTextBox + // + this.cc65PathTextBox.Location = new System.Drawing.Point(65, 37); + this.cc65PathTextBox.Name = "cc65PathTextBox"; + this.cc65PathTextBox.Size = new System.Drawing.Size(379, 20); + this.cc65PathTextBox.TabIndex = 2; + this.cc65PathTextBox.Text = "C:\\something"; + this.cc65PathTextBox.TextChanged += new System.EventHandler(this.cc65PathTextBox_TextChanged); + // + // merlin32PathTextBox + // + this.merlin32PathTextBox.Location = new System.Drawing.Point(65, 78); + this.merlin32PathTextBox.Name = "merlin32PathTextBox"; + this.merlin32PathTextBox.Size = new System.Drawing.Size(379, 20); + this.merlin32PathTextBox.TabIndex = 6; + this.merlin32PathTextBox.Text = "C:\\something"; + this.merlin32PathTextBox.TextChanged += new System.EventHandler(this.merlin32PathTextBox_TextChanged); + // + // asmMerln32Label + // + this.asmMerln32Label.AutoSize = true; + this.asmMerln32Label.Location = new System.Drawing.Point(6, 81); + this.asmMerln32Label.Name = "asmMerln32Label"; + this.asmMerln32Label.Size = new System.Drawing.Size(53, 13); + this.asmMerln32Label.TabIndex = 5; + this.asmMerln32Label.Text = "Merlin 32:"; + // + // asmCc65Label + // + this.asmCc65Label.AutoSize = true; + this.asmCc65Label.Location = new System.Drawing.Point(25, 40); + this.asmCc65Label.Name = "asmCc65Label"; + this.asmCc65Label.Size = new System.Drawing.Size(34, 13); + this.asmCc65Label.TabIndex = 1; + this.asmCc65Label.Text = "cc65:"; + // + // asmExplanationLabel + // + this.asmExplanationLabel.AutoSize = true; + this.asmExplanationLabel.Location = new System.Drawing.Point(7, 7); + this.asmExplanationLabel.Name = "asmExplanationLabel"; + this.asmExplanationLabel.Size = new System.Drawing.Size(179, 13); + this.asmExplanationLabel.TabIndex = 0; + this.asmExplanationLabel.Text = "Configure installed cross-assemblers:"; + // + // displayFormatTabPage + // + this.displayFormatTabPage.Controls.Add(this.fmtExplanationLabel); + this.displayFormatTabPage.Controls.Add(this.quickDisplayFormatGroup); + this.displayFormatTabPage.Controls.Add(this.useMerlinExpressions); + this.displayFormatTabPage.Controls.Add(this.operandWidthGroupBox); + this.displayFormatTabPage.Location = new System.Drawing.Point(4, 22); + this.displayFormatTabPage.Name = "displayFormatTabPage"; + this.displayFormatTabPage.Padding = new System.Windows.Forms.Padding(3); + this.displayFormatTabPage.Size = new System.Drawing.Size(614, 372); + this.displayFormatTabPage.TabIndex = 3; + this.displayFormatTabPage.Text = "Display Format"; + this.displayFormatTabPage.UseVisualStyleBackColor = true; + // + // fmtExplanationLabel + // + this.fmtExplanationLabel.AutoSize = true; + this.fmtExplanationLabel.Location = new System.Drawing.Point(7, 7); + this.fmtExplanationLabel.Name = "fmtExplanationLabel"; + this.fmtExplanationLabel.Size = new System.Drawing.Size(374, 13); + this.fmtExplanationLabel.TabIndex = 52; + this.fmtExplanationLabel.Text = "Configure display format options. This does not affect source code generation."; + // + // quickDisplayFormatGroup + // + this.quickDisplayFormatGroup.Controls.Add(this.quickFmtMerlin32Button); + this.quickDisplayFormatGroup.Controls.Add(this.quickFmtCc65Button); + this.quickDisplayFormatGroup.Controls.Add(this.quickFmtDefaultButton); + this.quickDisplayFormatGroup.Location = new System.Drawing.Point(348, 291); + this.quickDisplayFormatGroup.Name = "quickDisplayFormatGroup"; + this.quickDisplayFormatGroup.Size = new System.Drawing.Size(258, 75); + this.quickDisplayFormatGroup.TabIndex = 51; + this.quickDisplayFormatGroup.TabStop = false; + this.quickDisplayFormatGroup.Text = "Quick Set"; + // + // quickFmtMerlin32Button + // + this.quickFmtMerlin32Button.Location = new System.Drawing.Point(173, 30); + this.quickFmtMerlin32Button.Name = "quickFmtMerlin32Button"; + this.quickFmtMerlin32Button.Size = new System.Drawing.Size(75, 23); + this.quickFmtMerlin32Button.TabIndex = 2; + this.quickFmtMerlin32Button.Text = "Merlin 32"; + this.quickFmtMerlin32Button.UseVisualStyleBackColor = true; + this.quickFmtMerlin32Button.Click += new System.EventHandler(this.quickFmtMerlin32Button_Click); + // + // quickFmtCc65Button + // + this.quickFmtCc65Button.Location = new System.Drawing.Point(92, 30); + this.quickFmtCc65Button.Name = "quickFmtCc65Button"; + this.quickFmtCc65Button.Size = new System.Drawing.Size(75, 23); + this.quickFmtCc65Button.TabIndex = 1; + this.quickFmtCc65Button.Text = "cc65"; + this.quickFmtCc65Button.UseVisualStyleBackColor = true; + this.quickFmtCc65Button.Click += new System.EventHandler(this.quickFmtCc65Button_Click); + // + // quickFmtDefaultButton + // + this.quickFmtDefaultButton.Location = new System.Drawing.Point(11, 30); + this.quickFmtDefaultButton.Name = "quickFmtDefaultButton"; + this.quickFmtDefaultButton.Size = new System.Drawing.Size(75, 23); + this.quickFmtDefaultButton.TabIndex = 0; + this.quickFmtDefaultButton.Text = "Default"; + this.quickFmtDefaultButton.UseVisualStyleBackColor = true; + this.quickFmtDefaultButton.Click += new System.EventHandler(this.quickFmtDefaultButton_Click); + // + // useMerlinExpressions + // + this.useMerlinExpressions.AutoSize = true; + this.useMerlinExpressions.Location = new System.Drawing.Point(6, 153); + this.useMerlinExpressions.Name = "useMerlinExpressions"; + this.useMerlinExpressions.Size = new System.Drawing.Size(158, 17); + this.useMerlinExpressions.TabIndex = 49; + this.useMerlinExpressions.Text = "Use Merlin-style expressions"; + this.useMerlinExpressions.UseVisualStyleBackColor = true; + this.useMerlinExpressions.CheckedChanged += new System.EventHandler(this.shiftAfterAdjustCheckBox_CheckedChanged); + // + // operandWidthGroupBox + // + this.operandWidthGroupBox.Controls.Add(this.disambPrefix24TextBox); + this.operandWidthGroupBox.Controls.Add(this.disambPrefix16TextBox); + this.operandWidthGroupBox.Controls.Add(this.disambPrefix24Label); + this.operandWidthGroupBox.Controls.Add(this.disambPrefix16Label); + this.operandWidthGroupBox.Controls.Add(this.disambOperandPrefixLabel); + this.operandWidthGroupBox.Controls.Add(this.disambSuffix24Label); + this.operandWidthGroupBox.Controls.Add(this.disambSuffix16Label); + this.operandWidthGroupBox.Controls.Add(this.disambOpcodeSuffixLabel); + this.operandWidthGroupBox.Controls.Add(this.disambSuffix24TextBox); + this.operandWidthGroupBox.Controls.Add(this.disambSuffix16TextBox); + this.operandWidthGroupBox.Location = new System.Drawing.Point(6, 38); + this.operandWidthGroupBox.Name = "operandWidthGroupBox"; + this.operandWidthGroupBox.Size = new System.Drawing.Size(295, 98); + this.operandWidthGroupBox.TabIndex = 48; + this.operandWidthGroupBox.TabStop = false; + this.operandWidthGroupBox.Text = "Operand Width Disambiguator"; + // + // disambPrefix24TextBox + // + this.disambPrefix24TextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.disambPrefix24TextBox.Location = new System.Drawing.Point(220, 64); + this.disambPrefix24TextBox.MaxLength = 8; + this.disambPrefix24TextBox.Name = "disambPrefix24TextBox"; + this.disambPrefix24TextBox.Size = new System.Drawing.Size(62, 20); + this.disambPrefix24TextBox.TabIndex = 13; + this.disambPrefix24TextBox.Text = ".placeho"; + this.disambPrefix24TextBox.TextChanged += new System.EventHandler(this.WidthDisamControlChanged); + // + // disambPrefix16TextBox + // + this.disambPrefix16TextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.disambPrefix16TextBox.Location = new System.Drawing.Point(220, 38); + this.disambPrefix16TextBox.MaxLength = 8; + this.disambPrefix16TextBox.Name = "disambPrefix16TextBox"; + this.disambPrefix16TextBox.Size = new System.Drawing.Size(62, 20); + this.disambPrefix16TextBox.TabIndex = 12; + this.disambPrefix16TextBox.Text = ".placeho"; + this.disambPrefix16TextBox.TextChanged += new System.EventHandler(this.WidthDisamControlChanged); + // + // disambPrefix24Label + // + this.disambPrefix24Label.AutoSize = true; + this.disambPrefix24Label.Location = new System.Drawing.Point(171, 66); + this.disambPrefix24Label.Name = "disambPrefix24Label"; + this.disambPrefix24Label.Size = new System.Drawing.Size(36, 13); + this.disambPrefix24Label.TabIndex = 11; + this.disambPrefix24Label.Text = "24 bit:"; + // + // disambPrefix16Label + // + this.disambPrefix16Label.AutoSize = true; + this.disambPrefix16Label.Location = new System.Drawing.Point(171, 40); + this.disambPrefix16Label.Name = "disambPrefix16Label"; + this.disambPrefix16Label.Size = new System.Drawing.Size(36, 13); + this.disambPrefix16Label.TabIndex = 10; + this.disambPrefix16Label.Text = "16 bit:"; + // + // disambOperandPrefixLabel + // + this.disambOperandPrefixLabel.AutoSize = true; + this.disambOperandPrefixLabel.Location = new System.Drawing.Point(171, 20); + this.disambOperandPrefixLabel.Name = "disambOperandPrefixLabel"; + this.disambOperandPrefixLabel.Size = new System.Drawing.Size(79, 13); + this.disambOperandPrefixLabel.TabIndex = 9; + this.disambOperandPrefixLabel.Text = "Operand prefix:"; + // + // disambSuffix24Label + // + this.disambSuffix24Label.AutoSize = true; + this.disambSuffix24Label.Location = new System.Drawing.Point(10, 66); + this.disambSuffix24Label.Name = "disambSuffix24Label"; + this.disambSuffix24Label.Size = new System.Drawing.Size(36, 13); + this.disambSuffix24Label.TabIndex = 8; + this.disambSuffix24Label.Text = "24 bit:"; + // + // disambSuffix16Label + // + this.disambSuffix16Label.AutoSize = true; + this.disambSuffix16Label.Location = new System.Drawing.Point(10, 40); + this.disambSuffix16Label.Name = "disambSuffix16Label"; + this.disambSuffix16Label.Size = new System.Drawing.Size(36, 13); + this.disambSuffix16Label.TabIndex = 7; + this.disambSuffix16Label.Text = "16 bit:"; + // + // disambOpcodeSuffixLabel + // + this.disambOpcodeSuffixLabel.AutoSize = true; + this.disambOpcodeSuffixLabel.Location = new System.Drawing.Point(7, 20); + this.disambOpcodeSuffixLabel.Name = "disambOpcodeSuffixLabel"; + this.disambOpcodeSuffixLabel.Size = new System.Drawing.Size(75, 13); + this.disambOpcodeSuffixLabel.TabIndex = 6; + this.disambOpcodeSuffixLabel.Text = "Opcode suffix:"; + // + // disambSuffix24TextBox + // + this.disambSuffix24TextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.disambSuffix24TextBox.Location = new System.Drawing.Point(62, 64); + this.disambSuffix24TextBox.MaxLength = 8; + this.disambSuffix24TextBox.Name = "disambSuffix24TextBox"; + this.disambSuffix24TextBox.Size = new System.Drawing.Size(62, 20); + this.disambSuffix24TextBox.TabIndex = 5; + this.disambSuffix24TextBox.Text = ".placeho"; + this.disambSuffix24TextBox.TextChanged += new System.EventHandler(this.WidthDisamControlChanged); + // + // disambSuffix16TextBox + // + this.disambSuffix16TextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.disambSuffix16TextBox.Location = new System.Drawing.Point(62, 38); + this.disambSuffix16TextBox.MaxLength = 8; + this.disambSuffix16TextBox.Name = "disambSuffix16TextBox"; + this.disambSuffix16TextBox.Size = new System.Drawing.Size(62, 20); + this.disambSuffix16TextBox.TabIndex = 3; + this.disambSuffix16TextBox.Text = ".placeho"; + this.disambSuffix16TextBox.TextChanged += new System.EventHandler(this.WidthDisamControlChanged); + // + // pseudoOpTabPage + // + this.pseudoOpTabPage.Controls.Add(this.quickPseudoSetGroup); + this.pseudoOpTabPage.Controls.Add(this.strDciHiTextBox); + this.pseudoOpTabPage.Controls.Add(this.strDciHiLabel); + this.pseudoOpTabPage.Controls.Add(this.strDciTextBox); + this.pseudoOpTabPage.Controls.Add(this.strDciLabel); + this.pseudoOpTabPage.Controls.Add(this.strLen16HiTextBox); + this.pseudoOpTabPage.Controls.Add(this.strLen16HiLabel); + this.pseudoOpTabPage.Controls.Add(this.strLen16TextBox); + this.pseudoOpTabPage.Controls.Add(this.strLen16Label); + this.pseudoOpTabPage.Controls.Add(this.strReverseHiTextBox); + this.pseudoOpTabPage.Controls.Add(this.strReverseHiLabel); + this.pseudoOpTabPage.Controls.Add(this.strReverseTextBox); + this.pseudoOpTabPage.Controls.Add(this.strReverseLabel); + this.pseudoOpTabPage.Controls.Add(this.strNullTermHiTextBox); + this.pseudoOpTabPage.Controls.Add(this.strNullTermHiLabel); + this.pseudoOpTabPage.Controls.Add(this.strNullTermTextBox); + this.pseudoOpTabPage.Controls.Add(this.strNullTermLabel); + this.pseudoOpTabPage.Controls.Add(this.strLen8HiTextBox); + this.pseudoOpTabPage.Controls.Add(this.strLen8HiLabel); + this.pseudoOpTabPage.Controls.Add(this.strLen8TextBox); + this.pseudoOpTabPage.Controls.Add(this.strLen8Label); + this.pseudoOpTabPage.Controls.Add(this.strGenericHiTextBox); + this.pseudoOpTabPage.Controls.Add(this.strGenericHiLabel); + this.pseudoOpTabPage.Controls.Add(this.strGenericTextBox); + this.pseudoOpTabPage.Controls.Add(this.strGenericLabel); + this.pseudoOpTabPage.Controls.Add(this.popExplanationLabel); + this.pseudoOpTabPage.Controls.Add(this.denseTextBox); + this.pseudoOpTabPage.Controls.Add(this.denseLabel); + this.pseudoOpTabPage.Controls.Add(this.fillTextBox); + this.pseudoOpTabPage.Controls.Add(this.fillLabel); + this.pseudoOpTabPage.Controls.Add(this.defineBigData2TextBox); + this.pseudoOpTabPage.Controls.Add(this.defineBigData2Label); + this.pseudoOpTabPage.Controls.Add(this.defineData4TextBox); + this.pseudoOpTabPage.Controls.Add(this.defineData4Label); + this.pseudoOpTabPage.Controls.Add(this.defineData3TextBox); + this.pseudoOpTabPage.Controls.Add(this.defineData3Label); + this.pseudoOpTabPage.Controls.Add(this.defineData2TextBox); + this.pseudoOpTabPage.Controls.Add(this.defineData2Label); + this.pseudoOpTabPage.Controls.Add(this.regWidthDirectiveTextBox); + this.pseudoOpTabPage.Controls.Add(this.regWidthDirectiveLabel); + this.pseudoOpTabPage.Controls.Add(this.orgDirectiveTextBox); + this.pseudoOpTabPage.Controls.Add(this.orgDirectiveLabel); + this.pseudoOpTabPage.Controls.Add(this.defineData1TextBox); + this.pseudoOpTabPage.Controls.Add(this.defineData1Label); + this.pseudoOpTabPage.Controls.Add(this.equDirectiveTextBox); + this.pseudoOpTabPage.Controls.Add(this.equDirectiveLabel); + this.pseudoOpTabPage.Location = new System.Drawing.Point(4, 22); + this.pseudoOpTabPage.Name = "pseudoOpTabPage"; + this.pseudoOpTabPage.Padding = new System.Windows.Forms.Padding(3); + this.pseudoOpTabPage.Size = new System.Drawing.Size(614, 372); + this.pseudoOpTabPage.TabIndex = 2; + this.pseudoOpTabPage.Text = "Pseudo-Op"; + this.pseudoOpTabPage.UseVisualStyleBackColor = true; + // + // quickPseudoSetGroup + // + this.quickPseudoSetGroup.Controls.Add(this.quickPseudoMerlin32); + this.quickPseudoSetGroup.Controls.Add(this.quickPseudoCc65Button); + this.quickPseudoSetGroup.Controls.Add(this.quickPseudoDefaultButton); + this.quickPseudoSetGroup.Location = new System.Drawing.Point(348, 291); + this.quickPseudoSetGroup.Name = "quickPseudoSetGroup"; + this.quickPseudoSetGroup.Size = new System.Drawing.Size(258, 75); + this.quickPseudoSetGroup.TabIndex = 46; + this.quickPseudoSetGroup.TabStop = false; + this.quickPseudoSetGroup.Text = "Quick Set"; + // + // quickPseudoMerlin32 + // + this.quickPseudoMerlin32.Location = new System.Drawing.Point(173, 30); + this.quickPseudoMerlin32.Name = "quickPseudoMerlin32"; + this.quickPseudoMerlin32.Size = new System.Drawing.Size(75, 23); + this.quickPseudoMerlin32.TabIndex = 2; + this.quickPseudoMerlin32.Text = "Merlin 32"; + this.quickPseudoMerlin32.UseVisualStyleBackColor = true; + this.quickPseudoMerlin32.Click += new System.EventHandler(this.quickPseudoMerlin32_Click); + // + // quickPseudoCc65Button + // + this.quickPseudoCc65Button.Location = new System.Drawing.Point(92, 30); + this.quickPseudoCc65Button.Name = "quickPseudoCc65Button"; + this.quickPseudoCc65Button.Size = new System.Drawing.Size(75, 23); + this.quickPseudoCc65Button.TabIndex = 1; + this.quickPseudoCc65Button.Text = "cc65"; + this.quickPseudoCc65Button.UseVisualStyleBackColor = true; + this.quickPseudoCc65Button.Click += new System.EventHandler(this.quickPseudoCc65Button_Click); + // + // quickPseudoDefaultButton + // + this.quickPseudoDefaultButton.Location = new System.Drawing.Point(11, 30); + this.quickPseudoDefaultButton.Name = "quickPseudoDefaultButton"; + this.quickPseudoDefaultButton.Size = new System.Drawing.Size(75, 23); + this.quickPseudoDefaultButton.TabIndex = 0; + this.quickPseudoDefaultButton.Text = "Default"; + this.quickPseudoDefaultButton.UseVisualStyleBackColor = true; + this.quickPseudoDefaultButton.Click += new System.EventHandler(this.quickPseudoDefaultButton_Click); + // + // strDciHiTextBox + // + this.strDciHiTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.strDciHiTextBox.Location = new System.Drawing.Point(546, 230); + this.strDciHiTextBox.MaxLength = 8; + this.strDciHiTextBox.Name = "strDciHiTextBox"; + this.strDciHiTextBox.Size = new System.Drawing.Size(62, 20); + this.strDciHiTextBox.TabIndex = 44; + this.strDciHiTextBox.Text = ".placeho"; + this.strDciHiTextBox.TextChanged += new System.EventHandler(this.PseudoOpTextChanged); + // + // strDciHiLabel + // + this.strDciHiLabel.Location = new System.Drawing.Point(463, 232); + this.strDciHiLabel.Name = "strDciHiLabel"; + this.strDciHiLabel.Size = new System.Drawing.Size(79, 23); + this.strDciHiLabel.TabIndex = 43; + this.strDciHiLabel.Text = "DCI/hi:"; + this.strDciHiLabel.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // strDciTextBox + // + this.strDciTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.strDciTextBox.Location = new System.Drawing.Point(394, 230); + this.strDciTextBox.MaxLength = 8; + this.strDciTextBox.Name = "strDciTextBox"; + this.strDciTextBox.Size = new System.Drawing.Size(62, 20); + this.strDciTextBox.TabIndex = 42; + this.strDciTextBox.Text = ".placeho"; + this.strDciTextBox.TextChanged += new System.EventHandler(this.PseudoOpTextChanged); + // + // strDciLabel + // + this.strDciLabel.Location = new System.Drawing.Point(309, 232); + this.strDciLabel.Name = "strDciLabel"; + this.strDciLabel.Size = new System.Drawing.Size(82, 23); + this.strDciLabel.TabIndex = 41; + this.strDciLabel.Text = "DCI:"; + this.strDciLabel.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // strLen16HiTextBox + // + this.strLen16HiTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.strLen16HiTextBox.Location = new System.Drawing.Point(546, 192); + this.strLen16HiTextBox.MaxLength = 8; + this.strLen16HiTextBox.Name = "strLen16HiTextBox"; + this.strLen16HiTextBox.Size = new System.Drawing.Size(62, 20); + this.strLen16HiTextBox.TabIndex = 36; + this.strLen16HiTextBox.Text = ".placeho"; + this.strLen16HiTextBox.TextChanged += new System.EventHandler(this.PseudoOpTextChanged); + // + // strLen16HiLabel + // + this.strLen16HiLabel.Location = new System.Drawing.Point(463, 194); + this.strLen16HiLabel.Name = "strLen16HiLabel"; + this.strLen16HiLabel.Size = new System.Drawing.Size(79, 23); + this.strLen16HiLabel.TabIndex = 35; + this.strLen16HiLabel.Text = "2-byte len/hi:"; + this.strLen16HiLabel.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // strLen16TextBox + // + this.strLen16TextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.strLen16TextBox.Location = new System.Drawing.Point(394, 192); + this.strLen16TextBox.MaxLength = 8; + this.strLen16TextBox.Name = "strLen16TextBox"; + this.strLen16TextBox.Size = new System.Drawing.Size(62, 20); + this.strLen16TextBox.TabIndex = 34; + this.strLen16TextBox.Text = ".placeho"; + this.strLen16TextBox.TextChanged += new System.EventHandler(this.PseudoOpTextChanged); + // + // strLen16Label + // + this.strLen16Label.Location = new System.Drawing.Point(309, 194); + this.strLen16Label.Name = "strLen16Label"; + this.strLen16Label.Size = new System.Drawing.Size(82, 23); + this.strLen16Label.TabIndex = 33; + this.strLen16Label.Text = "2-byte len:"; + this.strLen16Label.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // strReverseHiTextBox + // + this.strReverseHiTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.strReverseHiTextBox.Location = new System.Drawing.Point(546, 154); + this.strReverseHiTextBox.MaxLength = 8; + this.strReverseHiTextBox.Name = "strReverseHiTextBox"; + this.strReverseHiTextBox.Size = new System.Drawing.Size(62, 20); + this.strReverseHiTextBox.TabIndex = 28; + this.strReverseHiTextBox.Text = ".placeho"; + this.strReverseHiTextBox.TextChanged += new System.EventHandler(this.PseudoOpTextChanged); + // + // strReverseHiLabel + // + this.strReverseHiLabel.Location = new System.Drawing.Point(463, 156); + this.strReverseHiLabel.Name = "strReverseHiLabel"; + this.strReverseHiLabel.Size = new System.Drawing.Size(79, 23); + this.strReverseHiLabel.TabIndex = 27; + this.strReverseHiLabel.Text = "Reverse/hi:"; + this.strReverseHiLabel.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // strReverseTextBox + // + this.strReverseTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.strReverseTextBox.Location = new System.Drawing.Point(394, 154); + this.strReverseTextBox.MaxLength = 8; + this.strReverseTextBox.Name = "strReverseTextBox"; + this.strReverseTextBox.Size = new System.Drawing.Size(62, 20); + this.strReverseTextBox.TabIndex = 26; + this.strReverseTextBox.Text = ".placeho"; + this.strReverseTextBox.TextChanged += new System.EventHandler(this.PseudoOpTextChanged); + // + // strReverseLabel + // + this.strReverseLabel.Location = new System.Drawing.Point(309, 156); + this.strReverseLabel.Name = "strReverseLabel"; + this.strReverseLabel.Size = new System.Drawing.Size(82, 23); + this.strReverseLabel.TabIndex = 25; + this.strReverseLabel.Text = "Reverse:"; + this.strReverseLabel.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // strNullTermHiTextBox + // + this.strNullTermHiTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.strNullTermHiTextBox.Location = new System.Drawing.Point(244, 230); + this.strNullTermHiTextBox.MaxLength = 8; + this.strNullTermHiTextBox.Name = "strNullTermHiTextBox"; + this.strNullTermHiTextBox.Size = new System.Drawing.Size(62, 20); + this.strNullTermHiTextBox.TabIndex = 40; + this.strNullTermHiTextBox.Text = ".placeho"; + this.strNullTermHiTextBox.TextChanged += new System.EventHandler(this.PseudoOpTextChanged); + // + // strNullTermHiLabel + // + this.strNullTermHiLabel.Location = new System.Drawing.Point(160, 232); + this.strNullTermHiLabel.Name = "strNullTermHiLabel"; + this.strNullTermHiLabel.Size = new System.Drawing.Size(80, 23); + this.strNullTermHiLabel.TabIndex = 39; + this.strNullTermHiLabel.Text = "Null term/hi:"; + this.strNullTermHiLabel.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // strNullTermTextBox + // + this.strNullTermTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.strNullTermTextBox.Location = new System.Drawing.Point(92, 230); + this.strNullTermTextBox.MaxLength = 8; + this.strNullTermTextBox.Name = "strNullTermTextBox"; + this.strNullTermTextBox.Size = new System.Drawing.Size(62, 20); + this.strNullTermTextBox.TabIndex = 38; + this.strNullTermTextBox.Text = ".placeho"; + this.strNullTermTextBox.TextChanged += new System.EventHandler(this.PseudoOpTextChanged); + // + // strNullTermLabel + // + this.strNullTermLabel.Location = new System.Drawing.Point(3, 232); + this.strNullTermLabel.Name = "strNullTermLabel"; + this.strNullTermLabel.Size = new System.Drawing.Size(85, 23); + this.strNullTermLabel.TabIndex = 37; + this.strNullTermLabel.Text = "Null term string:"; + this.strNullTermLabel.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // strLen8HiTextBox + // + this.strLen8HiTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.strLen8HiTextBox.Location = new System.Drawing.Point(244, 192); + this.strLen8HiTextBox.MaxLength = 8; + this.strLen8HiTextBox.Name = "strLen8HiTextBox"; + this.strLen8HiTextBox.Size = new System.Drawing.Size(62, 20); + this.strLen8HiTextBox.TabIndex = 32; + this.strLen8HiTextBox.Text = ".placeho"; + this.strLen8HiTextBox.TextChanged += new System.EventHandler(this.PseudoOpTextChanged); + // + // strLen8HiLabel + // + this.strLen8HiLabel.Location = new System.Drawing.Point(160, 194); + this.strLen8HiLabel.Name = "strLen8HiLabel"; + this.strLen8HiLabel.Size = new System.Drawing.Size(80, 23); + this.strLen8HiLabel.TabIndex = 31; + this.strLen8HiLabel.Text = "1-byte len/hi:"; + this.strLen8HiLabel.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // strLen8TextBox + // + this.strLen8TextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.strLen8TextBox.Location = new System.Drawing.Point(92, 192); + this.strLen8TextBox.MaxLength = 8; + this.strLen8TextBox.Name = "strLen8TextBox"; + this.strLen8TextBox.Size = new System.Drawing.Size(62, 20); + this.strLen8TextBox.TabIndex = 30; + this.strLen8TextBox.Text = ".placeho"; + this.strLen8TextBox.TextChanged += new System.EventHandler(this.PseudoOpTextChanged); + // + // strLen8Label + // + this.strLen8Label.Location = new System.Drawing.Point(7, 194); + this.strLen8Label.Name = "strLen8Label"; + this.strLen8Label.Size = new System.Drawing.Size(81, 23); + this.strLen8Label.TabIndex = 29; + this.strLen8Label.Text = "1-byte len:"; + this.strLen8Label.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // strGenericHiTextBox + // + this.strGenericHiTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.strGenericHiTextBox.Location = new System.Drawing.Point(244, 154); + this.strGenericHiTextBox.MaxLength = 8; + this.strGenericHiTextBox.Name = "strGenericHiTextBox"; + this.strGenericHiTextBox.Size = new System.Drawing.Size(62, 20); + this.strGenericHiTextBox.TabIndex = 24; + this.strGenericHiTextBox.Text = ".placeho"; + this.strGenericHiTextBox.TextChanged += new System.EventHandler(this.PseudoOpTextChanged); + // + // strGenericHiLabel + // + this.strGenericHiLabel.Location = new System.Drawing.Point(160, 156); + this.strGenericHiLabel.Name = "strGenericHiLabel"; + this.strGenericHiLabel.Size = new System.Drawing.Size(80, 23); + this.strGenericHiLabel.TabIndex = 23; + this.strGenericHiLabel.Text = "Generic/hi:"; + this.strGenericHiLabel.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // strGenericTextBox + // + this.strGenericTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.strGenericTextBox.Location = new System.Drawing.Point(92, 154); + this.strGenericTextBox.MaxLength = 8; + this.strGenericTextBox.Name = "strGenericTextBox"; + this.strGenericTextBox.Size = new System.Drawing.Size(62, 20); + this.strGenericTextBox.TabIndex = 22; + this.strGenericTextBox.Text = ".placeho"; + this.strGenericTextBox.TextChanged += new System.EventHandler(this.PseudoOpTextChanged); + // + // strGenericLabel + // + this.strGenericLabel.Location = new System.Drawing.Point(3, 156); + this.strGenericLabel.Name = "strGenericLabel"; + this.strGenericLabel.Size = new System.Drawing.Size(85, 23); + this.strGenericLabel.TabIndex = 21; + this.strGenericLabel.Text = "Generic string:"; + this.strGenericLabel.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // popExplanationLabel + // + this.popExplanationLabel.AutoSize = true; + this.popExplanationLabel.Location = new System.Drawing.Point(7, 7); + this.popExplanationLabel.Name = "popExplanationLabel"; + this.popExplanationLabel.Size = new System.Drawing.Size(541, 13); + this.popExplanationLabel.TabIndex = 0; + this.popExplanationLabel.Text = "Select pseudo-op names for display. This does not affect source code generation. " + + "Blank entries get default value."; + // + // denseTextBox + // + this.denseTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.denseTextBox.Location = new System.Drawing.Point(546, 116); + this.denseTextBox.MaxLength = 8; + this.denseTextBox.Name = "denseTextBox"; + this.denseTextBox.Size = new System.Drawing.Size(62, 20); + this.denseTextBox.TabIndex = 20; + this.denseTextBox.Text = ".placeho"; + this.denseTextBox.TextChanged += new System.EventHandler(this.PseudoOpTextChanged); + // + // denseLabel + // + this.denseLabel.Location = new System.Drawing.Point(460, 118); + this.denseLabel.Name = "denseLabel"; + this.denseLabel.Size = new System.Drawing.Size(82, 23); + this.denseLabel.TabIndex = 19; + this.denseLabel.Text = "Bulk data:"; + this.denseLabel.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // fillTextBox + // + this.fillTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.fillTextBox.Location = new System.Drawing.Point(395, 116); + this.fillTextBox.MaxLength = 8; + this.fillTextBox.Name = "fillTextBox"; + this.fillTextBox.Size = new System.Drawing.Size(62, 20); + this.fillTextBox.TabIndex = 18; + this.fillTextBox.Text = ".placeho"; + this.fillTextBox.TextChanged += new System.EventHandler(this.PseudoOpTextChanged); + // + // fillLabel + // + this.fillLabel.Location = new System.Drawing.Point(309, 118); + this.fillLabel.Name = "fillLabel"; + this.fillLabel.Size = new System.Drawing.Size(82, 23); + this.fillLabel.TabIndex = 17; + this.fillLabel.Text = "Fill:"; + this.fillLabel.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // defineBigData2TextBox + // + this.defineBigData2TextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.defineBigData2TextBox.Location = new System.Drawing.Point(92, 116); + this.defineBigData2TextBox.MaxLength = 8; + this.defineBigData2TextBox.Name = "defineBigData2TextBox"; + this.defineBigData2TextBox.Size = new System.Drawing.Size(62, 20); + this.defineBigData2TextBox.TabIndex = 16; + this.defineBigData2TextBox.Text = ".placeho"; + this.defineBigData2TextBox.TextChanged += new System.EventHandler(this.PseudoOpTextChanged); + // + // defineBigData2Label + // + this.defineBigData2Label.Location = new System.Drawing.Point(3, 113); + this.defineBigData2Label.Name = "defineBigData2Label"; + this.defineBigData2Label.Size = new System.Drawing.Size(85, 32); + this.defineBigData2Label.TabIndex = 15; + this.defineBigData2Label.Text = "Big-endian data, two bytes:"; + this.defineBigData2Label.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // defineData4TextBox + // + this.defineData4TextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.defineData4TextBox.Location = new System.Drawing.Point(546, 78); + this.defineData4TextBox.MaxLength = 8; + this.defineData4TextBox.Name = "defineData4TextBox"; + this.defineData4TextBox.Size = new System.Drawing.Size(62, 20); + this.defineData4TextBox.TabIndex = 14; + this.defineData4TextBox.Text = ".placeho"; + this.defineData4TextBox.TextChanged += new System.EventHandler(this.PseudoOpTextChanged); + // + // defineData4Label + // + this.defineData4Label.Location = new System.Drawing.Point(463, 80); + this.defineData4Label.Name = "defineData4Label"; + this.defineData4Label.Size = new System.Drawing.Size(79, 23); + this.defineData4Label.TabIndex = 13; + this.defineData4Label.Text = "Four bytes:"; + this.defineData4Label.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // defineData3TextBox + // + this.defineData3TextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.defineData3TextBox.Location = new System.Drawing.Point(395, 78); + this.defineData3TextBox.MaxLength = 8; + this.defineData3TextBox.Name = "defineData3TextBox"; + this.defineData3TextBox.Size = new System.Drawing.Size(62, 20); + this.defineData3TextBox.TabIndex = 12; + this.defineData3TextBox.Text = ".placeho"; + this.defineData3TextBox.TextChanged += new System.EventHandler(this.PseudoOpTextChanged); + // + // defineData3Label + // + this.defineData3Label.Location = new System.Drawing.Point(309, 80); + this.defineData3Label.Name = "defineData3Label"; + this.defineData3Label.Size = new System.Drawing.Size(82, 23); + this.defineData3Label.TabIndex = 11; + this.defineData3Label.Text = "Three bytes:"; + this.defineData3Label.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // defineData2TextBox + // + this.defineData2TextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.defineData2TextBox.Location = new System.Drawing.Point(244, 78); + this.defineData2TextBox.MaxLength = 8; + this.defineData2TextBox.Name = "defineData2TextBox"; + this.defineData2TextBox.Size = new System.Drawing.Size(62, 20); + this.defineData2TextBox.TabIndex = 10; + this.defineData2TextBox.Text = ".placeho"; + this.defineData2TextBox.TextChanged += new System.EventHandler(this.PseudoOpTextChanged); + // + // defineData2Label + // + this.defineData2Label.Location = new System.Drawing.Point(160, 80); + this.defineData2Label.Name = "defineData2Label"; + this.defineData2Label.Size = new System.Drawing.Size(80, 23); + this.defineData2Label.TabIndex = 9; + this.defineData2Label.Text = "Two bytes:"; + this.defineData2Label.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // regWidthDirectiveTextBox + // + this.regWidthDirectiveTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.regWidthDirectiveTextBox.Location = new System.Drawing.Point(395, 40); + this.regWidthDirectiveTextBox.MaxLength = 8; + this.regWidthDirectiveTextBox.Name = "regWidthDirectiveTextBox"; + this.regWidthDirectiveTextBox.Size = new System.Drawing.Size(62, 20); + this.regWidthDirectiveTextBox.TabIndex = 6; + this.regWidthDirectiveTextBox.Text = ".placeho"; + this.regWidthDirectiveTextBox.TextChanged += new System.EventHandler(this.PseudoOpTextChanged); + // + // regWidthDirectiveLabel + // + this.regWidthDirectiveLabel.Location = new System.Drawing.Point(312, 42); + this.regWidthDirectiveLabel.Name = "regWidthDirectiveLabel"; + this.regWidthDirectiveLabel.Size = new System.Drawing.Size(79, 23); + this.regWidthDirectiveLabel.TabIndex = 5; + this.regWidthDirectiveLabel.Text = "Reg width:"; + this.regWidthDirectiveLabel.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // orgDirectiveTextBox + // + this.orgDirectiveTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.orgDirectiveTextBox.Location = new System.Drawing.Point(244, 40); + this.orgDirectiveTextBox.MaxLength = 8; + this.orgDirectiveTextBox.Name = "orgDirectiveTextBox"; + this.orgDirectiveTextBox.Size = new System.Drawing.Size(62, 20); + this.orgDirectiveTextBox.TabIndex = 4; + this.orgDirectiveTextBox.Text = ".placeho"; + this.orgDirectiveTextBox.TextChanged += new System.EventHandler(this.PseudoOpTextChanged); + // + // orgDirectiveLabel + // + this.orgDirectiveLabel.Location = new System.Drawing.Point(160, 42); + this.orgDirectiveLabel.Name = "orgDirectiveLabel"; + this.orgDirectiveLabel.Size = new System.Drawing.Size(80, 23); + this.orgDirectiveLabel.TabIndex = 3; + this.orgDirectiveLabel.Text = "Origin:"; + this.orgDirectiveLabel.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // defineData1TextBox + // + this.defineData1TextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.defineData1TextBox.Location = new System.Drawing.Point(92, 78); + this.defineData1TextBox.MaxLength = 8; + this.defineData1TextBox.Name = "defineData1TextBox"; + this.defineData1TextBox.Size = new System.Drawing.Size(62, 20); + this.defineData1TextBox.TabIndex = 8; + this.defineData1TextBox.Text = ".placeho"; + this.defineData1TextBox.TextChanged += new System.EventHandler(this.PseudoOpTextChanged); + // + // defineData1Label + // + this.defineData1Label.Location = new System.Drawing.Point(3, 74); + this.defineData1Label.Name = "defineData1Label"; + this.defineData1Label.Size = new System.Drawing.Size(85, 32); + this.defineData1Label.TabIndex = 7; + this.defineData1Label.Text = "Little-endian data, one byte:"; + this.defineData1Label.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // equDirectiveTextBox + // + this.equDirectiveTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.equDirectiveTextBox.Location = new System.Drawing.Point(92, 40); + this.equDirectiveTextBox.MaxLength = 8; + this.equDirectiveTextBox.Name = "equDirectiveTextBox"; + this.equDirectiveTextBox.Size = new System.Drawing.Size(62, 20); + this.equDirectiveTextBox.TabIndex = 2; + this.equDirectiveTextBox.Text = ".placeho"; + this.equDirectiveTextBox.TextChanged += new System.EventHandler(this.PseudoOpTextChanged); + // + // equDirectiveLabel + // + this.equDirectiveLabel.Location = new System.Drawing.Point(3, 42); + this.equDirectiveLabel.Name = "equDirectiveLabel"; + this.equDirectiveLabel.Size = new System.Drawing.Size(85, 23); + this.equDirectiveLabel.TabIndex = 1; + this.equDirectiveLabel.Text = "Equate:"; + this.equDirectiveLabel.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // EditAppSettings + // + this.AcceptButton = this.okButton; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(624, 441); + this.Controls.Add(this.settingsTabControl); + this.Controls.Add(this.applyButton); + this.Controls.Add(this.okButton); + this.Controls.Add(this.cancelButton); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "EditAppSettings"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Edit Settings"; + this.Load += new System.EventHandler(this.EditAppSettings_Load); + this.settingsTabControl.ResumeLayout(false); + this.codeViewTabPage.ResumeLayout(false); + this.codeViewTabPage.PerformLayout(); + this.clipboardGroupBox.ResumeLayout(false); + this.clipboardGroupBox.PerformLayout(); + this.upperCaseGroupBox.ResumeLayout(false); + this.upperCaseGroupBox.PerformLayout(); + this.codeViewFontGroupBox.ResumeLayout(false); + this.codeViewFontGroupBox.PerformLayout(); + this.columnVisGroup.ResumeLayout(false); + this.asmConfigTabPage.ResumeLayout(false); + this.asmConfigTabPage.PerformLayout(); + this.displayFormatTabPage.ResumeLayout(false); + this.displayFormatTabPage.PerformLayout(); + this.quickDisplayFormatGroup.ResumeLayout(false); + this.operandWidthGroupBox.ResumeLayout(false); + this.operandWidthGroupBox.PerformLayout(); + this.pseudoOpTabPage.ResumeLayout(false); + this.pseudoOpTabPage.PerformLayout(); + this.quickPseudoSetGroup.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.Button applyButton; + private System.Windows.Forms.TabControl settingsTabControl; + private System.Windows.Forms.TabPage codeViewTabPage; + private System.Windows.Forms.TabPage asmConfigTabPage; + private System.Windows.Forms.TabPage pseudoOpTabPage; + private System.Windows.Forms.GroupBox columnVisGroup; + private System.Windows.Forms.Button showCol0; + private System.Windows.Forms.Button showCol8; + private System.Windows.Forms.Button showCol1; + private System.Windows.Forms.Button showCol7; + private System.Windows.Forms.Button showCol2; + private System.Windows.Forms.Button showCol6; + private System.Windows.Forms.Button showCol3; + private System.Windows.Forms.Button showCol5; + private System.Windows.Forms.Button showCol4; + private System.Windows.Forms.TextBox defineData1TextBox; + private System.Windows.Forms.Label defineData1Label; + private System.Windows.Forms.TextBox equDirectiveTextBox; + private System.Windows.Forms.Label equDirectiveLabel; + private System.Windows.Forms.TextBox denseTextBox; + private System.Windows.Forms.Label denseLabel; + private System.Windows.Forms.TextBox fillTextBox; + private System.Windows.Forms.Label fillLabel; + private System.Windows.Forms.TextBox defineBigData2TextBox; + private System.Windows.Forms.Label defineBigData2Label; + private System.Windows.Forms.TextBox defineData4TextBox; + private System.Windows.Forms.Label defineData4Label; + private System.Windows.Forms.TextBox defineData3TextBox; + private System.Windows.Forms.Label defineData3Label; + private System.Windows.Forms.TextBox defineData2TextBox; + private System.Windows.Forms.Label defineData2Label; + private System.Windows.Forms.TextBox regWidthDirectiveTextBox; + private System.Windows.Forms.Label regWidthDirectiveLabel; + private System.Windows.Forms.TextBox orgDirectiveTextBox; + private System.Windows.Forms.Label orgDirectiveLabel; + private System.Windows.Forms.Label popExplanationLabel; + private System.Windows.Forms.TextBox strLen8HiTextBox; + private System.Windows.Forms.Label strLen8HiLabel; + private System.Windows.Forms.TextBox strLen8TextBox; + private System.Windows.Forms.Label strLen8Label; + private System.Windows.Forms.TextBox strGenericHiTextBox; + private System.Windows.Forms.Label strGenericHiLabel; + private System.Windows.Forms.TextBox strGenericTextBox; + private System.Windows.Forms.Label strGenericLabel; + private System.Windows.Forms.TextBox strNullTermHiTextBox; + private System.Windows.Forms.Label strNullTermHiLabel; + private System.Windows.Forms.TextBox strNullTermTextBox; + private System.Windows.Forms.Label strNullTermLabel; + private System.Windows.Forms.TextBox strDciHiTextBox; + private System.Windows.Forms.Label strDciHiLabel; + private System.Windows.Forms.TextBox strDciTextBox; + private System.Windows.Forms.Label strDciLabel; + private System.Windows.Forms.TextBox strLen16HiTextBox; + private System.Windows.Forms.Label strLen16HiLabel; + private System.Windows.Forms.TextBox strLen16TextBox; + private System.Windows.Forms.Label strLen16Label; + private System.Windows.Forms.TextBox strReverseHiTextBox; + private System.Windows.Forms.Label strReverseHiLabel; + private System.Windows.Forms.TextBox strReverseTextBox; + private System.Windows.Forms.Label strReverseLabel; + private System.Windows.Forms.Label asmExplanationLabel; + private System.Windows.Forms.TextBox cc65PathTextBox; + private System.Windows.Forms.TextBox merlin32PathTextBox; + private System.Windows.Forms.Label asmMerln32Label; + private System.Windows.Forms.Label asmCc65Label; + private System.Windows.Forms.Button browseMerlin32Button; + private System.Windows.Forms.Button browseCc65Button; + private System.Windows.Forms.GroupBox codeViewFontGroupBox; + private System.Windows.Forms.Label currentFontLabel; + private System.Windows.Forms.Button selectFontButton; + private System.Windows.Forms.Label currentFontDisplayLabel; + private System.Windows.Forms.GroupBox quickPseudoSetGroup; + private System.Windows.Forms.Button quickPseudoMerlin32; + private System.Windows.Forms.Button quickPseudoCc65Button; + private System.Windows.Forms.Button quickPseudoDefaultButton; + private System.Windows.Forms.GroupBox upperCaseGroupBox; + private System.Windows.Forms.CheckBox upperOpcodeCheckBox; + private System.Windows.Forms.CheckBox upperHexCheckBox; + private System.Windows.Forms.Button upperAllUpperButton; + private System.Windows.Forms.Button upperAllLowerButton; + private System.Windows.Forms.CheckBox upperXYCheckBox; + private System.Windows.Forms.CheckBox upperSCheckBox; + private System.Windows.Forms.CheckBox upperACheckBox; + private System.Windows.Forms.CheckBox upperPseudoOpCheckBox; + private System.Windows.Forms.Button clearMerlin32Button; + private System.Windows.Forms.Button clearCc65Button; + private System.Windows.Forms.CheckBox disableLabelLocalizationCheckBox; + private System.Windows.Forms.CheckBox enableDebugCheckBox; + private System.Windows.Forms.CheckBox showAsmIdentCheckBox; + private System.Windows.Forms.TabPage displayFormatTabPage; + private System.Windows.Forms.CheckBox useMerlinExpressions; + private System.Windows.Forms.GroupBox operandWidthGroupBox; + private System.Windows.Forms.TextBox disambPrefix24TextBox; + private System.Windows.Forms.TextBox disambPrefix16TextBox; + private System.Windows.Forms.Label disambPrefix24Label; + private System.Windows.Forms.Label disambPrefix16Label; + private System.Windows.Forms.Label disambOperandPrefixLabel; + private System.Windows.Forms.Label disambSuffix24Label; + private System.Windows.Forms.Label disambSuffix16Label; + private System.Windows.Forms.Label disambOpcodeSuffixLabel; + private System.Windows.Forms.TextBox disambSuffix24TextBox; + private System.Windows.Forms.TextBox disambSuffix16TextBox; + private System.Windows.Forms.GroupBox quickDisplayFormatGroup; + private System.Windows.Forms.Button quickFmtMerlin32Button; + private System.Windows.Forms.Button quickFmtCc65Button; + private System.Windows.Forms.Button quickFmtDefaultButton; + private System.Windows.Forms.Label configAsmGenLabel; + private System.Windows.Forms.CheckBox longLabelNewLineCheckBox; + private System.Windows.Forms.Label fmtExplanationLabel; + private System.Windows.Forms.CheckBox showCycleCountsCheckBox; + private System.Windows.Forms.GroupBox clipboardGroupBox; + private System.Windows.Forms.ComboBox clipboardFormatComboBox; + private System.Windows.Forms.Label clipboardFormatLabel; + } +} \ No newline at end of file diff --git a/SourceGen/AppForms/EditAppSettings.cs b/SourceGen/AppForms/EditAppSettings.cs new file mode 100644 index 0000000..baf55a8 --- /dev/null +++ b/SourceGen/AppForms/EditAppSettings.cs @@ -0,0 +1,617 @@ +/* + * Copyright 2018 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.Drawing; +using System.Reflection; +using System.Text; +using System.Windows.Forms; + +namespace SourceGen.AppForms { + public partial class EditAppSettings : Form { + /// + /// Tab page enumeration. Numbers must match page indices in designer. + /// + public enum Tab { + Unknown = -1, + CodeList = 0, + Assembler = 1, + AsmFormat = 2, + PseudoOp = 3 + } + + /// + /// ProjectView reference. When the user hits Apply, the object's ApplyAppSettings + /// method will be invoked. + /// + private ProjectView mProjectView; + + /// + /// Copy of settings that we make changes to. On "Apply" or "OK", this is pushed + /// into the global settings object, and applied to the ProjectView. + /// + private AppSettings mSettings; + + /// + /// Dirty flag, set when anything in mSettings changes. Don't modify this directly. Use + /// the SetDirty() call so that the Apply button's enabled status gets updated. + /// + private bool mDirty; + + /// + /// Tab to show when dialog is first opened. + /// + private Tab mInitialTab; + + // Map buttons to column show/hide buttons. + private const int NUM_COLUMNS = ProjectView.CodeListColumnWidths.NUM_COLUMNS; + private string[] mColumnFormats = new string[NUM_COLUMNS]; + private Button[] mColButtons; + + // Map pseudo-op text entry fields to PseudoOpName properties. + private struct TextBoxPropertyMap { + public TextBox TextBox { get; private set; } + public PropertyInfo PropInfo { get; private set; } + + public TextBoxPropertyMap(TextBox textBox, string propName) { + TextBox = textBox; + PropInfo = typeof(PseudoOp.PseudoOpNames).GetProperty(propName); + } + } + private TextBoxPropertyMap[] mPseudoNameMap; + + + public EditAppSettings(ProjectView projectView, Tab initialTab) { + InitializeComponent(); + + mProjectView = projectView; + mInitialTab = initialTab; + + // Make a work copy, so we can discard changes if the user cancels out of the dialog. + projectView.SaveCodeListColumnWidths(); + mSettings = AppSettings.Global.GetCopy(); + + // Put buttons in an array. + mColButtons = new Button[] { + showCol0, showCol1, showCol2, showCol3, showCol4, + showCol5, showCol6, showCol7, showCol8 }; + Debug.Assert(NUM_COLUMNS == 9); + + // Extract formats from button labels. + for (int i = 0; i < NUM_COLUMNS; i++) { + mColButtons[i].Click += ColumnVisibilityButtonClick; + mColumnFormats[i] = mColButtons[i].Text; + } + + // Map text boxes to PseudoOpName fields. + mPseudoNameMap = new TextBoxPropertyMap[] { + new TextBoxPropertyMap(equDirectiveTextBox, "EquDirective"), + new TextBoxPropertyMap(orgDirectiveTextBox, "OrgDirective"), + new TextBoxPropertyMap(regWidthDirectiveTextBox, "RegWidthDirective"), + new TextBoxPropertyMap(defineData1TextBox, "DefineData1"), + new TextBoxPropertyMap(defineData2TextBox, "DefineData2"), + new TextBoxPropertyMap(defineData3TextBox, "DefineData3"), + new TextBoxPropertyMap(defineData4TextBox, "DefineData4"), + new TextBoxPropertyMap(defineBigData2TextBox, "DefineBigData2"), + new TextBoxPropertyMap(fillTextBox, "Fill"), + new TextBoxPropertyMap(denseTextBox, "Dense"), + new TextBoxPropertyMap(strGenericTextBox, "StrGeneric"), + new TextBoxPropertyMap(strGenericHiTextBox, "StrGenericHi"), + new TextBoxPropertyMap(strReverseTextBox, "StrReverse"), + new TextBoxPropertyMap(strReverseHiTextBox, "StrReverseHi"), + new TextBoxPropertyMap(strLen8TextBox, "StrLen8"), + new TextBoxPropertyMap(strLen8HiTextBox, "StrLen8Hi"), + new TextBoxPropertyMap(strLen16TextBox, "StrLen16"), + new TextBoxPropertyMap(strLen16HiTextBox, "StrLen16Hi"), + new TextBoxPropertyMap(strNullTermTextBox, "StrNullTerm"), + new TextBoxPropertyMap(strNullTermHiTextBox, "StrNullTermHi"), + new TextBoxPropertyMap(strDciTextBox, "StrDci"), + new TextBoxPropertyMap(strDciHiTextBox, "StrDciHi"), + }; + } + + private void EditAppSettings_Load(object sender, EventArgs e) { + // Column widths. We called SaveCodeListColumnWidths() earlier, so this + // should always be a valid serialized string. + string widthStr = mSettings.GetString(AppSettings.CDLV_COL_WIDTHS, null); + Debug.Assert(!string.IsNullOrEmpty(widthStr)); + ProjectView.CodeListColumnWidths widths = + ProjectView.CodeListColumnWidths.Deserialize(widthStr); + Debug.Assert(widths != null); + for (int i = 0; i < NUM_COLUMNS; i++) { + SetShowHideButton(i, widths.Width[i]); + } + + // Display localized font string. + FontConverter cvt = new FontConverter(); + currentFontDisplayLabel.Text = cvt.ConvertToString(mProjectView.CodeListViewFont); + + // Upper-case formatting. + upperHexCheckBox.Checked = mSettings.GetBool(AppSettings.FMT_UPPER_HEX_DIGITS, false); + upperOpcodeCheckBox.Checked = mSettings.GetBool( + AppSettings.FMT_UPPER_OP_MNEMONIC, false); + upperPseudoOpCheckBox.Checked = mSettings.GetBool( + AppSettings.FMT_UPPER_PSEUDO_OP_MNEMONIC, false); + upperACheckBox.Checked = mSettings.GetBool(AppSettings.FMT_UPPER_OPERAND_A, false); + upperSCheckBox.Checked = mSettings.GetBool(AppSettings.FMT_UPPER_OPERAND_S, false); + upperXYCheckBox.Checked = mSettings.GetBool(AppSettings.FMT_UPPER_OPERAND_XY, false); + + int clipIndex = mSettings.GetInt(AppSettings.CLIP_LINE_FORMAT, 0); + if (clipIndex >= 0 && clipIndex < clipboardFormatComboBox.Items.Count) { + clipboardFormatComboBox.SelectedIndex = clipIndex; + } + + enableDebugCheckBox.Checked = mSettings.GetBool(AppSettings.DEBUG_MENU_ENABLED, false); + + // Assemblers. + cc65PathTextBox.Text = + mSettings.GetString(AppSettings.ASM_CC65_EXECUTABLE, string.Empty); + merlin32PathTextBox.Text = + mSettings.GetString(AppSettings.ASM_MERLIN32_EXECUTABLE, string.Empty); + showAsmIdentCheckBox.Checked = + mSettings.GetBool(AppSettings.SRCGEN_ADD_IDENT_COMMENT, false); + disableLabelLocalizationCheckBox.Checked = + mSettings.GetBool(AppSettings.SRCGEN_DISABLE_LABEL_LOCALIZATION, false); + longLabelNewLineCheckBox.Checked = + mSettings.GetBool(AppSettings.SRCGEN_LONG_LABEL_NEW_LINE, false); + showCycleCountsCheckBox.Checked = + mSettings.GetBool(AppSettings.SRCGEN_SHOW_CYCLE_COUNTS, false); + + // Pseudo ops. + string opStrCereal = mSettings.GetString(AppSettings.FMT_PSEUDO_OP_NAMES, null); + if (!string.IsNullOrEmpty(opStrCereal)) { + PseudoOp.PseudoOpNames opNames = PseudoOp.PseudoOpNames.Deserialize(opStrCereal); + ImportPseudoOpNames(opNames); + } else { + // no data available, populate with blanks + //PseudoOp.PseudoOpNames opNames = PseudoOp.sDefaultPseudoOpNames; + ImportPseudoOpNames(new PseudoOp.PseudoOpNames()); + } + + PopulateWidthDisamSettings(); + + string exprMode = mSettings.GetString(AppSettings.FMT_EXPRESSION_MODE, string.Empty); + useMerlinExpressions.Checked = + (Asm65.Formatter.FormatConfig.ParseExpressionMode(exprMode) == + Asm65.Formatter.FormatConfig.ExpressionMode.Merlin); + + if (mInitialTab != Tab.Unknown) { + settingsTabControl.SelectTab((int)mInitialTab); + } + + mDirty = false; + UpdateControls(); + } + + /// + /// Updates controls. + /// + private void UpdateControls() { + applyButton.Enabled = mDirty; + + clearMerlin32Button.Enabled = !string.IsNullOrEmpty(merlin32PathTextBox.Text); + clearCc65Button.Enabled = !string.IsNullOrEmpty(cc65PathTextBox.Text); + } + + /// + /// Sets the dirty flag and updates the controls. + /// + /// New value for dirty flag. + private void SetDirty(bool dirty) { + mDirty = dirty; + UpdateControls(); + } + + private void okButton_Click(object sender, EventArgs e) { + ApplySettings(); + } + + private void applyButton_Click(object sender, EventArgs e) { + ApplySettings(); + } + + private void ApplySettings() { + PseudoOp.PseudoOpNames opNames = ExportPseudoOpNames(); + string pseudoCereal = opNames.Serialize(); + mSettings.SetString(AppSettings.FMT_PSEUDO_OP_NAMES, pseudoCereal); + + mProjectView.SetAppSettings(mSettings); + AsmGen.AssemblerVersionCache.QueryVersions(); + SetDirty(false); + } + + + #region Code View + + /// + /// Updates the text on a show/hide column button. + /// + /// Column index. + /// New width. + private void SetShowHideButton(int index, int width) { + Button button = mColButtons[index]; + string fmt = mColumnFormats[index]; + string show = Properties.Resources.SHOW_COL; + string hide = Properties.Resources.HIDE_COL; + button.Text = string.Format(fmt, (width == 0) ? show : hide); + } + + /// + /// Handler for all show/hide column buttons. + /// + /// Identifies the button that was clicked. + /// Stuff. + private void ColumnVisibilityButtonClick(object sender, EventArgs e) { + int index = -1; + for (int i = 0; i < mColButtons.Length; i++) { + if (sender == mColButtons[i]) { + index = i; + break; + } + } + Debug.Assert(index != -1); + + string widthStr = mSettings.GetString(AppSettings.CDLV_COL_WIDTHS, null); + Debug.Assert(!string.IsNullOrEmpty(widthStr)); + ProjectView.CodeListColumnWidths widths = + ProjectView.CodeListColumnWidths.Deserialize(widthStr); + if (widths.Width[index] == 0) { + // Expand to default width. The default width changes when the font + // changes, so it's best to just reacquire the default width set as needed. + ProjectView.CodeListColumnWidths defaultWidths = + mProjectView.GetDefaultCodeListColumnWidths(); + widths.Width[index] = defaultWidths.Width[index]; + } else { + widths.Width[index] = 0; + } + widthStr = widths.Serialize(); + mSettings.SetString(AppSettings.CDLV_COL_WIDTHS, widthStr); + SetShowHideButton(index, widths.Width[index]); + + SetDirty(true); + } + + private void selectFontButton_Click(object sender, EventArgs e) { + FontDialog dlg = new FontDialog(); + dlg.Font = mProjectView.CodeListViewFont; + dlg.ShowEffects = false; + Debug.WriteLine("Showing font dialog..."); + if (dlg.ShowDialog() != DialogResult.Cancel) { + FontConverter cvt = new FontConverter(); + // Store invariant string, display localized string. + mSettings.SetString(AppSettings.CDLV_FONT, cvt.ConvertToInvariantString(dlg.Font)); + currentFontDisplayLabel.Text = cvt.ConvertToString(dlg.Font); + SetDirty(true); + } + Debug.WriteLine("Font dialog done..."); + dlg.Dispose(); + } + + private void upperHexCheckBox_CheckedChanged(object sender, EventArgs e) { + mSettings.SetBool(AppSettings.FMT_UPPER_HEX_DIGITS, upperHexCheckBox.Checked); + SetDirty(true); + } + private void upperOpcodeCheckBox_CheckedChanged(object sender, EventArgs e) { + mSettings.SetBool(AppSettings.FMT_UPPER_OP_MNEMONIC, upperOpcodeCheckBox.Checked); + SetDirty(true); + } + private void upperPseudoOpCheckBox_CheckedChanged(object sender, EventArgs e) { + mSettings.SetBool(AppSettings.FMT_UPPER_PSEUDO_OP_MNEMONIC, + upperPseudoOpCheckBox.Checked); + SetDirty(true); + } + private void upperACheckBox_CheckedChanged(object sender, EventArgs e) { + mSettings.SetBool(AppSettings.FMT_UPPER_OPERAND_A, upperACheckBox.Checked); + SetDirty(true); + } + private void upperSCheckBox_CheckedChanged(object sender, EventArgs e) { + mSettings.SetBool(AppSettings.FMT_UPPER_OPERAND_S, upperSCheckBox.Checked); + SetDirty(true); + } + private void upperXYCheckBox_CheckedChanged(object sender, EventArgs e) { + mSettings.SetBool(AppSettings.FMT_UPPER_OPERAND_XY, upperXYCheckBox.Checked); + SetDirty(true); + } + private void upperAllLowerButton_Click(object sender, EventArgs e) { + upperHexCheckBox.Checked = + upperOpcodeCheckBox.Checked = + upperPseudoOpCheckBox.Checked = + upperACheckBox.Checked = + upperSCheckBox.Checked = + upperXYCheckBox.Checked = false; + } + private void upperAllUpperButton_Click(object sender, EventArgs e) { + upperHexCheckBox.Checked = + upperOpcodeCheckBox.Checked = + upperPseudoOpCheckBox.Checked = + upperACheckBox.Checked = + upperSCheckBox.Checked = + upperXYCheckBox.Checked = true; + } + + private void clipboardFormatComboBox_SelectedIndexChanged(object sender, EventArgs e) { + mSettings.SetInt(AppSettings.CLIP_LINE_FORMAT, clipboardFormatComboBox.SelectedIndex); + SetDirty(true); + } + + private void enableDebugCheckBox_CheckedChanged(object sender, EventArgs e) { + mSettings.SetBool(AppSettings.DEBUG_MENU_ENABLED, enableDebugCheckBox.Checked); + SetDirty(true); + } + + #endregion Code View + + + #region Asm Config + + private void browseCc65Button_Click(object sender, EventArgs e) { + string pathName = BrowseForExecutable("cc65 CL", "cl65.exe"); + if (pathName != null) { + cc65PathTextBox.Text = pathName; + mSettings.SetString(AppSettings.ASM_CC65_EXECUTABLE, pathName); + } + } + + private void cc65PathTextBox_TextChanged(object sender, EventArgs e) { + mSettings.SetString(AppSettings.ASM_CC65_EXECUTABLE, cc65PathTextBox.Text); + SetDirty(true); + } + + private void clearCc65Button_Click(object sender, EventArgs e) { + cc65PathTextBox.Text = string.Empty; + mSettings.SetString(AppSettings.ASM_CC65_EXECUTABLE, null); + SetDirty(true); + } + + private void browseMerlin32Button_Click(object sender, EventArgs e) { + string pathName = BrowseForExecutable("Merlin Assembler", "Merlin32.exe"); + if (pathName != null) { + merlin32PathTextBox.Text = pathName; + mSettings.SetString(AppSettings.ASM_MERLIN32_EXECUTABLE, pathName); + } + } + + private void clearMerlin32Button_Click(object sender, EventArgs e) { + merlin32PathTextBox.Text = string.Empty; + mSettings.SetString(AppSettings.ASM_MERLIN32_EXECUTABLE, null); + SetDirty(true); + } + + private void merlin32PathTextBox_TextChanged(object sender, EventArgs e) { + mSettings.SetString(AppSettings.ASM_MERLIN32_EXECUTABLE, merlin32PathTextBox.Text); + SetDirty(true); + } + + /// + /// Creates a file dialog to search for a specific executable. + /// + /// Human-readable filter string for UI. + /// Filename of executable. + /// Path of executable, or null if dialog was canceled. + private string BrowseForExecutable(string prefix, string name) { + string pathName = null; + + OpenFileDialog dlg = new OpenFileDialog(); + dlg.FileName = name; + dlg.Filter = prefix + "|" + name; + dlg.RestoreDirectory = true; + if (dlg.ShowDialog() != DialogResult.Cancel) { + pathName = dlg.FileName; + SetDirty(true); + } + dlg.Dispose(); + + return pathName; + } + + private void showAsmIdentCheckBox_CheckedChanged(object sender, EventArgs e) { + mSettings.SetBool(AppSettings.SRCGEN_ADD_IDENT_COMMENT, showAsmIdentCheckBox.Checked); + SetDirty(true); + } + + private void disableLabelLocalizationCheckBox_CheckedChanged(object sender, EventArgs e) { + mSettings.SetBool(AppSettings.SRCGEN_DISABLE_LABEL_LOCALIZATION, + disableLabelLocalizationCheckBox.Checked); + SetDirty(true); + } + + private void longLabelNewLineCheckBox_CheckedChanged(object sender, EventArgs e) { + mSettings.SetBool(AppSettings.SRCGEN_LONG_LABEL_NEW_LINE, + longLabelNewLineCheckBox.Checked); + SetDirty(true); + } + + private void showCycleCountsCheckBox_CheckedChanged(object sender, EventArgs e) { + mSettings.SetBool(AppSettings.SRCGEN_SHOW_CYCLE_COUNTS, + showCycleCountsCheckBox.Checked); + SetDirty(true); + } + + #endregion Asm Config + + + #region Display Format + + /// + /// Populates the width disambiguation text boxes. + /// + private void PopulateWidthDisamSettings() { + // Operand width disambiguation. This is a little tricky -- we have to query all + // settings then set all controls, or the field-updated callback may interfere + // with us by changing AppSettings. + string opcSuffixAbs = mSettings.GetString(AppSettings.FMT_OPCODE_SUFFIX_ABS, + string.Empty); + string opcSuffixLong = mSettings.GetString(AppSettings.FMT_OPCODE_SUFFIX_LONG, + string.Empty); + string opPrefixAbs = mSettings.GetString(AppSettings.FMT_OPERAND_PREFIX_ABS, + string.Empty); + string opPrefixLong = mSettings.GetString(AppSettings.FMT_OPERAND_PREFIX_LONG, + string.Empty); + + disambSuffix16TextBox.Text = opcSuffixAbs; + disambSuffix24TextBox.Text = opcSuffixLong; + disambPrefix16TextBox.Text = opPrefixAbs; + disambPrefix24TextBox.Text = opPrefixLong; + } + + /// + /// Sets all of the width disambiguation settings. Used for the quick-set buttons. + /// + private void SetWidthDisamSettings(string opcodeSuffixAbs, string opcodeSuffixLong, + string operandPrefixAbs, string operandPrefixLong) { + mSettings.SetString(AppSettings.FMT_OPCODE_SUFFIX_ABS, opcodeSuffixAbs); + mSettings.SetString(AppSettings.FMT_OPCODE_SUFFIX_LONG, opcodeSuffixLong); + mSettings.SetString(AppSettings.FMT_OPERAND_PREFIX_ABS, operandPrefixAbs); + mSettings.SetString(AppSettings.FMT_OPERAND_PREFIX_LONG, operandPrefixLong); + PopulateWidthDisamSettings(); + } + + // Called when text is typed. + private void WidthDisamControlChanged(object sender, EventArgs e) { + ExportWidthDisamSettings(); + } + + /// + /// Exports the current state of the width controls to the settings object. + /// + private void ExportWidthDisamSettings() { + mSettings.SetString(AppSettings.FMT_OPCODE_SUFFIX_ABS, disambSuffix16TextBox.Text); + mSettings.SetString(AppSettings.FMT_OPCODE_SUFFIX_LONG, disambSuffix24TextBox.Text); + mSettings.SetString(AppSettings.FMT_OPERAND_PREFIX_ABS, disambPrefix16TextBox.Text); + mSettings.SetString(AppSettings.FMT_OPERAND_PREFIX_LONG, disambPrefix24TextBox.Text); + SetDirty(true); + + //Debug.WriteLine("disam: '" + + // mSettings.GetString(AppSettings.FMT_OPCODE_SUFFIX_ABS, string.Empty) + "' '" + + // mSettings.GetString(AppSettings.FMT_OPCODE_SUFFIX_LONG, string.Empty) + "' '" + + // mSettings.GetString(AppSettings.FMT_OPERAND_PREFIX_ABS, string.Empty) + "' '" + + // mSettings.GetString(AppSettings.FMT_OPERAND_PREFIX_LONG, string.Empty) + "'"); + } + + private void shiftAfterAdjustCheckBox_CheckedChanged(object sender, EventArgs e) { + string mode = useMerlinExpressions.Checked ? + Asm65.Formatter.FormatConfig.ExpressionMode.Merlin.ToString() : + Asm65.Formatter.FormatConfig.ExpressionMode.Simple.ToString(); + mSettings.SetString(AppSettings.FMT_EXPRESSION_MODE, mode); + SetDirty(true); + } + + private void quickFmtDefaultButton_Click(object sender, EventArgs e) { + SetWidthDisamSettings(null, "l", "a:", "f:"); + useMerlinExpressions.Checked = false; + // dirty flag set by change callbacks + } + + private void quickFmtCc65Button_Click(object sender, EventArgs e) { + SetWidthDisamSettings(null, null, "a:", "f:"); + useMerlinExpressions.Checked = false; + // dirty flag set by change callbacks + } + + private void quickFmtMerlin32Button_Click(object sender, EventArgs e) { + SetWidthDisamSettings(":", "l", null, null); + useMerlinExpressions.Checked = true; + // dirty flag set by change callbacks + } + + #endregion Display Format + + + #region Pseudo-Op + + /// + /// Imports values from PseudoOpNames struct into text fields. + /// + private void ImportPseudoOpNames(PseudoOp.PseudoOpNames opNames) { + for (int i = 0; i < mPseudoNameMap.Length; i++) { + string str = (string)mPseudoNameMap[i].PropInfo.GetValue(opNames); + mPseudoNameMap[i].TextBox.Text = (str == null) ? string.Empty : str; + } + } + + /// + /// Exports values from text fields to a PseudoOpNames object. + /// + private PseudoOp.PseudoOpNames ExportPseudoOpNames() { + PseudoOp.PseudoOpNames opNames = new PseudoOp.PseudoOpNames(); + for (int i = 0; i < mPseudoNameMap.Length; i++) { + // NOTE: PseudoOpNames must be a class (not a struct) or this will fail. + // SetValue() would be invoked on a boxed copy that is discarded afterward. + mPseudoNameMap[i].PropInfo.SetValue(opNames, mPseudoNameMap[i].TextBox.Text); + } + return opNames; + } + + // Invoked when text is changed in any pseudo-op text box. + private void PseudoOpTextChanged(object sender, EventArgs e) { + // Just set the dirty flag. The (somewhat expensive) export will happen + // on Apply/OK. + SetDirty(true); + } + + private void quickPseudoDefaultButton_Click(object sender, EventArgs e) { + ImportPseudoOpNames(new PseudoOp.PseudoOpNames()); + } + + private void quickPseudoCc65Button_Click(object sender, EventArgs e) { + ImportPseudoOpNames(new PseudoOp.PseudoOpNames() { + EquDirective = "=", + OrgDirective = ".org", + DefineData1 = ".byte", + DefineData2 = ".word", + DefineData3 = ".faraddr", + DefineData4 = ".dword", + DefineBigData2 = ".dbyt", + Fill = ".res", + StrGeneric = ".byte", + StrNullTerm = ".asciiz", + }); + } + + private void quickPseudoMerlin32_Click(object sender, EventArgs e) { + // Note this doesn't quite match up with the Merlin generator, which uses + // the same pseudo-op for low/high ASCII but different string delimiters. We + // don't change the delimiters for the display list, so we want to tweak the + // opcode slightly. + //char hiAscii = '\u21e1'; + char hiAscii = '\u2191'; + ImportPseudoOpNames(new PseudoOp.PseudoOpNames() { + EquDirective = "equ", + OrgDirective = "org", + DefineData1 = "dfb", + DefineData2 = "dw", + DefineData3 = "adr", + DefineData4 = "adrl", + DefineBigData2 = "ddb", + Fill = "ds", + Dense = "hex", + StrGeneric = "asc", + StrGenericHi = "asc" + hiAscii, + StrReverse = "rev", + StrReverseHi = "rev" + hiAscii, + StrLen8 = "str", + StrLen8Hi = "str" + hiAscii, + StrLen16 = "strl", + StrLen16Hi = "strl" + hiAscii, + StrDci = "dci", + StrDciHi = "dci" + hiAscii, + }); + } + + #endregion Pseudo-Op + } +} diff --git a/SourceGen/AppForms/EditAppSettings.resx b/SourceGen/AppForms/EditAppSettings.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/SourceGen/AppForms/EditAppSettings.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/SourceGen/AppForms/EditComment.Designer.cs b/SourceGen/AppForms/EditComment.Designer.cs new file mode 100644 index 0000000..3ac1317 --- /dev/null +++ b/SourceGen/AppForms/EditComment.Designer.cs @@ -0,0 +1,153 @@ +/* + * Copyright 2018 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. + */ +namespace SourceGen.AppForms { + partial class EditComment { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.cancelButton = new System.Windows.Forms.Button(); + this.okButton = new System.Windows.Forms.Button(); + this.instructionLabel = new System.Windows.Forms.Label(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.asciiOnlyLabel = new System.Windows.Forms.Label(); + this.maxLengthLabel = new System.Windows.Forms.Label(); + this.numCharsLabel = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(426, 105); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 6; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + // + // okButton + // + this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.okButton.Location = new System.Drawing.Point(345, 105); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(75, 23); + this.okButton.TabIndex = 5; + this.okButton.Text = "OK"; + this.okButton.UseVisualStyleBackColor = true; + this.okButton.Click += new System.EventHandler(this.okButton_Click); + // + // instructionLabel + // + this.instructionLabel.AutoSize = true; + this.instructionLabel.Location = new System.Drawing.Point(13, 13); + this.instructionLabel.Name = "instructionLabel"; + this.instructionLabel.Size = new System.Drawing.Size(81, 13); + this.instructionLabel.TabIndex = 0; + this.instructionLabel.Text = "Enter comment:"; + // + // textBox1 + // + this.textBox1.Location = new System.Drawing.Point(13, 30); + this.textBox1.Name = "textBox1"; + this.textBox1.Size = new System.Drawing.Size(488, 20); + this.textBox1.TabIndex = 1; + this.textBox1.TextChanged += new System.EventHandler(this.textBox1_TextChanged); + // + // asciiOnlyLabel + // + this.asciiOnlyLabel.AutoSize = true; + this.asciiOnlyLabel.Location = new System.Drawing.Point(13, 57); + this.asciiOnlyLabel.Name = "asciiOnlyLabel"; + this.asciiOnlyLabel.Size = new System.Drawing.Size(145, 13); + this.asciiOnlyLabel.TabIndex = 2; + this.asciiOnlyLabel.Text = "• ASCII-only is recommended"; + // + // maxLengthLabel + // + this.maxLengthLabel.AutoSize = true; + this.maxLengthLabel.Location = new System.Drawing.Point(13, 74); + this.maxLengthLabel.Name = "maxLengthLabel"; + this.maxLengthLabel.Size = new System.Drawing.Size(281, 13); + this.maxLengthLabel.TabIndex = 3; + this.maxLengthLabel.Text = "• Limit to 52 or fewer characters for nice 80-column output"; + // + // numCharsLabel + // + this.numCharsLabel.Location = new System.Drawing.Point(386, 53); + this.numCharsLabel.Name = "numCharsLabel"; + this.numCharsLabel.Size = new System.Drawing.Size(115, 23); + this.numCharsLabel.TabIndex = 4; + this.numCharsLabel.Text = "{0} characters"; + this.numCharsLabel.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // EditComment + // + this.AcceptButton = this.okButton; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(513, 140); + this.Controls.Add(this.numCharsLabel); + this.Controls.Add(this.maxLengthLabel); + this.Controls.Add(this.asciiOnlyLabel); + this.Controls.Add(this.textBox1); + this.Controls.Add(this.instructionLabel); + this.Controls.Add(this.okButton); + this.Controls.Add(this.cancelButton); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "EditComment"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Edit Comment"; + this.Load += new System.EventHandler(this.EditComment_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.Label instructionLabel; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.Label asciiOnlyLabel; + private System.Windows.Forms.Label maxLengthLabel; + private System.Windows.Forms.Label numCharsLabel; + } +} \ No newline at end of file diff --git a/SourceGen/AppForms/EditComment.cs b/SourceGen/AppForms/EditComment.cs new file mode 100644 index 0000000..dc50bf2 --- /dev/null +++ b/SourceGen/AppForms/EditComment.cs @@ -0,0 +1,72 @@ +/* + * Copyright 2018 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.Diagnostics; +using System.Drawing; +using System.Windows.Forms; + +namespace SourceGen.AppForms { + public partial class EditComment : Form { + /// + /// Comment string being edited. + /// + public string Comment { get; set; } + + private string mNumCharsFormat; + + private Color mDefaultLabelColor; + + private const int RECOMMENDED_MAX_LENGTH = 52; + + + public EditComment() { + InitializeComponent(); + } + + private void EditComment_Load(object sender, EventArgs e) { + mDefaultLabelColor = asciiOnlyLabel.ForeColor; + + // Extract the format string from the label. + mNumCharsFormat = numCharsLabel.Text; + + textBox1.Text = Comment; + UpdateLengthLabel(); + } + + private void UpdateLengthLabel() { + numCharsLabel.Text = string.Format(mNumCharsFormat, textBox1.Text.Length); + } + + private void textBox1_TextChanged(object sender, EventArgs e) { + UpdateLengthLabel(); + + if (!CommonUtil.TextUtil.IsPrintableAscii(textBox1.Text)) { + asciiOnlyLabel.ForeColor = Color.Red; + } else { + asciiOnlyLabel.ForeColor = mDefaultLabelColor; + } + if (textBox1.Text.Length > RECOMMENDED_MAX_LENGTH) { + maxLengthLabel.ForeColor = Color.Red; + } else { + maxLengthLabel.ForeColor = mDefaultLabelColor; + } + } + + private void okButton_Click(object sender, EventArgs e) { + Comment = textBox1.Text; + } + } +} diff --git a/SourceGen/AppForms/EditComment.resx b/SourceGen/AppForms/EditComment.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/SourceGen/AppForms/EditComment.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/SourceGen/AppForms/EditData.Designer.cs b/SourceGen/AppForms/EditData.Designer.cs new file mode 100644 index 0000000..760831d --- /dev/null +++ b/SourceGen/AppForms/EditData.Designer.cs @@ -0,0 +1,559 @@ +/* + * Copyright 2018 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. + */ +namespace SourceGen.AppForms { + partial class EditData { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.cancelButton = new System.Windows.Forms.Button(); + this.okButton = new System.Windows.Forms.Button(); + this.selectFormatLabel = new System.Windows.Forms.Label(); + this.rawDataSectionLabel = new System.Windows.Forms.Label(); + this.radioSingleBytes = new System.Windows.Forms.RadioButton(); + this.radio16BitBig = new System.Windows.Forms.RadioButton(); + this.radio16BitLittle = new System.Windows.Forms.RadioButton(); + this.radio24BitLittle = new System.Windows.Forms.RadioButton(); + this.radio32BitLittle = new System.Windows.Forms.RadioButton(); + this.radioSimpleDataBinary = new System.Windows.Forms.RadioButton(); + this.radioSimpleDataDecimal = new System.Windows.Forms.RadioButton(); + this.radioSimpleDataHex = new System.Windows.Forms.RadioButton(); + this.radioDenseHex = new System.Windows.Forms.RadioButton(); + this.radioFill = new System.Windows.Forms.RadioButton(); + this.horizontalLine1 = new System.Windows.Forms.Label(); + this.horizontalLine2 = new System.Windows.Forms.Label(); + this.stringSectionLabel = new System.Windows.Forms.Label(); + this.horizontalLine3 = new System.Windows.Forms.Label(); + this.radioStringMixed = new System.Windows.Forms.RadioButton(); + this.radioStringMixedReverse = new System.Windows.Forms.RadioButton(); + this.radioStringNullTerm = new System.Windows.Forms.RadioButton(); + this.radioStringLen8 = new System.Windows.Forms.RadioButton(); + this.radioStringLen16 = new System.Windows.Forms.RadioButton(); + this.radioStringDci = new System.Windows.Forms.RadioButton(); + this.symbolEntryTextBox = new System.Windows.Forms.TextBox(); + this.symbolPartPanel = new System.Windows.Forms.Panel(); + this.radioSymbolPartBank = new System.Windows.Forms.RadioButton(); + this.radioSymbolPartHigh = new System.Windows.Forms.RadioButton(); + this.radioSymbolPartLow = new System.Windows.Forms.RadioButton(); + this.radioSimpleDataAscii = new System.Windows.Forms.RadioButton(); + this.radioDefaultFormat = new System.Windows.Forms.RadioButton(); + this.simpleDisplayAsGroupBox = new System.Windows.Forms.GroupBox(); + this.radioSimpleDataAddress = new System.Windows.Forms.RadioButton(); + this.radioSimpleDataSymbolic = new System.Windows.Forms.RadioButton(); + this.label1 = new System.Windows.Forms.Label(); + this.symbolPartPanel.SuspendLayout(); + this.simpleDisplayAsGroupBox.SuspendLayout(); + this.SuspendLayout(); + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(438, 461); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 23; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + // + // okButton + // + this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.okButton.Location = new System.Drawing.Point(357, 461); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(75, 23); + this.okButton.TabIndex = 22; + this.okButton.Text = "OK"; + this.okButton.UseVisualStyleBackColor = true; + this.okButton.Click += new System.EventHandler(this.okButton_Click); + // + // selectFormatLabel + // + this.selectFormatLabel.AutoSize = true; + this.selectFormatLabel.Location = new System.Drawing.Point(12, 9); + this.selectFormatLabel.Name = "selectFormatLabel"; + this.selectFormatLabel.Size = new System.Drawing.Size(253, 13); + this.selectFormatLabel.TabIndex = 0; + this.selectFormatLabel.Text = "Select data format ({0} bytes selected in {1} groups):"; + // + // rawDataSectionLabel + // + this.rawDataSectionLabel.AutoSize = true; + this.rawDataSectionLabel.Location = new System.Drawing.Point(12, 63); + this.rawDataSectionLabel.Name = "rawDataSectionLabel"; + this.rawDataSectionLabel.Size = new System.Drawing.Size(64, 13); + this.rawDataSectionLabel.TabIndex = 2; + this.rawDataSectionLabel.Text = "Simple Data"; + // + // radioSingleBytes + // + this.radioSingleBytes.AutoSize = true; + this.radioSingleBytes.Location = new System.Drawing.Point(14, 89); + this.radioSingleBytes.Name = "radioSingleBytes"; + this.radioSingleBytes.Size = new System.Drawing.Size(82, 17); + this.radioSingleBytes.TabIndex = 4; + this.radioSingleBytes.TabStop = true; + this.radioSingleBytes.Text = "Single &bytes"; + this.radioSingleBytes.UseVisualStyleBackColor = true; + this.radioSingleBytes.CheckedChanged += new System.EventHandler(this.MainGroup_CheckedChanged); + // + // radio16BitBig + // + this.radio16BitBig.AutoSize = true; + this.radio16BitBig.Location = new System.Drawing.Point(14, 135); + this.radio16BitBig.Name = "radio16BitBig"; + this.radio16BitBig.Size = new System.Drawing.Size(137, 17); + this.radio16BitBig.TabIndex = 6; + this.radio16BitBig.TabStop = true; + this.radio16BitBig.Text = "16-bit words, big-endian"; + this.radio16BitBig.UseVisualStyleBackColor = true; + this.radio16BitBig.CheckedChanged += new System.EventHandler(this.MainGroup_CheckedChanged); + // + // radio16BitLittle + // + this.radio16BitLittle.AutoSize = true; + this.radio16BitLittle.Location = new System.Drawing.Point(14, 112); + this.radio16BitLittle.Name = "radio16BitLittle"; + this.radio16BitLittle.Size = new System.Drawing.Size(141, 17); + this.radio16BitLittle.TabIndex = 5; + this.radio16BitLittle.TabStop = true; + this.radio16BitLittle.Text = "16-bit words, little-endian"; + this.radio16BitLittle.UseVisualStyleBackColor = true; + this.radio16BitLittle.CheckedChanged += new System.EventHandler(this.MainGroup_CheckedChanged); + // + // radio24BitLittle + // + this.radio24BitLittle.AutoSize = true; + this.radio24BitLittle.Location = new System.Drawing.Point(14, 158); + this.radio24BitLittle.Name = "radio24BitLittle"; + this.radio24BitLittle.Size = new System.Drawing.Size(141, 17); + this.radio24BitLittle.TabIndex = 7; + this.radio24BitLittle.TabStop = true; + this.radio24BitLittle.Text = "24-bit words, little-endian"; + this.radio24BitLittle.UseVisualStyleBackColor = true; + this.radio24BitLittle.CheckedChanged += new System.EventHandler(this.MainGroup_CheckedChanged); + // + // radio32BitLittle + // + this.radio32BitLittle.AutoSize = true; + this.radio32BitLittle.Location = new System.Drawing.Point(14, 181); + this.radio32BitLittle.Name = "radio32BitLittle"; + this.radio32BitLittle.Size = new System.Drawing.Size(141, 17); + this.radio32BitLittle.TabIndex = 8; + this.radio32BitLittle.TabStop = true; + this.radio32BitLittle.Text = "32-bit words, little-endian"; + this.radio32BitLittle.UseVisualStyleBackColor = true; + this.radio32BitLittle.CheckedChanged += new System.EventHandler(this.MainGroup_CheckedChanged); + // + // radioSimpleDataBinary + // + this.radioSimpleDataBinary.AutoSize = true; + this.radioSimpleDataBinary.Location = new System.Drawing.Point(6, 64); + this.radioSimpleDataBinary.Name = "radioSimpleDataBinary"; + this.radioSimpleDataBinary.Size = new System.Drawing.Size(54, 17); + this.radioSimpleDataBinary.TabIndex = 4; + this.radioSimpleDataBinary.TabStop = true; + this.radioSimpleDataBinary.Text = "Binary"; + this.radioSimpleDataBinary.UseVisualStyleBackColor = true; + this.radioSimpleDataBinary.CheckedChanged += new System.EventHandler(this.SimpleDisplay_CheckedChanged); + // + // radioSimpleDataDecimal + // + this.radioSimpleDataDecimal.AutoSize = true; + this.radioSimpleDataDecimal.Location = new System.Drawing.Point(6, 41); + this.radioSimpleDataDecimal.Name = "radioSimpleDataDecimal"; + this.radioSimpleDataDecimal.Size = new System.Drawing.Size(63, 17); + this.radioSimpleDataDecimal.TabIndex = 3; + this.radioSimpleDataDecimal.TabStop = true; + this.radioSimpleDataDecimal.Text = "Decimal"; + this.radioSimpleDataDecimal.UseVisualStyleBackColor = true; + this.radioSimpleDataDecimal.CheckedChanged += new System.EventHandler(this.SimpleDisplay_CheckedChanged); + // + // radioSimpleDataHex + // + this.radioSimpleDataHex.AutoSize = true; + this.radioSimpleDataHex.Location = new System.Drawing.Point(6, 18); + this.radioSimpleDataHex.Name = "radioSimpleDataHex"; + this.radioSimpleDataHex.Size = new System.Drawing.Size(44, 17); + this.radioSimpleDataHex.TabIndex = 2; + this.radioSimpleDataHex.TabStop = true; + this.radioSimpleDataHex.Text = "Hex"; + this.radioSimpleDataHex.UseVisualStyleBackColor = true; + this.radioSimpleDataHex.CheckedChanged += new System.EventHandler(this.SimpleDisplay_CheckedChanged); + // + // radioDenseHex + // + this.radioDenseHex.AutoSize = true; + this.radioDenseHex.Location = new System.Drawing.Point(14, 247); + this.radioDenseHex.Name = "radioDenseHex"; + this.radioDenseHex.Size = new System.Drawing.Size(130, 17); + this.radioDenseHex.TabIndex = 11; + this.radioDenseHex.TabStop = true; + this.radioDenseHex.Text = "Densely-&packed bytes"; + this.radioDenseHex.UseVisualStyleBackColor = true; + this.radioDenseHex.CheckedChanged += new System.EventHandler(this.MainGroup_CheckedChanged); + // + // radioFill + // + this.radioFill.AutoSize = true; + this.radioFill.Location = new System.Drawing.Point(14, 270); + this.radioFill.Name = "radioFill"; + this.radioFill.Size = new System.Drawing.Size(88, 17); + this.radioFill.TabIndex = 12; + this.radioFill.TabStop = true; + this.radioFill.Text = "&Fill with value"; + this.radioFill.UseVisualStyleBackColor = true; + this.radioFill.CheckedChanged += new System.EventHandler(this.MainGroup_CheckedChanged); + // + // horizontalLine1 + // + this.horizontalLine1.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.horizontalLine1.Location = new System.Drawing.Point(12, 81); + this.horizontalLine1.Name = "horizontalLine1"; + this.horizontalLine1.Size = new System.Drawing.Size(500, 2); + this.horizontalLine1.TabIndex = 3; + // + // horizontalLine2 + // + this.horizontalLine2.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.horizontalLine2.Location = new System.Drawing.Point(12, 239); + this.horizontalLine2.Name = "horizontalLine2"; + this.horizontalLine2.Size = new System.Drawing.Size(320, 2); + this.horizontalLine2.TabIndex = 10; + // + // stringSectionLabel + // + this.stringSectionLabel.AutoSize = true; + this.stringSectionLabel.Location = new System.Drawing.Point(12, 308); + this.stringSectionLabel.Name = "stringSectionLabel"; + this.stringSectionLabel.Size = new System.Drawing.Size(34, 13); + this.stringSectionLabel.TabIndex = 13; + this.stringSectionLabel.Text = "String"; + // + // horizontalLine3 + // + this.horizontalLine3.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.horizontalLine3.Location = new System.Drawing.Point(12, 326); + this.horizontalLine3.Name = "horizontalLine3"; + this.horizontalLine3.Size = new System.Drawing.Size(320, 2); + this.horizontalLine3.TabIndex = 14; + // + // radioStringMixed + // + this.radioStringMixed.AutoSize = true; + this.radioStringMixed.Location = new System.Drawing.Point(14, 334); + this.radioStringMixed.Name = "radioStringMixed"; + this.radioStringMixed.Size = new System.Drawing.Size(257, 17); + this.radioStringMixed.TabIndex = 15; + this.radioStringMixed.TabStop = true; + this.radioStringMixed.Text = "Mixed ASCII ({0} bytes) and non-ASCII ({1} bytes)"; + this.radioStringMixed.UseVisualStyleBackColor = true; + this.radioStringMixed.CheckedChanged += new System.EventHandler(this.MainGroup_CheckedChanged); + // + // radioStringMixedReverse + // + this.radioStringMixedReverse.AutoSize = true; + this.radioStringMixedReverse.Location = new System.Drawing.Point(14, 357); + this.radioStringMixedReverse.Name = "radioStringMixedReverse"; + this.radioStringMixedReverse.Size = new System.Drawing.Size(275, 17); + this.radioStringMixedReverse.TabIndex = 16; + this.radioStringMixedReverse.TabStop = true; + this.radioStringMixedReverse.Text = "Reversed ASCII ({0} bytes) and non-ASCII ({1} bytes)"; + this.radioStringMixedReverse.UseVisualStyleBackColor = true; + this.radioStringMixedReverse.CheckedChanged += new System.EventHandler(this.MainGroup_CheckedChanged); + // + // radioStringNullTerm + // + this.radioStringNullTerm.AutoSize = true; + this.radioStringNullTerm.Location = new System.Drawing.Point(14, 381); + this.radioStringNullTerm.Name = "radioStringNullTerm"; + this.radioStringNullTerm.Size = new System.Drawing.Size(151, 17); + this.radioStringNullTerm.TabIndex = 17; + this.radioStringNullTerm.TabStop = true; + this.radioStringNullTerm.Text = "Null-terminated strings ({0})"; + this.radioStringNullTerm.UseVisualStyleBackColor = true; + this.radioStringNullTerm.CheckedChanged += new System.EventHandler(this.MainGroup_CheckedChanged); + // + // radioStringLen8 + // + this.radioStringLen8.AutoSize = true; + this.radioStringLen8.Location = new System.Drawing.Point(14, 405); + this.radioStringLen8.Name = "radioStringLen8"; + this.radioStringLen8.Size = new System.Drawing.Size(197, 17); + this.radioStringLen8.TabIndex = 18; + this.radioStringLen8.TabStop = true; + this.radioStringLen8.Text = "Strings prefixed with 8-bit length ({0})"; + this.radioStringLen8.UseVisualStyleBackColor = true; + this.radioStringLen8.CheckedChanged += new System.EventHandler(this.MainGroup_CheckedChanged); + // + // radioStringLen16 + // + this.radioStringLen16.AutoSize = true; + this.radioStringLen16.Location = new System.Drawing.Point(14, 429); + this.radioStringLen16.Name = "radioStringLen16"; + this.radioStringLen16.Size = new System.Drawing.Size(203, 17); + this.radioStringLen16.TabIndex = 19; + this.radioStringLen16.TabStop = true; + this.radioStringLen16.Text = "Strings prefixed with 16-bit length ({0})"; + this.radioStringLen16.UseVisualStyleBackColor = true; + this.radioStringLen16.CheckedChanged += new System.EventHandler(this.MainGroup_CheckedChanged); + // + // radioStringDci + // + this.radioStringDci.AutoSize = true; + this.radioStringDci.Location = new System.Drawing.Point(14, 453); + this.radioStringDci.Name = "radioStringDci"; + this.radioStringDci.Size = new System.Drawing.Size(170, 17); + this.radioStringDci.TabIndex = 20; + this.radioStringDci.TabStop = true; + this.radioStringDci.Text = "Dextral character inverted ({0})"; + this.radioStringDci.UseVisualStyleBackColor = true; + this.radioStringDci.CheckedChanged += new System.EventHandler(this.MainGroup_CheckedChanged); + // + // symbolEntryTextBox + // + this.symbolEntryTextBox.Location = new System.Drawing.Point(108, 62); + this.symbolEntryTextBox.Name = "symbolEntryTextBox"; + this.symbolEntryTextBox.Size = new System.Drawing.Size(200, 20); + this.symbolEntryTextBox.TabIndex = 1; + this.symbolEntryTextBox.TextChanged += new System.EventHandler(this.symbolEntryTextBox_TextChanged); + // + // symbolPartPanel + // + this.symbolPartPanel.Controls.Add(this.radioSymbolPartBank); + this.symbolPartPanel.Controls.Add(this.radioSymbolPartHigh); + this.symbolPartPanel.Controls.Add(this.radioSymbolPartLow); + this.symbolPartPanel.Location = new System.Drawing.Point(108, 86); + this.symbolPartPanel.Name = "symbolPartPanel"; + this.symbolPartPanel.Size = new System.Drawing.Size(200, 20); + this.symbolPartPanel.TabIndex = 27; + // + // radioSymbolPartBank + // + this.radioSymbolPartBank.AutoSize = true; + this.radioSymbolPartBank.Location = new System.Drawing.Point(103, 1); + this.radioSymbolPartBank.Name = "radioSymbolPartBank"; + this.radioSymbolPartBank.Size = new System.Drawing.Size(50, 17); + this.radioSymbolPartBank.TabIndex = 2; + this.radioSymbolPartBank.TabStop = true; + this.radioSymbolPartBank.Text = "Bank"; + this.radioSymbolPartBank.UseVisualStyleBackColor = true; + this.radioSymbolPartBank.CheckedChanged += new System.EventHandler(this.PartGroup_CheckedChanged); + // + // radioSymbolPartHigh + // + this.radioSymbolPartHigh.AutoSize = true; + this.radioSymbolPartHigh.Location = new System.Drawing.Point(52, 1); + this.radioSymbolPartHigh.Name = "radioSymbolPartHigh"; + this.radioSymbolPartHigh.Size = new System.Drawing.Size(47, 17); + this.radioSymbolPartHigh.TabIndex = 1; + this.radioSymbolPartHigh.TabStop = true; + this.radioSymbolPartHigh.Text = "High"; + this.radioSymbolPartHigh.UseVisualStyleBackColor = true; + this.radioSymbolPartHigh.CheckedChanged += new System.EventHandler(this.PartGroup_CheckedChanged); + // + // radioSymbolPartLow + // + this.radioSymbolPartLow.AutoSize = true; + this.radioSymbolPartLow.Location = new System.Drawing.Point(4, 1); + this.radioSymbolPartLow.Name = "radioSymbolPartLow"; + this.radioSymbolPartLow.Size = new System.Drawing.Size(45, 17); + this.radioSymbolPartLow.TabIndex = 0; + this.radioSymbolPartLow.TabStop = true; + this.radioSymbolPartLow.Text = "Low"; + this.radioSymbolPartLow.UseVisualStyleBackColor = true; + this.radioSymbolPartLow.CheckedChanged += new System.EventHandler(this.PartGroup_CheckedChanged); + // + // radioSimpleDataAscii + // + this.radioSimpleDataAscii.AutoSize = true; + this.radioSimpleDataAscii.Location = new System.Drawing.Point(6, 87); + this.radioSimpleDataAscii.Name = "radioSimpleDataAscii"; + this.radioSimpleDataAscii.Size = new System.Drawing.Size(52, 17); + this.radioSimpleDataAscii.TabIndex = 5; + this.radioSimpleDataAscii.TabStop = true; + this.radioSimpleDataAscii.Text = "ASCII"; + this.radioSimpleDataAscii.UseVisualStyleBackColor = true; + this.radioSimpleDataAscii.CheckedChanged += new System.EventHandler(this.SimpleDisplay_CheckedChanged); + // + // radioDefaultFormat + // + this.radioDefaultFormat.AutoSize = true; + this.radioDefaultFormat.Location = new System.Drawing.Point(14, 30); + this.radioDefaultFormat.Name = "radioDefaultFormat"; + this.radioDefaultFormat.Size = new System.Drawing.Size(59, 17); + this.radioDefaultFormat.TabIndex = 1; + this.radioDefaultFormat.TabStop = true; + this.radioDefaultFormat.Text = "&Default"; + this.radioDefaultFormat.UseVisualStyleBackColor = true; + this.radioDefaultFormat.CheckedChanged += new System.EventHandler(this.MainGroup_CheckedChanged); + // + // simpleDisplayAsGroupBox + // + this.simpleDisplayAsGroupBox.Controls.Add(this.radioSimpleDataAddress); + this.simpleDisplayAsGroupBox.Controls.Add(this.radioSimpleDataSymbolic); + this.simpleDisplayAsGroupBox.Controls.Add(this.radioSimpleDataAscii); + this.simpleDisplayAsGroupBox.Controls.Add(this.radioSimpleDataHex); + this.simpleDisplayAsGroupBox.Controls.Add(this.symbolPartPanel); + this.simpleDisplayAsGroupBox.Controls.Add(this.radioSimpleDataBinary); + this.simpleDisplayAsGroupBox.Controls.Add(this.symbolEntryTextBox); + this.simpleDisplayAsGroupBox.Controls.Add(this.radioSimpleDataDecimal); + this.simpleDisplayAsGroupBox.Location = new System.Drawing.Point(190, 84); + this.simpleDisplayAsGroupBox.Name = "simpleDisplayAsGroupBox"; + this.simpleDisplayAsGroupBox.Size = new System.Drawing.Size(320, 114); + this.simpleDisplayAsGroupBox.TabIndex = 24; + this.simpleDisplayAsGroupBox.TabStop = false; + this.simpleDisplayAsGroupBox.Text = "Display As..."; + // + // radioSimpleDataAddress + // + this.radioSimpleDataAddress.AutoSize = true; + this.radioSimpleDataAddress.Location = new System.Drawing.Point(89, 18); + this.radioSimpleDataAddress.Name = "radioSimpleDataAddress"; + this.radioSimpleDataAddress.Size = new System.Drawing.Size(63, 17); + this.radioSimpleDataAddress.TabIndex = 6; + this.radioSimpleDataAddress.TabStop = true; + this.radioSimpleDataAddress.Text = "&Address"; + this.radioSimpleDataAddress.UseVisualStyleBackColor = true; + // + // radioSimpleDataSymbolic + // + this.radioSimpleDataSymbolic.AutoSize = true; + this.radioSimpleDataSymbolic.Location = new System.Drawing.Point(89, 41); + this.radioSimpleDataSymbolic.Name = "radioSimpleDataSymbolic"; + this.radioSimpleDataSymbolic.Size = new System.Drawing.Size(115, 17); + this.radioSimpleDataSymbolic.TabIndex = 7; + this.radioSimpleDataSymbolic.TabStop = true; + this.radioSimpleDataSymbolic.Text = "&Symbolic reference"; + this.radioSimpleDataSymbolic.UseVisualStyleBackColor = true; + this.radioSimpleDataSymbolic.CheckedChanged += new System.EventHandler(this.SimpleDisplay_CheckedChanged); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 221); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(54, 13); + this.label1.TabIndex = 9; + this.label1.Text = "Bulk Data"; + // + // EditData + // + this.AcceptButton = this.okButton; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(525, 496); + this.Controls.Add(this.label1); + this.Controls.Add(this.simpleDisplayAsGroupBox); + this.Controls.Add(this.radioDefaultFormat); + this.Controls.Add(this.radioStringDci); + this.Controls.Add(this.radioStringLen16); + this.Controls.Add(this.radioStringLen8); + this.Controls.Add(this.radioStringNullTerm); + this.Controls.Add(this.radioStringMixedReverse); + this.Controls.Add(this.radioStringMixed); + this.Controls.Add(this.stringSectionLabel); + this.Controls.Add(this.horizontalLine1); + this.Controls.Add(this.horizontalLine2); + this.Controls.Add(this.horizontalLine3); + this.Controls.Add(this.radioFill); + this.Controls.Add(this.radioDenseHex); + this.Controls.Add(this.radio32BitLittle); + this.Controls.Add(this.radio24BitLittle); + this.Controls.Add(this.radio16BitLittle); + this.Controls.Add(this.radio16BitBig); + this.Controls.Add(this.radioSingleBytes); + this.Controls.Add(this.rawDataSectionLabel); + this.Controls.Add(this.selectFormatLabel); + this.Controls.Add(this.okButton); + this.Controls.Add(this.cancelButton); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "EditData"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Edit Data"; + this.Load += new System.EventHandler(this.EditData_Load); + this.Shown += new System.EventHandler(this.EditData_Shown); + this.symbolPartPanel.ResumeLayout(false); + this.symbolPartPanel.PerformLayout(); + this.simpleDisplayAsGroupBox.ResumeLayout(false); + this.simpleDisplayAsGroupBox.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.Label selectFormatLabel; + private System.Windows.Forms.Label rawDataSectionLabel; + private System.Windows.Forms.RadioButton radioSingleBytes; + private System.Windows.Forms.RadioButton radio16BitBig; + private System.Windows.Forms.RadioButton radio16BitLittle; + private System.Windows.Forms.RadioButton radio24BitLittle; + private System.Windows.Forms.RadioButton radio32BitLittle; + private System.Windows.Forms.RadioButton radioSimpleDataBinary; + private System.Windows.Forms.RadioButton radioSimpleDataDecimal; + private System.Windows.Forms.RadioButton radioSimpleDataHex; + private System.Windows.Forms.RadioButton radioDenseHex; + private System.Windows.Forms.RadioButton radioFill; + private System.Windows.Forms.Label horizontalLine1; + private System.Windows.Forms.Label horizontalLine2; + private System.Windows.Forms.Label stringSectionLabel; + private System.Windows.Forms.Label horizontalLine3; + private System.Windows.Forms.RadioButton radioStringMixed; + private System.Windows.Forms.RadioButton radioStringMixedReverse; + private System.Windows.Forms.RadioButton radioStringNullTerm; + private System.Windows.Forms.RadioButton radioStringLen8; + private System.Windows.Forms.RadioButton radioStringLen16; + private System.Windows.Forms.RadioButton radioStringDci; + private System.Windows.Forms.TextBox symbolEntryTextBox; + private System.Windows.Forms.Panel symbolPartPanel; + private System.Windows.Forms.RadioButton radioSymbolPartBank; + private System.Windows.Forms.RadioButton radioSymbolPartHigh; + private System.Windows.Forms.RadioButton radioSymbolPartLow; + private System.Windows.Forms.RadioButton radioSimpleDataAscii; + private System.Windows.Forms.RadioButton radioDefaultFormat; + private System.Windows.Forms.GroupBox simpleDisplayAsGroupBox; + private System.Windows.Forms.RadioButton radioSimpleDataSymbolic; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.RadioButton radioSimpleDataAddress; + } +} \ No newline at end of file diff --git a/SourceGen/AppForms/EditData.cs b/SourceGen/AppForms/EditData.cs new file mode 100644 index 0000000..c3d2e3f --- /dev/null +++ b/SourceGen/AppForms/EditData.cs @@ -0,0 +1,953 @@ +/* + * Copyright 2018 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.Windows.Forms; + +using CommonUtil; + +namespace SourceGen.AppForms { + public partial class EditData : Form { + /// + /// Result set that describes the formatting to perform. Not all regions will have + /// the same format, e.g. the "mixed ASCII" mode will alternate strings and bytes + /// (rather than a dedicated "mixed ASCII" format type). + /// + public SortedList Results { get; private set; } + + /// + /// Selected offsets. An otherwise contiguous range of offsets can be broken up + /// by user-specified labels and address discontinuities, so this needs to be + /// processed by range. + /// + public TypedRangeSet Selection { private get; set; } + + /// + /// FormatDescriptor from the first offset. May be null if the offset doesn't + /// have a format descriptor specified. This will be used to configure the + /// dialog controls if the format is suited to the selection. The goal is to + /// make single-item editing work as expected. + /// + public FormatDescriptor FirstFormatDescriptor { private get; set; } + + /// + /// Raw file data. + /// + private byte[] mFileData; + + /// + /// Symbol table to use when resolving symbolic values. + /// + private SymbolTable mSymbolTable; + + /// + /// Formatter to use when displaying addresses and hex values. + /// + private Asm65.Formatter mFormatter; + + /// + /// Set this during initial control configuration, so we know to ignore the CheckedChanged + /// events. + /// + private bool mIsInitialSetup; + + /// + /// Set to true if, during the initial setup, the format defined by FirstFormatDescriptor + /// was unavailable. + /// + private bool mPreferredFormatUnavailable; + + + public EditData(byte[] fileData, SymbolTable symbolTable, Asm65.Formatter formatter) { + InitializeComponent(); + + mFileData = fileData; + mSymbolTable = symbolTable; + mFormatter = formatter; + + //Results = new List(); + } + + private void EditData_Load(object sender, EventArgs e) { + DateTime startWhen = DateTime.Now; + + mIsInitialSetup = true; + + // Determine which of the various options is suitable for the selected offsets. + // Disable any radio buttons that won't work. + AnalyzeRanges(); + + // Configure the dialog from the FormatDescriptor, if one is available. + Debug.WriteLine("First FD: " + FirstFormatDescriptor); + SetControlsFromDescriptor(FirstFormatDescriptor); + + if (mPreferredFormatUnavailable) { + // This can happen when e.g. a bunch of stuff is formatted as null-terminated + // strings. We don't recognize a lone zero as a string, but we allow it if + // it's next to a bunch of others. If you come back later and try to format + // just that one byte, you end up here. + // TODO(maybe): make it more obvious what's going on? + Debug.WriteLine("NOTE: preferred format unavailable"); + } + + mIsInitialSetup = false; + UpdateControls(); + + Debug.WriteLine("EditData dialog load time: " + + (DateTime.Now - startWhen).TotalMilliseconds + " ms"); + } + + private void EditData_Shown(object sender, EventArgs e) { + // Start with the focus in the text box if the initial format allows for a + // symbolic reference. This way they can start typing immediately. + if (simpleDisplayAsGroupBox.Enabled) { + symbolEntryTextBox.Focus(); + } + } + + /// + /// Handles CheckedChanged event for all radio buttons in main group. This will + /// fire twice when a radio button is clicked (once to un-check the old, once + /// to check the new). + /// + private void MainGroup_CheckedChanged(object sender, EventArgs e) { + // Enable/disable the style group and the low/high/bank radio group. + // Update preview window. + UpdateControls(); + } + + /// + /// Handles CheckedChanged event for radio buttons in the simple-data "display as" + /// group box. + /// + private void SimpleDisplay_CheckedChanged(object sender, EventArgs e) { + // Enable/disable the low/high/bank radio group. + UpdateControls(); + } + + /// + /// Handles CheckedChanged event for all radio buttons in symbol-part group. + /// + private void PartGroup_CheckedChanged(object sender, EventArgs e) { + // not currently using a preview window; could add one for single items? + } + + private void symbolEntryTextBox_TextChanged(object sender, EventArgs e) { + // Make sure Symbol is checked if they're typing text in. + Debug.Assert(radioSimpleDataSymbolic.Enabled); + radioSimpleDataSymbolic.Checked = true; + // Update OK button based on symbol validity. + UpdateControls(); + } + + private void okButton_Click(object sender, EventArgs e) { + CreateDescriptorListFromControls(); + FormatDescriptor.DebugDumpSortedList(Results); + } + + /// + /// Updates all of the controls to reflect the current internal state. + /// + private void UpdateControls() { + if (mIsInitialSetup) { + return; + } + + // Configure the simple data "display as" style box. + bool wantStyle = false; + int simpleWidth = -1; + bool isBigEndian = false; + if (radioSingleBytes.Checked) { + wantStyle = true; + simpleWidth = 1; + } else if (radio16BitLittle.Checked) { + wantStyle = true; + simpleWidth = 2; + } else if (radio16BitBig.Checked) { + wantStyle = true; + simpleWidth = 2; + isBigEndian = true; + } else if (radio24BitLittle.Checked) { + wantStyle = true; + simpleWidth = 3; + } else if (radio32BitLittle.Checked) { + wantStyle = true; + simpleWidth = 4; + } + bool focusOnSymbol = !simpleDisplayAsGroupBox.Enabled && wantStyle; + simpleDisplayAsGroupBox.Enabled = wantStyle; + if (wantStyle) { + // TODO(soon): compute on first need and save results; this is getting called + // 2x as radio buttons are hit, and might be slow on large data sets + radioSimpleDataAscii.Enabled = IsRawAsciiCompatible(simpleWidth, isBigEndian); + } + + // Enable the symbolic reference entry box if the "display as" group is enabled. + // That way instead of "click 16-bit", "click symbol", "enter symbol", the user + // can skip the second step. + symbolEntryTextBox.Enabled = simpleDisplayAsGroupBox.Enabled; + symbolPartPanel.Enabled = radioSimpleDataSymbolic.Checked; + + // If we just enabled the group box, set the focus on the symbol entry box. This + // removes another click from the steps, though it's a bit aggressive if you're + // trying to arrow your way through the items. + if (focusOnSymbol) { + symbolEntryTextBox.Focus(); + } + + bool isOk = true; + if (radioSimpleDataSymbolic.Checked) { + // Just check for correct format. References to non-existent labels are allowed. + isOk = Asm65.Label.ValidateLabel(symbolEntryTextBox.Text); + + // Actually, let's discourage references to auto-labels. + if (isOk && mSymbolTable.TryGetValue(symbolEntryTextBox.Text, out Symbol sym)) { + isOk = sym.SymbolSource != Symbol.Source.Auto; + } + } + okButton.Enabled = isOk; + } + + /// + /// Analyzes the selection to see which data formatting options are suitable. + /// Disables radio buttons and updates labels. + /// + /// Call this once, when the dialog is first loaded. + /// + private void AnalyzeRanges() { + Debug.Assert(Selection.Count != 0); + + string fmt = (Selection.RangeCount == 1) ? + Properties.Resources.FMT_FORMAT_SINGLE_GROUP : + Properties.Resources.FMT_FORMAT_MULTIPLE_GROUPS; + selectFormatLabel.Text = string.Format(fmt, Selection.Count, Selection.RangeCount); + + IEnumerator iter = Selection.RangeListIterator; + + int mixedAsciiOkCount = 0; + int mixedAsciiNotCount = 0; + int nullTermStringCount = 0; + int len8StringCount = 0; + int len16StringCount = 0; + int dciStringCount = 0; + //int revDciStringCount = 0; + + // For each range, check to see if the data within qualifies for the various + // options. If any of them fail to meet the criteria, the option is disabled + // for all ranges. + while (iter.MoveNext()) { + TypedRangeSet.TypedRange rng = iter.Current; + Debug.WriteLine("Testing [" + rng.Low + ", " + rng.High + "]"); + + // Start with the easy ones. Single-byte and dense are always enabled. + + int count = rng.High - rng.Low + 1; + Debug.Assert(count > 0); + if ((count & 0x01) != 0) { + // not divisible by 2, disallow 16-bit entries + radio16BitLittle.Enabled = false; + radio16BitBig.Enabled = false; + } + if ((count & 0x03) != 0) { + // not divisible by 4, disallow 32-bit entries + radio32BitLittle.Enabled = false; + } + if ((count / 3) * 3 != count) { + // not divisible by 3, disallow 24-bit entries + radio24BitLittle.Enabled = false; + } + + + // Check for run of bytes (2 or more of the same thing). Remember that + // we check this one region at a time, and each region could have different + // bytes, but so long as the bytes are all the same within a region we're good. + if (radioFill.Enabled && count > 1 && + DataAnalysis.RecognizeRun(mFileData, rng.Low, rng.High) == count) { + // LGTM + } else { + radioFill.Enabled = false; + } + + // See if there's enough string data to make it worthwhile. We use an + // arbitrary threshold of 2+ ASCII characters, and require twice as many + // ASCII as non-ASCII. We arbitrarily require the strings to be either + // high or low ASCII, and treat the other as non-ASCII. (We could relax + // this -- we generate separate items for each string and non-ASCII chunk -- + // but I'm trying to hide the option when the buffer doesn't really seem + // to be holding strings. Could replace with some sort of minimum string + // length requirement?) + if (radioStringMixed.Enabled) { + int asciiCount; + DataAnalysis.CountAsciiBytes(mFileData, rng.Low, rng.High, + out int lowAscii, out int highAscii, out int nonAscii); + if (highAscii > lowAscii) { + asciiCount = highAscii; + nonAscii += lowAscii; + } else { + asciiCount = lowAscii; + nonAscii += highAscii; + } + + if (asciiCount >= 2 && asciiCount >= nonAscii * 2) { + // Looks good + mixedAsciiOkCount += asciiCount; + mixedAsciiNotCount += nonAscii; + } else { + // Fail + radioStringMixed.Enabled = false; + radioStringMixedReverse.Enabled = false; + mixedAsciiOkCount = mixedAsciiNotCount = -1; + } + } + + // Check for null-terminated strings. Zero-length strings are allowed, but + // not counted -- we want to have some actual character data. Individual + // strings need to be entirely high-ASCII or low-ASCII, but not all strings + // in a region have to be the same. + if (radioStringNullTerm.Enabled) { + int strCount = DataAnalysis.RecognizeNullTerminatedStrings(mFileData, + rng.Low, rng.High); + if (strCount > 0) { + nullTermStringCount += strCount; + } else { + radioStringNullTerm.Enabled = false; + nullTermStringCount = -1; + } + } + + // Check for strings prefixed with an 8-bit length. + if (radioStringLen8.Enabled) { + int strCount = DataAnalysis.RecognizeLen8Strings(mFileData, rng.Low, rng.High); + if (strCount > 0) { + len8StringCount += strCount; + } else { + radioStringLen8.Enabled = false; + len8StringCount = -1; + } + } + + // Check for strings prefixed with a 16-bit length. + if (radioStringLen16.Enabled) { + int strCount = DataAnalysis.RecognizeLen16Strings(mFileData, rng.Low, rng.High); + if (strCount > 0) { + len16StringCount += strCount; + } else { + radioStringLen16.Enabled = false; + len16StringCount = -1; + } + } + + // Check for DCI strings. All strings within a single range must have the + // same "polarity", e.g. low ASCII terminated by high ASCII. + if (radioStringDci.Enabled) { + int strCount = DataAnalysis.RecognizeDciStrings(mFileData, rng.Low, rng.High); + if (strCount > 0) { + dciStringCount += strCount; + } else { + radioStringDci.Enabled = false; + dciStringCount = -1; + } + } + + //// Check for reverse DCI strings. All strings within a single range must have the + //// same "polarity", e.g. low ASCII terminated by high ASCII. + //if (radioStringDciReverse.Enabled) { + // int strCount = DataAnalysis.RecognizeReverseDciStrings(mFileData, + // rng.Low, rng.High); + // if (strCount > 0) { + // revDciStringCount += strCount; + // } else { + // radioStringDciReverse.Enabled = false; + // revDciStringCount = -1; + // } + //} + } + + // Update the dialog with string and character counts, summed across all regions. + + if (mixedAsciiOkCount > 0) { + Debug.Assert(radioStringMixed.Enabled); + radioStringMixed.Text = string.Format(radioStringMixed.Text, + mixedAsciiOkCount, mixedAsciiNotCount); + radioStringMixedReverse.Text = string.Format(radioStringMixedReverse.Text, + mixedAsciiOkCount, mixedAsciiNotCount); + } else { + Debug.Assert(!radioStringMixed.Enabled); + radioStringMixed.Text = string.Format(radioStringMixed.Text, "xx", "xx"); + radioStringMixedReverse.Text = string.Format(radioStringMixedReverse.Text, + "xx", "xx"); + } + + if (nullTermStringCount > 0) { + Debug.Assert(radioStringNullTerm.Enabled); + radioStringNullTerm.Text = string.Format(radioStringNullTerm.Text, nullTermStringCount); + } else { + Debug.Assert(!radioStringNullTerm.Enabled); + radioStringNullTerm.Text = string.Format(radioStringNullTerm.Text, "xx"); + } + + if (len8StringCount > 0) { + Debug.Assert(radioStringLen8.Enabled); + radioStringLen8.Text = string.Format(radioStringLen8.Text, len8StringCount); + } else { + Debug.Assert(!radioStringLen8.Enabled); + radioStringLen8.Text = string.Format(radioStringLen8.Text, "xx"); + } + + if (len16StringCount > 0) { + Debug.Assert(radioStringLen16.Enabled); + radioStringLen16.Text = string.Format(radioStringLen16.Text, len16StringCount); + } else { + Debug.Assert(!radioStringLen16.Enabled); + radioStringLen16.Text = string.Format(radioStringLen16.Text, "xx"); + } + + if (dciStringCount > 0) { + Debug.Assert(radioStringDci.Enabled); + radioStringDci.Text = string.Format(radioStringDci.Text, dciStringCount); + } else { + Debug.Assert(!radioStringDci.Enabled); + radioStringDci.Text = string.Format(radioStringDci.Text, "xx"); + } + + //if (revDciStringCount > 0) { + // Debug.Assert(radioStringDciReverse.Enabled); + // radioStringDciReverse.Text = + // string.Format(radioStringDciReverse.Text, revDciStringCount); + //} else { + // Debug.Assert(!radioStringDciReverse.Enabled); + // radioStringDciReverse.Text = string.Format(radioStringDciReverse.Text, "xx"); + //} + } + + /// + /// Determines whether the data in the buffer can be represented as ASCII values. + /// Using ".DD1 'A'" for 0x41 is obvious, but we also allow ".DD2 'A'" for + /// 0x41 0x00. 16-bit character constants are more likely as intermediate + /// operands, but could be found in data areas. + /// + /// High and low ASCII are allowed, and may be freely mixed. + /// + /// Testing explicitly is probably excessive, and possibly counter-productive if + /// the user is trying to flag an area that is a mix of ASCII and non-ASCII and + /// just wants hex for the rest, but we'll give it a try. + /// + /// Number of bytes per character. + /// Word endian-ness. + /// True if data in all regions can be represented as high or low ASCII. + private bool IsRawAsciiCompatible(int wordWidth, bool isBigEndian) { + IEnumerator iter = Selection.RangeListIterator; + while (iter.MoveNext()) { + TypedRangeSet.TypedRange rng = iter.Current; + Debug.Assert(((rng.High - rng.Low + 1) / wordWidth) * wordWidth == + rng.High - rng.Low + 1); + for (int i = rng.Low; i <= rng.High; i += wordWidth) { + int val = RawData.GetWord(mFileData, rng.Low, wordWidth, isBigEndian); + if (val < 0x20 || (val >= 0x7f && val < 0xa0) || val >= 0xff) { + // bad value, fail + return false; + } + } + } + return true; + } + + /// + /// Configures the dialog controls based on the provided format descriptor. If + /// the desired options are unavailable, a suitable default is selected instead. + /// + /// FormatDescriptor to use. + private void SetControlsFromDescriptor(FormatDescriptor dfd) { + Debug.Assert(mIsInitialSetup); + radioSimpleDataHex.Checked = true; + radioSymbolPartLow.Checked = true; + + if (dfd == null) { + radioDefaultFormat.Checked = true; + return; + } + + RadioButton preferredFormat; + + switch (dfd.FormatType) { + case FormatDescriptor.Type.NumericLE: + case FormatDescriptor.Type.NumericBE: + switch (dfd.Length) { + case 1: + preferredFormat = radioSingleBytes; + break; + case 2: + preferredFormat = + (dfd.FormatType == FormatDescriptor.Type.NumericLE ? + radio16BitLittle : radio16BitBig); + break; + case 3: + preferredFormat = radio24BitLittle; + break; + case 4: + preferredFormat = radio32BitLittle; + break; + default: + Debug.Assert(false); + preferredFormat = radioDefaultFormat; + break; + } + if (preferredFormat.Enabled) { + switch (dfd.FormatSubType) { + case FormatDescriptor.SubType.None: + case FormatDescriptor.SubType.Hex: + radioSimpleDataHex.Checked = true; + break; + case FormatDescriptor.SubType.Decimal: + radioSimpleDataDecimal.Checked = true; + break; + case FormatDescriptor.SubType.Binary: + radioSimpleDataBinary.Checked = true; + break; + case FormatDescriptor.SubType.Ascii: + radioSimpleDataAscii.Checked = true; + break; + case FormatDescriptor.SubType.Address: + radioSimpleDataAddress.Checked = true; + break; + case FormatDescriptor.SubType.Symbol: + radioSimpleDataSymbolic.Checked = true; + switch (dfd.SymbolRef.ValuePart) { + case WeakSymbolRef.Part.Low: + radioSymbolPartLow.Checked = true; + break; + case WeakSymbolRef.Part.High: + radioSymbolPartHigh.Checked = true; + break; + case WeakSymbolRef.Part.Bank: + radioSymbolPartBank.Checked = true; + break; + default: + Debug.Assert(false); + break; + } + Debug.Assert(dfd.HasSymbol); + symbolEntryTextBox.Text = dfd.SymbolRef.Label; + break; + default: + Debug.Assert(false); + break; + } + } else { + // preferred format not enabled; leave Hex/Low checked + } + break; + case FormatDescriptor.Type.String: + switch (dfd.FormatSubType) { + case FormatDescriptor.SubType.None: + preferredFormat = radioStringMixed; + break; + case FormatDescriptor.SubType.Reverse: + preferredFormat = radioStringMixedReverse; + break; + case FormatDescriptor.SubType.CString: + preferredFormat = radioStringNullTerm; + break; + case FormatDescriptor.SubType.L8String: + preferredFormat = radioStringLen8; + break; + case FormatDescriptor.SubType.L16String: + preferredFormat = radioStringLen16; + break; + case FormatDescriptor.SubType.Dci: + preferredFormat = radioStringDci; + break; + case FormatDescriptor.SubType.DciReverse: + preferredFormat = radioDefaultFormat; + break; + default: + Debug.Assert(false); + preferredFormat = radioDefaultFormat; + break; + } + break; + case FormatDescriptor.Type.Dense: + preferredFormat = radioDenseHex; + break; + case FormatDescriptor.Type.Fill: + preferredFormat = radioFill; + break; + default: + // Should not be here. + Debug.Assert(false); + preferredFormat = radioDefaultFormat; + break; + } + + if (preferredFormat.Enabled) { + preferredFormat.Checked = true; + } else { + mPreferredFormatUnavailable = true; + radioDefaultFormat.Checked = true; + } + } + + /// + /// Creates a list of FormatDescriptors, based on the current control configuration. + /// + /// The entries in the list are guaranteed to be sorted by start address and not + /// overlap. + /// + /// We assume that whatever the control gives us is correct, e.g. it's not going + /// to tell us to put a buffer full of zeroes into a DCI string. + /// + /// Result list. + private void CreateDescriptorListFromControls() { + FormatDescriptor.Type type = FormatDescriptor.Type.Default; + FormatDescriptor.SubType subType = FormatDescriptor.SubType.None; + WeakSymbolRef symbolRef = null; + int chunkLength = -1; + + // Decode the "display as" panel, if it's relevant. + if (radioSimpleDataHex.Enabled) { + if (radioSimpleDataHex.Checked) { + subType = FormatDescriptor.SubType.Hex; + } else if (radioSimpleDataDecimal.Checked) { + subType = FormatDescriptor.SubType.Decimal; + } else if (radioSimpleDataBinary.Checked) { + subType = FormatDescriptor.SubType.Binary; + } else if (radioSimpleDataAscii.Checked) { + subType = FormatDescriptor.SubType.Ascii; + } else if (radioSimpleDataAddress.Checked) { + subType = FormatDescriptor.SubType.Address; + } else if (radioSimpleDataSymbolic.Checked) { + WeakSymbolRef.Part part; + if (radioSymbolPartLow.Checked) { + part = WeakSymbolRef.Part.Low; + } else if (radioSymbolPartHigh.Checked) { + part = WeakSymbolRef.Part.High; + } else if (radioSymbolPartBank.Checked) { + part = WeakSymbolRef.Part.Bank; + } else { + Debug.Assert(false); + part = WeakSymbolRef.Part.Low; + } + subType = FormatDescriptor.SubType.Symbol; + symbolRef = new WeakSymbolRef(symbolEntryTextBox.Text, part); + } else { + Debug.Assert(false); + } + } else { + subType = 0; // set later, or doesn't matter + } + + // Decode the main format. + if (radioDefaultFormat.Checked) { + // Default/None; note this would create a multi-byte Default format, which isn't + // really allowed. What we actually want to do is remove the explicit formatting + // from all spanned offsets, so we use a dedicated type for that. + type = FormatDescriptor.Type.REMOVE; + } else if (radioSingleBytes.Checked) { + type = FormatDescriptor.Type.NumericLE; + chunkLength = 1; + } else if (radio16BitLittle.Checked) { + type = FormatDescriptor.Type.NumericLE; + chunkLength = 2; + } else if (radio16BitBig.Checked) { + type = FormatDescriptor.Type.NumericBE; + chunkLength = 2; + } else if (radio24BitLittle.Checked) { + type = FormatDescriptor.Type.NumericLE; + chunkLength = 3; + } else if (radio32BitLittle.Checked) { + type = FormatDescriptor.Type.NumericLE; + chunkLength = 4; + } else if (radioDenseHex.Checked) { + type = FormatDescriptor.Type.Dense; + } else if (radioFill.Checked) { + type = FormatDescriptor.Type.Fill; + } else if (radioStringMixed.Checked) { + type = FormatDescriptor.Type.String; + } else if (radioStringMixedReverse.Checked) { + type = FormatDescriptor.Type.String; + subType = FormatDescriptor.SubType.Reverse; + } else if (radioStringNullTerm.Checked) { + type = FormatDescriptor.Type.String; + subType = FormatDescriptor.SubType.CString; + } else if (radioStringLen8.Checked) { + type = FormatDescriptor.Type.String; + subType = FormatDescriptor.SubType.L8String; + } else if (radioStringLen16.Checked) { + type = FormatDescriptor.Type.String; + subType = FormatDescriptor.SubType.L16String; + } else if (radioStringDci.Checked) { + type = FormatDescriptor.Type.String; + subType = FormatDescriptor.SubType.Dci; + //} else if (radioStringDciReverse.Checked) { + // type = FormatDescriptor.Type.String; + // subType = FormatDescriptor.SubType.DciReverse; + } else { + Debug.Assert(false); + // default/none + } + + + Results = new SortedList(); + + IEnumerator iter = Selection.RangeListIterator; + while (iter.MoveNext()) { + TypedRangeSet.TypedRange rng = iter.Current; + + if (type == FormatDescriptor.Type.String) { + // We want to create one FormatDescriptor object per string. That way + // each string gets its own line. + if ((subType == FormatDescriptor.SubType.None || + subType == FormatDescriptor.SubType.Reverse)) { + CreateMixedStringEntries(rng.Low, rng.High, subType); + } else if (subType == FormatDescriptor.SubType.CString) { + CreateCStringEntries(rng.Low, rng.High, subType); + } else if (subType == FormatDescriptor.SubType.L8String || + subType == FormatDescriptor.SubType.L16String) { + CreateLengthStringEntries(rng.Low, rng.High, subType); + } else if (subType == FormatDescriptor.SubType.Dci || + subType == FormatDescriptor.SubType.DciReverse) { + CreateDciStringEntries(rng.Low, rng.High, subType); + } else { + Debug.Assert(false); + CreateMixedStringEntries(rng.Low, rng.High, subType); // shrug + } + } else { + CreateSimpleEntries(type, subType, chunkLength, symbolRef, rng.Low, rng.High); + } + } + } + + /// + /// Creates one or more FormatDescriptor entries for the specified range, adding them + /// to the Results list. + /// + /// This will either create one entry that spans the entire range (for e.g. strings + /// and bulk data), or create equal-sized chunks. + /// + /// Region data type. + /// Region data sub-type. + /// Length of a chunk, or -1 for full buffer. + /// Symbol reference, or null if not applicable. + /// Offset of first byte in range. + /// Offset of last byte in range. + private void CreateSimpleEntries(FormatDescriptor.Type type, + FormatDescriptor.SubType subType, int chunkLength, + WeakSymbolRef symbolRef, int low, int high) { + + if (chunkLength == -1) { + chunkLength = (high - low) + 1; + } + Debug.Assert(((high - low + 1) / chunkLength) * chunkLength == high - low + 1); + + // Either we have one chunk, or we have multiple chunks with the same type and + // length. Either way, we only need to create the descriptor once. (This is + // safe because FormatDescriptor instances are immutable.) + // + // Because certain details, like the fill byte and high-vs-low ASCII, are pulled + // out of the data stream at format time, we don't have to dig for them now. + FormatDescriptor dfd; + if (subType == FormatDescriptor.SubType.Symbol) { + dfd = FormatDescriptor.Create(chunkLength, symbolRef, + type == FormatDescriptor.Type.NumericBE); + } else { + dfd = FormatDescriptor.Create(chunkLength, type, subType); + } + + while (low <= high) { + Results.Add(low, dfd); + low += chunkLength; + } + } + + /// + /// Creates one or more FormatDescriptor entries for the specified range, adding them + /// to the Results list. + /// + /// Offset of first byte in range. + /// Offset of last byte in range. + /// String sub-type. + private void CreateMixedStringEntries(int low, int high, + FormatDescriptor.SubType subType) { + int stringStart = -1; + int highBit = 0; + int cur; + for (cur = low; cur <= high; cur++) { + byte val = mFileData[cur]; + if (CommonUtil.TextUtil.IsHiLoAscii(val)) { + // is ASCII + if (stringStart >= 0) { + // was in a string + if (highBit != (val & 0x80)) { + // end of string due to high bit flip, output + CreateStringOrByte(stringStart, cur - stringStart, subType); + // start a new string + stringStart = cur; + } else { + // still in string, keep going + } + } else { + // wasn't in a string, start one + stringStart = cur; + } + highBit = val & 0x80; + } else { + // not ASCII + if (stringStart >= 0) { + // was in a string, output it + CreateStringOrByte(stringStart, cur - stringStart, subType); + stringStart = -1; + } + // output as single byte + CreateByteFD(cur, FormatDescriptor.SubType.Hex); + } + } + if (stringStart >= 0) { + // close out the string + CreateStringOrByte(stringStart, cur - stringStart, subType); + } + } + + /// + /// Creates a format descriptor for ASCII data. If the data is only one byte long, + /// a single-byte ASCII char item is emitted instead. + /// + /// Offset of first byte. + /// Length of string. + /// String sub-type. + private void CreateStringOrByte(int offset, int length, + FormatDescriptor.SubType subType) { + Debug.Assert(length > 0); + if (length == 1) { + // single byte, output as single ASCII char rather than 1-byte string + CreateByteFD(offset, FormatDescriptor.SubType.Ascii); + } else { + FormatDescriptor dfd; + dfd = FormatDescriptor.Create(length, + FormatDescriptor.Type.String, subType); + Results.Add(offset, dfd); + } + } + + /// + /// Creates a format descriptor for a single-byte numeric value. + /// + /// File offset. + /// How to format the item. + private void CreateByteFD(int offset, FormatDescriptor.SubType subType) { + FormatDescriptor dfd = FormatDescriptor.Create(1, + FormatDescriptor.Type.NumericLE, subType); + Results.Add(offset, dfd); + } + + /// + /// Creates one or more FormatDescriptor entries for the specified range, adding them + /// to the Results list. + /// + /// Offset of first byte in range. + /// Offset of last byte in range. + /// String sub-type. + private void CreateCStringEntries(int low, int high, + FormatDescriptor.SubType subType) { + int startOffset = low; + for (int i = low; i <= high; i++) { + if (mFileData[i] == 0x00) { + // End of string. Zero-length strings are allowed. + FormatDescriptor dfd = FormatDescriptor.Create( + i - startOffset + 1, FormatDescriptor.Type.String, subType); + Results.Add(startOffset, dfd); + startOffset = i + 1; + } else { + // keep going + } + } + + // Earlier analysis guaranteed that the last byte in the buffer is 0x00. + Debug.Assert(startOffset == high + 1); + } + + /// + /// Creates one or more FormatDescriptor entries for the specified range, adding them + /// to the Results list. + /// + /// Offset of first byte in range. + /// Offset of last byte in range. + /// String sub-type. + private void CreateLengthStringEntries(int low, int high, + FormatDescriptor.SubType subType) { + int i; + for (i = low; i <= high;) { + int length = mFileData[i]; + if (subType == FormatDescriptor.SubType.L16String) { + length |= mFileData[i + 1] << 8; + length += 2; + } else { + length++; + } + // Zero-length strings are allowed. + FormatDescriptor dfd = FormatDescriptor.Create(length, + FormatDescriptor.Type.String, subType); + Results.Add(i, dfd); + i += length; + } + + Debug.Assert(i == high + 1); + } + + /// + /// Creates one or more FormatDescriptor entries for the specified range, adding them + /// to the Results list. + /// + /// Offset of first byte in range. + /// Offset of last byte in range. + /// String sub-type. + private void CreateDciStringEntries(int low, int high, + FormatDescriptor.SubType subType) { + int start, end, adj, endMask; + if (subType == FormatDescriptor.SubType.Dci) { + start = low; + end = high + 1; + adj = 1; + } else if (subType == FormatDescriptor.SubType.DciReverse) { + start = high; + end = low - 1; + adj = -1; + } else { + Debug.Assert(false); + return; + } + + // Zero-length strings aren't a thing for DCI. The analyzer requires that all + // strings in a region have the same polarity, so just grab the last byte. + endMask = mFileData[end - 1] & 0x80; + + int stringStart = start; + for (int i = start; i != end; i += adj) { + byte val = mFileData[i]; + if ((val & 0x80) == endMask) { + // found the end of a string + int length = (i - stringStart) * adj + 1; + FormatDescriptor dfd = FormatDescriptor.Create(length, + FormatDescriptor.Type.String, subType); + Results.Add(stringStart < i ? stringStart : i, dfd); + stringStart = i + adj; + } + } + + Debug.Assert(stringStart == end); + } + } +} diff --git a/SourceGen/AppForms/EditData.resx b/SourceGen/AppForms/EditData.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/SourceGen/AppForms/EditData.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/SourceGen/AppForms/EditDefSymbol.Designer.cs b/SourceGen/AppForms/EditDefSymbol.Designer.cs new file mode 100644 index 0000000..0f2aac9 --- /dev/null +++ b/SourceGen/AppForms/EditDefSymbol.Designer.cs @@ -0,0 +1,265 @@ +/* + * Copyright 2018 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. + */ +namespace SourceGen.AppForms { + partial class EditDefSymbol { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.cancelButton = new System.Windows.Forms.Button(); + this.okButton = new System.Windows.Forms.Button(); + this.labelLabel = new System.Windows.Forms.Label(); + this.labelTextBox = new System.Windows.Forms.TextBox(); + this.valueTextBox = new System.Windows.Forms.TextBox(); + this.labelNotesLabel = new System.Windows.Forms.Label(); + this.valueLabel = new System.Windows.Forms.Label(); + this.valueNotesLabel = new System.Windows.Forms.Label(); + this.commentLabel = new System.Windows.Forms.Label(); + this.commentTextBox = new System.Windows.Forms.TextBox(); + this.commentNotesLabel = new System.Windows.Forms.Label(); + this.symbolTypeGroupBox = new System.Windows.Forms.GroupBox(); + this.constantRadioButton = new System.Windows.Forms.RadioButton(); + this.addressRadioButton = new System.Windows.Forms.RadioButton(); + this.labelUniqueLabel = new System.Windows.Forms.Label(); + this.symbolTypeGroupBox.SuspendLayout(); + this.SuspendLayout(); + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(203, 253); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 5; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + // + // okButton + // + this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.okButton.Location = new System.Drawing.Point(122, 253); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(75, 23); + this.okButton.TabIndex = 4; + this.okButton.Text = "OK"; + this.okButton.UseVisualStyleBackColor = true; + this.okButton.Click += new System.EventHandler(this.okButton_Click); + // + // labelLabel + // + this.labelLabel.AutoSize = true; + this.labelLabel.Location = new System.Drawing.Point(14, 16); + this.labelLabel.Name = "labelLabel"; + this.labelLabel.Size = new System.Drawing.Size(36, 13); + this.labelLabel.TabIndex = 6; + this.labelLabel.Text = "Label:"; + // + // labelTextBox + // + this.labelTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.labelTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.labelTextBox.Location = new System.Drawing.Point(77, 14); + this.labelTextBox.Name = "labelTextBox"; + this.labelTextBox.Size = new System.Drawing.Size(200, 20); + this.labelTextBox.TabIndex = 0; + this.labelTextBox.TextChanged += new System.EventHandler(this.labelTextBox_TextChanged); + // + // valueTextBox + // + this.valueTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.valueTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.valueTextBox.Location = new System.Drawing.Point(77, 81); + this.valueTextBox.Name = "valueTextBox"; + this.valueTextBox.Size = new System.Drawing.Size(200, 20); + this.valueTextBox.TabIndex = 1; + this.valueTextBox.TextChanged += new System.EventHandler(this.valueTextBox_TextChanged); + // + // labelNotesLabel + // + this.labelNotesLabel.AutoSize = true; + this.labelNotesLabel.Location = new System.Drawing.Point(74, 37); + this.labelNotesLabel.Name = "labelNotesLabel"; + this.labelNotesLabel.Size = new System.Drawing.Size(187, 13); + this.labelNotesLabel.TabIndex = 9; + this.labelNotesLabel.Text = "• 2+ alphanumerics, starting with letter"; + // + // valueLabel + // + this.valueLabel.AutoSize = true; + this.valueLabel.Location = new System.Drawing.Point(14, 83); + this.valueLabel.Name = "valueLabel"; + this.valueLabel.Size = new System.Drawing.Size(37, 13); + this.valueLabel.TabIndex = 7; + this.valueLabel.Text = "Value:"; + // + // valueNotesLabel + // + this.valueNotesLabel.AutoSize = true; + this.valueNotesLabel.Location = new System.Drawing.Point(74, 104); + this.valueNotesLabel.Name = "valueNotesLabel"; + this.valueNotesLabel.Size = new System.Drawing.Size(155, 13); + this.valueNotesLabel.TabIndex = 11; + this.valueNotesLabel.Text = "• Decimal, hex ($), or binary (%)"; + // + // commentLabel + // + this.commentLabel.AutoSize = true; + this.commentLabel.Location = new System.Drawing.Point(17, 137); + this.commentLabel.Name = "commentLabel"; + this.commentLabel.Size = new System.Drawing.Size(54, 13); + this.commentLabel.TabIndex = 8; + this.commentLabel.Text = "Comment:"; + // + // commentTextBox + // + this.commentTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.commentTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.commentTextBox.Location = new System.Drawing.Point(77, 134); + this.commentTextBox.Name = "commentTextBox"; + this.commentTextBox.Size = new System.Drawing.Size(200, 20); + this.commentTextBox.TabIndex = 2; + // + // commentNotesLabel + // + this.commentNotesLabel.AutoSize = true; + this.commentNotesLabel.Location = new System.Drawing.Point(74, 157); + this.commentNotesLabel.Name = "commentNotesLabel"; + this.commentNotesLabel.Size = new System.Drawing.Size(55, 13); + this.commentNotesLabel.TabIndex = 12; + this.commentNotesLabel.Text = "• Optional"; + // + // symbolTypeGroupBox + // + this.symbolTypeGroupBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.symbolTypeGroupBox.Controls.Add(this.constantRadioButton); + this.symbolTypeGroupBox.Controls.Add(this.addressRadioButton); + this.symbolTypeGroupBox.Location = new System.Drawing.Point(13, 190); + this.symbolTypeGroupBox.Name = "symbolTypeGroupBox"; + this.symbolTypeGroupBox.Size = new System.Drawing.Size(264, 48); + this.symbolTypeGroupBox.TabIndex = 3; + this.symbolTypeGroupBox.TabStop = false; + this.symbolTypeGroupBox.Text = "Symbol Type"; + // + // constantRadioButton + // + this.constantRadioButton.AutoSize = true; + this.constantRadioButton.Location = new System.Drawing.Point(99, 20); + this.constantRadioButton.Name = "constantRadioButton"; + this.constantRadioButton.Size = new System.Drawing.Size(67, 17); + this.constantRadioButton.TabIndex = 1; + this.constantRadioButton.TabStop = true; + this.constantRadioButton.Text = "Constant"; + this.constantRadioButton.UseVisualStyleBackColor = true; + // + // addressRadioButton + // + this.addressRadioButton.AutoSize = true; + this.addressRadioButton.Location = new System.Drawing.Point(6, 20); + this.addressRadioButton.Name = "addressRadioButton"; + this.addressRadioButton.Size = new System.Drawing.Size(63, 17); + this.addressRadioButton.TabIndex = 0; + this.addressRadioButton.TabStop = true; + this.addressRadioButton.Text = "Address"; + this.addressRadioButton.UseVisualStyleBackColor = true; + // + // labelUniqueLabel + // + this.labelUniqueLabel.AutoSize = true; + this.labelUniqueLabel.Location = new System.Drawing.Point(74, 53); + this.labelUniqueLabel.Name = "labelUniqueLabel"; + this.labelUniqueLabel.Size = new System.Drawing.Size(160, 13); + this.labelUniqueLabel.TabIndex = 10; + this.labelUniqueLabel.Text = "• Unique among project symbols"; + // + // EditDefSymbol + // + this.AcceptButton = this.okButton; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(290, 288); + this.Controls.Add(this.labelUniqueLabel); + this.Controls.Add(this.symbolTypeGroupBox); + this.Controls.Add(this.commentNotesLabel); + this.Controls.Add(this.commentTextBox); + this.Controls.Add(this.commentLabel); + this.Controls.Add(this.valueNotesLabel); + this.Controls.Add(this.valueLabel); + this.Controls.Add(this.labelNotesLabel); + this.Controls.Add(this.valueTextBox); + this.Controls.Add(this.labelTextBox); + this.Controls.Add(this.labelLabel); + this.Controls.Add(this.okButton); + this.Controls.Add(this.cancelButton); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "EditDefSymbol"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Edit Symbol"; + this.Load += new System.EventHandler(this.EditDefSymbol_Load); + this.symbolTypeGroupBox.ResumeLayout(false); + this.symbolTypeGroupBox.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.Label labelLabel; + private System.Windows.Forms.TextBox labelTextBox; + private System.Windows.Forms.TextBox valueTextBox; + private System.Windows.Forms.Label labelNotesLabel; + private System.Windows.Forms.Label valueLabel; + private System.Windows.Forms.Label valueNotesLabel; + private System.Windows.Forms.Label commentLabel; + private System.Windows.Forms.TextBox commentTextBox; + private System.Windows.Forms.Label commentNotesLabel; + private System.Windows.Forms.GroupBox symbolTypeGroupBox; + private System.Windows.Forms.RadioButton constantRadioButton; + private System.Windows.Forms.RadioButton addressRadioButton; + private System.Windows.Forms.Label labelUniqueLabel; + } +} \ No newline at end of file diff --git a/SourceGen/AppForms/EditDefSymbol.cs b/SourceGen/AppForms/EditDefSymbol.cs new file mode 100644 index 0000000..5fafa68 --- /dev/null +++ b/SourceGen/AppForms/EditDefSymbol.cs @@ -0,0 +1,132 @@ +/* + * Copyright 2018 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.Drawing; +using System.Windows.Forms; + +using Asm65; + +namespace SourceGen.AppForms { + public partial class EditDefSymbol : Form { + /// + /// Set to previous value before calling; may be null if creating a new symbol. + /// Will be set to new value on OK result. + /// + public DefSymbol DefSym { get; set; } + + /// + /// Format object to use when formatting addresses and constants. + /// + private Formatter NumFormatter { get; set; } + + /// + /// List of existing symbols, for uniqueness check. The list will not be modified. + /// + private SortedList DefSymbolList { get; set; } + + // Saved off at dialog load time. + private Color mDefaultLabelColor; + + + public EditDefSymbol(Formatter formatter, SortedList defList) { + InitializeComponent(); + + NumFormatter = formatter; + DefSymbolList = defList; + } + + private void EditDefSymbol_Load(object sender, EventArgs e) { + mDefaultLabelColor = labelNotesLabel.ForeColor; + + if (DefSym != null) { + labelTextBox.Text = DefSym.Label; + valueTextBox.Text = NumFormatter.FormatValueInBase(DefSym.Value, + DefSym.DataDescriptor.NumBase); + commentTextBox.Text = DefSym.Comment; + + if (DefSym.SymbolType == Symbol.Type.Constant) { + constantRadioButton.Checked = true; + } else { + addressRadioButton.Checked = true; + } + } else { + addressRadioButton.Checked = true; + } + + UpdateControls(); + } + + private void UpdateControls() { + bool labelValid, labelUnique, valueValid; + + // Label must be valid and not already exist in project symbol list. (It's okay + // if it exists elsewhere.) + labelValid = Asm65.Label.ValidateLabel(labelTextBox.Text); + + if (DefSymbolList.TryGetValue(labelTextBox.Text, out DefSymbol existing)) { + // It's okay if it's the same object. + labelUnique = (existing == DefSym); + } else { + labelUnique = true; + } + + // Value must be blank, meaning "erase any earlier definition", or valid value. + // (Hmm... don't currently have a way to specify "no symbol" in DefSymbol.) + //if (!string.IsNullOrEmpty(valueTextBox.Text)) { + valueValid = ParseValue(out int unused1, out int unused2); + //} else { + // valueValid = true; + //} + + labelNotesLabel.ForeColor = labelValid ? mDefaultLabelColor : Color.Red; + labelUniqueLabel.ForeColor = labelUnique ? mDefaultLabelColor : Color.Red; + valueNotesLabel.ForeColor = valueValid ? mDefaultLabelColor : Color.Red; + + okButton.Enabled = labelValid && labelUnique && valueValid; + } + + private bool ParseValue(out int value, out int numBase) { + string str = valueTextBox.Text; + if (str.IndexOf('/') >= 0) { + // treat as address + numBase = 16; + return Asm65.Address.ParseAddress(str, (1 << 24) - 1, out value); + } else { + return Asm65.Number.TryParseInt(str, out value, out numBase); + } + } + + private void okButton_Click(object sender, EventArgs e) { + bool isConstant = constantRadioButton.Checked; + + ParseValue(out int value, out int numBase); + FormatDescriptor.SubType subType = FormatDescriptor.GetSubTypeForBase(numBase); + DefSym = new DefSymbol(labelTextBox.Text, value, Symbol.Source.Project, + isConstant ? Symbol.Type.Constant : Symbol.Type.ExternalAddr, + subType, commentTextBox.Text, string.Empty); + } + + private void labelTextBox_TextChanged(object sender, EventArgs e) { + UpdateControls(); + } + + private void valueTextBox_TextChanged(object sender, EventArgs e) { + UpdateControls(); + } + } +} diff --git a/SourceGen/AppForms/EditDefSymbol.resx b/SourceGen/AppForms/EditDefSymbol.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/SourceGen/AppForms/EditDefSymbol.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/SourceGen/AppForms/EditLabel.Designer.cs b/SourceGen/AppForms/EditLabel.Designer.cs new file mode 100644 index 0000000..f1ae745 --- /dev/null +++ b/SourceGen/AppForms/EditLabel.Designer.cs @@ -0,0 +1,225 @@ +/* + * Copyright 2018 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. + */ +namespace SourceGen.AppForms { + partial class EditLabel { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.cancelButton = new System.Windows.Forms.Button(); + this.okButton = new System.Windows.Forms.Button(); + this.instructionLabel = new System.Windows.Forms.Label(); + this.labelTextBox = new System.Windows.Forms.TextBox(); + this.maxLengthLabel = new System.Windows.Forms.Label(); + this.firstLetterLabel = new System.Windows.Forms.Label(); + this.validCharsLabel = new System.Windows.Forms.Label(); + this.notDuplicateLabel = new System.Windows.Forms.Label(); + this.labelTypeGroupBox = new System.Windows.Forms.GroupBox(); + this.radioButtonExport = new System.Windows.Forms.RadioButton(); + this.radioButtonGlobal = new System.Windows.Forms.RadioButton(); + this.radioButtonLocal = new System.Windows.Forms.RadioButton(); + this.labelTypeGroupBox.SuspendLayout(); + this.SuspendLayout(); + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(267, 202); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 8; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + // + // okButton + // + this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.okButton.Location = new System.Drawing.Point(186, 202); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(75, 23); + this.okButton.TabIndex = 7; + this.okButton.Text = "OK"; + this.okButton.UseVisualStyleBackColor = true; + this.okButton.Click += new System.EventHandler(this.okButton_Click); + // + // instructionLabel + // + this.instructionLabel.AutoSize = true; + this.instructionLabel.Location = new System.Drawing.Point(13, 13); + this.instructionLabel.Name = "instructionLabel"; + this.instructionLabel.Size = new System.Drawing.Size(60, 13); + this.instructionLabel.TabIndex = 0; + this.instructionLabel.Text = "Enter label:"; + // + // labelTextBox + // + this.labelTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.labelTextBox.Location = new System.Drawing.Point(13, 30); + this.labelTextBox.Name = "labelTextBox"; + this.labelTextBox.Size = new System.Drawing.Size(325, 20); + this.labelTextBox.TabIndex = 1; + this.labelTextBox.TextChanged += new System.EventHandler(this.labelTextBox_TextChanged); + // + // maxLengthLabel + // + this.maxLengthLabel.AutoSize = true; + this.maxLengthLabel.Location = new System.Drawing.Point(13, 57); + this.maxLengthLabel.Name = "maxLengthLabel"; + this.maxLengthLabel.Size = new System.Drawing.Size(251, 13); + this.maxLengthLabel.TabIndex = 2; + this.maxLengthLabel.Text = "• Must be 2-32 characters long (or blank to remove)"; + // + // firstLetterLabel + // + this.firstLetterLabel.AutoSize = true; + this.firstLetterLabel.Location = new System.Drawing.Point(12, 73); + this.firstLetterLabel.Name = "firstLetterLabel"; + this.firstLetterLabel.Size = new System.Drawing.Size(187, 13); + this.firstLetterLabel.TabIndex = 3; + this.firstLetterLabel.Text = "• Must start with a letter or underscore"; + // + // validCharsLabel + // + this.validCharsLabel.AutoSize = true; + this.validCharsLabel.Location = new System.Drawing.Point(12, 89); + this.validCharsLabel.Name = "validCharsLabel"; + this.validCharsLabel.Size = new System.Drawing.Size(297, 13); + this.validCharsLabel.TabIndex = 4; + this.validCharsLabel.Text = "• Valid characters are ASCII letters, numbers, and underscore"; + // + // notDuplicateLabel + // + this.notDuplicateLabel.AutoSize = true; + this.notDuplicateLabel.Location = new System.Drawing.Point(13, 105); + this.notDuplicateLabel.Name = "notDuplicateLabel"; + this.notDuplicateLabel.Size = new System.Drawing.Size(217, 13); + this.notDuplicateLabel.TabIndex = 5; + this.notDuplicateLabel.Text = "• Must not be a duplicate of an existing label"; + // + // labelTypeGroupBox + // + this.labelTypeGroupBox.Controls.Add(this.radioButtonExport); + this.labelTypeGroupBox.Controls.Add(this.radioButtonGlobal); + this.labelTypeGroupBox.Controls.Add(this.radioButtonLocal); + this.labelTypeGroupBox.Location = new System.Drawing.Point(13, 132); + this.labelTypeGroupBox.Name = "labelTypeGroupBox"; + this.labelTypeGroupBox.Size = new System.Drawing.Size(137, 93); + this.labelTypeGroupBox.TabIndex = 6; + this.labelTypeGroupBox.TabStop = false; + this.labelTypeGroupBox.Text = "Label Type"; + // + // radioButtonExport + // + this.radioButtonExport.AutoSize = true; + this.radioButtonExport.Location = new System.Drawing.Point(7, 67); + this.radioButtonExport.Name = "radioButtonExport"; + this.radioButtonExport.Size = new System.Drawing.Size(120, 17); + this.radioButtonExport.TabIndex = 2; + this.radioButtonExport.TabStop = true; + this.radioButtonExport.Text = "Global and &exported"; + this.radioButtonExport.UseVisualStyleBackColor = true; + // + // radioButtonGlobal + // + this.radioButtonGlobal.AutoSize = true; + this.radioButtonGlobal.Location = new System.Drawing.Point(7, 43); + this.radioButtonGlobal.Name = "radioButtonGlobal"; + this.radioButtonGlobal.Size = new System.Drawing.Size(55, 17); + this.radioButtonGlobal.TabIndex = 1; + this.radioButtonGlobal.TabStop = true; + this.radioButtonGlobal.Text = "&Global"; + this.radioButtonGlobal.UseVisualStyleBackColor = true; + // + // radioButtonLocal + // + this.radioButtonLocal.AutoSize = true; + this.radioButtonLocal.Location = new System.Drawing.Point(7, 20); + this.radioButtonLocal.Name = "radioButtonLocal"; + this.radioButtonLocal.Size = new System.Drawing.Size(106, 17); + this.radioButtonLocal.TabIndex = 0; + this.radioButtonLocal.TabStop = true; + this.radioButtonLocal.Text = "&Local (if possible)"; + this.radioButtonLocal.UseVisualStyleBackColor = true; + // + // EditLabel + // + this.AcceptButton = this.okButton; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(354, 237); + this.Controls.Add(this.labelTypeGroupBox); + this.Controls.Add(this.notDuplicateLabel); + this.Controls.Add(this.validCharsLabel); + this.Controls.Add(this.firstLetterLabel); + this.Controls.Add(this.maxLengthLabel); + this.Controls.Add(this.labelTextBox); + this.Controls.Add(this.instructionLabel); + this.Controls.Add(this.okButton); + this.Controls.Add(this.cancelButton); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "EditLabel"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Edit Label"; + this.Load += new System.EventHandler(this.EditLabel_Load); + this.labelTypeGroupBox.ResumeLayout(false); + this.labelTypeGroupBox.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.Label instructionLabel; + private System.Windows.Forms.TextBox labelTextBox; + private System.Windows.Forms.Label maxLengthLabel; + private System.Windows.Forms.Label firstLetterLabel; + private System.Windows.Forms.Label validCharsLabel; + private System.Windows.Forms.Label notDuplicateLabel; + private System.Windows.Forms.GroupBox labelTypeGroupBox; + private System.Windows.Forms.RadioButton radioButtonLocal; + private System.Windows.Forms.RadioButton radioButtonGlobal; + private System.Windows.Forms.RadioButton radioButtonExport; + } +} \ No newline at end of file diff --git a/SourceGen/AppForms/EditLabel.cs b/SourceGen/AppForms/EditLabel.cs new file mode 100644 index 0000000..cc7070c --- /dev/null +++ b/SourceGen/AppForms/EditLabel.cs @@ -0,0 +1,147 @@ +/* + * Copyright 2018 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.Diagnostics; +using System.Drawing; +using System.Windows.Forms; + +namespace SourceGen.AppForms { + public partial class EditLabel : Form { + /// + /// Symbol object. May initially be null. When the dialog completes successfully, + /// this will have the new symbol, or null if the user deleted the label. + /// + public Symbol LabelSym { get; set; } + + /// + /// Address we are editing the label for. + /// + public int Address { private get; set; } + + // Saved off at dialog load time. + private Color mDefaultLabelColor; + + // Reference to DisasmProject's SymbolTable. + private SymbolTable mSymbolTable; + + + public EditLabel(SymbolTable symbolTable) { + InitializeComponent(); + mSymbolTable = symbolTable; + } + + private void EditLabel_Load(object sender, EventArgs e) { + mDefaultLabelColor = maxLengthLabel.ForeColor; + + if (LabelSym == null) { + labelTextBox.Text = string.Empty; + radioButtonLocal.Checked = true; + } else { + labelTextBox.Text = LabelSym.Label; + switch (LabelSym.SymbolType) { + case Symbol.Type.LocalOrGlobalAddr: + radioButtonLocal.Checked = true; + break; + case Symbol.Type.GlobalAddr: + radioButtonGlobal.Checked = true; + break; + case Symbol.Type.GlobalAddrExport: + radioButtonExport.Checked = true; + break; + default: + Debug.Assert(false); // WTF + radioButtonLocal.Checked = true; + break; + } + } + } + + private void labelTextBox_TextChanged(object sender, EventArgs e) { + string str = labelTextBox.Text; + bool valid = true; + + if (str.Length == 1 || str.Length > Asm65.Label.MAX_LABEL_LEN) { + valid = false; + maxLengthLabel.ForeColor = Color.Red; + } else { + maxLengthLabel.ForeColor = mDefaultLabelColor; + } + + // Regex never matches on strings of length 0 or 1, but we don't want + // to complain about that since we're already doing that above. + // TODO(maybe): Ideally this wouldn't light up if the only problem was a + // non-alpha first character, since the next test will call that out. + if (str.Length > 1) { + if (!Asm65.Label.ValidateLabel(str)) { + valid = false; + validCharsLabel.ForeColor = Color.Red; + } else { + validCharsLabel.ForeColor = mDefaultLabelColor; + } + } else { + validCharsLabel.ForeColor = mDefaultLabelColor; + } + + if (str.Length > 0 && + !((str[0] >= 'A' && str[0] <= 'Z') || (str[0] >= 'a' && str[0] <= 'z') || + str[0] == '_')) { + // This should have been caught by the regex. We just want to set the + // color on the "first character must be" instruction text. + Debug.Assert(!valid); + firstLetterLabel.ForeColor = Color.Red; + } else { + firstLetterLabel.ForeColor = mDefaultLabelColor; + } + + // Refuse to continue if the label already exists. The only exception is if + // it's the same symbol, and it's user-defined. (If they're trying to edit an + // auto label, we want to force them to change the name.) + // + // NOTE: if label matching is case-insensitive, we want to allow a situation + // where a label is being renamed from "FOO" to "Foo". We should be able to + // test for object equality on the Symbol to determine if we're renaming a + // symbol to itself. + if (valid && mSymbolTable.TryGetValue(str, out Symbol sym) && + (sym != LabelSym || LabelSym.SymbolSource != Symbol.Source.User)) { + valid = false; + notDuplicateLabel.ForeColor = Color.Red; + } else { + notDuplicateLabel.ForeColor = mDefaultLabelColor; + } + + okButton.Enabled = valid; + } + + private void okButton_Click(object sender, EventArgs e) { + if (string.IsNullOrEmpty(labelTextBox.Text)) { + LabelSym = null; + } else { + Symbol.Type symbolType; + if (radioButtonLocal.Checked) { + symbolType = Symbol.Type.LocalOrGlobalAddr; + } else if (radioButtonGlobal.Checked) { + symbolType = Symbol.Type.GlobalAddr; + } else if (radioButtonExport.Checked) { + symbolType = Symbol.Type.GlobalAddrExport; + } else { + Debug.Assert(false); // WTF + symbolType = Symbol.Type.LocalOrGlobalAddr; + } + LabelSym = new Symbol(labelTextBox.Text, Address, Symbol.Source.User, symbolType); + } + } + } +} diff --git a/SourceGen/AppForms/EditLabel.resx b/SourceGen/AppForms/EditLabel.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/SourceGen/AppForms/EditLabel.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/SourceGen/AppForms/EditLongComment.Designer.cs b/SourceGen/AppForms/EditLongComment.Designer.cs new file mode 100644 index 0000000..0c8b331 --- /dev/null +++ b/SourceGen/AppForms/EditLongComment.Designer.cs @@ -0,0 +1,201 @@ +/* + * Copyright 2018 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. + */ +namespace SourceGen.AppForms { + partial class EditLongComment { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.entryTextBox = new System.Windows.Forms.TextBox(); + this.textEntryLabel = new System.Windows.Forms.Label(); + this.sampleOutputLabel = new System.Windows.Forms.Label(); + this.displayTextBox = new System.Windows.Forms.TextBox(); + this.okButton = new System.Windows.Forms.Button(); + this.cancelButton = new System.Windows.Forms.Button(); + this.maximumWidthLabel = new System.Windows.Forms.Label(); + this.maxWidthComboBox = new System.Windows.Forms.ComboBox(); + this.boxModeCheckBox = new System.Windows.Forms.CheckBox(); + this.SuspendLayout(); + // + // entryTextBox + // + this.entryTextBox.AcceptsReturn = true; + this.entryTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.entryTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.entryTextBox.Location = new System.Drawing.Point(12, 29); + this.entryTextBox.Multiline = true; + this.entryTextBox.Name = "entryTextBox"; + this.entryTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.entryTextBox.Size = new System.Drawing.Size(509, 150); + this.entryTextBox.TabIndex = 0; + this.entryTextBox.Text = "01234567890123456789012345678901234567890123456789012345678901234567890123456789"; + this.entryTextBox.TextChanged += new System.EventHandler(this.entryTextBox_TextChanged); + // + // textEntryLabel + // + this.textEntryLabel.AutoSize = true; + this.textEntryLabel.Location = new System.Drawing.Point(13, 12); + this.textEntryLabel.Name = "textEntryLabel"; + this.textEntryLabel.Size = new System.Drawing.Size(101, 13); + this.textEntryLabel.TabIndex = 1; + this.textEntryLabel.Text = "Enter comment text:"; + // + // sampleOutputLabel + // + this.sampleOutputLabel.AutoSize = true; + this.sampleOutputLabel.Location = new System.Drawing.Point(12, 240); + this.sampleOutputLabel.Name = "sampleOutputLabel"; + this.sampleOutputLabel.Size = new System.Drawing.Size(88, 13); + this.sampleOutputLabel.TabIndex = 5; + this.sampleOutputLabel.Text = "Expected output:"; + // + // displayTextBox + // + this.displayTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.displayTextBox.BackColor = System.Drawing.SystemColors.Window; + this.displayTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.displayTextBox.Location = new System.Drawing.Point(12, 258); + this.displayTextBox.Multiline = true; + this.displayTextBox.Name = "displayTextBox"; + this.displayTextBox.ReadOnly = true; + this.displayTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.displayTextBox.Size = new System.Drawing.Size(509, 163); + this.displayTextBox.TabIndex = 6; + this.displayTextBox.Text = "01234567890123456789012345678901234567890123456789012345678901234567890123456789"; + // + // okButton + // + this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.okButton.Location = new System.Drawing.Point(365, 438); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(75, 23); + this.okButton.TabIndex = 7; + this.okButton.Text = "OK"; + this.okButton.UseVisualStyleBackColor = true; + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(446, 438); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 8; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + // + // maximumWidthLabel + // + this.maximumWidthLabel.AutoSize = true; + this.maximumWidthLabel.Location = new System.Drawing.Point(12, 198); + this.maximumWidthLabel.Name = "maximumWidthLabel"; + this.maximumWidthLabel.Size = new System.Drawing.Size(101, 13); + this.maximumWidthLabel.TabIndex = 2; + this.maximumWidthLabel.Text = "Maximum line width:"; + // + // maxWidthComboBox + // + this.maxWidthComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.maxWidthComboBox.FormattingEnabled = true; + this.maxWidthComboBox.Items.AddRange(new object[] { + "30", + "40", + "64", + "80"}); + this.maxWidthComboBox.Location = new System.Drawing.Point(119, 195); + this.maxWidthComboBox.Name = "maxWidthComboBox"; + this.maxWidthComboBox.Size = new System.Drawing.Size(74, 21); + this.maxWidthComboBox.TabIndex = 3; + this.maxWidthComboBox.SelectedIndexChanged += new System.EventHandler(this.maxWidthComboBox_SelectedIndexChanged); + // + // boxModeCheckBox + // + this.boxModeCheckBox.AutoSize = true; + this.boxModeCheckBox.Location = new System.Drawing.Point(264, 197); + this.boxModeCheckBox.Name = "boxModeCheckBox"; + this.boxModeCheckBox.Size = new System.Drawing.Size(92, 17); + this.boxModeCheckBox.TabIndex = 4; + this.boxModeCheckBox.Text = "Render in box"; + this.boxModeCheckBox.UseVisualStyleBackColor = true; + this.boxModeCheckBox.CheckedChanged += new System.EventHandler(this.boxModeCheckBox_CheckedChanged); + // + // EditLongComment + // + this.AcceptButton = this.okButton; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(534, 473); + this.Controls.Add(this.boxModeCheckBox); + this.Controls.Add(this.maxWidthComboBox); + this.Controls.Add(this.maximumWidthLabel); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.okButton); + this.Controls.Add(this.displayTextBox); + this.Controls.Add(this.sampleOutputLabel); + this.Controls.Add(this.textEntryLabel); + this.Controls.Add(this.entryTextBox); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.MinimumSize = new System.Drawing.Size(550, 512); + this.Name = "EditLongComment"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Edit Long Comment"; + this.HelpButtonClicked += new System.ComponentModel.CancelEventHandler(this.EditLongComment_HelpButtonClicked); + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.EditLongComment_FormClosing); + this.Load += new System.EventHandler(this.EditLongComment_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TextBox entryTextBox; + private System.Windows.Forms.Label textEntryLabel; + private System.Windows.Forms.Label sampleOutputLabel; + private System.Windows.Forms.TextBox displayTextBox; + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.Label maximumWidthLabel; + private System.Windows.Forms.ComboBox maxWidthComboBox; + private System.Windows.Forms.CheckBox boxModeCheckBox; + } +} \ No newline at end of file diff --git a/SourceGen/AppForms/EditLongComment.cs b/SourceGen/AppForms/EditLongComment.cs new file mode 100644 index 0000000..fad944f --- /dev/null +++ b/SourceGen/AppForms/EditLongComment.cs @@ -0,0 +1,130 @@ +/* + * Copyright 2018 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.Linq; +using System.Text; +using System.Windows.Forms; + +namespace SourceGen.AppForms { + public partial class EditLongComment : Form { + /// + /// Get or set the multi-line comment object. On exit, will be set to null if + /// the user wants to delete the comment. + /// + public MultiLineComment LongComment { get; set; } + + private Asm65.Formatter mFormatter; + + + public EditLongComment(Asm65.Formatter formatter) { + InitializeComponent(); + + mFormatter = formatter; + LongComment = new MultiLineComment(string.Empty); + } + + private void EditLongComment_Load(object sender, EventArgs e) { + Debug.Assert(LongComment != null); + entryTextBox.Text = LongComment.Text; + boxModeCheckBox.Checked = LongComment.BoxMode; + + maxWidthComboBox.SelectedIndex = 0; + for (int i = 0; i < maxWidthComboBox.Items.Count; i++) { + string item = (string) maxWidthComboBox.Items[i]; + if (int.Parse(item) == LongComment.MaxWidth) { + maxWidthComboBox.SelectedIndex = i; + break; + } + } + + FormatInput(); + } + + // Handle Ctrl+Enter. + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { + if (keyData == (Keys.Control | Keys.Enter)) { + DialogResult = DialogResult.OK; + Close(); + return true; + } + return base.ProcessCmdKey(ref msg, keyData); + } + + private void EditLongComment_FormClosing(object sender, FormClosingEventArgs e) { + if (string.IsNullOrEmpty(entryTextBox.Text)) { + LongComment = null; + } else { + LongComment = CreateMLC(); + } + } + + private void entryTextBox_TextChanged(object sender, EventArgs e) { + FormatInput(); + } + + private void maxWidthComboBox_SelectedIndexChanged(object sender, EventArgs e) { + FormatInput(); + } + + private void boxModeCheckBox_CheckedChanged(object sender, EventArgs e) { + FormatInput(); + } + + /// + /// Creates a MultiLineComment from the current state of the dialog. + /// + /// New MultiLineComment object. Returns null if the dialog is still + /// in the process of initializing. + private MultiLineComment CreateMLC() { + if (maxWidthComboBox.SelectedItem == null) { + return null; // still initializing + } + return new MultiLineComment(entryTextBox.Text, boxModeCheckBox.Checked, + int.Parse((string)maxWidthComboBox.SelectedItem)); + } + + /// + /// Formats entryTextBox.Text into displayTextBox.Text. + /// + private void FormatInput() { + MultiLineComment mlc = CreateMLC(); + if (mlc == null) { + return; + } + List lines = mlc.FormatText(mFormatter, string.Empty); + + StringBuilder sb = new StringBuilder(entryTextBox.Text.Length + lines.Count * 2); + //sb.AppendFormat("### got {0} lines\r\n", lines.Count); + bool first = true; + foreach (string line in lines) { + if (first) { + first = false; + } else { + sb.Append("\r\n"); + } + sb.Append(line); + } + + displayTextBox.Text = sb.ToString(); + } + + private void EditLongComment_HelpButtonClicked(object sender, System.ComponentModel.CancelEventArgs e) { + HelpAccess.ShowHelp(HelpAccess.Topic.EditLongComment); + } + } +} diff --git a/SourceGen/AppForms/EditLongComment.resx b/SourceGen/AppForms/EditLongComment.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/SourceGen/AppForms/EditLongComment.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/SourceGen/AppForms/EditNote.Designer.cs b/SourceGen/AppForms/EditNote.Designer.cs new file mode 100644 index 0000000..2b52009 --- /dev/null +++ b/SourceGen/AppForms/EditNote.Designer.cs @@ -0,0 +1,221 @@ +/* + * Copyright 2018 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. + */ +namespace SourceGen.AppForms { + partial class EditNote { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.cancelButton = new System.Windows.Forms.Button(); + this.okButton = new System.Windows.Forms.Button(); + this.headerLabel = new System.Windows.Forms.Label(); + this.noteTextBox = new System.Windows.Forms.TextBox(); + this.colorGroupBox = new System.Windows.Forms.GroupBox(); + this.colorOrangeRadio = new System.Windows.Forms.RadioButton(); + this.colorPinkRadio = new System.Windows.Forms.RadioButton(); + this.colorYellowRadio = new System.Windows.Forms.RadioButton(); + this.colorBlueRadio = new System.Windows.Forms.RadioButton(); + this.colorGreenRadio = new System.Windows.Forms.RadioButton(); + this.colorDefaultRadio = new System.Windows.Forms.RadioButton(); + this.colorGroupBox.SuspendLayout(); + this.SuspendLayout(); + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(552, 225); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 3; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + // + // okButton + // + this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.okButton.Location = new System.Drawing.Point(471, 225); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(75, 23); + this.okButton.TabIndex = 2; + this.okButton.Text = "OK"; + this.okButton.UseVisualStyleBackColor = true; + // + // headerLabel + // + this.headerLabel.AutoSize = true; + this.headerLabel.Location = new System.Drawing.Point(13, 13); + this.headerLabel.Name = "headerLabel"; + this.headerLabel.Size = new System.Drawing.Size(59, 13); + this.headerLabel.TabIndex = 0; + this.headerLabel.Text = "Enter note:"; + // + // noteTextBox + // + this.noteTextBox.AcceptsReturn = true; + this.noteTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.noteTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.noteTextBox.Location = new System.Drawing.Point(13, 33); + this.noteTextBox.Multiline = true; + this.noteTextBox.Name = "noteTextBox"; + this.noteTextBox.Size = new System.Drawing.Size(614, 113); + this.noteTextBox.TabIndex = 1; + // + // colorGroupBox + // + this.colorGroupBox.Controls.Add(this.colorOrangeRadio); + this.colorGroupBox.Controls.Add(this.colorPinkRadio); + this.colorGroupBox.Controls.Add(this.colorYellowRadio); + this.colorGroupBox.Controls.Add(this.colorBlueRadio); + this.colorGroupBox.Controls.Add(this.colorGreenRadio); + this.colorGroupBox.Controls.Add(this.colorDefaultRadio); + this.colorGroupBox.Location = new System.Drawing.Point(13, 153); + this.colorGroupBox.Name = "colorGroupBox"; + this.colorGroupBox.Size = new System.Drawing.Size(182, 95); + this.colorGroupBox.TabIndex = 4; + this.colorGroupBox.TabStop = false; + this.colorGroupBox.Text = "Select Highlight Color"; + // + // colorOrangeRadio + // + this.colorOrangeRadio.AutoSize = true; + this.colorOrangeRadio.Location = new System.Drawing.Point(98, 68); + this.colorOrangeRadio.Name = "colorOrangeRadio"; + this.colorOrangeRadio.Size = new System.Drawing.Size(60, 17); + this.colorOrangeRadio.TabIndex = 5; + this.colorOrangeRadio.TabStop = true; + this.colorOrangeRadio.Text = "&Orange"; + this.colorOrangeRadio.UseVisualStyleBackColor = true; + // + // colorPinkRadio + // + this.colorPinkRadio.AutoSize = true; + this.colorPinkRadio.Location = new System.Drawing.Point(98, 44); + this.colorPinkRadio.Name = "colorPinkRadio"; + this.colorPinkRadio.Size = new System.Drawing.Size(46, 17); + this.colorPinkRadio.TabIndex = 4; + this.colorPinkRadio.TabStop = true; + this.colorPinkRadio.Text = "&Pink"; + this.colorPinkRadio.UseVisualStyleBackColor = true; + // + // colorYellowRadio + // + this.colorYellowRadio.AutoSize = true; + this.colorYellowRadio.Location = new System.Drawing.Point(98, 20); + this.colorYellowRadio.Name = "colorYellowRadio"; + this.colorYellowRadio.Size = new System.Drawing.Size(56, 17); + this.colorYellowRadio.TabIndex = 3; + this.colorYellowRadio.TabStop = true; + this.colorYellowRadio.Text = "&Yellow"; + this.colorYellowRadio.UseVisualStyleBackColor = true; + // + // colorBlueRadio + // + this.colorBlueRadio.AutoSize = true; + this.colorBlueRadio.Location = new System.Drawing.Point(7, 68); + this.colorBlueRadio.Name = "colorBlueRadio"; + this.colorBlueRadio.Size = new System.Drawing.Size(46, 17); + this.colorBlueRadio.TabIndex = 2; + this.colorBlueRadio.TabStop = true; + this.colorBlueRadio.Text = "&Blue"; + this.colorBlueRadio.UseVisualStyleBackColor = true; + // + // colorGreenRadio + // + this.colorGreenRadio.AutoSize = true; + this.colorGreenRadio.Location = new System.Drawing.Point(7, 44); + this.colorGreenRadio.Name = "colorGreenRadio"; + this.colorGreenRadio.Size = new System.Drawing.Size(54, 17); + this.colorGreenRadio.TabIndex = 1; + this.colorGreenRadio.TabStop = true; + this.colorGreenRadio.Text = "&Green"; + this.colorGreenRadio.UseVisualStyleBackColor = true; + // + // colorDefaultRadio + // + this.colorDefaultRadio.AutoSize = true; + this.colorDefaultRadio.Location = new System.Drawing.Point(7, 20); + this.colorDefaultRadio.Name = "colorDefaultRadio"; + this.colorDefaultRadio.Size = new System.Drawing.Size(51, 17); + this.colorDefaultRadio.TabIndex = 0; + this.colorDefaultRadio.TabStop = true; + this.colorDefaultRadio.Text = "&None"; + this.colorDefaultRadio.UseVisualStyleBackColor = true; + // + // EditNote + // + this.AcceptButton = this.okButton; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(639, 260); + this.Controls.Add(this.colorGroupBox); + this.Controls.Add(this.noteTextBox); + this.Controls.Add(this.headerLabel); + this.Controls.Add(this.okButton); + this.Controls.Add(this.cancelButton); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "EditNote"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Edit Note"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.EditNote_FormClosing); + this.Load += new System.EventHandler(this.EditNote_Load); + this.colorGroupBox.ResumeLayout(false); + this.colorGroupBox.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.Label headerLabel; + private System.Windows.Forms.TextBox noteTextBox; + private System.Windows.Forms.GroupBox colorGroupBox; + private System.Windows.Forms.RadioButton colorOrangeRadio; + private System.Windows.Forms.RadioButton colorPinkRadio; + private System.Windows.Forms.RadioButton colorYellowRadio; + private System.Windows.Forms.RadioButton colorBlueRadio; + private System.Windows.Forms.RadioButton colorGreenRadio; + private System.Windows.Forms.RadioButton colorDefaultRadio; + } +} \ No newline at end of file diff --git a/SourceGen/AppForms/EditNote.cs b/SourceGen/AppForms/EditNote.cs new file mode 100644 index 0000000..ca411ac --- /dev/null +++ b/SourceGen/AppForms/EditNote.cs @@ -0,0 +1,104 @@ +/* + * Copyright 2018 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.Diagnostics; +using System.Drawing; +using System.Windows.Forms; + +namespace SourceGen.AppForms { + public partial class EditNote : Form { + /// + /// Get or set the note object. On exit, will be set to null if the user wants + /// to delete the note. + /// + public MultiLineComment Note { get; set; } + + // Highlight color palette. Unless the user has funky theme, the color will be + // replacing a white background, and will be overlaid with black text, so should + // be on the lighter end of the spectrum. + private enum ColorList { + None = 0, Green, Blue, Yellow, Pink, Orange + } + private static Color[] sColors = new Color[] { + Color.FromArgb(0), // None + Color.LightGreen, + Color.LightBlue, + Color.Yellow, //LightGoldenrodYellow, + Color.LightPink, + Color.Orange + }; + private RadioButton[] mColorButtons; + + + public EditNote() { + InitializeComponent(); + Note = new MultiLineComment(string.Empty); + } + + private void EditNote_Load(object sender, EventArgs e) { + noteTextBox.Text = Note.Text; + + mColorButtons = new RadioButton[] { + colorDefaultRadio, + colorGreenRadio, + colorBlueRadio, + colorYellowRadio, + colorPinkRadio, + colorOrangeRadio + }; + Debug.Assert(mColorButtons.Length == sColors.Length); + + // Configure radio buttons. + colorDefaultRadio.Checked = true; + if (Note != null) { + Color curColor = Note.BackgroundColor; + for (int i = 0; i < sColors.Length; i++) { + // Can't just compare colors, because the sColors entries are "known" and + // have some additional properties set. Comparing the RGB values works. + if (sColors[i].ToArgb() == curColor.ToArgb()) { + mColorButtons[i].Checked = true; + break; + } + } + } + } + + // Handle Ctrl+Enter. + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { + if (keyData == (Keys.Control | Keys.Enter)) { + DialogResult = DialogResult.OK; + Close(); + return true; + } + return base.ProcessCmdKey(ref msg, keyData); + } + + private void EditNote_FormClosing(object sender, FormClosingEventArgs e) { + if (string.IsNullOrEmpty(noteTextBox.Text)) { + Note = null; + } else { + Color bkgndColor = Color.Fuchsia; + for (int i = 0; i < mColorButtons.Length; i++) { + if (mColorButtons[i].Checked) { + bkgndColor = sColors[i]; + break; + } + } + Note = new MultiLineComment(noteTextBox.Text, bkgndColor); + } + } + } +} diff --git a/SourceGen/AppForms/EditNote.resx b/SourceGen/AppForms/EditNote.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/SourceGen/AppForms/EditNote.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/SourceGen/AppForms/EditOperand.Designer.cs b/SourceGen/AppForms/EditOperand.Designer.cs new file mode 100644 index 0000000..2d29147 --- /dev/null +++ b/SourceGen/AppForms/EditOperand.Designer.cs @@ -0,0 +1,382 @@ +/* + * Copyright 2018 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. + */ +namespace SourceGen.AppForms { + partial class EditOperand { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.cancelButton = new System.Windows.Forms.Button(); + this.okButton = new System.Windows.Forms.Button(); + this.mainRadioPanel = new System.Windows.Forms.Panel(); + this.symbolPartPanel = new System.Windows.Forms.Panel(); + this.radioButtonBank = new System.Windows.Forms.RadioButton(); + this.radioButtonHigh = new System.Windows.Forms.RadioButton(); + this.radioButtonLow = new System.Windows.Forms.RadioButton(); + this.symbolTextBox = new System.Windows.Forms.TextBox(); + this.radioButtonSymbol = new System.Windows.Forms.RadioButton(); + this.radioButtonAscii = new System.Windows.Forms.RadioButton(); + this.radioButtonBinary = new System.Windows.Forms.RadioButton(); + this.radioButtonDecimal = new System.Windows.Forms.RadioButton(); + this.radioButtonHex = new System.Windows.Forms.RadioButton(); + this.radioButtonDefault = new System.Windows.Forms.RadioButton(); + this.selectFormatLabel = new System.Windows.Forms.Label(); + this.previewLabel = new System.Windows.Forms.Label(); + this.previewTextBox = new System.Windows.Forms.TextBox(); + this.symbolShortcutsGroupBox = new System.Windows.Forms.GroupBox(); + this.operandAndProjRadioButton = new System.Windows.Forms.RadioButton(); + this.operandAndLabelRadioButton = new System.Windows.Forms.RadioButton(); + this.labelInsteadRadioButton = new System.Windows.Forms.RadioButton(); + this.operandOnlyRadioButton = new System.Windows.Forms.RadioButton(); + this.mainRadioPanel.SuspendLayout(); + this.symbolPartPanel.SuspendLayout(); + this.symbolShortcutsGroupBox.SuspendLayout(); + this.SuspendLayout(); + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(199, 377); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 4; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + // + // okButton + // + this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.okButton.Location = new System.Drawing.Point(118, 377); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(75, 23); + this.okButton.TabIndex = 3; + this.okButton.Text = "OK"; + this.okButton.UseVisualStyleBackColor = true; + this.okButton.Click += new System.EventHandler(this.okButton_Click); + // + // mainRadioPanel + // + this.mainRadioPanel.Controls.Add(this.symbolPartPanel); + this.mainRadioPanel.Controls.Add(this.symbolTextBox); + this.mainRadioPanel.Controls.Add(this.radioButtonSymbol); + this.mainRadioPanel.Controls.Add(this.radioButtonAscii); + this.mainRadioPanel.Controls.Add(this.radioButtonBinary); + this.mainRadioPanel.Controls.Add(this.radioButtonDecimal); + this.mainRadioPanel.Controls.Add(this.radioButtonHex); + this.mainRadioPanel.Controls.Add(this.radioButtonDefault); + this.mainRadioPanel.Location = new System.Drawing.Point(12, 29); + this.mainRadioPanel.Name = "mainRadioPanel"; + this.mainRadioPanel.Size = new System.Drawing.Size(261, 173); + this.mainRadioPanel.TabIndex = 0; + // + // symbolPartPanel + // + this.symbolPartPanel.Controls.Add(this.radioButtonBank); + this.symbolPartPanel.Controls.Add(this.radioButtonHigh); + this.symbolPartPanel.Controls.Add(this.radioButtonLow); + this.symbolPartPanel.Location = new System.Drawing.Point(69, 150); + this.symbolPartPanel.Name = "symbolPartPanel"; + this.symbolPartPanel.Size = new System.Drawing.Size(154, 18); + this.symbolPartPanel.TabIndex = 7; + // + // radioButtonBank + // + this.radioButtonBank.AutoSize = true; + this.radioButtonBank.Location = new System.Drawing.Point(100, 0); + this.radioButtonBank.Name = "radioButtonBank"; + this.radioButtonBank.Size = new System.Drawing.Size(50, 17); + this.radioButtonBank.TabIndex = 2; + this.radioButtonBank.TabStop = true; + this.radioButtonBank.Text = "&Bank"; + this.radioButtonBank.UseVisualStyleBackColor = true; + this.radioButtonBank.CheckedChanged += new System.EventHandler(this.PartGroup_CheckedChanged); + // + // radioButtonHigh + // + this.radioButtonHigh.AutoSize = true; + this.radioButtonHigh.Location = new System.Drawing.Point(50, 0); + this.radioButtonHigh.Name = "radioButtonHigh"; + this.radioButtonHigh.Size = new System.Drawing.Size(47, 17); + this.radioButtonHigh.TabIndex = 1; + this.radioButtonHigh.TabStop = true; + this.radioButtonHigh.Text = "&High"; + this.radioButtonHigh.UseVisualStyleBackColor = true; + this.radioButtonHigh.CheckedChanged += new System.EventHandler(this.PartGroup_CheckedChanged); + // + // radioButtonLow + // + this.radioButtonLow.AutoSize = true; + this.radioButtonLow.Location = new System.Drawing.Point(0, 0); + this.radioButtonLow.Name = "radioButtonLow"; + this.radioButtonLow.Size = new System.Drawing.Size(45, 17); + this.radioButtonLow.TabIndex = 0; + this.radioButtonLow.TabStop = true; + this.radioButtonLow.Text = "&Low"; + this.radioButtonLow.UseVisualStyleBackColor = true; + this.radioButtonLow.CheckedChanged += new System.EventHandler(this.PartGroup_CheckedChanged); + // + // symbolTextBox + // + this.symbolTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.symbolTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.symbolTextBox.Location = new System.Drawing.Point(69, 124); + this.symbolTextBox.MaxLength = 128; + this.symbolTextBox.Name = "symbolTextBox"; + this.symbolTextBox.Size = new System.Drawing.Size(179, 20); + this.symbolTextBox.TabIndex = 6; + this.symbolTextBox.TextChanged += new System.EventHandler(this.symbolTextBox_TextChanged); + // + // radioButtonSymbol + // + this.radioButtonSymbol.AutoSize = true; + this.radioButtonSymbol.Location = new System.Drawing.Point(4, 124); + this.radioButtonSymbol.Name = "radioButtonSymbol"; + this.radioButtonSymbol.Size = new System.Drawing.Size(59, 17); + this.radioButtonSymbol.TabIndex = 5; + this.radioButtonSymbol.TabStop = true; + this.radioButtonSymbol.Text = "&Symbol"; + this.radioButtonSymbol.UseVisualStyleBackColor = true; + this.radioButtonSymbol.CheckedChanged += new System.EventHandler(this.MainGroup_CheckedChanged); + // + // radioButtonAscii + // + this.radioButtonAscii.AutoSize = true; + this.radioButtonAscii.Location = new System.Drawing.Point(4, 100); + this.radioButtonAscii.Name = "radioButtonAscii"; + this.radioButtonAscii.Size = new System.Drawing.Size(100, 17); + this.radioButtonAscii.TabIndex = 4; + this.radioButtonAscii.TabStop = true; + this.radioButtonAscii.Text = "&ASCII character"; + this.radioButtonAscii.UseVisualStyleBackColor = true; + this.radioButtonAscii.CheckedChanged += new System.EventHandler(this.MainGroup_CheckedChanged); + // + // radioButtonBinary + // + this.radioButtonBinary.AutoSize = true; + this.radioButtonBinary.Location = new System.Drawing.Point(4, 76); + this.radioButtonBinary.Name = "radioButtonBinary"; + this.radioButtonBinary.Size = new System.Drawing.Size(54, 17); + this.radioButtonBinary.TabIndex = 3; + this.radioButtonBinary.TabStop = true; + this.radioButtonBinary.Text = "&Binary"; + this.radioButtonBinary.UseVisualStyleBackColor = true; + this.radioButtonBinary.CheckedChanged += new System.EventHandler(this.MainGroup_CheckedChanged); + // + // radioButtonDecimal + // + this.radioButtonDecimal.AutoSize = true; + this.radioButtonDecimal.Location = new System.Drawing.Point(4, 52); + this.radioButtonDecimal.Name = "radioButtonDecimal"; + this.radioButtonDecimal.Size = new System.Drawing.Size(63, 17); + this.radioButtonDecimal.TabIndex = 2; + this.radioButtonDecimal.TabStop = true; + this.radioButtonDecimal.Text = "&Decimal"; + this.radioButtonDecimal.UseVisualStyleBackColor = true; + this.radioButtonDecimal.CheckedChanged += new System.EventHandler(this.MainGroup_CheckedChanged); + // + // radioButtonHex + // + this.radioButtonHex.AutoSize = true; + this.radioButtonHex.Location = new System.Drawing.Point(4, 28); + this.radioButtonHex.Name = "radioButtonHex"; + this.radioButtonHex.Size = new System.Drawing.Size(86, 17); + this.radioButtonHex.TabIndex = 1; + this.radioButtonHex.TabStop = true; + this.radioButtonHex.Text = "&Hexadecimal"; + this.radioButtonHex.UseVisualStyleBackColor = true; + this.radioButtonHex.CheckedChanged += new System.EventHandler(this.MainGroup_CheckedChanged); + // + // radioButtonDefault + // + this.radioButtonDefault.AutoSize = true; + this.radioButtonDefault.Location = new System.Drawing.Point(4, 4); + this.radioButtonDefault.Name = "radioButtonDefault"; + this.radioButtonDefault.Size = new System.Drawing.Size(59, 17); + this.radioButtonDefault.TabIndex = 0; + this.radioButtonDefault.TabStop = true; + this.radioButtonDefault.Text = "&Default"; + this.radioButtonDefault.UseVisualStyleBackColor = true; + this.radioButtonDefault.CheckedChanged += new System.EventHandler(this.MainGroup_CheckedChanged); + // + // selectFormatLabel + // + this.selectFormatLabel.AutoSize = true; + this.selectFormatLabel.Location = new System.Drawing.Point(13, 13); + this.selectFormatLabel.Name = "selectFormatLabel"; + this.selectFormatLabel.Size = new System.Drawing.Size(114, 13); + this.selectFormatLabel.TabIndex = 0; + this.selectFormatLabel.Text = "Select operand format:"; + // + // previewLabel + // + this.previewLabel.AutoSize = true; + this.previewLabel.Location = new System.Drawing.Point(11, 210); + this.previewLabel.Name = "previewLabel"; + this.previewLabel.Size = new System.Drawing.Size(48, 13); + this.previewLabel.TabIndex = 1; + this.previewLabel.Text = "Preview:"; + // + // previewTextBox + // + this.previewTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.previewTextBox.Enabled = false; + this.previewTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.previewTextBox.Location = new System.Drawing.Point(66, 208); + this.previewTextBox.Name = "previewTextBox"; + this.previewTextBox.Size = new System.Drawing.Size(207, 20); + this.previewTextBox.TabIndex = 2; + // + // symbolShortcutsGroupBox + // + this.symbolShortcutsGroupBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.symbolShortcutsGroupBox.Controls.Add(this.operandAndProjRadioButton); + this.symbolShortcutsGroupBox.Controls.Add(this.operandAndLabelRadioButton); + this.symbolShortcutsGroupBox.Controls.Add(this.labelInsteadRadioButton); + this.symbolShortcutsGroupBox.Controls.Add(this.operandOnlyRadioButton); + this.symbolShortcutsGroupBox.Location = new System.Drawing.Point(13, 248); + this.symbolShortcutsGroupBox.Name = "symbolShortcutsGroupBox"; + this.symbolShortcutsGroupBox.Size = new System.Drawing.Size(261, 118); + this.symbolShortcutsGroupBox.TabIndex = 5; + this.symbolShortcutsGroupBox.TabStop = false; + this.symbolShortcutsGroupBox.Text = "Symbol Shortcuts"; + // + // operandAndProjRadioButton + // + this.operandAndProjRadioButton.AutoSize = true; + this.operandAndProjRadioButton.Location = new System.Drawing.Point(7, 92); + this.operandAndProjRadioButton.Name = "operandAndProjRadioButton"; + this.operandAndProjRadioButton.Size = new System.Drawing.Size(212, 17); + this.operandAndProjRadioButton.TabIndex = 3; + this.operandAndProjRadioButton.TabStop = true; + this.operandAndProjRadioButton.Text = "Set operand AND create &project symbol"; + this.operandAndProjRadioButton.UseVisualStyleBackColor = true; + // + // operandAndLabelRadioButton + // + this.operandAndLabelRadioButton.AutoSize = true; + this.operandAndLabelRadioButton.Location = new System.Drawing.Point(7, 68); + this.operandAndLabelRadioButton.Name = "operandAndLabelRadioButton"; + this.operandAndLabelRadioButton.Size = new System.Drawing.Size(249, 17); + this.operandAndLabelRadioButton.TabIndex = 2; + this.operandAndLabelRadioButton.TabStop = true; + this.operandAndLabelRadioButton.Text = "Set &operand AND create label at target address"; + this.operandAndLabelRadioButton.UseVisualStyleBackColor = true; + // + // labelInsteadRadioButton + // + this.labelInsteadRadioButton.AutoSize = true; + this.labelInsteadRadioButton.Location = new System.Drawing.Point(7, 44); + this.labelInsteadRadioButton.Name = "labelInsteadRadioButton"; + this.labelInsteadRadioButton.Size = new System.Drawing.Size(200, 17); + this.labelInsteadRadioButton.TabIndex = 1; + this.labelInsteadRadioButton.TabStop = true; + this.labelInsteadRadioButton.Text = "&Create label at target address instead"; + this.labelInsteadRadioButton.UseVisualStyleBackColor = true; + // + // operandOnlyRadioButton + // + this.operandOnlyRadioButton.AutoSize = true; + this.operandOnlyRadioButton.Location = new System.Drawing.Point(7, 20); + this.operandOnlyRadioButton.Name = "operandOnlyRadioButton"; + this.operandOnlyRadioButton.Size = new System.Drawing.Size(162, 17); + this.operandOnlyRadioButton.TabIndex = 0; + this.operandOnlyRadioButton.TabStop = true; + this.operandOnlyRadioButton.Text = "&Just set the operand (default)"; + this.operandOnlyRadioButton.UseVisualStyleBackColor = true; + // + // EditOperand + // + this.AcceptButton = this.okButton; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(286, 412); + this.Controls.Add(this.symbolShortcutsGroupBox); + this.Controls.Add(this.previewTextBox); + this.Controls.Add(this.previewLabel); + this.Controls.Add(this.selectFormatLabel); + this.Controls.Add(this.mainRadioPanel); + this.Controls.Add(this.okButton); + this.Controls.Add(this.cancelButton); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "EditOperand"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Edit Operand"; + this.Load += new System.EventHandler(this.EditOperand_Load); + this.Shown += new System.EventHandler(this.EditOperand_Shown); + this.mainRadioPanel.ResumeLayout(false); + this.mainRadioPanel.PerformLayout(); + this.symbolPartPanel.ResumeLayout(false); + this.symbolPartPanel.PerformLayout(); + this.symbolShortcutsGroupBox.ResumeLayout(false); + this.symbolShortcutsGroupBox.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.Panel mainRadioPanel; + private System.Windows.Forms.TextBox symbolTextBox; + private System.Windows.Forms.RadioButton radioButtonSymbol; + private System.Windows.Forms.RadioButton radioButtonAscii; + private System.Windows.Forms.RadioButton radioButtonBinary; + private System.Windows.Forms.RadioButton radioButtonDecimal; + private System.Windows.Forms.RadioButton radioButtonHex; + private System.Windows.Forms.RadioButton radioButtonDefault; + private System.Windows.Forms.Label selectFormatLabel; + private System.Windows.Forms.Label previewLabel; + private System.Windows.Forms.TextBox previewTextBox; + private System.Windows.Forms.Panel symbolPartPanel; + private System.Windows.Forms.RadioButton radioButtonBank; + private System.Windows.Forms.RadioButton radioButtonHigh; + private System.Windows.Forms.RadioButton radioButtonLow; + private System.Windows.Forms.GroupBox symbolShortcutsGroupBox; + private System.Windows.Forms.RadioButton operandAndProjRadioButton; + private System.Windows.Forms.RadioButton operandAndLabelRadioButton; + private System.Windows.Forms.RadioButton labelInsteadRadioButton; + private System.Windows.Forms.RadioButton operandOnlyRadioButton; + } +} \ No newline at end of file diff --git a/SourceGen/AppForms/EditOperand.cs b/SourceGen/AppForms/EditOperand.cs new file mode 100644 index 0000000..4f2a4de --- /dev/null +++ b/SourceGen/AppForms/EditOperand.cs @@ -0,0 +1,531 @@ +/* + * Copyright 2018 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.Diagnostics; +using System.Text; +using System.Windows.Forms; + +using Asm65; + +namespace SourceGen.AppForms { + public partial class EditOperand : Form { + /// + /// In/out. May be null on entry if the offset doesn't have a format descriptor + /// specified. Will be null on exit if "default" is selected. + /// + public FormatDescriptor FormatDescriptor { get; set; } + + public enum SymbolShortcutAction { + None = 0, CreateLabelInstead, CreateLabelAlso, CreateProjectSymbolAlso + } + + /// + /// Remember the last option we used. + /// + private static SymbolShortcutAction sLastAction = SymbolShortcutAction.None; + + /// + /// On OK dialog exit, specifies that an additional action should be taken. + /// + public SymbolShortcutAction ShortcutAction { get; private set; } + + /// + /// Additional argument, meaning dependent on ShortcutAction. This will either be + /// the target label offset or the project symbol value. + /// + public int ShortcutArg { get; private set; } + + /// + /// Width of full instruction, including opcode. + /// + private int mInstructionLength; + + /// + /// Number of hexadecimal digits to show in the preview. Sometimes you want + /// to force this to be longer or shorter than InstructionLength would indicate, + /// e.g. "BRA $1000" has a 1-byte operand. + /// + private int mPreviewHexDigits; + + /// + /// Operand value, extracted from file data. For a relative branch, this will be + /// an address instead. Only used for preview window. + /// + private int mOperandValue; + + /// + /// Is the operand an immediate value? If so, we enable the symbol part selection. + /// + private bool mIsExtendedImmediate; + + /// + /// Is the operand a PC relative offset? + /// + private bool mIsPcRelative; + + /// + /// Special handling for block move instructions (MVN/MVP). + /// + private bool mIsBlockMove; + + /// + /// If set, show a '#' in the preview indow. + /// + private bool mShowHashPrefix; + + ///// + ///// Symbol table to use when resolving symbolic values. + ///// + //private SymbolTable SymbolTable { get; set; } + + /// + /// Project reference. + /// + private DisasmProject mProject; + + /// + /// Formatter to use when displaying addresses and hex values. + /// + private Formatter mFormatter; + + /// + /// Copy of operand Anattribs. + /// + private Anattrib mAttr; + + /// + /// Set this during initial control configuration, so we know to ignore the CheckedChanged + /// events. + /// + private bool mIsInitialSetup; + + /// + /// Set to true if the user has entered a symbol that matches an auto-generated symbol. + /// + private bool mIsSymbolAuto; + + + public EditOperand(int offset, DisasmProject project, Asm65.Formatter formatter) { + InitializeComponent(); + + mProject = project; + mFormatter = formatter; + + // Configure the appearance. + mAttr = mProject.GetAnattrib(offset); + OpDef op = mProject.CpuDef.GetOpDef(mProject.FileData[offset]); + mInstructionLength = mAttr.Length; + mPreviewHexDigits = (mAttr.Length - 1) * 2; + if (mAttr.OperandAddress >= 0) { + // Use this as the operand value when available. This lets us present + // relative branch instructions in the expected form. + mOperandValue = mAttr.OperandAddress; + + if (op.AddrMode == OpDef.AddressMode.PCRel) { + mPreviewHexDigits = 4; + mIsPcRelative = true; + } else if (op.AddrMode == OpDef.AddressMode.PCRelLong || + op.AddrMode == OpDef.AddressMode.StackPCRelLong) { + mIsPcRelative = true; + } + } else { + int opVal = op.GetOperand(mProject.FileData, offset, mAttr.StatusFlags); + mOperandValue = opVal; + if (op.AddrMode == OpDef.AddressMode.BlockMove) { + // MVN and MVP screw things up by having two operands in one instruction. + // We deal with this by passing in the value from the second byte + // (source bank) as the value, and applying the chosen format to both bytes. + mIsBlockMove = true; + mOperandValue = opVal >> 8; + mPreviewHexDigits = 2; + } + } + mIsExtendedImmediate = op.IsExtendedImmediate; // Imm, PEA, MVN/MVP + mShowHashPrefix = op.IsImmediate; // just Imm + } + + private void EditOperand_Load(object sender, EventArgs e) { + mIsInitialSetup = true; + + // Can this be represented as high or low ASCII? + radioButtonAscii.Enabled = CommonUtil.TextUtil.IsHiLoAscii(mOperandValue); + + // Configure the dialog from the FormatDescriptor, if one is available. + SetControlsFromDescriptor(FormatDescriptor); + + // Do this whether or not symbol is checked -- want to have this set when the + // dialog is initially in default format. + switch (sLastAction) { + case SymbolShortcutAction.CreateLabelInstead: + labelInsteadRadioButton.Checked = true; + break; + case SymbolShortcutAction.CreateLabelAlso: + operandAndLabelRadioButton.Checked = true; + break; + case SymbolShortcutAction.CreateProjectSymbolAlso: + operandAndProjRadioButton.Checked = true; + break; + default: + operandOnlyRadioButton.Checked = true; + break; + } + + mIsInitialSetup = false; + UpdateControls(); + } + + private void EditOperand_Shown(object sender, EventArgs e) { + // Start with the focus in the text box. This way they can start typing + // immediately. + symbolTextBox.Focus(); + } + + private void symbolTextBox_TextChanged(object sender, EventArgs e) { + // Make sure Symbol is checked if they're typing text in. + radioButtonSymbol.Checked = true; + UpdateControls(); + } + + /// + /// Handles CheckedChanged event for all radio buttons in main group. + /// + private void MainGroup_CheckedChanged(object sender, EventArgs e) { + // Enable/disable the low/high/bank radio group. + // Update preview window. + UpdateControls(); + } + + /// + /// Handles CheckedChanged event for all radio buttons in symbol-part group. + /// + private void PartGroup_CheckedChanged(object sender, EventArgs e) { + // Update preview window. + UpdateControls(); + } + + private void okButton_Click(object sender, EventArgs e) { + FormatDescriptor = CreateDescriptorFromControls(); + + // + // Extract the current shortcut action. For dialog configuration purposes we + // want to capture the current state. For the caller, we force it to "none" + // if we're not using a symbol format. + // + SymbolShortcutAction action = SymbolShortcutAction.None; + if (labelInsteadRadioButton.Checked) { + action = SymbolShortcutAction.CreateLabelInstead; + } else if (operandAndLabelRadioButton.Checked) { + action = SymbolShortcutAction.CreateLabelAlso; + } else if (operandAndProjRadioButton.Checked) { + action = SymbolShortcutAction.CreateProjectSymbolAlso; + } else if (operandOnlyRadioButton.Checked) { + action = SymbolShortcutAction.None; + } else { + Debug.Assert(false); + action = SymbolShortcutAction.None; + } + sLastAction = action; + + if (radioButtonSymbol.Checked && FormatDescriptor != null) { + // Only report a shortcut action if they've entered a symbol. If they + // checked symbol but left the field blank, they're just trying to delete + // the format. + ShortcutAction = action; + } else { + ShortcutAction = SymbolShortcutAction.None; + } + } + + /// + /// Updates all of the controls to reflect the current internal state. + /// + private void UpdateControls() { + if (mIsInitialSetup) { + return; + } + symbolPartPanel.Enabled = radioButtonSymbol.Checked && mIsExtendedImmediate; + symbolShortcutsGroupBox.Enabled = radioButtonSymbol.Checked; + + SetPreviewText(); + + bool isOk = true; + if (radioButtonSymbol.Checked) { + // Just check for correct format. References to non-existent labels are allowed. + // + // We try to block references to auto labels, but it's possible to get around it + // (replace auto label with user label, reference non-existent auto label, + // remove user label). We could try harder, but currently not necessary. + isOk = !mIsSymbolAuto && Asm65.Label.ValidateLabel(symbolTextBox.Text); + + // Allow empty strings as a way to delete the label and return to "default". + if (string.IsNullOrEmpty(symbolTextBox.Text)) { + isOk = true; + } + + ConfigureSymbolShortcuts(); + } + okButton.Enabled = isOk; + } + + /// + /// Sets the text displayed in the "preview" text box. + /// + private void SetPreviewText() { + //symbolValueLabel.Text = string.Empty; + mIsSymbolAuto = false; + + FormatDescriptor dfd = CreateDescriptorFromControls(); + if (dfd == null) { + // Default format. We can't actually know what this look like, so just + // clear the box. + previewTextBox.Text = string.Empty; + return; + } + + if (dfd.FormatSubType == FormatDescriptor.SubType.Symbol && + string.IsNullOrEmpty(dfd.SymbolRef.Label)) { + // no label yet, nothing to show + previewTextBox.Text = string.Empty; + return; + } + + StringBuilder preview = new StringBuilder(); + if (mShowHashPrefix) { + preview.Append('#'); + } + + switch (dfd.FormatSubType) { + case FormatDescriptor.SubType.Hex: + preview.Append(mFormatter.FormatHexValue(mOperandValue, mPreviewHexDigits)); + break; + case FormatDescriptor.SubType.Decimal: + preview.Append(mFormatter.FormatDecimalValue(mOperandValue)); + break; + case FormatDescriptor.SubType.Binary: + preview.Append(mFormatter.FormatBinaryValue(mOperandValue, 8)); + break; + case FormatDescriptor.SubType.Ascii: + preview.Append(mFormatter.FormatAsciiOrHex(mOperandValue)); + break; + case FormatDescriptor.SubType.Symbol: + if (mProject.SymbolTable.TryGetValue(dfd.SymbolRef.Label, out Symbol sym)) { + if (mIsBlockMove) { + // For a 24-bit symbol, we grab the high byte. This is the + // expected behavior, according to Eyes & Lichty; see the + // explanation of the MVP instruction. For an 8-bit symbol + // the assembler just takes the value. + // TODO(someday): allow a different symbol for each part of the + // operand. + if (sym.Value > 0xff) { + radioButtonBank.Checked = true; + } else { + radioButtonLow.Checked = true; + } + dfd = CreateDescriptorFromControls(); + } + + // Hack to make relative branches look right in the preview window. + // Otherwise they show up like " + /// Configures the buttons in the "symbol shortcuts" group box. The entire box is + /// disabled unless "symbol" is selected. Other options are selectively enabled or + /// disabled as appropriate for the current input. If we disable the selection option, + /// the selection will be reset to default. + /// + private void ConfigureSymbolShortcuts() { + // operandOnlyRadioButton: always enabled + // labelInsteadRadioButton: symbol is unknown and operand address has no label + // operandAndLabelRadioButton: same as labelInstead + // operandAndProjRadioButton: symbol is unknown and operand address is outside project + + string labelStr = symbolTextBox.Text; + ShortcutArg = -1; + + // Is this a known symbol? If so, disable most options and bail. + if (mProject.SymbolTable.TryGetValue(labelStr, out Symbol sym)) { + labelInsteadRadioButton.Enabled = operandAndLabelRadioButton.Enabled = + operandAndProjRadioButton.Enabled = false; + operandOnlyRadioButton.Checked = true; + return; + } + + if (mAttr.OperandOffset >= 0) { + // Operand target is inside the file. Does the target offset already have a label? + bool hasLabel = mProject.UserLabels.ContainsKey(mAttr.OperandOffset); + labelInsteadRadioButton.Enabled = operandAndLabelRadioButton.Enabled = + !hasLabel; + operandAndProjRadioButton.Enabled = false; + ShortcutArg = mAttr.OperandOffset; + } else if (mAttr.OperandAddress >= 0) { + // Operand target is outside the file. + labelInsteadRadioButton.Enabled = operandAndLabelRadioButton.Enabled = false; + operandAndProjRadioButton.Enabled = true; + ShortcutArg = mAttr.OperandAddress; + } else { + // Probably an immediate operand. + labelInsteadRadioButton.Enabled = operandAndLabelRadioButton.Enabled = + operandAndProjRadioButton.Enabled = false; + } + + // Select the default option if the currently-selected option is no longer available. + if ((labelInsteadRadioButton.Checked && !labelInsteadRadioButton.Enabled) || + (operandAndLabelRadioButton.Checked && !operandAndLabelRadioButton.Enabled) || + (operandAndProjRadioButton.Checked && !operandAndProjRadioButton.Enabled)) { + operandOnlyRadioButton.Checked = true; + } + } + + /// + /// Configures the dialog controls based on the provided format descriptor. + /// + /// FormatDescriptor to use. + private void SetControlsFromDescriptor(FormatDescriptor dfd) { + Debug.Assert(mIsInitialSetup); + radioButtonLow.Checked = true; + + if (dfd == null) { + radioButtonDefault.Checked = true; + return; + } + + // NOTE: it's entirely possible to have a weird format (e.g. string) if the + // instruction used to be hinted as data. Handle it gracefully. + switch (dfd.FormatType) { + case FormatDescriptor.Type.NumericLE: + switch (dfd.FormatSubType) { + case FormatDescriptor.SubType.Hex: + radioButtonHex.Checked = true; + break; + case FormatDescriptor.SubType.Decimal: + radioButtonDecimal.Checked = true; + break; + case FormatDescriptor.SubType.Binary: + radioButtonBinary.Checked = true; + break; + case FormatDescriptor.SubType.Ascii: + radioButtonAscii.Checked = true; + break; + case FormatDescriptor.SubType.Symbol: + Debug.Assert(dfd.HasSymbol); + radioButtonSymbol.Checked = true; + switch (dfd.SymbolRef.ValuePart) { + case WeakSymbolRef.Part.Low: + radioButtonLow.Checked = true; + break; + case WeakSymbolRef.Part.High: + radioButtonHigh.Checked = true; + break; + case WeakSymbolRef.Part.Bank: + radioButtonBank.Checked = true; + break; + default: + Debug.Assert(false); + break; + } + symbolTextBox.Text = dfd.SymbolRef.Label; + break; + case FormatDescriptor.SubType.None: + default: + // Unexpected; call it hex. + radioButtonHex.Checked = true; + break; + } + break; + case FormatDescriptor.Type.NumericBE: + case FormatDescriptor.Type.String: + case FormatDescriptor.Type.Fill: + default: + // Unexpected; used to be data? + radioButtonDefault.Checked = true; + break; + } + } + + /// + /// Creates a FormatDescriptor from the current state of the dialog controls. + /// + /// New FormatDescriptor. + private FormatDescriptor CreateDescriptorFromControls() { + if (radioButtonSymbol.Checked) { + if (string.IsNullOrEmpty(symbolTextBox.Text)) { + // empty symbol --> default format (intuitive way to delete label reference) + return null; + } + WeakSymbolRef.Part part; + if (radioButtonLow.Checked) { + part = WeakSymbolRef.Part.Low; + } else if (radioButtonHigh.Checked) { + part = WeakSymbolRef.Part.High; + } else if (radioButtonBank.Checked) { + part = WeakSymbolRef.Part.Bank; + } else { + Debug.Assert(false); + part = WeakSymbolRef.Part.Low; + } + return FormatDescriptor.Create(mInstructionLength, + new WeakSymbolRef(symbolTextBox.Text, part), false); + } + + FormatDescriptor.SubType subType; + if (radioButtonDefault.Checked) { + return null; + } else if (radioButtonHex.Checked) { + subType = FormatDescriptor.SubType.Hex; + } else if (radioButtonDecimal.Checked) { + subType = FormatDescriptor.SubType.Decimal; + } else if (radioButtonBinary.Checked) { + subType = FormatDescriptor.SubType.Binary; + } else if (radioButtonAscii.Checked) { + subType = FormatDescriptor.SubType.Ascii; + } else if (radioButtonSymbol.Checked) { + subType = FormatDescriptor.SubType.Symbol; + } else { + Debug.Assert(false); + subType = FormatDescriptor.SubType.None; + } + + return FormatDescriptor.Create(mInstructionLength, + FormatDescriptor.Type.NumericLE, subType); + } + } +} diff --git a/SourceGen/AppForms/EditOperand.resx b/SourceGen/AppForms/EditOperand.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/SourceGen/AppForms/EditOperand.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/SourceGen/AppForms/EditProjectProperties.Designer.cs b/SourceGen/AppForms/EditProjectProperties.Designer.cs new file mode 100644 index 0000000..cc371d4 --- /dev/null +++ b/SourceGen/AppForms/EditProjectProperties.Designer.cs @@ -0,0 +1,632 @@ +/* + * Copyright 2018 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. + */ +namespace SourceGen.AppForms { + partial class EditProjectProperties { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + System.Windows.Forms.ListViewItem listViewItem1 = new System.Windows.Forms.ListViewItem(new string[] { + "0123456789AB", + "%00000000", + "Const", + "This is a test to gauge column widths"}, -1); + this.tabControl1 = new System.Windows.Forms.TabControl(); + this.generalTab = new System.Windows.Forms.TabPage(); + this.analysisGroupBox = new System.Windows.Forms.GroupBox(); + this.seekAltTargetCheckBox = new System.Windows.Forms.CheckBox(); + this.minStringCharsComboBox = new System.Windows.Forms.ComboBox(); + this.minCharsForStringLabel = new System.Windows.Forms.Label(); + this.analyzeUncategorizedCheckBox = new System.Windows.Forms.CheckBox(); + this.entryFlagsGroupBox = new System.Windows.Forms.GroupBox(); + this.flagsLabel = new System.Windows.Forms.Label(); + this.currentFlagsLabel = new System.Windows.Forms.Label(); + this.changeFlagButton = new System.Windows.Forms.Button(); + this.cpuGroupBox = new System.Windows.Forms.GroupBox(); + this.undocInstrCheckBox = new System.Windows.Forms.CheckBox(); + this.cpuComboBox = new System.Windows.Forms.ComboBox(); + this.symbolsTab = new System.Windows.Forms.TabPage(); + this.importSymbolsButton = new System.Windows.Forms.Button(); + this.editSymbolButton = new System.Windows.Forms.Button(); + this.removeSymbolButton = new System.Windows.Forms.Button(); + this.newSymbolButton = new System.Windows.Forms.Button(); + this.symbolsDefinedLabel = new System.Windows.Forms.Label(); + this.projectSymbolsListView = new System.Windows.Forms.ListView(); + this.nameColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.valueColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.typeColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.commentColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.symbolFilesTab = new System.Windows.Forms.TabPage(); + this.symbolFileDownButton = new System.Windows.Forms.Button(); + this.symbolFileUpButton = new System.Windows.Forms.Button(); + this.addSymbolFilesButton = new System.Windows.Forms.Button(); + this.symbolFileRemoveButton = new System.Windows.Forms.Button(); + this.symbolFilesListBox = new System.Windows.Forms.ListBox(); + this.configuredFilesLabel = new System.Windows.Forms.Label(); + this.extensionScriptsTab = new System.Windows.Forms.TabPage(); + this.extensionScriptRemoveButton = new System.Windows.Forms.Button(); + this.addExtensionScriptsButton = new System.Windows.Forms.Button(); + this.extensionScriptsListBox = new System.Windows.Forms.ListBox(); + this.configuredScriptsLabel = new System.Windows.Forms.Label(); + this.labelUndoRedoNote = new System.Windows.Forms.Label(); + this.cancelButton = new System.Windows.Forms.Button(); + this.okButton = new System.Windows.Forms.Button(); + this.applyButton = new System.Windows.Forms.Button(); + this.tabControl1.SuspendLayout(); + this.generalTab.SuspendLayout(); + this.analysisGroupBox.SuspendLayout(); + this.entryFlagsGroupBox.SuspendLayout(); + this.cpuGroupBox.SuspendLayout(); + this.symbolsTab.SuspendLayout(); + this.symbolFilesTab.SuspendLayout(); + this.extensionScriptsTab.SuspendLayout(); + this.SuspendLayout(); + // + // tabControl1 + // + this.tabControl1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tabControl1.Controls.Add(this.generalTab); + this.tabControl1.Controls.Add(this.symbolsTab); + this.tabControl1.Controls.Add(this.symbolFilesTab); + this.tabControl1.Controls.Add(this.extensionScriptsTab); + this.tabControl1.Location = new System.Drawing.Point(2, 2); + this.tabControl1.Multiline = true; + this.tabControl1.Name = "tabControl1"; + this.tabControl1.SelectedIndex = 0; + this.tabControl1.Size = new System.Drawing.Size(622, 318); + this.tabControl1.TabIndex = 0; + // + // generalTab + // + this.generalTab.Controls.Add(this.analysisGroupBox); + this.generalTab.Controls.Add(this.entryFlagsGroupBox); + this.generalTab.Controls.Add(this.cpuGroupBox); + this.generalTab.Location = new System.Drawing.Point(4, 22); + this.generalTab.Name = "generalTab"; + this.generalTab.Padding = new System.Windows.Forms.Padding(3); + this.generalTab.Size = new System.Drawing.Size(614, 292); + this.generalTab.TabIndex = 0; + this.generalTab.Text = "General"; + this.generalTab.UseVisualStyleBackColor = true; + // + // analysisGroupBox + // + this.analysisGroupBox.Controls.Add(this.seekAltTargetCheckBox); + this.analysisGroupBox.Controls.Add(this.minStringCharsComboBox); + this.analysisGroupBox.Controls.Add(this.minCharsForStringLabel); + this.analysisGroupBox.Controls.Add(this.analyzeUncategorizedCheckBox); + this.analysisGroupBox.Location = new System.Drawing.Point(225, 7); + this.analysisGroupBox.Name = "analysisGroupBox"; + this.analysisGroupBox.Size = new System.Drawing.Size(204, 163); + this.analysisGroupBox.TabIndex = 2; + this.analysisGroupBox.TabStop = false; + this.analysisGroupBox.Text = "Analysis Parameters"; + // + // seekAltTargetCheckBox + // + this.seekAltTargetCheckBox.AutoSize = true; + this.seekAltTargetCheckBox.Location = new System.Drawing.Point(7, 45); + this.seekAltTargetCheckBox.Name = "seekAltTargetCheckBox"; + this.seekAltTargetCheckBox.Size = new System.Drawing.Size(130, 17); + this.seekAltTargetCheckBox.TabIndex = 3; + this.seekAltTargetCheckBox.Text = "Seek alternate targets"; + this.seekAltTargetCheckBox.UseVisualStyleBackColor = true; + this.seekAltTargetCheckBox.CheckedChanged += new System.EventHandler(this.seekAltTargetCheckBox_CheckedChanged); + // + // minStringCharsComboBox + // + this.minStringCharsComboBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.minStringCharsComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.minStringCharsComboBox.FormattingEnabled = true; + this.minStringCharsComboBox.Items.AddRange(new object[] { + "None (disabled)", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10"}); + this.minStringCharsComboBox.Location = new System.Drawing.Point(7, 88); + this.minStringCharsComboBox.Name = "minStringCharsComboBox"; + this.minStringCharsComboBox.Size = new System.Drawing.Size(191, 21); + this.minStringCharsComboBox.TabIndex = 2; + this.minStringCharsComboBox.SelectedIndexChanged += new System.EventHandler(this.minStringCharsComboBox_SelectedIndexChanged); + // + // minCharsForStringLabel + // + this.minCharsForStringLabel.AutoSize = true; + this.minCharsForStringLabel.Location = new System.Drawing.Point(7, 71); + this.minCharsForStringLabel.Name = "minCharsForStringLabel"; + this.minCharsForStringLabel.Size = new System.Drawing.Size(147, 13); + this.minCharsForStringLabel.TabIndex = 1; + this.minCharsForStringLabel.Text = "Minimum characters for string:"; + // + // analyzeUncategorizedCheckBox + // + this.analyzeUncategorizedCheckBox.AutoSize = true; + this.analyzeUncategorizedCheckBox.Location = new System.Drawing.Point(7, 21); + this.analyzeUncategorizedCheckBox.Name = "analyzeUncategorizedCheckBox"; + this.analyzeUncategorizedCheckBox.Size = new System.Drawing.Size(157, 17); + this.analyzeUncategorizedCheckBox.TabIndex = 0; + this.analyzeUncategorizedCheckBox.Text = "Analyze uncategorized data"; + this.analyzeUncategorizedCheckBox.UseVisualStyleBackColor = true; + this.analyzeUncategorizedCheckBox.CheckedChanged += new System.EventHandler(this.analyzeUncategorizedCheckBox_CheckedChanged); + // + // entryFlagsGroupBox + // + this.entryFlagsGroupBox.Controls.Add(this.flagsLabel); + this.entryFlagsGroupBox.Controls.Add(this.currentFlagsLabel); + this.entryFlagsGroupBox.Controls.Add(this.changeFlagButton); + this.entryFlagsGroupBox.Location = new System.Drawing.Point(7, 92); + this.entryFlagsGroupBox.Name = "entryFlagsGroupBox"; + this.entryFlagsGroupBox.Size = new System.Drawing.Size(204, 78); + this.entryFlagsGroupBox.TabIndex = 1; + this.entryFlagsGroupBox.TabStop = false; + this.entryFlagsGroupBox.Text = "Entry Flags"; + // + // flagsLabel + // + this.flagsLabel.AutoSize = true; + this.flagsLabel.Location = new System.Drawing.Point(7, 20); + this.flagsLabel.Name = "flagsLabel"; + this.flagsLabel.Size = new System.Drawing.Size(35, 13); + this.flagsLabel.TabIndex = 0; + this.flagsLabel.Text = "Flags:"; + // + // currentFlagsLabel + // + this.currentFlagsLabel.AutoSize = true; + this.currentFlagsLabel.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.currentFlagsLabel.Location = new System.Drawing.Point(38, 21); + this.currentFlagsLabel.Name = "currentFlagsLabel"; + this.currentFlagsLabel.Size = new System.Drawing.Size(163, 13); + this.currentFlagsLabel.TabIndex = 1; + this.currentFlagsLabel.Text = "N- V- M- X- D- I- Z- C- E-"; + // + // changeFlagButton + // + this.changeFlagButton.Location = new System.Drawing.Point(10, 42); + this.changeFlagButton.Name = "changeFlagButton"; + this.changeFlagButton.Size = new System.Drawing.Size(91, 23); + this.changeFlagButton.TabIndex = 2; + this.changeFlagButton.Text = "Change"; + this.changeFlagButton.UseVisualStyleBackColor = true; + this.changeFlagButton.Click += new System.EventHandler(this.changeFlagButton_Click); + // + // cpuGroupBox + // + this.cpuGroupBox.Controls.Add(this.undocInstrCheckBox); + this.cpuGroupBox.Controls.Add(this.cpuComboBox); + this.cpuGroupBox.Location = new System.Drawing.Point(7, 7); + this.cpuGroupBox.Name = "cpuGroupBox"; + this.cpuGroupBox.Size = new System.Drawing.Size(204, 78); + this.cpuGroupBox.TabIndex = 0; + this.cpuGroupBox.TabStop = false; + this.cpuGroupBox.Text = "CPU"; + // + // undocInstrCheckBox + // + this.undocInstrCheckBox.AutoSize = true; + this.undocInstrCheckBox.Location = new System.Drawing.Point(7, 47); + this.undocInstrCheckBox.Name = "undocInstrCheckBox"; + this.undocInstrCheckBox.Size = new System.Drawing.Size(189, 17); + this.undocInstrCheckBox.TabIndex = 1; + this.undocInstrCheckBox.Text = "Enable undocumented instructions"; + this.undocInstrCheckBox.UseVisualStyleBackColor = true; + this.undocInstrCheckBox.CheckedChanged += new System.EventHandler(this.undocInstrCheckBox_CheckedChanged); + // + // cpuComboBox + // + this.cpuComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cpuComboBox.FormattingEnabled = true; + this.cpuComboBox.Items.AddRange(new object[] { + "MOS 6502", + "WDC W65C02S", + "WDC W65C816S"}); + this.cpuComboBox.Location = new System.Drawing.Point(6, 19); + this.cpuComboBox.Name = "cpuComboBox"; + this.cpuComboBox.Size = new System.Drawing.Size(190, 21); + this.cpuComboBox.TabIndex = 0; + this.cpuComboBox.SelectedIndexChanged += new System.EventHandler(this.cpuComboBox_SelectedIndexChanged); + // + // symbolsTab + // + this.symbolsTab.Controls.Add(this.importSymbolsButton); + this.symbolsTab.Controls.Add(this.editSymbolButton); + this.symbolsTab.Controls.Add(this.removeSymbolButton); + this.symbolsTab.Controls.Add(this.newSymbolButton); + this.symbolsTab.Controls.Add(this.symbolsDefinedLabel); + this.symbolsTab.Controls.Add(this.projectSymbolsListView); + this.symbolsTab.Location = new System.Drawing.Point(4, 22); + this.symbolsTab.Name = "symbolsTab"; + this.symbolsTab.Padding = new System.Windows.Forms.Padding(3); + this.symbolsTab.Size = new System.Drawing.Size(614, 292); + this.symbolsTab.TabIndex = 1; + this.symbolsTab.Text = "Project Symbols"; + this.symbolsTab.UseVisualStyleBackColor = true; + // + // importSymbolsButton + // + this.importSymbolsButton.Location = new System.Drawing.Point(506, 158); + this.importSymbolsButton.Name = "importSymbolsButton"; + this.importSymbolsButton.Size = new System.Drawing.Size(102, 23); + this.importSymbolsButton.TabIndex = 5; + this.importSymbolsButton.Text = "&Import..."; + this.importSymbolsButton.UseVisualStyleBackColor = true; + this.importSymbolsButton.Click += new System.EventHandler(this.importSymbolsButton_Click); + // + // editSymbolButton + // + this.editSymbolButton.Location = new System.Drawing.Point(506, 52); + this.editSymbolButton.Name = "editSymbolButton"; + this.editSymbolButton.Size = new System.Drawing.Size(102, 23); + this.editSymbolButton.TabIndex = 3; + this.editSymbolButton.Text = "&Edit Symbol..."; + this.editSymbolButton.UseVisualStyleBackColor = true; + this.editSymbolButton.Click += new System.EventHandler(this.editSymbolButton_Click); + // + // removeSymbolButton + // + this.removeSymbolButton.Location = new System.Drawing.Point(506, 81); + this.removeSymbolButton.Name = "removeSymbolButton"; + this.removeSymbolButton.Size = new System.Drawing.Size(102, 23); + this.removeSymbolButton.TabIndex = 4; + this.removeSymbolButton.Text = "&Remove"; + this.removeSymbolButton.UseVisualStyleBackColor = true; + this.removeSymbolButton.Click += new System.EventHandler(this.removeSymbolButton_Click); + // + // newSymbolButton + // + this.newSymbolButton.Location = new System.Drawing.Point(506, 23); + this.newSymbolButton.Name = "newSymbolButton"; + this.newSymbolButton.Size = new System.Drawing.Size(102, 23); + this.newSymbolButton.TabIndex = 2; + this.newSymbolButton.Text = "&New Symbol..."; + this.newSymbolButton.UseVisualStyleBackColor = true; + this.newSymbolButton.Click += new System.EventHandler(this.newSymbolButton_Click); + // + // symbolsDefinedLabel + // + this.symbolsDefinedLabel.AutoSize = true; + this.symbolsDefinedLabel.Location = new System.Drawing.Point(7, 7); + this.symbolsDefinedLabel.Name = "symbolsDefinedLabel"; + this.symbolsDefinedLabel.Size = new System.Drawing.Size(133, 13); + this.symbolsDefinedLabel.TabIndex = 0; + this.symbolsDefinedLabel.Text = "Symbols defined in project:"; + // + // projectSymbolsListView + // + this.projectSymbolsListView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.nameColumnHeader, + this.valueColumnHeader, + this.typeColumnHeader, + this.commentColumnHeader}); + this.projectSymbolsListView.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.projectSymbolsListView.FullRowSelect = true; + this.projectSymbolsListView.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable; + this.projectSymbolsListView.HideSelection = false; + this.projectSymbolsListView.Items.AddRange(new System.Windows.Forms.ListViewItem[] { + listViewItem1}); + this.projectSymbolsListView.Location = new System.Drawing.Point(11, 23); + this.projectSymbolsListView.MultiSelect = false; + this.projectSymbolsListView.Name = "projectSymbolsListView"; + this.projectSymbolsListView.Size = new System.Drawing.Size(489, 259); + this.projectSymbolsListView.TabIndex = 1; + this.projectSymbolsListView.UseCompatibleStateImageBehavior = false; + this.projectSymbolsListView.View = System.Windows.Forms.View.Details; + this.projectSymbolsListView.SelectedIndexChanged += new System.EventHandler(this.projectSymbolsListView_SelectedIndexChanged); + this.projectSymbolsListView.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.projectSymbolsListView_MouseDoubleClick); + // + // nameColumnHeader + // + this.nameColumnHeader.Text = "Name"; + this.nameColumnHeader.Width = 109; + // + // valueColumnHeader + // + this.valueColumnHeader.Text = "Value"; + this.valueColumnHeader.Width = 69; + // + // typeColumnHeader + // + this.typeColumnHeader.Text = "Type"; + this.typeColumnHeader.Width = 42; + // + // commentColumnHeader + // + this.commentColumnHeader.Text = "Comment"; + this.commentColumnHeader.Width = 264; + // + // symbolFilesTab + // + this.symbolFilesTab.Controls.Add(this.symbolFileDownButton); + this.symbolFilesTab.Controls.Add(this.symbolFileUpButton); + this.symbolFilesTab.Controls.Add(this.addSymbolFilesButton); + this.symbolFilesTab.Controls.Add(this.symbolFileRemoveButton); + this.symbolFilesTab.Controls.Add(this.symbolFilesListBox); + this.symbolFilesTab.Controls.Add(this.configuredFilesLabel); + this.symbolFilesTab.Location = new System.Drawing.Point(4, 22); + this.symbolFilesTab.Name = "symbolFilesTab"; + this.symbolFilesTab.Padding = new System.Windows.Forms.Padding(3); + this.symbolFilesTab.Size = new System.Drawing.Size(614, 292); + this.symbolFilesTab.TabIndex = 2; + this.symbolFilesTab.Text = "Symbol Files"; + this.symbolFilesTab.UseVisualStyleBackColor = true; + // + // symbolFileDownButton + // + this.symbolFileDownButton.Location = new System.Drawing.Point(319, 57); + this.symbolFileDownButton.Name = "symbolFileDownButton"; + this.symbolFileDownButton.Size = new System.Drawing.Size(75, 23); + this.symbolFileDownButton.TabIndex = 4; + this.symbolFileDownButton.Text = "Down"; + this.symbolFileDownButton.UseVisualStyleBackColor = true; + this.symbolFileDownButton.Click += new System.EventHandler(this.symbolFileDownButton_Click); + // + // symbolFileUpButton + // + this.symbolFileUpButton.Location = new System.Drawing.Point(319, 28); + this.symbolFileUpButton.Name = "symbolFileUpButton"; + this.symbolFileUpButton.Size = new System.Drawing.Size(75, 23); + this.symbolFileUpButton.TabIndex = 3; + this.symbolFileUpButton.Text = "Up"; + this.symbolFileUpButton.UseVisualStyleBackColor = true; + this.symbolFileUpButton.Click += new System.EventHandler(this.symbolFileUpButton_Click); + // + // addSymbolFilesButton + // + this.addSymbolFilesButton.Location = new System.Drawing.Point(8, 249); + this.addSymbolFilesButton.Name = "addSymbolFilesButton"; + this.addSymbolFilesButton.Size = new System.Drawing.Size(134, 23); + this.addSymbolFilesButton.TabIndex = 2; + this.addSymbolFilesButton.Text = "Add Symbol Files..."; + this.addSymbolFilesButton.UseVisualStyleBackColor = true; + this.addSymbolFilesButton.Click += new System.EventHandler(this.addSymbolFilesButton_Click); + // + // symbolFileRemoveButton + // + this.symbolFileRemoveButton.Location = new System.Drawing.Point(319, 97); + this.symbolFileRemoveButton.Name = "symbolFileRemoveButton"; + this.symbolFileRemoveButton.Size = new System.Drawing.Size(75, 23); + this.symbolFileRemoveButton.TabIndex = 5; + this.symbolFileRemoveButton.Text = "Remove"; + this.symbolFileRemoveButton.UseVisualStyleBackColor = true; + this.symbolFileRemoveButton.Click += new System.EventHandler(this.symbolFileRemoveButton_Click); + // + // symbolFilesListBox + // + this.symbolFilesListBox.FormattingEnabled = true; + this.symbolFilesListBox.Location = new System.Drawing.Point(8, 28); + this.symbolFilesListBox.Name = "symbolFilesListBox"; + this.symbolFilesListBox.SelectionMode = System.Windows.Forms.SelectionMode.MultiExtended; + this.symbolFilesListBox.Size = new System.Drawing.Size(305, 212); + this.symbolFilesListBox.TabIndex = 1; + this.symbolFilesListBox.SelectedIndexChanged += new System.EventHandler(this.symbolFilesListBox_SelectedIndexChanged); + // + // configuredFilesLabel + // + this.configuredFilesLabel.AutoSize = true; + this.configuredFilesLabel.Location = new System.Drawing.Point(7, 7); + this.configuredFilesLabel.Name = "configuredFilesLabel"; + this.configuredFilesLabel.Size = new System.Drawing.Size(160, 13); + this.configuredFilesLabel.TabIndex = 0; + this.configuredFilesLabel.Text = "Currently configured symbol files:"; + // + // extensionScriptsTab + // + this.extensionScriptsTab.Controls.Add(this.extensionScriptRemoveButton); + this.extensionScriptsTab.Controls.Add(this.addExtensionScriptsButton); + this.extensionScriptsTab.Controls.Add(this.extensionScriptsListBox); + this.extensionScriptsTab.Controls.Add(this.configuredScriptsLabel); + this.extensionScriptsTab.Location = new System.Drawing.Point(4, 22); + this.extensionScriptsTab.Name = "extensionScriptsTab"; + this.extensionScriptsTab.Padding = new System.Windows.Forms.Padding(3); + this.extensionScriptsTab.Size = new System.Drawing.Size(614, 292); + this.extensionScriptsTab.TabIndex = 3; + this.extensionScriptsTab.Text = "Extension Scripts"; + this.extensionScriptsTab.UseVisualStyleBackColor = true; + // + // extensionScriptRemoveButton + // + this.extensionScriptRemoveButton.Location = new System.Drawing.Point(320, 28); + this.extensionScriptRemoveButton.Name = "extensionScriptRemoveButton"; + this.extensionScriptRemoveButton.Size = new System.Drawing.Size(75, 23); + this.extensionScriptRemoveButton.TabIndex = 3; + this.extensionScriptRemoveButton.Text = "Remove"; + this.extensionScriptRemoveButton.UseVisualStyleBackColor = true; + this.extensionScriptRemoveButton.Click += new System.EventHandler(this.extensionScriptRemoveButton_Click); + // + // addExtensionScriptsButton + // + this.addExtensionScriptsButton.Location = new System.Drawing.Point(8, 249); + this.addExtensionScriptsButton.Name = "addExtensionScriptsButton"; + this.addExtensionScriptsButton.Size = new System.Drawing.Size(134, 23); + this.addExtensionScriptsButton.TabIndex = 2; + this.addExtensionScriptsButton.Text = "Add Scripts..."; + this.addExtensionScriptsButton.UseVisualStyleBackColor = true; + this.addExtensionScriptsButton.Click += new System.EventHandler(this.addExtensionScriptsButton_Click); + // + // extensionScriptsListBox + // + this.extensionScriptsListBox.FormattingEnabled = true; + this.extensionScriptsListBox.Location = new System.Drawing.Point(8, 28); + this.extensionScriptsListBox.Name = "extensionScriptsListBox"; + this.extensionScriptsListBox.SelectionMode = System.Windows.Forms.SelectionMode.MultiExtended; + this.extensionScriptsListBox.Size = new System.Drawing.Size(305, 212); + this.extensionScriptsListBox.TabIndex = 1; + this.extensionScriptsListBox.SelectedIndexChanged += new System.EventHandler(this.extensionScriptsListBox_SelectedIndexChanged); + // + // configuredScriptsLabel + // + this.configuredScriptsLabel.AutoSize = true; + this.configuredScriptsLabel.Location = new System.Drawing.Point(7, 7); + this.configuredScriptsLabel.Name = "configuredScriptsLabel"; + this.configuredScriptsLabel.Size = new System.Drawing.Size(185, 13); + this.configuredScriptsLabel.TabIndex = 0; + this.configuredScriptsLabel.Text = "Currently configured extension scripts:"; + // + // labelUndoRedoNote + // + this.labelUndoRedoNote.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.labelUndoRedoNote.AutoSize = true; + this.labelUndoRedoNote.Location = new System.Drawing.Point(12, 331); + this.labelUndoRedoNote.Name = "labelUndoRedoNote"; + this.labelUndoRedoNote.Size = new System.Drawing.Size(248, 13); + this.labelUndoRedoNote.TabIndex = 1; + this.labelUndoRedoNote.Text = "NOTE: changes are added to the undo/redo buffer"; + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(537, 326); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 4; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + // + // okButton + // + this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.okButton.Location = new System.Drawing.Point(456, 326); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(75, 23); + this.okButton.TabIndex = 3; + this.okButton.Text = "OK"; + this.okButton.UseVisualStyleBackColor = true; + this.okButton.Click += new System.EventHandler(this.okButton_Click); + // + // applyButton + // + this.applyButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.applyButton.Location = new System.Drawing.Point(354, 326); + this.applyButton.Name = "applyButton"; + this.applyButton.Size = new System.Drawing.Size(75, 23); + this.applyButton.TabIndex = 2; + this.applyButton.Text = "Apply"; + this.applyButton.UseVisualStyleBackColor = true; + this.applyButton.Click += new System.EventHandler(this.applyButton_Click); + // + // EditProjectProperties + // + this.AcceptButton = this.okButton; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(624, 361); + this.Controls.Add(this.applyButton); + this.Controls.Add(this.okButton); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.labelUndoRedoNote); + this.Controls.Add(this.tabControl1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "EditProjectProperties"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Edit Project Properties"; + this.Load += new System.EventHandler(this.EditProperties_Load); + this.tabControl1.ResumeLayout(false); + this.generalTab.ResumeLayout(false); + this.analysisGroupBox.ResumeLayout(false); + this.analysisGroupBox.PerformLayout(); + this.entryFlagsGroupBox.ResumeLayout(false); + this.entryFlagsGroupBox.PerformLayout(); + this.cpuGroupBox.ResumeLayout(false); + this.cpuGroupBox.PerformLayout(); + this.symbolsTab.ResumeLayout(false); + this.symbolsTab.PerformLayout(); + this.symbolFilesTab.ResumeLayout(false); + this.symbolFilesTab.PerformLayout(); + this.extensionScriptsTab.ResumeLayout(false); + this.extensionScriptsTab.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TabControl tabControl1; + private System.Windows.Forms.TabPage generalTab; + private System.Windows.Forms.ComboBox cpuComboBox; + private System.Windows.Forms.TabPage symbolsTab; + private System.Windows.Forms.Label labelUndoRedoNote; + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.Button applyButton; + private System.Windows.Forms.Label currentFlagsLabel; + private System.Windows.Forms.Button changeFlagButton; + private System.Windows.Forms.Label symbolsDefinedLabel; + private System.Windows.Forms.ListView projectSymbolsListView; + private System.Windows.Forms.ColumnHeader nameColumnHeader; + private System.Windows.Forms.ColumnHeader valueColumnHeader; + private System.Windows.Forms.ColumnHeader typeColumnHeader; + private System.Windows.Forms.TabPage symbolFilesTab; + private System.Windows.Forms.Button addSymbolFilesButton; + private System.Windows.Forms.Button symbolFileRemoveButton; + private System.Windows.Forms.ListBox symbolFilesListBox; + private System.Windows.Forms.Label configuredFilesLabel; + private System.Windows.Forms.Button removeSymbolButton; + private System.Windows.Forms.Button newSymbolButton; + private System.Windows.Forms.ColumnHeader commentColumnHeader; + private System.Windows.Forms.GroupBox entryFlagsGroupBox; + private System.Windows.Forms.Label flagsLabel; + private System.Windows.Forms.GroupBox cpuGroupBox; + private System.Windows.Forms.CheckBox undocInstrCheckBox; + private System.Windows.Forms.Button editSymbolButton; + private System.Windows.Forms.Button symbolFileDownButton; + private System.Windows.Forms.Button symbolFileUpButton; + private System.Windows.Forms.GroupBox analysisGroupBox; + private System.Windows.Forms.ComboBox minStringCharsComboBox; + private System.Windows.Forms.Label minCharsForStringLabel; + private System.Windows.Forms.CheckBox analyzeUncategorizedCheckBox; + private System.Windows.Forms.TabPage extensionScriptsTab; + private System.Windows.Forms.Button extensionScriptRemoveButton; + private System.Windows.Forms.Button addExtensionScriptsButton; + private System.Windows.Forms.ListBox extensionScriptsListBox; + private System.Windows.Forms.Label configuredScriptsLabel; + private System.Windows.Forms.Button importSymbolsButton; + private System.Windows.Forms.CheckBox seekAltTargetCheckBox; + } +} \ No newline at end of file diff --git a/SourceGen/AppForms/EditProjectProperties.cs b/SourceGen/AppForms/EditProjectProperties.cs new file mode 100644 index 0000000..ff59a56 --- /dev/null +++ b/SourceGen/AppForms/EditProjectProperties.cs @@ -0,0 +1,631 @@ +/* + * Copyright 2018 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.Windows.Forms; + +using Asm65; +using CommonUtil; + +namespace SourceGen.AppForms { + /// + /// Edit project properties. + /// + /// Changes are made locally, and pushed to NewProps when OK or Apply is clicked. When + /// the dialog exits, if NewProps is non-null, the caller should apply those changes + /// regardless of the dialog's return value. + /// + public partial class EditProjectProperties : Form { + /// + /// Working set. Used internally to hold state. + /// + private ProjectProperties WorkProps { get; set; } + + /// + /// New set. Updated when Apply or OK is hit. This will be null if no changes have + /// been applied. + /// + public ProjectProperties NewProps { get; private set; } + + /// + /// Format object to use when formatting addresses and constants. + /// + public Formatter NumFormatter { get; set; } + + /// + /// Dirty flag. Ideally this would just be "WorkProps != OldProps", but it doesn't + /// seem worthwhile to maintain an equality operator. + /// + private bool mDirty; + + /// + /// Project directory, if one has been established; otherwise empty. + /// + private string mProjectDir; + + + public EditProjectProperties(string projectDir) { + InitializeComponent(); + + mProjectDir = projectDir; + } + + /// + /// Sets the initial state from an existing ProjectProperties object. This must be + /// called, and must be called before the dialog is shown. + /// + /// Object to clone. + public void SetInitialProps(ProjectProperties props) { + WorkProps = new ProjectProperties(props); + } + + private void EditProperties_Load(object sender, EventArgs e) { + // Configure CPU chooser. This must match the order of strings in the designer. + switch (WorkProps.CpuType) { + case CpuDef.CpuType.Cpu6502: + cpuComboBox.SelectedIndex = 0; + break; + case CpuDef.CpuType.Cpu65C02: + cpuComboBox.SelectedIndex = 1; + break; + case CpuDef.CpuType.Cpu65816: + cpuComboBox.SelectedIndex = 2; + break; + default: + Debug.Assert(false); + cpuComboBox.SelectedIndex = 0; + break; + } + + undocInstrCheckBox.Checked = WorkProps.IncludeUndocumentedInstr; + analyzeUncategorizedCheckBox.Checked = + WorkProps.AnalysisParams.AnalyzeUncategorizedData; + seekAltTargetCheckBox.Checked = + WorkProps.AnalysisParams.SeekNearbyTargets; + + int matchLen = WorkProps.AnalysisParams.MinCharsForString; + int selIndex; + if (matchLen == DataAnalysis.MIN_CHARS_FOR_STRING_DISABLED) { + selIndex = 0; // disabled + } else { + selIndex = matchLen - 2; + } + if (selIndex < 0 || selIndex >= minStringCharsComboBox.Items.Count) { + Debug.Assert(false, "bad MinCharsForString " + matchLen); + selIndex = 0; + } + minStringCharsComboBox.SelectedIndex = selIndex; + + LoadProjectSymbols(); + LoadPlatformSymbolFiles(); + LoadExtensionScriptNames(); + + // Various callbacks will have fired while configuring controls. Reset to "clean". + mDirty = false; + UpdateControls(); + } + + private void okButton_Click(object sender, EventArgs e) { + NewProps = new ProjectProperties(WorkProps); + } + + private void applyButton_Click(object sender, EventArgs e) { + NewProps = new ProjectProperties(WorkProps); + mDirty = false; + UpdateControls(); + } + + private void UpdateControls() { + // + // General tab + // + applyButton.Enabled = mDirty; + + const string FLAGS = "CZIDXMVNE"; // flags, in order low to high, plus emu bit + const string VALUES = "-?01"; + StringBuilder sb = new StringBuilder(27); + StatusFlags flags = WorkProps.EntryFlags; + for (int i = 0; i < 9; i++) { + // Want to show P reg flags (first 8) in conventional high-to-low order. + int idx = (7 - i) + (i == 8 ? 9 : 0); + int val = flags.GetBit((StatusFlags.FlagBits)idx); + sb.Append(FLAGS[idx]); + sb.Append(VALUES[val + 2]); + sb.Append(' '); + } + + currentFlagsLabel.Text = sb.ToString(); + + // + // Project symbols tab + // + int symSelCount = projectSymbolsListView.SelectedIndices.Count; + removeSymbolButton.Enabled = (symSelCount == 1); + editSymbolButton.Enabled = (symSelCount == 1); + + // + // Platform symbol files tab + // + int fileSelCount = symbolFilesListBox.SelectedIndices.Count; + symbolFileRemoveButton.Enabled = (fileSelCount != 0); + symbolFileUpButton.Enabled = (fileSelCount == 1 && + symbolFilesListBox.SelectedIndices[0] != 0); + symbolFileDownButton.Enabled = (fileSelCount == 1 && + symbolFilesListBox.SelectedIndices[0] != symbolFilesListBox.Items.Count - 1); + + // + // Extension Scripts tab + // + fileSelCount = extensionScriptsListBox.SelectedIndices.Count; + extensionScriptRemoveButton.Enabled = (fileSelCount != 0); + } + + + #region General + + /// + /// Converts the CPU combo box selection to a CpuType enum value. + /// + /// Selection index. + /// CPU type. + private CpuDef.CpuType CpuSelectionToCpuType(int sel) { + switch (sel) { + case 0: return CpuDef.CpuType.Cpu6502; + case 1: return CpuDef.CpuType.Cpu65C02; + case 2: return CpuDef.CpuType.Cpu65816; + default: + Debug.Assert(false); + return CpuDef.CpuType.Cpu6502; + } + } + + private void cpuComboBox_SelectedIndexChanged(object sender, EventArgs e) { + CpuDef.CpuType cpuType = CpuSelectionToCpuType(cpuComboBox.SelectedIndex); + if (WorkProps.CpuType != cpuType) { + WorkProps.CpuType = cpuType; + mDirty = true; + UpdateControls(); + } + } + + private void undocInstrCheckBox_CheckedChanged(object sender, EventArgs e) { + if (WorkProps.IncludeUndocumentedInstr != undocInstrCheckBox.Checked) { + WorkProps.IncludeUndocumentedInstr = undocInstrCheckBox.Checked; + mDirty = true; + UpdateControls(); + } + } + + private void changeFlagButton_Click(object sender, EventArgs e) { + EditStatusFlags dlg = new EditStatusFlags(); + dlg.FlagValue = WorkProps.EntryFlags; + + CpuDef cpuDef = CpuDef.GetBestMatch(WorkProps.CpuType, + WorkProps.IncludeUndocumentedInstr); + dlg.HasEmuFlag = cpuDef.HasEmuFlag; + + dlg.ShowDialog(); + if (dlg.DialogResult == DialogResult.OK) { + if (WorkProps.EntryFlags != dlg.FlagValue) { + // Flags changed. + WorkProps.EntryFlags = dlg.FlagValue; + mDirty = true; + UpdateControls(); + } + } + + dlg.Dispose(); + } + + private void analyzeUncategorizedCheckBox_CheckedChanged(object sender, EventArgs e) { + WorkProps.AnalysisParams.AnalyzeUncategorizedData = + analyzeUncategorizedCheckBox.Checked; + mDirty = true; + UpdateControls(); + } + + private void seekAltTargetCheckBox_CheckedChanged(object sender, EventArgs e) { + WorkProps.AnalysisParams.SeekNearbyTargets = + seekAltTargetCheckBox.Checked; + mDirty = true; + UpdateControls(); + } + + private void minStringCharsComboBox_SelectedIndexChanged(object sender, EventArgs e) { + int index = minStringCharsComboBox.SelectedIndex; + int newVal; + if (index == 0) { + newVal = DataAnalysis.MIN_CHARS_FOR_STRING_DISABLED; + } else { + newVal = index + 2; + } + + if (newVal != WorkProps.AnalysisParams.MinCharsForString) { + WorkProps.AnalysisParams.MinCharsForString = newVal; + mDirty = true; + UpdateControls(); + } + } + + #endregion General + + + #region Project Symbols + + private ListViewItem.ListViewSubItem[] mSymbolSubArray = + new ListViewItem.ListViewSubItem[3]; + + /// + /// Loads the project symbols into the ListView. + /// + private void LoadProjectSymbols() { + // The set should be small enough that we don't need to worry about updating + // the item list incrementally. + //Debug.WriteLine("LPS loading " + WorkProps.ProjectSyms.Count + " project symbols"); + projectSymbolsListView.BeginUpdate(); + projectSymbolsListView.Items.Clear(); + foreach (KeyValuePair kvp in WorkProps.ProjectSyms) { + DefSymbol defSym = kvp.Value; + string typeStr; + if (defSym.SymbolType == Symbol.Type.Constant) { + typeStr = Properties.Resources.ABBREV_CONSTANT; + } else { + typeStr = Properties.Resources.ABBREV_ADDRESS; + } + + ListViewItem lvi = new ListViewItem(); + lvi.Text = defSym.Label; + mSymbolSubArray[0] = new ListViewItem.ListViewSubItem(lvi, + NumFormatter.FormatValueInBase(defSym.Value, defSym.DataDescriptor.NumBase)); + mSymbolSubArray[1] = new ListViewItem.ListViewSubItem(lvi, typeStr); + mSymbolSubArray[2] = new ListViewItem.ListViewSubItem(lvi, defSym.Comment); + lvi.SubItems.AddRange(mSymbolSubArray); + + projectSymbolsListView.Items.Add(lvi); + } + projectSymbolsListView.EndUpdate(); + } + + + private void projectSymbolsListView_SelectedIndexChanged(object sender, EventArgs e) { + // Need to enable/disable the edit+remove buttons depending on the number of + // selected items. + UpdateControls(); + } + + private void newSymbolButton_Click(object sender, EventArgs e) { + EditDefSymbol dlg = new EditDefSymbol(NumFormatter, WorkProps.ProjectSyms); + dlg.ShowDialog(); + if (dlg.DialogResult == DialogResult.OK) { + Debug.WriteLine("ADD: " + dlg.DefSym); + WorkProps.ProjectSyms[dlg.DefSym.Label] = dlg.DefSym; + mDirty = true; + LoadProjectSymbols(); + UpdateControls(); + } + dlg.Dispose(); + } + + private void editSymbolButton_Click(object sender, EventArgs e) { + // Single-select list view, button dimmed when no selection. + Debug.Assert(projectSymbolsListView.SelectedItems.Count == 1); + ListViewItem item = projectSymbolsListView.SelectedItems[0]; + DefSymbol defSym = WorkProps.ProjectSyms[item.Text]; + DoEditSymbol(defSym); + } + + private void projectSymbolsListView_MouseDoubleClick(object sender, MouseEventArgs e) { + ListViewHitTestInfo info = projectSymbolsListView.HitTest(e.X, e.Y); + DefSymbol defSym = WorkProps.ProjectSyms[info.Item.Text]; + DoEditSymbol(defSym); + } + + private void DoEditSymbol(DefSymbol defSym) { + EditDefSymbol dlg = new EditDefSymbol(NumFormatter, WorkProps.ProjectSyms); + dlg.DefSym = defSym; + dlg.ShowDialog(); + if (dlg.DialogResult == DialogResult.OK) { + // Label might have changed, so remove old before adding new. + WorkProps.ProjectSyms.Remove(defSym.Label); + WorkProps.ProjectSyms[dlg.DefSym.Label] = dlg.DefSym; + mDirty = true; + LoadProjectSymbols(); + UpdateControls(); + } + dlg.Dispose(); + } + + private void removeSymbolButton_Click(object sender, EventArgs e) { + // Single-select list view, button dimmed when no selection. + Debug.Assert(projectSymbolsListView.SelectedItems.Count == 1); + + int selectionIndex = projectSymbolsListView.SelectedIndices[0]; + ListViewItem item = projectSymbolsListView.SelectedItems[0]; + DefSymbol defSym = WorkProps.ProjectSyms[item.Text]; + WorkProps.ProjectSyms.Remove(defSym.Label); + mDirty = true; + LoadProjectSymbols(); + UpdateControls(); + + // Restore selection, so you can hit "Remove" repeatedly to delete + // multiple items. + int newCount = projectSymbolsListView.Items.Count; + if (selectionIndex >= newCount) { + selectionIndex = newCount - 1; + } + if (selectionIndex >= 0) { + projectSymbolsListView.SelectedIndices.Add(selectionIndex); + removeSymbolButton.Focus(); + } + } + + #endregion Project Symbols + + + #region Platform symbol files + + /// + /// Loads the platform symbol file names into the list control. + /// + private void LoadPlatformSymbolFiles() { + symbolFilesListBox.BeginUpdate(); + symbolFilesListBox.Items.Clear(); + + foreach (string fileName in WorkProps.PlatformSymbolFileIdentifiers) { + symbolFilesListBox.Items.Add(fileName); + } + + symbolFilesListBox.EndUpdate(); + } + + private void symbolFilesListBox_SelectedIndexChanged(object sender, EventArgs e) { + // Enable/disable buttons as the selection changes. + UpdateControls(); + } + + private void addSymbolFilesButton_Click(object sender, EventArgs e) { + OpenFileDialog fileDlg = new OpenFileDialog(); + fileDlg.Filter = PlatformSymbols.FILENAME_FILTER; + fileDlg.Multiselect = true; + fileDlg.InitialDirectory = RuntimeDataAccess.GetDirectory(); + fileDlg.RestoreDirectory = true; // doesn't seem to work? + if (fileDlg.ShowDialog() != DialogResult.OK) { + return; + } + + foreach (string pathName in fileDlg.FileNames) { + // I'm assuming the full names got the Path.GetFullPath() canonicalization and + // don't need further processing. Also, I'm assuming that all files live in + // the same directory, so if one is in an invalid location then they all are. + ExternalFile ef = ExternalFile.CreateFromPath(pathName, mProjectDir); + if (ef == null) { + // Files not found in runtime or project directory. + string projDir = mProjectDir; + if (string.IsNullOrEmpty(projDir)) { + projDir = Properties.Resources.UNSET; + } + string msg = string.Format(Properties.Resources.EXTERNAL_FILE_BAD_DIR, + RuntimeDataAccess.GetDirectory(), projDir, pathName); + MessageBox.Show(msg, Properties.Resources.EXTERNAL_FILE_BAD_DIR_CAPTION, + MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + string ident = ef.Identifier; + + if (WorkProps.PlatformSymbolFileIdentifiers.Contains(ident)) { + Debug.WriteLine("Already present: " + ident); + continue; + } + + Debug.WriteLine("Adding symbol file: " + ident); + WorkProps.PlatformSymbolFileIdentifiers.Add(ident); + mDirty = true; + } + + if (mDirty) { + LoadPlatformSymbolFiles(); + UpdateControls(); + } + } + + private void symbolFileUpButton_Click(object sender, EventArgs e) { + Debug.Assert(symbolFilesListBox.SelectedIndices.Count == 1); + int selIndex = symbolFilesListBox.SelectedIndices[0]; + Debug.Assert(selIndex > 0); + + MoveSingleItem(selIndex, symbolFilesListBox.SelectedItem, -1); + } + + private void symbolFileDownButton_Click(object sender, EventArgs e) { + Debug.Assert(symbolFilesListBox.SelectedIndices.Count == 1); + int selIndex = symbolFilesListBox.SelectedIndices[0]; + Debug.Assert(selIndex < symbolFilesListBox.Items.Count - 1); + + MoveSingleItem(selIndex, symbolFilesListBox.SelectedItem, +1); + } + + private void MoveSingleItem(int selIndex, object selectedItem, int adj) { + object selected = symbolFilesListBox.SelectedItem; + symbolFilesListBox.Items.Remove(selected); + symbolFilesListBox.Items.Insert(selIndex + adj, selected); + symbolFilesListBox.SetSelected(selIndex + adj, true); + + // do the same operation in the file name list + string str = WorkProps.PlatformSymbolFileIdentifiers[selIndex]; + WorkProps.PlatformSymbolFileIdentifiers.RemoveAt(selIndex); + WorkProps.PlatformSymbolFileIdentifiers.Insert(selIndex + adj, str); + + mDirty = true; + UpdateControls(); + } + + private void symbolFileRemoveButton_Click(object sender, EventArgs e) { + Debug.Assert(symbolFilesListBox.SelectedIndices.Count > 0); + for (int i = symbolFilesListBox.SelectedIndices.Count - 1; i >= 0; i--) { + int index = symbolFilesListBox.SelectedIndices[i]; + symbolFilesListBox.Items.RemoveAt(index); + WorkProps.PlatformSymbolFileIdentifiers.RemoveAt(index); + } + + mDirty = true; + UpdateControls(); + } + + private void importSymbolsButton_Click(object sender, EventArgs e) { + OpenFileDialog fileDlg = new OpenFileDialog(); + + fileDlg.Filter = ProjectFile.FILENAME_FILTER + "|" + + Properties.Resources.FILE_FILTER_ALL; + fileDlg.FilterIndex = 1; + if (fileDlg.ShowDialog() != DialogResult.OK) { + return; + } + string projPathName = Path.GetFullPath(fileDlg.FileName); + + DisasmProject newProject = new DisasmProject(); + if (!ProjectFile.DeserializeFromFile(projPathName, newProject, + out FileLoadReport report)) { + ProjectLoadIssues dlg = new ProjectLoadIssues(); + dlg.Messages = report.Format(); + dlg.CanContinue = false; + dlg.ShowDialog(); + // ignore dlg.DialogResult + dlg.Dispose(); + return; + } + + // Import all user labels that were marked as "global export". These become + // external-address project symbols. + int foundCount = 0; + foreach (KeyValuePair kvp in newProject.UserLabels) { + if (kvp.Value.SymbolType == Symbol.Type.GlobalAddrExport) { + Symbol sym = kvp.Value; + DefSymbol defSym = new DefSymbol(sym.Label, sym.Value, Symbol.Source.Project, + Symbol.Type.ExternalAddr, FormatDescriptor.SubType.None, + string.Empty, string.Empty); + WorkProps.ProjectSyms[defSym.Label] = defSym; + foundCount++; + } + } + if (foundCount != 0) { + mDirty = true; + LoadProjectSymbols(); + UpdateControls(); + } + + newProject.Cleanup(); + + // Tell the user we did something. Might be nice to tell them how many weren't + // already present. + string msg; + if (foundCount == 0) { + msg = Properties.Resources.SYMBOL_IMPORT_NONE; + } else { + msg = string.Format(Properties.Resources.SYMBOL_IMPORT_GOOD, foundCount); + } + MessageBox.Show(msg, Properties.Resources.SYMBOL_IMPORT_CAPTION, + MessageBoxButtons.OK, MessageBoxIcon.Information); + } + + #endregion Platform symbol files + + + #region Extension scripts + + /// + /// Loads the extension script file names into the list control. + /// + private void LoadExtensionScriptNames() { + extensionScriptsListBox.BeginUpdate(); + extensionScriptsListBox.Items.Clear(); + + foreach (string fileName in WorkProps.ExtensionScriptFileIdentifiers) { + extensionScriptsListBox.Items.Add(fileName); + } + + extensionScriptsListBox.EndUpdate(); + } + + private void extensionScriptsListBox_SelectedIndexChanged(object sender, EventArgs e) { + // Enable/disable buttons as the selection changes. + UpdateControls(); + } + + private void addExtensionScriptsButton_Click(object sender, EventArgs e) { + OpenFileDialog fileDlg = new OpenFileDialog(); + fileDlg.Filter = Sandbox.ScriptManager.FILENAME_FILTER; + fileDlg.Multiselect = true; + fileDlg.InitialDirectory = RuntimeDataAccess.GetDirectory(); + fileDlg.RestoreDirectory = true; // doesn't seem to work? + if (fileDlg.ShowDialog() != DialogResult.OK) { + return; + } + + foreach (string pathName in fileDlg.FileNames) { + // I'm assuming the full names got the Path.GetFullPath() canonicalization and + // don't need further processing. Also, I'm assuming that all files live in + // the same directory, so if one is in an invalid location then they all are. + ExternalFile ef = ExternalFile.CreateFromPath(pathName, mProjectDir); + if (ef == null) { + // Files not found in runtime or project directory. + string projDir = mProjectDir; + if (string.IsNullOrEmpty(projDir)) { + projDir = Properties.Resources.UNSET; + } + string msg = string.Format(Properties.Resources.EXTERNAL_FILE_BAD_DIR, + RuntimeDataAccess.GetDirectory(), projDir, pathName); + MessageBox.Show(msg, Properties.Resources.EXTERNAL_FILE_BAD_DIR_CAPTION, + MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + string ident = ef.Identifier; + + if (WorkProps.ExtensionScriptFileIdentifiers.Contains(ident)) { + Debug.WriteLine("Already present: " + ident); + continue; + } + + Debug.WriteLine("Adding extension script: " + ident); + WorkProps.ExtensionScriptFileIdentifiers.Add(ident); + mDirty = true; + } + + if (mDirty) { + LoadExtensionScriptNames(); + UpdateControls(); + } + } + + private void extensionScriptRemoveButton_Click(object sender, EventArgs e) { + Debug.Assert(extensionScriptsListBox.SelectedIndices.Count > 0); + for (int i = extensionScriptsListBox.SelectedIndices.Count - 1; i >= 0; i--) { + int index = extensionScriptsListBox.SelectedIndices[i]; + extensionScriptsListBox.Items.RemoveAt(index); + WorkProps.ExtensionScriptFileIdentifiers.RemoveAt(index); + } + + mDirty = true; + UpdateControls(); + } + + #endregion Extension scripts + } +} diff --git a/SourceGen/AppForms/EditProjectProperties.resx b/SourceGen/AppForms/EditProjectProperties.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/SourceGen/AppForms/EditProjectProperties.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/SourceGen/AppForms/EditStatusFlags.Designer.cs b/SourceGen/AppForms/EditStatusFlags.Designer.cs new file mode 100644 index 0000000..b7a26e1 --- /dev/null +++ b/SourceGen/AppForms/EditStatusFlags.Designer.cs @@ -0,0 +1,883 @@ +/* + * Copyright 2018 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. + */ +namespace SourceGen.AppForms { + partial class EditStatusFlags { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.defaultLabel = new System.Windows.Forms.Label(); + this.zeroLabel = new System.Windows.Forms.Label(); + this.oneLabel = new System.Windows.Forms.Label(); + this.indeterminateLabel = new System.Windows.Forms.Label(); + this.panelN = new System.Windows.Forms.Panel(); + this.flagNLabel = new System.Windows.Forms.Label(); + this.radioNIndeterminate = new System.Windows.Forms.RadioButton(); + this.radioNOne = new System.Windows.Forms.RadioButton(); + this.radioNZero = new System.Windows.Forms.RadioButton(); + this.radioNDefault = new System.Windows.Forms.RadioButton(); + this.panelV = new System.Windows.Forms.Panel(); + this.flagVLabel = new System.Windows.Forms.Label(); + this.radioVIndeterminate = new System.Windows.Forms.RadioButton(); + this.radioVOne = new System.Windows.Forms.RadioButton(); + this.radioVZero = new System.Windows.Forms.RadioButton(); + this.radioVDefault = new System.Windows.Forms.RadioButton(); + this.okButton = new System.Windows.Forms.Button(); + this.cancelButton = new System.Windows.Forms.Button(); + this.panelM = new System.Windows.Forms.Panel(); + this.flagMLabel = new System.Windows.Forms.Label(); + this.radioMIndeterminate = new System.Windows.Forms.RadioButton(); + this.radioMOne = new System.Windows.Forms.RadioButton(); + this.radioMZero = new System.Windows.Forms.RadioButton(); + this.radioMDefault = new System.Windows.Forms.RadioButton(); + this.panelX = new System.Windows.Forms.Panel(); + this.flagXLabel = new System.Windows.Forms.Label(); + this.radioXIndeterminate = new System.Windows.Forms.RadioButton(); + this.radioXOne = new System.Windows.Forms.RadioButton(); + this.radioXZero = new System.Windows.Forms.RadioButton(); + this.radioXDefault = new System.Windows.Forms.RadioButton(); + this.panelD = new System.Windows.Forms.Panel(); + this.flagDLabel = new System.Windows.Forms.Label(); + this.radioDIndeterminate = new System.Windows.Forms.RadioButton(); + this.radioDOne = new System.Windows.Forms.RadioButton(); + this.radioDZero = new System.Windows.Forms.RadioButton(); + this.radioDDefault = new System.Windows.Forms.RadioButton(); + this.panelI = new System.Windows.Forms.Panel(); + this.flagILabel = new System.Windows.Forms.Label(); + this.radioIIndeterminate = new System.Windows.Forms.RadioButton(); + this.radioIOne = new System.Windows.Forms.RadioButton(); + this.radioIZero = new System.Windows.Forms.RadioButton(); + this.radioIDefault = new System.Windows.Forms.RadioButton(); + this.panelZ = new System.Windows.Forms.Panel(); + this.flagZLabel = new System.Windows.Forms.Label(); + this.radioZIndeterminate = new System.Windows.Forms.RadioButton(); + this.radioZOne = new System.Windows.Forms.RadioButton(); + this.radioZZero = new System.Windows.Forms.RadioButton(); + this.radioZDefault = new System.Windows.Forms.RadioButton(); + this.panelC = new System.Windows.Forms.Panel(); + this.flagCLabel = new System.Windows.Forms.Label(); + this.radioCIndeterminate = new System.Windows.Forms.RadioButton(); + this.radioCOne = new System.Windows.Forms.RadioButton(); + this.radioCZero = new System.Windows.Forms.RadioButton(); + this.radioCDefault = new System.Windows.Forms.RadioButton(); + this.panelE = new System.Windows.Forms.Panel(); + this.flagELabel = new System.Windows.Forms.Label(); + this.radioEIndeterminate = new System.Windows.Forms.RadioButton(); + this.radioEOne = new System.Windows.Forms.RadioButton(); + this.radioEZero = new System.Windows.Forms.RadioButton(); + this.radioEDefault = new System.Windows.Forms.RadioButton(); + this.instructionLabel = new System.Windows.Forms.Label(); + this.labelLongRegsNote = new System.Windows.Forms.Label(); + this.resetButton = new System.Windows.Forms.Button(); + this.panelN.SuspendLayout(); + this.panelV.SuspendLayout(); + this.panelM.SuspendLayout(); + this.panelX.SuspendLayout(); + this.panelD.SuspendLayout(); + this.panelI.SuspendLayout(); + this.panelZ.SuspendLayout(); + this.panelC.SuspendLayout(); + this.panelE.SuspendLayout(); + this.SuspendLayout(); + // + // defaultLabel + // + this.defaultLabel.Location = new System.Drawing.Point(12, 70); + this.defaultLabel.Name = "defaultLabel"; + this.defaultLabel.Size = new System.Drawing.Size(90, 20); + this.defaultLabel.TabIndex = 4; + this.defaultLabel.Text = "Default"; + this.defaultLabel.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // zeroLabel + // + this.zeroLabel.Location = new System.Drawing.Point(12, 90); + this.zeroLabel.Name = "zeroLabel"; + this.zeroLabel.Size = new System.Drawing.Size(90, 20); + this.zeroLabel.TabIndex = 5; + this.zeroLabel.Text = "Zero"; + this.zeroLabel.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // oneLabel + // + this.oneLabel.Location = new System.Drawing.Point(12, 110); + this.oneLabel.Name = "oneLabel"; + this.oneLabel.Size = new System.Drawing.Size(90, 20); + this.oneLabel.TabIndex = 6; + this.oneLabel.Text = "One"; + this.oneLabel.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // indeterminateLabel + // + this.indeterminateLabel.Location = new System.Drawing.Point(12, 130); + this.indeterminateLabel.Name = "indeterminateLabel"; + this.indeterminateLabel.Size = new System.Drawing.Size(90, 20); + this.indeterminateLabel.TabIndex = 7; + this.indeterminateLabel.Text = "Indeterminate"; + this.indeterminateLabel.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // panelN + // + this.panelN.Controls.Add(this.flagNLabel); + this.panelN.Controls.Add(this.radioNIndeterminate); + this.panelN.Controls.Add(this.radioNOne); + this.panelN.Controls.Add(this.radioNZero); + this.panelN.Controls.Add(this.radioNDefault); + this.panelN.Location = new System.Drawing.Point(108, 47); + this.panelN.Name = "panelN"; + this.panelN.Size = new System.Drawing.Size(26, 110); + this.panelN.TabIndex = 9; + // + // flagNLabel + // + this.flagNLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.flagNLabel.Location = new System.Drawing.Point(3, 0); + this.flagNLabel.Name = "flagNLabel"; + this.flagNLabel.Size = new System.Drawing.Size(20, 20); + this.flagNLabel.TabIndex = 0; + this.flagNLabel.Text = "N"; + this.flagNLabel.TextAlign = System.Drawing.ContentAlignment.TopCenter; + // + // radioNIndeterminate + // + this.radioNIndeterminate.AutoSize = true; + this.radioNIndeterminate.Location = new System.Drawing.Point(6, 83); + this.radioNIndeterminate.Name = "radioNIndeterminate"; + this.radioNIndeterminate.Size = new System.Drawing.Size(14, 13); + this.radioNIndeterminate.TabIndex = 4; + this.radioNIndeterminate.TabStop = true; + this.radioNIndeterminate.UseVisualStyleBackColor = true; + // + // radioNOne + // + this.radioNOne.AutoSize = true; + this.radioNOne.Location = new System.Drawing.Point(6, 63); + this.radioNOne.Name = "radioNOne"; + this.radioNOne.Size = new System.Drawing.Size(14, 13); + this.radioNOne.TabIndex = 3; + this.radioNOne.TabStop = true; + this.radioNOne.UseVisualStyleBackColor = true; + // + // radioNZero + // + this.radioNZero.AutoSize = true; + this.radioNZero.Location = new System.Drawing.Point(6, 43); + this.radioNZero.Name = "radioNZero"; + this.radioNZero.Size = new System.Drawing.Size(14, 13); + this.radioNZero.TabIndex = 2; + this.radioNZero.TabStop = true; + this.radioNZero.UseVisualStyleBackColor = true; + // + // radioNDefault + // + this.radioNDefault.AutoSize = true; + this.radioNDefault.Location = new System.Drawing.Point(6, 23); + this.radioNDefault.Name = "radioNDefault"; + this.radioNDefault.Size = new System.Drawing.Size(14, 13); + this.radioNDefault.TabIndex = 1; + this.radioNDefault.TabStop = true; + this.radioNDefault.UseVisualStyleBackColor = true; + // + // panelV + // + this.panelV.Controls.Add(this.flagVLabel); + this.panelV.Controls.Add(this.radioVIndeterminate); + this.panelV.Controls.Add(this.radioVOne); + this.panelV.Controls.Add(this.radioVZero); + this.panelV.Controls.Add(this.radioVDefault); + this.panelV.Location = new System.Drawing.Point(140, 47); + this.panelV.Name = "panelV"; + this.panelV.Size = new System.Drawing.Size(26, 110); + this.panelV.TabIndex = 14; + // + // flagVLabel + // + this.flagVLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.flagVLabel.Location = new System.Drawing.Point(3, 0); + this.flagVLabel.Name = "flagVLabel"; + this.flagVLabel.Size = new System.Drawing.Size(20, 20); + this.flagVLabel.TabIndex = 0; + this.flagVLabel.Text = "V"; + this.flagVLabel.TextAlign = System.Drawing.ContentAlignment.TopCenter; + // + // radioVIndeterminate + // + this.radioVIndeterminate.AutoSize = true; + this.radioVIndeterminate.Location = new System.Drawing.Point(6, 83); + this.radioVIndeterminate.Name = "radioVIndeterminate"; + this.radioVIndeterminate.Size = new System.Drawing.Size(14, 13); + this.radioVIndeterminate.TabIndex = 4; + this.radioVIndeterminate.TabStop = true; + this.radioVIndeterminate.UseVisualStyleBackColor = true; + // + // radioVOne + // + this.radioVOne.AutoSize = true; + this.radioVOne.Location = new System.Drawing.Point(6, 63); + this.radioVOne.Name = "radioVOne"; + this.radioVOne.Size = new System.Drawing.Size(14, 13); + this.radioVOne.TabIndex = 3; + this.radioVOne.TabStop = true; + this.radioVOne.UseVisualStyleBackColor = true; + // + // radioVZero + // + this.radioVZero.AutoSize = true; + this.radioVZero.Location = new System.Drawing.Point(6, 43); + this.radioVZero.Name = "radioVZero"; + this.radioVZero.Size = new System.Drawing.Size(14, 13); + this.radioVZero.TabIndex = 2; + this.radioVZero.TabStop = true; + this.radioVZero.UseVisualStyleBackColor = true; + // + // radioVDefault + // + this.radioVDefault.AutoSize = true; + this.radioVDefault.Location = new System.Drawing.Point(6, 23); + this.radioVDefault.Name = "radioVDefault"; + this.radioVDefault.Size = new System.Drawing.Size(14, 13); + this.radioVDefault.TabIndex = 1; + this.radioVDefault.TabStop = true; + this.radioVDefault.UseVisualStyleBackColor = true; + // + // okButton + // + this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.okButton.Location = new System.Drawing.Point(281, 205); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(75, 23); + this.okButton.TabIndex = 0; + this.okButton.Text = "OK"; + this.okButton.UseVisualStyleBackColor = true; + this.okButton.Click += new System.EventHandler(this.okButton_Click); + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(362, 205); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 1; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + // + // panelM + // + this.panelM.Controls.Add(this.flagMLabel); + this.panelM.Controls.Add(this.radioMIndeterminate); + this.panelM.Controls.Add(this.radioMOne); + this.panelM.Controls.Add(this.radioMZero); + this.panelM.Controls.Add(this.radioMDefault); + this.panelM.Location = new System.Drawing.Point(172, 47); + this.panelM.Name = "panelM"; + this.panelM.Size = new System.Drawing.Size(26, 110); + this.panelM.TabIndex = 15; + // + // flagMLabel + // + this.flagMLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.flagMLabel.Location = new System.Drawing.Point(3, 0); + this.flagMLabel.Name = "flagMLabel"; + this.flagMLabel.Size = new System.Drawing.Size(20, 20); + this.flagMLabel.TabIndex = 0; + this.flagMLabel.Text = "M"; + this.flagMLabel.TextAlign = System.Drawing.ContentAlignment.TopCenter; + // + // radioMIndeterminate + // + this.radioMIndeterminate.AutoSize = true; + this.radioMIndeterminate.Location = new System.Drawing.Point(6, 83); + this.radioMIndeterminate.Name = "radioMIndeterminate"; + this.radioMIndeterminate.Size = new System.Drawing.Size(14, 13); + this.radioMIndeterminate.TabIndex = 4; + this.radioMIndeterminate.TabStop = true; + this.radioMIndeterminate.UseVisualStyleBackColor = true; + // + // radioMOne + // + this.radioMOne.AutoSize = true; + this.radioMOne.Location = new System.Drawing.Point(6, 63); + this.radioMOne.Name = "radioMOne"; + this.radioMOne.Size = new System.Drawing.Size(14, 13); + this.radioMOne.TabIndex = 3; + this.radioMOne.TabStop = true; + this.radioMOne.UseVisualStyleBackColor = true; + // + // radioMZero + // + this.radioMZero.AutoSize = true; + this.radioMZero.Location = new System.Drawing.Point(6, 43); + this.radioMZero.Name = "radioMZero"; + this.radioMZero.Size = new System.Drawing.Size(14, 13); + this.radioMZero.TabIndex = 2; + this.radioMZero.TabStop = true; + this.radioMZero.UseVisualStyleBackColor = true; + // + // radioMDefault + // + this.radioMDefault.AutoSize = true; + this.radioMDefault.Location = new System.Drawing.Point(6, 23); + this.radioMDefault.Name = "radioMDefault"; + this.radioMDefault.Size = new System.Drawing.Size(14, 13); + this.radioMDefault.TabIndex = 1; + this.radioMDefault.TabStop = true; + this.radioMDefault.UseVisualStyleBackColor = true; + // + // panelX + // + this.panelX.Controls.Add(this.flagXLabel); + this.panelX.Controls.Add(this.radioXIndeterminate); + this.panelX.Controls.Add(this.radioXOne); + this.panelX.Controls.Add(this.radioXZero); + this.panelX.Controls.Add(this.radioXDefault); + this.panelX.Location = new System.Drawing.Point(204, 47); + this.panelX.Name = "panelX"; + this.panelX.Size = new System.Drawing.Size(26, 110); + this.panelX.TabIndex = 16; + // + // flagXLabel + // + this.flagXLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.flagXLabel.Location = new System.Drawing.Point(3, 0); + this.flagXLabel.Name = "flagXLabel"; + this.flagXLabel.Size = new System.Drawing.Size(20, 20); + this.flagXLabel.TabIndex = 0; + this.flagXLabel.Text = "X"; + this.flagXLabel.TextAlign = System.Drawing.ContentAlignment.TopCenter; + // + // radioXIndeterminate + // + this.radioXIndeterminate.AutoSize = true; + this.radioXIndeterminate.Location = new System.Drawing.Point(6, 83); + this.radioXIndeterminate.Name = "radioXIndeterminate"; + this.radioXIndeterminate.Size = new System.Drawing.Size(14, 13); + this.radioXIndeterminate.TabIndex = 4; + this.radioXIndeterminate.TabStop = true; + this.radioXIndeterminate.UseVisualStyleBackColor = true; + // + // radioXOne + // + this.radioXOne.AutoSize = true; + this.radioXOne.Location = new System.Drawing.Point(6, 63); + this.radioXOne.Name = "radioXOne"; + this.radioXOne.Size = new System.Drawing.Size(14, 13); + this.radioXOne.TabIndex = 3; + this.radioXOne.TabStop = true; + this.radioXOne.UseVisualStyleBackColor = true; + // + // radioXZero + // + this.radioXZero.AutoSize = true; + this.radioXZero.Location = new System.Drawing.Point(6, 43); + this.radioXZero.Name = "radioXZero"; + this.radioXZero.Size = new System.Drawing.Size(14, 13); + this.radioXZero.TabIndex = 2; + this.radioXZero.TabStop = true; + this.radioXZero.UseVisualStyleBackColor = true; + // + // radioXDefault + // + this.radioXDefault.AutoSize = true; + this.radioXDefault.Location = new System.Drawing.Point(6, 23); + this.radioXDefault.Name = "radioXDefault"; + this.radioXDefault.Size = new System.Drawing.Size(14, 13); + this.radioXDefault.TabIndex = 1; + this.radioXDefault.TabStop = true; + this.radioXDefault.UseVisualStyleBackColor = true; + // + // panelD + // + this.panelD.Controls.Add(this.flagDLabel); + this.panelD.Controls.Add(this.radioDIndeterminate); + this.panelD.Controls.Add(this.radioDOne); + this.panelD.Controls.Add(this.radioDZero); + this.panelD.Controls.Add(this.radioDDefault); + this.panelD.Location = new System.Drawing.Point(236, 47); + this.panelD.Name = "panelD"; + this.panelD.Size = new System.Drawing.Size(26, 110); + this.panelD.TabIndex = 17; + // + // flagDLabel + // + this.flagDLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.flagDLabel.Location = new System.Drawing.Point(3, 0); + this.flagDLabel.Name = "flagDLabel"; + this.flagDLabel.Size = new System.Drawing.Size(20, 20); + this.flagDLabel.TabIndex = 0; + this.flagDLabel.Text = "D"; + this.flagDLabel.TextAlign = System.Drawing.ContentAlignment.TopCenter; + // + // radioDIndeterminate + // + this.radioDIndeterminate.AutoSize = true; + this.radioDIndeterminate.Location = new System.Drawing.Point(6, 83); + this.radioDIndeterminate.Name = "radioDIndeterminate"; + this.radioDIndeterminate.Size = new System.Drawing.Size(14, 13); + this.radioDIndeterminate.TabIndex = 4; + this.radioDIndeterminate.TabStop = true; + this.radioDIndeterminate.UseVisualStyleBackColor = true; + // + // radioDOne + // + this.radioDOne.AutoSize = true; + this.radioDOne.Location = new System.Drawing.Point(6, 63); + this.radioDOne.Name = "radioDOne"; + this.radioDOne.Size = new System.Drawing.Size(14, 13); + this.radioDOne.TabIndex = 3; + this.radioDOne.TabStop = true; + this.radioDOne.UseVisualStyleBackColor = true; + // + // radioDZero + // + this.radioDZero.AutoSize = true; + this.radioDZero.Location = new System.Drawing.Point(6, 43); + this.radioDZero.Name = "radioDZero"; + this.radioDZero.Size = new System.Drawing.Size(14, 13); + this.radioDZero.TabIndex = 2; + this.radioDZero.TabStop = true; + this.radioDZero.UseVisualStyleBackColor = true; + // + // radioDDefault + // + this.radioDDefault.AutoSize = true; + this.radioDDefault.Location = new System.Drawing.Point(6, 23); + this.radioDDefault.Name = "radioDDefault"; + this.radioDDefault.Size = new System.Drawing.Size(14, 13); + this.radioDDefault.TabIndex = 1; + this.radioDDefault.TabStop = true; + this.radioDDefault.UseVisualStyleBackColor = true; + // + // panelI + // + this.panelI.Controls.Add(this.flagILabel); + this.panelI.Controls.Add(this.radioIIndeterminate); + this.panelI.Controls.Add(this.radioIOne); + this.panelI.Controls.Add(this.radioIZero); + this.panelI.Controls.Add(this.radioIDefault); + this.panelI.Location = new System.Drawing.Point(268, 47); + this.panelI.Name = "panelI"; + this.panelI.Size = new System.Drawing.Size(26, 110); + this.panelI.TabIndex = 18; + // + // flagILabel + // + this.flagILabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.flagILabel.Location = new System.Drawing.Point(3, 0); + this.flagILabel.Name = "flagILabel"; + this.flagILabel.Size = new System.Drawing.Size(20, 20); + this.flagILabel.TabIndex = 0; + this.flagILabel.Text = "I"; + this.flagILabel.TextAlign = System.Drawing.ContentAlignment.TopCenter; + // + // radioIIndeterminate + // + this.radioIIndeterminate.AutoSize = true; + this.radioIIndeterminate.Location = new System.Drawing.Point(6, 83); + this.radioIIndeterminate.Name = "radioIIndeterminate"; + this.radioIIndeterminate.Size = new System.Drawing.Size(14, 13); + this.radioIIndeterminate.TabIndex = 4; + this.radioIIndeterminate.TabStop = true; + this.radioIIndeterminate.UseVisualStyleBackColor = true; + // + // radioIOne + // + this.radioIOne.AutoSize = true; + this.radioIOne.Location = new System.Drawing.Point(6, 63); + this.radioIOne.Name = "radioIOne"; + this.radioIOne.Size = new System.Drawing.Size(14, 13); + this.radioIOne.TabIndex = 3; + this.radioIOne.TabStop = true; + this.radioIOne.UseVisualStyleBackColor = true; + // + // radioIZero + // + this.radioIZero.AutoSize = true; + this.radioIZero.Location = new System.Drawing.Point(6, 43); + this.radioIZero.Name = "radioIZero"; + this.radioIZero.Size = new System.Drawing.Size(14, 13); + this.radioIZero.TabIndex = 2; + this.radioIZero.TabStop = true; + this.radioIZero.UseVisualStyleBackColor = true; + // + // radioIDefault + // + this.radioIDefault.AutoSize = true; + this.radioIDefault.Location = new System.Drawing.Point(6, 23); + this.radioIDefault.Name = "radioIDefault"; + this.radioIDefault.Size = new System.Drawing.Size(14, 13); + this.radioIDefault.TabIndex = 1; + this.radioIDefault.TabStop = true; + this.radioIDefault.UseVisualStyleBackColor = true; + // + // panelZ + // + this.panelZ.Controls.Add(this.flagZLabel); + this.panelZ.Controls.Add(this.radioZIndeterminate); + this.panelZ.Controls.Add(this.radioZOne); + this.panelZ.Controls.Add(this.radioZZero); + this.panelZ.Controls.Add(this.radioZDefault); + this.panelZ.Location = new System.Drawing.Point(300, 47); + this.panelZ.Name = "panelZ"; + this.panelZ.Size = new System.Drawing.Size(26, 110); + this.panelZ.TabIndex = 19; + // + // flagZLabel + // + this.flagZLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.flagZLabel.Location = new System.Drawing.Point(3, 0); + this.flagZLabel.Name = "flagZLabel"; + this.flagZLabel.Size = new System.Drawing.Size(20, 20); + this.flagZLabel.TabIndex = 0; + this.flagZLabel.Text = "Z"; + this.flagZLabel.TextAlign = System.Drawing.ContentAlignment.TopCenter; + // + // radioZIndeterminate + // + this.radioZIndeterminate.AutoSize = true; + this.radioZIndeterminate.Location = new System.Drawing.Point(6, 83); + this.radioZIndeterminate.Name = "radioZIndeterminate"; + this.radioZIndeterminate.Size = new System.Drawing.Size(14, 13); + this.radioZIndeterminate.TabIndex = 4; + this.radioZIndeterminate.TabStop = true; + this.radioZIndeterminate.UseVisualStyleBackColor = true; + // + // radioZOne + // + this.radioZOne.AutoSize = true; + this.radioZOne.Location = new System.Drawing.Point(6, 63); + this.radioZOne.Name = "radioZOne"; + this.radioZOne.Size = new System.Drawing.Size(14, 13); + this.radioZOne.TabIndex = 3; + this.radioZOne.TabStop = true; + this.radioZOne.UseVisualStyleBackColor = true; + // + // radioZZero + // + this.radioZZero.AutoSize = true; + this.radioZZero.Location = new System.Drawing.Point(6, 43); + this.radioZZero.Name = "radioZZero"; + this.radioZZero.Size = new System.Drawing.Size(14, 13); + this.radioZZero.TabIndex = 2; + this.radioZZero.TabStop = true; + this.radioZZero.UseVisualStyleBackColor = true; + // + // radioZDefault + // + this.radioZDefault.AutoSize = true; + this.radioZDefault.Location = new System.Drawing.Point(6, 23); + this.radioZDefault.Name = "radioZDefault"; + this.radioZDefault.Size = new System.Drawing.Size(14, 13); + this.radioZDefault.TabIndex = 1; + this.radioZDefault.TabStop = true; + this.radioZDefault.UseVisualStyleBackColor = true; + // + // panelC + // + this.panelC.Controls.Add(this.flagCLabel); + this.panelC.Controls.Add(this.radioCIndeterminate); + this.panelC.Controls.Add(this.radioCOne); + this.panelC.Controls.Add(this.radioCZero); + this.panelC.Controls.Add(this.radioCDefault); + this.panelC.Location = new System.Drawing.Point(332, 47); + this.panelC.Name = "panelC"; + this.panelC.Size = new System.Drawing.Size(26, 110); + this.panelC.TabIndex = 20; + // + // flagCLabel + // + this.flagCLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.flagCLabel.Location = new System.Drawing.Point(3, 0); + this.flagCLabel.Name = "flagCLabel"; + this.flagCLabel.Size = new System.Drawing.Size(20, 20); + this.flagCLabel.TabIndex = 0; + this.flagCLabel.Text = "C"; + this.flagCLabel.TextAlign = System.Drawing.ContentAlignment.TopCenter; + // + // radioCIndeterminate + // + this.radioCIndeterminate.AutoSize = true; + this.radioCIndeterminate.Location = new System.Drawing.Point(6, 83); + this.radioCIndeterminate.Name = "radioCIndeterminate"; + this.radioCIndeterminate.Size = new System.Drawing.Size(14, 13); + this.radioCIndeterminate.TabIndex = 4; + this.radioCIndeterminate.TabStop = true; + this.radioCIndeterminate.UseVisualStyleBackColor = true; + // + // radioCOne + // + this.radioCOne.AutoSize = true; + this.radioCOne.Location = new System.Drawing.Point(6, 63); + this.radioCOne.Name = "radioCOne"; + this.radioCOne.Size = new System.Drawing.Size(14, 13); + this.radioCOne.TabIndex = 3; + this.radioCOne.TabStop = true; + this.radioCOne.UseVisualStyleBackColor = true; + // + // radioCZero + // + this.radioCZero.AutoSize = true; + this.radioCZero.Location = new System.Drawing.Point(6, 43); + this.radioCZero.Name = "radioCZero"; + this.radioCZero.Size = new System.Drawing.Size(14, 13); + this.radioCZero.TabIndex = 2; + this.radioCZero.TabStop = true; + this.radioCZero.UseVisualStyleBackColor = true; + // + // radioCDefault + // + this.radioCDefault.AutoSize = true; + this.radioCDefault.Location = new System.Drawing.Point(6, 23); + this.radioCDefault.Name = "radioCDefault"; + this.radioCDefault.Size = new System.Drawing.Size(14, 13); + this.radioCDefault.TabIndex = 1; + this.radioCDefault.TabStop = true; + this.radioCDefault.UseVisualStyleBackColor = true; + // + // panelE + // + this.panelE.Controls.Add(this.flagELabel); + this.panelE.Controls.Add(this.radioEIndeterminate); + this.panelE.Controls.Add(this.radioEOne); + this.panelE.Controls.Add(this.radioEZero); + this.panelE.Controls.Add(this.radioEDefault); + this.panelE.Location = new System.Drawing.Point(381, 47); + this.panelE.Name = "panelE"; + this.panelE.Size = new System.Drawing.Size(26, 110); + this.panelE.TabIndex = 21; + // + // flagELabel + // + this.flagELabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.flagELabel.Location = new System.Drawing.Point(3, 0); + this.flagELabel.Name = "flagELabel"; + this.flagELabel.Size = new System.Drawing.Size(20, 20); + this.flagELabel.TabIndex = 0; + this.flagELabel.Text = "E"; + this.flagELabel.TextAlign = System.Drawing.ContentAlignment.TopCenter; + // + // radioEIndeterminate + // + this.radioEIndeterminate.AutoSize = true; + this.radioEIndeterminate.Location = new System.Drawing.Point(6, 83); + this.radioEIndeterminate.Name = "radioEIndeterminate"; + this.radioEIndeterminate.Size = new System.Drawing.Size(14, 13); + this.radioEIndeterminate.TabIndex = 4; + this.radioEIndeterminate.TabStop = true; + this.radioEIndeterminate.UseVisualStyleBackColor = true; + // + // radioEOne + // + this.radioEOne.AutoSize = true; + this.radioEOne.Location = new System.Drawing.Point(6, 63); + this.radioEOne.Name = "radioEOne"; + this.radioEOne.Size = new System.Drawing.Size(14, 13); + this.radioEOne.TabIndex = 3; + this.radioEOne.TabStop = true; + this.radioEOne.UseVisualStyleBackColor = true; + // + // radioEZero + // + this.radioEZero.AutoSize = true; + this.radioEZero.Location = new System.Drawing.Point(6, 43); + this.radioEZero.Name = "radioEZero"; + this.radioEZero.Size = new System.Drawing.Size(14, 13); + this.radioEZero.TabIndex = 2; + this.radioEZero.TabStop = true; + this.radioEZero.UseVisualStyleBackColor = true; + // + // radioEDefault + // + this.radioEDefault.AutoSize = true; + this.radioEDefault.Location = new System.Drawing.Point(6, 23); + this.radioEDefault.Name = "radioEDefault"; + this.radioEDefault.Size = new System.Drawing.Size(14, 13); + this.radioEDefault.TabIndex = 1; + this.radioEDefault.TabStop = true; + this.radioEDefault.UseVisualStyleBackColor = true; + // + // instructionLabel + // + this.instructionLabel.AutoSize = true; + this.instructionLabel.Location = new System.Drawing.Point(12, 9); + this.instructionLabel.Name = "instructionLabel"; + this.instructionLabel.Size = new System.Drawing.Size(297, 13); + this.instructionLabel.TabIndex = 3; + this.instructionLabel.Text = "Override processor state values determined by code analyzer:"; + // + // labelLongRegsNote + // + this.labelLongRegsNote.AutoSize = true; + this.labelLongRegsNote.Location = new System.Drawing.Point(13, 169); + this.labelLongRegsNote.Name = "labelLongRegsNote"; + this.labelLongRegsNote.Size = new System.Drawing.Size(341, 13); + this.labelLongRegsNote.TabIndex = 8; + this.labelLongRegsNote.Text = "Tip: to configure 16-bit registers on 65802/65816, set M, X, and E to 0."; + // + // resetButton + // + this.resetButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.resetButton.Location = new System.Drawing.Point(12, 205); + this.resetButton.Name = "resetButton"; + this.resetButton.Size = new System.Drawing.Size(112, 23); + this.resetButton.TabIndex = 2; + this.resetButton.Text = "Reset to Default"; + this.resetButton.UseVisualStyleBackColor = true; + this.resetButton.Click += new System.EventHandler(this.resetButton_Click); + // + // EditStatusFlags + // + this.AcceptButton = this.okButton; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(449, 240); + this.Controls.Add(this.resetButton); + this.Controls.Add(this.labelLongRegsNote); + this.Controls.Add(this.instructionLabel); + this.Controls.Add(this.panelE); + this.Controls.Add(this.panelC); + this.Controls.Add(this.panelZ); + this.Controls.Add(this.panelI); + this.Controls.Add(this.panelD); + this.Controls.Add(this.panelX); + this.Controls.Add(this.panelM); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.okButton); + this.Controls.Add(this.panelV); + this.Controls.Add(this.panelN); + this.Controls.Add(this.indeterminateLabel); + this.Controls.Add(this.oneLabel); + this.Controls.Add(this.zeroLabel); + this.Controls.Add(this.defaultLabel); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "EditStatusFlags"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Edit Status Flag Override"; + this.Load += new System.EventHandler(this.EditStatusFlags_Load); + this.panelN.ResumeLayout(false); + this.panelN.PerformLayout(); + this.panelV.ResumeLayout(false); + this.panelV.PerformLayout(); + this.panelM.ResumeLayout(false); + this.panelM.PerformLayout(); + this.panelX.ResumeLayout(false); + this.panelX.PerformLayout(); + this.panelD.ResumeLayout(false); + this.panelD.PerformLayout(); + this.panelI.ResumeLayout(false); + this.panelI.PerformLayout(); + this.panelZ.ResumeLayout(false); + this.panelZ.PerformLayout(); + this.panelC.ResumeLayout(false); + this.panelC.PerformLayout(); + this.panelE.ResumeLayout(false); + this.panelE.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label defaultLabel; + private System.Windows.Forms.Label zeroLabel; + private System.Windows.Forms.Label oneLabel; + private System.Windows.Forms.Label indeterminateLabel; + private System.Windows.Forms.Panel panelN; + private System.Windows.Forms.Label flagNLabel; + private System.Windows.Forms.RadioButton radioNIndeterminate; + private System.Windows.Forms.RadioButton radioNOne; + private System.Windows.Forms.RadioButton radioNZero; + private System.Windows.Forms.RadioButton radioNDefault; + private System.Windows.Forms.Panel panelV; + private System.Windows.Forms.Label flagVLabel; + private System.Windows.Forms.RadioButton radioVIndeterminate; + private System.Windows.Forms.RadioButton radioVOne; + private System.Windows.Forms.RadioButton radioVZero; + private System.Windows.Forms.RadioButton radioVDefault; + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.Panel panelM; + private System.Windows.Forms.Label flagMLabel; + private System.Windows.Forms.RadioButton radioMIndeterminate; + private System.Windows.Forms.RadioButton radioMOne; + private System.Windows.Forms.RadioButton radioMZero; + private System.Windows.Forms.RadioButton radioMDefault; + private System.Windows.Forms.Panel panelX; + private System.Windows.Forms.Label flagXLabel; + private System.Windows.Forms.RadioButton radioXIndeterminate; + private System.Windows.Forms.RadioButton radioXOne; + private System.Windows.Forms.RadioButton radioXZero; + private System.Windows.Forms.RadioButton radioXDefault; + private System.Windows.Forms.Panel panelD; + private System.Windows.Forms.Label flagDLabel; + private System.Windows.Forms.RadioButton radioDIndeterminate; + private System.Windows.Forms.RadioButton radioDOne; + private System.Windows.Forms.RadioButton radioDZero; + private System.Windows.Forms.RadioButton radioDDefault; + private System.Windows.Forms.Panel panelI; + private System.Windows.Forms.Label flagILabel; + private System.Windows.Forms.RadioButton radioIIndeterminate; + private System.Windows.Forms.RadioButton radioIOne; + private System.Windows.Forms.RadioButton radioIZero; + private System.Windows.Forms.RadioButton radioIDefault; + private System.Windows.Forms.Panel panelZ; + private System.Windows.Forms.Label flagZLabel; + private System.Windows.Forms.RadioButton radioZIndeterminate; + private System.Windows.Forms.RadioButton radioZOne; + private System.Windows.Forms.RadioButton radioZZero; + private System.Windows.Forms.RadioButton radioZDefault; + private System.Windows.Forms.Panel panelC; + private System.Windows.Forms.Label flagCLabel; + private System.Windows.Forms.RadioButton radioCIndeterminate; + private System.Windows.Forms.RadioButton radioCOne; + private System.Windows.Forms.RadioButton radioCZero; + private System.Windows.Forms.RadioButton radioCDefault; + private System.Windows.Forms.Panel panelE; + private System.Windows.Forms.Label flagELabel; + private System.Windows.Forms.RadioButton radioEIndeterminate; + private System.Windows.Forms.RadioButton radioEOne; + private System.Windows.Forms.RadioButton radioEZero; + private System.Windows.Forms.RadioButton radioEDefault; + private System.Windows.Forms.Label instructionLabel; + private System.Windows.Forms.Label labelLongRegsNote; + private System.Windows.Forms.Button resetButton; + } +} diff --git a/SourceGen/AppForms/EditStatusFlags.cs b/SourceGen/AppForms/EditStatusFlags.cs new file mode 100644 index 0000000..448d26d --- /dev/null +++ b/SourceGen/AppForms/EditStatusFlags.cs @@ -0,0 +1,140 @@ +/* + * Copyright 2018 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.Diagnostics; +using System.Windows.Forms; + +using Asm65; + +namespace SourceGen.AppForms { + public partial class EditStatusFlags : Form { + /// + /// In/out status flag value. + /// + public StatusFlags FlagValue { get; set; } + + /// + /// Set this if the CPU has an emulation flag (65802/65816). If this isn't + /// set, the M, X, and E flag buttons will be disabled. + /// + public bool HasEmuFlag { get; set; } + + + public EditStatusFlags() { + InitializeComponent(); + } + + private void EditStatusFlags_Load(object sender, EventArgs e) { + if (!HasEmuFlag) { + panelM.Enabled = false; + panelX.Enabled = false; + panelE.Enabled = false; + + // I'm not going to force the M/X/E flags to have a particular value based + // on the CPU definition. The flags aren't used for non-65802/65816, so + // the values are irrelevant. If somebody is switching between CPUs I think + // it'd be weird to force the values during editing but leave any non-edited + // values alone. If they want to switch to 65816, set M/X/E, and then switch + // back, they're welcome to do so. + } + + SetCheckedButtons(); + } + + /// + /// Calls SetChecked() for each flag. + /// + private void SetCheckedButtons() { + SetChecked(FlagValue.N, radioNDefault, radioNZero, radioNOne, radioNIndeterminate); + SetChecked(FlagValue.V, radioVDefault, radioVZero, radioVOne, radioVIndeterminate); + SetChecked(FlagValue.M, radioMDefault, radioMZero, radioMOne, radioMIndeterminate); + SetChecked(FlagValue.X, radioXDefault, radioXZero, radioXOne, radioXIndeterminate); + SetChecked(FlagValue.D, radioDDefault, radioDZero, radioDOne, radioDIndeterminate); + SetChecked(FlagValue.I, radioIDefault, radioIZero, radioIOne, radioIIndeterminate); + SetChecked(FlagValue.Z, radioZDefault, radioZZero, radioZOne, radioZIndeterminate); + SetChecked(FlagValue.C, radioCDefault, radioCZero, radioCOne, radioCIndeterminate); + SetChecked(FlagValue.E, radioEDefault, radioEZero, radioEOne, radioEIndeterminate); + } + + /// + /// Sets the "checked" flag on the appropriate radio button. + /// + private void SetChecked(int value, RadioButton def, RadioButton zero, RadioButton one, + RadioButton indeterminate) { + switch (value) { + case TriState16.UNSPECIFIED: + def.Checked = true; + break; + case TriState16.INDETERMINATE: + indeterminate.Checked = true; + break; + case 0: + zero.Checked = true; + break; + case 1: + one.Checked = true; + break; + default: + throw new Exception("Unexpected value " + value); + } + } + + private void okButton_Click(object sender, EventArgs e) { + StatusFlags flags = new StatusFlags(); + + flags.N = GetChecked(radioNDefault, radioNZero, radioNOne, radioNIndeterminate); + flags.V = GetChecked(radioVDefault, radioVZero, radioVOne, radioVIndeterminate); + flags.M = GetChecked(radioMDefault, radioMZero, radioMOne, radioMIndeterminate); + flags.X = GetChecked(radioXDefault, radioXZero, radioXOne, radioXIndeterminate); + flags.D = GetChecked(radioDDefault, radioDZero, radioDOne, radioDIndeterminate); + flags.I = GetChecked(radioIDefault, radioIZero, radioIOne, radioIIndeterminate); + flags.Z = GetChecked(radioZDefault, radioZZero, radioZOne, radioZIndeterminate); + flags.C = GetChecked(radioCDefault, radioCZero, radioCOne, radioCIndeterminate); + flags.E = GetChecked(radioEDefault, radioEZero, radioEOne, radioEIndeterminate); + + //// If they're setting emulation mode, also set M/X to 1. This is implicitly + //// true, but things are a bit clearer if we make it explicit. + //if (flags.E == 1) { + // flags.M = flags.X = 1; + //} + + FlagValue = flags; + } + + /// + /// Identifies the checked radio button and returns the appropriate TriState16 value. + /// + private int GetChecked(RadioButton def, RadioButton zero, RadioButton one, + RadioButton indeterminate) { + if (zero.Checked) { + return 0; + } else if (one.Checked) { + return 1; + } else if (indeterminate.Checked) { + return TriState16.INDETERMINATE; + } else if (def.Checked) { + return TriState16.UNSPECIFIED; + } else { + throw new Exception("No radio button selected"); + } + } + + private void resetButton_Click(object sender, EventArgs e) { + FlagValue = new StatusFlags(); + SetCheckedButtons(); + } + } +} diff --git a/SourceGen/AppForms/EditStatusFlags.resx b/SourceGen/AppForms/EditStatusFlags.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/SourceGen/AppForms/EditStatusFlags.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/SourceGen/AppForms/FindBox.Designer.cs b/SourceGen/AppForms/FindBox.Designer.cs new file mode 100644 index 0000000..dc39102 --- /dev/null +++ b/SourceGen/AppForms/FindBox.Designer.cs @@ -0,0 +1,89 @@ +/* + * Copyright 2018 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. + */ +namespace SourceGen.AppForms { + partial class FindBox { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.okButton = new System.Windows.Forms.Button(); + this.findTextBox = new System.Windows.Forms.TextBox(); + this.SuspendLayout(); + // + // okButton + // + this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.okButton.Location = new System.Drawing.Point(222, 13); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(75, 23); + this.okButton.TabIndex = 1; + this.okButton.Text = "Find"; + this.okButton.UseVisualStyleBackColor = true; + this.okButton.Click += new System.EventHandler(this.okButton_Click); + // + // findTextBox + // + this.findTextBox.Location = new System.Drawing.Point(12, 15); + this.findTextBox.Name = "findTextBox"; + this.findTextBox.Size = new System.Drawing.Size(197, 20); + this.findTextBox.TabIndex = 0; + // + // FindBox + // + this.AcceptButton = this.okButton; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(309, 48); + this.Controls.Add(this.findTextBox); + this.Controls.Add(this.okButton); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "FindBox"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Find..."; + this.Load += new System.EventHandler(this.FindBox_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.TextBox findTextBox; + } +} \ No newline at end of file diff --git a/SourceGen/AppForms/FindBox.cs b/SourceGen/AppForms/FindBox.cs new file mode 100644 index 0000000..333ecf2 --- /dev/null +++ b/SourceGen/AppForms/FindBox.cs @@ -0,0 +1,50 @@ +/* + * Copyright 2018 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.Windows.Forms; + +namespace SourceGen.AppForms { + public partial class FindBox : Form { + /// + /// Text to find. + /// + public string TextToFind { get; set; } + + public FindBox() { + InitializeComponent(); + } + + private void FindBox_Load(object sender, EventArgs e) { + if (!string.IsNullOrEmpty(TextToFind)) { + findTextBox.Text = TextToFind; + findTextBox.SelectAll(); + } + } + + // Without a "cancel" button, the escape key does nothing. + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { + if (keyData == Keys.Escape) { + Close(); + return true; + } + return base.ProcessCmdKey(ref msg, keyData); + } + + private void okButton_Click(object sender, EventArgs e) { + TextToFind = findTextBox.Text; + } + } +} diff --git a/SourceGen/AppForms/FindBox.resx b/SourceGen/AppForms/FindBox.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/SourceGen/AppForms/FindBox.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/SourceGen/AppForms/GotoBox.Designer.cs b/SourceGen/AppForms/GotoBox.Designer.cs new file mode 100644 index 0000000..6891231 --- /dev/null +++ b/SourceGen/AppForms/GotoBox.Designer.cs @@ -0,0 +1,181 @@ +/* + * Copyright 2018 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. + */ +namespace SourceGen.AppForms { + partial class GotoBox { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.instructionLabel = new System.Windows.Forms.Label(); + this.targetTextBox = new System.Windows.Forms.TextBox(); + this.okButton = new System.Windows.Forms.Button(); + this.offsetLabel = new System.Windows.Forms.Label(); + this.addressLabel = new System.Windows.Forms.Label(); + this.labelLabel = new System.Windows.Forms.Label(); + this.addressValueLabel = new System.Windows.Forms.Label(); + this.offsetValueLabel = new System.Windows.Forms.Label(); + this.labelValueLabel = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // instructionLabel + // + this.instructionLabel.AutoSize = true; + this.instructionLabel.Location = new System.Drawing.Point(13, 13); + this.instructionLabel.Name = "instructionLabel"; + this.instructionLabel.Size = new System.Drawing.Size(215, 52); + this.instructionLabel.TabIndex = 0; + this.instructionLabel.Text = "Enter target location as one of:\r\n • Hex file offset (with \'+\', e.g. +500)\r\n • He" + + "x address (e.g. 1000, $1000, 00/1000)\r\n • Label (case-sensitive)\r\n"; + // + // targetTextBox + // + this.targetTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.targetTextBox.Location = new System.Drawing.Point(13, 78); + this.targetTextBox.Name = "targetTextBox"; + this.targetTextBox.Size = new System.Drawing.Size(215, 20); + this.targetTextBox.TabIndex = 1; + this.targetTextBox.TextChanged += new System.EventHandler(this.targetTextBox_TextChanged); + // + // okButton + // + this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.okButton.Location = new System.Drawing.Point(240, 76); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(75, 23); + this.okButton.TabIndex = 2; + this.okButton.Text = "Go"; + this.okButton.UseVisualStyleBackColor = true; + this.okButton.Click += new System.EventHandler(this.okButton_Click); + // + // offsetLabel + // + this.offsetLabel.AutoSize = true; + this.offsetLabel.Location = new System.Drawing.Point(13, 111); + this.offsetLabel.Name = "offsetLabel"; + this.offsetLabel.Size = new System.Drawing.Size(38, 13); + this.offsetLabel.TabIndex = 3; + this.offsetLabel.Text = "Offset:"; + // + // addressLabel + // + this.addressLabel.AutoSize = true; + this.addressLabel.Location = new System.Drawing.Point(12, 129); + this.addressLabel.Name = "addressLabel"; + this.addressLabel.Size = new System.Drawing.Size(48, 13); + this.addressLabel.TabIndex = 4; + this.addressLabel.Text = "Address:"; + // + // labelLabel + // + this.labelLabel.AutoSize = true; + this.labelLabel.Location = new System.Drawing.Point(12, 147); + this.labelLabel.Name = "labelLabel"; + this.labelLabel.Size = new System.Drawing.Size(36, 13); + this.labelLabel.TabIndex = 5; + this.labelLabel.Text = "Label:"; + // + // addressValueLabel + // + this.addressValueLabel.AutoSize = true; + this.addressValueLabel.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.addressValueLabel.Location = new System.Drawing.Point(66, 129); + this.addressValueLabel.Name = "addressValueLabel"; + this.addressValueLabel.Size = new System.Drawing.Size(49, 13); + this.addressValueLabel.TabIndex = 6; + this.addressValueLabel.Text = "01/2345"; + // + // offsetValueLabel + // + this.offsetValueLabel.AutoSize = true; + this.offsetValueLabel.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.offsetValueLabel.Location = new System.Drawing.Point(66, 111); + this.offsetValueLabel.Name = "offsetValueLabel"; + this.offsetValueLabel.Size = new System.Drawing.Size(37, 13); + this.offsetValueLabel.TabIndex = 7; + this.offsetValueLabel.Text = "+1234"; + // + // labelValueLabel + // + this.labelValueLabel.AutoSize = true; + this.labelValueLabel.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.labelValueLabel.Location = new System.Drawing.Point(66, 147); + this.labelValueLabel.Name = "labelValueLabel"; + this.labelValueLabel.Size = new System.Drawing.Size(37, 13); + this.labelValueLabel.TabIndex = 8; + this.labelValueLabel.Text = "FUBAR"; + // + // GotoBox + // + this.AcceptButton = this.okButton; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(327, 171); + this.Controls.Add(this.labelValueLabel); + this.Controls.Add(this.offsetValueLabel); + this.Controls.Add(this.addressValueLabel); + this.Controls.Add(this.labelLabel); + this.Controls.Add(this.addressLabel); + this.Controls.Add(this.offsetLabel); + this.Controls.Add(this.okButton); + this.Controls.Add(this.targetTextBox); + this.Controls.Add(this.instructionLabel); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "GotoBox"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Go To Line"; + this.Load += new System.EventHandler(this.GotoBox_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label instructionLabel; + private System.Windows.Forms.TextBox targetTextBox; + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.Label offsetLabel; + private System.Windows.Forms.Label addressLabel; + private System.Windows.Forms.Label labelLabel; + private System.Windows.Forms.Label addressValueLabel; + private System.Windows.Forms.Label offsetValueLabel; + private System.Windows.Forms.Label labelValueLabel; + } +} \ No newline at end of file diff --git a/SourceGen/AppForms/GotoBox.cs b/SourceGen/AppForms/GotoBox.cs new file mode 100644 index 0000000..f4c5245 --- /dev/null +++ b/SourceGen/AppForms/GotoBox.cs @@ -0,0 +1,119 @@ +/* + * Copyright 2018 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.Windows.Forms; + +using Asm65; + +namespace SourceGen.AppForms { + public partial class GotoBox : Form { + /// + /// On success, this will hold the target offset. + /// + public int TargetOffset { get; private set; } + + /// + /// Reference to project. + /// + private DisasmProject mProject; + + /// + /// Reference to formatter. This determines how values are displayed. + /// + private Formatter mFormatter; + + + public GotoBox(DisasmProject proj, Formatter formatter) { + InitializeComponent(); + + mProject = proj; + mFormatter = formatter; + TargetOffset = -1; + } + + private void GotoBox_Load(object sender, EventArgs e) { + UpdateDisplay(); + } + + // Without a "cancel" button, the escape key does nothing. + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { + if (keyData == Keys.Escape) { + Close(); + return true; + } + return base.ProcessCmdKey(ref msg, keyData); + } + + private void okButton_Click(object sender, EventArgs e) { + } + + private void targetTextBox_TextChanged(object sender, EventArgs e) { + ProcessInput(); + UpdateDisplay(); + okButton.Enabled = (TargetOffset >= 0); + } + + private void ProcessInput() { + TargetOffset = -1; + + string input = targetTextBox.Text.Trim(); + if (string.IsNullOrEmpty(input)) { + return; + } + if (input[0] == '+') { + // this can only be an offset; convert as hexadecimal number + try { + TargetOffset = Convert.ToInt32(input.Substring(1), 16); + } catch (Exception) { + } + return; + } + + // Try it as a label. If they give the label a hex name (e.g. "A001") they + // can prefix it with '$' to disambiguate the address. + int labelOffset = mProject.FindLabelByName(input); + if (labelOffset >= 0) { + TargetOffset = labelOffset; + } else if (Address.ParseAddress(input, 1<<24, out int addr)) { + // could be a valid address + int offset = mProject.AddrMap.AddressToOffset(0, addr); + if (offset >= 0) { + TargetOffset = offset; + } + } + } + + private void UpdateDisplay() { + string offsetStr = string.Empty; + string addressStr = string.Empty; + string labelStr = string.Empty; + + if (TargetOffset >= 0) { + offsetStr = mFormatter.FormatOffset24(TargetOffset); + int addr = mProject.GetAnattrib(TargetOffset).Address; + addressStr = mFormatter.FormatAddress(addr, addr > 0xffff); + Symbol sym = mProject.GetAnattrib(TargetOffset).Symbol; + if (sym != null) { + labelStr = sym.Label; + } + } + + offsetValueLabel.Text = offsetStr; + addressValueLabel.Text = addressStr; + labelValueLabel.Text = labelStr; + } + } +} diff --git a/SourceGen/AppForms/GotoBox.resx b/SourceGen/AppForms/GotoBox.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/SourceGen/AppForms/GotoBox.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/SourceGen/AppForms/ProjectLoadIssues.Designer.cs b/SourceGen/AppForms/ProjectLoadIssues.Designer.cs new file mode 100644 index 0000000..2a67e4a --- /dev/null +++ b/SourceGen/AppForms/ProjectLoadIssues.Designer.cs @@ -0,0 +1,134 @@ +/* + * Copyright 2018 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. + */ +namespace SourceGen.AppForms { + partial class ProjectLoadIssues { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.cancelButton = new System.Windows.Forms.Button(); + this.okButton = new System.Windows.Forms.Button(); + this.labelSomeIssues = new System.Windows.Forms.Label(); + this.messageTextBox = new System.Windows.Forms.TextBox(); + this.invalidDiscardLabel = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(469, 184); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 1; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + // + // okButton + // + this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.okButton.Location = new System.Drawing.Point(388, 184); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(75, 23); + this.okButton.TabIndex = 0; + this.okButton.Text = "Continue"; + this.okButton.UseVisualStyleBackColor = true; + // + // labelSomeIssues + // + this.labelSomeIssues.AutoSize = true; + this.labelSomeIssues.Location = new System.Drawing.Point(13, 13); + this.labelSomeIssues.Name = "labelSomeIssues"; + this.labelSomeIssues.Size = new System.Drawing.Size(257, 13); + this.labelSomeIssues.TabIndex = 2; + this.labelSomeIssues.Text = "Problems were detected while loading the project file:"; + // + // messageTextBox + // + this.messageTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.messageTextBox.BackColor = System.Drawing.SystemColors.Window; + this.messageTextBox.Location = new System.Drawing.Point(12, 37); + this.messageTextBox.Multiline = true; + this.messageTextBox.Name = "messageTextBox"; + this.messageTextBox.ReadOnly = true; + this.messageTextBox.Size = new System.Drawing.Size(533, 113); + this.messageTextBox.TabIndex = 3; + // + // invalidDiscardLabel + // + this.invalidDiscardLabel.AutoSize = true; + this.invalidDiscardLabel.ForeColor = System.Drawing.Color.Red; + this.invalidDiscardLabel.Location = new System.Drawing.Point(13, 158); + this.invalidDiscardLabel.Name = "invalidDiscardLabel"; + this.invalidDiscardLabel.Size = new System.Drawing.Size(301, 13); + this.invalidDiscardLabel.TabIndex = 4; + this.invalidDiscardLabel.Text = "Invalid data items will be discarded when you save the project."; + // + // ProjectLoadIssues + // + this.AcceptButton = this.okButton; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(556, 219); + this.Controls.Add(this.invalidDiscardLabel); + this.Controls.Add(this.messageTextBox); + this.Controls.Add(this.labelSomeIssues); + this.Controls.Add(this.okButton); + this.Controls.Add(this.cancelButton); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "ProjectLoadIssues"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Project Load Issues"; + this.Load += new System.EventHandler(this.ProjectLoadIssues_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.Label labelSomeIssues; + private System.Windows.Forms.TextBox messageTextBox; + private System.Windows.Forms.Label invalidDiscardLabel; + } +} \ No newline at end of file diff --git a/SourceGen/AppForms/ProjectLoadIssues.cs b/SourceGen/AppForms/ProjectLoadIssues.cs new file mode 100644 index 0000000..084b81b --- /dev/null +++ b/SourceGen/AppForms/ProjectLoadIssues.cs @@ -0,0 +1,63 @@ +/* + * Copyright 2018 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.Drawing; +using System.Windows.Forms; + +namespace SourceGen.AppForms { + /// + /// Display errors and warnings generated while attempting to open a project. + /// + public partial class ProjectLoadIssues : Form { + /// + /// Multi-line message for text box. + /// + public string Messages { get; set; } + + /// + /// Enable or disable the Continue button. Defaults to true. + /// + public bool CanContinue { get; set; } + + /// + /// Enable or disable the Cancel button. Defaults to true. + /// + public bool CanCancel { get; set; } + + + public ProjectLoadIssues() { + InitializeComponent(); + CanContinue = CanCancel = true; + } + + private void ProjectLoadIssues_Load(object sender, EventArgs e) { + messageTextBox.Text = Messages; + + if (!CanContinue) { + okButton.Enabled = false; + + // No point warning them about invalid data if they can't continue. + invalidDiscardLabel.Visible = false; + } + if (!CanCancel) { + cancelButton.Enabled = false; + + // They're stuck with the problem. + invalidDiscardLabel.Visible = false; + } + } + } +} diff --git a/SourceGen/AppForms/ProjectLoadIssues.resx b/SourceGen/AppForms/ProjectLoadIssues.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/SourceGen/AppForms/ProjectLoadIssues.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/SourceGen/AppForms/ProjectView.Designer.cs b/SourceGen/AppForms/ProjectView.Designer.cs new file mode 100644 index 0000000..72a2a68 --- /dev/null +++ b/SourceGen/AppForms/ProjectView.Designer.cs @@ -0,0 +1,1616 @@ +/* + * Copyright 2018 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. + */ +namespace SourceGen.AppForms +{ + partial class ProjectView + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ProjectView)); + this.mainMenuStrip = new System.Windows.Forms.MenuStrip(); + this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.newToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.openToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.saveToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.saveAsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.closeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem4 = new System.Windows.Forms.ToolStripSeparator(); + this.assembleToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.printToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem5 = new System.Windows.Forms.ToolStripSeparator(); + this.recentProjectsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.noRecentsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem10 = new System.Windows.Forms.ToolStripSeparator(); + this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.editToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.undoToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.redoToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem1 = new System.Windows.Forms.ToolStripSeparator(); + this.copyToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem2 = new System.Windows.Forms.ToolStripSeparator(); + this.selectAllToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.findToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.findNextToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.gotoToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem3 = new System.Windows.Forms.ToolStripSeparator(); + this.editHeaderCommentToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.projectPropertiesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem7 = new System.Windows.Forms.ToolStripSeparator(); + this.settingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.actionsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.setAddressToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.overrideStatusFlagsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.editLabelToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.editOperandToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.editDataFormatToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.editCommentToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.editLongCommentToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.editNoteToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.editProjectSymbolToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.actionsMenuSeparator1 = new System.Windows.Forms.ToolStripSeparator(); + this.hintAsCodeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.hintAsDataToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.hintAsInlineDataToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.removeHintToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem11 = new System.Windows.Forms.ToolStripSeparator(); + this.toggleSingleBytesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.deleteNoteCommentToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem9 = new System.Windows.Forms.ToolStripSeparator(); + this.showHexDumpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.hexDumpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.aSCIIChartToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.helpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.contentsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.dEBUGToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.reanalyzeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.showUndoRedoHistoryToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.showAnalyzerOutputToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.showAnalysisTimersToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.extensionScriptInfoToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem6 = new System.Windows.Forms.ToolStripSeparator(); + this.toggleOwnerDrawToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toggleCommentRulersToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.useKeepAliveHackToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem8 = new System.Windows.Forms.ToolStripSeparator(); + this.sourceGenTestsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.mainStatusStrip = new System.Windows.Forms.StatusStrip(); + this.toolStripStatusLabel = new System.Windows.Forms.ToolStripStatusLabel(); + this.mainToolStrip = new System.Windows.Forms.ToolStrip(); + this.navigateBackToolStripButton = new System.Windows.Forms.ToolStripButton(); + this.navigateFwdToolStripButton = new System.Windows.Forms.ToolStripButton(); + this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); + this.newToolStripButton = new System.Windows.Forms.ToolStripButton(); + this.openToolStripButton = new System.Windows.Forms.ToolStripButton(); + this.saveToolStripButton = new System.Windows.Forms.ToolStripButton(); + this.printToolStripButton = new System.Windows.Forms.ToolStripButton(); + this.toolStripSeparator = new System.Windows.Forms.ToolStripSeparator(); + this.cutToolStripButton = new System.Windows.Forms.ToolStripButton(); + this.copyToolStripButton = new System.Windows.Forms.ToolStripButton(); + this.pasteToolStripButton = new System.Windows.Forms.ToolStripButton(); + this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); + this.helpToolStripButton = new System.Windows.Forms.ToolStripButton(); + this.mainSplitterLeft = new System.Windows.Forms.SplitContainer(); + this.leftPanelSplitter = new System.Windows.Forms.SplitContainer(); + this.referencesGroupBox = new System.Windows.Forms.GroupBox(); + this.referencesListView = new System.Windows.Forms.ListView(); + this.offsetColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.addressColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.typeColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.notesGroupBox = new System.Windows.Forms.GroupBox(); + this.notesListView = new System.Windows.Forms.ListView(); + this.notesOffsetColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.notesNoteColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.mainSplitterRight = new System.Windows.Forms.SplitContainer(); + this.noProjectPanel = new System.Windows.Forms.Panel(); + this.debugModeEnabledLabel = new System.Windows.Forms.Label(); + this.recentProjectsLabel = new System.Windows.Forms.Label(); + this.recentProjectLabel2 = new System.Windows.Forms.LinkLabel(); + this.versionLabel = new System.Windows.Forms.Label(); + this.sourceGenLabel = new System.Windows.Forms.Label(); + this.logoPictureBox = new System.Windows.Forms.PictureBox(); + this.recentProjectLabel1 = new System.Windows.Forms.LinkLabel(); + this.openExistingLabel = new System.Windows.Forms.LinkLabel(); + this.newProjectLink = new System.Windows.Forms.LinkLabel(); + this.codeListView = new System.Windows.Forms.ListView(); + this.columnOffset = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnAddr = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnBytes = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnFlags = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnAttr = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnLabel = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnOpcode = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnOperand = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnComment = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.rightPanelSplitter = new System.Windows.Forms.SplitContainer(); + this.symbolsGroupBox = new System.Windows.Forms.GroupBox(); + this.symbolUserCheckBox = new System.Windows.Forms.CheckBox(); + this.symbolProjectCheckBox = new System.Windows.Forms.CheckBox(); + this.symbolAddressCheckBox = new System.Windows.Forms.CheckBox(); + this.symbolConstantCheckBox = new System.Windows.Forms.CheckBox(); + this.symbolAutoCheckBox = new System.Windows.Forms.CheckBox(); + this.symbolPlatformCheckBox = new System.Windows.Forms.CheckBox(); + this.symbolListView = new System.Windows.Forms.ListView(); + this.symbolTypeColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.symbolNameColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.symbolValueColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.infoGroupBox = new System.Windows.Forms.GroupBox(); + this.infoTextBox = new System.Windows.Forms.TextBox(); + this.mainMenuStrip.SuspendLayout(); + this.mainStatusStrip.SuspendLayout(); + this.mainToolStrip.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mainSplitterLeft)).BeginInit(); + this.mainSplitterLeft.Panel1.SuspendLayout(); + this.mainSplitterLeft.Panel2.SuspendLayout(); + this.mainSplitterLeft.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.leftPanelSplitter)).BeginInit(); + this.leftPanelSplitter.Panel1.SuspendLayout(); + this.leftPanelSplitter.Panel2.SuspendLayout(); + this.leftPanelSplitter.SuspendLayout(); + this.referencesGroupBox.SuspendLayout(); + this.notesGroupBox.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.mainSplitterRight)).BeginInit(); + this.mainSplitterRight.Panel1.SuspendLayout(); + this.mainSplitterRight.Panel2.SuspendLayout(); + this.mainSplitterRight.SuspendLayout(); + this.noProjectPanel.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.logoPictureBox)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.rightPanelSplitter)).BeginInit(); + this.rightPanelSplitter.Panel1.SuspendLayout(); + this.rightPanelSplitter.Panel2.SuspendLayout(); + this.rightPanelSplitter.SuspendLayout(); + this.symbolsGroupBox.SuspendLayout(); + this.infoGroupBox.SuspendLayout(); + this.SuspendLayout(); + // + // mainMenuStrip + // + this.mainMenuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.fileToolStripMenuItem, + this.editToolStripMenuItem, + this.actionsToolStripMenuItem, + this.toolsToolStripMenuItem, + this.helpToolStripMenuItem, + this.dEBUGToolStripMenuItem}); + this.mainMenuStrip.Location = new System.Drawing.Point(0, 0); + this.mainMenuStrip.Name = "mainMenuStrip"; + this.mainMenuStrip.Size = new System.Drawing.Size(805, 24); + this.mainMenuStrip.TabIndex = 0; + this.mainMenuStrip.Text = "Main MenuStrip"; + // + // fileToolStripMenuItem + // + this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.newToolStripMenuItem, + this.openToolStripMenuItem, + this.saveToolStripMenuItem, + this.saveAsToolStripMenuItem, + this.closeToolStripMenuItem, + this.toolStripMenuItem4, + this.assembleToolStripMenuItem, + this.printToolStripMenuItem, + this.toolStripMenuItem5, + this.recentProjectsToolStripMenuItem, + this.toolStripMenuItem10, + this.exitToolStripMenuItem}); + this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; + this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20); + this.fileToolStripMenuItem.Text = "File"; + // + // newToolStripMenuItem + // + this.newToolStripMenuItem.Name = "newToolStripMenuItem"; + this.newToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.N))); + this.newToolStripMenuItem.Size = new System.Drawing.Size(208, 22); + this.newToolStripMenuItem.Text = "New"; + this.newToolStripMenuItem.Click += new System.EventHandler(this.newToolStripMenuItem_Click); + // + // openToolStripMenuItem + // + this.openToolStripMenuItem.Name = "openToolStripMenuItem"; + this.openToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.O))); + this.openToolStripMenuItem.Size = new System.Drawing.Size(208, 22); + this.openToolStripMenuItem.Text = "Open"; + this.openToolStripMenuItem.Click += new System.EventHandler(this.openToolStripMenuItem_Click); + // + // saveToolStripMenuItem + // + this.saveToolStripMenuItem.Name = "saveToolStripMenuItem"; + this.saveToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.S))); + this.saveToolStripMenuItem.Size = new System.Drawing.Size(208, 22); + this.saveToolStripMenuItem.Text = "Save"; + this.saveToolStripMenuItem.Click += new System.EventHandler(this.saveToolStripMenuItem_Click); + // + // saveAsToolStripMenuItem + // + this.saveAsToolStripMenuItem.Name = "saveAsToolStripMenuItem"; + this.saveAsToolStripMenuItem.Size = new System.Drawing.Size(208, 22); + this.saveAsToolStripMenuItem.Text = "Save As..."; + this.saveAsToolStripMenuItem.Click += new System.EventHandler(this.saveAsToolStripMenuItem_Click); + // + // closeToolStripMenuItem + // + this.closeToolStripMenuItem.Name = "closeToolStripMenuItem"; + this.closeToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.W))); + this.closeToolStripMenuItem.Size = new System.Drawing.Size(208, 22); + this.closeToolStripMenuItem.Text = "Close"; + this.closeToolStripMenuItem.Click += new System.EventHandler(this.closeToolStripMenuItem_Click); + // + // toolStripMenuItem4 + // + this.toolStripMenuItem4.Name = "toolStripMenuItem4"; + this.toolStripMenuItem4.Size = new System.Drawing.Size(205, 6); + // + // assembleToolStripMenuItem + // + this.assembleToolStripMenuItem.Name = "assembleToolStripMenuItem"; + this.assembleToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)(((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Shift) + | System.Windows.Forms.Keys.A))); + this.assembleToolStripMenuItem.Size = new System.Drawing.Size(208, 22); + this.assembleToolStripMenuItem.Text = "Assemble..."; + this.assembleToolStripMenuItem.Click += new System.EventHandler(this.assembleToolStripMenuItem_Click); + // + // printToolStripMenuItem + // + this.printToolStripMenuItem.Name = "printToolStripMenuItem"; + this.printToolStripMenuItem.Size = new System.Drawing.Size(208, 22); + this.printToolStripMenuItem.Text = "Print..."; + this.printToolStripMenuItem.Visible = false; + // + // toolStripMenuItem5 + // + this.toolStripMenuItem5.Name = "toolStripMenuItem5"; + this.toolStripMenuItem5.Size = new System.Drawing.Size(205, 6); + // + // recentProjectsToolStripMenuItem + // + this.recentProjectsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.noRecentsToolStripMenuItem}); + this.recentProjectsToolStripMenuItem.Name = "recentProjectsToolStripMenuItem"; + this.recentProjectsToolStripMenuItem.Size = new System.Drawing.Size(208, 22); + this.recentProjectsToolStripMenuItem.Text = "Recent Projects"; + this.recentProjectsToolStripMenuItem.DropDownOpening += new System.EventHandler(this.recentProjectsToolStripMenuItem_DropDownOpening); + // + // noRecentsToolStripMenuItem + // + this.noRecentsToolStripMenuItem.Enabled = false; + this.noRecentsToolStripMenuItem.Name = "noRecentsToolStripMenuItem"; + this.noRecentsToolStripMenuItem.Size = new System.Drawing.Size(109, 22); + this.noRecentsToolStripMenuItem.Text = "(none)"; + // + // toolStripMenuItem10 + // + this.toolStripMenuItem10.Name = "toolStripMenuItem10"; + this.toolStripMenuItem10.Size = new System.Drawing.Size(205, 6); + // + // exitToolStripMenuItem + // + this.exitToolStripMenuItem.Name = "exitToolStripMenuItem"; + this.exitToolStripMenuItem.Size = new System.Drawing.Size(208, 22); + this.exitToolStripMenuItem.Text = "Exit"; + this.exitToolStripMenuItem.Click += new System.EventHandler(this.exitToolStripMenuItem_Click); + // + // editToolStripMenuItem + // + this.editToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.undoToolStripMenuItem, + this.redoToolStripMenuItem, + this.toolStripMenuItem1, + this.copyToolStripMenuItem, + this.toolStripMenuItem2, + this.selectAllToolStripMenuItem, + this.findToolStripMenuItem, + this.findNextToolStripMenuItem, + this.gotoToolStripMenuItem, + this.toolStripMenuItem3, + this.editHeaderCommentToolStripMenuItem, + this.projectPropertiesToolStripMenuItem, + this.toolStripMenuItem7, + this.settingsToolStripMenuItem}); + this.editToolStripMenuItem.Name = "editToolStripMenuItem"; + this.editToolStripMenuItem.Size = new System.Drawing.Size(39, 20); + this.editToolStripMenuItem.Text = "Edit"; + // + // undoToolStripMenuItem + // + this.undoToolStripMenuItem.Name = "undoToolStripMenuItem"; + this.undoToolStripMenuItem.ShortcutKeyDisplayString = ""; + this.undoToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Z))); + this.undoToolStripMenuItem.Size = new System.Drawing.Size(201, 22); + this.undoToolStripMenuItem.Text = "Undo"; + this.undoToolStripMenuItem.Click += new System.EventHandler(this.undoToolStripMenuItem_Click); + // + // redoToolStripMenuItem + // + this.redoToolStripMenuItem.Name = "redoToolStripMenuItem"; + this.redoToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Y))); + this.redoToolStripMenuItem.Size = new System.Drawing.Size(201, 22); + this.redoToolStripMenuItem.Text = "Redo"; + this.redoToolStripMenuItem.Click += new System.EventHandler(this.redoToolStripMenuItem_Click); + // + // toolStripMenuItem1 + // + this.toolStripMenuItem1.Name = "toolStripMenuItem1"; + this.toolStripMenuItem1.Size = new System.Drawing.Size(198, 6); + // + // copyToolStripMenuItem + // + this.copyToolStripMenuItem.Name = "copyToolStripMenuItem"; + this.copyToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.C))); + this.copyToolStripMenuItem.Size = new System.Drawing.Size(201, 22); + this.copyToolStripMenuItem.Text = "Copy"; + this.copyToolStripMenuItem.Click += new System.EventHandler(this.copyToolStripMenuItem_Click); + // + // toolStripMenuItem2 + // + this.toolStripMenuItem2.Name = "toolStripMenuItem2"; + this.toolStripMenuItem2.Size = new System.Drawing.Size(198, 6); + // + // selectAllToolStripMenuItem + // + this.selectAllToolStripMenuItem.Name = "selectAllToolStripMenuItem"; + this.selectAllToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.A))); + this.selectAllToolStripMenuItem.Size = new System.Drawing.Size(201, 22); + this.selectAllToolStripMenuItem.Text = "Select All"; + this.selectAllToolStripMenuItem.Click += new System.EventHandler(this.selectAllToolStripMenuItem_Click); + // + // findToolStripMenuItem + // + this.findToolStripMenuItem.Name = "findToolStripMenuItem"; + this.findToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.F))); + this.findToolStripMenuItem.Size = new System.Drawing.Size(201, 22); + this.findToolStripMenuItem.Text = "Find..."; + this.findToolStripMenuItem.Click += new System.EventHandler(this.findToolStripMenuItem_Click); + // + // findNextToolStripMenuItem + // + this.findNextToolStripMenuItem.Name = "findNextToolStripMenuItem"; + this.findNextToolStripMenuItem.ShortcutKeys = System.Windows.Forms.Keys.F3; + this.findNextToolStripMenuItem.Size = new System.Drawing.Size(201, 22); + this.findNextToolStripMenuItem.Text = "Find Next"; + this.findNextToolStripMenuItem.Click += new System.EventHandler(this.findNextToolStripMenuItem_Click); + // + // gotoToolStripMenuItem + // + this.gotoToolStripMenuItem.Name = "gotoToolStripMenuItem"; + this.gotoToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.G))); + this.gotoToolStripMenuItem.Size = new System.Drawing.Size(201, 22); + this.gotoToolStripMenuItem.Text = "Go To..."; + this.gotoToolStripMenuItem.Click += new System.EventHandler(this.gotoToolStripMenuItem_Click); + // + // toolStripMenuItem3 + // + this.toolStripMenuItem3.Name = "toolStripMenuItem3"; + this.toolStripMenuItem3.Size = new System.Drawing.Size(198, 6); + // + // editHeaderCommentToolStripMenuItem + // + this.editHeaderCommentToolStripMenuItem.Name = "editHeaderCommentToolStripMenuItem"; + this.editHeaderCommentToolStripMenuItem.Size = new System.Drawing.Size(201, 22); + this.editHeaderCommentToolStripMenuItem.Text = "Edit Header Comment..."; + this.editHeaderCommentToolStripMenuItem.Click += new System.EventHandler(this.editHeaderCommentToolStripMenuItem_Click); + // + // projectPropertiesToolStripMenuItem + // + this.projectPropertiesToolStripMenuItem.Name = "projectPropertiesToolStripMenuItem"; + this.projectPropertiesToolStripMenuItem.Size = new System.Drawing.Size(201, 22); + this.projectPropertiesToolStripMenuItem.Text = "Project Properties..."; + this.projectPropertiesToolStripMenuItem.Click += new System.EventHandler(this.projectPropertiesToolStripMenuItem_Click); + // + // toolStripMenuItem7 + // + this.toolStripMenuItem7.Name = "toolStripMenuItem7"; + this.toolStripMenuItem7.Size = new System.Drawing.Size(198, 6); + // + // settingsToolStripMenuItem + // + this.settingsToolStripMenuItem.Name = "settingsToolStripMenuItem"; + this.settingsToolStripMenuItem.Size = new System.Drawing.Size(201, 22); + this.settingsToolStripMenuItem.Text = "Settings..."; + this.settingsToolStripMenuItem.Click += new System.EventHandler(this.settingsToolStripMenuItem_Click); + // + // actionsToolStripMenuItem + // + this.actionsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.setAddressToolStripMenuItem, + this.overrideStatusFlagsToolStripMenuItem, + this.editLabelToolStripMenuItem, + this.editOperandToolStripMenuItem, + this.editDataFormatToolStripMenuItem, + this.editCommentToolStripMenuItem, + this.editLongCommentToolStripMenuItem, + this.editNoteToolStripMenuItem, + this.editProjectSymbolToolStripMenuItem, + this.actionsMenuSeparator1, + this.hintAsCodeToolStripMenuItem, + this.hintAsDataToolStripMenuItem, + this.hintAsInlineDataToolStripMenuItem, + this.removeHintToolStripMenuItem, + this.toolStripMenuItem11, + this.toggleSingleBytesToolStripMenuItem, + this.deleteNoteCommentToolStripMenuItem, + this.toolStripMenuItem9, + this.showHexDumpToolStripMenuItem}); + this.actionsToolStripMenuItem.Name = "actionsToolStripMenuItem"; + this.actionsToolStripMenuItem.Size = new System.Drawing.Size(59, 20); + this.actionsToolStripMenuItem.Text = "Actions"; + this.actionsToolStripMenuItem.DropDownOpening += new System.EventHandler(this.ActionsMenuOpening); + // + // setAddressToolStripMenuItem + // + this.setAddressToolStripMenuItem.Name = "setAddressToolStripMenuItem"; + this.setAddressToolStripMenuItem.Size = new System.Drawing.Size(255, 22); + this.setAddressToolStripMenuItem.Text = "Set Address..."; + this.setAddressToolStripMenuItem.Click += new System.EventHandler(this.EditAddress_Click); + // + // overrideStatusFlagsToolStripMenuItem + // + this.overrideStatusFlagsToolStripMenuItem.Name = "overrideStatusFlagsToolStripMenuItem"; + this.overrideStatusFlagsToolStripMenuItem.Size = new System.Drawing.Size(255, 22); + this.overrideStatusFlagsToolStripMenuItem.Text = "Override Status Flags..."; + this.overrideStatusFlagsToolStripMenuItem.Click += new System.EventHandler(this.EditStatusFlags_Click); + // + // editLabelToolStripMenuItem + // + this.editLabelToolStripMenuItem.Name = "editLabelToolStripMenuItem"; + this.editLabelToolStripMenuItem.Size = new System.Drawing.Size(255, 22); + this.editLabelToolStripMenuItem.Text = "Edit Label..."; + this.editLabelToolStripMenuItem.Click += new System.EventHandler(this.EditLabel_Click); + // + // editOperandToolStripMenuItem + // + this.editOperandToolStripMenuItem.Name = "editOperandToolStripMenuItem"; + this.editOperandToolStripMenuItem.Size = new System.Drawing.Size(255, 22); + this.editOperandToolStripMenuItem.Text = "Edit Operand..."; + this.editOperandToolStripMenuItem.Click += new System.EventHandler(this.EditOperand_Click); + // + // editDataFormatToolStripMenuItem + // + this.editDataFormatToolStripMenuItem.Name = "editDataFormatToolStripMenuItem"; + this.editDataFormatToolStripMenuItem.Size = new System.Drawing.Size(255, 22); + this.editDataFormatToolStripMenuItem.Text = "Edit Data Format..."; + this.editDataFormatToolStripMenuItem.Click += new System.EventHandler(this.EditData_Click); + // + // editCommentToolStripMenuItem + // + this.editCommentToolStripMenuItem.Name = "editCommentToolStripMenuItem"; + this.editCommentToolStripMenuItem.Size = new System.Drawing.Size(255, 22); + this.editCommentToolStripMenuItem.Text = "Edit Comment..."; + this.editCommentToolStripMenuItem.Click += new System.EventHandler(this.EditComment_Click); + // + // editLongCommentToolStripMenuItem + // + this.editLongCommentToolStripMenuItem.Name = "editLongCommentToolStripMenuItem"; + this.editLongCommentToolStripMenuItem.Size = new System.Drawing.Size(255, 22); + this.editLongCommentToolStripMenuItem.Text = "Edit Long Comment..."; + this.editLongCommentToolStripMenuItem.Click += new System.EventHandler(this.EditLongComment_Click); + // + // editNoteToolStripMenuItem + // + this.editNoteToolStripMenuItem.Name = "editNoteToolStripMenuItem"; + this.editNoteToolStripMenuItem.Size = new System.Drawing.Size(255, 22); + this.editNoteToolStripMenuItem.Text = "Edit Note..."; + this.editNoteToolStripMenuItem.Click += new System.EventHandler(this.EditNote_Click); + // + // editProjectSymbolToolStripMenuItem + // + this.editProjectSymbolToolStripMenuItem.Name = "editProjectSymbolToolStripMenuItem"; + this.editProjectSymbolToolStripMenuItem.Size = new System.Drawing.Size(255, 22); + this.editProjectSymbolToolStripMenuItem.Text = "Edit Project Symbol..."; + this.editProjectSymbolToolStripMenuItem.Click += new System.EventHandler(this.EditProjectSymbol_Click); + // + // actionsMenuSeparator1 + // + this.actionsMenuSeparator1.Name = "actionsMenuSeparator1"; + this.actionsMenuSeparator1.Size = new System.Drawing.Size(252, 6); + // + // hintAsCodeToolStripMenuItem + // + this.hintAsCodeToolStripMenuItem.Name = "hintAsCodeToolStripMenuItem"; + this.hintAsCodeToolStripMenuItem.Size = new System.Drawing.Size(255, 22); + this.hintAsCodeToolStripMenuItem.Text = "Hint As Code Entry Point"; + this.hintAsCodeToolStripMenuItem.Click += new System.EventHandler(this.MarkAsCode_Click); + // + // hintAsDataToolStripMenuItem + // + this.hintAsDataToolStripMenuItem.Name = "hintAsDataToolStripMenuItem"; + this.hintAsDataToolStripMenuItem.Size = new System.Drawing.Size(255, 22); + this.hintAsDataToolStripMenuItem.Text = "Hint As Data Start"; + this.hintAsDataToolStripMenuItem.Click += new System.EventHandler(this.MarkAsData_Click); + // + // hintAsInlineDataToolStripMenuItem + // + this.hintAsInlineDataToolStripMenuItem.Name = "hintAsInlineDataToolStripMenuItem"; + this.hintAsInlineDataToolStripMenuItem.Size = new System.Drawing.Size(255, 22); + this.hintAsInlineDataToolStripMenuItem.Text = "Hint As Inline Data"; + this.hintAsInlineDataToolStripMenuItem.Click += new System.EventHandler(this.MarkAsInlineData_Click); + // + // removeHintToolStripMenuItem + // + this.removeHintToolStripMenuItem.Name = "removeHintToolStripMenuItem"; + this.removeHintToolStripMenuItem.Size = new System.Drawing.Size(255, 22); + this.removeHintToolStripMenuItem.Text = "Remove Hints"; + this.removeHintToolStripMenuItem.Click += new System.EventHandler(this.MarkAsNoHint_Click); + // + // toolStripMenuItem11 + // + this.toolStripMenuItem11.Name = "toolStripMenuItem11"; + this.toolStripMenuItem11.Size = new System.Drawing.Size(252, 6); + // + // toggleSingleBytesToolStripMenuItem + // + this.toggleSingleBytesToolStripMenuItem.Name = "toggleSingleBytesToolStripMenuItem"; + this.toggleSingleBytesToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.B))); + this.toggleSingleBytesToolStripMenuItem.Size = new System.Drawing.Size(255, 22); + this.toggleSingleBytesToolStripMenuItem.Text = "Toggle Single-Byte Format"; + this.toggleSingleBytesToolStripMenuItem.Click += new System.EventHandler(this.ToggleSingleBytes_Click); + // + // deleteNoteCommentToolStripMenuItem + // + this.deleteNoteCommentToolStripMenuItem.Name = "deleteNoteCommentToolStripMenuItem"; + this.deleteNoteCommentToolStripMenuItem.ShortcutKeys = System.Windows.Forms.Keys.Delete; + this.deleteNoteCommentToolStripMenuItem.Size = new System.Drawing.Size(255, 22); + this.deleteNoteCommentToolStripMenuItem.Text = "Delete Note/Long Comment"; + this.deleteNoteCommentToolStripMenuItem.Click += new System.EventHandler(this.DeleteNoteComment_Click); + // + // toolStripMenuItem9 + // + this.toolStripMenuItem9.Name = "toolStripMenuItem9"; + this.toolStripMenuItem9.Size = new System.Drawing.Size(252, 6); + // + // showHexDumpToolStripMenuItem + // + this.showHexDumpToolStripMenuItem.Name = "showHexDumpToolStripMenuItem"; + this.showHexDumpToolStripMenuItem.Size = new System.Drawing.Size(255, 22); + this.showHexDumpToolStripMenuItem.Text = "Show Hex Dump"; + this.showHexDumpToolStripMenuItem.Click += new System.EventHandler(this.ShowHexDump_Click); + // + // toolsToolStripMenuItem + // + this.toolsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.hexDumpToolStripMenuItem, + this.aSCIIChartToolStripMenuItem}); + this.toolsToolStripMenuItem.Name = "toolsToolStripMenuItem"; + this.toolsToolStripMenuItem.Size = new System.Drawing.Size(47, 20); + this.toolsToolStripMenuItem.Text = "Tools"; + // + // hexDumpToolStripMenuItem + // + this.hexDumpToolStripMenuItem.Name = "hexDumpToolStripMenuItem"; + this.hexDumpToolStripMenuItem.Size = new System.Drawing.Size(139, 22); + this.hexDumpToolStripMenuItem.Text = "Hex Dump..."; + this.hexDumpToolStripMenuItem.Click += new System.EventHandler(this.hexDumpToolStripMenuItem_Click); + // + // aSCIIChartToolStripMenuItem + // + this.aSCIIChartToolStripMenuItem.Name = "aSCIIChartToolStripMenuItem"; + this.aSCIIChartToolStripMenuItem.Size = new System.Drawing.Size(139, 22); + this.aSCIIChartToolStripMenuItem.Text = "ASCII Chart"; + this.aSCIIChartToolStripMenuItem.Click += new System.EventHandler(this.aSCIIChartToolStripMenuItem_Click); + // + // helpToolStripMenuItem + // + this.helpToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.contentsToolStripMenuItem, + this.aboutToolStripMenuItem}); + this.helpToolStripMenuItem.Name = "helpToolStripMenuItem"; + this.helpToolStripMenuItem.Size = new System.Drawing.Size(44, 20); + this.helpToolStripMenuItem.Text = "Help"; + // + // contentsToolStripMenuItem + // + this.contentsToolStripMenuItem.Name = "contentsToolStripMenuItem"; + this.contentsToolStripMenuItem.ShortcutKeys = System.Windows.Forms.Keys.F1; + this.contentsToolStripMenuItem.Size = new System.Drawing.Size(155, 22); + this.contentsToolStripMenuItem.Text = "View Help..."; + this.contentsToolStripMenuItem.Click += new System.EventHandler(this.viewHelpToolStripMenuItem_Click); + // + // aboutToolStripMenuItem + // + this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem"; + this.aboutToolStripMenuItem.Size = new System.Drawing.Size(155, 22); + this.aboutToolStripMenuItem.Text = "About..."; + this.aboutToolStripMenuItem.Click += new System.EventHandler(this.aboutToolStripMenuItem_Click); + // + // dEBUGToolStripMenuItem + // + this.dEBUGToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.reanalyzeToolStripMenuItem, + this.showUndoRedoHistoryToolStripMenuItem, + this.showAnalyzerOutputToolStripMenuItem, + this.showAnalysisTimersToolStripMenuItem, + this.extensionScriptInfoToolStripMenuItem, + this.toolStripMenuItem6, + this.toggleOwnerDrawToolStripMenuItem, + this.toggleCommentRulersToolStripMenuItem, + this.useKeepAliveHackToolStripMenuItem, + this.toolStripMenuItem8, + this.sourceGenTestsToolStripMenuItem}); + this.dEBUGToolStripMenuItem.Name = "dEBUGToolStripMenuItem"; + this.dEBUGToolStripMenuItem.Size = new System.Drawing.Size(56, 20); + this.dEBUGToolStripMenuItem.Text = "DEBUG"; + this.dEBUGToolStripMenuItem.DropDownOpened += new System.EventHandler(this.dEBUGToolStripMenuItem_DropDownOpened); + // + // reanalyzeToolStripMenuItem + // + this.reanalyzeToolStripMenuItem.Name = "reanalyzeToolStripMenuItem"; + this.reanalyzeToolStripMenuItem.ShortcutKeys = System.Windows.Forms.Keys.F5; + this.reanalyzeToolStripMenuItem.Size = new System.Drawing.Size(209, 22); + this.reanalyzeToolStripMenuItem.Text = "Re-analyze"; + this.reanalyzeToolStripMenuItem.Click += new System.EventHandler(this.reanalyzeToolStripMenuItem_Click); + // + // showUndoRedoHistoryToolStripMenuItem + // + this.showUndoRedoHistoryToolStripMenuItem.Name = "showUndoRedoHistoryToolStripMenuItem"; + this.showUndoRedoHistoryToolStripMenuItem.Size = new System.Drawing.Size(209, 22); + this.showUndoRedoHistoryToolStripMenuItem.Text = "Show Undo/Redo History"; + this.showUndoRedoHistoryToolStripMenuItem.Click += new System.EventHandler(this.showUndoRedoHistoryToolStripMenuItem_Click); + // + // showAnalyzerOutputToolStripMenuItem + // + this.showAnalyzerOutputToolStripMenuItem.Name = "showAnalyzerOutputToolStripMenuItem"; + this.showAnalyzerOutputToolStripMenuItem.Size = new System.Drawing.Size(209, 22); + this.showAnalyzerOutputToolStripMenuItem.Text = "Show Analyzer Output"; + this.showAnalyzerOutputToolStripMenuItem.Click += new System.EventHandler(this.showAnalyzerOutputToolStripMenuItem_Click); + // + // showAnalysisTimersToolStripMenuItem + // + this.showAnalysisTimersToolStripMenuItem.Name = "showAnalysisTimersToolStripMenuItem"; + this.showAnalysisTimersToolStripMenuItem.Size = new System.Drawing.Size(209, 22); + this.showAnalysisTimersToolStripMenuItem.Text = "Show Analysis Timers"; + this.showAnalysisTimersToolStripMenuItem.Click += new System.EventHandler(this.showAnalysisTimersToolStripMenuItem_Click); + // + // extensionScriptInfoToolStripMenuItem + // + this.extensionScriptInfoToolStripMenuItem.Name = "extensionScriptInfoToolStripMenuItem"; + this.extensionScriptInfoToolStripMenuItem.Size = new System.Drawing.Size(209, 22); + this.extensionScriptInfoToolStripMenuItem.Text = "Extension Script Info..."; + this.extensionScriptInfoToolStripMenuItem.Click += new System.EventHandler(this.extensionScriptInfoToolStripMenuItem_Click); + // + // toolStripMenuItem6 + // + this.toolStripMenuItem6.Name = "toolStripMenuItem6"; + this.toolStripMenuItem6.Size = new System.Drawing.Size(206, 6); + // + // toggleOwnerDrawToolStripMenuItem + // + this.toggleOwnerDrawToolStripMenuItem.Name = "toggleOwnerDrawToolStripMenuItem"; + this.toggleOwnerDrawToolStripMenuItem.Size = new System.Drawing.Size(209, 22); + this.toggleOwnerDrawToolStripMenuItem.Text = "Toggle OwnerDraw"; + this.toggleOwnerDrawToolStripMenuItem.Click += new System.EventHandler(this.toggleOwnerDrawToolStripMenuItem_Click); + // + // toggleCommentRulersToolStripMenuItem + // + this.toggleCommentRulersToolStripMenuItem.Name = "toggleCommentRulersToolStripMenuItem"; + this.toggleCommentRulersToolStripMenuItem.Size = new System.Drawing.Size(209, 22); + this.toggleCommentRulersToolStripMenuItem.Text = "Toggle Comment Rulers"; + this.toggleCommentRulersToolStripMenuItem.Click += new System.EventHandler(this.toggleCommentRulersToolStripMenuItem_Click); + // + // useKeepAliveHackToolStripMenuItem + // + this.useKeepAliveHackToolStripMenuItem.Name = "useKeepAliveHackToolStripMenuItem"; + this.useKeepAliveHackToolStripMenuItem.Size = new System.Drawing.Size(209, 22); + this.useKeepAliveHackToolStripMenuItem.Text = "Use Keep-Alive Hack"; + this.useKeepAliveHackToolStripMenuItem.Click += new System.EventHandler(this.useKeepAliveHackToolStripMenuItem_Click); + // + // toolStripMenuItem8 + // + this.toolStripMenuItem8.Name = "toolStripMenuItem8"; + this.toolStripMenuItem8.Size = new System.Drawing.Size(206, 6); + // + // sourceGenTestsToolStripMenuItem + // + this.sourceGenTestsToolStripMenuItem.Name = "sourceGenTestsToolStripMenuItem"; + this.sourceGenTestsToolStripMenuItem.Size = new System.Drawing.Size(209, 22); + this.sourceGenTestsToolStripMenuItem.Text = "Source Generation Tests..."; + this.sourceGenTestsToolStripMenuItem.Click += new System.EventHandler(this.sourceGenTestsToolStripMenuItem_Click); + // + // mainStatusStrip + // + this.mainStatusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.toolStripStatusLabel}); + this.mainStatusStrip.Location = new System.Drawing.Point(0, 592); + this.mainStatusStrip.Name = "mainStatusStrip"; + this.mainStatusStrip.Size = new System.Drawing.Size(805, 22); + this.mainStatusStrip.TabIndex = 1; + this.mainStatusStrip.Text = "Main StatusStrip"; + // + // toolStripStatusLabel + // + this.toolStripStatusLabel.Name = "toolStripStatusLabel"; + this.toolStripStatusLabel.Size = new System.Drawing.Size(39, 17); + this.toolStripStatusLabel.Text = "Ready"; + // + // mainToolStrip + // + this.mainToolStrip.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden; + this.mainToolStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.navigateBackToolStripButton, + this.navigateFwdToolStripButton, + this.toolStripSeparator2, + this.newToolStripButton, + this.openToolStripButton, + this.saveToolStripButton, + this.printToolStripButton, + this.toolStripSeparator, + this.cutToolStripButton, + this.copyToolStripButton, + this.pasteToolStripButton, + this.toolStripSeparator1, + this.helpToolStripButton}); + this.mainToolStrip.Location = new System.Drawing.Point(0, 24); + this.mainToolStrip.Name = "mainToolStrip"; + this.mainToolStrip.Size = new System.Drawing.Size(805, 25); + this.mainToolStrip.TabIndex = 2; + this.mainToolStrip.Text = "toolStrip1"; + // + // navigateBackToolStripButton + // + this.navigateBackToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.navigateBackToolStripButton.Image = ((System.Drawing.Image)(resources.GetObject("navigateBackToolStripButton.Image"))); + this.navigateBackToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta; + this.navigateBackToolStripButton.Name = "navigateBackToolStripButton"; + this.navigateBackToolStripButton.Size = new System.Drawing.Size(23, 22); + this.navigateBackToolStripButton.Text = "toolStripButton2"; + this.navigateBackToolStripButton.ToolTipText = "Navigate Backward"; + this.navigateBackToolStripButton.Click += new System.EventHandler(this.navigateBackToolStripButton_Click); + // + // navigateFwdToolStripButton + // + this.navigateFwdToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.navigateFwdToolStripButton.Image = ((System.Drawing.Image)(resources.GetObject("navigateFwdToolStripButton.Image"))); + this.navigateFwdToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta; + this.navigateFwdToolStripButton.Name = "navigateFwdToolStripButton"; + this.navigateFwdToolStripButton.Size = new System.Drawing.Size(23, 22); + this.navigateFwdToolStripButton.Text = "toolStripButton1"; + this.navigateFwdToolStripButton.ToolTipText = "Navigate Forward"; + this.navigateFwdToolStripButton.Click += new System.EventHandler(this.navigateFwdToolStripButton_Click); + // + // toolStripSeparator2 + // + this.toolStripSeparator2.Name = "toolStripSeparator2"; + this.toolStripSeparator2.Size = new System.Drawing.Size(6, 25); + // + // newToolStripButton + // + this.newToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.newToolStripButton.Image = ((System.Drawing.Image)(resources.GetObject("newToolStripButton.Image"))); + this.newToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta; + this.newToolStripButton.Name = "newToolStripButton"; + this.newToolStripButton.Size = new System.Drawing.Size(23, 22); + this.newToolStripButton.Text = "&New"; + this.newToolStripButton.Click += new System.EventHandler(this.newToolStripButton_Click); + // + // openToolStripButton + // + this.openToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.openToolStripButton.Image = ((System.Drawing.Image)(resources.GetObject("openToolStripButton.Image"))); + this.openToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta; + this.openToolStripButton.Name = "openToolStripButton"; + this.openToolStripButton.Size = new System.Drawing.Size(23, 22); + this.openToolStripButton.Text = "&Open"; + this.openToolStripButton.Click += new System.EventHandler(this.openToolStripButton_Click); + // + // saveToolStripButton + // + this.saveToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.saveToolStripButton.Image = ((System.Drawing.Image)(resources.GetObject("saveToolStripButton.Image"))); + this.saveToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta; + this.saveToolStripButton.Name = "saveToolStripButton"; + this.saveToolStripButton.Size = new System.Drawing.Size(23, 22); + this.saveToolStripButton.Text = "&Save"; + this.saveToolStripButton.Click += new System.EventHandler(this.saveToolStripButton_Click); + // + // printToolStripButton + // + this.printToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.printToolStripButton.Image = ((System.Drawing.Image)(resources.GetObject("printToolStripButton.Image"))); + this.printToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta; + this.printToolStripButton.Name = "printToolStripButton"; + this.printToolStripButton.Size = new System.Drawing.Size(23, 22); + this.printToolStripButton.Text = "&Print"; + this.printToolStripButton.Visible = false; + // + // toolStripSeparator + // + this.toolStripSeparator.Name = "toolStripSeparator"; + this.toolStripSeparator.Size = new System.Drawing.Size(6, 25); + // + // cutToolStripButton + // + this.cutToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.cutToolStripButton.Image = ((System.Drawing.Image)(resources.GetObject("cutToolStripButton.Image"))); + this.cutToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta; + this.cutToolStripButton.Name = "cutToolStripButton"; + this.cutToolStripButton.Size = new System.Drawing.Size(23, 22); + this.cutToolStripButton.Text = "C&ut"; + this.cutToolStripButton.Visible = false; + // + // copyToolStripButton + // + this.copyToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.copyToolStripButton.Image = ((System.Drawing.Image)(resources.GetObject("copyToolStripButton.Image"))); + this.copyToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta; + this.copyToolStripButton.Name = "copyToolStripButton"; + this.copyToolStripButton.Size = new System.Drawing.Size(23, 22); + this.copyToolStripButton.Text = "&Copy"; + // + // pasteToolStripButton + // + this.pasteToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.pasteToolStripButton.Image = ((System.Drawing.Image)(resources.GetObject("pasteToolStripButton.Image"))); + this.pasteToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta; + this.pasteToolStripButton.Name = "pasteToolStripButton"; + this.pasteToolStripButton.Size = new System.Drawing.Size(23, 22); + this.pasteToolStripButton.Text = "&Paste"; + this.pasteToolStripButton.Visible = false; + // + // toolStripSeparator1 + // + this.toolStripSeparator1.Name = "toolStripSeparator1"; + this.toolStripSeparator1.Size = new System.Drawing.Size(6, 25); + // + // helpToolStripButton + // + this.helpToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.helpToolStripButton.Image = ((System.Drawing.Image)(resources.GetObject("helpToolStripButton.Image"))); + this.helpToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta; + this.helpToolStripButton.Name = "helpToolStripButton"; + this.helpToolStripButton.Size = new System.Drawing.Size(23, 22); + this.helpToolStripButton.Text = "He&lp"; + this.helpToolStripButton.Click += new System.EventHandler(this.helpToolStripButton_Click); + // + // mainSplitterLeft + // + this.mainSplitterLeft.Dock = System.Windows.Forms.DockStyle.Fill; + this.mainSplitterLeft.FixedPanel = System.Windows.Forms.FixedPanel.Panel1; + this.mainSplitterLeft.Location = new System.Drawing.Point(0, 49); + this.mainSplitterLeft.Name = "mainSplitterLeft"; + // + // mainSplitterLeft.Panel1 + // + this.mainSplitterLeft.Panel1.Controls.Add(this.leftPanelSplitter); + this.mainSplitterLeft.Panel1MinSize = 100; + // + // mainSplitterLeft.Panel2 + // + this.mainSplitterLeft.Panel2.Controls.Add(this.mainSplitterRight); + this.mainSplitterLeft.Panel2MinSize = 350; + this.mainSplitterLeft.Size = new System.Drawing.Size(805, 543); + this.mainSplitterLeft.SplitterDistance = 168; + this.mainSplitterLeft.TabIndex = 3; + // + // leftPanelSplitter + // + this.leftPanelSplitter.Dock = System.Windows.Forms.DockStyle.Fill; + this.leftPanelSplitter.Location = new System.Drawing.Point(0, 0); + this.leftPanelSplitter.Name = "leftPanelSplitter"; + this.leftPanelSplitter.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // leftPanelSplitter.Panel1 + // + this.leftPanelSplitter.Panel1.Controls.Add(this.referencesGroupBox); + this.leftPanelSplitter.Panel1MinSize = 100; + // + // leftPanelSplitter.Panel2 + // + this.leftPanelSplitter.Panel2.Controls.Add(this.notesGroupBox); + this.leftPanelSplitter.Panel2MinSize = 100; + this.leftPanelSplitter.Size = new System.Drawing.Size(168, 543); + this.leftPanelSplitter.SplitterDistance = 350; + this.leftPanelSplitter.TabIndex = 0; + this.leftPanelSplitter.SplitterMoved += new System.Windows.Forms.SplitterEventHandler(this.SidePanelSplitter_SplitterMoved); + // + // referencesGroupBox + // + this.referencesGroupBox.Controls.Add(this.referencesListView); + this.referencesGroupBox.Dock = System.Windows.Forms.DockStyle.Fill; + this.referencesGroupBox.Location = new System.Drawing.Point(0, 0); + this.referencesGroupBox.Name = "referencesGroupBox"; + this.referencesGroupBox.Size = new System.Drawing.Size(168, 350); + this.referencesGroupBox.TabIndex = 2; + this.referencesGroupBox.TabStop = false; + this.referencesGroupBox.Text = "References"; + // + // referencesListView + // + this.referencesListView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.offsetColumnHeader, + this.addressColumnHeader, + this.typeColumnHeader}); + this.referencesListView.Dock = System.Windows.Forms.DockStyle.Fill; + this.referencesListView.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.referencesListView.FullRowSelect = true; + this.referencesListView.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable; + this.referencesListView.Location = new System.Drawing.Point(3, 16); + this.referencesListView.MultiSelect = false; + this.referencesListView.Name = "referencesListView"; + this.referencesListView.Size = new System.Drawing.Size(162, 331); + this.referencesListView.TabIndex = 1; + this.referencesListView.UseCompatibleStateImageBehavior = false; + this.referencesListView.View = System.Windows.Forms.View.Details; + this.referencesListView.ColumnWidthChanged += new System.Windows.Forms.ColumnWidthChangedEventHandler(this.referencesListView_ColumnWidthChanged); + this.referencesListView.SizeChanged += new System.EventHandler(this.referencesListView_SizeChanged); + this.referencesListView.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.referencesListView_MouseDoubleClick); + // + // offsetColumnHeader + // + this.offsetColumnHeader.Text = "Offset"; + this.offsetColumnHeader.Width = 52; + // + // addressColumnHeader + // + this.addressColumnHeader.Text = "Addr"; + this.addressColumnHeader.Width = 54; + // + // typeColumnHeader + // + this.typeColumnHeader.Text = "Type"; + // + // notesGroupBox + // + this.notesGroupBox.Controls.Add(this.notesListView); + this.notesGroupBox.Dock = System.Windows.Forms.DockStyle.Fill; + this.notesGroupBox.Location = new System.Drawing.Point(0, 0); + this.notesGroupBox.Name = "notesGroupBox"; + this.notesGroupBox.Size = new System.Drawing.Size(168, 189); + this.notesGroupBox.TabIndex = 0; + this.notesGroupBox.TabStop = false; + this.notesGroupBox.Text = "Notes"; + // + // notesListView + // + this.notesListView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.notesOffsetColumnHeader, + this.notesNoteColumnHeader}); + this.notesListView.Dock = System.Windows.Forms.DockStyle.Fill; + this.notesListView.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.notesListView.FullRowSelect = true; + this.notesListView.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable; + this.notesListView.Location = new System.Drawing.Point(3, 16); + this.notesListView.MultiSelect = false; + this.notesListView.Name = "notesListView"; + this.notesListView.OwnerDraw = true; + this.notesListView.Size = new System.Drawing.Size(162, 170); + this.notesListView.TabIndex = 0; + this.notesListView.UseCompatibleStateImageBehavior = false; + this.notesListView.View = System.Windows.Forms.View.Details; + this.notesListView.VirtualMode = true; + this.notesListView.CacheVirtualItems += new System.Windows.Forms.CacheVirtualItemsEventHandler(this.notesListView_CacheVirtualItems); + this.notesListView.ColumnWidthChanged += new System.Windows.Forms.ColumnWidthChangedEventHandler(this.notesListView_ColumnWidthChanged); + this.notesListView.DrawColumnHeader += new System.Windows.Forms.DrawListViewColumnHeaderEventHandler(this.notesListView_DrawColumnHeader); + this.notesListView.DrawItem += new System.Windows.Forms.DrawListViewItemEventHandler(this.notesListView_DrawItem); + this.notesListView.DrawSubItem += new System.Windows.Forms.DrawListViewSubItemEventHandler(this.notesListView_DrawSubItem); + this.notesListView.RetrieveVirtualItem += new System.Windows.Forms.RetrieveVirtualItemEventHandler(this.notesListView_RetrieveVirtualItem); + this.notesListView.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.notesListView_MouseDoubleClick); + // + // notesOffsetColumnHeader + // + this.notesOffsetColumnHeader.Text = "Offset"; + this.notesOffsetColumnHeader.Width = 52; + // + // notesNoteColumnHeader + // + this.notesNoteColumnHeader.Text = "Note"; + this.notesNoteColumnHeader.Width = 400; + // + // mainSplitterRight + // + this.mainSplitterRight.Dock = System.Windows.Forms.DockStyle.Fill; + this.mainSplitterRight.FixedPanel = System.Windows.Forms.FixedPanel.Panel2; + this.mainSplitterRight.Location = new System.Drawing.Point(0, 0); + this.mainSplitterRight.Name = "mainSplitterRight"; + // + // mainSplitterRight.Panel1 + // + this.mainSplitterRight.Panel1.Controls.Add(this.noProjectPanel); + this.mainSplitterRight.Panel1.Controls.Add(this.codeListView); + this.mainSplitterRight.Panel1MinSize = 150; + // + // mainSplitterRight.Panel2 + // + this.mainSplitterRight.Panel2.Controls.Add(this.rightPanelSplitter); + this.mainSplitterRight.Panel2MinSize = 100; + this.mainSplitterRight.Size = new System.Drawing.Size(633, 543); + this.mainSplitterRight.SplitterDistance = 446; + this.mainSplitterRight.TabIndex = 0; + // + // noProjectPanel + // + this.noProjectPanel.Controls.Add(this.debugModeEnabledLabel); + this.noProjectPanel.Controls.Add(this.recentProjectsLabel); + this.noProjectPanel.Controls.Add(this.recentProjectLabel2); + this.noProjectPanel.Controls.Add(this.versionLabel); + this.noProjectPanel.Controls.Add(this.sourceGenLabel); + this.noProjectPanel.Controls.Add(this.logoPictureBox); + this.noProjectPanel.Controls.Add(this.recentProjectLabel1); + this.noProjectPanel.Controls.Add(this.openExistingLabel); + this.noProjectPanel.Controls.Add(this.newProjectLink); + this.noProjectPanel.Dock = System.Windows.Forms.DockStyle.Fill; + this.noProjectPanel.Location = new System.Drawing.Point(0, 0); + this.noProjectPanel.Name = "noProjectPanel"; + this.noProjectPanel.Size = new System.Drawing.Size(446, 543); + this.noProjectPanel.TabIndex = 1; + // + // debugModeEnabledLabel + // + this.debugModeEnabledLabel.AutoSize = true; + this.debugModeEnabledLabel.Location = new System.Drawing.Point(219, 135); + this.debugModeEnabledLabel.Name = "debugModeEnabledLabel"; + this.debugModeEnabledLabel.Size = new System.Drawing.Size(271, 13); + this.debugModeEnabledLabel.TabIndex = 8; + this.debugModeEnabledLabel.Text = "Debug mode • extended logging and validation enabled"; + this.debugModeEnabledLabel.Visible = false; + // + // recentProjectsLabel + // + this.recentProjectsLabel.AutoSize = true; + this.recentProjectsLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.recentProjectsLabel.Location = new System.Drawing.Point(25, 307); + this.recentProjectsLabel.Name = "recentProjectsLabel"; + this.recentProjectsLabel.Size = new System.Drawing.Size(157, 20); + this.recentProjectsLabel.TabIndex = 7; + this.recentProjectsLabel.Text = "Most recent projects:"; + // + // recentProjectLabel2 + // + this.recentProjectLabel2.AutoSize = true; + this.recentProjectLabel2.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.recentProjectLabel2.Location = new System.Drawing.Point(40, 427); + this.recentProjectLabel2.Name = "recentProjectLabel2"; + this.recentProjectLabel2.Size = new System.Drawing.Size(135, 20); + this.recentProjectLabel2.TabIndex = 6; + this.recentProjectLabel2.TabStop = true; + this.recentProjectLabel2.Text = "Recent project #2"; + this.recentProjectLabel2.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.recentProjectLabel2_LinkClicked); + // + // versionLabel + // + this.versionLabel.AutoSize = true; + this.versionLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 20.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.versionLabel.Location = new System.Drawing.Point(213, 90); + this.versionLabel.Name = "versionLabel"; + this.versionLabel.Size = new System.Drawing.Size(273, 31); + this.versionLabel.TabIndex = 5; + this.versionLabel.Text = "Version X.Y.Z Alpha1"; + // + // sourceGenLabel + // + this.sourceGenLabel.AutoSize = true; + this.sourceGenLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 24F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.sourceGenLabel.Location = new System.Drawing.Point(213, 32); + this.sourceGenLabel.Name = "sourceGenLabel"; + this.sourceGenLabel.Size = new System.Drawing.Size(346, 37); + this.sourceGenLabel.TabIndex = 4; + this.sourceGenLabel.Text = "6502bench SourceGen"; + // + // logoPictureBox + // + this.logoPictureBox.ImageLocation = "SourceGen\\RuntimeData\\Logo.png"; + this.logoPictureBox.Location = new System.Drawing.Point(4, 4); + this.logoPictureBox.Name = "logoPictureBox"; + this.logoPictureBox.Size = new System.Drawing.Size(203, 145); + this.logoPictureBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.logoPictureBox.TabIndex = 3; + this.logoPictureBox.TabStop = false; + // + // recentProjectLabel1 + // + this.recentProjectLabel1.AutoSize = true; + this.recentProjectLabel1.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.recentProjectLabel1.Location = new System.Drawing.Point(40, 367); + this.recentProjectLabel1.Name = "recentProjectLabel1"; + this.recentProjectLabel1.Size = new System.Drawing.Size(135, 20); + this.recentProjectLabel1.TabIndex = 2; + this.recentProjectLabel1.TabStop = true; + this.recentProjectLabel1.Text = "Recent project #1"; + this.recentProjectLabel1.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.recentProjectLabel1_LinkClicked); + // + // openExistingLabel + // + this.openExistingLabel.AutoSize = true; + this.openExistingLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.openExistingLabel.Location = new System.Drawing.Point(25, 247); + this.openExistingLabel.Name = "openExistingLabel"; + this.openExistingLabel.Size = new System.Drawing.Size(157, 20); + this.openExistingLabel.TabIndex = 1; + this.openExistingLabel.TabStop = true; + this.openExistingLabel.Text = "Open existing project"; + this.openExistingLabel.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.openExistingLabel_LinkClicked); + // + // newProjectLink + // + this.newProjectLink.AutoSize = true; + this.newProjectLink.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.newProjectLink.Location = new System.Drawing.Point(25, 187); + this.newProjectLink.Name = "newProjectLink"; + this.newProjectLink.Size = new System.Drawing.Size(129, 20); + this.newProjectLink.TabIndex = 0; + this.newProjectLink.TabStop = true; + this.newProjectLink.Text = "Start new project"; + this.newProjectLink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.newFileLink_LinkClicked); + // + // codeListView + // + this.codeListView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.columnOffset, + this.columnAddr, + this.columnBytes, + this.columnFlags, + this.columnAttr, + this.columnLabel, + this.columnOpcode, + this.columnOperand, + this.columnComment}); + this.codeListView.Dock = System.Windows.Forms.DockStyle.Fill; + this.codeListView.Font = new System.Drawing.Font("Consolas", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.codeListView.FullRowSelect = true; + this.codeListView.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable; + this.codeListView.HideSelection = false; + this.codeListView.Location = new System.Drawing.Point(0, 0); + this.codeListView.Name = "codeListView"; + this.codeListView.OwnerDraw = true; + this.codeListView.Size = new System.Drawing.Size(446, 543); + this.codeListView.TabIndex = 0; + this.codeListView.UseCompatibleStateImageBehavior = false; + this.codeListView.VirtualMode = true; + this.codeListView.CacheVirtualItems += new System.Windows.Forms.CacheVirtualItemsEventHandler(this.codeListView_CacheVirtualItems); + this.codeListView.ColumnWidthChanged += new System.Windows.Forms.ColumnWidthChangedEventHandler(this.codeListView_ColumnWidthChanged); + this.codeListView.DrawColumnHeader += new System.Windows.Forms.DrawListViewColumnHeaderEventHandler(this.codeListView_DrawColumnHeader); + this.codeListView.DrawItem += new System.Windows.Forms.DrawListViewItemEventHandler(this.codeListView_DrawItem); + this.codeListView.DrawSubItem += new System.Windows.Forms.DrawListViewSubItemEventHandler(this.codeListView_DrawSubItem); + this.codeListView.ItemSelectionChanged += new System.Windows.Forms.ListViewItemSelectionChangedEventHandler(this.codeListView_ItemSelectionChanged); + this.codeListView.RetrieveVirtualItem += new System.Windows.Forms.RetrieveVirtualItemEventHandler(this.codeListView_RetrieveVirtualItem); + this.codeListView.SelectedIndexChanged += new System.EventHandler(this.codeListView_SelectedIndexChanged); + this.codeListView.VirtualItemsSelectionRangeChanged += new System.Windows.Forms.ListViewVirtualItemsSelectionRangeChangedEventHandler(this.codeListView_VirtualItemsSelectionRangeChanged); + this.codeListView.MouseClick += new System.Windows.Forms.MouseEventHandler(this.codeListView_MouseClick); + this.codeListView.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.codeListView_MouseDoubleClick); + // + // columnOffset + // + this.columnOffset.Text = "offset"; + this.columnOffset.Width = -2; + // + // columnAddr + // + this.columnAddr.Text = "addr"; + this.columnAddr.Width = -2; + // + // columnBytes + // + this.columnBytes.Text = "bytes"; + this.columnBytes.Width = -2; + // + // columnFlags + // + this.columnFlags.Text = "flags"; + this.columnFlags.Width = -2; + // + // columnAttr + // + this.columnAttr.Text = "attr"; + this.columnAttr.Width = -2; + // + // columnLabel + // + this.columnLabel.Text = "label"; + this.columnLabel.Width = -2; + // + // columnOpcode + // + this.columnOpcode.Text = "opcode"; + this.columnOpcode.Width = -2; + // + // columnOperand + // + this.columnOperand.Text = "operand"; + this.columnOperand.Width = -2; + // + // columnComment + // + this.columnComment.Text = "comment"; + this.columnComment.Width = -2; + // + // rightPanelSplitter + // + this.rightPanelSplitter.Dock = System.Windows.Forms.DockStyle.Fill; + this.rightPanelSplitter.FixedPanel = System.Windows.Forms.FixedPanel.Panel2; + this.rightPanelSplitter.Location = new System.Drawing.Point(0, 0); + this.rightPanelSplitter.Name = "rightPanelSplitter"; + this.rightPanelSplitter.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // rightPanelSplitter.Panel1 + // + this.rightPanelSplitter.Panel1.Controls.Add(this.symbolsGroupBox); + this.rightPanelSplitter.Panel1MinSize = 200; + // + // rightPanelSplitter.Panel2 + // + this.rightPanelSplitter.Panel2.Controls.Add(this.infoGroupBox); + this.rightPanelSplitter.Panel2MinSize = 100; + this.rightPanelSplitter.Size = new System.Drawing.Size(183, 543); + this.rightPanelSplitter.SplitterDistance = 400; + this.rightPanelSplitter.TabIndex = 8; + this.rightPanelSplitter.SplitterMoved += new System.Windows.Forms.SplitterEventHandler(this.SidePanelSplitter_SplitterMoved); + // + // symbolsGroupBox + // + this.symbolsGroupBox.Controls.Add(this.symbolUserCheckBox); + this.symbolsGroupBox.Controls.Add(this.symbolProjectCheckBox); + this.symbolsGroupBox.Controls.Add(this.symbolAddressCheckBox); + this.symbolsGroupBox.Controls.Add(this.symbolConstantCheckBox); + this.symbolsGroupBox.Controls.Add(this.symbolAutoCheckBox); + this.symbolsGroupBox.Controls.Add(this.symbolPlatformCheckBox); + this.symbolsGroupBox.Controls.Add(this.symbolListView); + this.symbolsGroupBox.Dock = System.Windows.Forms.DockStyle.Fill; + this.symbolsGroupBox.Location = new System.Drawing.Point(0, 0); + this.symbolsGroupBox.Name = "symbolsGroupBox"; + this.symbolsGroupBox.Size = new System.Drawing.Size(183, 400); + this.symbolsGroupBox.TabIndex = 2; + this.symbolsGroupBox.TabStop = false; + this.symbolsGroupBox.Text = "Symbols"; + // + // symbolUserCheckBox + // + this.symbolUserCheckBox.Appearance = System.Windows.Forms.Appearance.Button; + this.symbolUserCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.symbolUserCheckBox.Location = new System.Drawing.Point(6, 19); + this.symbolUserCheckBox.Name = "symbolUserCheckBox"; + this.symbolUserCheckBox.Size = new System.Drawing.Size(39, 23); + this.symbolUserCheckBox.TabIndex = 7; + this.symbolUserCheckBox.Text = "User"; + this.symbolUserCheckBox.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.symbolUserCheckBox.UseVisualStyleBackColor = true; + this.symbolUserCheckBox.CheckedChanged += new System.EventHandler(this.symbolUserCheckBox_CheckedChanged); + // + // symbolProjectCheckBox + // + this.symbolProjectCheckBox.Appearance = System.Windows.Forms.Appearance.Button; + this.symbolProjectCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.symbolProjectCheckBox.Location = new System.Drawing.Point(51, 19); + this.symbolProjectCheckBox.Name = "symbolProjectCheckBox"; + this.symbolProjectCheckBox.Size = new System.Drawing.Size(39, 23); + this.symbolProjectCheckBox.TabIndex = 6; + this.symbolProjectCheckBox.Text = "Proj"; + this.symbolProjectCheckBox.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.symbolProjectCheckBox.UseVisualStyleBackColor = true; + this.symbolProjectCheckBox.CheckedChanged += new System.EventHandler(this.symbolProjectCheckBox_CheckedChanged); + // + // symbolAddressCheckBox + // + this.symbolAddressCheckBox.Appearance = System.Windows.Forms.Appearance.Button; + this.symbolAddressCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.symbolAddressCheckBox.Location = new System.Drawing.Point(5, 48); + this.symbolAddressCheckBox.Name = "symbolAddressCheckBox"; + this.symbolAddressCheckBox.Size = new System.Drawing.Size(39, 23); + this.symbolAddressCheckBox.TabIndex = 5; + this.symbolAddressCheckBox.Text = "Addr"; + this.symbolAddressCheckBox.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.symbolAddressCheckBox.UseVisualStyleBackColor = true; + this.symbolAddressCheckBox.CheckedChanged += new System.EventHandler(this.symbolAddrCheckBox_CheckedChanged); + // + // symbolConstantCheckBox + // + this.symbolConstantCheckBox.Appearance = System.Windows.Forms.Appearance.Button; + this.symbolConstantCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.symbolConstantCheckBox.Location = new System.Drawing.Point(51, 48); + this.symbolConstantCheckBox.Name = "symbolConstantCheckBox"; + this.symbolConstantCheckBox.Size = new System.Drawing.Size(39, 23); + this.symbolConstantCheckBox.TabIndex = 4; + this.symbolConstantCheckBox.Text = "Cnst"; + this.symbolConstantCheckBox.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.symbolConstantCheckBox.UseVisualStyleBackColor = true; + this.symbolConstantCheckBox.CheckedChanged += new System.EventHandler(this.symbolConstantCheckBox_CheckedChanged); + // + // symbolAutoCheckBox + // + this.symbolAutoCheckBox.Appearance = System.Windows.Forms.Appearance.Button; + this.symbolAutoCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.symbolAutoCheckBox.Location = new System.Drawing.Point(141, 19); + this.symbolAutoCheckBox.Name = "symbolAutoCheckBox"; + this.symbolAutoCheckBox.Size = new System.Drawing.Size(39, 23); + this.symbolAutoCheckBox.TabIndex = 1; + this.symbolAutoCheckBox.Text = "Auto"; + this.symbolAutoCheckBox.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.symbolAutoCheckBox.UseVisualStyleBackColor = true; + this.symbolAutoCheckBox.CheckedChanged += new System.EventHandler(this.symbolAutoCheckBox_CheckedChanged); + // + // symbolPlatformCheckBox + // + this.symbolPlatformCheckBox.Appearance = System.Windows.Forms.Appearance.Button; + this.symbolPlatformCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.symbolPlatformCheckBox.Location = new System.Drawing.Point(96, 19); + this.symbolPlatformCheckBox.Name = "symbolPlatformCheckBox"; + this.symbolPlatformCheckBox.Size = new System.Drawing.Size(39, 23); + this.symbolPlatformCheckBox.TabIndex = 3; + this.symbolPlatformCheckBox.Text = "Plat"; + this.symbolPlatformCheckBox.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.symbolPlatformCheckBox.UseVisualStyleBackColor = true; + this.symbolPlatformCheckBox.CheckedChanged += new System.EventHandler(this.symbolPlatformCheckBox_CheckedChanged); + // + // symbolListView + // + this.symbolListView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.symbolListView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.symbolTypeColumnHeader, + this.symbolNameColumnHeader, + this.symbolValueColumnHeader}); + this.symbolListView.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.symbolListView.FullRowSelect = true; + this.symbolListView.GridLines = true; + this.symbolListView.Location = new System.Drawing.Point(3, 79); + this.symbolListView.MultiSelect = false; + this.symbolListView.Name = "symbolListView"; + this.symbolListView.Size = new System.Drawing.Size(177, 318); + this.symbolListView.TabIndex = 2; + this.symbolListView.UseCompatibleStateImageBehavior = false; + this.symbolListView.View = System.Windows.Forms.View.Details; + this.symbolListView.VirtualMode = true; + this.symbolListView.CacheVirtualItems += new System.Windows.Forms.CacheVirtualItemsEventHandler(this.symbolListView_CacheVirtualItems); + this.symbolListView.ColumnClick += new System.Windows.Forms.ColumnClickEventHandler(this.symbolListView_ColumnClick); + this.symbolListView.ColumnWidthChanged += new System.Windows.Forms.ColumnWidthChangedEventHandler(this.symbolListView_ColumnWidthChanged); + this.symbolListView.RetrieveVirtualItem += new System.Windows.Forms.RetrieveVirtualItemEventHandler(this.symbolListView_RetrieveVirtualItem); + this.symbolListView.SizeChanged += new System.EventHandler(this.symbolListView_SizeChanged); + this.symbolListView.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.symbolListView_MouseDoubleClick); + // + // symbolTypeColumnHeader + // + this.symbolTypeColumnHeader.Text = "Type"; + this.symbolTypeColumnHeader.Width = 44; + // + // symbolNameColumnHeader + // + this.symbolNameColumnHeader.Text = "Name"; + // + // symbolValueColumnHeader + // + this.symbolValueColumnHeader.Text = "Value"; + // + // infoGroupBox + // + this.infoGroupBox.Controls.Add(this.infoTextBox); + this.infoGroupBox.Dock = System.Windows.Forms.DockStyle.Fill; + this.infoGroupBox.Location = new System.Drawing.Point(0, 0); + this.infoGroupBox.Name = "infoGroupBox"; + this.infoGroupBox.Size = new System.Drawing.Size(183, 139); + this.infoGroupBox.TabIndex = 0; + this.infoGroupBox.TabStop = false; + this.infoGroupBox.Text = "Info"; + // + // infoTextBox + // + this.infoTextBox.Dock = System.Windows.Forms.DockStyle.Fill; + this.infoTextBox.Location = new System.Drawing.Point(3, 16); + this.infoTextBox.Multiline = true; + this.infoTextBox.Name = "infoTextBox"; + this.infoTextBox.ReadOnly = true; + this.infoTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.infoTextBox.Size = new System.Drawing.Size(177, 120); + this.infoTextBox.TabIndex = 0; + // + // ProjectView + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(805, 614); + this.Controls.Add(this.mainSplitterLeft); + this.Controls.Add(this.mainStatusStrip); + this.Controls.Add(this.mainToolStrip); + this.Controls.Add(this.mainMenuStrip); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.MinimumSize = new System.Drawing.Size(800, 600); + this.Name = "ProjectView"; + this.Text = "6502bench SourceGen"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ProjectView_FormClosing); + this.Load += new System.EventHandler(this.ProjectView_Load); + this.SizeChanged += new System.EventHandler(this.ProjectView_SizeChanged); + this.mainMenuStrip.ResumeLayout(false); + this.mainMenuStrip.PerformLayout(); + this.mainStatusStrip.ResumeLayout(false); + this.mainStatusStrip.PerformLayout(); + this.mainToolStrip.ResumeLayout(false); + this.mainToolStrip.PerformLayout(); + this.mainSplitterLeft.Panel1.ResumeLayout(false); + this.mainSplitterLeft.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.mainSplitterLeft)).EndInit(); + this.mainSplitterLeft.ResumeLayout(false); + this.leftPanelSplitter.Panel1.ResumeLayout(false); + this.leftPanelSplitter.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.leftPanelSplitter)).EndInit(); + this.leftPanelSplitter.ResumeLayout(false); + this.referencesGroupBox.ResumeLayout(false); + this.notesGroupBox.ResumeLayout(false); + this.mainSplitterRight.Panel1.ResumeLayout(false); + this.mainSplitterRight.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.mainSplitterRight)).EndInit(); + this.mainSplitterRight.ResumeLayout(false); + this.noProjectPanel.ResumeLayout(false); + this.noProjectPanel.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.logoPictureBox)).EndInit(); + this.rightPanelSplitter.Panel1.ResumeLayout(false); + this.rightPanelSplitter.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.rightPanelSplitter)).EndInit(); + this.rightPanelSplitter.ResumeLayout(false); + this.symbolsGroupBox.ResumeLayout(false); + this.infoGroupBox.ResumeLayout(false); + this.infoGroupBox.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.MenuStrip mainMenuStrip; + private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem openToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem editToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem copyToolStripMenuItem; + private System.Windows.Forms.StatusStrip mainStatusStrip; + private System.Windows.Forms.ToolStrip mainToolStrip; + private System.Windows.Forms.ToolStripButton newToolStripButton; + private System.Windows.Forms.ToolStripButton openToolStripButton; + private System.Windows.Forms.ToolStripButton saveToolStripButton; + private System.Windows.Forms.ToolStripButton printToolStripButton; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator; + private System.Windows.Forms.ToolStripButton cutToolStripButton; + private System.Windows.Forms.ToolStripButton copyToolStripButton; + private System.Windows.Forms.ToolStripButton pasteToolStripButton; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; + private System.Windows.Forms.ToolStripButton helpToolStripButton; + private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel; + private System.Windows.Forms.SplitContainer mainSplitterLeft; + private System.Windows.Forms.SplitContainer mainSplitterRight; + private System.Windows.Forms.ListView codeListView; + private System.Windows.Forms.SplitContainer leftPanelSplitter; + private System.Windows.Forms.ListView referencesListView; + private System.Windows.Forms.Panel noProjectPanel; + private System.Windows.Forms.LinkLabel newProjectLink; + private System.Windows.Forms.LinkLabel openExistingLabel; + private System.Windows.Forms.LinkLabel recentProjectLabel1; + private System.Windows.Forms.ColumnHeader columnOffset; + private System.Windows.Forms.ColumnHeader columnAddr; + private System.Windows.Forms.ColumnHeader columnBytes; + private System.Windows.Forms.ColumnHeader columnFlags; + private System.Windows.Forms.ColumnHeader columnAttr; + private System.Windows.Forms.ColumnHeader columnLabel; + private System.Windows.Forms.ColumnHeader columnOpcode; + private System.Windows.Forms.ColumnHeader columnOperand; + private System.Windows.Forms.ColumnHeader columnComment; + private System.Windows.Forms.ToolStripMenuItem undoToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem redoToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem1; + private System.Windows.Forms.CheckBox symbolAutoCheckBox; + private System.Windows.Forms.GroupBox symbolsGroupBox; + private System.Windows.Forms.ListView symbolListView; + private System.Windows.Forms.ColumnHeader symbolNameColumnHeader; + private System.Windows.Forms.ColumnHeader symbolValueColumnHeader; + private System.Windows.Forms.ColumnHeader symbolTypeColumnHeader; + private System.Windows.Forms.ColumnHeader offsetColumnHeader; + private System.Windows.Forms.ColumnHeader addressColumnHeader; + private System.Windows.Forms.ColumnHeader typeColumnHeader; + private System.Windows.Forms.GroupBox referencesGroupBox; + private System.Windows.Forms.GroupBox infoGroupBox; + private System.Windows.Forms.CheckBox symbolAddressCheckBox; + private System.Windows.Forms.CheckBox symbolConstantCheckBox; + private System.Windows.Forms.CheckBox symbolPlatformCheckBox; + private System.Windows.Forms.CheckBox symbolProjectCheckBox; + private System.Windows.Forms.CheckBox symbolUserCheckBox; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem2; + private System.Windows.Forms.ToolStripMenuItem selectAllToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem actionsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem setAddressToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem overrideStatusFlagsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem editLabelToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem editOperandToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem editDataFormatToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem editCommentToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem editLongCommentToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem editNoteToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator actionsMenuSeparator1; + private System.Windows.Forms.ToolStripMenuItem hintAsCodeToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem hintAsDataToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem hintAsInlineDataToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem removeHintToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem toolsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem helpToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem dEBUGToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem toggleOwnerDrawToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem3; + private System.Windows.Forms.ToolStripMenuItem editHeaderCommentToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem saveToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem saveAsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem newToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem4; + private System.Windows.Forms.ToolStripMenuItem printToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem5; + private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem; + private System.Windows.Forms.PictureBox logoPictureBox; + private System.Windows.Forms.Label versionLabel; + private System.Windows.Forms.Label sourceGenLabel; + private System.Windows.Forms.ToolStripMenuItem closeToolStripMenuItem; + private System.Windows.Forms.LinkLabel recentProjectLabel2; + private System.Windows.Forms.SplitContainer rightPanelSplitter; + private System.Windows.Forms.GroupBox notesGroupBox; + private System.Windows.Forms.ListView notesListView; + private System.Windows.Forms.ColumnHeader notesOffsetColumnHeader; + private System.Windows.Forms.ColumnHeader notesNoteColumnHeader; + private System.Windows.Forms.TextBox infoTextBox; + private System.Windows.Forms.ToolStripMenuItem reanalyzeToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem projectPropertiesToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem settingsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem showUndoRedoHistoryToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem showAnalysisTimersToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem assembleToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem6; + private System.Windows.Forms.ToolStripMenuItem toggleCommentRulersToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem sourceGenTestsToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem8; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem7; + private System.Windows.Forms.ToolStripMenuItem aboutToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem extensionScriptInfoToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem showAnalyzerOutputToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem contentsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem hexDumpToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem9; + private System.Windows.Forms.ToolStripMenuItem showHexDumpToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem recentProjectsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem noRecentsToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem10; + private System.Windows.Forms.Label recentProjectsLabel; + private System.Windows.Forms.ToolStripMenuItem useKeepAliveHackToolStripMenuItem; + private System.Windows.Forms.Label debugModeEnabledLabel; + private System.Windows.Forms.ToolStripButton navigateFwdToolStripButton; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; + private System.Windows.Forms.ToolStripButton navigateBackToolStripButton; + private System.Windows.Forms.ToolStripMenuItem findToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem findNextToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem gotoToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem aSCIIChartToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem11; + private System.Windows.Forms.ToolStripMenuItem toggleSingleBytesToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem editProjectSymbolToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem deleteNoteCommentToolStripMenuItem; + } +} + diff --git a/SourceGen/AppForms/ProjectView.cs b/SourceGen/AppForms/ProjectView.cs new file mode 100644 index 0000000..850510a --- /dev/null +++ b/SourceGen/AppForms/ProjectView.cs @@ -0,0 +1,4357 @@ +/* + * Copyright 2018 faddenSoft + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Text; +using System.Web.Script.Serialization; +using System.Windows.Forms; + +using Asm65; +using CommonUtil; +using CommonWinForms; +using SourceGen.Sandbox; + +namespace SourceGen.AppForms { + /// + /// Main application form. This is the top-level application object. + /// + public partial class ProjectView : Form { + private const string LOGO_FILE_NAME = "Logo.png"; + private const string SETTINGS_FILE_NAME = "SourceGen-settings"; + + #region Project state + + // Currently open project, or null if none. + private DisasmProject mProject; + + // Pathname to 65xx data file. + private string mDataPathName; + + // Pathname of .dis65 file. This will be empty for a new project. + private string mProjectPathName; + + /// + /// Symbol subset, used to supply data to the symbol ListView. Initialized with + /// an empty symbol table. + /// + private SymbolTableSubset mSymbolSubset = new SymbolTableSubset(new SymbolTable()); + + /// + /// Current code list view selection. The length will match the DisplayList Count. + /// + /// A simple foreach through codeListView.SelectedIndices on a 500K-line data set + /// takes about 2.5 seconds on a fast Win10 x64 machine. Fortunately the control + /// notifies us of changes to the selection, so we can track it ourselves. + /// + private VirtualListViewSelection mCodeViewSelection = new VirtualListViewSelection(); + + /// + /// Data backing the codeListView. + /// + private DisplayList mDisplayList; + + #endregion Project state + + + /// + /// Returns the font currently in use by the code ListView. + /// + public Font CodeListViewFont { get { return codeListView.Font; } } + + /// + /// List of recently-opened projects. + /// + private List mRecentProjectPaths = new List(MAX_RECENT_PROJECTS); + private const int MAX_RECENT_PROJECTS = 6; + + /// + /// Menu items for the Actions and codeListView context menus. The menu items can + /// only be in one place at a time, so we move them around when the menu is opened. + /// + private ToolStripItem[] mActionsMenuItems; + + /// + /// Activity log generated by the code and data analyzers. Displayed in window. + /// + private DebugLog mGenerationLog; + + /// + /// Timing data generated during analysis. + /// + TaskTimer mReanalysisTimer = new TaskTimer(); + + /// + /// Base control to show when no project is open. + /// + private Control mNoProjectControl; + + /// + /// Base control to show when project is open. + /// + private Control mProjectControl; + + /// + /// Performance hack. + /// + private bool mRestoringSelection; + + /// + /// Stack for navigate forward/backward. + /// + private NavStack mNavStack = new NavStack(); + + /// + /// Output format configuration. + /// + private Formatter.FormatConfig mFormatterConfig; + + /// + /// Output format controller. + /// + /// This is shared with the DisplayList. + /// + private Formatter mOutputFormatter; + + /// + /// Pseudo-op names. + /// + /// This is shared with the DisplayList. + /// + private PseudoOp.PseudoOpNames mPseudoOpNames; + + /// + /// String we most recently searched for. + /// + private string mFindString = string.Empty; + + /// + /// Initial start point of most recent search. + /// + private int mFindStartIndex = -1; + + /// + /// Used to highlight the line that is the target of the selected line. + /// + private int mTargetHighlightIndex = -1; + + /// + /// CPU definition used when the Formatter was created. If the CPU choice or + /// inclusion of undocumented opcodes changes, we need to wipe the formatter. + /// + private CpuDef mOutputFormatterCpuDef; + + /// + /// Instruction description object. Used for Info window. + /// + private OpDescription mOpDesc = OpDescription.GetOpDescription(null); + + /// + /// If true, plugins will execute in the main application's AppDomain instead of + /// the sandbox. + /// + private bool mUseMainAppDomainForPlugins = false; + + /// + /// Project hex dump viewer. This is just for viewing the project contents, so this + /// dialog is tied to the project. + /// + /// The general-purpose file dump windows are independent (currently untracked). + /// + private Tools.HexDumpViewer mHexDumpDialog; + + /// + /// Floating ASCII chart dialog. Not tied to the project. + /// + private Tools.AsciiChart mAsciiChartDialog; + + + #region Init and settings + + public ProjectView() { + InitializeComponent(); + + ScriptManager.UseKeepAliveHack = true; + +#if DEBUG + debugModeEnabledLabel.Visible = true; +#endif + } + + private void ProjectView_Load(object sender, EventArgs e) { + if (RuntimeDataAccess.GetDirectory() == null) { + MessageBox.Show(Properties.Resources.RUNTIME_DIR_NOT_FOUND, + Properties.Resources.RUNTIME_DIR_NOT_FOUND_CAPTION, + MessageBoxButtons.OK, MessageBoxIcon.Error); + Application.Exit(); + } + try { + PluginDllCache.PreparePluginDir(); + } catch (Exception ex) { + string msg = string.Format(Properties.Resources.PLUGIN_DIR_FAIL, + PluginDllCache.GetPluginDirPath() + ": " + ex.Message); + MessageBox.Show(msg, Properties.Resources.PLUGIN_DIR_FAIL_CAPTION, + MessageBoxButtons.OK, MessageBoxIcon.Error); + Application.Exit(); + } + + logoPictureBox.ImageLocation = RuntimeDataAccess.GetPathName(LOGO_FILE_NAME); + versionLabel.Text = string.Format(Properties.Resources.VERSION_FMT, + Program.ProgramVersion); + + toolStripStatusLabel.Text = Properties.Resources.STATUS_READY; + + mProjectControl = this.codeListView; + mNoProjectControl = this.noProjectPanel; + + // Clone the menu structure from the designer. The same items are used for + // both Edit > Actions and the right-click context menu in codeListView. + mActionsMenuItems = new ToolStripItem[actionsToolStripMenuItem.DropDownItems.Count]; + for (int i = 0; i < actionsToolStripMenuItem.DropDownItems.Count; i++) { + mActionsMenuItems[i] = actionsToolStripMenuItem.DropDownItems[i]; + } + + // Init primary ListView (virtual, ownerdraw) + InitCodeListView(); + + // Init Symbols ListView (virtual, non-ownerdraw) + symbolListView.SetDoubleBuffered(true); + InitSymbolListView(); + + LoadAppSettings(); + ApplyAppSettings(); + + // Init References ListView (non-virtual, non-ownerdraw) + referencesListView.SetDoubleBuffered(true); + + UpdateActionMenu(); + UpdateMenuItemsAndTitle(); + UpdateRecentLinks(); + + ShowNoProject(); + } + + private void InitCodeListView() { + ListView cv = codeListView; + cv.View = View.Details; + + // Create an empty place-holder for the context menu. + codeListView.ContextMenuStrip = new ContextMenuStrip(); + + // When the Actions or context menu are opened, all menu items get transferred over. + codeListView.ContextMenuStrip.Opening += codeListView_CmsOpening; + + // Set default widths, in case we don't have a value for this in AppSettings. + CodeListColumnWidths widths = GetDefaultCodeListColumnWidths(); + SetCodeListHeaderWidths(widths); + + // This gets invoked when the user starts typing characters while the ListView + // has focus. Useful in a file viewer to find something by the first few + // characters. Less useful for us, though we could potentially use it as a + // shortcut to jump to an offset or label. + //codeListView.SearchForVirtualItem += + // new SearchForVirtualItemEventHandler(codeListView_SearchForVirtualItem); + + // Can't simply set this to "true", because the member is protected. + codeListView.SetDoubleBuffered(true); + } + + /// + /// Loads settings from the settings file into AppSettings.Global. Does not apply + /// them to the ProjectView. + /// + private void LoadAppSettings() { + AppSettings settings = AppSettings.Global; + + // Set some default settings for first-time use. The general rule is to set + // a default value of false, 0, or the empty string, so we only need to set + // values here when that isn't the case. The point at which the setting is + // retrieved is expected to do something reasonable by default. + + settings.SetBool(AppSettings.SYMWIN_SHOW_USER, true); + settings.SetBool(AppSettings.SYMWIN_SHOW_PROJECT, true); + settings.SetBool(AppSettings.SYMWIN_SHOW_PLATFORM, false); + settings.SetBool(AppSettings.SYMWIN_SHOW_AUTO, false); + settings.SetBool(AppSettings.SYMWIN_SHOW_CONST, true); + settings.SetBool(AppSettings.SYMWIN_SHOW_ADDR, true); + settings.SetBool(AppSettings.SYMWIN_SORT_ASCENDING, true); + settings.SetInt(AppSettings.SYMWIN_SORT_COL, (int)SymbolTableSubset.SortCol.Name); + + settings.SetBool(AppSettings.FMT_UPPER_OPERAND_A, true); + settings.SetBool(AppSettings.FMT_UPPER_OPERAND_S, true); + settings.SetBool(AppSettings.FMT_ADD_SPACE_FULL_COMMENT, true); + settings.SetString(AppSettings.FMT_OPCODE_SUFFIX_LONG, "l"); + settings.SetString(AppSettings.FMT_OPERAND_PREFIX_ABS, "a:"); + settings.SetString(AppSettings.FMT_OPERAND_PREFIX_LONG, "f:"); + + settings.SetBool(AppSettings.SRCGEN_ADD_IDENT_COMMENT, true); + settings.SetBool(AppSettings.SRCGEN_LONG_LABEL_NEW_LINE, true); + +#if DEBUG + settings.SetBool(AppSettings.DEBUG_MENU_ENABLED, true); +#else + settings.SetBool(AppSettings.DEBUG_MENU_ENABLED, false); +#endif + + // Load the settings file, and merge it into the globals. + string settingsDir = Path.GetDirectoryName(RuntimeDataAccess.GetDirectory()); + string settingsPath = Path.Combine(settingsDir, SETTINGS_FILE_NAME); + try { + string text = File.ReadAllText(settingsPath); + AppSettings fileSettings = AppSettings.Deserialize(text); + AppSettings.Global.MergeSettings(fileSettings); + Debug.WriteLine("Settings file loaded and merged"); + } catch (Exception ex) { + Debug.WriteLine("Unable to read settings file: " + ex.Message); + } + } + + /// + /// Saves AppSettings to a file. + /// + private void SaveAppSettings() { + if (!AppSettings.Global.Dirty) { + Debug.WriteLine("Settings not dirty, not saving"); + return; + } + + // Collect some window and column widths. Don't grab the window size if we're + // maximized or minimized. + if (this.WindowState == FormWindowState.Normal) { + AppSettings.Global.SetInt(AppSettings.MAIN_WINDOW_WIDTH, this.Size.Width); + AppSettings.Global.SetInt(AppSettings.MAIN_WINDOW_HEIGHT, this.Size.Height); + AppSettings.Global.SetInt(AppSettings.MAIN_LEFT_SPLITTER_DIST, + mainSplitterLeft.SplitterDistance); + AppSettings.Global.SetInt(AppSettings.MAIN_RIGHT_SPLITTER_DIST, + mainSplitterRight.SplitterDistance); + AppSettings.Global.SetInt(AppSettings.MAIN_LEFT_SIDE_SPLITTER_DIST, + leftPanelSplitter.SplitterDistance); + AppSettings.Global.SetInt(AppSettings.MAIN_RIGHT_SIDE_SPLITTER_DIST, + rightPanelSplitter.SplitterDistance); + } + SerializeReferencesColumnWidths(); + SerializeNotesColumnWidths(); + SerializeSymbolColumnWidths(); + + string settingsDir = Path.GetDirectoryName(RuntimeDataAccess.GetDirectory()); + string settingsPath = Path.Combine(settingsDir, SETTINGS_FILE_NAME); + try { + string cereal = AppSettings.Global.Serialize(); + File.WriteAllText(settingsPath, cereal); + AppSettings.Global.Dirty = false; + Debug.WriteLine("Saved settings (" + settingsPath + ")"); + } catch (Exception ex) { + Debug.WriteLine("Failed to save settings: " + ex.Message); + } + } + + /// + /// Replaces the contents of the global settings object with the new settings, + /// then applies them to the ProjectView. + /// + /// + public void SetAppSettings(AppSettings settings) { + AppSettings.Global.ReplaceSettings(settings); + ApplyAppSettings(); + + // We get called whenever Apply or OK is hit in the settings editor, so it's + // a pretty good time to save the settings out. + SaveAppSettings(); + } + + /// + /// Applies "actionable" settings to the ProjectView, pulling them out of the global + /// settings object. If a project is open, refreshes the display list and all sub-windows. + /// + private void ApplyAppSettings() { + Debug.WriteLine("ApplyAppSettings..."); + AppSettings settings = AppSettings.Global; + + // Main window size. + this.Size = new Size( + settings.GetInt(AppSettings.MAIN_WINDOW_WIDTH, 1280), + settings.GetInt(AppSettings.MAIN_WINDOW_HEIGHT, 720)); + // Left splitter with is distance from left edge of window. + mainSplitterLeft.SplitterDistance = + settings.GetInt(AppSettings.MAIN_LEFT_SPLITTER_DIST, 250); + // Right splitter posn is distance from right edge of left splitter. + mainSplitterRight.SplitterDistance = + settings.GetInt(AppSettings.MAIN_RIGHT_SPLITTER_DIST, (1280 - 250) - 250); + + leftPanelSplitter.SplitterDistance = + settings.GetInt(AppSettings.MAIN_LEFT_SIDE_SPLITTER_DIST, 350); + rightPanelSplitter.SplitterDistance = + settings.GetInt(AppSettings.MAIN_RIGHT_SIDE_SPLITTER_DIST, 400); + + // Configure column widths. + string widthStr = settings.GetString(AppSettings.CDLV_COL_WIDTHS, null); + if (!string.IsNullOrEmpty(widthStr)) { + CodeListColumnWidths widths = CodeListColumnWidths.Deserialize(widthStr); + if (widths != null) { + SetCodeListHeaderWidths(widths); + } + } + DeserializeReferencesColumnWidths(); + DeserializeNotesColumnWidths(); + DeserializeSymbolColumnWidths(); + + // Set up the formatter. + mFormatterConfig = new Formatter.FormatConfig(); + AsmGen.GenCommon.ConfigureFormatterFromSettings(AppSettings.Global, + ref mFormatterConfig); + mFormatterConfig.mEndOfLineCommentDelimiter = ";"; + mFormatterConfig.mFullLineCommentDelimiterBase = ";"; + mFormatterConfig.mBoxLineCommentDelimiter = string.Empty; + mFormatterConfig.mAllowHighAsciiCharConst = true; + mOutputFormatter = new Formatter(mFormatterConfig); + mOutputFormatterCpuDef = null; + + // Set pseudo-op names. Entries aren't allowed to be blank, so we start with the + // default values and merge in whatever the user has configured. + mPseudoOpNames = PseudoOp.sDefaultPseudoOpNames.GetCopy(); + string pseudoCereal = settings.GetString(AppSettings.FMT_PSEUDO_OP_NAMES, null); + if (!string.IsNullOrEmpty(pseudoCereal)) { + PseudoOp.PseudoOpNames deser = PseudoOp.PseudoOpNames.Deserialize(pseudoCereal); + if (deser != null) { + mPseudoOpNames.Merge(deser); + } + } + + // Configure the Symbols window. + symbolUserCheckBox.Checked = + settings.GetBool(AppSettings.SYMWIN_SHOW_USER, false); + symbolAutoCheckBox.Checked = + settings.GetBool(AppSettings.SYMWIN_SHOW_AUTO, false); + symbolProjectCheckBox.Checked = + settings.GetBool(AppSettings.SYMWIN_SHOW_PROJECT, false); + symbolPlatformCheckBox.Checked = + settings.GetBool(AppSettings.SYMWIN_SHOW_PLATFORM, false); + symbolConstantCheckBox.Checked = + settings.GetBool(AppSettings.SYMWIN_SHOW_CONST, false); + symbolAddressCheckBox.Checked = + settings.GetBool(AppSettings.SYMWIN_SHOW_ADDR, false); + + // Set the code list view font. + string fontStr = settings.GetString(AppSettings.CDLV_FONT, null); + if (!string.IsNullOrEmpty(fontStr)) { + FontConverter cvt = new FontConverter(); + try { + Font font = cvt.ConvertFromInvariantString(fontStr) as Font; + codeListView.Font = font; + Debug.WriteLine("Set font to " + font.ToString()); + } catch (Exception ex) { + Debug.WriteLine("Font convert failed: " + ex.Message); + } + } + + // Unpack the recent-project list. + UnpackRecentProjectList(); + + // Enable the DEBUG menu if configured. + bool showDebugMenu = AppSettings.Global.GetBool(AppSettings.DEBUG_MENU_ENABLED, false); + if (dEBUGToolStripMenuItem.Visible != showDebugMenu) { + dEBUGToolStripMenuItem.Visible = showDebugMenu; + mainMenuStrip.Refresh(); + } + + // Finally, update the display list with all the fancy settings. + if (mDisplayList != null) { + // Regenerate the display list with the latest formatter config and + // pseudo-op definition. (These are set as part of the refresh.) + UndoableChange uc = + UndoableChange.CreateDummyChange(UndoableChange.ReanalysisScope.DisplayOnly); + ApplyChanges(new ChangeSet(uc), false); + } + } + + // Make sure we pick up changes to the window size. We don't catch size-chage events + // for the left/right splitter widths because that should be picked up by the sub-windows. + private void ProjectView_SizeChanged(object sender, EventArgs e) { + AppSettings.Global.Dirty = true; + } + + // This handles the splitters in the side panels, e.g. between References and Notes. + private void SidePanelSplitter_SplitterMoved(object sender, SplitterEventArgs e) { + AppSettings.Global.Dirty = true; + } + + public void ShowProject() { + mProjectControl.Show(); + mNoProjectControl.Hide(); + + codeListView.Focus(); + saveToolStripMenuItem.Enabled = true; + saveToolStripButton.Enabled = true; + saveAsToolStripMenuItem.Enabled = true; + closeToolStripMenuItem.Enabled = true; + assembleToolStripMenuItem.Enabled = true; + printToolStripMenuItem.Enabled = true; + copyToolStripMenuItem.Enabled = true; + copyToolStripButton.Enabled = true; + selectAllToolStripMenuItem.Enabled = true; + findToolStripMenuItem.Enabled = true; + findNextToolStripMenuItem.Enabled = true; + gotoToolStripMenuItem.Enabled = true; + editHeaderCommentToolStripMenuItem.Enabled = true; + projectPropertiesToolStripMenuItem.Enabled = true; + + showUndoRedoHistoryToolStripMenuItem.Enabled = true; + showAnalysisTimersToolStripMenuItem.Enabled = true; + showAnalyzerOutputToolStripMenuItem.Enabled = true; + toggleOwnerDrawToolStripMenuItem.Enabled = true; + reanalyzeToolStripMenuItem.Enabled = true; + toggleCommentRulersToolStripMenuItem.Enabled = true; + extensionScriptInfoToolStripMenuItem.Enabled = true; + + mNavStack.Clear(); + UpdateMenuItemsAndTitle(); + } + + public void ShowNoProject() { + mProjectControl.Hide(); + mNoProjectControl.Show(); + + saveToolStripMenuItem.Enabled = false; + saveToolStripButton.Enabled = false; + saveAsToolStripMenuItem.Enabled = false; + closeToolStripMenuItem.Enabled = false; + assembleToolStripMenuItem.Enabled = false; + printToolStripMenuItem.Enabled = false; + copyToolStripMenuItem.Enabled = false; + copyToolStripButton.Enabled = false; + selectAllToolStripMenuItem.Enabled = false; + findToolStripMenuItem.Enabled = false; + findNextToolStripMenuItem.Enabled = false; + gotoToolStripMenuItem.Enabled = false; + editHeaderCommentToolStripMenuItem.Enabled = false; + projectPropertiesToolStripMenuItem.Enabled = false; + + showUndoRedoHistoryToolStripMenuItem.Enabled = false; + showAnalysisTimersToolStripMenuItem.Enabled = false; + showAnalyzerOutputToolStripMenuItem.Enabled = false; + toggleOwnerDrawToolStripMenuItem.Enabled = false; + reanalyzeToolStripMenuItem.Enabled = false; + toggleCommentRulersToolStripMenuItem.Enabled = false; + extensionScriptInfoToolStripMenuItem.Enabled = false; + + UpdateMenuItemsAndTitle(); + } + + private void UnpackRecentProjectList() { + mRecentProjectPaths.Clear(); + + string cereal = AppSettings.Global.GetString( + AppSettings.PRVW_RECENT_PROJECT_LIST, null); + if (string.IsNullOrEmpty(cereal)) { + return; + } + + try { + JavaScriptSerializer ser = new JavaScriptSerializer(); + mRecentProjectPaths = ser.Deserialize>(cereal); + } catch (Exception ex) { + Debug.WriteLine("Failed deserializing recent projects: " + ex.Message); + return; + } + } + + /// + /// Ensures that the named project is at the top of the list. If it's elsewhere + /// in the list, move it to the top. Excess items are removed. + /// + /// + private void UpdateRecentProjectList(string projectPath) { + if (string.IsNullOrEmpty(projectPath)) { + // This can happen if you create a new project, then close the window + // without having saved it. + return; + } + int index = mRecentProjectPaths.IndexOf(projectPath); + if (index == 0) { + // Already in the list, nothing changes. No need to update anything else. + return; + } + if (index > 0) { + mRecentProjectPaths.RemoveAt(index); + } + mRecentProjectPaths.Insert(0, projectPath); + + // Trim the list to the max allowed. + while (mRecentProjectPaths.Count > MAX_RECENT_PROJECTS) { + Debug.WriteLine("Recent projects: dropping " + + mRecentProjectPaths[MAX_RECENT_PROJECTS]); + mRecentProjectPaths.RemoveAt(MAX_RECENT_PROJECTS); + } + + // Store updated list in app settings. JSON-in-JSON is ugly and inefficient, + // but it'll do for now. + JavaScriptSerializer ser = new JavaScriptSerializer(); + string cereal = ser.Serialize(mRecentProjectPaths); + AppSettings.Global.SetString(AppSettings.PRVW_RECENT_PROJECT_LIST, cereal); + + UpdateRecentLinks(); + } + + /// + /// Updates the links on the no-project control. + /// + private void UpdateRecentLinks() { + if (mRecentProjectPaths.Count >= 1) { + recentProjectLabel1.Visible = true; + recentProjectLabel1.Text = string.Format(Properties.Resources.RECENT_PROJECT_LINK, + 1, Path.GetFileName(mRecentProjectPaths[0])); + } else { + recentProjectLabel1.Visible = false; + } + if (mRecentProjectPaths.Count >= 2) { + recentProjectLabel2.Visible = true; + recentProjectLabel2.Text = string.Format(Properties.Resources.RECENT_PROJECT_LINK, + 2, Path.GetFileName(mRecentProjectPaths[1])); + } else { + recentProjectLabel2.Visible = false; + } + } + + private void recentProjectsToolStripMenuItem_DropDownOpening(object sender, EventArgs e) { + ToolStripItemCollection recents = recentProjectsToolStripMenuItem.DropDownItems; + + recents.Clear(); + if (mRecentProjectPaths.Count == 0) { + recents.Add(noRecentsToolStripMenuItem); + } else { + for (int i = 0; i < mRecentProjectPaths.Count; i++) { + string pathName = mRecentProjectPaths[i]; + string menuName = string.Format("{0}: {1}", i + 1, + /*Path.GetFileName(*/ pathName /*)*/); + recents.Add(new ToolStripMenuItem(menuName, null, (sendr, arg) => { + if (DoClose()) { + DoOpenFile(pathName); + } + })); + } + } + } + + #endregion Init and settings + + + #region Project management + + private bool PrepareNewProject(string dataPathName, Setup.SystemDef sysDef) { + DisasmProject proj = new DisasmProject(); + mDataPathName = dataPathName; + mProjectPathName = string.Empty; + byte[] fileData = null; + try { + fileData = LoadDataFile(dataPathName); + } catch (Exception ex) { + Debug.WriteLine("PrepareNewProject exception: " + ex); + string message = Properties.Resources.OPEN_DATA_FAIL_CAPTION; + string caption = Properties.Resources.OPEN_DATA_FAIL_MESSAGE + ": " + ex.Message; + MessageBox.Show(caption, message, MessageBoxButtons.OK, MessageBoxIcon.Error); + return false; + } + proj.UseMainAppDomainForPlugins = mUseMainAppDomainForPlugins; + proj.Initialize(fileData.Length); + proj.PrepForNew(fileData, Path.GetFileName(dataPathName)); + + proj.LongComments.Add(DisplayList.Line.HEADER_COMMENT_OFFSET, + new MultiLineComment("6502bench SourceGen v" + Program.ProgramVersion)); + + // The system definition provides a set of defaults that can be overridden. + // We pull everything of interest out and then discard the object. + proj.ApplySystemDef(sysDef); + + mProject = proj; + + return true; + } + + private void FinishPrep() { + string messages = mProject.LoadExternalFiles(); + if (messages.Length != 0) { + // ProjectLoadIssues isn't quite the right dialog, but it'll do. + ProjectLoadIssues dlg = new ProjectLoadIssues(); + dlg.CanCancel = false; + dlg.Messages = messages; + dlg.ShowDialog(); + dlg.Dispose(); + } + + mDisplayList = new DisplayList(mProject, mOutputFormatter, mPseudoOpNames); + + // Prep the symbol table subset object. Replace the old one with a new one. + mSymbolSubset = new SymbolTableSubset(mProject.SymbolTable); + + RefreshProject(UndoableChange.ReanalysisScope.CodeAndData); + ShowProject(); + InvalidateControls(null); + + // Want to do this after ShowProject() or we see a weird glitch. + UpdateRecentProjectList(mProjectPathName); + } + + /// + /// Loads the data file, reading it entirely into memory. + /// + /// All errors are reported as exceptions. + /// + /// Full pathname. + /// Data file contents. + private byte[] LoadDataFile(string dataFileName) { + byte[] fileData; + + using (FileStream fs = File.Open(dataFileName, FileMode.Open, FileAccess.Read)) { + // Check length; should have been caught earlier. + if (fs.Length > DisasmProject.MAX_DATA_FILE_SIZE) { + throw new InvalidDataException( + string.Format(Properties.Resources.OPEN_DATA_TOO_LARGE, + fs.Length / 1024, DisasmProject.MAX_DATA_FILE_SIZE / 1024)); + } else if (fs.Length == 0) { + throw new InvalidDataException(Properties.Resources.OPEN_DATA_EMPTY); + } + fileData = new byte[fs.Length]; + int actual = fs.Read(fileData, 0, (int)fs.Length); + if (actual != fs.Length) { + // Not expected -- should be able to read the entire file in one shot. + throw new Exception(Properties.Resources.OPEN_DATA_PARTIAL_READ); + } + } + + return fileData; + } + + /// + /// Applies the changes to the project, adds them to the undo stack, and updates + /// the display. + /// + /// Set of changes to apply. + private void ApplyUndoableChanges(ChangeSet cs) { + if (cs.Count == 0) { + Debug.WriteLine("ApplyUndoableChanges: change set is empty"); + } + ApplyChanges(cs, false); + mProject.PushChangeSet(cs); + UpdateMenuItemsAndTitle(); + + // If the debug dialog is visible, update it. + if (mShowUndoRedoHistoryDialog != null) { + mShowUndoRedoHistoryDialog.BodyText = mProject.DebugGetUndoRedoHistory(); + } + } + + /// + /// Applies the changes to the project, and updates the display. + /// + /// This is called by the undo/redo commands. Don't call this directly from the + /// various UI-driven functions, as this does not add the change to the undo stack. + /// + /// Set of changes to apply. + /// If set, undo the changes instead. + private void ApplyChanges(ChangeSet cs, bool backward) { + mReanalysisTimer.Clear(); + mReanalysisTimer.StartTask("ProjectView.ApplyChanges()"); + + mReanalysisTimer.StartTask("Save selection"); + int topItem = codeListView.TopItem.Index; + int topOffset = mDisplayList[topItem].FileOffset; + DisplayList.SavedSelection savedSel = DisplayList.SavedSelection.Generate( + mDisplayList, mCodeViewSelection, topOffset); + //savedSel.DebugDump(); + mReanalysisTimer.EndTask("Save selection"); + + mReanalysisTimer.StartTask("Apply changes"); + UndoableChange.ReanalysisScope needReanalysis = mProject.ApplyChanges(cs, backward, + out RangeSet affectedOffsets); + mReanalysisTimer.EndTask("Apply changes"); + + string refreshTaskStr = "Refresh w/reanalysis=" + needReanalysis; + mReanalysisTimer.StartTask(refreshTaskStr); + if (needReanalysis != UndoableChange.ReanalysisScope.None) { + Debug.WriteLine("Refreshing project (" + needReanalysis + ")"); + RefreshProject(needReanalysis); + } else { + Debug.WriteLine("Refreshing " + affectedOffsets.Count + " offsets"); + RefreshCodeListViewEntries(affectedOffsets); + mProject.Validate(); // shouldn't matter w/o reanalysis, but do it anyway + } + mReanalysisTimer.EndTask(refreshTaskStr); + + VirtualListViewSelection newSel = savedSel.Restore(mDisplayList, out int topIndex); + //newSel.DebugDump(); + + // Refresh the various windows, and restore the selection. + mReanalysisTimer.StartTask("Invalidate controls"); + InvalidateControls(newSel); + mReanalysisTimer.EndTask("Invalidate controls"); + + // This apparently has to be done after the EndUpdate, and inside try/catch. + // See https://stackoverflow.com/questions/626315/ for notes. + try { + Debug.WriteLine("Setting TopItem to index=" + topIndex); + codeListView.TopItem = codeListView.Items[topIndex]; + } catch (NullReferenceException) { + Debug.WriteLine("Caught an NRE from TopItem"); + } + + mReanalysisTimer.EndTask("ProjectView.ApplyChanges()"); + + //mReanalysisTimer.DumpTimes("ProjectView timers:", mGenerationLog); + if (mShowAnalysisTimersDialog != null) { + string timerStr = mReanalysisTimer.DumpToString("ProjectView timers:"); + mShowAnalysisTimersDialog.BodyText = timerStr; + } + } + + /// + /// Refreshes the project after something of substance has changed. Some + /// re-analysis will be done, followed by a complete rebuild of the DisplayList. + /// + /// Indicates whether reanalysis is required, and + /// what level. + private void RefreshProject(UndoableChange.ReanalysisScope reanalysisRequired) { + Debug.Assert(reanalysisRequired != UndoableChange.ReanalysisScope.None); + + // NOTE: my goal is to arrange things so that reanalysis (data-only, and ideally + // code+data) takes less than 100ms. With that response time there's no need for + // background processing and progress bars. Since we need to do data-only + // reanalysis after many common operations, the program becomes unpleasant to + // use if we miss this goal, and progress bars won't make it less so. + + // Changing the CPU type or whether undocumented instructions are supported + // invalidates the Formatter's mnemonic cache. We can change these values + // through undo/redo, so we need to check it here. + if (mOutputFormatterCpuDef != mProject.CpuDef) { // reference equality is fine + Debug.WriteLine("CpuDef has changed, resetting formatter (now " + + mProject.CpuDef + ")"); + mOutputFormatter = new Formatter(mFormatterConfig); + mDisplayList.SetFormatter(mOutputFormatter); + mDisplayList.SetPseudoOpNames(mPseudoOpNames); + mOutputFormatterCpuDef = mProject.CpuDef; + } + + if (mDisplayList.Count > 200000) { + string prevStatus = toolStripStatusLabel.Text; + + // The Windows stuff can take 50-100ms, potentially longer than the actual + // work, so don't bother unless the file is very large. + try { + mReanalysisTimer.StartTask("Do Windows stuff"); + Application.UseWaitCursor = true; + Cursor.Current = Cursors.WaitCursor; + toolStripStatusLabel.Text = Properties.Resources.STATUS_RECALCULATING; + Refresh(); // redraw status label + mReanalysisTimer.EndTask("Do Windows stuff"); + + DoRefreshProject(reanalysisRequired); + } finally { + Application.UseWaitCursor = false; + toolStripStatusLabel.Text = prevStatus; + } + } else { + DoRefreshProject(reanalysisRequired); + } + + if (FormatDescriptor.DebugCreateCount != 0) { + Debug.WriteLine("FormatDescriptor total=" + FormatDescriptor.DebugCreateCount + + " prefab=" + FormatDescriptor.DebugPrefabCount + " (" + + (FormatDescriptor.DebugPrefabCount * 100) / FormatDescriptor.DebugCreateCount + + "%)"); + } + } + + /// + /// Updates all of the specified ListView entries. This is called after minor changes, + /// such as editing a comment or renaming a label, that can be handled by regenerating + /// selected parts of the DisplayList. + /// + /// + private void RefreshCodeListViewEntries(RangeSet offsetSet) { + IEnumerator iter = offsetSet.RangeListIterator; + while (iter.MoveNext()) { + RangeSet.Range range = iter.Current; + mDisplayList.GenerateRange(range.Low, range.High); + } + } + + private void DoRefreshProject(UndoableChange.ReanalysisScope reanalysisRequired) { + if (reanalysisRequired != UndoableChange.ReanalysisScope.DisplayOnly) { + mGenerationLog = new CommonUtil.DebugLog(); + mGenerationLog.SetMinPriority(CommonUtil.DebugLog.Priority.Debug); + mGenerationLog.SetShowRelTime(true); + + mReanalysisTimer.StartTask("Call DisasmProject.Analyze()"); + mProject.Analyze(reanalysisRequired, mGenerationLog, mReanalysisTimer); + mReanalysisTimer.EndTask("Call DisasmProject.Analyze()"); + } + + if (mGenerationLog != null) { + //mReanalysisTimer.StartTask("Save _log"); + //mGenerationLog.WriteToFile(@"C:\Src\WorkBench\SourceGen\TestData\_log.txt"); + //mReanalysisTimer.EndTask("Save _log"); + + if (mShowAnalyzerOutputDialog != null) { + mShowAnalyzerOutputDialog.BodyText = mGenerationLog.WriteToString(); + } + } + + mReanalysisTimer.StartTask("Generate DisplayList"); + mDisplayList.GenerateAll(); + mReanalysisTimer.EndTask("Generate DisplayList"); + } + + #endregion Project management + + + #region Main window UI event handlers + + /// + /// Invalidates and forces an update on our various windows. + /// + private void InvalidateControls(VirtualListViewSelection newSel) { + codeListView.BeginUpdate(); + ClearCodeListViewCache(); + if (mDisplayList != null) { + codeListView.VirtualListSize = mDisplayList.Count; + } + + mReanalysisTimer.StartTask("Restore selection"); + mRestoringSelection = true; + if (newSel != null) { + RestoreSelection(newSel); // want to do this between Begin/EndUpdate + } else if (mDisplayList != null) { + // No selection to restore. This should only happen for the initial + // render, when nothing is yet selected. + // + // Set the length on the code view. + mCodeViewSelection.SetLength(mDisplayList.Count); + } else { + // Just closed the project, nothing to do at all. + } + + mRestoringSelection = false; + mReanalysisTimer.EndTask("Restore selection"); + + UpdateActionMenu(); + codeListView.EndUpdate(); + + InvalidateSymbolListView(); + InvalidateNotesListView(); + UpdateReferenceView(); + UpdateInfoView(); + } + + /// + /// Updates menu item enable status; necessary because that determines whether the + /// associated keyboard shortcuts are active. + /// + /// Updates the main form title to show project name and modification status. + /// + /// This does not handle items that only toggle enabledness when a project is opened + /// or closed. + /// + private void UpdateMenuItemsAndTitle() { + undoToolStripMenuItem.Enabled = (mProject != null && mProject.CanUndo); + redoToolStripMenuItem.Enabled = (mProject != null && mProject.CanRedo); + + navigateBackToolStripButton.Enabled = (mProject != null && mNavStack.HasBackward); + navigateFwdToolStripButton.Enabled = (mProject != null && mNavStack.HasForward); + + // Update main window title. + StringBuilder sb = new StringBuilder(); + sb.Append(Properties.Resources.TITLE_BASE); + if (mProject != null) { + sb.Append(" - "); + if (string.IsNullOrEmpty(mProjectPathName)) { + sb.Append(Properties.Resources.TITLE_NEW_PROJECT); + } else { + sb.Append(Path.GetFileName(mProjectPathName)); + } + + if (mProject.IsDirty) { + sb.Append(" "); + sb.Append(Properties.Resources.TITLE_MODIFIED); + } + } + Text = sb.ToString(); + } + + /// + /// Restores the ListView selection by applying a diff between the old and + /// new selection bitmaps. + /// + /// The virtual list view doesn't change the selection when we rebuild the + /// list. It would be expensive to set all the bits, so we just update the + /// entries that changed. + /// + /// Before returning, mCodeViewSelection is replaced with curSel. + /// + /// Selection bits for the current display list. + private void RestoreSelection(VirtualListViewSelection curSel) { + Debug.Assert(curSel != null); + + // We have to replace mCodeViewSelection immediately, because changing + // the selection will cause ItemSelectionChanged events to fire, invoking + // callbacks that expect the new selection object. Things will explode if + // the older list was shorter. + VirtualListViewSelection prevSel = mCodeViewSelection; + mCodeViewSelection = curSel; + + // Set everything that has changed between the two sets. + int debugNumChanged = 0; + int count = Math.Min(prevSel.Length, curSel.Length); + int i; + for (i = 0; i < count; i++) { + if (prevSel[i] != curSel[i]) { + codeListView.Items[i].Selected = curSel[i]; + debugNumChanged++; + } + } + // Set everything that wasn't there before. New entries default to unselected, + // so we only need to do this if the new value is "true". + for (; i < curSel.Length; i++) { + // An ItemSelectionChanged event will fire that will cause curSel[i] to + // be assigned. This is fine. + if (curSel[i]) { + codeListView.Items[i].Selected = curSel[i]; + debugNumChanged++; + } + } + + Debug.WriteLine("RestoreSelection: changed " + debugNumChanged + + " of " + curSel.Length + " lines"); + } + + // This gets the key events for all controls associated with the main form, + // regardless of which has focus, making it useful for keyboard shortcuts. + // Return true to indicate that we've handled the key. + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { + // Ctrl-Shift-Z is an alias for Redo (Ctrl-Y). + if (keyData == (Keys.Control | Keys.Shift | Keys.Z)) { + if (redoToolStripMenuItem.Enabled) { + redoToolStripMenuItem_Click(null, null); + } + return true; + } + + // Navigation keys. Alt+left/right is intuitive key binding used by Eclipse, + // Ctrl+[shift]+minus is weird Visual Studio binding. + if (keyData == (Keys.Alt | Keys.Left) || + keyData == (Keys.Control | Keys.OemMinus)) { + if (navigateBackToolStripButton.Enabled) { + navigateBackToolStripButton_Click(null, null); + } + return true; + } + if (keyData == (Keys.Alt | Keys.Right) || + keyData == (Keys.Control | Keys.Shift | Keys.OemMinus)) { + if (navigateFwdToolStripButton.Enabled) { + navigateFwdToolStripButton_Click(null, null); + } + return true; + } + + return base.ProcessCmdKey(ref msg, keyData); + } + + private void navigateBackToolStripButton_Click(object sender, EventArgs e) { + if (!mNavStack.HasBackward) { + // toolbar button should have been disabled + return; + } + int backOff = mNavStack.Pop(); + UpdateMenuItemsAndTitle(); + Debug.WriteLine("Nav back: +" + backOff.ToString("x6")); + GoToOffset(backOff, false, false); + } + + private void navigateFwdToolStripButton_Click(object sender, EventArgs e) { + if (!mNavStack.HasForward) { + // toolbar button should have been disabled + return; + } + int fwdOff = mNavStack.PushPrevious(); + UpdateMenuItemsAndTitle(); + Debug.WriteLine("Nav fwd: +" + fwdOff.ToString("x6")); + GoToOffset(fwdOff, false, false); + } + + /// + /// Determines whether any part of the specified offset is currently visible in the + /// code list view. + /// + private bool IsOffsetVisible(int offset) { + int firstLineIndex = mDisplayList.FindLineIndexByOffset(offset); + int lastLineIndex = firstLineIndex + 1; + while (lastLineIndex < mDisplayList.Count && + mDisplayList[lastLineIndex].FileOffset == offset) { + lastLineIndex++; + } + lastLineIndex--; + //Debug.WriteLine("Check vis: first=" + firstLineIndex + " last=" + lastLineIndex); + return codeListView.IsItemVisible(codeListView.Items[firstLineIndex]) || + codeListView.IsItemVisible(codeListView.Items[lastLineIndex]); + } + + // File > New (Ctrl+N) + private void newToolStripMenuItem_Click(object sender, EventArgs e) { + DoNew(); + } + private void newToolStripButton_Click(object sender, EventArgs e) { + newToolStripMenuItem_Click(sender, e); + } + + private void newFileLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { + DoNew(); + } + + private void DoNew() { + if (!DoClose()) { + return; + } + + Setup.NewProject dlg = new Setup.NewProject(); + dlg.ShowDialog(); + if (dlg.DialogResult == DialogResult.OK) { + bool ok = PrepareNewProject(Path.GetFullPath(dlg.DataFileName), dlg.SystemDef); + if (ok) { + FinishPrep(); + } + } + + dlg.Dispose(); + } + + // File > Open (Ctrl+O) + private void openToolStripMenuItem_Click(object sender, EventArgs e) { + DoOpen(); + } + private void openToolStripButton_Click(object sender, EventArgs e) { + openToolStripMenuItem_Click(sender, e); + } + + private void recentProjectLabel1_LinkClicked(object sender, + LinkLabelLinkClickedEventArgs e) { + Debug.Assert(mRecentProjectPaths.Count > 0); + if (DoClose()) { + DoOpenFile(mRecentProjectPaths[0]); + } + } + + private void recentProjectLabel2_LinkClicked(object sender, + LinkLabelLinkClickedEventArgs e) { + Debug.Assert(mRecentProjectPaths.Count > 1); + if (DoClose()) { + DoOpenFile(mRecentProjectPaths[1]); + } + } + + private void openExistingLabel_LinkClicked(object sender, + LinkLabelLinkClickedEventArgs e) { + DoOpen(); + } + + /// + /// Handles opening an existing project by letting the user select the project file. + /// + private void DoOpen() { + if (!DoClose()) { + return; + } + + OpenFileDialog fileDlg = new OpenFileDialog(); + + fileDlg.Filter = ProjectFile.FILENAME_FILTER + "|" + + Properties.Resources.FILE_FILTER_ALL; + fileDlg.FilterIndex = 1; + if (fileDlg.ShowDialog() != DialogResult.OK) { + return; + } + + string projPathName = Path.GetFullPath(fileDlg.FileName); + DoOpenFile(projPathName); + } + + /// + /// Handles opening an existing project, given a pathname to the project file. + /// + private void DoOpenFile(string projPathName) { + Debug.WriteLine("DoOpenFile: " + projPathName); + Debug.Assert(mProject == null); + + if (!File.Exists(projPathName)) { + string msg = string.Format(Properties.Resources.ERR_FILE_NOT_FOUND, projPathName); + MessageBox.Show(msg, Properties.Resources.ERR_FILE_GENERIC_CAPTION, + MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + DisasmProject newProject = new DisasmProject(); + newProject.UseMainAppDomainForPlugins = mUseMainAppDomainForPlugins; + + // Deserialize the project file. I want to do this before loading the data file + // in case we decide to store the data file name in the project (e.g. the data + // file is a disk image or zip archive, and we need to know which part(s) to + // extract). + if (!ProjectFile.DeserializeFromFile(projPathName, newProject, + out FileLoadReport report)) { + // Should probably use a less-busy dialog for something simple like + // "permission denied", but the open file dialog handles most simple + // stuff directly. + ProjectLoadIssues dlg = new ProjectLoadIssues(); + dlg.Messages = report.Format(); + dlg.CanContinue = false; + dlg.ShowDialog(); + // ignore dlg.DialogResult + dlg.Dispose(); + return; + } + + // Now open the data file, generating the pathname by stripping off the ".dis65" + // extension. If we can't find the file, show a message box and offer the option to + // locate it manually, repeating the process until successful or canceled. + const string UNKNOWN_FILE = "UNKNOWN"; + string dataPathName; + if (projPathName.Length <= ProjectFile.FILENAME_EXT.Length) { + dataPathName = UNKNOWN_FILE; + } else { + dataPathName = projPathName.Substring(0, + projPathName.Length - ProjectFile.FILENAME_EXT.Length); + } + byte[] fileData; + while ((fileData = FindValidDataFile(ref dataPathName, newProject, + out bool cancel)) == null) { + if (cancel) { + // give up + Debug.WriteLine("Abandoning attempt to open project"); + return; + } + } + + // If there were warnings, notify the user and give the a chance to cancel. + if (report.Count != 0) { + ProjectLoadIssues dlg = new ProjectLoadIssues(); + dlg.Messages = report.Format(); + dlg.ShowDialog(); + DialogResult result = dlg.DialogResult; + dlg.Dispose(); + + if (result != DialogResult.OK) { + return; + } + } + + mProject = newProject; + mProjectPathName = mProject.ProjectPathName = projPathName; + mProject.SetFileData(fileData, Path.GetFileName(dataPathName)); + FinishPrep(); + } + + /// + /// Finds and loads the specified data file. The file's length and CRC must match + /// the project's expectations. + /// + /// Full path to file. + /// Project object. + /// Returns true if we want to cancel the attempt. + /// + private byte[] FindValidDataFile(ref string dataPathName, DisasmProject proj, + out bool cancel) { + FileInfo fi = new FileInfo(dataPathName); + if (!fi.Exists) { + Debug.WriteLine("File '" + dataPathName + "' doesn't exist"); + dataPathName = ChooseDataFile(dataPathName, + string.Format(Properties.Resources.OPEN_DATA_DOESNT_EXIST, dataPathName)); + cancel = (dataPathName == null); + return null; + } + if (fi.Length != proj.FileDataLength) { + Debug.WriteLine("File '" + dataPathName + "' has length=" + fi.Length + + ", expected " + proj.FileDataLength); + dataPathName = ChooseDataFile(dataPathName, + string.Format(Properties.Resources.OPEN_DATA_WRONG_LENGTH, + fi.Length, proj.FileDataLength)); + cancel = (dataPathName == null); + return null; + } + byte[] fileData = null; + try { + fileData = LoadDataFile(dataPathName); + } catch (Exception ex) { + Debug.WriteLine("File '" + dataPathName + "' failed to load: " + ex.Message); + dataPathName = ChooseDataFile(dataPathName, + string.Format(Properties.Resources.OPEN_DATA_LOAD_FAILED, ex.Message)); + cancel = (dataPathName == null); + return null; + } + uint crc = CRC32.OnWholeBuffer(0, fileData); + if (crc != proj.FileDataCrc32) { + Debug.WriteLine("File '" + dataPathName + "' has CRC32=" + crc + + ", expected " + proj.FileDataCrc32); + // Format the CRC as signed decimal, so that interested parties can + // easily replace the value in the .dis65 file. + dataPathName = ChooseDataFile(dataPathName, + string.Format(Properties.Resources.OPEN_DATA_WRONG_CRC, + (int) crc, (int) proj.FileDataCrc32)); + cancel = (dataPathName == null); + return null; + } + + cancel = false; + return fileData; + } + + /// + /// Displays a "do you want to pick a different file" message, then (on OK) allows the + /// user to select a file. + /// + /// Pathname of original file. + /// Message to display in the message box. + /// Full path of file to open. + private string ChooseDataFile(string origPath, string errorMsg) { + DataFileLoadIssue dlg = new DataFileLoadIssue(); + dlg.PathName = origPath; + dlg.Message = errorMsg; + dlg.ShowDialog(); + DialogResult result = dlg.DialogResult; + dlg.Dispose(); + if (result != DialogResult.OK) { + return null; + } + + OpenFileDialog fileDlg = new OpenFileDialog(); + fileDlg.FileName = Path.GetFileName(origPath); + fileDlg.Filter = Properties.Resources.FILE_FILTER_ALL; + if (fileDlg.ShowDialog() != DialogResult.OK) { + return null; + } + + string newPath = Path.GetFullPath(fileDlg.FileName); + Debug.WriteLine("User selected data file " + newPath); + return newPath; + } + + // File > Save (Ctrl+S) + private void saveToolStripMenuItem_Click(object sender, EventArgs e) { + if (string.IsNullOrEmpty(mProjectPathName)) { + saveAsToolStripMenuItem_Click(sender, e); + return; + } + + DoSave(mProjectPathName); + } + private void saveToolStripButton_Click(object sender, EventArgs e) { + saveToolStripMenuItem_Click(sender, e); + } + + // File > Save As... + private void saveAsToolStripMenuItem_Click(object sender, EventArgs e) { + SaveFileDialog fileDlg = new SaveFileDialog(); + + fileDlg.Filter = ProjectFile.FILENAME_FILTER + "|" + + Properties.Resources.FILE_FILTER_ALL; + fileDlg.FilterIndex = 1; + fileDlg.ValidateNames = true; + fileDlg.AddExtension = true; + fileDlg.FileName = Path.GetFileName(mDataPathName) + ProjectFile.FILENAME_EXT; + if (fileDlg.ShowDialog() == DialogResult.OK) { + string pathName = Path.GetFullPath(fileDlg.FileName); + Debug.WriteLine("Project save path: " + pathName); + if (DoSave(pathName)) { + // Success, record the path name. + mProjectPathName = mProject.ProjectPathName = pathName; + + // add it to the title bar + UpdateMenuItemsAndTitle(); + } + } + } + + private bool DoSave(string pathName) { + Debug.WriteLine("SAVING " + pathName); + if (!ProjectFile.SerializeToFile(mProject, pathName, out string errorMessage)) { + MessageBox.Show(Properties.Resources.ERR_PROJECT_SAVE_FAIL + ": " + errorMessage, + Properties.Resources.OPERATION_FAILED, + MessageBoxButtons.OK, MessageBoxIcon.Error); + return false; + } + + mProject.ResetDirtyFlag(); + // If the debug dialog is visible, update it. + if (mShowUndoRedoHistoryDialog != null) { + mShowUndoRedoHistoryDialog.BodyText = mProject.DebugGetUndoRedoHistory(); + } + UpdateMenuItemsAndTitle(); + + // Update this, in case this was a new project. + UpdateRecentProjectList(pathName); + + // Seems like a good time to save this off too. + SaveAppSettings(); + + return true; + } + + // App is closing. + private void ProjectView_FormClosing(object sender, FormClosingEventArgs e) { + Debug.WriteLine("Main app form closing (reason=" + e.CloseReason + ")"); + if (!DoClose()) { + e.Cancel = true; + return; + } + SaveAppSettings(); + } + + // File > Close + private void closeToolStripMenuItem_Click(object sender, EventArgs e) { + if (!DoClose()) { + Debug.WriteLine("Close canceled"); + } + } + + /// + /// Closes the project and associated modeless dialogs. Unsaved changes will be + /// lost, so if the project has outstanding changes the user will be given the + /// opportunity to cancel. + /// + /// True if the project was closed, false if the user chose to cancel. + private bool DoClose() { + Debug.WriteLine("ProjectView.DoClose() - dirty=" + + (mProject == null ? "N/A" : mProject.IsDirty.ToString())); + if (mProject != null && mProject.IsDirty) { + DialogResult result = MessageBox.Show(Properties.Resources.UNSAVED_CHANGES, + Properties.Resources.UNSAVED_CHANGES_CAPTION, MessageBoxButtons.OKCancel, + MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2); + if (result == DialogResult.Cancel) { + return false; + } + } + + // Close modeless dialogs that depend on project. + if (mShowUndoRedoHistoryDialog != null) { + mShowUndoRedoHistoryDialog.Close(); + } + if (mShowAnalysisTimersDialog != null) { + mShowAnalysisTimersDialog.Close(); + } + if (mShowAnalyzerOutputDialog != null) { + mShowAnalyzerOutputDialog.Close(); + } + if (mHexDumpDialog != null) { + mHexDumpDialog.Close(); + } + + // Discard all project state. + if (mProject != null) { + mProject.Cleanup(); + mProject = null; + } + mDataPathName = null; + mProjectPathName = null; + mSymbolSubset = new SymbolTableSubset(new SymbolTable()); + mCodeViewSelection = new VirtualListViewSelection(); + mDisplayList = null; + codeListView.VirtualListSize = 0; + codeListView.Items.Clear(); + ShowNoProject(); + InvalidateControls(null); + + mGenerationLog = null; + + // Not necessary, but it lets us check the memory monitor to see if we got + // rid of everything. + GC.Collect(); + + return true; + } + + // File > Assemble... + private void assembleToolStripMenuItem_Click(object sender, EventArgs e) { + if (string.IsNullOrEmpty(mProjectPathName)) { + // We need a project pathname so we know where to write the assembler + // source files, and what to call the output files. We could just pop up the + // Save As dialog, but that seems confusing unless we do a custom dialog with + // an explanation, or have some annoying click-through. + // + // This only appears for never-saved projects, not projects with unsaved data. + MessageBox.Show(Properties.Resources.SAVE_BEFORE_ASM_TEXT, + Properties.Resources.SAVE_BEFORE_ASM_CAPTION, + MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + + AsmGen.GenAndAsm dlg = new AsmGen.GenAndAsm(this, mProject, mProjectPathName); + dlg.ShowDialog(); + } + + // File > Exit + private void exitToolStripMenuItem_Click(object sender, EventArgs e) { + // Unsaved-data check happens in form closing event. + Application.Exit(); + } + + // Edit > Undo, Ctrl+Z (may be called with null/null) + private void undoToolStripMenuItem_Click(object sender, EventArgs e) { + if (!mProject.CanUndo) { + Debug.WriteLine("Nothing to undo"); + return; + } + ChangeSet cs = mProject.PopUndoSet(); + ApplyChanges(cs, true); + UpdateMenuItemsAndTitle(); + + // If the debug dialog is visible, update it. + if (mShowUndoRedoHistoryDialog != null) { + mShowUndoRedoHistoryDialog.BodyText = mProject.DebugGetUndoRedoHistory(); + } + } + + // Edit > Redo, Ctrl+Y (may be called with null/null) + private void redoToolStripMenuItem_Click(object sender, EventArgs e) { + if (!mProject.CanRedo) { + Debug.WriteLine("Nothing to redo"); + return; + } + ChangeSet cs = mProject.PopRedoSet(); + ApplyChanges(cs, false); + UpdateMenuItemsAndTitle(); + + // If the debug dialog is visible, update it. + if (mShowUndoRedoHistoryDialog != null) { + mShowUndoRedoHistoryDialog.BodyText = mProject.DebugGetUndoRedoHistory(); + } + } + + // Edit > Select All (Ctrl+A) + private void selectAllToolStripMenuItem_Click(object sender, EventArgs e) { + codeListView.SelectAll(); +#if false + try { + Application.UseWaitCursor = true; + Cursor.Current = Cursors.WaitCursor; + codeListView.BeginUpdate(); + int max = codeListView.VirtualListSize; + for (int i = 0; i < max; i++) { + //codeListView.Items[i].Selected = true; + codeListView.SelectedIndices.Add(i); + if ((i % 50000) == 0) { + toolStripStatusLabel.Text = string.Format( + Properties.Resources.STATUS_SELECTING, (i * 100) / max); + //Application.DoEvents(); // <-- this is unwise + Refresh(); // <-- updates status line but not mouse + } + } + } finally { + codeListView.EndUpdate(); + Application.UseWaitCursor = false; + toolStripStatusLabel.Text = Properties.Resources.STATUS_READY; + } +#endif + } + + // Edit > Copy (Ctrl+C) + private void copyToolStripMenuItem_Click(object sender, EventArgs e) { + const int AssemblerSource = 0; + const int Disassembly = 1; + const bool addCsv = true; + + int format = AppSettings.Global.GetInt(AppSettings.CLIP_LINE_FORMAT, AssemblerSource); + StringBuilder fullText = new StringBuilder(codeListView.SelectedIndices.Count * 50); + StringBuilder csv = new StringBuilder(codeListView.SelectedIndices.Count * 40); + StringBuilder sb = new StringBuilder(100); + + int addrAdj = mProject.CpuDef.HasAddr16 ? 6 : 9; + int disAdj = (format != Disassembly) ? 0 : addrAdj + 10; + + // Walking through the selected indices can be slow for a large file, so we + // run through the full list and pick out the selected items with our parallel + // structure. (I'm assuming that "select all" will be a common precursor.) + for (int i = 0; i < mDisplayList.Count; i++) { + if (!mCodeViewSelection[i]) { + continue; + } + DisplayList.Line line = mDisplayList[i]; + DisplayList.FormattedParts parts = mDisplayList.GetFormattedParts(i); + switch (line.LineType) { + case DisplayList.Line.Type.Code: + case DisplayList.Line.Type.Data: + case DisplayList.Line.Type.EquDirective: + case DisplayList.Line.Type.RegWidthDirective: + case DisplayList.Line.Type.OrgDirective: + if (format == Disassembly) { + if (!string.IsNullOrEmpty(parts.Addr)) { + sb.Append(parts.Addr); + sb.Append(": "); + } + + // shorten the "..." + string bytesStr = parts.Bytes; + if (bytesStr != null && bytesStr.Length > 8) { + bytesStr = bytesStr.Substring(0, 8) + "+"; + } + TextUtil.AppendPaddedString(sb, bytesStr, disAdj); + } + TextUtil.AppendPaddedString(sb, parts.Label, disAdj + 9); + TextUtil.AppendPaddedString(sb, parts.Opcode, disAdj + 9 + 8); + TextUtil.AppendPaddedString(sb, parts.Operand, disAdj + 9 + 8 + 11); + if (string.IsNullOrEmpty(parts.Comment)) { + // Trim trailing spaces off opcode or operand. + TextUtil.TrimEnd(sb); + } else { + sb.Append(parts.Comment); + } + sb.Append("\r\n"); + break; + case DisplayList.Line.Type.LongComment: + if (format == Disassembly) { + TextUtil.AppendPaddedString(sb, string.Empty, disAdj); + } + sb.Append(parts.Comment); + sb.Append("\r\n"); + break; + case DisplayList.Line.Type.Note: + // don't include notes + break; + case DisplayList.Line.Type.Blank: + sb.Append("\r\n"); + break; + default: + Debug.Assert(false); + break; + } + fullText.Append(sb); + + if (addCsv) { + csv.Append(TextUtil.EscapeCSV(parts.Offset)); csv.Append(','); + csv.Append(TextUtil.EscapeCSV(parts.Addr)); csv.Append(','); + csv.Append(TextUtil.EscapeCSV(parts.Bytes)); csv.Append(','); + csv.Append(TextUtil.EscapeCSV(parts.Flags)); csv.Append(','); + csv.Append(TextUtil.EscapeCSV(parts.Attr)); csv.Append(','); + csv.Append(TextUtil.EscapeCSV(parts.Label)); csv.Append(','); + csv.Append(TextUtil.EscapeCSV(parts.Opcode)); csv.Append(','); + csv.Append(TextUtil.EscapeCSV(parts.Operand)); csv.Append(','); + csv.Append(TextUtil.EscapeCSV(parts.Comment)); + csv.Append("\r\n"); + } + + sb.Clear(); + } + + // We want to have both plain text and CSV data on the clipboard. To add both + // formats we need to stream it to a DataObject. Complicating matters is Excel's + // entirely reasonable desire to have data in UTF-8 rather than UTF-16. + // + // (I'm not sure pasting assembly bits into Excel is actually useful, so this + // should probably be optional.) + // + // https://stackoverflow.com/a/369219/294248 + DataObject dataObject = new DataObject(); + dataObject.SetText(fullText.ToString()); + if (addCsv) { + byte[] csvData = Encoding.UTF8.GetBytes(csv.ToString()); + MemoryStream stream = new MemoryStream(csvData); + dataObject.SetData(DataFormats.CommaSeparatedValue, stream); + } + Clipboard.SetDataObject(dataObject, true); + } + + // Edit > Find... (Ctrl+F) + private void findToolStripMenuItem_Click(object sender, EventArgs e) { + FindBox dlg = new FindBox(); + dlg.TextToFind = mFindString; + if (dlg.ShowDialog() == DialogResult.OK) { + mFindString = dlg.TextToFind; + mFindStartIndex = -1; + FindText(); + } + dlg.Dispose(); + } + + // Edit > Find Next (F3) + private void findNextToolStripMenuItem_Click(object sender, EventArgs e) { + FindText(); + } + + private void FindText() { + if (string.IsNullOrEmpty(mFindString)) { + return; + } + + int index; + if (codeListView.SelectedIndices.Count > 0) { + index = codeListView.SelectedIndices[0]; + } else { + index = 0; + } + + // Start one past the currently-selected item. + index++; + if (index == mDisplayList.Count) { + index = 0; + } + //Debug.WriteLine("FindText index=" + index + " start=" + mFindStartIndex + + // " str=" + mFindString); + while (index != mFindStartIndex) { + if (mFindStartIndex < 0) { + // need to latch this inside the loop so the initial test doesn't fail + mFindStartIndex = index; + } + + string searchStr = mDisplayList.GetSearchString(index); + int matchPos = searchStr.IndexOf(mFindString, + StringComparison.InvariantCultureIgnoreCase); + if (matchPos >= 0) { + //Debug.WriteLine("Match " + index + ": " + searchStr); + codeListView.EnsureVisible(index); + codeListView.DeselectAll(); + codeListView.SelectedIndices.Add(index); + return; + } + + index++; + if (index == mDisplayList.Count) { + index = 0; + } + } + + // Announce that we've wrapped around, then clear the start index. + MessageBox.Show(Properties.Resources.FIND_REACHED_START, + Properties.Resources.FIND_REACHED_START_CAPTION, MessageBoxButtons.OK, + MessageBoxIcon.Information); + mFindStartIndex = -1; + } + + // Edit > Go To... + private void gotoToolStripMenuItem_Click(object sender, EventArgs e) { + GotoBox dlg = new GotoBox(mProject, mOutputFormatter); + if (dlg.ShowDialog() == DialogResult.OK) { + GoToOffset(dlg.TargetOffset, false, true); + } + dlg.Dispose(); + } + + // Edit > Project Properties... + private void projectPropertiesToolStripMenuItem_Click(object sender, EventArgs e) { + string projectDir = string.Empty; + if (!string.IsNullOrEmpty(mProjectPathName)) { + projectDir = Path.GetDirectoryName(mProjectPathName); + } + EditProjectProperties dlg = new EditProjectProperties(projectDir); + dlg.SetInitialProps(mProject.ProjectProps); + dlg.NumFormatter = mOutputFormatter; + dlg.ShowDialog(); + ProjectProperties newProps = dlg.NewProps; + dlg.Dispose(); + + if (newProps != null) { + UndoableChange uc = UndoableChange.CreateProjectPropertiesChange( + mProject.ProjectProps, newProps); + ApplyUndoableChanges(new ChangeSet(uc)); + } + } + + // Edit > Settings... + private void settingsToolStripMenuItem_Click(object sender, EventArgs e) { + ShowAppSettings(EditAppSettings.Tab.Unknown); + } + + /// + /// Opens the app settings dialog. + /// + /// Tab to present to the user. + public void ShowAppSettings(EditAppSettings.Tab initialTab) { + EditAppSettings dlg = new EditAppSettings(this, initialTab); + dlg.ShowDialog(); + dlg.Dispose(); + } + + // Help > View Help... + private void viewHelpToolStripMenuItem_Click(object sender, EventArgs e) { + HelpAccess.ShowHelp(HelpAccess.Topic.Contents); + } + private void helpToolStripButton_Click(object sender, EventArgs e) { + viewHelpToolStripMenuItem_Click(sender, e); + } + + // Help > About... + private void aboutToolStripMenuItem_Click(object sender, EventArgs e) { + AboutBox dlg = new AboutBox(); + dlg.ShowDialog(); + dlg.Dispose(); + } + + private void codeListView_MouseClick(object sender, MouseEventArgs e) { + //if (e.Button == MouseButtons.Left) { + // Debug.WriteLine("LEFT CLICK"); + //} else if (e.Button == MouseButtons.Right) { + // Debug.WriteLine("RIGHT CLICK"); + // //ShowRightClickMenu(); + //} else { + // Debug.WriteLine("CLICK " + e.Button); + //} + } + + private void codeListView_MouseDoubleClick(object sender, MouseEventArgs e) { + ListViewHitTestInfo info = codeListView.HitTest(e.X, e.Y); + int row = info.Item.Index; + int col = info.Item.SubItems.IndexOf(info.SubItem); + // col will be -1 for e.g. blank lines [not anymore?] + string value = col < 0 ? "-" : info.Item.SubItems[col].Text; + Debug.WriteLine(string.Format("R{0}:C{1} val '{2}'", row, col, value)); + + // It's possible to select multiple lines with shift-double-click. We + // handle that by checking to see what UpdateActionMenu() decided was available. + + // Clicking on some types of lines, such as ORG directives, results in + // specific behavior regardless of which column you click in. We're just + // checking the clicked-on line to decide what action to take. If it doesn't + // make sense to do for a multi-line selection, the action will have been + // disabled. + DisplayList.Line line = mDisplayList[row]; + switch (line.LineType) { + case DisplayList.Line.Type.EquDirective: + // Currently only does something for project symbols; platform symbols + // do nothing. + if (editProjectSymbolToolStripMenuItem.Enabled) { + EditProjectSymbol_Click(sender, e); + } + break; + case DisplayList.Line.Type.OrgDirective: + if (setAddressToolStripMenuItem.Enabled) { + EditAddress_Click(sender, e); + } + break; + case DisplayList.Line.Type.RegWidthDirective: + if (overrideStatusFlagsToolStripMenuItem.Enabled) { + EditStatusFlags_Click(sender, e); + } + break; + case DisplayList.Line.Type.LongComment: + if (editLongCommentToolStripMenuItem.Enabled) { + EditLongComment_Click(sender, e); + } + break; + case DisplayList.Line.Type.Note: + if (editNoteToolStripMenuItem.Enabled) { + EditNote_Click(sender, e); + } + break; + + case DisplayList.Line.Type.Code: + case DisplayList.Line.Type.Data: + // For code and data, we have to break it down by column. + switch ((ColumnIndex)col) { + case ColumnIndex.Offset: + // does nothing + break; + case ColumnIndex.Address: + // edit address + if (setAddressToolStripMenuItem.Enabled) { + EditAddress_Click(sender, e); + } + break; + case ColumnIndex.Bytes: + if (showHexDumpToolStripMenuItem.Enabled) { + ShowHexDump_Click(sender, e); + } + break; + case ColumnIndex.Flags: + if (overrideStatusFlagsToolStripMenuItem.Enabled) { + EditStatusFlags_Click(sender, e); + } + break; + case ColumnIndex.Attributes: + // does nothing + break; + case ColumnIndex.Label: + if (editLabelToolStripMenuItem.Enabled) { + EditLabel_Click(sender, e); + } + break; + case ColumnIndex.Opcode: + // File offset should always be valid, since we excluded the EQU + // statements and header comment earlier. + if (line.FileOffset >= 0) { + Anattrib attr = mProject.GetAnattrib(line.FileOffset); + // Does this have an operand with an in-file target offset? + if (attr.OperandOffset >= 0) { + // Yup, find the line for that offset and jump to it. + GoToOffset(attr.OperandOffset, false, true); + //int targetIndex = + // mDisplayList.FindLineIndexByOffset(attr.OperandOffset); + //GoToOffset(mDisplayList[targetIndex].FileOffset); + } else if (attr.IsDataStart || attr.IsInlineDataStart) { + // If it's an Address or Symbol, we can try to resolve + // the value. + int operandOffset = DataAnalysis.GetDataOperandOffset( + mProject, line.FileOffset); + if (operandOffset >= 0) { + GoToOffset(operandOffset, false, true); + //int targetIndex = + // mDisplayList.FindLineIndexByOffset(operandOffset); + //GoToOffset(mDisplayList[targetIndex].FileOffset); + } + } + } + break; + case ColumnIndex.Operand: + if (editOperandToolStripMenuItem.Enabled) { + EditOperand_Click(sender, e); + } else if (editDataFormatToolStripMenuItem.Enabled) { + EditData_Click(sender, e); + } + break; + case ColumnIndex.Comment: + if (editCommentToolStripMenuItem.Enabled) { + EditComment_Click(sender, e); + } + break; + + } + break; + + default: + Debug.WriteLine("Double-click: unhandled line type " + line.LineType); + break; + } + } + + /// + /// Moves the view and selection to the specified offset. We want to select stuff + /// differently if we're jumping to a note vs. jumping to an instruction. + /// + /// Offset to jump to. + /// If set, push new offset onto navigation stack. + private void GoToOffset(int gotoOffset, bool jumpToNote, bool doPush) { + int curSelIndex = -1; + if (codeListView.SelectedIndices.Count > 0) { + curSelIndex = codeListView.SelectedIndices[0]; + } + + int topLineIndex = mDisplayList.FindLineIndexByOffset(gotoOffset); + if (topLineIndex < 0) { + Debug.Assert(false, "failed goto offset +" + gotoOffset.ToString("x6")); + return; + } + int lastLineIndex; + if (jumpToNote) { + // Select all note lines, disregard the rest. + while (mDisplayList[topLineIndex].LineType != DisplayList.Line.Type.Note) { + topLineIndex++; + Debug.Assert(mDisplayList[topLineIndex].FileOffset == gotoOffset); + } + lastLineIndex = topLineIndex + 1; + while (lastLineIndex < mDisplayList.Count && + mDisplayList[lastLineIndex].LineType == DisplayList.Line.Type.Note) { + lastLineIndex++; + } + } else if (gotoOffset < 0) { + // This is the offset of the header comment or a .EQ directive. Don't mess with it. + lastLineIndex = topLineIndex + 1; + } else { + // Advance to the code or data line. + while (mDisplayList[topLineIndex].LineType != DisplayList.Line.Type.Code && + mDisplayList[topLineIndex].LineType != DisplayList.Line.Type.Data) { + topLineIndex++; + } + lastLineIndex = topLineIndex + 1; + } + + // Make sure the item is visible. For notes, this can span multiple lines. + codeListView.EnsureVisible(lastLineIndex - 1); + codeListView.EnsureVisible(topLineIndex); + + // Update the selection. + codeListView.DeselectAll(); + for (int i = topLineIndex; i < lastLineIndex; i++) { + codeListView.Items[i].Selected = true; + } + + if (doPush) { + if (curSelIndex >= 0) { + // Update the back stack and associated controls. + mNavStack.Push(mDisplayList[curSelIndex].FileOffset, gotoOffset); + UpdateMenuItemsAndTitle(); + } else { + // This can happen when the project is first opened and nothing is selected. + Debug.WriteLine("no selection to go back to"); + } + } + } + + // Fires when the selection changes, causing the SelectionIndices list to change. + // For multi-select, this seems to be called with an empty list. + private void codeListView_SelectedIndexChanged(object sender, EventArgs e) { + // Update the "references" and "info" window contents. + UpdateReferenceView(); + UpdateInfoView(); + + UpdateSelectionHighlight(); + } + + // Virtual ListView selection tracking + private void codeListView_ItemSelectionChanged(object sender, + ListViewItemSelectionChangedEventArgs e) { + mCodeViewSelection.ItemSelectionChanged(e); + + // Don't try to call mCodeViewSelection.DebugValidateSelectionCount here. + // Events will fire during RestoreSelection() at a point where the + // SelectedIndices don't match up. + + if (!mRestoringSelection) { + UpdateActionMenu(); + } + } + + // Virtual ListView selection tracking + private void codeListView_VirtualItemsSelectionRangeChanged(object sender, + ListViewVirtualItemsSelectionRangeChangedEventArgs e) { + mCodeViewSelection.VirtualItemsSelectionRangeChanged(e); + + if (!mRestoringSelection) { + UpdateActionMenu(); + } + } + + /// + /// Enables or disables the menu items in the Actions menu. + /// + /// We want to do this whenever the selection changes so that any keyboard shortcuts + /// are enabled or disabled appropriately. This does need to be reasonably fast + /// for large files. + /// + /// The outcome of this method -- menu items being enabled or disabled -- is also + /// used by the double-click handler. + /// + private void UpdateActionMenu() { + if (mProject == null) { + // Disable all actions. + foreach (ToolStripItem item in mActionsMenuItems) { + item.Enabled = false; + } + return; + } + + // While restoring the selection, the SelectedIndices won't match up, because + // part of the restore process is setting and clearing the control to match + // what's in the mCodeViewSelection array. There's no value in repeating + // this for every event caused by the restoration, so we don't expect to be + // called at all during a restore. (Just make sure to call here after the + // restore is complete.) + Debug.Assert(!mRestoringSelection); + Debug.Assert(mCodeViewSelection.DebugValidateSelectionCount( + codeListView.SelectedIndices.Count), "selection count mismatch"); + + ListView.SelectedIndexCollection sel = codeListView.SelectedIndices; + EntityCounts entityCounts; + + // Use IsSingleItemSelected(), rather than just checking sel.Count, because we + // want the user to be able to e.g. EditData on a multi-line string even if all + // lines in the string are selected. + if (IsSingleItemSelected()) { + entityCounts = GatherEntityCounts(sel[0]); + + DisplayList.Line line = mDisplayList[sel[0]]; + DisplayList.Line.Type lineType = line.LineType; + + bool isCodeOrData = (lineType == DisplayList.Line.Type.Code || + lineType == DisplayList.Line.Type.Data); + + setAddressToolStripMenuItem.Enabled = + (isCodeOrData || lineType == DisplayList.Line.Type.OrgDirective); + editOperandToolStripMenuItem.Enabled = + (lineType == DisplayList.Line.Type.Code && + mProject.GetAnattrib(line.FileOffset).IsInstructionWithOperand); + editDataFormatToolStripMenuItem.Enabled = + (lineType == DisplayList.Line.Type.Data); + editLabelToolStripMenuItem.Enabled = isCodeOrData; + editCommentToolStripMenuItem.Enabled = isCodeOrData; + editLongCommentToolStripMenuItem.Enabled = + (isCodeOrData || lineType == DisplayList.Line.Type.LongComment); + editNoteToolStripMenuItem.Enabled = + (isCodeOrData || lineType == DisplayList.Line.Type.Note); + overrideStatusFlagsToolStripMenuItem.Enabled = + (lineType == DisplayList.Line.Type.Code || + lineType == DisplayList.Line.Type.RegWidthDirective); + deleteNoteCommentToolStripMenuItem.Enabled = + (lineType == DisplayList.Line.Type.LongComment || + lineType == DisplayList.Line.Type.Note); + + if (lineType == DisplayList.Line.Type.EquDirective) { + // Only enable this for project symbols, not all EQU directives. + int symIndex = DisplayList.DefSymIndexFromOffset(line.FileOffset); + DefSymbol defSym = mProject.ActiveDefSymbolList[symIndex]; + editProjectSymbolToolStripMenuItem.Enabled = + (defSym.SymbolSource == Symbol.Source.Project); + } else { + editProjectSymbolToolStripMenuItem.Enabled = false; + } + } else { + entityCounts = GatherEntityCounts(-1); + + // Disable all single-target-only items. + setAddressToolStripMenuItem.Enabled = false; + editOperandToolStripMenuItem.Enabled = false; + editLabelToolStripMenuItem.Enabled = false; + editCommentToolStripMenuItem.Enabled = false; + editLongCommentToolStripMenuItem.Enabled = false; + editNoteToolStripMenuItem.Enabled = false; + overrideStatusFlagsToolStripMenuItem.Enabled = false; + deleteNoteCommentToolStripMenuItem.Enabled = false; + editProjectSymbolToolStripMenuItem.Enabled = false; + + if (sel.Count == 0) { + // Disable everything else. + editDataFormatToolStripMenuItem.Enabled = false; + hintAsCodeToolStripMenuItem.Enabled = false; + hintAsDataToolStripMenuItem.Enabled = false; + hintAsInlineDataToolStripMenuItem.Enabled = false; + removeHintToolStripMenuItem.Enabled = false; + } else { + // Must be all data items. Blank lines are okay. Currently allowing + // control lines as well. + editDataFormatToolStripMenuItem.Enabled = + (entityCounts.mDataLines > 0 && entityCounts.mCodeLines == 0); + } + } + + toggleSingleBytesToolStripMenuItem.Enabled = + (entityCounts.mDataLines > 0 && entityCounts.mCodeLines == 0); + + // So long as some code or data is highlighted, allow these. Don't worry about + // control lines. Disable options that would have no effect. + bool enableHints = (entityCounts.mDataLines > 0 || entityCounts.mCodeLines > 0); + hintAsCodeToolStripMenuItem.Enabled = enableHints && + (entityCounts.mDataHints != 0 || + entityCounts.mInlineDataHints != 0 || + entityCounts.mNoHints != 0); + hintAsDataToolStripMenuItem.Enabled = enableHints && + (entityCounts.mCodeHints != 0 || + entityCounts.mInlineDataHints != 0 || + entityCounts.mNoHints != 0); + hintAsInlineDataToolStripMenuItem.Enabled = enableHints && + (entityCounts.mCodeHints != 0 || + entityCounts.mDataHints != 0 || + entityCounts.mNoHints != 0); + removeHintToolStripMenuItem.Enabled = enableHints && + (entityCounts.mCodeHints != 0 || + entityCounts.mDataHints != 0 || + entityCounts.mInlineDataHints != 0); + + // Just leave this on. If they're in EQU-land or nothing is selected, it'll just + // open at the start of the file. + showHexDumpToolStripMenuItem.Enabled = true; + } + + /// + /// Entity count collection, for GatherEntityCounts. + /// + private class EntityCounts { + public int mCodeLines; + public int mDataLines; + public int mBlankLines; + public int mControlLines; + + public int mCodeHints; + public int mDataHints; + public int mInlineDataHints; + public int mNoHints; + }; + + /// + /// Gathers a count of different line types and offset hinting. + /// + /// If a single line is selected, pass the index in. + /// Otherwise, pass -1 to traverse the entire line list. + /// + private EntityCounts GatherEntityCounts(int singleLineIndex) { + //DateTime startWhen = DateTime.Now; + int codeLines, dataLines, blankLines, controlLines; + int codeHints, dataHints, inlineDataHints, noHints; + codeLines = dataLines = blankLines = controlLines = 0; + codeHints = dataHints = inlineDataHints = noHints = 0; + + int startIndex, endIndex; + if (singleLineIndex < 0) { + startIndex = 0; + endIndex = mDisplayList.Count - 1; + } else { + startIndex = endIndex = singleLineIndex; + } + + for (int i = startIndex; i <= endIndex; i++) { + if (!mCodeViewSelection[i]) { + continue; + } + DisplayList.Line line = mDisplayList[i]; + switch (line.LineType) { + case DisplayList.Line.Type.Code: + codeLines++; + break; + case DisplayList.Line.Type.Data: + dataLines++; + break; + case DisplayList.Line.Type.Blank: + // Don't generally care how many blank lines there are, but we do want + // to exclude them from the other categories: if we have nothing but + // blank lines, there's nothing to do. + blankLines++; + break; + default: + // These are only editable as single-line items. We do allow mass + // code hint selection to include them (they will be ignored). + // org, equ, rwid, long comment... + controlLines++; + break; + } + + // A single line can span multiple offsets, each of which could have a + // different hint. + for (int offset = line.FileOffset; offset < line.FileOffset + line.OffsetSpan; + offset++) { + switch (mProject.TypeHints[offset]) { + case CodeAnalysis.TypeHint.Code: + codeHints++; + break; + case CodeAnalysis.TypeHint.Data: + dataHints++; + break; + case CodeAnalysis.TypeHint.InlineData: + inlineDataHints++; + break; + case CodeAnalysis.TypeHint.NoHint: + noHints++; + break; + default: + Debug.Assert(false); + break; + } + } + } + + //Debug.WriteLine("GatherEntityCounts (len=" + mCodeViewSelection.Length + ") took " + + // (DateTime.Now - startWhen).TotalMilliseconds + " ms"); + + return new EntityCounts() { + mCodeLines = codeLines, + mDataLines = dataLines, + mBlankLines = blankLines, + mControlLines = controlLines, + mCodeHints = codeHints, + mDataHints = dataHints, + mInlineDataHints = inlineDataHints, + mNoHints = noHints + }; + } + + /// + /// Determines whether the current selection spans a single item. This could be a + /// single-line item or a multi-line item. + /// + private bool IsSingleItemSelected() { + ListView.SelectedIndexCollection sel = codeListView.SelectedIndices; + if (sel.Count == 0) { + return false; + } else if (sel.Count == 1) { + return true; + } + + // The selection is presented in sorted order, so we can just check the + // first and last entries to see if they're the same. + // + // Performance note: SelectedIndices[] appears to be dynamic. In a very + // large list (500K+ entries), requesting sel[sel.Count - 1] can take a few + // seconds. As an optimization we just give up if the selection spans + // more than a few hundred lines. (At worst, this requires clicking a single line + // of a comment/note to edit it as an individual item.) + // TODO(maybe): iterate over mCodeViewSelection instead? Would need more stuff there. + if (sel.Count >= 500) { + Debug.WriteLine("Selection very large (" + sel.Count + "), not checking for " + + "single-item span"); + return false; + } + DisplayList.Line firstItem = mDisplayList[sel[0]]; + DisplayList.Line lastItem = mDisplayList[sel[sel.Count - 1]]; + if (firstItem.FileOffset == lastItem.FileOffset && + firstItem.LineType == lastItem.LineType) { + return true; + } + return false; + } + + /// + /// Updates the selection highlight. When a code item with an operand offset is + /// selected, such as a branch, we want to highlight the address and label of the + /// target. + /// + private void UpdateSelectionHighlight() { + int targetIndex = FindSelectionHighlight(); + + if (mTargetHighlightIndex != targetIndex) { + mTargetHighlightIndex = targetIndex; + Debug.WriteLine("Selection highlight now " + targetIndex); + + // Force a redraw. + codeListView.BeginUpdate(); + //ClearCodeListViewCache(); // not necessary; only formatting has changed + codeListView.EndUpdate(); + } + } + + private int FindSelectionHighlight() { + ListView.SelectedIndexCollection sel = codeListView.SelectedIndices; + if (sel.Count != 1) { + return -1; + } + DisplayList.Line line = mDisplayList[codeListView.SelectedIndices[0]]; + if (!line.IsCodeOrData) { + return -1; + } + Debug.Assert(line.FileOffset >= 0); + + // Does this have an operand with an in-file target offset? + Anattrib attr = mProject.GetAnattrib(line.FileOffset); + if (attr.OperandOffset >= 0) { + return mDisplayList.FindCodeDataIndexByOffset(attr.OperandOffset); + } else if (attr.IsDataStart || attr.IsInlineDataStart) { + // If it's an Address or Symbol, we can try to resolve + // the value. + int operandOffset = DataAnalysis.GetDataOperandOffset(mProject, line.FileOffset); + if (operandOffset >= 0) { + return mDisplayList.FindCodeDataIndexByOffset(operandOffset); + } + } + return -1; + } + + /// + /// Handles an "opening" event for the codeListView's ContextMenuStrip. + /// + /// This puts all of the Actions menu items in the pop-up context menu. + /// + private void codeListView_CmsOpening(object sender, CancelEventArgs e) { + codeListView.ContextMenuStrip.Items.AddRange(mActionsMenuItems); + e.Cancel = false; + } + + /// + /// Handles an "opening" event for the ProjectView's Actions menu. + /// + /// This puts all of the Actions menu items in the Actions menu. + /// + private void ActionsMenuOpening(object sender, EventArgs e) { + actionsToolStripMenuItem.DropDownItems.AddRange(mActionsMenuItems); + } + + private void EditAddress_Click(Object sender, EventArgs e) { + ListView.SelectedIndexCollection sel = codeListView.SelectedIndices; + Debug.Assert(IsSingleItemSelected()); + int offset = mDisplayList[sel[0]].FileOffset; + + EditAddress dlg = new EditAddress(); + Anattrib attr = mProject.GetAnattrib(offset); + dlg.MaxAddressValue = mProject.CpuDef.MaxAddressValue; + dlg.SetInitialAddress(attr.Address); + dlg.ShowDialog(); + + if (dlg.DialogResult == DialogResult.OK) { + if (offset == 0 && dlg.Address < 0) { + // Not allowed. The AddressMap will just put it back, which confuses + // the undo operation. + Debug.WriteLine("Not allowed to remove address at offset +000000"); + } else if (attr.Address != dlg.Address) { + Debug.WriteLine("Changing addr at offset +" + offset.ToString("x6") + + " to " + dlg.Address); + + AddressMap addrMap = mProject.AddrMap; + // Get the previous address map entry for this exact offset, if one + // exists. This may be different from the value used as the default + // (attr.Address), which is the address assigned to the offset, in + // the case where no previous mapping existed. + int prevAddress = addrMap.Get(offset); + UndoableChange uc = UndoableChange.CreateAddressChange(offset, + prevAddress, dlg.Address); + ChangeSet cs = new ChangeSet(uc); + ApplyUndoableChanges(cs); + } else { + Debug.WriteLine("No change to address"); + } + } + + dlg.Dispose(); + } + + private void EditOperand_Click(Object sender, EventArgs e) { + Debug.Assert(IsSingleItemSelected()); + ListView.SelectedIndexCollection sel = codeListView.SelectedIndices; + int offset = mDisplayList[sel[0]].FileOffset; + + EditOperand dlg = new EditOperand(offset, mProject, mOutputFormatter); + + // We'd really like to pass in an indication of what the "default" format actually + // resolved to, but we don't always know. If this offset has a FormatDescriptor, + // we might not have auto-generated the label that would have been used otherwise. + + // We're editing the FormatDescriptor from OperandFormats, not Anattribs; + // the latter may have auto-generated stuff. + if (mProject.OperandFormats.TryGetValue(offset, out FormatDescriptor dfd)) { + dlg.FormatDescriptor = dfd; + } + + dlg.ShowDialog(); + if (dlg.DialogResult == DialogResult.OK) { + ChangeSet cs = new ChangeSet(1); + + if (dlg.FormatDescriptor != dfd && dlg.ShortcutAction != + EditOperand.SymbolShortcutAction.CreateLabelInstead) { + // Note EditOperand returns a null descriptor when the user selects Default. + // This is different from how EditData works, since that has to deal with + // multiple regions. + Debug.WriteLine("Changing " + dfd + " to " + dlg.FormatDescriptor); + UndoableChange uc = UndoableChange.CreateOperandFormatChange(offset, + dfd, dlg.FormatDescriptor); + cs.Add(uc); + } else if (dfd != null && dlg.ShortcutAction == + EditOperand.SymbolShortcutAction.CreateLabelInstead) { + Debug.WriteLine("Removing existing label for CreateLabelInstead"); + UndoableChange uc = UndoableChange.CreateOperandFormatChange(offset, + dfd, null); + cs.Add(uc); + } else { + Debug.WriteLine("No change to format descriptor"); + } + + switch (dlg.ShortcutAction) { + case EditOperand.SymbolShortcutAction.CreateLabelInstead: + case EditOperand.SymbolShortcutAction.CreateLabelAlso: + Debug.Assert(!mProject.UserLabels.ContainsKey(dlg.ShortcutArg)); + Anattrib targetAttr = mProject.GetAnattrib(dlg.ShortcutArg); + Symbol newLabel = new Symbol(dlg.FormatDescriptor.SymbolRef.Label, + targetAttr.Address, Symbol.Source.User, Symbol.Type.LocalOrGlobalAddr); + UndoableChange uc = UndoableChange.CreateLabelChange(dlg.ShortcutArg, + null, newLabel); + cs.Add(uc); + break; + case EditOperand.SymbolShortcutAction.CreateProjectSymbolAlso: + Debug.Assert(!mProject.ProjectProps.ProjectSyms.ContainsKey( + dlg.FormatDescriptor.SymbolRef.Label)); + DefSymbol defSym = new DefSymbol(dlg.FormatDescriptor.SymbolRef.Label, + dlg.ShortcutArg, Symbol.Source.Project, Symbol.Type.ExternalAddr, + FormatDescriptor.SubType.Hex, string.Empty, string.Empty); + ProjectProperties newProps = new ProjectProperties(mProject.ProjectProps); + newProps.ProjectSyms.Add(defSym.Label, defSym); + uc = UndoableChange.CreateProjectPropertiesChange( + mProject.ProjectProps, newProps); + cs.Add(uc); + break; + case EditOperand.SymbolShortcutAction.None: + break; + } + + if (cs.Count != 0) { + ApplyUndoableChanges(cs); + } + } + + dlg.Dispose(); + } + + private void EditData_Click(Object sender, EventArgs e) { + ListView.SelectedIndexCollection sel = codeListView.SelectedIndices; + Debug.Assert(sel.Count > 0); + + EditData dlg = new EditData(mProject.FileData, mProject.SymbolTable, mOutputFormatter); + + TypedRangeSet trs = dlg.Selection = GroupedOffsetSetFromSelected(); + if (trs.Count == 0) { + Debug.Assert(false, "EditData found nothing to edit"); // shouldn't happen + dlg.Dispose(); + return; + } + + // If the first offset has a FormatDescriptor, pass that in as a recommendation + // for the default value in the dialog. This allows single-item editing to work + // as expected. If the format can't be applied to the full selection (which + // would disable that radio button), the dialog will have to pick something + // that does work. + // + // We could pull this out of Anattribs, which would let the dialog reflect the + // auto-format that the user was just looking at. However, I think it's better + // if the dialog shows what's actually there, i.e. no formatting at all. + IEnumerator iter = + (IEnumerator) trs.GetEnumerator(); + iter.MoveNext(); + TypedRangeSet.Tuple firstOffset = iter.Current; + if (mProject.OperandFormats.TryGetValue(firstOffset.Value, out FormatDescriptor dfd)) { + dlg.FirstFormatDescriptor = dfd; + } + + dlg.ShowDialog(); + if (dlg.DialogResult == DialogResult.OK) { + // Merge the changes into the OperandFormats list. We need to remove all + // FormatDescriptors that overlap the selected region. We don't need to + // pass the selection set in, because the dlg.Results list spans the exact + // set of ranges. + // + // If nothing actually changed, don't generate an undo record. + ChangeSet cs = mProject.GenerateFormatMergeSet(dlg.Results); + if (cs.Count != 0) { + ApplyUndoableChanges(cs); + } else { + Debug.WriteLine("No change to data formats"); + } + } + + dlg.Dispose(); + } + + private void ToggleSingleBytes_Click(object sender, EventArgs e) { + TypedRangeSet trs = GroupedOffsetSetFromSelected(); + if (trs.Count == 0) { + Debug.Assert(false, "nothing to edit"); // shouldn't happen + return; + } + + // Check the format descriptor of the first selected offset. + int firstOffset = -1; + foreach (TypedRangeSet.Tuple tup in trs) { + firstOffset = tup.Value; + break; + } + Debug.Assert(mProject.GetAnattrib(firstOffset).IsDataStart); + bool toDefault = false; + if (mProject.OperandFormats.TryGetValue(firstOffset, out FormatDescriptor curDfd)) { + if (curDfd.FormatType == FormatDescriptor.Type.NumericLE && + curDfd.FormatSubType == FormatDescriptor.SubType.None && + curDfd.Length == 1) { + // Currently single-byte, toggle to default. + toDefault = true; + } + } + + // Iterate through the selected regions. + SortedList newFmts = new SortedList(); + IEnumerator rngIter = trs.RangeListIterator; + while (rngIter.MoveNext()) { + TypedRangeSet.TypedRange rng = rngIter.Current; + if (toDefault) { + // Create a single REMOVE descriptor that covers the full span. + FormatDescriptor newDfd = FormatDescriptor.Create(rng.High - rng.Low + 1, + FormatDescriptor.Type.REMOVE, FormatDescriptor.SubType.None); + newFmts.Add(rng.Low, newDfd); + } else { + // Add individual single-byte format descriptors for everything. + FormatDescriptor newDfd = FormatDescriptor.Create(1, + FormatDescriptor.Type.NumericLE, FormatDescriptor.SubType.None); + for (int i = rng.Low; i <= rng.High; i++) { + newFmts.Add(i, newDfd); + } + } + } + + ChangeSet cs = mProject.GenerateFormatMergeSet(newFmts); + if (cs.Count != 0) { + ApplyUndoableChanges(cs); + } + } + + private void DeleteNoteComment_Click(object sender, EventArgs e) { + Debug.Assert(IsSingleItemSelected()); + ListView.SelectedIndexCollection sel = codeListView.SelectedIndices; + DisplayList.Line line = mDisplayList[sel[0]]; + int offset = line.FileOffset; + + UndoableChange uc; + if (line.LineType == DisplayList.Line.Type.Note) { + if (!mProject.Notes.TryGetValue(offset, out MultiLineComment oldNote)) { + Debug.Assert(false); + return; + } + uc = UndoableChange.CreateNoteChange(offset, oldNote, null); + } else if (line.LineType == DisplayList.Line.Type.LongComment) { + if (!mProject.LongComments.TryGetValue(offset, out MultiLineComment oldComment)) { + Debug.Assert(false); + return; + } + uc = UndoableChange.CreateLongCommentChange(offset, oldComment, null); + } else { + Debug.Assert(false); + return; + } + ChangeSet cs = new ChangeSet(uc); + ApplyUndoableChanges(cs); + } + + private void EditLabel_Click(Object sender, EventArgs e) { + ListView.SelectedIndexCollection sel = codeListView.SelectedIndices; + Debug.Assert(IsSingleItemSelected()); + int offset = mDisplayList[sel[0]].FileOffset; + + EditLabel dlg = new EditLabel(mProject.SymbolTable); + Anattrib attr = mProject.GetAnattrib(offset); + + dlg.LabelSym = attr.Symbol; + dlg.Address = attr.Address; + dlg.ShowDialog(); + if (dlg.DialogResult == DialogResult.OK) { + // NOTE: if label matching is case-insensitive, we want to allow a situation + // where a label is being renamed from "FOO" to "Foo". (We should be able to + // test for object equality on the Symbol.) + if (attr.Symbol != dlg.LabelSym) { + Debug.WriteLine("Changing label at offset +" + offset.ToString("x6")); + + // For undo/redo, we want to update the UserLabels value. This may + // be different from the Anattrib symbol, which can have an auto-generated + // value. + Symbol oldUserValue = null; + if (mProject.UserLabels.ContainsKey(offset)) { + oldUserValue = mProject.UserLabels[offset]; + } + UndoableChange uc = UndoableChange.CreateLabelChange(offset, + oldUserValue, dlg.LabelSym); + ChangeSet cs = new ChangeSet(uc); + ApplyUndoableChanges(cs); + } + } + + dlg.Dispose(); + } + + private void EditStatusFlags_Click(Object sender, EventArgs e) { + ListView.SelectedIndexCollection sel = codeListView.SelectedIndices; + Debug.Assert(IsSingleItemSelected()); + int offset = mDisplayList[sel[0]].FileOffset; + + EditStatusFlags dlg = new EditStatusFlags(); + dlg.HasEmuFlag = mProject.CpuDef.HasEmuFlag; + dlg.FlagValue = mProject.StatusFlagOverrides[offset]; + dlg.ShowDialog(); + if (dlg.DialogResult == DialogResult.OK) { + if (dlg.FlagValue != mProject.StatusFlagOverrides[offset]) { + UndoableChange uc = UndoableChange.CreateStatusFlagChange(offset, + mProject.StatusFlagOverrides[offset], dlg.FlagValue); + ChangeSet cs = new ChangeSet(uc); + ApplyUndoableChanges(cs); + } + } + + dlg.Dispose(); + } + + private void EditComment_Click(Object sender, EventArgs e) { + ListView.SelectedIndexCollection sel = codeListView.SelectedIndices; + Debug.Assert(IsSingleItemSelected()); + int offset = mDisplayList[sel[0]].FileOffset; + + EditComment dlg = new EditComment(); + string oldComment = dlg.Comment = mProject.Comments[offset]; + dlg.ShowDialog(); + + if (dlg.DialogResult == DialogResult.OK) { + if (!oldComment.Equals(dlg.Comment)) { + Debug.WriteLine("Changing comment at +" + offset.ToString("x6")); + + UndoableChange uc = UndoableChange.CreateCommentChange(offset, + oldComment, dlg.Comment); + ChangeSet cs = new ChangeSet(uc); + ApplyUndoableChanges(cs); + } + } + + dlg.Dispose(); + } + + private void EditLongComment_Click(Object sender, EventArgs e) { + ListView.SelectedIndexCollection sel = codeListView.SelectedIndices; + Debug.Assert(IsSingleItemSelected()); + EditLongComment(mDisplayList[sel[0]].FileOffset); + } + + private void editHeaderCommentToolStripMenuItem_Click(object sender, EventArgs e) { + EditLongComment(DisplayList.Line.HEADER_COMMENT_OFFSET); + } + + private void EditLongComment(int offset) { + EditLongComment dlg = new EditLongComment(mOutputFormatter); + if (mProject.LongComments.TryGetValue(offset, out MultiLineComment oldComment)) { + dlg.LongComment = oldComment; + } + dlg.ShowDialog(); + + if (dlg.DialogResult == DialogResult.OK) { + MultiLineComment newComment = dlg.LongComment; + if (oldComment != newComment) { + Debug.WriteLine("Changing long comment at +" + offset.ToString("x6")); + + UndoableChange uc = UndoableChange.CreateLongCommentChange(offset, + oldComment, newComment); + ChangeSet cs = new ChangeSet(uc); + ApplyUndoableChanges(cs); + } + } + + dlg.Dispose(); + } + + private void EditNote_Click(Object sender, EventArgs e) { + ListView.SelectedIndexCollection sel = codeListView.SelectedIndices; + Debug.Assert(IsSingleItemSelected()); + int offset = mDisplayList[sel[0]].FileOffset; + + EditNote dlg = new EditNote(); + if (mProject.Notes.TryGetValue(offset, out MultiLineComment oldNote)) { + dlg.Note = oldNote; + } + dlg.ShowDialog(); + + if (dlg.DialogResult == DialogResult.OK) { + MultiLineComment newNote = dlg.Note; + if (oldNote != newNote) { + Debug.WriteLine("Changing note at +" + offset.ToString("x6")); + + UndoableChange uc = UndoableChange.CreateNoteChange(offset, + oldNote, newNote); + ChangeSet cs = new ChangeSet(uc); + ApplyUndoableChanges(cs); + } + } + + dlg.Dispose(); + } + + private void EditProjectSymbol_Click(object sender, EventArgs e) { + Debug.Assert(IsSingleItemSelected()); + ListView.SelectedIndexCollection sel = codeListView.SelectedIndices; + DisplayList.Line line = mDisplayList[sel[0]]; + int symIndex = DisplayList.DefSymIndexFromOffset(line.FileOffset); + DefSymbol origDefSym = mProject.ActiveDefSymbolList[symIndex]; + Debug.Assert(origDefSym.SymbolSource == Symbol.Source.Project); + + EditDefSymbol dlg = new EditDefSymbol(mOutputFormatter, + mProject.ProjectProps.ProjectSyms); + dlg.DefSym = origDefSym; + if (dlg.ShowDialog() == DialogResult.OK) { + ProjectProperties newProps = new ProjectProperties(mProject.ProjectProps); + newProps.ProjectSyms.Remove(origDefSym.Label); + newProps.ProjectSyms[dlg.DefSym.Label] = dlg.DefSym; + + UndoableChange uc = UndoableChange.CreateProjectPropertiesChange( + mProject.ProjectProps, newProps); + ChangeSet cs = new ChangeSet(uc); + ApplyUndoableChanges(cs); + } + dlg.Dispose(); + } + + private void MarkAsCode_Click(Object sender, EventArgs e) { + MarkAsType(CodeAnalysis.TypeHint.Code); + } + private void MarkAsData_Click(Object sender, EventArgs e) { + MarkAsType(CodeAnalysis.TypeHint.Data); + } + private void MarkAsInlineData_Click(Object sender, EventArgs e) { + MarkAsType(CodeAnalysis.TypeHint.InlineData); + } + private void MarkAsNoHint_Click(Object sender, EventArgs e) { + MarkAsType(CodeAnalysis.TypeHint.NoHint); + } + private void MarkAsType(CodeAnalysis.TypeHint hint) { + RangeSet sel = OffsetSetFromSelected(); + TypedRangeSet newSet = new TypedRangeSet(); + TypedRangeSet undoSet = new TypedRangeSet(); + + foreach (int offset in sel) { + if (offset < 0) { + // header comment + continue; + } + CodeAnalysis.TypeHint oldType = mProject.TypeHints[offset]; + if (oldType == hint) { + // no change, don't add to set + continue; + } + undoSet.Add(offset, (int)oldType); + newSet.Add(offset, (int)hint); + } + if (newSet.Count == 0) { + Debug.WriteLine("No changes found (" + hint + ", " + sel.Count + " offsets)"); + return; + } + + UndoableChange uc = UndoableChange.CreateTypeHintChange(undoSet, newSet); + ChangeSet cs = new ChangeSet(uc); + + ApplyUndoableChanges(cs); + } + + private void ShowHexDump_Click(object sender, EventArgs e) { + if (mHexDumpDialog == null) { + // Create and show modeless dialog. This one is "always on top" by default, + // to allow the user to click around to various points. + mHexDumpDialog = new Tools.HexDumpViewer(mProject.FileData, mOutputFormatter); + mHexDumpDialog.OnWindowClosing += (arg) => { + Debug.WriteLine("Hex dump dialog closed"); + //showHexDumpToolStripMenuItem.Checked = false; + mHexDumpDialog = null; + }; + mHexDumpDialog.TopMost = true; + mHexDumpDialog.Show(); + //showHexDumpToolStripMenuItem.Checked = true; + } + + // Bring it to the front of the window stack. This also transfers focus to the + // window. + mHexDumpDialog.BringToFront(); + + // Set the dialog's position. + ListView.SelectedIndexCollection sel = codeListView.SelectedIndices; + if (sel.Count > 0) { + int firstIndex = sel[0]; + int lastIndex = sel[sel.Count - 1]; + // offsets can be < 0 if they've selected EQU statements + int firstOffset = Math.Max(0, mDisplayList[firstIndex].FileOffset); + int lastOffset = Math.Max(firstOffset, mDisplayList[lastIndex].FileOffset + + mDisplayList[lastIndex].OffsetSpan - 1); + mHexDumpDialog.ShowOffsetRange(firstOffset, lastOffset); + } + } + + private void aSCIIChartToolStripMenuItem_Click(object sender, EventArgs e) { + // Show or hide the modeless dialog. + if (mAsciiChartDialog == null) { + Tools.AsciiChart dlg = new Tools.AsciiChart(); + dlg.OnWindowClosing += (arg) => { + Debug.WriteLine("ASCII chart closed"); + aSCIIChartToolStripMenuItem.Checked = false; + mAsciiChartDialog = null; + }; + dlg.Show(); + mAsciiChartDialog = dlg; + aSCIIChartToolStripMenuItem.Checked = true; + } else { + // Ask the dialog to close. Do the cleanup in the event. + mAsciiChartDialog.Close(); + } + + } + + /// + /// Converts the ListView's selected items into a set of offsets. If a line + /// spans multiple offsets (e.g. a 3-byte instruction), offsets for every + /// byte are included. + /// + /// Boundaries such as labels and address changes are ignored. + /// + /// RangeSet with all offsets. + private RangeSet OffsetSetFromSelected() { + RangeSet rs = new RangeSet(); + + foreach (int index in codeListView.SelectedIndices) { + int offset = mDisplayList[index].FileOffset; + + // Mark every byte of an instruction or multi-byte data item -- + // everything that is represented by the line the user selected. + int len; + if (offset >= 0) { + len = mProject.GetAnattrib(offset).Length; + } else { + // header area + len = 1; + } + Debug.Assert(len > 0); + for (int i = offset; i < offset + len; i++) { + rs.Add(i); + } + } + return rs; + } + + /// + /// Converts the ListView's selected items into a set of offsets. If a line + /// spans multiple offsets (e.g. a 3-byte instruction), offsets for every + /// byte are included. + /// + /// Contiguous regions with user labels or address changes are split into + /// independent regions by using a serial number for the range type. Same for + /// long comments and notes. + /// + /// We don't split based on existing data format items. That would make it impossible + /// to convert from (say) a collection of single bytes to a collection of double bytes + /// or a string. It should not be possible to select part of a formatted section, + /// unless the user has been playing weird games with type hints to get overlapping + /// format descriptors. + /// + /// TypedRangeSet with all offsets. + private TypedRangeSet GroupedOffsetSetFromSelected() { + TypedRangeSet rs = new TypedRangeSet(); + int groupNum = 0; + int expectedAddr = -1; + + bool thing = false; + if (thing) { + DateTime nowWhen = DateTime.Now; + int selCount = 0; + for (int i = 0; i < mDisplayList.Count; i++) { + ListViewItem lvi = codeListView.Items[i]; + selCount += lvi.Selected ? 1 : 0; + } + Debug.WriteLine("Sel count (" + selCount + ") took " + + (DateTime.Now - nowWhen).TotalMilliseconds + " ms"); + } + + DateTime startWhen = DateTime.Now; + int prevOffset = -1; + foreach (int index in codeListView.SelectedIndices) { + // Don't add an offset to the set if the only part of it that is selected + // is a directive or blank line. We only care about file offsets, so skip + // anything that isn't code or data. + if (!mDisplayList[index].IsCodeOrData) { + continue; + } + + int offset = mDisplayList[index].FileOffset; + if (offset == prevOffset) { + // This is a continuation of a multi-line item like a string. We've + // already accounted for all bytes associated with this offset. + continue; + } + Anattrib attr = mProject.GetAnattrib(offset); + + if (expectedAddr == -1) { + expectedAddr = attr.Address; + } + // Check for user labels. + if (mProject.UserLabels.ContainsKey(offset)) { + //if (mProject.GetAnattrib(offset).Symbol != null) { + // We consider auto labels when splitting regions for the data analysis, + // but I don't think we want to take them into account here. The specific + // example that threw me was loading a 16-bit value from an address table. + // The code does "LDA table,X / STA / LDA table+1,X / STA", which puts auto + // labels at the first two addresses -- splitting the region. That's good + // for the uncategorized data analyzer, but very annoying if you want to + // slap a 16-bit numeric format on all entries. + groupNum++; + } else if (mProject.HasCommentOrNote(offset)) { + // Don't carry across a long comment or note. + groupNum++; + } else if (attr.Address != expectedAddr) { + // For a contiguous selection, this should only happen if there's a .ORG + // address change. For a selection that skips code/data lines this is + // expected. In the later case, incrementing the group number is + // unnecessary but harmless. + Debug.WriteLine("Address break: " + attr.Address + " vs. " + expectedAddr); + //Debug.Assert(mProject.AddrMap.Get(offset) >= 0); + + expectedAddr = attr.Address; + groupNum++; + } + + // Mark every byte of an instruction or multi-byte data item -- + // everything that is represented by the line the user selected. Control + // statements and blank lines aren't relevant here, as we only care about + // file offsets. + int len = mDisplayList[index].OffsetSpan; // attr.Length; + Debug.Assert(len > 0); + for (int i = offset; i < offset + len; i++) { + rs.Add(i, groupNum); + } + // Advance the address. + expectedAddr += len; + + prevOffset = offset; + } + Debug.WriteLine("Offset selection conv took " + + (DateTime.Now - startWhen).TotalMilliseconds + " ms"); + return rs; + } + + #endregion // Main window UI event handlers + + + #region codeListView OwnerDraw implementation + + private enum ColumnIndex { + Offset = 0, Address, Bytes, Flags, Attributes, Label, Opcode, Operand, Comment + } + + /// + /// Handy class for collecting column widths for the code list view. + /// + public class CodeListColumnWidths { + public const int NUM_COLUMNS = (int)ColumnIndex.Comment + 1; + + /// + /// Primary storage for column widths. + /// + public int[] Width { get; private set; } + + public int Offset { + get { return Width[0]; } + set { Width[0] = value; } + } + public int Address { + get { return Width[1]; } + set { Width[1] = value; } + } + public int Bytes { + get { return Width[2]; } + set { Width[2] = value; } + } + public int Flags { + get { return Width[3]; } + set { Width[3] = value; } + } + public int Attributes { + get { return Width[4]; } + set { Width[4] = value; } + } + public int Label { + get { return Width[5]; } + set { Width[5] = value; } + } + public int Opcode { + get { return Width[6]; } + set { Width[6] = value; } + } + public int Operand { + get { return Width[7]; } + set { Width[7] = value; } + } + public int Comment { + get { return Width[8]; } + set { Width[8] = value; } + } + + public CodeListColumnWidths() { + Width = new int[NUM_COLUMNS]; + } + + public string Serialize() { + StringBuilder sb = new StringBuilder(64); + sb.Append("cw"); + for (int i = 0; i < NUM_COLUMNS; i++) { + sb.Append(','); + sb.Append(Width[i]); + } + return sb.ToString(); + } + + public static CodeListColumnWidths Deserialize(string cereal) { + CodeListColumnWidths widths = new CodeListColumnWidths(); + string[] splitted = cereal.Split(','); + if (splitted.Length != NUM_COLUMNS + 1) { + Debug.WriteLine("Column width parse failed: wrong count: " + splitted.Length); + return null; + } + if (splitted[0] != "cw") { + Debug.WriteLine("Column width parse failed: bad magic: " + splitted[0]); + return null; + } + try { + for (int i = 0; i < NUM_COLUMNS; i++) { + widths.Width[i] = int.Parse(splitted[i + 1]); + } + } catch (Exception ex) { + Debug.WriteLine("Column width parse failed: " + ex.Message); + return null; + } + return widths; + } + + public override string ToString() { + return Serialize(); + } + } + + /// + /// Gets the default column widths for the code list view, based on the currently + /// configured font. + /// + /// Column width set. + public CodeListColumnWidths GetDefaultCodeListColumnWidths() { + CodeListColumnWidths widths = new CodeListColumnWidths(); + Graphics gfx = codeListView.CreateGraphics(); + widths.Offset = GetCodeListStringWidth(gfx, "X+000000"); + widths.Address = GetCodeListStringWidth(gfx, "X00/0000"); + widths.Bytes = GetCodeListStringWidth(gfx, "X00000000"); + widths.Flags = GetCodeListStringWidth(gfx, "X00000000 0"); + widths.Attributes = GetCodeListStringWidth(gfx, "X######"); + widths.Label = GetCodeListStringWidth(gfx, "XMMMMMMMMM"); + widths.Opcode = GetCodeListStringWidth(gfx, "XMMMMMMM"); + widths.Operand = GetCodeListStringWidth(gfx, "XMMMMMMMMMMMMM"); + widths.Comment = GetCodeListStringWidth(gfx, + "XMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM"); + return widths; + } + private int GetCodeListStringWidth(Graphics gfx, string str) { + SizeF size = gfx.MeasureString(str, codeListView.Font); + return (int)Math.Round(size.Width); + } + + /// + /// Saves the code list column widths into AppSettings. + /// + /// + public void SaveCodeListColumnWidths() { + CodeListColumnWidths widths = new CodeListColumnWidths(); + for (int i = 0; i < CodeListColumnWidths.NUM_COLUMNS; i++) { + widths.Width[i] = codeListView.Columns[i].Width; + } + + string cereal = widths.Serialize(); + AppSettings.Global.SetString(AppSettings.CDLV_COL_WIDTHS, cereal); + } + + /// + /// Configures the column widths. + /// + private void SetCodeListHeaderWidths(CodeListColumnWidths widths) { + Debug.WriteLine("Setting column widths: " + widths); + for (int i = 0; i < CodeListColumnWidths.NUM_COLUMNS; i++) { + codeListView.Columns[i].Width = widths.Width[i]; + } + } + + private void codeListView_ColumnWidthChanged(object sender, + ColumnWidthChangedEventArgs e) { + //Debug.WriteLine("Column width changed: " + e.ColumnIndex); + // This fires during initial setup when things don't have widths. A little + // risky to save the widths off now; would be safer to do it right before we write + // the settings file. + SaveCodeListColumnWidths(); + } + + private void codeListView_DrawColumnHeader(object sender, + DrawListViewColumnHeaderEventArgs e) { + ListView lv = e.Header.ListView; + string text = lv.Columns[e.ColumnIndex].Text; + + // Adjust rect to match standard control for 10pt fonts, and + // reserve a couple pixels at the far right end for the separator. + Rectangle rect = new Rectangle(e.Bounds.X + 3, e.Bounds.Y + 4, + e.Bounds.Width - 4, e.Bounds.Height - 4); + TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.EndEllipsis | + TextFormatFlags.SingleLine; + + TextRenderer.DrawText(e.Graphics, text, lv.Font, rect, lv.ForeColor, flags); + + Pen pen = new Pen(Color.LightGray); + //Pen pen = new Pen(Color.Blue); + e.Graphics.DrawLine(pen, e.Bounds.X + e.Bounds.Width - 1, e.Bounds.Y, + e.Bounds.X + e.Bounds.Width - 1, e.Bounds.Y + e.Bounds.Height); + } + + private void codeListView_DrawItem(object sender, + DrawListViewItemEventArgs e) { + // Only draw the full-line items here. Do not draw them later. + + DisplayList.Line line = mDisplayList[e.ItemIndex]; + if (line.LineType != DisplayList.Line.Type.LongComment && + line.LineType != DisplayList.Line.Type.Note) { + return; + } + + // Column 5 is the label. We put long comments and notes there. + int leftColsWidth = 0; + for (int i = 0; i < (int)ColumnIndex.Label; i++) { + leftColsWidth += e.Item.ListView.Columns[i].Width; + } + + // No sub-items, just one long comment. + ListView lv = e.Item.ListView; + ListViewItem lvi = e.Item; + + // Set colors based on selection and focus. + if (lvi.Selected && lv.Focused) { + lvi.BackColor = SystemColors.Highlight; + lvi.ForeColor = lv.BackColor; + } else if (e.Item.Selected && !lv.Focused) { + lvi.BackColor = SystemColors.Control; + lvi.ForeColor = lv.ForeColor; + } else { + lvi.ForeColor = lv.ForeColor; + if (line.BackgroundColor.ToArgb() == 0) { + lvi.BackColor = lv.BackColor; + } else { + // Highlight the entire line. + lvi.BackColor = line.BackgroundColor; + } + } + + e.DrawBackground(); + + if ((e.State & ListViewItemStates.Selected) != 0) { + e.DrawFocusRectangle(); + } + + Rectangle rect = new Rectangle(e.Bounds.X + 3 + leftColsWidth, e.Bounds.Y + 2, + e.Bounds.Width - 3 - leftColsWidth, e.Bounds.Height - 2); + + TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.EndEllipsis | + TextFormatFlags.SingleLine; + + Font font = lv.Font; + TextRenderer.DrawText(e.Graphics, lvi.Text, font, rect, + lvi.ForeColor, flags); + } + + private void codeListView_DrawSubItem(object sender, + DrawListViewSubItemEventArgs e) { + // Draw the multi-column items here. + + ListView lv = e.Item.ListView; + ListViewItem lvi = e.Item; + + DisplayList.Line.Type lineType = mDisplayList[e.ItemIndex].LineType; + if (lineType == DisplayList.Line.Type.LongComment || + lineType == DisplayList.Line.Type.Note) { + return; + } + + // Set colors based on selection and focus. + if (lvi.Selected && lv.Focused) { + e.SubItem.BackColor = SystemColors.Highlight; + e.SubItem.ForeColor = lv.BackColor; + } else if (lvi.Selected && !lv.Focused) { + e.SubItem.BackColor = SystemColors.Control; + e.SubItem.ForeColor = lv.ForeColor; + } else { + if (e.ItemIndex == mTargetHighlightIndex && + (e.ColumnIndex == (int)ColumnIndex.Address || + e.ColumnIndex == (int)ColumnIndex.Label) && + !string.IsNullOrEmpty(e.SubItem.Text)) { + e.SubItem.BackColor = Color.LightBlue; + } else { + e.SubItem.BackColor = lv.BackColor; + } + e.SubItem.ForeColor = lv.ForeColor; + } + + e.DrawBackground(); + + // Shift the text so it lines up with the standard control at 10pts. + // Not strictly necessary, and possibly unwise, since the behavior seems + // to change for larger fonts. + Rectangle rect = new Rectangle(e.Bounds.X + 3, e.Bounds.Y + 2, + e.Bounds.Width - 3, e.Bounds.Height - 2); + + TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.EndEllipsis | + TextFormatFlags.SingleLine; + + Font font = lv.Font; + + TextRenderer.DrawText(e.Graphics, e.SubItem.Text, font, rect, + e.SubItem.ForeColor, flags); + + // Draw the focus rectangle. It's annoying that we have to draw it for every + // sub-item even with FullRowSelect, but DrawItem always happens first, and + // there's no equivalent at the end. (It's unclear how useful the focus rect + // actually is, but it's part of the standard dialog behavior.) + if (lv.FullRowSelect) { + e.DrawFocusRectangle(e.Item.Bounds); + } else { + e.DrawFocusRectangle(e.Bounds); + } + } + #endregion // codeListView OwnerDraw implementation + + + #region codeListView Virtual + // For a half-megabyte file, the ListViewItem creation could take 40+ seconds. + + // Internal array for holding temporary state. Avoids frequent allocations. + private ListViewItem.ListViewSubItem[] mSubArray = + new ListViewItem.ListViewSubItem[CodeListColumnWidths.NUM_COLUMNS - 1]; + + // Array of blank sub-items, for entries that span multiple columns. The + // virtual mode requires fully populating sub-items. + private static ListViewItem.ListViewSubItem[] mBlankArray = + new ListViewItem.ListViewSubItem[CodeListColumnWidths.NUM_COLUMNS - 1] { + new ListViewItem.ListViewSubItem(), + new ListViewItem.ListViewSubItem(), + new ListViewItem.ListViewSubItem(), + new ListViewItem.ListViewSubItem(), + new ListViewItem.ListViewSubItem(), + new ListViewItem.ListViewSubItem(), + new ListViewItem.ListViewSubItem(), + new ListViewItem.ListViewSubItem() + }; + + /// + /// Cache of previously-constructed ListViewItems. The ListView will request items + /// continuously as they are moused-over, so this is fairly important. + /// + private ListViewItem[] mItemCache; + private int mItemCacheFirst; + + /// + /// Clears the contents of the ListViewItem cache. Do this whenever the backing + /// store is updated. + /// + private void ClearCodeListViewCache() { + mItemCache = null; + mItemCacheFirst = -1; + } + + //private ListViewItem mDummy; + private void codeListView_RetrieveVirtualItem(object sender, + RetrieveVirtualItemEventArgs e) { + //Debug.WriteLine("Retrieve " + e.ItemIndex); + //if (mDummy == null) { + // mDummy = new ListViewItem(); + // mDummy.Text = "dummy"; + // mDummy.SubItems.AddRange(mBlankArray); + //} + //e.Item = mDummy; + //return; + + // Is item cached? + if (mItemCache != null && e.ItemIndex >= mItemCacheFirst && + e.ItemIndex < mItemCacheFirst + mItemCache.Length) { + // Yes, return existing item. + e.Item = mItemCache[e.ItemIndex - mItemCacheFirst]; + } else { + // No, create item. + e.Item = CreateCodeListViewItem(e.ItemIndex); + } + } + + private void codeListView_CacheVirtualItems(object sender, CacheVirtualItemsEventArgs e) { + if (mItemCache != null && e.StartIndex >= mItemCacheFirst && + e.EndIndex <= mItemCacheFirst + mItemCache.Length) { + // Already have this span cached. + //Debug.WriteLine("LVICache " + e.StartIndex + " - " + e.EndIndex + + // " already cached"); + return; + } + + //Debug.WriteLine("LVICache " + e.StartIndex + " - " + e.EndIndex + " generating"); + mItemCacheFirst = e.StartIndex; + int len = e.EndIndex - e.StartIndex + 1; // end is inclusive + mItemCache = new ListViewItem[len]; + for (int i = 0; i < len; i++) { + mItemCache[i] = CreateCodeListViewItem(e.StartIndex + i); + } + } + + private ListViewItem CreateCodeListViewItem(int index) { + DisplayList.Line line = mDisplayList[index]; + DisplayList.FormattedParts parts = mDisplayList.GetFormattedParts(index); + ListViewItem lvi = new ListViewItem(); + + if (line.LineType == DisplayList.Line.Type.Blank) { + // no sub-items + lvi.Text = String.Empty; + lvi.SubItems.AddRange(mBlankArray); + } else if (line.LineType == DisplayList.Line.Type.LongComment || + line.LineType == DisplayList.Line.Type.Note) { + lvi.Text = parts.Comment.Replace("&", "&&"); + lvi.SubItems.AddRange(mBlankArray); + } else { + lvi.Text = parts.Offset; + mSubArray[0] = new ListViewItem.ListViewSubItem(lvi, parts.Addr); + mSubArray[1] = new ListViewItem.ListViewSubItem(lvi, parts.Bytes); + mSubArray[2] = new ListViewItem.ListViewSubItem(lvi, parts.Flags); + mSubArray[3] = new ListViewItem.ListViewSubItem(lvi, parts.Attr); + mSubArray[4] = new ListViewItem.ListViewSubItem(lvi, parts.Label); + mSubArray[5] = new ListViewItem.ListViewSubItem(lvi, parts.Opcode); + mSubArray[6] = new ListViewItem.ListViewSubItem(lvi, + parts.Operand.Replace("&", "&&")); + mSubArray[7] = new ListViewItem.ListViewSubItem(lvi, parts.Comment == null ? + string.Empty : parts.Comment.Replace("&", "&&")); + Debug.Assert(CodeListColumnWidths.NUM_COLUMNS - 1 == 8); + lvi.SubItems.AddRange(mSubArray); + } + return lvi; + } + + #endregion codeListView Virtual + + + #region symbolListView Virtual and UI handling + + /// + /// Cache of previously-constructed ListViewItems. The ListView will request items + /// continuously as they are moused-over, so this is fairly important. + /// + private ListViewItem[] mSymbolItemCache; + private int mSymbolItemCacheFirst; + + // Temporary array, used during ListViewItem creation. + private ListViewItem.ListViewSubItem[] mSymbolSubArray = + new ListViewItem.ListViewSubItem[2]; + + private string[] mSymbolColumnHeaderNames; + + private void InitSymbolListView() { + // Save a copy of the column header names as entered in the designer. + mSymbolColumnHeaderNames = new string[3]; + mSymbolColumnHeaderNames[0] = symbolTypeColumnHeader.Text; + mSymbolColumnHeaderNames[1] = symbolNameColumnHeader.Text; + mSymbolColumnHeaderNames[2] = symbolValueColumnHeader.Text; + SetSymbolColumnHeaders(); + } + + /// + /// Clears the contents of the ListViewItem cache. Do this whenever the backing + /// store is updated. + /// + private void ClearSymbolListViewCache() { + mSymbolItemCache = null; + mSymbolItemCacheFirst = -1; + } + + private void symbolListView_RetrieveVirtualItem(object sender, + RetrieveVirtualItemEventArgs e) { + // Is item cached? + if (mSymbolItemCache != null && e.ItemIndex >= mSymbolItemCacheFirst && + e.ItemIndex < mSymbolItemCacheFirst + mSymbolItemCache.Length) { + // Yes, return existing item. + e.Item = mSymbolItemCache[e.ItemIndex - mSymbolItemCacheFirst]; + } else { + // No, create item. + e.Item = CreateSymbolListViewItem(e.ItemIndex); + } + } + + private void symbolListView_CacheVirtualItems(object sender, CacheVirtualItemsEventArgs e) { + if (mSymbolItemCache != null && e.StartIndex >= mSymbolItemCacheFirst && + e.EndIndex <= mSymbolItemCacheFirst + mSymbolItemCache.Length) { + // Already have this span cached. + //Debug.WriteLine("LVICache " + e.StartIndex + " - " + e.EndIndex + + // " already cached"); + return; + } + + //Debug.WriteLine("LVICache " + e.StartIndex + " - " + e.EndIndex + " generating"); + mSymbolItemCacheFirst = e.StartIndex; + int len = e.EndIndex - e.StartIndex + 1; // end is inclusive + mSymbolItemCache = new ListViewItem[len]; + for (int i = 0; i < len; i++) { + mSymbolItemCache[i] = CreateSymbolListViewItem(e.StartIndex + i); + } + } + + private ListViewItem CreateSymbolListViewItem(int index) { + Symbol sym = mSymbolSubset.GetSubsetItem(index); + ListViewItem lvi = new ListViewItem(); + + lvi.Text = sym.SourceTypeString; + mSymbolSubArray[0] = new ListViewItem.ListViewSubItem(lvi, sym.Label); + mSymbolSubArray[1] = new ListViewItem.ListViewSubItem(lvi, + mOutputFormatter.FormatHexValue(sym.Value, 0)); + lvi.SubItems.AddRange(mSymbolSubArray); + return lvi; + } + + private void InvalidateSymbolListView() { + symbolListView.BeginUpdate(); + ClearSymbolListViewCache(); + symbolListView.VirtualListSize = mSymbolSubset.GetSubsetCount(); + symbolListView.EndUpdate(); + } + + // Column header click. Update sort. + private void symbolListView_ColumnClick(object sender, ColumnClickEventArgs e) { + //Debug.WriteLine("Click on " + e.Column); + + SymbolTableSubset.SortCol prevCol = mSymbolSubset.SortColumn; + // SortCol happens to match the ListView column numbers, so just cast it + SymbolTableSubset.SortCol clickCol = (SymbolTableSubset.SortCol)e.Column; + + if (prevCol == clickCol) { + mSymbolSubset.SortAscending = !mSymbolSubset.SortAscending; + } else { + mSymbolSubset.SortColumn = clickCol; + } + + SetSymbolColumnHeaders(); + InvalidateSymbolListView(); + } + + private void symbolListView_MouseDoubleClick(object sender, MouseEventArgs e) { + ListViewHitTestInfo info = symbolListView.HitTest(e.X, e.Y); + int row = info.Item.Index; + Symbol sym = mSymbolSubset.GetSubsetItem(row); + + if (sym.SymbolSource == Symbol.Source.Auto || sym.SymbolSource == Symbol.Source.User) { + int offset = mProject.FindLabelByName(sym.Label); + if (offset >= 0) { + GoToOffset(offset, false, true); + codeListView.Focus(); + } else { + Debug.WriteLine("DClick symbol: " + sym + ": label not found"); + } + } else { + Debug.WriteLine("DClick symbol: " + sym + ": not label"); + } + } + + /// + /// Sets the ListView column headers, adding a glyph to show sort direction. + /// Sadly, this is significantly easier than adding a graphic. + /// + private void SetSymbolColumnHeaders() { + SymbolTableSubset.SortCol sortCol = mSymbolSubset.SortColumn; + + // Pick a pair of symbols. + string sortStr = mSymbolSubset.SortAscending ? + "\u25b2" : "\u25bc"; // BLACK UP-POINTING TRIANGLE and DOWN- + //"\u2191" : "\u2193"; // UPWARDS ARROW and DOWNWARDS ARROW + //"\u2b06" : "\u2b07"; // UPWARDS BLACK ARROW and DOWNWARDS BLACK ARROW + //"\u234d" : "\u2354"; // APL FUNCTIONAL SYMBOL QUAD DELTA and ...QUAD DEL + + symbolTypeColumnHeader.Text = + (sortCol == SymbolTableSubset.SortCol.Type ? sortStr : "") + + mSymbolColumnHeaderNames[0]; + symbolNameColumnHeader.Text = + (sortCol == SymbolTableSubset.SortCol.Name ? sortStr : "") + + mSymbolColumnHeaderNames[1]; + symbolValueColumnHeader.Text = + (sortCol == SymbolTableSubset.SortCol.Value ? sortStr : "") + + mSymbolColumnHeaderNames[2]; + } + + private void symbolListView_ColumnWidthChanged(object sender, + ColumnWidthChangedEventArgs e) { + UpdateLastSymbolColumnWidth(); + } + private void symbolListView_SizeChanged(object sender, EventArgs e) { + UpdateLastSymbolColumnWidth(); + } + private void UpdateLastSymbolColumnWidth() { + const int ADJ = 4; // fudge factor needed to prevent horizontal scrollbar + int leftWidths = symbolListView.Columns[0].Width + symbolListView.Columns[1].Width; + int lastWidth = symbolListView.Size.Width - leftWidths - ADJ; + if (lastWidth < 0) { + lastWidth = 0; + } + symbolListView.Columns[2].Width = lastWidth; + AppSettings.Global.Dirty = true; + } + + private void SerializeSymbolColumnWidths() { + int[] values = new int[] { + symbolListView.Columns[0].Width, + symbolListView.Columns[1].Width, + symbolListView.Columns[2].Width + }; + AppSettings.Global.SetString(AppSettings.SYMWIN_COL_WIDTHS, + TextUtil.SerializeIntArray(values)); + } + + private void DeserializeSymbolColumnWidths() { + string str = AppSettings.Global.GetString(AppSettings.SYMWIN_COL_WIDTHS, null); + if (!string.IsNullOrEmpty(str)) { + int[] values = TextUtil.DeserializeIntArray(str); + if (values.Length == symbolListView.Columns.Count) { + for (int i = 0; i < values.Length; i++) { + symbolListView.Columns[i].Width = values[i]; + } + } + } + // The updates should automatically trigger the last-column-width adjuster. + } + + private void symbolUserCheckBox_CheckedChanged(object sender, EventArgs e) { + mSymbolSubset.IncludeUserLabels = symbolUserCheckBox.Checked; + InvalidateSymbolListView(); + } + private void symbolProjectCheckBox_CheckedChanged(object sender, EventArgs e) { + mSymbolSubset.IncludeProjectSymbols = symbolProjectCheckBox.Checked; + InvalidateSymbolListView(); + } + private void symbolPlatformCheckBox_CheckedChanged(object sender, EventArgs e) { + mSymbolSubset.IncludePlatformSymbols = symbolPlatformCheckBox.Checked; + InvalidateSymbolListView(); + } + private void symbolAutoCheckBox_CheckedChanged(object sender, EventArgs e) { + mSymbolSubset.IncludeAutoLabels = symbolAutoCheckBox.Checked; + InvalidateSymbolListView(); + } + private void symbolAddrCheckBox_CheckedChanged(object sender, EventArgs e) { + mSymbolSubset.IncludeAddresses = symbolAddressCheckBox.Checked; + InvalidateSymbolListView(); + } + private void symbolConstantCheckBox_CheckedChanged(object sender, EventArgs e) { + mSymbolSubset.IncludeConstants = symbolConstantCheckBox.Checked; + InvalidateSymbolListView(); + } + #endregion symbolListView Virtual and UI handling + + + #region referencesListView stuff + + private ListViewItem.ListViewSubItem[] mXrefSubArray = + new ListViewItem.ListViewSubItem[2]; + + /// + /// Updates the "references" view to reflect the current selection. + /// + /// The number of references to any given address should be relatively small, and + /// won't change without a data refresh, so there's no need for virtual items + /// or output caching. + /// + private void UpdateReferenceView() { + referencesListView.BeginUpdate(); + try { + referencesListView.Items.Clear(); + + // Determine which line is selected. If it's not code, data, or an EQU + // directive, there's no data to populate. + ListView.SelectedIndexCollection sel = codeListView.SelectedIndices; + if (sel.Count != 1) { + return; + } + DisplayList.Line.Type type = mDisplayList[sel[0]].LineType; + if (type != DisplayList.Line.Type.Code && + type != DisplayList.Line.Type.Data && + type != DisplayList.Line.Type.EquDirective) { + // Code, data, and platform symbol EQUs have xrefs. + return; + } + + // Find the appropriate xref set. + int offset = mDisplayList[sel[0]].FileOffset; + XrefSet xrefs; + if (offset < 0) { + int index = DisplayList.DefSymIndexFromOffset(offset); + DefSymbol defSym = mProject.ActiveDefSymbolList[index]; + xrefs = defSym.Xrefs; + } else { + xrefs = mProject.GetXrefSet(offset); + } + if (xrefs == null || xrefs.Count == 0) { + return; + } + + Asm65.Formatter formatter = mOutputFormatter; + bool showBank = !mProject.CpuDef.HasAddr16; + for (int i = 0; i < xrefs.Count; i++) { + XrefSet.Xref xr = xrefs[i]; + ListViewItem lvi = new ListViewItem(); + + string typeStr; + switch (xr.Type) { + case XrefSet.XrefType.BranchOperand: + typeStr = "branch "; + break; + case XrefSet.XrefType.InstrOperand: + typeStr = "instr "; + break; + case XrefSet.XrefType.DataOperand: + typeStr = "data "; + break; + default: + Debug.Assert(false); + typeStr = "??? "; + break; + } + + lvi.Text = formatter.FormatOffset24(xr.Offset); + mXrefSubArray[0] = new ListViewItem.ListViewSubItem(lvi, + formatter.FormatAddress(mProject.GetAnattrib(xr.Offset).Address, + showBank)); + mXrefSubArray[1] = new ListViewItem.ListViewSubItem(lvi, + (xr.IsSymbolic ? "Sym " : "Num ") + typeStr + + formatter.FormatAdjustment(-xr.Adjustment)); + lvi.SubItems.AddRange(mXrefSubArray); + + referencesListView.Items.Add(lvi); + } + } finally { + referencesListView.EndUpdate(); + } + } + + private void referencesListView_MouseDoubleClick(object sender, MouseEventArgs e) { + ListViewHitTestInfo info = referencesListView.HitTest(e.X, e.Y); + ListViewItem item = info.Item; + + // The easiest way to do this is to just parse it back out of the ListViewItem. + int offset; + try { + offset = Convert.ToInt32(item.Text.Substring(1), 16); + } catch (Exception ex) { + Debug.Assert(false, "Bad ref offset '" + item.Text + "': " + ex.Message); + return; + } + Debug.WriteLine("DClick refs, offset=+" + offset.ToString("x6")); + + // Jump to the note, and shift the focus back to the code view. + GoToOffset(offset, false, true); + codeListView.Focus(); + } + + private void referencesListView_ColumnWidthChanged(object sender, + ColumnWidthChangedEventArgs e) { + //Debug.WriteLine("CH: " + e.ColumnIndex + " " + + // referencesListView.Columns[e.ColumnIndex].Width); + UpdateLastReferencesColumnWidth(); + } + private void referencesListView_SizeChanged(object sender, EventArgs e) { + UpdateLastReferencesColumnWidth(); + } + private void UpdateLastReferencesColumnWidth() { + const int ADJ = 4; // fudge factor needed to prevent horizontal scrollbar + int leftWidths = referencesListView.Columns[0].Width + + referencesListView.Columns[1].Width; + int lastWidth = referencesListView.Size.Width - leftWidths - ADJ; + if (lastWidth < 0) { + lastWidth = 0; + } + referencesListView.Columns[2].Width = lastWidth; + AppSettings.Global.Dirty = true; + } + + private void SerializeReferencesColumnWidths() { + int[] values = new int[] { + referencesListView.Columns[0].Width, + referencesListView.Columns[1].Width, + referencesListView.Columns[2].Width + }; + AppSettings.Global.SetString(AppSettings.REFWIN_COL_WIDTHS, + TextUtil.SerializeIntArray(values)); + } + + private void DeserializeReferencesColumnWidths() { + string str = AppSettings.Global.GetString(AppSettings.REFWIN_COL_WIDTHS, null); + if (!string.IsNullOrEmpty(str)) { + int[] values = TextUtil.DeserializeIntArray(str); + if (values.Length == referencesListView.Columns.Count) { + for (int i = 0; i < values.Length; i++) { + referencesListView.Columns[i].Width = values[i]; + } + } + } + // The updates should automatically trigger the last-column-width adjuster. + } + + #endregion referencesListView stuff + + + #region notesListView Virtual and UI handling + + // I'm not expecting there to be a lot of notes, but making this virtual avoids + // having to update the item set when things change. (We could also just rebuild + // the item list after any change is applied... but if we do have a lot of notes, + // that could be much worse.) Only updating the list when the notes object changes + // would be optimal, but requires probing ChangeSets. This was easier. + + /// + /// Cache of previously-constructed ListViewItems. The ListView will request items + /// continuously as they are moused-over, so this is fairly important. + /// + private ListViewItem[] mNotesItemCache; + private int mNotesItemCacheFirst; + + // Temporary array, used during ListViewItem creation. + private ListViewItem.ListViewSubItem[] mNotesSubArray = + new ListViewItem.ListViewSubItem[1]; + + /// + /// Clears the contents of the ListViewItem cache. Do this whenever the backing + /// store is updated. + /// + private void ClearNotesListViewCache() { + mNotesItemCache = null; + mNotesItemCacheFirst = -1; + } + + private void notesListView_RetrieveVirtualItem(object sender, + RetrieveVirtualItemEventArgs e) { + // Is item cached? + if (mNotesItemCache != null && e.ItemIndex >= mNotesItemCacheFirst && + e.ItemIndex < mNotesItemCacheFirst + mNotesItemCache.Length) { + // Yes, return existing item. + e.Item = mNotesItemCache[e.ItemIndex - mNotesItemCacheFirst]; + } else { + // No, create item. + e.Item = CreateNotesListViewItem(e.ItemIndex); + } + } + + private void notesListView_CacheVirtualItems(object sender, CacheVirtualItemsEventArgs e) { + if (mNotesItemCache != null && e.StartIndex >= mNotesItemCacheFirst && + e.EndIndex <= mNotesItemCacheFirst + mNotesItemCache.Length) { + // Already have this span cached. + return; + } + + mNotesItemCacheFirst = e.StartIndex; + int len = e.EndIndex - e.StartIndex + 1; // end is inclusive + mNotesItemCache = new ListViewItem[len]; + for (int i = 0; i < len; i++) { + mNotesItemCache[i] = CreateNotesListViewItem(e.StartIndex + i); + } + } + + private ListViewItem CreateNotesListViewItem(int index) { + int offset = mProject.Notes.Keys[index]; + MultiLineComment mlc = mProject.Notes.Values[index]; + ListViewItem lvi = new ListViewItem(); + + lvi.Text = mOutputFormatter.FormatOffset24(offset); + lvi.Tag = mlc; + mNotesSubArray[0] = new ListViewItem.ListViewSubItem(lvi, + mlc.Text.Replace("\r\n", " \u2022 ")); + lvi.SubItems.AddRange(mNotesSubArray); + return lvi; + } + + private void InvalidateNotesListView() { + notesListView.BeginUpdate(); + ClearNotesListViewCache(); + if (mProject == null) { + notesListView.VirtualListSize = 0; + } else { + notesListView.VirtualListSize = mProject.Notes.Count; + } + notesListView.EndUpdate(); + } + + private void notesListView_MouseDoubleClick(object sender, MouseEventArgs e) { + ListViewHitTestInfo info = notesListView.HitTest(e.X, e.Y); + int row = info.Item.Index; + int offset = mProject.Notes.Keys[row]; + Debug.WriteLine("DClick Notes row=" + row + " offset=+" + offset.ToString("x6")); + + // Jump to the note, and shift the focus back to the code view. + GoToOffset(offset, true, true); + codeListView.Focus(); + } + + private void notesListView_DrawColumnHeader(object sender, + DrawListViewColumnHeaderEventArgs e) { + + ListView lv = e.Header.ListView; + string text = lv.Columns[e.ColumnIndex].Text; + + // Adjust rect to match standard control for 10pt fonts, and + // reserve a couple pixels at the far right end for the separator. + Rectangle rect = new Rectangle(e.Bounds.X + 3, e.Bounds.Y + 4, + e.Bounds.Width - 4, e.Bounds.Height - 4); + TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.EndEllipsis | + TextFormatFlags.SingleLine; + + TextRenderer.DrawText(e.Graphics, text, lv.Font, rect, lv.ForeColor, flags); + + Pen pen = new Pen(Color.LightGray); + e.Graphics.DrawLine(pen, e.Bounds.X + e.Bounds.Width - 1, e.Bounds.Y, + e.Bounds.X + e.Bounds.Width - 1, e.Bounds.Y + e.Bounds.Height); + } + + private void notesListView_DrawItem(object sender, + DrawListViewItemEventArgs e) { + // We have no full-width items. + } + + private void notesListView_DrawSubItem(object sender, + DrawListViewSubItemEventArgs e) { + // Draw the multi-column items here. + + ListView lv = e.Item.ListView; + ListViewItem lvi = e.Item; + MultiLineComment mlc = (MultiLineComment) lvi.Tag; + + // Set colors based on selection and focus. + if (lvi.Selected && lv.Focused && e.ColumnIndex == 0) { + e.SubItem.BackColor = SystemColors.Highlight; + e.SubItem.ForeColor = codeListView.BackColor; + } else { + if (e.ColumnIndex == 1 && mlc.BackgroundColor.ToArgb() != 0) { + e.SubItem.BackColor = mlc.BackgroundColor; + } else { + e.SubItem.BackColor = lv.BackColor; + } + e.SubItem.ForeColor = lv.ForeColor; + } + + e.DrawBackground(); + + // Shift the text so it lines up with the standard control at 10pts. + // Not strictly necessary, and possibly unwise, since the behavior seems + // to change for larger fonts. + Rectangle rect = new Rectangle(e.Bounds.X + 3, e.Bounds.Y + 2, + e.Bounds.Width - 3, e.Bounds.Height - 2); + + TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.EndEllipsis | + TextFormatFlags.SingleLine; + + Font font = lv.Font; + + // TODO(maybe): consider drawing the note text with a proportional font. We + // don't need multi-line stuff to line up, and it'll let us show more of + // the text in a narrow window. + TextRenderer.DrawText(e.Graphics, e.SubItem.Text, font, rect, + e.SubItem.ForeColor, flags); + + // Draw the focus rectangle. It's annoying that we have to draw it for every + // sub-item even with FullRowSelect, but DrawItem always happens first, and + // there's no equivalent at the end. (It's unclear how useful the focus rect + // actually is, but it's part of the standard dialog behavior.) + if (lv.FullRowSelect) { + e.DrawFocusRectangle(e.Item.Bounds); + } else { + e.DrawFocusRectangle(e.Bounds); + } + } + + private void notesListView_ColumnWidthChanged(object sender, + ColumnWidthChangedEventArgs e) { + // We don't auto-adjust column widths, but we do want to make sure that the + // width adjustment causes the settings to be saved. + AppSettings.Global.Dirty = true; + } + + private void SerializeNotesColumnWidths() { + int[] values = new int[] { + notesListView.Columns[0].Width, + notesListView.Columns[1].Width + }; + AppSettings.Global.SetString(AppSettings.NOTEWIN_COL_WIDTHS, + TextUtil.SerializeIntArray(values)); + } + + private void DeserializeNotesColumnWidths() { + string str = AppSettings.Global.GetString(AppSettings.NOTEWIN_COL_WIDTHS, null); + if (!string.IsNullOrEmpty(str)) { + int[] values = TextUtil.DeserializeIntArray(str); + if (values.Length == notesListView.Columns.Count) { + for (int i = 0; i < values.Length; i++) { + notesListView.Columns[i].Width = values[i]; + } + } + } + } + + #endregion // notesListView Virtual and UI handling + + + #region Info view + + /// + /// Updates the Info window for the current selection. + /// + private void UpdateInfoView() { + // I'm seeing weird behavior where, if you do a bunch of up-arrow/down-arrow + // movement in the main window, the info window will eventually freeze. You can + // refresh individual lines by dragging across them with the mouse. Resizing + // the splitter pane leaves the window apparently blank, but moving or resizing + // the application window causes an immediate redraw. + // + // Simply calling Invalidate() didn't help, which shouldn't be too surprising + // since I expect assignment to the Text field to do something similar. Refresh() + // feels a bit heavy-handed but it gets the job done. + // + // I see similar behavior from RichTextBox. The References ListView, which is + // also updated when the selection changes, seems to work correctly though. + // + // This is on Win10 Pro x64, as of 2018/07/28. + DoUpdateInfoView(); + infoTextBox.Refresh(); + } + private void DoUpdateInfoView() { + ListView.SelectedIndexCollection sel = codeListView.SelectedIndices; + if (sel.Count != 1) { + // Nothing selected, or multiple lines selected. + // Calling here from SelectedIndexChanged events works fine for single-item + // selections, but the SelectedIndices list is always empty for multi-select. + // If we add this to other selection events it ends up getting called 3-4 + // times when you arrow around. + + //infoRichTextBox.Text = "(" + + // string.Format(Properties.Resources.FMT_LINES_SELECTED, sel.Count) + ")"; + infoTextBox.Text = string.Empty; + return; + } + int lineIndex = sel[0]; + DisplayList.Line line = mDisplayList[lineIndex]; + StringBuilder sb = new StringBuilder(250); + + // NOTE: this should be made easier to localize + string lineTypeStr; + string extraStr = string.Empty; + switch (line.LineType) { + case DisplayList.Line.Type.Code: + lineTypeStr = "code"; + break; + case DisplayList.Line.Type.Data: + if (mProject.GetAnattrib(line.FileOffset).IsInlineData) { + lineTypeStr = "inline data"; + } else { + lineTypeStr = "data"; + } + break; + case DisplayList.Line.Type.LongComment: + lineTypeStr = "comment"; + break; + case DisplayList.Line.Type.Note: + lineTypeStr = "note"; + break; + case DisplayList.Line.Type.Blank: + lineTypeStr = "blank line"; + //lineTypeStr = "blank line (+" + + // mOutputFormatter.FormatOffset24(line.FileOffset) + ")"; + break; + case DisplayList.Line.Type.OrgDirective: + lineTypeStr = "address directive"; + break; + case DisplayList.Line.Type.RegWidthDirective: + lineTypeStr = "register width directive"; + break; + case DisplayList.Line.Type.EquDirective: { + lineTypeStr = "equate"; + int symIndex = DisplayList.DefSymIndexFromOffset(line.FileOffset); + DefSymbol defSym = mProject.ActiveDefSymbolList[symIndex]; + string sourceStr; + if (defSym.SymbolSource == Symbol.Source.Project) { + sourceStr = "project symbol definition"; + } else if (defSym.SymbolSource == Symbol.Source.Platform) { + sourceStr = "platform symbol file"; + } else { + sourceStr = "???"; + } + extraStr = "Source: " + sourceStr; + } + break; + default: + lineTypeStr = "???"; + break; + } + + // For anything that isn't code or data, show something simple and bail. + if (line.OffsetSpan == 0) { + sb.AppendFormat(Properties.Resources.FMT_INFO_LINE_SUM_NON, + lineIndex, lineTypeStr); + if (!string.IsNullOrEmpty(extraStr)) { + sb.Append("\r\n\r\n"); + sb.Append(extraStr); + } + infoTextBox.Text = sb.ToString(); + return; + } + Debug.Assert(line.IsCodeOrData); + + Anattrib attr = mProject.GetAnattrib(line.FileOffset); + + // Show number of bytes of code/data. + if (line.OffsetSpan == 1) { + sb.AppendFormat(Properties.Resources.FMT_INFO_LINE_SUM_SINGULAR, + lineIndex, line.OffsetSpan, lineTypeStr); + } else { + sb.AppendFormat(Properties.Resources.FMT_INFO_LINE_SUM_PLURAL, + lineIndex, line.OffsetSpan, lineTypeStr); + } + sb.Append("\r\n"); + + if (!mProject.OperandFormats.TryGetValue(line.FileOffset, out FormatDescriptor dfd)) { + // No user-specified format, but there may be a generated format. + sb.AppendFormat(Properties.Resources.FMT_INFO_FD_SUM, + Properties.Resources.DEFAULT_VALUE); + if (attr.DataDescriptor != null) { + sb.Append(" ["); + sb.Append(attr.DataDescriptor.ToUiString()); + sb.Append("]"); + } + } else { + // User-specified operand format. + // If the descriptor has a weak reference to an unknown symbol, should we + // call that out here? + sb.AppendFormat(Properties.Resources.FMT_INFO_FD_SUM, dfd.ToUiString()); + } + sb.Append("\r\n"); + + // Debug only + //sb.Append("DEBUG: opAddr=" + attr.OperandAddress.ToString("x4") + + // " opOff=" + attr.OperandOffset.ToString("x4") + "\r\n"); + + sb.Append("\u2022Attributes:"); + if (attr.IsHinted) { + sb.Append(" Hinted("); + for (int i = 0; i < line.OffsetSpan; i++) { + switch (mProject.TypeHints[line.FileOffset + i]) { + case CodeAnalysis.TypeHint.Code: + sb.Append("C"); + break; + case CodeAnalysis.TypeHint.Data: + sb.Append("D"); + break; + case CodeAnalysis.TypeHint.InlineData: + sb.Append("I"); + break; + default: + break; + } + if (i > 8) { + sb.Append("..."); + break; + } + } + sb.Append(')'); + } + if (attr.IsEntryPoint) { + sb.Append(" EntryPoint"); + } + if (attr.IsBranchTarget) { + sb.Append(" BranchTarget"); + } + if (attr.DoesNotContinue) { + sb.Append(" NoContinue"); + } + if (attr.DoesNotBranch) { + sb.Append(" NoBranch"); + } + if (mProject.StatusFlagOverrides[line.FileOffset].AsInt != 0) { + sb.Append(" StatusFlags"); + } + sb.Append("\r\n\r\n"); + + if (attr.IsInstruction) { + Asm65.OpDef op = mProject.CpuDef.GetOpDef(mProject.FileData[line.FileOffset]); + + string shortDesc = mOpDesc.GetShortDescription(op.Mnemonic); + if (!string.IsNullOrEmpty(shortDesc)) { + if (op.IsUndocumented) { + sb.Append("\u23e9[*] "); + } else { + sb.Append("\u23e9 "); + } + sb.Append(shortDesc); + string addrStr = mOpDesc.GetAddressModeDescription(op.AddrMode); + if (!string.IsNullOrEmpty(addrStr)) { + sb.Append(", "); + sb.Append(addrStr); + } + sb.Append("\r\n"); + } + + sb.Append("\u2022Cycles: "); + int cycles = op.Cycles; + Asm65.OpDef.CycleMod cycMods = op.CycleMods; + sb.Append(cycles.ToString()); + if (cycMods != 0) { + sb.Append(" ("); + int workBits = (int)cycMods; + while (workBits != 0) { + // Isolate rightmost bit. + int firstBit = (~workBits + 1) & workBits; + sb.Append(mOpDesc.GetCycleModDescription((OpDef.CycleMod)firstBit)); + // Remove from set. + workBits &= ~firstBit; + if (workBits != 0) { + // more to come + sb.Append(", "); + } + } + sb.Append(")"); + } + sb.Append("\r\n"); + + const string FLAGS = "NVMXDIZC"; + sb.Append("\u2022Flags affected: "); + Asm65.StatusFlags affectedFlags = op.FlagsAffected; + for (int i = 0; i < 8; i++) { + if (affectedFlags.GetBit((StatusFlags.FlagBits)(7 - i)) >= 0) { + sb.Append(' '); + sb.Append(FLAGS[i]); + } else { + sb.Append(" -"); + } + } + sb.Append("\r\n"); + + string longDesc = mOpDesc.GetLongDescription(op.Mnemonic); + if (!string.IsNullOrEmpty(longDesc)) { + sb.Append("\r\n"); + sb.Append(longDesc); + sb.Append("\r\n"); + } + } else { + // do we want descriptions of the pseudo-ops? + } + + + // Publish + infoTextBox.Text = sb.ToString(); + } + + #endregion // Info view + + + #region Tools items + + private void hexDumpToolStripMenuItem_Click(object sender, EventArgs e) { + OpenFileDialog fileDlg = new OpenFileDialog(); + fileDlg.Filter = Properties.Resources.FILE_FILTER_ALL; + fileDlg.FilterIndex = 1; + if (fileDlg.ShowDialog() != DialogResult.OK) { + return; + } + string fileName = fileDlg.FileName; + FileInfo fi = new FileInfo(fileName); + if (fi.Length > Tools.HexDumpViewer.MAX_LENGTH) { + string msg = string.Format(Properties.Resources.ERR_FILE_TOO_LARGE, + Tools.HexDumpViewer.MAX_LENGTH); + MessageBox.Show(msg, Properties.Resources.OPEN_DATA_FAIL_CAPTION, + MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + byte[] data; + try { + data = File.ReadAllBytes(fileName); + } catch (Exception ex) { + MessageBox.Show(ex.Message); + return; + } + + // Fire and forget. + Tools.HexDumpViewer dlg = new Tools.HexDumpViewer(data, mOutputFormatter); + dlg.Text = Path.GetFileName(fileName); + dlg.Show(); + } + + #endregion Tools items + + + #region Debug menu items + + private void dEBUGToolStripMenuItem_DropDownOpened(object sender, EventArgs e) { + toggleOwnerDrawToolStripMenuItem.Checked = codeListView.OwnerDraw; + toggleCommentRulersToolStripMenuItem.Checked = MultiLineComment.DebugShowRuler; + useKeepAliveHackToolStripMenuItem.Checked = ScriptManager.UseKeepAliveHack; + } + + private void reanalyzeToolStripMenuItem_Click(object sender, EventArgs e) { + Debug.WriteLine("Reanalyzing..."); + // Call through ApplyChanges so we update the timer task output. + UndoableChange uc = + UndoableChange.CreateDummyChange(UndoableChange.ReanalysisScope.CodeAndData); + ApplyChanges(new ChangeSet(uc), false); + UpdateMenuItemsAndTitle(); // in case something changed + } + + private Tools.ShowText mShowUndoRedoHistoryDialog; + + private void showUndoRedoHistoryToolStripMenuItem_Click(object sender, EventArgs e) { + // Show or hide the modeless dialog. + if (mShowUndoRedoHistoryDialog == null) { + Tools.ShowText dlg = new Tools.ShowText(); + dlg.Title = "Undo/Redo History"; + dlg.BodyText = mProject.DebugGetUndoRedoHistory(); + dlg.OnWindowClosing += (arg) => { + Debug.WriteLine("Undo/redo dialog closed"); + showUndoRedoHistoryToolStripMenuItem.Checked = false; + mShowUndoRedoHistoryDialog = null; + }; + dlg.Show(); + mShowUndoRedoHistoryDialog = dlg; + showUndoRedoHistoryToolStripMenuItem.Checked = true; + } else { + // Ask the dialog to close. Do the cleanup in the event. + mShowUndoRedoHistoryDialog.Close(); + } + } + + private Tools.ShowText mShowAnalyzerOutputDialog; + + private void showAnalyzerOutputToolStripMenuItem_Click(object sender, EventArgs e) { + // Show or hide the modeless dialog. + if (mShowAnalyzerOutputDialog == null) { + Tools.ShowText dlg = new Tools.ShowText(); + dlg.Title = "Analyzer Output"; + if (mGenerationLog == null) { + dlg.BodyText = "(no data yet)"; + } else { + dlg.BodyText = mGenerationLog.WriteToString(); + } + dlg.OnWindowClosing += (arg) => { + Debug.WriteLine("Analyzer output dialog closed"); + showAnalyzerOutputToolStripMenuItem.Checked = false; + mShowAnalyzerOutputDialog = null; + }; + dlg.Show(); + mShowAnalyzerOutputDialog = dlg; + showAnalyzerOutputToolStripMenuItem.Checked = true; + } else { + // Ask the dialog to close. Do the cleanup in the event. + mShowAnalyzerOutputDialog.Close(); + } + } + + private Tools.ShowText mShowAnalysisTimersDialog; + + private void showAnalysisTimersToolStripMenuItem_Click(object sender, EventArgs e) { + // Show or hide the modeless dialog. + if (mShowAnalysisTimersDialog == null) { + Tools.ShowText dlg = new Tools.ShowText(); + dlg.Title = "Analysis Timers"; + dlg.BodyText = "(no data yet)"; + dlg.OnWindowClosing += (arg) => { + Debug.WriteLine("Analysis timers dialog closed"); + showAnalysisTimersToolStripMenuItem.Checked = false; + mShowAnalysisTimersDialog = null; + }; + dlg.Show(); + mShowAnalysisTimersDialog = dlg; + showAnalysisTimersToolStripMenuItem.Checked = true; + } else { + // Ask the dialog to close. Do the cleanup in the event. + mShowAnalysisTimersDialog.Close(); + } + } + + private void toggleOwnerDrawToolStripMenuItem_Click(object sender, EventArgs e) { + Debug.WriteLine("TOGGLE OWNERDRAW"); + bool newState = !codeListView.OwnerDraw; + codeListView.OwnerDraw = newState; + notesListView.OwnerDraw = newState; + } + + private void toggleCommentRulersToolStripMenuItem_Click(object sender, EventArgs e) { + MultiLineComment.DebugShowRuler = !MultiLineComment.DebugShowRuler; + // Don't need to repeat the analysis, but we do want to save/restore the + // selection and top position when the comment fields change size. + UndoableChange uc = + UndoableChange.CreateDummyChange(UndoableChange.ReanalysisScope.DataOnly); + ApplyChanges(new ChangeSet(uc), false); + } + + private void useKeepAliveHackToolStripMenuItem_Click(object sender, EventArgs e) { + ScriptManager.UseKeepAliveHack = !ScriptManager.UseKeepAliveHack; + } + + private void sourceGenTestsToolStripMenuItem_Click(object sender, EventArgs e) { + Tests.GenTestRunner dlg = new Tests.GenTestRunner(); + dlg.ShowDialog(); + dlg.Dispose(); + } + + private void extensionScriptInfoToolStripMenuItem_Click(object sender, EventArgs e) { + string info = mProject.DebugGetLoadedScriptInfo(); + + Tools.ShowText dlg = new Tools.ShowText(); + dlg.Title = "Loaded Extension Script Info"; + dlg.BodyText = info; + dlg.ShowDialog(); + } + + #endregion Debug menu items + } +} diff --git a/SourceGen/AppForms/ProjectView.resx b/SourceGen/AppForms/ProjectView.resx new file mode 100644 index 0000000..666c9b1 --- /dev/null +++ b/SourceGen/AppForms/ProjectView.resx @@ -0,0 +1,319 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 132, 17 + + + 269, 17 + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAA8SURBVDhPY8AHOGPO/ocySQcgzWQbANNMlgHImkk2YJho + JhVTbgA2V4AFSQXD2BCoEOmAYgNAgLABDAwANqCmjcoYW+kAAAAASUVORK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAA5SURBVDhPYyAEOGPO/ocyyQMgAygyBGYA2YYgGzBcDUEX + JBVTbgApYCRqBgGKNIMARZpBAL9mBgYAlSmmjRN9gzIAAAAASUVORK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAERSURBVDhPrZDbSgJRGIXnpewd6jXsjSQvIrwoI0RQMChU + 0iiDPCGiE3ZCRkvR8VzTeBhnyR5/ccaZNnPhB4t9sdf6Ln5hb8QeathNJFVFKF5C8DqL4ksDVHWGDf7j + LHyPg6NjviSaFqlu5yQYR+KpupaIkrMknCxT3Y7v/NYYb0ITK1c3BarbWWhLQ7IR0cTKReyZ6lZ0XYei + ztHpK4bAc+h1FgQijzSxMptrGIxVSO0xX3AaStFki7bUMVFmaMm/eJMGfIH/MkGzLep0AXn4h/r3CJV3 + mS9gn2bY4UY/UzQ7E9TqfeTFtnuB+XAfzSHKr11kSl/uBebDiZ89ZCst3OUkdwL28sIVsE83ock+EIQV + 2Mz2wxeg6/UAAAAASUVORK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAJHSURBVDhPxZBdSNNhFMb/F110ZZEVhVBgeeHNICiiuggp + olAUyyxI0oSaH1QYC3N+tKnp5ubm1JUua5uuqdNKMwr7kApFItTUkWZqVhSVYmao5Nevvy7UoYR3HXh4 + 4XCe33nOKyy3lAY7l9RWMo0O/raWXxEyo5spVYTNvOGyfIRPfW+ptOkXqaPl6T83hcRmExSdgzAz3NVm + YWyoYla/B+1M9JtxWLPpaH22JORIjI6gKAMB0jyEimIdo4OlbuaprwVMOOMovammpDADc34qppwUrmnl + 5Kni3aFlFg2j3y1z5mnRTJccnNIltQhwq0jFry+mOXNtpWZWDx1Z1NhV3C3JwGFOw25SYjVe5oYhiUKd + HKMmwQUrMWUw/CF3NnZvvYKqUh1TvUroS3fXe7HXkwidMngTS2t5KLbregSzMY2f3Wr4qKW6LJvGR1rX + 0MLor8OhKYTJBn/GHvvxrliCTBrsOqXIoOBHh5K+hmSq7FqmexTQHuUytkaKxuNMNgYyVneA4Qd7GKjc + hjLaRzxH7gIU6JIZaEvgtk1D8wsxSWecCDgNzWFMvwxm/PkhRmr3Mli1nW9lvjRdWc0Jf+/5jzRmyWmv + S+GOLQu6U6BFjPvqKOP1AYw88WOoZif9DgmfLVtxaj1RSLdwNvrkPCA3M54KqxrnvRia9MKcGrUrqFOt + 5H7qKsqT1mGO9+Lqhc2ELdw+U/r0i+gVZ8hMiCDx3DHORwZyKnQ/hw/uYt9uCTskPvh6e7Fp41rWr/Fg + g6eHO+A/lyD8ARfG3mk9fv1YAAAAAElFTkSuQmCC + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIySURBVDhPrZLfS5NRGMfff6H7boIuuq2pMZyL1eAt11CW + DcOKsB9vpFmaLtNExco0av6CbIVLJ61Wk3BSkT/AFCkRZSpZmrmiJQ41xSaCwdfznL15XEUX0Reem5f3 + 8znnec4j/Zc8fxYGla91CS3eRTx0z6OpMYS7jmnU1X6B/VYA18snUVoyjsKCt8jLHcH5c36ouCQR2NUJ + 1Nas4G9ZXlmFKbULh1Kf8lJxSfI+WeCCyopv6q+/h+DQ/DJ2WV5Ao1FgPegRAveDOS4oLfmq/h6dn/DH + 4AJizD4UXJrCAUuzEDgbZrjgou2DiohshIcnQtgme5GTPYbkJKcQ1N8OckHW2REVi+RXuM8fxGaDG4oy + ALPZIQQ11Z+5QDk1oKJ/hjv7P2FTfCMOH3mFxMQ6IbhROYWOdrCnBI4dfwPr0V4+bRoY9UzXppMjcDdS + rC8hy3YhuFI2gTYf2A4Aza4f7N2/o/zaLB8qDYx6zszwr8P7k1thNFYIweXCMXgeAfedq2xxwjClZUeV + Jd2GtDNFETiJwfs8MBjKhMCWN8pgoLoqzE8miH1GjE7G4PsZjE7OQsm9ij2mFg7rdrug1xcJAa2l4w7W + r00Cgk/n38S7wBwC04u4UGxHrMHF4CbEJtyDLj5fCDIzhljfSxzeavRgyw4Zj9t64GvvQ0d3P3pfD2Kv + 2QqNvgFxDN6urYdWmyMElJMnevh60obRktA701PRtGlg1DOdSkXwzrisaMG/RZLWAE60OMW5fNhvAAAA + AElFTkSuQmCC + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIpSURBVDhPtZL/T1JRGMb5p1itrVZbbRpqZbawnBENV1I0 + jGlByTSyJTXJwq2oKZQb1KAv6JCYWSxvBrkkZUq4CeQEiRABFeLL072Xa0zRra31bO8v57zP5znnPYf1 + X+TxhWF6O7VtGYcnwbSWijKPOLzYrPSvLPwLS3huGUMlT7o9wGD9grVUBj+icdid03S9tDmgNxNwTgVQ + J+rA8XNtWwM+uuZATMwxmQVRycuJFNyzIRitDlScugKzjSgFRGJJaIwEsrk8AsHIhnSL/Ssck37UNipQ + I5DjtuYV7uksRYhr2kebhx2eP6nrycFIEh5fBA/1Nvru8q5+PDaOovK0rABwfwugWzcErfkzHhjsePL6 + E7q1VrTdNUDcrgGvSYlDZHN5XTNOnL8BVe8AJAoNDtZfLgDu9L1BPJmikzcrk81hlRwodZJwdBXziwnI + OrVoaOkiT8C8hKLHBPO7CbywOaE1jeC+bhAd6meQdvZC1KoG/5IS3MZ2HObLUHZSggvkWq3wOvbWiAqA + VpWeyStVfCUNf3AZ4zNhfHCFMEDMgye+hYr6FrDLzxQAUuVTpr0ocn74mchg5vsKRt1RcHp2Qv9+kZ78 + UcE17KkWFgHNN/uQzgBkGKLJPBZiecyGchjzrmFwPIF++xJUbDbUQzEacIArLpopSRSP4CUN1Obf1Abz + uqob5KjiXwWH/GVl5HPt5zZh37GL2H1EiF1VZ7GDI6CNW5r/TSzWbwHYL0mKJ5czAAAAAElFTkSuQmCC + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGDSURBVDhPrZFNSwJRGIX9NYGbFoUlFElY1EJQKEYhCJsi + LaVsERnRF5iCaSZJO1toCDVGFkgoFpWQWWRR2aIvUxm1BKN1wSnHCFw4TOCzue+9nPNw4eVVnav4Izzb + QfxeGZ5TWaxT/rK3irzmC7CsusvC1G4IkbNLboIiDieF4GGUKeTeClDpppF8eeEu2PIfwfrzizSdw3Hk + EnKlFpkMzV2wH77AosOFTV8A+vkl9CiHuJeLJNNZjM8tYWB0FkTvMAwmy/8ERTR6CwjlGAi1Ccence6C + 1NsXzN4PKIxJLLgeIJ2MoXvmFraNBKK3eXZRIveJPvs7FIYniEkXZENOdE+GIZ2Ko10TwLK7tJmKmL0F + EEYarYM+NMnt0C1sQzpx/lcSEnZ2gcKY/gs0dlmZuWvmjjmpwA1qxVp2AWFIMAF/OAGBzMjMI7ZrtJCb + 4Df3o4Zfxy7QrdxDRFKol5khkpR2H4qmIOzUQNBGwrsXYxccnNOQqNbQ0KGGZ+eEPVwdeLxvqqrf4wGh + TNAAAAAASUVORK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHkSURBVDhPvZHfS1NhHIf3p5QypLr2D4goMwoMCi/qIugH + Xe1Cr7qKDIMkZixwNhfWLGWbnuki0kXKzLU023KubBNPJrbRdOzocm6e2dPOO21mMS+CHvjcvOf9PF++ + 79H9M+7RT2iRRsIi9sEAXe43yAvf2LpSHq28G9uAnytNT4jMLewtcQ2Ht2pF8ps/aOt+gccX5lxD694S + +1BQFD1RkN5DSFa4Z3uONKbgHE3h8KZ4OJTC1J8UiSzmfhd2uf1CoJHbyKOsZokl0kKwm+aeJaov+wjO + rpQkVqdXfOz0bWAcVLghfaXxkUz3y2VxvpMGSwL3uMKh+gHezSSLEnNhX23vtYzKUirDfGyFj/Iy1mdx + UWqR8iKhwtQLxjgH659y4EwvVXWPiwJt3/Ws+muywRrlqvkDdx3zQrCN8l1ldnEd3/QqFmkS/akHJYGS + zjLzOUEwEsMf+sLI2zmaOou/93pPGoM5zvk7UU7fnBKxSBPoT7SXBNW1F/9Io2lKCNTCeomUyrS8xnBA + wfUqyf1eP5U1ptJD/o1LzeNCsHPydtqdr6k4aiwvOHvNSya3ibU/QIdrEkvfhJislc32MfYfuV1eUGPw + FF7bIVJVZ0N/soPK421UHGstlFvYd/hWecF/Qqf7CR0A5wwgSQA2AAAAAElFTkSuQmCC + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAJSSURBVDhPtZJrSJNRGMdf6IN9KbpQn/pUEH2JIoLqQ0Zh + FqYZRmJG1iKmUqKyLB2pqSm6vC1Nm5GXoeatEsVJ0RASR3eNzegikRq5lrV3857Fr/d9ddlICoL+8OfA + Oef/e57zcIT/os7WLMw302muSGJ2689qqi7A44q8IzjtNYzarzHQm8tZtT8FmRqu6LToMxN+B8qhCbGR + KVcDE85ajKUaxoaryEuL4UVXIudPB5Ko2oy98xjDptXERuz3hsgAOTzlqqMk6yjdllzE90UM9Wp5azlB + S1kwkeG+1CSv4mmBQPThfd6Ahqq8GYB4A11yBKmaMLQxoZyLDkGjDiZOFUhUuB+FsWsUQFiArzegtlzH + pFjPpMPA2GA2jucx2KqWK7ZWLqO7dBGP9D5KWLbfto3eAKMhi3FHBeP9GYy9PMXos4OIrYvJrzSRbWjm + wuV6EnVG4tLLiEzSExGf4w0oL05nZEDPaK+akceBuO9v4uPtFUrYo6npbzhdE/QPOQmNSiPouHYOUpaf + gvgqA/dDf9wd63G1r2SgUlAqyyq/1anYUGfG2mdXwne7bOwJUc1AinOS+NxzBpd5HWLbUhyNPvRdF5S2 + v05/54tbqvzBifWNHUvPOwLC4/CXwrv2HsB3+w6EwosJOB5ESeElfGpayGD1AmwlArHSm+W2PR1clToo + MrbT0mFTVtlbN6xFuJQar3wQz5Q9VksD+7XyPctrJdx4p5s605M5gKz8lJPSDwtGFbKboJ1blAN52vKb + PdXm80/AfDokTVu+8DfPXv9XCcIPTvjvLQ8YoakAAAAASUVORK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIVSURBVDhPtVJNaxNRFM1PyE+Yn1AUXLjK0uWgDWQZwUUX + KsGFBEEcCkIwqBEpGiydsSo2kupsasdo7Yi2toh0sFZjG5JpiZo20/TpVOmH5njvm8BYahEXHji8+968 + c+55l4n8F0zM+rhVWkHmdg29A/PoK1Yw8uIjOp/3xpvqBgrjLeilZbjNLXxZ34bwt6jexMVCGRndQenl + 0p+NWHzPXoP3rQ3bAbQhQM0E5Np2BKprbZzrm8TIs8puE+68+r0NwwZiacCwALEBCVcAqet8JlAjk1PZ + JzsNJt6u4+FMS3ZmMV9mmFNAMhesbBZLC6oFdOsd8oVXocmdx018Ej9k1FgqiJ0zgS6qlR6BVI4iEFRN + IJlxMF/1cfTMcGiQvbskB6ZqgairJ6BCTJKYu9tlAUW1oSRsNDwfB+JXQ4PzN6s07W0ZPxDS5aSgJEFn + 06Y9CaOqSauJRvMr9qmXQ4P8/RoWvU16eyBUEq5kbigwiKoOMTBQ0zbKlTq6TxihwejkZ1iOJwfEwmiC + BQ49yaW50J7Fh0xJw3IxbM3hwo2x0ICRHZzFgveTunYERK5lgo5YMxx8WPFw5Li+U8wYm66jNz+Naov+ + Beqiao58N5NrPluoryJO0QeKU7sNGKPPazh9aRzGo/eYmVvEMk270fTlmzl2N3XW9xL/jv7iaxw7+wAH + E9ew//AVxE8OItv/9O/Cf0ck8gud2vKswuxNZgAAAABJRU5ErkJggg== + + + + + AAABAAEAICAAAAEAGACoDAAAFgAAACgAAAAgAAAAQAAAAAEAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABKTUFQUkdxb2eDfnRbXFJnZ1xLTkFOUEJKTjtWWUhsaVxybWBraWJVVkpBRTZARTQ9QzhZV1NxbmZa + WE9UVUxESkU8QS48QDB0c29zcGdmaGB9eXBAQjY/Qjk/QjlQUk1SUk07PjU2OS81OC80Nyc2Oi13dG9S + VktqaWE4OSw/QTI+QDI8PzI9PjF3c2lcXVFLSz8/QjhISj4zNSc4OSx3dGxHS0JkZVleYlxeYlhYXlRg + YltrbGU8QDdeYVhLTUYaGBsfHCEmIiQsKS0jICQnIyknIykpKSckIygqJikuKi8wLDEuKS80MDUhHiEj + ICItKjEuKS41MDUpJCknIiguLDIgHB0bGSA5NS9RUEhPS0A2My0bFxwQDg8UExksKzIXFxsbGR8gHSUh + HiYiICctKjIqJy4YGRsdHSMnIioqJS0mIiouKTE/PEYfHCEWFhowLjgnIyslISknIisnIiksKzMgGyAm + JCwwLS80MjQzMTMoJikmIioPDRAWFBkjHyUUFBkaGR4iICgnJCshHiYrKjErKjMZGBocHSMrJi0rJy4o + JCstKTA5Nj8gHSIWFRsvLTcrKC8pJCsrJi4qJSwsLDQiHiQrJy81OC8/QzU7PzMgJR0oJS0REBMZGB0o + JygVFRobGR4hHyYlIysiICcmJCwtKzQcGh4aGiEwKzItKTAsKDAuKTI3Mz0hHiMWFRouKzMvKzMvKjMx + LTQpJCwsKjIlISYwKzMvMy04PTM2OjEhIx0pJy8SEBMdGiAsKywWFRkfHiUpJSsrKTAvLDMnJSwvLzgd + HSEbGyIyLjZAPEUpJS45NT01MjwhHyQXFxwtKjI/O0QvKzMyLTYtKDApJy8oJSorKDBER0NLUUtCSUQp + LSomJCsSERUeHCMyMTQTEhYcGyJmYm89OURFQkomIyo2NUAcHB8bGyIxLjU7OEM/PEZKR081MjskIScY + GB0yLjczMDg2Mjw5NT8uKjEpKTEoJCovLDQeIRchJRkYGxQMDwoqJi0UFBgdHCIjIiUSEhUiIScrJzA0 + Mjw9OUMnJCw7O0YcHCAeHiYqJi1IRU89O0U5N0M4NT4mIykZGB02Mzw5NTw6Nj81MToxLDUxMDkoIykw + Ljc3Mjc0LjQxKzIsJy0oJi0WFBcgHycqJSsSEhYoJy45NT0vLTY/O0clIyo6OUYfHyMjJCsrJy5GRFFA + PEhIRlE7OEImIigWFhw2Mzw3Mjw2MTo2MjstKDEyMjsnIicxLjY9Pzw8QDozNjQfISAqJy4XFRkkIysv + LjATExYtKzM3NDwsKTIlIisnJSw7O0ggHyIkJS0tKTIrJzAsKDAwLDQ7OEQoJSoXFxw3NT81MTg1MDo1 + MDkvKjMwMDgnIygwLDYaHhsjKiMjKCQVGBcpJS0ZFhofHiUlJSYQEBMoKDAnJCwnJCwnJC0lIyk7PEgf + HiInJy8uKTAwLDMwLDUyLTY7OUQqJisYGB06N0EzLjc2Mjo0LzcwLDQvLjYnIic1MTgjHyAkISEiHyAg + HCAmIikaFxwiISgjHyQZGRsbGiYcGSQlIi0nJS4eGiY1NUU8QD4dHioqJTAtKDUqJjIvKjQ6N0IuLDEc + HSQyLz00MDkrJzMwKzcrJzIsKTEuLDMoJDJgaGBQWFVNVlRDR0QbFyIfHiIUEh5HS0ouMjiOkFulpmm0 + sm47OzXFxXi8u2+anqbJyIGpqW+ko2eioHNXVkY7OTw5PDjJyHmqqmk9Ozy1s3GVl2Nsa1F6e1S6unPW + 1YBLT0W+vnOhpGw7QUDIyHlGTT+hpGk6QDw2Ozs2PUJlak/V1X99gVhjZktna1bIyajR0IyMj5TExY66 + u5Cxs6yFhoeZm4uvsYqOkp2XmaLV1IO5u4u/v57X1Yqcm5jBwnLCwaPQ0ZSrrHFaYEq9vXJITzWWml05 + QTM0OTqVlmKYmmZDSD9VV0a/v3a+vnVTWU3IyHw/RT2qq2uOkV6zs3HMy3g0ODnBwXOVl19ARj2wsW6Q + k2GFh1zGxndLT0RqbFBVWUeys3C5unNMUkfb2n6qrGiAg1A9RDZQUVJucE2ysm+urnhycGxeXlg5QD1I + UEwoLykxNzI7QThCRT0zNzQuMy8uNTFBRkhgYVplZmBARD1ITD5XWVhydFTDw3nBwnlBRUBTWlJfZl5Y + XlZ1dnRwcGZdX1ZJUEgPDAwmIyVQT1MwKzMXFBMZFRUzMTU/PT0hHiEjICIiHyAkICAdGhscGRwfHB4i + ICIkICMnJCQeGhogHB0pJSggHR0XExkWFBkdGhosKyVLS0MzMTEXExMSEREbGhtDREAzLzZIRUonKS4Q + ERo9Nz82Mj40MzskJCcrJy8mIisuKzAhHiQdGyIfGyMjICQjICkmIisuKS0rJy0kISofHSIeGyIlIiYf + HiYrKS4vLjAyNDYhICIkJTEpJi0XFyAaHRMwLTR8eWWxsWympmZJRUWwr3KzsnAqKDCjoWiurG5nZE+R + j2G3tXG0snBLR0K3tXGzsW43MzqgnWefnWZlYUyXlWEvKzKtq2onIiy3tm6trWseHSG3tnU3NTSFhlog + Ih8qJzDMyYo3OTSqqWZOSUs/PEeRkGJvbU/V1H5BPT++vHQoJDOxsG9BPT9bWEqGhGBoZU+EgmPOzXqR + jmFsalGioWkyLje6uXJdWkl6eVIeHyMICBS8u3M5NzSPj14RFA4uKjG5t32Skl9jZEVUTk7NzX9yclA4 + ODeYlmSNi2DFw3gnIzItKTXAvnVXU0vHxn2urGxoZVu1s3B0cVWOjGChoGiUkmTCwHY1MDfEw3aCglUV + Fhzg34STkWB7fFUfIhovKzJiYFe2tnCBgU9STU/V032vrmgvLTKysXChn2mEgVx1clajoWiwrm5YVEmF + glxKRkQ3Mz40MDtQTEYxLTpAPD9CPUI+OT8xLjYuKzNHSD0MDRm8unQ5ODcREhkoLCUoJSxQTVMpLjUN + EBhEQEo5NkIbHCgzNzsVFB0aGiAZGCAYFyAYGB8WFR8pKCc5OTIiISghICchICggHyYgHyYfHiYgHyYi + ICcdHCEtLjI4OzsYGiFWVUUiICsSFBopMSwoJy1VWV08QUQpLTE4Nj07O0E3PD4+REc9QkY9Q0YtLzEl + JyoxNjc6P0M6PkI5PD8zODorLzAoLS4rMDI1OjwyNjg1OTsmKSoqLC8oKywzODk1O0ItLDIgICgXGhsw + OTJFTEpkaXFBSUgfIyMICQgKDAopLjBFTkttc3xJUVEJDAkICwgLDgk2Pz4+REJKUVQrLiwVGBQNEQwn + LC02PjosNzg4PzsHCgYYGRsPERMkKCotMTAvNjAeIxoJDAcdJRVITExBR0g0Oj48Pj88PT0+Pz9AQkFR + V1hOVVU2PD9DREQ9Pj5TVVU8QT1UV1JCRkRFSElYWllJS0tJSko6REK3s7IxNzMyNTYqLS8vMjUfICI5 + Oz0uMiomKSQbHBwnLSZYWlxCRUgsMDRISU4iIiEuLS5JSkpbXl5DSEk8QkpTUVQQDw5CQUJQUlBjZmhG + TExGR0o4NzYgISJAQEI0OztxdnY1PDwtLjEKCQkdHiAjJSkuMTQ4PTsxNDMbGhsQFwdaYFhbZWJKVVM4 + Ozg3ODY0NDNLT0lxeHNXYl5NWVc0MzE5OTg3NjRtc21jbGZbZGFGS0cuLi0uLiwyNTRWYFteaWNXYlwc + HBwVFhcOEBEsMzRES0lJUkw/RT4UFBUuNDA9Pzo3OjomKiskJSEkJyMoKic8PDhLTU0xNTcpLC8qKyUj + JiEnKCJPUUs+QUA1OTosLSsiJSMnKScrLCo2ODg3OTo3OToWFxcQEhIODxAUFhYgICElKCchJCALDQw0 + NzdCRUI5Pj8sMjVNTEwdIy4iJy5PUE9KTk42PD41Oz1KTE0ZIjEsLjBUV1Y+Q0M7QUNAQkQ3PEEVHCQx + MzQ5Pj46P0A7Pj41NzkHCw8VGh0nKS0hJCYkKCcjJiQRERE2OTwuMjExNTYsMTM1OkEiMUEpOkpCQkFN + T1YzNzg3Oz5BU2ATHi46Rk9BRUQ0Ojs2Ojw3ODguQ1YfNEcsLTA2OzszOTk0ODkiJSkSITIhNkojJCYf + IiUgJSQeIiAYJzUzOUQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + + + \ No newline at end of file diff --git a/SourceGen/AppSettings.cs b/SourceGen/AppSettings.cs new file mode 100644 index 0000000..78ab6cc --- /dev/null +++ b/SourceGen/AppSettings.cs @@ -0,0 +1,320 @@ +/* + * Copyright 2018 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.Text; +using System.Web.Script.Serialization; + +namespace SourceGen { + /// + /// Application settings registry. This holds both user-accessible settings and saved + /// values like window widths. + /// + /// Everything is stored as name/value pairs, where the value is serialized as a string. + /// Names are case-sensitive. + /// + /// We don't discard things we don't recognize. If we somehow end up reading a config + /// file from a newer version of the app, the various settings will be retained. + /// + public class AppSettings { + #region Names + + // Name constants. Having them defined here avoids collisions and misspellings, and + // makes it easy to find all uses. + + // Main window. + public const string MAIN_WINDOW_WIDTH = "main-window-width"; + public const string MAIN_WINDOW_HEIGHT = "main-window-height"; + public const string MAIN_LEFT_SPLITTER_DIST = "main-left-splitter-dist"; + public const string MAIN_RIGHT_SPLITTER_DIST = "main-right-splitter-dist"; + public const string MAIN_LEFT_SIDE_SPLITTER_DIST = "main-left-side-splitter-dist"; + public const string MAIN_RIGHT_SIDE_SPLITTER_DIST = "main-right-side-splitter-dist"; + + // New project dialog. + public const string NEWP_SELECTED_SYSTEM = "newp-selected-system"; + + // Formatting choices. + public const string FMT_UPPER_HEX_DIGITS = "fmt-upper-hex-digits"; + public const string FMT_UPPER_OP_MNEMONIC = "fmt-upper-op-mnemonic"; + public const string FMT_UPPER_PSEUDO_OP_MNEMONIC = "fmt-upper-pseudo-op-mnemonic"; + public const string FMT_UPPER_OPERAND_A = "fmt-upper-operand-a"; + public const string FMT_UPPER_OPERAND_S = "fmt-upper-operand-s"; + public const string FMT_UPPER_OPERAND_XY = "fmt-upper-operand-xy"; + public const string FMT_ADD_SPACE_FULL_COMMENT = "fmt-add-space-full-comment"; + + public const string FMT_OPCODE_SUFFIX_ABS = "fmt-opcode-suffix-abs"; + public const string FMT_OPCODE_SUFFIX_LONG = "fmt-opcode-suffix-long"; + public const string FMT_OPERAND_PREFIX_ABS = "fmt-operand-prefix-abs"; + public const string FMT_OPERAND_PREFIX_LONG = "fmt-operand-prefix-long"; + public const string FMT_EXPRESSION_MODE = "fmt-expression-mode"; + + public const string FMT_PSEUDO_OP_NAMES = "fmt-pseudo-op-names"; + + public const string CLIP_LINE_FORMAT = "clip-line-format"; + + // Symbol-list window options. + public const string SYMWIN_SHOW_USER = "symwin-show-user"; + public const string SYMWIN_SHOW_AUTO = "symwin-show-auto"; + public const string SYMWIN_SHOW_PROJECT = "symwin-show-project"; + public const string SYMWIN_SHOW_PLATFORM = "symwin-show-platform"; + public const string SYMWIN_SHOW_CONST = "symwin-show-const"; + public const string SYMWIN_SHOW_ADDR = "symwin-show-addr"; + public const string SYMWIN_SORT_ASCENDING = "symwin-sort-ascending"; + public const string SYMWIN_SORT_COL = "symwin-sort-col"; + + public const string SYMWIN_COL_WIDTHS = "symwin-col-widths"; + + // References window options. + public const string REFWIN_COL_WIDTHS = "refwin-col-widths"; + + // Notes window options. + public const string NOTEWIN_COL_WIDTHS = "notewin-col-widths"; + + // Code List View settings. + public const string CDLV_COL_WIDTHS = "cdlv-col-widths"; + public const string CDLV_FONT = "cdlv-font"; + + // Hex dump viewer settings. + public const string HEXD_ASCII_ONLY = "hexd-ascii-only"; + public const string HEXD_CHAR_CONV = "hexd-char-conv"; + + // ASCII chart viewer settings. + public const string ASCCH_MODE = "ascch-mode"; + + // Source generation settings. + public const string SRCGEN_DEFAULT_ASM = "srcgen-default-asm"; + public const string SRCGEN_ADD_IDENT_COMMENT = "srcgen-add-ident-comment"; + public const string SRCGEN_DISABLE_LABEL_LOCALIZATION = "srcgen-disable-label-localization"; + public const string SRCGEN_LONG_LABEL_NEW_LINE = "srcgen-long-label-new-line"; + public const string SRCGEN_SHOW_CYCLE_COUNTS = "srcgen-show-cycle-counts"; + + // Main project view settings. + public const string PRVW_RECENT_PROJECT_LIST = "prvw-recent-project-list"; + + // cc65 assembler settings + public const string ASM_CC65_EXECUTABLE = "asm-cc65-executable"; + + // Merlin 32 assembler settings. + public const string ASM_MERLIN32_EXECUTABLE = "asm-merlin32-executable"; + + // Internal debugging features. + public const string DEBUG_MENU_ENABLED = "debug-menu-enabled"; + + #endregion Names + + #region Implementation + + // App settings file header. + public const string MAGIC = "### 6502bench SourceGen settings v1.0 ###"; + + + /// + /// Single global instance of app settings. + /// + public static AppSettings Global { + get { + return sSingleton; + } + } + private static AppSettings sSingleton = new AppSettings(); + + + /// + /// Dirty flag, set to true by every "set" call. + /// + public bool Dirty { get; set; } + + /// + /// Settings storage. + /// + private Dictionary mSettings = new Dictionary(); + + + private AppSettings() { } + + /// + /// Creates a copy of this object. + /// + /// + public AppSettings GetCopy() { + AppSettings copy = new AppSettings(); + //copy.mSettings.EnsureCapacity(mSettings.Count); + foreach (KeyValuePair kvp in mSettings) { + copy.mSettings.Add(kvp.Key, kvp.Value); + } + return copy; + } + + /// + /// Replaces the existing list of settings with a new list. + /// + /// This can be used to replace the contents of the global settings object without + /// discarding the object itself, which is useful in case something has cached a + /// reference to the singleton. + /// + /// + public void ReplaceSettings(AppSettings newSettings) { + // Clone the new list, and stuff it into the old object. This way the + // objects aren't sharing lists. + mSettings = newSettings.GetCopy().mSettings; + Dirty = true; + } + + /// + /// Merges settings from another settings object into this one. + /// + /// + /// + public void MergeSettings(AppSettings newSettings) { + foreach (KeyValuePair kvp in newSettings.mSettings) { + mSettings[kvp.Key] = kvp.Value; + } + Dirty = true; + } + + /// + /// Retrieves an integer setting. + /// + /// Setting name. + /// Setting default value. + /// The value found, or the default value if no setting with the specified + /// name exists, or the stored value is not an integer. + public int GetInt(string name, int defaultValue) { + if (!mSettings.TryGetValue(name, out string valueStr)) { + return defaultValue; + } + if (!int.TryParse(valueStr, out int value)) { + Debug.WriteLine("Warning: int parse failed on " + name + "=" + valueStr); + return defaultValue; + } + return value; + } + + /// + /// Sets an integer setting. + /// + /// Setting name. + /// Setting value. + public void SetInt(string name, int value) { + mSettings[name] = value.ToString(); + Dirty = true; + } + + /// + /// Retrieves a boolean setting. + /// + /// Setting name. + /// Setting default value. + /// The value found, or the default value if no setting with the specified + /// name exists, or the stored value is not a boolean. + public bool GetBool(string name, bool defaultValue) { + if (!mSettings.TryGetValue(name, out string valueStr)) { + return defaultValue; + } + if (!bool.TryParse(valueStr, out bool value)) { + Debug.WriteLine("Warning: bool parse failed on " + name + "=" + valueStr); + return defaultValue; + } + return value; + } + + /// + /// Sets a boolean setting. + /// + /// Setting name. + /// Setting value. + public void SetBool(string name, bool value) { + mSettings[name] = value.ToString(); + Dirty = true; + } + + /// + /// Retrieves a string setting. The default value will be returned if the key + /// is not found, or if the value is null. + /// + /// Setting name. + /// Setting default value. + /// The value found, or defaultValue if not value is found. + public string GetString(string name, string defaultValue) { + if (!mSettings.TryGetValue(name, out string valueStr) || valueStr == null) { + return defaultValue; + } + return valueStr; + } + + /// + /// Sets a string setting. + /// + /// Setting name. + /// Setting value. + public void SetString(string name, string value) { + if (value == null) { + mSettings.Remove(name); + } else { + mSettings[name] = value; + } + Dirty = true; + } + + /// + /// Serializes settings dictionary into a string, for saving settings to a file. + /// + /// Serialized settings. + public string Serialize() { + StringBuilder sb = new StringBuilder(1024); + 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 + + JavaScriptSerializer ser = new JavaScriptSerializer(); + string cereal = ser.Serialize(mSettings); + + // add some linefeeds to make it easier for humans + cereal = CommonUtil.TextUtil.NonQuoteReplace(cereal, ",\"", ",\r\n\""); + sb.Append(cereal); + + // Stick a linefeed at the end. + sb.Append("\r\n"); + return sb.ToString(); + } + + /// + /// Deserializes settings from a string, for loading settings from a file. + /// + /// Serialized settings. + /// Deserialized settings, or null if deserialization failed. + public static AppSettings Deserialize(string cereal) { + if (!cereal.StartsWith(MAGIC)) { + return null; + } + + // Skip past header. + cereal = cereal.Substring(MAGIC.Length); + + AppSettings settings = new AppSettings(); + JavaScriptSerializer ser = new JavaScriptSerializer(); + try { + settings.mSettings = ser.Deserialize>(cereal); + return settings; + } catch (Exception ex) { + Debug.WriteLine("Settings deserialization failed: " + ex.Message); + return null; + } + } + + #endregion Implementation + } +} diff --git a/SourceGen/AsmGen/AsmCc65.cs b/SourceGen/AsmGen/AsmCc65.cs new file mode 100644 index 0000000..e2f78ca --- /dev/null +++ b/SourceGen/AsmGen/AsmCc65.cs @@ -0,0 +1,121 @@ +/* + * Copyright 2018 faddenSoft + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; + +using CommonUtil; + +namespace SourceGen.AsmGen { + public class AsmCc65 : IAssembler { + private List PathNames { get; set; } + + private string WorkDirectory { get; set; } + + + // IAssembler + public AssemblerVersion QueryVersion() { + string exe = AppSettings.Global.GetString(AppSettings.ASM_CC65_EXECUTABLE, null); + if (string.IsNullOrEmpty(exe)) { + return null; + } + + ShellCommand cmd = new ShellCommand(exe, "--version", + System.IO.Directory.GetCurrentDirectory(), null); + cmd.Execute(); + if (string.IsNullOrEmpty(cmd.Stdout)) { + return null; + } + + // Windows - Stderr: "cl65.exe V2.17\r\n" + // Linux - Stderr: "cl65 V2.17 - Git N/A\n" + // Other platforms may not have the ".exe". Find first occurrence of " V". + + const string PREFIX = " V"; + string str = cmd.Stderr; + int start = str.IndexOf(PREFIX); + int end = (start < 0) ? -1 : str.IndexOfAny(new char[] { ' ', '\r', '\n' }, start + 1); + + if (start < 0 || end < 0 || start + PREFIX.Length >= end) { + Debug.WriteLine("Couldn't find version in " + str); + return null; + } + start += PREFIX.Length; + string versionStr = str.Substring(start, end - start); + CommonUtil.Version version = CommonUtil.Version.Parse(versionStr); + if (!version.IsValid) { + return null; + } + return new AssemblerVersion(versionStr, version); + } + + // IAssembler + public void Configure(List pathNames, string workDirectory) { + // Clone pathNames, in case the caller decides to modify the original. + PathNames = new List(pathNames.Count); + foreach (string str in pathNames) { + PathNames.Add(str); + } + + WorkDirectory = workDirectory; + } + + // IAssembler + public AssemblerResults RunAssembler(BackgroundWorker worker) { + // Reduce input file to a partial path if possible. This is really just to make + // what we display to the user a little easier to read. + string pathName = PathNames[0]; + if (pathName.StartsWith(WorkDirectory)) { + pathName = pathName.Remove(0, WorkDirectory.Length + 1); + } else { + // Unexpected, but shouldn't be a problem. + Debug.WriteLine("NOTE: source file is not in work directory"); + } + + //string home = AppSettings.Global.GetString(AppSettings.ASM_CC65_HOME, null); + //string cl65 = AppSettings.Global.GetString(AppSettings.ASM_CC65_EXECUTABLE, null); + //Debug.Assert(!string.IsNullOrEmpty(cl65)); + //Debug.Assert(!string.IsNullOrEmpty(home)); + + //Dictionary envVars = new Dictionary(1); + //envVars.Add("CC65_HOME", home); + + //string exePath = Path.Combine(home, cl65); + + string cl65 = AppSettings.Global.GetString(AppSettings.ASM_CC65_EXECUTABLE, null); + if (string.IsNullOrEmpty(cl65)) { + Debug.WriteLine("Assembler not configured"); + return null; + } + + // Wrap pathname in quotes in case it has spaces. + // (Do we need to shell-escape quotes in the pathName?) + ShellCommand cmd = new ShellCommand(cl65, + "--target none \"" + pathName + "\"", WorkDirectory, null); + cmd.Execute(); + + // Can't really do anything with a "cancel" request. + + // Output filename is the input filename without the ".S". Since the filename + // was generated by us we can be confident in the format. + string outputFile = PathNames[0].Substring(0, PathNames[0].Length - 2); + + return new AssemblerResults(cmd.FullCommandLine, cmd.ExitCode, cmd.Stdout, + cmd.Stderr, outputFile); + } + } +} diff --git a/SourceGen/AsmGen/AsmMerlin32.cs b/SourceGen/AsmGen/AsmMerlin32.cs new file mode 100644 index 0000000..61f187e --- /dev/null +++ b/SourceGen/AsmGen/AsmMerlin32.cs @@ -0,0 +1,110 @@ +/* + * Copyright 2018 faddenSoft + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; + +using CommonUtil; + +namespace SourceGen.AsmGen { + public class AsmMerlin32 : IAssembler { + private List PathNames { get; set; } + + private string WorkDirectory { get; set; } + + + // IAssembler + public AssemblerVersion QueryVersion() { + string exe = AppSettings.Global.GetString(AppSettings.ASM_MERLIN32_EXECUTABLE, null); + if (string.IsNullOrEmpty(exe)) { + return null; + } + + ShellCommand cmd = new ShellCommand(exe, string.Empty, + System.IO.Directory.GetCurrentDirectory(), null); + cmd.Execute(); + if (string.IsNullOrEmpty(cmd.Stdout)) { + return null; + } + + // Stdout: "C:\Src\WorkBench\Merlin32.exe v 1.0, (c) Brutal Deluxe ..." + // Other platforms may not have the ".exe". Find first occurrence of " v ". + + const string PREFIX = " v "; // not expecting this to appear in the path + string str = cmd.Stdout; + int start = str.IndexOf(PREFIX); + int end = (start < 0) ? -1 : str.IndexOf(',', start); + + if (start < 0 || end < 0 || start + PREFIX.Length >= end) { + Debug.WriteLine("Couldn't find version in " + str); + return null; + } + start += PREFIX.Length; + string versionStr = str.Substring(start, end - start); + CommonUtil.Version version = CommonUtil.Version.Parse(versionStr); + if (!version.IsValid) { + return null; + } + return new AssemblerVersion(versionStr, version); + } + + // IAssembler + public void Configure(List pathNames, string workDirectory) { + // Clone pathNames, in case the caller decides to modify the original. + PathNames = new List(pathNames.Count); + foreach (string str in pathNames) { + PathNames.Add(str); + } + + WorkDirectory = workDirectory; + } + + // IAssembler + public AssemblerResults RunAssembler(BackgroundWorker worker) { + // Reduce input file to a partial path if possible. This is really just to make + // what we display to the user a little easier to read. + string pathName = PathNames[0]; + if (pathName.StartsWith(WorkDirectory)) { + pathName = pathName.Remove(0, WorkDirectory.Length + 1); + } else { + // Unexpected, but shouldn't be a problem. + Debug.WriteLine("NOTE: source file is not in work directory"); + } + + string exe = AppSettings.Global.GetString(AppSettings.ASM_MERLIN32_EXECUTABLE, null); + if (string.IsNullOrEmpty(exe)) { + Debug.WriteLine("Assembler not configured"); + return null; + } + + // Wrap pathname in quotes in case it has spaces. + // (Do we need to shell-escape quotes in the pathName?) + ShellCommand cmd = new ShellCommand(exe, ". \"" + pathName + "\"", + WorkDirectory, null); + cmd.Execute(); + + // Can't really do anything with a "cancel" request. + + // Output filename is the input filename without the ".S". Since the filename + // was generated by us we can be confident in the format. + string outputFile = PathNames[0].Substring(0, PathNames[0].Length - 2); + + return new AssemblerResults(cmd.FullCommandLine, cmd.ExitCode, cmd.Stdout, + cmd.Stderr, outputFile); + } + } +} diff --git a/SourceGen/AsmGen/AssemblerInfo.cs b/SourceGen/AsmGen/AssemblerInfo.cs new file mode 100644 index 0000000..5b1c176 --- /dev/null +++ b/SourceGen/AsmGen/AssemblerInfo.cs @@ -0,0 +1,154 @@ +/* + * Copyright 2018 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; +using System.Collections.Generic; + +namespace SourceGen.AsmGen { + /// + /// Static information on assemblers supported by SourceGen. This is relevant for both + /// assembly source generation and assembler execution. Nothing here is affected + /// by whether or not the assembler in question is actually installed. + /// + public class AssemblerInfo { + /// + /// Enumeration of supported assemblers. Alphabetical order looks nicest. + /// + public enum Id { + Unknown = 0, + Cc65, + Merlin32 + } + + /// + /// Identifier. + /// + public Id AssemblerId { get; private set; } + + /// + /// Human-readable name. + /// + public string Name { get; private set; } + + + private AssemblerInfo(Id id, string name) { + AssemblerId = id; + Name = name; + } + + // For simplicity, this is 1:1 with the Id enum. + private static AssemblerInfo[] sInfo = new AssemblerInfo[] { + new AssemblerInfo(Id.Unknown, "???"), + new AssemblerInfo(Id.Cc65, "cc65"), + new AssemblerInfo(Id.Merlin32, "Merlin 32") + }; + + /// + /// Returns an AssemblerInfo object for the specified id. + /// + /// Assembler identifier. + /// Reference to AssemblerInfo object. + public static AssemblerInfo GetAssemblerInfo(Id id) { + return sInfo[(int)id]; + } + + /// + /// Generator factory method. + /// + /// ID of assembler to return generator object for. + /// New source generator object. + public static IGenerator GetGenerator(AssemblerInfo.Id id) { + switch (id) { + case Id.Cc65: + return new GenCc65(); + case Id.Merlin32: + return new GenMerlin32(); + default: + return null; + } + } + + /// + /// Assembler factory method. + /// + /// ID of assembler to return assembler object for. + /// New assembler interface object. + public static IAssembler GetAssembler(AssemblerInfo.Id id) { + switch (id) { + case Id.Cc65: + return new AsmCc65(); + case Id.Merlin32: + return new AsmMerlin32(); + default: + return null; + } + } + + /// + /// Provides a way to iterate through the set of known assemblers. This is probably + /// YAGNI -- we could just return the array -- but it would allow us to apply filters, + /// e.g. strip out assemblers that don't support 65816 code when that's the selected + /// CPU definition. + /// + private class AssemblerInfoIterator : IEnumerator { + private int mIndex = -1; + + public AssemblerInfo Current { + get { + if (mIndex < 0) { + // not started + return null; + } + return sInfo[mIndex]; + } + } + + object IEnumerator.Current { + get { + return Current; + } + } + + public void Dispose() { } + + public bool MoveNext() { + if (mIndex < 0) { + // skip element 0 (Unknown) + mIndex = 1; + } else { + mIndex++; + if (mIndex >= sInfo.Length) { + return false; + } + } + return true; + } + + public void Reset() { + mIndex = -1; + } + } + + public static IEnumerator GetInfoEnumerator() { + return new AssemblerInfoIterator(); + } + + + public override string ToString() { + return "Asm " + ((int)AssemblerId).ToString() + ": " + Name; + } + } +} diff --git a/SourceGen/AsmGen/AssemblerProgress.Designer.cs b/SourceGen/AsmGen/AssemblerProgress.Designer.cs new file mode 100644 index 0000000..7d2c523 --- /dev/null +++ b/SourceGen/AsmGen/AssemblerProgress.Designer.cs @@ -0,0 +1,113 @@ +/* + * Copyright 2018 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. + */ +namespace SourceGen.AsmGen { + partial class AssemblerProgress { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.progressLabel = new System.Windows.Forms.Label(); + this.progressBar1 = new System.Windows.Forms.ProgressBar(); + this.cancelButton = new System.Windows.Forms.Button(); + this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker(); + this.SuspendLayout(); + // + // progressLabel + // + this.progressLabel.AutoSize = true; + this.progressLabel.Location = new System.Drawing.Point(13, 13); + this.progressLabel.Name = "progressLabel"; + this.progressLabel.Size = new System.Drawing.Size(61, 13); + this.progressLabel.TabIndex = 1; + this.progressLabel.Text = "Preparing..."; + // + // progressBar1 + // + this.progressBar1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.progressBar1.Location = new System.Drawing.Point(12, 39); + this.progressBar1.Name = "progressBar1"; + this.progressBar1.Size = new System.Drawing.Size(600, 23); + this.progressBar1.Style = System.Windows.Forms.ProgressBarStyle.Marquee; + this.progressBar1.TabIndex = 2; + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.cancelButton.Location = new System.Drawing.Point(275, 79); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 0; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + // + // backgroundWorker1 + // + this.backgroundWorker1.WorkerReportsProgress = true; + this.backgroundWorker1.WorkerSupportsCancellation = true; + this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork); + this.backgroundWorker1.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(this.backgroundWorker1_ProgressChanged); + this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted); + // + // AssemblerProgress + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(624, 114); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.progressBar1); + this.Controls.Add(this.progressLabel); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "AssemblerProgress"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Assembling Source..."; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.AssemblerProgress_FormClosing); + this.Load += new System.EventHandler(this.AssemblerProgress_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label progressLabel; + private System.Windows.Forms.ProgressBar progressBar1; + private System.Windows.Forms.Button cancelButton; + private System.ComponentModel.BackgroundWorker backgroundWorker1; + } +} \ No newline at end of file diff --git a/SourceGen/AsmGen/AssemblerProgress.cs b/SourceGen/AsmGen/AssemblerProgress.cs new file mode 100644 index 0000000..62a1d85 --- /dev/null +++ b/SourceGen/AsmGen/AssemblerProgress.cs @@ -0,0 +1,124 @@ +/* + * Copyright 2018 faddenSoft + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Windows.Forms; + +namespace SourceGen.AsmGen { + /// + /// Dialog that shows the progress of the assembler, and allows cancellation. + /// + public partial class AssemblerProgress : Form { + /// + /// On success, assembler results will be here. + /// + public AssemblerResults Results { get; private set; } + + /// + /// Assembler executor. + /// + private IAssembler mAssembler; + + + /// + /// Constructor. + /// + /// Fully-configured source generator. + public AssemblerProgress(IAssembler asm) { + InitializeComponent(); + + mAssembler = asm; + } + + private void AssemblerProgress_Load(object sender, EventArgs e) { + backgroundWorker1.RunWorkerAsync(); + } + + private void cancelButton_Click(object sender, EventArgs e) { + backgroundWorker1.CancelAsync(); + cancelButton.Enabled = false; + + // We don't have a polite way to ask a shell command to stop, so we should + // kill the process here. Need to figure out how to do that. We don't need + // to clean up partial output. + } + + private void AssemblerProgress_FormClosing(object sender, FormClosingEventArgs e) { + // Strictly speaking, we should treat this as a cancel request, and set + // e.Cancel = true to prevent the form from closing until the assembler stops. + // However, we don't currently kill runaway processes, which would leave the + // user with no way to close the dialog, potentially requiring them to kill the + // entire app with unsaved work. Better to abandon the runaway process. + // + // We call CancelAsync so that the results are discarded should the worker + // eventually finish. + if (backgroundWorker1.IsBusy) { + backgroundWorker1.CancelAsync(); + DialogResult = DialogResult.Cancel; + } + } + + // NOTE: executes on work thread. DO NOT do any UI work here. DO NOT access + // the Results property directly. + private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { + BackgroundWorker worker = sender as BackgroundWorker; + + AssemblerResults results = mAssembler.RunAssembler(worker); + if (worker.CancellationPending) { + e.Cancel = true; + } else { + e.Result = results; + } + } + + // Callback that fires when a progress update is made. + private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) { + int percent = e.ProgressPercentage; + string msg = e.UserState as string; + + Debug.Assert(percent >= 0 && percent <= 100); + + if (!string.IsNullOrEmpty(msg)) { + progressLabel.Text = msg; + } + progressBar1.Value = percent; + } + + // Callback that fires when execution completes. + private void backgroundWorker1_RunWorkerCompleted(object sender, + RunWorkerCompletedEventArgs e) { + if (e.Cancelled) { + Debug.WriteLine("CANCELED"); + DialogResult = DialogResult.Cancel; + } else if (e.Error != null) { + // Unexpected -- shell command execution shouldn't throw exceptions. + MessageBox.Show(e.Error.ToString(), Properties.Resources.OPERATION_FAILED, + MessageBoxButtons.OK, MessageBoxIcon.Error); + DialogResult = DialogResult.Cancel; + } else { + // Make results available in properties. + Results = e.Result as AssemblerResults; + Debug.WriteLine("Asm complete, exit=" + Results.ExitCode); + DialogResult = DialogResult.OK; + } + + // Whatever the case, we're done. + this.Close(); + } + } +} diff --git a/SourceGen/AsmGen/AssemblerProgress.resx b/SourceGen/AsmGen/AssemblerProgress.resx new file mode 100644 index 0000000..59099f2 --- /dev/null +++ b/SourceGen/AsmGen/AssemblerProgress.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/SourceGen/AsmGen/AssemblerVersion.cs b/SourceGen/AsmGen/AssemblerVersion.cs new file mode 100644 index 0000000..f52a1ae --- /dev/null +++ b/SourceGen/AsmGen/AssemblerVersion.cs @@ -0,0 +1,104 @@ +/* + * Copyright 2018 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; + +namespace SourceGen.AsmGen { + public class AssemblerVersion { + /// + /// Version string reported by the assembler. + /// + public string VersionStr { get; private set; } + + /// + /// Version string converted to a Version object. For very complex version strings, + /// some information may be lost in the conversion. + /// + public CommonUtil.Version Version { get; private set; } + + //// Command pathname and modification date. Useful for caching values. + //private string ExeName { get; set; } + //private DateTime ExeModWhen { get; set; } + + public AssemblerVersion(string versionStr, CommonUtil.Version version) { + VersionStr = versionStr; + Version = version; + } + + public static AssemblerVersion GetVersion(AssemblerInfo.Id id) { + IAssembler asm = AssemblerInfo.GetAssembler(id); + if (asm == null) { + Debug.WriteLine("Assembler " + id + " not configured"); + return null; + } + return asm.QueryVersion(); + } + + public override string ToString() { + return "[" + VersionStr + "/" + Version + "]"; + } + } + + /// + /// Maintains a cache of the versions of installed assemblers. + /// + public static class AssemblerVersionCache { + private static Dictionary sVersions = + new Dictionary(); + private static bool sQueried = false; + + /// + /// Queries the versions from all known assemblers, replacing any previously held data. + /// + public static void QueryVersions() { + IEnumerator iter = AssemblerInfo.GetInfoEnumerator(); + while (iter.MoveNext()) { + AssemblerInfo.Id id = iter.Current.AssemblerId; + if (id == AssemblerInfo.Id.Unknown) { + continue; + } + + AssemblerVersion vers = null; + IAssembler asm = AssemblerInfo.GetAssembler(id); + if (asm != null) { + vers = asm.QueryVersion(); + } + + Debug.WriteLine("Asm version query: " + id + "=" + vers); + sVersions[id] = vers; + } + + sQueried = true; + } + + /// + /// Returns the version information, or null if the query failed for this assembler. + /// + /// Assembler identifier. + /// Version info. + public static AssemblerVersion GetVersion(AssemblerInfo.Id id) { + if (!sQueried) { + QueryVersions(); + } + if (sVersions.TryGetValue(id, out AssemblerVersion vers)) { + return vers; + } else { + return null; + } + } + } +} diff --git a/SourceGen/AsmGen/GenAndAsm.Designer.cs b/SourceGen/AsmGen/GenAndAsm.Designer.cs new file mode 100644 index 0000000..2110dc7 --- /dev/null +++ b/SourceGen/AsmGen/GenAndAsm.Designer.cs @@ -0,0 +1,298 @@ +/* + * Copyright 2018 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. + */ +namespace SourceGen.AsmGen { + partial class GenAndAsm { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.assemblerSettingsButton = new System.Windows.Forms.Button(); + this.previewFileComboBox = new System.Windows.Forms.ComboBox(); + this.previewFileLabel = new System.Windows.Forms.Label(); + this.workDirectoryTextBox = new System.Windows.Forms.TextBox(); + this.outputFileLabel = new System.Windows.Forms.Label(); + this.generateButton = new System.Windows.Forms.Button(); + this.previewTextBox = new System.Windows.Forms.TextBox(); + this.assemblerComboBox = new System.Windows.Forms.ComboBox(); + this.assemblerLabel = new System.Windows.Forms.Label(); + this.configureAsmLinkLabel = new System.Windows.Forms.LinkLabel(); + this.cmdOutputTextBox = new System.Windows.Forms.TextBox(); + this.runAssemblerButton = new System.Windows.Forms.Button(); + this.basePanel = new System.Windows.Forms.Panel(); + this.closeButton = new System.Windows.Forms.Button(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); + this.splitContainer1.Panel1.SuspendLayout(); + this.splitContainer1.Panel2.SuspendLayout(); + this.splitContainer1.SuspendLayout(); + this.basePanel.SuspendLayout(); + this.SuspendLayout(); + // + // splitContainer1 + // + this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer1.Location = new System.Drawing.Point(0, 0); + this.splitContainer1.Name = "splitContainer1"; + this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // splitContainer1.Panel1 + // + this.splitContainer1.Panel1.Controls.Add(this.assemblerSettingsButton); + this.splitContainer1.Panel1.Controls.Add(this.previewFileComboBox); + this.splitContainer1.Panel1.Controls.Add(this.previewFileLabel); + this.splitContainer1.Panel1.Controls.Add(this.workDirectoryTextBox); + this.splitContainer1.Panel1.Controls.Add(this.outputFileLabel); + this.splitContainer1.Panel1.Controls.Add(this.generateButton); + this.splitContainer1.Panel1.Controls.Add(this.previewTextBox); + this.splitContainer1.Panel1.Controls.Add(this.assemblerComboBox); + this.splitContainer1.Panel1.Controls.Add(this.assemblerLabel); + this.splitContainer1.Panel1MinSize = 150; + // + // splitContainer1.Panel2 + // + this.splitContainer1.Panel2.Controls.Add(this.configureAsmLinkLabel); + this.splitContainer1.Panel2.Controls.Add(this.cmdOutputTextBox); + this.splitContainer1.Panel2.Controls.Add(this.runAssemblerButton); + this.splitContainer1.Panel2MinSize = 100; + this.splitContainer1.Size = new System.Drawing.Size(784, 612); + this.splitContainer1.SplitterDistance = 400; + this.splitContainer1.TabIndex = 0; + // + // assemblerSettingsButton + // + this.assemblerSettingsButton.Location = new System.Drawing.Point(241, 10); + this.assemblerSettingsButton.Name = "assemblerSettingsButton"; + this.assemblerSettingsButton.Size = new System.Drawing.Size(75, 23); + this.assemblerSettingsButton.TabIndex = 3; + this.assemblerSettingsButton.Text = "Settings"; + this.assemblerSettingsButton.UseVisualStyleBackColor = true; + this.assemblerSettingsButton.Click += new System.EventHandler(this.assemblerSettingsButton_Click); + // + // previewFileComboBox + // + this.previewFileComboBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.previewFileComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.previewFileComboBox.FormattingEnabled = true; + this.previewFileComboBox.Items.AddRange(new object[] { + "SampleFile#031234_Merlin32.S"}); + this.previewFileComboBox.Location = new System.Drawing.Point(83, 58); + this.previewFileComboBox.Name = "previewFileComboBox"; + this.previewFileComboBox.Size = new System.Drawing.Size(262, 21); + this.previewFileComboBox.TabIndex = 5; + this.previewFileComboBox.SelectedIndexChanged += new System.EventHandler(this.previewFileComboBox_SelectedIndexChanged); + // + // previewFileLabel + // + this.previewFileLabel.AutoSize = true; + this.previewFileLabel.Location = new System.Drawing.Point(13, 61); + this.previewFileLabel.Name = "previewFileLabel"; + this.previewFileLabel.Size = new System.Drawing.Size(64, 13); + this.previewFileLabel.TabIndex = 4; + this.previewFileLabel.Text = "Preview file:"; + // + // workDirectoryTextBox + // + this.workDirectoryTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.workDirectoryTextBox.Location = new System.Drawing.Point(445, 59); + this.workDirectoryTextBox.Name = "workDirectoryTextBox"; + this.workDirectoryTextBox.ReadOnly = true; + this.workDirectoryTextBox.Size = new System.Drawing.Size(327, 20); + this.workDirectoryTextBox.TabIndex = 7; + this.workDirectoryTextBox.Text = "C:\\this\\that\\theother"; + // + // outputFileLabel + // + this.outputFileLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.outputFileLabel.AutoSize = true; + this.outputFileLabel.Location = new System.Drawing.Point(360, 62); + this.outputFileLabel.Name = "outputFileLabel"; + this.outputFileLabel.Size = new System.Drawing.Size(79, 13); + this.outputFileLabel.TabIndex = 6; + this.outputFileLabel.Text = "Work directory:"; + // + // generateButton + // + this.generateButton.Location = new System.Drawing.Point(363, 10); + this.generateButton.Name = "generateButton"; + this.generateButton.Size = new System.Drawing.Size(94, 23); + this.generateButton.TabIndex = 0; + this.generateButton.Text = "Generate"; + this.generateButton.UseVisualStyleBackColor = true; + this.generateButton.Click += new System.EventHandler(this.generateButton_Click); + // + // previewTextBox + // + this.previewTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.previewTextBox.BackColor = System.Drawing.SystemColors.Window; + this.previewTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.previewTextBox.Location = new System.Drawing.Point(13, 85); + this.previewTextBox.MaxLength = 0; + this.previewTextBox.Multiline = true; + this.previewTextBox.Name = "previewTextBox"; + this.previewTextBox.ReadOnly = true; + this.previewTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.previewTextBox.Size = new System.Drawing.Size(759, 303); + this.previewTextBox.TabIndex = 8; + // + // assemblerComboBox + // + this.assemblerComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.assemblerComboBox.FormattingEnabled = true; + this.assemblerComboBox.Items.AddRange(new object[] { + "Merlin32", + "CA65"}); + this.assemblerComboBox.Location = new System.Drawing.Point(83, 12); + this.assemblerComboBox.Name = "assemblerComboBox"; + this.assemblerComboBox.Size = new System.Drawing.Size(152, 21); + this.assemblerComboBox.TabIndex = 2; + this.assemblerComboBox.SelectedIndexChanged += new System.EventHandler(this.assemblerComboBox_SelectedIndexChanged); + // + // assemblerLabel + // + this.assemblerLabel.AutoSize = true; + this.assemblerLabel.Location = new System.Drawing.Point(13, 15); + this.assemblerLabel.Name = "assemblerLabel"; + this.assemblerLabel.Size = new System.Drawing.Size(58, 13); + this.assemblerLabel.TabIndex = 1; + this.assemblerLabel.Text = "Assembler:"; + // + // configureAsmLinkLabel + // + this.configureAsmLinkLabel.AutoSize = true; + this.configureAsmLinkLabel.Location = new System.Drawing.Point(124, 11); + this.configureAsmLinkLabel.Name = "configureAsmLinkLabel"; + this.configureAsmLinkLabel.Size = new System.Drawing.Size(126, 13); + this.configureAsmLinkLabel.TabIndex = 1; + this.configureAsmLinkLabel.TabStop = true; + this.configureAsmLinkLabel.Text = "Assembler not configured"; + this.configureAsmLinkLabel.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.configureAsmLinkLabel_LinkClicked); + // + // cmdOutputTextBox + // + this.cmdOutputTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.cmdOutputTextBox.BackColor = System.Drawing.SystemColors.Window; + this.cmdOutputTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cmdOutputTextBox.Location = new System.Drawing.Point(10, 36); + this.cmdOutputTextBox.MaxLength = 0; + this.cmdOutputTextBox.Multiline = true; + this.cmdOutputTextBox.Name = "cmdOutputTextBox"; + this.cmdOutputTextBox.ReadOnly = true; + this.cmdOutputTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.cmdOutputTextBox.Size = new System.Drawing.Size(762, 169); + this.cmdOutputTextBox.TabIndex = 2; + // + // runAssemblerButton + // + this.runAssemblerButton.Location = new System.Drawing.Point(10, 6); + this.runAssemblerButton.Name = "runAssemblerButton"; + this.runAssemblerButton.Size = new System.Drawing.Size(97, 23); + this.runAssemblerButton.TabIndex = 0; + this.runAssemblerButton.Text = "Run Assembler"; + this.runAssemblerButton.UseVisualStyleBackColor = true; + this.runAssemblerButton.Click += new System.EventHandler(this.runAssemblerButton_Click); + // + // basePanel + // + this.basePanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.basePanel.Controls.Add(this.splitContainer1); + this.basePanel.Location = new System.Drawing.Point(0, 0); + this.basePanel.Name = "basePanel"; + this.basePanel.Size = new System.Drawing.Size(784, 612); + this.basePanel.TabIndex = 0; + // + // closeButton + // + this.closeButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.closeButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.closeButton.Location = new System.Drawing.Point(697, 626); + this.closeButton.Name = "closeButton"; + this.closeButton.Size = new System.Drawing.Size(75, 23); + this.closeButton.TabIndex = 0; + this.closeButton.Text = "Close"; + this.closeButton.UseVisualStyleBackColor = true; + // + // GenAndAsm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.closeButton; + this.ClientSize = new System.Drawing.Size(784, 661); + this.Controls.Add(this.closeButton); + this.Controls.Add(this.basePanel); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.MinimumSize = new System.Drawing.Size(800, 600); + this.Name = "GenAndAsm"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Generate and Assemble"; + this.Load += new System.EventHandler(this.GenAndAsm_Load); + this.splitContainer1.Panel1.ResumeLayout(false); + this.splitContainer1.Panel1.PerformLayout(); + this.splitContainer1.Panel2.ResumeLayout(false); + this.splitContainer1.Panel2.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); + this.splitContainer1.ResumeLayout(false); + this.basePanel.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.SplitContainer splitContainer1; + private System.Windows.Forms.Panel basePanel; + private System.Windows.Forms.Button closeButton; + private System.Windows.Forms.Button generateButton; + private System.Windows.Forms.TextBox previewTextBox; + private System.Windows.Forms.ComboBox assemblerComboBox; + private System.Windows.Forms.Label assemblerLabel; + private System.Windows.Forms.TextBox cmdOutputTextBox; + private System.Windows.Forms.Button runAssemblerButton; + private System.Windows.Forms.TextBox workDirectoryTextBox; + private System.Windows.Forms.Label outputFileLabel; + private System.Windows.Forms.ComboBox previewFileComboBox; + private System.Windows.Forms.Label previewFileLabel; + private System.Windows.Forms.Button assemblerSettingsButton; + private System.Windows.Forms.LinkLabel configureAsmLinkLabel; + } +} \ No newline at end of file diff --git a/SourceGen/AsmGen/GenAndAsm.cs b/SourceGen/AsmGen/GenAndAsm.cs new file mode 100644 index 0000000..28faf60 --- /dev/null +++ b/SourceGen/AsmGen/GenAndAsm.cs @@ -0,0 +1,411 @@ +/* + * Copyright 2018 faddenSoft + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Text; +using System.Windows.Forms; + +namespace SourceGen.AsmGen { + public partial class GenAndAsm : Form { + private const int PREVIEW_BUF_SIZE = 64 * 1024; // 64KB should be enough for preview + private static string NO_PREVIEW_FILES = + "<" + Properties.Resources.NO_FILES_AVAILABLE + ">"; + + /// + /// Holds data for the preview combo box. + /// + private class ComboPath { + public string FileName { get; private set; } + public string PathName { get; private set; } + public ComboPath(string pathName) { + PathName = pathName; + if (string.IsNullOrEmpty(pathName)) { + FileName = NO_PREVIEW_FILES; + } else { + FileName = Path.GetFileName(pathName); + } + } + public override string ToString() { + return FileName; + } + } + + /// + /// Project view. Needed so we can pop open the assembler settings dialog. + /// + private AppForms.ProjectView mProjView; + + /// + /// Project with data. + /// + private DisasmProject mProject; + + /// + /// Directory where generated files and assembler output will go. + /// + private string mWorkDirectory; + + /// + /// Base file name. For example, if this is "GenFile", we might generate + /// "GenFile_Cc65.S". + /// + private string mBaseFileName; + + /// + /// Currently-selected assembler ID. + /// + private AssemblerInfo.Id mSelectedAssemblerId; + + /// + /// Results from last source generation. + /// + private List mGenerationResults; + + /// + /// Holds an item for the pick-your-assembler combox box. + /// + private class AsmComboItem { + public AssemblerInfo.Id AssemblerId { get; private set; } + public string Name { get; private set; } + public AssemblerVersion AsmVersion { get; private set; } + + public AsmComboItem(AssemblerInfo info, AssemblerVersion version) { + AssemblerId = info.AssemblerId; + Name = info.Name; + AsmVersion = version; + } + // This determines what the combo box shows. + public override string ToString() { + if (AsmVersion == null) { + return Name + " " + Properties.Resources.ASM_LATEST_VERSION; + } else { + return Name + " v" + AsmVersion.VersionStr; + } + } + } + + + /// + /// Constructor. + /// + /// Full path to the project file. + public GenAndAsm(AppForms.ProjectView projectView, DisasmProject project, + string projectPathName) { + InitializeComponent(); + + mProjView = projectView; + mProject = project; + mWorkDirectory = Path.GetDirectoryName(projectPathName); + mBaseFileName = Path.GetFileNameWithoutExtension(projectPathName); + + workDirectoryTextBox.Text = mWorkDirectory; + + // Make the splitter visible. This is a little tricky because the parent color + // affects the children as well. + // https://stackoverflow.com/a/22888877/294248 + this.splitContainer1.Panel1.BackColor = SystemColors.ControlDark; + this.splitContainer1.Panel2.BackColor = SystemColors.ControlDark; + this.splitContainer1.BackColor = SystemColors.ControlDark; + this.splitContainer1.Panel1.BackColor = SystemColors.Control; + this.splitContainer1.Panel2.BackColor = SystemColors.Control; + } + + private void GenAndAsm_Load(object sender, EventArgs e) { + // Try to select the previously-used asm format. + string defaultAsm = + AppSettings.Global.GetString(AppSettings.SRCGEN_DEFAULT_ASM, string.Empty); + PopulateAssemblerComboBox(defaultAsm); + + ResetElements(); + } + + /// + /// Populates the assembler combo box. Attempts to match the defaultAsm arg with + /// the entries to configure the initial value. + /// + private void PopulateAssemblerComboBox(string defaultAsm) { + //assemblerComboBox.DisplayMember = "Name"; // show this property + + assemblerComboBox.Items.Clear(); + IEnumerator iter = AssemblerInfo.GetInfoEnumerator(); + bool foundMatch = false; + while (iter.MoveNext()) { + AssemblerInfo info = iter.Current; + AssemblerVersion version = AssemblerVersionCache.GetVersion(info.AssemblerId); + AsmComboItem item = new AsmComboItem(info, version); + assemblerComboBox.Items.Add(item); + if (item.AssemblerId.ToString() == defaultAsm) { + Debug.WriteLine("matched current " + defaultAsm); + assemblerComboBox.SelectedItem = item; + foundMatch = true; + } + } + if (!foundMatch) { + // Need to do this or box will show empty. + assemblerComboBox.SelectedIndex = 0; + } + } + + private void assemblerComboBox_SelectedIndexChanged(object sender, EventArgs e) { + AsmComboItem sel = (AsmComboItem)assemblerComboBox.SelectedItem; + if (mSelectedAssemblerId != sel.AssemblerId) { + // Selection changed, discard window contents. + mSelectedAssemblerId = sel.AssemblerId; + AppSettings.Global.SetString(AppSettings.SRCGEN_DEFAULT_ASM, + mSelectedAssemblerId.ToString()); + ResetElements(); + } + } + + private void previewFileComboBox_SelectedIndexChanged(object sender, EventArgs e) { + ComboPath cpath = (ComboPath) previewFileComboBox.SelectedItem; + if (string.IsNullOrEmpty(cpath.PathName)) { + // nothing to do + return; + } + + previewTextBox.BackColor = SystemColors.Window; + previewTextBox.Enabled = true; + LoadPreviewFile(cpath.PathName); + } + + /// + /// Resets all of the active elements to the initial state, before any source code + /// was generated. + /// + private void ResetElements() { + mGenerationResults = null; + previewFileComboBox.Items.Clear(); + previewFileComboBox.Items.Add(new ComboPath(null)); + previewFileComboBox.SelectedIndex = 0; + + previewTextBox.Text = string.Empty; + previewTextBox.BackColor = SystemColors.Control; + previewTextBox.Enabled = false; + + cmdOutputTextBox.Text = string.Empty; + cmdOutputTextBox.BackColor = SystemColors.Control; + + UpdateAssemblerControls(); + } + + /// + /// Updates the controls in the lower (assembler) half of the dialog. + /// + private void UpdateAssemblerControls() { + bool asmConf = IsAssemblerConfigured(); + Debug.WriteLine("ID=" + mSelectedAssemblerId + " asmConf=" + asmConf); + configureAsmLinkLabel.Visible = !asmConf; + if (mGenerationResults == null || !asmConf) { + runAssemblerButton.Enabled = false; + } else { + runAssemblerButton.Enabled = true; + } + } + + /// + /// Returns true if the selected assembler has been configured. + /// + private bool IsAssemblerConfigured() { + string settingStr; + switch (mSelectedAssemblerId) { + case AssemblerInfo.Id.Cc65: + settingStr = AppSettings.ASM_CC65_EXECUTABLE; + break; + case AssemblerInfo.Id.Merlin32: + settingStr = AppSettings.ASM_MERLIN32_EXECUTABLE; + break; + default: + Debug.Assert(false); + return false; + } + return !string.IsNullOrEmpty(AppSettings.Global.GetString(settingStr, null)); + } + + private void assemblerSettingsButton_Click(object sender, EventArgs e) { + DoSettings(); + } + + private void configureAsmLinkLabel_LinkClicked(object sender, + LinkLabelLinkClickedEventArgs e) { + DoSettings(); + } + + /// + /// Configures assembler-specific items, such as the installation directory of the + /// assembler binary. + /// + private void DoSettings() { + // Pop open the app settings dialog, with the appropriate tab selected. + mProjView.ShowAppSettings(AppForms.EditAppSettings.Tab.Assembler); + + // Update the controls based on whether or not the assembler is now available. + UpdateAssemblerControls(); + AsmComboItem item = (AsmComboItem)assemblerComboBox.SelectedItem; + PopulateAssemblerComboBox(item.AssemblerId.ToString()); + } + + private void generateButton_Click(object sender, EventArgs e) { + IGenerator gen = AssemblerInfo.GetGenerator(mSelectedAssemblerId); + if (gen == null) { + Debug.WriteLine("Unable to get generator for " + mSelectedAssemblerId); + return; + } + gen.Configure(mProject, mWorkDirectory, mBaseFileName, + AssemblerVersionCache.GetVersion(mSelectedAssemblerId), AppSettings.Global); + + GeneratorProgress dlg = new GeneratorProgress(gen); + dlg.ShowDialog(); + Debug.WriteLine("Dialog returned: " + dlg.DialogResult); + + List pathNames = dlg.Results; + dlg.Dispose(); + + if (pathNames == null) { + // errors already reported + return; + } + + ResetElements(); + mGenerationResults = pathNames; + previewFileComboBox.Items.Clear(); + foreach (string str in pathNames) { + previewFileComboBox.Items.Add(new ComboPath(str)); + } + previewFileComboBox.SelectedIndex = 0; // should trigger update + + UpdateAssemblerControls(); + } + + private void LoadPreviewFile(string pathName) { + Debug.WriteLine("LOAD " + pathName); + + try { + using (StreamReader sr = new StreamReader(pathName, Encoding.UTF8)) { + char[] bigbuf = new char[PREVIEW_BUF_SIZE]; + int actual = sr.Read(bigbuf, 0, bigbuf.Length); + string str = CharArrayToLineNumberedString(bigbuf); + if (actual < PREVIEW_BUF_SIZE) { + previewTextBox.Text = str; + } else { + previewTextBox.Text = str + "\r\n" + + Properties.Resources.ERR_TOO_LARGE_FOR_PREVIEW; + } + } + } catch (Exception ex) { + previewTextBox.Text = ex.ToString(); + } + } + + /// + /// Converts a char[] to a string, inserting line numbers. + /// + /// + /// + private static string CharArrayToLineNumberedString(char[] data) { + StringBuilder sb = new StringBuilder(data.Length + data.Length / 40); + int lineStart = 0; + int lineNum = 0; + + for (int i = 0; i < data.Length; i++) { + if (data[i] == '\n') { + sb.AppendFormat("{0,4:D0} ", ++lineNum); + sb.Append(data, lineStart, i - lineStart + 1); + lineStart = i + 1; + } + } + + return sb.ToString(); + } + + private void runAssemblerButton_Click(object sender, EventArgs e) { + IAssembler asm = AssemblerInfo.GetAssembler(mSelectedAssemblerId); + if (asm == null) { + Debug.WriteLine("Unable to get assembler for " + mSelectedAssemblerId); + return; + } + + asm.Configure(mGenerationResults, mWorkDirectory); + AssemblerProgress dlg = new AssemblerProgress(asm); + dlg.ShowDialog(); + Debug.WriteLine("Dialog returned: " + dlg.DialogResult); + if (dlg.DialogResult != DialogResult.OK) { + // Cancelled, or failed to even run the assembler. + return; + } + + AssemblerResults results = dlg.Results; + if (results == null) { + Debug.WriteLine("Dialog returned OK, but no assembler results found"); + Debug.Assert(false); + return; + } + + StringBuilder sb = + new StringBuilder(results.Stdout.Length + results.Stderr.Length + 200); + sb.Append(results.CommandLine); + sb.Append("\r\n"); + sb.AppendFormat("ExitCode={0} - ", results.ExitCode); + if (results.ExitCode == 0) { + FileInfo fi = new FileInfo(results.OutputPathName); + if (!fi.Exists) { + MessageBox.Show(Properties.Resources.ASM_OUTPUT_NOT_FOUND, + Properties.Resources.ASM_MISMATCH_CAPTION, + MessageBoxButtons.OK, MessageBoxIcon.Error); + sb.Append(Properties.Resources.ASM_MATCH_FAILURE); + } else if (!CommonUtil.FileUtil.CompareBinaryFile(mProject.FileData, + results.OutputPathName, out int offset, out byte fileVal)) { + if (fi.Length != mProject.FileData.Length && + offset == fi.Length || offset == mProject.FileData.Length) { + // The files matched up to the point where one ended. + string msg = string.Format(Properties.Resources.ASM_MISMATCH_LENGTH_FMT, + fi.Length, mProject.FileData.Length); + MessageBox.Show(msg, Properties.Resources.ASM_MISMATCH_CAPTION, + MessageBoxButtons.OK, MessageBoxIcon.Error); + sb.Append(msg); + } else { + string msg = string.Format(Properties.Resources.ASM_MISMATCH_DATA_FMT, + offset, fileVal, mProject.FileData[offset]); + MessageBox.Show(msg, Properties.Resources.ASM_MISMATCH_CAPTION, + MessageBoxButtons.OK, MessageBoxIcon.Error); + sb.Append(msg); + } + } else { + sb.Append(Properties.Resources.ASM_MATCH_SUCCESS); + } + } + sb.Append("\r\n\r\n"); + + if (results.Stdout != null && results.Stdout.Length > 2) { + sb.Append("----- stdout -----\r\n"); + sb.Append(results.Stdout); + sb.Append("\r\n"); + } + if (results.Stderr != null && results.Stderr.Length > 2) { + sb.Append("----- stderr -----\r\n"); + sb.Append(results.Stderr); + sb.Append("\r\n"); + } + + cmdOutputTextBox.Text = sb.ToString(); + cmdOutputTextBox.BackColor = SystemColors.Window; + } + } +} diff --git a/SourceGen/AsmGen/GenAndAsm.resx b/SourceGen/AsmGen/GenAndAsm.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/SourceGen/AsmGen/GenAndAsm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/SourceGen/AsmGen/GenCc65.cs b/SourceGen/AsmGen/GenCc65.cs new file mode 100644 index 0000000..1d27371 --- /dev/null +++ b/SourceGen/AsmGen/GenCc65.cs @@ -0,0 +1,637 @@ +/* + * Copyright 2018 faddenSoft + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Text; + +using Asm65; +using CommonUtil; + +namespace SourceGen.AsmGen { + /// + /// Generate source code compatible with the cc65 assembler (https://github.com/cc65/cc65). + /// + public class GenCc65 : IGenerator { + private const string ASM_FILE_SUFFIX = "_cc65.S"; + private const int MAX_OPERAND_LEN = 64; + + // IGenerator + public DisasmProject Project { get; private set; } + + // IGenerator + public Formatter SourceFormatter { get; private set; } + + // IGenerator + public AppSettings Settings { get; private set; } + + // IGenerator + public AssemblerQuirks Quirks { get; private set; } + + // IGenerator + public LabelLocalizer Localizer { get { return mLocalizer; } } + + /// + /// Working directory, i.e. where we write our output file(s). + /// + private string mWorkDirectory; + + /// + /// If set, long labels get their own line. + /// + private bool mLongLabelNewLine; + + /// + /// Base filename. Typically the project file name without the ".dis65" extension. + /// + private string mFileNameBase; + + /// + /// StringBuilder to use when composing a line. Held here to reduce allocations. + /// + private StringBuilder mLineBuilder = new StringBuilder(100); + + /// + /// Label localization helper. + /// + private LabelLocalizer mLocalizer; + + /// + /// Stream to send the output to. + /// + private StreamWriter mOutStream; + + /// + /// The first time we output a high-ASCII string, we generate a macro for it. + /// + private bool mHighAsciiMacroOutput; + + /// + /// Holds detected version of configured assembler. + /// + private CommonUtil.Version mAsmVersion = CommonUtil.Version.NO_VERSION; + + // We test against this in a few places. + private static CommonUtil.Version V2_17 = new CommonUtil.Version(2, 17); + + + // Pseudo-op string constants. + private static PseudoOp.PseudoOpNames sDataOpNames = new PseudoOp.PseudoOpNames() { + EquDirective = "=", + OrgDirective = ".org", + //RegWidthDirective // .a8, .a16, .i8, .i16 + DefineData1 = ".byte", + DefineData2 = ".word", + DefineData3 = ".faraddr", + DefineData4 = ".dword", + DefineBigData2 = ".dbyt", + //DefineBigData3 + //DefineBigData4 + Fill = ".res", + //Dense // no equivalent, use .byte with comma-separated args + StrGeneric = ".byte", + //StrReverse + StrNullTerm = ".asciiz", + //StrLen8 // macro with .strlen? + //StrLen16 + //StrDci + //StrDciReverse + }; + + + // IGenerator + public void Configure(DisasmProject project, string workDirectory, string fileNameBase, + AssemblerVersion asmVersion, AppSettings settings) { + Debug.Assert(project != null); + Debug.Assert(!string.IsNullOrEmpty(workDirectory)); + Debug.Assert(!string.IsNullOrEmpty(fileNameBase)); + + Project = project; + Quirks = new AssemblerQuirks(); + if (asmVersion != null) { + mAsmVersion = asmVersion.Version; + if (mAsmVersion <= V2_17) { + // cc65 v2.17: https://github.com/cc65/cc65/issues/717 + Quirks.BlockMoveArgsReversed = true; + // cc65 v2.17: https://github.com/cc65/cc65/issues/754 + Quirks.NoPcRelBankWrap = true; + } + } + + mWorkDirectory = workDirectory; + mFileNameBase = fileNameBase; + Settings = settings; + + mLongLabelNewLine = Settings.GetBool(AppSettings.SRCGEN_LONG_LABEL_NEW_LINE, false); + } + + // IGenerator + public List GenerateSource(BackgroundWorker worker) { + List pathNames = new List(1); + + string fileName = mFileNameBase + ASM_FILE_SUFFIX; + string pathName = Path.Combine(mWorkDirectory, fileName); + pathNames.Add(pathName); + + Formatter.FormatConfig config = new Formatter.FormatConfig(); + GenCommon.ConfigureFormatterFromSettings(Settings, ref config); + config.mForceAbsOpcodeSuffix = string.Empty; + config.mForceLongOpcodeSuffix = string.Empty; + config.mForceAbsOperandPrefix = "a:"; // absolute + config.mForceLongOperandPrefix = "f:"; // far + config.mEndOfLineCommentDelimiter = ";"; + config.mFullLineCommentDelimiterBase = ";"; + config.mBoxLineCommentDelimiter = ";"; + config.mAllowHighAsciiCharConst = false; + config.mExpressionMode = Formatter.FormatConfig.ExpressionMode.Simple; + SourceFormatter = new Formatter(config); + + string msg = string.Format(Properties.Resources.PROGRESS_GENERATING_FMT, pathName); + worker.ReportProgress(0, msg); + + mLocalizer = new LabelLocalizer(Project); + if (!Settings.GetBool(AppSettings.SRCGEN_DISABLE_LABEL_LOCALIZATION, false)) { + mLocalizer.LocalPrefix = "@"; + mLocalizer.Analyze(); + } + + // Use UTF-8 encoding, without a byte-order mark. + using (StreamWriter sw = new StreamWriter(pathName, false, new UTF8Encoding(false))) { + mOutStream = sw; + + if (Settings.GetBool(AppSettings.SRCGEN_ADD_IDENT_COMMENT, false)) { + if (mAsmVersion.IsValid && mAsmVersion <= V2_17) { + OutputLine(SourceFormatter.FullLineCommentDelimiter + + string.Format(Properties.Resources.GENERATED_FOR_VERSION, + "cc65", mAsmVersion.ToString())); + } else { + OutputLine(SourceFormatter.FullLineCommentDelimiter + + string.Format(Properties.Resources.GENERATED_FOR_LATEST, "cc65")); + } + } + + GenCommon.Generate(this, sw, worker); + } + mOutStream = null; + + return pathNames; + } + + // IGenerator + public void OutputAsmConfig() { + CpuDef cpuDef = Project.CpuDef; + string cpuStr; + if (cpuDef.Type == CpuDef.CpuType.Cpu65816) { + cpuStr = "65816"; + } else if (cpuDef.Type == CpuDef.CpuType.Cpu65C02) { + cpuStr = "65C02"; + } else if (cpuDef.Type == CpuDef.CpuType.Cpu6502 && cpuDef.HasUndocumented) { + cpuStr = "6502X"; + } else { + cpuStr = "6502"; + } + + OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(".setcpu"), + '\"' + cpuStr + '\"', string.Empty); + } + + /// + /// Map the mnemonics we chose for undocumented opcodes to the cc65 mnemonics. + /// + /// We don't include the double- and triple-byte NOPs here, as cc65 doesn't + /// appear to have a definition for them (as of 2.17). + /// + private static Dictionary sUndocMap = new Dictionary() { + { OpName.ASR, "alr" }, // imm 0x4b + { OpName.ANC, "anc" }, // imm 0x0b (and others) + { OpName.ANE, "ane" }, // imm 0x8b + { OpName.ARR, "arr" }, // imm 0x6b + { OpName.SBX, "axs" }, // imm 0xcb + { OpName.DCP, "dcp" }, // abs 0xcf + { OpName.ISB, "isc" }, // abs 0xef + { OpName.HLT, "jam" }, // abs 0x02 (and others) + { OpName.LAE, "las" }, // abs,y 0xbb + { OpName.LXA, "lax" }, // imm 0xab + { OpName.RLA, "rla" }, // abs 0x2f + { OpName.RRA, "rra" }, // abs 0x6f + { OpName.SAX, "sax" }, // abs 0x8f + { OpName.SHA, "sha" }, // abs,y 0x9f + { OpName.SHX, "shx" }, // abs,y 0x9e + { OpName.SHY, "shy" }, // abs,x 0x9c + { OpName.SLO, "slo" }, // abs 0x0f + { OpName.SRE, "sre" }, // abs 0x4f + { OpName.SHS, "tas" }, // abs,y 0x9b + }; + + // IGenerator + public string ReplaceMnemonic(OpDef op) { + if ((op == OpDef.OpWDM_WDM || op == OpDef.OpBRK_StackInt) && mAsmVersion <= V2_17) { + // cc65 v2.17 doesn't support WDM, and assembles BRK to opcode $05. + // https://github.com/cc65/cc65/issues/715 + // https://github.com/cc65/cc65/issues/716 + return null; + } else if (op.IsUndocumented) { + if (sUndocMap.TryGetValue(op.Mnemonic, out string newValue)) { + if ((op.Mnemonic == OpName.ANC && op.Opcode != 0x0b) || + (op.Mnemonic == OpName.HLT && op.Opcode != 0x02)) { + // There are multiple opcodes for the same thing. cc65 outputs + // one specific thing, so we need to match that, and just do a hex + // dump for the others. + return null; + } + return newValue; + } + return null; + } else { + return string.Empty; + } + } + + // IGenerator + public void GenerateShortSequence(int offset, int length, out string opcode, + out string operand) { + Debug.Assert(length >= 1 && length <= 4); + + // Use a comma-separated list of individual hex bytes. + opcode = sDataOpNames.DefineData1; + + StringBuilder sb = new StringBuilder(length * 4); + for (int i = 0; i < length; i++) { + if (i != 0) { + sb.Append(','); + } + sb.Append(SourceFormatter.FormatHexValue(Project.FileData[offset + i], 2)); + } + operand = sb.ToString(); + } + + // IGenerator + public void OutputDataOp(int offset) { + Formatter formatter = SourceFormatter; + byte[] data = Project.FileData; + Anattrib attr = Project.GetAnattrib(offset); + + string labelStr = string.Empty; + if (attr.Symbol != null) { + labelStr = mLocalizer.ConvLabel(attr.Symbol.Label); + } + + string commentStr = SourceFormatter.FormatEolComment(Project.Comments[offset]); + string opcodeStr, operandStr; + + FormatDescriptor dfd = attr.DataDescriptor; + Debug.Assert(dfd != null); + int length = dfd.Length; + Debug.Assert(length > 0); + + bool multiLine = false; + switch (dfd.FormatType) { + case FormatDescriptor.Type.Default: + if (length != 1) { + Debug.Assert(false); + length = 1; + } + opcodeStr = sDataOpNames.DefineData1; + int operand = RawData.GetWord(data, offset, length, false); + operandStr = formatter.FormatHexValue(operand, length * 2); + break; + case FormatDescriptor.Type.NumericLE: + opcodeStr = sDataOpNames.GetDefineData(length); + operand = RawData.GetWord(data, offset, length, false); + operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, + mLocalizer.LabelMap, dfd, operand, length, false); + break; + case FormatDescriptor.Type.NumericBE: + opcodeStr = sDataOpNames.GetDefineBigData(length); + if (opcodeStr == null) { + // Nothing defined, output as comma-separated single-byte values. + GenerateShortSequence(offset, length, out opcodeStr, out operandStr); + } else { + operand = RawData.GetWord(data, offset, length, true); + operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, + mLocalizer.LabelMap, dfd, operand, length, false); + } + break; + case FormatDescriptor.Type.Fill: + opcodeStr = sDataOpNames.Fill; + operandStr = length + "," + formatter.FormatHexValue(data[offset], 2); + break; + case FormatDescriptor.Type.Dense: + multiLine = true; + opcodeStr = operandStr = null; + OutputDenseHex(offset, length, labelStr, commentStr); + break; + case FormatDescriptor.Type.String: + multiLine = true; + opcodeStr = operandStr = null; + OutputString(offset, labelStr, commentStr); + break; + default: + opcodeStr = "???"; + operandStr = "***"; + break; + } + + if (!multiLine) { + opcodeStr = formatter.FormatPseudoOp(opcodeStr); + OutputLine(labelStr, opcodeStr, operandStr, commentStr); + } + } + + private void OutputDenseHex(int offset, int length, string labelStr, string commentStr) { + Formatter formatter = SourceFormatter; + byte[] data = Project.FileData; + StringBuilder sb = new StringBuilder(MAX_OPERAND_LEN); + + string opcodeStr = formatter.FormatPseudoOp(sDataOpNames.DefineData1); + + int maxPerLine = MAX_OPERAND_LEN / 4; + int numChunks = (length + maxPerLine - 1) / maxPerLine; + for (int chunk = 0; chunk < numChunks; chunk++) { + int chunkStart = chunk * maxPerLine; + int chunkEnd = Math.Min((chunk + 1) * maxPerLine, length); + for (int i = chunkStart; i < chunkEnd; i++) { + if (i != chunkStart) { + sb.Append(','); + } + sb.Append(formatter.FormatHexValue(data[offset + i], 2)); + } + + OutputLine(labelStr, opcodeStr, sb.ToString(), commentStr); + labelStr = commentStr = string.Empty; + sb.Clear(); + } + } + + /// + /// Outputs formatted data in an unformatted way, because the code generator couldn't + /// figure out how to do something better. + /// + private void OutputNoJoy(int offset, int length, string labelStr, string commentStr) { + byte[] data = Project.FileData; + Debug.Assert(length > 0); + Debug.Assert(offset >= 0 && offset < data.Length); + + bool singleValue = true; + byte val = data[offset]; + for (int i = 1; i < length; i++) { + if (data[offset + i] != val) { + singleValue = false; + break; + } + } + + if (singleValue) { + string opcodeStr = SourceFormatter.FormatPseudoOp(sDataOpNames.Fill); + string operandStr = length + "," + SourceFormatter.FormatHexValue(val, 2); + OutputLine(labelStr, opcodeStr, operandStr, commentStr); + } else { + OutputDenseHex(offset, length, labelStr, commentStr); + } + } + + // IGenerator + public void OutputEquDirective(string name, string valueStr, string comment) { + OutputLine(name, SourceFormatter.FormatPseudoOp(sDataOpNames.EquDirective), + valueStr, SourceFormatter.FormatEolComment(comment)); + } + + // IGenerator + public void OutputOrgDirective(int address) { + OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(sDataOpNames.OrgDirective), + SourceFormatter.FormatHexValue(address, 4), string.Empty); + } + + // IGenerator + public void OutputRegWidthDirective(int offset, int prevM, int prevX, int newM, int newX) { + if (prevM != newM) { + string mop = (newM == 0) ? ".a16" : ".a8"; + OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(mop), + string.Empty, string.Empty); + } + if (prevX != newX) { + string xop = (newX == 0) ? ".i16" : ".i8"; + OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(xop), + string.Empty, string.Empty); + } + } + + // IGenerator + public void OutputLine(string fullLine) { + mOutStream.WriteLine(fullLine); + } + + // IGenerator + public void OutputLine(string label, string opcode, string operand, string comment) { + // If a label is provided, and it doesn't start with a '.' (indicating that it's + // a directive), and this isn't an EQU directive, add a ':'. Might be easier to + // just ".feature labels_without_colons", but I'm trying to do things the way + // that cc65 users will expect. + if (!string.IsNullOrEmpty(label) && label[0] != '.' && + !String.Equals(opcode, sDataOpNames.EquDirective, + StringComparison.InvariantCultureIgnoreCase)) { + label += ':'; + + if (mLongLabelNewLine && label.Length >= 9) { + mOutStream.WriteLine(label); + label = string.Empty; + } + } + + mLineBuilder.Clear(); + TextUtil.AppendPaddedString(mLineBuilder, label, 9); + TextUtil.AppendPaddedString(mLineBuilder, opcode, 9 + 8); + TextUtil.AppendPaddedString(mLineBuilder, operand, 9 + 8 + 11); + if (string.IsNullOrEmpty(comment)) { + // Trim trailing spaces off of opcode or operand. If they want trailing + // spaces at the end of a comment, that's fine. + CommonUtil.TextUtil.TrimEnd(mLineBuilder); + } else { + mLineBuilder.Append(comment); + } + + mOutStream.WriteLine(mLineBuilder.ToString()); + } + + private void OutputString(int offset, string labelStr, string commentStr) { + // Normal ASCII strings are straightforward: they're just part of a .byte + // directive, and can mix with anything else in the .byte. + // + // For CString we can use .asciiz, but only if the string fits on one line + // and doesn't include delimiters. For L8String and L16String we can + // define simple macros, but their use has a similar restriction. High-ASCII + // strings also require a macro. + // + // We might be able to define a macro for DCI and Reverse as well. + // + // The limitation on strings with delimiters arises because (1) I don't see a + // way to escape them within a string, and (2) the simple macro workarounds + // only take a single argument, not a comma-separated list of stuff. + // + // Some ideas here: + // https://groups.google.com/forum/#!topic/comp.sys.apple2.programmer/5Wkw8mUPcU0 + + Formatter formatter = SourceFormatter; + byte[] data = Project.FileData; + Anattrib attr = Project.GetAnattrib(offset); + FormatDescriptor dfd = attr.DataDescriptor; + Debug.Assert(dfd != null); + Debug.Assert(dfd.FormatType == FormatDescriptor.Type.String); + Debug.Assert(dfd.Length > 0); + + bool highAscii = false; + int leadingBytes = 0; + int trailingBytes = 0; + bool showLeading = false; + bool showTrailing = false; + + switch (dfd.FormatSubType) { + case FormatDescriptor.SubType.None: + highAscii = (data[offset] & 0x80) != 0; + break; + case FormatDescriptor.SubType.Dci: + highAscii = (data[offset] & 0x80) != 0; + trailingBytes = 1; + showTrailing = true; + break; + case FormatDescriptor.SubType.Reverse: + highAscii = (data[offset] & 0x80) != 0; + break; + case FormatDescriptor.SubType.DciReverse: + highAscii = (data[offset + dfd.Length - 1] & 0x80) != 0; + leadingBytes = 1; + showLeading = true; + break; + case FormatDescriptor.SubType.CString: + highAscii = (data[offset] & 0x80) != 0; + trailingBytes = 1; + showTrailing = true; + break; + case FormatDescriptor.SubType.L8String: + if (dfd.Length > 1) { + highAscii = (data[offset + 1] & 0x80) != 0; + } + leadingBytes = 1; + showLeading = true; + break; + case FormatDescriptor.SubType.L16String: + if (dfd.Length > 2) { + highAscii = (data[offset + 2] & 0x80) != 0; + } + leadingBytes = 2; + showLeading = true; + break; + default: + Debug.Assert(false); + return; + } + + char delim = '"'; + StringGather gath = null; + + // Run the string through so we can see if it'll fit on one line. As a minor + // optimization, we skip this step for "generic" strings, which are probably + // the most common thing. + if (dfd.FormatSubType != FormatDescriptor.SubType.None || highAscii) { + gath = new StringGather(this, labelStr, "???", commentStr, delim, + delim, StringGather.ByteStyle.CommaSep, MAX_OPERAND_LEN, true); + FeedGath(gath, data, offset, dfd.Length, leadingBytes, showLeading, + trailingBytes, showTrailing); + Debug.Assert(gath.NumLinesOutput > 0); + } + + string opcodeStr = formatter.FormatPseudoOp(sDataOpNames.StrGeneric); + + switch (dfd.FormatSubType) { + case FormatDescriptor.SubType.None: + // Special case for simple short high-ASCII strings. These have no + // leading or trailing bytes. We can improve this a bit by handling + // arbitrarily long strings by simply breaking them across lines. + Debug.Assert(leadingBytes == 0); + Debug.Assert(trailingBytes == 0); + if (highAscii && gath.NumLinesOutput == 1 && !gath.HasDelimiter) { + if (!mHighAsciiMacroOutput) { + mHighAsciiMacroOutput = true; + // Output a macro for high-ASCII strings. + OutputLine(".macro", "HiAscii", "Arg", string.Empty); + OutputLine(string.Empty, ".repeat", ".strlen(Arg), I", string.Empty); + OutputLine(string.Empty, ".byte", ".strat(Arg, I) | $80", string.Empty); + OutputLine(string.Empty, ".endrep", string.Empty, string.Empty); + OutputLine(".endmacro", string.Empty, string.Empty, string.Empty); + } + opcodeStr = formatter.FormatPseudoOp("HiAscii"); + highAscii = false; + } + break; + case FormatDescriptor.SubType.Dci: + case FormatDescriptor.SubType.Reverse: + case FormatDescriptor.SubType.DciReverse: + // Full configured above. + break; + case FormatDescriptor.SubType.CString: + if (gath.NumLinesOutput == 1 && !gath.HasDelimiter) { + opcodeStr = sDataOpNames.StrNullTerm; + showTrailing = false; + } + break; + case FormatDescriptor.SubType.L8String: + case FormatDescriptor.SubType.L16String: + // Implement macros? + break; + default: + Debug.Assert(false); + return; + } + + if (highAscii) { + OutputNoJoy(offset, dfd.Length, labelStr, commentStr); + return; + } + + // Create a new StringGather, with the final opcode choice. + gath = new StringGather(this, labelStr, opcodeStr, commentStr, delim, + delim, StringGather.ByteStyle.CommaSep, MAX_OPERAND_LEN, false); + FeedGath(gath, data, offset, dfd.Length, leadingBytes, showLeading, + trailingBytes, showTrailing); + } + + /// + /// Feeds the bytes into the StringGather. + /// + private void FeedGath(StringGather gath, byte[] data, int offset, int length, + int leadingBytes, bool showLeading, int trailingBytes, bool showTrailing) { + int startOffset = offset; + int strEndOffset = offset + length - trailingBytes; + + if (showLeading) { + while (leadingBytes-- > 0) { + gath.WriteByte(data[offset++]); + } + } else { + offset += leadingBytes; + } + for (; offset < strEndOffset; offset++) { + gath.WriteChar((char)(data[offset] & 0x7f)); + } + while (showTrailing && trailingBytes-- > 0) { + gath.WriteByte(data[offset++]); + } + gath.Finish(); + } + } +} diff --git a/SourceGen/AsmGen/GenCommon.cs b/SourceGen/AsmGen/GenCommon.cs new file mode 100644 index 0000000..a8e9369 --- /dev/null +++ b/SourceGen/AsmGen/GenCommon.cs @@ -0,0 +1,329 @@ +/* + * Copyright 2018 faddenSoft + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; + +using Asm65; + +namespace SourceGen.AsmGen { + public class GenCommon { + /// + /// Generates assembly source. + /// + /// This code is common to all generators. + /// + /// + /// + /// + /// + public static void Generate(IGenerator gen, StreamWriter sw, BackgroundWorker worker) { + DisasmProject proj = gen.Project; + Formatter formatter = gen.SourceFormatter; + int offset = 0; + + bool doAddCycles = gen.Settings.GetBool(AppSettings.SRCGEN_SHOW_CYCLE_COUNTS, false); + + GenerateHeader(gen, sw); + + // Used for M/X flag tracking. + StatusFlags prevFlags = StatusFlags.AllIndeterminate; + + int lastProgress = 0; + + while (offset < proj.FileData.Length) { + Anattrib attr = proj.GetAnattrib(offset); + + if (attr.IsInstructionStart && offset > 0 && + proj.GetAnattrib(offset - 1).IsData) { + // Transition from data to code. (Don't add blank line for inline data.) + gen.OutputLine(string.Empty); + } + + // Long comments come first. + if (proj.LongComments.TryGetValue(offset, out MultiLineComment longComment)) { + List formatted = longComment.FormatText(formatter, string.Empty); + foreach (string str in formatted) { + gen.OutputLine(str); + } + } + + // Check for address change. + int orgAddr = proj.AddrMap.Get(offset); + if (orgAddr >= 0) { + gen.OutputOrgDirective(orgAddr); + } + + if (attr.IsInstructionStart) { + // Generate M/X reg width directive, if necessary. + // NOTE: we can suppress the initial directive if we know what the + // target assembler's default assumption is. Probably want to handle + // that in the ORG output handler. + if (proj.CpuDef.HasEmuFlag) { + StatusFlags curFlags = attr.StatusFlags; + curFlags.M = attr.StatusFlags.ShortM ? 1 : 0; + curFlags.X = attr.StatusFlags.ShortX ? 1 : 0; + if (curFlags.M != prevFlags.M || curFlags.X != prevFlags.X) { + // changed, output directive + gen.OutputRegWidthDirective(offset, prevFlags.M, prevFlags.X, + curFlags.M, curFlags.X); + } + + prevFlags = curFlags; + } + + // Look for embedded instructions. + int len; + for (len = 1; len < attr.Length; len++) { + if (proj.GetAnattrib(offset + len).IsInstructionStart) { + break; + } + } + + // Output instruction. + GenerateInstruction(gen, sw, offset, len, doAddCycles); + + if (attr.DoesNotContinue) { + gen.OutputLine(string.Empty); + } + + offset += len; + } else { + gen.OutputDataOp(offset); + offset += attr.Length; + } + + // Update progress meter. We don't want to spam it, so just ping it 10x. + int curProgress = (offset * 10) / proj.FileData.Length; + if (lastProgress != curProgress) { + if (worker.CancellationPending) { + Debug.WriteLine("GenCommon got cancellation request"); + return; + } + lastProgress = curProgress; + worker.ReportProgress(curProgress * 10); + //System.Threading.Thread.Sleep(500); + } + } + } + + private static void GenerateHeader(IGenerator gen, StreamWriter sw) { + DisasmProject proj = gen.Project; + Formatter formatter = gen.SourceFormatter; + + // Check for header comment. + if (proj.LongComments.TryGetValue(DisplayList.Line.HEADER_COMMENT_OFFSET, + out MultiLineComment headerComment)) { + List formatted = headerComment.FormatText(formatter, string.Empty); + foreach (string str in formatted) { + gen.OutputLine(str); + } + } + + gen.OutputAsmConfig(); + + // Format symbols. + foreach (DefSymbol defSym in proj.ActiveDefSymbolList) { + // Use an operand length of 1 so things are shown as concisely as possible. + string valueStr = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, + gen.Localizer.LabelMap, defSym.DataDescriptor, defSym.Value, 1, false); + gen.OutputEquDirective(defSym.Label, valueStr, defSym.Comment); + } + + // If there was at least one symbol, output a blank line. + if (proj.ActiveDefSymbolList.Count != 0) { + gen.OutputLine(string.Empty); + } + } + + private static void GenerateInstruction(IGenerator gen, StreamWriter sw, int offset, + int instrBytes, bool doAddCycles) { + DisasmProject proj = gen.Project; + Formatter formatter = gen.SourceFormatter; + byte[] data = proj.FileData; + Anattrib attr = proj.GetAnattrib(offset); + + string labelStr = string.Empty; + if (attr.Symbol != null) { + labelStr = gen.Localizer.ConvLabel(attr.Symbol.Label); + } + + OpDef op = proj.CpuDef.GetOpDef(data[offset]); + int operand = op.GetOperand(data, offset, attr.StatusFlags); + int instrLen = op.GetLength(attr.StatusFlags); + OpDef.WidthDisambiguation wdis = OpDef.WidthDisambiguation.None; + if (op.IsWidthPotentiallyAmbiguous) { + wdis = OpDef.GetWidthDisambiguation(instrLen, operand); + } + + string replMnemonic = gen.ReplaceMnemonic(op); + string opcodeStr = formatter.FormatOpcode(op, wdis); + + string formattedOperand = null; + int operandLen = instrLen - 1; + bool isPcRel = false; + bool isPcRelBankWrap = false; + + // Tweak branch instructions. We want to show the absolute address rather + // than the relative offset (which happens with the OperandAddress assignment + // below), and 1-byte branches should always appear as a 4-byte hex value. + if (op.AddrMode == OpDef.AddressMode.PCRel) { + Debug.Assert(attr.OperandAddress >= 0); + operandLen = 2; + isPcRel = true; + } else if (op.AddrMode == OpDef.AddressMode.PCRelLong || + op.AddrMode == OpDef.AddressMode.StackPCRelLong) { + isPcRel = true; + } + if (isPcRel) { + int branchDist = attr.Address - attr.OperandAddress; + isPcRelBankWrap = branchDist > 32767 || branchDist < -32768; + } + + // 16-bit operands outside bank 0 need to include the bank when computing + // symbol adjustment. + int operandForSymbol = operand; + if (attr.OperandAddress >= 0) { + operandForSymbol = attr.OperandAddress; + } + + // Check Length to watch for bogus descriptors (?) + if (attr.DataDescriptor != null && attr.Length == attr.DataDescriptor.Length) { + // Format operand as directed. + if (op.AddrMode == OpDef.AddressMode.BlockMove) { + // Special handling for the double-operand block move. + string opstr1 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, + gen.Localizer.LabelMap, attr.DataDescriptor, operand >> 8, 1, false); + string opstr2 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, + gen.Localizer.LabelMap, attr.DataDescriptor, operand & 0xff, 1, false); + if (gen.Quirks.BlockMoveArgsReversed) { + string tmp = opstr1; + opstr1 = opstr2; + opstr2 = tmp; + } + formattedOperand = opstr1 + "," + opstr2; + } else { + formattedOperand = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, + gen.Localizer.LabelMap, attr.DataDescriptor, + operandForSymbol, operandLen, isPcRel); + } + } else { + // Show operand value in hex. + if (op.AddrMode == OpDef.AddressMode.BlockMove) { + int arg1, arg2; + if (gen.Quirks.BlockMoveArgsReversed) { + arg1 = operand & 0xff; + arg2 = operand >> 8; + } else { + arg1 = operand >> 8; + arg2 = operand & 0xff; + } + formattedOperand = formatter.FormatHexValue(arg1, 2) + "," + + formatter.FormatHexValue(arg2, 2); + } else { + if (operandLen == 2) { + // This is necessary for 16-bit operands, like "LDA abs" and "PEA val", + // when outside bank zero. The bank is included in the operand address, + // but we don't want to show it here. + operandForSymbol &= 0xffff; + } + formattedOperand = formatter.FormatHexValue(operandForSymbol, operandLen * 2); + } + } + string operandStr = formatter.FormatOperand(op, formattedOperand, wdis); + + string eolComment = proj.Comments[offset]; + if (doAddCycles) { + bool branchCross = (attr.Address & 0xff00) != (operandForSymbol & 0xff00); + int cycles = proj.CpuDef.GetCycles(op.Opcode, attr.StatusFlags, attr.BranchTaken, + branchCross); + if (cycles > 0) { + eolComment = cycles.ToString() + " " + eolComment; + } else { + eolComment = (-cycles).ToString() + "+ " + eolComment; + } + } + string commentStr = formatter.FormatEolComment(eolComment); + + if (attr.Length != instrBytes) { + // This instruction has another instruction inside it. Throw out what we + // computed and just output as bytes. + gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr); + } else if (isPcRelBankWrap && gen.Quirks.NoPcRelBankWrap) { + // Some assemblers have trouble generating PC-relative operands that wrap + // around the bank. Output as raw hex. + gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr); + } else if (op.AddrMode == OpDef.AddressMode.BlockMove && + gen.Quirks.BlockMoveArgsReversed) { + // On second thought, just don't even output the wrong thing. + gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr); + } else if (replMnemonic == null) { + // No mnemonic exists for this opcode. + gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr); + } else if (replMnemonic != string.Empty) { + // A replacement mnemonic has been provided. + opcodeStr = formatter.FormatMnemonic(replMnemonic, wdis); + } + gen.OutputLine(labelStr, opcodeStr, operandStr, commentStr); + + // Assemblers like Merlin32 try to be helpful and track SEP/REP, but they do the + // wrong thing if we're in emulation mode. Force flags back to short. + if (proj.CpuDef.HasEmuFlag && gen.Quirks.TracksSepRepNotEmu && op == OpDef.OpREP_Imm) { + if ((operand & 0x30) != 0 && attr.StatusFlags.E == 1) { + gen.OutputRegWidthDirective(offset, 0, 0, 1, 1); + } + } + } + + /// + /// Configures some common format config items from the app settings. Uses a + /// passed-in object, rather than the global settings. + /// + /// Application settings. + /// Format config struct. + public static void ConfigureFormatterFromSettings(AppSettings settings, + ref Formatter.FormatConfig config) { + config.mUpperHexDigits = + settings.GetBool(AppSettings.FMT_UPPER_HEX_DIGITS, false); + config.mUpperOpcodes = + settings.GetBool(AppSettings.FMT_UPPER_OP_MNEMONIC, false); + config.mUpperPseudoOpcodes = + settings.GetBool(AppSettings.FMT_UPPER_PSEUDO_OP_MNEMONIC, false); + config.mUpperOperandA = + settings.GetBool(AppSettings.FMT_UPPER_OPERAND_A, false); + config.mUpperOperandS = + settings.GetBool(AppSettings.FMT_UPPER_OPERAND_S, false); + config.mUpperOperandXY = + settings.GetBool(AppSettings.FMT_UPPER_OPERAND_XY, false); + config.mAddSpaceLongComment = + settings.GetBool(AppSettings.FMT_ADD_SPACE_FULL_COMMENT, true); + + config.mForceAbsOpcodeSuffix = + settings.GetString(AppSettings.FMT_OPCODE_SUFFIX_ABS, string.Empty); + config.mForceLongOpcodeSuffix = + settings.GetString(AppSettings.FMT_OPCODE_SUFFIX_LONG, string.Empty); + config.mForceAbsOperandPrefix = + settings.GetString(AppSettings.FMT_OPERAND_PREFIX_ABS, string.Empty); + config.mForceLongOperandPrefix = + settings.GetString(AppSettings.FMT_OPERAND_PREFIX_LONG, string.Empty); + + string exprMode = settings.GetString(AppSettings.FMT_EXPRESSION_MODE, string.Empty); + config.mExpressionMode = Formatter.FormatConfig.ParseExpressionMode(exprMode); + } + } +} diff --git a/SourceGen/AsmGen/GenMerlin32.cs b/SourceGen/AsmGen/GenMerlin32.cs new file mode 100644 index 0000000..aeb83da --- /dev/null +++ b/SourceGen/AsmGen/GenMerlin32.cs @@ -0,0 +1,594 @@ +/* + * Copyright 2018 faddenSoft + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Text; + +using Asm65; +using CommonUtil; + +namespace SourceGen.AsmGen { + /// + /// Generate source code compatible with Brutal Deluxe's Merlin 32 assembler + /// (https://www.brutaldeluxe.fr/products/crossdevtools/merlin/). + /// + public class GenMerlin32 : IGenerator { + private const string ASM_FILE_SUFFIX = "_Merlin32.S"; + private const int MAX_OPERAND_LEN = 64; + + // IGenerator + public DisasmProject Project { get; private set; } + + // IGenerator + public Formatter SourceFormatter { get; private set; } + + // IGenerator + public AppSettings Settings { get; private set; } + + // IGenerator + public AssemblerQuirks Quirks { get; private set; } + + // IGenerator + public LabelLocalizer Localizer { get { return mLocalizer; } } + + /// + /// Working directory, i.e. where we write our output file(s). + /// + private string mWorkDirectory; + + /// + /// If set, long labels get their own line. + /// + private bool mLongLabelNewLine; + + /// + /// Base filename. Typically the project file name without the ".dis65" extension. + /// + private string mFileNameBase; + + /// + /// StringBuilder to use when composing a line. Held here to reduce allocations. + /// + private StringBuilder mLineBuilder = new StringBuilder(100); + + /// + /// Label localization helper. + /// + private LabelLocalizer mLocalizer; + + /// + /// Stream to send the output to. + /// + private StreamWriter mOutStream; + + /// + /// Holds detected version of configured assembler. + /// + private CommonUtil.Version mAsmVersion = CommonUtil.Version.NO_VERSION; + + + // Semi-convenient way to hold all the interesting string constants in one place. + // Note the actual usage of the pseudo-op may not match what the main app does, + // e.g. RegWidthDirective behaves differently from "mx". I'm just trying to avoid + // having string constants scattered all over. + private static PseudoOp.PseudoOpNames sDataOpNames = new PseudoOp.PseudoOpNames() { + EquDirective = "equ", + OrgDirective = "org", + RegWidthDirective = "mx", + DefineData1 = "dfb", + DefineData2 = "dw", + DefineData3 = "adr", + DefineData4 = "adrl", + DefineBigData2 = "ddb", + //DefineBigData3 + //DefineBigData4 + Fill = "ds", + Dense = "hex", + StrGeneric = "asc", + StrGenericHi = "asc", + StrReverse = "rev", + StrReverseHi = "rev", + //StrNullTerm + StrLen8 = "str", + StrLen8Hi = "str", + StrLen16 = "strl", + StrLen16Hi = "strl", + StrDci = "dci", + StrDciHi = "dci", + //StrDciReverse + }; + + + // IGenerator + public void Configure(DisasmProject project, string workDirectory, string fileNameBase, + AssemblerVersion asmVersion, AppSettings settings) { + Debug.Assert(project != null); + Debug.Assert(!string.IsNullOrEmpty(workDirectory)); + Debug.Assert(!string.IsNullOrEmpty(fileNameBase)); + + Project = project; + Quirks = new AssemblerQuirks(); + Quirks.TracksSepRepNotEmu = true; + Quirks.NoPcRelBankWrap = true; + + mWorkDirectory = workDirectory; + mFileNameBase = fileNameBase; + Settings = settings; + + mLongLabelNewLine = Settings.GetBool(AppSettings.SRCGEN_LONG_LABEL_NEW_LINE, false); + } + + // IGenerator; executes on background thread + public List GenerateSource(BackgroundWorker worker) { + List pathNames = new List(1); + + string fileName = mFileNameBase + ASM_FILE_SUFFIX; + string pathName = Path.Combine(mWorkDirectory, fileName); + pathNames.Add(pathName); + + Formatter.FormatConfig config = new Formatter.FormatConfig(); + GenCommon.ConfigureFormatterFromSettings(Settings, ref config); + config.mForceAbsOpcodeSuffix = ":"; + config.mForceLongOpcodeSuffix = "l"; + config.mForceAbsOperandPrefix = string.Empty; + config.mForceLongOperandPrefix = string.Empty; + config.mEndOfLineCommentDelimiter = ";"; + config.mFullLineCommentDelimiterBase = ";"; + config.mBoxLineCommentDelimiter = string.Empty; + config.mAllowHighAsciiCharConst = true; + config.mExpressionMode = Formatter.FormatConfig.ExpressionMode.Merlin; + SourceFormatter = new Formatter(config); + + string msg = string.Format(Properties.Resources.PROGRESS_GENERATING_FMT, pathName); + worker.ReportProgress(0, msg); + + mLocalizer = new LabelLocalizer(Project); + if (!Settings.GetBool(AppSettings.SRCGEN_DISABLE_LABEL_LOCALIZATION, false)) { + mLocalizer.LocalPrefix = ":"; + mLocalizer.Analyze(); + } + + // Use UTF-8 encoding, without a byte-order mark. + using (StreamWriter sw = new StreamWriter(pathName, false, new UTF8Encoding(false))) { + mOutStream = sw; + + if (Settings.GetBool(AppSettings.SRCGEN_ADD_IDENT_COMMENT, false)) { + // No version-specific stuff yet. + OutputLine(SourceFormatter.FullLineCommentDelimiter + + string.Format(Properties.Resources.GENERATED_FOR_LATEST, "Merlin 32")); + } + + GenCommon.Generate(this, sw, worker); + } + mOutStream = null; + + return pathNames; + } + + // IGenerator + public void OutputDataOp(int offset) { + Formatter formatter = SourceFormatter; + byte[] data = Project.FileData; + Anattrib attr = Project.GetAnattrib(offset); + + string labelStr = string.Empty; + if (attr.Symbol != null) { + labelStr = mLocalizer.ConvLabel(attr.Symbol.Label); + } + + string commentStr = SourceFormatter.FormatEolComment(Project.Comments[offset]); + string opcodeStr, operandStr; + + FormatDescriptor dfd = attr.DataDescriptor; + Debug.Assert(dfd != null); + int length = dfd.Length; + Debug.Assert(length > 0); + + bool multiLine = false; + switch (dfd.FormatType) { + case FormatDescriptor.Type.Default: + if (length != 1) { + Debug.Assert(false); + length = 1; + } + opcodeStr = sDataOpNames.DefineData1; + int operand = RawData.GetWord(data, offset, length, false); + operandStr = formatter.FormatHexValue(operand, length * 2); + break; + case FormatDescriptor.Type.NumericLE: + opcodeStr = sDataOpNames.GetDefineData(length); + operand = RawData.GetWord(data, offset, length, false); + operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, + mLocalizer.LabelMap, dfd, operand, length, false); + break; + case FormatDescriptor.Type.NumericBE: + opcodeStr = sDataOpNames.GetDefineBigData(length); + if (opcodeStr == null) { + // Nothing defined, output as comma-separated single-byte values. + GenerateShortSequence(offset, length, out opcodeStr, out operandStr); + } else { + operand = RawData.GetWord(data, offset, length, true); + operandStr = PseudoOp.FormatNumericOperand(formatter, Project.SymbolTable, + mLocalizer.LabelMap, dfd, operand, length, false); + } + break; + case FormatDescriptor.Type.Fill: + opcodeStr = sDataOpNames.Fill; + operandStr = length + "," + formatter.FormatHexValue(data[offset], 2); + break; + case FormatDescriptor.Type.Dense: + multiLine = true; + opcodeStr = operandStr = null; + OutputDenseHex(offset, length, labelStr, commentStr); + break; + case FormatDescriptor.Type.String: + multiLine = true; + opcodeStr = operandStr = null; + OutputString(offset, labelStr, commentStr); + break; + default: + opcodeStr = "???"; + operandStr = "***"; + break; + } + + if (!multiLine) { + opcodeStr = formatter.FormatPseudoOp(opcodeStr); + OutputLine(labelStr, opcodeStr, operandStr, commentStr); + } + } + + private void OutputDenseHex(int offset, int length, string labelStr, string commentStr) { + Formatter formatter = SourceFormatter; + byte[] data = Project.FileData; + int maxPerLine = MAX_OPERAND_LEN / 2; + + string opcodeStr = formatter.FormatPseudoOp(sDataOpNames.Dense); + for (int i = 0; i < length; i += maxPerLine) { + int subLen = length - i; + if (subLen > maxPerLine) { + subLen = maxPerLine; + } + string operandStr = formatter.FormatDenseHex(data, offset + i, subLen); + + OutputLine(labelStr, opcodeStr, operandStr, commentStr); + labelStr = commentStr = string.Empty; + } + } + + // IGenerator + public string ReplaceMnemonic(OpDef op) { + if (op.IsUndocumented) { + return null; + } else { + return string.Empty; + } + } + + // IGenerator + public void GenerateShortSequence(int offset, int length, out string opcode, + out string operand) { + Debug.Assert(length >= 1 && length <= 4); + + // Use a comma-separated list of individual hex bytes. + opcode = sDataOpNames.DefineData1; + + StringBuilder sb = new StringBuilder(length * 4); + for (int i = 0; i < length; i++) { + if (i != 0) { + sb.Append(','); + } + sb.Append(SourceFormatter.FormatHexValue(Project.FileData[offset + i], 2)); + } + operand = sb.ToString(); + } + + // IGenerator + public void OutputAsmConfig() { + // nothing to do + } + + // IGenerator + public void OutputEquDirective(string name, string valueStr, string comment) { + OutputLine(name, SourceFormatter.FormatPseudoOp(sDataOpNames.EquDirective), + valueStr, SourceFormatter.FormatEolComment(comment)); + } + + // IGenerator + public void OutputOrgDirective(int address) { + OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(sDataOpNames.OrgDirective), + SourceFormatter.FormatHexValue(address, 4), string.Empty); + } + + // IGenerator + public void OutputRegWidthDirective(int offset, int prevM, int prevX, int newM, int newX) { + // prevM/prevX may be ambiguous for offset 0, but otherwise everything + // should be either 0 or 1. + Debug.Assert(newM == 0 || newM == 1); + Debug.Assert(newX == 0 || newX == 1); + + if (offset == 0 && newM == 1 && newX == 1) { + // Assembler defaults to short regs, so we can skip this. + return; + } + OutputLine(string.Empty, + SourceFormatter.FormatPseudoOp(sDataOpNames.RegWidthDirective), + "%" + newM + newX, string.Empty); + } + + // IGenerator + public void OutputLine(string fullLine) { + mOutStream.WriteLine(fullLine); + } + + // IGenerator + public void OutputLine(string label, string opcode, string operand, string comment) { + // Split long label, but not on EQU directives (confuses the assembler). + if (mLongLabelNewLine && label.Length >= 9 && + !String.Equals(opcode, sDataOpNames.EquDirective, + StringComparison.InvariantCultureIgnoreCase)) { + mOutStream.WriteLine(label); + label = string.Empty; + } + + mLineBuilder.Clear(); + TextUtil.AppendPaddedString(mLineBuilder, label, 9); + TextUtil.AppendPaddedString(mLineBuilder, opcode, 9 + 6); + TextUtil.AppendPaddedString(mLineBuilder, operand, 9 + 6 + 11); + if (string.IsNullOrEmpty(comment)) { + // Trim trailing spaces off of opcode or operand. If they want trailing + // spaces at the end of a comment, that's fine. + CommonUtil.TextUtil.TrimEnd(mLineBuilder); + } else { + mLineBuilder.Append(comment); + } + + mOutStream.WriteLine(mLineBuilder.ToString()); + } + + + private enum RevMode { Forward, Reverse, BlockReverse }; + + private void OutputString(int offset, string labelStr, string commentStr) { + // This gets complicated. + // + // For Dci, L8String, and L16String, the entire string needs to fit in the + // operand of one line. If it can't, we need to separate the length byte/word + // or inverted character out, and just dump the rest as ASCII. Computing the + // line length requires factoring delimiter character escapes. (NOTE: contrary + // to the documentation, STR and STRL do include trailing hex characters in the + // length calculation, so it's possible to escape delimiters.) + // + // For Reverse, we can span lines, but only if we emit the lines in + // backward order. Also, Merlin doesn't allow hex to be embedded in a REV + // operation, so we can't use REV if the string contains a delimiter. + // + // DciReverse is deprecated, but we can handle it as a Reverse string with a + // trailing byte on a following line. + // + // For aesthetic purposes, zero-length CString, L8String, and L16String + // should be output as DFB/DW zeroes rather than an empty string -- makes + // it easier to read. + + Formatter formatter = SourceFormatter; + byte[] data = Project.FileData; + Anattrib attr = Project.GetAnattrib(offset); + FormatDescriptor dfd = attr.DataDescriptor; + Debug.Assert(dfd != null); + Debug.Assert(dfd.FormatType == FormatDescriptor.Type.String); + Debug.Assert(dfd.Length > 0); + + bool highAscii = false; + int showZeroes = 0; + int leadingBytes = 0; + int trailingBytes = 0; + bool showLeading = false; + bool showTrailing = false; + RevMode revMode = RevMode.Forward; + + switch (dfd.FormatSubType) { + case FormatDescriptor.SubType.None: + highAscii = (data[offset] & 0x80) != 0; + break; + case FormatDescriptor.SubType.Dci: + highAscii = (data[offset] & 0x80) != 0; + break; + case FormatDescriptor.SubType.Reverse: + highAscii = (data[offset] & 0x80) != 0; + revMode = RevMode.Reverse; + break; + case FormatDescriptor.SubType.DciReverse: + highAscii = (data[offset + dfd.Length - 1] & 0x80) != 0; + revMode = RevMode.Reverse; + break; + case FormatDescriptor.SubType.CString: + highAscii = (data[offset] & 0x80) != 0; + if (dfd.Length == 1) { + showZeroes = 1; // empty null-terminated string + } + trailingBytes = 1; + showTrailing = true; + break; + case FormatDescriptor.SubType.L8String: + if (dfd.Length > 1) { + highAscii = (data[offset + 1] & 0x80) != 0; + } else { + //showZeroes = 1; + } + leadingBytes = 1; + break; + case FormatDescriptor.SubType.L16String: + if (dfd.Length > 2) { + highAscii = (data[offset + 2] & 0x80) != 0; + } else { + //showZeroes = 2; + } + leadingBytes = 2; + break; + default: + Debug.Assert(false); + return; + } + + if (showZeroes != 0) { + // Empty string. Just output the length byte(s) or null terminator. + GenerateShortSequence(offset, showZeroes, out string opcode, out string operand); + OutputLine(labelStr, opcode, operand, commentStr); + return; + } + + // Merlin 32 uses single-quote for low ASCII, double-quote for high ASCII. When + // quoting the delimiter we use a hexadecimal value. We need to bear in mind that + // we're forcing the characters to low ASCII, but the actual character being + // escaped might be in high ASCII. Hence delim vs. delimReplace. + char delim = highAscii ? '"' : '\''; + char delimReplace = highAscii ? ((char)(delim | 0x80)) : delim; + StringGather gath = null; + + // Run the string through so we can see if it'll fit on one line. As a minor + // optimization, we skip this step for "generic" strings, which are probably + // the most common thing. + if (dfd.FormatSubType != FormatDescriptor.SubType.None) { + gath = new StringGather(this, labelStr, "???", commentStr, delim, + delimReplace, StringGather.ByteStyle.DenseHex, MAX_OPERAND_LEN, true); + FeedGath(gath, data, offset, dfd.Length, revMode, leadingBytes, showLeading, + trailingBytes, showTrailing); + Debug.Assert(gath.NumLinesOutput > 0); + } + + string opcodeStr; + + switch (dfd.FormatSubType) { + case FormatDescriptor.SubType.None: + opcodeStr = highAscii ? sDataOpNames.StrGenericHi : sDataOpNames.StrGeneric; + break; + case FormatDescriptor.SubType.Dci: + if (gath.NumLinesOutput == 1) { + opcodeStr = highAscii ? sDataOpNames.StrDciHi : sDataOpNames.StrDci; + } else { + opcodeStr = highAscii ? sDataOpNames.StrGenericHi : sDataOpNames.StrGeneric; + trailingBytes = 1; + showTrailing = true; + } + break; + case FormatDescriptor.SubType.Reverse: + if (gath.HasDelimiter) { + // can't include escaped delimiters in REV + opcodeStr = highAscii ? sDataOpNames.StrGenericHi : sDataOpNames.StrGeneric; + revMode = RevMode.Forward; + } else if (gath.NumLinesOutput > 1) { + opcodeStr = highAscii ? sDataOpNames.StrReverseHi : sDataOpNames.StrReverse; + revMode = RevMode.BlockReverse; + } else { + opcodeStr = highAscii ? sDataOpNames.StrReverseHi : sDataOpNames.StrReverse; + Debug.Assert(revMode == RevMode.Reverse); + } + break; + case FormatDescriptor.SubType.DciReverse: + // Mostly punt -- output as ASCII with special handling for first byte. + opcodeStr = highAscii ? sDataOpNames.StrGenericHi : sDataOpNames.StrGeneric; + revMode = RevMode.Forward; + leadingBytes = 1; + showLeading = true; + break; + case FormatDescriptor.SubType.CString: + //opcodeStr = sDataOpNames.StrNullTerm[highAscii ? 1 : 0]; + opcodeStr = highAscii ? sDataOpNames.StrGenericHi : sDataOpNames.StrGeneric; + break; + case FormatDescriptor.SubType.L8String: + if (gath.NumLinesOutput == 1) { + opcodeStr = highAscii ? sDataOpNames.StrLen8Hi : sDataOpNames.StrLen8; + } else { + opcodeStr = highAscii ? sDataOpNames.StrGenericHi : sDataOpNames.StrGeneric; + leadingBytes = 1; + showLeading = true; + } + break; + case FormatDescriptor.SubType.L16String: + if (gath.NumLinesOutput == 1) { + opcodeStr = highAscii ? sDataOpNames.StrLen16Hi : sDataOpNames.StrLen16; + } else { + opcodeStr = highAscii ? sDataOpNames.StrGenericHi : sDataOpNames.StrGeneric; + leadingBytes = 2; + showLeading = true; + } + break; + default: + Debug.Assert(false); + return; + } + + opcodeStr = formatter.FormatPseudoOp(opcodeStr); + + // Create a new StringGather, with the final opcode choice. + gath = new StringGather(this, labelStr, opcodeStr, commentStr, delim, + delimReplace, StringGather.ByteStyle.DenseHex, MAX_OPERAND_LEN, false); + FeedGath(gath, data, offset, dfd.Length, revMode, leadingBytes, showLeading, + trailingBytes, showTrailing); + } + + /// + /// Feeds the bytes into the StringGather. + /// + private void FeedGath(StringGather gath, byte[] data, int offset, int length, + RevMode revMode, int leadingBytes, bool showLeading, + int trailingBytes, bool showTrailing) { + int startOffset = offset; + int strEndOffset = offset + length - trailingBytes; + + if (showLeading) { + while (leadingBytes-- > 0) { + gath.WriteByte(data[offset++]); + } + } else { + offset += leadingBytes; + } + if (revMode == RevMode.BlockReverse) { + const int maxPerLine = MAX_OPERAND_LEN - 2; + int numBlockLines = (length + maxPerLine - 1) / maxPerLine; + + for (int chunk = 0; chunk < numBlockLines; chunk++) { + int chunkOffset = startOffset + chunk * maxPerLine; + int endOffset = chunkOffset + maxPerLine; + if (endOffset > strEndOffset) { + endOffset = strEndOffset; + } + for (int off = endOffset - 1; off >= chunkOffset; off--) { + gath.WriteChar((char)(data[off] & 0x7f)); + } + } + } else { + for (; offset < strEndOffset; offset++) { + if (revMode == RevMode.Forward) { + gath.WriteChar((char)(data[offset] & 0x7f)); + } else if (revMode == RevMode.Reverse) { + int posn = startOffset + (strEndOffset - offset) - 1; + gath.WriteChar((char)(data[posn] & 0x7f)); + } else { + Debug.Assert(false); + } + } + } + while (showTrailing && trailingBytes-- > 0) { + gath.WriteByte(data[offset++]); + } + gath.Finish(); + } + } +} diff --git a/SourceGen/AsmGen/GeneratorProgress.Designer.cs b/SourceGen/AsmGen/GeneratorProgress.Designer.cs new file mode 100644 index 0000000..43911d7 --- /dev/null +++ b/SourceGen/AsmGen/GeneratorProgress.Designer.cs @@ -0,0 +1,114 @@ +/* + * Copyright 2018 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. + */ +namespace SourceGen.AsmGen { + partial class GeneratorProgress { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.progressLabel = new System.Windows.Forms.Label(); + this.cancelButton = new System.Windows.Forms.Button(); + this.progressBar1 = new System.Windows.Forms.ProgressBar(); + this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker(); + this.SuspendLayout(); + // + // progressLabel + // + this.progressLabel.AutoSize = true; + this.progressLabel.Location = new System.Drawing.Point(13, 13); + this.progressLabel.Name = "progressLabel"; + this.progressLabel.Size = new System.Drawing.Size(61, 13); + this.progressLabel.TabIndex = 1; + this.progressLabel.Text = "Preparing..."; + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.cancelButton.Location = new System.Drawing.Point(275, 79); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 0; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click); + // + // progressBar1 + // + this.progressBar1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.progressBar1.Location = new System.Drawing.Point(12, 39); + this.progressBar1.Name = "progressBar1"; + this.progressBar1.Size = new System.Drawing.Size(600, 23); + this.progressBar1.Style = System.Windows.Forms.ProgressBarStyle.Continuous; + this.progressBar1.TabIndex = 2; + // + // backgroundWorker1 + // + this.backgroundWorker1.WorkerReportsProgress = true; + this.backgroundWorker1.WorkerSupportsCancellation = true; + this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork); + this.backgroundWorker1.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(this.backgroundWorker1_ProgressChanged); + this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted); + // + // GeneratorProgress + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(624, 114); + this.Controls.Add(this.progressBar1); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.progressLabel); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "GeneratorProgress"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Generating Source..."; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.GeneratorProgress_FormClosing); + this.Load += new System.EventHandler(this.GeneratorProgress_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label progressLabel; + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.ProgressBar progressBar1; + private System.ComponentModel.BackgroundWorker backgroundWorker1; + } +} \ No newline at end of file diff --git a/SourceGen/AsmGen/GeneratorProgress.cs b/SourceGen/AsmGen/GeneratorProgress.cs new file mode 100644 index 0000000..d6a8dae --- /dev/null +++ b/SourceGen/AsmGen/GeneratorProgress.cs @@ -0,0 +1,124 @@ +/* + * Copyright 2018 faddenSoft + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Windows.Forms; + +namespace SourceGen.AsmGen { + /// + /// Dialog that shows the progress of the assembler, and allows cancellation. + /// + public partial class GeneratorProgress : Form { + /// + /// Full pathnames of generated files. Will be null on error or user cancelation. + /// + public List Results { get; private set; } + + private IGenerator mGenerator; + + + /// + /// Constructor. + /// + /// Fully-configured source generator. + public GeneratorProgress(IGenerator gen) { + InitializeComponent(); + + mGenerator = gen; + } + + private void GeneratorProgress_Load(object sender, EventArgs e) { + backgroundWorker1.RunWorkerAsync(); + } + + private void cancelButton_Click(object sender, EventArgs e) { + backgroundWorker1.CancelAsync(); + cancelButton.Enabled = false; + } + + private void GeneratorProgress_FormClosing(object sender, FormClosingEventArgs e) { + // The close button will close the dialog without canceling the event. We + // cancel it here, which should cause it to stop relatively quickly, but we don't + // wait for it on the off chance that something weird is going on and it got + // stuck. If nothing else, this gives the user a chance to save their work. + if (backgroundWorker1.IsBusy) { + backgroundWorker1.CancelAsync(); + DialogResult = DialogResult.Cancel; + } + } + + // NOTE: executes on work thread. DO NOT do any UI work here. DO NOT access + // the Results property directly. + private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { + BackgroundWorker worker = sender as BackgroundWorker; + + // This will throw an I/O exception if there's a problem with the file. This + // will be caught and transferred to RunWorkerCompleted. + List fileNames = mGenerator.GenerateSource(worker); + if (worker.CancellationPending) { + e.Cancel = true; + } else { + e.Result = fileNames; + } + } + + // Callback that fires when a progress update is made. + private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) { + int percent = e.ProgressPercentage; + string msg = e.UserState as string; + + Debug.Assert(percent >= 0 && percent <= 100); + + if (!string.IsNullOrEmpty(msg)) { + progressLabel.Text = msg; + } + progressBar1.Value = percent; + } + + // Callback that fires when execution completes. + private void backgroundWorker1_RunWorkerCompleted(object sender, + RunWorkerCompletedEventArgs e) { + if (e.Cancelled) { + Debug.WriteLine("CANCELED"); + DialogResult = DialogResult.Cancel; + } else if (e.Error != null) { + // This should only happen on a file I/O error, e.g. out of disk space or + // unable to overwrite an existing file. + MessageBox.Show(e.Error.ToString(), Properties.Resources.OPERATION_FAILED, + MessageBoxButtons.OK, MessageBoxIcon.Error); + DialogResult = DialogResult.Cancel; + } else { + // Make results available in property. + Results = e.Result as List; + + if (Results == null || Results.Count == 0) { + // Shouldn't happen -- generator should have reported error. + MessageBox.Show("Internal error: no files generated", + Properties.Resources.OPERATION_FAILED, + MessageBoxButtons.OK, MessageBoxIcon.Error); + } else { + Debug.WriteLine("SUCCESS " + Results.Count); + } + DialogResult = DialogResult.OK; + } + + // Whatever the case, we're done. + this.Close(); + } + } +} diff --git a/SourceGen/AsmGen/GeneratorProgress.resx b/SourceGen/AsmGen/GeneratorProgress.resx new file mode 100644 index 0000000..59099f2 --- /dev/null +++ b/SourceGen/AsmGen/GeneratorProgress.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/SourceGen/AsmGen/IAssembler.cs b/SourceGen/AsmGen/IAssembler.cs new file mode 100644 index 0000000..d884b8b --- /dev/null +++ b/SourceGen/AsmGen/IAssembler.cs @@ -0,0 +1,67 @@ +/* + * Copyright 2018 faddenSoft + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace SourceGen.AsmGen { + /// + /// Common interface for executing assemblers. + /// + public interface IAssembler { + /// + /// Queries the assembler for its version. + /// + /// Assembler version info, or null if query failed. + AssemblerVersion QueryVersion(); + + /// + /// Configures the object. Pass in the list of pathnames returned by IGenerator.Run(), + /// and the working directory to use for the shell command. + /// + /// Assembler source pathnames. + /// Working directory for shell command. + void Configure(List pathNames, string workDirectory); + + /// + /// Executes the assembler. Must call Configure() first. Executed on background thread. + /// + /// Async work object, used to report progress updates and + /// check for cancellation. + /// Execution results, or null on internal failure. + AssemblerResults RunAssembler(BackgroundWorker worker); + } + + /// + /// Set of values returned by the assembler. + /// + public class AssemblerResults { + public string CommandLine { get; private set; } + public int ExitCode { get; private set; } + public string Stdout { get; private set; } + public string Stderr { get; private set; } + public string OutputPathName { get; private set; } + + public AssemblerResults(string commandLine, int exitCode, string stdout, string stderr, + string outputFile) { + CommandLine = commandLine; + ExitCode = exitCode; + Stdout = stdout; + Stderr = stderr; + OutputPathName = outputFile; + } + } +} diff --git a/SourceGen/AsmGen/IGenerator.cs b/SourceGen/AsmGen/IGenerator.cs new file mode 100644 index 0000000..fc33913 --- /dev/null +++ b/SourceGen/AsmGen/IGenerator.cs @@ -0,0 +1,154 @@ +/* + * Copyright 2018 faddenSoft + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Collections.Generic; +using System.ComponentModel; + +using Asm65; + +namespace SourceGen.AsmGen { + /// + /// Common interface for generating assembler-specific source code. + /// + public interface IGenerator { + /// + /// Configure generator. Must be called before calling any other method or using + /// properties. + /// + /// Project to generate source for. + /// Directory in which to create output files. + /// Name to use as base for filenames. + /// Version of assembler to target. Pass in null + /// to target latest known version. + /// App settings object. + void Configure(DisasmProject project, string workDirectory, string fileNameBase, + AssemblerVersion asmVersion, AppSettings settings); + + /// + /// Project object with file data and Anattribs. + /// + DisasmProject Project { get; } + + /// + /// Source code formatter. + /// + Formatter SourceFormatter { get; } + + /// + /// Application settings. + /// + AppSettings Settings { get; } + + /// + /// Assembler-specific behavior. Used to handle quirky behavior for things that + /// are otherwise managed by common code. + /// + AssemblerQuirks Quirks { get; } + + LabelLocalizer Localizer { get; } + + /// + /// Generates source files on a background thread. Method must not make any UI calls. + /// + /// Async work object, used to report progress updates and + /// check for cancellation. + /// List of pathnames of generated files. + List GenerateSource(BackgroundWorker worker); + + /// + /// Provides an opportunity for the assembler to replace a mnemonic with another. This + /// is primarily intended for undocumented ops, which don't have standard mnemonics, + /// and hence can vary between assemblers. + /// + /// + /// + /// Replacement mnemonic, an empty string if the original is fine, or + /// null if the op is not supported at all and should be emitted as hex. + string ReplaceMnemonic(OpDef op); + + /// + /// Generates an opcode/operand pair for a short sequence of bytes (1-4 bytes). + /// Does not produce any source output. + /// + /// + /// + /// + /// + void GenerateShortSequence(int offset, int length, out string opcode, out string operand); + + /// + /// Outputs zero or more lines of assembler configuration. This comes after the + /// header comment but before any directives. Useful for configuring the CPU type + /// and assembler options. + /// + void OutputAsmConfig(); + + /// + /// Outputs one or more lines of data for the specified offset. + /// + /// + void OutputDataOp(int offset); + + /// + /// Outputs an equate directive. The numeric value is already formatted. + /// + /// + /// + /// + void OutputEquDirective(string name, string valueStr, string comment); + + /// + /// Outputs a code origin directive. + /// + /// + void OutputOrgDirective(int address); + + /// + /// Notify the assembler of a change in register width. + /// + /// Merlin32 always sets both values (e.g. "MX %00"), cc65 sets each register + /// individually (".A16", ".I8"). We need to accommodate both styles. + /// + /// + /// + /// + /// + /// + void OutputRegWidthDirective(int offset, int prevM, int prevX, int newM, int newX); + + /// + /// Output a line of source code. All elements must be fully formatted. The + /// items will be padded with spaces to fit specific column widths. + /// + /// + /// + /// + /// + void OutputLine(string label, string opcode, string operand, string comment); + + /// + /// Output a line of source code. + /// + /// + void OutputLine(string fullLine); + } + + public class AssemblerQuirks { + public bool BlockMoveArgsReversed { get; set; } + public bool TracksSepRepNotEmu { get; set; } + public bool NoPcRelBankWrap { get; set; } + } +} \ No newline at end of file diff --git a/SourceGen/AsmGen/LabelLocalizer.cs b/SourceGen/AsmGen/LabelLocalizer.cs new file mode 100644 index 0000000..ecbd32c --- /dev/null +++ b/SourceGen/AsmGen/LabelLocalizer.cs @@ -0,0 +1,313 @@ +/* + * Copyright 2018 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; +using System.Collections.Generic; +using System.Diagnostics; + +/* +Some assemblers support "local labels", with varying definitions of scope and features. +Generally speaking, local labels only need to be unique within a certain limited scope, and +they aren't included in end-of-assembly symbol lists. + +One popular form defines its scope as being between two global labels. So this is allowed: + + glob1 lda #$00 + :local sta $00 + glob2 lda #$01 + :local sta $01 + +but this would cause an error: + + glob1 lda #$00 + :local sta $00 + glob2 lda #$01 + bne :local + +because the local symbol table is cleared when a global symbol is encountered. + +Another common form allows backward references to labels that don't go out of scope until +they're re-used. This is useful for short loops. + +As a further limitation, assemblers seem to want the first label encountered in a program +to be global. + +The Symbol.SymbolType enum allows a label to be defined as "local or global". We can output +these with the local-symbol syntax, potentially rewriting them to have non-unique names like +"loop", but we can't promote (demote?) a label to local unless there are no references to it +that cross a global label. + +The cross-reference table we generate as part of the analysis process provides a full list of +label references, so we just need to iterate through the label list until we can't find +anything else that needs to be made global. + +Because the definition of "local label" is somewhat assembler-specific, it's best to defer +this analysis to code generation time, when the specific characteristics of the target +assembler can be taken into account. + +References to an offset can be numeric or symbolic. A purely numeric reference like "LDA $2000" +will always map to the offset associated with address $2000, but a symbolic reference might be +offset. For example, the LDA instruction could reference a label at $2008 as "LDA FOO-8". +The assembler cares about the symbolic references, not the actual offsets or addresses. For +this reason we can ignore references to an address with a label if those references don't +actually use the label. (One consequence of this is that formatting an operand as hex +eliminates it from the set of things for us to consider. Also, ORG directives have no effect +on the localizer.) + +Labels that are marked as global, but to which there are no references, could in theory be +elided. To do this we would have to omit them from the generated code, which would be +annoying and weird if (say) the user added them to label an external entry point. + + +The eventual output of our efforts is a map from the original symbol name to the local symbol +name. This must be applied to both labels and operands. +*/ + +namespace SourceGen.AsmGen { + public class LabelLocalizer { + /// + /// A pairing of an offset with a label string. (Essentially mAnattribs[n].Symbol + /// with all the fluff trimmed away.) + /// + /// The label string isn't actually all that useful, since we can pull it back out + /// of anattrib, but it makes life a little easier during debugging. These get + /// put into a List, so switching to a plain int offset doesn't necessarily help us + /// much because the ints get boxed. + /// + private class OffsetLabel { + public int Offset { get; private set; } + public string Label { get; private set; } + + public OffsetLabel(int offset, string label) { + Offset = offset; + Label = label; + } + + public override string ToString() { + return "+" + Offset.ToString("x6") + "(" + Label + ")"; + } + } + + /// + /// A pair of offsets. An operand (instruction or data) at the source offset + /// references a label at the destination offset. + /// + private class OffsetPair { + public int SrcOffset { get; private set; } // offset from which reference is made + public int DstOffset { get; private set; } // offset being referred to + + public OffsetPair(int src, int dst) { + SrcOffset = src; + DstOffset = dst; + } + + public override string ToString() { + return "src=+" + SrcOffset.ToString("x6") + " dst=+" + DstOffset.ToString("x6"); + } + } + + /// + /// Map from label string to local label string. This will be null until Analyze() + /// has executed. + /// + public Dictionary LabelMap { get; private set; } + + /// + /// String to prefix to local labels. + /// + public string LocalPrefix { get; set; } + + /// + /// Project reference. + /// + private DisasmProject mProject; + + // Work state. + private List mGlobalLabels = new List(); + private List mOffsetPairs = new List(); + private BitArray mGlobalFlags; + + + public LabelLocalizer(DisasmProject project) { + mProject = project; + mGlobalFlags = new BitArray(mProject.FileDataLength); + + LocalPrefix = "!?"; + } + + /// + /// Applies the LabelMap to the label. If the LabelMap is null, or does not have an + /// entry for the label, the original label is returned. + /// + /// Label to convert. + /// New label, or original label. + public string ConvLabel(string label) { + if (LabelMap != null) { + if (LabelMap.TryGetValue(label, out string newLabel)) { + label = newLabel; + } + } + return label; + } + + /// + /// Analyzes labels to identify which ones may be treated as non-global. + /// + public void Analyze() { + mGlobalFlags.SetAll(false); + + // Currently we only support the "local labels have scope that ends at a global + // label" variety. The basic idea is to start by assuming that everything not + // explicitly marked global is local, and then identify situations like this: + // + // lda :local + // global eor #$ff + // :local sta $00 + // + // The reference crosses a global label, so the "target" label must be made global. + // This can have ripple effects, so we have to iterate. Note it doesn't matter + // whether "global" is referenced anywhere. + // + // The current algorithm uses a straightforward O(n^2) approach. + + // Step 1: generate source/target pairs and global label list + GenerateLists(); + + // Step 2: walk through the list of global symbols, identifying source/target + // pairs that cross them. If a pair matches, the target label is added to the + // mGlobalLabels list, and removed from the pair list. + for (int index = 0; index < mGlobalLabels.Count; index++) { + FindIntersectingPairs(mGlobalLabels[index]); + } + + // Step 3: for each local label, add an entry to the map with the appropriate + // local-label syntax. + LabelMap = new Dictionary(); + for (int i = 0; i < mProject.FileDataLength; i++) { + if (mGlobalFlags[i]) { + continue; + } + Symbol sym = mProject.GetAnattrib(i).Symbol; + if (sym == null) { + continue; + } + + LabelMap[sym.Label] = LocalPrefix + sym.Label; + } + + // Take out the trash. + mGlobalLabels.Clear(); + mOffsetPairs.Clear(); + } + + /// + /// Generates the initial mGlobalFlags and mGlobalLabels lists, as well as the + /// full cross-reference pair list. + /// + private void GenerateLists() { + // For every offset that has a label, add an entry to the source/target pair list + // for every offset that references it. + // + // If the label isn't marked as "local or global", add it to the global-label list. + // + // The first label encountered is always treated as global. Note it may not appear + // at offset zero. + + bool first = true; + + for (int i = 0; i < mProject.FileDataLength; i++) { + Symbol sym = mProject.GetAnattrib(i).Symbol; + if (sym == null) { + // No label at this offset. + continue; + } + + if (first || sym.SymbolType != Symbol.Type.LocalOrGlobalAddr) { + first = false; + mGlobalFlags[i] = true; + mGlobalLabels.Add(new OffsetLabel(i, sym.Label)); + + // Don't add to pairs list. + continue; + } + + // If nothing actually references this label, the xref set will be empty. + XrefSet xrefs = mProject.GetXrefSet(i); + if (xrefs != null) { + foreach (XrefSet.Xref xref in xrefs) { + if (!xref.IsSymbolic) { + continue; + } + + mOffsetPairs.Add(new OffsetPair(xref.Offset, i)); + } + } + } + } + + /// + /// Identifies all label reference pairs that cross the specified global label. When + /// a matching pair is found, the pair's destination label is marked as global and + /// added to the global label list. + /// + /// Global label of interest. + private void FindIntersectingPairs(OffsetLabel glabel) { + Debug.Assert(mGlobalFlags[glabel.Offset]); + + int globOffset = glabel.Offset; + for (int i = 0; i < mOffsetPairs.Count; i++) { + OffsetPair pair = mOffsetPairs[i]; + + // If the destination was marked global earlier, remove and ignore this entry. + // Note this also means that pair.DstOffset != label.Offset. + if (mGlobalFlags[pair.DstOffset]) { + mOffsetPairs.RemoveAt(i); + i--; + continue; + } + + // Check to see if the global label falls between the source and destination + // offsets. + // + // If the reference source is itself a global label, it can reference local + // labels forward, but not backward. We need to take that into account for + // the case where label.Offset==pair.SrcOffset. + bool intersect; + if (pair.SrcOffset < pair.DstOffset) { + // Forward reference. src==glob is ok + intersect = pair.SrcOffset < globOffset && pair.DstOffset >= globOffset; + } else { + // Backward reference. src==glob is bad + intersect = pair.SrcOffset >= globOffset && pair.DstOffset <= globOffset; + } + + if (intersect) { + //Debug.WriteLine("Global " + glabel + " btwn " + pair + " (" + + // mProject.GetAnattrib(pair.DstOffset).Symbol.Label + ")"); + + // Change the destination label to global. + mGlobalFlags[pair.DstOffset] = true; + mGlobalLabels.Add(new OffsetLabel(pair.DstOffset, + mProject.GetAnattrib(pair.DstOffset).Symbol.Label)); + + // Carefully remove it from the list we're iterating through. + mOffsetPairs.RemoveAt(i); + i--; + } + } + } + } +} diff --git a/SourceGen/AsmGen/StringGather.cs b/SourceGen/AsmGen/StringGather.cs new file mode 100644 index 0000000..798f349 --- /dev/null +++ b/SourceGen/AsmGen/StringGather.cs @@ -0,0 +1,227 @@ +/* + * Copyright 2018 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.Diagnostics; + +namespace SourceGen.AsmGen { + /// + /// Multi-line string gatherer. Accumulates characters and raw bytes, emitting + /// them when we have a full operand's worth. + /// + /// If the delimiter character appears, it will be output inline as a raw byte. + /// The low-ASCII string ['hello'world'] will become [27,'hello',27,'world',27] + /// (or something similar). + /// + public class StringGather { + // Inputs. + public IGenerator Gen { get; private set; } + public string Label { get; private set; } + public string Opcode { get; private set; } + public string Comment { get; private set; } + public char Delimiter { get; private set; } + public char DelimiterReplacement { get; private set; } + public ByteStyle ByteStyleX { get; private set; } + public int MaxOperandLen { get; private set; } + public bool IsTestRun { get; private set; } + + public enum ByteStyle { DenseHex, CommaSep }; + + // Outputs. + public bool HasDelimiter { get; private set; } + public int NumLinesOutput { get; private set; } + + private char[] mHexChars; + + /// + /// Character collection buffer. The delimiters are written into the buffer + /// because they're mixed with bytes, particularly when we have to escape the + /// delimiter character. Strings might start or end with escaped delimiters, + /// so we don't add them until we have to. + private char[] mBuffer; + + /// + /// Next available character position. + /// + private int mIndex = 0; + + /// + /// State of the buffer, based on the last thing we added. + /// + private enum State { + Unknown = 0, + StartOfLine, + InQuote, + OutQuote + } + private State mState = State.StartOfLine; + + /// + /// Constructor. + /// + /// Reference back to generator, for output function and + /// format options. + /// Line label. Appears on first output line only. + /// Opcode to use for all lines. + /// End-of-line comment. Appears on first output line + /// only. + /// String delimiter character. + /// If true, no file output is produced. + public StringGather(IGenerator gen, string label, string opcode, + string comment, char delimiter, char delimReplace, ByteStyle byteStyle, + int maxOperandLen, bool isTestRun) { + Gen = gen; + Label = label; + Opcode = opcode; + Comment = comment; + Delimiter = delimiter; + DelimiterReplacement = delimReplace; + ByteStyleX = byteStyle; + MaxOperandLen = maxOperandLen; + IsTestRun = isTestRun; + + mBuffer = new char[MaxOperandLen]; + mHexChars = Gen.SourceFormatter.HexDigits; + } + + /// + /// Write a character into the buffer. + /// + /// Character to add. + public void WriteChar(char ch) { + Debug.Assert(ch >= 0 && ch <= 0xff); + if (ch == Delimiter) { + // Must write it as a byte. + HasDelimiter = true; + WriteByte((byte)DelimiterReplacement); + return; + } + + // If we're at the start of a line, add delimiter, then new char. + // If we're inside quotes, just add the character. We must have space for + // two chars (new char, close quote). + // If we're outside quotes, add a comma and delimiter, then the character. + // We must have 4 chars remaining (comma, open quote, new char, close quote). + switch (mState) { + case State.StartOfLine: + mBuffer[mIndex++] = Delimiter; + break; + case State.InQuote: + if (mIndex + 2 > MaxOperandLen) { + Flush(); + mBuffer[mIndex++] = Delimiter; + } + break; + case State.OutQuote: + if (mIndex + 4 > MaxOperandLen) { + Flush(); + mBuffer[mIndex++] = Delimiter; + } else { + mBuffer[mIndex++] = ','; + mBuffer[mIndex++] = Delimiter; + } + break; + default: + Debug.Assert(false); + break; + } + mBuffer[mIndex++] = ch; + mState = State.InQuote; + } + + /// + /// Write a hex value into the buffer. + /// + /// Value to add. + public void WriteByte(byte val) { + // If we're at the start of a line, just output the byte. + // If we're inside quotes, emit a delimiter, comma, and the byte. We must + // have space for four (DenseHex) or five (CommaSep) chars. + // If we're outside quotes, add the byte. We must have two (DenseHex) or + // four (CommaSep) chars remaining. + switch (mState) { + case State.StartOfLine: + break; + case State.InQuote: + int minWidth = (ByteStyleX == ByteStyle.CommaSep) ? 5 : 4; + if (mIndex + minWidth > MaxOperandLen) { + Flush(); + } else { + mBuffer[mIndex++] = Delimiter; + mBuffer[mIndex++] = ','; + } + break; + case State.OutQuote: + minWidth = (ByteStyleX == ByteStyle.CommaSep) ? 4 : 2; + if (mIndex + minWidth > MaxOperandLen) { + Flush(); + } else { + if (ByteStyleX == ByteStyle.CommaSep) { + mBuffer[mIndex++] = ','; + } + } + break; + default: + Debug.Assert(false); + break; + } + + if (ByteStyleX == ByteStyle.CommaSep) { + mBuffer[mIndex++] = '$'; + } + mBuffer[mIndex++] = mHexChars[val >> 4]; + mBuffer[mIndex++] = mHexChars[val & 0x0f]; + mState = State.OutQuote; + } + + /// + /// Tells the object to flush any pending data to the output. + /// + public void Finish() { + Flush(); + } + + /// + /// Outputs the buffer of pending data. A closing delimiter will be added if needed. + /// + private void Flush() { + switch (mState) { + case State.StartOfLine: + // empty string; put out a pair of delimiters + mBuffer[mIndex++] = Delimiter; + mBuffer[mIndex++] = Delimiter; + NumLinesOutput++; + break; + case State.InQuote: + // add delimiter and finish + mBuffer[mIndex++] = Delimiter; + NumLinesOutput++; + break; + case State.OutQuote: + // just output it + NumLinesOutput++; + break; + } + if (!IsTestRun) { + Gen.OutputLine(Label, Opcode, new string(mBuffer, 0, mIndex), + Comment); + } + mIndex = 0; + + // Erase these after first use so we don't put them on every line. + Label = Comment = string.Empty; + } + } +} diff --git a/SourceGen/ChangeSet.cs b/SourceGen/ChangeSet.cs new file mode 100644 index 0000000..4e953f9 --- /dev/null +++ b/SourceGen/ChangeSet.cs @@ -0,0 +1,109 @@ +/* + * Copyright 2018 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; +using System.Collections.Generic; +using System.Diagnostics; + +namespace SourceGen { + /// + /// Holds information about a set of changes. + /// + /// Does not have hooks into other data structures. This just holds the information + /// about the changes. + /// + public class ChangeSet : IEnumerable { + private List mChanges; + + /// + /// Constructs an empty ChangeSet with the specified initial capacity. + /// + /// Initial number of elements that the set can contain. + public ChangeSet(int capacity) { + mChanges = new List(capacity); + } + + /// + /// Constructs a ChangeSet with a single change. + /// + public ChangeSet(UndoableChange ac) { + mChanges = new List(1); + mChanges.Add(ac); + } + + /// + /// The number of changes in the set. + /// + public int Count { get { return mChanges.Count; } } + + /// + /// Returns the Nth change in the set. + /// + /// Change index. + public UndoableChange this[int key] { + get { + return mChanges[key]; + } + } + + /// + /// Adds a change to the change set. + /// + /// Change to add. + public void Add(UndoableChange change) { + Debug.Assert(change != null); + mChanges.Add(change); + } + + /// + /// Adds a change to the change set if the object is non-null. + /// + /// Change to add, or null. + public void AddNonNull(UndoableChange change) { + if (change != null) { + Add(change); + } + } + + /// + /// Trims unused capacity from the set. + /// + public void TrimExcess() { + mChanges.TrimExcess(); + } + + // IEnumerable, so we can use foreach syntax when going forward + public IEnumerator GetEnumerator() { + return mChanges.GetEnumerator(); + } + + // IEnumerable: generic version + IEnumerator IEnumerable.GetEnumerator() { + return mChanges.GetEnumerator(); + } + + // TODO(maybe): reverse-order enumerator? + + public override string ToString() { + string str = "[CS: count=" + mChanges.Count; + if (mChanges.Count > 0) { + str += " {0:" + mChanges[0] + "}"; + } + str += "]"; + return str; + } + } +} diff --git a/SourceGen/CodeAnalysis.cs b/SourceGen/CodeAnalysis.cs new file mode 100644 index 0000000..df2845f --- /dev/null +++ b/SourceGen/CodeAnalysis.cs @@ -0,0 +1,1070 @@ +/* + * Copyright 2018 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 Asm65; +using CommonUtil; +using PluginCommon; +using SourceGen.Sandbox; + +namespace SourceGen { + /// + /// Instruction analyzer. + /// + /// All data held in this object is transient, and will be discarded when analysis + /// completes. All user-defined values should be held elsewhere and provided as inputs + /// to the analyzer. Any change that merits re-analysis should be handled by creating a + /// new instance of this object. + /// + /// See the comments at the top of UndoableChange for a list of things that can + /// mandate code re-analysis. + /// + public class CodeAnalysis { + /// + /// Type hints are specified by the user. The identify a region as being code + /// or data. The code analyzer will stop at data-hinted regions, and will + /// process any code-hinted regions during the dead-code pass. + /// + /// The hints are not used directly by the data analyzer, but the effects they + /// have on the Anattrib array are. + /// + public enum TypeHint : sbyte { + // No hint. Default value populated in new arrays. + NoHint = 0, + + // Byte is an instruction. If the code analyzer doesn't find this + // naturally, it will be scanned. + Code, + + // Byte is inline data. Execution continues "through" the byte. + InlineData, + + // Byte is data. Execution halts. + Data + } + + /// + /// Class for handling callbacks from extension scripts. + /// + private class ScriptSupport : MarshalByRefObject, PluginCommon.IApplication { + private CodeAnalysis mOuter; + + public ScriptSupport(CodeAnalysis ca) { + mOuter = ca; + } + + /// + /// Call this when analysis is complete, to ensure that over-active scripts + /// can't keep doing things. (This is not part of IApplication.) + /// + public void Shutdown() { + mOuter = null; + } + + public void DebugLog(string msg) { + mOuter.mDebugLog.LogI("PLUGIN: " + msg); + } + + public bool SetOperandFormat(int offset, DataSubType subType, string label) { + return mOuter.SetOperandFormat(offset, subType, label); + } + + public bool SetInlineDataFormat(int offset, int length, DataType type, + DataSubType subType, string label) { + return mOuter.SetInlineDataFormat(offset, length, type, subType, label); + } + } + + /// + /// Extension script manager. + /// + private ScriptManager mScriptManager; + + /// + /// Local object that implements the IApplication interface for plugins. + /// + private ScriptSupport mScriptSupport; + + /// + /// List of interesting plugins. If we have plugins that don't do code inlining we + /// can ignore them. (I'm using an array instead of a List<IPlugin> as a + /// micro-optimization; see https://stackoverflow.com/a/454923/294248 .) + /// + private IPlugin[] mScriptArray; + + /// + /// CPU to use when analyzing data. + /// + private CpuDef mCpuDef; + + /// + /// Map of offsets to addresses. + /// + private AddressMap mAddrMap; + + /// + /// Reference to 65xx data. + /// + private byte[] mFileData; + + /// + /// Attributes, one per byte in input file. + /// + private Anattrib[] mAnattribs; + + /// + /// Reference to type hint array, one hint per byte. + /// + private TypeHint[] mTypeHints; + + /// + /// Reference to status flag override array, one entry per byte. + /// + private StatusFlags[] mStatusFlagOverrides; + + /// + /// Initial status flags to use at entry points. + /// + private StatusFlags mEntryFlags; + + /// + /// Debug trace log. + /// + private DebugLog mDebugLog = new DebugLog(DebugLog.Priority.Silent); + + + /// + /// Constructor. + /// + /// 65xx code stream. + /// CPU definition to use when interpreting code. + /// Anattrib array. Expected to be newly allocated, all + /// entries set to default values. + /// Map of offsets to addresses. + /// Type hints, one per byte. + /// Status flag overrides for instruction-start + /// bytes. + /// Status flags to use at code entry points. + /// Extension script manager. + /// Object that receives debug log messages. + public CodeAnalysis(byte[] data, CpuDef cpuDef, Anattrib[] anattribs, + AddressMap addrMap, TypeHint[] hints, StatusFlags[] statusFlagOverrides, + StatusFlags entryFlags, ScriptManager scriptMan, DebugLog debugLog) { + mFileData = data; + mCpuDef = cpuDef; + mAnattribs = anattribs; + mAddrMap = addrMap; + mTypeHints = hints; + mStatusFlagOverrides = statusFlagOverrides; + mEntryFlags = entryFlags; + mScriptManager = scriptMan; + mDebugLog = debugLog; + + mScriptSupport = new ScriptSupport(this); + } + + // Internal log functions. If we're concerned about performance overhead due to + // call-site string concatenation, we can #ifdef these to nothing in release builds, + // which should allow the compiler to elide the concat. +#if false + private void LogV(int offset, string msg) { + if (mDebugLog.IsLoggable(DebugLog.Priority.Verbose)) { + mDebugLog.LogV("+" + offset.ToString("x6") + " " + msg); + } + } +#else + private void LogV(int offset, string msg) { } +#endif +#if true + private void LogD(int offset, string msg) { + if (mDebugLog.IsLoggable(DebugLog.Priority.Debug)) { + mDebugLog.LogD("+" + offset.ToString("x6") + " " + msg); + } + } + private void LogI(int offset, string msg) { + if (mDebugLog.IsLoggable(DebugLog.Priority.Info)) { + mDebugLog.LogI("+" + offset.ToString("x6") + " " + msg); + } + } + private void LogW(int offset, string msg) { + if (mDebugLog.IsLoggable(DebugLog.Priority.Warning)) { + mDebugLog.LogW("+" + offset.ToString("x6") + " " + msg); + } + } + private void LogE(int offset, string msg) { + if (mDebugLog.IsLoggable(DebugLog.Priority.Error)) { + mDebugLog.LogE("+" + offset.ToString("x6") + " " + msg); + } + } +#else + private void LogD(int offset, string msg) { } + private void LogI(int offset, string msg) { } + private void LogW(int offset, string msg) { } + private void LogE(int offset, string msg) { } +#endif + + /// + /// Analyze a blob of code and data, annotating all code areas. + /// + /// Also identifies data embedded in code, e.g. parameter blocks following a JSR, + /// with the help of extension scripts. + /// + /// Failing here can leave us in a strange state, so prefer to work around unexpected + /// inputs rather than bailing entirely. + /// + public void Analyze() { + List scanOffsets = new List(); + + mDebugLog.LogI("Analyzing code: " + mFileData.Length + " bytes, CPU=" + mCpuDef.Name); + + PrepareScripts(); + + SetAddresses(); + + // Set the "is data" and "is inline data" flags on anything that the user has + // flagged as being such. This tells us to stop processing or skip over bytes + // as we work. We don't need to flag code hints explicitly for analysis, but + // we want to be able to display the flags in the info window. + // + // The data recognizers may spot additional inline data offsets as we work. This + // can cause a race if it mis-identifies code that is also a branch target; + // whichever marks the code first will win. + UnpackTypeHints(); + + // Find starting place, based on type hints. + // We only set the "visited" flag on the instruction start, so if the user + // puts a code hint in the middle of an instruction, we will find it and + // treat it as an entry point. (This is useful for embedded instructions + // that are branched to by code we aren't able to detect.) + int searchStart = FindFirstUnvisitedInstruction(0); + while (searchStart >= 0) { + mAnattribs[searchStart].IsEntryPoint = true; + mAnattribs[searchStart].StatusFlags = mEntryFlags; + mAnattribs[searchStart].ApplyStatusFlags(mStatusFlagOverrides[searchStart]); + + int offset = searchStart; + while (true) { + bool embedded = (mAnattribs[offset].IsInstruction && + !mAnattribs[offset].IsVisited); + LogI(offset, "Scan chunk (vis=" + mAnattribs[offset].IsVisited + + " chg=" + mAnattribs[offset].IsChanged + + (embedded ? " embedded " : "") + ")"); + + AnalyzeSegment(offset, scanOffsets); + + // Did anything new get added? + if (scanOffsets.Count == 0) { + break; + } + + // Pop one off the end. + int lastItem = scanOffsets.Count - 1; + offset = scanOffsets[lastItem]; + scanOffsets.RemoveAt(lastItem); + } + + searchStart = FindFirstUnvisitedInstruction(searchStart); + } + + mScriptSupport.Shutdown(); + + MarkUnexecutedEmbeddedCode(); + } + + /// + /// Prepare a list of relevant extension scripts. + /// + private void PrepareScripts() { + if (mScriptManager == null) { + // Currently happens for regression tests with no external files. + mScriptArray = new IPlugin[0]; + return; + } + + // Include all scripts. + mScriptArray = mScriptManager.GetAllInstances().ToArray(); + + // Prep them. + mScriptManager.PrepareScripts(mScriptSupport); + } + + /// + /// Sets the address for every byte in the input. + /// + private void SetAddresses() { + // The AddressMap will have at least one entry, will start at offset 0, and + // will exactly span the file. + foreach (AddressMap.AddressMapEntry ent in mAddrMap) { + int addr = ent.Addr; + for (int i = ent.Offset; i < ent.Offset + ent.Length; i++) { + mAnattribs[i].Address = addr++; + } + } + } + + /// + /// Sets the "is xxxxx" flags on type-hinted entries, so that the code analyzer + /// can find them easily. + /// + private void UnpackTypeHints() { + Debug.Assert(mTypeHints.Length == mAnattribs.Length); + int offset = 0; + foreach (TypeHint hint in mTypeHints) { + switch (hint) { + case TypeHint.Code: + // Set the IsInstruction flag to prevent inline data from being + // placed here. + OpDef op = mCpuDef.GetOpDef(mFileData[offset]); + if (op == OpDef.OpInvalid) { + LogI(offset, "Ignoring code hint on illegal opcode"); + } else { + mAnattribs[offset].IsHinted = true; + mAnattribs[offset].IsInstruction = true; + } + break; + case TypeHint.Data: + // Tells the code analyzer to stop. Does not define a data analyzer + // "uncategorized data" boundary. + mAnattribs[offset].IsHinted = true; + mAnattribs[offset].IsData = true; + break; + case TypeHint.InlineData: + // Tells the code analyzer to walk across these. + mAnattribs[offset].IsHinted = true; + mAnattribs[offset].IsInlineData = true; + break; + case TypeHint.NoHint: + break; + default: + Debug.Assert(false); + break; + } + offset++; + } + } + + /// + /// Finds the first offset that is hinted as code but hasn't yet been visited. + /// + /// This might be in the middle of an already-visited instruction. + /// + /// Offset at which to start the search. + /// Offset found. + private int FindFirstUnvisitedInstruction(int start) { + for (int i = start; i < mAnattribs.Length; i++) { + if (mAnattribs[i].IsHinted && mTypeHints[i] == TypeHint.Code && + !mAnattribs[i].IsVisited) { + LogD(i, "Unvisited code hint"); + if (mAnattribs[i].IsData || mAnattribs[i].IsInlineData) { + // Maybe the user put a code hint on something that was + // later recognized as inline data? Shouldn't have been allowed. + LogW(i, "Weird: code hint on data/inline"); + continue; + } + return i; + } + } + return -1; + } + + /// + /// Finds bits of code that are part of embedded instructions but not actually + /// executed, and marks them as inline data. + /// + private void MarkUnexecutedEmbeddedCode() { + // The problem arises when you have a line like 4C 60 EA, with a branch to the + // middle byte. The formatter will print "JMP $EA60", then " RTS", and + // then should print NOP. The problem is that the NOP wasn't reached by the + // code analyzer, and so isn't tagged as an instruction start. It's effectively + // inline data, so we need to mark it that way. + // + // We don't have a quick way to find these, so we just run through the list. + for (int offset = 0; offset < mFileData.Length; ) { + if (mAnattribs[offset].IsInstructionStart) { + int len; + for (len = 1; len < mAnattribs[offset].Length; len++) { + if (mAnattribs[offset + len].IsInstructionStart) { + break; + } + } + + offset += len; + } else if (mAnattribs[offset].IsInstruction) { + // bingo + LogI(offset, "Fixing embedded orphan"); + mAnattribs[offset].IsInstruction = false; + mAnattribs[offset].IsInlineData = true; + mAnattribs[offset].DataDescriptor = FormatDescriptor.Create(1, + FormatDescriptor.Type.NumericLE, FormatDescriptor.SubType.None); + offset++; + } else { + offset++; + } + } + } + + /// + /// Analyzes a code segment. A code segment is a contiguous series of instructions. + /// We halt if we encounter a return, always-taken branch, or the end of the + /// current address map section. + /// + /// If we find branches to unvisited code, or previously-visited code that has + /// different status flags, we add that to the list of offsets to scan. + /// + /// Starting offset. + /// Collection to which additional offsets of interest will + /// be added. + private void AnalyzeSegment(int offset, List scanOffsets) { + while (offset < mFileData.Length) { + if (mAnattribs[offset].IsVisited && !mAnattribs[offset].IsChanged) { + // already visited, not changed; nothing to do + LogD(offset, "Visited and not changed, bailing"); + return; + } + + bool firstVisit = !mAnattribs[offset].IsVisited; + + // Set "visited" flag, clear "changed". + mAnattribs[offset].IsVisited = true; + mAnattribs[offset].IsChanged = false; + + if (mAnattribs[offset].IsData) { + // This area was declared to be data. Go no further. This shouldn't + // usually happen -- either we should have stopped tracing, or we + // should have identified the data area as code. + LogI(offset, "Code ran into data section"); + Debug.Assert(false); + return; + } else if (mAnattribs[offset].IsInlineData) { + // Generally this won't happen, because we ignore branches into inline data + // areas, we reject attempts to convert code to inline data, and we can't + // start in an inline area because the hint is wrong. However, it's possible + // for a JSR to a new section to be registered, and then before we get to + // it an extension script formats the area as inline data. In that case + // the inline data "wins", and we stop here. + LogW(offset, "Code ran into inline data section"); + return; + } + + // Identify the instruction, and see if it runs off the end of the file. + // If it does, treat it as data. + OpDef op = mCpuDef.GetOpDef(mFileData[offset]); + int instrLen = op.GetLength(mAnattribs[offset].StatusFlags); + LogV(offset, "OP $" + mFileData[offset].ToString("X2") + " len=" + instrLen); + if (offset + instrLen > mFileData.Length) { + // Instruction runs off the end. It's possible we visited here before with + // short M/X flags, or some other code jumps to code embedded in our + // operand. Whatever the case, we want to clear the instruction flag from + // the first byte. We can mark it as data so subsequent passes don't + // bump into this. + LogW(offset, "Instruction runs off end of file"); + mAnattribs[offset].IsInstructionStart = false; + mAnattribs[offset].IsInstruction = false; + mAnattribs[offset].IsData = true; + return; + } + if (mAnattribs[offset + instrLen -1].Address != + mAnattribs[offset].Address + instrLen - 1) { + // Address change happened mid-instruction. Mark it as data. + LogW(offset, "Detected address change mid-instruction"); + mAnattribs[offset].IsInstructionStart = false; + mAnattribs[offset].IsInstruction = false; + mAnattribs[offset].IsData = true; + return; + } + + // Instruction not defined for this CPU. Treat as data. + if (op.AddrMode == OpDef.AddressMode.Unknown) { + LogW(offset, "Instruction stream encountered invalid opcode ($" + + mFileData[offset].ToString("x2") + ")"); + return; + } + + // Flag as start of valid instruction, and mark all bytes as instructions. + // There's a possible conflict here if the first byte is marked as an + // instruction, but bytes within the instruction are marked as data. The + // easiest thing to do here is steamroll the data flags. + // + // (To cause this, hint a 3-byte instruction as data/inline-data, then + // hint the first byte of the instruction as code.) + mAnattribs[offset].IsInstructionStart = true; + mAnattribs[offset].Length = instrLen; + for (int i = offset; i < offset + instrLen; i++) { + if (mAnattribs[i].IsData) { + LogW(i, "Stripping mid-instruction data flag"); + mAnattribs[i].IsData = false; + } else if (mAnattribs[i].IsInlineData) { + LogW(i, "Stripping mid-instruction inline-data flag"); + mAnattribs[i].IsInlineData = false; + } + mAnattribs[i].IsInstruction = true; + } + + // Compute the effect on the status flags. + StatusFlags newFlags, condBranchTakenFlags; + if (op == OpDef.OpPLP_StackPull) { + // PLP restores flags from the stack. + newFlags = condBranchTakenFlags = GuessFlagsForPLP(offset); + } else { + op.ComputeFlagChanges(mAnattribs[offset].StatusFlags, mFileData, offset, + out newFlags, out condBranchTakenFlags); + } + + // Handle stuff that won't be different on a subsequent visit. + if (firstVisit) { + // Decode the operand for instructions that reference an address. If + // the target address is within the file's address space, record the + // offset as well. This doesn't examine immediate operands. + DecodeOperandAddress(offset, op); + } + + int branchOffset = -1; + bool doBranch, doContinue; + + // Check for branching. + if (op.IsBranch) { + if (mAnattribs[offset].IsOperandOffsetDirect) { + branchOffset = mAnattribs[offset].OperandOffset; + } + if (branchOffset >= 0 && branchOffset < mFileData.Length) { + doBranch = true; + } else { + // External branch. Very common for JSR to ROM routines and JMP + // through an indirect address. Not usually expected for relative + // branches. + if (op.Effect != OpDef.FlowEffect.CallSubroutine) { + LogD(offset, "Branch goes external"); + } + doBranch = false; + mAnattribs[offset].IsExternalBranch = true; + } + } else { + doBranch = false; + } + + // Check continuation to next instruction. + switch (op.Effect) { + case OpDef.FlowEffect.Cont: + case OpDef.FlowEffect.CallSubroutine: + case OpDef.FlowEffect.ConditionalBranch: + doContinue = true; + break; + default: + doContinue = false; + break; + } + + // Some 6502 code works around the lack of a branch-always instruction with + // a complement pair (e.g. BCC + BCS), so we don't want to continue past a branch + // always taken. The converse is also true: don't pursue a branch if it's + // never taken. An example from 6502.org: + // "... a common sequence on the 6502 family is: + // CLEAR_FLAG CLC + // DB $B0 + // SET_FLAG SEC + // ROR FLAG + // RTS + // When entering via CLEAR_FLAG, the $B0 becomes a 2-cycle BCS instruction, which + // is not taken (since the carry is clear). Since BCS does not affect any flags, + // it serves, in this situation, as a two byte, two cycle NOP and provides a + // subtle, but useful way to efficiently skip the SEC instruction." + + // Revise branch/cont for conditional branch instructions. + if (op.Effect == OpDef.FlowEffect.ConditionalBranch) { + OpDef.BranchTaken taken = + OpDef.IsBranchTaken(op, mAnattribs[offset].StatusFlags); + if (taken == OpDef.BranchTaken.Never) { + doBranch = false; + } else if (taken == OpDef.BranchTaken.Always) { + doContinue = false; + } + mAnattribs[offset].BranchTaken = taken; + } + + // Make sure destination isn't already flagged as data. + if (doBranch) { + Debug.Assert(branchOffset >= 0); + if (mAnattribs[branchOffset].IsData || mAnattribs[branchOffset].IsInlineData) { + LogW(offset, "Ignoring branch to +" + branchOffset.ToString("x6") + + " (data region)"); + doBranch = false; + branchOffset = -1; + } + } + + LogV(offset, "doBranch=" + doBranch + ", doCont=" + doContinue); + + if (doBranch) { + // Flag the destination offset as a branch target. + mAnattribs[branchOffset].IsBranchTarget = true; + + // Merge our status flags with theirs. + StatusFlags branchStatusBefore = mAnattribs[branchOffset].StatusFlags; + mAnattribs[branchOffset].MergeStatusFlags(condBranchTakenFlags); + mAnattribs[branchOffset].ApplyStatusFlags(mStatusFlagOverrides[branchOffset]); + + // If we need to (re-)scan this offset, add it to the list. + //AttribFlags branchFlags = mAnattribs[branchOffset].mAttribFlags; + bool addToScan = false; + string why; + if (!mAnattribs[branchOffset].IsVisited) { + // Not yet visited. Some flags may have been set by earlier branch. + // Merge status flags and add to scan list if not already present. + addToScan = true; + why = "(not visited)"; + } else { + // Visited before. If the status flags changed, set "changed" and + // add to scan offsets. + if (branchStatusBefore != mAnattribs[branchOffset].StatusFlags) { + mAnattribs[branchOffset].IsChanged = true; + addToScan = true; + } + why = "(flags: " + branchStatusBefore + " -> " + + mAnattribs[branchOffset].StatusFlags + ")"; + } + if (addToScan && !scanOffsets.Contains(branchOffset)) { + LogD(offset, "Adding " + branchOffset.ToString("x4") + + " to scan list " + why); + scanOffsets.Add(branchOffset); + } + } + + if (!doContinue) { + mAnattribs[offset].DoesNotContinue = true; + break; + } else { + mAnattribs[offset].DoesNotContinue = false; + } + + // Sanity check to avoid infinite loop. + if (instrLen <= 0) { + LogE(offset, "Internal error: instruction length " + instrLen); + throw new Exception("Instruction length was " + instrLen); + } + + int nextOffset = offset + instrLen; + if (nextOffset >= mFileData.Length) { + // next instruction is off the end of the file + LogW(offset, "Execution ran off the end of the file"); + break; + } + + // On first visit, check for JSR/JSL inline call. + if (firstVisit) { + // Currently ignoring OpDef.OpJSR_AbsIndexXInd + if (op == OpDef.OpJSR_Abs || op == OpDef.OpJSR_AbsLong) { + CheckForInlineCall(op, offset, out bool noContinue); + if (noContinue) { + LogD(offset, "Script declared inline call no-continue"); + mAnattribs[offset].DoesNotContinue = true; + break; + } + } + } + + // Are we about to walk into inline data? + int inlineDataGapLen = 0; + while (nextOffset < mFileData.Length && mAnattribs[nextOffset].IsInlineData) { + // Skip over it to find next instruction (or next inline data chunk). + // Note Anattrib.Length==0 unless a format has been applied, so we just + // walk forward a byte at a time. + inlineDataGapLen++; + nextOffset++; + } + + // Re-check after inline data advance. + if (nextOffset >= mFileData.Length) { + // next instruction is off the end of the file + LogW(offset, "Execution ran off the end of the file"); + break; + } + if (mAnattribs[nextOffset].IsData) { + // Drove into a data section + LogW(offset, "Execution ran into a data area"); + break; + } + + // Make sure we don't "continue" across an ORG. + // NOTE: it's possible to do some crazy things with multiple ORGs that will + // cause us to misinterpret things, but I don't think that matters. What's + // important is that the code analyzer doesn't drive into a data area. + int expectedAddr = mAnattribs[offset].Address + mAnattribs[offset].Length + + inlineDataGapLen; + if (mAnattribs[nextOffset].Address != expectedAddr) { + LogW(offset, "Execution ran across address change (" + + expectedAddr.ToString("x4") + " vs. " + + mAnattribs[nextOffset].Address.ToString("x4") + ")"); + break; + } + + // Merge the updated status flags into the next instruction. + StatusFlags nextStatusBefore = mAnattribs[nextOffset].StatusFlags; + mAnattribs[nextOffset].MergeStatusFlags(newFlags); + mAnattribs[nextOffset].ApplyStatusFlags(mStatusFlagOverrides[nextOffset]); + + // If we've already visited the next offset, and the updated status flags are + // the same as the previous status flags, then there's nothing to gain by + // continuing forward. + if (mAnattribs[nextOffset].IsVisited) { + if (!mAnattribs[nextOffset].IsChanged && + nextStatusBefore == mAnattribs[nextOffset].StatusFlags) { + // Instruction has been visited, hasn't been flagged as changed, + // and our status flag merge had no effect. No need to continue + // through. + LogV(offset, "Not re-examining " + nextOffset); + break; + } + } + + offset = nextOffset; + } + } + + /// + /// Attempts to guess what the flags will be after a PLP instruction. + /// + /// Offset of PLP instruction. + /// Best guess at status flags. + private StatusFlags GuessFlagsForPLP(int plpOffset) { + // We're not tracking stack contents or register contents, so this just + // generally won't work. However, there's a lot of code that uses PHP to + // save the current state and PLP to restore it, so if we can find a nearby + // PHP we can just grab from that. + // + // Failing that, we mark all flags as "indeterminate" and let the user sort + // out what it should be. It's unlikely to matter except for M/X flags on + // the 65816. + // + // The emulation flag is not part of the status register, even if we do carry + // it around like one. The E-flag is always carried over from the previous + // instruction. + + int backOffsetLimit = plpOffset - 128; // arbitrary 128-byte reach + if (backOffsetLimit < 0) { + backOffsetLimit = 0; + } + StatusFlags flags = StatusFlags.AllIndeterminate; + for (int offset = plpOffset - 1; offset >= backOffsetLimit; offset--) { + Anattrib attr = mAnattribs[offset]; + if (!attr.IsInstructionStart || !attr.IsVisited) { + continue; + } + OpDef op = mCpuDef.GetOpDef(mFileData[offset]); + if (op == OpDef.OpPHP_StackPush) { + LogI(plpOffset, "Found visited PHP at +" + offset.ToString("x6")); + flags = mAnattribs[offset].StatusFlags; + break; + } + } + + // Transfer the 'E' flag. + flags.E = mAnattribs[plpOffset].StatusFlags.E; + return flags; + } + + /// + /// Extracts the address from the operand of an absolute or relative operation. + /// Anything that could be referenced by a label or address equate is appropriate. + /// The goal is to identify data and branch targets, not generate a second copy + /// of the operand. + /// + /// The operand's address, and if applicable, the operand's file offset, are + /// stored in the Anattrib array. + /// + /// For PC-relative operands (e.g. branches) it's tempting to simply adjust the file + /// offset by the specified amount and convert that to an address. If the file + /// has multiple ORGs, this can produce incorrect results. We need to convert the + /// opcode's offset to an address, adjust by the operand, and then find the file + /// offset that corresponds to the target address. + /// + /// Doesn't do anything with immediate data. + /// + /// Offset of the instruction opcode. + /// Opcode being handled. (Passed in because the caller has it + /// handy.) + private void DecodeOperandAddress(int offset, OpDef op) { + //StatusFlags flags = mAnattribs[offset].StatusFlags; + + int operand = op.GetOperand(mFileData, offset, mAnattribs[offset].StatusFlags); + + // Add the bank to get a 24-bit address. We're currently using the program bank + // (K) rather than the data bank (B), which is correct for absolute and relative + // branches but wrong for 16-bit data operations. We currently have no way to + // know what the value of B is, so we use K because there's some small chance + // of it being correct. + // TODO(someday): figure out how to get the correct value for the B reg + int bank = mAnattribs[offset].Address & 0x7fff0000; + + // Extract target address. + switch (op.AddrMode) { + // These might refer to a location in the file, or might be external. + case OpDef.AddressMode.Abs: + case OpDef.AddressMode.AbsIndexX: + case OpDef.AddressMode.AbsIndexY: + case OpDef.AddressMode.AbsIndexXInd: + case OpDef.AddressMode.AbsInd: + case OpDef.AddressMode.AbsIndLong: + case OpDef.AddressMode.StackAbs: + mAnattribs[offset].OperandAddress = operand | bank; + break; + case OpDef.AddressMode.DP: + case OpDef.AddressMode.DPIndexX: + case OpDef.AddressMode.DPIndexY: + case OpDef.AddressMode.DPIndexXInd: + case OpDef.AddressMode.DPInd: + case OpDef.AddressMode.DPIndLong: + case OpDef.AddressMode.DPIndIndexY: + case OpDef.AddressMode.DPIndIndexYLong: + case OpDef.AddressMode.StackDPInd: + // always bank 0 + mAnattribs[offset].OperandAddress = operand; + break; + case OpDef.AddressMode.AbsIndexXLong: + case OpDef.AddressMode.AbsLong: + // 24-bit address, don't add bank + mAnattribs[offset].OperandAddress = operand; + break; + case OpDef.AddressMode.PCRel: // rel operand; convert to absolute addr + mAnattribs[offset].OperandAddress = + Asm65.Helper.RelOffset8(mAnattribs[offset].Address, + (sbyte)operand) | bank; + break; + case OpDef.AddressMode.PCRelLong: + case OpDef.AddressMode.StackPCRelLong: + mAnattribs[offset].OperandAddress = + Asm65.Helper.RelOffset16(mAnattribs[offset].Address, + (short)operand) | bank; + break; + default: + // Immediate, implied, accumulator, stack relative. We can't do + // immediate yet because we won't necessarily have a final assessment + // of the operand width. + Debug.Assert(mAnattribs[offset].OperandAddress == -1); + break; + } + + if (mAnattribs[offset].OperandAddress >= 0) { + int operandOffset = mAddrMap.AddressToOffset(offset, + mAnattribs[offset].OperandAddress); + if (operandOffset >= 0) { + mAnattribs[offset].OperandOffset = operandOffset; + + // Set a flag if this is a direct offset. This is used when tracing + // through jump instructions, as we can't necessarily decode an indirect + // jump. (There are *some* indirect JMPs we can handle, if the operand + // is an address in the file data area.) + switch (op.AddrMode) { + case OpDef.AddressMode.Abs: + case OpDef.AddressMode.AbsLong: + case OpDef.AddressMode.DP: + case OpDef.AddressMode.PCRel: + case OpDef.AddressMode.PCRelLong: + case OpDef.AddressMode.StackPCRelLong: + case OpDef.AddressMode.StackAbs: + mAnattribs[offset].IsOperandOffsetDirect = true; + break; + default: + mAnattribs[offset].IsOperandOffsetDirect = false; + break; + } + } + } else { + Debug.Assert(mAnattribs[offset].OperandOffset == -1); + Debug.Assert(!mAnattribs[offset].IsOperandOffsetDirect); + } + } + + /// + /// Queries script extensions to check to see if a JSR or JSL is actually an inline call. + /// + /// Instruction being examined. + /// File offset of start of instruction. + /// Set if any plugin declares the call to be no-continue. + private void CheckForInlineCall(OpDef op, int offset, out bool noContinue) { + noContinue = false; + for (int i = 0; i < mScriptArray.Length; i++) { + IPlugin script = mScriptArray[i]; + if (op == OpDef.OpJSR_Abs) { + script.CheckJsr(offset, out bool noCont); + noContinue |= noCont; + } else if (op == OpDef.OpJSR_AbsLong) { + script.CheckJsl(offset, out bool noCont); + noContinue |= noCont; + } else { + Debug.Assert(false); + } + } + } + + private bool SetOperandFormat(int offset, DataSubType subType, string label) { + if (offset <= 0 || offset > mFileData.Length) { + throw new Exception("SOF: bad args: offset=+" + offset.ToString("x6") + + " subType=" + subType + " label='" + label + "'; file length is" + + mFileData.Length); + } + + // Don't overwrite existing format. + if (mAnattribs[offset].DataDescriptor != null) { + LogW(offset, "SOF: already have a descriptor here"); + return false; + } + + // Must be the start of an instruction. + if (!mAnattribs[offset].IsInstructionStart) { + LogW(offset, "SOF: not an instruction start"); + return false; + } + + if (subType == DataSubType.Symbol && string.IsNullOrEmpty(label)) { + LogW(offset, "SOF rej: label required for subType=" + subType); + return false; + } + + FormatDescriptor.SubType subFmt = ConvertPluginSubType(subType, out bool isNumericSub); + if (!isNumericSub && subFmt != FormatDescriptor.SubType.None) { + LogW(offset, "SOF: bad sub-type " + subType); + return false; + } + + int instrLen = mAnattribs[offset].Length; + Debug.Assert(instrLen > 0); + + FormatDescriptor fd; + if (subType == DataSubType.Symbol) { + fd = FormatDescriptor.Create(instrLen, + new WeakSymbolRef(label, WeakSymbolRef.Part.Low), + false); + } else { + fd = FormatDescriptor.Create(instrLen, FormatDescriptor.Type.NumericLE, subFmt); + } + mAnattribs[offset].DataDescriptor = fd; + return true; + } + + /// + /// Handles a set data format call from an extension script. + /// + private bool SetInlineDataFormat(int offset, int length, DataType type, + DataSubType subType, string label) { + if (offset <= 0 || offset + length > mFileData.Length) { + throw new Exception("SIDF: bad args: offset=+" + offset.ToString("x6") + + " len=" + length + " type=" + type + " subType=" + subType + + " label='" + label + "'; file length is" + mFileData.Length); + } + + // Already formatted? We only check the initial offset -- overlapping format + // descriptors aren't strictly illegal. + if (mAnattribs[offset].DataDescriptor != null) { + LogW(offset, "SIDF: already have a descriptor here"); + return false; + } + + // Don't allow formatting of any bytes that are identified as instructions or + // were hinted by the user as something other than inline data. If the code + // analyzer comes crashing through later they'll just stomp on what we've done. + for (int i = offset; i < offset + length; i++) { + if (mTypeHints[i] != TypeHint.NoHint && mTypeHints[i] != TypeHint.InlineData) { + LogW(offset, "SIDF rej: already a hint at " + i.ToString("x6") + + " (" + mTypeHints[i] + ")"); + return false; + } + if (mAnattribs[offset].IsInstruction) { + LogW(offset, "SIDF rej: not for use with instructions"); + return false; + } + } + + // Convert type to FormatDescriptor type, and do some validity checks. + FormatDescriptor.Type fmt = ConvertPluginType(type); + FormatDescriptor.SubType subFmt = ConvertPluginSubType(subType, out bool isNumericSub); + + if (type == DataType.Dense && subType != DataSubType.None) { + throw new Exception("SIDF rej: dense data must use subType=None"); + } + + if (isNumericSub && fmt != FormatDescriptor.Type.NumericLE && + fmt != FormatDescriptor.Type.NumericBE) { + throw new Exception("SIDF rej: bad type/subType combo: type=" + + type + " subType= " + subType); + } + if ((type == DataType.NumericLE || type == DataType.NumericBE) && + (length < 1 || length > 4)) { + throw new Exception("SIDF rej: bad length for numeric item (" + + length + ")"); + } + if (subType == DataSubType.Symbol && string.IsNullOrEmpty(label)) { + throw new Exception("SIDF rej: label required for subType=" + subType); + } + + // Looks good, create a descriptor, and mark all bytes as inline data. + FormatDescriptor fd; + if (subType == DataSubType.Symbol) { + fd = FormatDescriptor.Create(length, + new WeakSymbolRef(label, WeakSymbolRef.Part.Low), + type == DataType.NumericBE); + } else { + fd = FormatDescriptor.Create(length, fmt, subFmt); + } + mAnattribs[offset].DataDescriptor = fd; + for (int i = offset; i < offset + length; i++) { + mAnattribs[i].IsInlineData = true; + } + return true; + } + + private FormatDescriptor.Type ConvertPluginType(DataType pluginType) { + switch (pluginType) { + case DataType.NumericLE: + return FormatDescriptor.Type.NumericLE; + case DataType.NumericBE: + return FormatDescriptor.Type.NumericBE; + case DataType.Dense: + return FormatDescriptor.Type.Dense; + case DataType.String: + case DataType.Fill: + default: + // not appropriate for operands, or inline data (?) + throw new Exception("Instr format rej: unexpected format type " + pluginType); + } + } + + private FormatDescriptor.SubType ConvertPluginSubType(DataSubType pluginSubType, + out bool isNumericSub) { + isNumericSub = true; + switch (pluginSubType) { + case DataSubType.None: + isNumericSub = false; + return FormatDescriptor.SubType.None; + case DataSubType.Hex: + return FormatDescriptor.SubType.Hex; + case DataSubType.Decimal: + return FormatDescriptor.SubType.Decimal; + case DataSubType.Binary: + return FormatDescriptor.SubType.Binary; + case DataSubType.Ascii: + return FormatDescriptor.SubType.Ascii; + case DataSubType.Address: + return FormatDescriptor.SubType.Address; + case DataSubType.Symbol: + return FormatDescriptor.SubType.Symbol; + default: + throw new Exception("Instr format rej: unexpected sub type " + pluginSubType); + } + } + } +} \ No newline at end of file diff --git a/SourceGen/DataAnalysis.cs b/SourceGen/DataAnalysis.cs new file mode 100644 index 0000000..322e8e3 --- /dev/null +++ b/SourceGen/DataAnalysis.cs @@ -0,0 +1,978 @@ +/* + * Copyright 2018 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.Diagnostics; + +using Asm65; +using CommonUtil; + +namespace SourceGen { + /// + /// Auto-detection of structured data. + /// + /// This class doesn't really hold any state. It's just a convenient place to collect + /// the items needed by the analyzer methods. + /// + public class DataAnalysis { + // Minimum number of consecutive identical bytes for something to be called a "run". + private const int MIN_RUN_LENGTH = 5; + // Minimum length for treating data as a run if the byte is a valid ASCII value. + // (Alternatively, the maximum length of an ASCII string composed of single characters.) + // Anything shorter than this is handled with a string directive, anything this long or + // longer becomes FILL. This should be larger than the MinCharsForString parameter. + private const int MIN_RUN_LENGTH_ASCII = 62; + + // Minimum length for an ASCII string. Anything shorter is just output as bytes. + public const int DEFAULT_MIN_STRING_LENGTH = 4; + // Set min chars to this to disable string detection. + public const int MIN_CHARS_FOR_STRING_DISABLED = int.MaxValue; + + /// + /// Project with which we are associated. + /// + private DisasmProject mProject; + + /// + /// Reference to 65xx data. + /// + private byte[] mFileData; + + /// + /// Attributes, one per byte in input file. + /// + private Anattrib[] mAnattribs; + + /// + /// Configurable parameters. + /// + private ProjectProperties.AnalysisParameters mAnalysisParams; + + + /// + /// Debug trace log. + /// + private DebugLog mDebugLog = new DebugLog(DebugLog.Priority.Silent); + public DebugLog DebugLog { + set { + mDebugLog = value; + } + } + + + public DataAnalysis(DisasmProject proj, Anattrib[] anattribs) { + mProject = proj; + mAnattribs = anattribs; + + mFileData = proj.FileData; + mAnalysisParams = proj.ProjectProps.AnalysisParams; + } + + // Internal log functions. If we're concerned about performance overhead due to + // call-site string concatenation, we can #ifdef these to nothing in release builds, + // which should allow the compiler to elide the concat. +#if false + private void LogV(int offset, string msg) { + if (mDebugLog.IsLoggable(DebugLog.Priority.Verbose)) { + mDebugLog.LogV("+" + offset.ToString("x6") + " " + msg); + } + } +#else + private void LogV(int offset, string msg) { } +#endif + private void LogD(int offset, string msg) { + if (mDebugLog.IsLoggable(DebugLog.Priority.Debug)) { + mDebugLog.LogD("+" + offset.ToString("x6") + " " + msg); + } + } + private void LogI(int offset, string msg) { + if (mDebugLog.IsLoggable(DebugLog.Priority.Info)) { + mDebugLog.LogI("+" + offset.ToString("x6") + " " + msg); + } + } + private void LogW(int offset, string msg) { + if (mDebugLog.IsLoggable(DebugLog.Priority.Warning)) { + mDebugLog.LogW("+" + offset.ToString("x6") + " " + msg); + } + } + private void LogE(int offset, string msg) { + if (mDebugLog.IsLoggable(DebugLog.Priority.Error)) { + mDebugLog.LogE("+" + offset.ToString("x6") + " " + msg); + } + } + + /// + /// Analyzes instruction operands and Address data descriptors to identify references + /// to offsets within the file. + /// + /// Instructions with format descriptors are left alone. Instructions with + /// operand offsets but no descriptor will have a descriptor generated + /// using the label at the target offset; if the target offset is unlabeled, + /// a unique label will be generated. Data descriptors with type=Address are + /// handled the same way. + /// + /// In some cases, such as a reference to the middle of an instruction, we will + /// label a nearby location instead. + /// + /// This should be called after code analysis has run, user labels and format + /// descriptors have been applied, and platform/project symbols have been merged + /// into the symbol table. + /// + /// True on success. + public void AnalyzeDataTargets() { + mDebugLog.LogI("Analyzing data targets..."); + + for (int offset = 0; offset < mAnattribs.Length; offset++) { + Anattrib attr = mAnattribs[offset]; + if (attr.IsInstructionStart) { + if (attr.DataDescriptor != null) { + // It's being shown as numeric, or as a reference to some other symbol. + // Either way there's nothing further for us to do. (Technically we + // would want to treat it like the no-descriptor case if the type was + // numeric/Address, but we don't allow that for instructions.) + Debug.Assert(attr.DataDescriptor.FormatSubType != + FormatDescriptor.SubType.Address); + continue; + } + int operandOffset = attr.OperandOffset; + if (operandOffset >= 0) { + // This is an offset reference: a branch or data access instruction whose + // target is inside the file. Create a FormatDescriptor for it, and + // generate a label at the target if one is not already present. + SetDataTarget(offset, attr.Length, operandOffset); + } + + // We advance by a single byte, rather than .Length, in case there's + // an instruction embedded inside another one. + } else if (attr.DataDescriptor != null) { + // We can't check IsDataStart / IsInlineDataStart because the bytes might + // still be uncategorized. If there's a user-specified format, check it + // to see if it's an address. + FormatDescriptor dfd = attr.DataDescriptor; + + // Is this numeric/Address? + if ((dfd.FormatType == FormatDescriptor.Type.NumericLE || + dfd.FormatType == FormatDescriptor.Type.NumericBE) && + dfd.FormatSubType == FormatDescriptor.SubType.Address) { + // Treat like an absolute address. Convert the operand + // to an address, then resolve the file offset. + int address = RawData.GetWord(mFileData, offset, dfd.Length, + (dfd.FormatType == FormatDescriptor.Type.NumericBE)); + if (dfd.Length < 3) { + // Bank not specified by data, add current program bank. Not always + // correct, but should be often enough. In most cases we'd just + // assume a correct data bank register, but here we need to find + // a file offset, so we have to assume data bank == program bank + // (unless we find a good way to track the data bank register). + address |= attr.Address & 0x7fff0000; + } + int operandOffset = mProject.AddrMap.AddressToOffset(offset, address); + if (operandOffset >= 0) { + SetDataTarget(offset, dfd.Length, operandOffset); + } + } + + // For other formats, we don't need to do anything. Numeric/Address is + // the only one that represents an offset reference. Numeric/Symbol + // is a name reference. The others are just data. + + // There shouldn't be any data items inside other data items, so we + // can just skip forward. + offset += mAnattribs[offset].DataDescriptor.Length - 1; + } + } + } + + /// + /// Extracts the operand offset from a data item. + /// + /// Project reference. + /// Offset of data item. + /// Operand offset, or -1 if not applicable. + public static int GetDataOperandOffset(DisasmProject proj, int offset) { + Anattrib attr = proj.GetAnattrib(offset); + if (!attr.IsDataStart && !attr.IsInlineDataStart) { + return -1; + } + FormatDescriptor dfd = attr.DataDescriptor; + + // Is this numeric/Address or numeric/Symbol? + if ((dfd.FormatType != FormatDescriptor.Type.NumericLE && + dfd.FormatType != FormatDescriptor.Type.NumericBE) || + (dfd.FormatSubType != FormatDescriptor.SubType.Address && + dfd.FormatSubType != FormatDescriptor.SubType.Symbol)) { + return -1; + } + + // Treat like an absolute address. Convert the operand + // to an address, then resolve the file offset. + int address = RawData.GetWord(proj.FileData, offset, dfd.Length, + (dfd.FormatType == FormatDescriptor.Type.NumericBE)); + if (dfd.Length < 3) { + // Add the program bank where the data bank should go. Not perfect but + // we don't have anything better at the moment. + address |= attr.Address & 0x7fff0000; + } + int operandOffset = proj.AddrMap.AddressToOffset(offset, address); + return operandOffset; + } + + /// + /// Creates a FormatDescriptor in the Anattrib array at srcOffset that links to + /// targetOffset, or a nearby label. If targetOffset doesn't have a useful label, + /// one will be generated. + /// + /// This is used for both instruction and data operands. + /// + /// Offset of instruction or address data. + /// Length of instruction or data item. + /// Offset of target. + private void SetDataTarget(int srcOffset, int srcLen, int targetOffset) { + // NOTE: don't try to cache mAnattribs[targetOffset] -- we may be changing + // targetOffset and/or altering the Anattrib entry, so grabbing a copy of the + // struct may lead to problems. + + // If the target offset has a symbol assigned, use it. Otherwise, try to + // find something nearby that might be more appropriate. + int origTargetOffset = targetOffset; + if (mAnattribs[targetOffset].Symbol == null) { + if (mAnalysisParams.SeekNearbyTargets) { + targetOffset = FindAlternateTarget(srcOffset, targetOffset); + } + + // If we're not interested in seeking nearby targets, or we are but we failed + // to find something useful, we need to make sure that we're not pointing + // into the middle of the instruction. The assembler will only see labels on + // the opcode bytes, so if we're pointing at the middle we need to back up. + if (mAnattribs[targetOffset].IsInstruction && + !mAnattribs[targetOffset].IsInstructionStart) { + while (!mAnattribs[--targetOffset].IsInstructionStart) { + // Should not be possible to move past the start of the file, + // since we know we're in the middle of an instruction. + Debug.Assert(targetOffset > 0); + } + } else if (!mAnattribs[targetOffset].IsInstruction && + !mAnattribs[targetOffset].IsStart) { + // This is not part of an instruction, and is not the start of a formatted + // data area. However, it might be part of a formatted data area, in which + // case we need to avoid creating an auto label in the middle. So we seek + // backward, looking for the first offset with a descriptor. If that + // descriptor includes this offset, we set the target offset to that. + // (Note the uncategorized data pass hasn't run yet, so only instructions + // and offsets identified by users or scripts have been categorized.) + int scanOffset = targetOffset; + while (--scanOffset > 0) { + FormatDescriptor dfd = mAnattribs[scanOffset].DataDescriptor; + if (dfd != null && scanOffset + dfd.Length > targetOffset) { + // Descriptor encompasses target offset. Adjust target. + targetOffset = scanOffset; + break; + } + } + } + } + + if (mAnattribs[targetOffset].Symbol == null) { + // No label at target offset, generate one. + // + // Generally speaking, the label we generate will be unique, because it + // incorporates the address. It's possible through various means to end + // up with a user or platform label that matches an auto label, so we + // need to do some renaming in that case. Shouldn't happen often. + Symbol sym = SymbolTable.GenerateUniqueForAddress(mAnattribs[targetOffset].Address, + mProject.SymbolTable); + mAnattribs[targetOffset].Symbol = sym; + // This will throw if the symbol already exists. That is the desired + // behavior, as that would be a bug. + mProject.SymbolTable.Add(sym); + } + + // Create a Numeric/Symbol descriptor that references the target label. If the + // source offset already had a descriptor (e.g. Numeric/Address data item), + // this will replace it in the Anattrib array. (The user-specified format + // is unaffected.) + // + // Doing this by target symbol, rather than offset in a Numeric/Address item, + // allows us to avoid carrying the adjustment stuff everywhere. OTOH we have + // to manually refactor label renames in the display list if we don't want to + // redo the data analysis. + bool isBigEndian = false; + if (mAnattribs[srcOffset].DataDescriptor != null) { + LogD(srcOffset, "Replacing " + mAnattribs[srcOffset].DataDescriptor + + " with reference to " + mAnattribs[targetOffset].Symbol.Label + + ", adj=" + (origTargetOffset - targetOffset)); + if (mAnattribs[srcOffset].DataDescriptor.FormatType == + FormatDescriptor.Type.NumericBE) { + isBigEndian = true; + } + } else { + LogV(srcOffset, "Creating weak reference to label " + + mAnattribs[targetOffset].Symbol.Label + + ", adj=" + (origTargetOffset - targetOffset)); + } + mAnattribs[srcOffset].DataDescriptor = FormatDescriptor.Create(srcLen, + new WeakSymbolRef(mAnattribs[targetOffset].Symbol.Label, WeakSymbolRef.Part.Low), + isBigEndian); + } + + private int FindAlternateTarget(int srcOffset, int targetOffset) { + int origTargetOffset = targetOffset; + + // Is the target outside the instruction stream? If it's just referencing data, + // do a simple check and move on. + if (!mAnattribs[targetOffset].IsInstruction) { + // We want to use user-defined labels whenever possible. If they're accessing + // memory within a few bytes, use that. We don't want to do this for + // code references, though, or our branches will get all weird. + // TODO(someday): make MAX user-configurable? Seek forward as well as backward? + const int MAX = 4; + for (int probeOffset = targetOffset - 1; + probeOffset >= 0 && probeOffset != targetOffset - MAX; probeOffset--) { + Symbol sym = mAnattribs[probeOffset].Symbol; + if (sym != null && sym.SymbolSource == Symbol.Source.User) { + // Found a nearby user label. Make sure it's actually nearby. + int addrDiff = mAnattribs[targetOffset].Address - + mAnattribs[probeOffset].Address; + if (addrDiff == targetOffset - probeOffset) { + targetOffset = probeOffset; + } else { + Debug.WriteLine("NOT probing past address boundary change"); + } + break; + } + } + return targetOffset; + } + + // Target is an instruction. Is the source an instruction or data element + // (e.g. ".dd2 "). + if (!mAnattribs[srcOffset].IsInstructionStart) { + // Might be address-1 to set up an RTS. If the target address isn't + // an instruction start, check to see if the following byte is. + if (!mAnattribs[targetOffset].IsInstructionStart && + targetOffset + 1 < mAnattribs.Length && + mAnattribs[targetOffset + 1].IsInstructionStart) { + LogD(srcOffset, "Offsetting address reference"); + targetOffset++; + } + return targetOffset; + } + + // Source is an instruction, so we have an instruction referencing an instruction. + // Could be a branch, an address push, or self-modifying code. + OpDef op = mProject.CpuDef.GetOpDef(mProject.FileData[srcOffset]); + if (op.IsBranch) { + // Don't mess with jumps and branches -- always go directly to the + // target address. + } else if (op == OpDef.OpPEA_StackAbs || op == OpDef.OpPER_StackPCRelLong) { + // They might be pushing address-1 to set up an RTS. If the target address isn't + // an instruction start, check to see if the following byte is. + if (!mAnattribs[targetOffset].IsInstructionStart && + targetOffset + 1 < mAnattribs.Length && + mAnattribs[targetOffset + 1].IsInstructionStart) { + LogD(srcOffset, "Offsetting PEA/PER"); + targetOffset++; + } + } else { + // Data operation (LDA, STA, etc). This could be self-modifying code, or + // an indexed access with an offset base address (LDA addr-1,Y) to an + // adjacent data area. Check to see if there's data right after this. + bool nearbyData = false; + for (int i = targetOffset + 1; i <= targetOffset + 2; i++) { + if (i < mAnattribs.Length && !mAnattribs[i].IsInstruction) { + targetOffset = i; + nearbyData = true; + break; + } + } + if (!nearbyData && !mAnattribs[targetOffset].IsInstructionStart) { + // There's no data nearby, and the target is not the start of the + // instruction, so this is probably self-modifying code. We want + // the label to be on the opcode, so back up to the instruction start. + while (!mAnattribs[--targetOffset].IsInstructionStart) { + // Should not be possible to move past the start of the file, + // since we know we're in the middle of an instruction. + Debug.Assert(targetOffset > 0); + } + } + } + + if (targetOffset != origTargetOffset) { + LogV(srcOffset, "Creating instruction ref adj=" + + (origTargetOffset - targetOffset)); + } + + return targetOffset; + } + + /// + /// Analyzes uncategorized regions of the file to see if they fit common patterns. + /// + /// This is re-run after most changes to the project, so we don't want to do anything + /// crazily expensive. + /// + /// True on success. + public void AnalyzeUncategorized() { + // TODO(someday): we can make this faster. The data doesn't change, so we + // only need to do a full scan once, when the file is first loaded. We can + // create a TypedRangeSet for runs of identical bytes, using the byte value + // as the type. A second TypedRangeSet would identify runs of ASCII chars, + // with different types for high/low ASCII (and PETSCII?). AnalyzeRange() would + // then just need to find the intersection with the sets, which should be + // significantly faster. We would need to re-do the scan if the parameters + // for things like min match length change. + + FormatDescriptor oneByteDefault = FormatDescriptor.Create(1, + FormatDescriptor.Type.Default, FormatDescriptor.SubType.None); + FormatDescriptor.DebugPrefabBump(-1); + + // If it hasn't been identified as code or data, set the "data" flag to + // give it a positive identification as data. (This should be the only + // place outside of CodeAnalysis that sets this flag.) This isn't strictly + // necessary, but it helps us assert things when pieces start moving around. + for (int offset = 0; offset < mAnattribs.Length; offset++) { + Anattrib attr = mAnattribs[offset]; + if (attr.IsInlineData) { + // While we're here, add a default format descriptor for inline data + // that doesn't have one. We don't try to analyze it otherwise. + if (attr.DataDescriptor == null) { + mAnattribs[offset].DataDescriptor = oneByteDefault; + FormatDescriptor.DebugPrefabBump(); + } + } else if (!attr.IsInstruction) { + mAnattribs[offset].IsData = true; + } + } + + mDebugLog.LogI("Analyzing uncategorized data..."); + + int startOffset = -1; + for (int offset = 0; offset < mAnattribs.Length; ) { + // We want to find a contiguous series of offsets which are not known + // to hold code or data. We stop if we encounter a user-defined label + // or format descriptor. + Anattrib attr = mAnattribs[offset]; + + if (attr.IsInstruction || attr.IsInlineData || attr.IsDataStart) { + // Instruction, inline data, or formatted data known to be here. Analyze + // previous chunk, then advance past this. + if (startOffset >= 0) { + AnalyzeRange(startOffset, offset - 1); + startOffset = -1; + } + if (attr.IsInstruction) { + // Because of embedded instructions, we can't simply leap forward. + offset++; + } else { + Debug.Assert(attr.Length > 0); + offset += attr.Length; + } + } else if (attr.Symbol != null || mProject.HasCommentOrNote(offset)) { + // In an uncategorized area, but we want to break at this byte + // so the user or auto label doesn't get buried in the middle of + // a large chunk. + // + // This is similar to, but independent of, GroupedOffsetSetFromSelected() + // in ProjectView. This is for auto-detection, the other is for user + // selection. It's best if the two behave similarly though. + if (startOffset >= 0) { + AnalyzeRange(startOffset, offset - 1); + } + startOffset = offset; + offset++; + } else { + // This offset is uncategorized, keep gathering. + if (startOffset < 0) { + startOffset = offset; + } + offset++; + + // Check to see if the address has changed from the previous entry. + if (offset < mAnattribs.Length && + mAnattribs[offset-1].Address + 1 != mAnattribs[offset].Address) { + // Must be an ORG here. Scan previous region. + AnalyzeRange(startOffset, offset - 1); + startOffset = -1; + } + } + } + if (startOffset >= 0) { + AnalyzeRange(startOffset, mAnattribs.Length - 1); + } + } + + /// + /// Analyzes a range of bytes, looking for opportunities to promote uncategorized + /// data to a more structured form. + /// + /// Offset of first byte in range. + /// Offset of last byte in range. + private void AnalyzeRange(int start, int end) { + // TODO(someday): consider copying the buffer into a string and using Regex. This + // can be done fairly quickly with "unsafe" code, e.g.: + // https://stackoverflow.com/questions/3028768/net-regular-expressions-on-bytes-instead-of-chars + // Could be useful for ASCII stuff and the repeated-byte detector, e.g.: + // https://stackoverflow.com/questions/1660694/regular-expression-to-match-any-character-being-repeated-more-than-10-times + + mDebugLog.LogI("Analyzing +" + start.ToString("x6") + " - +" + end.ToString("x6")); + + int minStringChars = mAnalysisParams.MinCharsForString; + bool doAnalysis = mAnalysisParams.AnalyzeUncategorizedData; + FormatDescriptor oneByteDefault = FormatDescriptor.Create(1, + FormatDescriptor.Type.Default, FormatDescriptor.SubType.None); + FormatDescriptor.DebugPrefabBump(-1); + + while (start <= end) { + if (!doAnalysis) { + // Analysis is disabled, so just mark everything as single-byte data. + mAnattribs[start].DataDescriptor = oneByteDefault; + FormatDescriptor.DebugPrefabBump(); + start++; + continue; + } + + // Check for block of repeated values. + int length = RecognizeRun(mFileData, start, end); + bool isAscii = TextUtil.IsPrintableAscii((char)(mFileData[start] & 0x7f)); + if (length >= MIN_RUN_LENGTH) { + // Output as run or ASCII string. Prefer ASCII if the string is short + // enough to fit on one line (e.g. 64 chars including delimiters) and + // meets the minimum string length threshold. + if (isAscii && length <= MIN_RUN_LENGTH_ASCII && length >= minStringChars) { + // string -- if we create the descriptor here, we save a little time, + // but strings like "*****hello" turn into two separate strings. + //LogV(start, "String from run of '" + (char)(mFileData[start] & 0x7f) + + // "': " + length + " bytes"); + //mAnattribs[start].DataDescriptor = FormatDescriptor.CreateDescriptor( + // length, FormatDescriptor.Type.String, + // FormatDescriptor.SubType.None); + //start += length; + //continue; + } else { + // run + LogV(start, "Run of 0x" + mFileData[start].ToString("x2") + ": " + + length + " bytes"); + mAnattribs[start].DataDescriptor = FormatDescriptor.Create( + length, FormatDescriptor.Type.Fill, + FormatDescriptor.SubType.None); + start += length; + continue; + } + } + + length = RecognizeAscii(mFileData, start, end); + if (length >= minStringChars) { + LogV(start, "ASCII string, len=" + length + " bytes"); + mAnattribs[start].DataDescriptor = FormatDescriptor.Create(length, + FormatDescriptor.Type.String, FormatDescriptor.SubType.None); + start += length; + continue; + } + + // Nothing found, output as single byte. This is the easiest form for users + // to edit. + mAnattribs[start].DataDescriptor = oneByteDefault; + FormatDescriptor.DebugPrefabBump(); + + // It's tempting to advance by the "length" result from RecognizeRun, and if + // we were just looking for runs of identical bytes we could. However, that + // would lose short ASCII strings that began with repeated bytes, e.g. "---%". + + start++; + } + } + + #region Static analyzer methods + + /// + /// Checks for a repeated run of the same byte. + /// + /// Raw data. + /// Offset of first byte in range. + /// Offset of last byte in range. + /// Length of run. + public static int RecognizeRun(byte[] fileData, int start, int end) { + byte first = fileData[start]; + int index = start; + while (++index <= end) { + if (fileData[index] != first) { + break; + } + } + return index - start; + } + + /// + /// Checks for a run of ASCII values. Both high and low ASCII are recognized, + /// but the entire run must be one or the other. + /// + /// Raw data. + /// Offset of first byte in range. + /// Offset of last byte in range. + /// Length of run. + public static int RecognizeAscii(byte[] fileData, int start, int end) { + // This won't find a mix of Apple II high/inverse/flashing text. + byte firstHi = (byte)(fileData[start] & 0x80); + + int index; + for (index = start; index <= end; index++) { + char ch = (char)fileData[index]; + if (!TextUtil.IsPrintableAscii((char)(ch & 0x7f)) || (ch & 0x80) != firstHi) { + break; + } + } + + return index - start; + } + + /// + /// Counts the number of low-ASCII, high-ASCII, and non-ASCII values in the + /// specified region. + /// + /// Raw data. + /// Offset of first byte in range. + /// Offset of last byte in range + /// Set to the number of low-ASCII bytes found. + /// Set to the number of high-ASCII bytes found. + /// Set to the number of non-ASCII bytes found. + public static void CountAsciiBytes(byte[] fileData, int start, int end, + out int lowAscii, out int highAscii, out int nonAscii) { + lowAscii = highAscii = nonAscii = 0; + + for (int i = start; i <= end; i++) { + byte val = fileData[i]; + if (val < 0x20) { + nonAscii++; + } else if (val < 0x7f) { + lowAscii++; + } else if (val < 0xa0) { + nonAscii++; + } else if (val < 0xff) { + highAscii++; + } else { + nonAscii++; + } + } + } + + /// + /// Counts the number of null-terminated strings in the buffer. + /// + /// Zero-length strings are allowed but not included in the count. + /// + /// Each string must be either high-ASCII or low-ASCII, not a mix. + /// + /// If any bad data is found, the scan aborts and returns -1. + /// + /// Raw data. + /// Offset of first byte in range. + /// Offset of last byte in range. + /// Number of strings found, or -1 if bad data identified. + public static int RecognizeNullTerminatedStrings(byte[] fileData, int start, int end) { + // Quick test. + if (fileData[end] != 0x00) { + return -1; + } + + int stringCount = 0; + int expectedHiBit = -1; + int stringLen = 0; + for (int i = start; i <= end; i++) { + byte val = fileData[i]; + if (val == 0x00) { + // End of string. Only update count if string wasn't empty. + if (stringLen != 0) { + stringCount++; + } + stringLen = 0; + expectedHiBit = -1; + } else { + if (expectedHiBit == -1) { + // First byte in string, set hi/lo expectation. + expectedHiBit = val & 0x80; + } else if ((val & 0x80) != expectedHiBit) { + // Mixed ASCII or non-ASCII, fail. + return -1; + } + val &= 0x7f; + if (val < 0x20 || val == 0x7f) { + // Non-ASCII, fail. + return -1; + } + stringLen++; + } + } + + return stringCount; + } + + /// + /// Counts strings prefixed with an 8-bit length. + /// + /// Each string must be either high-ASCII or low-ASCII, not a mix. + /// + /// Zero-length strings are allowed but not counted. + /// + /// Raw data. + /// Offset of first byte in range. + /// Offset of last byte in range. + /// Number of strings found, or -1 if bad data identified. + public static int RecognizeLen8Strings(byte[] fileData, int start, int end) { + int posn = start; + int remaining = end - start + 1; + int stringCount = 0; + + while (remaining > 0) { + int strLen = fileData[posn++]; + if (strLen > --remaining) { + // Buffer doesn't hold entire string, fail. + return -1; + } + + if (strLen == 0) { + continue; + } + stringCount++; + remaining -= strLen; + + int expectedHiBit = fileData[posn] & 0x80; + + while (strLen-- != 0) { + byte val = fileData[posn++]; + if ((val & 0x80) != expectedHiBit) { + // Mixed ASCII, fail. + return -1; + } + val &= 0x7f; + if (val < 0x20 || val == 0x7f) { + // Non-ASCII, fail. + return -1; + } + } + } + + return stringCount; + } + + /// + /// Counts strings prefixed with a 16-bit length. + /// + /// Each string must be either high-ASCII or low-ASCII, not a mix. + /// + /// Zero-length strings are allowed but not counted. + /// + /// Raw data. + /// Offset of first byte in range. + /// Offset of last byte in range. + /// Number of strings found, or -1 if bad data identified. + public static int RecognizeLen16Strings(byte[] fileData, int start, int end) { + int posn = start; + int remaining = end - start + 1; + int stringCount = 0; + + while (remaining > 0) { + if (remaining < 2) { + // Not enough bytes for length, fail. + return -1; + } + int strLen = fileData[posn++]; + strLen |= fileData[posn++] << 8; + remaining -= 2; + if (strLen > remaining) { + // Buffer doesn't hold entire string, fail. + return -1; + } + + if (strLen == 0) { + continue; + } + stringCount++; + remaining -= strLen; + + int expectedHiBit = fileData[posn] & 0x80; + + while (strLen-- != 0) { + byte val = fileData[posn++]; + if ((val & 0x80) != expectedHiBit) { + // Mixed ASCII, fail. + return -1; + } + val &= 0x7f; + if (val < 0x20 || val == 0x7f) { + // Non-ASCII, fail. + return -1; + } + } + } + + return stringCount; + } + + /// + /// Counts strings in Dextral Character Inverted format, meaning the high bit on the + /// last byte is the opposite of the preceding. + /// + /// Each string must be at least two bytes. To reduce false-positives, we require + /// that all strings have the same hi/lo pattern. + /// + /// Raw data. + /// Offset of first byte in range. + /// Offset of last byte in range. + /// Number of strings found, or -1 if bad data identified. + public static int RecognizeDciStrings(byte[] fileData, int start, int end) { + int expectedHiBit = fileData[start] & 0x80; + int stringCount = 0; + int stringLen = 0; + + // Quick test on last byte. + if ((fileData[end] & 0x80) == expectedHiBit) { + return -1; + } + + for (int i = start; i <= end; i++) { + byte val = fileData[i]; + if ((val & 0x80) != expectedHiBit) { + // end of string + if (stringLen == 0) { + // Got two consecutive bytes with end-marker polarity... fail. + return -1; + } + stringCount++; + stringLen = 0; + } else { + stringLen++; + } + + val &= 0x7f; + if (val < 0x20 || val == 0x7f) { + // Non-ASCII, fail. + return -1; + } + } + + return stringCount; + } + + /// + /// Counts strings in reverse Dextral Character Inverted format, meaning the string is + /// stored in reverse order in memory, and the high bit on the first (last) byte is + /// the opposite of the rest. + /// + /// Each string must be at least two bytes. To reduce false-positives, we require + /// that all strings have the same hi/lo pattern. + /// + /// Raw data. + /// Offset of first byte in range. + /// Offset of last byte in range. + /// Number of strings found, or -1 if bad data identified. + public static int RecognizeReverseDciStrings(byte[] fileData, int start, int end) { + int expectedHiBit = fileData[end] & 0x80; + int stringCount = 0; + int stringLen = 0; + + // Quick test on last (first) byte. + if ((fileData[start] & 0x80) == expectedHiBit) { + return -1; + } + + for (int i = end; i >= start; i--) { + byte val = fileData[i]; + if ((val & 0x80) != expectedHiBit) { + // end of string + if (stringLen == 0) { + // Got two consecutive bytes with end-marker polarity... fail. + return -1; + } + stringCount++; + stringLen = 0; + } else { + stringLen++; + } + + val &= 0x7f; + if (val < 0x20 || val == 0x7f) { + // Non-ASCII, fail. + return -1; + } + } + + return stringCount; + } + + #endregion // Static analyzers + } +} + + + +#if false + /// + /// Iterator that generates a list of offsets which are not known to hold code or data. + /// + /// Generates a set of integers in ascending order. + /// + private class UndeterminedValueIterator : IEnumerator { + /// + /// Index of current item, or -1 if we're not started yet. + /// + private int mCurIndex; + + /// + /// Reference to Anattrib array we're iterating over. + /// + private Anattrib[] mAnattribs; + + + /// + /// Constructor. + /// + public UndeterminedValueIterator(Anattrib[] anattribs) { + mAnattribs = anattribs; + Reset(); + } + + // IEnumerator: current element + public object Current { + get { + if (mCurIndex < 0) { + // not started + return null; + } + return mCurIndex; + } + } + + // IEnumerator: move to the next element, returning false if there isn't one + public bool MoveNext() { + while (++mCurIndex < mAnattribs.Length) { + Anattrib attr = mAnattribs[mCurIndex]; + if (attr.IsInstructionStart) { + // skip past instruction + mCurIndex += attr.Length - 1; + } else if (attr.IsUncategorized) { + // got one + return true; + } + } + + return false; + } + + // IEnumerator: reset state + public void Reset() { + mCurIndex = -1; + } + } +#endif diff --git a/SourceGen/DefSymbol.cs b/SourceGen/DefSymbol.cs new file mode 100644 index 0000000..93e0b0d --- /dev/null +++ b/SourceGen/DefSymbol.cs @@ -0,0 +1,104 @@ +/* + * Copyright 2018 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; + +namespace SourceGen { + /// + /// Subclass of Symbol used for symbols defined in the platform or project. + /// + /// Instances are immutable. + /// + public class DefSymbol : Symbol { + /// + /// Data format descriptor. + /// + public FormatDescriptor DataDescriptor { get; private set; } + + /// + /// User-supplied comment. + /// + public string Comment { get; private set; } + + public string Tag { get; private set; } + + /// + /// Cross-reference data, generated by the analyzer. + /// + public XrefSet Xrefs { get; private set; } + + // NOTE: might be nice to identify the symbol's origin, e.g. which platform + // symbol file it was defined in. This could then be stored in a + // DisplayList line, for benefit of the Info panel. + + /// + /// Internal base-object constructor, called by other constructors. + /// + private DefSymbol(string label, int value, Source source, Type type) + : base(label, value, source, type) { + Debug.Assert(source == Source.Platform || source == Source.Project); + Debug.Assert(type == Type.ExternalAddr || type == Type.Constant); + Xrefs = new XrefSet(); + } + + /// + /// Constructor. + /// + /// Symbol's label. + /// Symbol's value. + /// Symbol source (general point of origin). + /// Symbol type. + /// Format descriptor sub-type, so we know how the + /// user wants the value to be displayed. + /// End-of-line comment. + /// Symbol tag, used for grouping platform symbols. + public DefSymbol(string label, int value, Source source, Type type, + FormatDescriptor.SubType formatSubType, string comment, string tag) + : this(label, value, source, type) { + Debug.Assert(comment != null); + Debug.Assert(tag != null); + + // Length doesn't matter; use 1 to get prefab object. + DataDescriptor = FormatDescriptor.Create(1, + FormatDescriptor.Type.NumericLE, formatSubType); + + Comment = comment; + Tag = tag; + } + + /// + /// Constructs a DefSymbol from a Symbol and a format descriptor. This is used + /// for project symbols. + /// + /// Base symbol. + /// Format descriptor. + /// End-of-line comment. + public DefSymbol(Symbol sym, FormatDescriptor dfd, string comment) + : this(sym.Label, sym.Value, sym.SymbolSource, sym.SymbolType) { + Debug.Assert(comment != null); + + DataDescriptor = dfd; + Comment = comment; + Tag = string.Empty; + } + + public override string ToString() { + return base.ToString() + ":" + DataDescriptor + ";" + Comment + + (string.IsNullOrEmpty(Tag) ? "" : " [" + Tag + "]"); + } + } +} diff --git a/SourceGen/DisasmProject.cs b/SourceGen/DisasmProject.cs new file mode 100644 index 0000000..e73f15a --- /dev/null +++ b/SourceGen/DisasmProject.cs @@ -0,0 +1,1590 @@ +/* + * Copyright 2018 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 Asm65; +using CommonUtil; +using SourceGen.Sandbox; + +namespace SourceGen { + /// + /// All state for an open project. + /// + /// This class does no file I/O or user interaction. + /// + public class DisasmProject { + // Arbitrary 1MB limit. Could be increased to 16MB if performance is acceptable. + public const int MAX_DATA_FILE_SIZE = 1 << 20; + + // File magic. + private const long MAGIC = 6982516645493599905; + + + #region Data that is saved and restored + // All data held by structures in this section are persistent, and will be + // written to the project file. Anything not in this section may be discarded + // at any time. Smaller items are kept in arrays, with one entry per byte + // of file data. + + // Length of input data. (This is redundant with FileData.Length while in memory, + // but we want this value to be serialized into the project file.) + public int FileDataLength { get; private set; } + + // CRC-32 on input data. + public uint FileDataCrc32 { get; private set; } + + // Map file offsets to addresses. + public AddressMap AddrMap { get { return mAddrMap; } } + private AddressMap mAddrMap; + + // Type hints. Default value is "no hint". + public CodeAnalysis.TypeHint[] TypeHints { get; private set; } + + // Status flag overrides. Default value is "all unspecified". + public StatusFlags[] StatusFlagOverrides { get; private set; } + + // End-of-line comments. Empty string means "no comment". + public string[] Comments { get; private set; } + + // Full line, possibly multi-line comments. + public Dictionary LongComments { get; private set; } + + // Notes, which are like comments but not included in the assembled output. + public SortedList Notes { get; private set; } + + // Labels, defined by the user; uses file offset as key. Ideally the label names + // are unique, but there are ways around that. + public Dictionary UserLabels { get; private set; } + + // Format descriptors for operands and data items; uses file offset as key. + public SortedList OperandFormats { get; private set; } + + // Project properties. Includes CPU type, platform symbol file names, project + // symbols, etc. + public ProjectProperties ProjectProps { get; private set; } + + #endregion // data to save & restore + + + /// + /// The contents of the 65xx data file. + /// + public byte[] FileData { get { return mFileData; } } + private byte[] mFileData; + + /// + /// CPU definition to use when analyzing input. + /// + public CpuDef CpuDef { get; private set; } + + /// + /// If true, plugins will execute in the main application's AppDomain instead of + /// the sandbox. Must be set before calling Initialize(). + /// + public bool UseMainAppDomainForPlugins { get; set; } + + /// + /// Full pathname of project file. The directory name is needed when loading + /// platform symbols and extension scripts from the project directory, and the + /// filename is used to give project-local extension scripts unique DLL names. + /// + /// For a new project that hasn't been saved yet, this will be empty. + /// + public string ProjectPathName { get; set; } + + // Name of data file. This is used for debugging and when naming DLLs for + // project-local extension scripts. Just the filename, not the path. + private string mDataFileName; + + // This holds working state for the code and data analyzers. Some of the state + // is presented directly to the user, e.g. status flags. All of the data here + // should be considered transient; it may be discarded at any time without + // causing user data loss. + private Anattrib[] mAnattribs; + + // A snapshot of the Anattribs array, taken after code analysis has completed, + // before data analysis has begun. + private Anattrib[] mCodeOnlyAnattribs; + + // Symbol lists loaded from platform symbol files. This is essentially a list + // of lists, of symbols. + private List PlatformSyms { get; set; } + + // Extension script manager. Controls AppDomain sandbox. + private ScriptManager mScriptManager; + + // All symbols, including user-defined, platform-specific, and auto-generated, keyed by + // label string. This is rebuilt whenever we do a refresh, and modified whenever + // labels or platform definitions are edited. + // + // Note this includes project/platform symbols that will not be in the assembled output. + public SymbolTable SymbolTable { get; private set; } + + // Cross-reference data, indexed by file offset. + private Dictionary mXrefs = new Dictionary(); + + // Project and platform symbols that are being referenced from code. + public List ActiveDefSymbolList { get; private set; } + + // List of changes for undo/redo. + private List mUndoList = new List(); + + // Index of slot where next undo operation will be placed. + private int mUndoTop = 0; + + // Index of top when the file was last saved. + private int mUndoSaveIndex = 0; + + + /// + /// Constructs a new project. + /// + public DisasmProject() { } + + /// + /// Prepares the object by instantiating various fields, some of which are sized to + /// match the length of the data file. The data file may not have been loaded yet + /// (e.g. when deserializing a project file). + /// + public void Initialize(int fileDataLen) { + Debug.Assert(FileDataLength == 0); // i.e. Initialize() hasn't run yet + Debug.Assert(fileDataLen > 0); + + FileDataLength = fileDataLen; + ProjectPathName = string.Empty; + + mAddrMap = new AddressMap(fileDataLen); + mAddrMap.Set(0, 0x1000); // default load address to $1000; override later + + // Default value is "no hint". + TypeHints = new CodeAnalysis.TypeHint[fileDataLen]; + + // Default value is "unspecified" for all bits. + StatusFlagOverrides = new StatusFlags[fileDataLen]; + + Comments = new string[fileDataLen]; + + // Populate with empty strings so we don't have to worry about null refs. + for (int i = 0; i < Comments.Length; i++) { + Comments[i] = string.Empty; + } + + LongComments = new Dictionary(); + Notes = new SortedList(); + + UserLabels = new Dictionary(); + OperandFormats = new SortedList(); + ProjectProps = new ProjectProperties(); + + SymbolTable = new SymbolTable(); + PlatformSyms = new List(); + ActiveDefSymbolList = new List(); + + // Default to 65816. This will be replaced with value from project file or + // system definition. + ProjectProps.CpuType = CpuDef.CpuType.Cpu65816; + ProjectProps.IncludeUndocumentedInstr = false; + UpdateCpuDef(); + } + + /// + /// Discards resources, notably the sandbox AppDomain. + /// + public void Cleanup() { + Debug.WriteLine("DisasmProject.Cleanup(): scriptMgr=" + mScriptManager); + if (mScriptManager != null) { + mScriptManager.Cleanup(); + mScriptManager = null; + } + } + + /// + /// Prepares the DisasmProject for use as a new project. + /// + /// 65xx data file contents. + /// Data file's filename (not pathname). + public void PrepForNew(byte[] fileData, string dataFileName) { + Debug.Assert(fileData.Length == FileDataLength); + + mFileData = fileData; + mDataFileName = dataFileName; + FileDataCrc32 = CommonUtil.CRC32.OnWholeBuffer(0, mFileData); + + // Mark the first byte as code so we have something to do. + TypeHints[0] = CodeAnalysis.TypeHint.Code; + } + + /// + /// Pulls items of interest out of the system definition object and applies them + /// to the project. Call this after LoadDataFile() for a new project. + /// + /// Target system definition. + public void ApplySystemDef(Setup.SystemDef sysDef) { + CpuDef.CpuType cpuType = CpuDef.GetCpuTypeFromName(sysDef.Cpu); + bool includeUndoc = Setup.SystemDefaults.GetUndocumentedOpcodes(sysDef); + CpuDef tmpDef = CpuDef.GetBestMatch(cpuType, includeUndoc); + + int loadAddr = Setup.SystemDefaults.GetLoadAddress(sysDef); + mAddrMap.Set(0, loadAddr); + + // Store the best-matched CPU in properties, rather than whichever was originally + // requested. This way the behavior of the project is the same for everyone, even + // if somebody has a newer app version with specialized handling for the + // originally-specified CPU. + ProjectProps.CpuType = tmpDef.Type; + ProjectProps.IncludeUndocumentedInstr = includeUndoc; + UpdateCpuDef(); + + ProjectProps.EntryFlags = Setup.SystemDefaults.GetEntryFlags(sysDef); + + foreach (string str in sysDef.SymbolFiles) { + ProjectProps.PlatformSymbolFileIdentifiers.Add(str); + } + foreach (string str in sysDef.ExtensionScripts) { + ProjectProps.ExtensionScriptFileIdentifiers.Add(str); + } + } + + public void UpdateCpuDef() { + CpuDef = CpuDef.GetBestMatch(ProjectProps.CpuType, + ProjectProps.IncludeUndocumentedInstr); + } + + /// + /// Sets the file CRC. Called during deserialization. + /// + /// Data file CRC. + public void SetFileCrc(uint crc) { + Debug.Assert(FileDataLength > 0); + FileDataCrc32 = crc; + } + + /// + /// Sets the file data array. Used when the project is created from a project file. + /// + /// 65xx data file contents. + /// Data file's filename (not pathname). + public void SetFileData(byte[] fileData, string dataFileName) { + Debug.Assert(fileData.Length == FileDataLength); + Debug.Assert(CRC32.OnWholeBuffer(0, fileData) == FileDataCrc32); + mFileData = fileData; + mDataFileName = dataFileName; + } + + /// + /// Loads platform symbol files and extension scripts. + /// + /// Call this on initial load and whenever the set of platform symbol files changes + /// in the project config. + /// + /// Failures here will be reported to the user but aren't fatal. + /// + /// String with all warnings from load process. + public string LoadExternalFiles() { + TaskTimer timer = new TaskTimer(); + timer.StartTask("Total"); + + StringBuilder sb = new StringBuilder(); + + string projectDir = string.Empty; + if (!string.IsNullOrEmpty(ProjectPathName)) { + projectDir = Path.GetDirectoryName(ProjectPathName); + } + + // Load the platform symbols first. + timer.StartTask("Platform Symbols"); + PlatformSyms.Clear(); + foreach (string fileIdent in ProjectProps.PlatformSymbolFileIdentifiers) { + PlatformSymbols ps = new PlatformSymbols(); + bool ok = ps.LoadFromFile(fileIdent, projectDir, out FileLoadReport report); + if (ok) { + PlatformSyms.Add(ps); + } + if (report.Count > 0) { + sb.Append(report.Format()); + } + } + timer.EndTask("Platform Symbols"); + + // Instantiate the script manager on first use. + timer.StartTask("Create ScriptManager"); + if (mScriptManager == null) { + mScriptManager = new ScriptManager(this); + } else { + mScriptManager.Clear(); + } + timer.EndTask("Create ScriptManager"); + + // Load the extension script files. + timer.StartTask("Load Extension Scripts"); + foreach (string fileIdent in ProjectProps.ExtensionScriptFileIdentifiers) { + bool ok = mScriptManager.LoadPlugin(fileIdent, out FileLoadReport report); + if (report.Count > 0) { + sb.Append(report.Format()); + } + } + timer.EndTask("Load Extension Scripts"); + + timer.EndTask("Total"); + timer.DumpTimes("Time to load external files:"); + + return sb.ToString(); + } + + /// + /// Analyzes the file data. This is the main entry point for code/data analysis. + /// + /// How much work to do. + /// Object to send debug output to. + /// Task timestamp collection object. + public void Analyze(UndoableChange.ReanalysisScope reanalysisRequired, + CommonUtil.DebugLog debugLog, TaskTimer reanalysisTimer) { + // This method doesn't report failures. It succeeds to the best of its ability, + // and handles problems by discarding bad data. The overall philosophy is that + // the program will never generate bad data, and any bad project file contents + // (possibly introduced by hand-editing) are identified at load time, called out + // to the user, and discarded. + Debug.Assert(reanalysisRequired != UndoableChange.ReanalysisScope.None); + reanalysisTimer.StartTask("DisasmProject.Analyze()"); + + // Populate the symbol table with platform symbols, in file load order, then + // merge in the project symbols, potentially replacing platform symbols that + // have the same label. This version of the table is passed to plugins during + // code analysis. + reanalysisTimer.StartTask("SymbolTable init"); + SymbolTable.Clear(); + MergePlatformProjectSymbols(); + // Merge user labels into the symbol table, overwriting platform/project symbols + // where they conflict. Labels whose values are out of sync (because of a change + // to the address map) are updated as part of this. + UpdateAndMergeUserLabels(); + reanalysisTimer.EndTask("SymbolTable init"); + + if (reanalysisRequired == UndoableChange.ReanalysisScope.CodeAndData) { + // Always want to start with a blank array. Going to be lazy and let the + // system allocator handle that for us. + mAnattribs = new Anattrib[mFileData.Length]; + + reanalysisTimer.StartTask("CodeAnalysis.Analyze"); + + CodeAnalysis ca = new CodeAnalysis(mFileData, CpuDef, mAnattribs, mAddrMap, + TypeHints, StatusFlagOverrides, ProjectProps.EntryFlags, mScriptManager, + debugLog); + + ca.Analyze(); + reanalysisTimer.EndTask("CodeAnalysis.Analyze"); + + // Save a copy of the current state. + mCodeOnlyAnattribs = new Anattrib[mAnattribs.Length]; + Array.Copy(mAnattribs, mCodeOnlyAnattribs, mAnattribs.Length); + } else { + // Load Anattribs array from the stored copy. + Debug.WriteLine("Partial reanalysis"); + reanalysisTimer.StartTask("CodeAnalysis (restore prev)"); + Debug.Assert(mCodeOnlyAnattribs != null); + Array.Copy(mCodeOnlyAnattribs, mAnattribs, mAnattribs.Length); + reanalysisTimer.EndTask("CodeAnalysis (restore prev)"); + } + + reanalysisTimer.StartTask("Apply labels, formats, etc."); + // Apply any user-defined labels to the Anattribs array. + ApplyUserLabels(debugLog); + + // Apply user-created format descriptors to instructions and data items. + ApplyFormatDescriptors(debugLog); + reanalysisTimer.EndTask("Apply labels, formats, etc."); + + reanalysisTimer.StartTask("DataAnalysis"); + DataAnalysis da = new DataAnalysis(this, mAnattribs); + da.DebugLog = debugLog; + + reanalysisTimer.StartTask("DataAnalysis.AnalyzeDataTargets"); + da.AnalyzeDataTargets(); + reanalysisTimer.EndTask("DataAnalysis.AnalyzeDataTargets"); + + // Analyze uncategorized regions. When this completes, the Anattrib array will + // be complete for every offset, and the file will be traversible by walking + // through the lengths of each entry. + reanalysisTimer.StartTask("DataAnalysis.AnalyzeUncategorized"); + da.AnalyzeUncategorized(); + reanalysisTimer.EndTask("DataAnalysis.AnalyzeUncategorized"); + + reanalysisTimer.EndTask("DataAnalysis"); + + reanalysisTimer.StartTask("RemoveHiddenLabels"); + RemoveHiddenLabels(); + reanalysisTimer.EndTask("RemoveHiddenLabels"); + + + // ---------- + // NOTE: we could add an additional re-analysis entry point here, that just deals with + // platform symbols and xrefs, to be used after a change to project symbols. We'd + // need to check all existing refs to confirm that the symbol hasn't been removed. + // Symbol updates are sufficiently infrequent that this probably isn't worthwhile. + + // NOTE: we could at this point apply platform address symbols as code labels, so + // that locations in the code that correspond to well-known addresses would pick + // up the appropriate label instead of getting auto-labeled. It's unclear + // whether this is desirable, especially if the user is planning to modify the + // output later on, and it could mess things up if we start slapping + // labels into the middle of data regions. It's generally safer to treat + // platform symbols as labels for constants and external references. If somebody + // finds an important use case we can revisit this; might merit a special type + // of equate or section in the platform symbol definition file. + + reanalysisTimer.StartTask("GeneratePlatformSymbolRefs"); + // Generate references to platform and project external symbols. + GeneratePlatformSymbolRefs(); + reanalysisTimer.EndTask("GeneratePlatformSymbolRefs"); + + reanalysisTimer.StartTask("GenerateXrefs"); + // Generate cross-reference lists. + mXrefs.Clear(); + GenerateXrefs(); + reanalysisTimer.EndTask("GenerateXrefs"); + + reanalysisTimer.StartTask("GenerateActiveDefSymbolList"); + // Generate the list of project/platform symbols that are being used. + GenerateActiveDefSymbolList(); + reanalysisTimer.EndTask("GenerateActiveDefSymbolList"); + + Validate(); + + reanalysisTimer.EndTask("DisasmProject.Analyze()"); + //reanalysisTimer.DumpTimes("DisasmProject timers:", debugLog); + + debugLog.LogI("Analysis complete"); + } + + /// + /// Applies user labels to the Anattribs array. Symbols with stale Value fields will + /// be replaced. + /// + /// Log for debug messages. + private void ApplyUserLabels(DebugLog genLog) { + foreach (KeyValuePair kvp in UserLabels) { + int offset = kvp.Key; + if (offset < 0 || offset >= mAnattribs.Length) { + genLog.LogE("Invalid offset +" + offset.ToString("x6") + + "(label=" + kvp.Value.Label + ")"); + continue; // ignore this + } + + if (mAnattribs[offset].Symbol != null) { + genLog.LogW("Multiple labels at offset +" + offset.ToString("x6") + + ": " + kvp.Value.Label + " / " + mAnattribs[offset].Symbol.Label); + continue; + } + + int expectedAddr = kvp.Value.Value; + Debug.Assert(expectedAddr == mAddrMap.OffsetToAddress(offset)); + + // Add direct reference to the UserLabels Symbol object. + mAnattribs[offset].Symbol = kvp.Value; + } + } + + /// + /// Applies user-defined format descriptors to the Anattribs array. This specifies the + /// format for instruction operands, and identifies data items. + /// + /// Log for debug messages. + private void ApplyFormatDescriptors(DebugLog genLog) { + foreach (KeyValuePair kvp in OperandFormats) { + int offset = kvp.Key; + + // If you hint as data, apply formats, and then hint as code, all sorts + // of strange things can happen. We want to ignore anything that doesn't + // appear to be valid. While we're at it, we do some internal consistency + // checks in the name of catching bugs as soon as possible. + + // Check offset. + if (offset < 0 || offset >= mAnattribs.Length) { + genLog.LogE("Invalid offset +" + offset.ToString("x6") + + "(desc=" + kvp.Value + ")"); + Debug.Assert(false); + continue; // ignore this one + } + + // Make sure it doesn't run off the end + if (offset + kvp.Value.Length > mAnattribs.Length) { + genLog.LogE("Invalid offset+len +" + offset.ToString("x6") + + " len=" + kvp.Value.Length + " file=" + mAnattribs.Length); + Debug.Assert(false); + continue; // ignore this one + } + + if (mAnattribs[offset].IsInstructionStart) { + // Check length for instruction formatters. + if (kvp.Value.Length != mAnattribs[offset].Length) { + genLog.LogW("Unexpected length on instr format descriptor (" + + kvp.Value.Length + " vs " + mAnattribs[offset].Length + ")"); + continue; // ignore this one + } + if (!kvp.Value.IsValidForInstruction) { + genLog.LogW("Descriptor not valid for instruction: " + kvp.Value); + continue; // ignore this one + } + } else if (mAnattribs[offset].IsInstruction) { + // Mid-instruction format. + genLog.LogW("Unexpected mid-instruction format descriptor at +" + + offset.ToString("x6")); + continue; // ignore this one + } + + mAnattribs[offset].DataDescriptor = kvp.Value; + } + } + + /// + /// Merges symbols from PlatformSymbols and ProjectSymbols into SymbolTable. + /// + /// This should be done before any other symbol assignment or generation, so that user + /// labels take precedence (by virtue of overwriting the earlier platform symbols), + /// and auto label generation can propery generate a unique label. + /// + /// Within platform symbol loading, later symbols should replace earlier symbols, + /// so that ordering of platform files behaves in an intuitive fashion. + /// + private void MergePlatformProjectSymbols() { + // Start by pulling in the platform symbols. + foreach (PlatformSymbols ps in PlatformSyms) { + foreach (Symbol sym in ps) { + SymbolTable[sym.Label] = sym; + } + } + + // Now add project symbols, overwriting platform symbols with the same label. + foreach (KeyValuePair kvp in ProjectProps.ProjectSyms) { + SymbolTable[kvp.Value.Label] = kvp.Value; + } + } + + /// + /// Merges symbols from UserLabels into SymbolTable. Existing entries with matching + /// labels will be replaced. + /// + private void UpdateAndMergeUserLabels() { + // We store symbols as label+value, but for a user label the actual value is + // the address of the offset the label is associated with. It's convenient + // to store labels as Symbols because we also want the Type value, and it avoids + // having to create Symbol objects on the fly. If the value in the UserLabel + // is wrong, we fix it here. + + Dictionary changes = new Dictionary(); + + foreach (KeyValuePair kvp in UserLabels) { + int offset = kvp.Key; + Symbol sym = kvp.Value; + int expectedAddr = mAddrMap.OffsetToAddress(offset); + if (sym.Value != expectedAddr) { + Symbol newSym = new Symbol(sym.Label, expectedAddr, sym.SymbolSource, + sym.SymbolType); + Debug.WriteLine("Replacing label sym: " + sym + " --> " + newSym); + changes[offset] = newSym; + sym = newSym; + } + SymbolTable[kvp.Value.Label] = sym; + } + + // If we updated any symbols, merge the changes back into UserLabels. + if (changes.Count != 0) { + Debug.WriteLine("...merging " + changes.Count + " symbols into UserLabels"); + } + foreach (KeyValuePair kvp in changes) { + UserLabels[kvp.Key] = kvp.Value; + } + } + + /// + /// Removes user labels from the symbol table if they're in the middle of an + /// instruction or multi-byte data area. (Easy way to cause this: hint a 3-byte + /// instruction as data, add a label to the middle byte, remove hints.) + /// + /// Call this after the code and data analysis passes have completed. Any + /// references to the hidden labels will just fall through. It will be possible + /// to create multiple labels with the same name, because the app won't see them + /// in the symbol table. + /// + private void RemoveHiddenLabels() { + // TODO(someday): keep the symbols in the symbol table so we can't create a + // duplicate, but flag it as hidden. The symbol resolver will need to know + // to ignore it. Provide a way for users to purge them. We could just blow + // them out of UserLabels right now, but I'm trying to avoid discarding user- + // created data without permission. + foreach (KeyValuePair kvp in UserLabels) { + int offset = kvp.Key; + if (!mAnattribs[offset].IsStart) { + Debug.WriteLine("Stripping hidden label '" + kvp.Value.Label + "'"); + SymbolTable.Remove(kvp.Value); + } + } + } + + /// + /// Generates references to symbols in the project/platform symbol tables. + /// + /// For each instruction or data item that appears to reference an address, and + /// does not have a target offset, look for a matching address in the symbol tables. + /// + /// This works pretty well for addresses, but is a little rough for constants. + /// + /// Call this after the code and data analysis passes have completed. This doesn't + /// interact with labels, so the ordering there doesn't matter. + /// + public void GeneratePlatformSymbolRefs() { + bool checkNearby = ProjectProps.AnalysisParams.SeekNearbyTargets; + + for (int offset = 0; offset < mAnattribs.Length; ) { + Anattrib attr = mAnattribs[offset]; + if (attr.IsInstructionStart && attr.DataDescriptor == null && + attr.OperandAddress >= 0 && attr.OperandOffset < 0) { + // Has an operand address, but not an offset, meaning it's a reference + // to an address outside the scope of the file. See if it has a + // platform symbol definition. + // + // It might seem unwise to examine the full symbol table, because it has + // non-project non-platform symbols in it. However, any matching user + // labels would have been applied already. Also, we want to ensure that + // conflicting user labels take precedence, e.g. creating a user label "COUT" + // will prevent a platform symbol with the same name from being visible. + // Using the full symbol table is potentially a tad less efficient than + // looking for a match exclusively in project/platform symbols, but it's + // the correct thing to do. + Symbol sym = SymbolTable.FindAddressByValue(attr.OperandAddress); + + // If we didn't find it, check addr-1. This is very helpful when working + // with pointers, because it gets us references to "PTR+1" when "PTR" is + // defined. (It's potentially helpful in labeling the "near side" of an + // address map split as well, since the first byte past is an external + // address, and a label at the end of the current region will be offset + // from by this.) + if (sym == null && (attr.OperandAddress & 0xffff) != 0xffff && checkNearby) { + sym = SymbolTable.FindAddressByValue(attr.OperandAddress - 1); + } + // TODO(maybe): if this is a 65816, check for a symbol at addr-2, for the + // benefit of long pointers. + if (sym != null) { + mAnattribs[offset].DataDescriptor = + FormatDescriptor.Create(mAnattribs[offset].Length, + new WeakSymbolRef(sym.Label, WeakSymbolRef.Part.Low), false); + + // Used to do this here; now do it in GenerateXrefs() so we can + // pick up user-edited operand formats that reference project symbols. + //(sym as DefSymbol).Xrefs.Add(new XrefSet.Xref(offset, + // XrefSet.XrefType.NameReference, 0)); + } + } + + if (attr.IsDataStart || attr.IsInlineDataStart) { + offset += attr.Length; + } else { + // Advance by one, not attr.Length, so we don't miss embedded instructions. + offset++; + } + } + } + + /// + /// Generates labels for branch and data targets, and xref lists for all referenced + /// offsets. Also generates Xref entries for DefSymbols (for .eq directives). + /// + /// Call this after the code and data analysis passes have completed. + /// + public void GenerateXrefs() { + // Xref generation. There are two general categories of references: + // (1) Numeric reference. Comes from instructions (e.g. "LDA $1000" or "BRA $1000") + // and Numeric/Address data items. + // (2) Symbolic reference. Comes from instructions and data with Symbol format + // descriptors. In some cases this may be a partial ref, e.g. "LDA #>label". + // The symbol's value may not match the operand, in which case an adjustment + // is applied. + // + // We want to tag both. So if "LDA $1000" becomes "LDA label-2", we want to + // add a numeric reference to the code at $1000, and a symbolic reference to the + // labe at $1002, that point back to the LDA instruction. These are presented + // slightly differently to the user. For a symbolic reference with no adjustment, + // we don't add the (redundant) numeric reference. + // + // In some cases the numeric reference will land in the middle of an instruction + // or multi-byte data area and won't be visible. + + // Clear previous cross-reference data from project/platform symbols. These + // symbols don't have file offsets, so we can't store them in the main mXrefs + // list. + foreach (Symbol sym in SymbolTable) { + if (sym is DefSymbol) { + (sym as DefSymbol).Xrefs.Clear(); + } + } + + // Create a mapping from label (which must be unique) to file offset. This + // is different from UserLabels (which only has user-created labels, and is + // sorted by offset) and SymbolTable (which has constants and platform symbols, + // and uses the address as value rather than the offset). + SortedList labelList = new SortedList(mFileData.Length, + Asm65.Label.LABEL_COMPARER); + for (int offset = 0; offset < mAnattribs.Length; offset++) { + Anattrib attr = mAnattribs[offset]; + if (attr.Symbol != null) { + try { + labelList.Add(attr.Symbol.Label, offset); + } catch (ArgumentException ex) { + // Duplicate UserLabel entries are stripped when projects are loaded, + // but it might be possible to cause this by hiding/unhiding a + // label (e.g. using hints to place it in the middle of an instruction). + // Just ignore the duplicate. + Debug.WriteLine("Xref ignoring duplicate label '" + attr.Symbol.Label + + "': " + ex.Message); + } + } + } + + // Walk through the Anattrib array, adding xref entries to things referenced + // by the entity at the current offset. + for (int offset = 0; offset < mAnattribs.Length; ) { + Anattrib attr = mAnattribs[offset]; + + XrefSet.XrefType xrefType = XrefSet.XrefType.Unknown; + if (attr.IsInstruction) { + OpDef op = CpuDef.GetOpDef(FileData[offset]); + if (op.IsBranch) { + xrefType = XrefSet.XrefType.BranchOperand; + } else { + xrefType = XrefSet.XrefType.InstrOperand; + } + } else if (attr.IsData || attr.IsInlineData) { + xrefType = XrefSet.XrefType.DataOperand; + } + + bool hasZeroOffsetSym = false; + if (attr.DataDescriptor != null) { + FormatDescriptor dfd = attr.DataDescriptor; + if (dfd.FormatSubType == FormatDescriptor.SubType.Symbol) { + // For instructions with address operands that resolve in-file, grab + // the target offset. + int operandOffset = -1; + if (attr.IsInstructionStart) { + operandOffset = attr.OperandOffset; + } + + // Is this a reference to a label? + if (labelList.TryGetValue(dfd.SymbolRef.Label, out int symOffset)) { + // Compute adjustment. + int adj = 0; + if (operandOffset >= 0) { + // We can compute (symOffset - operandOffset), but that gives us + // the offset adjustment, not the address adjustment. + adj = mAnattribs[symOffset].Address - + mAnattribs[operandOffset].Address; + } + + AddXref(symOffset, new XrefSet.Xref(offset, true, xrefType, adj)); + if (adj == 0) { + hasZeroOffsetSym = true; + } + } else if (SymbolTable.TryGetValue(dfd.SymbolRef.Label, out Symbol sym)) { + // Is this a reference to a project/platform symbol? + if (sym.SymbolSource == Symbol.Source.Project || + sym.SymbolSource == Symbol.Source.Platform) { + DefSymbol defSym = sym as DefSymbol; + int adj = 0; + if (operandOffset >= 0) { + adj = defSym.Value - operandOffset; + } + defSym.Xrefs.Add(new XrefSet.Xref(offset, true, xrefType, adj)); + } else { + Debug.WriteLine("NOTE: not xrefing '" + sym.Label + "'"); + Debug.Assert(false); // not possible? + } + } + } else if (dfd.FormatSubType == FormatDescriptor.SubType.Address) { + // not expecting this format on an instruction operand + Debug.Assert(attr.IsData || attr.IsInlineData); + int operandOffset = RawData.GetWord(mFileData, offset, + dfd.Length, dfd.FormatType == FormatDescriptor.Type.NumericBE); + AddXref(operandOffset, new XrefSet.Xref(offset, false, xrefType, 0)); + } + + // Look for instruction offset references. We skip this if we've already + // added a reference from a symbol with zero adjustment, since that would + // just leave a duplicate entry. (The symbolic ref wins because we need + // it for the label localizer and possibly the label refactorer.) + if (!hasZeroOffsetSym && attr.IsInstructionStart && attr.OperandOffset >= 0) { + AddXref(attr.OperandOffset, new XrefSet.Xref(offset, false, xrefType, 0)); + } + } + + if (attr.IsDataStart) { + // There shouldn't be data items inside of other data items. + offset += attr.Length; + } else { + // Advance by one, not attr.Length, so we don't miss embedded instructions. + offset++; + } + } + } + + /// + /// Adds an Xref entry to an XrefSet. The XrefSet will be created if necessary. + /// + /// File offset for which cross-references are being noted. + /// Cross reference to add to the set. + private void AddXref(int offset, XrefSet.Xref xref) { + if (!mXrefs.TryGetValue(offset, out XrefSet xset)) { + xset = mXrefs[offset] = new XrefSet(); + } + xset.Add(xref); + } + + /// + /// Returns the XrefSet for the specified offset. May return null if the set is + /// empty. + /// + public XrefSet GetXrefSet(int offset) { + mXrefs.TryGetValue(offset, out XrefSet xset); + return xset; // will be null if not found + } + + /// + /// Generates the list of project/platform symbols that are being used. Any + /// DefSymbol with a non-empty Xrefs is included. Previous contents are cleared. + /// + /// The list is sorted primarily by value, secondarily by symbol name. + /// + /// Call this after Xrefs are generated. + /// + private void GenerateActiveDefSymbolList() { + ActiveDefSymbolList.Clear(); + + foreach (Symbol sym in SymbolTable) { + if (!(sym is DefSymbol)) { + continue; + } + DefSymbol defSym = sym as DefSymbol; + if (defSym.Xrefs.Count == 0) { + continue; + } + ActiveDefSymbolList.Add(defSym); + } + + // We could make symbol source the primary sort key, so that all platform + // symbols appear before all project symbols. Not sure if that's better. + // + // Could also skip this by replacing the earlier foreach with a walk through + // SymbolTable.mSymbolsByValue, but I'm not sure that should be exposed. + ActiveDefSymbolList.Sort(delegate (DefSymbol a, DefSymbol b) { + if (a.Value < b.Value) { + return -1; + } else if (a.Value > b.Value) { + return 1; + } + return Asm65.Label.LABEL_COMPARER.Compare(a.Label, b.Label); + }); + } + + /// + /// Checks some stuff. Problems are handled with assertions, so this is only + /// useful in debug builds. + /// + public void Validate() { + // Confirm that we can walk through the file, stepping directly from the start + // of one thing to the start of the next. + int offset = 0; + while (offset < mFileData.Length) { + Anattrib attr = mAnattribs[offset]; + Debug.Assert(attr.IsStart); + Debug.Assert(attr.Length != 0); + offset += attr.Length; + + // Sometimes embedded instructions continue past the "outer" instruction, + // usually because we're misinterpreting the code. We need to deal with + // that here. + int extraInstrBytes = 0; + while (offset < mFileData.Length && mAnattribs[offset].IsInstruction && + !mAnattribs[offset].IsInstructionStart) { + extraInstrBytes++; + offset++; + } + //if (extraInstrBytes > 0) { Debug.WriteLine("EIB=" + extraInstrBytes); } + // Max instruction len is 4, so the stray part must be shorter. + Debug.Assert(extraInstrBytes < 4); + } + Debug.Assert(offset == mFileData.Length); + + // Confirm that all bytes are tagged as code, data, or inline data. The Asserts + // in Anattrib should confirm that nothing is tagged as more than one thing. + for (offset = 0; offset < mAnattribs.Length; offset++) { + Anattrib attr = mAnattribs[offset]; + Debug.Assert(attr.IsInstruction || attr.IsInlineData || attr.IsData); + } + + // Confirm that there are no Default format entries in OperandFormats. + foreach (KeyValuePair kvp in OperandFormats) { + Debug.Assert(kvp.Value.FormatType != FormatDescriptor.Type.Default); + Debug.Assert(kvp.Value.FormatType != FormatDescriptor.Type.REMOVE); + } + } + + /// + /// Generates a ChangeSet that merges the FormatDescriptors in the new list into + /// OperandFormats. + /// + /// All existing descriptors that overlap with new descriptors will be removed. + /// In cases where old and new descriptors have the same starting offset, this + /// will be handled with a single change object. + /// + /// If old and new descriptors are identical, no change object will be generated. + /// It's possible for this to return an empty change set. + /// + /// List of new format descriptors. + /// Change set. + public ChangeSet GenerateFormatMergeSet(SortedList newList) { + Debug.WriteLine("Generating format merge set..."); + ChangeSet cs = new ChangeSet(newList.Count * 2); + + // The Keys and Values properties are documented to return the internal data + // structure, not make a copy, so this will be fast. + IList mainKeys = OperandFormats.Keys; + IList mainValues = OperandFormats.Values; + IList newKeys = newList.Keys; + IList newValues = newList.Values; + + // The basic idea is to walk through the new list, checking each entry for + // conflicts with the main list. If there's no conflict, we create a change + // object for the new item. If there is a conflict, we resolve it appropriately. + // + // The check on the main list is very fast because both lists are in sorted + // order, so we can just walk the main list forward. If a main-list entry + // conflicts, we create a removal object, and advance the main index. + int mainIndex = 0; + int newIndex = 0; + while (newIndex < newKeys.Count) { + int newOffset = newKeys[newIndex]; + int newLength = newValues[newIndex].Length; + if (mainIndex >= mainKeys.Count) { + // We've run off the end of the main list. Just add the new item. + UndoableChange uc = UndoableChange.CreateActualOperandFormatChange( + newOffset, null, newValues[newIndex]); + cs.AddNonNull(uc); + newIndex++; + continue; + } + + // Check for overlap by computing the intersection. Start and end form two + // points; the intersection is the largest of the start points and the + // smallest of the end points. If the result of the computation puts end before + // start, there's no overlap. + int mainOffset = mainKeys[mainIndex]; + int mainLength = mainValues[mainIndex].Length; + Debug.Assert(newLength > 0 && mainLength > 0); + int interStart = Math.Max(mainOffset, newOffset); + int interEnd = Math.Min(mainOffset + mainLength, newOffset + newLength); + // exclusive end point, so interEnd == interStart means no overlap + if (interEnd > interStart) { + Debug.WriteLine("Found overlap: main(+" + mainOffset.ToString("x6") + + "," + mainLength + ") : new(+" + newOffset.ToString("x6") + + "," + newLength + ")"); + + // See if the initial offsets are identical. If so, put the add and + // remove into a single change. This isn't strictly necessary, but it's + // slightly more efficient. + if (mainOffset == newOffset) { + // Check to see if the descriptors are identical. If so, ignore this. + if (mainValues[mainIndex] == newValues[newIndex]) { + Debug.WriteLine(" --> no-op change " + newValues[newIndex]); + } else { + Debug.WriteLine(" --> replace change " + newValues[newIndex]); + UndoableChange uc = UndoableChange.CreateActualOperandFormatChange( + newOffset, mainValues[mainIndex], newValues[newIndex]); + cs.AddNonNull(uc); + } + } else { + // Remove the old entry, add the new entry. + Debug.WriteLine(" --> remove/add change " + newValues[newIndex]); + UndoableChange ruc = UndoableChange.CreateActualOperandFormatChange( + mainOffset, mainValues[mainIndex], null); + UndoableChange auc = UndoableChange.CreateActualOperandFormatChange( + newOffset, null, newValues[newIndex]); + cs.AddNonNull(ruc); + cs.AddNonNull(auc); + } + newIndex++; + + // Remove all other main-list entries that overlap with this one. + while (++mainIndex < mainKeys.Count) { + mainOffset = mainKeys[mainIndex]; + mainLength = mainValues[mainIndex].Length; + interStart = Math.Max(mainOffset, newOffset); + interEnd = Math.Min(mainOffset + mainLength, newOffset + newLength); + // exclusive end point, so interEnd == interStart means no overlap + if (interEnd <= interStart) { + break; + } + Debug.WriteLine(" also remove +" + mainOffset.ToString("x6") + + mainValues[mainIndex]); + UndoableChange uc = UndoableChange.CreateActualOperandFormatChange( + mainOffset, mainValues[mainIndex], null); + cs.AddNonNull(uc); + } + } else { + // No overlap. If the main entry is earlier, we can cross it off the list + // and advance to the next one. Otherwise, we add the change and advance + // that list. + if (mainOffset < newOffset) { + mainIndex++; + } else { + Debug.WriteLine("Add non-overlap " + newOffset.ToString("x6") + + newValues[newIndex]); + UndoableChange uc = UndoableChange.CreateActualOperandFormatChange( + newOffset, null, newValues[newIndex]); + cs.AddNonNull(uc); + newIndex++; + } + } + } + + // Trim away excess capacity, since this will probably be sitting in an undo + // list for a long time. + cs.TrimExcess(); + Debug.WriteLine("Total " + cs.Count + " changes"); + return cs; + } + + /// + /// Returns the analyzer attributes for the specified byte offset. + /// + /// Bear in mind that Anattrib is a struct, and thus the return value is a copy. + /// + public Anattrib GetAnattrib(int offset) { + return mAnattribs[offset]; + } + + /// + /// Returns true if the offset has a long comment or note. Used for determining how to + /// split up a data area. Currently not returning true for an end-of-line comment. + /// + /// Offset of interest. + /// True if a comment or note was found. + public bool HasCommentOrNote(int offset) { + return (LongComments.ContainsKey(offset) || + Notes.ContainsKey(offset)); + } + + /// + /// True if an "undo" operation is available. + /// + public bool CanUndo { get { return mUndoTop > 0; } } + + /// + /// True if a "redo" operation is available. + /// + public bool CanRedo { get { return mUndoTop < mUndoList.Count; } } + + /// + /// True if something has changed since the last time the file was saved. + /// + public bool IsDirty { get { return mUndoTop != mUndoSaveIndex; } } + + /// + /// Sets the save index equal to the undo position. Do this after the file has + /// been successfully saved. + /// + public void ResetDirtyFlag() { + mUndoSaveIndex = mUndoTop; + } + + /// + /// Returns the next undo operation, and moves the pointer to the previous item. + /// + public ChangeSet PopUndoSet() { + if (!CanUndo) { + throw new Exception("Can't undo"); + } + Debug.WriteLine("PopUndoSet: returning entry " + (mUndoTop - 1) + ": " + + mUndoList[mUndoTop - 1]); + return mUndoList[--mUndoTop]; + } + + /// + /// Returns the next redo operation, and moves the pointer to the next item. + /// + /// + public ChangeSet PopRedoSet() { + if (!CanRedo) { + throw new Exception("Can't redo"); + } + Debug.WriteLine("PopRedoSet: returning entry " + mUndoTop + ": " + + mUndoList[mUndoTop]); + return mUndoList[mUndoTop++]; + } + + /// + /// Adds a change set to the undo list. All redo operations above it on the + /// stack are removed. + /// + /// We currently allow empty sets. + /// + /// Set to push. + public void PushChangeSet(ChangeSet changeSet) { + Debug.WriteLine("PushChangeSet: adding " + changeSet); + + // Remove all of the "redo" entries from the current position to the end. + if (mUndoTop < mUndoList.Count) { + Debug.WriteLine("PushChangeSet: removing " + (mUndoList.Count - mUndoTop) + + " entries"); + mUndoList.RemoveRange(mUndoTop, mUndoList.Count - mUndoTop); + } + + mUndoList.Add(changeSet); + mUndoTop = mUndoList.Count; + } + + public string DebugGetUndoRedoHistory() { + StringBuilder sb = new StringBuilder(); + sb.Append("Bracketed change will be overwritten by next action\r\n\r\n"); + + for (int i = 0; i < mUndoList.Count; i++) { + ChangeSet cs = mUndoList[i]; + + char lbr, rbr; + if (i == mUndoTop) { + lbr = '['; + rbr = ']'; + } else { + lbr = rbr = ' '; + } + sb.AppendFormat("{0}{3,3:D}{1}{2}: {4} change{5}\r\n", + lbr, rbr, i == mUndoSaveIndex ? "*" : " ", + i, cs.Count, cs.Count == 1 ? "" : "s"); + + for (int j = 0; j < cs.Count; j++) { + UndoableChange uc = cs[j]; + sb.AppendFormat(" type={0} offset=+{1} reReq={2}\r\n", + uc.Type, uc.HasOffset ? uc.Offset.ToString("x6") : "N/A", + uc.ReanalysisRequired); + } + } + if (mUndoTop == mUndoList.Count) { + sb.AppendFormat("[ - ]{0}\r\n", mUndoTop == mUndoSaveIndex ? "*" : " "); + } + + return sb.ToString(); + } + + /// + /// Applies the changes to the project, and updates the display. + /// + /// Set of changes to apply. + /// If set, undo the changes instead. + /// List of offsets affected by change. + /// An indication of the level of reanalysis required. If this returns None, + /// the list of offsets to update will be in affectedOffsets. + public UndoableChange.ReanalysisScope ApplyChanges(ChangeSet cs, bool backward, + out RangeSet affectedOffsets) { + affectedOffsets = new RangeSet(); + + UndoableChange.ReanalysisScope needReanalysis = UndoableChange.ReanalysisScope.None; + + // TODO(maybe): if changes overlap, we need to apply them in reverse order when + // "backward" is set. This requires a reverse-order enumerator from + // ChangeSet. Not currently needed. + foreach (UndoableChange uc in cs) { + object oldValue, newValue; + + // Unpack change, flipping old/new for undo. + if (!backward) { + oldValue = uc.OldValue; + newValue = uc.NewValue; + } else { + oldValue = uc.NewValue; + newValue = uc.OldValue; + } + int offset = uc.Offset; + + switch (uc.Type) { + case UndoableChange.ChangeType.Dummy: + //if (uc.ReanalysisRequired == UndoableChange.ReanalysisFlags.None) { + // affectedOffsets.AddRange(0, FileData.Length - 1); + //} + break; + case UndoableChange.ChangeType.SetAddress: { + AddressMap addrMap = AddrMap; + if (addrMap.Get(offset) != (int)oldValue) { + Debug.WriteLine("GLITCH: old address value mismatch (" + + addrMap.Get(offset) + " vs " + (int)oldValue + ")"); + Debug.Assert(false); + } + addrMap.Set(offset, (int)newValue); + + Debug.WriteLine("Map offset +" + offset.ToString("x6") + " to $" + + ((int)newValue).ToString("x6")); + + // ignore affectedOffsets + Debug.Assert(uc.ReanalysisRequired == + UndoableChange.ReanalysisScope.CodeAndData); + } + break; + case UndoableChange.ChangeType.SetTypeHint: { + // Always requires full code+data re-analysis. + ApplyTypeHints((TypedRangeSet)oldValue, (TypedRangeSet)newValue); + // ignore affectedOffsets + Debug.Assert(uc.ReanalysisRequired == + UndoableChange.ReanalysisScope.CodeAndData); + } + break; + case UndoableChange.ChangeType.SetStatusFlagOverride: { + if (StatusFlagOverrides[offset] != (StatusFlags)oldValue) { + Debug.WriteLine("GLITCH: old status flag mismatch (" + + StatusFlagOverrides[offset] + " vs " + + (StatusFlags)oldValue + ")"); + Debug.Assert(false); + } + StatusFlagOverrides[offset] = (StatusFlags)newValue; + // ignore affectedOffsets + Debug.Assert(uc.ReanalysisRequired == + UndoableChange.ReanalysisScope.CodeAndData); + } + break; + case UndoableChange.ChangeType.SetLabel: { + // NOTE: this is about managing changes to UserLabels. Adding + // or removing a user-defined label requires a full reanalysis, + // even if there was already an auto-generated label present, + // so we don't need to undo/redo Anattribs for anything except + // for renaming a user-defined label. + UserLabels.TryGetValue(offset, out Symbol oldSym); + if (oldSym != (Symbol) oldValue) { + Debug.WriteLine("GLITCH: old label value mismatch ('" + + oldSym + "' vs '" + oldValue + "')"); + Debug.Assert(false); + } + + if (newValue == null) { + // We're removing a user label. + UserLabels.Remove(offset); + SymbolTable.Remove((Symbol)oldValue); // unnecessary? + Debug.Assert(uc.ReanalysisRequired == + UndoableChange.ReanalysisScope.DataOnly); + } else { + // We're adding or renaming a user label. + // + // We should not be changing a label to the same value as an + // existing label -- the dialog should have prevented it. + // This is important because, if we edit a label to match an + // auto-generated label, we'll have a duplicate label unless we + // do a full code+data reanalysis. If we're okay with reanalyzing + // on user-label renames, we can allow such conflicts. + if (oldValue != null) { + SymbolTable.Remove((Symbol)oldValue); + } + UserLabels[offset] = (Symbol)newValue; + //SymbolTable[((Symbol)newValue).Label] = (Symbol)newValue; + SymbolTable.Add((Symbol)newValue); + Debug.Assert(oldSym != null || uc.ReanalysisRequired == + UndoableChange.ReanalysisScope.DataOnly); + } + + if (uc.ReanalysisRequired == UndoableChange.ReanalysisScope.None) { + // Shouldn't really be "changing" from null to null, but + // it's legal, so don't blow up if it happens. + // (The assert on SymbolSource is older -- we now only care about + // what's in UserLabels, which are always Source=User.) + Debug.Assert((oldValue == null && newValue == null) || + (((Symbol)oldValue).SymbolSource == Symbol.Source.User && + ((Symbol)newValue).SymbolSource == Symbol.Source.User)); + // Not doing a full refresh, so keep this up to date. + mAnattribs[offset].Symbol = (Symbol)newValue; + + if (oldValue != null) { + // Update everything in Anattribs and OperandFormats that + // referenced the old symbol. + RefactorLabel(offset, ((Symbol)oldValue).Label); + } + + affectedOffsets.Add(offset); + + // Use the cross-reference table to identify the offsets that + // we need to update. + if (mXrefs.TryGetValue(offset, out XrefSet xrefs)) { + foreach (XrefSet.Xref xr in xrefs) { + // This isn't quite right -- in theory we should be adding + // all offsets that are part of the instruction, so that + // affectedOffsets can hold a contiguous range instead of + // a collection of opcode offsets. In practice, for a + // label change, it shouldn't matter. + affectedOffsets.Add(xr.Offset); + } + } + } else { + // We're not calling RefactorLabel() here because we should + // only be doing the reanalysis if we're adding or removing + // the label, not renaming it. If that changes, we'll need + // to do the refactor here, though we can skip Anattribs work. + Debug.Assert(oldValue == null || newValue == null); + } + } + break; + case UndoableChange.ChangeType.SetOperandFormat: { + // Note this is used for data/inline-data as well as instructions. + OperandFormats.TryGetValue(offset, out FormatDescriptor current); + if (current != (FormatDescriptor)oldValue) { + Debug.WriteLine("GLITCH: old operand format mismatch (" + + current + " vs " + oldValue + ")"); + Debug.Assert(false); + } + if (newValue == null) { + OperandFormats.Remove(offset); + mAnattribs[offset].DataDescriptor = null; + } else { + OperandFormats[offset] = mAnattribs[offset].DataDescriptor = + (FormatDescriptor)newValue; + } + if (uc.ReanalysisRequired == UndoableChange.ReanalysisScope.None) { + // Add every offset in the range. The length might be changing + // (e.g. an offset with a single byte is now the start of a + // 10-byte string), so touch everything that was affected by + // the old descriptor or is affected by the new descriptor. + // [This may no longer be necessary -- size changes now + // require reanalysis.] + int afctLen = 1; + if (oldValue != null) { + afctLen = + Math.Max(afctLen, ((FormatDescriptor)oldValue).Length); + } + if (newValue != null) { + afctLen = + Math.Max(afctLen, ((FormatDescriptor)newValue).Length); + } + + for (int i = offset; i < offset + afctLen; i++) { + affectedOffsets.Add(i); + } + } + } + break; + case UndoableChange.ChangeType.SetComment: { + if (!Comments[offset].Equals(oldValue)) { + Debug.WriteLine("GLITCH: old comment value mismatch ('" + + Comments[offset] + "' vs '" + oldValue + "')"); + Debug.Assert(false); + } + Comments[offset] = (string)newValue; + + // Only affects this offset. + affectedOffsets.Add(offset); + } + break; + case UndoableChange.ChangeType.SetLongComment: { + LongComments.TryGetValue(offset, out MultiLineComment current); + if (current != (MultiLineComment)oldValue) { + Debug.WriteLine("GLITCH: old long comment value mismatch ('" + + current + "' vs '" + oldValue + "')"); + Debug.Assert(false); + } + if (newValue == null) { + LongComments.Remove(offset); + } else { + LongComments[offset] = (MultiLineComment)newValue; + } + + // Only affects this offset. + affectedOffsets.Add(offset); + } + break; + case UndoableChange.ChangeType.SetNote: { + Notes.TryGetValue(offset, out MultiLineComment current); + if (current != (MultiLineComment)oldValue) { + Debug.WriteLine("GLITCH: old note value mismatch ('" + + current + "' vs '" + oldValue + "')"); + Debug.Assert(false); + } + if (newValue == null) { + Notes.Remove(offset); + } else { + Notes[offset] = (MultiLineComment)newValue; + } + + // Only affects this offset. + affectedOffsets.Add(offset); + } + break; + case UndoableChange.ChangeType.SetProjectProperties: { + bool needExternalFileReload = !CommonUtil.Container.StringListEquals( + ((ProjectProperties)oldValue).PlatformSymbolFileIdentifiers, + ((ProjectProperties)newValue).PlatformSymbolFileIdentifiers, + null /*StringComparer.InvariantCulture*/); + needExternalFileReload |= !CommonUtil.Container.StringListEquals( + ((ProjectProperties)oldValue).ExtensionScriptFileIdentifiers, + ((ProjectProperties)newValue).ExtensionScriptFileIdentifiers, + null); + + // ProjectProperties are mutable, so create a new object that's + // a clone of the one that will live in the undo buffer. + ProjectProps = new ProjectProperties((ProjectProperties)newValue); + + // Most of the properties are simply used during the reanalysis + // process. This must be set explicitly. NOTE: replacing this + // could cause cached data (such as Formatter strings) to be + // discarded, so ideally we wouldn't do this unless we know the + // CPU definition has changed (or we know that GetBestMatch is + // memoizing results and will return the same object). + Debug.WriteLine("Replacing CPU def object"); + UpdateCpuDef(); + + if (needExternalFileReload) { + LoadExternalFiles(); + } + } + break; + default: + break; + } + needReanalysis |= uc.ReanalysisRequired; + } + + return needReanalysis; + } + + /// + /// Updates all symbolic references to the old label. + /// + /// Offset with the just-renamed label. + /// Previous value. + private void RefactorLabel(int labelOffset, string oldLabel) { + if (!mXrefs.TryGetValue(labelOffset, out XrefSet xrefs)) { + // This can happen if you add a label in the middle of nowhere and rename it. + Debug.WriteLine("RefactorLabel: no references to " + oldLabel); + return; + } + + string newLabel = mAnattribs[labelOffset].Symbol.Label; + + // + // Update format descriptors in Anattribs. + // + foreach (XrefSet.Xref xr in xrefs) { + FormatDescriptor dfd = mAnattribs[xr.Offset].DataDescriptor; + if (dfd == null) { + // Should be a data target reference here? Where'd the xref come from? + Debug.Assert(false); + continue; + } + if (!dfd.HasSymbol) { + // The auto-gen stuff would have created a symbol, but the user can + // override that and display as e.g. hex. + continue; + } + if (!Label.LABEL_COMPARER.Equals(oldLabel, dfd.SymbolRef.Label)) { + // This can happen if the xref is based on the operand offset, + // but the user picked a different symbol. The xref generator + // creates entries for both target offsets, but only one will + // have a matching label. + continue; + } + + mAnattribs[xr.Offset].DataDescriptor = FormatDescriptor.Create( + dfd.Length, new WeakSymbolRef(newLabel, dfd.SymbolRef.ValuePart), + dfd.FormatType == FormatDescriptor.Type.NumericBE); + } + + // + // Update value in OperandFormats. + // + foreach (XrefSet.Xref xr in xrefs) { + if (!OperandFormats.TryGetValue(xr.Offset, out FormatDescriptor dfd)) { + // Probably an auto-generated symbol ref, so no entry in OperandFormats. + continue; + } + if (!dfd.HasSymbol) { + continue; + } + if (!Label.LABEL_COMPARER.Equals(oldLabel, dfd.SymbolRef.Label)) { + continue; + } + + Debug.WriteLine("Replacing symbol at +" + xr.Offset.ToString("x6") + + " with " + newLabel); + OperandFormats[xr.Offset] = FormatDescriptor.Create( + dfd.Length, new WeakSymbolRef(newLabel, dfd.SymbolRef.ValuePart), + dfd.FormatType == FormatDescriptor.Type.NumericBE); + } + } + + + /// + /// Applies the values in the set to the project hints. + /// + /// Previous values; must match current contents. + /// Values to apply. + private void ApplyTypeHints(TypedRangeSet oldSet, TypedRangeSet newSet) { + CodeAnalysis.TypeHint[] hints = TypeHints; + foreach (TypedRangeSet.Tuple tuple in newSet) { + CodeAnalysis.TypeHint curType = hints[tuple.Value]; + if (!oldSet.GetType(tuple.Value, out int oldType) || oldType != (int)curType) { + Debug.WriteLine("Type mismatch at " + tuple.Value); + Debug.Assert(false); + } + + //Debug.WriteLine("Set +" + tuple.Value.ToString("x6") + " to " + + // (CodeAnalysis.TypeHint)tuple.Type + " (was " + + // curType + ")"); + + hints[tuple.Value] = (CodeAnalysis.TypeHint)tuple.Type; + } + } + + /// + /// Finds a label by name. SymbolTable must be populated. + /// + /// Label to find. + /// File offset associated with label, or -1 if not found. + public int FindLabelByName(string name) { + // We're interested in user labels and auto-generated labels. Do a lookup in + // SymbolTable to find the symbol, then if it's user or auto, we do a second + // search to find the file offset it's associated with. The second search + // requires a linear walk through anattribs; if we do this often we'll want to + // maintain a symbol-to-offset structure. + // + // This will not find "hidden" labels, i.e. labels that are in the middle of an + // instruction or multi-byte data area, because those are removed from SymbolTable. + if (!SymbolTable.TryGetValue(name, out Symbol sym)) { + return -1; + } + if (sym.SymbolSource != Symbol.Source.Auto && sym.SymbolSource != Symbol.Source.User) { + return -1; + } + for (int i = 0; i < mAnattribs.Length; i++) { + if (mAnattribs[i].Symbol == sym) { + return i; + } + } + Debug.WriteLine("NOTE: symbol '" + name + "' exists, but wasn't found in labels"); + return -1; + } + + /// + /// For debugging purposes, get some information about the currently loaded + /// extension scripts. + /// + public string DebugGetLoadedScriptInfo() { + return mScriptManager.DebugGetLoadedScriptInfo(); + } + } +} diff --git a/SourceGen/DisplayList.cs b/SourceGen/DisplayList.cs new file mode 100644 index 0000000..03af122 --- /dev/null +++ b/SourceGen/DisplayList.cs @@ -0,0 +1,1194 @@ +/* + * Copyright 2018 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.Drawing; +using System.Text; + +using Asm65; + +namespace SourceGen { + /// + /// Converts file data and Anattrib contents into a series of strings and format metadata. + /// This is used as the backing store for ProjectView's codeListView. + /// + public class DisplayList { + /// + /// List of display lines. + /// + private List mLineList; + + /// + /// Project that contains the data we're formatting, notably the FileData and + /// Anattribs arrays. + /// + private DisasmProject mProject; + + /// + /// Code/data formatter. + /// + private Formatter mFormatter; + + /// + /// If set, prepend cycle counts to EOL comments. + /// + private bool mShowCycleCounts; + + /// + /// Names for pseudo-ops. + /// + private PseudoOp.PseudoOpNames mPseudoOpNames; + + + /// + /// Holds a collection of formatted strings. Instances are immutable. + /// + public class FormattedParts { + public string Offset { get; private set; } + public string Addr { get; private set; } + public string Bytes { get; private set; } + public string Flags { get; private set; } + public string Attr { get; private set; } + public string Label { get; private set; } + public string Opcode { get; private set; } + public string Operand { get; private set; } + public string Comment { get; private set; } + + // Use factory methods. + private FormattedParts() { } + + public static FormattedParts Create(string offset, string addr, string bytes, + string flags, string attr, string label, string opcode, string operand, + string comment, string debug) { + FormattedParts parts = new FormattedParts(); + parts.Offset = offset; + parts.Addr = addr; + parts.Bytes = bytes; + parts.Flags = flags; + parts.Attr = attr; + parts.Label = label; + parts.Opcode = opcode; + parts.Operand = operand; + parts.Comment = comment; + return parts; + } + + public static FormattedParts CreateBlankLine() { + FormattedParts parts = new FormattedParts(); + return parts; + } + + public static FormattedParts CreateLongComment(string comment) { + FormattedParts parts = new FormattedParts(); + parts.Comment = comment; + return parts; + } + + public static FormattedParts CreateDirective(string opstr, string addrStr) { + FormattedParts parts = new FormattedParts(); + parts.Opcode = opstr; + parts.Operand = addrStr; + return parts; + } + + public static FormattedParts CreateEquDirective(string label, string opstr, + string addrStr, string comment) { + FormattedParts parts = new FormattedParts(); + parts.Label = label; + parts.Opcode = opstr; + parts.Operand = addrStr; + parts.Comment = comment; + return parts; + } + } + + /// + /// One of these per line of output in the display. It should be possible to draw + /// all of the output without needing to refer back to the project data. (Currently + /// making an exception for some selection-dependent field highlighting.) + /// + /// Base fields are immutable, but the Parts property is set after creation. + /// + public class Line { + // Extremely-negative offset value ensures it's at the very top. + public const int HEADER_COMMENT_OFFSET = int.MinValue + 1; + + [FlagsAttribute] + public enum Type { + Unclassified = 0, + + // Primary functional items. + Code = 1 << 0, + Data = 1 << 1, // includes inline data + CodeOrData = (Code | Data), + + // Decorative items, added by user or formatter. + LongComment = 1 << 2, + Note = 1 << 3, + Blank = 1 << 4, + + // Assembler directives. + OrgDirective = 1 << 5, + EquDirective = 1 << 6, + RegWidthDirective = 1 << 7, + } + + /// + /// Line type. + /// + public Type LineType { get; private set; } + + /// + /// Numeric offset value. Used to map a line item to the Anattrib. Note this is + /// set for all lines, and is the the same for all lines in a multi-line sequence, + /// e.g. every line in a long comment has the file offset with which it is associated. + /// + public int FileOffset { get; private set; } + + /// + /// Number of offsets this line covers. Will be > 0 for code and data, zero for + /// everything else. The same value is used for all lines in a multi-line sequence. + /// + public int OffsetSpan { get; private set; } + + /// + /// For multi-line entries, this indicates which line is represented. For + /// single-line entries, this will be zero. + /// + public int SubLineIndex { get; private set; } + + /// + /// Strings for display. Creation may be deferred. Use the DisplayList + /// GetFormattedParts() method to access this property. + /// + public FormattedParts Parts { get; set; } + + /// + /// Background color, used for notes. + /// + public Color BackgroundColor { get; set; } + + /// + /// String for searching. May be created on demand when the Line is first searched. + /// + public string SearchString { get; set; } + + + public Line(int offset, int span, Type type) { + FileOffset = offset; + OffsetSpan = span; + LineType = type; + SubLineIndex = 0; + } + + public Line(int offset, int span, Type type, int subLineIndex) { + FileOffset = offset; + OffsetSpan = span; + LineType = type; + SubLineIndex = subLineIndex; + } + + /// + /// True if this line is code or data. + /// + public bool IsCodeOrData { + get { + return LineType == Type.Code || LineType == Type.Data; + } + } + + /// + /// Returns true if the specified offset is represented by this line. There + /// will be only one code/data line for a given offset, but there may be + /// multiple others (comments, notes, etc.) associated with it. + /// + /// + /// + public bool Contains(int offset) { + // Note OffsetSpan can be zero. + return (offset == FileOffset || + (offset >= FileOffset && offset < FileOffset + OffsetSpan)); + } + + public override string ToString() { + return "Line type=" + LineType + " off=+" + FileOffset.ToString("x6") + + " span=" + OffsetSpan; + } + } + + /// + /// Captures the set of selected lines. Lines are identified by offset and type. + /// + /// The idea is to save the selection, rebuild the list -- potentially moving + /// stuff around -- and then rebuild the selection bitmap by finding matching + /// items. + /// + /// We don't try to identify parts of multi-line things. If you've selected + /// part of a multi-line string, then when we restore the selection you'll have + /// the entire string selected. For the operations that are possible across + /// multiple offsets, this seems like reasonable behavior. + /// + /// We can't precisely restore the selection in terms of which file offsets + /// are selected. If you select one byte and apply a code hint, we'll restore + /// the selection to a line with 1-4 bytes. This gets weird if you hit "undo", + /// as you will then have 1-4 bytes selected rather than the original one. It + /// might be better to just clear the selection on "undo". + /// + public class SavedSelection { + private class Tag { + public int mOffset; + public int mSpan; + public Line.Type mTypes; + + public Tag(int offset, int span, Line.Type lineType) { + //Debug.Assert(offset >= 0); + Debug.Assert(span >= 0); + mOffset = offset; + mSpan = (span == 0) ? 1 : span; + mTypes = lineType; + } + } + + private List mSelectionTags = new List(); + + /// + /// This is a place to save the file offset associated with the ListView's + /// TopItem, so we can position the list appropriately. + /// + private int mTopOffset; + + // Use Generate(). + private SavedSelection() { } + + /// + /// Creates a new SavedSelection object, generating a list of tags from the + /// lines that are currently selected. + /// + /// If nothing is selected, SavedSelection will have no members. + /// + /// Display list, with list of Lines. + /// Bit vector specifying which lines are selected. + /// New SavedSelection object. + public static SavedSelection Generate(DisplayList dl, VirtualListViewSelection sel, + int topOffset) { + SavedSelection savedSel = new SavedSelection(); + //Debug.Assert(topOffset >= 0); + savedSel.mTopOffset = topOffset; + + List lineList = dl.mLineList; + Debug.Assert(lineList.Count == sel.Length); + + // Generate tags, which are a combination of the offset, span, and a merge + // of types of all the lines associated with that offset. + // + // We may want to consider some sort of optimization for a "select all" + // operation, although there aren't many changes you can make after selecting + // all lines in a very large file. + Tag tag = null; + int curOffset = -1; + for (int i = 0; i < lineList.Count; i++) { + if (!sel[i]) { + continue; + } + Line line = lineList[i]; + // Code hinting can transform code to data and vice-versa, so we + // want the tag to reflect the fact that both could exist. + Line.Type lineType = line.LineType; + if (lineType == Line.Type.Code || lineType == Line.Type.Data) { + lineType = Line.Type.CodeOrData; + } + if (line.FileOffset != curOffset) { + // advanced to new offset, flush previous + if (tag != null) { + savedSel.mSelectionTags.Add(tag); + } + curOffset = line.FileOffset; + + tag = new Tag(line.FileOffset, line.OffsetSpan, lineType); + } else { + // another item at same offset + tag.mSpan = Math.Max(tag.mSpan, line.OffsetSpan); + tag.mTypes |= lineType; + } + } + if (curOffset == -1) { + // It's hard to cause an action that requires save/restore when you don't + // have anything selected in the ListView. However, this can happen if + // you do a sequence like: + // - Open a file that starts with a JMP followed by data. + // - Click on the blank line below the code, which has the code's offset, + // and select "remove hint". This causes the blank line to vanish, + // so the Restore() won't select anything. + // - Click "undo". + Debug.WriteLine("NOTE: no selection found"); + } else { + // Add the in-progress tag to the list. + savedSel.mSelectionTags.Add(tag); + } + + return savedSel; + } + + /// + /// Creates a selection set by identifying the set of lines in the display list + /// that correspond to items in the SavedSelection tag list. + /// + /// Display list, with list of Lines. + /// Set of selected lines. + public VirtualListViewSelection Restore(DisplayList dl, out int topIndex) { + List lineList = dl.mLineList; + VirtualListViewSelection sel = new VirtualListViewSelection(lineList.Count); + + topIndex = -1; + + // Walk through the tag list, which is ordered by ascending offset, and + // through the display list, which is similarly ordered. + int tagIndex = 0; + int lineIndex = 0; + while (tagIndex < mSelectionTags.Count && lineIndex < lineList.Count) { + Tag tag = mSelectionTags[tagIndex]; + int lineOffset = lineList[lineIndex].FileOffset; + + // If a line encompassing this offset was at the top of the ListView + // control before, use this line's index as the top. + if (topIndex < 0 && lineList[lineIndex].Contains(mTopOffset)) { + topIndex = lineIndex; + } + + if (lineOffset >= tag.mOffset && lineOffset < tag.mOffset + tag.mSpan) { + // Intersection. If the line type matches, add it to the set. + if ((tag.mTypes & lineList[lineIndex].LineType) != 0) { + sel[lineIndex] = true; + } + + // Advance to the next line entry. + lineIndex++; + } else if (tag.mOffset < lineOffset) { + // advance tag + tagIndex++; + } else { + Debug.Assert(tag.mOffset > lineOffset); + lineIndex++; + } + } + + // Continue search for topIndex, if necessary. + while (topIndex < 0 && lineIndex < lineList.Count) { + if (lineList[lineIndex].Contains(mTopOffset)) { + topIndex = lineIndex; + break; + } + lineIndex++; + } + Debug.WriteLine("TopOffset +" + mTopOffset.ToString("x6") + + " --> index " + topIndex); + if (topIndex < 0) { + // This can happen if you delete the header comment while scrolled + // to the top of the list. + topIndex = 0; + } + return sel; + } + + public void DebugDump() { + Debug.WriteLine("Selection (" + mSelectionTags.Count + " offsets):"); + foreach (Tag tag in mSelectionTags) { + Debug.WriteLine(" +" + tag.mOffset.ToString("x6") + "/" + + tag.mSpan + ": " + tag.mTypes); + } + } + } + + + + /// + /// Constructor. + /// + /// Project object. + /// Formatter object. + public DisplayList(DisasmProject proj, Formatter formatter, + PseudoOp.PseudoOpNames opNames) { + mProject = proj; + mFormatter = formatter; + mPseudoOpNames = opNames; + + mLineList = new List(); + mShowCycleCounts = AppSettings.Global.GetBool(AppSettings.SRCGEN_SHOW_CYCLE_COUNTS, + false); + } + + /// + /// Changes the Formatter object. Clears the display list, instigating a full re-render. + /// + /// Formatter object. + public void SetFormatter(Formatter formatter) { + mFormatter = formatter; + mLineList.Clear(); + + // We probably just changed settings, so update this as well. + mShowCycleCounts = AppSettings.Global.GetBool(AppSettings.SRCGEN_SHOW_CYCLE_COUNTS, + false); + } + + /// + /// Changes the pseudo-op name object. Clears the display list, instigating a + /// full re-render. + /// + /// Pseudo-op names. + public void SetPseudoOpNames(PseudoOp.PseudoOpNames opNames) { + mPseudoOpNames = opNames; + mLineList.Clear(); + } + + /// + /// Number of lines in the list. + /// + public int Count { get { return mLineList.Count; } } + + /// + /// Retrieves the Nth element. + /// + public Line this[int key] { + get { + return mLineList[key]; + } + } + + /// + /// Returns the Line's FormattedParts object, generating it first if necessary. + /// + /// Object with formatted strings. + public FormattedParts GetFormattedParts(int index) { + Line line = mLineList[index]; + if (line.Parts == null) { + FormattedParts parts; + switch (line.LineType) { + case Line.Type.Code: + parts = GenerateInstructionLine(mProject, mFormatter, + line.FileOffset, line.OffsetSpan, mShowCycleCounts); + break; + case Line.Type.Data: + parts = GenerateDataLine(mProject, mFormatter, mPseudoOpNames, + line.FileOffset, line.SubLineIndex); + break; + case Line.Type.Blank: + // Nothing to do. + parts = FormattedParts.CreateBlankLine(); + break; + case Line.Type.OrgDirective: + case Line.Type.RegWidthDirective: + case Line.Type.LongComment: + case Line.Type.Note: + // should have been done already + default: + Debug.Assert(false); + parts = FormattedParts.Create("x", "x", "x", "x", "x", "x", "x", "x", + "x", "x"); + break; + } + line.Parts = parts; + } + return line.Parts; + } + + /// + /// Returns a string with the concatenation of the searchable portions of the line. + /// Different sections are separated with an unlikely unicode character. The goal + /// is to have a single string per line that can be searched quickly, without having + /// adjacent fields spill into each other. + /// + /// Line index. + /// Formatted line contents. + public string GetSearchString(int index) { + Line line = mLineList[index]; + if (line.SearchString == null) { + const char sep = '\u203b'; // REFERENCE MARK + + FormattedParts parts = GetFormattedParts(index); + StringBuilder sb = new StringBuilder(); + // Some parts may be null, e.g. for long comments. Append() can deal. + sb.Append(parts.Label); + sb.Append(sep); + sb.Append(parts.Opcode); + sb.Append(sep); + sb.Append(parts.Operand); + sb.Append(sep); + sb.Append(parts.Comment); + line.SearchString = sb.ToString(); + } + return line.SearchString; + } + + /// + /// Finds the first line entry that encompasses the specified offset. + /// + /// Offset to search for. Negative values are allowed. + /// Line list index, or -1 if not found. + private static int FindLineByOffset(List lineList, int offset) { + if (lineList.Count == 0) { + return -1; + } + + int low = 0; + int high = lineList.Count - 1; + int mid = -1; + bool found = false; + while (low <= high) { + mid = (low + high) / 2; + Line line = lineList[mid]; + + if (line.Contains(offset)) { + // found a match + found = true; + break; + } else if (line.FileOffset > offset) { + // too big, move the high end in + high = mid - 1; + } else if (line.FileOffset < offset) { + // too small, move the low end in + low = mid + 1; + } else { + // WTF + throw new Exception("Bad binary search"); + } + } + + if (!found) { + return -1; + } + + // We found *a* matching line. Seek backward to find the *first* matching line. + while (mid > 0) { + Line upLine = lineList[mid - 1]; + if (upLine.Contains(offset)) { + mid--; + } else { + break; + } + } + + return mid; + } + + /// + /// Finds the first line entry that encompasses the specified offset. + /// + /// Offset to search for. + /// Line list index, or -1 if not found. + public int FindLineIndexByOffset(int offset) { + return FindLineByOffset(mLineList, offset); + } + + /// + /// Finds the code or data line entry that encompasses the specified offset. + /// + /// Offset to search for. + /// Line list index, or -1 if not found. + public int FindCodeDataIndexByOffset(int offset) { + if (offset < 0) { + // Header offset. No code or data here. + return -1; + } + int index = FindLineByOffset(mLineList, offset); + if (index < 0) { + return -1; + } + while (mLineList[index].LineType != Line.Type.Code && + mLineList[index].LineType != Line.Type.Data) { + index++; + } + return index; + } + + /// + /// Generates Lines for the entire project. + /// + public void GenerateAll() { + mLineList.Clear(); + GenerateHeaderLines(mProject, mFormatter, mPseudoOpNames, mLineList); + GenerateLineList(mProject, mFormatter, mPseudoOpNames, + 0, mProject.FileData.Length - 1, mLineList); + + Debug.Assert(ValidateLineList(), "Display list failed validation"); + } + + /// + /// Generates a list of Lines for the specified range of offsets, replacing + /// existing values. + /// + /// First offset. Must be the start of an instruction + /// or data area. + /// End offset (inclusive). + public void GenerateRange(int startOffset, int endOffset) { + if (startOffset < 0) { + ClearHeaderLines(); + GenerateHeaderLines(mProject, mFormatter, mPseudoOpNames, mLineList); + if (endOffset < 0) { + // nothing else to do + return; + } + // do the rest + startOffset = 0; + } + Debug.Assert(startOffset >= 0); + Debug.Assert(endOffset < mProject.FileData.Length); + Debug.Assert(endOffset >= startOffset); + //Debug.WriteLine("DL gen range [" + startOffset + "," + endOffset + "]"); + + // Find the start index. The start offset should always appear at the + // start of a Line because it comes from item selection. + int startIndex = FindLineByOffset(mLineList, startOffset); + if (startIndex < 0) { + Debug.Assert(false, "Unable to find startOffset " + startOffset); + GenerateAll(); + return; + } + // Find the end index. The end offset can be part of a multi-line data item, like + // a long string. Find the first Line that starts at an offset larger than endOffset. + int endIndex; + if (startOffset == endOffset) { + // Simple optimization for single-offset groups. + endIndex = startIndex; + } else { + endIndex = FindLineByOffset(mLineList, endOffset); + } + if (endIndex < 0) { + Debug.Assert(false, "Unable to find endOffset " + endOffset); + GenerateAll(); + return; + } + // There may be more than one line involved, so we need to scan forward. + for (endIndex++; endIndex < mLineList.Count; endIndex++) { + if (mLineList[endIndex].FileOffset > endOffset) { + endIndex--; + break; + } + } + if (endIndex == mLineList.Count) { + // whoops, loop ended before we had a chance to decrement + endIndex = mLineList.Count - 1; + } + Debug.WriteLine("GenerateRange: offset [+" + startOffset.ToString("x6") + ",+" + + endOffset.ToString("x6") + + "] maps to index [" + startIndex + "," + endIndex + "]"); + Debug.Assert(endIndex >= startIndex); + + // Create temporary list to hold new lines. Set the initial capacity to + // the previous size, on the assumption that it won't change much. + List newLines = new List(endIndex - startIndex + 1); + GenerateLineList(mProject, mFormatter, mPseudoOpNames, startOffset, endOffset, newLines); + + // Out with the old, in with the new. + mLineList.RemoveRange(startIndex, endIndex - startIndex + 1); + mLineList.InsertRange(startIndex, newLines); + + Debug.Assert(ValidateLineList(), "Display list failed validation"); + } + + /// + /// Validates the line list, confirming that every offset is represented exactly once. + /// + /// True if all is well. + private bool ValidateLineList() { + int expectedOffset = 0; + int lastOffset = Int32.MinValue; + foreach (Line line in mLineList) { + // Header lines aren't guaranteed to be sequential and don't have a span. + // They are expected to be in sorted order, and to be unique (with the + // notable exception of the header comment, which is multi-line). + if (line.FileOffset < 0) { + if (line.FileOffset < lastOffset || (line.LineType != Line.Type.LongComment && + line.FileOffset == lastOffset)) { + Debug.WriteLine("Header offsets went backward: cur=" + + line.FileOffset + " last=" + lastOffset); + return false; + } + lastOffset = line.FileOffset; + continue; + } + + // Blank lines and comments can appear before or after code/data. They + // must have the offset of the associated line, and a span of zero. + if (line.FileOffset != expectedOffset && line.FileOffset != lastOffset) { + Debug.WriteLine("ValidateLineList: bad offset " + line.FileOffset + + " (last=" + lastOffset + ", expected next=" + expectedOffset + ")"); + return false; + } + + if (line.SubLineIndex != 0) { + // In the middle of a multi-line thing, don't advance last/expected. + Debug.Assert(line.FileOffset == lastOffset); + } else { + lastOffset = expectedOffset; + expectedOffset += line.OffsetSpan; + } + } + + if (expectedOffset != mProject.FileData.Length) { + Debug.WriteLine("ValidateLineList: did not cover entire file: last offset " + + expectedOffset + ", file has " + mProject.FileData.Length); + return false; + } + + return true; + } + + /// + /// Removes all header lines from the display list. + /// + private void ClearHeaderLines() { + // Find the first non-header item. + int endIndex = FindLineByOffset(mLineList, 0); + if (endIndex == 0) { + // no header lines present + Debug.WriteLine("No header lines found"); + return; + } + Debug.WriteLine("Removing " + endIndex + " header lines"); + mLineList.RemoveRange(0, endIndex); + } + + /// + /// Generates a synthetic offset for the FileOffset field from an index value. The + /// index arg is the index of an entry in the DisasmProject.ActiveDefSymbolList. + /// (The exact algorithm isn't too important, as these offsets are not stored in the + /// project file.) + /// + private static int DefSymOffsetFromIndex(int index) { + Debug.Assert(index >= 0 && index < (1 << 24)); + return index - (1 << 24); + } + + /// + /// Returns the DisasmProject.ActiveDefSymbolList index for an EQU line with + /// the specified file offset. + /// + public static int DefSymIndexFromOffset(int offset) { + Debug.Assert(offset < 0); + return offset + (1 << 24); + } + + /// + /// Generates the header lines (header comment, EQU directives), and inserts them at + /// the top of the list. + /// + /// This does not currently do incremental generation. Call ClearHeaderLines() before + /// calling here if you're not starting with an empty list. + /// + /// Project reference. + /// Output formatter. + /// Pseudo-op names. + /// List to add output lines to. + private static void GenerateHeaderLines(DisasmProject proj, Formatter formatter, + PseudoOp.PseudoOpNames opNames, List fullLines) { + List tmpLines = new List(); + Line line; + FormattedParts parts; + + // Check for header comment. + if (proj.LongComments.TryGetValue(Line.HEADER_COMMENT_OFFSET, + out MultiLineComment headerComment)) { + List formatted = headerComment.FormatText(formatter, string.Empty); + StringListToLines(formatted, Line.HEADER_COMMENT_OFFSET, Line.Type.LongComment, + Color.FromArgb(0), tmpLines); + } + + // Format symbols. + int index = 0; + foreach (DefSymbol defSym in proj.ActiveDefSymbolList) { + line = new Line(DefSymOffsetFromIndex(index), 0, Line.Type.EquDirective); + // Use an operand length of 1 so things are shown as concisely as possible. + string valueStr = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, + null, defSym.DataDescriptor, defSym.Value, 1, false); + string comment = formatter.FormatEolComment(defSym.Comment); + parts = FormattedParts.CreateEquDirective(defSym.Label, + formatter.FormatPseudoOp(opNames.EquDirective), + valueStr, comment); + line.Parts = parts; + tmpLines.Add(line); + index++; + } + + if (proj.ActiveDefSymbolList.Count != 0) { + // We had some EQUs, throw a blank line at the end. + index++; + line = new Line(DefSymOffsetFromIndex(index), 0, Line.Type.Blank); + tmpLines.Add(line); + } + + fullLines.InsertRange(0, tmpLines); + } + + /// + /// Generates lines for the specified range of file offsets. + /// + /// Does not generate formatted parts in most cases; that usually happens on demand. + /// Complicated items, such as word-wrapped long comments, may be generated now + /// and saved off. + /// + /// This still needs a formatter arg even when no text is rendered because some + /// options, like maximum per-line operand length, might affect how many lines + /// are generated. + /// + /// Project reference. + /// Output formatter. + /// Offset of first byte. + /// Offset of last byte. + /// List to add output lines to. + private static void GenerateLineList(DisasmProject proj, Formatter formatter, + PseudoOp.PseudoOpNames opNames, int startOffset, int endOffset, List lines) { + //Debug.WriteLine("GenerateRange [+" + startOffset.ToString("x6") + ",+" + + // endOffset.ToString("x6") + "]"); + + Debug.Assert(startOffset >= 0); + Debug.Assert(endOffset >= startOffset); + + // Find the previous status flags for M/X tracking. + StatusFlags prevFlags = StatusFlags.AllIndeterminate; + if (proj.CpuDef.HasEmuFlag) { + for (int scanoff = startOffset - 1; scanoff >= 0; scanoff--) { + Anattrib attr = proj.GetAnattrib(scanoff); + if (attr.IsInstructionStart) { + prevFlags = attr.StatusFlags; + // Apply the same tweak here that we do to curFlags below. + prevFlags.M = attr.StatusFlags.ShortM ? 1 : 0; + prevFlags.X = attr.StatusFlags.ShortX ? 1 : 0; + Debug.WriteLine("GenerateLineList startOff=+" + + startOffset.ToString("x6") + " using initial flags from +" + + scanoff.ToString("x6") + ": " + prevFlags); + break; + } + } + } + + int offset = startOffset; + while (offset <= endOffset) { + Anattrib attr = proj.GetAnattrib(offset); + if (attr.IsInstructionStart && offset > 0 && + proj.GetAnattrib(offset - 1).IsData) { + // Transition from data to code. (Don't add blank line for inline data.) + lines.Add(GenerateBlankLine(offset)); + } + + // Insert long comments and notes. These may span multiple display lines, + // and require word-wrap, so it's easiest just to render them fully here. + if (proj.Notes.TryGetValue(offset, out MultiLineComment noteData)) { + List formatted = noteData.FormatText(formatter, "NOTE: "); + StringListToLines(formatted, offset, Line.Type.Note, + noteData.BackgroundColor, lines); + } + if (proj.LongComments.TryGetValue(offset, out MultiLineComment longComment)) { + List formatted = longComment.FormatText(formatter, string.Empty); + StringListToLines(formatted, offset, Line.Type.LongComment, + longComment.BackgroundColor, lines); + } + + if (attr.IsInstructionStart) { + // Generate reg width directive, if necessary. + if (proj.CpuDef.HasEmuFlag) { + // Changing from "ambiguous but assumed short" to "definitively short" + // merits a directive, notably at the start of the file. The tricky + // part is that E=1 means definitively M=1 X=1. And maybe + // indeterminate E also means that. + // + // We don't want to mess with Anattrib, but we do need to tell the + // assembler something. So we tweak our local copy and propagate it. + string operandStr = string.Empty; + StatusFlags curFlags = attr.StatusFlags; + curFlags.M = attr.StatusFlags.ShortM ? 1 : 0; + curFlags.X = attr.StatusFlags.ShortX ? 1 : 0; + if (curFlags.M != prevFlags.M) { + operandStr = (curFlags.M == 0) ? "longm" : "shortm"; + } + + if (curFlags.X != prevFlags.X) { + if (operandStr.Length > 0) { + operandStr += ","; + } + operandStr += (curFlags.X == 0) ? "longx" : "shortx"; + } + + if (operandStr.Length > 0) { + Line rwLine = new Line(offset, 0, Line.Type.RegWidthDirective); + // FormatPseudoOp isn't quite right for the operand, but there + // isn't anything more suitable, and there are only eight + // possible values. Having the operand capitalization match the + // pseudo-op's feels reasonable. + rwLine.Parts = FormattedParts.CreateDirective( + formatter.FormatPseudoOp(opNames.RegWidthDirective), + formatter.FormatPseudoOp(operandStr)); + lines.Add(rwLine); + } + prevFlags = curFlags; + } + + // Look for embedded instructions. + int len; + for (len = 1; len < attr.Length; len++) { + if (proj.GetAnattrib(offset + len).IsInstructionStart) { + break; + } + } + + // Create Line entry. Offset span only covers the instruction up to + // the point where the embedded instruction starts. + Line line = new Line(offset, len, Line.Type.Code); + lines.Add(line); + + // Insert blank after an instruction that doesn't continue. Provides a + // break in code, and before a data area. + // TODO(maybe): Might also want to do this if the next offset is data, + // to make things look nicer when code runs directly into data. + if (attr.DoesNotContinue) { + lines.Add(GenerateBlankLine(offset)); + } + + offset += len; + } else { + Debug.Assert(attr.DataDescriptor != null); + int numLines = + PseudoOp.ComputeRequiredLineCount(formatter, attr.DataDescriptor); + for (int i = 0; i < numLines; i++) { + Line line = new Line(offset, attr.Length, Line.Type.Data, i); + lines.Add(line); + } + offset += attr.Length; + } + } + + // See if there were any address shifts in this section. If so, add an ORG + // statement as the first entry for the offset. We're expecting to have very + // few AddressMap entries (usually just one), so it's more efficient to process + // them here and walk through the sub-list than it is to ping the address map + // at every line. + // + // It should not be possible for an address map change to appear in the middle + // of an instruction or data item. + foreach (AddressMap.AddressMapEntry ent in proj.AddrMap) { + if (ent.Offset < startOffset || ent.Offset > endOffset) { + continue; + } + int index = FindLineByOffset(lines, ent.Offset); + if (index < 0) { + Debug.WriteLine("Couldn't find offset " + ent.Offset + + " in range we just generated"); + Debug.Assert(false); + continue; + } + if (lines[index].LineType == Line.Type.Blank) { + index++; + } + Line topLine = lines[index]; + Line newLine = new Line(topLine.FileOffset, 0, Line.Type.OrgDirective); + string addrStr = formatter.FormatHexValue(ent.Addr, 4); + newLine.Parts = FormattedParts.CreateDirective( + formatter.FormatPseudoOp(opNames.OrgDirective), addrStr); + lines.Insert(index, newLine); + + // Prepend a blank line if the previous line wasn't already blank, and this + // isn't the ORG at the start of the file. (This may temporarily do + // double-spacing if we do a partial update, because we won't be able to + // "see" the previous line. Harmless.) + if (ent.Offset != 0 && index > 0 && lines[index-1].LineType != Line.Type.Blank) { + Line blankLine = new Line(topLine.FileOffset, 0, Line.Type.Blank); + lines.Insert(index, blankLine); + } + } + } + + /// + /// Generates a blank line entry. + /// + private static Line GenerateBlankLine(int offset) { + return new Line(offset, 0, Line.Type.Blank); + } + + /// + /// Takes a list of strings and adds them to the Line list as long comments. + /// + /// + /// + /// + /// + /// + private static void StringListToLines(List list, int offset, Line.Type lineType, + Color color, List lines) { + foreach (string str in list) { + Line line = new Line(offset, 0, lineType); + FormattedParts parts = FormattedParts.CreateLongComment(str); + line.Parts = parts; + line.BackgroundColor = color; + lines.Add(line); + } + } + + private static FormattedParts GenerateInstructionLine(DisasmProject proj, + Formatter formatter, int offset, int instrBytes, bool showCycleCounts) { + Anattrib attr = proj.GetAnattrib(offset); + byte[] data = proj.FileData; + + string offsetStr = formatter.FormatOffset24(offset); + + int addr = attr.Address; + string addrStr = formatter.FormatAddress(addr, !proj.CpuDef.HasAddr16); + string bytesStr = formatter.FormatBytes(data, offset, instrBytes); + string flagsStr = attr.StatusFlags.ToString(proj.CpuDef.HasEmuFlag); + string attrStr = attr.ToAttrString(); + + string labelStr = string.Empty; + if (attr.Symbol != null) { + labelStr = attr.Symbol.Label; + } + + OpDef op = proj.CpuDef.GetOpDef(data[offset]); + int operand = op.GetOperand(data, offset, attr.StatusFlags); + int instrLen = op.GetLength(attr.StatusFlags); + OpDef.WidthDisambiguation wdis = OpDef.WidthDisambiguation.None; + if (op.IsWidthPotentiallyAmbiguous) { + wdis = OpDef.GetWidthDisambiguation(instrLen, operand); + } + + string opcodeStr = formatter.FormatOpcode(op, wdis); + if (attr.Length != instrBytes) { + // An instruction is embedded inside this one. Note that BRK is a two-byte + // instruction, so don't freak out if you see it marked as embedded when a + // $00 is followed by actual code. (But be a little freaked out that your + // code is running into a BRK.) + //opcodeStr = opcodeStr + " \u00bb"; // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + opcodeStr = opcodeStr + " \u23e9"; // BLACK RIGHT-POINTING DOUBLE TRIANGLE + } + + string formattedOperand = null; + int operandLen = instrLen - 1; + bool isPcRel = false; + + // Tweak branch instructions. We want to show the absolute address rather + // than the relative offset (which happens with the OperandAddress assignment + // below), and 1-byte branches should always appear as a 4-byte hex value. + if (op.AddrMode == OpDef.AddressMode.PCRel) { + Debug.Assert(attr.OperandAddress >= 0); + operandLen = 2; + isPcRel = true; + } else if (op.AddrMode == OpDef.AddressMode.PCRelLong || + op.AddrMode == OpDef.AddressMode.StackPCRelLong) { + isPcRel = true; + } + + // Use the OperandAddress when available. This is important for relative branch + // instructions and PER, where we want to show the target address rather than the + // operand value. + int operandForSymbol = operand; + if (attr.OperandAddress >= 0) { + operandForSymbol = attr.OperandAddress; + } + + // Check Length to watch for bogus descriptors (?) + if (attr.DataDescriptor != null && attr.Length == attr.DataDescriptor.Length) { + // Format operand as directed. + if (op.AddrMode == OpDef.AddressMode.BlockMove) { + // Special handling for the double-operand block move. + string opstr1 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, + null, attr.DataDescriptor, operand >> 8, 1, false); + string opstr2 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, + null, attr.DataDescriptor, operand & 0xff, 1, false); + formattedOperand = opstr1 + "," + opstr2; + } else { + formattedOperand = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, + null, attr.DataDescriptor, operandForSymbol, operandLen, isPcRel); + } + } else { + // Show operand value in hex. + if (op.AddrMode == OpDef.AddressMode.BlockMove) { + formattedOperand = formatter.FormatHexValue(operand >> 8, 2) + "," + + formatter.FormatHexValue(operand & 0xff, 2); + } else { + if (operandLen == 2) { + // This is necessary for 16-bit operands, like "LDA abs" and "PEA val", + // when outside bank zero. The bank is included in the operand address, + // but we don't want to show it here. + operandForSymbol &= 0xffff; + } + formattedOperand = formatter.FormatHexValue(operandForSymbol, operandLen * 2); + } + } + string operandStr = formatter.FormatOperand(op, formattedOperand, wdis); + + string eolComment = proj.Comments[offset]; + if (showCycleCounts) { + bool branchCross = (attr.Address & 0xff00) != (operandForSymbol & 0xff00); + int cycles = proj.CpuDef.GetCycles(op.Opcode, attr.StatusFlags, attr.BranchTaken, + branchCross); + if (cycles > 0) { + eolComment = cycles.ToString() + " " + eolComment; + } else { + eolComment = (-cycles).ToString() + "+ " + eolComment; + } + } + string commentStr = formatter.FormatEolComment(eolComment); + + string debugStr = string.Empty; + //debugStr = "opOff=" + + // (attr.OperandOffset < 0 ? "-" : "+" + attr.OperandOffset.ToString("x6")); + + FormattedParts parts = FormattedParts.Create(offsetStr, addrStr, bytesStr, + flagsStr, attrStr, labelStr, opcodeStr, operandStr, commentStr, debugStr); + return parts; + } + + private static FormattedParts GenerateDataLine(DisasmProject proj, Formatter formatter, + PseudoOp.PseudoOpNames opNames, int offset, int subLineIndex) { + Anattrib attr = proj.GetAnattrib(offset); + byte[] data = proj.FileData; + + string offsetStr, addrStr, bytesStr, flagsStr, attrStr, labelStr, opcodeStr, + operandStr, commentStr, debugStr; + offsetStr = addrStr = bytesStr = flagsStr = attrStr = labelStr = opcodeStr = + operandStr = commentStr = debugStr = string.Empty; + + PseudoOp.PseudoOut pout = PseudoOp.FormatDataOp(formatter, opNames, proj.SymbolTable, + null, attr.DataDescriptor, proj.FileData, offset, subLineIndex); + if (subLineIndex == 0) { + offsetStr = formatter.FormatOffset24(offset); + + addrStr = formatter.FormatAddress(attr.Address, !proj.CpuDef.HasAddr16); + if (attr.Symbol != null) { + labelStr = attr.Symbol.Label; + } + + bytesStr = formatter.FormatBytes(data, offset, attr.Length); + attrStr = attr.ToAttrString(); + + opcodeStr = formatter.FormatPseudoOp(pout.Opcode); + } else { + opcodeStr = " +"; + } + + operandStr = pout.Operand; + + if (subLineIndex == 0) { + commentStr = formatter.FormatEolComment(proj.Comments[offset]); + + //debugStr = "opOff=" + + // (attr.OperandOffset < 0 ? "-" : "+" + attr.OperandOffset.ToString("x6")); + } + + FormattedParts parts = FormattedParts.Create(offsetStr, addrStr, bytesStr, + flagsStr, attrStr, labelStr, opcodeStr, operandStr, commentStr, debugStr); + return parts; + } + } +} diff --git a/SourceGen/Examples/A2-Amper-fdraw/AMPERFDRAW#061d60 b/SourceGen/Examples/A2-Amper-fdraw/AMPERFDRAW#061d60 new file mode 100644 index 0000000..a513b2c Binary files /dev/null and b/SourceGen/Examples/A2-Amper-fdraw/AMPERFDRAW#061d60 differ diff --git a/SourceGen/Examples/A2-Amper-fdraw/AMPERFDRAW#061d60.dis65 b/SourceGen/Examples/A2-Amper-fdraw/AMPERFDRAW#061d60.dis65 new file mode 100644 index 0000000..957678e --- /dev/null +++ b/SourceGen/Examples/A2-Amper-fdraw/AMPERFDRAW#061d60.dis65 @@ -0,0 +1,420 @@ +### 6502bench SourceGen dis65 v1.0 ### +{ +"_ContentVersion":1,"FileDataLength":661,"FileDataCrc32":2075163852,"ProjectProps":{ +"CpuName":"6502","IncludeUndocumentedInstr":false,"EntryFlags":32702671,"AnalysisParams":{ +"AnalyzeUncategorizedData":true,"MinCharsForString":4,"SeekNearbyTargets":true}, +"PlatformSymbolFileIdentifiers":["RT:Apple/F8-ROM.sym65","RT:Apple/Cxxx-IO.sym65","RT:Apple/Applesoft.sym65","PROJ:fdraw-exports.sym65"],"ExtensionScriptFileIdentifiers":[],"ProjectSyms":{ +}}, +"AddressMap":[{ +"Offset":0,"Addr":7520}],"TypeHints":[{ +"Low":0,"High":0,"Hint":"Code"}, +{ +"Low":16,"High":16,"Hint":"Code"}, +{ +"Low":85,"High":85,"Hint":"Code"}, +{ +"Low":112,"High":112,"Hint":"Code"}, +{ +"Low":118,"High":118,"Hint":"Code"}, +{ +"Low":177,"High":177,"Hint":"Code"}, +{ +"Low":209,"High":209,"Hint":"Code"}, +{ +"Low":228,"High":228,"Hint":"Code"}, +{ +"Low":252,"High":252,"Hint":"Code"}, +{ +"Low":255,"High":255,"Hint":"Code"}, +{ +"Low":261,"High":261,"Hint":"Code"}, +{ +"Low":267,"High":267,"Hint":"Code"}, +{ +"Low":283,"High":283,"Hint":"Code"}, +{ +"Low":289,"High":289,"Hint":"Code"}, +{ +"Low":295,"High":295,"Hint":"Code"}, +{ +"Low":301,"High":301,"Hint":"Code"}, +{ +"Low":304,"High":304,"Hint":"Code"}],"StatusFlagOverrides":{ +}, +"Comments":{ +"0":"JMP, in case it got","2":" trashed","37":"eat token, jump","85":"match Init result","95":"init \"previous hplot\"","96":" coord to zero","100":"279/2","104":"191/2","112":"page 1","114":"$c054","118":"page 2","120":"$c055","128":"probably useful","145":"restore color","191":"eat \u0027)\u0027 (we assume)","194":"X/Y unaltered","199":"multiply x32","209":"get color","238":"about to start drawing on 2?","240":"yes, show page 1","242":"no, show page 2","252":"well, that was easy","304":"check next token","307":"is this an \"HPLOT TO\"?","311":"get the first coord","317":"see if single point","322":"nope, draw line","324":"draw point, and save x/y","327":" for subsequent HPLOT TO","330":"\"HPLOT TO\", restore the","333":" previous coord to x0/y0","336":"(can\u0027t rely on f_in_zzz","339":" being there -- we might","342":" have drawn a rect)","348":"eat the TO","351":"get the coords","354":"draw it","357":"shift 1-\u003e0 for next round","363":"another TO?","365":"yes, branch","367":"no, save prev and bail","373":"store X/Y/A in coord1","463":"get vertex buffer address","469":"copy to A1L","477":"eat the comma","480":"get index buffer address","483":"leave it in LINNUM","489":"get the count","492":"range check (0-127)","500":"nothing to do","502":"double it","503":"stash it","516":"eat the AT","519":"the code that reads the","521":" hi-res coordinates will","522":" overwrite LINNUM, so","524":" we have to save \u0026 restore","553":"use BIT to skip the inc","569":"must be 0-127","580":"0-255, ok","584":"512+","588":"280-511","593":"Y is neg or \u003e 255","606":"hi byte of vertex","608":"x-coord","612":"sign-extend hi byte","630":"y-coord","634":"sign-extend hi byte","649":"$20 or $40","654":"Center-point coordinates","655":" for array-based line","656":" draw (\u0026AT, \u0026PLOT).","657":"16-bit coordinates for","659":" array-based line draw"}, +"LongComments":{ +"-2147483647":{ +"Text":"\r\nAmper-fdraw\r\nBy Andy McFadden\r\nFor fdraw version 0.3\r\n\r\nApplesoft ampersand interface for fdraw.\r\n\r\n","BoxMode":true,"MaxWidth":30,"BackgroundColor":0}, +"0":{ +"Text":"Prepare the ampersand vector.\r\n\r\nIdeally we\u0027d check to see if the existing vector is different from ours, and if so, jump to it when we get a token we don\u0027t recognize. Not convinced there\u0027s an actual use case for this.","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"16":{ +"Text":"\r\nEntry point from BASIC. The token is in A.\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"85":{ +"Text":"\r\n\u0026NEW - initialize\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"112":{ +"Text":"\r\n\u0026HGR - show page 1 with mixed text, and clear screen. Sets the color to zero.\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"118":{ +"Text":"\r\n\u0026HGR2 - show page 2 with no text, and clear screen. Sets the color to zero.\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"122":{ +"Text":"We go slightly out of our way to clear the screen before tripping the softswitches. This avoids flashing the previous hi-res page contents when\r\nentering from text mode.\r\n\r\nWe also want to go nomix-page2 but page1-mix (note reverse order) to avoid flashing text pg 2.","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"177":{ +"Text":"\r\n\u0026SCRN({1,2}) - set the current hi-res page\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"209":{ +"Text":"\r\n\u0026HCOLOR={0-7} - set the current color\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"228":{ +"Text":"\r\n\u0026INVERSE - flip pages\r\n\r\nIf we\u0027re currently drawing on $20, we set the page to $40 and hit $c054 to show $20. And vice-versa. The goal is to make double-buffered animation easy.","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"252":{ +"Text":"\r\n\u0026CLEAR - clear current page to current color\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"255":{ +"Text":"\r\n\u0026XDRAW left, top, right, bottom - draw rectangle outline\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"261":{ +"Text":"\r\n\u0026DRAW left,top,right,bottom - draw filled rectangle\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"267":{ +"Text":"\r\n\u0026EXP {0,1} - set line draw mode\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"283":{ +"Text":"\r\n\u0026COS cx,cy,rad - draw filled circle\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"289":{ +"Text":"\r\n\u0026SIN cx,cy,rad - draw filled circle\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"295":{ +"Text":"\r\n\u0026AT x,y - select center for array draw\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"301":{ +"Text":"\r\n\u0026PLOT vertexAddr, indexAddr, indexCount [AT cx,cy] - draw lines from arrays of vertices and indices\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"304":{ +"Text":"\r\n\u0026HPLOT x,y - draw a point\r\n\u0026HPLOT TO x,y - draw a line from last point to x,y\r\n\u0026HPLOT x0,y0 to x1,y1 - draw a line\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"370":{ +"Text":"Get coordinates and store in X1/Y1.","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"383":{ +"Text":"Save x0/y0 as our \"previous\" coordinate.","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"402":{ +"Text":"Copy X1/Y1 to X0/Y0.","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"421":{ +"Text":"Store X/Y/A into array-center.","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"431":{ +"Text":"Get left/top/right/bottom coordinates.","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"447":{ +"Text":"Get cetner coordinates and radius.","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"463":{ +"Text":"\r\nArray-draw handler.\r\n\r\nWe know that fdraw doesn\u0027t use LINNUM or A1L/A1H, so it\u0027s safe to use them here.\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"509":{ +"Text":"Check for optional AT at cx,cy.\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"563":{ +"Text":"\r\nGet the Nth vertex, specified by ]cur, and load it into X/Y/A (xlo/xhi/y). Returns with carry set if the vertex is invalid.\r\n\r\nIncrements ]cur by 1.\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"604":{ +"Text":"\r\nGet VX and VY, merging with AC, and store in 16-bit g_out_x and g_out_y. Range not checked here. On entry, A has vertex index.\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"649":{ +"Text":"\r\nGlobal variables.\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}}, +"Notes":{ +}, +"UserLabels":{ +"16":{ +"Label":"dispatch","Value":7536,"Source":"User","Type":"LocalOrGlobalAddr"}, +"40":{ +"Label":"cmdtab","Value":7560,"Source":"User","Type":"LocalOrGlobalAddr"}, +"29":{ +"Label":"match","Value":7549,"Source":"User","Type":"LocalOrGlobalAddr"}, +"18":{ +"Label":"loop","Value":7538,"Source":"User","Type":"LocalOrGlobalAddr"}, +"70":{ +"Label":"jmptabh","Value":7590,"Source":"User","Type":"LocalOrGlobalAddr"}, +"55":{ +"Label":"jmptabl","Value":7575,"Source":"User","Type":"LocalOrGlobalAddr"}, +"85":{ +"Label":"h_new","Value":7605,"Source":"User","Type":"GlobalAddr"}, +"112":{ +"Label":"h_hgr","Value":7632,"Source":"User","Type":"GlobalAddr"}, +"118":{ +"Label":"h_hgr2","Value":7638,"Source":"User","Type":"GlobalAddr"}, +"122":{ +"Label":"hgr_com","Value":7642,"Source":"User","Type":"LocalOrGlobalAddr"}, +"170":{ +"Label":"pg1","Value":7690,"Source":"User","Type":"LocalOrGlobalAddr"}, +"177":{ +"Label":"h_scrn","Value":7697,"Source":"User","Type":"GlobalAddr"}, +"191":{ +"Label":"okay","Value":7711,"Source":"User","Type":"LocalOrGlobalAddr"}, +"209":{ +"Label":"h_hcolor","Value":7729,"Source":"User","Type":"GlobalAddr"}, +"228":{ +"Label":"h_inverse","Value":7748,"Source":"User","Type":"GlobalAddr"}, +"219":{ +"Label":"okay1","Value":7739,"Source":"User","Type":"LocalOrGlobalAddr"}, +"649":{ +"Label":"g_cur_page","Value":8169,"Source":"User","Type":"LocalOrGlobalAddr"}, +"243":{ +"Label":"showpg1","Value":7763,"Source":"User","Type":"LocalOrGlobalAddr"}, +"252":{ +"Label":"h_clear","Value":7772,"Source":"User","Type":"GlobalAddr"}, +"255":{ +"Label":"h_xdraw","Value":7775,"Source":"User","Type":"GlobalAddr"}, +"431":{ +"Label":"getltrb","Value":7951,"Source":"User","Type":"LocalOrGlobalAddr"}, +"261":{ +"Label":"h_draw","Value":7781,"Source":"User","Type":"GlobalAddr"}, +"267":{ +"Label":"h_exp","Value":7787,"Source":"User","Type":"GlobalAddr"}, +"277":{ +"Label":"okay2","Value":7797,"Source":"User","Type":"LocalOrGlobalAddr"}, +"283":{ +"Label":"h_cos","Value":7803,"Source":"User","Type":"GlobalAddr"}, +"447":{ +"Label":"getcxcyr","Value":7967,"Source":"User","Type":"LocalOrGlobalAddr"}, +"289":{ +"Label":"h_sin","Value":7809,"Source":"User","Type":"GlobalAddr"}, +"295":{ +"Label":"h_at","Value":7815,"Source":"User","Type":"GlobalAddr"}, +"421":{ +"Label":"storeac","Value":7941,"Source":"User","Type":"LocalOrGlobalAddr"}, +"301":{ +"Label":"h_plot","Value":7821,"Source":"User","Type":"GlobalAddr"}, +"463":{ +"Label":"array_draw","Value":7983,"Source":"User","Type":"LocalOrGlobalAddr"}, +"304":{ +"Label":"h_hplot","Value":7824,"Source":"User","Type":"GlobalAddr"}, +"330":{ +"Label":"leadingto","Value":7850,"Source":"User","Type":"LocalOrGlobalAddr"}, +"370":{ +"Label":"getx1y1","Value":7890,"Source":"User","Type":"LocalOrGlobalAddr"}, +"402":{ +"Label":"copy1to0","Value":7922,"Source":"User","Type":"LocalOrGlobalAddr"}, +"348":{ +"Label":"hplot_to","Value":7868,"Source":"User","Type":"LocalOrGlobalAddr"}, +"383":{ +"Label":"copy0toprev","Value":7903,"Source":"User","Type":"LocalOrGlobalAddr"}, +"651":{ +"Label":"g_prefxl","Value":8171,"Source":"User","Type":"LocalOrGlobalAddr"}, +"652":{ +"Label":"g_prefixh","Value":8172,"Source":"User","Type":"LocalOrGlobalAddr"}, +"653":{ +"Label":"g_prevy","Value":8173,"Source":"User","Type":"LocalOrGlobalAddr"}, +"654":{ +"Label":"g_ac_xl","Value":8174,"Source":"User","Type":"LocalOrGlobalAddr"}, +"655":{ +"Label":"g_ac_xh","Value":8175,"Source":"User","Type":"LocalOrGlobalAddr"}, +"656":{ +"Label":"g_ac_y","Value":8176,"Source":"User","Type":"LocalOrGlobalAddr"}, +"411":{ +"Label":"store0","Value":7931,"Source":"User","Type":"LocalOrGlobalAddr"}, +"373":{ +"Label":"store1","Value":7893,"Source":"User","Type":"LocalOrGlobalAddr"}, +"562":{ +"Label":"done","Value":8082,"Source":"User","Type":"LocalOrGlobalAddr"}, +"499":{ +"Label":"countok","Value":8019,"Source":"User","Type":"LocalOrGlobalAddr"}, +"534":{ +"Label":"noat","Value":8054,"Source":"User","Type":"LocalOrGlobalAddr"}, +"563":{ +"Label":"getvertex","Value":8083,"Source":"User","Type":"LocalOrGlobalAddr"}, +"554":{ +"Label":"skip2","Value":8074,"Source":"User","Type":"LocalOrGlobalAddr"}, +"556":{ +"Label":"skip","Value":8076,"Source":"User","Type":"LocalOrGlobalAddr"}, +"657":{ +"Label":"g_out_x","Value":8177,"Source":"User","Type":"LocalOrGlobalAddr"}, +"590":{ +"Label":"xok","Value":8110,"Source":"User","Type":"LocalOrGlobalAddr"}, +"602":{ +"Label":"badv","Value":8122,"Source":"User","Type":"LocalOrGlobalAddr"}, +"659":{ +"Label":"g_out_y","Value":8179,"Source":"User","Type":"LocalOrGlobalAddr"}, +"603":{ +"Label":"goodv","Value":8123,"Source":"User","Type":"LocalOrGlobalAddr"}, +"604":{ +"Label":"calcvertex","Value":8124,"Source":"User","Type":"LocalOrGlobalAddr"}, +"635":{ +"Label":"ypos","Value":8155,"Source":"User","Type":"LocalOrGlobalAddr"}, +"645":{ +"Label":"nocarry","Value":8165,"Source":"User","Type":"LocalOrGlobalAddr"}, +"650":{ +"Label":"g_hcolor","Value":8170,"Source":"User","Type":"LocalOrGlobalAddr"}, +"392":{ +"Label":"storeprv","Value":7912,"Source":"User","Type":"LocalOrGlobalAddr"}, +"613":{ +"Label":"xok1","Value":8133,"Source":"User","Type":"LocalOrGlobalAddr"}}, +"OperandFormats":{ +"5":{ +"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"dispatch","Part":"Low"}}, +"7":{ +"Length":3,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"AMPERV","Part":"Low"}}, +"10":{ +"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"dispatch","Part":"High"}}, +"12":{ +"Length":3,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"AMPERV","Part":"Low"}}, +"40":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"TOK_NEW","Part":"Low"}}, +"41":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"TOK_HGR","Part":"Low"}}, +"42":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"TOK_HGR2","Part":"Low"}}, +"43":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"TOK_SCRN","Part":"Low"}}, +"44":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"TOK_HCOLOR","Part":"Low"}}, +"45":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"TOK_INVERSE","Part":"Low"}}, +"46":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"TOK_CLEAR","Part":"Low"}}, +"47":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"TOK_HPLOT","Part":"Low"}}, +"48":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"TOK_XDRAW","Part":"Low"}}, +"49":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"TOK_DRAW","Part":"Low"}}, +"50":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"TOK_EXP","Part":"Low"}}, +"51":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"TOK_COS","Part":"Low"}}, +"52":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"TOK_SIN","Part":"Low"}}, +"53":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"TOK_AT","Part":"Low"}}, +"54":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"TOK_PLOT","Part":"Low"}}, +"55":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_new","Part":"Low"}}, +"56":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_hgr","Part":"Low"}}, +"57":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_hgr2","Part":"Low"}}, +"58":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_scrn","Part":"Low"}}, +"59":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_hcolor","Part":"Low"}}, +"60":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_inverse","Part":"Low"}}, +"61":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_clear","Part":"Low"}}, +"62":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_hplot","Part":"Low"}}, +"63":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_xdraw","Part":"Low"}}, +"64":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_draw","Part":"Low"}}, +"65":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_exp","Part":"Low"}}, +"66":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_cos","Part":"Low"}}, +"67":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_sin","Part":"Low"}}, +"68":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_at","Part":"Low"}}, +"69":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_plot","Part":"Low"}}, +"70":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_new","Part":"High"}}, +"71":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_hgr","Part":"High"}}, +"72":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_hgr2","Part":"High"}}, +"73":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_scrn","Part":"High"}}, +"74":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_hcolor","Part":"High"}}, +"75":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_inverse","Part":"High"}}, +"76":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_clear","Part":"High"}}, +"77":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_hplot","Part":"High"}}, +"78":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_xdraw","Part":"High"}}, +"79":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_draw","Part":"High"}}, +"80":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_exp","Part":"High"}}, +"81":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_cos","Part":"High"}}, +"82":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_sin","Part":"High"}}, +"83":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_at","Part":"High"}}, +"84":{ +"Length":1,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"h_plot","Part":"High"}}, +"100":{ +"Length":2,"Format":"NumericLE","SubFormat":"Decimal","SymbolRef":null}, +"104":{ +"Length":2,"Format":"NumericLE","SubFormat":"Decimal","SymbolRef":null}, +"212":{ +"Length":2,"Format":"NumericLE","SubFormat":"Decimal","SymbolRef":null}, +"307":{ +"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"TOK_TO","Part":"Low"}}, +"320":{ +"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"TOK_TO","Part":"Low"}}, +"363":{ +"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"TOK_TO","Part":"Low"}}, +"473":{ +"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"LINNUM","Part":"Low"}}, +"492":{ +"Length":2,"Format":"NumericLE","SubFormat":"Decimal","SymbolRef":null}, +"512":{ +"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"TOK_AT","Part":"Low"}}, +"522":{ +"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"LINNUM","Part":"Low"}}, +"529":{ +"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"LINNUM","Part":"Low"}}, +"582":{ +"Length":2,"Format":"NumericLE","SubFormat":"Decimal","SymbolRef":null}, +"586":{ +"Length":2,"Format":"NumericLE","SubFormat":"Decimal","SymbolRef":null}, +"598":{ +"Length":2,"Format":"NumericLE","SubFormat":"Decimal","SymbolRef":null}, +"657":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"659":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}}} diff --git a/SourceGen/Examples/A2-Amper-fdraw/AMPERFDRAW.S.txt b/SourceGen/Examples/A2-Amper-fdraw/AMPERFDRAW.S.txt new file mode 100644 index 0000000..c0b4bc3 --- /dev/null +++ b/SourceGen/Examples/A2-Amper-fdraw/AMPERFDRAW.S.txt @@ -0,0 +1,549 @@ +******************************** +* * +* Amper-fdraw * +* By Andy McFadden * +* For fdraw version 0.3 * +* * +* Applesoft ampersand * +* interface for fdraw. * +* * +* Developed with Merlin-16 * +* * +******************************** + + lst off + org $1d60 + +* All of the handler entry points can fit on a single +* page, so it's possible to save a few bytes by +* dropping the high jump table and just hardcoding +* the first page into the jump. This requires that +* the ORG be at $xx00. + + PUT FDRAW.DEFS + +* Applesoft BASIC tokens. +tok_plot equ $8d +tok_hgr2 equ $90 +tok_hgr equ $91 +tok_hcolor equ $92 +tok_hplot equ $93 +tok_draw equ $94 +tok_xdraw equ $95 +tok_inverse equ $9e +tok_clear equ $bd +tok_new equ $bf +tok_to equ $c1 +tok_at equ $c5 +*tok_sgn equ $d2 +tok_scrn equ $d7 +tok_exp equ $dd +tok_cos equ $de +tok_sin equ $df + +* System locations. +PCL equ $3a ;used by monitor +PCH equ $3b ;used by monitor +A1L equ $3c ;used by monitor +A1H equ $3d ;used by monitor +LINNUM equ $50 ;50-51 +FACLO equ $a1 +CHRGET equ $b1 ;advance ptr, get next tok +CHRGOT equ $b7 ;get next tok (no advance) +TXTPTR equ $b8 +HPAG equ $e6 ;$20 or $40 + +AMPERV equ $3f5 + +TXTCLR equ $c050 +TXTSET equ $c051 +MIXCLR equ $c052 +MIXSET equ $c053 +LOWSCR equ $c054 +HISCR equ $c055 +LORES equ $c056 +HIRES equ $c057 + +ERROR equ $d412 ;error based on X reg +FRMNUM equ $dd67 +SynError equ $dec9 ;throw SYNTAX ERROR +CHKCOM equ $debe +IllQError equ $e199 ;throw ILLEGAL QUANTITY ERROR +GETADR equ $e752 +GETBYT equ $e6f8 ;gets byte, in X/FACLO +HFNS equ $f6b9 ;get hi-res x/y for hplot + +* Prepare the ampersand vector. +* +* Ideally we'd check to see if the existing vector is +* different from ours, and if so, jump to it when we +* get a token we don't recognize. Not convinced +* there's an actual use case for this. +init + lda #$4c ;JMP, in case it got + sta AMPERV ; trashed + lda #dispatch + sta AMPERV+2 + rts + +* Entry point from BASIC. The token is in A. +dispatch + ldx #:cmdend-:cmdtab-1 +]loop cmp :cmdtab,x + beq :match + dex + bpl ]loop + jmp SynError + +:match + lda :jmptabh,x +* lda #>h_new ;all on first page + pha + lda :jmptabl,x + pha + jmp CHRGET ;eat token, jump + + +:cmdtab dfb tok_new + dfb tok_hgr + dfb tok_hgr2 + dfb tok_scrn + dfb tok_hcolor + dfb tok_inverse + dfb tok_clear + dfb tok_hplot + dfb tok_xdraw + dfb tok_draw + dfb tok_exp + dfb tok_cos + dfb tok_sin + dfb tok_at + dfb tok_plot +:cmdend + +:jmptabl dfb h_new-1 + dfb >h_hgr-1 + dfb >h_hgr2-1 + dfb >h_scrn-1 + dfb >h_hcolor-1 + dfb >h_inverse-1 + dfb >h_clear-1 + dfb >h_hplot-1 + dfb >h_xdraw-1 + dfb >h_draw-1 + dfb >h_exp-1 + dfb >h_cos-1 + dfb >h_sin-1 + dfb >h_at-1 + dfb >h_plot-1 + + +******************************** +* &NEW - initialize +h_new + lda #$20 ;match Init result + sta g_cur_page + lda #$00 + sta g_hcolor + tax ;init "previous hplot" + tay ; coord to zero + jsr storeprv + ldx #139 ;279/2 + ldy #0 + lda #95 ;191/2 + jsr storeac + jmp f_Init + +******************************** +* &HGR - show page 1 with mixed text, and clear screen. +* Sets the color to zero. +h_hgr + ldx #$20 ;page 1 + lda #$00 ;$c054 + beq hgr_com + +******************************** +* &HGR2 - show page 2 with no text, and clear screen. +* Sets the color to zero. +h_hgr2 + ldx #$40 ;page 2 + lda #$01 ;$c055 + ;fall through to hgr_com + +* We go slightly out of our way to clear the screen +* before tripping the softswitches. This avoids +* flashing the previous hi-res page contents when +* entering from text mode. +* +* We also want to go nomix-page2 but page1-mix +* (note reverse order) to avoid flashing text pg 2. +hgr_com stx f_in_arg + stx g_cur_page + stx HPAG ;probably useful + pha + jsr f_SetPage + lda #$00 + sta f_in_arg + jsr f_SetColor + jsr f_Clear + lda g_hcolor ;restore color + sta f_in_arg + jsr f_SetColor + bit TXTCLR ;$c050 + bit HIRES ;$c057 + pla + beq :pg1 + bit MIXCLR ;$c052 + bit HISCR ;$c055 + rts +:pg1 bit LOWSCR ;$c054 + bit MIXSET ;$c053 + rts + +******************************** +* &SCRN({1,2}) - set the current hi-res page +h_scrn + jsr GETBYT + cpx #1 + beq :okay + cpx #2 + beq :okay + jmp IllQError +:okay jsr CHRGET ;eat ')' (we assume) + txa ;X/Y unaltered + asl + asl + asl + asl + asl ;multiply x32 + sta g_cur_page + sta f_in_arg + jmp f_SetPage + +******************************** +* &HCOLOR={0-7} - set the current color +h_hcolor + jsr GETBYT ;get color + cpx #8 + blt :okay + jmp IllQError +:okay stx f_in_arg + stx g_hcolor + jmp f_SetColor + +******************************** +* &INVERSE - flip pages +* +* If we're currently drawing on $20, we set the page +* to $40 and hit $c054 to show $20. And vice-versa. +* The goal is to make double-buffered animation easy. +h_inverse + lda g_cur_page + eor #$60 + sta g_cur_page + ldx #$00 + cmp #$40 ;about to start drawing on 2? + beq :showpg1 ;yes, show page 1 + inx ;no, show page 2 +:showpg1 ldy LOWSCR,x + sta f_in_arg + jmp f_SetPage + +******************************** +* &CLEAR - clear current page to current color. +h_clear + jmp f_Clear ;well, that was easy + +******************************** +* &XDRAW left,top,right,bottom - draw rectangle outline +h_xdraw + jsr getltrb + jmp f_DrawRect + +******************************** +* &DRAW left,top,right,bottom - draw filled rectangle +h_draw + jsr getltrb + jmp f_FillRect + +******************************** +* &EXP {0,1} - set line draw mode +h_exp + jsr GETBYT + cpx #2 + blt :okay + jmp IllQError +:okay stx f_in_arg + jmp f_SetLineMode + +******************************** +* &COS cx,cy,rad - draw outline circle +h_cos + jsr getcxcyr + jmp f_DrawCircle + +******************************** +* &SIN cx,cy,rad - draw filled circle +h_sin + jsr getcxcyr + jmp f_FillCircle + +******************************** +* &AT x,y - select center for array draw +h_at + jsr HFNS + jmp storeac + +******************************** +* &PLOT vertexAddr, indexAddr, indexCount [AT cx,cy] +* draw lines from arrays of vertices and indices +h_plot jmp array_draw + +******************************** +* &HPLOT x,y - draw a point +* &HPLOT TO x,y - draw a line from last point to x,y +* &HPLOT x0,y0 to x1,y1 - draw a line + lst on ;last token handler -- +h_hplot equ * ; must be on first page + lst off ; to omit high byte table + + jsr CHRGOT ;check next token + lst off + cmp #tok_to ;is this an "HPLOT TO"? + beq :leadingto + jsr getx1y1 ;get the first coord + jsr copy1to0 + jsr CHRGOT ;see if single point + cmp #tok_to + beq :hplot_to ;nope, draw line + jsr copy0toprev ;draw point, and save x/y + jmp f_DrawPoint ; for subsequent HPLOT TO + +:leadingto ;"HPLOT TO", restore the + lda g_prevxl ; previous coord to x0/y0 + sta f_in_x0l ;(can't rely on f_in_zzz + lda g_prevxh ; being there -- we might + sta f_in_x0h ; have drawn a rect) + lda g_prevy + sta f_in_y0 +:hplot_to + jsr CHRGET ;eat the TO + jsr getx1y1 ;get the coords + jsr f_DrawLine ;draw it + jsr copy1to0 ;shift 1->0 for next round + jsr CHRGOT + cmp #tok_to ;another TO? + beq :hplot_to ;yes, branch + jmp copy0toprev ;no, save prev and bail + +* Get coordinates and store in X1/Y1. +getx1y1 + jsr HFNS +store1 stx f_in_x1l ;store X/Y/A in coord1 + sty f_in_x1h + sta f_in_y1 + rts + +* Save x0/y0 as our "previous" coordinate. +copy0toprev + ldx f_in_x0l + ldy f_in_x0h + lda f_in_y0 +storeprv stx g_prevxl ;store X/Y/A in g_prev + sty g_prevxh + sta g_prevy + rts + +* Copy X1/Y1 into X0/Y0. +copy1to0 + ldx f_in_x1l + ldy f_in_x1h + lda f_in_y1 +store0 stx f_in_x0l ;store X/Y/A in coord 0 + sty f_in_x0h + sta f_in_y0 + rts + +* Store X/Y/A into array-center. +storeac stx g_ac_xl + sty g_ac_xh + sta g_ac_y + rts + +* Get left/top/right/bottom coordinates. +getltrb + jsr HFNS + jsr store0 ;save as X0/Y0 + jsr CHKCOM ;eat a comma + jsr HFNS + jsr store1 ;save as X1/Y1 + rts + +* Get center coordinates and radius. +getcxcyr + jsr HFNS ;get CX and CY + jsr store0 ;save as X0/Y0 + jsr CHKCOM ;eat a comma + jsr GETBYT ;convert to 0-255 + stx f_in_rad + rts + +* Array-draw handler. +* +* We know that fdraw doesn't use LINNUM or A1L/A1H, +* so it's safe to use them here. +array_draw +]vertices equ A1L ;2b +]indices equ LINNUM ;2b +]count equ PCL +]cur equ PCH + + jsr FRMNUM ;get vertex buffer address + jsr GETADR + lda LINNUM ;copy to A1L + sta ]vertices + lda LINNUM+1 + sta ]vertices+1 + jsr CHKCOM ;eat the comma + jsr FRMNUM ;get index buffer address + jsr GETADR ;leave it in LINNUM + jsr CHKCOM + jsr GETBYT ;get the count + cpx #128 ;range check (0-127) + blt :countok + jmp IllQError +:countok txa + beq :done ;nothing to do + asl ;double it + sta ]count ;stash it + lda #$00 + sta ]cur + +* Check for optional AT cx,cy. + jsr CHRGOT + cmp #tok_at + bne :noat + JSR CHRGET ;eat the AT + lda LINNUM ;the code that reads the + pha ; hi-res coordinates will + lda LINNUM+1 ; overwrite LINNUM, so + pha ; we have to save & restore + jsr h_at + pla + sta LINNUM+1 + pla + sta LINNUM +:noat + +]loop jsr getvertex + bcs :skip2 + jsr store0 + jsr getvertex + bcs :skip + jsr store1 + jsr f_DrawLine + dfb $2c ;BIT addr +:skip2 inc ]cur +:skip lda ]cur + cmp ]count + blt ]loop +:done rts + +* Get the Nth vertex, specified by ]cur, and load it +* into X/Y/A (xlo/xhi/y). Returns with carry set if +* the vertex is invalid. +* +* Increments ]cur by 1. +getvertex + ldy ]cur + inc ]cur + lda (]indices),y + bmi :badv ;must be 0-127 + jsr :calcvertex + + ldx g_out_x + ldy g_out_x+1 + beq :xok ;0-255, ok + cpy #1 + bne :badv ;512+ + cpx #280-256 + bge :badv ;280-511 +:xok + lda g_out_y+1 + bne :badv ;Y is neg or > 255 + lda g_out_y + cmp #192 + bcc :goodv +:badv + sec +:goodv rts + +* Get VX and VY, merging with AC, and store in +* 16-bit g_out_x and g_out_y. Range not checked +* here. On entry, A has vertex index. +:calcvertex + asl + tay + ldx #$00 ;hi byte of vertex + lda (]vertices),y ;x-coord + bpl :xpos + dex ;sign-extend hi byte +:xpos clc + adc g_ac_xl + sta g_out_x + txa + adc g_ac_xh + sta g_out_x+1 + + iny + ldx #$00 + lda (]vertices),y ;y-coord + bpl :ypos + dex ;sign-extend hi byte +:ypos clc + adc g_ac_y + sta g_out_y + bcc :nocarry + inx +:nocarry stx g_out_y+1 + rts + + + +******************************** +* Global variables + +g_cur_page ds 1 ;$20 or $40 +g_hcolor ds 1 +g_prevxl ds 1 +g_prevxh ds 1 +g_prevy ds 1 +g_ac_xl ds 1 ;Center-point coordinates +g_ac_xh ds 1 ; for array-based line +g_ac_y ds 1 ; draw (&AT, &PLOT). +g_out_x ds 2 ;16-bit coordinates for +g_out_y ds 2 ; array-based line draw + + + + lst on +end equ * + sav amperfdraw + lst off diff --git a/SourceGen/Examples/A2-Amper-fdraw/FDRAW.DEFS.S.TXT b/SourceGen/Examples/A2-Amper-fdraw/FDRAW.DEFS.S.TXT new file mode 100644 index 0000000..e39c7bc --- /dev/null +++ b/SourceGen/Examples/A2-Amper-fdraw/FDRAW.DEFS.S.TXT @@ -0,0 +1,44 @@ +******************************** +* * +* Definitions for fdraw v0.3 * +* * +* Use "PUT" to include these * +* definitions in code that * +* uses fdraw. * +* * +******************************** + +fdraw_start equ $6000 + +f_Init equ fdraw_start+0 +f_majvers equ fdraw_start+3 +f_minvers equ fdraw_start+4 + +f_in_arg equ fdraw_start+5 +f_in_x0l equ fdraw_start+6 +f_in_x0h equ fdraw_start+7 +f_in_y0 equ fdraw_start+8 +f_in_x1l equ fdraw_start+9 +f_in_x1h equ fdraw_start+10 +f_in_y1 equ fdraw_start+11 +f_in_rad equ fdraw_start+12 + +f_SetColor equ fdraw_start+16 +f_SetPage equ fdraw_start+19 +f_Clear equ fdraw_start+22 +f_DrawPoint equ fdraw_start+25 +f_DrawLine equ fdraw_start+28 +f_DrawRect equ fdraw_start+31 +f_FillRect equ fdraw_start+34 +f_DrawCircle equ fdraw_start+37 +f_FillCircle equ fdraw_start+40 +f_SetLineMode equ fdraw_start+43 + +f_FillRaster equ fdraw_start+49 +f_rast_top equ fdraw_start+52 +f_rast_bottom equ fdraw_start+53 + +f_rastx0l equ fdraw_start+54 +f_rastx0h equ fdraw_start+56 +f_rastx1l equ fdraw_start+58 +f_rastx1h equ fdraw_start+60 diff --git a/SourceGen/Examples/A2-Amper-fdraw/fdraw-exports.sym65 b/SourceGen/Examples/A2-Amper-fdraw/fdraw-exports.sym65 new file mode 100644 index 0000000..8a376da --- /dev/null +++ b/SourceGen/Examples/A2-Amper-fdraw/fdraw-exports.sym65 @@ -0,0 +1,40 @@ +; Copyright 2018 faddenSoft. All Rights Reserved. +; See the LICENSE.txt file for distribution terms (Apache 2.0). + +*SYNOPSIS Addresses exported from fdraw + +; These come from FDRAW.DEFS.S, which would be included as a source +; file in any code that wanted to use the fdraw library. + +fdraw_start = $6000 +f_Init @ $6000 ;init library; sets color=0 page=$20 +f_majvers @ $6003 ;library major version +f_minvers @ $6004 ;library minor version +f_in_arg @ $6005 ;arg for misc functions +f_in_x0l @ $6006 ;low part of X0 coord (0-279) +f_in_x0h @ $6007 ;high part of X0 coord (0-279) +f_in_y0 @ $6008 ;Y0 coord (0-191) +f_in_x1l @ $6009 ;low part of X1 coord (0-279) +f_in_x1h @ $600a ;high part of X1 coord (0-279) +f_in_y1 @ $600b ;Y1 coord (0-191) +f_in_rad @ $600c ;circle radius (0-255) + +f_SetColor @ $6010 ;set color used for drawing (0-7) +f_SetPage @ $6013 ;set page to value in arg ($20 or $40) +f_Clear @ $6016 ;erase current page to current color +f_DrawPoint @ $6019 ;plot a single point at X0,Y0 +f_DrawLine @ $601c ;draw line from X0,Y0 to X1,Y1 (inclusive) +f_DrawRect @ $601f ;draw rect w/corners X0,Y0 and X1,Y1 +f_FillRect @ $6022 ;draw filled rect w/corners X0,Y0 and X1,Y1 +f_DrawCircle @ $6025 ;draw circle at X0,Y0 with radius=rad +f_FillCircle @ $6028 ;draw filled circle at X0,Y0 with radius=rad +f_SetLineMode @ $602b ;set line mode from arg (0=normal, 1=xdraw) + +f_FillRaster @ $6031 ;draw shape from raster table +f_rast_top @ $6034 ;top line (0-191) +f_rast_bottom @ $6035 ;bottom line (inclusive) (0-191) + +f_rastx0l @ $6036 ;address of rastx1l table (read-only) +f_rastx0h @ $6038 ;address of rastx1h table (read-only) +f_rastx1l @ $603a ;address of rastx2l table (read-only) +f_rastx1h @ $603c ;address of rastx2h table (read-only) diff --git a/SourceGen/Examples/A2-HP-CDA/HardPressed.CDA#b90100 b/SourceGen/Examples/A2-HP-CDA/HardPressed.CDA#b90100 new file mode 100644 index 0000000..bbbd7f9 Binary files /dev/null and b/SourceGen/Examples/A2-HP-CDA/HardPressed.CDA#b90100 differ diff --git a/SourceGen/Examples/A2-HP-CDA/HardPressed.CDA#b90100.dis65 b/SourceGen/Examples/A2-HP-CDA/HardPressed.CDA#b90100.dis65 new file mode 100644 index 0000000..9f7a072 --- /dev/null +++ b/SourceGen/Examples/A2-HP-CDA/HardPressed.CDA#b90100.dis65 @@ -0,0 +1,282 @@ +### 6502bench SourceGen dis65 v1.0 ### +{ +"_ContentVersion":1,"FileDataLength":1816,"FileDataCrc32":775804905,"ProjectProps":{ +"CpuName":"65816","IncludeUndocumentedInstr":false,"EntryFlags":12780031,"AnalysisParams":{ +"AnalyzeUncategorizedData":true,"MinCharsForString":4,"SeekNearbyTargets":true}, +"PlatformSymbolFileIdentifiers":["RT:Apple/Cxxx-IO.sym65","RT:Apple/IIgs-ROM.sym65","RT:Apple/GSOS.sym65"],"ExtensionScriptFileIdentifiers":["RT:Apple/GSOS.cs","RT:Apple/IIgs-Toolbox.cs"],"ProjectSyms":{ +}}, +"AddressMap":[{ +"Offset":0,"Addr":131072}, +{ +"Offset":158,"Addr":196608}, +{ +"Offset":232,"Addr":0}],"TypeHints":[{ +"Low":358,"High":358,"Hint":"Code"}],"StatusFlagOverrides":{ +}, +"Comments":{ +"306":"3 states for item 1","308":"2 here"}, +"LongComments":{ +"-2147483647":{ +"Text":"6502bench SourceGen v1.0.0-alpha1","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"0":{ +"Text":"~ExpressLoad segment header","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"158":{ +"Text":"Code segment header\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"232":{ +"Text":"\r\nStart of code.\r\n\r\nThis begins with the header required for CDAs.\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"296":{ +"Text":"\r\nGlobal variable storage.\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"310":{ +"Text":"\r\nText screen line offsets.\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"865":{ +"Text":"\r\nCDA shutdown entry point.\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"1597":{ +"Text":"\r\nMesgData\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"358":{ +"Text":"\r\nMain entry point from system.\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"1672":{ +"Text":"Not sure what this is.\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}}, +"Notes":{ +"232":{ +"Text":"Start of the interesting stuff. The values that are updated by the relocating loader are all based off of 00/0000, so that\u0027s where we set the ORG. This confuses things a bit, since we seem to be running our code off of zero page, but it mostly seems to work.\r\n\r\nThis could use further refinement, but I\u0027m not sure there\u0027s much point without actual OMF support.","BoxMode":false,"MaxWidth":80,"BackgroundColor":-7278960}}, +"UserLabels":{ +"72":{ +"Label":"ExpressLoad","Value":131144,"Source":"User","Type":"LocalOrGlobalAddr"}, +"232":{ +"Label":"Header","Value":0,"Source":"User","Type":"LocalOrGlobalAddr"}, +"306":{ +"Label":"max1","Value":74,"Source":"User","Type":"LocalOrGlobalAddr"}, +"300":{ +"Label":"hilite_opt","Value":68,"Source":"User","Type":"LocalOrGlobalAddr"}, +"304":{ +"Label":"stat2","Value":72,"Source":"User","Type":"LocalOrGlobalAddr"}, +"302":{ +"Label":"stat1","Value":70,"Source":"User","Type":"LocalOrGlobalAddr"}, +"298":{ +"Label":"global_flags","Value":66,"Source":"User","Type":"LocalOrGlobalAddr"}, +"296":{ +"Label":"global_mode","Value":64,"Source":"User","Type":"LocalOrGlobalAddr"}, +"308":{ +"Label":"max2","Value":76,"Source":"User","Type":"LocalOrGlobalAddr"}, +"358":{ +"Label":"Main","Value":126,"Source":"User","Type":"GlobalAddr"}, +"770":{ +"Label":"fail_cause","Value":538,"Source":"User","Type":"LocalOrGlobalAddr"}, +"948":{ +"Label":"InitScreen","Value":716,"Source":"User","Type":"GlobalAddr"}, +"671":{ +"Label":"ErrorMain","Value":439,"Source":"User","Type":"GlobalAddr"}, +"865":{ +"Label":"ShutDown","Value":633,"Source":"User","Type":"GlobalAddr"}, +"310":{ +"Label":"texttab","Value":78,"Source":"User","Type":"LocalOrGlobalAddr"}, +"866":{ +"Label":"GetKey","Value":634,"Source":"User","Type":"GlobalAddr"}, +"932":{ +"Label":"event_rec","Value":700,"Source":"User","Type":"LocalOrGlobalAddr"}, +"1175":{ +"Label":"err_flag","Value":943,"Source":"User","Type":"LocalOrGlobalAddr"}, +"1270":{ +"Label":"DrawScreen","Value":1038,"Source":"User","Type":"GlobalAddr"}, +"1477":{ +"Label":"items","Value":1245,"Source":"User","Type":"LocalOrGlobalAddr"}, +"1483":{ +"Label":"item1","Value":1251,"Source":"User","Type":"LocalOrGlobalAddr"}, +"1573":{ +"Label":"item2","Value":1341,"Source":"User","Type":"LocalOrGlobalAddr"}, +"1465":{ +"Label":"cur_line","Value":1233,"Source":"User","Type":"LocalOrGlobalAddr"}, +"1467":{ +"Label":"cur_item","Value":1235,"Source":"User","Type":"LocalOrGlobalAddr"}, +"1469":{ +"Label":"item_stat","Value":1237,"Source":"User","Type":"LocalOrGlobalAddr"}, +"1471":{ +"Label":"item_ptr","Value":1239,"Source":"User","Type":"LocalOrGlobalAddr"}, +"1473":{ +"Label":"cmask","Value":1241,"Source":"User","Type":"LocalOrGlobalAddr"}, +"1475":{ +"Label":"csub","Value":1243,"Source":"User","Type":"LocalOrGlobalAddr"}, +"1493":{ +"Label":"title1","Value":1261,"Source":"User","Type":"LocalOrGlobalAddr"}, +"1501":{ +"Label":"i1compr","Value":1269,"Source":"User","Type":"LocalOrGlobalAddr"}, +"1525":{ +"Label":"i1decomp","Value":1293,"Source":"User","Type":"LocalOrGlobalAddr"}, +"1549":{ +"Label":"i1inact","Value":1317,"Source":"User","Type":"LocalOrGlobalAddr"}, +"1581":{ +"Label":"title2","Value":1349,"Source":"User","Type":"LocalOrGlobalAddr"}, +"1624":{ +"Label":"data_in","Value":1392,"Source":"User","Type":"LocalOrGlobalAddr"}, +"1626":{ +"Label":"data_out","Value":1394,"Source":"User","Type":"LocalOrGlobalAddr"}, +"1646":{ +"Label":"SendMsg","Value":1414,"Source":"User","Type":"GlobalAddr"}, +"1597":{ +"Label":"idstring","Value":1365,"Source":"User","Type":"LocalOrGlobalAddr"}}, +"OperandFormats":{ +"0":{ +"Length":72,"Format":"Dense","SubFormat":"None","SymbolRef":null}, +"72":{ +"Length":86,"Format":"Dense","SubFormat":"None","SymbolRef":null}, +"158":{ +"Length":74,"Format":"Dense","SubFormat":"None","SymbolRef":null}, +"232":{ +"Length":20,"Format":"String","SubFormat":"L8String","SymbolRef":null}, +"252":{ +"Length":4,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"Main","Part":"Low"}}, +"256":{ +"Length":4,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"ShutDown","Part":"Low"}}, +"260":{ +"Length":36,"Format":"String","SubFormat":"CString","SymbolRef":null}, +"296":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"298":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"300":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"302":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"304":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"306":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"308":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"310":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"312":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"314":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"316":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"318":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"320":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"322":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"324":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"326":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"328":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"330":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"332":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"334":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"336":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"338":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"340":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"342":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"344":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"346":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"348":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"350":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"352":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"354":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"356":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"770":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"772":{ +"Length":31,"Format":"String","SubFormat":"CString","SymbolRef":null}, +"803":{ +"Length":29,"Format":"String","SubFormat":"CString","SymbolRef":null}, +"832":{ +"Length":33,"Format":"String","SubFormat":"CString","SymbolRef":null}, +"877":{ +"Length":3,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"880":{ +"Length":3,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"883":{ +"Length":3,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"event_rec","Part":"Bank"}}, +"886":{ +"Length":3,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"event_rec","Part":"Low"}}, +"1175":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"1177":{ +"Length":20,"Format":"String","SubFormat":"CString","SymbolRef":null}, +"1465":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"1467":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"1469":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"1471":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"1473":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"1475":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"1477":{ +"Length":2,"Format":"NumericLE","SubFormat":"Address","SymbolRef":null}, +"1479":{ +"Length":2,"Format":"NumericLE","SubFormat":"Address","SymbolRef":null}, +"1481":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"1483":{ +"Length":2,"Format":"NumericLE","SubFormat":"Address","SymbolRef":null}, +"1485":{ +"Length":2,"Format":"NumericLE","SubFormat":"Address","SymbolRef":null}, +"1487":{ +"Length":2,"Format":"NumericLE","SubFormat":"Address","SymbolRef":null}, +"1489":{ +"Length":2,"Format":"NumericLE","SubFormat":"Address","SymbolRef":null}, +"1491":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"1493":{ +"Length":8,"Format":"String","SubFormat":"CString","SymbolRef":null}, +"1501":{ +"Length":24,"Format":"String","SubFormat":"CString","SymbolRef":null}, +"1525":{ +"Length":24,"Format":"String","SubFormat":"CString","SymbolRef":null}, +"1549":{ +"Length":24,"Format":"String","SubFormat":"CString","SymbolRef":null}, +"1573":{ +"Length":2,"Format":"NumericLE","SubFormat":"Address","SymbolRef":null}, +"1575":{ +"Length":2,"Format":"NumericLE","SubFormat":"Address","SymbolRef":null}, +"1577":{ +"Length":2,"Format":"NumericLE","SubFormat":"Address","SymbolRef":null}, +"1579":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"1581":{ +"Length":8,"Format":"String","SubFormat":"CString","SymbolRef":null}, +"1589":{ +"Length":4,"Format":"String","SubFormat":"CString","SymbolRef":null}, +"1593":{ +"Length":4,"Format":"String","SubFormat":"CString","SymbolRef":null}, +"1597":{ +"Length":27,"Format":"String","SubFormat":"L8String","SymbolRef":null}, +"1624":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"1650":{ +"Length":3,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"idstring","Part":"Bank"}}, +"1653":{ +"Length":3,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"idstring","Part":"Low"}}, +"1658":{ +"Length":3,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"data_out","Part":"Bank"}}, +"1672":{ +"Length":144,"Format":"Dense","SubFormat":"None","SymbolRef":null}}} diff --git a/SourceGen/Examples/A2-HP-CDA/cda.asm b/SourceGen/Examples/A2-HP-CDA/cda.asm new file mode 100644 index 0000000..63398bd --- /dev/null +++ b/SourceGen/Examples/A2-HP-CDA/cda.asm @@ -0,0 +1,850 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; cda.asm - HardPressed classic desk accessory +; Copyright (C) 1993 by Andy McFadden +; +; This is an example of communication with HardPressed. It illustrates +; getting and setting the INIT's status with SendRequest. The calls used +; here are the ONLY ones which are guaranteed to exist in future versions +; of HardPressed. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + CASE ON + OBJCASE ON + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +Header START +; +; System-required header for the CDA. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + dc I1'str_end-str_begin' +str_begin anop + dc C'HardPressed Control' +str_end anop + + dc A4'Main' + dc A4'ShutDown' + + dc C'Copyright (C) 1993 by Andy McFadden',H'00' + END + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +Globals DATA +; +; Program-wide defs. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; +; DP defs +; +ptr gequ $00 ;4b +ptr2 gequ $04 ;4b + +; +; Softswitches +; +CLR80VID equ $e0c00c +SETALTCHR equ $e0c00f + +; +; Global variables +; +global_mode ds 2 +global_flags ds 2 + +; CDA display status +dMaxOpt equ 2 +hilite_opt ds 2 +stat_tab anop +stat1 ds 2 +stat2 ds 2 +max_tab anop +max1 dc I2'3' ;3 states for item 1 +max2 dc I2'2' ;2 here + +; +; reasons for failure +; +failP8 gequ 1 +failInactive gequ 2 +failBusy gequ 3 + +; +; Text screen line offsets +; +texttab ANOP + dc I2'$400,$480,$500,$580,$600,$680,$700,$780' + dc I2'$428,$4a8,$528,$5a8,$628,$6a8,$728,$7a8' + dc I2'$450,$4d0,$550,$5d0,$650,$6d0,$750,$7d0' + END + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +Main START +; +; Calling conventions: +; (called by control panel) +; +; Stack is on page 1, DP is on page 0. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + using MesgData + using Globals + + phb + phk + plb + +; see if GS/OS is active + lda >$e100bc ;OS_KIND + and #$00ff + cmp #$0001 ;GS/OS + beq is_gsos + lda #failP8 + jmp ErrorMain +is_gsos anop + +; see if HardPressed is active + lda #dMping + ldx #$0000 ;no data_in + txy + jsr SendMsg ;leaves version in data_out+$02 + bcc hp_active ;if no error, HP is up +; cmp #$0120 ;reqNotAccepted +; beq nohear ;else probably $0122 (invalidReq) + +inactive anop + lda #failInactive + jmp ErrorMain +hp_busy anop + lda #failBusy + jmp ErrorMain + +hp_active anop + lda #dMgetStatus + ldx #$0000 ;no data_in + txy + jsr SendMsg ;failure here probably means that + bcs hp_busy ; HP is busy (must be _AlertWindow) + +; +; From here on, assume HardPressed is active and functioning normally. +; + lda data_out+2 ;split the mode and the flags into + tax ; two parts + and #$00ff + sta global_mode + txa + and #$ff00 + sta global_flags + + lda #$0000 + jsr InitScreen ;set 40 cols, clear, draw border + +; init menu stuff + stz hilite_opt + + ldx #$0000 + lda global_mode + cmp #dVpolOn + beq got_pol + inx + cmp #dVpolDecode + beq got_pol + inx +got_pol anop + stx stat1 + + ldx #$0000 + lda global_flags + bit #fGverify + beq got_ver + inx +got_ver anop + stx stat2 + +; +; main loop +; +redraw_loop ANOP + jsr DrawScreen + +key_loop ANOP + jsr GetKey + + cmp #$000d ;return? + beq save_status + cmp #$001b ;escape? + bne not_esc + brl escape_hit +not_esc anop + + cmp #$000a ;Ctrl-J (down arrow)? + beq dn + cmp #$000b ;Ctrl-K (up arrow)? + beq up + cmp #$0008 ;Ctrl-H (left arrow)? + beq left + cmp #$0015 ;Ctrl-U (right arrow)? + beq right + + pea $0008 ;sbBadKeypress + ldx #$3803 ;_SysBeep2 + jsl $e10000 + bra key_loop + +dn anop + lda hilite_opt + inc A + sta hilite_opt + cmp #dMaxOpt + blt redraw_loop + stz hilite_opt + bra redraw_loop + +up anop + lda hilite_opt + dec A + bpl upstore + lda #dMaxOpt-1 +upstore anop + sta hilite_opt + bra redraw_loop + +right anop + lda hilite_opt + asl A + tax + lda stat_tab,x + inc A + cmp max_tab,x + blt rightstore + lda #$0000 +rightstore anop + sta stat_tab,x + bra redraw_loop + +left anop + lda hilite_opt + asl A + tax + lda stat_tab,x + dec A + bpl leftstore + lda max_tab,x + dec A +leftstore anop + sta stat_tab,x + bra redraw_loop + +; +; If return was hit, send the new status to the INIT +; +save_status ANOP + ldx #dVpolOn ;translate the menu index into + lda stat1 ; the HP state value + beq got_opol + ldx #dVpolDecode + cmp #$0001 + beq got_opol + ldx #dVpolOff +got_opol anop + stx global_mode + +; set/clear the "verify" flag without disturbing any of the others + lda #fGverify ;verify flag + ldx stat2 ;is verify on? + beq v_off + tsb global_flags ;enable verify + bra v_set +v_off anop + trb global_flags ;disable verify +v_set anop + + lda global_mode ;now put it together with the mode + ora global_flags + tay ;lo word is status + ldx #$0000 ;hi word is zero + lda #dMsetStatus + jsr SendMsg + bcc Done + pea $000c ;sbOperationFailed + ldx #$3803 ;_SysBeep2 + jsl $e10000 + bra Done + +; if escape was hit, just exit +escape_hit ANOP + +Done ANOP + plb + rtl + END + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +ErrorMain START +; +; Calling conventions: +; JMP from Main with reason for failure in Acc +; +; Displays a screen which tells the user why he/she/it can't use the CDA at +; this time. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + using Globals + + sta fail_cause + + lda #$0001 + jsr InitScreen + +; +; Just draw the message from here +; + phb + pea $e0e0 ;write to text page in bank $e0 + plb + plb + + ldx #11*2 + lda >texttab,x + clc + adc #3 ;start at column 4 + sta fail_cause + cmp #failP8 + bne fail2 +fail1 anop + lda #fail_p8 + bra fail_comm +fail2 anop + cmp #failInactive + bne fail3 + lda #fail_hp + bra fail_comm +fail3 anop + lda #fail_busy +fail_comm anop + sta $e0c010 +lp lda >$e0c000 + bpl lp + sta >$e0c010 + rep #$20 + LONGA ON + and #$007f + +Done ANOP + rts +Fail ANOP + lda #$0020 ;call it a space bar + rts + +event_rec anop +what ds 2 +message ds 4 +when ds 4 +where ds 4 +modifiers ds 2 + END + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +InitScreen START +; +; Calling conventions: +; JSR with 0 or 1 in acc (indicating normal or error start) +; +; Sets 40 columns and mousetext, then draws the title, border, and key +; instructions. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + using Globals + + sta err_flag + + phb + pea $e0e0 ;write to text page in bank $e0 + plb + plb + + sep #$20 + LONGA OFF + sta |CLR80VID ;set 40 columns + sta |SETALTCHR ;we want MouseText + rep #$20 + LONGA ON + +home ANOP + ldx #23*2 +loop anop + lda >texttab,x + sta texttab ;addr of line 0 ($400, duh) + sta ptr + ldx #2*2 + lda >texttab,x + sta ptr2 + ldy #37 ;two at a time, starting from 37/38 +tloop1 anop + lda #$dfdf ;normal '_' + sta (ptr),y + lda #$4c4c ;flashing 'L' + sta (ptr2),y + dey + dey + bpl tloop1 ;should end at -1 + + ldx #1*2 + lda >texttab,x + sta ptr + lda #$a05a ;'Z ' (right-bar, space) + sta (ptr) +; lda #$a041 ;'A ' (apple symbol, space) +; ldy #$0002 +; sta (ptr),y + + sep #$20 + LONGA OFF + ldy #$0002 + ldx #$0000 +tloop2 anop + lda >title_str,x + beq tloop2_done + sta (ptr),y + inx + iny + bra tloop2 +tloop2_done anop + lda #$a0 + sta (ptr),y + iny + + lda #$20 +tloop3 anop + cpy #39 + bge tloop3_done + sta (ptr),y + iny + bra tloop3 +tloop3_done anop + lda #$5f ;left-bar + sta (ptr),y + rep #$20 + LONGA ON + + +box ANOP + ldx #2*2 +edge anop + lda >texttab,x + sta texttab,x + sta ptr + ldy #37 +bloop anop + lda #$4c4c ;flashing 'L' - upper line + sta (ptr),y + dey + dey + bpl bloop ;should end at -1 + +text ANOP + ldx #22*2 + lda >texttab,x + sta ptr + + ldy #$0002 + ldx #$0000 + lda >err_flag + beq noerr + ldx #finstr_str-instr_str +noerr anop + sep #$20 + LONGA OFF +instr_loop anop + lda >instr_str,x + beq instr_done + sta (ptr),y + iny + inx + bra instr_loop +instr_done anop + rep #$20 + LONGA ON + + +Done ANOP + plb + rts + +err_flag ds 2 + +msg_tab anop + MSB ON +title_str dc C'HardPressed Control',H'00' +instr_str dc C'Select: ',H'48a055a04aa04b' + dc C' Cancel:Esc Save: ',H'4d00' +finstr_str dc C' Exit: ',H'4d00' + MSB OFF + END + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +DrawScreen START +; +; Calling conventions: +; JSR +; +; Draws the menu items (selected one in inverse), the appropriate selection, +; and a check mark for defaults. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + using Globals + +; +; Initialize counters +; + lda #4 + sta cur_line + lda #0 + sta cur_item + lda #$00e0 + sta $60, then it's inverse + bge islorn ; lower case or normal text + cmp #$40 ;if it's < $40, it's inverse + blt islorn ; punctuation (e.g. ':') + sec + sbc csub +islorn anop + sta [:screen_data + sta ptr+1 + +:loop + lda (ptr) + sta ptr2 + ldy #$01 + lda (ptr),y + sta ptr2+1 + beq :done + + clc ;need Y-reg to start at 0 + lda ptr + adc #$02 + sta ptr + lda ptr+1 + adc #$00 + sta ptr+1 + dey ;back to zero + +:txtloop + lda (ptr),y + beq :txtdone + sta (ptr2),y + iny + bra :txtloop +:txtdone + iny ;advance ptr according to Y + tya ;(could have >256 bytes of data) + clc + adc ptr + sta ptr + lda ptr+1 + adc #$00 + sta ptr+1 + bra :loop + +:done + rts + +* table of cache sizes +:cache_tab + asc " 8163264" + +* initial screen data +:screen_data + dw $500+1 ;line 1, column 1 + asc "ZipGS Control v1.1 By Andy McFadden",00 + dw $680+1 ;line 5, column 1 +statline asc "Your MHz/ K ZipGS is ",00 +dr_enab equ $680+26 + dw $780+5 + asc "Slot 1:",00 + dw $428+5 + asc "Slot 2:",00 + dw $4a8+5 + asc "Slot 3:",00 + dw $528+5 + asc "Slot 4:",00 + dw $780+21 + asc "Slot 5:",00 + dw $428+21 + asc "Slot 6:",00 + dw $4a8+21 + asc "Slot 7:",00 + dw $528+21 + asc "Speakr:",00 + dw $628+1 + asc "Bank Switch LC cache:",00 +dr_optb equ $628+23 + dw $6a8+1 + asc "Paddle Delay:",00 +dr_optp equ $6a8+15 + dw $6a8+21 + asc "AppleTalk Dl:",00 +dr_opta equ $6a8+35 + dw $728+1 + asc "Counter Del :",00 +dr_optc equ $728+15 + dw $728+21 + asc "Follow CPS :",00 +dr_optf equ $728+35 + dw $4d0+3 + asc "[ ]",00 +dr_spdbar equ $4d0+4 + dw $550+0 + asc "________________________________________",00 + dw $650+3 ;line 19, column 2 + asc "E:Enable D:Disable Arrows:Speed",00 + dw $6d0+3 ;line 20, column 2 + asc "1-7:Slot S:Speaker BPACF:Option",00 + dw $750+1 ;line 22, column 1 + asc "Return stores and exits, Escape aborts",00 + + dw 0 ;end of list + +* +* Draw speed-related info +* (percent, MHz, the speed bar, etc) +* +draw_speed +* insert percent of max into speed bar + lda zip_lock ;get current zip speed + asl A + asl A + asl A ;want it x8 + tay + ldx #$00 +:ploop lda :perc_tab,y + sta :speed+5,x + iny + inx + cpx #$06 ;6 chars each + blt :ploop + +* print MHz + clc + xce + rep #$30 ;16 bits for both + mx %00 + lda zip_mhz + and #$00ff ;ignore zip_mhz+1 + xba ;mult * 256 + sta tmp + lsr A + lsr A + lsr A + lsr A ;now only x16 + sta ptr + + lda zip_lock ;for each tick, drop 1/16th + and #$00ff + tax + inx + lda tmp +:subloop dex + beq :subdone ;don't subtract on 100% + sec + sbc ptr + bra :subloop + +:subdone sta tmp ;now result = MHz * 256 + ldx #$00a0 + stx ptr ;leftmost char + and #$ff00 + cmp #$0a00 + blt :ok + sec + sbc #$0a00 ;subtract 10 + ldx #"1" + stx ptr +:ok + clc + adc #"0"*256 + ora ptr ;put space or 1 on left + sta :speed+18 ;(16-bit store) + +* We got the whole number okay, now we need the +* fractional percentage. This burns a few cycles +* here... not pretty neither. What we want to do +* is convert 0-255 in the low byte of tmp to 0-99. +* +* We count 0-255 or less, incrementing twice every five, +* and subtracting one every 64. We get roughly 0-99. +* This is reasonably accurate, within +/- .02. + + sep #$30 ;short acc, short xy + mx %11 + sec ;return to emulation mode + xce + + stz ptr + lda tmp ;exactly zero? + beq :iszero + + ldy #$05 ;counting 5-1, from first + lda #$02 + sta ptr ;result; start at two + lda #$00 + sed ;how often do you see this? +:xloop + inc A + sta ptr+1 ;save counter + and #$3f ;divisible by 64? + bne :not64 ;nope + lda ptr + sec + sbc #$01 ;have to sbc for decimal mode + sta ptr +:not64 + dey + bne :not5 + lda ptr + clc + adc #$02 ;add two + sta ptr + ldy #$05 ;reset Y +:not5 + lda ptr+1 + cmp tmp ;the base 16 fraction + blt :xloop + + cld ;very important! + +:iszero + lda ptr ;now that we've got it, + tax ; store it. + and #$f0 + lsr A + lsr A + lsr A + lsr A + clc + adc #"0" + sta :speed+21 + txa + and #$0f + clc + adc #"0" + sta :speed+22 + +* draw the speed thermometer bar + lda #$10 + sec + sbc zip_lock + tax ;now x has 1=6.25%, 10=100% + lda #$3f ;inverse mode AND mask + sta tmp ;AND mask + ldy #$00 +:bloop + lda :speed,y + and tmp + sta dr_spdbar,y + iny + lda :speed,y + and tmp + sta dr_spdbar,y + iny + dex + bne :not0 + lda #$ff ;X hit 0, so switch to normal + sta tmp +:not0 cpy #32 ;2 * 16 settings + blt :bloop + rts + +* table of percentages, padded to 8 bytes each +:perc_tab + asc "100.00",0000 + asc " 93.75",0000 + asc " 87.50",0000 + asc " 81.25",0000 + asc " 75.00",0000 + asc " 68.75",0000 + asc " 62.50",0000 + asc " 56.25",0000 + asc " 50.00",0000 + asc " 43.75",0000 + asc " 37.50",0000 + asc " 31.25",0000 + asc " 25.00",0000 + asc " 18.75",0000 + asc " 12.50",0000 + asc " 6.25",0000 + +* text for speed bar +:speed asc " xxx.xx% xx.xx MHZ ",00 + + +* +* Draw options +* (enabled/disabled, external delay, etc) +* +draw_opts +* Start with board enable/disable. We do this one +* specially (print "enabled." instead of "enab"). +* Easier to special case than do "right". + lda zip_enb + and #$10 ;want bit 4 + beq :enb + + lda #<:disabled_txt + sta ptr + lda #>:disabled_txt + sta ptr+1 + bra :copy +:enb + lda #<:enabled_txt + sta ptr + lda #>:enabled_txt + sta ptr+1 + +:copy ldy #$00 +:loop lda (ptr),y + beq :done + sta dr_enab,y + iny + bra :loop +:done + +* now do each of the other five (bpacf) + lda #dr_optb + sta ptr2+1 + lda #$80 + jsr draw_enab + + lda #dr_optp + sta ptr2+1 + lda #$40 + jsr draw_enab + + lda #dr_opta + sta ptr2+1 + lda #$20 + jsr draw_enab + + lda #dr_optc + sta ptr2+1 + lda #$10 + jsr draw_enab + + lda #dr_optf + sta ptr2+1 + lda #$08 + jmp draw_enab + +:disabled_txt + asc "disabled.",00 +:enabled_txt + asc "enabled. ",00 + +* draw_opts subroutine +draw_enab + sta tmp ;save the mask + lda zip_opts ;get ZipGS options register + and tmp ;strip off the boring ones + bne :enb ;note: reverse of board enb + + lda #<:disb_txt + sta ptr + lda #>:disb_txt + sta ptr+1 + bra :copy +:enb + lda #<:enab_txt + sta ptr + lda #>:enab_txt + sta ptr+1 + +:copy ldy #$03 +:loop lda (ptr),y + sta (ptr2),y + dey + bpl :loop + + rts + +:enab_txt asc "enab" +:disb_txt asc "disb" + +* +* Draw slot info +* +draw_slot + ldx #$00 + lda #$02 ;bit 2 = slot 1 + +:loop + sta tmp + lda :dr_slot,x + sta ptr + lda :dr_slot+1,x + sta ptr+1 + lda zip_slot + and tmp + beq :disb + +:enb lda #<:slow_txt + sta ptr2 + lda #>:slow_txt + sta ptr2+1 + bra :copy + +:disb lda #<:fast_txt + sta ptr2 + lda #>:fast_txt + sta ptr2+1 + +:copy ldy #$03 +:cloop lda (ptr2),y + sta (ptr),y + dey + bpl :cloop + inx + inx + cpx #16 + bge :done + lda tmp + asl A + bcc :loop + lda #$01 ;go back and do speaker delay + bra :loop + +:done + rts + +:dr_slot + dw $780+13 ;slot 1 + dw $428+13 + dw $4a8+13 + dw $528+13 ;... + dw $780+29 + dw $428+29 + dw $4a8+29 ;slot 7 + dw $528+29 ;speaker delay + +:slow_txt asc "Slow" +:fast_txt asc "Fast" + + +* +* Calculate the max ZipGS speed +* +* I pulled this out of the source for the Zip S16 +* program. I don't pretend to understand most of +* what this does. If you figure it out, send me a +* note... +* +calc_speed + clc + xce ;set native mode + rep #$30 ;16 bits + mx %00 + + stz :ascii ;clear the output buffer + stz :ascii+2 + stz :ascii+4 + stz :ascii+6 + jsr getspeed ;do the actual speed calc + + lda result ;result is decimal speed, + sta zip_cyc ; so convert to a decimal + pha ; string + pea $0000 ;hiaddr + pea :ascii ;loaddr + pea $0006 ;length + pea $0000 ;unsigned + ldx #$260b ;_Int2Dec + jsl $e10000 + + sep #$30 ;back to emulation mode + mx %11 + sec + xce + +* at this point, the buffer would contain "8005" for +* a speed of 8.005. This reduces it to "8.00". + ldy #$ff +:loop iny + lda :ascii,y ;seek to end + cmp #$00 + bne :loop + + lda #$20 + sta :ascii-1,y ;kill last char + sta :ascii,y ;extend length by one + lda #$00 + sta :ascii+2,y + + dey ;now scoot the two decimal + ldx #$02 ; places over by one to make +:loop2 dey ; room for the period + lda :ascii,y + sta :ascii+1,y + dex + bne :loop2 + + lda #$2e ;stuff the decimal point in + sta :ascii,y + +* now it's in a format which makes output nice. We +* want it in a numeric format too, so... + stz zip_mhz ;init to zero + lda :ascii-2,y ;10s column + ora #$80 ;set hi bit + sta statline+5 ;stuff it into static string + cmp #"1" ;is it a 1 (not expecting 25) + bne :lt10 ;yes, we're less than 10 + lda #10 + sta zip_mhz +:lt10 + lda :ascii-1,y ;1s column + ora #$80 ;set hi bit + sta statline+6 ;stuff it + sec + sbc #"0" ;convert to numeric + clc + adc zip_mhz ;add to what we got already + sta zip_mhz + +* (this was confusing... the speed bar assumes a max +* speed of x.00, and goes from there. If this showed +* 9.10 MHz and the speed bar said 9.00 MHz, it wouldn't +* make much sense. Easier to omit it than explain it.) +* +* lda :ascii+1,y ;0.1 column +* ora #$80 +* sta statline+8 +* lda :ascii+2,y :0.01 column +* ora #$80 +* sta statline+9 + + rts + +:ascii ds 10 + + +* +* This is the meat of the speed calculator. +* +getspeed + mx %00 ;still 16 bits on entry + stz result + +* I think this forces the card to cache us + ldy #$00ae +:loop lda :loop,y + dey + bne :loop + + php + sei + sep #$30 + mx %11 ;back to 8 bits + +* set up the board and /gs firmware + lda cyareg + sta :save_cya + and #$7f ;set slow mode (!) + sta cyareg + +* lda #$5a +* sta lock +* sta lock +* sta lock +* sta lock + lda #$00 ;FIX me + sta speed ;set 100% speed + sta enable ;enable board + lda options + sta :save_opts + and #$d7 ;disable ext del, CPS + sta options + +* now we do something weird + lda #$80 + sta tag1 ;this is a no-op? + lda #$00 + sta tag1 ; (should clear cshupd) + +* now we get down and get funky + rep #$30 + mx %00 + ldy #$0064 + ldx #$0000 + +:loop1 ldal lock + bpl :loop1 +:loop2 ldal lock + bmi :loop2 +:loop3 ldal lock + bpl :loop3 + +:loop4 txa + ldx #$0005 +:delay1 dex + bne :delay1 + nop + nop + + clc + adc #$0001 + tax + ldal lock + bmi :loop4 + + dey + bne :loop3 + + txa + clc + adc #$0005 + sta result ;ta-da! + + sep #$20 + mx %10 ;short a, long x + lda :save_opts + sta options + lda :save_cya + sta cyareg +* lda #$a5 +* sta lock + rep #$20 + plp + + rts + +:save_cya dfb $00 +:save_opts dfb $00 +result dw $0000 + mx %11 ;short acc/regs after this + + +* +* Variable data stash +* +zip_mhz dfb $00 ;6-10 MHz +zip_cyc dw $0000 ;raw #of cycles counted +zip_enb dfb $00 ;what ENABLE should be +zip_lock dfb $00 ;what LOCK should look like (/16) +zip_slot dfb $00 ;what SLOTENAB should look like +zip_opts dfb $00 ;what OPTIONS should look like +ent_enb dfb $00 ;ENABLE on entry +ent_lock dfb $00 ;LOCK on entry +ent_slot dfb $00 ;SLOTENAB on entry +ent_opts dfb $00 ;OPTIONS on entry + + +******************************** +* * +* Fancy text routines * +* (See FancyText demo for doc) * +* * +******************************** +color_scrn + +* +* Colors are: +* $00 - Black $08 - Brown +* $01 - Deep red $09 - Orange +* $02 - Deep blue $0a - Light gray +* $03 - Purple $0b - Pink +* $04 - Dark green $0c - Green +* $05 - Dark gray $0d - Yellow +* $06 - Medium blue $0e - Aquamarine +* $07 - Light blue $0f - White +* + +* setup + lda #color_data + sta ptr+1 + lda tbcolor ;save current text/background color + pha + + lda clrkbd ;clear keyboard status + +* main loop +:main_loop + lda kbd ;key hit? + bmi :done ;yup, exit + + ldy #$00 + lda (ptr),y ;get count + beq :main_loop ;count == 0, so wait for key + + tax + iny + +* this is important part... shift bits around, +* wait for right time, and change. + +:color_loop + lda (ptr),y ;get color + pha ;save for later + iny + + lda (ptr),y ;get line + asl A ;mult x4 + asl A ;8 lines per, but one bit in horicnt + clc + adc #$80 ;add 128, just because + iny + +:wait_loop ;wait until scanner reaches line + cmp vertcnt ;4 + bne :wait_loop ;3 + + pla ;3 found it, so change color + sta tbcolor ;4 + + dex ;all done with changes? + bne :color_loop ;nope, keep going + +* done with colors, so branch back to top of main loop + bra :main_loop + +* all done, so restore color and exit +:done + pla + sta tbcolor + rts + +* +* Data +* +color_data + dfb $07 ;7 regions +cc1 dfb $00 ;to be set to current color + dfb 0 ;starting at line 0 + dfb $0f ;black text/white bkgnd + dfb 1 ;starting at line 1 + dfb $f6 ;white text/blue bkgnd + dfb 4 ;starting at line 4 + dfb $d6 ;yellow text/blue bkgnd + dfb 17 ;starting at line 9 + dfb $c6 ;green text/blue bkgnd + dfb 18 ;starting at line 18 + dfb $f6 ;white text/blue bkgnd + dfb 19 ;starting at line 19 +cc2 dfb $00 ;current color + dfb 23 ;line 23 (one up from bottom) + +* +* Save the object file +* + lst on + typ $ff ;make it a SYS file + sav ZIPPY + lst off ;suppress symbol table + + + END ;ignore everything else + +* +* Zip GS tech info +* +* I got this from David Empson, who get it from Zip Technologies. +* + +* ZipChip GS Special Registers Ex ZIP Technology, 12 October 1990 +* +* Registers must be unlocked before they can be accessed (see $C05A). +* Locking them will re-enable the annunciators. +* +* Writing to any I/O location $C058-$C05F (whether registers are locked or +* unlocked) will reset delay in progress. +* +* $C058 R No operation +* +* $C058 W Write any value to force poweron/reset bit to COLD (forces next +* reset to restore ZIP registers to defaults/switch settings). +* +* $C059 R/W 76543210 +* *....... Bank Switch Language Card cache disable=1/enable=0? +* .*...... Paddle delay (5 ms) disable=0/enable=1 $C070/$C020 +* ..*..... External delay (5 ms) disable=0/enable=1 +* ...*.... Counter delay (5 ms) disable=0/enable=1 $C02E/$C07E +* ....*... CPS follow disable=0/enable=1 +* .....*.. Last Reset warm? READ ONLY +* ......*. Hardware DMA READ ONLY +* .......* non-GS (0)/GS (1) READ ONLY +* +* $C05A R 76543210 +* ****.... Current ZIP Speed, 0=100%, F=6.25%, in 6.25% increments +* ....1111 +* +* $C05A W Write values as follows: +* $5x Unlock ZIP registers (must write 4 times) +* $Ax Lock ZIP registers +* other Force ZIP to follow system clock (i.e. disable card) +* +* $C05B R 76543210 +* *....... 1msclk - clock with 1 ms period +* .*...... cshupd - Tag data at $C05F updated (read $C05F to reset) +* ..*..... Bank Switch Language Card cache (0), don't (1) +* ...*.... Board disable - 0=enabled, 1=disabled +* ....*... delay in effect (0=ZIP, 1=Slow) +* .....*.. rombank (0/1) - not in development version +* ......** Cache RAM size (00=8k, 01=16k, 10=32k, 11=64k) +* +* $C05B W Write any value to force ZIP to current speed (i.e. enable card) +* +* $C05C R/W 76543210 +* *******. Slot 7-1 delay enable (all slots 52-54 ms) +* .......* Speaker delay enable (5 ms) +* +* $C05D R Current 65816 bank +* +* $C05D W 76543210 +* ****.... Set ZIP speed, 0=100%, F=6.25%, in 6.25% increments +* ....**** Don't care +* +* $C05E R Read last Tag data written and force the next write to +* create a trash tag value. +* +* $C05E W No operation +* +* $C05F R Read last Tag data written and reset cshupd. Note: apparently +* any write to a ZIP register (unlocked) will clear cshupd, but cshupd says +* that this location must be read. +* +* $C05F W No operation diff --git a/SourceGen/Examples/A2-lz4fh/LZ4FH6502#060300 b/SourceGen/Examples/A2-lz4fh/LZ4FH6502#060300 new file mode 100644 index 0000000..16a6aa1 Binary files /dev/null and b/SourceGen/Examples/A2-lz4fh/LZ4FH6502#060300 differ diff --git a/SourceGen/Examples/A2-lz4fh/LZ4FH6502#060300.dis65 b/SourceGen/Examples/A2-lz4fh/LZ4FH6502#060300.dis65 new file mode 100644 index 0000000..8aea974 --- /dev/null +++ b/SourceGen/Examples/A2-lz4fh/LZ4FH6502#060300.dis65 @@ -0,0 +1,166 @@ +### 6502bench SourceGen dis65 v1.0 ### +{ +"_ContentVersion":1,"FileDataLength":194,"FileDataCrc32":177347202,"ProjectProps":{ +"CpuName":"6502","IncludeUndocumentedInstr":false,"EntryFlags":32702671,"AnalysisParams":{ +"AnalyzeUncategorizedData":true,"MinCharsForString":4,"SeekNearbyTargets":true}, +"PlatformSymbolFileIdentifiers":["RT:Apple/F8-ROM.sym65"],"ExtensionScriptFileIdentifiers":[],"ProjectSyms":{ +"A1H":{ +"DataDescriptor":{ +"Length":1,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"Comment":"hide platform def","Label":"A1H","Value":-1,"Source":"Project","Type":"Constant"}, +"copyptr":{ +"DataDescriptor":{ +"Length":1,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"Comment":"2b","Label":"copyptr","Value":0,"Source":"Project","Type":"ExternalAddr"}, +"dstptr":{ +"DataDescriptor":{ +"Length":1,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"Comment":"2b A1H","Label":"dstptr","Value":62,"Source":"Project","Type":"ExternalAddr"}, +"in_dst":{ +"DataDescriptor":{ +"Length":1,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"Comment":"","Label":"in_dst","Value":766,"Source":"Project","Type":"ExternalAddr"}, +"in_src":{ +"DataDescriptor":{ +"Length":1,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"Comment":"","Label":"in_src","Value":764,"Source":"Project","Type":"ExternalAddr"}, +"lz4fh_magic":{ +"DataDescriptor":{ +"Length":1,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"Comment":"ascii \u0027f\u0027","Label":"lz4fh_magic","Value":102,"Source":"Project","Type":"Constant"}, +"savlen":{ +"DataDescriptor":{ +"Length":1,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"Comment":"1b","Label":"savlen","Value":3,"Source":"Project","Type":"ExternalAddr"}, +"savmix":{ +"DataDescriptor":{ +"Length":1,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"Comment":"1b","Label":"savmix","Value":2,"Source":"Project","Type":"ExternalAddr"}, +"srcptr":{ +"DataDescriptor":{ +"Length":1,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"Comment":"2b A1L","Label":"srcptr","Value":60,"Source":"Project","Type":"ExternalAddr"}, +"tok_empty":{ +"DataDescriptor":{ +"Length":1,"Format":"NumericLE","SubFormat":"Decimal","SymbolRef":null}, +"Comment":"","Label":"tok_empty","Value":253,"Source":"Project","Type":"Constant"}, +"tok_eod":{ +"DataDescriptor":{ +"Length":1,"Format":"NumericLE","SubFormat":"Decimal","SymbolRef":null}, +"Comment":"","Label":"tok_eod","Value":254,"Source":"Project","Type":"Constant"}}}, +"AddressMap":[{ +"Offset":0,"Addr":768}],"TypeHints":[{ +"Low":0,"High":0,"Hint":"Code"}],"StatusFlagOverrides":{ +"39":2,"48":2}, +"Comments":{ +"0":"copy source address to zero page","15":"copy destination address to zero page","27":"does magic match?","54":"success!","83":"get mixed-length byte","87":"get the literal length","93":"sets carry for \u003e= 15","101":"get length extension","103":"(carry set) add 15 - will not exceed 255","109":"if len is 255, copy 0-254","112":"16 cycles/byte","115":"this gets us the +1","122":"carry cleared by hi3","129":"Y=0; DEY so next INY goes to 0","139":"get length extension","141":"\"normal\" values are 0-236","145":"will not exceed 255","147":"min match; won\u0027t exceed 255","149":"save match len for later","151":"and keep it in X","153":"match offset, lo","158":"match offset, hi","160":"OR in hi-res page","172":"hi5 clears carry","180":"18 cycles/byte"}, +"LongComments":{ +"-2147483647":{ +"Text":"LZ4FH uncompression for 6502\r\nBy Andy McFadden\r\nVersion 1.0.1, August 2015\r\n*\r\nRefactored for size \u0026 speed by Peter Ferrie.\r\n","BoxMode":true,"MaxWidth":40,"BackgroundColor":0}, +"0":{ +"Text":"\r\nThe parameters (in_src and in_dst) are stashed at the top of the text input buffer. We use this, rather than having them poked directly into the code, so that the 6502 and 65816 implementations work the same way without either getting weird.\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"37":{ +"Text":"\r\nThese stubs increment the high byte and then jump back. This saves a cycle because branch-not-taken becomes the common case. We assume that we\u0027re not unpacking data at $FFxx, so BNE is branch-always.\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"55":{ +"Text":"Handle \"special\" match values (value in A)\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"81":{ +"Text":"Get the mixed-length byte and handle the literal.\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"105":{ +"Text":"\r\nAt this point, srcptr holds the address of the \"mix\" word or the length extension, and dstptr holds the address of the next output location. So we want to read from (srcptr),y+1 and write to (dstptr),y. We can do this by sticking the DEY between the LDA and STA.\r\n\r\nWe could save a couple of cycles by substituting addr,y in place of (dp),y, but the added setup cost would only benefit longer literal strings.\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"114":{ +"Text":"Advance srcptr by savlen+1, and dstptr by savlen\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"130":{ +"Text":"\r\nHandle match. Y holds an offset into srcptr such taht we need to increment it once to get the next interesting byte.\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"147":{ +"Text":"Put the destination address into copyptr.\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"164":{ +"Text":"\r\nAdvance srcptr past the encoded match while we still remember how many bytes it took to encode. Y is indexing the last value used, so we want to go advance srcptr by Y+1.\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"172":{ +"Text":"\r\nCopy the match. The length is in X. Note this must be a forward copy so overlapped data works.\r\n\r\nWe know the match is at least 4 bytes long, so we could save a few cycles by not doing the ADC #4 earlier, and unrolling the first 4 load/store operations here.\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"182":{ +"Text":"Advance dstptr past copied data.","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}}, +"Notes":{ +}, +"UserLabels":{ +"0":{ +"Label":"entry","Value":768,"Source":"User","Type":"LocalOrGlobalAddr"}, +"31":{ +"Label":"fail","Value":799,"Source":"User","Type":"LocalOrGlobalAddr"}, +"75":{ +"Label":"goodmagic","Value":843,"Source":"User","Type":"LocalOrGlobalAddr"}, +"37":{ +"Label":"hi2","Value":805,"Source":"User","Type":"LocalOrGlobalAddr"}, +"41":{ +"Label":"hi3","Value":809,"Source":"User","Type":"LocalOrGlobalAddr"}, +"46":{ +"Label":"hi4","Value":814,"Source":"User","Type":"LocalOrGlobalAddr"}, +"50":{ +"Label":"notempty","Value":818,"Source":"User","Type":"LocalOrGlobalAddr"}, +"55":{ +"Label":"specialmatch","Value":823,"Source":"User","Type":"LocalOrGlobalAddr"}, +"101":{ +"Label":"nohi2","Value":869,"Source":"User","Type":"LocalOrGlobalAddr"}, +"122":{ +"Label":"nohi3","Value":890,"Source":"User","Type":"LocalOrGlobalAddr"}, +"129":{ +"Label":"nohi4","Value":897,"Source":"User","Type":"LocalOrGlobalAddr"}, +"81":{ +"Label":"mainloop","Value":849,"Source":"User","Type":"LocalOrGlobalAddr"}, +"70":{ +"Label":"hi5","Value":838,"Source":"User","Type":"LocalOrGlobalAddr"}, +"172":{ +"Label":"nohi5","Value":940,"Source":"User","Type":"LocalOrGlobalAddr"}, +"130":{ +"Label":"noliteral","Value":898,"Source":"User","Type":"LocalOrGlobalAddr"}, +"105":{ +"Label":"shortlit","Value":873,"Source":"User","Type":"LocalOrGlobalAddr"}, +"107":{ +"Label":"litloop","Value":875,"Source":"User","Type":"LocalOrGlobalAddr"}, +"147":{ +"Label":"shortmatch","Value":915,"Source":"User","Type":"LocalOrGlobalAddr"}, +"160":{ +"Label":"_desthi","Value":928,"Source":"User","Type":"LocalOrGlobalAddr"}, +"174":{ +"Label":"copyloop","Value":942,"Source":"User","Type":"LocalOrGlobalAddr"}}, +"OperandFormats":{ +"27":{ +"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"lz4fh_magic","Part":"Low"}}, +"37":{ +"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"srcptr","Part":"Low"}}, +"41":{ +"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"srcptr","Part":"Low"}}, +"46":{ +"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"dstptr","Part":"Low"}}, +"50":{ +"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"tok_eod","Part":"Low"}}, +"55":{ +"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"tok_empty","Part":"Low"}}, +"66":{ +"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"srcptr","Part":"Low"}}, +"70":{ +"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"srcptr","Part":"Low"}}, +"79":{ +"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"srcptr","Part":"Low"}}, +"103":{ +"Length":2,"Format":"NumericLE","SubFormat":"Decimal","SymbolRef":null}, +"141":{ +"Length":2,"Format":"NumericLE","SubFormat":"Decimal","SymbolRef":null}, +"145":{ +"Length":2,"Format":"NumericLE","SubFormat":"Decimal","SymbolRef":null}, +"147":{ +"Length":2,"Format":"NumericLE","SubFormat":"Decimal","SymbolRef":null}, +"162":{ +"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"copyptr","Part":"Low"}}, +"190":{ +"Length":2,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"dstptr","Part":"Low"}}}} diff --git a/SourceGen/Examples/A2-lz4fh/LZ4FH6502.S.txt b/SourceGen/Examples/A2-lz4fh/LZ4FH6502.S.txt new file mode 100644 index 0000000..928caf2 --- /dev/null +++ b/SourceGen/Examples/A2-lz4fh/LZ4FH6502.S.txt @@ -0,0 +1,242 @@ +******************************** +* * +* LZ4FH uncompression for 6502 * +* By Andy McFadden * +* Version 1.0.1, August 2015 * +* * +* Refactored for size & speed * +* by Peter Ferrie. * +* * +* Developed with Merlin-16 * +* * +******************************** + lst off + org $0300 + +* +* Constants +* +lz4fh_magic equ $66 ;ascii 'f' +tok_empty equ 253 +tok_eod equ 254 + +overrun_check equ 0 + +* +* Variable storage +* +srcptr equ $3c ;2b a1l +dstptr equ $3e ;2b a1h +copyptr equ $00 ;2b +savmix equ $02 ;1b +savlen equ $03 ;1b + +* +* ROM routines +* +bell equ $ff3a +monitor equ $ff69 + +* +* Parameters, stashed at the top of the text input +* buffer. We use this, rather than just having them +* poked directly into the code, so that the 6502 and +* 65816 implementations work the same way without +* either getting weird. +* +in_src equ $2fc ;2b +in_dst equ $2fe ;2b + +entry + lda in_src ;copy source address to zero page + sta srcptr + lda in_src+1 + sta srcptr+1 + lda in_dst ;copy destination address to zero page + sta dstptr + lda in_dst+1 + sta dstptr+1 + sta _desthi+1 + + ldy #$00 + lda (srcptr),y + cmp #lz4fh_magic ;does magic match? + beq goodmagic + +fail + jsr bell + jmp monitor + +* These stubs increment the high byte and then jump +* back. This saves a cycle because branch-not-taken +* becomes the common case. We assume that we're not +* unpacking data at $FFxx, so BNE is branch-always. +hi2 + inc srcptr+1 + bne nohi2 + +hi3 + inc srcptr+1 + clc + bcc nohi3 + +hi4 + inc dstptr+1 + bne nohi4 + +notempty + cmp #tok_eod + bne fail + rts ;success! + +* handle "special" match values (value in A) +specialmatch + cmp #tok_empty + bne notempty + + tya ;empty match, advance srcptr + adc srcptr ; past and jump to main loop + sta srcptr + bcc mainloop + inc srcptr+1 + bne mainloop + +hi5 + inc srcptr+1 + clc + bcc nohi5 + +goodmagic + inc srcptr + bne mainloop + inc srcptr+1 + +mainloop +* Get the mixed-length byte and handle the literal. + ldy #$00 + lda (srcptr),y ;get mixed-length byte + sta savmix + lsr A ;get the literal length + lsr A + lsr A + lsr A + beq noliteral + cmp #$0f ;sets carry for >= 15 + bne shortlit + + inc srcptr + beq hi2 +nohi2 + lda (srcptr),y ;get length extension + adc #14 ;(carry set) add 15 - will not exceed 255 + +* At this point, srcptr holds the address of the "mix" +* word or the length extension, and dstptr holds the +* address of the next output location. So we want to +* read from (srcptr),y+1 and write to (dstptr),y. +* We can do this by sticking the DEY between the LDA +* and STA. +* +* We could save a couple of cycles by substituting +* addr,y in place of (dp),y, but the added setup cost +* would only benefit longer literal strings. +shortlit tax + tay +:litloop + lda (srcptr),y ;5 + dey ;2 if len is 255, copy 0-254 + sta (dstptr),y ;6 + bne :litloop ;3 -> 16 cycles/byte + +* Advance srcptr by savlen+1, and dstptr by savlen + txa + sec ;this gets us the +1 + adc srcptr + sta srcptr + bcs hi3 +nohi3 ;carry cleared by hi3 + txa + adc dstptr + sta dstptr + bcs hi4 +nohi4 + dey ;Y=0; DEY so next INY goes to 0 + +* Handle match. Y holds an offset into srcptr such +* that we need to increment it once to get the next +* interesting byte. +noliteral + lda savmix + and #$0f + cmp #$0f + blt :shortmatch ;BCC + + iny + lda (srcptr),y ;get length extension + cmp #237 ;"normal" values are 0-236 + bge specialmatch ;BCS + adc #15 ;will not exceed 255 + +* Put the destination address into copyptr. +:shortmatch + adc #4 ;min match; won't exceed 255 + sta savlen ;save match len for later + tax ;and keep it in X + iny + lda (srcptr),y ;match offset, lo + sta copyptr + iny + lda (srcptr),y ;match offset, hi +_desthi ora #$00 ;OR in hi-res page + sta copyptr+1 + +* Advance srcptr past the encoded match while we still +* remember how many bytes it took to encode. Y is +* indexing the last value used, so we want to go +* advance srcptr by Y+1. + + tya + sec + adc srcptr + sta srcptr + bcs hi5 +nohi5 ;hi5 clears carry + +* Copy the match. The length is in X. Note this +* must be a forward copy so overlapped data works. +* +* We know the match is at least 4 bytes long, so +* we could save a few cycles by not doing the +* ADC #4 earlier, and unrolling the first 4 +* load/store operations here. + ldy #$00 +:copyloop + lda (copyptr),y ;5 + sta (dstptr),y ;6 + iny ;2 + dex ;2 + bne :copyloop ;3 -> 18 cycles/byte + +* advance dstptr past copied data + lda dstptr + adc savlen ;carry is clear + sta dstptr + bcc mainloop + inc dstptr+1 + + DO overrun_check + LDA dstptr+1 + CMP #$60 + bcc mainloop + BRK + BRK + + ELSE + + bne mainloop ;always (not unpacking at $FFxx) + + FIN + + lst on + sav LZ4FH6502 + lst off diff --git a/SourceGen/Examples/A2-lz4fh/LZ4FH65816#060300 b/SourceGen/Examples/A2-lz4fh/LZ4FH65816#060300 new file mode 100644 index 0000000..770258c Binary files /dev/null and b/SourceGen/Examples/A2-lz4fh/LZ4FH65816#060300 differ diff --git a/SourceGen/Examples/A2-lz4fh/LZ4FH65816#060300.dis65 b/SourceGen/Examples/A2-lz4fh/LZ4FH65816#060300.dis65 new file mode 100644 index 0000000..296924f --- /dev/null +++ b/SourceGen/Examples/A2-lz4fh/LZ4FH65816#060300.dis65 @@ -0,0 +1,98 @@ +### 6502bench SourceGen dis65 v1.0 ### +{ +"_ContentVersion":1,"FileDataLength":121,"FileDataCrc32":657890661,"ProjectProps":{ +"CpuName":"65816","IncludeUndocumentedInstr":false,"EntryFlags":32702671,"AnalysisParams":{ +"AnalyzeUncategorizedData":true,"MinCharsForString":4,"SeekNearbyTargets":true}, +"PlatformSymbolFileIdentifiers":["RT:Apple/F8-ROM.sym65"],"ExtensionScriptFileIdentifiers":[],"ProjectSyms":{ +"in_dst":{ +"DataDescriptor":{ +"Length":1,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"Comment":"","Label":"in_dst","Value":766,"Source":"Project","Type":"ExternalAddr"}, +"in_src":{ +"DataDescriptor":{ +"Length":1,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"Comment":"","Label":"in_src","Value":764,"Source":"Project","Type":"ExternalAddr"}, +"lz4fh_magic":{ +"DataDescriptor":{ +"Length":1,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"Comment":"ascii \u0027f\u0027","Label":"lz4fh_magic","Value":102,"Source":"Project","Type":"Constant"}, +"savlen":{ +"DataDescriptor":{ +"Length":1,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"Comment":"2b","Label":"savlen","Value":2,"Source":"Project","Type":"ExternalAddr"}, +"savmix":{ +"DataDescriptor":{ +"Length":1,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"Comment":"2b","Label":"savmix","Value":0,"Source":"Project","Type":"ExternalAddr"}, +"tok_empty":{ +"DataDescriptor":{ +"Length":1,"Format":"NumericLE","SubFormat":"Decimal","SymbolRef":null}, +"Comment":"","Label":"tok_empty","Value":253,"Source":"Project","Type":"Constant"}, +"tok_eod":{ +"DataDescriptor":{ +"Length":1,"Format":"NumericLE","SubFormat":"Decimal","SymbolRef":null}, +"Comment":"","Label":"tok_eod","Value":254,"Source":"Project","Type":"Constant"}}}, +"AddressMap":[{ +"Offset":0,"Addr":768}],"TypeHints":[{ +"Low":0,"High":0,"Hint":"Code"}],"StatusFlagOverrides":{ +}, +"Comments":{ +"0":"go native","2":"16-bit acc/index","30":"end-of-data or error?","33":"return to emulation mode","62":"length \u003e= 15, get next","68":"(carry set) + 15 - won\u0027t exceed 255","71":"MVN wants length-1","85":"add length extension","91":"\"normal\" values are 0-236","96":"carry clear; won\u0027t exceed 255","99":"min match, -1 for MVN","102":"spill A while we get offset","104":"load source buffer offset","108":"save srcptr for later","109":"OR in hi-res page","118":"restore srcptr"}, +"LongComments":{ +"-2147483647":{ +"Text":"LZ4FH uncompression for 65816\r\nBy Andy McFadden\r\nVersion 1.0.1, August 2015\r\n*\r\nRefactored for size \u0026 speed by Peter Ferrie.","BoxMode":true,"MaxWidth":40,"BackgroundColor":0}, +"38":{ +"Text":"Handle \"special\" match length values (in A)\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"71":{ +"Text":"\r\nAt this point, Y holds the address of the next compressed data byte, X has the address of the next output position, and A has the length of the literal.\r\n\r\nThe MVN instruction moves (A+1) bytes from X to Y, advancing X and Y.\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}, +"75":{ +"Text":"Now handle the match.\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}}, +"Notes":{ +}, +"UserLabels":{ +"0":{ +"Label":"entry","Value":768,"Source":"User","Type":"GlobalAddr"}, +"109":{ +"Label":"_dstmod","Value":877,"Source":"User","Type":"LocalOrGlobalAddr"}, +"43":{ +"Label":"mainloop","Value":811,"Source":"User","Type":"LocalOrGlobalAddr"}, +"24":{ +"Label":"fail","Value":792,"Source":"User","Type":"LocalOrGlobalAddr"}, +"30":{ +"Label":"notempty","Value":798,"Source":"User","Type":"LocalOrGlobalAddr"}, +"38":{ +"Label":"specialmatch","Value":806,"Source":"User","Type":"LocalOrGlobalAddr"}, +"71":{ +"Label":"shortlit","Value":839,"Source":"User","Type":"LocalOrGlobalAddr"}, +"75":{ +"Label":"noliteral","Value":843,"Source":"User","Type":"LocalOrGlobalAddr"}, +"99":{ +"Label":"shortmatch","Value":867,"Source":"User","Type":"LocalOrGlobalAddr"}}, +"OperandFormats":{ +"13":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"19":{ +"Length":3,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"lz4fh_magic","Part":"Low"}}, +"30":{ +"Length":3,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"tok_eod","Part":"Low"}}, +"38":{ +"Length":3,"Format":"NumericLE","SubFormat":"Symbol","SymbolRef":{ +"Label":"tok_empty","Part":"Low"}}, +"43":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"62":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"68":{ +"Length":3,"Format":"NumericLE","SubFormat":"Decimal","SymbolRef":null}, +"85":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}, +"91":{ +"Length":3,"Format":"NumericLE","SubFormat":"Decimal","SymbolRef":null}, +"96":{ +"Length":3,"Format":"NumericLE","SubFormat":"Decimal","SymbolRef":null}, +"99":{ +"Length":3,"Format":"NumericLE","SubFormat":"Decimal","SymbolRef":null}, +"104":{ +"Length":2,"Format":"NumericLE","SubFormat":"Hex","SymbolRef":null}}} diff --git a/SourceGen/Examples/A2-lz4fh/LZ4FH65816.S.txt b/SourceGen/Examples/A2-lz4fh/LZ4FH65816.S.txt new file mode 100644 index 0000000..72569f6 --- /dev/null +++ b/SourceGen/Examples/A2-lz4fh/LZ4FH65816.S.txt @@ -0,0 +1,142 @@ +********************************* +* * +* LZ4FH uncompression for 65816 * +* By Andy McFadden * +* Version 1.0.1, August 2015 * +* * +* Refactored for size & speed * +* by Peter Ferrie. * +* * +* Developed with Merlin-16 * +* * +********************************* + lst off + org $0300 + + xc ;allow 65c02 opcodes + xc ;allow 65816 opcodes + +* +* Constants +* +lz4fh_magic equ $66 ;ascii 'f' +tok_empty equ 253 +tok_eod equ 254 + +* +* Variable storage +* +savmix equ $00 ;2b +savlen equ $02 ;2b + +* +* ROM routines +* +bell equ $ff3a +monitor equ $ff69 + +* +* Parameters. +* +* in_dst must be $2000 or $4000 +* +in_src equ $2fc ;2b +in_dst equ $2fe ;2b + +* Main entry point. +entry + clc ;go native + xce + rep #$30 ;16-bit acc/index + mx %00 ; tell Merlin + + ldx in_src + ldy in_dst + sty _dstmod+1 + + lda $0000,x + inx + and #$00ff + cmp #lz4fh_magic + beq mainloop + +fail + jsr bell + jmp monitor + +notempty + cmp #tok_eod ;end-of-data or error + +* exit + sec ;return to emulation mode + xce + bne fail + rts + + mx %00 ;undo the sec/xce + +* handle "special" match length values (in A) +specialmatch + cmp #tok_empty + bne notempty + +mainloop + lda $0000,x + inx + sta savmix + and #$00f0 + beq noliteral + lsr A + lsr A + lsr A + lsr A + cmp #$000f + bne shortlit + + lda $0000,x ;length >= 15, get next + inx + and #$00ff + adc #14 ;(carry set) +15 - won't exceed 255 + +* At this point, Y holds the address of the next +* compressed data byte, X has the address of the +* next output position, and A has the length of +* the literal. +* +* The MVN instruction moves (A+1) bytes from X +* to Y, advancing X and Y. +shortlit + dec A ;MVN wants length-1 + mvn $00,$00 ;7 cycles/byte + +* Now handle the match. +noliteral + lda savmix + and #$000f + cmp #$000f + blt :shortmatch ;BCC + + lda $0000,x ;add length extension + inx + and #$00ff + cmp #237 ;"normal" values are 0-236 + bge specialmatch + adc #15 ;carry clear; won't exceed 255 +:shortmatch + adc #3 ;min match, -1 for MVN + sta savlen ;spill A while we get offset + + lda $0000,x ;load source buffer offset + inx + inx + phx ;save srcptr for later +_dstmod ora #$ff00 ;OR in hi-res page + tax + lda savlen + mvn $00,$00 + plx ;restore srcptr + bra mainloop + + lst on + sav LZ4FH65816 + lst off diff --git a/SourceGen/Examples/README.md b/SourceGen/Examples/README.md new file mode 100644 index 0000000..2e8664e --- /dev/null +++ b/SourceGen/Examples/README.md @@ -0,0 +1,28 @@ +# SourceGen Examples # + +These are some sample projects you can play with. The binaries are +accompanied by the original source code, so you can compare the SourceGen +project to the original. + + * Tutorial: a simple project, intended for use with the tutorial in + the manual. + * A2-lz4fh: two functions for unpacking a simplified form of LZ4 compression. + One is 6502, the other is 65816. + [(Full project)](https://github.com/fadden/fhpack) + * A2-Amper-fdraw: 6502 code that provides an Applesoft BASIC interface + to a machine-language graphics library. The public interface of the + graphics library is defined in a .sym65 file. + [(Full project)](https://github.com/fadden/fdraw) + * A2-HP-CDA: HardPressed Classic Desk Accessory. This is 65816 code + in OMF loader format, which SourceGen doesn't support, so it's a little + rough. + [(Full project)](https://fadden.com/apple2/hardpressed.html) + * A2-Zippy: a program for controlling an Apple IIgs CPU accelerator card. + 65816 sources, with a little bit of ProDOS 8 and IIgs toolbox usage. + [(Full project)](https://fadden.com/apple2/misc.html#zippy) + +(You may be wondering why some of the example files have filenames with +things like "#061d60" in them. It's a method of preserving the file type +for Apple II files used by some utilities. The potential advantage here +for SourceGen is that the file type often determines the load address, +possibly removing some initial guesswork.) diff --git a/SourceGen/Examples/Tutorial/Tutorial1 b/SourceGen/Examples/Tutorial/Tutorial1 new file mode 100644 index 0000000..79009e8 Binary files /dev/null and b/SourceGen/Examples/Tutorial/Tutorial1 differ diff --git a/SourceGen/Examples/Tutorial/Tutorial1.S b/SourceGen/Examples/Tutorial/Tutorial1.S new file mode 100644 index 0000000..dda99b7 --- /dev/null +++ b/SourceGen/Examples/Tutorial/Tutorial1.S @@ -0,0 +1,62 @@ +; Copyright 2018 faddenSoft. All Rights Reserved. +; See the LICENSE.txt file for distribution terms (Apache 2.0). +; +; Assembler: Merlin 32 + +INPUT equ $3000 +OUTPUT equ $0400 + + org $1000 + + ldy #END-AFTER +copy lda BEFORE,y + sta AFTER,y + dey + bmi done + bpl copy + + dfb $00 +stuff asc 'hello!' + +done jmp AFTER + +BEFORE + org $2000 +AFTER + + lda INPUT ;expecting 0-3 + cmp #4 + blt :valid + lda #4 ;error message +:valid asl A + tax + lda stringtab,x ;set load to address + sta _load+1 + lda stringtab+1,x + sta _load+2 + + ldy #12 ;fixed-width strings +_load lda $0000,y ;self-modifying code + ora #$80 + sta OUTPUT,y + dey + bpl _load + + rts + +stringtab + dw string0 + dw string1 + dw string2 + dw string3 + dw stringX + dfb $00 + +string0 asc 'string zero ' +string1 asc 'string one ' +string2 asc 'string two ' +string3 asc 'string three ' +stringX asc 'invalid index' + +END + diff --git a/SourceGen/ExternalFile.cs b/SourceGen/ExternalFile.cs new file mode 100644 index 0000000..6dc483f --- /dev/null +++ b/SourceGen/ExternalFile.cs @@ -0,0 +1,264 @@ +/* + * Copyright 2018 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.Diagnostics; +using System.IO; + +namespace SourceGen { + /// + /// Manages references to external files, notably symbol files (.sym65) and extension + /// scripts. Identifiers look like "RT:subdir/file.sym65". + /// + /// Instances are immutable. + /// + public class ExternalFile { + private const string INVALID_IDENT = "!!!INVALID!!!"; // probably don't need localization + + /// + /// Pathname separator character for use for file identifiers. We want this + /// to be the same on all platforms, with local conversion, so we should probably be + /// using something like ':' that makes Windows barf. In practice, being rigorous + /// doesn't seem important, and '/' is pretty universal these days. Just don't do \. + /// + private const char PATH_SEP_CHAR = '/'; + + private const string RUNTIME_DIR_PREFIX = "RT:"; + private const string PROJECT_DIR_PREFIX = "PROJ:"; + + private enum Location { + Unknown = 0, + RuntimeDir, + ProjectDir + } + + // Not sure there's value in tracking the type, except for some validation checks. + private enum Type { + Unknown = 0, + SymbolFile, + ExtensionScript + } + + /// + /// Identifier for this file. + /// + public string Identifier { get { return mIdent; } } + private string mIdent; + + /// + /// File location. + /// + private Location mIdentLocation; + + /// + /// File type. + /// + private Type mIdentType; + + /// + /// Identifier without location prefix or filename extension. + /// + private string mInnards; + + + /// + /// Creates a new ExternalFile instance from the identifier. + /// + public static ExternalFile CreateFromIdent(string ident) { + if (!DecodeIdent(ident, out Location identLocation, out Type identType, + out string innards)) { + return null; + } + return new ExternalFile(ident, identLocation, identType, innards); + } + + /// + /// Creates a new ExternalFile instance from a full path. + /// + /// Full path of external file, in canonical + /// form. + /// Full path to directory in which project file lives, in + /// canonical form. If the project hasn't been saved yet, pass an empty string. + /// New object, or null if the path isn't valid. + public static ExternalFile CreateFromPath(string pathName, string projectDir) { + string stripDir; + + string rtDir = RuntimeDataAccess.GetDirectory(); + string prefix; + + // Check path prefix for RT:, and full directory name for PROJ:. + if (pathName.StartsWith(rtDir)) { + stripDir = rtDir; + prefix = RUNTIME_DIR_PREFIX; + } else if (!string.IsNullOrEmpty(projectDir) && + Path.GetDirectoryName(pathName) == projectDir) { + stripDir = projectDir; + prefix = PROJECT_DIR_PREFIX; + } else { + Debug.WriteLine("Path not in RuntimeData or project: " + pathName); + return null; + } + + // Remove directory component. + string partialPath = pathName.Substring(stripDir.Length); + + // If directory string didn't end with '/' or '\\', remove char from start. + if (partialPath[0] == '\\' || partialPath[0] == '/') { + partialPath = partialPath.Substring(1); + } + + // Replace canonical path sep with '/'. + partialPath = partialPath.Replace(Path.DirectorySeparatorChar, PATH_SEP_CHAR); + + string ident = prefix + partialPath; + Debug.WriteLine("Converted path '" + pathName + "' to ident '" + ident + "'"); + return CreateFromIdent(ident); + } + + /// + /// Internal constructor. + /// + private ExternalFile(string ident, Location identLocation, Type identType, + string innards) { + mIdent = ident; + mIdentLocation = identLocation; + mIdentType = identType; + mInnards = innards; + } + + /// + /// Decodes an ident string into its constituent parts. + /// + private static bool DecodeIdent(string ident, out Location identLocation, + out Type identType, out string innards) { + identLocation = Location.Unknown; + identType = Type.Unknown; + innards = string.Empty; + + int prefixLen; + if (ident.StartsWith(RUNTIME_DIR_PREFIX)) { + identLocation = Location.RuntimeDir; + prefixLen = RUNTIME_DIR_PREFIX.Length; + } else if (ident.StartsWith(PROJECT_DIR_PREFIX)) { + identLocation = Location.ProjectDir; + prefixLen = PROJECT_DIR_PREFIX.Length; + } else { + return false; + } + + int extLen; + if (ident.EndsWith(PlatformSymbols.FILENAME_EXT)) { + identType = Type.SymbolFile; + extLen = PlatformSymbols.FILENAME_EXT.Length; + } else if (ident.EndsWith(Sandbox.ScriptManager.FILENAME_EXT)) { + identType = Type.ExtensionScript; + extLen = Sandbox.ScriptManager.FILENAME_EXT.Length; + } else { + return false; + } + + // Fail idents with no actual name, e.g. "RT:.cs". + if (ident.Length == prefixLen + extLen) { + return false; + } + + innards = ident.Substring(prefixLen, ident.Length - prefixLen - extLen); + return true; + } + + /// + /// Strips the prefix and filename extension off of an identifier. + /// + /// Stripped identifier, or null if the identifier was malformed. + public string GetInnards() { + return mInnards; + } + + /// + /// Converts an identifier to a full path. For PROJ: identifiers, the project + /// directory argument is used. + /// + /// Identifier to convert. + /// Full path to directory in which project file lives, in + /// canonical form. If the project hasn't been saved yet, pass an empty string. + /// Full path, or null if the identifier points to a file outside the + /// directory, or if this is a ProjectDir ident and the project dir isn't set. + public string GetPathName(string projectDir) { + string dir; + + bool subdirAllowed; + switch (mIdentLocation) { + case Location.RuntimeDir: + dir = RuntimeDataAccess.GetDirectory(); + subdirAllowed = true; + break; + case Location.ProjectDir: + if (string.IsNullOrEmpty(projectDir)) { + // Shouldn't happen in practice -- we don't create PROJ: identifiers + // unless a project directory has been established. + Debug.Assert(false); + return null; + } + dir = projectDir; + subdirAllowed = false; + break; + default: + Debug.Assert(false); + return null; + } + + int extLen = mIdent.IndexOf(':') + 1; + string fullPath = Path.GetFullPath(Path.Combine(dir, mIdent.Substring(extLen))); + + // Confirm the file actually lives in the directory. RT: files can be anywhere + // below the RuntimeData directory, while PROJ: files must live in the project + // directory. + if (subdirAllowed) { + dir += Path.DirectorySeparatorChar; + if (!fullPath.StartsWith(dir)) { + Debug.WriteLine("WARNING: ident resolves outside subdir: " + mIdent); + Debug.Assert(false); + return null; + } + } else { + if (dir != Path.GetDirectoryName(fullPath)) { + Debug.WriteLine("WARNING: ident resolves outside dir: " + mIdent); + return null; + } + } + + return fullPath; + } + + /// + /// Generates a script DLL name from the ident. If the ident is for a project-scope + /// extension script, the project's file name will be included. + /// + /// Full path to project. + /// DLL filename. + public string GenerateDllName(string projectFileName) { + switch (mIdentLocation) { + case Location.RuntimeDir: + return "RT_" + mInnards.Replace(PATH_SEP_CHAR, '_') + ".dll"; + case Location.ProjectDir: + string noExt = Path.GetFileNameWithoutExtension(projectFileName); + return "PROJ_" + noExt + "_" + mInnards.Replace(PATH_SEP_CHAR, '_') + ".dll"; + default: + Debug.Assert(false); + return null; + } + } + } +} diff --git a/SourceGen/FormatDescriptor.cs b/SourceGen/FormatDescriptor.cs new file mode 100644 index 0000000..af5d440 --- /dev/null +++ b/SourceGen/FormatDescriptor.cs @@ -0,0 +1,423 @@ +/* + * Copyright 2018 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; + +namespace SourceGen { + /// + /// Format descriptor for data items and instruction operands. Instances are immutable. + /// + /// A list of these is saved as part of the project definition. Code and data that + /// doesn't have one of these will be formatted with default behavior. For data that + /// means a single hexadecimal byte. + /// + /// These are referenced from the project and the Anattribs array. Entries in the + /// latter may come from the project (as specified by the user), or be auto-generated + /// by the data analysis pass. + /// + /// There may be a large number of these, so try to keep the size down. These are usually + /// stored in lists, not arrays, so declaring as a struct wouldn't help with that. + /// + public class FormatDescriptor { + /// + /// General data type. + /// + /// The UI only allows big-endian values in certain situations. Internally we want + /// to be orthogonal in case the policy changes. + /// + public enum Type : byte { + Unknown = 0, + REMOVE, // special type, only used by operand editor + Default, // means "unformatted", same effect as not having a FormatDescriptor + NumericLE, // 1-4 byte number, little-endian + NumericBE, // 1-4 byte number, big-endian + String, // character string + Dense, // raw data, represented as compactly as possible + Fill // fill memory with a value + } + + /// + /// Additional data type detail. + /// + /// Some things are extracted from the data itself, e.g. we don't need to specify if + /// a string is high- or low-ASCII, or what value to use for Fill. + /// + public enum SubType : byte { + None = 0, + + // NumericLE/BE; default is "raw", which can have a context-specific display format + Hex, + Decimal, + Binary, + Ascii, // aspirational; falls back on hex if data not suited + Address, // wants to be an address, but no symbol defined + Symbol, // symbolic ref; replace with Expression, someday? + + // String; default is straight text + Reverse, // plain ASCII in reverse order + CString, // null-terminated + L8String, // 8-bit length prefix + L16String, // 16-bit length prefix + Dci, // Dextral Character Inverted + DciReverse, // DCI with string backwards [deprecated -- no asm supports this] + + // Dense; no sub-types + + // Fill; default is non-ignore + Ignore // TODO(someday): use this for "don't care" sections + } + + private const int MAX_NUMERIC_LEN = 4; + + // Create some "stock" descriptors. For simple cases we return one of these + // instead of allocating a new object. + private static FormatDescriptor ONE_DEFAULT = new FormatDescriptor(1, + Type.Default, SubType.None); + private static FormatDescriptor ONE_NONE = new FormatDescriptor(1, + Type.NumericLE, SubType.None); + private static FormatDescriptor ONE_HEX = new FormatDescriptor(1, + Type.NumericLE, SubType.Hex); + private static FormatDescriptor ONE_DECIMAL = new FormatDescriptor(1, + Type.NumericLE, SubType.Decimal); + private static FormatDescriptor ONE_BINARY = new FormatDescriptor(1, + Type.NumericLE, SubType.Binary); + private static FormatDescriptor ONE_ASCII = new FormatDescriptor(1, + Type.NumericLE, SubType.Ascii); + + /// + /// Length, in bytes, of the data to be formatted. + /// + /// For an instruction, this must match what the code analyzer found as the length + /// of the entire instruction, or the descriptor will be ignored. + /// + /// For data items, this determines the length of the formatted region. + /// + public int Length { get; private set; } + + /// + /// Primary format. The actual data must match the format: + /// - Numeric values must be 1-4 bytes. + /// - String values must be ASCII characters with a common high bit (although + /// the start or end may diverge from this based on the sub-type). + /// - Fill areas must contain identical bytes. + /// + public Type FormatType { get; private set; } + + /// + /// Sub-format specifier. Each primary format has specific sub-formats, but we + /// lump them all together for convenience. + /// + public SubType FormatSubType { get; private set; } + + /// + /// Symbol reference for Type=Numeric SubType=Symbol. null otherwise. + /// + /// Numeric values, such as addresses and constants, can be generated with an + /// expression. Currently we only support using a single symbol, but the goal + /// is to allow free-form expressions like "(sym1+sym2+$80)/3". + /// + /// If the symbol exists, the symbol's name will be shown, possibly with an adjustment + /// to make the symbol value match the operand or data item. + /// + /// Note this reference has a "part" modifier, so we can use it for e.g. "#>label". + /// + public WeakSymbolRef SymbolRef { get; private set; } + + // Crude attempt to see how effective the prefab object creation is. Note we create + // these for DefSymbols, so there will be one prefab for every platform symbol entry. + public static int DebugCreateCount { get; private set; } + public static int DebugPrefabCount { get; private set; } + public static void DebugPrefabBump(int adj=1) { + DebugCreateCount += adj; + DebugPrefabCount += adj; + } + + + /// + /// Constructor for base type data item. + /// + /// Length, in bytes. + /// Format type. + /// Format sub-type. + private FormatDescriptor(int length, Type fmt, SubType subFmt) { + Debug.Assert(length > 0); + Debug.Assert(length <= MAX_NUMERIC_LEN || !IsNumeric); + Debug.Assert(fmt != Type.Default || length == 1); + + Length = length; + FormatType = fmt; + FormatSubType = subFmt; + } + + /// + /// Constructor for symbol item. + /// + /// Length, in bytes. + /// Weak symbol reference. + /// Set to true for big-endian data. + private FormatDescriptor(int length, WeakSymbolRef sym, bool isBigEndian) { + Debug.Assert(sym != null); + Debug.Assert(length > 0 && length <= MAX_NUMERIC_LEN); + Length = length; + FormatType = isBigEndian ? Type.NumericBE : Type.NumericLE; + FormatSubType = SubType.Symbol; + SymbolRef = sym; + } + + /// + /// Returns a descriptor with the requested characteristics. For common cases this + /// returns a pre-allocated object, for less-common cases this allocates a new object. + /// + /// Objects are immutable and do not specify a file offset, so they may be re-used + /// by the caller. + /// + /// Length, in bytes. + /// Format type. + /// Format sub-type. + /// New or pre-allocated descriptor. + public static FormatDescriptor Create(int length, Type fmt, SubType subFmt) { + DebugCreateCount++; + DebugPrefabCount++; + if (length == 1) { + if (fmt == Type.Default) { + Debug.Assert(subFmt == SubType.None); + return ONE_DEFAULT; + } else if (fmt == Type.NumericLE) { + switch (subFmt) { + case SubType.None: + return ONE_NONE; + case SubType.Hex: + return ONE_HEX; + case SubType.Decimal: + return ONE_DECIMAL; + case SubType.Binary: + return ONE_BINARY; + case SubType.Ascii: + return ONE_ASCII; + } + } + } + // For a new file, this will be mostly strings and Fill. + DebugPrefabCount--; + return new FormatDescriptor(length, fmt, subFmt); + } + + /// + /// Returns a descriptor with a symbol. + /// + /// Length, in bytes. + /// Weak symbol reference. + /// Set to true for big-endian data. + /// New or pre-allocated descriptor. + public static FormatDescriptor Create(int length, WeakSymbolRef sym, bool isBigEndian) { + DebugCreateCount++; + return new FormatDescriptor(length, sym, isBigEndian); + } + + /// + /// True if the descriptor is okay to use on an instruction operand. The CPU only + /// understands little-endian numeric values, so that's all we allow. + /// + public bool IsValidForInstruction { + get { + switch (FormatType) { + case Type.Default: + case Type.NumericLE: + //case Type.NumericBE: + return true; + default: + return false; + } + } + } + + /// + /// True if the FormatDescriptor has a symbol. + /// + public bool HasSymbol { + get { + Debug.Assert(SymbolRef == null || (IsNumeric && FormatSubType == SubType.Symbol)); + return SymbolRef != null; + } + } + + /// + /// True if the FormatDescriptor is a numeric type (NumericLE or NumericBE). + /// + public bool IsNumeric { + get { + return FormatType == Type.NumericLE || FormatType == Type.NumericBE; + } + } + + /// + /// True if the FormatDescriptor has a symbol or is Numeric/Address. + /// + public bool HasSymbolOrAddress { + // Derived from other fields, so you can ignore this in equality tests. This is + // of interest to undo/redo, since changing a symbol reference can affect data scan. + get { + return HasSymbol || FormatSubType == SubType.Address; + } + } + + /// + /// Numeric base specific by format/sub-format. Returns 16 when uncertain. + /// + public int NumBase { + get { + if (FormatType != Type.NumericLE && FormatType != Type.NumericBE) { + Debug.Assert(false); + return 16; + } + switch (FormatSubType) { + case SubType.None: + case SubType.Hex: + return 16; + case SubType.Decimal: + return 10; + case SubType.Binary: + return 2; + default: + Debug.Assert(false); + return 16; + } + } + } + + /// + /// Returns the FormatSubType enum constant for the specified numeric base. + /// + /// Base (2, 10, or 16). + /// Enum value. + public static SubType GetSubTypeForBase(int numBase) { + switch (numBase) { + case 2: return SubType.Binary; + case 10: return SubType.Decimal; + case 16: return SubType.Hex; + default: + Debug.Assert(false); + return SubType.Hex; + } + } + + /// + /// Generates a string describing the format, suitable for use in the UI. + /// + public string ToUiString() { + // NOTE: this should be made easier to localize + switch (FormatSubType) { + case SubType.None: + switch (FormatType) { + case Type.Default: + case Type.NumericLE: + return "Numeric (little-endian)"; + case Type.NumericBE: + return "Numeric (big-endian)"; + case Type.String: + return "String (generic)"; + case Type.Dense: + return "Dense"; + case Type.Fill: + return "Fill"; + default: + return "???"; + } + case SubType.Hex: + return "Numeric, Hex"; + case SubType.Decimal: + return "Numeric, Decimal"; + case SubType.Binary: + return "Numeric, Binary"; + case SubType.Ascii: + return "ASCII"; + case SubType.Address: + return "Address"; + case SubType.Symbol: + return "Symbol \"" + SymbolRef.Label + "\""; + + case SubType.Reverse: + return "String (reverse)"; + case SubType.CString: + return "String (null-term)"; + case SubType.L8String: + return "String (1-byte len)"; + case SubType.L16String: + return "String (2-byte len)"; + case SubType.Dci: + return "String (DCI)"; + case SubType.DciReverse: + return "String (RevDCI)"; + + default: + return "???"; + } + } + + public override string ToString() { + return "[FmtDesc: len=" + Length + " fmt=" + FormatType + " sub=" + FormatSubType + + " sym=" + SymbolRef + "]"; + } + + + public static bool operator ==(FormatDescriptor a, FormatDescriptor b) { + if (ReferenceEquals(a, b)) { + return true; // same object, or both null + } + if (ReferenceEquals(a, null) || ReferenceEquals(b, null)) { + return false; // one is null + } + return a.Length == b.Length && a.FormatType == b.FormatType && + a.FormatSubType == b.FormatSubType && a.SymbolRef == b.SymbolRef; + } + public static bool operator !=(FormatDescriptor a, FormatDescriptor b) { + return !(a == b); + } + public override bool Equals(object obj) { + return obj is FormatDescriptor && this == (FormatDescriptor)obj; + } + public override int GetHashCode() { + int hashCode = 0; + if (SymbolRef != null) { + hashCode = SymbolRef.GetHashCode(); + } + hashCode ^= Length; + hashCode ^= (int)FormatType; + hashCode ^= (int)FormatSubType; + return hashCode; + } + + + /// + /// Debugging utility function to dump a sorted list of objects. + /// + public static void DebugDumpSortedList(SortedList list) { + if (list == null) { + Debug.WriteLine("FormatDescriptor list is empty"); + return; + } + Debug.WriteLine("FormatDescriptor list (" + list.Count + " entries)"); + foreach (KeyValuePair kvp in list) { + int offset = kvp.Key; + FormatDescriptor dfd = kvp.Value; + Debug.WriteLine(" +" + offset.ToString("x6") + ",+" + + (offset + dfd.Length - 1).ToString("x6") + ": " + dfd.FormatType + + "(" + dfd.FormatSubType + ")"); + } + } + } +} diff --git a/SourceGen/HelpAccess.cs b/SourceGen/HelpAccess.cs new file mode 100644 index 0000000..2fbad24 --- /dev/null +++ b/SourceGen/HelpAccess.cs @@ -0,0 +1,90 @@ +/* + * Copyright 2018 faddenSoft + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; + +using CommonUtil; + +/* +There are a few different options for viewing help files: + (1) Microsoft HTML Help. Requires writing stuff in a specific way and then running a + tool to turn it into a .chm file, which then requires a help viewer application. + Feels a little weak in terms of future-proofing and cross-platform support. + (2) Plain HTML, using System.Windows.Forms.WebBrowser class. This seems like a nice + way to go, but we need to provide all the standard controls, and it means we have + a web browser running in-process. + (3) Plain HTML, with the Microsoft.Toolkit.Win32.UI.Controls.WinForms.WebView control. + Similar to WebBrowser, but newer and fancier, and probably less portable. + (4) Plain HTML, viewed with the system browser. We outsource the problem. The big + problem here is that the easy/portable way (Process.Start(url)) discards the anchor + part (the bit after '#'). There are workarounds, but they seem to involve dredging + the default browser out of the Registry. + (5) Custom roll-your-own solution. Have you seen this round thing I invented? I'm + calling it a "wheel". + +For now I'm going with #4, and dealing with anchors by ignoring them: the help menu item +just opens the TOC, and individual UI items don't have help buttons. + +What we need in terms of API is a way to say, "show the help for XYZ". The rest can be +encapsulated here. +*/ + +namespace SourceGen { + /// + /// Help viewer API. + /// + public static class HelpAccess { + private const string HELP_DIR = "Help"; // directory inside RuntimeData + + /// + /// Help topics. + /// + public enum Topic { + Contents, // table of contents + + Main, // main window, general workflow + + // Editors + EditLongComment, + } + + private static Dictionary sTopicMap = new Dictionary() { + { Topic.Contents, "index.html" }, + { Topic.Main, "main.html" }, + { Topic.EditLongComment, "editor.html#long-comment" } + }; + + /// + /// Opens a window with the specified help topic. + /// + /// + public static void ShowHelp(Topic topic) { + if (!sTopicMap.TryGetValue(topic, out string fileName)) { + Debug.Assert(false, "Unable to find " + topic + " in map"); + return; + } + + string helpFilePath = Path.Combine(RuntimeDataAccess.GetDirectory(), + HELP_DIR, fileName); + string url = "file://" + helpFilePath; + //url = url.Replace("#", "%23"); + Debug.WriteLine("Requesting help URL: " + url); + ShellCommand.OpenUrl(url); + } + } +} diff --git a/SourceGen/MultiLineComment.cs b/SourceGen/MultiLineComment.cs new file mode 100644 index 0000000..d07e0c1 --- /dev/null +++ b/SourceGen/MultiLineComment.cs @@ -0,0 +1,276 @@ +/* + * Copyright 2018 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.Drawing; +using System.Text; + +namespace SourceGen { + /// + /// Representation of a multi-line comment, which is a string plus some format directives. + /// Used for long comments and notes. + /// + /// Instances are immutable. + /// + public class MultiLineComment { + /// + /// If set, sticks a MaxWidth "ruler" at the top, and makes spaces visible. + /// + public static bool DebugShowRuler { get; set; } + + /// + /// Unformatted text. + /// + public string Text { get; private set; } + + /// + /// Set to true to render text surrounded by a box of ASCII characters. + /// + public bool BoxMode { get; private set; } + + /// + /// Maximum line width. Box mode effectively reduces this by four. + /// + public int MaxWidth { get; private set; } + + /// + /// Background color for notes. + /// + public Color BackgroundColor { get; private set; } + + + /// + /// Constructor. Object will have a max width of 80 and not be boxed. + /// + /// Unformatted comment text. + public MultiLineComment(string text) { + Debug.Assert(text != null); // empty string is okay + Text = text; + BoxMode = false; + MaxWidth = 80; + BackgroundColor = Color.FromArgb(0); + } + + /// + /// Constructor. Used for long comments. + /// + /// Unformatted text. + /// Set to true to enable box mode. + /// Maximum line width. + public MultiLineComment(string text, bool boxMode, int maxWidth) : this(text) { + Debug.Assert((!boxMode && maxWidth > 1) || (boxMode && maxWidth > 5)); + BoxMode = boxMode; + MaxWidth = maxWidth; + } + + /// + /// Constructor. Used for notes. + /// + /// Unformatted text. + /// Background color. + public MultiLineComment(string text, Color bkgndColor) : this(text) { + BackgroundColor = bkgndColor; + } + + /// + /// Generates one or more lines of formatted text. + /// + /// Formatter, with comment delimiters. + /// String to prepend to text before formatting. If this + /// is non-empty, comment delimiters aren't emitted. (Used for notes.) + /// Array of formatted strings. + public List FormatText(Asm65.Formatter formatter, string textPrefix) { + const char boxChar = '*'; + const char spcRep = '\u2219'; + string workString = string.IsNullOrEmpty(textPrefix) ? Text : textPrefix + Text; + List lines = new List(); + + string linePrefix; + if (!string.IsNullOrEmpty(textPrefix)) { + linePrefix = string.Empty; + } else if (BoxMode) { + linePrefix = formatter.BoxLineCommentDelimiter; + } else { + linePrefix = formatter.FullLineCommentDelimiter; + } + + StringBuilder sb = new StringBuilder(MaxWidth); + if (DebugShowRuler) { + for (int i = 0; i < MaxWidth; i++) { + sb.Append((i % 10).ToString()); + } + lines.Add(sb.ToString()); + sb.Clear(); + } + string boxLine, spaces; + if (BoxMode) { + for (int i = 0; i < MaxWidth - linePrefix.Length; i++) { + sb.Append(boxChar); + } + boxLine = sb.ToString(); + sb.Clear(); + for (int i = 0; i < MaxWidth; i++) { + sb.Append(' '); + } + spaces = sb.ToString(); + sb.Clear(); + + } else { + boxLine = spaces = null; + } + + if (BoxMode && workString.Length > 0) { + lines.Add(linePrefix + boxLine); + } + + int lineWidth = BoxMode ? + MaxWidth - linePrefix.Length - 4 : + MaxWidth - linePrefix.Length; + int startIndex = 0; + int breakIndex = -1; + for (int i = 0; i < workString.Length; i++) { + // Spaces and hyphens are different. For example, if width is 10, + // "long wordsmore words" becomes: + // 0123456789 + // long words + // more words + // However, "long words-more words" becomes: + // long + // words-more + // words + // because the hyphen is retained but the space is discarded. + + if (workString[i] == '\r' || workString[i] == '\n') { + // explicit line break, emit line + string str = workString.Substring(startIndex, i - startIndex); + if (DebugShowRuler) { str = str.Replace(' ', spcRep); } + if (BoxMode) { + if (str == "" + boxChar) { + // asterisk on a line by itself means "output row of asterisks" + str = linePrefix + boxLine; + } else { + int padLen = lineWidth - str.Length; + str = linePrefix + boxChar + " " + str + + spaces.Substring(0, padLen + 1) + boxChar; + } + } else { + str = linePrefix + str; + } + lines.Add(str); + // Eat the LF in CRLF. We don't actually work right with just LF, + // because this will consume LFLF, but it's okay to insist that the + // string use CRLF for line breaks. + if (i < workString.Length - 1 && workString[i + 1] == '\n') { + i++; + } + startIndex = i + 1; + breakIndex = -1; + } else if (workString[i] == ' ') { + // can break on a space even if it's one char too far + breakIndex = i; + } + + if (i - startIndex >= lineWidth) { + // this character was one too many, break line one back + if (breakIndex <= 0) { + // no break found, just chop it + string str = workString.Substring(startIndex, i - startIndex); + if (DebugShowRuler) { str = str.Replace(' ', spcRep); } + if (BoxMode) { + str = linePrefix + boxChar + " " + str + " " + boxChar; + } else { + str = linePrefix + str; + } + lines.Add(str); + startIndex = i; + } else { + // Copy everything from start to break. If the break was a hyphen, + // we want to keep it. + int adj = 0; + if (workString[breakIndex] == '-') { + adj = 1; + } + string str = workString.Substring(startIndex, + breakIndex + adj - startIndex); + if (DebugShowRuler) { str = str.Replace(' ', spcRep); } + if (BoxMode) { + int padLen = lineWidth - str.Length; + str = linePrefix + boxChar + " " + str + + spaces.Substring(0, padLen + 1) + boxChar; + } else { + str = linePrefix + str; + } + lines.Add(str); + startIndex = breakIndex + 1; + breakIndex = -1; + } + } + + if (workString[i] == '-') { + // can break on hyphen if it fits in line + breakIndex = i; + } + } + + if (startIndex < workString.Length) { + // Output remainder. + string str = workString.Substring(startIndex, workString.Length - startIndex); + if (DebugShowRuler) { str = str.Replace(' ', spcRep); } + if (BoxMode) { + int padLen = lineWidth - str.Length; + str = linePrefix + boxChar + " " + str + + spaces.Substring(0, padLen + 1) + boxChar; + } else { + str = linePrefix + str; + } + lines.Add(str); + } + + if (BoxMode && workString.Length > 0) { + lines.Add(linePrefix + boxLine); + } + + return lines; + } + + + public override string ToString() { + return "MLC box=" + BoxMode + " width=" + MaxWidth + " text='" + Text + "'"; + } + + + public static bool operator ==(MultiLineComment a, MultiLineComment b) { + if (ReferenceEquals(a, b)) { + return true; // same object, or both null + } + if (ReferenceEquals(a, null) || ReferenceEquals(b, null)) { + return false; // one is null + } + return a.Text.Equals(b.Text) && a.BoxMode == b.BoxMode && a.MaxWidth == b.MaxWidth + && a.BackgroundColor == b.BackgroundColor; + } + public static bool operator !=(MultiLineComment a, MultiLineComment b) { + return !(a == b); + } + public override bool Equals(object obj) { + return obj is MultiLineComment && this == (MultiLineComment)obj; + } + public override int GetHashCode() { + return Text.GetHashCode() ^ MaxWidth ^ (BoxMode ? 1 : 0) ^ BackgroundColor.ToArgb(); + } + } +} diff --git a/SourceGen/NavStack.cs b/SourceGen/NavStack.cs new file mode 100644 index 0000000..3beb06e --- /dev/null +++ b/SourceGen/NavStack.cs @@ -0,0 +1,153 @@ +/* + * Copyright 2018 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.Text; + +namespace SourceGen { + /// + /// Maintains a record of interesting places we've been. + /// + public class NavStack { + // If you're at offset 10, and you jump to offset 20, we push offset 10 onto the + // back list. If you hit back, you want to be at offset 10. If you then hit + // forward, you want to jump to offset 20. So how does 20 get on there? + // + // The trick is to record the "from" and "to" position at each step. When moving + // backward we go the previous "from" position. When moving forward we move to + // the next "to" position. This makes the movement asymmetric, but it means that + // that forward movement is always to places we've jumped to, and backward movement + // is to places we jumped away from. + + // TODO(someday): record more about what was selected, so e.g. when we move back or + // forward to a Note we can highlight it appropriately. + // TODO(someday): once we have the above, we can change the back button to a pop-up + // list of locations (like the way VS 2017 does it). + + private class OffsetPair { + public int From { get; set; } + public int To { get; set; } + + public OffsetPair(int from, int to) { + From = from; + To = to; + } + public override string ToString() { + return "[fr=+" + From.ToString("x6") + " to=+" + To.ToString("x6") + "]"; + } + } + + // Offset stack. Popped items remain in place temporarily. + private List mStack = new List(); + + // Current stack position. This is one past the most-recently-pushed element. + private int mCursor = 0; + + + public NavStack() { } + + /// + /// True if there is an opportunity to pop backward. + /// + public bool HasBackward { + get { + return mCursor > 0; + } + } + + /// + /// True if there is an opportunity to push forward. + /// + public bool HasForward { + get { + return mCursor < mStack.Count; + } + } + + /// + /// Clears the back stack. + /// + public void Clear() { + mStack.Clear(); + mCursor = 0; + } + + /// + /// Pops the top entry off the stack. This moves the cursor but doesn't actually + /// remove the item. + /// + /// The "from" element of the popped entry. + public int Pop() { + if (mCursor == 0) { + throw new Exception("Stack is empty"); + } + mCursor--; + //Debug.WriteLine("NavStack popped +" + mStack[mCursor] + + // " (now cursor=" + mCursor + ") -- " + this); + return mStack[mCursor].From; + } + + /// + /// Pushes a new entry onto the stack at the cursor. If there were additional + /// entries past the cursor, they will be discarded. + /// + /// If the same entry is already at the top of the stack, the entry will not be added. + /// + /// File offset associated with line we are moving from. + /// This may be negative if we're moving from a header comment or .EQ directive. + /// File offset associated with line we are moving to. This + /// may be negative if we're moving to the header comment or a .EQ directive. + public void Push(int fromOffset, int toOffset) { + if (mStack.Count > mCursor) { + mStack.RemoveRange(mCursor, mStack.Count - mCursor); + } + OffsetPair newPair = new OffsetPair(fromOffset, toOffset); + mStack.Add(newPair); + mCursor++; + //Debug.WriteLine("NavStack pushed +" + newPair + " -- " + this); + } + + /// + /// Pushes a previous entry back onto the stack. + /// + /// The "to" element of the pushed entry. + public int PushPrevious() { + if (mCursor == mStack.Count) { + throw new Exception("At top of stack"); + } + int fwdOff = mStack[mCursor].To; + mCursor++; + //Debug.WriteLine("NavStack pushed prev (now cursor=" + mCursor + ") -- " + this); + return fwdOff; + } + + public override string ToString() { + StringBuilder sb = new StringBuilder(); + sb.Append("NavStack:"); + for (int i = 0; i < mStack.Count; i++) { + if (i == mCursor) { + sb.Append(" [*]"); + } + sb.Append(mStack[i]); + } + if (mCursor == mStack.Count) { + sb.Append(" [*]"); + } + return sb.ToString(); + } + } +} diff --git a/SourceGen/PlatformSymbols.cs b/SourceGen/PlatformSymbols.cs new file mode 100644 index 0000000..8a15605 --- /dev/null +++ b/SourceGen/PlatformSymbols.cs @@ -0,0 +1,253 @@ +/* + * Copyright 2018 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; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Text.RegularExpressions; + +using CommonUtil; +using SourceGen.Sandbox; + +namespace SourceGen { + /// + /// Loads and maintains a collection of platform-specific symbols from a ".sym65" file. + /// + public class PlatformSymbols : IEnumerable { + public const string FILENAME_EXT = ".sym65"; + public static readonly string FILENAME_FILTER = Properties.Resources.FILE_FILTER_SYM65; + + /// + /// Regex pattern for name/value pairs in symbol file. + /// + /// Alphanumeric ASCII + underscore for label, which must start at beginning of line. + /// Value is somewhat arbitrary, but ends if we see a comment delimiter (semicolon). + /// Spaces are allowed between tokens. + /// + /// Group 1 is the name, group 2 is '=' or '@', group 3 is the value, group 4 is + /// the comment (optional). + /// + private const string NAME_VALUE_PATTERN = + @"^([A-Za-z0-9_]+)\s*([@=])\s*([^\ ;]+)\s*(;.*)?$"; + private static Regex sNameValueRegex = new Regex(NAME_VALUE_PATTERN); + + private const string TAG_CMD = "*TAG"; + + /// + /// List of symbols. We keep them sorted by label because labels must be unique. + /// + /// Idea: we could retain the end-of-line comments, and add them as comments in the + /// EQU section of the disassembly. + /// + private SortedList mSymbols = + new SortedList(Asm65.Label.LABEL_COMPARER); + + + public PlatformSymbols() { } + + // IEnumerable + public IEnumerator GetEnumerator() { + return mSymbols.Values.GetEnumerator(); + } + + // IEnumerable + IEnumerator IEnumerable.GetEnumerator() { + return mSymbols.Values.GetEnumerator(); + } + + /// + /// Loads platform symbols. + /// + /// Relative pathname of file to open. + /// Report of warnings and errors. + /// True on success (no errors), false on failure. + public bool LoadFromFile(string fileIdent, string projectDir, out FileLoadReport report) { + // These files shouldn't be enormous. Do it the easy way. + report = new FileLoadReport(fileIdent); + + ExternalFile ef = ExternalFile.CreateFromIdent(fileIdent); + if (ef == null) { + report.Add(FileLoadItem.Type.Error, + CommonUtil.Properties.Resources.ERR_FILE_NOT_FOUND + ": " + fileIdent); + return false; + } + + string pathName = ef.GetPathName(projectDir); + if (pathName == null) { + report.Add(FileLoadItem.Type.Error, + Properties.Resources.ERR_BAD_IDENT + ": " + fileIdent); + return false; + } + string[] lines; + try { + lines = File.ReadAllLines(pathName); + } catch (IOException ioe) { + Debug.WriteLine("Platform symbol load failed: " + ioe); + report.Add(FileLoadItem.Type.Error, + CommonUtil.Properties.Resources.ERR_FILE_NOT_FOUND + ": " + pathName); + return false; + } + + string tag = string.Empty; + + int lineNum = 0; + foreach (string line in lines) { + lineNum++; // first line is line 1, says Vim and VisualStudio + if (string.IsNullOrEmpty(line) || line[0] == ';') { + // ignore + } else if (line[0] == '*') { + if (line.StartsWith(TAG_CMD)) { + tag = ParseTag(line); + } else { + // Do something clever with *SYNOPSIS? + Debug.WriteLine("CMD: " + line); + } + } else { + MatchCollection matches = sNameValueRegex.Matches(line); + if (matches.Count == 1) { + //Debug.WriteLine("GOT '" + matches[0].Groups[1] + "' " + + // matches[0].Groups[2] + " '" + matches[0].Groups[3] + "'"); + string label = matches[0].Groups[1].Value; + bool isConst = (matches[0].Groups[2].Value[0] == '='); + string badParseMsg; + int value, numBase; + bool parseOk; + if (isConst) { + // Allow various numeric options, and preserve the value. + parseOk = Asm65.Number.TryParseInt(matches[0].Groups[3].Value, + out value, out numBase); + badParseMsg = + CommonUtil.Properties.Resources.ERR_INVALID_NUMERIC_CONSTANT; + } else { + // Allow things like "05/1000". Always hex. + numBase = 16; + parseOk = Asm65.Address.ParseAddress(matches[0].Groups[3].Value, + (1 << 24) - 1, out value); + badParseMsg = CommonUtil.Properties.Resources.ERR_INVALID_ADDRESS; + } + if (!parseOk) { + report.Add(lineNum, FileLoadItem.NO_COLUMN, FileLoadItem.Type.Warning, + badParseMsg); + } else { + string comment = matches[0].Groups[4].Value; + if (comment.Length > 0) { + // remove ';' + comment = comment.Substring(1); + } + FormatDescriptor.SubType subType = + FormatDescriptor.GetSubTypeForBase(numBase); + DefSymbol symDef = new DefSymbol(label, value, Symbol.Source.Platform, + isConst ? Symbol.Type.Constant : Symbol.Type.ExternalAddr, + subType, comment, tag); + if (mSymbols.ContainsKey(label)) { + // This is very easy to do -- just define the same symbol twice + // in the same file. We don't really need to do anything about + // it though. + Debug.WriteLine("NOTE: stomping previous definition of " + label); + } + mSymbols[label] = symDef; + } + } else { + report.Add(lineNum, FileLoadItem.NO_COLUMN, FileLoadItem.Type.Warning, + CommonUtil.Properties.Resources.ERR_SYNTAX); + } + } + } + + return !report.HasErrors; + } + + /// + /// Parses the tag out of a tag command line. The tag is pretty much everything after + /// the "*TAG", with whitespace stripped off the start and end. The empty string + /// is valid. + /// + /// Line to parse. + /// Tag string. + private string ParseTag(string line) { + Debug.Assert(line.StartsWith(TAG_CMD)); + string tag = line.Substring(TAG_CMD.Length).Trim(); + return tag; + } + + /// + /// One-off function to convert the IIgs toolbox function info from NList.Data.TXT + /// to .sym65 format. Doesn't really belong in here, but I'm too lazy to put it + /// anywhere else. + /// + public static void ConvertNiftyListToolboxFuncs(string inPath, string outPath) { + const string TOOL_START = "* System tools"; + const string TOOL_END = "* User tools"; + const string PATTERN = @"^([0-9a-fA-F]{4}) (\w+)(.*)"; + Regex parseRegex = new Regex(PATTERN); + System.Text.StringBuilder sb = new System.Text.StringBuilder(); + + string[] lines = File.ReadAllLines(inPath); + List outs = new List(); + + bool inTools = false; + foreach (string line in lines) { + if (line == TOOL_START) { + inTools = true; + continue; + } else if (line == TOOL_END) { + break; + } + if (!inTools) { + continue; + } + if (line.Substring(5, 4) == "=== ") { + // make this a comment + outs.Add("; " + line.Substring(5)); + continue; + } + MatchCollection matches = parseRegex.Matches(line); + if (matches.Count != 1) { + Debug.WriteLine("NConv: bad match on '" + line + "'"); + outs.Add("; " + line); + continue; + } + + GroupCollection group = matches[0].Groups; + string outStr; + if (matches[0].Groups.Count != 4) { + Debug.WriteLine("NConv: partial match (" + group.Count + ") on '" + + line + "'"); + outStr = ";" + group[0]; + } else { + sb.Clear(); + sb.Append(group[2]); + while (sb.Length < 19) { // not really worried about speed + sb.Append(' '); + } + sb.Append(" = $"); + sb.Append(group[1]); + while (sb.Length < 32) { + sb.Append(' '); + } + sb.Append(';'); + sb.Append(group[3]); + outs.Add(sb.ToString()); + } + } + + File.WriteAllLines(outPath, outs); + Debug.WriteLine("NConv complete (" + outs.Count + " lines)"); + } + } +} diff --git a/SourceGen/Program.cs b/SourceGen/Program.cs new file mode 100644 index 0000000..5238a1c --- /dev/null +++ b/SourceGen/Program.cs @@ -0,0 +1,42 @@ +/* + * Copyright 2018 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.Diagnostics; +using System.Windows.Forms; + +namespace SourceGen { + static class Program { + // Version number applied to the program as a whole. + public static readonly CommonUtil.Version ProgramVersion = + new CommonUtil.Version(1, 0, 0, CommonUtil.Version.PreRelType.Alpha, 1); + + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() { + // Run some utility class unit tests if we're built for debug. + Debug.Assert(CommonUtil.RangeSet.Test()); + Debug.Assert(CommonUtil.TypedRangeSet.Test()); + Debug.Assert(CommonUtil.Version.Test()); + Debug.Assert(Asm65.CpuDef.DebugValidate()); + + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new AppForms.ProjectView()); + } + } +} diff --git a/SourceGen/ProjectFile.cs b/SourceGen/ProjectFile.cs new file mode 100644 index 0000000..9a77fa4 --- /dev/null +++ b/SourceGen/ProjectFile.cs @@ -0,0 +1,726 @@ +/* + * Copyright 2018 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.Drawing; +using System.IO; +using System.Text; +using System.Web.Script.Serialization; + +using CommonUtil; + +namespace SourceGen { + /// + /// 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. + /// + public static class ProjectFile { + public const string FILENAME_EXT = ".dis65"; + public static readonly string FILENAME_FILTER = Properties.Resources.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 = 1; + + private static bool ADD_CRLF = true; + + + /// + /// Serializes the project and writes it to the specified file. + /// + /// Project to serialize. + /// Output path name. + /// Human-readable error string, or an empty string if all + /// went well. + /// True on success. + 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(Properties.Resources.ERR_FILE_READ_ONLY, + 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; + } + } + + /// + /// Reads the specified file and deserializes it into the project. + /// + /// Input path name. + /// Project to deserialize into. + /// File load report, which may contain errors or warnings. + /// True on success. + 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, Properties.Resources.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, + Properties.Resources.ERR_PROJECT_FILE_CORRUPT + ": " + ex); + return false; + } + } else { + report.Add(FileLoadItem.Type.Error, Properties.Resources.ERR_NOT_PROJECT_FILE); + return false; + } + } + +#if false + public Dictionary IntKeysToStrings(Dictionary input) { + Dictionary output = new Dictionary(); + + foreach (KeyValuePair entry in input) { + output.Add(entry.Key.ToString(), entry.Value); + } + return output; + } + public Dictionary StringKeysToInts(Dictionary input) { + Dictionary output = new Dictionary(); + + foreach (KeyValuePair 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 + } + + /// + /// Somewhat sloppy-looking JSON state dump. + /// + 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 SerAnalysisParameters AnalysisParams { get; set; } + public List PlatformSymbolFileIdentifiers { get; set; } + public List ExtensionScriptFileIdentifiers { get; set; } + public SortedList ProjectSyms { get; set; } + + public SerProjectProperties() { } + public SerProjectProperties(ProjectProperties props) { + CpuName = Asm65.CpuDef.GetCpuNameFromType(props.CpuType); + IncludeUndocumentedInstr = props.IncludeUndocumentedInstr; + EntryFlags = props.EntryFlags.AsInt; + 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(); + foreach (KeyValuePair 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 = mlc.BackgroundColor.ToArgb(); + } + } + 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 AddressMap { get; set; } + public List TypeHints { get; set; } + public Dictionary StatusFlagOverrides { get; set; } + public Dictionary Comments { get; set; } + public Dictionary LongComments { get; set; } + public Dictionary Notes { get; set; } + public Dictionary UserLabels { get; set; } + public Dictionary
012345: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 0123456789abcdef