mirror of
https://github.com/fadden/6502bench.git
synced 2024-12-01 07:50:37 +00:00
Add Data Bank Register management, part 1
On the 65816, 16-bit data access instructions (e.g. LDA abs) are expanded to 24 bits by merging in the Data Bank Register (B). The value of the register is difficult to determine via static analysis, so we need a way to annotate the disassembly with the correct value. Without this, the mapping of address to file offset will sometimes be incorrect. This change adds the basic data structures and "fixup" function, a functional but incomplete editor, and source for a new test case.
This commit is contained in:
parent
44522dc2f2
commit
18e6951f17
@ -74,5 +74,35 @@ namespace CommonUtil {
|
||||
Debug.WriteLine("Crashed while crashing");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears an array to a specific value, similar to memset() in libc. This is much
|
||||
/// faster than setting array elements individually.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// From https://stackoverflow.com/a/18659408/294248
|
||||
///
|
||||
/// Invokes Array.Copy() on overlapping elements. Other approaches involve using
|
||||
/// Buffer.BlockCopy or unsafe code. Apparently .NET Core has an Array.Fill(), but
|
||||
/// that doesn't exist in .NET Framework.
|
||||
///
|
||||
/// We could get off the line a little faster by setting the first 16 or so elements in
|
||||
/// a loop, bailing out if we finish early, so we don't start calling Array.Copy() until
|
||||
/// it's actually faster to do so. I don't expect to be calling this often or for
|
||||
/// small arrays though.
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">Array element type.</typeparam>
|
||||
/// <param name="array">Array reference.</param>
|
||||
/// <param name="elem">Initialization value.</param>
|
||||
public static void Memset<T>(T[] array, T elem) {
|
||||
//Array.Fill(array, elem);
|
||||
int length = array.Length;
|
||||
if (length == 0) return;
|
||||
array[0] = elem;
|
||||
int count;
|
||||
for (count = 1; count <= length / 2; count *= 2)
|
||||
Array.Copy(array, 0, array, count, count);
|
||||
Array.Copy(array, 0, array, count, length - count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,8 @@ namespace SourceGen {
|
||||
InlineData = 1 << 2, // byte is inline data
|
||||
Data = 1 << 3, // byte is data
|
||||
|
||||
UsesDataBankReg = 1 << 4, // operand value should be merged with DBR
|
||||
|
||||
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
|
||||
@ -113,6 +115,18 @@ namespace SourceGen {
|
||||
return IsInstructionStart || IsDataStart || IsInlineDataStart;
|
||||
}
|
||||
}
|
||||
public bool UsesDataBankReg {
|
||||
get {
|
||||
return (mAttribFlags & AttribFlags.UsesDataBankReg) != 0;
|
||||
}
|
||||
set {
|
||||
if (value) {
|
||||
mAttribFlags |= AttribFlags.UsesDataBankReg;
|
||||
} else {
|
||||
mAttribFlags &= ~AttribFlags.UsesDataBankReg;
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool IsEntryPoint {
|
||||
get {
|
||||
return (mAttribFlags & AttribFlags.EntryPoint) != 0;
|
||||
|
@ -838,14 +838,17 @@ namespace SourceGen {
|
||||
/// The operand's address, and if applicable, the operand's file offset, are
|
||||
/// stored in the Anattrib array.
|
||||
///
|
||||
/// Doesn't do anything with immediate data.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
///
|
||||
/// This is called once per instruction, on the analyzer's first visit.
|
||||
/// </remarks>
|
||||
/// <param name="offset">Offset of the instruction opcode.</param>
|
||||
/// <param name="op">Opcode being handled. (Passed in because the caller has it
|
||||
/// handy.)</param>
|
||||
@ -854,26 +857,36 @@ namespace SourceGen {
|
||||
|
||||
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
|
||||
// Add the bank to get a 24-bit address. For some instructions the relevant bank
|
||||
// is known, because the operand is merged with the Program Bank Register (K) or
|
||||
// is always in bank 0. For some we need the Data Bank Register (B).
|
||||
//
|
||||
// Instead of trying to track the B register during code analysis, we mark the
|
||||
// relevant instructions now and fix them up later. We can get away with this
|
||||
// because the DBR is only applied to data-load instructions, which don't affect
|
||||
// the flow of the analysis pass. The value of B *is* affected by the analysis
|
||||
// pass because a "smart PLB" handler needs to know where all the code is, so it's
|
||||
// more efficient to figure it out later.
|
||||
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.StackAbs:
|
||||
case OpDef.AddressMode.Abs: // uses DBR iff !IsAbsolutePBR
|
||||
case OpDef.AddressMode.AbsIndexX: // uses DBR
|
||||
case OpDef.AddressMode.AbsIndexY: // uses DBR
|
||||
if (!op.IsAbsolutePBR) {
|
||||
mAnattribs[offset].UsesDataBankReg = true;
|
||||
}
|
||||
// Merge the PBR even if we eventually want the DBR; less to fix later.
|
||||
mAnattribs[offset].OperandAddress = operand | bank;
|
||||
break;
|
||||
case OpDef.AddressMode.AbsInd: // JMP (addr), always bank 0
|
||||
case OpDef.AddressMode.AbsIndLong: // JMP [addr], always bank 0
|
||||
case OpDef.AddressMode.StackAbs: // assume PBR
|
||||
case OpDef.AddressMode.AbsIndexXInd: // JMP (addr,X); uses program bank
|
||||
mAnattribs[offset].OperandAddress = operand | bank;
|
||||
break;
|
||||
case OpDef.AddressMode.AbsInd: // JMP (addr); always bank 0
|
||||
case OpDef.AddressMode.AbsIndLong: // JMP [addr]; always bank 0
|
||||
case OpDef.AddressMode.DP:
|
||||
case OpDef.AddressMode.DPIndexX:
|
||||
case OpDef.AddressMode.DPIndexY:
|
||||
@ -888,7 +901,7 @@ namespace SourceGen {
|
||||
break;
|
||||
case OpDef.AddressMode.AbsIndexXLong:
|
||||
case OpDef.AddressMode.AbsLong:
|
||||
// 24-bit address, don't add bank
|
||||
// 24-bit address, don't alter bank
|
||||
mAnattribs[offset].OperandAddress = operand;
|
||||
break;
|
||||
case OpDef.AddressMode.PCRel: // rel operand; convert to absolute addr
|
||||
@ -1207,5 +1220,88 @@ namespace SourceGen {
|
||||
throw new PluginException("Instr format rej: unknown sub type " + pluginSubType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data Bank Register value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is primarily a value from $00-ff, but we also want to encode the B=K special
|
||||
/// mode.
|
||||
/// </remarks>
|
||||
public enum DbrValue : short {
|
||||
// $00-ff is bank number
|
||||
Unknown = -1, // unknown / do-nothing
|
||||
ProgramBankReg = -2 // set B=K
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the value of the Data Bank Register (DBR, register 'B') for relevant
|
||||
/// instructions, and updates the Anattrib OperandOffset value.
|
||||
/// </summary>
|
||||
public void ApplyDataBankRegister(Dictionary<int, DbrValue> userValues) {
|
||||
Debug.Assert(!mCpuDef.HasAddr16); // 65816 only
|
||||
|
||||
short[] bval = new short[mAnattribs.Length];
|
||||
|
||||
// Initialize all entries to "unknown".
|
||||
Misc.Memset(bval, (short)DbrValue.Unknown);
|
||||
|
||||
// Set B=K every time we cross an address boundary and the program bank changes.
|
||||
DbrValue prevBank = DbrValue.Unknown;
|
||||
foreach (AddressMap.AddressMapEntry ent in mAddrMap) {
|
||||
int mapBank = ent.Addr >> 16;
|
||||
if (mapBank != (int)prevBank) {
|
||||
bval[ent.Offset] = (short)mapBank;
|
||||
prevBank = (DbrValue)mapBank;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the user-specified values.
|
||||
foreach (KeyValuePair<int, DbrValue> kvp in userValues) {
|
||||
bval[kvp.Key] = (short)kvp.Value;
|
||||
}
|
||||
|
||||
// Run through the file, looking for PHK/PLB pairs. When we find one, set an
|
||||
// entry for the PLB instruction unless an entry already exists there.
|
||||
|
||||
// ? look for LDA #imm8 / PHA / PLB?
|
||||
|
||||
// ...
|
||||
|
||||
|
||||
// Run through file, updating instructions as needed.
|
||||
short curVal = (short)DbrValue.Unknown;
|
||||
for (int offset = 0; offset < mAnattribs.Length; offset++) {
|
||||
if (bval[offset] != (short)DbrValue.Unknown) {
|
||||
curVal = bval[offset];
|
||||
}
|
||||
if (!mAnattribs[offset].UsesDataBankReg) {
|
||||
continue;
|
||||
}
|
||||
Debug.Assert(mAnattribs[offset].IsInstructionStart);
|
||||
Debug.Assert(curVal != (short)DbrValue.Unknown);
|
||||
|
||||
int bank;
|
||||
if (curVal == (short)DbrValue.ProgramBankReg) {
|
||||
bank = mAnattribs[offset].Address >> 16;
|
||||
} else {
|
||||
Debug.Assert(curVal >= 0 && curVal < 256);
|
||||
bank = curVal << 16;
|
||||
}
|
||||
|
||||
int newAddr = (mAnattribs[offset].OperandAddress & 0x0000ffff) | bank;
|
||||
int newOffset = mAddrMap.AddressToOffset(offset, newAddr);
|
||||
if (newAddr != mAnattribs[offset].OperandAddress ||
|
||||
newOffset != mAnattribs[offset].OperandOffset) {
|
||||
Debug.WriteLine("DBR rewrite at +" + offset.ToString("x6") + ": $" +
|
||||
mAnattribs[offset].OperandAddress.ToString("x6") + "/+" +
|
||||
mAnattribs[offset].OperandOffset.ToString("x6") + " --> $" +
|
||||
newAddr.ToString("x6") + "/+" + newOffset.ToString("x6"));
|
||||
|
||||
mAnattribs[offset].OperandAddress = newAddr;
|
||||
mAnattribs[offset].OperandOffset = newOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -74,6 +74,11 @@ namespace SourceGen {
|
||||
/// </summary>
|
||||
public string[] Comments { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Data Bank Register overrides.
|
||||
/// </summary>
|
||||
public Dictionary<int, CodeAnalysis.DbrValue> DbrValues { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Full line, possibly multi-line comments.
|
||||
/// </summary>
|
||||
@ -274,6 +279,7 @@ namespace SourceGen {
|
||||
Comments[i] = string.Empty;
|
||||
}
|
||||
|
||||
DbrValues = new Dictionary<int, CodeAnalysis.DbrValue>();
|
||||
LongComments = new Dictionary<int, MultiLineComment>();
|
||||
Notes = new SortedList<int, MultiLineComment>();
|
||||
|
||||
@ -807,6 +813,13 @@ namespace SourceGen {
|
||||
ca.Analyze();
|
||||
reanalysisTimer.EndTask("CodeAnalysis.Analyze");
|
||||
|
||||
if (!CpuDef.HasAddr16) {
|
||||
// 24-bit address space, so DBR matters
|
||||
reanalysisTimer.StartTask("CodeAnalysis.ApplyDataBankRegister");
|
||||
ca.ApplyDataBankRegister(DbrValues);
|
||||
reanalysisTimer.EndTask("CodeAnalysis.ApplyDataBankRegister");
|
||||
}
|
||||
|
||||
// Save a copy of the current state.
|
||||
mCodeOnlyAnattribs = new Anattrib[mAnattribs.Length];
|
||||
Array.Copy(mAnattribs, mCodeOnlyAnattribs, mAnattribs.Length);
|
||||
@ -831,6 +844,8 @@ namespace SourceGen {
|
||||
DataAnalysis da = new DataAnalysis(this, mAnattribs);
|
||||
da.DebugLog = debugLog;
|
||||
|
||||
// Convert references to addresses into references to labels, generating labels
|
||||
// as needed.
|
||||
reanalysisTimer.StartTask("DataAnalysis.AnalyzeDataTargets");
|
||||
da.AnalyzeDataTargets();
|
||||
reanalysisTimer.EndTask("DataAnalysis.AnalyzeDataTargets");
|
||||
@ -2103,6 +2118,27 @@ namespace SourceGen {
|
||||
UndoableChange.ReanalysisScope.CodeAndData);
|
||||
}
|
||||
break;
|
||||
case UndoableChange.ChangeType.SetDataBank: {
|
||||
// If there's no entry, treat it as an entry with value = Unknown.
|
||||
if (!DbrValues.TryGetValue(offset, out CodeAnalysis.DbrValue current)) {
|
||||
current = CodeAnalysis.DbrValue.Unknown;
|
||||
}
|
||||
if (current != (CodeAnalysis.DbrValue)oldValue) {
|
||||
Debug.WriteLine("GLITCH: old DBR value mismatch (" +
|
||||
current + " vs " + oldValue + ")");
|
||||
Debug.Assert(false);
|
||||
}
|
||||
if ((CodeAnalysis.DbrValue)newValue == CodeAnalysis.DbrValue.Unknown) {
|
||||
DbrValues.Remove(offset);
|
||||
} else {
|
||||
DbrValues[offset] = (CodeAnalysis.DbrValue)newValue;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
@ -1858,6 +1858,41 @@ namespace SourceGen {
|
||||
EditLongComment(LineListGen.Line.HEADER_COMMENT_OFFSET);
|
||||
}
|
||||
|
||||
public bool CanEditDataBank() {
|
||||
if (mProject.CpuDef.HasAddr16) {
|
||||
return false; // only available for 65816
|
||||
}
|
||||
if (SelectionAnalysis.mNumItemsSelected != 1) {
|
||||
return false;
|
||||
}
|
||||
return (SelectionAnalysis.mLineType == LineListGen.Line.Type.Code /*||
|
||||
is data bank */);
|
||||
}
|
||||
|
||||
public void EditDataBank() {
|
||||
int selIndex = mMainWin.CodeListView_GetFirstSelectedIndex();
|
||||
int offset = CodeLineList[selIndex].FileOffset;
|
||||
|
||||
CodeAnalysis.DbrValue curValue;
|
||||
if (!mProject.DbrValues.TryGetValue(offset, out curValue)) {
|
||||
curValue = CodeAnalysis.DbrValue.Unknown;
|
||||
}
|
||||
|
||||
EditDataBank dlg = new EditDataBank(mMainWin, mProject.AddrMap, mFormatter, curValue);
|
||||
if (dlg.ShowDialog() != true) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dlg.Result != curValue) {
|
||||
Debug.WriteLine("Changing DBR at +" + offset.ToString("x6") + " to $" +
|
||||
((int)(dlg.Result)).ToString("x2"));
|
||||
UndoableChange uc =
|
||||
UndoableChange.CreateDataBankChange(offset, curValue, dlg.Result);
|
||||
ChangeSet cs = new ChangeSet(uc);
|
||||
ApplyUndoableChanges(cs);
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanEditLabel() {
|
||||
if (SelectionAnalysis.mNumItemsSelected != 1) {
|
||||
return false;
|
||||
@ -4095,7 +4130,7 @@ namespace SourceGen {
|
||||
}
|
||||
|
||||
public void ShowFileHexDump() {
|
||||
if (!OpenAnyFile(out string pathName)) {
|
||||
if (!OpenAnyFile(null, out string pathName)) {
|
||||
return;
|
||||
}
|
||||
FileInfo fi = new FileInfo(pathName);
|
||||
@ -4134,7 +4169,7 @@ namespace SourceGen {
|
||||
}
|
||||
|
||||
public void SliceFiles() {
|
||||
if (!OpenAnyFile(out string pathName)) {
|
||||
if (!OpenAnyFile(null, out string pathName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -4144,7 +4179,7 @@ namespace SourceGen {
|
||||
}
|
||||
|
||||
public void ConvertOmf() {
|
||||
if (!OpenAnyFile(out string pathName)) {
|
||||
if (!OpenAnyFile(Res.Strings.OMF_SELECT_FILE, out string pathName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -4183,11 +4218,14 @@ namespace SourceGen {
|
||||
ov.ShowDialog();
|
||||
}
|
||||
|
||||
private bool OpenAnyFile(out string pathName) {
|
||||
private bool OpenAnyFile(string title, out string pathName) {
|
||||
OpenFileDialog fileDlg = new OpenFileDialog() {
|
||||
Filter = Res.Strings.FILE_FILTER_ALL,
|
||||
FilterIndex = 1
|
||||
};
|
||||
if (title != null) {
|
||||
fileDlg.Title = title;
|
||||
}
|
||||
if (fileDlg.ShowDialog() != true) {
|
||||
pathName = null;
|
||||
return false;
|
||||
@ -4285,7 +4323,7 @@ namespace SourceGen {
|
||||
}
|
||||
|
||||
public void Debug_ApplesoftToHtml() {
|
||||
if (!OpenAnyFile(out string basPathName)) {
|
||||
if (!OpenAnyFile(null, out string basPathName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -133,6 +133,7 @@ limitations under the License.
|
||||
<system:String x:Key="str_OmfSegCommentFmt">Segment {0:D2}: Kind={1}; Attrs={2}; Name='{3}'</system:String>
|
||||
<system:String x:Key="str_OmfSegHdrCommentFmt">Segment {0:D2}: {3} {1,-9} Name='{2}', Length={4}</system:String>
|
||||
<system:String x:Key="str_OmfSegNoteFmt">Seg{0:D2}: {1} '{2}'</system:String>
|
||||
<system:String x:Key="str_OmfSelectFile">Select OMF file</system:String>
|
||||
<system:String x:Key="str_OpenDataDoesntExist">The file doesn't exist.</system:String>
|
||||
<system:String x:Key="str_OpenDataEmpty">File is empty</system:String>
|
||||
<system:String x:Key="str_OpenDataFailCaption">Unable to load data file</system:String>
|
||||
|
@ -247,6 +247,8 @@ namespace SourceGen.Res {
|
||||
(string)Application.Current.FindResource("str_OmfSegHdrCommentFmt");
|
||||
public static string OMF_SEG_NOTE_FMT =
|
||||
(string)Application.Current.FindResource("str_OmfSegNoteFmt");
|
||||
public static string OMF_SELECT_FILE =
|
||||
(string)Application.Current.FindResource("str_OmfSelectFile");
|
||||
public static string OPEN_DATA_DOESNT_EXIST =
|
||||
(string)Application.Current.FindResource("str_OpenDataDoesntExist");
|
||||
public static string OPEN_DATA_EMPTY =
|
||||
|
@ -339,6 +339,9 @@ not help you debug 6502 projects.</p>
|
||||
stackoverflow.com links.)</li>
|
||||
<li>Applesoft to HTML. An experimental feature that formats an
|
||||
Applesoft program as HTML.</li>
|
||||
<li>Apply Edit Commands. An experimental feature for importing values,
|
||||
such as end-of-line comments, that have been generated by other
|
||||
programs.</li>
|
||||
<li>Apply Platform Symbols. An experimental feature for turning platform
|
||||
symbols into address labels. This will run through the list of all
|
||||
symbols loaded from .sym65 files and find addresses that fall within
|
||||
|
106
SourceGen/SGTestData/Source/20222-data-bank.S
Normal file
106
SourceGen/SGTestData/Source/20222-data-bank.S
Normal file
@ -0,0 +1,106 @@
|
||||
; Copyright 2020 faddenSoft. All Rights Reserved.
|
||||
; See the LICENSE.txt file for distribution terms (Apache 2.0).
|
||||
;
|
||||
; Assembler: Merlin 32
|
||||
|
||||
org $1000
|
||||
|
||||
start
|
||||
clc
|
||||
xce
|
||||
sep #$30
|
||||
mx %11
|
||||
|
||||
lda start
|
||||
lda bank2
|
||||
|
||||
phk
|
||||
plb
|
||||
lda start
|
||||
lda bank2
|
||||
|
||||
lda #$02
|
||||
pha
|
||||
plb ;EDIT: set DBR=$02
|
||||
lda start
|
||||
lda bank2
|
||||
|
||||
lda #$08
|
||||
pha
|
||||
plb ;EDIT: set DBR=$08
|
||||
jsl bank2
|
||||
|
||||
lda #$02
|
||||
pha
|
||||
plb ;EDIT: set DBR=$02
|
||||
jsl bank3
|
||||
|
||||
phk
|
||||
plb
|
||||
bit start
|
||||
|
||||
jml alldone
|
||||
|
||||
code2bz
|
||||
dw snippet ;EDIT: format as 16-bit address
|
||||
snippet rts
|
||||
|
||||
code2zero
|
||||
adr code2b ;EDIT: format as 24-bit address
|
||||
|
||||
|
||||
|
||||
org $022000
|
||||
bank2
|
||||
ldal bank2 ;EDIT: set DBR=$08
|
||||
ldx #$00
|
||||
jsr code2a
|
||||
jsr :call2bz
|
||||
jsr :call2zero
|
||||
nop
|
||||
|
||||
lda #$03
|
||||
pha
|
||||
plb ;EDIT: set DBR=$03
|
||||
lda bank3dat
|
||||
|
||||
rtl
|
||||
|
||||
:call2bz
|
||||
jmp (code2bz) ;source/target in bank 0
|
||||
:call2zero
|
||||
jmp [code2zero] ;source in bank 0
|
||||
|
||||
code2a
|
||||
jmp (bank2addr,x) ;both addresses formed with K
|
||||
code2b
|
||||
nop
|
||||
rts
|
||||
|
||||
bank2addr
|
||||
dw code2b
|
||||
|
||||
|
||||
org $033000
|
||||
bank3
|
||||
ldal bank3 ;EDIT: set DBR=$02
|
||||
jsr :post_plb
|
||||
beq :skip_mayhem
|
||||
; Test PHK/PLB with an address shift in the middle. Not really something
|
||||
; that should ever happen, but we need to handle everything.
|
||||
phk
|
||||
org $033020 ;EDIT: set address
|
||||
:post_plb
|
||||
plb
|
||||
:self nop
|
||||
bra :skip_mayhem
|
||||
|
||||
:skip_mayhem
|
||||
lda bank2addr
|
||||
rtl
|
||||
|
||||
bank3dat
|
||||
dw bank3dat
|
||||
|
||||
|
||||
alldone rts
|
@ -130,6 +130,9 @@
|
||||
<Compile Include="WpfGui\EditComment.xaml.cs">
|
||||
<DependentUpon>EditComment.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="WpfGui\EditDataBank.xaml.cs">
|
||||
<DependentUpon>EditDataBank.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="WpfGui\EditDataOperand.xaml.cs">
|
||||
<DependentUpon>EditDataOperand.xaml</DependentUpon>
|
||||
</Compile>
|
||||
@ -331,6 +334,10 @@
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="WpfGui\EditDataBank.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="WpfGui\EditDataOperand.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
@ -79,6 +79,9 @@ namespace SourceGen {
|
||||
// Adds, updates, or removes an AddressMap entry.
|
||||
SetAddress,
|
||||
|
||||
// Adds, updates, or removes a data bank register value.
|
||||
SetDataBank,
|
||||
|
||||
// Changes the type hint.
|
||||
SetTypeHint,
|
||||
|
||||
@ -199,6 +202,30 @@ namespace SourceGen {
|
||||
return uc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an UndoableChange for a data bank register update.
|
||||
/// </summary>
|
||||
/// <param name="offset"></param>
|
||||
/// <param name="oldValue"></param>
|
||||
/// <param name="newValue"></param>
|
||||
/// <returns></returns>
|
||||
public static UndoableChange CreateDataBankChange(int offset,
|
||||
CodeAnalysis.DbrValue oldValue, CodeAnalysis.DbrValue newValue) {
|
||||
if (oldValue == newValue) {
|
||||
Debug.WriteLine("No-op DBR change at +" + offset.ToString("x6") + ": " + oldValue);
|
||||
}
|
||||
UndoableChange uc = new UndoableChange();
|
||||
uc.Type = ChangeType.SetDataBank;
|
||||
uc.Offset = offset;
|
||||
uc.OldValue = oldValue;
|
||||
uc.NewValue = newValue;
|
||||
// We don't strictly need to re-analyze the code, since the current implementation
|
||||
// handles it as a post-analysis fixup, but this lets us avoid having to compute the
|
||||
// affected offsets.
|
||||
uc.ReanalysisRequired = ReanalysisScope.CodeAndData;
|
||||
return uc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an UndoableChange for a type hint update. Rather than adding a
|
||||
/// separate UndoableChange for each affected offset -- which could span the
|
||||
|
60
SourceGen/WpfGui/EditDataBank.xaml
Normal file
60
SourceGen/WpfGui/EditDataBank.xaml
Normal file
@ -0,0 +1,60 @@
|
||||
<!--
|
||||
Copyright 2020 faddenSoft
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<Window x:Class="SourceGen.WpfGui.EditDataBank"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SourceGen.WpfGui"
|
||||
mc:Ignorable="d"
|
||||
Title="Set Data Bank"
|
||||
SizeToContent="WidthAndHeight" ResizeMode="NoResize"
|
||||
ShowInTaskbar="False" WindowStartupLocation="CenterOwner"
|
||||
ContentRendered="Window_ContentRendered">
|
||||
|
||||
<Grid Margin="8">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Grid.Row="0" Orientation="Horizontal">
|
||||
<TextBlock Text="Enter bank:"/>
|
||||
<TextBox Name="bankValueBox" Margin="8,1,0,0" Width="24" MaxLength="2"
|
||||
Text="{Binding DataBankStr, FallbackValue=88}"
|
||||
FontFamily="{StaticResource GeneralMonoFont}"/>
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Row="1" Margin="0,4,0,0" Text="• Enter hex value, or 'K' to track PBR"/>
|
||||
<TextBlock Grid.Row="2" Margin="0,0,0,0" Text="• Leave blank to reset to default"/>
|
||||
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" Margin="0,16,0,0">
|
||||
<TextBlock Text="Banks in use:" Margin="0,3,0,0"/>
|
||||
<ComboBox Name="bankCombo" Margin="8,1,0,0" Width="200"
|
||||
FontFamily="{StaticResource GeneralMonoFont}"
|
||||
ItemsSource="{Binding BankLabels}" DisplayMemberPath="Label"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,16,0,0">
|
||||
<Button Content="OK" IsDefault="True" Width="70"
|
||||
IsEnabled="{Binding IsValid}" Click="OkButton_Click"/>
|
||||
<Button Content="Cancel" IsCancel="True" Width="70" Margin="4,0,0,0"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
136
SourceGen/WpfGui/EditDataBank.xaml.cs
Normal file
136
SourceGen/WpfGui/EditDataBank.xaml.cs
Normal file
@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright 2020 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
|
||||
using Asm65;
|
||||
using CommonUtil;
|
||||
|
||||
namespace SourceGen.WpfGui {
|
||||
/// <summary>
|
||||
/// Edit Data Bank dialog.
|
||||
/// </summary>
|
||||
public partial class EditDataBank : Window, INotifyPropertyChanged {
|
||||
private const string PROG_BANK_STR = "K";
|
||||
|
||||
public CodeAnalysis.DbrValue Result { get; private set; }
|
||||
|
||||
// INotifyPropertyChanged implementation
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
private void OnPropertyChanged([CallerMemberName] string propertyName = "") {
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
private AddressMap mAddrMap;
|
||||
private Formatter mFormatter;
|
||||
|
||||
private string mDataBankStr;
|
||||
public string DataBankStr {
|
||||
get { return mDataBankStr; }
|
||||
set { mDataBankStr = value; OnPropertyChanged(); }
|
||||
}
|
||||
|
||||
private bool mIsValid;
|
||||
public bool IsValid {
|
||||
get { return mIsValid; }
|
||||
set { mIsValid = value; OnPropertyChanged(); }
|
||||
}
|
||||
|
||||
public class BankLabel {
|
||||
public CodeAnalysis.DbrValue Bank { get; private set; }
|
||||
public string Label { get; private set; }
|
||||
|
||||
public BankLabel(CodeAnalysis.DbrValue bank, string label) {
|
||||
Bank = bank;
|
||||
Label = label;
|
||||
}
|
||||
}
|
||||
public List<BankLabel> BankLabels { get; private set; } = new List<BankLabel>();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="owner">Parent window.</param>
|
||||
public EditDataBank(Window owner, AddressMap addrMap, Formatter formatter,
|
||||
CodeAnalysis.DbrValue curValue) {
|
||||
InitializeComponent();
|
||||
Owner = owner;
|
||||
DataContext = this;
|
||||
|
||||
mAddrMap = addrMap;
|
||||
mFormatter = formatter;
|
||||
|
||||
if (curValue == CodeAnalysis.DbrValue.ProgramBankReg) {
|
||||
DataBankStr = PROG_BANK_STR;
|
||||
} else if (curValue == CodeAnalysis.DbrValue.Unknown) {
|
||||
DataBankStr = string.Empty;
|
||||
} else if ((int)curValue >= 0 && (int)curValue <= 255) {
|
||||
// Format as address rather than hexvalue so we don't get leading '$'.
|
||||
DataBankStr = formatter.FormatAddress((int)curValue, false);
|
||||
} else {
|
||||
Debug.Assert(false, "invalid DBR value " + curValue);
|
||||
DataBankStr = string.Empty;
|
||||
}
|
||||
|
||||
// TODO: combo box
|
||||
BankLabels.Add(new BankLabel((CodeAnalysis.DbrValue)1, "(other)"));
|
||||
BankLabels.Add(new BankLabel((CodeAnalysis.DbrValue)1, "$02 FirstBankLabel"));
|
||||
BankLabels.Add(new BankLabel((CodeAnalysis.DbrValue)1, "$88 FancyBank"));
|
||||
bankCombo.SelectedIndex = 0;
|
||||
|
||||
IsValid = true; // TODO: validate
|
||||
}
|
||||
|
||||
private void Window_ContentRendered(object sender, EventArgs e) {
|
||||
bankValueBox.SelectAll();
|
||||
bankValueBox.Focus();
|
||||
}
|
||||
|
||||
private void OkButton_Click(object sender, RoutedEventArgs e) {
|
||||
Result = GetValue(DataBankStr);
|
||||
DialogResult = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a DBR value string to a value.
|
||||
/// </summary>
|
||||
/// <param name="valueStr">String to convert.</param>
|
||||
/// <returns>DBR value.</returns>
|
||||
private static CodeAnalysis.DbrValue GetValue(string valueStr) {
|
||||
if (valueStr == PROG_BANK_STR) {
|
||||
return CodeAnalysis.DbrValue.ProgramBankReg;
|
||||
} else {
|
||||
// Try to parse as 1- or 2-digit hex value.
|
||||
try {
|
||||
int val = Convert.ToInt32(valueStr, 16);
|
||||
if (val < 0 || val > 255) {
|
||||
// invalid value
|
||||
return CodeAnalysis.DbrValue.Unknown;
|
||||
}
|
||||
return (CodeAnalysis.DbrValue)val;
|
||||
} catch (Exception ex) {
|
||||
Debug.WriteLine("Result parse failed: " + ex.Message);
|
||||
return CodeAnalysis.DbrValue.Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -79,6 +79,7 @@ limitations under the License.
|
||||
<KeyGesture>Ctrl+Semicolon</KeyGesture>
|
||||
</RoutedUICommand.InputGestures>
|
||||
</RoutedUICommand>
|
||||
<RoutedUICommand x:Key="EditDataBankCmd" Text="Edit Data Bank..."/>
|
||||
<RoutedUICommand x:Key="EditHeaderCommentCmd" Text="Edit Header Comment..."/>
|
||||
<RoutedUICommand x:Key="EditOperandCmd" Text="Edit Operand...">
|
||||
<RoutedUICommand.InputGestures>
|
||||
@ -236,6 +237,8 @@ limitations under the License.
|
||||
Executed="EditAppSettingsCmd_Executed"/>
|
||||
<CommandBinding Command="{StaticResource EditCommentCmd}"
|
||||
CanExecute="CanEditComment" Executed="EditCommentCmd_Executed"/>
|
||||
<CommandBinding Command="{StaticResource EditDataBankCmd}"
|
||||
CanExecute="CanEditDataBank" Executed="EditDataBankCmd_Executed"/>
|
||||
<CommandBinding Command="{StaticResource EditHeaderCommentCmd}"
|
||||
CanExecute="IsProjectOpen" Executed="EditHeaderCommentCmd_Executed"/>
|
||||
<CommandBinding Command="{StaticResource EditLabelCmd}"
|
||||
@ -402,6 +405,7 @@ limitations under the License.
|
||||
<MenuItem Command="{StaticResource EditOperandCmd}"/>
|
||||
<MenuItem Command="{StaticResource EditCommentCmd}" InputGestureText="Ctrl+;"/>
|
||||
<MenuItem Command="{StaticResource EditLongCommentCmd}"/>
|
||||
<MenuItem Command="{StaticResource EditDataBankCmd}"/>
|
||||
<MenuItem Command="{StaticResource EditNoteCmd}"/>
|
||||
<MenuItem Command="{StaticResource EditProjectSymbolCmd}"/>
|
||||
<MenuItem Command="{StaticResource CreateLocalVariableTableCmd}"/>
|
||||
|
@ -1016,6 +1016,10 @@ namespace SourceGen.WpfGui {
|
||||
e.CanExecute = IsProjectOpen() && mMainCtrl.CanEditComment();
|
||||
}
|
||||
|
||||
private void CanEditDataBank(object sender, CanExecuteRoutedEventArgs e) {
|
||||
e.CanExecute = IsProjectOpen() && mMainCtrl.CanEditDataBank();
|
||||
}
|
||||
|
||||
private void CanEditLabel(object sender, CanExecuteRoutedEventArgs e) {
|
||||
e.CanExecute = IsProjectOpen() && mMainCtrl.CanEditLabel();
|
||||
}
|
||||
@ -1170,6 +1174,10 @@ namespace SourceGen.WpfGui {
|
||||
mMainCtrl.EditComment();
|
||||
}
|
||||
|
||||
private void EditDataBankCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
|
||||
mMainCtrl.EditDataBank();
|
||||
}
|
||||
|
||||
private void EditHeaderCommentCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
|
||||
mMainCtrl.EditHeaderComment();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user